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
80dd44e5
Unverified
Commit
80dd44e5
authored
Jan 02, 2018
by
honfika
Committed by
GitHub
Jan 02, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #353 from justcoding121/develop
beta|develop
parents
7a44e456
5fe25998
Hide whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
971 additions
and
637 deletions
+971
-637
ProxyTestController.cs
.../Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs
+18
-2
MainWindow.xaml.cs
Examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs
+9
-5
SessionListItem.cs
Examples/Titanium.Web.Proxy.Examples.Wpf/SessionListItem.cs
+25
-5
Titanium.Web.Proxy.Examples.Wpf.csproj
...Proxy.Examples.Wpf/Titanium.Web.Proxy.Examples.Wpf.csproj
+2
-2
packages.config
Examples/Titanium.Web.Proxy.Examples.Wpf/packages.config
+1
-1
AsyncEventHandler.cs
Titanium.Web.Proxy/EventArguments/AsyncEventHandler.cs
+7
-0
SessionEventArgs.cs
Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
+196
-71
TunnelConnectEventArgs.cs
Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs
+4
-3
FuncExtensions.cs
Titanium.Web.Proxy/Extensions/FuncExtensions.cs
+16
-5
SslExtensions.cs
Titanium.Web.Proxy/Extensions/SslExtensions.cs
+22
-0
StreamExtensions.cs
Titanium.Web.Proxy/Extensions/StreamExtensions.cs
+38
-62
TcpExtensions.cs
Titanium.Web.Proxy/Extensions/TcpExtensions.cs
+43
-1
HttpHelper.cs
Titanium.Web.Proxy/Helpers/HttpHelper.cs
+10
-2
HttpRequestWriter.cs
Titanium.Web.Proxy/Helpers/HttpRequestWriter.cs
+1
-2
HttpResponseWriter.cs
Titanium.Web.Proxy/Helpers/HttpResponseWriter.cs
+2
-103
HttpWriter.cs
Titanium.Web.Proxy/Helpers/HttpWriter.cs
+200
-13
Tcp.cs
Titanium.Web.Proxy/Helpers/Tcp.cs
+8
-2
ConnectResponse.cs
Titanium.Web.Proxy/Http/ConnectResponse.cs
+1
-1
HeaderCollection.cs
Titanium.Web.Proxy/Http/HeaderCollection.cs
+19
-6
HttpWebClient.cs
Titanium.Web.Proxy/Http/HttpWebClient.cs
+25
-40
KnownHeaders.cs
Titanium.Web.Proxy/Http/KnownHeaders.cs
+53
-0
Request.cs
Titanium.Web.Proxy/Http/Request.cs
+22
-17
Response.cs
Titanium.Web.Proxy/Http/Response.cs
+29
-17
HttpHeader.cs
Titanium.Web.Proxy/Models/HttpHeader.cs
+6
-10
DebugCustomBufferedStream.cs
Titanium.Web.Proxy/Network/DebugCustomBufferedStream.cs
+50
-0
ProxyClient.cs
Titanium.Web.Proxy/Network/ProxyClient.cs
+2
-2
TcpConnection.cs
Titanium.Web.Proxy/Network/Tcp/TcpConnection.cs
+17
-18
TcpConnectionFactory.cs
Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs
+15
-18
ProxyAuthorizationHandler.cs
Titanium.Web.Proxy/ProxyAuthorizationHandler.cs
+12
-12
ProxyServer.cs
Titanium.Web.Proxy/ProxyServer.cs
+10
-26
RequestHandler.cs
Titanium.Web.Proxy/RequestHandler.cs
+93
-168
ResponseHandler.cs
Titanium.Web.Proxy/ResponseHandler.cs
+3
-6
ProxyConstants.cs
Titanium.Web.Proxy/Shared/ProxyConstants.cs
+2
-8
Titanium.Web.Proxy.csproj
Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
+2
-2
WinAuthHandler.cs
Titanium.Web.Proxy/WinAuthHandler.cs
+8
-7
No files found.
Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs
View file @
80dd44e5
...
@@ -143,7 +143,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
...
@@ -143,7 +143,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
}
}
//intecept & cancel redirect or update requests
//intecept & cancel redirect or update requests
p
ublic
async
Task
OnRequest
(
object
sender
,
SessionEventArgs
e
)
p
rivate
async
Task
OnRequest
(
object
sender
,
SessionEventArgs
e
)
{
{
Console
.
WriteLine
(
"Active Client Connections:"
+
((
ProxyServer
)
sender
).
ClientConnectionCount
);
Console
.
WriteLine
(
"Active Client Connections:"
+
((
ProxyServer
)
sender
).
ClientConnectionCount
);
Console
.
WriteLine
(
e
.
WebSession
.
Request
.
Url
);
Console
.
WriteLine
(
e
.
WebSession
.
Request
.
Url
);
...
@@ -151,6 +151,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
...
@@ -151,6 +151,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
//read request headers
//read request headers
requestHeaderHistory
[
e
.
Id
]
=
e
.
WebSession
.
Request
.
Headers
;
requestHeaderHistory
[
e
.
Id
]
=
e
.
WebSession
.
Request
.
Headers
;
////This sample shows how to get the multipart form data headers
//if (e.WebSession.Request.Host == "mail.yahoo.com" && e.WebSession.Request.IsMultipartFormData)
//{
// e.MultipartRequestPartSent += MultipartRequestPartSent;
//}
if
(
e
.
WebSession
.
Request
.
HasBody
)
if
(
e
.
WebSession
.
Request
.
HasBody
)
{
{
//Get/Set request body bytes
//Get/Set request body bytes
...
@@ -185,7 +191,17 @@ namespace Titanium.Web.Proxy.Examples.Basic
...
@@ -185,7 +191,17 @@ namespace Titanium.Web.Proxy.Examples.Basic
}
}
//Modify response
//Modify response
public
async
Task
OnResponse
(
object
sender
,
SessionEventArgs
e
)
private
void
MultipartRequestPartSent
(
object
sender
,
MultipartRequestPartSentEventArgs
e
)
{
var
session
=
(
SessionEventArgs
)
sender
;
Console
.
WriteLine
(
"Multipart form data headers:"
);
foreach
(
var
header
in
e
.
Headers
)
{
Console
.
WriteLine
(
header
);
}
}
private
async
Task
OnResponse
(
object
sender
,
SessionEventArgs
e
)
{
{
Console
.
WriteLine
(
"Active Server Connections:"
+
((
ProxyServer
)
sender
).
ServerConnectionCount
);
Console
.
WriteLine
(
"Active Server Connections:"
+
((
ProxyServer
)
sender
).
ServerConnectionCount
);
...
...
Examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs
View file @
80dd44e5
...
@@ -11,7 +11,6 @@ using Titanium.Web.Proxy.EventArguments;
...
@@ -11,7 +11,6 @@ using Titanium.Web.Proxy.EventArguments;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network
;
namespace
Titanium.Web.Proxy.Examples.Wpf
namespace
Titanium.Web.Proxy.Examples.Wpf
{
{
...
@@ -63,13 +62,14 @@ namespace Titanium.Web.Proxy.Examples.Wpf
...
@@ -63,13 +62,14 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public
MainWindow
()
public
MainWindow
()
{
{
proxyServer
=
new
ProxyServer
();
proxyServer
=
new
ProxyServer
();
//proxyServer.CertificateEngine = CertificateEngine.
BouncyCastle
;
//proxyServer.CertificateEngine = CertificateEngine.
DefaultWindows
;
proxyServer
.
TrustRootCertificate
=
true
;
proxyServer
.
TrustRootCertificate
=
true
;
proxyServer
.
CertificateManager
.
TrustRootCertificateAsAdministrator
();
proxyServer
.
CertificateManager
.
TrustRootCertificateAsAdministrator
();
proxyServer
.
ForwardToUpstreamGateway
=
true
;
proxyServer
.
ForwardToUpstreamGateway
=
true
;
var
explicitEndPoint
=
new
ExplicitProxyEndPoint
(
IPAddress
.
Any
,
8000
,
true
)
var
explicitEndPoint
=
new
ExplicitProxyEndPoint
(
IPAddress
.
Any
,
8000
,
true
)
{
{
ExcludedHttpsHostNameRegex
=
new
[]
{
"ssllabs.com"
},
//IncludedHttpsHostNameRegex = new string[0],
//IncludedHttpsHostNameRegex = new string[0],
};
};
...
@@ -134,10 +134,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf
...
@@ -134,10 +134,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem
item
=
null
;
SessionListItem
item
=
null
;
await
Dispatcher
.
InvokeAsync
(()
=>
await
Dispatcher
.
InvokeAsync
(()
=>
{
{
if
(
sessionDictionary
.
TryGetValue
(
e
.
WebSession
,
out
var
item2
))
if
(
sessionDictionary
.
TryGetValue
(
e
.
WebSession
,
out
item
))
{
{
item2
.
Update
();
item
.
Update
();
item
=
item2
;
}
}
});
});
...
@@ -147,6 +146,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
...
@@ -147,6 +146,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{
{
e
.
WebSession
.
Response
.
KeepBody
=
true
;
e
.
WebSession
.
Response
.
KeepBody
=
true
;
await
e
.
GetResponseBody
();
await
e
.
GetResponseBody
();
await
Dispatcher
.
InvokeAsync
(()
=>
{
item
.
Update
();
});
}
}
}
}
}
}
...
...
Examples/Titanium.Web.Proxy.Examples.Wpf/SessionListItem.cs
View file @
80dd44e5
...
@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
...
@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private
string
protocol
;
private
string
protocol
;
private
string
host
;
private
string
host
;
private
string
url
;
private
string
url
;
private
long
bodySize
;
private
long
?
bodySize
;
private
string
process
;
private
string
process
;
private
long
receivedDataCount
;
private
long
receivedDataCount
;
private
long
sentDataCount
;
private
long
sentDataCount
;
...
@@ -48,7 +48,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
...
@@ -48,7 +48,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
set
=>
SetField
(
ref
url
,
value
);
set
=>
SetField
(
ref
url
,
value
);
}
}
public
long
BodySize
public
long
?
BodySize
{
{
get
=>
bodySize
;
get
=>
bodySize
;
set
=>
SetField
(
ref
bodySize
,
value
);
set
=>
SetField
(
ref
bodySize
,
value
);
...
@@ -76,8 +76,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
...
@@ -76,8 +76,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
protected
void
SetField
<
T
>(
ref
T
field
,
T
value
,[
CallerMemberName
]
string
propertyName
=
null
)
protected
void
SetField
<
T
>(
ref
T
field
,
T
value
,[
CallerMemberName
]
string
propertyName
=
null
)
{
{
field
=
value
;
if
(!
Equals
(
field
,
value
))
OnPropertyChanged
(
propertyName
);
{
field
=
value
;
OnPropertyChanged
(
propertyName
);
}
}
}
[
NotifyPropertyChangedInvocator
]
[
NotifyPropertyChangedInvocator
]
...
@@ -105,7 +108,24 @@ namespace Titanium.Web.Proxy.Examples.Wpf
...
@@ -105,7 +108,24 @@ namespace Titanium.Web.Proxy.Examples.Wpf
Url
=
request
.
RequestUri
.
AbsolutePath
;
Url
=
request
.
RequestUri
.
AbsolutePath
;
}
}
BodySize
=
response
?.
ContentLength
??
-
1
;
if
(!
IsTunnelConnect
)
{
long
responseSize
=
-
1
;
if
(
response
!=
null
)
{
if
(
response
.
ContentLength
!=
-
1
)
{
responseSize
=
response
.
ContentLength
;
}
else
if
(
response
.
IsBodyRead
&&
response
.
Body
!=
null
)
{
responseSize
=
response
.
Body
.
Length
;
}
}
BodySize
=
responseSize
;
}
Process
=
GetProcessDescription
(
WebSession
.
ProcessId
.
Value
);
Process
=
GetProcessDescription
(
WebSession
.
ProcessId
.
Value
);
}
}
...
...
Examples/Titanium.Web.Proxy.Examples.Wpf/Titanium.Web.Proxy.Examples.Wpf.csproj
View file @
80dd44e5
...
@@ -51,8 +51,8 @@
...
@@ -51,8 +51,8 @@
<WarningLevel>
4
</WarningLevel>
<WarningLevel>
4
</WarningLevel>
</PropertyGroup>
</PropertyGroup>
<ItemGroup>
<ItemGroup>
<Reference
Include=
"StreamExtended, Version=1.0.1
10
.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL"
>
<Reference
Include=
"StreamExtended, Version=1.0.1
32
.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL"
>
<HintPath>
..\..\packages\StreamExtended.1.0.1
10
-beta\lib\net45\StreamExtended.dll
</HintPath>
<HintPath>
..\..\packages\StreamExtended.1.0.1
32
-beta\lib\net45\StreamExtended.dll
</HintPath>
</Reference>
</Reference>
<Reference
Include=
"System"
/>
<Reference
Include=
"System"
/>
<Reference
Include=
"System.Data"
/>
<Reference
Include=
"System.Data"
/>
...
...
Examples/Titanium.Web.Proxy.Examples.Wpf/packages.config
View file @
80dd44e5
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
packages
>
<
packages
>
<
package
id
=
"StreamExtended"
version
=
"1.0.1
10
-beta"
targetFramework
=
"net45"
/>
<
package
id
=
"StreamExtended"
version
=
"1.0.1
32
-beta"
targetFramework
=
"net45"
/>
</
packages
>
</
packages
>
\ No newline at end of file
Titanium.Web.Proxy/EventArguments/AsyncEventHandler.cs
0 → 100644
View file @
80dd44e5
using
System
;
using
System.Threading.Tasks
;
namespace
Titanium.Web.Proxy.EventArguments
{
public
delegate
Task
AsyncEventHandler
<
TEventArgs
>(
object
sender
,
TEventArgs
e
);
}
\ No newline at end of file
Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
View file @
80dd44e5
...
@@ -3,8 +3,9 @@ using System.Collections.Generic;
...
@@ -3,8 +3,9 @@ using System.Collections.Generic;
using
System.IO
;
using
System.IO
;
using
System.Net
;
using
System.Net
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
StreamExtended.Helpers
;
using
StreamExtended.Network
;
using
Titanium.Web.Proxy.Decompression
;
using
Titanium.Web.Proxy.Decompression
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http.Responses
;
using
Titanium.Web.Proxy.Http.Responses
;
...
@@ -21,6 +22,8 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -21,6 +22,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
/// </summary>
public
class
SessionEventArgs
:
EventArgs
,
IDisposable
public
class
SessionEventArgs
:
EventArgs
,
IDisposable
{
{
private
static
readonly
byte
[]
emptyData
=
new
byte
[
0
];
/// <summary>
/// <summary>
/// Size of Buffers used by this object
/// Size of Buffers used by this object
/// </summary>
/// </summary>
...
@@ -31,6 +34,8 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -31,6 +34,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
/// </summary>
private
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
;
private
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
;
private
readonly
Action
<
Exception
>
exceptionFunc
;
/// <summary>
/// <summary>
/// Backing field for corresponding public property
/// Backing field for corresponding public property
/// </summary>
/// </summary>
...
@@ -39,9 +44,9 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -39,9 +44,9 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// <summary>
/// Holds a reference to client
/// Holds a reference to client
/// </summary>
/// </summary>
internal
ProxyClient
ProxyClient
{
get
;
set
;
}
internal
ProxyClient
ProxyClient
{
get
;
}
internal
bool
H
asMulipartEventSubscribers
=>
MultipartRequestPartSent
!=
null
;
private
bool
h
asMulipartEventSubscribers
=>
MultipartRequestPartSent
!=
null
;
/// <summary>
/// <summary>
/// Returns a unique Id for this request/response session
/// Returns a unique Id for this request/response session
...
@@ -101,12 +106,14 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -101,12 +106,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// <summary>
/// Constructor to initialize the proxy
/// Constructor to initialize the proxy
/// </summary>
/// </summary>
internal
SessionEventArgs
(
int
bufferSize
,
internal
SessionEventArgs
(
int
bufferSize
,
ProxyEndPoint
endPoint
,
ProxyEndPoint
endPoint
,
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
)
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
,
Action
<
Exception
>
exceptionFunc
)
{
{
this
.
bufferSize
=
bufferSize
;
this
.
bufferSize
=
bufferSize
;
this
.
httpResponseHandler
=
httpResponseHandler
;
this
.
httpResponseHandler
=
httpResponseHandler
;
this
.
exceptionFunc
=
exceptionFunc
;
ProxyClient
=
new
ProxyClient
();
ProxyClient
=
new
ProxyClient
();
WebSession
=
new
HttpWebClient
(
bufferSize
);
WebSession
=
new
HttpWebClient
(
bufferSize
);
...
@@ -135,42 +142,21 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -135,42 +142,21 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// <summary>
/// Read request body content as bytes[] for current session
/// Read request body content as bytes[] for current session
/// </summary>
/// </summary>
private
async
Task
ReadRequestBody
()
private
async
Task
ReadRequestBody
Async
()
{
{
WebSession
.
Request
.
EnsureBodyAvailable
(
false
);
WebSession
.
Request
.
EnsureBodyAvailable
(
false
);
//Caching check
var
request
=
WebSession
.
Request
;
if
(!
WebSession
.
Request
.
IsBodyRead
)
{
//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
ProxyClient
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
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
ProxyClient
.
ClientStreamReader
.
CopyBytesToStream
(
requestBodyStream
,
WebSession
.
Request
.
ContentLength
);
}
else
if
(
WebSession
.
Request
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Request
.
HttpVersion
.
Minor
==
0
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
requestBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Request
.
Body
=
await
GetDecompressedResponseBody
(
WebSession
.
Request
.
ContentEncoding
,
requestBodyStream
.
ToArray
());
//If not already read (not cached yet)
}
if
(!
request
.
IsBodyRead
)
{
var
body
=
await
ReadBodyAsync
(
ProxyClient
.
ClientStreamReader
,
request
.
IsChunked
,
request
.
ContentLength
,
request
.
ContentEncoding
,
true
);
request
.
Body
=
body
;
//Now set the flag to true
//Now set the flag to true
//So that next time we can deliver body from cache
//So that next time we can deliver body from cache
WebSession
.
Request
.
IsBodyRead
=
true
;
request
.
IsBodyRead
=
true
;
var
body
=
WebSession
.
Request
.
Body
;
OnDataSent
(
body
,
0
,
body
.
Length
);
OnDataSent
(
body
,
0
,
body
.
Length
);
}
}
}
}
...
@@ -181,73 +167,208 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -181,73 +167,208 @@ namespace Titanium.Web.Proxy.EventArguments
internal
async
Task
ClearResponse
()
internal
async
Task
ClearResponse
()
{
{
//siphon out the body
//siphon out the body
await
ReadResponseBody
();
await
ReadResponseBody
Async
();
WebSession
.
Response
=
new
Response
();
WebSession
.
Response
=
new
Response
();
}
}
internal
void
OnDataSent
(
byte
[]
buffer
,
int
offset
,
int
count
)
internal
void
OnDataSent
(
byte
[]
buffer
,
int
offset
,
int
count
)
{
{
DataSent
?.
Invoke
(
this
,
new
DataEventArgs
(
buffer
,
offset
,
count
));
try
{
DataSent
?.
Invoke
(
this
,
new
DataEventArgs
(
buffer
,
offset
,
count
));
}
catch
(
Exception
ex
)
{
var
ex2
=
new
Exception
(
"Exception thrown in user event"
,
ex
);
exceptionFunc
(
ex2
);
}
}
}
internal
void
OnDataReceived
(
byte
[]
buffer
,
int
offset
,
int
count
)
internal
void
OnDataReceived
(
byte
[]
buffer
,
int
offset
,
int
count
)
{
{
DataReceived
?.
Invoke
(
this
,
new
DataEventArgs
(
buffer
,
offset
,
count
));
try
{
DataReceived
?.
Invoke
(
this
,
new
DataEventArgs
(
buffer
,
offset
,
count
));
}
catch
(
Exception
ex
)
{
var
ex2
=
new
Exception
(
"Exception thrown in user event"
,
ex
);
exceptionFunc
(
ex2
);
}
}
}
internal
void
OnMultipartRequestPartSent
(
string
boundary
,
HeaderCollection
headers
)
internal
void
OnMultipartRequestPartSent
(
string
boundary
,
HeaderCollection
headers
)
{
{
MultipartRequestPartSent
?.
Invoke
(
this
,
new
MultipartRequestPartSentEventArgs
(
boundary
,
headers
));
try
{
MultipartRequestPartSent
?.
Invoke
(
this
,
new
MultipartRequestPartSentEventArgs
(
boundary
,
headers
));
}
catch
(
Exception
ex
)
{
var
ex2
=
new
Exception
(
"Exception thrown in user event"
,
ex
);
exceptionFunc
(
ex2
);
}
}
}
/// <summary>
/// <summary>
/// Read response body as byte[] for current response
/// Read response body as byte[] for current response
/// </summary>
/// </summary>
private
async
Task
ReadResponseBody
()
private
async
Task
ReadResponseBody
Async
()
{
{
if
(!
WebSession
.
Request
.
RequestLocked
)
if
(!
WebSession
.
Request
.
RequestLocked
)
{
{
throw
new
Exception
(
"You cannot read the response body before request is made to server."
);
throw
new
Exception
(
"You cannot read the response body before request is made to server."
);
}
}
var
response
=
WebSession
.
Response
;
if
(!
response
.
HasBody
)
{
return
;
}
//If not already read (not cached yet)
//If not already read (not cached yet)
if
(!
WebSession
.
Response
.
IsBodyRead
)
if
(!
response
.
IsBodyRead
)
{
var
body
=
await
ReadBodyAsync
(
WebSession
.
ServerConnection
.
StreamReader
,
response
.
IsChunked
,
response
.
ContentLength
,
response
.
ContentEncoding
,
false
);
response
.
Body
=
body
;
//Now set the flag to true
//So that next time we can deliver body from cache
response
.
IsBodyRead
=
true
;
OnDataReceived
(
body
,
0
,
body
.
Length
);
}
}
private
async
Task
<
byte
[
]>
ReadBodyAsync
(
CustomBinaryReader
reader
,
bool
isChunked
,
long
contentLength
,
string
contentEncoding
,
bool
isRequest
)
{
using
(
var
bodyStream
=
new
MemoryStream
())
{
{
if
(
WebSession
.
Response
.
HasBody
)
var
writer
=
new
HttpWriter
(
bodyStream
,
bufferSize
);
if
(
isRequest
)
{
await
CopyRequestBodyAsync
(
writer
,
true
);
}
else
{
{
using
(
var
responseBodyStream
=
new
MemoryStream
())
await
CopyResponseBodyAsync
(
writer
,
true
);
}
return
await
GetDecompressedBodyAsync
(
contentEncoding
,
bodyStream
.
ToArray
());
}
}
/// <summary>
/// This is called when the request is PUT/POST/PATCH to read the body
/// </summary>
/// <returns></returns>
internal
async
Task
CopyRequestBodyAsync
(
HttpWriter
writer
,
bool
removeChunkedEncoding
)
{
// End the operation
var
request
=
WebSession
.
Request
;
var
reader
=
ProxyClient
.
ClientStreamReader
;
long
contentLength
=
request
.
ContentLength
;
//send the request body bytes to server
if
(
contentLength
>
0
&&
hasMulipartEventSubscribers
&&
request
.
IsMultipartFormData
)
{
string
boundary
=
HttpHelper
.
GetBoundaryFromContentType
(
request
.
ContentType
);
using
(
var
copyStream
=
new
CopyStream
(
reader
,
writer
,
bufferSize
))
using
(
var
copyStreamReader
=
new
CustomBinaryReader
(
copyStream
,
bufferSize
))
{
while
(
contentLength
>
copyStream
.
ReadBytes
)
{
{
//If chuncked the read chunk by chunk until we hit chunk end symbol
long
read
=
await
ReadUntilBoundaryAsync
(
copyStreamReader
,
contentLength
,
boundary
);
if
(
WebSession
.
Response
.
IsChunked
)
if
(
read
==
0
)
{
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStreamChunked
(
responseBodyStream
)
;
break
;
}
}
else
if
(
contentLength
>
copyStream
.
ReadBytes
)
{
{
if
(
WebSession
.
Response
.
ContentLength
>
0
)
var
headers
=
new
HeaderCollection
();
await
HeaderParser
.
ReadHeaders
(
copyStreamReader
,
headers
);
OnMultipartRequestPartSent
(
boundary
,
headers
);
}
}
await
copyStream
.
FlushAsync
();
}
}
else
{
await
writer
.
CopyBodyAsync
(
reader
,
request
.
IsChunked
,
contentLength
,
removeChunkedEncoding
);
}
}
internal
async
Task
CopyResponseBodyAsync
(
HttpWriter
writer
,
bool
removeChunkedEncoding
)
{
var
response
=
WebSession
.
Response
;
var
reader
=
WebSession
.
ServerConnection
.
StreamReader
;
await
writer
.
CopyBodyAsync
(
reader
,
response
.
IsChunked
,
response
.
ContentLength
,
removeChunkedEncoding
);
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
private
async
Task
<
long
>
ReadUntilBoundaryAsync
(
CustomBinaryReader
reader
,
long
totalBytesToRead
,
string
boundary
)
{
int
bufferDataLength
=
0
;
var
buffer
=
BufferPool
.
GetBuffer
(
bufferSize
);
try
{
int
boundaryLength
=
boundary
.
Length
+
4
;
long
bytesRead
=
0
;
while
(
bytesRead
<
totalBytesToRead
&&
(
reader
.
DataAvailable
||
await
reader
.
FillBufferAsync
()))
{
byte
newChar
=
reader
.
ReadByteFromBuffer
();
buffer
[
bufferDataLength
]
=
newChar
;
bufferDataLength
++;
bytesRead
++;
if
(
bufferDataLength
>=
boundaryLength
)
{
int
startIdx
=
bufferDataLength
-
boundaryLength
;
if
(
buffer
[
startIdx
]
==
'-'
&&
buffer
[
startIdx
+
1
]
==
'-'
)
{
startIdx
+=
2
;
bool
ok
=
true
;
for
(
int
i
=
0
;
i
<
boundary
.
Length
;
i
++)
{
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
if
(
buffer
[
startIdx
+
i
]
!=
boundary
[
i
])
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
responseBodyStream
,
WebSession
.
Response
.
ContentLength
);
{
ok
=
false
;
break
;
}
}
}
else
if
(
WebSession
.
Response
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Response
.
HttpVersion
.
Minor
==
0
||
WebSession
.
Response
.
ContentLength
==
-
1
)
if
(
ok
)
{
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
responseBodyStream
,
long
.
MaxValue
)
;
break
;
}
}
}
}
}
WebSession
.
Response
.
Body
=
await
GetDecompressedResponseBody
(
WebSession
.
Response
.
ContentEncoding
,
responseBodyStream
.
ToArray
());
if
(
bufferDataLength
==
buffer
.
Length
)
{
//boundary is not longer than 70 bytes according to the specification, so keeping the last 100 (minimum 74) bytes is enough
const
int
bytesToKeep
=
100
;
Buffer
.
BlockCopy
(
buffer
,
buffer
.
Length
-
bytesToKeep
,
buffer
,
0
,
bytesToKeep
);
bufferDataLength
=
bytesToKeep
;
}
}
}
}
else
{
WebSession
.
Response
.
Body
=
new
byte
[
0
];
}
//set this to true for caching
return
bytesRead
;
WebSession
.
Response
.
IsBodyRead
=
true
;
}
var
body
=
WebSession
.
Response
.
Body
;
finally
OnDataReceived
(
body
,
0
,
body
.
Length
);
{
BufferPool
.
ReturnBuffer
(
buffer
);
}
}
}
}
...
@@ -259,7 +380,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -259,7 +380,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
{
if
(!
WebSession
.
Request
.
IsBodyRead
)
if
(!
WebSession
.
Request
.
IsBodyRead
)
{
{
await
ReadRequestBody
();
await
ReadRequestBody
Async
();
}
}
return
WebSession
.
Request
.
Body
;
return
WebSession
.
Request
.
Body
;
...
@@ -273,7 +394,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -273,7 +394,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
{
if
(!
WebSession
.
Request
.
IsBodyRead
)
if
(!
WebSession
.
Request
.
IsBodyRead
)
{
{
await
ReadRequestBody
();
await
ReadRequestBody
Async
();
}
}
return
WebSession
.
Request
.
BodyString
;
return
WebSession
.
Request
.
BodyString
;
...
@@ -293,7 +414,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -293,7 +414,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
.
IsBodyRead
)
if
(!
WebSession
.
Request
.
IsBodyRead
)
{
{
await
ReadRequestBody
();
await
ReadRequestBody
Async
();
}
}
WebSession
.
Request
.
Body
=
body
;
WebSession
.
Request
.
Body
=
body
;
...
@@ -314,7 +435,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -314,7 +435,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
.
IsBodyRead
)
if
(!
WebSession
.
Request
.
IsBodyRead
)
{
{
await
ReadRequestBody
();
await
ReadRequestBody
Async
();
}
}
await
SetRequestBody
(
WebSession
.
Request
.
Encoding
.
GetBytes
(
body
));
await
SetRequestBody
(
WebSession
.
Request
.
Encoding
.
GetBytes
(
body
));
...
@@ -328,7 +449,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -328,7 +449,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
{
if
(!
WebSession
.
Response
.
IsBodyRead
)
if
(!
WebSession
.
Response
.
IsBodyRead
)
{
{
await
ReadResponseBody
();
await
ReadResponseBody
Async
();
}
}
return
WebSession
.
Response
.
Body
;
return
WebSession
.
Response
.
Body
;
...
@@ -342,7 +463,7 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -342,7 +463,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
{
if
(!
WebSession
.
Response
.
IsBodyRead
)
if
(!
WebSession
.
Response
.
IsBodyRead
)
{
{
await
ReadResponseBody
();
await
ReadResponseBody
Async
();
}
}
return
WebSession
.
Response
.
BodyString
;
return
WebSession
.
Response
.
BodyString
;
...
@@ -400,12 +521,12 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -400,12 +521,12 @@ namespace Titanium.Web.Proxy.EventArguments
await
SetResponseBody
(
bodyBytes
);
await
SetResponseBody
(
bodyBytes
);
}
}
private
async
Task
<
byte
[
]>
GetDecompressed
ResponseBody
(
string
encodingType
,
byte
[]
responseB
odyStream
)
private
async
Task
<
byte
[
]>
GetDecompressed
BodyAsync
(
string
encodingType
,
byte
[]
b
odyStream
)
{
{
var
decompressionFactory
=
new
DecompressionFactory
();
var
decompressionFactory
=
new
DecompressionFactory
();
var
decompressor
=
decompressionFactory
.
Create
(
encodingType
);
var
decompressor
=
decompressionFactory
.
Create
(
encodingType
);
return
await
decompressor
.
Decompress
(
responseB
odyStream
,
bufferSize
);
return
await
decompressor
.
Decompress
(
b
odyStream
,
bufferSize
);
}
}
/// <summary>
/// <summary>
...
@@ -493,8 +614,8 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -493,8 +614,8 @@ namespace Titanium.Web.Proxy.EventArguments
{
{
var
response
=
new
RedirectResponse
();
var
response
=
new
RedirectResponse
();
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
Headers
.
AddHeader
(
"Location"
,
url
);
response
.
Headers
.
AddHeader
(
KnownHeaders
.
Location
,
url
);
response
.
Body
=
new
byte
[
0
]
;
response
.
Body
=
emptyData
;
await
Respond
(
response
);
await
Respond
(
response
);
}
}
...
@@ -527,6 +648,10 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -527,6 +648,10 @@ namespace Titanium.Web.Proxy.EventArguments
httpResponseHandler
=
null
;
httpResponseHandler
=
null
;
CustomUpStreamProxyUsed
=
null
;
CustomUpStreamProxyUsed
=
null
;
DataSent
=
null
;
DataReceived
=
null
;
MultipartRequestPartSent
=
null
;
WebSession
.
FinishSession
();
WebSession
.
FinishSession
();
}
}
}
}
...
...
Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs
View file @
80dd44e5
using
Titanium.Web.Proxy.Http
;
using
System
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
namespace
Titanium.Web.Proxy.EventArguments
namespace
Titanium.Web.Proxy.EventArguments
...
@@ -7,8 +8,8 @@ namespace Titanium.Web.Proxy.EventArguments
...
@@ -7,8 +8,8 @@ namespace Titanium.Web.Proxy.EventArguments
{
{
public
bool
IsHttpsConnect
{
get
;
set
;
}
public
bool
IsHttpsConnect
{
get
;
set
;
}
internal
TunnelConnectSessionEventArgs
(
int
bufferSize
,
ProxyEndPoint
endPoint
,
ConnectRequest
connectRequest
)
internal
TunnelConnectSessionEventArgs
(
int
bufferSize
,
ProxyEndPoint
endPoint
,
ConnectRequest
connectRequest
,
Action
<
Exception
>
exceptionFunc
)
:
base
(
bufferSize
,
endPoint
,
null
)
:
base
(
bufferSize
,
endPoint
,
null
,
exceptionFunc
)
{
{
WebSession
.
Request
=
connectRequest
;
WebSession
.
Request
=
connectRequest
;
}
}
...
...
Titanium.Web.Proxy/Extensions/FuncExtensions.cs
View file @
80dd44e5
using
System
;
using
System
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.EventArguments
;
namespace
Titanium.Web.Proxy.Extensions
namespace
Titanium.Web.Proxy.Extensions
{
{
internal
static
class
FuncExtensions
internal
static
class
FuncExtensions
{
{
public
static
void
InvokeParallel
<
T
>(
this
Func
<
object
,
T
,
Task
>
callback
,
object
sender
,
T
args
)
public
static
void
InvokeParallel
<
T
>(
this
AsyncEventHandler
<
T
>
callback
,
object
sender
,
T
args
)
{
{
var
invocationList
=
callback
.
GetInvocationList
();
var
invocationList
=
callback
.
GetInvocationList
();
var
handlerTasks
=
new
Task
[
invocationList
.
Length
];
var
handlerTasks
=
new
Task
[
invocationList
.
Length
];
for
(
int
i
=
0
;
i
<
invocationList
.
Length
;
i
++)
for
(
int
i
=
0
;
i
<
invocationList
.
Length
;
i
++)
{
{
handlerTasks
[
i
]
=
((
Func
<
object
,
T
,
Task
>)
invocationList
[
i
])(
sender
,
args
);
handlerTasks
[
i
]
=
((
AsyncEventHandler
<
T
>)
invocationList
[
i
])(
sender
,
args
);
}
}
Task
.
WhenAll
(
handlerTasks
).
Wait
();
Task
.
WhenAll
(
handlerTasks
).
Wait
();
}
}
public
static
async
Task
InvokeParallelAsync
<
T
>(
this
Func
<
object
,
T
,
Task
>
callback
,
object
sender
,
T
args
,
Action
<
Exception
>
exceptionFunc
)
public
static
async
Task
InvokeParallelAsync
<
T
>(
this
AsyncEventHandler
<
T
>
callback
,
object
sender
,
T
args
,
Action
<
Exception
>
exceptionFunc
)
{
{
var
invocationList
=
callback
.
GetInvocationList
();
var
invocationList
=
callback
.
GetInvocationList
();
var
handlerTasks
=
new
Task
[
invocationList
.
Length
];
var
handlerTasks
=
new
Task
[
invocationList
.
Length
];
for
(
int
i
=
0
;
i
<
invocationList
.
Length
;
i
++)
for
(
int
i
=
0
;
i
<
invocationList
.
Length
;
i
++)
{
{
handlerTasks
[
i
]
=
In
vokeAsync
((
Func
<
object
,
T
,
Task
>)
invocationList
[
i
],
sender
,
args
,
exceptionFunc
);
handlerTasks
[
i
]
=
In
ternalInvokeAsync
((
AsyncEventHandler
<
T
>)
invocationList
[
i
],
sender
,
args
,
exceptionFunc
);
}
}
await
Task
.
WhenAll
(
handlerTasks
);
await
Task
.
WhenAll
(
handlerTasks
);
}
}
private
static
async
Task
InvokeAsync
<
T
>(
Func
<
object
,
T
,
Task
>
callback
,
object
sender
,
T
args
,
Action
<
Exception
>
exceptionFunc
)
public
static
async
Task
InvokeAsync
<
T
>(
this
AsyncEventHandler
<
T
>
callback
,
object
sender
,
T
args
,
Action
<
Exception
>
exceptionFunc
)
{
var
invocationList
=
callback
.
GetInvocationList
();
for
(
int
i
=
0
;
i
<
invocationList
.
Length
;
i
++)
{
await
InternalInvokeAsync
((
AsyncEventHandler
<
T
>)
invocationList
[
i
],
sender
,
args
,
exceptionFunc
);
}
}
private
static
async
Task
InternalInvokeAsync
<
T
>(
AsyncEventHandler
<
T
>
callback
,
object
sender
,
T
args
,
Action
<
Exception
>
exceptionFunc
)
{
{
try
try
{
{
...
...
Titanium.Web.Proxy/Extensions/SslExtensions.cs
0 → 100644
View file @
80dd44e5
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Text
;
using
System.Threading.Tasks
;
using
StreamExtended
;
namespace
Titanium.Web.Proxy.Extensions
{
static
class
SslExtensions
{
public
static
string
GetServerName
(
this
ClientHelloInfo
clientHelloInfo
)
{
if
(
clientHelloInfo
.
Extensions
!=
null
&&
clientHelloInfo
.
Extensions
.
TryGetValue
(
"server_name"
,
out
var
serverNameExtension
))
{
return
serverNameExtension
.
Data
;
}
return
null
;
}
}
}
Titanium.Web.Proxy/Extensions/StreamExtensions.cs
View file @
80dd44e5
using
System
;
using
System
;
using
System.Globalization
;
using
System.IO
;
using
System.IO
;
using
System.Threading
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
StreamExtended.
Network
;
using
StreamExtended.
Helpers
;
namespace
Titanium.Web.Proxy.Extensions
namespace
Titanium.Web.Proxy.Extensions
{
{
...
@@ -18,83 +18,59 @@ namespace Titanium.Web.Proxy.Extensions
...
@@ -18,83 +18,59 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="output"></param>
/// <param name="output"></param>
/// <param name="onCopy"></param>
/// <param name="onCopy"></param>
/// <param name="bufferSize"></param>
/// <param name="bufferSize"></param>
internal
static
async
Task
CopyToAsync
(
this
Stream
input
,
Stream
output
,
Action
<
byte
[],
int
,
int
>
onCopy
,
int
bufferSize
)
internal
static
Task
CopyToAsync
(
this
Stream
input
,
Stream
output
,
Action
<
byte
[],
int
,
int
>
onCopy
,
int
bufferSize
)
{
{
byte
[]
buffer
=
new
byte
[
bufferSize
];
return
CopyToAsync
(
input
,
output
,
onCopy
,
bufferSize
,
CancellationToken
.
None
);
while
(
true
)
{
int
num
=
await
input
.
ReadAsync
(
buffer
,
0
,
buffer
.
Length
).
ConfigureAwait
(
false
);
int
bytesRead
;
if
((
bytesRead
=
num
)
!=
0
)
{
await
output
.
WriteAsync
(
buffer
,
0
,
bytesRead
).
ConfigureAwait
(
false
);
onCopy
?.
Invoke
(
buffer
,
0
,
bytesRead
);
}
else
{
break
;
}
}
}
}
/// <summary>
/// <summary>
///
copies the specified bytes to the stream from the input stream
///
Copy streams asynchronously
/// </summary>
/// </summary>
/// <param name="streamReader"></param>
/// <param name="input"></param>
/// <param name="stream"></param>
/// <param name="output"></param>
/// <param name="totalBytesToRead"></param>
/// <param name="onCopy"></param>
/// <returns></returns>
/// <param name="bufferSize"></param>
internal
static
async
Task
CopyBytesToStream
(
this
CustomBinaryReader
streamReader
,
Stream
stream
,
long
totalBytesToRead
)
/// <param name="cancellationToken"></param>
internal
static
async
Task
CopyToAsync
(
this
Stream
input
,
Stream
output
,
Action
<
byte
[],
int
,
int
>
onCopy
,
int
bufferSize
,
CancellationToken
cancellationToken
)
{
{
var
buffer
=
streamReader
.
Buffer
;
byte
[]
buffer
=
BufferPool
.
GetBuffer
(
bufferSize
);
long
remainingBytes
=
totalBytesToRead
;
try
while
(
remainingBytes
>
0
)
{
{
int
bytesToRead
=
buffer
.
Length
;
while
(!
cancellationToken
.
IsCancellationRequested
)
if
(
remainingBytes
<
bytesToRead
)
{
bytesToRead
=
(
int
)
remainingBytes
;
}
int
bytesRead
=
await
streamReader
.
ReadBytesAsync
(
buffer
,
bytesToRead
);
if
(
bytesRead
==
0
)
{
{
break
;
// cancellation is not working on Socket ReadAsync
// https://github.com/dotnet/corefx/issues/15033
int
num
=
await
input
.
ReadAsync
(
buffer
,
0
,
buffer
.
Length
,
CancellationToken
.
None
).
WithCancellation
(
cancellationToken
);
int
bytesRead
;
if
((
bytesRead
=
num
)
!=
0
&&
!
cancellationToken
.
IsCancellationRequested
)
{
await
output
.
WriteAsync
(
buffer
,
0
,
bytesRead
,
CancellationToken
.
None
);
onCopy
?.
Invoke
(
buffer
,
0
,
bytesRead
);
}
else
{
break
;
}
}
}
}
remainingBytes
-=
bytesRead
;
finally
{
await
stream
.
WriteAsync
(
buffer
,
0
,
bytesRead
);
BufferPool
.
ReturnBuffer
(
buffer
);
}
}
}
}
/// <summary>
private
static
async
Task
<
T
>
WithCancellation
<
T
>(
this
Task
<
T
>
task
,
CancellationToken
cancellationToken
)
/// 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
)
{
{
while
(
true
)
var
tcs
=
new
TaskCompletionSource
<
bool
>();
using
(
cancellationToken
.
Register
(
s
=>
((
TaskCompletionSource
<
bool
>)
s
).
TrySetResult
(
true
),
tcs
))
{
{
string
chuchkHead
=
await
clientStreamReader
.
ReadLineAsync
();
if
(
task
!=
await
Task
.
WhenAny
(
task
,
tcs
.
Task
))
int
chunkSize
=
int
.
Parse
(
chuchkHead
,
NumberStyles
.
HexNumber
);
if
(
chunkSize
!=
0
)
{
await
CopyBytesToStream
(
clientStreamReader
,
stream
,
chunkSize
);
//chunk trail
await
clientStreamReader
.
ReadLineAsync
();
}
else
{
{
await
clientStreamReader
.
ReadLineAsync
();
return
default
(
T
);
break
;
}
}
}
}
return
await
task
;
}
}
}
}
}
}
Titanium.Web.Proxy/Extensions/TcpExtensions.cs
View file @
80dd44e5
using
Titanium.Web.Proxy.Helpers
;
using
System
;
using
System.Net.Sockets
;
using
System.Reflection
;
using
Titanium.Web.Proxy.Helpers
;
namespace
Titanium.Web.Proxy.Extensions
namespace
Titanium.Web.Proxy.Extensions
{
{
internal
static
class
TcpExtensions
internal
static
class
TcpExtensions
{
{
private
static
readonly
Func
<
Socket
,
bool
>
socketCleanedUpGetter
;
static
TcpExtensions
()
{
var
property
=
typeof
(
Socket
).
GetProperty
(
"CleanedUp"
,
BindingFlags
.
NonPublic
|
BindingFlags
.
Instance
);
if
(
property
!=
null
)
{
var
method
=
property
.
GetMethod
;
if
(
method
!=
null
&&
method
.
ReturnType
==
typeof
(
bool
))
{
socketCleanedUpGetter
=
(
Func
<
Socket
,
bool
>)
Delegate
.
CreateDelegate
(
typeof
(
Func
<
Socket
,
bool
>),
method
);
}
}
}
/// <summary>
/// <summary>
/// Gets the local port from a native TCP row object.
/// Gets the local port from a native TCP row object.
...
@@ -24,5 +41,30 @@ namespace Titanium.Web.Proxy.Extensions
...
@@ -24,5 +41,30 @@ namespace Titanium.Web.Proxy.Extensions
{
{
return
(
tcpRow
.
remotePort1
<<
8
)
+
tcpRow
.
remotePort2
+
(
tcpRow
.
remotePort3
<<
24
)
+
(
tcpRow
.
remotePort4
<<
16
);
return
(
tcpRow
.
remotePort1
<<
8
)
+
tcpRow
.
remotePort2
+
(
tcpRow
.
remotePort3
<<
24
)
+
(
tcpRow
.
remotePort4
<<
16
);
}
}
internal
static
void
CloseSocket
(
this
TcpClient
tcpClient
)
{
if
(
tcpClient
==
null
)
{
return
;
}
try
{
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
if
(
socketCleanedUpGetter
==
null
||
!
socketCleanedUpGetter
(
tcpClient
.
Client
))
{
tcpClient
.
LingerState
=
new
LingerOption
(
true
,
0
);
}
tcpClient
.
Close
();
}
catch
{
}
}
}
}
}
}
Titanium.Web.Proxy/Helpers/HttpHelper.cs
View file @
80dd44e5
using
System
;
using
System
;
using
System.Text
;
using
System.Text
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Shared
;
using
Titanium.Web.Proxy.Shared
;
namespace
Titanium.Web.Proxy.Helpers
namespace
Titanium.Web.Proxy.Helpers
...
@@ -28,7 +29,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -28,7 +29,7 @@ namespace Titanium.Web.Proxy.Helpers
foreach
(
string
parameter
in
parameters
)
foreach
(
string
parameter
in
parameters
)
{
{
var
split
=
parameter
.
Split
(
ProxyConstants
.
EqualSplit
,
2
);
var
split
=
parameter
.
Split
(
ProxyConstants
.
EqualSplit
,
2
);
if
(
split
.
Length
==
2
&&
split
[
0
].
Trim
().
Equals
(
"charset"
,
StringComparison
.
CurrentCultureIgnoreCase
))
if
(
split
.
Length
==
2
&&
split
[
0
].
Trim
().
Equals
(
KnownHeaders
.
ContentTypeCharset
,
StringComparison
.
CurrentCultureIgnoreCase
))
{
{
string
value
=
split
[
1
];
string
value
=
split
[
1
];
if
(
value
.
Equals
(
"x-user-defined"
,
StringComparison
.
OrdinalIgnoreCase
))
if
(
value
.
Equals
(
"x-user-defined"
,
StringComparison
.
OrdinalIgnoreCase
))
...
@@ -65,7 +66,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -65,7 +66,7 @@ namespace Titanium.Web.Proxy.Helpers
foreach
(
string
parameter
in
parameters
)
foreach
(
string
parameter
in
parameters
)
{
{
var
split
=
parameter
.
Split
(
ProxyConstants
.
EqualSplit
,
2
);
var
split
=
parameter
.
Split
(
ProxyConstants
.
EqualSplit
,
2
);
if
(
split
.
Length
==
2
&&
split
[
0
].
Trim
().
Equals
(
"boundary"
,
StringComparison
.
CurrentCultureIgnoreCase
))
if
(
split
.
Length
==
2
&&
split
[
0
].
Trim
().
Equals
(
KnownHeaders
.
ContentTypeBoundary
,
StringComparison
.
CurrentCultureIgnoreCase
))
{
{
string
value
=
split
[
1
];
string
value
=
split
[
1
];
if
(
value
.
Length
>
2
&&
value
[
0
]
==
'"'
&&
value
[
value
.
Length
-
1
]
==
'"'
)
if
(
value
.
Length
>
2
&&
value
[
0
]
==
'"'
&&
value
[
value
.
Length
-
1
]
==
'"'
)
...
@@ -97,6 +98,13 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -97,6 +98,13 @@ namespace Titanium.Web.Proxy.Helpers
if
(
hostname
.
Split
(
ProxyConstants
.
DotSplit
).
Length
>
2
)
if
(
hostname
.
Split
(
ProxyConstants
.
DotSplit
).
Length
>
2
)
{
{
int
idx
=
hostname
.
IndexOf
(
ProxyConstants
.
DotSplit
);
int
idx
=
hostname
.
IndexOf
(
ProxyConstants
.
DotSplit
);
//issue #352
if
(
hostname
.
Substring
(
0
,
idx
).
Contains
(
"-"
))
{
return
hostname
;
}
string
rootDomain
=
hostname
.
Substring
(
idx
+
1
);
string
rootDomain
=
hostname
.
Substring
(
idx
+
1
);
return
"*."
+
rootDomain
;
return
"*."
+
rootDomain
;
}
}
...
...
Titanium.Web.Proxy/Helpers/HttpRequestWriter.cs
View file @
80dd44e5
...
@@ -4,8 +4,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -4,8 +4,7 @@ namespace Titanium.Web.Proxy.Helpers
{
{
sealed
class
HttpRequestWriter
:
HttpWriter
sealed
class
HttpRequestWriter
:
HttpWriter
{
{
public
HttpRequestWriter
(
Stream
stream
,
int
bufferSize
)
public
HttpRequestWriter
(
Stream
stream
,
int
bufferSize
)
:
base
(
stream
,
bufferSize
)
:
base
(
stream
,
bufferSize
,
true
)
{
{
}
}
}
}
...
...
Titanium.Web.Proxy/Helpers/HttpResponseWriter.cs
View file @
80dd44e5
using
System
;
using
System
;
using
System.Globalization
;
using
System.IO
;
using
System.IO
;
using
System.Text
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
StreamExtended.Network
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Shared
;
namespace
Titanium.Web.Proxy.Helpers
namespace
Titanium.Web.Proxy.Helpers
{
{
sealed
class
HttpResponseWriter
:
HttpWriter
sealed
class
HttpResponseWriter
:
HttpWriter
{
{
public
HttpResponseWriter
(
Stream
stream
,
int
bufferSize
)
public
HttpResponseWriter
(
Stream
stream
,
int
bufferSize
)
:
base
(
stream
,
bufferSize
)
:
base
(
stream
,
bufferSize
,
true
)
{
{
}
}
...
@@ -39,102 +33,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -39,102 +33,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns>
/// <returns></returns>
public
Task
WriteResponseStatusAsync
(
Version
version
,
int
code
,
string
description
)
public
Task
WriteResponseStatusAsync
(
Version
version
,
int
code
,
string
description
)
{
{
return
WriteLineAsync
(
$"HTTP/
{
version
.
Major
}
.
{
version
.
Minor
}
{
code
}
{
description
}
"
);
return
WriteLineAsync
(
Response
.
CreateResponseLine
(
version
,
code
,
description
));
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal
async
Task
WriteResponseBodyAsync
(
byte
[]
data
,
bool
isChunked
)
{
if
(!
isChunked
)
{
await
BaseStream
.
WriteAsync
(
data
,
0
,
data
.
Length
);
}
else
{
await
WriteResponseBodyChunkedAsync
(
data
);
}
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="inStreamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <returns></returns>
internal
async
Task
WriteResponseBodyAsync
(
int
bufferSize
,
CustomBinaryReader
inStreamReader
,
bool
isChunked
,
long
contentLength
)
{
if
(!
isChunked
)
{
//http 1.0
if
(
contentLength
==
-
1
)
{
contentLength
=
long
.
MaxValue
;
}
await
inStreamReader
.
CopyBytesToStream
(
BaseStream
,
contentLength
);
}
else
{
await
WriteResponseBodyChunkedAsync
(
inStreamReader
);
}
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <returns></returns>
internal
async
Task
WriteResponseBodyChunkedAsync
(
CustomBinaryReader
inStreamReader
)
{
while
(
true
)
{
string
chunkHead
=
await
inStreamReader
.
ReadLineAsync
();
int
chunkSize
=
int
.
Parse
(
chunkHead
,
NumberStyles
.
HexNumber
);
if
(
chunkSize
!=
0
)
{
var
chunkHeadBytes
=
Encoding
.
ASCII
.
GetBytes
(
chunkSize
.
ToString
(
"x2"
));
await
BaseStream
.
WriteAsync
(
chunkHeadBytes
,
0
,
chunkHeadBytes
.
Length
);
await
BaseStream
.
WriteAsync
(
ProxyConstants
.
NewLineBytes
,
0
,
ProxyConstants
.
NewLineBytes
.
Length
);
await
inStreamReader
.
CopyBytesToStream
(
BaseStream
,
chunkSize
);
await
BaseStream
.
WriteAsync
(
ProxyConstants
.
NewLineBytes
,
0
,
ProxyConstants
.
NewLineBytes
.
Length
);
await
inStreamReader
.
ReadLineAsync
();
}
else
{
await
inStreamReader
.
ReadLineAsync
();
await
BaseStream
.
WriteAsync
(
ProxyConstants
.
ChunkEnd
,
0
,
ProxyConstants
.
ChunkEnd
.
Length
);
break
;
}
}
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
internal
async
Task
WriteResponseBodyChunkedAsync
(
byte
[]
data
)
{
var
chunkHead
=
Encoding
.
ASCII
.
GetBytes
(
data
.
Length
.
ToString
(
"x2"
));
await
BaseStream
.
WriteAsync
(
chunkHead
,
0
,
chunkHead
.
Length
);
await
BaseStream
.
WriteAsync
(
ProxyConstants
.
NewLineBytes
,
0
,
ProxyConstants
.
NewLineBytes
.
Length
);
await
BaseStream
.
WriteAsync
(
data
,
0
,
data
.
Length
);
await
BaseStream
.
WriteAsync
(
ProxyConstants
.
NewLineBytes
,
0
,
ProxyConstants
.
NewLineBytes
.
Length
);
await
BaseStream
.
WriteAsync
(
ProxyConstants
.
ChunkEnd
,
0
,
ProxyConstants
.
ChunkEnd
.
Length
);
}
}
}
}
}
}
Titanium.Web.Proxy/Helpers/HttpWriter.cs
View file @
80dd44e5
using
System.IO
;
using
System
;
using
System.Globalization
;
using
System.IO
;
using
System.Text
;
using
System.Text
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
StreamExtended.Helpers
;
using
StreamExtended.Network
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Shared
;
using
Titanium.Web.Proxy.Shared
;
namespace
Titanium.Web.Proxy.Helpers
namespace
Titanium.Web.Proxy.Helpers
{
{
abstract
class
HttpWriter
:
Stream
Writer
internal
class
HttpWriter
:
CustomBinary
Writer
{
{
protected
HttpWriter
(
Stream
stream
,
int
bufferSize
,
bool
leaveOpen
)
public
int
BufferSize
{
get
;
}
:
base
(
stream
,
Encoding
.
ASCII
,
bufferSize
,
leaveOpen
)
private
readonly
char
[]
charBuffer
;
private
static
readonly
byte
[]
newLine
=
ProxyConstants
.
NewLine
;
private
static
readonly
Encoder
encoder
=
Encoding
.
ASCII
.
GetEncoder
();
internal
HttpWriter
(
Stream
stream
,
int
bufferSize
)
:
base
(
stream
)
{
{
NewLine
=
ProxyConstants
.
NewLine
;
BufferSize
=
bufferSize
;
// ASCII encoder max byte count is char count + 1
charBuffer
=
new
char
[
BufferSize
-
1
];
}
}
public
void
WriteHeaders
(
HeaderCollection
headers
,
bool
flush
=
true
)
public
Task
WriteLineAsync
(
)
{
{
if
(
headers
!=
null
)
return
WriteAsync
(
newLine
);
}
public
async
Task
WriteAsync
(
string
value
)
{
int
charCount
=
value
.
Length
;
value
.
CopyTo
(
0
,
charBuffer
,
0
,
charCount
);
if
(
charCount
<
BufferSize
)
{
{
foreach
(
var
header
in
headers
)
var
buffer
=
BufferPool
.
GetBuffer
(
BufferSize
);
try
{
{
header
.
WriteToStream
(
this
);
int
idx
=
encoder
.
GetBytes
(
charBuffer
,
0
,
charCount
,
buffer
,
0
,
true
);
await
WriteAsync
(
buffer
,
0
,
idx
);
}
finally
{
BufferPool
.
ReturnBuffer
(
buffer
);
}
}
}
}
else
WriteLine
();
if
(
flush
)
{
{
Flush
();
var
buffer
=
new
byte
[
charCount
+
1
];
int
idx
=
encoder
.
GetBytes
(
charBuffer
,
0
,
charCount
,
buffer
,
0
,
true
);
await
WriteAsync
(
buffer
,
0
,
idx
);
}
}
}
}
public
async
Task
WriteLineAsync
(
string
value
)
{
await
WriteAsync
(
value
);
await
WriteLineAsync
();
}
/// <summary>
/// <summary>
/// Write the headers to client
/// Write the headers to client
/// </summary>
/// </summary>
...
@@ -50,8 +84,161 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -50,8 +84,161 @@ namespace Titanium.Web.Proxy.Helpers
await
WriteLineAsync
();
await
WriteLineAsync
();
if
(
flush
)
if
(
flush
)
{
{
// flush the stream
await
FlushAsync
();
}
}
public
async
Task
WriteAsync
(
byte
[]
data
,
bool
flush
=
false
)
{
await
WriteAsync
(
data
,
0
,
data
.
Length
);
if
(
flush
)
{
// flush the stream
await
FlushAsync
();
await
FlushAsync
();
}
}
}
}
public
async
Task
WriteAsync
(
byte
[]
data
,
int
offset
,
int
count
,
bool
flush
)
{
await
WriteAsync
(
data
,
offset
,
count
);
if
(
flush
)
{
// flush the stream
await
FlushAsync
();
}
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal
Task
WriteBodyAsync
(
byte
[]
data
,
bool
isChunked
)
{
if
(
isChunked
)
{
return
WriteBodyChunkedAsync
(
data
);
}
return
WriteAsync
(
data
);
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="streamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <param name="removeChunkedEncoding"></param>
/// <returns></returns>
internal
Task
CopyBodyAsync
(
CustomBinaryReader
streamReader
,
bool
isChunked
,
long
contentLength
,
bool
removeChunkedEncoding
)
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if
(
isChunked
)
{
//Need to revist, find any potential bugs
//send the body bytes to server in chunks
return
CopyBodyChunkedAsync
(
streamReader
,
removeChunkedEncoding
);
}
//http 1.0
if
(
contentLength
==
-
1
)
{
contentLength
=
long
.
MaxValue
;
}
//If not chunked then its easy just read the amount of bytes mentioned in content length header
return
CopyBytesFromStream
(
streamReader
,
contentLength
);
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private
async
Task
WriteBodyChunkedAsync
(
byte
[]
data
)
{
var
chunkHead
=
Encoding
.
ASCII
.
GetBytes
(
data
.
Length
.
ToString
(
"x2"
));
await
WriteAsync
(
chunkHead
);
await
WriteLineAsync
();
await
WriteAsync
(
data
);
await
WriteLineAsync
();
await
WriteLineAsync
(
"0"
);
await
WriteLineAsync
();
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="reader"></param>
/// <param name="removeChunkedEncoding"></param>
/// <returns></returns>
private
async
Task
CopyBodyChunkedAsync
(
CustomBinaryReader
reader
,
bool
removeChunkedEncoding
)
{
while
(
true
)
{
string
chunkHead
=
await
reader
.
ReadLineAsync
();
int
chunkSize
=
int
.
Parse
(
chunkHead
,
NumberStyles
.
HexNumber
);
if
(!
removeChunkedEncoding
)
{
await
WriteLineAsync
(
chunkHead
);
}
if
(
chunkSize
!=
0
)
{
await
CopyBytesFromStream
(
reader
,
chunkSize
);
}
if
(!
removeChunkedEncoding
)
{
await
WriteLineAsync
();
}
//chunk trail
await
reader
.
ReadLineAsync
();
if
(
chunkSize
==
0
)
{
break
;
}
}
}
/// <summary>
/// Copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="reader"></param>
/// <param name="count"></param>
/// <returns></returns>
private
async
Task
CopyBytesFromStream
(
CustomBinaryReader
reader
,
long
count
)
{
var
buffer
=
reader
.
Buffer
;
long
remainingBytes
=
count
;
while
(
remainingBytes
>
0
)
{
int
bytesToRead
=
buffer
.
Length
;
if
(
remainingBytes
<
bytesToRead
)
{
bytesToRead
=
(
int
)
remainingBytes
;
}
int
bytesRead
=
await
reader
.
ReadBytesAsync
(
buffer
,
bytesToRead
);
if
(
bytesRead
==
0
)
{
break
;
}
remainingBytes
-=
bytesRead
;
await
WriteAsync
(
buffer
,
0
,
bytesRead
);
}
}
}
}
}
}
Titanium.Web.Proxy/Helpers/Tcp.cs
View file @
80dd44e5
...
@@ -2,6 +2,7 @@ using System;
...
@@ -2,6 +2,7 @@ using System;
using
System.Collections.Generic
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.IO
;
using
System.Runtime.InteropServices
;
using
System.Runtime.InteropServices
;
using
System.Threading
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Network.Tcp
;
using
Titanium.Web.Proxy.Network.Tcp
;
...
@@ -111,16 +112,21 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -111,16 +112,21 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
/// </summary>
/// <param name="clientStream"></param>
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
/// <param name="serverStream"></param>
/// <param name="bufferSize"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <param name="onDataReceive"></param>
/// <returns></returns>
/// <returns></returns>
internal
static
async
Task
SendRaw
(
Stream
clientStream
,
Stream
serverStream
,
int
bufferSize
,
internal
static
async
Task
SendRaw
(
Stream
clientStream
,
Stream
serverStream
,
int
bufferSize
,
Action
<
byte
[],
int
,
int
>
onDataSend
,
Action
<
byte
[],
int
,
int
>
onDataReceive
)
Action
<
byte
[],
int
,
int
>
onDataSend
,
Action
<
byte
[],
int
,
int
>
onDataReceive
)
{
{
var
cts
=
new
CancellationTokenSource
();
//Now async relay all server=>client & client=>server data
//Now async relay all server=>client & client=>server data
var
sendRelay
=
clientStream
.
CopyToAsync
(
serverStream
,
onDataSend
,
bufferSize
);
var
sendRelay
=
clientStream
.
CopyToAsync
(
serverStream
,
onDataSend
,
bufferSize
,
cts
.
Token
);
var
receiveRelay
=
serverStream
.
CopyToAsync
(
clientStream
,
onDataReceive
,
bufferSize
,
cts
.
Token
);
var
receiveRelay
=
serverStream
.
CopyToAsync
(
clientStream
,
onDataReceive
,
bufferSize
);
await
Task
.
WhenAny
(
sendRelay
,
receiveRelay
);
cts
.
Cancel
();
await
Task
.
WhenAll
(
sendRelay
,
receiveRelay
);
await
Task
.
WhenAll
(
sendRelay
,
receiveRelay
);
}
}
...
...
Titanium.Web.Proxy/Http/ConnectResponse.cs
View file @
80dd44e5
...
@@ -22,7 +22,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -22,7 +22,7 @@ namespace Titanium.Web.Proxy.Http
StatusDescription
=
"Connection Established"
StatusDescription
=
"Connection Established"
};
};
response
.
Headers
.
AddHeader
(
"Timestamp"
,
DateTime
.
Now
.
ToString
());
response
.
Headers
.
AddHeader
(
KnownHeaders
.
Timestamp
,
DateTime
.
Now
.
ToString
());
return
response
;
return
response
;
}
}
}
}
...
...
Titanium.Web.Proxy/Http/HeaderCollection.cs
View file @
80dd44e5
...
@@ -63,6 +63,21 @@ namespace Titanium.Web.Proxy.Http
...
@@ -63,6 +63,21 @@ namespace Titanium.Web.Proxy.Http
return
null
;
return
null
;
}
}
public
HttpHeader
GetFirstHeader
(
string
name
)
{
if
(
Headers
.
TryGetValue
(
name
,
out
var
header
))
{
return
header
;
}
if
(
NonUniqueHeaders
.
TryGetValue
(
name
,
out
var
headers
))
{
return
headers
.
FirstOrDefault
();
}
return
null
;
}
/// <summary>
/// <summary>
/// Returns all headers
/// Returns all headers
/// </summary>
/// </summary>
...
@@ -235,7 +250,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -235,7 +250,7 @@ namespace Titanium.Web.Proxy.Http
return
null
;
return
null
;
}
}
internal
string
SetOrAddHeaderValue
(
string
headerName
,
string
value
)
internal
void
SetOrAddHeaderValue
(
string
headerName
,
string
value
)
{
{
if
(
Headers
.
TryGetValue
(
headerName
,
out
var
header
))
if
(
Headers
.
TryGetValue
(
headerName
,
out
var
header
))
{
{
...
@@ -245,8 +260,6 @@ namespace Titanium.Web.Proxy.Http
...
@@ -245,8 +260,6 @@ namespace Titanium.Web.Proxy.Http
{
{
Headers
.
Add
(
headerName
,
new
HttpHeader
(
headerName
,
value
));
Headers
.
Add
(
headerName
,
new
HttpHeader
(
headerName
,
value
));
}
}
return
null
;
}
}
/// <summary>
/// <summary>
...
@@ -255,12 +268,12 @@ namespace Titanium.Web.Proxy.Http
...
@@ -255,12 +268,12 @@ namespace Titanium.Web.Proxy.Http
internal
void
FixProxyHeaders
()
internal
void
FixProxyHeaders
()
{
{
//If proxy-connection close was returned inform to close the connection
//If proxy-connection close was returned inform to close the connection
string
proxyHeader
=
GetHeaderValueOrNull
(
"proxy-connection"
);
string
proxyHeader
=
GetHeaderValueOrNull
(
KnownHeaders
.
ProxyConnection
);
RemoveHeader
(
"proxy-connection"
);
RemoveHeader
(
KnownHeaders
.
ProxyConnection
);
if
(
proxyHeader
!=
null
)
if
(
proxyHeader
!=
null
)
{
{
SetOrAddHeaderValue
(
"connection"
,
proxyHeader
);
SetOrAddHeaderValue
(
KnownHeaders
.
Connection
,
proxyHeader
);
}
}
}
}
...
...
Titanium.Web.Proxy/Http/HttpWebClient.cs
View file @
80dd44e5
...
@@ -2,7 +2,6 @@ using System;
...
@@ -2,7 +2,6 @@ using System;
using
System.IO
;
using
System.IO
;
using
System.Net
;
using
System.Net
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network.Tcp
;
using
Titanium.Web.Proxy.Network.Tcp
;
...
@@ -74,61 +73,45 @@ namespace Titanium.Web.Proxy.Http
...
@@ -74,61 +73,45 @@ namespace Titanium.Web.Proxy.Http
connection
.
LastAccess
=
DateTime
.
Now
;
connection
.
LastAccess
=
DateTime
.
Now
;
ServerConnection
=
connection
;
ServerConnection
=
connection
;
}
}
/// <summary>
/// <summary>
/// Prepare and send the http(s) request
/// Prepare and send the http(s) request
/// </summary>
/// </summary>
/// <returns></returns>
/// <returns></returns>
internal
async
Task
SendRequest
(
bool
enable100ContinueBehaviour
)
internal
async
Task
SendRequest
(
bool
enable100ContinueBehaviour
)
{
{
var
stream
=
ServerConnection
.
Stream
;
var
upstreamProxy
=
ServerConnection
.
UpStreamProxy
;
byte
[]
requestBytes
;
bool
useUpstreamProxy
=
upstreamProxy
!=
null
&&
ServerConnection
.
IsHttps
==
false
;
using
(
var
ms
=
new
MemoryStream
())
using
(
var
writer
=
new
HttpRequestWriter
(
ms
,
bufferSize
))
{
var
upstreamProxy
=
ServerConnection
.
UpStreamProxy
;
bool
useUpstreamProxy
=
upstreamProxy
!=
null
&&
ServerConnection
.
IsHttps
==
false
;
var
writer
=
ServerConnection
.
StreamWriter
;
//prepare the request & headers
//prepare the request & headers
if
(
useUpstreamProxy
)
await
writer
.
WriteLineAsync
(
Request
.
CreateRequestLine
(
Request
.
Method
,
{
useUpstreamProxy
?
Request
.
OriginalUrl
:
Request
.
RequestUri
.
PathAndQuery
,
writer
.
WriteLine
(
$"
{
Request
.
Method
}
{
Request
.
OriginalUrl
}
HTTP/
{
Request
.
HttpVersion
.
Major
}
.
{
Request
.
HttpVersion
.
Minor
}
"
);
Request
.
HttpVersion
));
}
else
{
writer
.
WriteLine
(
$"
{
Request
.
Method
}
{
Request
.
RequestUri
.
PathAndQuery
}
HTTP/
{
Request
.
HttpVersion
.
Major
}
.
{
Request
.
HttpVersion
.
Minor
}
"
);
}
//Send Authentication to Upstream proxy if needed
//Send Authentication to Upstream proxy if needed
if
(
upstreamProxy
!=
null
if
(
upstreamProxy
!=
null
&&
ServerConnection
.
IsHttps
==
false
&&
ServerConnection
.
IsHttps
==
false
&&
!
string
.
IsNullOrEmpty
(
upstreamProxy
.
UserName
)
&&
!
string
.
IsNullOrEmpty
(
upstreamProxy
.
UserName
)
&&
upstreamProxy
.
Password
!=
null
)
&&
upstreamProxy
.
Password
!=
null
)
{
{
HttpHeader
.
ProxyConnectionKeepAlive
.
WriteToStream
(
writer
);
await
HttpHeader
.
ProxyConnectionKeepAlive
.
WriteToStreamAsync
(
writer
);
HttpHeader
.
GetProxyAuthorizationHeader
(
upstreamProxy
.
UserName
,
upstreamProxy
.
Password
).
WriteToStream
(
writer
);
await
HttpHeader
.
GetProxyAuthorizationHeader
(
upstreamProxy
.
UserName
,
upstreamProxy
.
Password
).
WriteToStreamAsync
(
writer
);
}
}
//write request headers
//write request headers
foreach
(
var
header
in
Request
.
Headers
)
foreach
(
var
header
in
Request
.
Headers
)
{
if
(
header
.
Name
!=
KnownHeaders
.
ProxyAuthorization
)
{
{
if
(
header
.
Name
!=
"Proxy-Authorization"
)
await
header
.
WriteToStreamAsync
(
writer
);
{
header
.
WriteToStream
(
writer
);
}
}
}
writer
.
WriteLine
();
writer
.
Flush
();
requestBytes
=
ms
.
ToArray
();
}
}
await
stream
.
WriteAsync
(
requestBytes
,
0
,
requestBytes
.
Length
);
await
writer
.
WriteLineAsync
();
await
stream
.
FlushAsync
();
if
(
enable100ContinueBehaviour
)
if
(
enable100ContinueBehaviour
)
{
{
...
@@ -191,6 +174,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -191,6 +174,7 @@ namespace Titanium.Web.Proxy.Http
Response
.
Is100Continue
=
true
;
Response
.
Is100Continue
=
true
;
Response
.
StatusCode
=
0
;
Response
.
StatusCode
=
0
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
//now receive response
//now receive response
await
ReceiveResponse
();
await
ReceiveResponse
();
return
;
return
;
...
@@ -203,6 +187,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -203,6 +187,7 @@ namespace Titanium.Web.Proxy.Http
Response
.
ExpectationFailed
=
true
;
Response
.
ExpectationFailed
=
true
;
Response
.
StatusCode
=
0
;
Response
.
StatusCode
=
0
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
//now receive response
//now receive response
await
ReceiveResponse
();
await
ReceiveResponse
();
return
;
return
;
...
...
Titanium.Web.Proxy/Http/KnownHeaders.cs
0 → 100644
View file @
80dd44e5
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Text
;
using
System.Threading.Tasks
;
namespace
Titanium.Web.Proxy.Http
{
public
static
class
KnownHeaders
{
// Both
public
const
string
Connection
=
"connection"
;
public
const
string
ConnectionClose
=
"close"
;
public
const
string
ConnectionKeepAlive
=
"keep-alive"
;
public
const
string
ContentLength
=
"content-length"
;
public
const
string
ContentType
=
"content-type"
;
public
const
string
ContentTypeCharset
=
"charset"
;
public
const
string
ContentTypeBoundary
=
"boundary"
;
public
const
string
Upgrade
=
"upgrade"
;
public
const
string
UpgradeWebsocket
=
"websocket"
;
// Request headers
public
const
string
AcceptEncoding
=
"accept-encoding"
;
public
const
string
Authorization
=
"Authorization"
;
public
const
string
Expect
=
"expect"
;
public
const
string
Expect100Continue
=
"100-continue"
;
public
const
string
Host
=
"host"
;
public
const
string
ProxyAuthorization
=
"Proxy-Authorization"
;
public
const
string
ProxyConnection
=
"Proxy-Connection"
;
public
const
string
ProxyConnectionClose
=
"close"
;
// Response headers
public
const
string
ContentEncoding
=
"content-encoding"
;
public
const
string
Location
=
"Location"
;
public
const
string
ProxyAuthenticate
=
"Proxy-Authenticate"
;
public
const
string
TransferEncoding
=
"transfer-encoding"
;
public
const
string
TransferEncodingChunked
=
"chunked"
;
// ???
public
const
string
Timestamp
=
"Timestamp"
;
}
}
Titanium.Web.Proxy/Http/Request.cs
View file @
80dd44e5
...
@@ -67,14 +67,14 @@ namespace Titanium.Web.Proxy.Http
...
@@ -67,14 +67,14 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
/// </summary>
public
string
Host
public
string
Host
{
{
get
=>
Headers
.
GetHeaderValueOrNull
(
"host"
);
get
=>
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
Host
);
set
=>
Headers
.
SetOrAddHeaderValue
(
"host"
,
value
);
set
=>
Headers
.
SetOrAddHeaderValue
(
KnownHeaders
.
Host
,
value
);
}
}
/// <summary>
/// <summary>
/// Content encoding header value
/// Content encoding header value
/// </summary>
/// </summary>
public
string
ContentEncoding
=>
Headers
.
GetHeaderValueOrNull
(
"content-encoding"
);
public
string
ContentEncoding
=>
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
ContentEncoding
);
/// <summary>
/// <summary>
/// Request content-length
/// Request content-length
...
@@ -83,7 +83,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -83,7 +83,7 @@ namespace Titanium.Web.Proxy.Http
{
{
get
get
{
{
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
"content-length"
);
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
ContentLength
);
if
(
headerValue
==
null
)
if
(
headerValue
==
null
)
{
{
...
@@ -102,12 +102,12 @@ namespace Titanium.Web.Proxy.Http
...
@@ -102,12 +102,12 @@ namespace Titanium.Web.Proxy.Http
{
{
if
(
value
>=
0
)
if
(
value
>=
0
)
{
{
Headers
.
SetOrAddHeaderValue
(
"content-length"
,
value
.
ToString
());
Headers
.
SetOrAddHeaderValue
(
KnownHeaders
.
ContentLength
,
value
.
ToString
());
IsChunked
=
false
;
IsChunked
=
false
;
}
}
else
else
{
{
Headers
.
RemoveHeader
(
"content-length"
);
Headers
.
RemoveHeader
(
KnownHeaders
.
ContentLength
);
}
}
}
}
}
}
...
@@ -117,8 +117,8 @@ namespace Titanium.Web.Proxy.Http
...
@@ -117,8 +117,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
/// </summary>
public
string
ContentType
public
string
ContentType
{
{
get
=>
Headers
.
GetHeaderValueOrNull
(
"content-type"
);
get
=>
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
ContentType
);
set
=>
Headers
.
SetOrAddHeaderValue
(
"content-type"
,
value
);
set
=>
Headers
.
SetOrAddHeaderValue
(
KnownHeaders
.
ContentType
,
value
);
}
}
/// <summary>
/// <summary>
...
@@ -128,19 +128,19 @@ namespace Titanium.Web.Proxy.Http
...
@@ -128,19 +128,19 @@ namespace Titanium.Web.Proxy.Http
{
{
get
get
{
{
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
"transfer-encoding"
);
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
TransferEncoding
);
return
headerValue
!=
null
&&
headerValue
.
ContainsIgnoreCase
(
"chunked"
);
return
headerValue
!=
null
&&
headerValue
.
ContainsIgnoreCase
(
KnownHeaders
.
TransferEncodingChunked
);
}
}
set
set
{
{
if
(
value
)
if
(
value
)
{
{
Headers
.
SetOrAddHeaderValue
(
"transfer-encoding"
,
"chunked"
);
Headers
.
SetOrAddHeaderValue
(
KnownHeaders
.
TransferEncoding
,
KnownHeaders
.
TransferEncodingChunked
);
ContentLength
=
-
1
;
ContentLength
=
-
1
;
}
}
else
else
{
{
Headers
.
RemoveHeader
(
"transfer-encoding"
);
Headers
.
RemoveHeader
(
KnownHeaders
.
TransferEncoding
);
}
}
}
}
}
}
...
@@ -152,8 +152,8 @@ namespace Titanium.Web.Proxy.Http
...
@@ -152,8 +152,8 @@ namespace Titanium.Web.Proxy.Http
{
{
get
get
{
{
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
"expect"
);
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
Expect
);
return
headerValue
!=
null
&&
headerValue
.
Equals
(
"100-continue"
);
return
headerValue
!=
null
&&
headerValue
.
Equals
(
KnownHeaders
.
Expect100Continue
);
}
}
}
}
...
@@ -241,14 +241,14 @@ namespace Titanium.Web.Proxy.Http
...
@@ -241,14 +241,14 @@ namespace Titanium.Web.Proxy.Http
{
{
get
get
{
{
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
"upgrade"
);
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
Upgrade
);
if
(
headerValue
==
null
)
if
(
headerValue
==
null
)
{
{
return
false
;
return
false
;
}
}
return
headerValue
.
Equals
(
"websocket"
,
StringComparison
.
CurrentCultureIgnoreCase
);
return
headerValue
.
Equals
(
KnownHeaders
.
UpgradeWebsocket
,
StringComparison
.
CurrentCultureIgnoreCase
);
}
}
}
}
...
@@ -275,7 +275,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -275,7 +275,7 @@ namespace Titanium.Web.Proxy.Http
get
get
{
{
var
sb
=
new
StringBuilder
();
var
sb
=
new
StringBuilder
();
sb
.
AppendLine
(
$"
{
Method
}
{
OriginalUrl
}
HTTP/
{
HttpVersion
.
Major
}
.
{
HttpVersion
.
Minor
}
"
);
sb
.
AppendLine
(
CreateRequestLine
(
Method
,
OriginalUrl
,
HttpVersion
)
);
foreach
(
var
header
in
Headers
)
foreach
(
var
header
in
Headers
)
{
{
sb
.
AppendLine
(
header
.
ToString
());
sb
.
AppendLine
(
header
.
ToString
());
...
@@ -286,6 +286,11 @@ namespace Titanium.Web.Proxy.Http
...
@@ -286,6 +286,11 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
internal
static
string
CreateRequestLine
(
string
httpMethod
,
string
httpUrl
,
Version
version
)
{
return
$"
{
httpMethod
}
{
httpUrl
}
HTTP/
{
version
.
Major
}
.
{
version
.
Minor
}
"
;
}
internal
static
void
ParseRequestLine
(
string
httpCmd
,
out
string
httpMethod
,
out
string
httpUrl
,
out
Version
version
)
internal
static
void
ParseRequestLine
(
string
httpCmd
,
out
string
httpMethod
,
out
string
httpUrl
,
out
Version
version
)
{
{
//break up the line into three components (method, remote URL & Http Version)
//break up the line into three components (method, remote URL & Http Version)
...
...
Titanium.Web.Proxy/Http/Response.cs
View file @
80dd44e5
...
@@ -42,12 +42,12 @@ namespace Titanium.Web.Proxy.Http
...
@@ -42,12 +42,12 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// <summary>
/// Content encoding for this response
/// Content encoding for this response
/// </summary>
/// </summary>
public
string
ContentEncoding
=>
Headers
.
GetHeaderValueOrNull
(
"content-encoding"
)?.
Trim
();
public
string
ContentEncoding
=>
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
ContentEncoding
)?.
Trim
();
/// <summary>
/// <summary>
/// Http version
/// Http version
/// </summary>
/// </summary>
public
Version
HttpVersion
{
get
;
set
;
}
public
Version
HttpVersion
{
get
;
set
;
}
=
HttpHeader
.
VersionUnknown
;
/// <summary>
/// <summary>
/// Keeps the response body data after the session is finished
/// Keeps the response body data after the session is finished
...
@@ -61,16 +61,24 @@ namespace Titanium.Web.Proxy.Http
...
@@ -61,16 +61,24 @@ namespace Titanium.Web.Proxy.Http
{
{
get
get
{
{
long
contentLength
=
ContentLength
;
//If content length is set to 0 the response has no body
if
(
contentLength
==
0
)
{
return
false
;
}
//Has body only if response is chunked or content length >0
//Has body only if response is chunked or content length >0
//If none are true then check if connection:close header exist, if so write response until server or client terminates the connection
//If none are true then check if connection:close header exist, if so write response until server or client terminates the connection
if
(
IsChunked
||
C
ontentLength
>
0
||
!
KeepAlive
)
if
(
IsChunked
||
c
ontentLength
>
0
||
!
KeepAlive
)
{
{
return
true
;
return
true
;
}
}
//has response if connection:keep-alive header exist and when version is http/1.0
//has response if connection:keep-alive header exist and when version is http/1.0
//Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream)
//Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream)
if
(
KeepAlive
&&
HttpVersion
.
Minor
==
0
)
if
(
KeepAlive
&&
HttpVersion
==
HttpHeader
.
Version1
0
)
{
{
return
true
;
return
true
;
}
}
...
@@ -86,11 +94,11 @@ namespace Titanium.Web.Proxy.Http
...
@@ -86,11 +94,11 @@ namespace Titanium.Web.Proxy.Http
{
{
get
get
{
{
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
"connection"
);
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
Connection
);
if
(
headerValue
!=
null
)
if
(
headerValue
!=
null
)
{
{
if
(
headerValue
.
ContainsIgnoreCase
(
"close"
))
if
(
headerValue
.
ContainsIgnoreCase
(
KnownHeaders
.
ConnectionClose
))
{
{
return
false
;
return
false
;
}
}
...
@@ -103,7 +111,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -103,7 +111,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// <summary>
/// Content type of this response
/// Content type of this response
/// </summary>
/// </summary>
public
string
ContentType
=>
Headers
.
GetHeaderValueOrNull
(
"content-type"
);
public
string
ContentType
=>
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
ContentType
);
/// <summary>
/// <summary>
/// Length of response body
/// Length of response body
...
@@ -112,15 +120,14 @@ namespace Titanium.Web.Proxy.Http
...
@@ -112,15 +120,14 @@ namespace Titanium.Web.Proxy.Http
{
{
get
get
{
{
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
"content-length"
);
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
ContentLength
);
if
(
headerValue
==
null
)
if
(
headerValue
==
null
)
{
{
return
-
1
;
return
-
1
;
}
}
long
.
TryParse
(
headerValue
,
out
long
contentLen
);
if
(
long
.
TryParse
(
headerValue
,
out
long
contentLen
))
if
(
contentLen
>=
0
)
{
{
return
contentLen
;
return
contentLen
;
}
}
...
@@ -131,12 +138,12 @@ namespace Titanium.Web.Proxy.Http
...
@@ -131,12 +138,12 @@ namespace Titanium.Web.Proxy.Http
{
{
if
(
value
>=
0
)
if
(
value
>=
0
)
{
{
Headers
.
SetOrAddHeaderValue
(
"content-length"
,
value
.
ToString
());
Headers
.
SetOrAddHeaderValue
(
KnownHeaders
.
ContentLength
,
value
.
ToString
());
IsChunked
=
false
;
IsChunked
=
false
;
}
}
else
else
{
{
Headers
.
RemoveHeader
(
"content-length"
);
Headers
.
RemoveHeader
(
KnownHeaders
.
ContentLength
);
}
}
}
}
}
}
...
@@ -148,19 +155,19 @@ namespace Titanium.Web.Proxy.Http
...
@@ -148,19 +155,19 @@ namespace Titanium.Web.Proxy.Http
{
{
get
get
{
{
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
"transfer-encoding"
);
string
headerValue
=
Headers
.
GetHeaderValueOrNull
(
KnownHeaders
.
TransferEncoding
);
return
headerValue
!=
null
&&
headerValue
.
ContainsIgnoreCase
(
"chunked"
);
return
headerValue
!=
null
&&
headerValue
.
ContainsIgnoreCase
(
KnownHeaders
.
TransferEncodingChunked
);
}
}
set
set
{
{
if
(
value
)
if
(
value
)
{
{
Headers
.
SetOrAddHeaderValue
(
"transfer-encoding"
,
"chunked"
);
Headers
.
SetOrAddHeaderValue
(
KnownHeaders
.
TransferEncoding
,
KnownHeaders
.
TransferEncodingChunked
);
ContentLength
=
-
1
;
ContentLength
=
-
1
;
}
}
else
else
{
{
Headers
.
RemoveHeader
(
"transfer-encoding"
);
Headers
.
RemoveHeader
(
KnownHeaders
.
TransferEncoding
);
}
}
}
}
}
}
...
@@ -228,7 +235,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -228,7 +235,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// <summary>
/// Gets the resposne status.
/// Gets the resposne status.
/// </summary>
/// </summary>
public
string
Status
=>
$"HTTP/
{
HttpVersion
?.
Major
}
.
{
HttpVersion
?.
Minor
}
{
StatusCode
}
{
StatusDescription
}
"
;
public
string
Status
=>
CreateResponseLine
(
HttpVersion
,
StatusCode
,
StatusDescription
)
;
/// <summary>
/// <summary>
/// Gets the header text.
/// Gets the header text.
...
@@ -249,6 +256,11 @@ namespace Titanium.Web.Proxy.Http
...
@@ -249,6 +256,11 @@ namespace Titanium.Web.Proxy.Http
}
}
}
}
internal
static
string
CreateResponseLine
(
Version
version
,
int
statusCode
,
string
statusDescription
)
{
return
$"HTTP/
{
version
.
Major
}
.
{
version
.
Minor
}
{
statusCode
}
{
statusDescription
}
"
;
}
internal
static
void
ParseResponseLine
(
string
httpStatus
,
out
Version
version
,
out
int
statusCode
,
out
string
statusDescription
)
internal
static
void
ParseResponseLine
(
string
httpStatus
,
out
Version
version
,
out
int
statusCode
,
out
string
statusDescription
)
{
{
var
httpResult
=
httpStatus
.
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
var
httpResult
=
httpStatus
.
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
...
...
Titanium.Web.Proxy/Models/HttpHeader.cs
View file @
80dd44e5
using
System
;
using
System
;
using
System.IO
;
using
System.Text
;
using
System.Text
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Http
;
namespace
Titanium.Web.Proxy.Models
namespace
Titanium.Web.Proxy.Models
{
{
...
@@ -10,6 +11,8 @@ namespace Titanium.Web.Proxy.Models
...
@@ -10,6 +11,8 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
/// </summary>
public
class
HttpHeader
public
class
HttpHeader
{
{
internal
static
readonly
Version
VersionUnknown
=
new
Version
(
0
,
0
);
internal
static
readonly
Version
Version10
=
new
Version
(
1
,
0
);
internal
static
readonly
Version
Version10
=
new
Version
(
1
,
0
);
internal
static
readonly
Version
Version11
=
new
Version
(
1
,
1
);
internal
static
readonly
Version
Version11
=
new
Version
(
1
,
1
);
...
@@ -54,19 +57,12 @@ namespace Titanium.Web.Proxy.Models
...
@@ -54,19 +57,12 @@ namespace Titanium.Web.Proxy.Models
internal
static
HttpHeader
GetProxyAuthorizationHeader
(
string
userName
,
string
password
)
internal
static
HttpHeader
GetProxyAuthorizationHeader
(
string
userName
,
string
password
)
{
{
var
result
=
new
HttpHeader
(
"Proxy-Authorization"
,
var
result
=
new
HttpHeader
(
KnownHeaders
.
ProxyAuthorization
,
"Basic "
+
Convert
.
ToBase64String
(
Encoding
.
UTF8
.
GetBytes
(
$"
{
userName
}
:
{
password
}
"
)));
"Basic "
+
Convert
.
ToBase64String
(
Encoding
.
UTF8
.
GetBytes
(
$"
{
userName
}
:
{
password
}
"
)));
return
result
;
return
result
;
}
}
internal
void
WriteToStream
(
StreamWriter
writer
)
internal
async
Task
WriteToStreamAsync
(
HttpWriter
writer
)
{
writer
.
Write
(
Name
);
writer
.
Write
(
": "
);
writer
.
WriteLine
(
Value
);
}
internal
async
Task
WriteToStreamAsync
(
StreamWriter
writer
)
{
{
await
writer
.
WriteAsync
(
Name
);
await
writer
.
WriteAsync
(
Name
);
await
writer
.
WriteAsync
(
": "
);
await
writer
.
WriteAsync
(
": "
);
...
...
Titanium.Web.Proxy/Network/DebugCustomBufferedStream.cs
0 → 100644
View file @
80dd44e5
#
if
DEBUG
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Text
;
using
System.Threading
;
using
System.Threading.Tasks
;
using
StreamExtended.Network
;
namespace
Titanium.Web.Proxy.Network
{
class
DebugCustomBufferedStream
:
CustomBufferedStream
{
private
const
string
basePath
=
@"."
;
private
static
int
counter
;
public
int
Counter
{
get
;
}
private
readonly
FileStream
fileStreamSent
;
private
readonly
FileStream
fileStreamReceived
;
public
DebugCustomBufferedStream
(
Stream
baseStream
,
int
bufferSize
)
:
base
(
baseStream
,
bufferSize
)
{
Counter
=
Interlocked
.
Increment
(
ref
counter
);
fileStreamSent
=
new
FileStream
(
Path
.
Combine
(
basePath
,
$"
{
Counter
}
_sent.dat"
),
FileMode
.
Create
);
fileStreamReceived
=
new
FileStream
(
Path
.
Combine
(
basePath
,
$"
{
Counter
}
_received.dat"
),
FileMode
.
Create
);
}
protected
override
void
OnDataSent
(
byte
[]
buffer
,
int
offset
,
int
count
)
{
fileStreamSent
.
Write
(
buffer
,
offset
,
count
);
}
protected
override
void
OnDataReceived
(
byte
[]
buffer
,
int
offset
,
int
count
)
{
fileStreamReceived
.
Write
(
buffer
,
offset
,
count
);
}
public
override
void
Flush
()
{
fileStreamSent
.
Flush
(
true
);
fileStreamReceived
.
Flush
(
true
);
base
.
Flush
();
}
}
}
#
endif
\ No newline at end of file
Titanium.Web.Proxy/Network/ProxyClient.cs
View file @
80dd44e5
...
@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy.Network
...
@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy.Network
internal
TcpClient
TcpClient
{
get
;
set
;
}
internal
TcpClient
TcpClient
{
get
;
set
;
}
/// <summary>
/// <summary>
///
h
olds the stream to client
///
H
olds the stream to client
/// </summary>
/// </summary>
internal
CustomBufferedStream
ClientStream
{
get
;
set
;
}
internal
CustomBufferedStream
ClientStream
{
get
;
set
;
}
...
@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy.Network
...
@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy.Network
internal
CustomBinaryReader
ClientStreamReader
{
get
;
set
;
}
internal
CustomBinaryReader
ClientStreamReader
{
get
;
set
;
}
/// <summary>
/// <summary>
///
u
sed to write line by line to client
///
U
sed to write line by line to client
/// </summary>
/// </summary>
internal
HttpResponseWriter
ClientStreamWriter
{
get
;
set
;
}
internal
HttpResponseWriter
ClientStreamWriter
{
get
;
set
;
}
}
}
...
...
Titanium.Web.Proxy/Network/Tcp/TcpConnection.cs
View file @
80dd44e5
...
@@ -2,6 +2,8 @@
...
@@ -2,6 +2,8 @@
using
System
;
using
System
;
using
System.Net
;
using
System.Net
;
using
System.Net.Sockets
;
using
System.Net.Sockets
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
namespace
Titanium.Web.Proxy.Network.Tcp
namespace
Titanium.Web.Proxy.Network.Tcp
...
@@ -11,6 +13,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
...
@@ -11,6 +13,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
/// </summary>
internal
class
TcpConnection
:
IDisposable
internal
class
TcpConnection
:
IDisposable
{
{
private
ProxyServer
proxyServer
{
get
;
}
internal
ExternalProxy
UpStreamProxy
{
get
;
set
;
}
internal
ExternalProxy
UpStreamProxy
{
get
;
set
;
}
internal
string
HostName
{
get
;
set
;
}
internal
string
HostName
{
get
;
set
;
}
...
@@ -34,10 +38,15 @@ namespace Titanium.Web.Proxy.Network.Tcp
...
@@ -34,10 +38,15 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal
TcpClient
TcpClient
{
private
get
;
set
;
}
internal
TcpClient
TcpClient
{
private
get
;
set
;
}
/// <summary>
/// <summary>
///
u
sed to read lines from server
///
U
sed to read lines from server
/// </summary>
/// </summary>
internal
CustomBinaryReader
StreamReader
{
get
;
set
;
}
internal
CustomBinaryReader
StreamReader
{
get
;
set
;
}
/// <summary>
/// Used to write lines to server
/// </summary>
internal
HttpRequestWriter
StreamWriter
{
get
;
set
;
}
/// <summary>
/// <summary>
/// Server stream
/// Server stream
/// </summary>
/// </summary>
...
@@ -48,9 +57,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
...
@@ -48,9 +57,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
/// </summary>
internal
DateTime
LastAccess
{
get
;
set
;
}
internal
DateTime
LastAccess
{
get
;
set
;
}
internal
TcpConnection
()
internal
TcpConnection
(
ProxyServer
proxyServer
)
{
{
LastAccess
=
DateTime
.
Now
;
LastAccess
=
DateTime
.
Now
;
this
.
proxyServer
=
proxyServer
;
this
.
proxyServer
.
UpdateServerConnectionCount
(
true
);
}
}
/// <summary>
/// <summary>
...
@@ -58,25 +69,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
...
@@ -58,25 +69,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
/// </summary>
public
void
Dispose
()
public
void
Dispose
()
{
{
StreamReader
?.
Dispose
();
Stream
?.
Dispose
();
Stream
?.
Dispose
();
StreamReader
?.
Dispose
();
TcpClient
.
CloseSocket
();
try
proxyServer
.
UpdateServerConnectionCount
(
false
);
{
if
(
TcpClient
!=
null
)
{
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
TcpClient
.
LingerState
=
new
LingerOption
(
true
,
0
);
TcpClient
.
Close
();
}
}
catch
{
}
}
}
}
}
}
}
Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs
View file @
80dd44e5
...
@@ -8,6 +8,7 @@ using System.Text;
...
@@ -8,6 +8,7 @@ using System.Text;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
namespace
Titanium.Web.Proxy.Network.Tcp
namespace
Titanium.Web.Proxy.Network.Tcp
...
@@ -68,24 +69,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
...
@@ -68,24 +69,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
if
(
useUpstreamProxy
&&
(
isConnect
||
isHttps
))
if
(
useUpstreamProxy
&&
(
isConnect
||
isHttps
))
{
{
using
(
var
writer
=
new
HttpRequestWriter
(
stream
,
server
.
BufferSize
))
var
writer
=
new
HttpRequestWriter
(
stream
,
server
.
BufferSize
);
{
await
writer
.
WriteLineAsync
(
$"CONNECT
{
remoteHostName
}
:
{
remotePort
}
HTTP/
{
httpVersion
}
"
);
await
writer
.
WriteLineAsync
(
$"CONNECT
{
remoteHostName
}
:
{
remotePort
}
HTTP/
{
httpVersion
}
"
);
await
writer
.
WriteLineAsync
(
$"Host:
{
remoteHostName
}
:
{
remotePort
}
"
);
await
writer
.
WriteLineAsync
(
$"Host:
{
remoteHostName
}
:
{
remotePort
}
"
);
await
writer
.
WriteLineAsync
(
$"
{
KnownHeaders
.
Connection
}
:
{
KnownHeaders
.
ConnectionKeepAlive
}
"
);
await
writer
.
WriteLineAsync
(
"Connection: Keep-Alive"
);
if
(!
string
.
IsNullOrEmpty
(
externalProxy
.
UserName
)
&&
externalProxy
.
Password
!=
null
)
{
await
HttpHeader
.
ProxyConnectionKeepAlive
.
WriteToStreamAsync
(
writer
);
await
writer
.
WriteLineAsync
(
"Proxy-Authorization"
+
": Basic "
+
Convert
.
ToBase64String
(
Encoding
.
UTF8
.
GetBytes
(
externalProxy
.
UserName
+
":"
+
externalProxy
.
Password
)));
}
await
writer
.
WriteLineAsync
();
if
(!
string
.
IsNullOrEmpty
(
externalProxy
.
UserName
)
&&
externalProxy
.
Password
!=
null
)
await
writer
.
FlushAsync
();
{
await
HttpHeader
.
ProxyConnectionKeepAlive
.
WriteToStreamAsync
(
writer
);
await
writer
.
WriteLineAsync
(
KnownHeaders
.
ProxyAuthorization
+
": Basic "
+
Convert
.
ToBase64String
(
Encoding
.
UTF8
.
GetBytes
(
externalProxy
.
UserName
+
":"
+
externalProxy
.
Password
)));
}
}
await
writer
.
WriteLineAsync
();
using
(
var
reader
=
new
CustomBinaryReader
(
stream
,
server
.
BufferSize
))
using
(
var
reader
=
new
CustomBinaryReader
(
stream
,
server
.
BufferSize
))
{
{
string
result
=
await
reader
.
ReadLineAsync
();
string
result
=
await
reader
.
ReadLineAsync
();
...
@@ -118,9 +116,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
...
@@ -118,9 +116,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
throw
;
throw
;
}
}
server
.
UpdateServerConnectionCount
(
true
);
return
new
TcpConnection
(
server
)
return
new
TcpConnection
{
{
UpStreamProxy
=
externalProxy
,
UpStreamProxy
=
externalProxy
,
UpStreamEndPoint
=
upStreamEndPoint
,
UpStreamEndPoint
=
upStreamEndPoint
,
...
@@ -130,6 +126,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
...
@@ -130,6 +126,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
UseUpstreamProxy
=
useUpstreamProxy
,
UseUpstreamProxy
=
useUpstreamProxy
,
TcpClient
=
client
,
TcpClient
=
client
,
StreamReader
=
new
CustomBinaryReader
(
stream
,
server
.
BufferSize
),
StreamReader
=
new
CustomBinaryReader
(
stream
,
server
.
BufferSize
),
StreamWriter
=
new
HttpRequestWriter
(
stream
,
server
.
BufferSize
),
Stream
=
stream
,
Stream
=
stream
,
Version
=
httpVersion
Version
=
httpVersion
};
};
...
...
Titanium.Web.Proxy/ProxyAuthorizationHandler.cs
View file @
80dd44e5
...
@@ -8,6 +8,7 @@ using Titanium.Web.Proxy.Exceptions;
...
@@ -8,6 +8,7 @@ using Titanium.Web.Proxy.Exceptions;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Shared
;
namespace
Titanium.Web.Proxy
namespace
Titanium.Web.Proxy
{
{
...
@@ -20,37 +21,36 @@ namespace Titanium.Web.Proxy
...
@@ -20,37 +21,36 @@ namespace Titanium.Web.Proxy
return
true
;
return
true
;
}
}
var
httpHeaders
=
session
.
WebSession
.
Request
.
Headers
.
ToArray
()
;
var
httpHeaders
=
session
.
WebSession
.
Request
.
Headers
;
try
try
{
{
var
header
=
httpHeaders
.
FirstOrDefault
(
t
=>
t
.
Name
==
"Proxy-Authorization"
);
var
header
=
httpHeaders
.
GetFirstHeader
(
KnownHeaders
.
ProxyAuthorization
);
if
(
header
==
null
)
if
(
header
==
null
)
{
{
session
.
WebSession
.
Response
=
await
SendAuthentication407Response
(
clientStreamWriter
,
"Proxy Authentication Required"
);
session
.
WebSession
.
Response
=
await
SendAuthentication407Response
(
clientStreamWriter
,
"Proxy Authentication Required"
);
return
false
;
return
false
;
}
}
string
headerValue
=
header
.
Value
.
Trim
(
);
var
headerValueParts
=
header
.
Value
.
Split
(
ProxyConstants
.
SpaceSplit
);
if
(
!
headerValue
.
StartsWith
(
"basic"
,
StringComparison
.
CurrentCultureIgnoreCase
))
if
(
headerValueParts
.
Length
!=
2
||
!
headerValueParts
[
0
].
Equals
(
"basic"
,
StringComparison
.
CurrentCultureIgnoreCase
))
{
{
//Return not authorized
//Return not authorized
session
.
WebSession
.
Response
=
await
SendAuthentication407Response
(
clientStreamWriter
,
"Proxy Authentication Invalid"
);
session
.
WebSession
.
Response
=
await
SendAuthentication407Response
(
clientStreamWriter
,
"Proxy Authentication Invalid"
);
return
false
;
return
false
;
}
}
headerValue
=
headerValue
.
Substring
(
5
).
Trim
();
string
decoded
=
Encoding
.
UTF8
.
GetString
(
Convert
.
FromBase64String
(
headerValueParts
[
1
]));
int
colonIndex
=
decoded
.
IndexOf
(
':'
);
string
decoded
=
Encoding
.
UTF8
.
GetString
(
Convert
.
FromBase64String
(
headerValue
));
if
(
colonIndex
==
-
1
)
if
(
decoded
.
Contains
(
":"
)
==
false
)
{
{
//Return not authorized
//Return not authorized
session
.
WebSession
.
Response
=
await
SendAuthentication407Response
(
clientStreamWriter
,
"Proxy Authentication Invalid"
);
session
.
WebSession
.
Response
=
await
SendAuthentication407Response
(
clientStreamWriter
,
"Proxy Authentication Invalid"
);
return
false
;
return
false
;
}
}
string
username
=
decoded
.
Substring
(
0
,
decoded
.
IndexOf
(
':'
)
);
string
username
=
decoded
.
Substring
(
0
,
colonIndex
);
string
password
=
decoded
.
Substring
(
decoded
.
IndexOf
(
':'
)
+
1
);
string
password
=
decoded
.
Substring
(
colonIndex
+
1
);
return
await
AuthenticateUserFunc
(
username
,
password
);
return
await
AuthenticateUserFunc
(
username
,
password
);
}
}
catch
(
Exception
e
)
catch
(
Exception
e
)
...
@@ -72,8 +72,8 @@ namespace Titanium.Web.Proxy
...
@@ -72,8 +72,8 @@ namespace Titanium.Web.Proxy
StatusDescription
=
description
StatusDescription
=
description
};
};
response
.
Headers
.
AddHeader
(
"Proxy-Authenticate"
,
$"Basic realm=\"
{
ProxyRealm
}
\""
);
response
.
Headers
.
AddHeader
(
KnownHeaders
.
ProxyAuthenticate
,
$"Basic realm=\"
{
ProxyRealm
}
\""
);
response
.
Headers
.
AddHeader
(
"Proxy-Connection"
,
"close"
);
response
.
Headers
.
AddHeader
(
KnownHeaders
.
ProxyConnection
,
KnownHeaders
.
ProxyConnectionClose
);
await
clientStreamWriter
.
WriteResponseAsync
(
response
);
await
clientStreamWriter
.
WriteResponseAsync
(
response
);
return
response
;
return
response
;
...
...
Titanium.Web.Proxy/ProxyServer.cs
View file @
80dd44e5
...
@@ -9,6 +9,7 @@ using System.Threading;
...
@@ -9,6 +9,7 @@ using System.Threading;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
StreamExtended.Network
;
using
StreamExtended.Network
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Helpers.WinHttp
;
using
Titanium.Web.Proxy.Helpers.WinHttp
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
...
@@ -164,22 +165,22 @@ namespace Titanium.Web.Proxy
...
@@ -164,22 +165,22 @@ namespace Titanium.Web.Proxy
/// <summary>
/// <summary>
/// Intercept request to server
/// Intercept request to server
/// </summary>
/// </summary>
public
event
Func
<
object
,
SessionEventArgs
,
Task
>
BeforeRequest
;
public
event
AsyncEventHandler
<
SessionEventArgs
>
BeforeRequest
;
/// <summary>
/// <summary>
/// Intercept response from server
/// Intercept response from server
/// </summary>
/// </summary>
public
event
Func
<
object
,
SessionEventArgs
,
Task
>
BeforeResponse
;
public
event
AsyncEventHandler
<
SessionEventArgs
>
BeforeResponse
;
/// <summary>
/// <summary>
/// Intercept tunnel connect reques
/// Intercept tunnel connect reques
/// </summary>
/// </summary>
public
event
Func
<
object
,
TunnelConnectSessionEventArgs
,
Task
>
TunnelConnectRequest
;
public
event
AsyncEventHandler
<
TunnelConnectSessionEventArgs
>
TunnelConnectRequest
;
/// <summary>
/// <summary>
/// Intercept tunnel connect response
/// Intercept tunnel connect response
/// </summary>
/// </summary>
public
event
Func
<
object
,
TunnelConnectSessionEventArgs
,
Task
>
TunnelConnectResponse
;
public
event
AsyncEventHandler
<
TunnelConnectSessionEventArgs
>
TunnelConnectResponse
;
/// <summary>
/// <summary>
/// Occurs when client connection count changed.
/// Occurs when client connection count changed.
...
@@ -229,12 +230,12 @@ namespace Titanium.Web.Proxy
...
@@ -229,12 +230,12 @@ namespace Titanium.Web.Proxy
/// <summary>
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary>
/// </summary>
public
event
Func
<
object
,
CertificateValidationEventArgs
,
Task
>
ServerCertificateValidationCallback
;
public
event
AsyncEventHandler
<
CertificateValidationEventArgs
>
ServerCertificateValidationCallback
;
/// <summary>
/// <summary>
/// Callback tooverride client certificate during SSL mutual authentication
/// Callback tooverride client certificate during SSL mutual authentication
/// </summary>
/// </summary>
public
event
Func
<
object
,
CertificateSelectionEventArgs
,
Task
>
ClientCertificateSelectionCallback
;
public
event
AsyncEventHandler
<
CertificateSelectionEventArgs
>
ClientCertificateSelectionCallback
;
/// <summary>
/// <summary>
/// Callback for error events in proxy
/// Callback for error events in proxy
...
@@ -612,16 +613,14 @@ namespace Titanium.Web.Proxy
...
@@ -612,16 +613,14 @@ namespace Titanium.Web.Proxy
/// <param name="serverConnection"></param>
/// <param name="serverConnection"></param>
private
void
Dispose
(
CustomBufferedStream
clientStream
,
CustomBinaryReader
clientStreamReader
,
HttpResponseWriter
clientStreamWriter
,
TcpConnection
serverConnection
)
private
void
Dispose
(
CustomBufferedStream
clientStream
,
CustomBinaryReader
clientStreamReader
,
HttpResponseWriter
clientStreamWriter
,
TcpConnection
serverConnection
)
{
{
clientStream
?.
Dispose
();
clientStreamReader
?.
Dispose
();
clientStreamReader
?.
Dispose
();
clientStreamWriter
?.
Dispose
();
clientStream
?.
Dispose
();
if
(
serverConnection
!=
null
)
if
(
serverConnection
!=
null
)
{
{
serverConnection
.
Dispose
();
serverConnection
.
Dispose
();
serverConnection
=
null
;
serverConnection
=
null
;
UpdateServerConnectionCount
(
false
);
}
}
}
}
...
@@ -766,22 +765,7 @@ namespace Titanium.Web.Proxy
...
@@ -766,22 +765,7 @@ namespace Titanium.Web.Proxy
finally
finally
{
{
UpdateClientConnectionCount
(
false
);
UpdateClientConnectionCount
(
false
);
tcpClient
.
CloseSocket
();
try
{
if
(
tcpClient
!=
null
)
{
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient
.
LingerState
=
new
LingerOption
(
true
,
0
);
tcpClient
.
Close
();
}
}
catch
{
}
}
}
}
}
...
...
Titanium.Web.Proxy/RequestHandler.cs
View file @
80dd44e5
...
@@ -49,7 +49,6 @@ namespace Titanium.Web.Proxy
...
@@ -49,7 +49,6 @@ namespace Titanium.Web.Proxy
{
{
//read the first line HTTP command
//read the first line HTTP command
string
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
string
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
if
(
string
.
IsNullOrEmpty
(
httpCmd
))
if
(
string
.
IsNullOrEmpty
(
httpCmd
))
{
{
return
;
return
;
...
@@ -86,20 +85,20 @@ namespace Titanium.Web.Proxy
...
@@ -86,20 +85,20 @@ namespace Titanium.Web.Proxy
await
HeaderParser
.
ReadHeaders
(
clientStreamReader
,
connectRequest
.
Headers
);
await
HeaderParser
.
ReadHeaders
(
clientStreamReader
,
connectRequest
.
Headers
);
var
connectArgs
=
new
TunnelConnectSessionEventArgs
(
BufferSize
,
endPoint
,
connectRequest
);
var
connectArgs
=
new
TunnelConnectSessionEventArgs
(
BufferSize
,
endPoint
,
connectRequest
,
ExceptionFunc
);
connectArgs
.
ProxyClient
.
TcpClient
=
tcpClient
;
connectArgs
.
ProxyClient
.
TcpClient
=
tcpClient
;
connectArgs
.
ProxyClient
.
ClientStream
=
clientStream
;
connectArgs
.
ProxyClient
.
ClientStream
=
clientStream
;
if
(
TunnelConnectRequest
!=
null
)
if
(
TunnelConnectRequest
!=
null
)
{
{
await
TunnelConnectRequest
.
Invoke
Parallel
Async
(
this
,
connectArgs
,
ExceptionFunc
);
await
TunnelConnectRequest
.
InvokeAsync
(
this
,
connectArgs
,
ExceptionFunc
);
}
}
if
(
await
CheckAuthorization
(
clientStreamWriter
,
connectArgs
)
==
false
)
if
(
await
CheckAuthorization
(
clientStreamWriter
,
connectArgs
)
==
false
)
{
{
if
(
TunnelConnectResponse
!=
null
)
if
(
TunnelConnectResponse
!=
null
)
{
{
await
TunnelConnectResponse
.
Invoke
Parallel
Async
(
this
,
connectArgs
,
ExceptionFunc
);
await
TunnelConnectResponse
.
InvokeAsync
(
this
,
connectArgs
,
ExceptionFunc
);
}
}
return
;
return
;
...
@@ -119,7 +118,7 @@ namespace Titanium.Web.Proxy
...
@@ -119,7 +118,7 @@ namespace Titanium.Web.Proxy
if
(
TunnelConnectResponse
!=
null
)
if
(
TunnelConnectResponse
!=
null
)
{
{
connectArgs
.
IsHttpsConnect
=
isClientHello
;
connectArgs
.
IsHttpsConnect
=
isClientHello
;
await
TunnelConnectResponse
.
Invoke
Parallel
Async
(
this
,
connectArgs
,
ExceptionFunc
);
await
TunnelConnectResponse
.
InvokeAsync
(
this
,
connectArgs
,
ExceptionFunc
);
}
}
if
(!
excluded
&&
isClientHello
)
if
(!
excluded
&&
isClientHello
)
...
@@ -153,39 +152,51 @@ namespace Titanium.Web.Proxy
...
@@ -153,39 +152,51 @@ namespace Titanium.Web.Proxy
return
;
return
;
}
}
//Now read the actual HTTPS request line
if
(
await
CanBeHttpMethod
(
clientStream
))
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
{
//Now read the actual HTTPS request line
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
}
else
{
// It can be for example some Google (Cloude Messaging for Chrome) magic
excluded
=
true
;
}
}
}
//Hostname is excluded or it is not an HTTPS connect
//Hostname is excluded or it is not an HTTPS connect
else
if
(
excluded
||
!
isClientHello
)
{
{
//create new connection
//create new connection
using
(
var
connection
=
await
GetServerConnection
(
connectArgs
,
true
))
using
(
var
connection
=
await
GetServerConnection
(
connectArgs
,
true
))
{
{
try
if
(
isClientHello
)
{
{
if
(
isClientHello
)
int
available
=
clientStream
.
Available
;
if
(
available
>
0
)
{
{
if
(
clientStream
.
Available
>
0
)
//send the buffered data
var
data
=
BufferPool
.
GetBuffer
(
BufferSize
);
try
{
{
//send the buffered data
// clientStream.Available sbould be at most BufferSize because it is using the same buffer size
var
data
=
new
byte
[
clientStream
.
Available
];
await
clientStream
.
ReadAsync
(
data
,
0
,
available
);
await
clientStream
.
ReadAsync
(
data
,
0
,
data
.
Length
);
await
connection
.
StreamWriter
.
WriteAsync
(
data
,
0
,
available
,
true
);
await
connection
.
Stream
.
WriteAsync
(
data
,
0
,
data
.
Length
);
}
await
connection
.
Stream
.
FlushAsync
();
finally
{
BufferPool
.
ReturnBuffer
(
data
);
}
}
var
serverHelloInfo
=
await
SslTools
.
PeekServerHello
(
connection
.
Stream
);
((
ConnectResponse
)
connectArgs
.
WebSession
.
Response
).
ServerHelloInfo
=
serverHelloInfo
;
}
}
await
TcpHelper
.
SendRaw
(
clientStream
,
connection
.
Stream
,
BufferSize
,
var
serverHelloInfo
=
await
SslTools
.
PeekServerHello
(
connection
.
Stream
);
(
buffer
,
offset
,
count
)
=>
{
connectArgs
.
OnDataSent
(
buffer
,
offset
,
count
);
},
(
buffer
,
offset
,
count
)
=>
{
connectArgs
.
OnDataReceived
(
buffer
,
offset
,
count
);
});
((
ConnectResponse
)
connectArgs
.
WebSession
.
Response
).
ServerHelloInfo
=
serverHelloInfo
;
}
finally
{
UpdateServerConnectionCount
(
false
);
}
}
await
TcpHelper
.
SendRaw
(
clientStream
,
connection
.
Stream
,
BufferSize
,
(
buffer
,
offset
,
count
)
=>
{
connectArgs
.
OnDataSent
(
buffer
,
offset
,
count
);
},
(
buffer
,
offset
,
count
)
=>
{
connectArgs
.
OnDataReceived
(
buffer
,
offset
,
count
);
});
}
}
return
;
return
;
...
@@ -196,8 +207,17 @@ namespace Titanium.Web.Proxy
...
@@ -196,8 +207,17 @@ namespace Titanium.Web.Proxy
disposed
=
await
HandleHttpSessionRequest
(
tcpClient
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
disposed
=
await
HandleHttpSessionRequest
(
tcpClient
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
httpRemoteUri
.
Scheme
==
UriSchemeHttps
?
httpRemoteUri
.
Host
:
null
,
endPoint
,
connectRequest
);
httpRemoteUri
.
Scheme
==
UriSchemeHttps
?
httpRemoteUri
.
Host
:
null
,
endPoint
,
connectRequest
);
}
}
catch
(
IOException
e
)
{
ExceptionFunc
(
new
Exception
(
"Connection was aborted"
,
e
));
}
catch
(
SocketException
e
)
{
ExceptionFunc
(
new
Exception
(
"Could not connect"
,
e
));
}
catch
(
Exception
e
)
catch
(
Exception
e
)
{
{
// is this the correct error message?
ExceptionFunc
(
new
Exception
(
"Error whilst authorizing request"
,
e
));
ExceptionFunc
(
new
Exception
(
"Error whilst authorizing request"
,
e
));
}
}
finally
finally
...
@@ -209,6 +229,34 @@ namespace Titanium.Web.Proxy
...
@@ -209,6 +229,34 @@ namespace Titanium.Web.Proxy
}
}
}
}
private
async
Task
<
bool
>
CanBeHttpMethod
(
CustomBufferedStream
clientStream
)
{
int
legthToCheck
=
10
;
for
(
int
i
=
0
;
i
<
legthToCheck
;
i
++)
{
int
b
=
await
clientStream
.
PeekByteAsync
(
i
);
if
(
b
==
-
1
)
{
return
false
;
}
if
(
b
==
' '
&&
i
>
2
)
{
// at least 3 letters and a space
return
true
;
}
if
(!
char
.
IsLetter
((
char
)
b
))
{
// non letter or too short
return
false
;
}
}
// only letters
return
true
;
}
/// <summary>
/// <summary>
/// This is called when this proxy acts as a reverse proxy (like a real http server)
/// This is called when this proxy acts as a reverse proxy (like a real http server)
/// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
/// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
...
@@ -228,18 +276,14 @@ namespace Titanium.Web.Proxy
...
@@ -228,18 +276,14 @@ namespace Titanium.Web.Proxy
{
{
if
(
endPoint
.
EnableSsl
)
if
(
endPoint
.
EnableSsl
)
{
{
var
client
Ssl
HelloInfo
=
await
SslTools
.
PeekClientHello
(
clientStream
);
var
clientHelloInfo
=
await
SslTools
.
PeekClientHello
(
clientStream
);
if
(
client
Ssl
HelloInfo
!=
null
)
if
(
clientHelloInfo
!=
null
)
{
{
var
sslStream
=
new
SslStream
(
clientStream
);
var
sslStream
=
new
SslStream
(
clientStream
);
clientStream
=
new
CustomBufferedStream
(
sslStream
,
BufferSize
);
clientStream
=
new
CustomBufferedStream
(
sslStream
,
BufferSize
);
string
sniHostName
=
null
;
string
sniHostName
=
clientHelloInfo
.
GetServerName
();
if
(
clientSslHelloInfo
.
Extensions
!=
null
&&
clientSslHelloInfo
.
Extensions
.
TryGetValue
(
"server_name"
,
out
var
serverNameExtension
))
{
sniHostName
=
serverNameExtension
.
Data
;
}
string
certName
=
HttpHelper
.
GetWildCardDomainName
(
sniHostName
??
endPoint
.
GenericCertificateName
);
string
certName
=
HttpHelper
.
GetWildCardDomainName
(
sniHostName
??
endPoint
.
GenericCertificateName
);
var
certificate
=
CertificateManager
.
CreateCertificate
(
certName
,
false
);
var
certificate
=
CertificateManager
.
CreateCertificate
(
certName
,
false
);
...
@@ -302,7 +346,7 @@ namespace Titanium.Web.Proxy
...
@@ -302,7 +346,7 @@ namespace Titanium.Web.Proxy
break
;
break
;
}
}
var
args
=
new
SessionEventArgs
(
BufferSize
,
endPoint
,
HandleHttpSessionResponse
)
var
args
=
new
SessionEventArgs
(
BufferSize
,
endPoint
,
HandleHttpSessionResponse
,
ExceptionFunc
)
{
{
ProxyClient
=
{
TcpClient
=
client
},
ProxyClient
=
{
TcpClient
=
client
},
WebSession
=
{
ConnectRequest
=
connectRequest
}
WebSession
=
{
ConnectRequest
=
connectRequest
}
...
@@ -349,7 +393,7 @@ namespace Titanium.Web.Proxy
...
@@ -349,7 +393,7 @@ namespace Titanium.Web.Proxy
//If user requested interception do it
//If user requested interception do it
if
(
BeforeRequest
!=
null
)
if
(
BeforeRequest
!=
null
)
{
{
await
BeforeRequest
.
Invoke
Parallel
Async
(
this
,
args
,
ExceptionFunc
);
await
BeforeRequest
.
InvokeAsync
(
this
,
args
,
ExceptionFunc
);
}
}
if
(
args
.
WebSession
.
Request
.
CancelRequest
)
if
(
args
.
WebSession
.
Request
.
CancelRequest
)
...
@@ -366,7 +410,6 @@ namespace Titanium.Web.Proxy
...
@@ -366,7 +410,6 @@ namespace Titanium.Web.Proxy
{
{
connection
.
Dispose
();
connection
.
Dispose
();
connection
=
null
;
connection
=
null
;
UpdateServerConnectionCount
(
false
);
}
}
if
(
connection
==
null
)
if
(
connection
==
null
)
...
@@ -379,16 +422,8 @@ namespace Titanium.Web.Proxy
...
@@ -379,16 +422,8 @@ namespace Titanium.Web.Proxy
{
{
//prepare the prefix content
//prepare the prefix content
var
requestHeaders
=
args
.
WebSession
.
Request
.
Headers
;
var
requestHeaders
=
args
.
WebSession
.
Request
.
Headers
;
byte
[]
requestBytes
;
await
connection
.
StreamWriter
.
WriteLineAsync
(
httpCmd
);
using
(
var
ms
=
new
MemoryStream
())
await
connection
.
StreamWriter
.
WriteHeadersAsync
(
requestHeaders
);
using
(
var
writer
=
new
HttpRequestWriter
(
ms
,
BufferSize
))
{
writer
.
WriteLine
(
httpCmd
);
writer
.
WriteHeaders
(
requestHeaders
);
requestBytes
=
ms
.
ToArray
();
}
await
connection
.
Stream
.
WriteAsync
(
requestBytes
,
0
,
requestBytes
.
Length
);
string
httpStatus
=
await
connection
.
StreamReader
.
ReadLineAsync
();
string
httpStatus
=
await
connection
.
StreamReader
.
ReadLineAsync
();
Response
.
ParseResponseLine
(
httpStatus
,
out
var
responseVersion
,
out
int
responseStatusCode
,
out
string
responseStatusDescription
);
Response
.
ParseResponseLine
(
httpStatus
,
out
var
responseVersion
,
out
int
responseStatusCode
,
out
string
responseStatusDescription
);
...
@@ -403,11 +438,12 @@ namespace Titanium.Web.Proxy
...
@@ -403,11 +438,12 @@ namespace Titanium.Web.Proxy
//If user requested call back then do it
//If user requested call back then do it
if
(
BeforeResponse
!=
null
&&
!
args
.
WebSession
.
Response
.
ResponseLocked
)
if
(
BeforeResponse
!=
null
&&
!
args
.
WebSession
.
Response
.
ResponseLocked
)
{
{
await
BeforeResponse
.
Invoke
Parallel
Async
(
this
,
args
,
ExceptionFunc
);
await
BeforeResponse
.
InvokeAsync
(
this
,
args
,
ExceptionFunc
);
}
}
await
TcpHelper
.
SendRaw
(
clientStream
,
connection
.
Stream
,
BufferSize
,
await
TcpHelper
.
SendRaw
(
clientStream
,
connection
.
Stream
,
BufferSize
,
(
buffer
,
offset
,
count
)
=>
{
args
.
OnDataSent
(
buffer
,
offset
,
count
);
},
(
buffer
,
offset
,
count
)
=>
{
args
.
OnDataReceived
(
buffer
,
offset
,
count
);
});
(
buffer
,
offset
,
count
)
=>
{
args
.
OnDataSent
(
buffer
,
offset
,
count
);
},
(
buffer
,
offset
,
count
)
=>
{
args
.
OnDataReceived
(
buffer
,
offset
,
count
);
});
args
.
Dispose
();
args
.
Dispose
();
break
;
break
;
...
@@ -478,15 +514,17 @@ namespace Titanium.Web.Proxy
...
@@ -478,15 +514,17 @@ namespace Titanium.Web.Proxy
//If 100 continue was the response inform that to the client
//If 100 continue was the response inform that to the client
if
(
Enable100ContinueBehaviour
)
if
(
Enable100ContinueBehaviour
)
{
{
var
clientStreamWriter
=
args
.
ProxyClient
.
ClientStreamWriter
;
if
(
request
.
Is100Continue
)
if
(
request
.
Is100Continue
)
{
{
await
args
.
ProxyClient
.
C
lientStreamWriter
.
WriteResponseStatusAsync
(
args
.
WebSession
.
Response
.
HttpVersion
,
(
int
)
HttpStatusCode
.
Continue
,
"Continue"
);
await
c
lientStreamWriter
.
WriteResponseStatusAsync
(
args
.
WebSession
.
Response
.
HttpVersion
,
(
int
)
HttpStatusCode
.
Continue
,
"Continue"
);
await
args
.
ProxyClient
.
C
lientStreamWriter
.
WriteLineAsync
();
await
c
lientStreamWriter
.
WriteLineAsync
();
}
}
else
if
(
request
.
ExpectationFailed
)
else
if
(
request
.
ExpectationFailed
)
{
{
await
args
.
ProxyClient
.
C
lientStreamWriter
.
WriteResponseStatusAsync
(
args
.
WebSession
.
Response
.
HttpVersion
,
(
int
)
HttpStatusCode
.
ExpectationFailed
,
"Expectation Failed"
);
await
c
lientStreamWriter
.
WriteResponseStatusAsync
(
args
.
WebSession
.
Response
.
HttpVersion
,
(
int
)
HttpStatusCode
.
ExpectationFailed
,
"Expectation Failed"
);
await
args
.
ProxyClient
.
C
lientStreamWriter
.
WriteLineAsync
();
await
c
lientStreamWriter
.
WriteLineAsync
();
}
}
}
}
...
@@ -513,8 +551,7 @@ namespace Titanium.Web.Proxy
...
@@ -513,8 +551,7 @@ namespace Titanium.Web.Proxy
//chunked send is not supported as of now
//chunked send is not supported as of now
request
.
ContentLength
=
body
.
Length
;
request
.
ContentLength
=
body
.
Length
;
var
newStream
=
args
.
WebSession
.
ServerConnection
.
Stream
;
await
args
.
WebSession
.
ServerConnection
.
StreamWriter
.
WriteAsync
(
body
);
await
newStream
.
WriteAsync
(
body
,
0
,
body
.
Length
);
}
}
else
else
{
{
...
@@ -523,7 +560,8 @@ namespace Titanium.Web.Proxy
...
@@ -523,7 +560,8 @@ namespace Titanium.Web.Proxy
//If its a post/put/patch request, then read the client html body and send it to server
//If its a post/put/patch request, then read the client html body and send it to server
if
(
request
.
HasBody
)
if
(
request
.
HasBody
)
{
{
await
SendClientRequestBody
(
args
);
HttpWriter
writer
=
args
.
WebSession
.
ServerConnection
.
StreamWriter
;
await
args
.
CopyRequestBodyAsync
(
writer
,
false
);
}
}
}
}
}
}
...
@@ -609,7 +647,7 @@ namespace Titanium.Web.Proxy
...
@@ -609,7 +647,7 @@ namespace Titanium.Web.Proxy
switch
(
header
.
Name
.
ToLower
())
switch
(
header
.
Name
.
ToLower
())
{
{
//these are the only encoding this proxy can read
//these are the only encoding this proxy can read
case
"accept-encoding"
:
case
KnownHeaders
.
AcceptEncoding
:
header
.
Value
=
"gzip,deflate"
;
header
.
Value
=
"gzip,deflate"
;
break
;
break
;
}
}
...
@@ -617,118 +655,5 @@ namespace Titanium.Web.Proxy
...
@@ -617,118 +655,5 @@ namespace Titanium.Web.Proxy
requestHeaders
.
FixProxyHeaders
();
requestHeaders
.
FixProxyHeaders
();
}
}
/// <summary>
/// This is called when the request is PUT/POST/PATCH to read the body
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private
async
Task
SendClientRequestBody
(
SessionEventArgs
args
)
{
// End the operation
var
postStream
=
args
.
WebSession
.
ServerConnection
.
Stream
;
//send the request body bytes to server
if
(
args
.
WebSession
.
Request
.
ContentLength
>
0
)
{
var
request
=
args
.
WebSession
.
Request
;
if
(
args
.
HasMulipartEventSubscribers
&&
request
.
IsMultipartFormData
)
{
string
boundary
=
HttpHelper
.
GetBoundaryFromContentType
(
request
.
ContentType
);
long
bytesToSend
=
args
.
WebSession
.
Request
.
ContentLength
;
while
(
bytesToSend
>
0
)
{
long
read
=
await
SendUntilBoundaryAsync
(
args
.
ProxyClient
.
ClientStream
,
postStream
,
bytesToSend
,
boundary
);
if
(
read
==
0
)
{
break
;
}
bytesToSend
-=
read
;
if
(
bytesToSend
>
0
)
{
var
headers
=
new
HeaderCollection
();
var
stream
=
new
CustomBufferedPeekStream
(
args
.
ProxyClient
.
ClientStream
);
await
HeaderParser
.
ReadHeaders
(
new
CustomBinaryReader
(
stream
,
BufferSize
),
headers
);
args
.
OnMultipartRequestPartSent
(
boundary
,
headers
);
}
}
}
else
{
await
args
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStream
(
postStream
,
args
.
WebSession
.
Request
.
ContentLength
);
}
}
//Need to revist, find any potential bugs
//send the request body bytes to server in chunks
else
if
(
args
.
WebSession
.
Request
.
IsChunked
)
{
await
args
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
postStream
);
}
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
public
async
Task
<
long
>
SendUntilBoundaryAsync
(
CustomBufferedStream
inputStream
,
CustomBufferedStream
outputStream
,
long
totalBytesToRead
,
string
boundary
)
{
int
bufferDataLength
=
0
;
// try to use the thread static buffer
var
buffer
=
BufferPool
.
GetBuffer
(
BufferSize
);
int
boundaryLength
=
boundary
.
Length
+
4
;
long
bytesRead
=
0
;
while
(
bytesRead
<
totalBytesToRead
&&
(
inputStream
.
DataAvailable
||
await
inputStream
.
FillBufferAsync
()))
{
byte
newChar
=
inputStream
.
ReadByteFromBuffer
();
buffer
[
bufferDataLength
]
=
newChar
;
bufferDataLength
++;
bytesRead
++;
if
(
bufferDataLength
>=
boundaryLength
)
{
int
startIdx
=
bufferDataLength
-
boundaryLength
;
if
(
buffer
[
startIdx
]
==
'-'
&&
buffer
[
startIdx
+
1
]
==
'-'
&&
buffer
[
bufferDataLength
-
2
]
==
'\r'
&&
buffer
[
bufferDataLength
-
1
]
==
'\n'
)
{
startIdx
+=
2
;
bool
ok
=
true
;
for
(
int
i
=
0
;
i
<
boundary
.
Length
;
i
++)
{
if
(
buffer
[
startIdx
+
i
]
!=
boundary
[
i
])
{
ok
=
false
;
break
;
}
}
if
(
ok
)
{
break
;
}
}
}
if
(
bufferDataLength
==
buffer
.
Length
)
{
//boundary is not longer than 70 bytes accounrding to the specification, so keeping the last 100 (minimum 74) bytes is enough
const
int
bytesToKeep
=
100
;
await
outputStream
.
WriteAsync
(
buffer
,
0
,
buffer
.
Length
-
bytesToKeep
);
Buffer
.
BlockCopy
(
buffer
,
buffer
.
Length
-
bytesToKeep
,
buffer
,
0
,
bytesToKeep
);
bufferDataLength
=
bytesToKeep
;
}
}
if
(
bytesRead
>
0
)
{
await
outputStream
.
WriteAsync
(
buffer
,
0
,
bufferDataLength
);
}
return
bytesRead
;
}
}
}
}
}
Titanium.Web.Proxy/ResponseHandler.cs
View file @
80dd44e5
...
@@ -43,7 +43,7 @@ namespace Titanium.Web.Proxy
...
@@ -43,7 +43,7 @@ namespace Titanium.Web.Proxy
//If user requested call back then do it
//If user requested call back then do it
if
(
BeforeResponse
!=
null
&&
!
response
.
ResponseLocked
)
if
(
BeforeResponse
!=
null
&&
!
response
.
ResponseLocked
)
{
{
await
BeforeResponse
.
Invoke
Parallel
Async
(
this
,
args
,
ExceptionFunc
);
await
BeforeResponse
.
InvokeAsync
(
this
,
args
,
ExceptionFunc
);
}
}
//if user requested to send request again
//if user requested to send request again
...
@@ -96,7 +96,7 @@ namespace Titanium.Web.Proxy
...
@@ -96,7 +96,7 @@ namespace Titanium.Web.Proxy
}
}
await
clientStreamWriter
.
WriteHeadersAsync
(
response
.
Headers
);
await
clientStreamWriter
.
WriteHeadersAsync
(
response
.
Headers
);
await
clientStreamWriter
.
Write
Response
BodyAsync
(
response
.
Body
,
isChunked
);
await
clientStreamWriter
.
WriteBodyAsync
(
response
.
Body
,
isChunked
);
}
}
else
else
{
{
...
@@ -105,12 +105,9 @@ namespace Titanium.Web.Proxy
...
@@ -105,12 +105,9 @@ namespace Titanium.Web.Proxy
//Write body if exists
//Write body if exists
if
(
response
.
HasBody
)
if
(
response
.
HasBody
)
{
{
await
clientStreamWriter
.
WriteResponseBodyAsync
(
BufferSize
,
args
.
WebSession
.
ServerConnection
.
StreamReader
,
await
args
.
CopyResponseBodyAsync
(
clientStreamWriter
,
false
);
response
.
IsChunked
,
response
.
ContentLength
);
}
}
}
}
await
clientStreamWriter
.
FlushAsync
();
}
}
catch
(
Exception
e
)
catch
(
Exception
e
)
{
{
...
...
Titanium.Web.Proxy/Shared/ProxyConstants.cs
View file @
80dd44e5
using
System.Text
;
namespace
Titanium.Web.Proxy.Shared
namespace
Titanium.Web.Proxy.Shared
{
{
/// <summary>
/// <summary>
/// Literals shared by Proxy Server
/// Literals shared by Proxy Server
...
@@ -14,10 +12,6 @@ namespace Titanium.Web.Proxy.Shared
...
@@ -14,10 +12,6 @@ namespace Titanium.Web.Proxy.Shared
internal
static
readonly
char
[]
SemiColonSplit
=
{
';'
};
internal
static
readonly
char
[]
SemiColonSplit
=
{
';'
};
internal
static
readonly
char
[]
EqualSplit
=
{
'='
};
internal
static
readonly
char
[]
EqualSplit
=
{
'='
};
internal
static
readonly
byte
[]
NewLineBytes
=
Encoding
.
ASCII
.
GetBytes
(
NewLine
);
internal
static
readonly
byte
[]
NewLine
=
{(
byte
)
'\r'
,
(
byte
)
'\n'
};
internal
static
readonly
byte
[]
ChunkEnd
=
Encoding
.
ASCII
.
GetBytes
(
0.
ToString
(
"x2"
)
+
NewLine
+
NewLine
);
internal
const
string
NewLine
=
"\r\n"
;
}
}
}
}
Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
View file @
80dd44e5
...
@@ -13,7 +13,7 @@
...
@@ -13,7 +13,7 @@
<ItemGroup>
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.3" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.3" />
<PackageReference Include="StreamExtended" Version="1.0.1
10
-beta" />
<PackageReference Include="StreamExtended" Version="1.0.1
32
-beta" />
</ItemGroup>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
...
@@ -21,7 +21,7 @@
...
@@ -21,7 +21,7 @@
<Version>4.4.0</Version>
<Version>4.4.0</Version>
</PackageReference>
</PackageReference>
<PackageReference Include="System.Security.Principal.Windows">
<PackageReference Include="System.Security.Principal.Windows">
<Version>4.4.
0
</Version>
<Version>4.4.
1
</Version>
</PackageReference>
</PackageReference>
</ItemGroup>
</ItemGroup>
...
...
Titanium.Web.Proxy/WinAuthHandler.cs
View file @
80dd44e5
...
@@ -3,6 +3,7 @@ using System.Collections.Generic;
...
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using
System.Linq
;
using
System.Linq
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network.WinAuth
;
using
Titanium.Web.Proxy.Network.WinAuth
;
...
@@ -84,9 +85,9 @@ namespace Titanium.Web.Proxy
...
@@ -84,9 +85,9 @@ namespace Titanium.Web.Proxy
string
scheme
=
authSchemes
.
FirstOrDefault
(
x
=>
authHeader
.
Value
.
Equals
(
x
,
StringComparison
.
OrdinalIgnoreCase
));
string
scheme
=
authSchemes
.
FirstOrDefault
(
x
=>
authHeader
.
Value
.
Equals
(
x
,
StringComparison
.
OrdinalIgnoreCase
));
//clear any existing headers to avoid confusing bad servers
//clear any existing headers to avoid confusing bad servers
if
(
args
.
WebSession
.
Request
.
Headers
.
NonUniqueHeaders
.
ContainsKey
(
"Authorization"
))
if
(
args
.
WebSession
.
Request
.
Headers
.
NonUniqueHeaders
.
ContainsKey
(
KnownHeaders
.
Authorization
))
{
{
args
.
WebSession
.
Request
.
Headers
.
NonUniqueHeaders
.
Remove
(
"Authorization"
);
args
.
WebSession
.
Request
.
Headers
.
NonUniqueHeaders
.
Remove
(
KnownHeaders
.
Authorization
);
}
}
//initial value will match exactly any of the schemes
//initial value will match exactly any of the schemes
...
@@ -94,16 +95,16 @@ namespace Titanium.Web.Proxy
...
@@ -94,16 +95,16 @@ namespace Titanium.Web.Proxy
{
{
string
clientToken
=
WinAuthHandler
.
GetInitialAuthToken
(
args
.
WebSession
.
Request
.
Host
,
scheme
,
args
.
Id
);
string
clientToken
=
WinAuthHandler
.
GetInitialAuthToken
(
args
.
WebSession
.
Request
.
Host
,
scheme
,
args
.
Id
);
var
auth
=
new
HttpHeader
(
"Authorization"
,
string
.
Concat
(
scheme
,
clientToken
));
var
auth
=
new
HttpHeader
(
KnownHeaders
.
Authorization
,
string
.
Concat
(
scheme
,
clientToken
));
//replace existing authorization header if any
//replace existing authorization header if any
if
(
args
.
WebSession
.
Request
.
Headers
.
Headers
.
ContainsKey
(
"Authorization"
))
if
(
args
.
WebSession
.
Request
.
Headers
.
Headers
.
ContainsKey
(
KnownHeaders
.
Authorization
))
{
{
args
.
WebSession
.
Request
.
Headers
.
Headers
[
"Authorization"
]
=
auth
;
args
.
WebSession
.
Request
.
Headers
.
Headers
[
KnownHeaders
.
Authorization
]
=
auth
;
}
}
else
else
{
{
args
.
WebSession
.
Request
.
Headers
.
Headers
.
Add
(
"Authorization"
,
auth
);
args
.
WebSession
.
Request
.
Headers
.
Headers
.
Add
(
KnownHeaders
.
Authorization
,
auth
);
}
}
//don't need to send body for Authorization request
//don't need to send body for Authorization request
...
@@ -122,7 +123,7 @@ namespace Titanium.Web.Proxy
...
@@ -122,7 +123,7 @@ namespace Titanium.Web.Proxy
string
clientToken
=
WinAuthHandler
.
GetFinalAuthToken
(
args
.
WebSession
.
Request
.
Host
,
serverToken
,
args
.
Id
);
string
clientToken
=
WinAuthHandler
.
GetFinalAuthToken
(
args
.
WebSession
.
Request
.
Host
,
serverToken
,
args
.
Id
);
//there will be an existing header from initial client request
//there will be an existing header from initial client request
args
.
WebSession
.
Request
.
Headers
.
Headers
[
"Authorization"
]
=
new
HttpHeader
(
"Authorization"
,
string
.
Concat
(
scheme
,
clientToken
));
args
.
WebSession
.
Request
.
Headers
.
Headers
[
KnownHeaders
.
Authorization
]
=
new
HttpHeader
(
KnownHeaders
.
Authorization
,
string
.
Concat
(
scheme
,
clientToken
));
//send body for final auth request
//send body for final auth request
if
(
args
.
WebSession
.
Request
.
HasBody
)
if
(
args
.
WebSession
.
Request
.
HasBody
)
...
...
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