@@ -26,16 +26,16 @@ private static void HandleClient(TcpClient Client)
26
26
{
27
27
28
28
string connectionGroup = null ;
29
-
30
29
Stream clientStream = null ;
31
30
CustomBinaryReader clientStreamReader = null ;
32
31
StreamWriter connectStreamWriter = null ;
33
32
string tunnelHostName = null ;
34
33
int tunnelPort = 0 ;
34
+
35
35
try
36
36
{
37
- connectionGroup = Dns . GetHostEntry ( ( ( IPEndPoint ) Client . Client . RemoteEndPoint ) . Address ) . HostName ;
38
-
37
+ //Separate HttpWebRequest connection group for each client
38
+ connectionGroup = ( ( IPEndPoint ) Client . Client . RemoteEndPoint ) . Address . ToString ( ) ;
39
39
40
40
clientStream = Client . GetStream ( ) ;
41
41
@@ -55,7 +55,7 @@ private static void HandleClient(TcpClient Client)
55
55
{
56
56
throw new EndOfStreamException ( ) ;
57
57
}
58
- //break up the line into three components
58
+ //break up the line into three components (method, remote URL & Http Version)
59
59
String [ ] splitBuffer = httpCmd . Split ( spaceSplit , 3 ) ;
60
60
61
61
String method = splitBuffer [ 0 ] ;
@@ -73,12 +73,13 @@ private static void HandleClient(TcpClient Client)
73
73
RequestVersion = "HTTP/1.0" ;
74
74
}
75
75
76
+ //Client wants to create a secure tcp tunnel (its a HTTPS request)
76
77
if ( splitBuffer [ 0 ] . ToUpper ( ) == "CONNECT" )
77
78
{
78
79
//Browser wants to create a secure tunnel
79
80
//instead = we are going to perform a man in the middle "attack"
80
81
//the user's browser should warn them of the certification errors,
81
- //so we need to install our root certficate in users machine as Certificate Authority.
82
+ //to avoid that we need to install our root certficate in users machine as Certificate Authority.
82
83
remoteUri = "https://" + splitBuffer [ 1 ] ;
83
84
tunnelHostName = splitBuffer [ 1 ] . Split ( ':' ) [ 0 ] ;
84
85
int . TryParse ( splitBuffer [ 1 ] . Split ( ':' ) [ 1 ] , out tunnelPort ) ;
@@ -109,34 +110,39 @@ private static void HandleClient(TcpClient Client)
109
110
connectStreamWriter . Flush ( ) ;
110
111
111
112
112
-
113
+ //If port is not 443 its not a HTTP request, so just relay
113
114
if ( tunnelPort != 443 )
114
115
{
115
-
116
-
117
116
TcpHelper . SendRaw ( tunnelHostName , tunnelPort , clientStreamReader . BaseStream ) ;
118
117
119
118
if ( clientStream != null )
120
119
clientStream . Close ( ) ;
121
120
122
121
return ;
123
122
}
123
+
124
+ //Create the fake certificate signed using our fake certificate authority
124
125
Monitor . Enter ( certificateAccessLock ) ;
125
126
var _certificate = ProxyServer . CertManager . CreateCertificate ( tunnelHostName ) ;
126
127
Monitor . Exit ( certificateAccessLock ) ;
127
128
128
129
SslStream sslStream = null ;
130
+ //Pinned certificate clients cannot be proxied
131
+ //Example dropbox.com uses certificate pinning
132
+ //So just relay the request after identifying it by first failure
129
133
if ( ! pinnedCertificateClients . Contains ( tunnelHostName ) && isSecure )
130
134
{
131
135
sslStream = new SslStream ( clientStream , true ) ;
132
136
try
133
137
{
138
+ //Successfully managed to authenticate the client using the fake certificate
134
139
sslStream . AuthenticateAsServer ( _certificate , false , SslProtocols . Tls | SslProtocols . Ssl3 | SslProtocols . Ssl2 , false ) ;
135
140
}
136
141
137
142
catch ( AuthenticationException ex )
138
143
{
139
-
144
+ //if authentication failed it could be because client uses pinned certificates
145
+ //So add the hostname to this list so that next time we can relay without touching it (tunnel the request)
140
146
if ( pinnedCertificateClients . Contains ( tunnelHostName ) == false )
141
147
{
142
148
pinnedCertificateClients . Add ( tunnelHostName ) ;
@@ -147,8 +153,7 @@ private static void HandleClient(TcpClient Client)
147
153
}
148
154
else
149
155
{
150
-
151
-
156
+ //Hostname was a previously failed request due to certificate pinning, just relay (tunnel the request)
152
157
TcpHelper . SendRaw ( tunnelHostName , tunnelPort , clientStreamReader . BaseStream ) ;
153
158
154
159
if ( clientStream != null )
@@ -175,6 +180,7 @@ private static void HandleClient(TcpClient Client)
175
180
securehost = remoteUri ;
176
181
}
177
182
183
+ //Now create the request
178
184
HandleHttpSessionRequest ( Client , httpCmd , connectionGroup , clientStream , tunnelHostName , requestLines , clientStreamReader , securehost ) ;
179
185
180
186
@@ -203,10 +209,8 @@ private static void HandleClient(TcpClient Client)
203
209
finally
204
210
{
205
211
206
-
207
212
}
208
213
209
-
210
214
}
211
215
private static void HandleHttpSessionRequest ( TcpClient Client , string httpCmd , string connectionGroup , Stream clientStream , string tunnelHostName , List < string > requestLines , CustomBinaryReader clientStreamReader , string securehost )
212
216
{
@@ -221,6 +225,7 @@ private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, s
221
225
args . securehost = securehost ;
222
226
try
223
227
{
228
+ //break up the line into three components (method, remote URL & Http Version)
224
229
var splitBuffer = httpCmd . Split ( spaceSplit , 3 ) ;
225
230
226
231
if ( splitBuffer . Length != 3 )
@@ -264,10 +269,10 @@ private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, s
264
269
var rawHeader = requestLines [ i ] ;
265
270
String [ ] header = rawHeader . ToLower ( ) . Trim ( ) . Split ( colonSpaceSplit , 2 , StringSplitOptions . None ) ;
266
271
272
+ //if request was upgrade to web-socket protocol then relay the request without proxying
267
273
if ( ( header [ 0 ] == "upgrade" ) && ( header [ 1 ] == "websocket" ) )
268
274
{
269
275
270
-
271
276
TcpHelper . SendRaw ( httpCmd , tunnelHostName , ref requestLines , args . IsSSLRequest , clientStreamReader . BaseStream ) ;
272
277
273
278
if ( clientStream != null )
@@ -285,6 +290,7 @@ private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, s
285
290
args . ProxyRequest . AllowAutoRedirect = false ;
286
291
args . ProxyRequest . AutomaticDecompression = DecompressionMethods . None ;
287
292
293
+ //If requested interception
288
294
if ( BeforeRequest != null )
289
295
{
290
296
args . RequestHostname = args . ProxyRequest . RequestUri . Host ;
@@ -299,6 +305,7 @@ private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, s
299
305
300
306
BeforeRequest ( null , args ) ;
301
307
}
308
+
302
309
string tmpLine ;
303
310
if ( args . CancelRequest )
304
311
{
@@ -320,7 +327,7 @@ private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, s
320
327
args . ProxyRequest . ConnectionGroupName = connectionGroup ;
321
328
args . ProxyRequest . AllowWriteStreamBuffering = true ;
322
329
323
-
330
+ //If request was modified by user
324
331
if ( args . RequestWasModified )
325
332
{
326
333
ASCIIEncoding encoding = new ASCIIEncoding ( ) ;
@@ -333,14 +340,18 @@ private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, s
333
340
}
334
341
else
335
342
{
343
+ //If its a post/put request, then read the client html body and send it to server
336
344
if ( method . ToUpper ( ) == "POST" || method . ToUpper ( ) == "PUT" )
337
345
{
338
346
args . ProxyRequest . BeginGetRequestStream ( new AsyncCallback ( GetRequestStreamCallback ) , args ) ;
339
347
}
340
348
else
341
349
{
350
+ //otherwise wait for response asynchronously
342
351
args . ProxyRequest . BeginGetResponse ( new AsyncCallback ( HandleHttpSessionResponse ) , args ) ;
343
352
353
+ //Now read the next request (if keep-Alive is enabled, otherwise exit thus thread)
354
+ //If client is pipeling the request, this will be immediately hit before response for previous request was made
344
355
if ( args . ProxyRequest . KeepAlive )
345
356
{
346
357
requestLines = new List < string > ( ) ;
@@ -357,11 +368,6 @@ private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, s
357
368
358
369
}
359
370
360
-
361
-
362
-
363
-
364
-
365
371
}
366
372
catch ( IOException )
367
373
{
@@ -378,10 +384,6 @@ private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, s
378
384
finally
379
385
{
380
386
381
-
382
-
383
-
384
-
385
387
}
386
388
387
389
@@ -453,6 +455,8 @@ private static void ReadRequestHeaders(ref List<string> RequestLines, HttpWebReq
453
455
case "user-agent" :
454
456
WebRequest . UserAgent = header [ 1 ] ;
455
457
break ;
458
+ //revisit this, transfer-encoding is not a request header according to spec
459
+ //But how to identify if client is sending chunked body for PUT/POST?
456
460
case "transfer-encoding" :
457
461
if ( header [ 1 ] . ToLower ( ) == "chunked" )
458
462
WebRequest . SendChunked = true ;
@@ -468,7 +472,6 @@ private static void ReadRequestHeaders(ref List<string> RequestLines, HttpWebReq
468
472
if ( header . Length >= 2 )
469
473
WebRequest . Headers . Add ( header [ 0 ] , header [ 1 ] ) ;
470
474
471
-
472
475
break ;
473
476
}
474
477
@@ -519,20 +522,16 @@ private static void GetRequestStreamCallback(IAsyncResult AsynchronousResult)
519
522
520
523
postStream . Close ( ) ;
521
524
}
522
- catch ( IOException ex )
525
+ catch ( IOException )
523
526
{
524
527
525
-
526
528
args . ProxyRequest . KeepAlive = false ;
527
- Debug . WriteLine ( ex . Message ) ;
528
529
return ;
529
530
}
530
- catch ( WebException ex )
531
+ catch ( WebException )
531
532
{
532
533
533
-
534
534
args . ProxyRequest . KeepAlive = false ;
535
- Debug . WriteLine ( ex . Message ) ;
536
535
return ;
537
536
538
537
}
@@ -616,9 +615,11 @@ private static void GetRequestStreamCallback(IAsyncResult AsynchronousResult)
616
615
617
616
}
618
617
}
619
-
618
+ //Http request body sent, now wait asynchronously for response
620
619
args . ProxyRequest . BeginGetResponse ( new AsyncCallback ( HandleHttpSessionResponse ) , args ) ;
621
-
620
+
621
+ //Now read the next request (if keep-Alive is enabled, otherwise exit thus thread)
622
+ //If client is pipeling the request, this will be immediately hit before response for previous request was made
622
623
if ( args . ProxyRequest . KeepAlive )
623
624
{
624
625
string httpCmd , tmpLine ;
@@ -630,6 +631,8 @@ private static void GetRequestStreamCallback(IAsyncResult AsynchronousResult)
630
631
}
631
632
httpCmd = requestLines . Count ( ) > 0 ? requestLines [ 0 ] : null ;
632
633
TcpClient Client = args . Client ;
634
+
635
+ //Http request body sent, now wait for next request
633
636
HandleHttpSessionRequest ( Client , httpCmd , args . ProxyRequest . ConnectionGroupName , args . ClientStream , args . tunnelHostName , requestLines , args . ClientStreamReader , args . securehost ) ;
634
637
}
635
638
}
0 commit comments