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
82326b01
Commit
82326b01
authored
Jun 17, 2016
by
Jehonathan
Committed by
GitHub
Jun 17, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #82 from justcoding121/master
sync with master
parents
e1cd5fcd
703f5219
Show whitespace changes
Inline
Side-by-side
Showing
44 changed files
with
1617 additions
and
569 deletions
+1617
-569
ProxyTestController.cs
.../Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs
+16
-3
README.md
README.md
+21
-11
CertificateHandler.cs
Titanium.Web.Proxy/CertificateHandler.cs
+121
-0
CompressionFactory.cs
Titanium.Web.Proxy/Compression/CompressionFactory.cs
+4
-1
DeflateCompression.cs
Titanium.Web.Proxy/Compression/DeflateCompression.cs
+5
-2
GZipCompression.cs
Titanium.Web.Proxy/Compression/GZipCompression.cs
+5
-2
ICompression.cs
Titanium.Web.Proxy/Compression/ICompression.cs
+3
-0
ZlibCompression.cs
Titanium.Web.Proxy/Compression/ZlibCompression.cs
+5
-2
DecompressionFactory.cs
Titanium.Web.Proxy/Decompression/DecompressionFactory.cs
+5
-2
DefaultDecompression.cs
Titanium.Web.Proxy/Decompression/DefaultDecompression.cs
+5
-1
DeflateDecompression.cs
Titanium.Web.Proxy/Decompression/DeflateDecompression.cs
+7
-4
GZipDecompression.cs
Titanium.Web.Proxy/Decompression/GZipDecompression.cs
+7
-4
IDecompression.cs
Titanium.Web.Proxy/Decompression/IDecompression.cs
+6
-4
ZlibDecompression.cs
Titanium.Web.Proxy/Decompression/ZlibDecompression.cs
+7
-4
CertificateSelectionEventArgs.cs
...Web.Proxy/EventArguments/CertificateSelectionEventArgs.cs
+24
-0
CertificateValidationEventArgs.cs
...eb.Proxy/EventArguments/CertificateValidationEventArgs.cs
+3
-9
SessionEventArgs.cs
Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
+34
-30
ByteArrayExtensions.cs
Titanium.Web.Proxy/Extensions/ByteArrayExtensions.cs
+23
-0
HttpWebRequestExtensions.cs
Titanium.Web.Proxy/Extensions/HttpWebRequestExtensions.cs
+8
-4
HttpWebResponseExtensions.cs
Titanium.Web.Proxy/Extensions/HttpWebResponseExtensions.cs
+8
-3
StreamExtensions.cs
Titanium.Web.Proxy/Extensions/StreamExtensions.cs
+145
-10
TcpExtensions.cs
Titanium.Web.Proxy/Extensions/TcpExtensions.cs
+6
-3
CustomBinaryReader.cs
Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs
+48
-35
SystemProxy.cs
Titanium.Web.Proxy/Helpers/SystemProxy.cs
+60
-27
Tcp.cs
Titanium.Web.Proxy/Helpers/Tcp.cs
+19
-5
HttpWebClient.cs
Titanium.Web.Proxy/Http/HttpWebClient.cs
+46
-16
Request.cs
Titanium.Web.Proxy/Http/Request.cs
+62
-2
Response.cs
Titanium.Web.Proxy/Http/Response.cs
+44
-3
OkResponse.cs
Titanium.Web.Proxy/Http/Responses/OkResponse.cs
+4
-3
RedirectResponse.cs
Titanium.Web.Proxy/Http/Responses/RedirectResponse.cs
+3
-0
EndPoint.cs
Titanium.Web.Proxy/Models/EndPoint.cs
+11
-0
ExternalProxy.cs
Titanium.Web.Proxy/Models/ExternalProxy.cs
+4
-8
CachedCertificate.cs
Titanium.Web.Proxy/Network/CachedCertificate.cs
+25
-0
CertificateManager.cs
Titanium.Web.Proxy/Network/CertificateManager.cs
+291
-0
CustomSslStream.cs
Titanium.Web.Proxy/Network/CustomSslStream.cs
+0
-28
ProxyClient.cs
Titanium.Web.Proxy/Network/ProxyClient.cs
+5
-5
TcpConnection.cs
Titanium.Web.Proxy/Network/TcpConnection.cs
+45
-0
TcpConnectionManager.cs
Titanium.Web.Proxy/Network/TcpConnectionManager.cs
+128
-55
ProxyServer.cs
Titanium.Web.Proxy/ProxyServer.cs
+125
-8
RequestHandler.cs
Titanium.Web.Proxy/RequestHandler.cs
+151
-147
ResponseHandler.cs
Titanium.Web.Proxy/ResponseHandler.cs
+65
-118
ProxyConstants.cs
Titanium.Web.Proxy/Shared/ProxyConstants.cs
+5
-6
Titanium.Web.Proxy.csproj
Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
+7
-3
appveyor.yml
appveyor.yml
+1
-1
No files found.
Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs
View file @
82326b01
...
@@ -15,6 +15,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
...
@@ -15,6 +15,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
ProxyServer
.
BeforeRequest
+=
OnRequest
;
ProxyServer
.
BeforeRequest
+=
OnRequest
;
ProxyServer
.
BeforeResponse
+=
OnResponse
;
ProxyServer
.
BeforeResponse
+=
OnResponse
;
ProxyServer
.
ServerCertificateValidationCallback
+=
OnCertificateValidation
;
ProxyServer
.
ServerCertificateValidationCallback
+=
OnCertificateValidation
;
ProxyServer
.
ClientCertificateSelectionCallback
+=
OnCertificateSelection
;
//Exclude Https addresses you don't want to proxy
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//Usefull for clients that use certificate pinning
...
@@ -129,13 +130,25 @@ namespace Titanium.Web.Proxy.Examples.Basic
...
@@ -129,13 +130,25 @@ namespace Titanium.Web.Proxy.Examples.Basic
/// </summary>
/// </summary>
/// <param name="sender"></param>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <param name="e"></param>
public
async
Task
OnCertificateValidation
(
object
sender
,
CertificateValidationEventArgs
e
)
public
Task
OnCertificateValidation
(
object
sender
,
CertificateValidationEventArgs
e
)
{
{
//set IsValid to true/false based on Certificate Errors
//set IsValid to true/false based on Certificate Errors
if
(
e
.
SslPolicyErrors
==
System
.
Net
.
Security
.
SslPolicyErrors
.
None
)
if
(
e
.
SslPolicyErrors
==
System
.
Net
.
Security
.
SslPolicyErrors
.
None
)
e
.
IsValid
=
true
;
e
.
IsValid
=
true
;
else
await
e
.
Session
.
Ok
(
"Cannot validate server certificate! Not safe to proceed."
);
return
Task
.
FromResult
(
0
);
}
/// <summary>
/// Allows overriding default client certificate selection logic during mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public
Task
OnCertificateSelection
(
object
sender
,
CertificateSelectionEventArgs
e
)
{
//set e.clientCertificate to override
return
Task
.
FromResult
(
0
);
}
}
}
}
}
}
\ No newline at end of file
README.md
View file @
82326b01
...
@@ -12,8 +12,11 @@ Features
...
@@ -12,8 +12,11 @@ Features
========
========
*
Supports Http(s) and most features of HTTP 1.1
*
Supports Http(s) and most features of HTTP 1.1
*
Supports relaying of WebSockets
*
Support redirect/block/update requests
*
Supports script injection
*
Supports updating response
*
Safely relays WebSocket requests over Http
*
Support mutual SSL authentication
*
Fully asynchronous proxy
Usage
Usage
=====
=====
...
@@ -35,6 +38,8 @@ Setup HTTP proxy:
...
@@ -35,6 +38,8 @@ Setup HTTP proxy:
ProxyServer
.
BeforeRequest
+=
OnRequest
;
ProxyServer
.
BeforeRequest
+=
OnRequest
;
ProxyServer
.
BeforeResponse
+=
OnResponse
;
ProxyServer
.
BeforeResponse
+=
OnResponse
;
ProxyServer
.
ServerCertificateValidationCallback
+=
OnCertificateValidation
;
ProxyServer
.
ServerCertificateValidationCallback
+=
OnCertificateValidation
;
ProxyServer
.
ClientCertificateSelectionCallback
+=
OnCertificateSelection
;
//Exclude Https addresses you don't want to proxy
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//Usefull for clients that use certificate pinning
...
@@ -85,8 +90,6 @@ Setup HTTP proxy:
...
@@ -85,8 +90,6 @@ Setup HTTP proxy:
Sample request and response event handlers
Sample request and response event handlers
```
csharp
```
csharp
//intecept & cancel, redirect or update requests
public
async
Task
OnRequest
(
object
sender
,
SessionEventArgs
e
)
public
async
Task
OnRequest
(
object
sender
,
SessionEventArgs
e
)
{
{
Console
.
WriteLine
(
e
.
WebSession
.
Request
.
Url
);
Console
.
WriteLine
(
e
.
WebSession
.
Request
.
Url
);
...
@@ -148,20 +151,27 @@ Sample request and response event handlers
...
@@ -148,20 +151,27 @@ Sample request and response event handlers
}
}
}
}
/// Allows overriding default certificate validation logic
/// Allows overriding default certificate validation logic
public
async
Task
OnCertificateValidation
(
object
sender
,
CertificateValidationEventArgs
e
)
public
Task
OnCertificateValidation
(
object
sender
,
CertificateValidationEventArgs
e
)
{
{
//set IsValid to true/false based on Certificate Errors
//set IsValid to true/false based on Certificate Errors
if
(
e
.
SslPolicyErrors
==
System
.
Net
.
Security
.
SslPolicyErrors
.
None
)
if
(
e
.
SslPolicyErrors
==
System
.
Net
.
Security
.
SslPolicyErrors
.
None
)
e
.
IsValid
=
true
;
e
.
IsValid
=
true
;
else
await
e
.
Session
.
Ok
(
"Cannot validate server certificate! Not safe to proceed."
);
return
Task
.
FromResult
(
0
);
}
/// Allows overriding default client certificate selection logic during mutual authentication
public
Task
OnCertificateSelection
(
object
sender
,
CertificateSelectionEventArgs
e
)
{
//set e.clientCertificate to override
return
Task
.
FromResult
(
0
);
}
}
```
```
Future roadmap
Future roadmap
============
============
*
Support mutual authenticatio
n
*
Implement Kerberos/NTLM authentication over HTTP protocols for windows domai
n
*
Support Server Name Indication (SNI) for transparent endpoints
*
Support Server Name Indication (SNI) for transparent endpoints
*
Support HTTP 2.0
*
Support HTTP 2.0
Titanium.Web.Proxy/CertificateHandler.cs
0 → 100644
View file @
82326b01
using
System
;
using
System.Linq
;
using
System.Net.Security
;
using
System.Security.Cryptography.X509Certificates
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.EventArguments
;
namespace
Titanium.Web.Proxy
{
public
partial
class
ProxyServer
{
/// <summary>
/// Call back to override server certificate validation
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
internal
static
bool
ValidateServerCertificate
(
object
sender
,
X509Certificate
certificate
,
X509Chain
chain
,
SslPolicyErrors
sslPolicyErrors
)
{
//if user callback is registered then do it
if
(
ServerCertificateValidationCallback
!=
null
)
{
var
args
=
new
CertificateValidationEventArgs
();
args
.
Certificate
=
certificate
;
args
.
Chain
=
chain
;
args
.
SslPolicyErrors
=
sslPolicyErrors
;
Delegate
[]
invocationList
=
ServerCertificateValidationCallback
.
GetInvocationList
();
Task
[]
handlerTasks
=
new
Task
[
invocationList
.
Length
];
for
(
int
i
=
0
;
i
<
invocationList
.
Length
;
i
++)
{
handlerTasks
[
i
]
=
((
Func
<
object
,
CertificateValidationEventArgs
,
Task
>)
invocationList
[
i
])(
null
,
args
);
}
Task
.
WhenAll
(
handlerTasks
).
Wait
();
return
args
.
IsValid
;
}
if
(
sslPolicyErrors
==
SslPolicyErrors
.
None
)
return
true
;
//By default
//do not allow this client to communicate with unauthenticated servers.
return
false
;
}
/// <summary>
/// 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>
/// <returns></returns>
internal
static
X509Certificate
SelectClientCertificate
(
object
sender
,
string
targetHost
,
X509CertificateCollection
localCertificates
,
X509Certificate
remoteCertificate
,
string
[]
acceptableIssuers
)
{
X509Certificate
clientCertificate
=
null
;
var
customSslStream
=
sender
as
SslStream
;
if
(
acceptableIssuers
!=
null
&&
acceptableIssuers
.
Length
>
0
&&
localCertificates
!=
null
&&
localCertificates
.
Count
>
0
)
{
// Use the first certificate that is from an acceptable issuer.
foreach
(
X509Certificate
certificate
in
localCertificates
)
{
string
issuer
=
certificate
.
Issuer
;
if
(
Array
.
IndexOf
(
acceptableIssuers
,
issuer
)
!=
-
1
)
clientCertificate
=
certificate
;
}
}
if
(
localCertificates
!=
null
&&
localCertificates
.
Count
>
0
)
clientCertificate
=
localCertificates
[
0
];
//If user call back is registered
if
(
ClientCertificateSelectionCallback
!=
null
)
{
var
args
=
new
CertificateSelectionEventArgs
();
args
.
targetHost
=
targetHost
;
args
.
localCertificates
=
localCertificates
;
args
.
remoteCertificate
=
remoteCertificate
;
args
.
acceptableIssuers
=
acceptableIssuers
;
args
.
clientCertificate
=
clientCertificate
;
Delegate
[]
invocationList
=
ClientCertificateSelectionCallback
.
GetInvocationList
();
Task
[]
handlerTasks
=
new
Task
[
invocationList
.
Length
];
for
(
int
i
=
0
;
i
<
invocationList
.
Length
;
i
++)
{
handlerTasks
[
i
]
=
((
Func
<
object
,
CertificateSelectionEventArgs
,
Task
>)
invocationList
[
i
])(
null
,
args
);
}
Task
.
WhenAll
(
handlerTasks
).
Wait
();
return
args
.
clientCertificate
;
}
return
clientCertificate
;
}
}
}
Titanium.Web.Proxy/Compression/CompressionFactory.cs
View file @
82326b01
namespace
Titanium.Web.Proxy.Compression
namespace
Titanium.Web.Proxy.Compression
{
{
class
CompressionFactory
/// <summary>
/// A factory to generate the compression methods based on the type of compression
/// </summary>
internal
class
CompressionFactory
{
{
public
ICompression
Create
(
string
type
)
public
ICompression
Create
(
string
type
)
{
{
...
...
Titanium.Web.Proxy/Compression/DeflateCompression.cs
View file @
82326b01
...
@@ -4,7 +4,10 @@ using System.Threading.Tasks;
...
@@ -4,7 +4,10 @@ using System.Threading.Tasks;
namespace
Titanium.Web.Proxy.Compression
namespace
Titanium.Web.Proxy.Compression
{
{
class
DeflateCompression
:
ICompression
/// <summary>
/// Concrete implementation of deflate compression
/// </summary>
internal
class
DeflateCompression
:
ICompression
{
{
public
async
Task
<
byte
[
]>
Compress
(
byte
[]
responseBody
)
public
async
Task
<
byte
[
]>
Compress
(
byte
[]
responseBody
)
{
{
...
@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
...
@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
{
{
using
(
var
zip
=
new
DeflateStream
(
ms
,
CompressionMode
.
Compress
,
true
))
using
(
var
zip
=
new
DeflateStream
(
ms
,
CompressionMode
.
Compress
,
true
))
{
{
await
zip
.
WriteAsync
(
responseBody
,
0
,
responseBody
.
Length
)
.
ConfigureAwait
(
false
)
;
await
zip
.
WriteAsync
(
responseBody
,
0
,
responseBody
.
Length
);
}
}
return
ms
.
ToArray
();
return
ms
.
ToArray
();
...
...
Titanium.Web.Proxy/Compression/GZipCompression.cs
View file @
82326b01
...
@@ -4,7 +4,10 @@ using System.Threading.Tasks;
...
@@ -4,7 +4,10 @@ using System.Threading.Tasks;
namespace
Titanium.Web.Proxy.Compression
namespace
Titanium.Web.Proxy.Compression
{
{
class
GZipCompression
:
ICompression
/// <summary>
/// concreate implementation of gzip compression
/// </summary>
internal
class
GZipCompression
:
ICompression
{
{
public
async
Task
<
byte
[
]>
Compress
(
byte
[]
responseBody
)
public
async
Task
<
byte
[
]>
Compress
(
byte
[]
responseBody
)
{
{
...
@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
...
@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
{
{
using
(
var
zip
=
new
GZipStream
(
ms
,
CompressionMode
.
Compress
,
true
))
using
(
var
zip
=
new
GZipStream
(
ms
,
CompressionMode
.
Compress
,
true
))
{
{
await
zip
.
WriteAsync
(
responseBody
,
0
,
responseBody
.
Length
)
.
ConfigureAwait
(
false
)
;
await
zip
.
WriteAsync
(
responseBody
,
0
,
responseBody
.
Length
);
}
}
return
ms
.
ToArray
();
return
ms
.
ToArray
();
...
...
Titanium.Web.Proxy/Compression/ICompression.cs
View file @
82326b01
...
@@ -2,6 +2,9 @@
...
@@ -2,6 +2,9 @@
namespace
Titanium.Web.Proxy.Compression
namespace
Titanium.Web.Proxy.Compression
{
{
/// <summary>
/// An inteface for http compression
/// </summary>
interface
ICompression
interface
ICompression
{
{
Task
<
byte
[
]>
Compress
(
byte
[]
responseBody
);
Task
<
byte
[
]>
Compress
(
byte
[]
responseBody
);
...
...
Titanium.Web.Proxy/Compression/ZlibCompression.cs
View file @
82326b01
...
@@ -4,7 +4,10 @@ using System.Threading.Tasks;
...
@@ -4,7 +4,10 @@ using System.Threading.Tasks;
namespace
Titanium.Web.Proxy.Compression
namespace
Titanium.Web.Proxy.Compression
{
{
class
ZlibCompression
:
ICompression
/// <summary>
/// concrete implementation of zlib compression
/// </summary>
internal
class
ZlibCompression
:
ICompression
{
{
public
async
Task
<
byte
[
]>
Compress
(
byte
[]
responseBody
)
public
async
Task
<
byte
[
]>
Compress
(
byte
[]
responseBody
)
{
{
...
@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
...
@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
{
{
using
(
var
zip
=
new
ZlibStream
(
ms
,
CompressionMode
.
Compress
,
true
))
using
(
var
zip
=
new
ZlibStream
(
ms
,
CompressionMode
.
Compress
,
true
))
{
{
await
zip
.
WriteAsync
(
responseBody
,
0
,
responseBody
.
Length
)
.
ConfigureAwait
(
false
)
;
await
zip
.
WriteAsync
(
responseBody
,
0
,
responseBody
.
Length
);
}
}
return
ms
.
ToArray
();
return
ms
.
ToArray
();
...
...
Titanium.Web.Proxy/Decompression/DecompressionFactory.cs
View file @
82326b01
namespace
Titanium.Web.Proxy.Decompression
namespace
Titanium.Web.Proxy.Decompression
{
{
class
DecompressionFactory
/// <summary>
/// A factory to generate the de-compression methods based on the type of compression
/// </summary>
internal
class
DecompressionFactory
{
{
public
IDecompression
Create
(
string
type
)
internal
IDecompression
Create
(
string
type
)
{
{
switch
(
type
)
switch
(
type
)
{
{
...
...
Titanium.Web.Proxy/Decompression/DefaultDecompression.cs
View file @
82326b01
...
@@ -2,7 +2,11 @@
...
@@ -2,7 +2,11 @@
namespace
Titanium.Web.Proxy.Decompression
namespace
Titanium.Web.Proxy.Decompression
{
{
class
DefaultDecompression
:
IDecompression
/// <summary>
/// When no compression is specified just return the byte array
/// </summary>
internal
class
DefaultDecompression
:
IDecompression
{
{
public
Task
<
byte
[
]>
Decompress
(
byte
[]
compressedArray
)
public
Task
<
byte
[
]>
Decompress
(
byte
[]
compressedArray
)
{
{
...
...
Titanium.Web.Proxy/Decompression/DeflateDecompression.cs
View file @
82326b01
...
@@ -5,7 +5,10 @@ using Titanium.Web.Proxy.Shared;
...
@@ -5,7 +5,10 @@ using Titanium.Web.Proxy.Shared;
namespace
Titanium.Web.Proxy.Decompression
namespace
Titanium.Web.Proxy.Decompression
{
{
class
DeflateDecompression
:
IDecompression
/// <summary>
/// concrete implementation of deflate de-compression
/// </summary>
internal
class
DeflateDecompression
:
IDecompression
{
{
public
async
Task
<
byte
[
]>
Decompress
(
byte
[]
compressedArray
)
public
async
Task
<
byte
[
]>
Decompress
(
byte
[]
compressedArray
)
{
{
...
@@ -13,14 +16,14 @@ namespace Titanium.Web.Proxy.Decompression
...
@@ -13,14 +16,14 @@ namespace Titanium.Web.Proxy.Decompression
using
(
var
decompressor
=
new
DeflateStream
(
stream
,
CompressionMode
.
Decompress
))
using
(
var
decompressor
=
new
DeflateStream
(
stream
,
CompressionMode
.
Decompress
))
{
{
var
buffer
=
new
byte
[
Constants
.
BUFFER_SIZE
];
var
buffer
=
new
byte
[
Proxy
Constants
.
BUFFER_SIZE
];
using
(
var
output
=
new
MemoryStream
())
using
(
var
output
=
new
MemoryStream
())
{
{
int
read
;
int
read
;
while
((
read
=
await
decompressor
.
ReadAsync
(
buffer
,
0
,
buffer
.
Length
)
.
ConfigureAwait
(
false
)
)
>
0
)
while
((
read
=
await
decompressor
.
ReadAsync
(
buffer
,
0
,
buffer
.
Length
))
>
0
)
{
{
await
output
.
WriteAsync
(
buffer
,
0
,
read
)
.
ConfigureAwait
(
false
)
;
await
output
.
WriteAsync
(
buffer
,
0
,
read
);
}
}
return
output
.
ToArray
();
return
output
.
ToArray
();
...
...
Titanium.Web.Proxy/Decompression/GZipDecompression.cs
View file @
82326b01
...
@@ -5,19 +5,22 @@ using Titanium.Web.Proxy.Shared;
...
@@ -5,19 +5,22 @@ using Titanium.Web.Proxy.Shared;
namespace
Titanium.Web.Proxy.Decompression
namespace
Titanium.Web.Proxy.Decompression
{
{
class
GZipDecompression
:
IDecompression
/// <summary>
/// concrete implementation of gzip de-compression
/// </summary>
internal
class
GZipDecompression
:
IDecompression
{
{
public
async
Task
<
byte
[
]>
Decompress
(
byte
[]
compressedArray
)
public
async
Task
<
byte
[
]>
Decompress
(
byte
[]
compressedArray
)
{
{
using
(
var
decompressor
=
new
GZipStream
(
new
MemoryStream
(
compressedArray
),
CompressionMode
.
Decompress
))
using
(
var
decompressor
=
new
GZipStream
(
new
MemoryStream
(
compressedArray
),
CompressionMode
.
Decompress
))
{
{
var
buffer
=
new
byte
[
Constants
.
BUFFER_SIZE
];
var
buffer
=
new
byte
[
Proxy
Constants
.
BUFFER_SIZE
];
using
(
var
output
=
new
MemoryStream
())
using
(
var
output
=
new
MemoryStream
())
{
{
int
read
;
int
read
;
while
((
read
=
await
decompressor
.
ReadAsync
(
buffer
,
0
,
buffer
.
Length
)
.
ConfigureAwait
(
false
)
)
>
0
)
while
((
read
=
await
decompressor
.
ReadAsync
(
buffer
,
0
,
buffer
.
Length
))
>
0
)
{
{
await
output
.
WriteAsync
(
buffer
,
0
,
read
)
.
ConfigureAwait
(
false
)
;
await
output
.
WriteAsync
(
buffer
,
0
,
read
);
}
}
return
output
.
ToArray
();
return
output
.
ToArray
();
}
}
...
...
Titanium.Web.Proxy/Decompression/IDecompression.cs
View file @
82326b01
using
System.IO
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
namespace
Titanium.Web.Proxy.Decompression
namespace
Titanium.Web.Proxy.Decompression
{
{
interface
IDecompression
/// <summary>
/// An interface for decompression
/// </summary>
internal
interface
IDecompression
{
{
Task
<
byte
[
]>
Decompress
(
byte
[]
compressedArray
);
Task
<
byte
[
]>
Decompress
(
byte
[]
compressedArray
);
}
}
...
...
Titanium.Web.Proxy/Decompression/ZlibDecompression.cs
View file @
82326b01
...
@@ -5,21 +5,24 @@ using Titanium.Web.Proxy.Shared;
...
@@ -5,21 +5,24 @@ using Titanium.Web.Proxy.Shared;
namespace
Titanium.Web.Proxy.Decompression
namespace
Titanium.Web.Proxy.Decompression
{
{
class
ZlibDecompression
:
IDecompression
/// <summary>
/// concrete implemetation of zlib de-compression
/// </summary>
internal
class
ZlibDecompression
:
IDecompression
{
{
public
async
Task
<
byte
[
]>
Decompress
(
byte
[]
compressedArray
)
public
async
Task
<
byte
[
]>
Decompress
(
byte
[]
compressedArray
)
{
{
var
memoryStream
=
new
MemoryStream
(
compressedArray
);
var
memoryStream
=
new
MemoryStream
(
compressedArray
);
using
(
var
decompressor
=
new
ZlibStream
(
memoryStream
,
CompressionMode
.
Decompress
))
using
(
var
decompressor
=
new
ZlibStream
(
memoryStream
,
CompressionMode
.
Decompress
))
{
{
var
buffer
=
new
byte
[
Constants
.
BUFFER_SIZE
];
var
buffer
=
new
byte
[
Proxy
Constants
.
BUFFER_SIZE
];
using
(
var
output
=
new
MemoryStream
())
using
(
var
output
=
new
MemoryStream
())
{
{
int
read
;
int
read
;
while
((
read
=
await
decompressor
.
ReadAsync
(
buffer
,
0
,
buffer
.
Length
)
.
ConfigureAwait
(
false
)
)
>
0
)
while
((
read
=
await
decompressor
.
ReadAsync
(
buffer
,
0
,
buffer
.
Length
))
>
0
)
{
{
await
output
.
WriteAsync
(
buffer
,
0
,
read
)
.
ConfigureAwait
(
false
)
;
await
output
.
WriteAsync
(
buffer
,
0
,
read
);
}
}
return
output
.
ToArray
();
return
output
.
ToArray
();
}
}
...
...
Titanium.Web.Proxy/EventArguments/CertificateSelectionEventArgs.cs
0 → 100644
View file @
82326b01
using
System
;
using
System.Security.Cryptography.X509Certificates
;
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
object
sender
{
get
;
internal
set
;
}
public
string
targetHost
{
get
;
internal
set
;
}
public
X509CertificateCollection
localCertificates
{
get
;
internal
set
;
}
public
X509Certificate
remoteCertificate
{
get
;
internal
set
;
}
public
string
[]
acceptableIssuers
{
get
;
internal
set
;
}
public
X509Certificate
clientCertificate
{
get
;
set
;
}
public
void
Dispose
()
{
throw
new
NotImplementedException
();
}
}
}
Titanium.Web.Proxy/EventArguments/CertificateValidationEventArgs.cs
View file @
82326b01
using
System
;
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Net.Security
;
using
System.Net.Security
;
using
System.Security.Cryptography.X509Certificates
;
using
System.Security.Cryptography.X509Certificates
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Http
;
namespace
Titanium.Web.Proxy.EventArguments
namespace
Titanium.Web.Proxy.EventArguments
{
{
/// <summary>
/// An argument passed on to the user for validating the server certificate during SSL authentication
/// </summary>
public
class
CertificateValidationEventArgs
:
EventArgs
,
IDisposable
public
class
CertificateValidationEventArgs
:
EventArgs
,
IDisposable
{
{
public
string
HostName
=>
Session
.
WebSession
.
Request
.
Host
;
public
SessionEventArgs
Session
{
get
;
internal
set
;
}
public
X509Certificate
Certificate
{
get
;
internal
set
;
}
public
X509Certificate
Certificate
{
get
;
internal
set
;
}
public
X509Chain
Chain
{
get
;
internal
set
;
}
public
X509Chain
Chain
{
get
;
internal
set
;
}
public
SslPolicyErrors
SslPolicyErrors
{
get
;
internal
set
;
}
public
SslPolicyErrors
SslPolicyErrors
{
get
;
internal
set
;
}
...
...
Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
View file @
82326b01
...
@@ -8,6 +8,7 @@ using Titanium.Web.Proxy.Http.Responses;
...
@@ -8,6 +8,7 @@ using Titanium.Web.Proxy.Http.Responses;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Extensions
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Network
;
using
Titanium.Web.Proxy.Network
;
using
System.Net
;
namespace
Titanium.Web.Proxy.EventArguments
namespace
Titanium.Web.Proxy.EventArguments
{
{
...
@@ -24,25 +25,28 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -24,25 +25,28 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
/// </summary>
internal
SessionEventArgs
()
internal
SessionEventArgs
()
{
{
Client
=
new
ProxyClient
();
Proxy
Client
=
new
ProxyClient
();
WebSession
=
new
HttpWeb
Session
();
WebSession
=
new
HttpWeb
Client
();
}
}
/// <summary>
/// <summary>
/// Holds a reference to
server connection
/// Holds a reference to
client
/// </summary>
/// </summary>
internal
ProxyClient
Client
{
get
;
set
;
}
internal
ProxyClient
Proxy
Client
{
get
;
set
;
}
/// <summary>
/// <summary>
/// Does this session uses SSL
/// Does this session uses SSL
/// </summary>
/// </summary>
public
bool
IsHttps
{
get
;
internal
set
;
}
public
bool
IsHttps
=>
WebSession
.
Request
.
RequestUri
.
Scheme
==
Uri
.
UriSchemeHttps
;
public
IPEndPoint
ClientEndPoint
=>
(
IPEndPoint
)
ProxyClient
.
TcpClient
.
Client
.
RemoteEndPoint
;
/// <summary>
/// <summary>
/// A web session corresponding to a single request/response sequence
/// A web session corresponding to a single request/response sequence
/// within a proxy connection
/// within a proxy connection
/// </summary>
/// </summary>
public
HttpWeb
Session
WebSession
{
get
;
set
;
}
public
HttpWeb
Client
WebSession
{
get
;
set
;
}
/// <summary>
/// <summary>
...
@@ -76,7 +80,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -76,7 +80,7 @@ namespace Titanium.Web.Proxy.EventArguments
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if
(
WebSession
.
Request
.
IsChunked
)
if
(
WebSession
.
Request
.
IsChunked
)
{
{
await
this
.
Client
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
requestBodyStream
).
ConfigureAwait
(
false
);
await
this
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
requestBodyStream
);
}
}
else
else
{
{
...
@@ -84,13 +88,13 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -84,13 +88,13 @@ namespace Titanium.Web.Proxy.EventArguments
if
(
WebSession
.
Request
.
ContentLength
>
0
)
if
(
WebSession
.
Request
.
ContentLength
>
0
)
{
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await
this
.
Client
.
ClientStreamReader
.
CopyBytesToStream
(
requestBodyStream
,
WebSession
.
Request
.
ContentLength
).
ConfigureAwait
(
false
);
await
this
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStream
(
requestBodyStream
,
WebSession
.
Request
.
ContentLength
);
}
}
else
if
(
WebSession
.
Request
.
HttpVersion
.
ToLower
().
Trim
().
Equals
(
"http/1.0"
)
)
else
if
(
WebSession
.
Request
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Request
.
HttpVersion
.
Minor
==
0
)
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
requestBodyStream
,
long
.
MaxValue
)
.
ConfigureAwait
(
false
)
;
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
requestBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Request
.
RequestBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Request
.
ContentEncoding
,
requestBodyStream
.
ToArray
())
.
ConfigureAwait
(
false
)
;
WebSession
.
Request
.
RequestBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Request
.
ContentEncoding
,
requestBodyStream
.
ToArray
());
}
}
//Now set the flag to true
//Now set the flag to true
...
@@ -113,21 +117,21 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -113,21 +117,21 @@ namespace Titanium.Web.Proxy.EventArguments
//If chuncked the read chunk by chunk until we hit chunk end symbol
//If chuncked the read chunk by chunk until we hit chunk end symbol
if
(
WebSession
.
Response
.
IsChunked
)
if
(
WebSession
.
Response
.
IsChunked
)
{
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStreamChunked
(
responseBodyStream
)
.
ConfigureAwait
(
false
)
;
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStreamChunked
(
responseBodyStream
);
}
}
else
else
{
{
if
(
WebSession
.
Response
.
ContentLength
>
0
)
if
(
WebSession
.
Response
.
ContentLength
>
0
)
{
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
responseBodyStream
,
WebSession
.
Response
.
ContentLength
)
.
ConfigureAwait
(
false
)
;
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
responseBodyStream
,
WebSession
.
Response
.
ContentLength
);
}
}
else
if
(
WebSession
.
Response
.
HttpVersion
.
ToLower
().
Trim
().
Equals
(
"http/1.0"
)
)
else
if
(
WebSession
.
Response
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Response
.
HttpVersion
.
Minor
==
0
)
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
responseBodyStream
,
long
.
MaxValue
)
.
ConfigureAwait
(
false
)
;
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
responseBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Response
.
ResponseBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Response
.
ContentEncoding
,
responseBodyStream
.
ToArray
())
.
ConfigureAwait
(
false
)
;
WebSession
.
Response
.
ResponseBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Response
.
ContentEncoding
,
responseBodyStream
.
ToArray
());
}
}
//set this to true for caching
//set this to true for caching
...
@@ -144,7 +148,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -144,7 +148,7 @@ namespace Titanium.Web.Proxy.EventArguments
if
(
WebSession
.
Request
.
RequestLocked
)
if
(
WebSession
.
Request
.
RequestLocked
)
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
await
ReadRequestBody
()
.
ConfigureAwait
(
false
)
;
await
ReadRequestBody
();
return
WebSession
.
Request
.
RequestBody
;
return
WebSession
.
Request
.
RequestBody
;
}
}
/// <summary>
/// <summary>
...
@@ -157,7 +161,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -157,7 +161,7 @@ namespace Titanium.Web.Proxy.EventArguments
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
await
ReadRequestBody
()
.
ConfigureAwait
(
false
)
;
await
ReadRequestBody
();
//Use the encoding specified in request to decode the byte[] data to string
//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
));
return
WebSession
.
Request
.
RequestBodyString
??
(
WebSession
.
Request
.
RequestBodyString
=
WebSession
.
Request
.
Encoding
.
GetString
(
WebSession
.
Request
.
RequestBody
));
...
@@ -175,7 +179,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -175,7 +179,7 @@ namespace Titanium.Web.Proxy.EventArguments
//syphon out the request body from client before setting the new body
//syphon out the request body from client before setting the new body
if
(!
WebSession
.
Request
.
RequestBodyRead
)
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
{
await
ReadRequestBody
()
.
ConfigureAwait
(
false
)
;
await
ReadRequestBody
();
}
}
WebSession
.
Request
.
RequestBody
=
body
;
WebSession
.
Request
.
RequestBody
=
body
;
...
@@ -198,7 +202,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -198,7 +202,7 @@ namespace Titanium.Web.Proxy.EventArguments
//syphon out the request body from client before setting the new body
//syphon out the request body from client before setting the new body
if
(!
WebSession
.
Request
.
RequestBodyRead
)
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
{
await
ReadRequestBody
()
.
ConfigureAwait
(
false
)
;
await
ReadRequestBody
();
}
}
await
SetRequestBody
(
WebSession
.
Request
.
Encoding
.
GetBytes
(
body
));
await
SetRequestBody
(
WebSession
.
Request
.
Encoding
.
GetBytes
(
body
));
...
@@ -214,7 +218,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -214,7 +218,7 @@ namespace Titanium.Web.Proxy.EventArguments
if
(!
WebSession
.
Request
.
RequestLocked
)
if
(!
WebSession
.
Request
.
RequestLocked
)
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
await
ReadResponseBody
()
.
ConfigureAwait
(
false
)
;
await
ReadResponseBody
();
return
WebSession
.
Response
.
ResponseBody
;
return
WebSession
.
Response
.
ResponseBody
;
}
}
...
@@ -227,7 +231,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -227,7 +231,7 @@ namespace Titanium.Web.Proxy.EventArguments
if
(!
WebSession
.
Request
.
RequestLocked
)
if
(!
WebSession
.
Request
.
RequestLocked
)
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
await
GetResponseBody
()
.
ConfigureAwait
(
false
)
;
await
GetResponseBody
();
return
WebSession
.
Response
.
ResponseBodyString
??
(
WebSession
.
Response
.
ResponseBodyString
=
WebSession
.
Response
.
Encoding
.
GetString
(
WebSession
.
Response
.
ResponseBody
));
return
WebSession
.
Response
.
ResponseBodyString
??
(
WebSession
.
Response
.
ResponseBodyString
=
WebSession
.
Response
.
Encoding
.
GetString
(
WebSession
.
Response
.
ResponseBody
));
}
}
...
@@ -244,7 +248,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -244,7 +248,7 @@ namespace Titanium.Web.Proxy.EventArguments
//syphon out the response body from server before setting the new body
//syphon out the response body from server before setting the new body
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
{
await
GetResponseBody
()
.
ConfigureAwait
(
false
)
;
await
GetResponseBody
();
}
}
WebSession
.
Response
.
ResponseBody
=
body
;
WebSession
.
Response
.
ResponseBody
=
body
;
...
@@ -268,12 +272,12 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -268,12 +272,12 @@ namespace Titanium.Web.Proxy.EventArguments
//syphon out the response body from server before setting the new body
//syphon out the response body from server before setting the new body
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
{
await
GetResponseBody
()
.
ConfigureAwait
(
false
)
;
await
GetResponseBody
();
}
}
var
bodyBytes
=
WebSession
.
Response
.
Encoding
.
GetBytes
(
body
);
var
bodyBytes
=
WebSession
.
Response
.
Encoding
.
GetBytes
(
body
);
await
SetResponseBody
(
bodyBytes
)
.
ConfigureAwait
(
false
)
;
await
SetResponseBody
(
bodyBytes
);
}
}
private
async
Task
<
byte
[
]>
GetDecompressedResponseBody
(
string
encodingType
,
byte
[]
responseBodyStream
)
private
async
Task
<
byte
[
]>
GetDecompressedResponseBody
(
string
encodingType
,
byte
[]
responseBodyStream
)
...
@@ -281,7 +285,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -281,7 +285,7 @@ namespace Titanium.Web.Proxy.EventArguments
var
decompressionFactory
=
new
DecompressionFactory
();
var
decompressionFactory
=
new
DecompressionFactory
();
var
decompressor
=
decompressionFactory
.
Create
(
encodingType
);
var
decompressor
=
decompressionFactory
.
Create
(
encodingType
);
return
await
decompressor
.
Decompress
(
responseBodyStream
)
.
ConfigureAwait
(
false
)
;
return
await
decompressor
.
Decompress
(
responseBodyStream
);
}
}
...
@@ -301,7 +305,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -301,7 +305,7 @@ namespace Titanium.Web.Proxy.EventArguments
var
result
=
Encoding
.
Default
.
GetBytes
(
html
);
var
result
=
Encoding
.
Default
.
GetBytes
(
html
);
await
Ok
(
result
)
.
ConfigureAwait
(
false
)
;
await
Ok
(
result
);
}
}
/// <summary>
/// <summary>
...
@@ -317,7 +321,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -317,7 +321,7 @@ namespace Titanium.Web.Proxy.EventArguments
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
ResponseBody
=
result
;
response
.
ResponseBody
=
result
;
await
Respond
(
response
)
.
ConfigureAwait
(
false
)
;
await
Respond
(
response
);
WebSession
.
Request
.
CancelRequest
=
true
;
WebSession
.
Request
.
CancelRequest
=
true
;
}
}
...
@@ -330,7 +334,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -330,7 +334,7 @@ namespace Titanium.Web.Proxy.EventArguments
response
.
ResponseHeaders
.
Add
(
new
Models
.
HttpHeader
(
"Location"
,
url
));
response
.
ResponseHeaders
.
Add
(
new
Models
.
HttpHeader
(
"Location"
,
url
));
response
.
ResponseBody
=
Encoding
.
ASCII
.
GetBytes
(
string
.
Empty
);
response
.
ResponseBody
=
Encoding
.
ASCII
.
GetBytes
(
string
.
Empty
);
await
Respond
(
response
)
.
ConfigureAwait
(
false
)
;
await
Respond
(
response
);
WebSession
.
Request
.
CancelRequest
=
true
;
WebSession
.
Request
.
CancelRequest
=
true
;
}
}
...
@@ -345,7 +349,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -345,7 +349,7 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession
.
Response
=
response
;
WebSession
.
Response
=
response
;
await
ProxyServer
.
HandleHttpSessionResponse
(
this
)
.
ConfigureAwait
(
false
)
;
await
ProxyServer
.
HandleHttpSessionResponse
(
this
);
}
}
}
}
...
...
Titanium.Web.Proxy/Extensions/ByteArrayExtensions.cs
0 → 100644
View file @
82326b01
using
System
;
namespace
Titanium.Web.Proxy.Extensions
{
public
static
class
ByteArrayExtensions
{
/// <summary>
/// Get the sub array from byte of data
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="data"></param>
/// <param name="index"></param>
/// <param name="length"></param>
/// <returns></returns>
public
static
T
[]
SubArray
<
T
>(
this
T
[]
data
,
int
index
,
int
length
)
{
T
[]
result
=
new
T
[
length
];
Array
.
Copy
(
data
,
index
,
result
,
0
,
length
);
return
result
;
}
}
}
Titanium.Web.Proxy/Extensions/HttpWebRequestExtensions.cs
View file @
82326b01
...
@@ -7,10 +7,14 @@ namespace Titanium.Web.Proxy.Extensions
...
@@ -7,10 +7,14 @@ namespace Titanium.Web.Proxy.Extensions
/// <summary>
/// <summary>
/// Extensions on HttpWebSession object
/// Extensions on HttpWebSession object
/// </summary>
/// </summary>
public
static
class
HttpWebRequestExtensions
internal
static
class
HttpWebRequestExtensions
{
{
//Get encoding of the HTTP request
/// <summary>
public
static
Encoding
GetEncoding
(
this
Request
request
)
/// parse the character encoding of request from request headers
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
internal
static
Encoding
GetEncoding
(
this
Request
request
)
{
{
try
try
{
{
...
@@ -19,7 +23,7 @@ namespace Titanium.Web.Proxy.Extensions
...
@@ -19,7 +23,7 @@ namespace Titanium.Web.Proxy.Extensions
return
Encoding
.
GetEncoding
(
"ISO-8859-1"
);
return
Encoding
.
GetEncoding
(
"ISO-8859-1"
);
//extract the encoding by finding the charset
//extract the encoding by finding the charset
var
contentTypes
=
request
.
ContentType
.
Split
(
Constants
.
SemiColonSplit
);
var
contentTypes
=
request
.
ContentType
.
Split
(
Proxy
Constants
.
SemiColonSplit
);
foreach
(
var
contentType
in
contentTypes
)
foreach
(
var
contentType
in
contentTypes
)
{
{
var
encodingSplit
=
contentType
.
Split
(
'='
);
var
encodingSplit
=
contentType
.
Split
(
'='
);
...
...
Titanium.Web.Proxy/Extensions/HttpWebResponseExtensions.cs
View file @
82326b01
...
@@ -4,9 +4,14 @@ using Titanium.Web.Proxy.Shared;
...
@@ -4,9 +4,14 @@ using Titanium.Web.Proxy.Shared;
namespace
Titanium.Web.Proxy.Extensions
namespace
Titanium.Web.Proxy.Extensions
{
{
public
static
class
HttpWebResponseExtensions
internal
static
class
HttpWebResponseExtensions
{
{
public
static
Encoding
GetResponseCharacterEncoding
(
this
Response
response
)
/// <summary>
/// Gets the character encoding of response from response headers
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
internal
static
Encoding
GetResponseCharacterEncoding
(
this
Response
response
)
{
{
try
try
{
{
...
@@ -15,7 +20,7 @@ namespace Titanium.Web.Proxy.Extensions
...
@@ -15,7 +20,7 @@ namespace Titanium.Web.Proxy.Extensions
return
Encoding
.
GetEncoding
(
"ISO-8859-1"
);
return
Encoding
.
GetEncoding
(
"ISO-8859-1"
);
//extract the encoding by finding the charset
//extract the encoding by finding the charset
var
contentTypes
=
response
.
ContentType
.
Split
(
Constants
.
SemiColonSplit
);
var
contentTypes
=
response
.
ContentType
.
Split
(
Proxy
Constants
.
SemiColonSplit
);
foreach
(
var
contentType
in
contentTypes
)
foreach
(
var
contentType
in
contentTypes
)
{
{
var
encodingSplit
=
contentType
.
Split
(
'='
);
var
encodingSplit
=
contentType
.
Split
(
'='
);
...
...
Titanium.Web.Proxy/Extensions/StreamExtensions.cs
View file @
82326b01
using
System
;
using
System.Globalization
;
using
System.Globalization
;
using
System.IO
;
using
System.IO
;
using
System.Text
;
using
System.Text
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
...
@@ -8,9 +7,19 @@ using Titanium.Web.Proxy.Shared;
...
@@ -8,9 +7,19 @@ using Titanium.Web.Proxy.Shared;
namespace
Titanium.Web.Proxy.Extensions
namespace
Titanium.Web.Proxy.Extensions
{
{
public
static
class
StreamHelper
/// <summary>
/// Extensions used for Stream and CustomBinaryReader objects
/// </summary>
internal
static
class
StreamExtensions
{
{
public
static
async
Task
CopyToAsync
(
this
Stream
input
,
string
initialData
,
Stream
output
)
/// <summary>
/// Copy streams asynchronously with an initial data inserted to the beginning of stream
/// </summary>
/// <param name="input"></param>
/// <param name="initialData"></param>
/// <param name="output"></param>
/// <returns></returns>
internal
static
async
Task
CopyToAsync
(
this
Stream
input
,
string
initialData
,
Stream
output
)
{
{
if
(!
string
.
IsNullOrEmpty
(
initialData
))
if
(!
string
.
IsNullOrEmpty
(
initialData
))
{
{
...
@@ -19,18 +28,24 @@ namespace Titanium.Web.Proxy.Extensions
...
@@ -19,18 +28,24 @@ namespace Titanium.Web.Proxy.Extensions
}
}
await
input
.
CopyToAsync
(
output
);
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
,
Stream
stream
,
long
totalBytesToRead
)
internal
static
async
Task
CopyBytesToStream
(
this
CustomBinaryReader
streamReader
,
Stream
stream
,
long
totalBytesToRead
)
{
{
var
totalbytesRead
=
0
;
var
totalbytesRead
=
0
;
long
bytesToRead
;
long
bytesToRead
;
if
(
totalBytesToRead
<
Constants
.
BUFFER_SIZE
)
if
(
totalBytesToRead
<
Proxy
Constants
.
BUFFER_SIZE
)
{
{
bytesToRead
=
totalBytesToRead
;
bytesToRead
=
totalBytesToRead
;
}
}
else
else
bytesToRead
=
Constants
.
BUFFER_SIZE
;
bytesToRead
=
Proxy
Constants
.
BUFFER_SIZE
;
while
(
totalbytesRead
<
totalBytesToRead
)
while
(
totalbytesRead
<
totalBytesToRead
)
...
@@ -50,11 +65,18 @@ namespace Titanium.Web.Proxy.Extensions
...
@@ -50,11 +65,18 @@ namespace Titanium.Web.Proxy.Extensions
await
stream
.
WriteAsync
(
buffer
,
0
,
buffer
.
Length
);
await
stream
.
WriteAsync
(
buffer
,
0
,
buffer
.
Length
);
}
}
}
}
/// <summary>
/// Copies the stream chunked
/// </summary>
/// <param name="clientStreamReader"></param>
/// <param name="stream"></param>
/// <returns></returns>
internal
static
async
Task
CopyBytesToStreamChunked
(
this
CustomBinaryReader
clientStreamReader
,
Stream
stream
)
internal
static
async
Task
CopyBytesToStreamChunked
(
this
CustomBinaryReader
clientStreamReader
,
Stream
stream
)
{
{
while
(
true
)
while
(
true
)
{
{
var
chuchkHead
=
await
clientStreamReader
.
ReadLineAsync
()
.
ConfigureAwait
(
false
)
;
var
chuchkHead
=
await
clientStreamReader
.
ReadLineAsync
();
var
chunkSize
=
int
.
Parse
(
chuchkHead
,
NumberStyles
.
HexNumber
);
var
chunkSize
=
int
.
Parse
(
chuchkHead
,
NumberStyles
.
HexNumber
);
if
(
chunkSize
!=
0
)
if
(
chunkSize
!=
0
)
...
@@ -62,14 +84,127 @@ namespace Titanium.Web.Proxy.Extensions
...
@@ -62,14 +84,127 @@ namespace Titanium.Web.Proxy.Extensions
var
buffer
=
await
clientStreamReader
.
ReadBytesAsync
(
chunkSize
);
var
buffer
=
await
clientStreamReader
.
ReadBytesAsync
(
chunkSize
);
await
stream
.
WriteAsync
(
buffer
,
0
,
buffer
.
Length
);
await
stream
.
WriteAsync
(
buffer
,
0
,
buffer
.
Length
);
//chunk trail
//chunk trail
await
clientStreamReader
.
ReadLineAsync
().
ConfigureAwait
(
false
);
await
clientStreamReader
.
ReadLineAsync
(
);
}
}
else
else
{
{
await
clientStreamReader
.
ReadLineAsync
().
ConfigureAwait
(
false
);
await
clientStreamReader
.
ReadLineAsync
(
);
break
;
break
;
}
}
}
}
}
}
/// <summary>
/// Writes the byte array body to the given stream; optionally chunked
/// </summary>
/// <param name="clientStream"></param>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal
static
async
Task
WriteResponseBody
(
this
Stream
clientStream
,
byte
[]
data
,
bool
isChunked
)
{
if
(!
isChunked
)
{
await
clientStream
.
WriteAsync
(
data
,
0
,
data
.
Length
);
}
else
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
,
Stream
outStream
,
bool
isChunked
,
long
ContentLength
)
{
if
(!
isChunked
)
{
//http 1.0
if
(
ContentLength
==
-
1
)
ContentLength
=
long
.
MaxValue
;
int
bytesToRead
=
ProxyConstants
.
BUFFER_SIZE
;
if
(
ContentLength
<
ProxyConstants
.
BUFFER_SIZE
)
bytesToRead
=
(
int
)
ContentLength
;
var
buffer
=
new
byte
[
ProxyConstants
.
BUFFER_SIZE
];
var
bytesRead
=
0
;
var
totalBytesRead
=
0
;
while
((
bytesRead
+=
await
inStreamReader
.
BaseStream
.
ReadAsync
(
buffer
,
0
,
bytesToRead
))
>
0
)
{
await
outStream
.
WriteAsync
(
buffer
,
0
,
bytesRead
);
totalBytesRead
+=
bytesRead
;
if
(
totalBytesRead
==
ContentLength
)
break
;
bytesRead
=
0
;
var
remainingBytes
=
(
ContentLength
-
totalBytesRead
);
bytesToRead
=
remainingBytes
>
(
long
)
ProxyConstants
.
BUFFER_SIZE
?
ProxyConstants
.
BUFFER_SIZE
:
(
int
)
remainingBytes
;
}
}
else
await
WriteResponseBodyChunked
(
inStreamReader
,
outStream
);
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal
static
async
Task
WriteResponseBodyChunked
(
this
CustomBinaryReader
inStreamReader
,
Stream
outStream
)
{
while
(
true
)
{
var
chunkHead
=
await
inStreamReader
.
ReadLineAsync
();
var
chunkSize
=
int
.
Parse
(
chunkHead
,
NumberStyles
.
HexNumber
);
if
(
chunkSize
!=
0
)
{
var
buffer
=
await
inStreamReader
.
ReadBytesAsync
(
chunkSize
);
var
chunkHeadBytes
=
Encoding
.
ASCII
.
GetBytes
(
chunkSize
.
ToString
(
"x2"
));
await
outStream
.
WriteAsync
(
chunkHeadBytes
,
0
,
chunkHeadBytes
.
Length
);
await
outStream
.
WriteAsync
(
ProxyConstants
.
NewLineBytes
,
0
,
ProxyConstants
.
NewLineBytes
.
Length
);
await
outStream
.
WriteAsync
(
buffer
,
0
,
chunkSize
);
await
outStream
.
WriteAsync
(
ProxyConstants
.
NewLineBytes
,
0
,
ProxyConstants
.
NewLineBytes
.
Length
);
await
inStreamReader
.
ReadLineAsync
();
}
else
{
await
inStreamReader
.
ReadLineAsync
();
await
outStream
.
WriteAsync
(
ProxyConstants
.
ChunkEnd
,
0
,
ProxyConstants
.
ChunkEnd
.
Length
);
break
;
}
}
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal
static
async
Task
WriteResponseBodyChunked
(
this
byte
[]
data
,
Stream
outStream
)
{
var
chunkHead
=
Encoding
.
ASCII
.
GetBytes
(
data
.
Length
.
ToString
(
"x2"
));
await
outStream
.
WriteAsync
(
chunkHead
,
0
,
chunkHead
.
Length
);
await
outStream
.
WriteAsync
(
ProxyConstants
.
NewLineBytes
,
0
,
ProxyConstants
.
NewLineBytes
.
Length
);
await
outStream
.
WriteAsync
(
data
,
0
,
data
.
Length
);
await
outStream
.
WriteAsync
(
ProxyConstants
.
NewLineBytes
,
0
,
ProxyConstants
.
NewLineBytes
.
Length
);
await
outStream
.
WriteAsync
(
ProxyConstants
.
ChunkEnd
,
0
,
ProxyConstants
.
ChunkEnd
.
Length
);
}
}
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Extensions/TcpExtensions.cs
View file @
82326b01
using
System.Net.Sockets
;
using
System.Net.Sockets
;
namespace
Titanium.Web.Proxy.Extensions
namespace
Titanium.Web.Proxy.Extensions
{
{
internal
static
class
TcpExtensions
internal
static
class
TcpExtensions
{
{
public
static
bool
IsConnected
(
this
Socket
client
)
/// <summary>
/// verifies if the underlying socket is connected before using a TcpClient connection
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
internal
static
bool
IsConnected
(
this
Socket
client
)
{
{
// This is how you can determine whether a socket is still connected.
// This is how you can determine whether a socket is still connected.
bool
blockingState
=
client
.
Blocking
;
bool
blockingState
=
client
.
Blocking
;
...
...
Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs
View file @
82326b01
using
System
;
using
System
;
using
System.Collections.Generic
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.IO
;
using
System.Net.Security
;
using
System.Net.Sockets
;
using
System.Text
;
using
System.Text
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.
Network
;
using
Titanium.Web.Proxy.
Extensions
;
using
Titanium.Web.Proxy.Shared
;
using
Titanium.Web.Proxy.Shared
;
namespace
Titanium.Web.Proxy.Helpers
namespace
Titanium.Web.Proxy.Helpers
{
{
/// <summary>
/// <summary>
/// A custom binary reader that would allo us to read string line by line
/// A custom binary reader that would allo us to read string line by line
/// using the specified encoding
/// using the specified encoding
/// as well as to read bytes as required
/// as well as to read bytes as required
/// </summary>
/// </summary>
public
class
CustomBinaryReader
:
IDisposable
internal
class
CustomBinaryReader
:
IDisposable
{
{
private
Stream
stream
;
private
Stream
stream
;
private
Encoding
encoding
;
internal
CustomBinaryReader
(
Stream
stream
)
internal
CustomBinaryReader
(
Stream
stream
)
{
{
this
.
stream
=
stream
;
this
.
stream
=
stream
;
}
//default to UTF-8
this
.
encoding
=
Encoding
.
UTF8
;
}
public
Stream
BaseStream
=>
stream
;
internal
Stream
BaseStream
=>
stream
;
/// <summary>
/// <summary>
/// Read a line from the byte stream
/// Read a line from the byte stream
...
@@ -34,32 +35,39 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -34,32 +35,39 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns>
/// <returns></returns>
internal
async
Task
<
string
>
ReadLineAsync
()
internal
async
Task
<
string
>
ReadLineAsync
()
{
{
var
readBuffer
=
new
StringBuilder
();
using
(
var
readBuffer
=
new
MemoryStream
())
{
try
try
{
{
var
lastChar
=
default
(
char
);
var
lastChar
=
default
(
char
);
var
buffer
=
new
byte
[
1
];
var
buffer
=
new
byte
[
1
];
while
(
await
this
.
stream
.
ReadAsync
(
buffer
,
0
,
1
).
ConfigureAwait
(
false
)
>
0
)
while
((
await
this
.
stream
.
ReadAsync
(
buffer
,
0
,
1
)
)
>
0
)
{
{
//if new line
if
(
lastChar
==
'\r'
&&
buffer
[
0
]
==
'\n'
)
if
(
lastChar
==
'\r'
&&
buffer
[
0
]
==
'\n'
)
{
{
return
readBuffer
.
Remove
(
readBuffer
.
Length
-
1
,
1
).
ToString
();
var
result
=
readBuffer
.
ToArray
();
return
encoding
.
GetString
(
result
.
SubArray
(
0
,
result
.
Length
-
1
));
}
}
//end of stream
if
(
buffer
[
0
]
==
'\0'
)
if
(
buffer
[
0
]
==
'\0'
)
{
{
return
readBuffer
.
ToString
(
);
return
encoding
.
GetString
(
readBuffer
.
ToArray
()
);
}
}
readBuffer
.
Append
((
char
)
buffer
[
0
]);
await
readBuffer
.
WriteAsync
(
buffer
,
0
,
1
);
//store last char for new line comparison
lastChar
=
(
char
)
buffer
[
0
];
lastChar
=
(
char
)
buffer
[
0
];
}
}
return
readBuffer
.
ToString
(
);
return
encoding
.
GetString
(
readBuffer
.
ToArray
()
);
}
}
catch
(
IOException
)
catch
(
IOException
)
{
{
return
readBuffer
.
ToString
();
throw
;
}
}
}
}
}
...
@@ -71,30 +79,35 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -71,30 +79,35 @@ namespace Titanium.Web.Proxy.Helpers
{
{
string
tmpLine
;
string
tmpLine
;
var
requestLines
=
new
List
<
string
>();
var
requestLines
=
new
List
<
string
>();
while
(!
string
.
IsNullOrEmpty
(
tmpLine
=
await
ReadLineAsync
()
.
ConfigureAwait
(
false
)
))
while
(!
string
.
IsNullOrEmpty
(
tmpLine
=
await
ReadLineAsync
()))
{
{
requestLines
.
Add
(
tmpLine
);
requestLines
.
Add
(
tmpLine
);
}
}
return
requestLines
;
return
requestLines
;
}
}
/// <summary>
/// Read the specified number of raw bytes from the base stream
/// </summary>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal
async
Task
<
byte
[
]>
ReadBytesAsync
(
long
totalBytesToRead
)
internal
async
Task
<
byte
[
]>
ReadBytesAsync
(
long
totalBytesToRead
)
{
{
int
bytesToRead
=
Constants
.
BUFFER_SIZE
;
int
bytesToRead
=
Proxy
Constants
.
BUFFER_SIZE
;
if
(
totalBytesToRead
<
Constants
.
BUFFER_SIZE
)
if
(
totalBytesToRead
<
Proxy
Constants
.
BUFFER_SIZE
)
bytesToRead
=
(
int
)
totalBytesToRead
;
bytesToRead
=
(
int
)
totalBytesToRead
;
var
buffer
=
new
byte
[
Constants
.
BUFFER_SIZE
];
var
buffer
=
new
byte
[
Proxy
Constants
.
BUFFER_SIZE
];
var
bytesRead
=
0
;
var
bytesRead
=
0
;
var
totalBytesRead
=
0
;
var
totalBytesRead
=
0
;
using
(
var
outStream
=
new
MemoryStream
())
using
(
var
outStream
=
new
MemoryStream
())
{
{
while
((
bytesRead
+=
await
this
.
stream
.
ReadAsync
(
buffer
,
0
,
bytesToRead
)
.
ConfigureAwait
(
false
)
)
>
0
)
while
((
bytesRead
+=
await
this
.
stream
.
ReadAsync
(
buffer
,
0
,
bytesToRead
))
>
0
)
{
{
await
outStream
.
WriteAsync
(
buffer
,
0
,
bytesRead
)
.
ConfigureAwait
(
false
)
;
await
outStream
.
WriteAsync
(
buffer
,
0
,
bytesRead
);
totalBytesRead
+=
bytesRead
;
totalBytesRead
+=
bytesRead
;
if
(
totalBytesRead
==
totalBytesToRead
)
if
(
totalBytesRead
==
totalBytesToRead
)
...
@@ -102,7 +115,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -102,7 +115,7 @@ namespace Titanium.Web.Proxy.Helpers
bytesRead
=
0
;
bytesRead
=
0
;
var
remainingBytes
=
(
totalBytesToRead
-
totalBytesRead
);
var
remainingBytes
=
(
totalBytesToRead
-
totalBytesRead
);
bytesToRead
=
remainingBytes
>
(
long
)
Constants
.
BUFFER_SIZE
?
Constants
.
BUFFER_SIZE
:
(
int
)
remainingBytes
;
bytesToRead
=
remainingBytes
>
(
long
)
ProxyConstants
.
BUFFER_SIZE
?
Proxy
Constants
.
BUFFER_SIZE
:
(
int
)
remainingBytes
;
}
}
return
outStream
.
ToArray
();
return
outStream
.
ToArray
();
...
...
Titanium.Web.Proxy/Helpers/SystemProxy.cs
View file @
82326b01
...
@@ -5,8 +5,12 @@ using System.Text.RegularExpressions;
...
@@ -5,8 +5,12 @@ using System.Text.RegularExpressions;
using
System.Collections.Generic
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Linq
;
/// <summary>
/// Helper classes for setting system proxy settings
/// </summary>
namespace
Titanium.Web.Proxy.Helpers
namespace
Titanium.Web.Proxy.Helpers
{
{
internal
static
class
NativeMethods
internal
static
class
NativeMethods
{
{
[
DllImport
(
"wininet.dll"
)]
[
DllImport
(
"wininet.dll"
)]
...
@@ -16,25 +20,25 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -16,25 +20,25 @@ namespace Titanium.Web.Proxy.Helpers
internal
class
HttpSystemProxyValue
internal
class
HttpSystemProxyValue
{
{
public
string
HostName
{
get
;
set
;
}
internal
string
HostName
{
get
;
set
;
}
public
int
Port
{
get
;
set
;
}
internal
int
Port
{
get
;
set
;
}
public
bool
IsSecure
{
get
;
set
;
}
internal
bool
IsHttps
{
get
;
set
;
}
public
override
string
ToString
()
public
override
string
ToString
()
{
{
if
(!
Is
Secure
)
if
(!
Is
Https
)
return
"http="
+
HostName
+
":"
+
Port
;
return
"http="
+
HostName
+
":"
+
Port
;
else
else
return
"https="
+
HostName
+
":"
+
Port
;
return
"https="
+
HostName
+
":"
+
Port
;
}
}
}
}
public
static
class
SystemProxyHelper
internal
static
class
SystemProxyHelper
{
{
public
const
int
InternetOptionSettingsChanged
=
39
;
internal
const
int
InternetOptionSettingsChanged
=
39
;
public
const
int
InternetOptionRefresh
=
37
;
internal
const
int
InternetOptionRefresh
=
37
;
public
static
void
SetHttpProxy
(
string
hostname
,
int
port
)
internal
static
void
SetHttpProxy
(
string
hostname
,
int
port
)
{
{
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
...
@@ -44,11 +48,11 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -44,11 +48,11 @@ namespace Titanium.Web.Proxy.Helpers
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
!
x
.
Is
Secure
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
!
x
.
Is
Https
);
existingSystemProxyValues
.
Add
(
new
HttpSystemProxyValue
()
existingSystemProxyValues
.
Add
(
new
HttpSystemProxyValue
()
{
{
HostName
=
hostname
,
HostName
=
hostname
,
Is
Secure
=
false
,
Is
Https
=
false
,
Port
=
port
Port
=
port
});
});
...
@@ -59,19 +63,21 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -59,19 +63,21 @@ namespace Titanium.Web.Proxy.Helpers
Refresh
();
Refresh
();
}
}
/// <summary>
public
static
void
RemoveHttpProxy
()
/// Remove the http proxy setting from current machine
/// </summary>
internal
static
void
RemoveHttpProxy
()
{
{
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
if
(
reg
!=
null
)
if
(
reg
!=
null
)
{
{
if
(
reg
.
GetValue
(
"ProxyServer"
)
!=
null
)
if
(
reg
.
GetValue
(
"ProxyServer"
)
!=
null
)
{
{
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
!
x
.
Is
Secure
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
!
x
.
Is
Https
);
if
(!(
existingSystemProxyValues
.
Count
()
==
0
))
if
(!(
existingSystemProxyValues
.
Count
()
==
0
))
{
{
...
@@ -89,7 +95,12 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -89,7 +95,12 @@ namespace Titanium.Web.Proxy.Helpers
Refresh
();
Refresh
();
}
}
public
static
void
SetHttpsProxy
(
string
hostname
,
int
port
)
/// <summary>
/// Set the HTTPS proxy server for current machine
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
internal
static
void
SetHttpsProxy
(
string
hostname
,
int
port
)
{
{
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
...
@@ -101,11 +112,11 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -101,11 +112,11 @@ namespace Titanium.Web.Proxy.Helpers
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
x
.
Is
Secure
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
x
.
Is
Https
);
existingSystemProxyValues
.
Add
(
new
HttpSystemProxyValue
()
existingSystemProxyValues
.
Add
(
new
HttpSystemProxyValue
()
{
{
HostName
=
hostname
,
HostName
=
hostname
,
Is
Secure
=
true
,
Is
Https
=
true
,
Port
=
port
Port
=
port
});
});
...
@@ -116,7 +127,10 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -116,7 +127,10 @@ namespace Titanium.Web.Proxy.Helpers
Refresh
();
Refresh
();
}
}
public
static
void
RemoveHttpsProxy
()
/// <summary>
/// Removes the https proxy setting to nothing
/// </summary>
internal
static
void
RemoveHttpsProxy
()
{
{
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
...
@@ -127,7 +141,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -127,7 +141,7 @@ namespace Titanium.Web.Proxy.Helpers
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
x
.
Is
Secure
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
x
.
Is
Https
);
if
(!(
existingSystemProxyValues
.
Count
()
==
0
))
if
(!(
existingSystemProxyValues
.
Count
()
==
0
))
{
{
...
@@ -146,7 +160,10 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -146,7 +160,10 @@ namespace Titanium.Web.Proxy.Helpers
Refresh
();
Refresh
();
}
}
public
static
void
DisableAllProxy
()
/// <summary>
/// Removes all types of proxy settings (both http & https)
/// </summary>
internal
static
void
DisableAllProxy
()
{
{
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
...
@@ -159,6 +176,12 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -159,6 +176,12 @@ namespace Titanium.Web.Proxy.Helpers
Refresh
();
Refresh
();
}
}
/// <summary>
/// Get the current system proxy setting values
/// </summary>
/// <param name="prevServerValue"></param>
/// <returns></returns>
private
static
List
<
HttpSystemProxyValue
>
GetSystemProxyValues
(
string
prevServerValue
)
private
static
List
<
HttpSystemProxyValue
>
GetSystemProxyValues
(
string
prevServerValue
)
{
{
var
result
=
new
List
<
HttpSystemProxyValue
>();
var
result
=
new
List
<
HttpSystemProxyValue
>();
...
@@ -187,6 +210,11 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -187,6 +210,11 @@ namespace Titanium.Web.Proxy.Helpers
return
result
;
return
result
;
}
}
/// <summary>
/// Parses the system proxy setting string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private
static
HttpSystemProxyValue
parseProxyValue
(
string
value
)
private
static
HttpSystemProxyValue
parseProxyValue
(
string
value
)
{
{
var
tmp
=
Regex
.
Replace
(
value
,
@"\s+"
,
" "
).
Trim
().
ToLower
();
var
tmp
=
Regex
.
Replace
(
value
,
@"\s+"
,
" "
).
Trim
().
ToLower
();
...
@@ -197,7 +225,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -197,7 +225,7 @@ namespace Titanium.Web.Proxy.Helpers
{
{
HostName
=
endPoint
.
Split
(
':'
)[
0
],
HostName
=
endPoint
.
Split
(
':'
)[
0
],
Port
=
int
.
Parse
(
endPoint
.
Split
(
':'
)[
1
]),
Port
=
int
.
Parse
(
endPoint
.
Split
(
':'
)[
1
]),
Is
Secure
=
false
Is
Https
=
false
};
};
}
}
else
if
(
tmp
.
StartsWith
(
"https="
))
else
if
(
tmp
.
StartsWith
(
"https="
))
...
@@ -207,13 +235,16 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -207,13 +235,16 @@ namespace Titanium.Web.Proxy.Helpers
{
{
HostName
=
endPoint
.
Split
(
':'
)[
0
],
HostName
=
endPoint
.
Split
(
':'
)[
0
],
Port
=
int
.
Parse
(
endPoint
.
Split
(
':'
)[
1
]),
Port
=
int
.
Parse
(
endPoint
.
Split
(
':'
)[
1
]),
Is
Secure
=
true
Is
Https
=
true
};
};
}
}
return
null
;
return
null
;
}
}
/// <summary>
/// Prepares the proxy server registry (create empty values if they don't exist)
/// </summary>
/// <param name="reg"></param>
private
static
void
prepareRegistry
(
RegistryKey
reg
)
private
static
void
prepareRegistry
(
RegistryKey
reg
)
{
{
if
(
reg
.
GetValue
(
"ProxyEnable"
)
==
null
)
if
(
reg
.
GetValue
(
"ProxyEnable"
)
==
null
)
...
@@ -228,7 +259,9 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -228,7 +259,9 @@ namespace Titanium.Web.Proxy.Helpers
}
}
/// <summary>
/// Refresh the settings so that the system know about a change in proxy setting
/// </summary>
private
static
void
Refresh
()
private
static
void
Refresh
()
{
{
NativeMethods
.
InternetSetOption
(
IntPtr
.
Zero
,
InternetOptionSettingsChanged
,
IntPtr
.
Zero
,
0
);
NativeMethods
.
InternetSetOption
(
IntPtr
.
Zero
,
InternetOptionSettingsChanged
,
IntPtr
.
Zero
,
0
);
...
...
Titanium.Web.Proxy/Helpers/Tcp.cs
View file @
82326b01
...
@@ -12,11 +12,24 @@ using Titanium.Web.Proxy.Shared;
...
@@ -12,11 +12,24 @@ using Titanium.Web.Proxy.Shared;
namespace
Titanium.Web.Proxy.Helpers
namespace
Titanium.Web.Proxy.Helpers
{
{
public
class
TcpHelper
internal
class
TcpHelper
{
{
public
async
static
Task
SendRaw
(
Stream
clientStream
,
string
httpCmd
,
List
<
HttpHeader
>
requestHeaders
,
string
hostName
,
/// <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="clientStream"></param>
/// <param name="httpCmd"></param>
/// <param name="requestHeaders"></param>
/// <param name="hostName"></param>
/// <param name="tunnelPort"></param>
/// <param name="isHttps"></param>
/// <returns></returns>
internal
async
static
Task
SendRaw
(
Stream
clientStream
,
string
httpCmd
,
List
<
HttpHeader
>
requestHeaders
,
string
hostName
,
int
tunnelPort
,
bool
isHttps
)
int
tunnelPort
,
bool
isHttps
)
{
{
//prepare the prefix content
StringBuilder
sb
=
null
;
StringBuilder
sb
=
null
;
if
(
httpCmd
!=
null
||
requestHeaders
!=
null
)
if
(
httpCmd
!=
null
||
requestHeaders
!=
null
)
{
{
...
@@ -38,7 +51,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -38,7 +51,7 @@ namespace Titanium.Web.Proxy.Helpers
TcpClient
tunnelClient
=
null
;
TcpClient
tunnelClient
=
null
;
Stream
tunnelStream
=
null
;
Stream
tunnelStream
=
null
;
//create the TcpClient to the server
try
try
{
{
tunnelClient
=
new
TcpClient
(
hostName
,
tunnelPort
);
tunnelClient
=
new
TcpClient
(
hostName
,
tunnelPort
);
...
@@ -50,7 +63,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -50,7 +63,7 @@ namespace Titanium.Web.Proxy.Helpers
try
try
{
{
sslStream
=
new
SslStream
(
tunnelStream
);
sslStream
=
new
SslStream
(
tunnelStream
);
await
sslStream
.
AuthenticateAsClientAsync
(
hostName
,
null
,
Constants
.
Supported
Protocols
,
false
);
await
sslStream
.
AuthenticateAsClientAsync
(
hostName
,
null
,
ProxyConstants
.
SupportedSsl
Protocols
,
false
);
tunnelStream
=
sslStream
;
tunnelStream
=
sslStream
;
}
}
catch
catch
...
@@ -64,6 +77,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -64,6 +77,7 @@ namespace Titanium.Web.Proxy.Helpers
Task
sendRelay
;
Task
sendRelay
;
//Now async relay all server=>client & client=>server data
if
(
sb
!=
null
)
if
(
sb
!=
null
)
sendRelay
=
clientStream
.
CopyToAsync
(
sb
.
ToString
(),
tunnelStream
);
sendRelay
=
clientStream
.
CopyToAsync
(
sb
.
ToString
(),
tunnelStream
);
else
else
...
@@ -72,7 +86,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -72,7 +86,7 @@ namespace Titanium.Web.Proxy.Helpers
var
receiveRelay
=
tunnelStream
.
CopyToAsync
(
string
.
Empty
,
clientStream
);
var
receiveRelay
=
tunnelStream
.
CopyToAsync
(
string
.
Empty
,
clientStream
);
await
Task
.
WhenAll
(
sendRelay
,
receiveRelay
)
.
ConfigureAwait
(
false
)
;
await
Task
.
WhenAll
(
sendRelay
,
receiveRelay
);
}
}
catch
catch
{
{
...
...
Titanium.Web.Proxy/Http/HttpWebClient.cs
View file @
82326b01
...
@@ -9,14 +9,23 @@ using Titanium.Web.Proxy.Shared;
...
@@ -9,14 +9,23 @@ using Titanium.Web.Proxy.Shared;
namespace
Titanium.Web.Proxy.Http
namespace
Titanium.Web.Proxy.Http
{
{
public
class
HttpWebSession
/// <summary>
{
/// Used to communicate with the server over HTTP(S)
/// </summary>
public
class
HttpWebClient
{
/// <summary>
/// Connection to server
/// </summary>
internal
TcpConnection
ServerConnection
{
get
;
set
;
}
internal
TcpConnection
ServerConnection
{
get
;
set
;
}
public
Request
Request
{
get
;
set
;
}
public
Request
Request
{
get
;
set
;
}
public
Response
Response
{
get
;
set
;
}
public
Response
Response
{
get
;
set
;
}
public
bool
IsSecure
/// <summary>
/// Is Https?
/// </summary>
public
bool
IsHttps
{
{
get
get
{
{
...
@@ -24,31 +33,40 @@ namespace Titanium.Web.Proxy.Http
...
@@ -24,31 +33,40 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="Connection"></param>
internal
void
SetConnection
(
TcpConnection
Connection
)
internal
void
SetConnection
(
TcpConnection
Connection
)
{
{
Connection
.
LastAccess
=
DateTime
.
Now
;
Connection
.
LastAccess
=
DateTime
.
Now
;
ServerConnection
=
Connection
;
ServerConnection
=
Connection
;
}
}
internal
HttpWeb
Session
()
internal
HttpWeb
Client
()
{
{
this
.
Request
=
new
Request
();
this
.
Request
=
new
Request
();
this
.
Response
=
new
Response
();
this
.
Response
=
new
Response
();
}
}
/// <summary>
/// Prepare & send the http(s) request
/// </summary>
/// <returns></returns>
internal
async
Task
SendRequest
()
internal
async
Task
SendRequest
()
{
{
Stream
stream
=
ServerConnection
.
Stream
;
Stream
stream
=
ServerConnection
.
Stream
;
StringBuilder
requestLines
=
new
StringBuilder
();
StringBuilder
requestLines
=
new
StringBuilder
();
//prepare the request & headers
requestLines
.
AppendLine
(
string
.
Join
(
" "
,
new
string
[
3
]
requestLines
.
AppendLine
(
string
.
Join
(
" "
,
new
string
[
3
]
{
{
this
.
Request
.
Method
,
this
.
Request
.
Method
,
this
.
Request
.
RequestUri
.
PathAndQuery
,
this
.
Request
.
RequestUri
.
PathAndQuery
,
this
.
Request
.
HttpVersion
string
.
Format
(
"HTTP/{0}.{1}"
,
this
.
Request
.
HttpVersion
.
Major
,
this
.
Request
.
HttpVersion
.
Minor
)
}));
}));
//write request headers
foreach
(
HttpHeader
httpHeader
in
this
.
Request
.
RequestHeaders
)
foreach
(
HttpHeader
httpHeader
in
this
.
Request
.
RequestHeaders
)
{
{
requestLines
.
AppendLine
(
httpHeader
.
Name
+
':'
+
httpHeader
.
Value
);
requestLines
.
AppendLine
(
httpHeader
.
Name
+
':'
+
httpHeader
.
Value
);
...
@@ -64,7 +82,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -64,7 +82,7 @@ namespace Titanium.Web.Proxy.Http
if
(
ProxyServer
.
Enable100ContinueBehaviour
)
if
(
ProxyServer
.
Enable100ContinueBehaviour
)
if
(
this
.
Request
.
ExpectContinue
)
if
(
this
.
Request
.
ExpectContinue
)
{
{
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
Constants
.
SpaceSplit
,
3
);
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
Proxy
Constants
.
SpaceSplit
,
3
);
var
responseStatusCode
=
httpResult
[
1
].
Trim
();
var
responseStatusCode
=
httpResult
[
1
].
Trim
();
var
responseStatusDescription
=
httpResult
[
2
].
Trim
();
var
responseStatusDescription
=
httpResult
[
2
].
Trim
();
...
@@ -73,30 +91,41 @@ namespace Titanium.Web.Proxy.Http
...
@@ -73,30 +91,41 @@ namespace Titanium.Web.Proxy.Http
&&
responseStatusDescription
.
ToLower
().
Equals
(
"continue"
))
&&
responseStatusDescription
.
ToLower
().
Equals
(
"continue"
))
{
{
this
.
Request
.
Is100Continue
=
true
;
this
.
Request
.
Is100Continue
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()
.
ConfigureAwait
(
false
)
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
}
else
if
(
responseStatusCode
.
Equals
(
"417"
)
else
if
(
responseStatusCode
.
Equals
(
"417"
)
&&
responseStatusDescription
.
ToLower
().
Equals
(
"expectation failed"
))
&&
responseStatusDescription
.
ToLower
().
Equals
(
"expectation failed"
))
{
{
this
.
Request
.
ExpectationFailed
=
true
;
this
.
Request
.
ExpectationFailed
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()
.
ConfigureAwait
(
false
)
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
}
}
}
}
}
/// <summary>
/// Receive & parse the http response from server
/// </summary>
/// <returns></returns>
internal
async
Task
ReceiveResponse
()
internal
async
Task
ReceiveResponse
()
{
{
//return if this is already read
//return if this is already read
if
(
this
.
Response
.
ResponseStatusCode
!=
null
)
return
;
if
(
this
.
Response
.
ResponseStatusCode
!=
null
)
return
;
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
Constants
.
SpaceSplit
,
3
);
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
Proxy
Constants
.
SpaceSplit
,
3
);
if
(
string
.
IsNullOrEmpty
(
httpResult
[
0
]))
if
(
string
.
IsNullOrEmpty
(
httpResult
[
0
]))
{
{
await
ServerConnection
.
StreamReader
.
ReadLineAsync
().
ConfigureAwait
(
false
);
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
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
=
httpResult
[
0
].
Trim
()
;
this
.
Response
.
HttpVersion
=
version
;
this
.
Response
.
ResponseStatusCode
=
httpResult
[
1
].
Trim
();
this
.
Response
.
ResponseStatusCode
=
httpResult
[
1
].
Trim
();
this
.
Response
.
ResponseStatusDescription
=
httpResult
[
2
].
Trim
();
this
.
Response
.
ResponseStatusDescription
=
httpResult
[
2
].
Trim
();
...
@@ -106,7 +135,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -106,7 +135,7 @@ namespace Titanium.Web.Proxy.Http
{
{
this
.
Response
.
Is100Continue
=
true
;
this
.
Response
.
Is100Continue
=
true
;
this
.
Response
.
ResponseStatusCode
=
null
;
this
.
Response
.
ResponseStatusCode
=
null
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()
.
ConfigureAwait
(
false
)
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
await
ReceiveResponse
();
await
ReceiveResponse
();
return
;
return
;
}
}
...
@@ -115,16 +144,17 @@ namespace Titanium.Web.Proxy.Http
...
@@ -115,16 +144,17 @@ namespace Titanium.Web.Proxy.Http
{
{
this
.
Response
.
ExpectationFailed
=
true
;
this
.
Response
.
ExpectationFailed
=
true
;
this
.
Response
.
ResponseStatusCode
=
null
;
this
.
Response
.
ResponseStatusCode
=
null
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()
.
ConfigureAwait
(
false
)
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
await
ReceiveResponse
();
await
ReceiveResponse
();
return
;
return
;
}
}
List
<
string
>
responseLines
=
await
ServerConnection
.
StreamReader
.
ReadAllLinesAsync
().
ConfigureAwait
(
false
);
//read response headers
List
<
string
>
responseLines
=
await
ServerConnection
.
StreamReader
.
ReadAllLinesAsync
();
for
(
int
index
=
0
;
index
<
responseLines
.
Count
;
++
index
)
for
(
int
index
=
0
;
index
<
responseLines
.
Count
;
++
index
)
{
{
string
[]
strArray
=
responseLines
[
index
].
Split
(
Constants
.
ColonSplit
,
2
);
string
[]
strArray
=
responseLines
[
index
].
Split
(
Proxy
Constants
.
ColonSplit
,
2
);
this
.
Response
.
ResponseHeaders
.
Add
(
new
HttpHeader
(
strArray
[
0
],
strArray
[
1
]));
this
.
Response
.
ResponseHeaders
.
Add
(
new
HttpHeader
(
strArray
[
0
],
strArray
[
1
]));
}
}
}
}
...
...
Titanium.Web.Proxy/Http/Request.cs
View file @
82326b01
...
@@ -7,12 +7,29 @@ using Titanium.Web.Proxy.Extensions;
...
@@ -7,12 +7,29 @@ using Titanium.Web.Proxy.Extensions;
namespace
Titanium.Web.Proxy.Http
namespace
Titanium.Web.Proxy.Http
{
{
/// <summary>
/// A HTTP(S) request object
/// </summary>
public
class
Request
public
class
Request
{
{
/// <summary>
/// Request Method
/// </summary>
public
string
Method
{
get
;
set
;
}
public
string
Method
{
get
;
set
;
}
/// <summary>
/// Request HTTP Uri
/// </summary>
public
Uri
RequestUri
{
get
;
set
;
}
public
Uri
RequestUri
{
get
;
set
;
}
public
string
HttpVersion
{
get
;
set
;
}
/// <summary>
/// Request Http Version
/// </summary>
public
Version
HttpVersion
{
get
;
set
;
}
/// <summary>
/// Request Http hostanem
/// </summary>
internal
string
Host
internal
string
Host
{
{
get
get
...
@@ -32,6 +49,9 @@ namespace Titanium.Web.Proxy.Http
...
@@ -32,6 +49,9 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Request content encoding
/// </summary>
internal
string
ContentEncoding
internal
string
ContentEncoding
{
{
get
get
...
@@ -47,6 +67,9 @@ namespace Titanium.Web.Proxy.Http
...
@@ -47,6 +67,9 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Request content-length
/// </summary>
public
long
ContentLength
public
long
ContentLength
{
{
get
get
...
@@ -65,7 +88,6 @@ namespace Titanium.Web.Proxy.Http
...
@@ -65,7 +88,6 @@ namespace Titanium.Web.Proxy.Http
}
}
set
set
{
{
var
header
=
RequestHeaders
.
FirstOrDefault
(
x
=>
x
.
Name
.
ToLower
()
==
"content-length"
);
var
header
=
RequestHeaders
.
FirstOrDefault
(
x
=>
x
.
Name
.
ToLower
()
==
"content-length"
);
if
(
value
>=
0
)
if
(
value
>=
0
)
...
@@ -85,6 +107,9 @@ namespace Titanium.Web.Proxy.Http
...
@@ -85,6 +107,9 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Request content-type
/// </summary>
public
string
ContentType
public
string
ContentType
{
{
get
get
...
@@ -106,6 +131,9 @@ namespace Titanium.Web.Proxy.Http
...
@@ -106,6 +131,9 @@ namespace Titanium.Web.Proxy.Http
}
}
/// <summary>
/// Is request body send as chunked bytes
/// </summary>
public
bool
IsChunked
public
bool
IsChunked
{
{
get
get
...
@@ -137,6 +165,9 @@ namespace Titanium.Web.Proxy.Http
...
@@ -137,6 +165,9 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Does this request has a 100-continue header?
/// </summary>
public
bool
ExpectContinue
public
bool
ExpectContinue
{
{
get
get
...
@@ -147,19 +178,37 @@ namespace Titanium.Web.Proxy.Http
...
@@ -147,19 +178,37 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Request Url
/// </summary>
public
string
Url
{
get
{
return
RequestUri
.
OriginalString
;
}
}
public
string
Url
{
get
{
return
RequestUri
.
OriginalString
;
}
}
/// <summary>
/// Encoding for this request
/// </summary>
internal
Encoding
Encoding
{
get
{
return
this
.
GetEncoding
();
}
}
internal
Encoding
Encoding
{
get
{
return
this
.
GetEncoding
();
}
}
/// <summary>
/// <summary>
/// Terminates the underlying Tcp Connection to client after current request
/// Terminates the underlying Tcp Connection to client after current request
/// </summary>
/// </summary>
internal
bool
CancelRequest
{
get
;
set
;
}
internal
bool
CancelRequest
{
get
;
set
;
}
/// <summary>
/// Request body as byte array
/// </summary>
internal
byte
[]
RequestBody
{
get
;
set
;
}
internal
byte
[]
RequestBody
{
get
;
set
;
}
/// <summary>
/// request body as string
/// </summary>
internal
string
RequestBodyString
{
get
;
set
;
}
internal
string
RequestBodyString
{
get
;
set
;
}
internal
bool
RequestBodyRead
{
get
;
set
;
}
internal
bool
RequestBodyRead
{
get
;
set
;
}
internal
bool
RequestLocked
{
get
;
set
;
}
internal
bool
RequestLocked
{
get
;
set
;
}
/// <summary>
/// Does this request has an upgrade to websocket header?
/// </summary>
internal
bool
UpgradeToWebSocket
internal
bool
UpgradeToWebSocket
{
{
get
get
...
@@ -176,8 +225,19 @@ namespace Titanium.Web.Proxy.Http
...
@@ -176,8 +225,19 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Request heade collection
/// </summary>
public
List
<
HttpHeader
>
RequestHeaders
{
get
;
set
;
}
public
List
<
HttpHeader
>
RequestHeaders
{
get
;
set
;
}
/// <summary>
/// Does server responsed positively for 100 continue request
/// </summary>
public
bool
Is100Continue
{
get
;
internal
set
;
}
public
bool
Is100Continue
{
get
;
internal
set
;
}
/// <summary>
/// Server responsed negatively for the request for 100 continue
/// </summary>
public
bool
ExpectationFailed
{
get
;
internal
set
;
}
public
bool
ExpectationFailed
{
get
;
internal
set
;
}
public
Request
()
public
Request
()
...
...
Titanium.Web.Proxy/Http/Response.cs
View file @
82326b01
...
@@ -4,10 +4,13 @@ using System.Linq;
...
@@ -4,10 +4,13 @@ using System.Linq;
using
System.Text
;
using
System.Text
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Extensions
;
using
System
;
namespace
Titanium.Web.Proxy.Http
namespace
Titanium.Web.Proxy.Http
{
{
/// <summary>
/// Http(s) response object
/// </summary>
public
class
Response
public
class
Response
{
{
public
string
ResponseStatusCode
{
get
;
set
;
}
public
string
ResponseStatusCode
{
get
;
set
;
}
...
@@ -15,7 +18,9 @@ namespace Titanium.Web.Proxy.Http
...
@@ -15,7 +18,9 @@ namespace Titanium.Web.Proxy.Http
internal
Encoding
Encoding
{
get
{
return
this
.
GetResponseCharacterEncoding
();
}
}
internal
Encoding
Encoding
{
get
{
return
this
.
GetResponseCharacterEncoding
();
}
}
/// <summary>
/// Content encoding for this response
/// </summary>
internal
string
ContentEncoding
internal
string
ContentEncoding
{
{
get
get
...
@@ -31,7 +36,11 @@ namespace Titanium.Web.Proxy.Http
...
@@ -31,7 +36,11 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
internal
string
HttpVersion
{
get
;
set
;
}
internal
Version
HttpVersion
{
get
;
set
;
}
/// <summary>
/// Keep the connection alive?
/// </summary>
internal
bool
ResponseKeepAlive
internal
bool
ResponseKeepAlive
{
{
get
get
...
@@ -48,6 +57,9 @@ namespace Titanium.Web.Proxy.Http
...
@@ -48,6 +57,9 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Content type of this response
/// </summary>
public
string
ContentType
public
string
ContentType
{
{
get
get
...
@@ -64,6 +76,9 @@ namespace Titanium.Web.Proxy.Http
...
@@ -64,6 +76,9 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Length of response body
/// </summary>
internal
long
ContentLength
internal
long
ContentLength
{
{
get
get
...
@@ -102,6 +117,9 @@ namespace Titanium.Web.Proxy.Http
...
@@ -102,6 +117,9 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Response transfer-encoding is chunked?
/// </summary>
internal
bool
IsChunked
internal
bool
IsChunked
{
{
get
get
...
@@ -140,14 +158,37 @@ namespace Titanium.Web.Proxy.Http
...
@@ -140,14 +158,37 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
/// <summary>
/// Collection of all response headers
/// </summary>
public
List
<
HttpHeader
>
ResponseHeaders
{
get
;
set
;
}
public
List
<
HttpHeader
>
ResponseHeaders
{
get
;
set
;
}
/// <summary>
/// Response network stream
/// </summary>
internal
Stream
ResponseStream
{
get
;
set
;
}
internal
Stream
ResponseStream
{
get
;
set
;
}
/// <summary>
/// response body contenst as byte array
/// </summary>
internal
byte
[]
ResponseBody
{
get
;
set
;
}
internal
byte
[]
ResponseBody
{
get
;
set
;
}
/// <summary>
/// response body as string
/// </summary>
internal
string
ResponseBodyString
{
get
;
set
;
}
internal
string
ResponseBodyString
{
get
;
set
;
}
internal
bool
ResponseBodyRead
{
get
;
set
;
}
internal
bool
ResponseBodyRead
{
get
;
set
;
}
internal
bool
ResponseLocked
{
get
;
set
;
}
internal
bool
ResponseLocked
{
get
;
set
;
}
/// <summary>
/// Is response 100-continue
/// </summary>
public
bool
Is100Continue
{
get
;
internal
set
;
}
public
bool
Is100Continue
{
get
;
internal
set
;
}
/// <summary>
/// expectation failed returned by server?
/// </summary>
public
bool
ExpectationFailed
{
get
;
internal
set
;
}
public
bool
ExpectationFailed
{
get
;
internal
set
;
}
public
Response
()
public
Response
()
...
...
Titanium.Web.Proxy/Http/Responses/OkResponse.cs
View file @
82326b01
using
Titanium.Web.Proxy.Network
;
namespace
Titanium.Web.Proxy.Http.Responses
namespace
Titanium.Web.Proxy.Http.Responses
{
{
/// <summary>
/// 200 Ok response
/// </summary>
public
class
OkResponse
:
Response
public
class
OkResponse
:
Response
{
{
public
OkResponse
()
public
OkResponse
()
...
...
Titanium.Web.Proxy/Http/Responses/RedirectResponse.cs
View file @
82326b01
...
@@ -2,6 +2,9 @@
...
@@ -2,6 +2,9 @@
namespace
Titanium.Web.Proxy.Http.Responses
namespace
Titanium.Web.Proxy.Http.Responses
{
{
/// <summary>
/// Redirect response
/// </summary>
public
class
RedirectResponse
:
Response
public
class
RedirectResponse
:
Response
{
{
public
RedirectResponse
()
public
RedirectResponse
()
...
...
Titanium.Web.Proxy/Models/EndPoint.cs
View file @
82326b01
...
@@ -4,6 +4,9 @@ using System.Net.Sockets;
...
@@ -4,6 +4,9 @@ using System.Net.Sockets;
namespace
Titanium.Web.Proxy.Models
namespace
Titanium.Web.Proxy.Models
{
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public
abstract
class
ProxyEndPoint
public
abstract
class
ProxyEndPoint
{
{
public
ProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
public
ProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
...
@@ -20,6 +23,10 @@ namespace Titanium.Web.Proxy.Models
...
@@ -20,6 +23,10 @@ namespace Titanium.Web.Proxy.Models
internal
TcpListener
listener
{
get
;
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
public
class
ExplicitProxyEndPoint
:
ProxyEndPoint
{
{
internal
bool
IsSystemHttpProxy
{
get
;
set
;
}
internal
bool
IsSystemHttpProxy
{
get
;
set
;
}
...
@@ -34,6 +41,10 @@ namespace Titanium.Web.Proxy.Models
...
@@ -34,6 +41,10 @@ namespace Titanium.Web.Proxy.Models
}
}
}
}
/// <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
public
class
TransparentProxyEndPoint
:
ProxyEndPoint
{
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
...
...
Titanium.Web.Proxy/Models/ExternalProxy.cs
View file @
82326b01
using
System
;
namespace
Titanium.Web.Proxy.Models
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Net
;
using
System.Text
;
using
System.Threading.Tasks
;
namespace
Titanium.Web.Proxy.Models
{
{
/// <summary>
/// An upstream proxy this proxy uses if any
/// </summary>
public
class
ExternalProxy
public
class
ExternalProxy
{
{
public
string
HostName
{
get
;
set
;
}
public
string
HostName
{
get
;
set
;
}
...
...
Titanium.Web.Proxy/Network/CachedCertificate.cs
0 → 100644
View file @
82326b01
using
System
;
using
System.Security.Cryptography.X509Certificates
;
namespace
Titanium.Web.Proxy.Network
{
/// <summary>
/// An object that holds the cached certificate
/// </summary>
internal
class
CachedCertificate
{
internal
X509Certificate2
Certificate
{
get
;
set
;
}
/// <summary>
/// last time this certificate was used
/// Usefull in determining its cache lifetime
/// </summary>
internal
DateTime
LastAccess
{
get
;
set
;
}
internal
CachedCertificate
()
{
LastAccess
=
DateTime
.
Now
;
}
}
}
Titanium.Web.Proxy/
Helpers
/CertificateManager.cs
→
Titanium.Web.Proxy/
Network
/CertificateManager.cs
View file @
82326b01
...
@@ -6,24 +6,28 @@ using System.Security.Cryptography.X509Certificates;
...
@@ -6,24 +6,28 @@ using System.Security.Cryptography.X509Certificates;
using
System.Reflection
;
using
System.Reflection
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
System.Threading
;
using
System.Threading
;
using
System.Linq
;
namespace
Titanium.Web.Proxy.
Helpers
namespace
Titanium.Web.Proxy.
Network
{
{
public
class
CertificateManager
:
IDisposable
/// <summary>
/// A class to manage SSL certificates used by this proxy server
/// </summary>
internal
class
CertificateManager
:
IDisposable
{
{
private
const
string
CertCreateFormat
=
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}"
;
"-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
readonly
IDictionary
<
string
,
X509Certificate2
>
_certificateCache
;
private
readonly
IDictionary
<
string
,
CachedCertificate
>
certificateCache
;
private
static
SemaphoreSlim
semaphoreLock
=
new
SemaphoreSlim
(
1
);
private
static
SemaphoreSlim
semaphoreLock
=
new
SemaphoreSlim
(
1
);
public
string
Issuer
{
get
;
private
set
;
}
internal
string
Issuer
{
get
;
private
set
;
}
public
string
RootCertificateName
{
get
;
private
set
;
}
internal
string
RootCertificateName
{
get
;
private
set
;
}
public
X509Store
MyStore
{
get
;
private
set
;
}
internal
X509Store
MyStore
{
get
;
private
set
;
}
public
X509Store
RootStore
{
get
;
private
set
;
}
internal
X509Store
RootStore
{
get
;
private
set
;
}
public
CertificateManager
(
string
issuer
,
string
rootCertificateName
)
internal
CertificateManager
(
string
issuer
,
string
rootCertificateName
)
{
{
Issuer
=
issuer
;
Issuer
=
issuer
;
RootCertificateName
=
rootCertificateName
;
RootCertificateName
=
rootCertificateName
;
...
@@ -31,17 +35,17 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -31,17 +35,17 @@ namespace Titanium.Web.Proxy.Helpers
MyStore
=
new
X509Store
(
StoreName
.
My
,
StoreLocation
.
CurrentUser
);
MyStore
=
new
X509Store
(
StoreName
.
My
,
StoreLocation
.
CurrentUser
);
RootStore
=
new
X509Store
(
StoreName
.
Root
,
StoreLocation
.
CurrentUser
);
RootStore
=
new
X509Store
(
StoreName
.
Root
,
StoreLocation
.
CurrentUser
);
_certificateCache
=
new
Dictionary
<
string
,
X509Certificate2
>();
certificateCache
=
new
Dictionary
<
string
,
CachedCertificate
>();
}
}
/// <summary>
/// <summary>
/// Attempts to move a self-signed certificate to the root store.
/// Attempts to move a self-signed certificate to the root store.
/// </summary>
/// </summary>
/// <returns>true if succeeded, else false</returns>
/// <returns>true if succeeded, else false</returns>
public
async
Task
<
bool
>
CreateTrustedRootCertificate
()
internal
async
Task
<
bool
>
CreateTrustedRootCertificate
()
{
{
X509Certificate2
rootCertificate
=
X509Certificate2
rootCertificate
=
await
CreateCertificate
(
RootStore
,
RootCertificateName
);
await
CreateCertificate
(
RootStore
,
RootCertificateName
,
true
);
return
rootCertificate
!=
null
;
return
rootCertificate
!=
null
;
}
}
...
@@ -49,12 +53,12 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -49,12 +53,12 @@ namespace Titanium.Web.Proxy.Helpers
/// Attempts to remove the self-signed certificate from the root store.
/// Attempts to remove the self-signed certificate from the root store.
/// </summary>
/// </summary>
/// <returns>true if succeeded, else false</returns>
/// <returns>true if succeeded, else false</returns>
public
async
Task
<
bool
>
DestroyTrustedRootCertificate
()
internal
bool
DestroyTrustedRootCertificate
()
{
{
return
await
DestroyCertificate
(
RootStore
,
RootCertificateName
);
return
DestroyCertificate
(
RootStore
,
RootCertificateName
,
false
);
}
}
public
X509Certificate2Collection
FindCertificates
(
string
certificateSubject
)
internal
X509Certificate2Collection
FindCertificates
(
string
certificateSubject
)
{
{
return
FindCertificates
(
MyStore
,
certificateSubject
);
return
FindCertificates
(
MyStore
,
certificateSubject
);
}
}
...
@@ -67,29 +71,44 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -67,29 +71,44 @@ namespace Titanium.Web.Proxy.Helpers
discoveredCertificates
:
null
;
discoveredCertificates
:
null
;
}
}
public
async
Task
<
X509Certificate2
>
CreateCertificate
(
string
certificateName
)
internal
async
Task
<
X509Certificate2
>
CreateCertificate
(
string
certificateName
,
bool
isRootCertificate
)
{
{
return
await
CreateCertificate
(
MyStore
,
certificateName
);
return
await
CreateCertificate
(
MyStore
,
certificateName
,
isRootCertificate
);
}
}
protected
async
virtual
Task
<
X509Certificate2
>
CreateCertificate
(
X509Store
store
,
string
certificateName
)
{
if
(
_certificateCache
.
ContainsKey
(
certificateName
))
return
_certificateCache
[
certificateName
];
/// <summary>
/// Create an SSL certificate
/// </summary>
/// <param name="store"></param>
/// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param>
/// <returns></returns>
protected
async
virtual
Task
<
X509Certificate2
>
CreateCertificate
(
X509Store
store
,
string
certificateName
,
bool
isRootCertificate
)
{
await
semaphoreLock
.
WaitAsync
();
await
semaphoreLock
.
WaitAsync
();
X509Certificate2
certificate
=
null
;
try
try
{
{
if
(
certificateCache
.
ContainsKey
(
certificateName
))
{
var
cached
=
certificateCache
[
certificateName
];
cached
.
LastAccess
=
DateTime
.
Now
;
return
cached
.
Certificate
;
}
X509Certificate2
certificate
=
null
;
store
.
Open
(
OpenFlags
.
ReadWrite
);
store
.
Open
(
OpenFlags
.
ReadWrite
);
string
certificateSubject
=
string
.
Format
(
"CN={0}, O={1}"
,
certificateName
,
Issuer
);
string
certificateSubject
=
string
.
Format
(
"CN={0}, O={1}"
,
certificateName
,
Issuer
);
var
certificates
=
X509Certificate2Collection
certificates
;
FindCertificates
(
store
,
certificateSubject
);
if
(
isRootCertificate
)
{
certificates
=
FindCertificates
(
store
,
certificateSubject
);
if
(
certificates
!=
null
)
if
(
certificates
!=
null
)
certificate
=
certificates
[
0
];
certificate
=
certificates
[
0
];
}
if
(
certificate
==
null
)
if
(
certificate
==
null
)
{
{
...
@@ -99,13 +118,18 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -99,13 +118,18 @@ namespace Titanium.Web.Proxy.Helpers
await
CreateCertificate
(
args
);
await
CreateCertificate
(
args
);
certificates
=
FindCertificates
(
store
,
certificateSubject
);
certificates
=
FindCertificates
(
store
,
certificateSubject
);
return
certificates
!=
null
?
//remove it from store
certificates
[
0
]
:
null
;
if
(!
isRootCertificate
)
DestroyCertificate
(
certificateName
);
if
(
certificates
!=
null
)
certificate
=
certificates
[
0
];
}
}
store
.
Close
();
store
.
Close
();
if
(
certificate
!=
null
&&
!
_certificateCache
.
ContainsKey
(
certificateName
))
_certificateCache
.
Add
(
certificateName
,
certificate
);
if
(
certificate
!=
null
&&
!
certificateCache
.
ContainsKey
(
certificateName
))
certificateCache
.
Add
(
certificateName
,
new
CachedCertificate
()
{
Certificate
=
certificate
});
return
certificate
;
return
certificate
;
}
}
...
@@ -113,8 +137,13 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -113,8 +137,13 @@ namespace Titanium.Web.Proxy.Helpers
{
{
semaphoreLock
.
Release
();
semaphoreLock
.
Release
();
}
}
}
}
/// <summary>
/// Create certificate using makecert.exe
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
protected
virtual
Task
<
int
>
CreateCertificate
(
string
[]
args
)
protected
virtual
Task
<
int
>
CreateCertificate
(
string
[]
args
)
{
{
...
@@ -152,18 +181,28 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -152,18 +181,28 @@ namespace Titanium.Web.Proxy.Helpers
return
tcs
.
Task
;
return
tcs
.
Task
;
}
}
/// <summary>
public
async
Task
<
bool
>
DestroyCertificate
(
string
certificateName
)
/// Destroy an SSL certificate from local store
/// </summary>
/// <param name="certificateName"></param>
/// <returns></returns>
internal
bool
DestroyCertificate
(
string
certificateName
)
{
{
return
await
DestroyCertificate
(
MyStore
,
certificateName
);
return
DestroyCertificate
(
MyStore
,
certificateName
,
false
);
}
}
protected
virtual
async
Task
<
bool
>
DestroyCertificate
(
X509Store
store
,
string
certificateName
)
{
await
semaphoreLock
.
WaitAsync
();
X509Certificate2Collection
certificates
=
null
;
/// <summary>
try
/// 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
);
store
.
Open
(
OpenFlags
.
ReadWrite
);
string
certificateSubject
=
string
.
Format
(
"CN={0}, O={1}"
,
certificateName
,
Issuer
);
string
certificateSubject
=
string
.
Format
(
"CN={0}, O={1}"
,
certificateName
,
Issuer
);
...
@@ -175,20 +214,19 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -175,20 +214,19 @@ namespace Titanium.Web.Proxy.Helpers
}
}
store
.
Close
();
store
.
Close
();
if
(
certificates
==
null
&&
if
(
removeFromCache
&&
_certificateCache
.
ContainsKey
(
certificateName
))
certificateCache
.
ContainsKey
(
certificateName
))
{
{
_certificateCache
.
Remove
(
certificateName
);
certificateCache
.
Remove
(
certificateName
);
}
}
return
certificates
==
null
;
return
certificates
==
null
;
}
}
finally
/// <summary>
{
/// Create the neccessary arguments for makecert.exe to create the required certificate
semaphoreLock
.
Release
();
/// </summary>
}
/// <param name="store"></param>
/// <param name="certificateName"></param>
}
/// <returns></returns>
protected
virtual
string
GetCertificateCreateArgs
(
X509Store
store
,
string
certificateName
)
protected
virtual
string
GetCertificateCreateArgs
(
X509Store
store
,
string
certificateName
)
{
{
bool
isRootCertificate
=
bool
isRootCertificate
=
...
@@ -203,6 +241,44 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -203,6 +241,44 @@ namespace Titanium.Web.Proxy.Helpers
return
certCreatArgs
;
return
certCreatArgs
;
}
}
private
static
bool
clearCertificates
{
get
;
set
;
}
/// <summary>
/// Stops the certificate cache clear process
/// </summary>
internal
void
StopClearIdleCertificates
()
{
clearCertificates
=
false
;
}
/// <summary>
/// A method to clear outdated certificates
/// </summary>
internal
async
void
ClearIdleCertificates
()
{
clearCertificates
=
true
;
while
(
clearCertificates
)
{
await
semaphoreLock
.
WaitAsync
();
try
{
var
cutOff
=
DateTime
.
Now
.
AddMinutes
(-
1
*
ProxyServer
.
CertificateCacheTimeOutMinutes
);
var
outdated
=
certificateCache
.
Where
(
x
=>
x
.
Value
.
LastAccess
<
cutOff
)
.
ToList
();
foreach
(
var
cache
in
outdated
)
certificateCache
.
Remove
(
cache
.
Key
);
}
finally
{
semaphoreLock
.
Release
();
}
//after a minute come back to check for outdated certificates in cache
await
Task
.
Delay
(
1000
*
60
);
}
}
public
void
Dispose
()
public
void
Dispose
()
{
{
if
(
MyStore
!=
null
)
if
(
MyStore
!=
null
)
...
...
Titanium.Web.Proxy/Network/CustomSslStream.cs
deleted
100644 → 0
View file @
e1cd5fcd
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Net.Security
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.EventArguments
;
namespace
Titanium.Web.Proxy.Network
{
/// <summary>
/// Used to pass in Session object for ServerCertificateValidation Callback
/// </summary>
internal
class
CustomSslStream
:
SslStream
{
/// <summary>
/// Holds the current session
/// </summary>
internal
SessionEventArgs
Session
{
get
;
set
;
}
public
CustomSslStream
(
Stream
innerStream
,
bool
leaveInnerStreamOpen
,
RemoteCertificateValidationCallback
userCertificateValidationCallback
)
:
base
(
innerStream
,
leaveInnerStreamOpen
,
userCertificateValidationCallback
)
{
}
}
}
Titanium.Web.Proxy/Network/ProxyClient.cs
View file @
82326b01
...
@@ -5,27 +5,27 @@ using Titanium.Web.Proxy.Helpers;
...
@@ -5,27 +5,27 @@ using Titanium.Web.Proxy.Helpers;
namespace
Titanium.Web.Proxy.Network
namespace
Titanium.Web.Proxy.Network
{
{
/// <summary>
/// <summary>
/// This class wraps Tcp connection to
Server
/// This class wraps Tcp connection to
client
/// </summary>
/// </summary>
public
class
ProxyClient
public
class
ProxyClient
{
{
/// <summary>
/// <summary>
/// TcpClient used to communicate with
server
/// TcpClient used to communicate with
client
/// </summary>
/// </summary>
internal
TcpClient
TcpClient
{
get
;
set
;
}
internal
TcpClient
TcpClient
{
get
;
set
;
}
/// <summary>
/// <summary>
/// holds the stream to
server
/// holds the stream to
client
/// </summary>
/// </summary>
internal
Stream
ClientStream
{
get
;
set
;
}
internal
Stream
ClientStream
{
get
;
set
;
}
/// <summary>
/// <summary>
/// Used to read line by line from
server
/// Used to read line by line from
client
/// </summary>
/// </summary>
internal
CustomBinaryReader
ClientStreamReader
{
get
;
set
;
}
internal
CustomBinaryReader
ClientStreamReader
{
get
;
set
;
}
/// <summary>
/// <summary>
/// used to write line by line to
server
/// used to write line by line to
client
/// </summary>
/// </summary>
internal
StreamWriter
ClientStreamWriter
{
get
;
set
;
}
internal
StreamWriter
ClientStreamWriter
{
get
;
set
;
}
...
...
Titanium.Web.Proxy/Network/TcpConnection.cs
0 → 100644
View file @
82326b01
using
System
;
using
System.IO
;
using
System.Net.Sockets
;
using
Titanium.Web.Proxy.Helpers
;
namespace
Titanium.Web.Proxy.Network
{
/// <summary>
/// An object that holds TcpConnection to a particular server & port
/// </summary>
public
class
TcpConnection
{
internal
string
HostName
{
get
;
set
;
}
internal
int
port
{
get
;
set
;
}
internal
bool
IsHttps
{
get
;
set
;
}
/// <summary>
/// Http version
/// </summary>
internal
Version
Version
{
get
;
set
;
}
internal
TcpClient
TcpClient
{
get
;
set
;
}
/// <summary>
/// used to read lines from server
/// </summary>
internal
CustomBinaryReader
StreamReader
{
get
;
set
;
}
/// <summary>
/// Server stream
/// </summary>
internal
Stream
Stream
{
get
;
set
;
}
/// <summary>
/// Last time this connection was used
/// </summary>
internal
DateTime
LastAccess
{
get
;
set
;
}
internal
TcpConnection
()
{
LastAccess
=
DateTime
.
Now
;
}
}
}
Titanium.Web.Proxy/Network/TcpConnectionManager.cs
View file @
82326b01
...
@@ -10,88 +10,124 @@ using Titanium.Web.Proxy.Helpers;
...
@@ -10,88 +10,124 @@ using Titanium.Web.Proxy.Helpers;
using
System.Threading
;
using
System.Threading
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Shared
;
using
Titanium.Web.Proxy.Shared
;
using
System.Security.Cryptography.X509Certificates
;
using
Titanium.Web.Proxy.EventArguments
;
namespace
Titanium.Web.Proxy.Network
namespace
Titanium.Web.Proxy.Network
{
{
public
class
TcpConnection
/// <summary>
/// A class that manages Tcp Connection to server used by this proxy server
/// </summary>
internal
class
TcpConnectionManager
{
{
internal
string
HostName
{
get
;
set
;
}
/// <summary>
internal
int
port
{
get
;
set
;
}
/// Connection cache
internal
bool
IsSecure
{
get
;
set
;
}
/// </summary>
internal
Version
Version
{
get
;
set
;
}
static
Dictionary
<
string
,
List
<
TcpConnection
>>
connectionCache
=
new
Dictionary
<
string
,
List
<
TcpConnection
>>();
internal
TcpClient
TcpClient
{
get
;
set
;
}
internal
CustomBinaryReader
StreamReader
{
get
;
set
;
}
internal
Stream
Stream
{
get
;
set
;
}
internal
DateTime
LastAccess
{
get
;
set
;
}
/// <summary>
/// A lock to manage concurrency
/// </summary>
static
SemaphoreSlim
connectionAccessLock
=
new
SemaphoreSlim
(
1
);
internal
TcpConnection
()
/// <summary>
/// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <returns></returns>
internal
static
async
Task
<
TcpConnection
>
GetClient
(
string
hostname
,
int
port
,
bool
isHttps
,
Version
version
)
{
{
LastAccess
=
DateTime
.
Now
;
List
<
TcpConnection
>
cachedConnections
=
null
;
}
TcpConnection
cached
=
null
;
}
internal
class
TcpConnectionManager
//Get a unique string to identify this connection
{
var
key
=
GetConnectionKey
(
hostname
,
port
,
isHttps
,
version
);
static
List
<
TcpConnection
>
ConnectionCache
=
new
List
<
TcpConnection
>();
internal
static
async
Task
<
TcpConnection
>
GetClient
(
SessionEventArgs
sessionArgs
,
string
hostname
,
int
port
,
bool
isSecure
,
Version
version
)
{
TcpConnection
cached
=
null
;
while
(
true
)
while
(
true
)
{
{
lock
(
ConnectionCache
)
await
connectionAccessLock
.
WaitAsync
();
try
{
{
cached
=
ConnectionCache
.
FirstOrDefault
(
x
=>
x
.
HostName
==
hostname
&&
x
.
port
==
port
&&
connectionCache
.
TryGetValue
(
key
,
out
cachedConnections
);
x
.
IsSecure
==
isSecure
&&
x
.
TcpClient
.
Connected
&&
x
.
Version
.
Equals
(
version
));
if
(
cached
!=
null
)
if
(
cachedConnections
!=
null
&&
cachedConnections
.
Count
>
0
)
ConnectionCache
.
Remove
(
cached
);
{
cached
=
cachedConnections
.
First
();
cachedConnections
.
Remove
(
cached
);
}
}
else
{
cached
=
null
;
}
}
finally
{
connectionAccessLock
.
Release
();
}
if
(
cached
!=
null
&&
!
cached
.
TcpClient
.
Client
.
IsConnected
())
if
(
cached
!=
null
&&
!
cached
.
TcpClient
.
Client
.
IsConnected
())
{
cached
.
TcpClient
.
Client
.
Dispose
();
cached
.
TcpClient
.
Close
();
continue
;
continue
;
}
if
(
cached
==
null
)
if
(
cached
==
null
)
break
;
break
;
}
}
if
(
cached
==
null
)
if
(
cached
==
null
)
cached
=
await
CreateClient
(
sessionArgs
,
hostname
,
port
,
isSecure
,
version
).
ConfigureAwait
(
false
);
cached
=
await
CreateClient
(
hostname
,
port
,
isHttps
,
version
);
//just create one more preemptively
if
(
cachedConnections
==
null
||
cachedConnections
.
Count
()
<=
2
)
if
(
ConnectionCache
.
Where
(
x
=>
x
.
HostName
==
hostname
&&
x
.
port
==
port
&&
x
.
IsSecure
==
isSecure
&&
x
.
TcpClient
.
Connected
&&
x
.
Version
.
Equals
(
version
)).
Count
()
<
2
)
{
{
var
task
=
CreateClient
(
sessionArgs
,
hostname
,
port
,
isSecure
,
version
)
var
task
=
CreateClient
(
hostname
,
port
,
isHttps
,
version
)
.
ContinueWith
(
x
=>
{
if
(
x
.
Status
==
TaskStatus
.
RanToCompletion
)
ReleaseClient
(
x
.
Result
);
});
.
ContinueWith
(
async
(
x
)
=>
{
if
(
x
.
Status
==
TaskStatus
.
RanToCompletion
)
await
ReleaseClient
(
x
.
Result
);
});
}
}
return
cached
;
return
cached
;
}
}
private
static
async
Task
<
TcpConnection
>
CreateClient
(
SessionEventArgs
sessionArgs
,
string
hostname
,
int
port
,
bool
isSecure
,
Version
version
)
/// <summary>
/// Get a string to identfiy the connection to a particular host, port
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <returns></returns>
internal
static
string
GetConnectionKey
(
string
hostname
,
int
port
,
bool
isHttps
,
Version
version
)
{
return
string
.
Format
(
"{0}:{1}:{2}:{3}:{4}"
,
hostname
.
ToLower
(),
port
,
isHttps
,
version
.
Major
,
version
.
Minor
);
}
/// <summary>
/// Create connection to a particular host/port optionally with SSL and a particular HTTP version
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <returns></returns>
private
static
async
Task
<
TcpConnection
>
CreateClient
(
string
hostname
,
int
port
,
bool
isHttps
,
Version
version
)
{
{
TcpClient
client
;
TcpClient
client
;
Stream
stream
;
Stream
stream
;
if
(
is
Secure
)
if
(
is
Https
)
{
{
Custom
SslStream
sslStream
=
null
;
SslStream
sslStream
=
null
;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if
(
ProxyServer
.
UpStreamHttpsProxy
!=
null
)
if
(
ProxyServer
.
UpStreamHttpsProxy
!=
null
)
{
{
client
=
new
TcpClient
(
ProxyServer
.
UpStreamHttpsProxy
.
HostName
,
ProxyServer
.
UpStreamHttpsProxy
.
Port
);
client
=
new
TcpClient
(
ProxyServer
.
UpStreamHttpsProxy
.
HostName
,
ProxyServer
.
UpStreamHttpsProxy
.
Port
);
stream
=
(
Stream
)
client
.
GetStream
();
stream
=
(
Stream
)
client
.
GetStream
();
using
(
var
writer
=
new
StreamWriter
(
stream
,
Encoding
.
ASCII
,
Constants
.
BUFFER_SIZE
,
true
))
using
(
var
writer
=
new
StreamWriter
(
stream
,
Encoding
.
ASCII
,
Proxy
Constants
.
BUFFER_SIZE
,
true
))
{
{
await
writer
.
WriteLineAsync
(
string
.
Format
(
"CONNECT {0}:{1} {2}"
,
sessionArgs
.
WebSession
.
Request
.
RequestUri
.
Host
,
sessionArgs
.
WebSession
.
Request
.
RequestUri
.
Port
,
sessionArgs
.
WebSession
.
Request
.
HttpV
ersion
));
await
writer
.
WriteLineAsync
(
string
.
Format
(
"CONNECT {0}:{1} {2}"
,
hostname
,
port
,
v
ersion
));
await
writer
.
WriteLineAsync
(
string
.
Format
(
"Host: {0}:{1}"
,
sessionArgs
.
WebSession
.
Request
.
RequestUri
.
Host
,
sessionArgs
.
WebSession
.
Request
.
RequestUri
.
P
ort
));
await
writer
.
WriteLineAsync
(
string
.
Format
(
"Host: {0}:{1}"
,
hostname
,
p
ort
));
await
writer
.
WriteLineAsync
(
"Connection: Keep-Alive"
);
await
writer
.
WriteLineAsync
(
"Connection: Keep-Alive"
);
await
writer
.
WriteLineAsync
();
await
writer
.
WriteLineAsync
();
await
writer
.
FlushAsync
();
await
writer
.
FlushAsync
();
...
@@ -100,12 +136,12 @@ namespace Titanium.Web.Proxy.Network
...
@@ -100,12 +136,12 @@ namespace Titanium.Web.Proxy.Network
using
(
var
reader
=
new
CustomBinaryReader
(
stream
))
using
(
var
reader
=
new
CustomBinaryReader
(
stream
))
{
{
var
result
=
await
reader
.
ReadLineAsync
()
.
ConfigureAwait
(
false
)
;
var
result
=
await
reader
.
ReadLineAsync
();
if
(!
result
.
ToLower
().
Contains
(
"200 connection established"
))
if
(!
result
.
ToLower
().
Contains
(
"200 connection established"
))
throw
new
Exception
(
"Upstream proxy failed to create a secure tunnel"
);
throw
new
Exception
(
"Upstream proxy failed to create a secure tunnel"
);
await
reader
.
ReadAllLinesAsync
()
.
ConfigureAwait
(
false
)
;
await
reader
.
ReadAllLinesAsync
();
}
}
}
}
else
else
...
@@ -116,9 +152,9 @@ namespace Titanium.Web.Proxy.Network
...
@@ -116,9 +152,9 @@ namespace Titanium.Web.Proxy.Network
try
try
{
{
sslStream
=
new
CustomSslStream
(
stream
,
true
,
new
RemoteCertificateValidationCallback
(
ProxyServer
.
ValidateServerCertificate
));
sslStream
=
new
SslStream
(
stream
,
true
,
new
RemoteCertificateValidationCallback
(
ProxyServer
.
ValidateServerCertificate
),
sslStream
.
Session
=
sessionArgs
;
new
LocalCertificateSelectionCallback
(
ProxyServer
.
SelectClientCertificate
))
;
await
sslStream
.
AuthenticateAsClientAsync
(
hostname
,
null
,
Constants
.
SupportedProtocols
,
false
).
ConfigureAwait
(
false
);
await
sslStream
.
AuthenticateAsClientAsync
(
hostname
,
null
,
ProxyConstants
.
SupportedSslProtocols
,
false
);
stream
=
(
Stream
)
sslStream
;
stream
=
(
Stream
)
sslStream
;
}
}
catch
catch
...
@@ -146,7 +182,7 @@ namespace Titanium.Web.Proxy.Network
...
@@ -146,7 +182,7 @@ namespace Titanium.Web.Proxy.Network
{
{
HostName
=
hostname
,
HostName
=
hostname
,
port
=
port
,
port
=
port
,
Is
Secure
=
isSecure
,
Is
Https
=
isHttps
,
TcpClient
=
client
,
TcpClient
=
client
,
StreamReader
=
new
CustomBinaryReader
(
stream
),
StreamReader
=
new
CustomBinaryReader
(
stream
),
Stream
=
stream
,
Stream
=
stream
,
...
@@ -154,30 +190,67 @@ namespace Titanium.Web.Proxy.Network
...
@@ -154,30 +190,67 @@ namespace Titanium.Web.Proxy.Network
};
};
}
}
/// <summary>
/// Returns a Tcp Connection back to cache for reuse by other requests
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
internal
static
async
Task
ReleaseClient
(
TcpConnection
connection
)
{
connection
.
LastAccess
=
DateTime
.
Now
;
var
key
=
GetConnectionKey
(
connection
.
HostName
,
connection
.
port
,
connection
.
IsHttps
,
connection
.
Version
);
await
connectionAccessLock
.
WaitAsync
();
try
{
List
<
TcpConnection
>
cachedConnections
;
connectionCache
.
TryGetValue
(
key
,
out
cachedConnections
);
if
(
cachedConnections
!=
null
)
cachedConnections
.
Add
(
connection
);
else
connectionCache
.
Add
(
key
,
new
List
<
TcpConnection
>()
{
connection
});
}
finally
{
connectionAccessLock
.
Release
();
}
}
private
static
bool
clearConenctions
{
get
;
set
;
}
internal
static
void
ReleaseClient
(
TcpConnection
Connection
)
/// <summary>
/// Stop clearing idle connections
/// </summary>
internal
static
void
StopClearIdleConnections
()
{
{
Connection
.
LastAccess
=
DateTime
.
Now
;
clearConenctions
=
false
;
ConnectionCache
.
Add
(
Connection
);
}
}
/// <summary>
/// A method to clear idle connections
/// </summary>
internal
async
static
void
ClearIdleConnections
()
internal
async
static
void
ClearIdleConnections
()
{
{
while
(
true
)
clearConenctions
=
true
;
while
(
clearConenctions
)
{
{
lock
(
ConnectionCache
)
await
connectionAccessLock
.
WaitAsync
();
try
{
{
var
cutOff
=
DateTime
.
Now
.
Add
Seconds
(-
60
);
var
cutOff
=
DateTime
.
Now
.
Add
Minutes
(-
1
*
ProxyServer
.
ConnectionCacheTimeOutMinutes
);
ConnectionCache
connectionCache
.
SelectMany
(
x
=>
x
.
Value
)
.
Where
(
x
=>
x
.
LastAccess
<
cutOff
)
.
Where
(
x
=>
x
.
LastAccess
<
cutOff
)
.
ToList
()
.
ToList
()
.
ForEach
(
x
=>
x
.
TcpClient
.
Close
());
.
ForEach
(
x
=>
x
.
TcpClient
.
Close
());
ConnectionCache
.
RemoveAll
(
x
=>
x
.
LastAccess
<
cutOff
);
connectionCache
.
ToList
().
ForEach
(
x
=>
x
.
Value
.
RemoveAll
(
y
=>
y
.
LastAccess
<
cutOff
)
);
}
}
finally
{
connectionAccessLock
.
Release
();
}
await
Task
.
Delay
(
1000
*
60
*
3
).
ConfigureAwait
(
false
);
//every minute run this
await
Task
.
Delay
(
1000
*
60
);
}
}
}
}
...
...
Titanium.Web.Proxy/ProxyServer.cs
View file @
82326b01
...
@@ -22,19 +22,61 @@ namespace Titanium.Web.Proxy
...
@@ -22,19 +22,61 @@ namespace Titanium.Web.Proxy
static
ProxyServer
()
static
ProxyServer
()
{
{
ProxyEndPoints
=
new
List
<
ProxyEndPoint
>();
ProxyEndPoints
=
new
List
<
ProxyEndPoint
>();
Initialize
();
//default values
ConnectionCacheTimeOutMinutes
=
3
;
CertificateCacheTimeOutMinutes
=
60
;
}
}
/// <summary>
/// Manages certificates used by this proxy
/// </summary>
private
static
CertificateManager
CertManager
{
get
;
set
;
}
private
static
CertificateManager
CertManager
{
get
;
set
;
}
/// <summary>
/// Does the root certificate used by this proxy is trusted by the machine?
/// </summary>
private
static
bool
certTrusted
{
get
;
set
;
}
private
static
bool
certTrusted
{
get
;
set
;
}
/// <summary>
/// Is the proxy currently running
/// </summary>
private
static
bool
proxyRunning
{
get
;
set
;
}
private
static
bool
proxyRunning
{
get
;
set
;
}
/// <summary>
/// Name of the root certificate issuer
/// </summary>
public
static
string
RootCertificateIssuerName
{
get
;
set
;
}
public
static
string
RootCertificateIssuerName
{
get
;
set
;
}
/// <summary>
/// Name of the root certificate
/// </summary>
public
static
string
RootCertificateName
{
get
;
set
;
}
public
static
string
RootCertificateName
{
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
/// </summary>
public
static
bool
Enable100ContinueBehaviour
{
get
;
set
;
}
public
static
bool
Enable100ContinueBehaviour
{
get
;
set
;
}
/// <summary>
/// Minutes TCP connection cache to servers to be kept alive when in idle state
/// </summary>
public
static
int
ConnectionCacheTimeOutMinutes
{
get
;
set
;
}
/// <summary>
/// Minutes certificates should be kept in cache when not used
/// </summary>
public
static
int
CertificateCacheTimeOutMinutes
{
get
;
set
;
}
/// <summary>
/// Intercept request to server
/// </summary>
public
static
event
Func
<
object
,
SessionEventArgs
,
Task
>
BeforeRequest
;
public
static
event
Func
<
object
,
SessionEventArgs
,
Task
>
BeforeRequest
;
/// <summary>
/// Intercept response from server
/// </summary>
public
static
event
Func
<
object
,
SessionEventArgs
,
Task
>
BeforeResponse
;
public
static
event
Func
<
object
,
SessionEventArgs
,
Task
>
BeforeResponse
;
/// <summary>
/// <summary>
...
@@ -52,14 +94,38 @@ namespace Titanium.Web.Proxy
...
@@ -52,14 +94,38 @@ namespace Titanium.Web.Proxy
/// </summary>
/// </summary>
public
static
event
Func
<
object
,
CertificateValidationEventArgs
,
Task
>
ServerCertificateValidationCallback
;
public
static
event
Func
<
object
,
CertificateValidationEventArgs
,
Task
>
ServerCertificateValidationCallback
;
/// <summary>
/// Callback tooverride client certificate during SSL mutual authentication
/// </summary>
public
static
event
Func
<
object
,
CertificateSelectionEventArgs
,
Task
>
ClientCertificateSelectionCallback
;
/// <summary>
/// A list of IpAddress & port this proxy is listening to
/// </summary>
public
static
List
<
ProxyEndPoint
>
ProxyEndPoints
{
get
;
set
;
}
public
static
List
<
ProxyEndPoint
>
ProxyEndPoints
{
get
;
set
;
}
/// <summary>
/// Initialize the proxy
/// </summary>
public
static
void
Initialize
()
public
static
void
Initialize
()
{
{
TcpConnectionManager
.
ClearIdleConnections
();
TcpConnectionManager
.
ClearIdleConnections
();
CertManager
.
ClearIdleCertificates
();
}
/// <summary>
/// Quit the proxy
/// </summary>
public
static
void
Quit
()
{
TcpConnectionManager
.
StopClearIdleConnections
();
CertManager
.
StopClearIdleCertificates
();
}
}
/// <summary>
/// Add a proxy end point
/// </summary>
/// <param name="endPoint"></param>
public
static
void
AddEndPoint
(
ProxyEndPoint
endPoint
)
public
static
void
AddEndPoint
(
ProxyEndPoint
endPoint
)
{
{
ProxyEndPoints
.
Add
(
endPoint
);
ProxyEndPoints
.
Add
(
endPoint
);
...
@@ -68,6 +134,11 @@ namespace Titanium.Web.Proxy
...
@@ -68,6 +134,11 @@ namespace Titanium.Web.Proxy
Listen
(
endPoint
);
Listen
(
endPoint
);
}
}
/// <summary>
/// Remove a proxy end point
/// Will throw error if the end point does'nt exist
/// </summary>
/// <param name="endPoint"></param>
public
static
void
RemoveEndPoint
(
ProxyEndPoint
endPoint
)
public
static
void
RemoveEndPoint
(
ProxyEndPoint
endPoint
)
{
{
...
@@ -80,10 +151,13 @@ namespace Titanium.Web.Proxy
...
@@ -80,10 +151,13 @@ namespace Titanium.Web.Proxy
QuitListen
(
endPoint
);
QuitListen
(
endPoint
);
}
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
/// <param name="endPoint"></param>
public
static
void
SetAsSystemHttpProxy
(
ExplicitProxyEndPoint
endPoint
)
public
static
void
SetAsSystemHttpProxy
(
ExplicitProxyEndPoint
endPoint
)
{
{
V
erify
Proxy
(
endPoint
);
V
alidateEndPointAsSystem
Proxy
(
endPoint
);
//clear any settings previously added
//clear any settings previously added
ProxyEndPoints
.
OfType
<
ExplicitProxyEndPoint
>().
ToList
().
ForEach
(
x
=>
x
.
IsSystemHttpProxy
=
false
);
ProxyEndPoints
.
OfType
<
ExplicitProxyEndPoint
>().
ToList
().
ForEach
(
x
=>
x
.
IsSystemHttpProxy
=
false
);
...
@@ -99,14 +173,21 @@ namespace Titanium.Web.Proxy
...
@@ -99,14 +173,21 @@ namespace Titanium.Web.Proxy
}
}
/// <summary>
/// Remove any HTTP proxy setting of current machien
/// </summary>
public
static
void
DisableSystemHttpProxy
()
public
static
void
DisableSystemHttpProxy
()
{
{
SystemProxyHelper
.
RemoveHttpProxy
();
SystemProxyHelper
.
RemoveHttpProxy
();
}
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
/// <param name="endPoint"></param>
public
static
void
SetAsSystemHttpsProxy
(
ExplicitProxyEndPoint
endPoint
)
public
static
void
SetAsSystemHttpsProxy
(
ExplicitProxyEndPoint
endPoint
)
{
{
V
erify
Proxy
(
endPoint
);
V
alidateEndPointAsSystem
Proxy
(
endPoint
);
if
(!
endPoint
.
EnableSsl
)
if
(!
endPoint
.
EnableSsl
)
{
{
...
@@ -133,16 +214,25 @@ namespace Titanium.Web.Proxy
...
@@ -133,16 +214,25 @@ namespace Titanium.Web.Proxy
Console
.
WriteLine
(
"Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy"
,
endPoint
.
GetType
().
Name
,
endPoint
.
IpAddress
,
endPoint
.
Port
);
Console
.
WriteLine
(
"Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy"
,
endPoint
.
GetType
().
Name
,
endPoint
.
IpAddress
,
endPoint
.
Port
);
}
}
/// <summary>
/// Remove any HTTPS proxy setting for current machine
/// </summary>
public
static
void
DisableSystemHttpsProxy
()
public
static
void
DisableSystemHttpsProxy
()
{
{
SystemProxyHelper
.
RemoveHttpsProxy
();
SystemProxyHelper
.
RemoveHttpsProxy
();
}
}
/// <summary>
/// Clear all proxy settings for current machine
/// </summary>
public
static
void
DisableAllSystemProxies
()
public
static
void
DisableAllSystemProxies
()
{
{
SystemProxyHelper
.
DisableAllProxy
();
SystemProxyHelper
.
DisableAllProxy
();
}
}
/// <summary>
/// Start this proxy server
/// </summary>
public
static
void
Start
()
public
static
void
Start
()
{
{
if
(
proxyRunning
)
if
(
proxyRunning
)
...
@@ -161,9 +251,14 @@ namespace Titanium.Web.Proxy
...
@@ -161,9 +251,14 @@ namespace Titanium.Web.Proxy
Listen
(
endPoint
);
Listen
(
endPoint
);
}
}
Initialize
();
proxyRunning
=
true
;
proxyRunning
=
true
;
}
}
/// <summary>
/// Stop this proxy server
/// </summary>
public
static
void
Stop
()
public
static
void
Stop
()
{
{
if
(!
proxyRunning
)
if
(!
proxyRunning
)
...
@@ -186,9 +281,15 @@ namespace Titanium.Web.Proxy
...
@@ -186,9 +281,15 @@ namespace Titanium.Web.Proxy
CertManager
.
Dispose
();
CertManager
.
Dispose
();
Quit
();
proxyRunning
=
false
;
proxyRunning
=
false
;
}
}
/// <summary>
/// Listen on the given end point on local machine
/// </summary>
/// <param name="endPoint"></param>
private
static
void
Listen
(
ProxyEndPoint
endPoint
)
private
static
void
Listen
(
ProxyEndPoint
endPoint
)
{
{
endPoint
.
listener
=
new
TcpListener
(
endPoint
.
IpAddress
,
endPoint
.
Port
);
endPoint
.
listener
=
new
TcpListener
(
endPoint
.
IpAddress
,
endPoint
.
Port
);
...
@@ -199,13 +300,20 @@ namespace Titanium.Web.Proxy
...
@@ -199,13 +300,20 @@ namespace Titanium.Web.Proxy
endPoint
.
listener
.
BeginAcceptTcpClient
(
OnAcceptConnection
,
endPoint
);
endPoint
.
listener
.
BeginAcceptTcpClient
(
OnAcceptConnection
,
endPoint
);
}
}
/// <summary>
/// Quit listening on the given end point
/// </summary>
/// <param name="endPoint"></param>
private
static
void
QuitListen
(
ProxyEndPoint
endPoint
)
private
static
void
QuitListen
(
ProxyEndPoint
endPoint
)
{
{
endPoint
.
listener
.
Stop
();
endPoint
.
listener
.
Stop
();
}
}
/// <summary>
private
static
void
VerifyProxy
(
ExplicitProxyEndPoint
endPoint
)
/// Verifiy if its safe to set this end point as System proxy
/// </summary>
/// <param name="endPoint"></param>
private
static
void
ValidateEndPointAsSystemProxy
(
ExplicitProxyEndPoint
endPoint
)
{
{
if
(
ProxyEndPoints
.
Contains
(
endPoint
)
==
false
)
if
(
ProxyEndPoints
.
Contains
(
endPoint
)
==
false
)
throw
new
Exception
(
"Cannot set endPoints not added to proxy as system proxy"
);
throw
new
Exception
(
"Cannot set endPoints not added to proxy as system proxy"
);
...
@@ -214,12 +322,17 @@ namespace Titanium.Web.Proxy
...
@@ -214,12 +322,17 @@ namespace Titanium.Web.Proxy
throw
new
Exception
(
"Cannot set system proxy settings before proxy has been started."
);
throw
new
Exception
(
"Cannot set system proxy settings before proxy has been started."
);
}
}
/// <summary>
/// When a connection is received from client act
/// </summary>
/// <param name="asyn"></param>
private
static
void
OnAcceptConnection
(
IAsyncResult
asyn
)
private
static
void
OnAcceptConnection
(
IAsyncResult
asyn
)
{
{
var
endPoint
=
(
ProxyEndPoint
)
asyn
.
AsyncState
;
var
endPoint
=
(
ProxyEndPoint
)
asyn
.
AsyncState
;
try
try
{
{
//based on end point type call appropriate request handlers
var
client
=
endPoint
.
listener
.
EndAcceptTcpClient
(
asyn
);
var
client
=
endPoint
.
listener
.
EndAcceptTcpClient
(
asyn
);
if
(
endPoint
.
GetType
()
==
typeof
(
TransparentProxyEndPoint
))
if
(
endPoint
.
GetType
()
==
typeof
(
TransparentProxyEndPoint
))
HandleClient
(
endPoint
as
TransparentProxyEndPoint
,
client
);
HandleClient
(
endPoint
as
TransparentProxyEndPoint
,
client
);
...
@@ -236,6 +349,10 @@ namespace Titanium.Web.Proxy
...
@@ -236,6 +349,10 @@ namespace Titanium.Web.Proxy
// so just return.
// so just return.
return
;
return
;
}
}
catch
{
//Other errors are discarded to keep proxy running
}
}
}
...
...
Titanium.Web.Proxy/RequestHandler.cs
View file @
82326b01
using
System
;
using
System
;
using
System.Collections.Generic
;
using
System.Collections.Generic
;
using
System.Globalization
;
using
System.IO
;
using
System.IO
;
using
System.Linq
;
using
System.Linq
;
using
System.Net.Security
;
using
System.Net.Security
;
using
System.Net.Sockets
;
using
System.Net.Sockets
;
using
System.Security.Authentication
;
using
System.Security.Authentication
;
using
System.Text
;
using
System.Text.RegularExpressions
;
using
System.Text.RegularExpressions
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Helpers
;
...
@@ -20,6 +18,9 @@ using System.Threading.Tasks;
...
@@ -20,6 +18,9 @@ using System.Threading.Tasks;
namespace
Titanium.Web.Proxy
namespace
Titanium.Web.Proxy
{
{
/// <summary>
/// Handle the requesr
/// </summary>
partial
class
ProxyServer
partial
class
ProxyServer
{
{
//This is called when client is aware of proxy
//This is called when client is aware of proxy
...
@@ -33,8 +34,7 @@ namespace Titanium.Web.Proxy
...
@@ -33,8 +34,7 @@ namespace Titanium.Web.Proxy
try
try
{
{
//read the first line HTTP command
//read the first line HTTP command
var
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
var
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
().
ConfigureAwait
(
false
);
if
(
string
.
IsNullOrEmpty
(
httpCmd
))
if
(
string
.
IsNullOrEmpty
(
httpCmd
))
{
{
...
@@ -43,8 +43,9 @@ namespace Titanium.Web.Proxy
...
@@ -43,8 +43,9 @@ namespace Titanium.Web.Proxy
}
}
//break up the line into three components (method, remote URL & Http Version)
//break up the line into three components (method, remote URL & Http Version)
var
httpCmdSplit
=
httpCmd
.
Split
(
Constants
.
SpaceSplit
,
3
);
var
httpCmdSplit
=
httpCmd
.
Split
(
Proxy
Constants
.
SpaceSplit
,
3
);
//Find the request Verb
var
httpVerb
=
httpCmdSplit
[
0
];
var
httpVerb
=
httpCmdSplit
[
0
];
if
(
httpVerb
.
ToUpper
()
==
"CONNECT"
)
if
(
httpVerb
.
ToUpper
()
==
"CONNECT"
)
...
@@ -52,39 +53,50 @@ namespace Titanium.Web.Proxy
...
@@ -52,39 +53,50 @@ namespace Titanium.Web.Proxy
else
else
httpRemoteUri
=
new
Uri
(
httpCmdSplit
[
1
]);
httpRemoteUri
=
new
Uri
(
httpCmdSplit
[
1
]);
string
httpVersion
=
"HTTP/1.1"
;
//parse the HTTP version
Version
version
=
new
Version
(
1
,
1
);
if
(
httpCmdSplit
.
Length
==
3
)
if
(
httpCmdSplit
.
Length
==
3
)
httpVersion
=
httpCmdSplit
[
2
];
{
string
httpVersion
=
httpCmdSplit
[
2
].
Trim
();
var
excluded
=
endPoint
.
ExcludedHttpsHostNameRegex
!=
null
?
endPoint
.
ExcludedHttpsHostNameRegex
.
Any
(
x
=>
Regex
.
IsMatch
(
httpRemoteUri
.
Host
,
x
))
:
false
;
if
(
httpVersion
==
"http/1.0"
)
{
version
=
new
Version
(
1
,
0
);
}
}
var
excluded
=
endPoint
.
ExcludedHttpsHostNameRegex
!=
null
?
endPoint
.
ExcludedHttpsHostNameRegex
.
Any
(
x
=>
Regex
.
IsMatch
(
httpRemoteUri
.
Host
,
x
))
:
false
;
//Client wants to create a secure tcp tunnel (its a HTTPS request)
//Client wants to create a secure tcp tunnel (its a HTTPS request)
if
(
httpVerb
.
ToUpper
()
==
"CONNECT"
&&
!
excluded
&&
httpRemoteUri
.
Port
!=
80
)
if
(
httpVerb
.
ToUpper
()
==
"CONNECT"
&&
!
excluded
&&
httpRemoteUri
.
Port
!=
80
)
{
{
httpRemoteUri
=
new
Uri
(
"https://"
+
httpCmdSplit
[
1
]);
httpRemoteUri
=
new
Uri
(
"https://"
+
httpCmdSplit
[
1
]);
await
clientStreamReader
.
ReadAllLinesAsync
().
ConfigureAwait
(
false
);
await
clientStreamReader
.
ReadAllLinesAsync
();
await
WriteConnectResponse
(
clientStreamWriter
,
httpVersion
).
ConfigureAwait
(
false
);
var
certificate
=
await
CertManager
.
CreateCertificate
(
httpRemoteUri
.
Host
);
await
WriteConnectResponse
(
clientStreamWriter
,
version
);
SslStream
sslStream
=
null
;
SslStream
sslStream
=
null
;
try
try
{
{
sslStream
=
new
SslStream
(
clientStream
,
true
);
//create the Tcp Connection to server and then release it to connection cache
//Just doing what CONNECT request is asking as to do
var
tunnelClient
=
await
TcpConnectionManager
.
GetClient
(
httpRemoteUri
.
Host
,
httpRemoteUri
.
Port
,
true
,
version
);
await
TcpConnectionManager
.
ReleaseClient
(
tunnelClient
);
sslStream
=
new
SslStream
(
clientStream
,
true
);
var
certificate
=
await
CertManager
.
CreateCertificate
(
httpRemoteUri
.
Host
,
false
);
//Successfully managed to authenticate the client using the fake certificate
//Successfully managed to authenticate the client using the fake certificate
await
sslStream
.
AuthenticateAsServerAsync
(
certificate
,
false
,
await
sslStream
.
AuthenticateAsServerAsync
(
certificate
,
false
,
Constants
.
SupportedProtocols
,
false
).
ConfigureAwait
(
false
);
ProxyConstants
.
SupportedSslProtocols
,
false
);
//HTTPS server created - we can now decrypt the client's traffic
clientStream
=
sslStream
;
clientStreamReader
=
new
CustomBinaryReader
(
sslStream
);
clientStreamReader
=
new
CustomBinaryReader
(
sslStream
);
clientStreamWriter
=
new
StreamWriter
(
sslStream
);
clientStreamWriter
=
new
StreamWriter
(
sslStream
);
//HTTPS server created - we can now decrypt the client's traffic
clientStream
=
sslStream
;
}
}
catch
catch
{
{
if
(
sslStream
!=
null
)
if
(
sslStream
!=
null
)
...
@@ -94,26 +106,29 @@ namespace Titanium.Web.Proxy
...
@@ -94,26 +106,29 @@ namespace Titanium.Web.Proxy
return
;
return
;
}
}
//Now read the actual HTTPS request line
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
()
.
ConfigureAwait
(
false
)
;
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
}
}
//Sorry cannot do a HTTPS request decrypt to port 80 at this time
else
if
(
httpVerb
.
ToUpper
()
==
"CONNECT"
)
else
if
(
httpVerb
.
ToUpper
()
==
"CONNECT"
)
{
{
await
clientStreamReader
.
ReadAllLinesAsync
().
ConfigureAwait
(
false
);
//Cyphen out CONNECT request headers
await
WriteConnectResponse
(
clientStreamWriter
,
httpVersion
).
ConfigureAwait
(
false
);
await
clientStreamReader
.
ReadAllLinesAsync
();
//write back successfull CONNECT response
await
WriteConnectResponse
(
clientStreamWriter
,
version
);
//Just relay the request/response without decrypting it
await
TcpHelper
.
SendRaw
(
clientStream
,
null
,
null
,
httpRemoteUri
.
Host
,
httpRemoteUri
.
Port
,
await
TcpHelper
.
SendRaw
(
clientStream
,
null
,
null
,
httpRemoteUri
.
Host
,
httpRemoteUri
.
Port
,
false
)
.
ConfigureAwait
(
false
)
;
false
);
Dispose
(
client
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
null
);
Dispose
(
client
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
null
);
return
;
return
;
}
}
//Now create the request
//Now create the request
await
HandleHttpSessionRequest
(
client
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
await
HandleHttpSessionRequest
(
client
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
httpRemoteUri
.
Scheme
==
Uri
.
UriSchemeHttps
?
true
:
false
)
.
ConfigureAwait
(
false
)
;
httpRemoteUri
.
Scheme
==
Uri
.
UriSchemeHttps
?
true
:
false
);
}
}
catch
catch
{
{
...
@@ -139,13 +154,13 @@ namespace Titanium.Web.Proxy
...
@@ -139,13 +154,13 @@ namespace Titanium.Web.Proxy
// certificate = CertManager.CreateCertificate(hostName);
// certificate = CertManager.CreateCertificate(hostName);
//}
//}
//else
//else
certificate
=
await
CertManager
.
CreateCertificate
(
endPoint
.
GenericCertificateName
);
certificate
=
await
CertManager
.
CreateCertificate
(
endPoint
.
GenericCertificateName
,
false
);
try
try
{
{
//Successfully managed to authenticate the client using the fake certificate
//Successfully managed to authenticate the client using the fake certificate
await
sslStream
.
AuthenticateAsServerAsync
(
certificate
,
false
,
await
sslStream
.
AuthenticateAsServerAsync
(
certificate
,
false
,
SslProtocols
.
Tls
,
false
)
.
ConfigureAwait
(
false
)
;
SslProtocols
.
Tls
,
false
);
clientStreamReader
=
new
CustomBinaryReader
(
sslStream
);
clientStreamReader
=
new
CustomBinaryReader
(
sslStream
);
clientStreamWriter
=
new
StreamWriter
(
sslStream
);
clientStreamWriter
=
new
StreamWriter
(
sslStream
);
...
@@ -167,19 +182,29 @@ namespace Titanium.Web.Proxy
...
@@ -167,19 +182,29 @@ namespace Titanium.Web.Proxy
clientStreamReader
=
new
CustomBinaryReader
(
clientStream
);
clientStreamReader
=
new
CustomBinaryReader
(
clientStream
);
}
}
var
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
()
.
ConfigureAwait
(
false
)
;
var
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
//Now create the request
//Now create the request
await
HandleHttpSessionRequest
(
tcpClient
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
await
HandleHttpSessionRequest
(
tcpClient
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
true
)
.
ConfigureAwait
(
false
)
;
true
);
}
}
/// <summary>
/// This is the core request handler method for a particular connection from client
/// </summary>
/// <param name="client"></param>
/// <param name="httpCmd"></param>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="isHttps"></param>
/// <returns></returns>
private
static
async
Task
HandleHttpSessionRequest
(
TcpClient
client
,
string
httpCmd
,
Stream
clientStream
,
private
static
async
Task
HandleHttpSessionRequest
(
TcpClient
client
,
string
httpCmd
,
Stream
clientStream
,
CustomBinaryReader
clientStreamReader
,
StreamWriter
clientStreamWriter
,
bool
I
sHttps
)
CustomBinaryReader
clientStreamReader
,
StreamWriter
clientStreamWriter
,
bool
i
sHttps
)
{
{
TcpConnection
connection
=
null
;
TcpConnection
connection
=
null
;
string
lastRequestHostName
=
null
;
//Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client)
while
(
true
)
while
(
true
)
{
{
if
(
string
.
IsNullOrEmpty
(
httpCmd
))
if
(
string
.
IsNullOrEmpty
(
httpCmd
))
...
@@ -189,58 +214,59 @@ namespace Titanium.Web.Proxy
...
@@ -189,58 +214,59 @@ namespace Titanium.Web.Proxy
}
}
var
args
=
new
SessionEventArgs
();
var
args
=
new
SessionEventArgs
();
args
.
Client
.
TcpClient
=
client
;
args
.
Proxy
Client
.
TcpClient
=
client
;
try
try
{
{
//break up the line into three components (method, remote URL & Http Version)
//break up the line into three components (method, remote URL & Http Version)
var
httpCmdSplit
=
httpCmd
.
Split
(
Constants
.
SpaceSplit
,
3
);
var
httpCmdSplit
=
httpCmd
.
Split
(
Proxy
Constants
.
SpaceSplit
,
3
);
var
httpMethod
=
httpCmdSplit
[
0
];
var
httpMethod
=
httpCmdSplit
[
0
];
var
httpVersion
=
httpCmdSplit
[
2
];
Version
version
;
//find the request HTTP version
if
(
httpVersion
==
"HTTP/1.1"
)
Version
version
=
new
Version
(
1
,
1
);
if
(
httpCmdSplit
.
Length
==
3
)
{
{
v
ersion
=
new
Version
(
1
,
1
);
v
ar
httpVersion
=
httpCmdSplit
[
2
].
ToLower
().
Trim
(
);
}
else
if
(
httpVersion
==
"http/1.0"
)
{
{
version
=
new
Version
(
1
,
0
);
version
=
new
Version
(
1
,
0
);
}
}
}
args
.
WebSession
.
Request
.
RequestHeaders
=
new
List
<
HttpHeader
>();
args
.
WebSession
.
Request
.
RequestHeaders
=
new
List
<
HttpHeader
>();
//Read the request headers
string
tmpLine
;
string
tmpLine
;
while
(!
string
.
IsNullOrEmpty
(
tmpLine
=
await
clientStreamReader
.
ReadLineAsync
()
.
ConfigureAwait
(
false
)
))
while
(!
string
.
IsNullOrEmpty
(
tmpLine
=
await
clientStreamReader
.
ReadLineAsync
()))
{
{
var
header
=
tmpLine
.
Split
(
new
char
[]
{
':'
}
,
2
);
var
header
=
tmpLine
.
Split
(
ProxyConstants
.
ColonSplit
,
2
);
args
.
WebSession
.
Request
.
RequestHeaders
.
Add
(
new
HttpHeader
(
header
[
0
],
header
[
1
]));
args
.
WebSession
.
Request
.
RequestHeaders
.
Add
(
new
HttpHeader
(
header
[
0
],
header
[
1
]));
}
}
var
httpRemoteUri
=
new
Uri
(!
IsHttps
?
httpCmdSplit
[
1
]
:
(
string
.
Concat
(
"https://"
,
args
.
WebSession
.
Request
.
Host
,
httpCmdSplit
[
1
])));
var
httpRemoteUri
=
new
Uri
(!
isHttps
?
httpCmdSplit
[
1
]
:
(
string
.
Concat
(
"https://"
,
args
.
WebSession
.
Request
.
Host
,
httpCmdSplit
[
1
])));
args
.
IsHttps
=
IsHttps
;
#if DEBUG
//Just ignore local requests while Debugging
//Its annoying
if
(
httpRemoteUri
.
Host
.
Contains
(
"localhost"
))
{
Dispose
(
client
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
null
);
break
;
}
#endif
args
.
WebSession
.
Request
.
RequestUri
=
httpRemoteUri
;
args
.
WebSession
.
Request
.
RequestUri
=
httpRemoteUri
;
args
.
WebSession
.
Request
.
Method
=
httpMethod
;
args
.
WebSession
.
Request
.
Method
=
httpMethod
;
args
.
WebSession
.
Request
.
HttpVersion
=
httpVersion
;
args
.
WebSession
.
Request
.
HttpVersion
=
version
;
args
.
Client
.
ClientStream
=
clientStream
;
args
.
ProxyClient
.
ClientStream
=
clientStream
;
args
.
Client
.
ClientStreamReader
=
clientStreamReader
;
args
.
ProxyClient
.
ClientStreamReader
=
clientStreamReader
;
args
.
Client
.
ClientStreamWriter
=
clientStreamWriter
;
args
.
ProxyClient
.
ClientStreamWriter
=
clientStreamWriter
;
if
(
args
.
WebSession
.
Request
.
UpgradeToWebSocket
)
{
await
TcpHelper
.
SendRaw
(
clientStream
,
httpCmd
,
args
.
WebSession
.
Request
.
RequestHeaders
,
httpRemoteUri
.
Host
,
httpRemoteUri
.
Port
,
args
.
IsHttps
).
ConfigureAwait
(
false
);
Dispose
(
client
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
args
);
return
;
}
PrepareRequestHeaders
(
args
.
WebSession
.
Request
.
RequestHeaders
,
args
.
WebSession
);
PrepareRequestHeaders
(
args
.
WebSession
.
Request
.
RequestHeaders
,
args
.
WebSession
);
args
.
WebSession
.
Request
.
Host
=
args
.
WebSession
.
Request
.
RequestUri
.
Host
;
args
.
WebSession
.
Request
.
Host
=
args
.
WebSession
.
Request
.
RequestUri
.
Authority
;
//If
requested interception
//If
user requested interception do it
if
(
BeforeRequest
!=
null
)
if
(
BeforeRequest
!=
null
)
{
{
Delegate
[]
invocationList
=
BeforeRequest
.
GetInvocationList
();
Delegate
[]
invocationList
=
BeforeRequest
.
GetInvocationList
();
...
@@ -251,55 +277,64 @@ namespace Titanium.Web.Proxy
...
@@ -251,55 +277,64 @@ namespace Titanium.Web.Proxy
handlerTasks
[
i
]
=
((
Func
<
object
,
SessionEventArgs
,
Task
>)
invocationList
[
i
])(
null
,
args
);
handlerTasks
[
i
]
=
((
Func
<
object
,
SessionEventArgs
,
Task
>)
invocationList
[
i
])(
null
,
args
);
}
}
await
Task
.
WhenAll
(
handlerTasks
)
.
ConfigureAwait
(
false
)
;
await
Task
.
WhenAll
(
handlerTasks
);
}
}
//construct the web request that we are going to issue on behalf of the client.
//if upgrading to websocket then relay the requet without reading the contents
connection
=
connection
==
null
?
if
(
args
.
WebSession
.
Request
.
UpgradeToWebSocket
)
await
TcpConnectionManager
.
GetClient
(
args
,
args
.
WebSession
.
Request
.
RequestUri
.
Host
,
args
.
WebSession
.
Request
.
RequestUri
.
Port
,
args
.
IsHttps
,
version
).
ConfigureAwait
(
false
)
{
:
lastRequestHostName
!=
args
.
WebSession
.
Request
.
RequestUri
.
Host
?
await
TcpConnectionManager
.
GetClient
(
args
,
args
.
WebSession
.
Request
.
RequestUri
.
Host
,
args
.
WebSession
.
Request
.
RequestUri
.
Port
,
args
.
IsHttps
,
version
).
ConfigureAwait
(
false
)
await
TcpHelper
.
SendRaw
(
clientStream
,
httpCmd
,
args
.
WebSession
.
Request
.
RequestHeaders
,
:
connection
;
httpRemoteUri
.
Host
,
httpRemoteUri
.
Port
,
args
.
IsHttps
);
Dispose
(
client
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
args
);
return
;
}
lastRequestHostName
=
args
.
WebSession
.
Request
.
RequestUri
.
Host
;
//construct the web request that we are going to issue on behalf of the client.
connection
=
await
TcpConnectionManager
.
GetClient
(
args
.
WebSession
.
Request
.
RequestUri
.
Host
,
args
.
WebSession
.
Request
.
RequestUri
.
Port
,
args
.
IsHttps
,
version
);
args
.
WebSession
.
Request
.
RequestLocked
=
true
;
args
.
WebSession
.
Request
.
RequestLocked
=
true
;
//If request was cancelled by user then dispose the client
if
(
args
.
WebSession
.
Request
.
CancelRequest
)
if
(
args
.
WebSession
.
Request
.
CancelRequest
)
{
{
Dispose
(
client
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
args
);
Dispose
(
client
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
args
);
break
;
break
;
}
}
//if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue
if
(
args
.
WebSession
.
Request
.
ExpectContinue
)
if
(
args
.
WebSession
.
Request
.
ExpectContinue
)
{
{
args
.
WebSession
.
SetConnection
(
connection
);
args
.
WebSession
.
SetConnection
(
connection
);
await
args
.
WebSession
.
SendRequest
()
.
ConfigureAwait
(
false
)
;
await
args
.
WebSession
.
SendRequest
();
}
}
//If 100 continue was the response inform that to the client
if
(
Enable100ContinueBehaviour
)
if
(
Enable100ContinueBehaviour
)
if
(
args
.
WebSession
.
Request
.
Is100Continue
)
if
(
args
.
WebSession
.
Request
.
Is100Continue
)
{
{
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"100"
,
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"100"
,
"Continue"
,
args
.
Client
.
ClientStreamWriter
);
"Continue"
,
args
.
Proxy
Client
.
ClientStreamWriter
);
await
args
.
Client
.
ClientStreamWriter
.
WriteLineAsync
();
await
args
.
Proxy
Client
.
ClientStreamWriter
.
WriteLineAsync
();
}
}
else
if
(
args
.
WebSession
.
Request
.
ExpectationFailed
)
else
if
(
args
.
WebSession
.
Request
.
ExpectationFailed
)
{
{
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"417"
,
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"417"
,
"Expectation Failed"
,
args
.
Client
.
ClientStreamWriter
);
"Expectation Failed"
,
args
.
Proxy
Client
.
ClientStreamWriter
);
await
args
.
Client
.
ClientStreamWriter
.
WriteLineAsync
();
await
args
.
Proxy
Client
.
ClientStreamWriter
.
WriteLineAsync
();
}
}
//If expect continue is not enabled then set the connectio and send request headers
if
(!
args
.
WebSession
.
Request
.
ExpectContinue
)
if
(!
args
.
WebSession
.
Request
.
ExpectContinue
)
{
{
args
.
WebSession
.
SetConnection
(
connection
);
args
.
WebSession
.
SetConnection
(
connection
);
await
args
.
WebSession
.
SendRequest
()
.
ConfigureAwait
(
false
)
;
await
args
.
WebSession
.
SendRequest
();
}
}
//If request was modified by user
//If request was modified by user
if
(
args
.
WebSession
.
Request
.
RequestBodyRead
)
if
(
args
.
WebSession
.
Request
.
RequestBodyRead
)
{
{
if
(
args
.
WebSession
.
Request
.
ContentEncoding
!=
null
)
if
(
args
.
WebSession
.
Request
.
ContentEncoding
!=
null
)
{
{
args
.
WebSession
.
Request
.
RequestBody
=
await
GetCompressedResponseBody
(
args
.
WebSession
.
Request
.
ContentEncoding
,
args
.
WebSession
.
Request
.
RequestBody
);
args
.
WebSession
.
Request
.
RequestBody
=
await
GetCompressedResponseBody
(
args
.
WebSession
.
Request
.
ContentEncoding
,
args
.
WebSession
.
Request
.
RequestBody
);
}
}
...
@@ -307,7 +342,7 @@ namespace Titanium.Web.Proxy
...
@@ -307,7 +342,7 @@ namespace Titanium.Web.Proxy
args
.
WebSession
.
Request
.
ContentLength
=
args
.
WebSession
.
Request
.
RequestBody
.
Length
;
args
.
WebSession
.
Request
.
ContentLength
=
args
.
WebSession
.
Request
.
RequestBody
.
Length
;
var
newStream
=
args
.
WebSession
.
ServerConnection
.
Stream
;
var
newStream
=
args
.
WebSession
.
ServerConnection
.
Stream
;
await
newStream
.
WriteAsync
(
args
.
WebSession
.
Request
.
RequestBody
,
0
,
args
.
WebSession
.
Request
.
RequestBody
.
Length
)
.
ConfigureAwait
(
false
)
;
await
newStream
.
WriteAsync
(
args
.
WebSession
.
Request
.
RequestBody
,
0
,
args
.
WebSession
.
Request
.
RequestBody
.
Length
);
}
}
else
else
{
{
...
@@ -316,26 +351,29 @@ namespace Titanium.Web.Proxy
...
@@ -316,26 +351,29 @@ namespace Titanium.Web.Proxy
//If its a post/put request, then read the client html body and send it to server
//If its a post/put request, then read the client html body and send it to server
if
(
httpMethod
.
ToUpper
()
==
"POST"
||
httpMethod
.
ToUpper
()
==
"PUT"
)
if
(
httpMethod
.
ToUpper
()
==
"POST"
||
httpMethod
.
ToUpper
()
==
"PUT"
)
{
{
await
SendClientRequestBody
(
args
)
.
ConfigureAwait
(
false
)
;
await
SendClientRequestBody
(
args
);
}
}
}
}
}
}
//If not expectation failed response was returned by server then parse response
if
(!
args
.
WebSession
.
Request
.
ExpectationFailed
)
if
(!
args
.
WebSession
.
Request
.
ExpectationFailed
)
{
{
await
HandleHttpSessionResponse
(
args
)
.
ConfigureAwait
(
false
)
;
await
HandleHttpSessionResponse
(
args
);
}
}
//if connection is closing exit
//if connection is closing exit
if
(
args
.
WebSession
.
Response
.
ResponseKeepAlive
==
false
)
if
(
args
.
WebSession
.
Response
.
ResponseKeepAlive
==
false
)
{
{
connection
.
TcpClient
.
Close
();
Dispose
(
client
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
args
);
Dispose
(
client
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
args
);
return
;
return
;
}
}
//send the tcp connection to server back to connection cache for reuse
await
TcpConnectionManager
.
ReleaseClient
(
connection
);
// read the next request
// read the next request
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
()
.
ConfigureAwait
(
false
)
;
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
}
}
catch
catch
...
@@ -346,19 +384,28 @@ namespace Titanium.Web.Proxy
...
@@ -346,19 +384,28 @@ namespace Titanium.Web.Proxy
}
}
if
(
connection
!=
null
)
TcpConnectionManager
.
ReleaseClient
(
connection
);
}
}
private
static
async
Task
WriteConnectResponse
(
StreamWriter
clientStreamWriter
,
string
httpVersion
)
/// <summary>
/// Write successfull CONNECT response to client
/// </summary>
/// <param name="clientStreamWriter"></param>
/// <param name="httpVersion"></param>
/// <returns></returns>
private
static
async
Task
WriteConnectResponse
(
StreamWriter
clientStreamWriter
,
Version
httpVersion
)
{
{
await
clientStreamWriter
.
WriteLineAsync
(
httpVersion
+
" 200 Connection established"
).
ConfigureAwait
(
false
);
await
clientStreamWriter
.
WriteLineAsync
(
string
.
Format
(
"HTTP/{0}.{1} {2}"
,
httpVersion
.
Major
,
httpVersion
.
Minor
,
"200 Connection established"
)
);
await
clientStreamWriter
.
WriteLineAsync
(
string
.
Format
(
"Timestamp: {0}"
,
DateTime
.
Now
))
.
ConfigureAwait
(
false
)
;
await
clientStreamWriter
.
WriteLineAsync
(
string
.
Format
(
"Timestamp: {0}"
,
DateTime
.
Now
));
await
clientStreamWriter
.
WriteLineAsync
()
.
ConfigureAwait
(
false
)
;
await
clientStreamWriter
.
WriteLineAsync
();
await
clientStreamWriter
.
FlushAsync
()
.
ConfigureAwait
(
false
)
;
await
clientStreamWriter
.
FlushAsync
();
}
}
private
static
void
PrepareRequestHeaders
(
List
<
HttpHeader
>
requestHeaders
,
HttpWebSession
webRequest
)
/// <summary>
/// prepare the request headers so that we can avoid encodings not parsable by this proxy
/// </summary>
/// <param name="requestHeaders"></param>
/// <param name="webRequest"></param>
private
static
void
PrepareRequestHeaders
(
List
<
HttpHeader
>
requestHeaders
,
HttpWebClient
webRequest
)
{
{
for
(
var
i
=
0
;
i
<
requestHeaders
.
Count
;
i
++)
for
(
var
i
=
0
;
i
<
requestHeaders
.
Count
;
i
++)
{
{
...
@@ -376,6 +423,11 @@ namespace Titanium.Web.Proxy
...
@@ -376,6 +423,11 @@ namespace Titanium.Web.Proxy
FixRequestProxyHeaders
(
requestHeaders
);
FixRequestProxyHeaders
(
requestHeaders
);
webRequest
.
Request
.
RequestHeaders
=
requestHeaders
;
webRequest
.
Request
.
RequestHeaders
=
requestHeaders
;
}
}
/// <summary>
/// Fix proxy specific headers
/// </summary>
/// <param name="headers"></param>
private
static
void
FixRequestProxyHeaders
(
List
<
HttpHeader
>
headers
)
private
static
void
FixRequestProxyHeaders
(
List
<
HttpHeader
>
headers
)
{
{
//If proxy-connection close was returned inform to close the connection
//If proxy-connection close was returned inform to close the connection
...
@@ -394,7 +446,12 @@ namespace Titanium.Web.Proxy
...
@@ -394,7 +446,12 @@ namespace Titanium.Web.Proxy
headers
.
RemoveAll
(
x
=>
x
.
Name
.
ToLower
()
==
"proxy-connection"
);
headers
.
RemoveAll
(
x
=>
x
.
Name
.
ToLower
()
==
"proxy-connection"
);
}
}
//This is called when the request is PUT/POST to read the body
/// <summary>
/// This is called when the request is PUT/POST to read the body
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private
static
async
Task
SendClientRequestBody
(
SessionEventArgs
args
)
private
static
async
Task
SendClientRequestBody
(
SessionEventArgs
args
)
{
{
// End the operation
// End the operation
...
@@ -404,7 +461,7 @@ namespace Titanium.Web.Proxy
...
@@ -404,7 +461,7 @@ namespace Titanium.Web.Proxy
{
{
try
try
{
{
await
args
.
Client
.
ClientStreamReader
.
CopyBytesToStream
(
postStream
,
args
.
WebSession
.
Request
.
ContentLength
).
ConfigureAwait
(
false
);
await
args
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStream
(
postStream
,
args
.
WebSession
.
Request
.
ContentLength
);
}
}
catch
catch
{
{
...
@@ -416,7 +473,7 @@ namespace Titanium.Web.Proxy
...
@@ -416,7 +473,7 @@ namespace Titanium.Web.Proxy
{
{
try
try
{
{
await
args
.
Client
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
postStream
).
ConfigureAwait
(
false
);
await
args
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
postStream
);
}
}
catch
catch
{
{
...
@@ -425,58 +482,5 @@ namespace Titanium.Web.Proxy
...
@@ -425,58 +482,5 @@ namespace Titanium.Web.Proxy
}
}
}
}
/// <summary>
/// Call back to override server certificate validation
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
internal
static
bool
ValidateServerCertificate
(
object
sender
,
X509Certificate
certificate
,
X509Chain
chain
,
SslPolicyErrors
sslPolicyErrors
)
{
var
param
=
sender
as
CustomSslStream
;
if
(
ServerCertificateValidationCallback
!=
null
)
{
var
args
=
new
CertificateValidationEventArgs
();
args
.
Session
=
param
.
Session
;
args
.
Certificate
=
certificate
;
args
.
Chain
=
chain
;
args
.
SslPolicyErrors
=
sslPolicyErrors
;
Delegate
[]
invocationList
=
ServerCertificateValidationCallback
.
GetInvocationList
();
Task
[]
handlerTasks
=
new
Task
[
invocationList
.
Length
];
for
(
int
i
=
0
;
i
<
invocationList
.
Length
;
i
++)
{
handlerTasks
[
i
]
=
((
Func
<
object
,
CertificateValidationEventArgs
,
Task
>)
invocationList
[
i
])(
null
,
args
);
}
Task
.
WhenAll
(
handlerTasks
).
Wait
();
if
(!
args
.
IsValid
)
{
param
.
Session
.
WebSession
.
Request
.
CancelRequest
=
true
;
}
return
args
.
IsValid
;
}
if
(
sslPolicyErrors
==
SslPolicyErrors
.
None
)
return
true
;
//By default
//do not allow this client to communicate with unauthenticated servers.
return
false
;
}
}
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/ResponseHandler.cs
View file @
82326b01
using
System
;
using
System
;
using
System.Collections.Generic
;
using
System.Collections.Generic
;
using
System.Globalization
;
using
System.IO
;
using
System.IO
;
using
System.Linq
;
using
System.Linq
;
using
System.Net.Sockets
;
using
System.Net.Sockets
;
using
System.Text
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Compression
;
using
Titanium.Web.Proxy.Compression
;
using
Titanium.Web.Proxy.Shared
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Extensions
;
namespace
Titanium.Web.Proxy
namespace
Titanium.Web.Proxy
{
{
/// <summary>
/// Handle the response from server
/// </summary>
partial
class
ProxyServer
partial
class
ProxyServer
{
{
//Called asynchronously when a request was successfully and we received the response
//Called asynchronously when a request was successfully and we received the response
public
static
async
Task
HandleHttpSessionResponse
(
SessionEventArgs
args
)
public
static
async
Task
HandleHttpSessionResponse
(
SessionEventArgs
args
)
{
{
await
args
.
WebSession
.
ReceiveResponse
().
ConfigureAwait
(
false
);
//read response & headers from server
await
args
.
WebSession
.
ReceiveResponse
();
try
try
{
{
if
(!
args
.
WebSession
.
Response
.
ResponseBodyRead
)
if
(!
args
.
WebSession
.
Response
.
ResponseBodyRead
)
args
.
WebSession
.
Response
.
ResponseStream
=
args
.
WebSession
.
ServerConnection
.
Stream
;
args
.
WebSession
.
Response
.
ResponseStream
=
args
.
WebSession
.
ServerConnection
.
Stream
;
//If user requested call back then do it
if
(
BeforeResponse
!=
null
&&
!
args
.
WebSession
.
Response
.
ResponseLocked
)
if
(
BeforeResponse
!=
null
&&
!
args
.
WebSession
.
Response
.
ResponseLocked
)
{
{
Delegate
[]
invocationList
=
BeforeResponse
.
GetInvocationList
();
Delegate
[]
invocationList
=
BeforeResponse
.
GetInvocationList
();
...
@@ -37,26 +38,28 @@ namespace Titanium.Web.Proxy
...
@@ -37,26 +38,28 @@ namespace Titanium.Web.Proxy
handlerTasks
[
i
]
=
((
Func
<
object
,
SessionEventArgs
,
Task
>)
invocationList
[
i
])(
null
,
args
);
handlerTasks
[
i
]
=
((
Func
<
object
,
SessionEventArgs
,
Task
>)
invocationList
[
i
])(
null
,
args
);
}
}
await
Task
.
WhenAll
(
handlerTasks
)
.
ConfigureAwait
(
false
)
;
await
Task
.
WhenAll
(
handlerTasks
);
}
}
args
.
WebSession
.
Response
.
ResponseLocked
=
true
;
args
.
WebSession
.
Response
.
ResponseLocked
=
true
;
//Write back to client 100-conitinue response if that's what server returned
if
(
args
.
WebSession
.
Response
.
Is100Continue
)
if
(
args
.
WebSession
.
Response
.
Is100Continue
)
{
{
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"100"
,
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"100"
,
"Continue"
,
args
.
Client
.
ClientStreamWriter
);
"Continue"
,
args
.
Proxy
Client
.
ClientStreamWriter
);
await
args
.
Client
.
ClientStreamWriter
.
WriteLineAsync
();
await
args
.
Proxy
Client
.
ClientStreamWriter
.
WriteLineAsync
();
}
}
else
if
(
args
.
WebSession
.
Response
.
ExpectationFailed
)
else
if
(
args
.
WebSession
.
Response
.
ExpectationFailed
)
{
{
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"417"
,
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"417"
,
"Expectation Failed"
,
args
.
Client
.
ClientStreamWriter
);
"Expectation Failed"
,
args
.
Proxy
Client
.
ClientStreamWriter
);
await
args
.
Client
.
ClientStreamWriter
.
WriteLineAsync
();
await
args
.
Proxy
Client
.
ClientStreamWriter
.
WriteLineAsync
();
}
}
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
args
.
WebSession
.
Response
.
ResponseStatusCode
,
//Write back response status to client
args
.
WebSession
.
Response
.
ResponseStatusDescription
,
args
.
Client
.
ClientStreamWriter
);
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
args
.
WebSession
.
Response
.
ResponseStatusCode
,
args
.
WebSession
.
Response
.
ResponseStatusDescription
,
args
.
ProxyClient
.
ClientStreamWriter
);
if
(
args
.
WebSession
.
Response
.
ResponseBodyRead
)
if
(
args
.
WebSession
.
Response
.
ResponseBodyRead
)
{
{
...
@@ -65,7 +68,7 @@ namespace Titanium.Web.Proxy
...
@@ -65,7 +68,7 @@ namespace Titanium.Web.Proxy
if
(
contentEncoding
!=
null
)
if
(
contentEncoding
!=
null
)
{
{
args
.
WebSession
.
Response
.
ResponseBody
=
await
GetCompressedResponseBody
(
contentEncoding
,
args
.
WebSession
.
Response
.
ResponseBody
)
.
ConfigureAwait
(
false
)
;
args
.
WebSession
.
Response
.
ResponseBody
=
await
GetCompressedResponseBody
(
contentEncoding
,
args
.
WebSession
.
Response
.
ResponseBody
);
if
(
isChunked
==
false
)
if
(
isChunked
==
false
)
args
.
WebSession
.
Response
.
ContentLength
=
args
.
WebSession
.
Response
.
ResponseBody
.
Length
;
args
.
WebSession
.
Response
.
ContentLength
=
args
.
WebSession
.
Response
.
ResponseBody
.
Length
;
...
@@ -73,23 +76,24 @@ namespace Titanium.Web.Proxy
...
@@ -73,23 +76,24 @@ namespace Titanium.Web.Proxy
args
.
WebSession
.
Response
.
ContentLength
=
-
1
;
args
.
WebSession
.
Response
.
ContentLength
=
-
1
;
}
}
await
WriteResponseHeaders
(
args
.
Client
.
ClientStreamWriter
,
args
.
WebSession
.
Response
.
ResponseHeaders
).
ConfigureAwait
(
false
);
await
WriteResponseHeaders
(
args
.
ProxyClient
.
ClientStreamWriter
,
args
.
WebSession
.
Response
.
ResponseHeaders
);
await
WriteResponseBody
(
args
.
Client
.
ClientStream
,
args
.
WebSession
.
Response
.
ResponseBody
,
isChunked
).
ConfigureAwait
(
false
);
await
args
.
ProxyClient
.
ClientStream
.
WriteResponseBody
(
args
.
WebSession
.
Response
.
ResponseBody
,
isChunked
);
}
}
else
else
{
{
await
WriteResponseHeaders
(
args
.
Client
.
ClientStreamWriter
,
args
.
WebSession
.
Response
.
ResponseHeaders
);
await
WriteResponseHeaders
(
args
.
Proxy
Client
.
ClientStreamWriter
,
args
.
WebSession
.
Response
.
ResponseHeaders
);
if
(
args
.
WebSession
.
Response
.
IsChunked
||
args
.
WebSession
.
Response
.
ContentLength
>
0
||
args
.
WebSession
.
Response
.
HttpVersion
.
ToLower
().
Trim
()
==
"http/1.0"
)
if
(
args
.
WebSession
.
Response
.
IsChunked
||
args
.
WebSession
.
Response
.
ContentLength
>
0
||
await
WriteResponseBody
(
args
.
WebSession
.
ServerConnection
.
StreamReader
,
args
.
Client
.
ClientStream
,
args
.
WebSession
.
Response
.
IsChunked
,
args
.
WebSession
.
Response
.
ContentLength
).
ConfigureAwait
(
false
);
(
args
.
WebSession
.
Response
.
HttpVersion
.
Major
==
1
&&
args
.
WebSession
.
Response
.
HttpVersion
.
Minor
==
0
))
await
args
.
WebSession
.
ServerConnection
.
StreamReader
.
WriteResponseBody
(
args
.
ProxyClient
.
ClientStream
,
args
.
WebSession
.
Response
.
IsChunked
,
args
.
WebSession
.
Response
.
ContentLength
);
}
}
await
args
.
Client
.
ClientStream
.
FlushAsync
();
await
args
.
Proxy
Client
.
ClientStream
.
FlushAsync
();
}
}
catch
catch
{
{
Dispose
(
args
.
Client
.
TcpClient
,
args
.
Client
.
ClientStream
,
args
.
Client
.
ClientStreamReader
,
args
.
Client
.
ClientStreamWriter
,
args
);
Dispose
(
args
.
ProxyClient
.
TcpClient
,
args
.
ProxyClient
.
ClientStream
,
args
.
ProxyClient
.
ClientStreamReader
,
args
.
Proxy
Client
.
ClientStreamWriter
,
args
);
}
}
finally
finally
{
{
...
@@ -97,20 +101,39 @@ namespace Titanium.Web.Proxy
...
@@ -97,20 +101,39 @@ namespace Titanium.Web.Proxy
}
}
}
}
/// <summary>
/// get the compressed response body from give response bytes
/// </summary>
/// <param name="encodingType"></param>
/// <param name="responseBodyStream"></param>
/// <returns></returns>
private
static
async
Task
<
byte
[
]>
GetCompressedResponseBody
(
string
encodingType
,
byte
[]
responseBodyStream
)
private
static
async
Task
<
byte
[
]>
GetCompressedResponseBody
(
string
encodingType
,
byte
[]
responseBodyStream
)
{
{
var
compressionFactory
=
new
CompressionFactory
();
var
compressionFactory
=
new
CompressionFactory
();
var
compressor
=
compressionFactory
.
Create
(
encodingType
);
var
compressor
=
compressionFactory
.
Create
(
encodingType
);
return
await
compressor
.
Compress
(
responseBodyStream
).
ConfigureAwait
(
false
);
return
await
compressor
.
Compress
(
responseBodyStream
);
}
}
/// <summary>
private
static
void
WriteResponseStatus
(
string
version
,
string
code
,
string
description
,
/// Write response status
/// </summary>
/// <param name="version"></param>
/// <param name="code"></param>
/// <param name="description"></param>
/// <param name="responseWriter"></param>
/// <returns></returns>
private
static
async
Task
WriteResponseStatus
(
Version
version
,
string
code
,
string
description
,
StreamWriter
responseWriter
)
StreamWriter
responseWriter
)
{
{
responseWriter
.
WriteLineAsync
(
string
.
Format
(
"{0} {1} {2}"
,
version
,
code
,
description
));
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
static
async
Task
WriteResponseHeaders
(
StreamWriter
responseWriter
,
List
<
HttpHeader
>
headers
)
private
static
async
Task
WriteResponseHeaders
(
StreamWriter
responseWriter
,
List
<
HttpHeader
>
headers
)
{
{
if
(
headers
!=
null
)
if
(
headers
!=
null
)
...
@@ -126,6 +149,11 @@ namespace Titanium.Web.Proxy
...
@@ -126,6 +149,11 @@ namespace Titanium.Web.Proxy
await
responseWriter
.
WriteLineAsync
();
await
responseWriter
.
WriteLineAsync
();
await
responseWriter
.
FlushAsync
();
await
responseWriter
.
FlushAsync
();
}
}
/// <summary>
/// Fix the proxy specific headers before sending response headers to client
/// </summary>
/// <param name="headers"></param>
private
static
void
FixResponseProxyHeaders
(
List
<
HttpHeader
>
headers
)
private
static
void
FixResponseProxyHeaders
(
List
<
HttpHeader
>
headers
)
{
{
//If proxy-connection close was returned inform to close the connection
//If proxy-connection close was returned inform to close the connection
...
@@ -139,101 +167,20 @@ namespace Titanium.Web.Proxy
...
@@ -139,101 +167,20 @@ namespace Titanium.Web.Proxy
}
}
else
else
{
{
connectionHeader
.
Value
=
"close"
;
connectionHeader
.
Value
=
proxyHeader
.
Value
;
}
}
headers
.
RemoveAll
(
x
=>
x
.
Name
.
ToLower
()
==
"proxy-connection"
);
headers
.
RemoveAll
(
x
=>
x
.
Name
.
ToLower
()
==
"proxy-connection"
);
}
}
private
static
async
Task
WriteResponseBody
(
Stream
clientStream
,
byte
[]
data
,
bool
isChunked
)
/// <summary>
{
/// Handle dispose of a client/server session
if
(!
isChunked
)
/// </summary>
{
/// <param name="client"></param>
await
clientStream
.
WriteAsync
(
data
,
0
,
data
.
Length
).
ConfigureAwait
(
false
);
/// <param name="clientStream"></param>
}
/// <param name="clientStreamReader"></param>
else
/// <param name="clientStreamWriter"></param>
await
WriteResponseBodyChunked
(
data
,
clientStream
).
ConfigureAwait
(
false
);
/// <param name="args"></param>
}
private
static
async
Task
WriteResponseBody
(
CustomBinaryReader
inStreamReader
,
Stream
outStream
,
bool
isChunked
,
long
ContentLength
)
{
if
(!
isChunked
)
{
//http 1.0
if
(
ContentLength
==
-
1
)
ContentLength
=
long
.
MaxValue
;
int
bytesToRead
=
Constants
.
BUFFER_SIZE
;
if
(
ContentLength
<
Constants
.
BUFFER_SIZE
)
bytesToRead
=
(
int
)
ContentLength
;
var
buffer
=
new
byte
[
Constants
.
BUFFER_SIZE
];
var
bytesRead
=
0
;
var
totalBytesRead
=
0
;
while
((
bytesRead
+=
await
inStreamReader
.
BaseStream
.
ReadAsync
(
buffer
,
0
,
bytesToRead
).
ConfigureAwait
(
false
))
>
0
)
{
await
outStream
.
WriteAsync
(
buffer
,
0
,
bytesRead
).
ConfigureAwait
(
false
);
totalBytesRead
+=
bytesRead
;
if
(
totalBytesRead
==
ContentLength
)
break
;
bytesRead
=
0
;
var
remainingBytes
=
(
ContentLength
-
totalBytesRead
);
bytesToRead
=
remainingBytes
>
(
long
)
Constants
.
BUFFER_SIZE
?
Constants
.
BUFFER_SIZE
:
(
int
)
remainingBytes
;
}
}
else
await
WriteResponseBodyChunked
(
inStreamReader
,
outStream
).
ConfigureAwait
(
false
);
}
//Send chunked response
private
static
async
Task
WriteResponseBodyChunked
(
CustomBinaryReader
inStreamReader
,
Stream
outStream
)
{
while
(
true
)
{
var
chunkHead
=
await
inStreamReader
.
ReadLineAsync
().
ConfigureAwait
(
false
);
var
chunkSize
=
int
.
Parse
(
chunkHead
,
NumberStyles
.
HexNumber
);
if
(
chunkSize
!=
0
)
{
var
buffer
=
await
inStreamReader
.
ReadBytesAsync
(
chunkSize
).
ConfigureAwait
(
false
);
var
chunkHeadBytes
=
Encoding
.
ASCII
.
GetBytes
(
chunkSize
.
ToString
(
"x2"
));
await
outStream
.
WriteAsync
(
chunkHeadBytes
,
0
,
chunkHeadBytes
.
Length
).
ConfigureAwait
(
false
);
await
outStream
.
WriteAsync
(
Constants
.
NewLineBytes
,
0
,
Constants
.
NewLineBytes
.
Length
).
ConfigureAwait
(
false
);
await
outStream
.
WriteAsync
(
buffer
,
0
,
chunkSize
).
ConfigureAwait
(
false
);
await
outStream
.
WriteAsync
(
Constants
.
NewLineBytes
,
0
,
Constants
.
NewLineBytes
.
Length
).
ConfigureAwait
(
false
);
await
inStreamReader
.
ReadLineAsync
().
ConfigureAwait
(
false
);
}
else
{
await
inStreamReader
.
ReadLineAsync
().
ConfigureAwait
(
false
);
await
outStream
.
WriteAsync
(
Constants
.
ChunkEnd
,
0
,
Constants
.
ChunkEnd
.
Length
).
ConfigureAwait
(
false
);
break
;
}
}
}
private
static
async
Task
WriteResponseBodyChunked
(
byte
[]
data
,
Stream
outStream
)
{
var
chunkHead
=
Encoding
.
ASCII
.
GetBytes
(
data
.
Length
.
ToString
(
"x2"
));
await
outStream
.
WriteAsync
(
chunkHead
,
0
,
chunkHead
.
Length
).
ConfigureAwait
(
false
);
await
outStream
.
WriteAsync
(
Constants
.
NewLineBytes
,
0
,
Constants
.
NewLineBytes
.
Length
).
ConfigureAwait
(
false
);
await
outStream
.
WriteAsync
(
data
,
0
,
data
.
Length
).
ConfigureAwait
(
false
);
await
outStream
.
WriteAsync
(
Constants
.
NewLineBytes
,
0
,
Constants
.
NewLineBytes
.
Length
).
ConfigureAwait
(
false
);
await
outStream
.
WriteAsync
(
Constants
.
ChunkEnd
,
0
,
Constants
.
ChunkEnd
.
Length
).
ConfigureAwait
(
false
);
}
private
static
void
Dispose
(
TcpClient
client
,
IDisposable
clientStream
,
IDisposable
clientStreamReader
,
private
static
void
Dispose
(
TcpClient
client
,
IDisposable
clientStream
,
IDisposable
clientStreamReader
,
IDisposable
clientStreamWriter
,
IDisposable
args
)
IDisposable
clientStreamWriter
,
IDisposable
args
)
{
{
...
...
Titanium.Web.Proxy/Shared/Constants.cs
→
Titanium.Web.Proxy/Shared/
Proxy
Constants.cs
View file @
82326b01
using
System
;
using
System
;
using
System.Security.Authentication
;
using
System.Security.Authentication
;
using
System.Text
;
using
System.Text
;
using
System.Text.RegularExpressions
;
namespace
Titanium.Web.Proxy.Shared
namespace
Titanium.Web.Proxy.Shared
{
{
/// <summary>
/// <summary>
/// Literals shared by Proxy Server
/// Literals shared by Proxy Server
/// </summary>
/// </summary>
internal
class
Constants
internal
class
Proxy
Constants
{
{
public
static
readonly
int
BUFFER_SIZE
=
8192
;
internal
static
readonly
char
[]
SpaceSplit
=
{
' '
};
internal
static
readonly
char
[]
SpaceSplit
=
{
' '
};
internal
static
readonly
char
[]
ColonSplit
=
{
':'
};
internal
static
readonly
char
[]
ColonSplit
=
{
':'
};
internal
static
readonly
char
[]
SemiColonSplit
=
{
';'
};
internal
static
readonly
char
[]
SemiColonSplit
=
{
';'
};
...
@@ -21,6 +18,8 @@ namespace Titanium.Web.Proxy.Shared
...
@@ -21,6 +18,8 @@ namespace Titanium.Web.Proxy.Shared
internal
static
readonly
byte
[]
ChunkEnd
=
internal
static
readonly
byte
[]
ChunkEnd
=
Encoding
.
ASCII
.
GetBytes
(
0.
ToString
(
"x2"
)
+
Environment
.
NewLine
+
Environment
.
NewLine
);
Encoding
.
ASCII
.
GetBytes
(
0.
ToString
(
"x2"
)
+
Environment
.
NewLine
+
Environment
.
NewLine
);
internal
static
SslProtocols
SupportedProtocols
=
SslProtocols
.
Tls
|
SslProtocols
.
Tls11
|
SslProtocols
.
Tls12
|
SslProtocols
.
Ssl3
;
public
static
SslProtocols
SupportedSslProtocols
=
SslProtocols
.
Tls
|
SslProtocols
.
Tls11
|
SslProtocols
.
Tls12
|
SslProtocols
.
Ssl3
;
public
static
readonly
int
BUFFER_SIZE
=
8192
;
}
}
}
}
Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
View file @
82326b01
...
@@ -49,6 +49,7 @@
...
@@ -49,6 +49,7 @@
<Reference
Include=
"System.Xml"
/>
<Reference
Include=
"System.Xml"
/>
</ItemGroup>
</ItemGroup>
<ItemGroup>
<ItemGroup>
<Compile
Include=
"CertificateHandler.cs"
/>
<Compile
Include=
"Compression\CompressionFactory.cs"
/>
<Compile
Include=
"Compression\CompressionFactory.cs"
/>
<Compile
Include=
"Compression\DeflateCompression.cs"
/>
<Compile
Include=
"Compression\DeflateCompression.cs"
/>
<Compile
Include=
"Compression\GZipCompression.cs"
/>
<Compile
Include=
"Compression\GZipCompression.cs"
/>
...
@@ -60,12 +61,15 @@
...
@@ -60,12 +61,15 @@
<Compile
Include=
"Decompression\GZipDecompression.cs"
/>
<Compile
Include=
"Decompression\GZipDecompression.cs"
/>
<Compile
Include=
"Decompression\IDecompression.cs"
/>
<Compile
Include=
"Decompression\IDecompression.cs"
/>
<Compile
Include=
"Decompression\ZlibDecompression.cs"
/>
<Compile
Include=
"Decompression\ZlibDecompression.cs"
/>
<Compile
Include=
"EventArguments\CertificateSelectionEventArgs.cs"
/>
<Compile
Include=
"EventArguments\CertificateValidationEventArgs.cs"
/>
<Compile
Include=
"EventArguments\CertificateValidationEventArgs.cs"
/>
<Compile
Include=
"Extensions\ByteArrayExtensions.cs"
/>
<Compile
Include=
"Network\CachedCertificate.cs"
/>
<Compile
Include=
"Network\ProxyClient.cs"
/>
<Compile
Include=
"Network\ProxyClient.cs"
/>
<Compile
Include=
"Exceptions\BodyNotFoundException.cs"
/>
<Compile
Include=
"Exceptions\BodyNotFoundException.cs"
/>
<Compile
Include=
"Extensions\HttpWebResponseExtensions.cs"
/>
<Compile
Include=
"Extensions\HttpWebResponseExtensions.cs"
/>
<Compile
Include=
"Extensions\HttpWebRequestExtensions.cs"
/>
<Compile
Include=
"Extensions\HttpWebRequestExtensions.cs"
/>
<Compile
Include=
"
Helpers
\CertificateManager.cs"
/>
<Compile
Include=
"
Network
\CertificateManager.cs"
/>
<Compile
Include=
"Helpers\Firefox.cs"
/>
<Compile
Include=
"Helpers\Firefox.cs"
/>
<Compile
Include=
"Helpers\SystemProxy.cs"
/>
<Compile
Include=
"Helpers\SystemProxy.cs"
/>
<Compile
Include=
"Models\EndPoint.cs"
/>
<Compile
Include=
"Models\EndPoint.cs"
/>
...
@@ -73,7 +77,7 @@
...
@@ -73,7 +77,7 @@
<Compile
Include=
"Http\Request.cs"
/>
<Compile
Include=
"Http\Request.cs"
/>
<Compile
Include=
"Http\Response.cs"
/>
<Compile
Include=
"Http\Response.cs"
/>
<Compile
Include=
"Models\ExternalProxy.cs"
/>
<Compile
Include=
"Models\ExternalProxy.cs"
/>
<Compile
Include=
"Network\
CustomSslStream
.cs"
/>
<Compile
Include=
"Network\
TcpConnection
.cs"
/>
<Compile
Include=
"Network\TcpConnectionManager.cs"
/>
<Compile
Include=
"Network\TcpConnectionManager.cs"
/>
<Compile
Include=
"Models\HttpHeader.cs"
/>
<Compile
Include=
"Models\HttpHeader.cs"
/>
<Compile
Include=
"Http\HttpWebClient.cs"
/>
<Compile
Include=
"Http\HttpWebClient.cs"
/>
...
@@ -87,7 +91,7 @@
...
@@ -87,7 +91,7 @@
<Compile
Include=
"Extensions\StreamExtensions.cs"
/>
<Compile
Include=
"Extensions\StreamExtensions.cs"
/>
<Compile
Include=
"Http\Responses\OkResponse.cs"
/>
<Compile
Include=
"Http\Responses\OkResponse.cs"
/>
<Compile
Include=
"Http\Responses\RedirectResponse.cs"
/>
<Compile
Include=
"Http\Responses\RedirectResponse.cs"
/>
<Compile
Include=
"Shared\Constants.cs"
/>
<Compile
Include=
"Shared\
Proxy
Constants.cs"
/>
</ItemGroup>
</ItemGroup>
<ItemGroup
/>
<ItemGroup
/>
<ItemGroup>
<ItemGroup>
...
...
appveyor.yml
View file @
82326b01
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
# - Section names should be unique on each level.
# - Section names should be unique on each level.
# version format
# version format
version
:
2.
2
{build}
version
:
2.
3000.
{build}
shallow_clone
:
true
shallow_clone
:
true
...
...
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