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
1acc1ad8
Commit
1acc1ad8
authored
Oct 28, 2016
by
Jehonathan
Committed by
GitHub
Oct 28, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #138 from aricih/release
ProcessId is fixed on HttpWebClient
parents
13bd6bc0
78aa6639
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1096 additions
and
1014 deletions
+1096
-1014
SessionEventArgs.cs
Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
+447
-448
Tcp.cs
Titanium.Web.Proxy/Helpers/Tcp.cs
+194
-188
HttpWebClient.cs
Titanium.Web.Proxy/Http/HttpWebClient.cs
+221
-238
EndPoint.cs
Titanium.Web.Proxy/Models/EndPoint.cs
+68
-64
ExternalProxy.cs
Titanium.Web.Proxy/Models/ExternalProxy.cs
+48
-13
TcpConnection.cs
Titanium.Web.Proxy/Network/TcpConnection.cs
+1
-1
TcpConnectionFactory.cs
Titanium.Web.Proxy/Network/TcpConnectionFactory.cs
+4
-7
ProxyServer.cs
Titanium.Web.Proxy/ProxyServer.cs
+34
-1
RequestHandler.cs
Titanium.Web.Proxy/RequestHandler.cs
+36
-20
TcpRow.cs
Titanium.Web.Proxy/Tcp/TcpRow.cs
+43
-34
No files found.
Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
View file @
1acc1ad8
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Text
;
using
Titanium.Web.Proxy.Exceptions
;
using
Titanium.Web.Proxy.Decompression
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http.Responses
;
using
Titanium.Web.Proxy.Extensions
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Network
;
using
System.Net
;
using
Titanium.Web.Proxy.Models
;
namespace
Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// Holds info related to a single proxy session (single request/response sequence)
/// A proxy session is bounded to a single connection from client
/// A proxy session ends when client terminates connection to proxy
/// or when server terminates connection from proxy
/// </summary>
public
class
SessionEventArgs
:
EventArgs
,
IDisposable
{
/// <summary>
/// Size of Buffers used by this object
/// </summary>
private
readonly
int
bufferSize
;
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private
readonly
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
;
/// <summary>
/// Holds a reference to client
/// </summary>
internal
ProxyClient
ProxyClient
{
get
;
set
;
}
//Should we send a rerequest
public
bool
ReRequest
{
get
;
set
;
}
/// <summary>
/// Does this session uses SSL
/// </summary>
public
bool
IsHttps
=>
WebSession
.
Request
.
RequestUri
.
Scheme
==
Uri
.
UriSchemeHttps
;
public
IPEndPoint
ClientEndPoint
=>
(
IPEndPoint
)
ProxyClient
.
TcpClient
.
Client
.
RemoteEndPoint
;
/// <summary>
/// A web session corresponding to a single request/response sequence
/// within a proxy connection
/// </summary>
public
HttpWebClient
WebSession
{
get
;
set
;
}
public
ExternalProxy
CustomUpStreamHttpProxyUsed
{
get
;
set
;
}
public
ExternalProxy
CustomUpStreamHttpsProxyUsed
{
get
;
set
;
}
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal
SessionEventArgs
(
int
bufferSize
,
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
)
{
this
.
bufferSize
=
bufferSize
;
this
.
httpResponseHandler
=
httpResponseHandler
;
ProxyClient
=
new
ProxyClient
();
WebSession
=
new
HttpWebClient
();
}
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
private
async
Task
ReadRequestBody
()
{
//GET request don't have a request body to read
if
((
WebSession
.
Request
.
Method
.
ToUpper
()
!=
"POST"
&&
WebSession
.
Request
.
Method
.
ToUpper
()
!=
"PUT"
))
{
throw
new
BodyNotFoundException
(
"Request don't have a body."
+
"Please verify that this request is a Http POST/PUT and request "
+
"content length is greater than zero before accessing the body."
);
}
//Caching check
if
(
WebSession
.
Request
.
RequestBody
==
null
)
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header
using
(
var
requestBodyStream
=
new
MemoryStream
())
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if
(
WebSession
.
Request
.
IsChunked
)
{
await
this
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
bufferSize
,
requestBodyStream
);
}
else
{
//If not chunked then its easy just read the whole body with the content length mentioned in the request header
if
(
WebSession
.
Request
.
ContentLength
>
0
)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await
this
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStream
(
bufferSize
,
requestBodyStream
,
WebSession
.
Request
.
ContentLength
);
}
else
if
(
WebSession
.
Request
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Request
.
HttpVersion
.
Minor
==
0
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
requestBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Request
.
RequestBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Request
.
ContentEncoding
,
requestBodyStream
.
ToArray
());
}
//Now set the flag to true
//So that next time we can deliver body from cache
WebSession
.
Request
.
RequestBodyRead
=
true
;
}
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
private
async
Task
ReadResponseBody
()
{
//If not already read (not cached yet)
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
using
(
var
responseBodyStream
=
new
MemoryStream
())
{
//If chuncked the read chunk by chunk until we hit chunk end symbol
if
(
WebSession
.
Response
.
IsChunked
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStreamChunked
(
bufferSize
,
responseBodyStream
);
}
else
{
if
(
WebSession
.
Response
.
ContentLength
>
0
)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
responseBodyStream
,
WebSession
.
Response
.
ContentLength
);
}
else
if
((
WebSession
.
Response
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Response
.
HttpVersion
.
Minor
==
0
)
||
WebSession
.
Response
.
ContentLength
==
-
1
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
responseBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Response
.
ResponseBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Response
.
ContentEncoding
,
responseBodyStream
.
ToArray
());
}
//set this to true for caching
WebSession
.
Response
.
ResponseBodyRead
=
true
;
}
}
/// <summary>
/// Gets the request body as bytes
/// </summary>
/// <returns></returns>
public
async
Task
<
byte
[
]>
GetRequestBody
()
{
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
await
ReadRequestBody
();
}
return
WebSession
.
Request
.
RequestBody
;
}
/// <summary>
/// Gets the request body as string
/// </summary>
/// <returns></returns>
public
async
Task
<
string
>
GetRequestBodyAsString
()
{
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
await
ReadRequestBody
();
}
//Use the encoding specified in request to decode the byte[] data to string
return
WebSession
.
Request
.
RequestBodyString
??
(
WebSession
.
Request
.
RequestBodyString
=
WebSession
.
Request
.
Encoding
.
GetString
(
WebSession
.
Request
.
RequestBody
));
}
/// <summary>
/// Sets the request body
/// </summary>
/// <param name="body"></param>
public
async
Task
SetRequestBody
(
byte
[]
body
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
//syphon out the request body from client before setting the new body
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
await
ReadRequestBody
();
}
WebSession
.
Request
.
RequestBody
=
body
;
if
(
WebSession
.
Request
.
IsChunked
==
false
)
{
WebSession
.
Request
.
ContentLength
=
body
.
Length
;
}
else
{
WebSession
.
Request
.
ContentLength
=
-
1
;
}
}
/// <summary>
/// Sets the body with the specified string
/// </summary>
/// <param name="body"></param>
public
async
Task
SetRequestBodyString
(
string
body
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
//syphon out the request body from client before setting the new body
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
await
ReadRequestBody
();
}
await
SetRequestBody
(
WebSession
.
Request
.
Encoding
.
GetBytes
(
body
));
}
/// <summary>
/// Gets the response body as byte array
/// </summary>
/// <returns></returns>
public
async
Task
<
byte
[
]>
GetResponseBody
()
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
await
ReadResponseBody
();
return
WebSession
.
Response
.
ResponseBody
;
}
/// <summary>
/// Gets the response body as string
/// </summary>
/// <returns></returns>
public
async
Task
<
string
>
GetResponseBodyAsString
()
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
await
GetResponseBody
();
return
WebSession
.
Response
.
ResponseBodyString
??
(
WebSession
.
Response
.
ResponseBodyString
=
WebSession
.
Response
.
Encoding
.
GetString
(
WebSession
.
Response
.
ResponseBody
));
}
/// <summary>
/// Set the response body bytes
/// </summary>
/// <param name="body"></param>
public
async
Task
SetResponseBody
(
byte
[]
body
)
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
//syphon out the response body from server before setting the new body
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
await
GetResponseBody
();
}
WebSession
.
Response
.
ResponseBody
=
body
;
//If there is a content length header update it
if
(
WebSession
.
Response
.
IsChunked
==
false
)
{
WebSession
.
Response
.
ContentLength
=
body
.
Length
;
}
else
{
WebSession
.
Response
.
ContentLength
=
-
1
;
}
}
/// <summary>
/// Replace the response body with the specified string
/// </summary>
/// <param name="body"></param>
public
async
Task
SetResponseBodyString
(
string
body
)
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
//syphon out the response body from server before setting the new body
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
await
GetResponseBody
();
}
var
bodyBytes
=
WebSession
.
Response
.
Encoding
.
GetBytes
(
body
);
await
SetResponseBody
(
bodyBytes
);
}
private
async
Task
<
byte
[
]>
GetDecompressedResponseBody
(
string
encodingType
,
byte
[]
responseBodyStream
)
{
var
decompressionFactory
=
new
DecompressionFactory
();
var
decompressor
=
decompressionFactory
.
Create
(
encodingType
);
return
await
decompressor
.
Decompress
(
responseBodyStream
,
bufferSize
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
public
async
Task
Ok
(
string
html
)
{
await
Ok
(
html
,
null
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
public
async
Task
Ok
(
string
html
,
Dictionary
<
string
,
HttpHeader
>
headers
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
if
(
html
==
null
)
{
html
=
string
.
Empty
;
}
var
result
=
Encoding
.
Default
.
GetBytes
(
html
);
await
Ok
(
result
,
headers
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
public
async
Task
Ok
(
byte
[]
result
)
{
await
Ok
(
result
,
null
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
/// <param name="headers"></param>
public
async
Task
Ok
(
byte
[]
result
,
Dictionary
<
string
,
HttpHeader
>
headers
)
{
var
response
=
new
OkResponse
();
if
(
headers
!=
null
&&
headers
.
Count
>
0
)
{
response
.
ResponseHeaders
=
headers
;
}
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
ResponseBody
=
result
;
await
Respond
(
response
);
WebSession
.
Request
.
CancelRequest
=
true
;
}
public
async
Task
Redirect
(
string
url
)
{
var
response
=
new
RedirectResponse
();
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
ResponseHeaders
.
Add
(
"Location"
,
new
Models
.
HttpHeader
(
"Location"
,
url
));
response
.
ResponseBody
=
Encoding
.
ASCII
.
GetBytes
(
string
.
Empty
);
await
Respond
(
response
);
WebSession
.
Request
.
CancelRequest
=
true
;
}
/// a generic responder method
public
async
Task
Respond
(
Response
response
)
{
WebSession
.
Request
.
RequestLocked
=
true
;
response
.
ResponseLocked
=
true
;
response
.
ResponseBodyRead
=
true
;
WebSession
.
Response
=
response
;
await
httpResponseHandler
(
this
);
}
/// <summary>
/// implement any cleanup here
/// </summary>
public
void
Dispose
()
{
}
}
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Text
;
using
Titanium.Web.Proxy.Exceptions
;
using
Titanium.Web.Proxy.Decompression
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http.Responses
;
using
Titanium.Web.Proxy.Extensions
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Network
;
using
System.Net
;
using
Titanium.Web.Proxy.Models
;
namespace
Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// Holds info related to a single proxy session (single request/response sequence)
/// A proxy session is bounded to a single connection from client
/// A proxy session ends when client terminates connection to proxy
/// or when server terminates connection from proxy
/// </summary>
public
class
SessionEventArgs
:
EventArgs
,
IDisposable
{
/// <summary>
/// Size of Buffers used by this object
/// </summary>
private
readonly
int
bufferSize
;
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private
readonly
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
;
/// <summary>
/// Holds a reference to client
/// </summary>
internal
ProxyClient
ProxyClient
{
get
;
set
;
}
//Should we send a rerequest
public
bool
ReRequest
{
get
;
set
;
}
/// <summary>
/// Does this session uses SSL
/// </summary>
public
bool
IsHttps
=>
WebSession
.
Request
.
RequestUri
.
Scheme
==
Uri
.
UriSchemeHttps
;
public
IPEndPoint
ClientEndPoint
=>
(
IPEndPoint
)
ProxyClient
.
TcpClient
.
Client
.
RemoteEndPoint
;
/// <summary>
/// A web session corresponding to a single request/response sequence
/// within a proxy connection
/// </summary>
public
HttpWebClient
WebSession
{
get
;
set
;
}
public
ExternalProxy
CustomUpStreamHttpProxyUsed
{
get
;
set
;
}
public
ExternalProxy
CustomUpStreamHttpsProxyUsed
{
get
;
set
;
}
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal
SessionEventArgs
(
int
bufferSize
,
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
)
{
this
.
bufferSize
=
bufferSize
;
this
.
httpResponseHandler
=
httpResponseHandler
;
ProxyClient
=
new
ProxyClient
();
WebSession
=
new
HttpWebClient
();
}
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
private
async
Task
ReadRequestBody
()
{
//GET request don't have a request body to read
if
((
WebSession
.
Request
.
Method
.
ToUpper
()
!=
"POST"
&&
WebSession
.
Request
.
Method
.
ToUpper
()
!=
"PUT"
))
{
throw
new
BodyNotFoundException
(
"Request don't have a body."
+
"Please verify that this request is a Http POST/PUT and request "
+
"content length is greater than zero before accessing the body."
);
}
//Caching check
if
(
WebSession
.
Request
.
RequestBody
==
null
)
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header
using
(
var
requestBodyStream
=
new
MemoryStream
())
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if
(
WebSession
.
Request
.
IsChunked
)
{
await
this
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
bufferSize
,
requestBodyStream
);
}
else
{
//If not chunked then its easy just read the whole body with the content length mentioned in the request header
if
(
WebSession
.
Request
.
ContentLength
>
0
)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await
this
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStream
(
bufferSize
,
requestBodyStream
,
WebSession
.
Request
.
ContentLength
);
}
else
if
(
WebSession
.
Request
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Request
.
HttpVersion
.
Minor
==
0
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
requestBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Request
.
RequestBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Request
.
ContentEncoding
,
requestBodyStream
.
ToArray
());
}
//Now set the flag to true
//So that next time we can deliver body from cache
WebSession
.
Request
.
RequestBodyRead
=
true
;
}
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
private
async
Task
ReadResponseBody
()
{
//If not already read (not cached yet)
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
using
(
var
responseBodyStream
=
new
MemoryStream
())
{
//If chuncked the read chunk by chunk until we hit chunk end symbol
if
(
WebSession
.
Response
.
IsChunked
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStreamChunked
(
bufferSize
,
responseBodyStream
);
}
else
{
if
(
WebSession
.
Response
.
ContentLength
>
0
)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
responseBodyStream
,
WebSession
.
Response
.
ContentLength
);
}
else
if
((
WebSession
.
Response
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Response
.
HttpVersion
.
Minor
==
0
)
||
WebSession
.
Response
.
ContentLength
==
-
1
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
responseBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Response
.
ResponseBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Response
.
ContentEncoding
,
responseBodyStream
.
ToArray
());
}
//set this to true for caching
WebSession
.
Response
.
ResponseBodyRead
=
true
;
}
}
/// <summary>
/// Gets the request body as bytes
/// </summary>
/// <returns></returns>
public
async
Task
<
byte
[
]>
GetRequestBody
()
{
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
await
ReadRequestBody
();
}
return
WebSession
.
Request
.
RequestBody
;
}
/// <summary>
/// Gets the request body as string
/// </summary>
/// <returns></returns>
public
async
Task
<
string
>
GetRequestBodyAsString
()
{
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
await
ReadRequestBody
();
}
//Use the encoding specified in request to decode the byte[] data to string
return
WebSession
.
Request
.
RequestBodyString
??
(
WebSession
.
Request
.
RequestBodyString
=
WebSession
.
Request
.
Encoding
.
GetString
(
WebSession
.
Request
.
RequestBody
));
}
/// <summary>
/// Sets the request body
/// </summary>
/// <param name="body"></param>
public
async
Task
SetRequestBody
(
byte
[]
body
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
//syphon out the request body from client before setting the new body
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
await
ReadRequestBody
();
}
WebSession
.
Request
.
RequestBody
=
body
;
if
(
WebSession
.
Request
.
IsChunked
==
false
)
{
WebSession
.
Request
.
ContentLength
=
body
.
Length
;
}
else
{
WebSession
.
Request
.
ContentLength
=
-
1
;
}
}
/// <summary>
/// Sets the body with the specified string
/// </summary>
/// <param name="body"></param>
public
async
Task
SetRequestBodyString
(
string
body
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
//syphon out the request body from client before setting the new body
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
await
ReadRequestBody
();
}
await
SetRequestBody
(
WebSession
.
Request
.
Encoding
.
GetBytes
(
body
));
}
/// <summary>
/// Gets the response body as byte array
/// </summary>
/// <returns></returns>
public
async
Task
<
byte
[
]>
GetResponseBody
()
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
await
ReadResponseBody
();
return
WebSession
.
Response
.
ResponseBody
;
}
/// <summary>
/// Gets the response body as string
/// </summary>
/// <returns></returns>
public
async
Task
<
string
>
GetResponseBodyAsString
()
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
await
GetResponseBody
();
return
WebSession
.
Response
.
ResponseBodyString
??
(
WebSession
.
Response
.
ResponseBodyString
=
WebSession
.
Response
.
Encoding
.
GetString
(
WebSession
.
Response
.
ResponseBody
));
}
/// <summary>
/// Set the response body bytes
/// </summary>
/// <param name="body"></param>
public
async
Task
SetResponseBody
(
byte
[]
body
)
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
//syphon out the response body from server before setting the new body
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
await
GetResponseBody
();
}
WebSession
.
Response
.
ResponseBody
=
body
;
//If there is a content length header update it
if
(
WebSession
.
Response
.
IsChunked
==
false
)
{
WebSession
.
Response
.
ContentLength
=
body
.
Length
;
}
else
{
WebSession
.
Response
.
ContentLength
=
-
1
;
}
}
/// <summary>
/// Replace the response body with the specified string
/// </summary>
/// <param name="body"></param>
public
async
Task
SetResponseBodyString
(
string
body
)
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
//syphon out the response body from server before setting the new body
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
await
GetResponseBody
();
}
var
bodyBytes
=
WebSession
.
Response
.
Encoding
.
GetBytes
(
body
);
await
SetResponseBody
(
bodyBytes
);
}
private
async
Task
<
byte
[
]>
GetDecompressedResponseBody
(
string
encodingType
,
byte
[]
responseBodyStream
)
{
var
decompressionFactory
=
new
DecompressionFactory
();
var
decompressor
=
decompressionFactory
.
Create
(
encodingType
);
return
await
decompressor
.
Decompress
(
responseBodyStream
,
bufferSize
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
public
async
Task
Ok
(
string
html
)
{
await
Ok
(
html
,
null
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
public
async
Task
Ok
(
string
html
,
Dictionary
<
string
,
HttpHeader
>
headers
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
if
(
html
==
null
)
{
html
=
string
.
Empty
;
}
var
result
=
Encoding
.
Default
.
GetBytes
(
html
);
await
Ok
(
result
,
headers
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
public
async
Task
Ok
(
byte
[]
result
)
{
await
Ok
(
result
,
null
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
/// <param name="headers"></param>
public
async
Task
Ok
(
byte
[]
result
,
Dictionary
<
string
,
HttpHeader
>
headers
)
{
var
response
=
new
OkResponse
();
if
(
headers
!=
null
&&
headers
.
Count
>
0
)
{
response
.
ResponseHeaders
=
headers
;
}
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
ResponseBody
=
result
;
await
Respond
(
response
);
WebSession
.
Request
.
CancelRequest
=
true
;
}
public
async
Task
Redirect
(
string
url
)
{
var
response
=
new
RedirectResponse
();
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
ResponseHeaders
.
Add
(
"Location"
,
new
Models
.
HttpHeader
(
"Location"
,
url
));
response
.
ResponseBody
=
Encoding
.
ASCII
.
GetBytes
(
string
.
Empty
);
await
Respond
(
response
);
WebSession
.
Request
.
CancelRequest
=
true
;
}
/// a generic responder method
public
async
Task
Respond
(
Response
response
)
{
WebSession
.
Request
.
RequestLocked
=
true
;
response
.
ResponseLocked
=
true
;
response
.
ResponseBodyRead
=
true
;
WebSession
.
Response
=
response
;
await
httpResponseHandler
(
this
);
}
/// <summary>
/// implement any cleanup here
/// </summary>
public
void
Dispose
()
{
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Helpers/Tcp.cs
View file @
1acc1ad8
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Net.NetworkInformation
;
using
System.Net.Security
;
using
System.Runtime.InteropServices
;
using
System.Security.Authentication
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network
;
using
Titanium.Web.Proxy.Tcp
;
namespace
Titanium.Web.Proxy.Helpers
{
internal
partial
class
NativeMethods
{
internal
const
int
AfInet
=
2
;
internal
enum
TcpTableType
{
BasicListener
,
BasicConnections
,
BasicAll
,
OwnerPidListener
,
OwnerPidConnections
,
OwnerPidAll
,
OwnerModuleListener
,
OwnerModuleConnections
,
OwnerModuleAll
,
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
[
StructLayout
(
LayoutKind
.
Sequential
)]
internal
struct
TcpTable
{
public
uint
length
;
public
TcpRow
row
;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
[
StructLayout
(
LayoutKind
.
Sequential
)]
internal
struct
TcpRow
{
public
TcpState
state
;
public
uint
localAddr
;
public
byte
localPort1
;
public
byte
localPort2
;
public
byte
localPort3
;
public
byte
localPort4
;
public
uint
remoteAddr
;
public
byte
remotePort1
;
public
byte
remotePort2
;
public
byte
remotePort3
;
public
byte
remotePort4
;
public
int
owningPid
;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[
DllImport
(
"iphlpapi.dll"
,
SetLastError
=
true
)]
internal
static
extern
uint
GetExtendedTcpTable
(
IntPtr
tcpTable
,
ref
int
size
,
bool
sort
,
int
ipVersion
,
int
tableClass
,
int
reserved
);
}
internal
class
TcpHelper
{
/// <summary>
/// Gets the extended TCP table.
/// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns>
internal
static
TcpTable
GetExtendedTcpTable
()
{
List
<
TcpRow
>
tcpRows
=
new
List
<
TcpRow
>();
IntPtr
tcpTable
=
IntPtr
.
Zero
;
int
tcpTableLength
=
0
;
if
(
NativeMethods
.
GetExtendedTcpTable
(
tcpTable
,
ref
tcpTableLength
,
false
,
NativeMethods
.
AfInet
,
(
int
)
NativeMethods
.
TcpTableType
.
OwnerPidAll
,
0
)
!=
0
)
{
try
{
tcpTable
=
Marshal
.
AllocHGlobal
(
tcpTableLength
);
if
(
NativeMethods
.
GetExtendedTcpTable
(
tcpTable
,
ref
tcpTableLength
,
true
,
NativeMethods
.
AfInet
,
(
int
)
NativeMethods
.
TcpTableType
.
OwnerPidAll
,
0
)
==
0
)
{
NativeMethods
.
TcpTable
table
=
(
NativeMethods
.
TcpTable
)
Marshal
.
PtrToStructure
(
tcpTable
,
typeof
(
NativeMethods
.
TcpTable
));
IntPtr
rowPtr
=
(
IntPtr
)((
long
)
tcpTable
+
Marshal
.
SizeOf
(
table
.
length
));
for
(
int
i
=
0
;
i
<
table
.
length
;
++
i
)
{
tcpRows
.
Add
(
new
TcpRow
((
NativeMethods
.
TcpRow
)
Marshal
.
PtrToStructure
(
rowPtr
,
typeof
(
NativeMethods
.
TcpRow
))));
rowPtr
=
(
IntPtr
)((
long
)
rowPtr
+
Marshal
.
SizeOf
(
typeof
(
NativeMethods
.
TcpRow
)));
}
}
}
finally
{
if
(
tcpTable
!=
IntPtr
.
Zero
)
{
Marshal
.
FreeHGlobal
(
tcpTable
);
}
}
}
return
new
TcpTable
(
tcpRows
);
}
/// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <returns></returns>
internal
static
async
Task
SendRaw
(
int
bufferSize
,
int
connectionTimeOutSeconds
,
string
remoteHostName
,
int
remotePort
,
string
httpCmd
,
Version
httpVersion
,
Dictionary
<
string
,
HttpHeader
>
requestHeaders
,
bool
isHttps
,
SslProtocols
supportedProtocols
,
RemoteCertificateValidationCallback
remoteCertificateValidationCallback
,
LocalCertificateSelectionCallback
localCertificateSelectionCallback
,
Stream
clientStream
,
TcpConnectionFactory
tcpConnectionFactory
)
{
//prepare the prefix content
StringBuilder
sb
=
null
;
if
(
httpCmd
!=
null
||
requestHeaders
!=
null
)
{
sb
=
new
StringBuilder
();
if
(
httpCmd
!=
null
)
{
sb
.
Append
(
httpCmd
);
sb
.
Append
(
Environment
.
NewLine
);
}
if
(
requestHeaders
!=
null
)
{
foreach
(
var
header
in
requestHeaders
.
Select
(
t
=>
t
.
Value
.
ToString
()))
{
sb
.
Append
(
header
);
sb
.
Append
(
Environment
.
NewLine
);
}
}
sb
.
Append
(
Environment
.
NewLine
);
}
var
tcpConnection
=
await
tcpConnectionFactory
.
CreateClient
(
bufferSize
,
connectionTimeOutSeconds
,
remoteHostName
,
remotePort
,
httpVersion
,
isHttps
,
supportedProtocols
,
remoteCertificateValidationCallback
,
localCertificateSelectionCallback
,
null
,
null
,
clientStream
);
try
{
Stream
tunnelStream
=
tcpConnection
.
Stream
;
Task
sendRelay
;
//Now async relay all server=>client & client=>server data
sendRelay
=
clientStream
.
CopyToAsync
(
sb
?.
ToString
()
??
string
.
Empty
,
tunnelStream
);
var
receiveRelay
=
tunnelStream
.
CopyToAsync
(
string
.
Empty
,
clientStream
);
await
Task
.
WhenAll
(
sendRelay
,
receiveRelay
);
}
finally
{
tcpConnection
.
Dispose
();
}
}
}
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Net.NetworkInformation
;
using
System.Net.Security
;
using
System.Runtime.InteropServices
;
using
System.Security.Authentication
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network
;
using
Titanium.Web.Proxy.Tcp
;
namespace
Titanium.Web.Proxy.Helpers
{
internal
enum
IpVersion
{
Ipv4
=
1
,
Ipv6
=
2
,
}
internal
partial
class
NativeMethods
{
internal
const
int
AfInet
=
2
;
internal
const
int
AfInet6
=
23
;
internal
enum
TcpTableType
{
BasicListener
,
BasicConnections
,
BasicAll
,
OwnerPidListener
,
OwnerPidConnections
,
OwnerPidAll
,
OwnerModuleListener
,
OwnerModuleConnections
,
OwnerModuleAll
,
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
[
StructLayout
(
LayoutKind
.
Sequential
)]
internal
struct
TcpTable
{
public
uint
length
;
public
TcpRow
row
;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
[
StructLayout
(
LayoutKind
.
Sequential
)]
internal
struct
TcpRow
{
public
TcpState
state
;
public
uint
localAddr
;
public
byte
localPort1
;
public
byte
localPort2
;
public
byte
localPort3
;
public
byte
localPort4
;
public
uint
remoteAddr
;
public
byte
remotePort1
;
public
byte
remotePort2
;
public
byte
remotePort3
;
public
byte
remotePort4
;
public
int
owningPid
;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[
DllImport
(
"iphlpapi.dll"
,
SetLastError
=
true
)]
internal
static
extern
uint
GetExtendedTcpTable
(
IntPtr
tcpTable
,
ref
int
size
,
bool
sort
,
int
ipVersion
,
int
tableClass
,
int
reserved
);
}
internal
class
TcpHelper
{
/// <summary>
/// Gets the extended TCP table.
/// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns>
internal
static
TcpTable
GetExtendedTcpTable
(
IpVersion
ipVersion
)
{
List
<
TcpRow
>
tcpRows
=
new
List
<
TcpRow
>();
IntPtr
tcpTable
=
IntPtr
.
Zero
;
int
tcpTableLength
=
0
;
var
ipVersionValue
=
ipVersion
==
IpVersion
.
Ipv4
?
NativeMethods
.
AfInet
:
NativeMethods
.
AfInet6
;
if
(
NativeMethods
.
GetExtendedTcpTable
(
tcpTable
,
ref
tcpTableLength
,
false
,
ipVersionValue
,
(
int
)
NativeMethods
.
TcpTableType
.
OwnerPidAll
,
0
)
!=
0
)
{
try
{
tcpTable
=
Marshal
.
AllocHGlobal
(
tcpTableLength
);
if
(
NativeMethods
.
GetExtendedTcpTable
(
tcpTable
,
ref
tcpTableLength
,
true
,
ipVersionValue
,
(
int
)
NativeMethods
.
TcpTableType
.
OwnerPidAll
,
0
)
==
0
)
{
NativeMethods
.
TcpTable
table
=
(
NativeMethods
.
TcpTable
)
Marshal
.
PtrToStructure
(
tcpTable
,
typeof
(
NativeMethods
.
TcpTable
));
IntPtr
rowPtr
=
(
IntPtr
)((
long
)
tcpTable
+
Marshal
.
SizeOf
(
table
.
length
));
for
(
int
i
=
0
;
i
<
table
.
length
;
++
i
)
{
tcpRows
.
Add
(
new
TcpRow
((
NativeMethods
.
TcpRow
)
Marshal
.
PtrToStructure
(
rowPtr
,
typeof
(
NativeMethods
.
TcpRow
))));
rowPtr
=
(
IntPtr
)((
long
)
rowPtr
+
Marshal
.
SizeOf
(
typeof
(
NativeMethods
.
TcpRow
)));
}
}
}
finally
{
if
(
tcpTable
!=
IntPtr
.
Zero
)
{
Marshal
.
FreeHGlobal
(
tcpTable
);
}
}
}
return
new
TcpTable
(
tcpRows
);
}
/// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <returns></returns>
internal
static
async
Task
SendRaw
(
int
bufferSize
,
int
connectionTimeOutSeconds
,
string
remoteHostName
,
int
remotePort
,
string
httpCmd
,
Version
httpVersion
,
Dictionary
<
string
,
HttpHeader
>
requestHeaders
,
bool
isHttps
,
SslProtocols
supportedProtocols
,
RemoteCertificateValidationCallback
remoteCertificateValidationCallback
,
LocalCertificateSelectionCallback
localCertificateSelectionCallback
,
Stream
clientStream
,
TcpConnectionFactory
tcpConnectionFactory
)
{
//prepare the prefix content
StringBuilder
sb
=
null
;
if
(
httpCmd
!=
null
||
requestHeaders
!=
null
)
{
sb
=
new
StringBuilder
();
if
(
httpCmd
!=
null
)
{
sb
.
Append
(
httpCmd
);
sb
.
Append
(
Environment
.
NewLine
);
}
if
(
requestHeaders
!=
null
)
{
foreach
(
var
header
in
requestHeaders
.
Select
(
t
=>
t
.
Value
.
ToString
()))
{
sb
.
Append
(
header
);
sb
.
Append
(
Environment
.
NewLine
);
}
}
sb
.
Append
(
Environment
.
NewLine
);
}
var
tcpConnection
=
await
tcpConnectionFactory
.
CreateClient
(
bufferSize
,
connectionTimeOutSeconds
,
remoteHostName
,
remotePort
,
httpVersion
,
isHttps
,
supportedProtocols
,
remoteCertificateValidationCallback
,
localCertificateSelectionCallback
,
null
,
null
,
clientStream
);
try
{
Stream
tunnelStream
=
tcpConnection
.
Stream
;
//Now async relay all server=>client & client=>server data
var
sendRelay
=
clientStream
.
CopyToAsync
(
sb
?.
ToString
()
??
string
.
Empty
,
tunnelStream
);
var
receiveRelay
=
tunnelStream
.
CopyToAsync
(
string
.
Empty
,
clientStream
);
await
Task
.
WhenAll
(
sendRelay
,
receiveRelay
);
}
finally
{
tcpConnection
.
Dispose
();
}
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Http/HttpWebClient.cs
View file @
1acc1ad8
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network
;
using
Titanium.Web.Proxy.Shared
;
using
Titanium.Web.Proxy.Tcp
;
namespace
Titanium.Web.Proxy.Http
{
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public
class
HttpWebClient
{
private
int
processId
;
/// <summary>
/// Connection to server
/// </summary>
internal
TcpConnection
ServerConnection
{
get
;
set
;
}
public
List
<
HttpHeader
>
ConnectHeaders
{
get
;
set
;
}
public
Request
Request
{
get
;
set
;
}
public
Response
Response
{
get
;
set
;
}
/// <summary>
/// PID of the process that is created the current session
/// </summary>
public
int
ProcessId
{
get
{
if
(
processId
==
0
)
{
TcpRow
tcpRow
=
TcpHelper
.
GetExtendedTcpTable
().
TcpRows
.
FirstOrDefault
(
row
=>
row
.
LocalEndPoint
.
Port
==
ServerConnection
.
port
);
processId
=
tcpRow
?.
ProcessId
??
-
1
;
}
return
processId
;
}
}
/// <summary>
/// Is Https?
/// </summary>
public
bool
IsHttps
=>
this
.
Request
.
RequestUri
.
Scheme
==
Uri
.
UriSchemeHttps
;
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="connection">Instance of <see cref="TcpConnection"/></param>
internal
void
SetConnection
(
TcpConnection
connection
)
{
connection
.
LastAccess
=
DateTime
.
Now
;
ServerConnection
=
connection
;
}
internal
HttpWebClient
()
{
this
.
Request
=
new
Request
();
this
.
Response
=
new
Response
();
}
/// <summary>
/// Prepare & send the http(s) request
/// </summary>
/// <returns></returns>
internal
async
Task
SendRequest
(
bool
enable100ContinueBehaviour
)
{
Stream
stream
=
ServerConnection
.
Stream
;
StringBuilder
requestLines
=
new
StringBuilder
();
//prepare the request & headers
if
((
ServerConnection
.
UpStreamHttpProxy
!=
null
&&
ServerConnection
.
IsHttps
==
false
)
||
(
ServerConnection
.
UpStreamHttpsProxy
!=
null
&&
ServerConnection
.
IsHttps
==
true
))
{
requestLines
.
AppendLine
(
string
.
Join
(
" "
,
this
.
Request
.
Method
,
this
.
Request
.
RequestUri
.
AbsoluteUri
,
$"HTTP/
{
this
.
Request
.
HttpVersion
.
Major
}
.
{
this
.
Request
.
HttpVersion
.
Minor
}
"
));
}
else
{
requestLines
.
AppendLine
(
string
.
Join
(
" "
,
this
.
Request
.
Method
,
this
.
Request
.
RequestUri
.
PathAndQuery
,
$"HTTP/
{
this
.
Request
.
HttpVersion
.
Major
}
.
{
this
.
Request
.
HttpVersion
.
Minor
}
"
));
}
//Send Authentication to Upstream proxy if needed
if
(
ServerConnection
.
UpStreamHttpProxy
!=
null
&&
ServerConnection
.
IsHttps
==
false
&&
!
string
.
IsNullOrEmpty
(
ServerConnection
.
UpStreamHttpProxy
.
UserName
)
&&
ServerConnection
.
UpStreamHttpProxy
.
Password
!=
null
)
{
requestLines
.
AppendLine
(
"Proxy-Connection: keep-alive"
);
requestLines
.
AppendLine
(
"Proxy-Authorization"
+
": Basic "
+
Convert
.
ToBase64String
(
Encoding
.
UTF8
.
GetBytes
(
ServerConnection
.
UpStreamHttpProxy
.
UserName
+
":"
+
ServerConnection
.
UpStreamHttpProxy
.
Password
)));
}
//write request headers
foreach
(
var
headerItem
in
this
.
Request
.
RequestHeaders
)
{
var
header
=
headerItem
.
Value
;
if
(
headerItem
.
Key
!=
"Proxy-Authorization"
)
{
requestLines
.
AppendLine
(
header
.
Name
+
':'
+
header
.
Value
);
}
}
//write non unique request headers
foreach
(
var
headerItem
in
this
.
Request
.
NonUniqueRequestHeaders
)
{
var
headers
=
headerItem
.
Value
;
foreach
(
var
header
in
headers
)
{
if
(
headerItem
.
Key
!=
"Proxy-Authorization"
)
{
requestLines
.
AppendLine
(
header
.
Name
+
':'
+
header
.
Value
);
}
}
}
requestLines
.
AppendLine
();
string
request
=
requestLines
.
ToString
();
byte
[]
requestBytes
=
Encoding
.
ASCII
.
GetBytes
(
request
);
await
stream
.
WriteAsync
(
requestBytes
,
0
,
requestBytes
.
Length
);
await
stream
.
FlushAsync
();
if
(
enable100ContinueBehaviour
)
{
if
(
this
.
Request
.
ExpectContinue
)
{
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
var
responseStatusCode
=
httpResult
[
1
].
Trim
();
var
responseStatusDescription
=
httpResult
[
2
].
Trim
();
//find if server is willing for expect continue
if
(
responseStatusCode
.
Equals
(
"100"
)
&&
responseStatusDescription
.
ToLower
().
Equals
(
"continue"
))
{
this
.
Request
.
Is100Continue
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
else
if
(
responseStatusCode
.
Equals
(
"417"
)
&&
responseStatusDescription
.
ToLower
().
Equals
(
"expectation failed"
))
{
this
.
Request
.
ExpectationFailed
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
}
}
}
/// <summary>
/// Receive & parse the http response from server
/// </summary>
/// <returns></returns>
internal
async
Task
ReceiveResponse
()
{
//return if this is already read
if
(
this
.
Response
.
ResponseStatusCode
!=
null
)
return
;
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
if
(
string
.
IsNullOrEmpty
(
httpResult
[
0
]))
{
//Empty content in first-line, try again
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
}
var
httpVersion
=
httpResult
[
0
].
Trim
().
ToLower
();
var
version
=
new
Version
(
1
,
1
);
if
(
httpVersion
==
"http/1.0"
)
{
version
=
new
Version
(
1
,
0
);
}
this
.
Response
.
HttpVersion
=
version
;
this
.
Response
.
ResponseStatusCode
=
httpResult
[
1
].
Trim
();
this
.
Response
.
ResponseStatusDescription
=
httpResult
[
2
].
Trim
();
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if
(
this
.
Response
.
ResponseStatusCode
.
Equals
(
"100"
)
&&
this
.
Response
.
ResponseStatusDescription
.
ToLower
().
Equals
(
"continue"
))
{
//Read the next line after 100-continue
this
.
Response
.
Is100Continue
=
true
;
this
.
Response
.
ResponseStatusCode
=
null
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
//now receive response
await
ReceiveResponse
();
return
;
}
else
if
(
this
.
Response
.
ResponseStatusCode
.
Equals
(
"417"
)
&&
this
.
Response
.
ResponseStatusDescription
.
ToLower
().
Equals
(
"expectation failed"
))
{
//read next line after expectation failed response
this
.
Response
.
ExpectationFailed
=
true
;
this
.
Response
.
ResponseStatusCode
=
null
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
//now receive response
await
ReceiveResponse
();
return
;
}
//Read the Response headers
//Read the response headers in to unique and non-unique header collections
string
tmpLine
;
while
(!
string
.
IsNullOrEmpty
(
tmpLine
=
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()))
{
var
header
=
tmpLine
.
Split
(
ProxyConstants
.
ColonSplit
,
2
);
var
newHeader
=
new
HttpHeader
(
header
[
0
],
header
[
1
]);
//if header exist in non-unique header collection add it there
if
(
Response
.
NonUniqueResponseHeaders
.
ContainsKey
(
newHeader
.
Name
))
{
Response
.
NonUniqueResponseHeaders
[
newHeader
.
Name
].
Add
(
newHeader
);
}
//if header is alread in unique header collection then move both to non-unique collection
else
if
(
Response
.
ResponseHeaders
.
ContainsKey
(
newHeader
.
Name
))
{
var
existing
=
Response
.
ResponseHeaders
[
newHeader
.
Name
];
var
nonUniqueHeaders
=
new
List
<
HttpHeader
>
{
existing
,
newHeader
};
Response
.
NonUniqueResponseHeaders
.
Add
(
newHeader
.
Name
,
nonUniqueHeaders
);
Response
.
ResponseHeaders
.
Remove
(
newHeader
.
Name
);
}
//add to unique header collection
else
{
Response
.
ResponseHeaders
.
Add
(
newHeader
.
Name
,
newHeader
);
}
}
}
}
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network
;
using
Titanium.Web.Proxy.Shared
;
namespace
Titanium.Web.Proxy.Http
{
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public
class
HttpWebClient
{
private
int
processId
;
/// <summary>
/// Connection to server
/// </summary>
internal
TcpConnection
ServerConnection
{
get
;
set
;
}
public
List
<
HttpHeader
>
ConnectHeaders
{
get
;
set
;
}
public
Request
Request
{
get
;
set
;
}
public
Response
Response
{
get
;
set
;
}
/// <summary>
/// PID of the process that is created the current session
/// </summary>
public
int
ProcessId
{
get
;
internal
set
;
}
/// <summary>
/// Is Https?
/// </summary>
public
bool
IsHttps
=>
this
.
Request
.
RequestUri
.
Scheme
==
Uri
.
UriSchemeHttps
;
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="connection">Instance of <see cref="TcpConnection"/></param>
internal
void
SetConnection
(
TcpConnection
connection
)
{
connection
.
LastAccess
=
DateTime
.
Now
;
ServerConnection
=
connection
;
}
internal
HttpWebClient
()
{
this
.
Request
=
new
Request
();
this
.
Response
=
new
Response
();
}
/// <summary>
/// Prepare & send the http(s) request
/// </summary>
/// <returns></returns>
internal
async
Task
SendRequest
(
bool
enable100ContinueBehaviour
)
{
Stream
stream
=
ServerConnection
.
Stream
;
StringBuilder
requestLines
=
new
StringBuilder
();
//prepare the request & headers
if
((
ServerConnection
.
UpStreamHttpProxy
!=
null
&&
ServerConnection
.
IsHttps
==
false
)
||
(
ServerConnection
.
UpStreamHttpsProxy
!=
null
&&
ServerConnection
.
IsHttps
==
true
))
{
requestLines
.
AppendLine
(
string
.
Join
(
" "
,
this
.
Request
.
Method
,
this
.
Request
.
RequestUri
.
AbsoluteUri
,
$"HTTP/
{
this
.
Request
.
HttpVersion
.
Major
}
.
{
this
.
Request
.
HttpVersion
.
Minor
}
"
));
}
else
{
requestLines
.
AppendLine
(
string
.
Join
(
" "
,
this
.
Request
.
Method
,
this
.
Request
.
RequestUri
.
PathAndQuery
,
$"HTTP/
{
this
.
Request
.
HttpVersion
.
Major
}
.
{
this
.
Request
.
HttpVersion
.
Minor
}
"
));
}
//Send Authentication to Upstream proxy if needed
if
(
ServerConnection
.
UpStreamHttpProxy
!=
null
&&
ServerConnection
.
IsHttps
==
false
&&
!
string
.
IsNullOrEmpty
(
ServerConnection
.
UpStreamHttpProxy
.
UserName
)
&&
ServerConnection
.
UpStreamHttpProxy
.
Password
!=
null
)
{
requestLines
.
AppendLine
(
"Proxy-Connection: keep-alive"
);
requestLines
.
AppendLine
(
"Proxy-Authorization"
+
": Basic "
+
Convert
.
ToBase64String
(
Encoding
.
UTF8
.
GetBytes
(
ServerConnection
.
UpStreamHttpProxy
.
UserName
+
":"
+
ServerConnection
.
UpStreamHttpProxy
.
Password
)));
}
//write request headers
foreach
(
var
headerItem
in
this
.
Request
.
RequestHeaders
)
{
var
header
=
headerItem
.
Value
;
if
(
headerItem
.
Key
!=
"Proxy-Authorization"
)
{
requestLines
.
AppendLine
(
header
.
Name
+
':'
+
header
.
Value
);
}
}
//write non unique request headers
foreach
(
var
headerItem
in
this
.
Request
.
NonUniqueRequestHeaders
)
{
var
headers
=
headerItem
.
Value
;
foreach
(
var
header
in
headers
)
{
if
(
headerItem
.
Key
!=
"Proxy-Authorization"
)
{
requestLines
.
AppendLine
(
header
.
Name
+
':'
+
header
.
Value
);
}
}
}
requestLines
.
AppendLine
();
string
request
=
requestLines
.
ToString
();
byte
[]
requestBytes
=
Encoding
.
ASCII
.
GetBytes
(
request
);
await
stream
.
WriteAsync
(
requestBytes
,
0
,
requestBytes
.
Length
);
await
stream
.
FlushAsync
();
if
(
enable100ContinueBehaviour
)
{
if
(
this
.
Request
.
ExpectContinue
)
{
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
var
responseStatusCode
=
httpResult
[
1
].
Trim
();
var
responseStatusDescription
=
httpResult
[
2
].
Trim
();
//find if server is willing for expect continue
if
(
responseStatusCode
.
Equals
(
"100"
)
&&
responseStatusDescription
.
ToLower
().
Equals
(
"continue"
))
{
this
.
Request
.
Is100Continue
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
else
if
(
responseStatusCode
.
Equals
(
"417"
)
&&
responseStatusDescription
.
ToLower
().
Equals
(
"expectation failed"
))
{
this
.
Request
.
ExpectationFailed
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
}
}
}
/// <summary>
/// Receive & parse the http response from server
/// </summary>
/// <returns></returns>
internal
async
Task
ReceiveResponse
()
{
//return if this is already read
if
(
this
.
Response
.
ResponseStatusCode
!=
null
)
return
;
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
if
(
string
.
IsNullOrEmpty
(
httpResult
[
0
]))
{
//Empty content in first-line, try again
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
}
var
httpVersion
=
httpResult
[
0
].
Trim
().
ToLower
();
var
version
=
new
Version
(
1
,
1
);
if
(
httpVersion
==
"http/1.0"
)
{
version
=
new
Version
(
1
,
0
);
}
this
.
Response
.
HttpVersion
=
version
;
this
.
Response
.
ResponseStatusCode
=
httpResult
[
1
].
Trim
();
this
.
Response
.
ResponseStatusDescription
=
httpResult
[
2
].
Trim
();
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if
(
this
.
Response
.
ResponseStatusCode
.
Equals
(
"100"
)
&&
this
.
Response
.
ResponseStatusDescription
.
ToLower
().
Equals
(
"continue"
))
{
//Read the next line after 100-continue
this
.
Response
.
Is100Continue
=
true
;
this
.
Response
.
ResponseStatusCode
=
null
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
//now receive response
await
ReceiveResponse
();
return
;
}
else
if
(
this
.
Response
.
ResponseStatusCode
.
Equals
(
"417"
)
&&
this
.
Response
.
ResponseStatusDescription
.
ToLower
().
Equals
(
"expectation failed"
))
{
//read next line after expectation failed response
this
.
Response
.
ExpectationFailed
=
true
;
this
.
Response
.
ResponseStatusCode
=
null
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
//now receive response
await
ReceiveResponse
();
return
;
}
//Read the Response headers
//Read the response headers in to unique and non-unique header collections
string
tmpLine
;
while
(!
string
.
IsNullOrEmpty
(
tmpLine
=
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()))
{
var
header
=
tmpLine
.
Split
(
ProxyConstants
.
ColonSplit
,
2
);
var
newHeader
=
new
HttpHeader
(
header
[
0
],
header
[
1
]);
//if header exist in non-unique header collection add it there
if
(
Response
.
NonUniqueResponseHeaders
.
ContainsKey
(
newHeader
.
Name
))
{
Response
.
NonUniqueResponseHeaders
[
newHeader
.
Name
].
Add
(
newHeader
);
}
//if header is alread in unique header collection then move both to non-unique collection
else
if
(
Response
.
ResponseHeaders
.
ContainsKey
(
newHeader
.
Name
))
{
var
existing
=
Response
.
ResponseHeaders
[
newHeader
.
Name
];
var
nonUniqueHeaders
=
new
List
<
HttpHeader
>
{
existing
,
newHeader
};
Response
.
NonUniqueResponseHeaders
.
Add
(
newHeader
.
Name
,
nonUniqueHeaders
);
Response
.
ResponseHeaders
.
Remove
(
newHeader
.
Name
);
}
//add to unique header collection
else
{
Response
.
ResponseHeaders
.
Add
(
newHeader
.
Name
,
newHeader
);
}
}
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Models/EndPoint.cs
View file @
1acc1ad8
using
System.Collections.Generic
;
using
System.Net
;
using
System.Net.Sockets
;
namespace
Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public
abstract
class
ProxyEndPoint
{
public
ProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
{
this
.
IpAddress
=
IpAddress
;
this
.
Port
=
Port
;
this
.
EnableSsl
=
EnableSsl
;
}
public
IPAddress
IpAddress
{
get
;
internal
set
;
}
public
int
Port
{
get
;
internal
set
;
}
public
bool
EnableSsl
{
get
;
internal
set
;
}
internal
TcpListener
listener
{
get
;
set
;
}
}
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
public
class
ExplicitProxyEndPoint
:
ProxyEndPoint
{
internal
bool
IsSystemHttpProxy
{
get
;
set
;
}
internal
bool
IsSystemHttpsProxy
{
get
;
set
;
}
public
List
<
string
>
ExcludedHttpsHostNameRegex
{
get
;
set
;
}
public
ExplicitProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
:
base
(
IpAddress
,
Port
,
EnableSsl
)
{
}
}
/// <summary>
/// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding
/// </summary>
public
class
TransparentProxyEndPoint
:
ProxyEndPoint
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//This is valid only when UseServerNameIndication is set to false
public
string
GenericCertificateName
{
get
;
set
;
}
// public bool UseServerNameIndication { get; set; }
public
TransparentProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
:
base
(
IpAddress
,
Port
,
EnableSsl
)
{
this
.
GenericCertificateName
=
"localhost"
;
}
}
}
using
System.Collections.Generic
;
using
System.Net
;
using
System.Net.Sockets
;
namespace
Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public
abstract
class
ProxyEndPoint
{
public
ProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
{
this
.
IpAddress
=
IpAddress
;
this
.
Port
=
Port
;
this
.
EnableSsl
=
EnableSsl
;
}
public
IPAddress
IpAddress
{
get
;
internal
set
;
}
public
int
Port
{
get
;
internal
set
;
}
public
bool
EnableSsl
{
get
;
internal
set
;
}
public
bool
IpV6Enabled
=>
IpAddress
==
IPAddress
.
IPv6Any
||
IpAddress
==
IPAddress
.
IPv6Loopback
||
IpAddress
==
IPAddress
.
IPv6None
;
internal
TcpListener
listener
{
get
;
set
;
}
}
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
public
class
ExplicitProxyEndPoint
:
ProxyEndPoint
{
internal
bool
IsSystemHttpProxy
{
get
;
set
;
}
internal
bool
IsSystemHttpsProxy
{
get
;
set
;
}
public
List
<
string
>
ExcludedHttpsHostNameRegex
{
get
;
set
;
}
public
ExplicitProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
:
base
(
IpAddress
,
Port
,
EnableSsl
)
{
}
}
/// <summary>
/// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding
/// </summary>
public
class
TransparentProxyEndPoint
:
ProxyEndPoint
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//This is valid only when UseServerNameIndication is set to false
public
string
GenericCertificateName
{
get
;
set
;
}
// public bool UseServerNameIndication { get; set; }
public
TransparentProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
:
base
(
IpAddress
,
Port
,
EnableSsl
)
{
this
.
GenericCertificateName
=
"localhost"
;
}
}
}
Titanium.Web.Proxy/Models/ExternalProxy.cs
View file @
1acc1ad8
namespace
Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any
/// </summary>
public
class
ExternalProxy
{
public
string
UserName
{
get
;
set
;
}
public
string
Password
{
get
;
set
;
}
public
string
HostName
{
get
;
set
;
}
public
int
Port
{
get
;
set
;
}
}
}
using
System
;
using
System.Net
;
namespace
Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any
/// </summary>
public
class
ExternalProxy
{
private
static
readonly
Lazy
<
NetworkCredential
>
DefaultCredentials
=
new
Lazy
<
NetworkCredential
>(()
=>
CredentialCache
.
DefaultNetworkCredentials
);
private
string
userName
;
private
string
password
;
public
bool
UseDefaultCredentials
{
get
;
set
;
}
public
string
UserName
{
get
{
return
UseDefaultCredentials
?
DefaultCredentials
.
Value
.
UserName
:
userName
;
}
set
{
userName
=
value
;
if
(
DefaultCredentials
.
Value
.
UserName
!=
userName
)
{
UseDefaultCredentials
=
false
;
}
}
}
public
string
Password
{
get
{
return
UseDefaultCredentials
?
DefaultCredentials
.
Value
.
Password
:
password
;
}
set
{
password
=
value
;
if
(
DefaultCredentials
.
Value
.
Password
!=
password
)
{
UseDefaultCredentials
=
false
;
}
}
}
public
string
HostName
{
get
;
set
;
}
public
int
Port
{
get
;
set
;
}
}
}
Titanium.Web.Proxy/Network/TcpConnection.cs
View file @
1acc1ad8
...
...
@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Network
internal
ExternalProxy
UpStreamHttpsProxy
{
get
;
set
;
}
internal
string
HostName
{
get
;
set
;
}
internal
int
p
ort
{
get
;
set
;
}
internal
int
P
ort
{
get
;
set
;
}
internal
bool
IsHttps
{
get
;
set
;
}
...
...
Titanium.Web.Proxy/Network/TcpConnectionFactory.cs
View file @
1acc1ad8
...
...
@@ -49,7 +49,7 @@ namespace Titanium.Web.Proxy.Network
SslStream
sslStream
=
null
;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if
(
externalHttpsProxy
!=
null
)
if
(
externalHttpsProxy
!=
null
&&
externalHttpsProxy
.
HostName
!=
remoteHostName
)
{
client
=
new
TcpClient
(
externalHttpsProxy
.
HostName
,
externalHttpsProxy
.
Port
);
stream
=
client
.
GetStream
();
...
...
@@ -100,17 +100,14 @@ namespace Titanium.Web.Proxy.Network
}
catch
{
if
(
sslStream
!=
null
)
{
sslStream
.
Dispose
();
}
sslStream
?.
Dispose
();
throw
;
}
}
else
{
if
(
externalHttpProxy
!=
null
)
if
(
externalHttpProxy
!=
null
&&
externalHttpProxy
.
HostName
!=
remoteHostName
)
{
client
=
new
TcpClient
(
externalHttpProxy
.
HostName
,
externalHttpProxy
.
Port
);
stream
=
client
.
GetStream
();
...
...
@@ -133,7 +130,7 @@ namespace Titanium.Web.Proxy.Network
UpStreamHttpProxy
=
externalHttpProxy
,
UpStreamHttpsProxy
=
externalHttpsProxy
,
HostName
=
remoteHostName
,
p
ort
=
remotePort
,
P
ort
=
remotePort
,
IsHttps
=
isHttps
,
TcpClient
=
client
,
StreamReader
=
new
CustomBinaryReader
(
stream
),
...
...
Titanium.Web.Proxy/ProxyServer.cs
View file @
1acc1ad8
...
...
@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy
/// </summary>
public
partial
class
ProxyServer
:
IDisposable
{
/// <summary>
/// Is the root certificate used by this proxy is valid?
/// </summary>
...
...
@@ -186,6 +186,11 @@ namespace Titanium.Web.Proxy
/// </summary>
public
bool
ProxyRunning
=>
proxyRunning
;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public
bool
ForwardToUpstreamGateway
{
get
;
set
;
}
/// <summary>
/// Constructor
/// </summary>
...
...
@@ -348,6 +353,12 @@ namespace Titanium.Web.Proxy
certificateCacheManager
.
TrustRootCertificate
();
}
if
(
ForwardToUpstreamGateway
&&
GetCustomUpStreamHttpProxyFunc
==
null
&&
GetCustomUpStreamHttpsProxyFunc
==
null
)
{
GetCustomUpStreamHttpProxyFunc
=
GetSystemUpStreamProxy
;
GetCustomUpStreamHttpsProxyFunc
=
GetSystemUpStreamProxy
;
}
foreach
(
var
endPoint
in
ProxyEndPoints
)
{
Listen
(
endPoint
);
...
...
@@ -358,6 +369,28 @@ namespace Titanium.Web.Proxy
proxyRunning
=
true
;
}
/// <summary>
/// Gets the system up stream proxy.
/// </summary>
/// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param>
/// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns>
private
Task
<
ExternalProxy
>
GetSystemUpStreamProxy
(
SessionEventArgs
sessionEventArgs
)
{
// Use built-in WebProxy class to handle PAC/WAPD scripts.
var
systemProxyResolver
=
new
WebProxy
();
var
systemProxyUri
=
systemProxyResolver
.
GetProxy
(
sessionEventArgs
.
WebSession
.
Request
.
RequestUri
);
// TODO: Apply authorization
var
systemProxy
=
new
ExternalProxy
{
HostName
=
systemProxyUri
.
Host
,
Port
=
systemProxyUri
.
Port
};
return
Task
.
FromResult
(
systemProxy
);
}
/// <summary>
/// Stop this proxy server
/// </summary>
...
...
Titanium.Web.Proxy/RequestHandler.cs
View file @
1acc1ad8
...
...
@@ -2,6 +2,7 @@
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Net
;
using
System.Net.Security
;
using
System.Net.Sockets
;
using
System.Security.Authentication
;
...
...
@@ -16,7 +17,6 @@ using Titanium.Web.Proxy.Shared;
using
Titanium.Web.Proxy.Http
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Extensions
;
using
System.Text
;
namespace
Titanium.Web.Proxy
{
...
...
@@ -25,12 +25,33 @@ namespace Titanium.Web.Proxy
/// </summary>
partial
class
ProxyServer
{
private
int
FindProcessIdFromLocalPort
(
int
port
,
IpVersion
ipVersion
)
{
var
tcpRow
=
TcpHelper
.
GetExtendedTcpTable
(
ipVersion
).
FirstOrDefault
(
row
=>
row
.
LocalEndPoint
.
Port
==
port
);
return
tcpRow
?.
ProcessId
??
0
;
}
private
int
GetProcessIdFromPort
(
int
port
,
bool
ipV6Enabled
)
{
var
processId
=
FindProcessIdFromLocalPort
(
port
,
IpVersion
.
Ipv4
);
if
(
processId
>
0
&&
!
ipV6Enabled
)
{
return
processId
;
}
return
FindProcessIdFromLocalPort
(
port
,
IpVersion
.
Ipv6
);
}
//This is called when client is aware of proxy
//So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
private
async
Task
HandleClient
(
ExplicitProxyEndPoint
endPoint
,
TcpClient
c
lient
)
private
async
Task
HandleClient
(
ExplicitProxyEndPoint
endPoint
,
TcpClient
tcpC
lient
)
{
Stream
clientStream
=
client
.
GetStream
();
var
processId
=
GetProcessIdFromPort
(((
IPEndPoint
)
tcpClient
.
Client
.
RemoteEndPoint
).
Port
,
endPoint
.
IpV6Enabled
);
Stream
clientStream
=
tcpClient
.
GetStream
();
clientStream
.
ReadTimeout
=
ConnectionTimeOutSeconds
*
1000
;
clientStream
.
WriteTimeout
=
ConnectionTimeOutSeconds
*
1000
;
...
...
@@ -145,7 +166,6 @@ namespace Titanium.Web.Proxy
//write back successfull CONNECT response
await
WriteConnectResponse
(
clientStreamWriter
,
version
);
await
TcpHelper
.
SendRaw
(
BUFFER_SIZE
,
ConnectionTimeOutSeconds
,
httpRemoteUri
.
Host
,
httpRemoteUri
.
Port
,
httpCmd
,
version
,
null
,
false
,
SupportedSslProtocols
,
...
...
@@ -157,7 +177,7 @@ namespace Titanium.Web.Proxy
return
;
}
//Now create the request
await
HandleHttpSessionRequest
(
client
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
await
HandleHttpSessionRequest
(
tcpClient
,
processId
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
httpRemoteUri
.
Scheme
==
Uri
.
UriSchemeHttps
?
httpRemoteUri
.
Host
:
null
,
connectRequestHeaders
,
null
,
null
);
}
catch
(
Exception
ex
)
...
...
@@ -170,6 +190,8 @@ namespace Titanium.Web.Proxy
//So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
private
async
Task
HandleClient
(
TransparentProxyEndPoint
endPoint
,
TcpClient
tcpClient
)
{
var
processId
=
GetProcessIdFromPort
(((
IPEndPoint
)
tcpClient
.
Client
.
RemoteEndPoint
).
Port
,
endPoint
.
IpV6Enabled
);
Stream
clientStream
=
tcpClient
.
GetStream
();
clientStream
.
ReadTimeout
=
ConnectionTimeOutSeconds
*
1000
;
...
...
@@ -199,10 +221,7 @@ namespace Titanium.Web.Proxy
}
catch
(
Exception
)
{
if
(
sslStream
!=
null
)
{
sslStream
.
Dispose
();
}
sslStream
.
Dispose
();
Dispose
(
sslStream
,
clientStreamReader
,
clientStreamWriter
,
null
);
return
;
...
...
@@ -219,11 +238,10 @@ namespace Titanium.Web.Proxy
var
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
//Now create the request
await
HandleHttpSessionRequest
(
tcpClient
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
await
HandleHttpSessionRequest
(
tcpClient
,
processId
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
endPoint
.
EnableSsl
?
endPoint
.
GenericCertificateName
:
null
,
null
);
}
private
async
Task
HandleHttpSessionRequestInternal
(
TcpConnection
connection
,
SessionEventArgs
args
,
ExternalProxy
customUpStreamHttpProxy
,
ExternalProxy
customUpStreamHttpsProxy
,
bool
CloseConnection
)
{
try
...
...
@@ -256,10 +274,8 @@ namespace Titanium.Web.Proxy
customUpStreamHttpProxy
??
UpStreamHttpProxy
,
customUpStreamHttpsProxy
??
UpStreamHttpsProxy
,
args
.
ProxyClient
.
ClientStream
);
}
args
.
WebSession
.
Request
.
RequestLocked
=
true
;
//If request was cancelled by user then dispose the client
if
(
args
.
WebSession
.
Request
.
CancelRequest
)
{
...
...
@@ -344,23 +360,25 @@ namespace Titanium.Web.Proxy
return
;
}
if
(
CloseConnection
&&
connection
!=
null
)
if
(
CloseConnection
)
{
//dispose
connection
.
Dispose
();
connection
?
.
Dispose
();
}
}
/// <summary>
/// This is the core request handler method for a particular connection from client
/// </summary>
/// <param name="client"></param>
/// <param name="processId"></param>
/// <param name="httpCmd"></param>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="httpsHostName"></param>
/// <returns></returns>
private
async
Task
HandleHttpSessionRequest
(
TcpClient
client
,
string
httpCmd
,
Stream
clientStream
,
private
async
Task
HandleHttpSessionRequest
(
TcpClient
client
,
int
processId
,
string
httpCmd
,
Stream
clientStream
,
CustomBinaryReader
clientStreamReader
,
StreamWriter
clientStreamWriter
,
string
httpsHostName
,
List
<
HttpHeader
>
connectHeaders
,
ExternalProxy
customUpStreamHttpProxy
=
null
,
ExternalProxy
customUpStreamHttpsProxy
=
null
)
{
TcpConnection
connection
=
null
;
...
...
@@ -378,6 +396,8 @@ namespace Titanium.Web.Proxy
var
args
=
new
SessionEventArgs
(
BUFFER_SIZE
,
HandleHttpSessionResponse
);
args
.
ProxyClient
.
TcpClient
=
client
;
args
.
WebSession
.
ConnectHeaders
=
connectHeaders
;
args
.
WebSession
.
ProcessId
=
processId
;
try
{
//break up the line into three components (method, remote URL & Http Version)
...
...
@@ -450,7 +470,6 @@ namespace Titanium.Web.Proxy
break
;
}
PrepareRequestHeaders
(
args
.
WebSession
.
Request
.
RequestHeaders
,
args
.
WebSession
);
args
.
WebSession
.
Request
.
Host
=
args
.
WebSession
.
Request
.
RequestUri
.
Authority
;
...
...
@@ -558,7 +577,6 @@ namespace Titanium.Web.Proxy
webRequest
.
Request
.
RequestHeaders
=
requestHeaders
;
}
/// <summary>
/// This is called when the request is PUT/POST to read the body
/// </summary>
...
...
@@ -580,9 +598,7 @@ namespace Titanium.Web.Proxy
else
if
(
args
.
WebSession
.
Request
.
IsChunked
)
{
await
args
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
BUFFER_SIZE
,
postStream
);
}
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Tcp/TcpRow.cs
View file @
1acc1ad8
using
System.Net
;
using
Titanium.Web.Proxy.Helpers
;
namespace
Titanium.Web.Proxy.Tcp
{
/// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
internal
class
TcpRow
{
/// <summary>
/// Initializes a new instance of the <see cref="TcpRow"/> class.
/// </summary>
/// <param name="tcpRow">TcpRow struct.</param>
public
TcpRow
(
NativeMethods
.
TcpRow
tcpRow
)
{
ProcessId
=
tcpRow
.
owningPid
;
int
localPort
=
(
tcpRow
.
localPort1
<<
8
)
+
(
tcpRow
.
localPort2
)
+
(
tcpRow
.
localPort3
<<
24
)
+
(
tcpRow
.
localPort4
<<
16
);
long
localAddress
=
tcpRow
.
localAddr
;
LocalEndPoint
=
new
IPEndPoint
(
localAddress
,
localPort
);
}
/// <summary>
/// Gets the local end point.
/// </summary>
public
IPEndPoint
LocalEndPoint
{
get
;
private
set
;
}
/// <summary>
/// Gets the process identifier.
/// </summary>
public
int
ProcessId
{
get
;
private
set
;
}
}
using
System.Net
;
using
Titanium.Web.Proxy.Helpers
;
namespace
Titanium.Web.Proxy.Tcp
{
/// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
internal
class
TcpRow
{
/// <summary>
/// Initializes a new instance of the <see cref="TcpRow"/> class.
/// </summary>
/// <param name="tcpRow">TcpRow struct.</param>
public
TcpRow
(
NativeMethods
.
TcpRow
tcpRow
)
{
ProcessId
=
tcpRow
.
owningPid
;
int
localPort
=
(
tcpRow
.
localPort1
<<
8
)
+
(
tcpRow
.
localPort2
)
+
(
tcpRow
.
localPort3
<<
24
)
+
(
tcpRow
.
localPort4
<<
16
);
long
localAddress
=
tcpRow
.
localAddr
;
LocalEndPoint
=
new
IPEndPoint
(
localAddress
,
localPort
);
int
remotePort
=
(
tcpRow
.
remotePort1
<<
8
)
+
(
tcpRow
.
remotePort2
)
+
(
tcpRow
.
remotePort3
<<
24
)
+
(
tcpRow
.
remotePort4
<<
16
);
long
remoteAddress
=
tcpRow
.
remoteAddr
;
RemoteEndPoint
=
new
IPEndPoint
(
remoteAddress
,
remotePort
);
}
/// <summary>
/// Gets the local end point.
/// </summary>
public
IPEndPoint
LocalEndPoint
{
get
;
private
set
;
}
/// <summary>
/// Gets the remote end point.
/// </summary>
public
IPEndPoint
RemoteEndPoint
{
get
;
private
set
;
}
/// <summary>
/// Gets the process identifier.
/// </summary>
public
int
ProcessId
{
get
;
private
set
;
}
}
}
\ No newline at end of file
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