22
33import static org .junit .Assert .assertEquals ;
44import static org .junit .Assert .assertNotNull ;
5+ import static org .junit .Assert .assertThrows ;
56import static org .junit .Assert .assertTrue ;
67import static org .junit .Assert .fail ;
78import static org .mockito .Mockito .mock ;
1819import java .io .IOException ;
1920import java .io .InputStreamReader ;
2021import java .io .PrintWriter ;
22+ import java .net .HttpRetryException ;
2123import java .net .Socket ;
2224import java .security .KeyStore ;
2325import java .util .concurrent .CountDownLatch ;
@@ -39,11 +41,9 @@ public class SslProxyTunnelEstablisherTest {
3941
4042 private TestSslProxy testProxy ;
4143 private SSLSocketFactory clientSslSocketFactory ;
42- private ProxyCredentialsProvider mProxyCredentialsProvider ;
4344
4445 @ Before
4546 public void setUp () throws Exception {
46- mProxyCredentialsProvider = mock (ProxyCredentialsProvider .class );
4747 // Create test certificates
4848 HeldCertificate proxyCa = new HeldCertificate .Builder ()
4949 .commonName ("Test Proxy CA" )
@@ -87,14 +87,15 @@ public void establishTunnelWithValidSslProxySucceeds() throws Exception {
8787 SslProxyTunnelEstablisher establisher = new SslProxyTunnelEstablisher ();
8888 String targetHost = "example.com" ;
8989 int targetPort = 443 ;
90+ ProxyCredentialsProvider proxyCredentialsProvider = mock (ProxyCredentialsProvider .class );
9091
9192 Socket tunnelSocket = establisher .establishTunnel (
9293 "localhost" ,
9394 testProxy .getPort (),
9495 targetHost ,
9596 targetPort ,
9697 clientSslSocketFactory ,
97- mProxyCredentialsProvider );
98+ proxyCredentialsProvider );
9899
99100 assertNotNull ("Tunnel socket should not be null" , tunnelSocket );
100101 assertTrue ("Tunnel socket should be connected" , tunnelSocket .isConnected ());
@@ -112,6 +113,7 @@ public void establishTunnelWithNotTrustedCertificatedThrows() throws Exception {
112113 SSLContext untrustedContext = SSLContext .getInstance ("TLS" );
113114 untrustedContext .init (null , null , null );
114115 SSLSocketFactory untrustedSocketFactory = untrustedContext .getSocketFactory ();
116+ ProxyCredentialsProvider proxyCredentialsProvider = mock (ProxyCredentialsProvider .class );
115117
116118 SslProxyTunnelEstablisher establisher = new SslProxyTunnelEstablisher ();
117119
@@ -122,7 +124,7 @@ public void establishTunnelWithNotTrustedCertificatedThrows() throws Exception {
122124 "example.com" ,
123125 443 ,
124126 untrustedSocketFactory ,
125- mProxyCredentialsProvider );
127+ proxyCredentialsProvider );
126128 fail ("Should have thrown exception for untrusted certificate" );
127129 } catch (IOException e ) {
128130 assertTrue ("Exception should be SSL-related" , e .getMessage ().contains ("certification" ));
@@ -132,6 +134,7 @@ public void establishTunnelWithNotTrustedCertificatedThrows() throws Exception {
132134 @ Test
133135 public void establishTunnelWithFailingProxyConnectionThrows () {
134136 SslProxyTunnelEstablisher establisher = new SslProxyTunnelEstablisher ();
137+ ProxyCredentialsProvider proxyCredentialsProvider = mock (ProxyCredentialsProvider .class );
135138
136139 try {
137140 establisher .establishTunnel (
@@ -140,7 +143,7 @@ public void establishTunnelWithFailingProxyConnectionThrows() {
140143 "example.com" ,
141144 443 ,
142145 clientSslSocketFactory ,
143- mProxyCredentialsProvider );
146+ proxyCredentialsProvider );
144147 fail ("Should have thrown exception for connection failure" );
145148 } catch (IOException e ) {
146149 // The implementation wraps the original exception with a descriptive message
@@ -149,26 +152,130 @@ public void establishTunnelWithFailingProxyConnectionThrows() {
149152 }
150153
151154 @ Test
152- public void bearerTokenIsPassedWhenSet () {
155+ public void bearerTokenIsPassedWhenSet () throws IOException , InterruptedException {
153156 SslProxyTunnelEstablisher establisher = new SslProxyTunnelEstablisher ();
154- try {
155- establisher .establishTunnel (
156- "localhost" ,
157- testProxy .getPort (),
158- "example.com" ,
159- 443 ,
160- clientSslSocketFactory ,
161- new ProxyCredentialsProvider () {
162- @ Override
163- public String getBearerToken () {
164- return "token" ;
165- }
166- });
167- boolean await = testProxy .getAuthorizationHeaderReceived ().await (5 , TimeUnit .SECONDS );
168- assertTrue ("Proxy should have received authorization header" , await );
169- } catch (IOException | InterruptedException e ) {
170- throw new RuntimeException (e );
171- }
157+ establisher .establishTunnel (
158+ "localhost" ,
159+ testProxy .getPort (),
160+ "example.com" ,
161+ 443 ,
162+ clientSslSocketFactory ,
163+ new ProxyCredentialsProvider () {
164+ @ Override
165+ public String getBearerToken () {
166+ return "token" ;
167+ }
168+ });
169+ boolean await = testProxy .getAuthorizationHeaderReceived ().await (5 , TimeUnit .SECONDS );
170+ assertTrue ("Proxy should have received authorization header" , await );
171+ }
172+
173+ @ Test
174+ public void establishTunnelWithNullCredentialsProviderDoesNotAddAuthHeader () throws Exception {
175+ SslProxyTunnelEstablisher establisher = new SslProxyTunnelEstablisher ();
176+
177+ Socket tunnelSocket = establisher .establishTunnel (
178+ "localhost" ,
179+ testProxy .getPort (),
180+ "example.com" ,
181+ 443 ,
182+ clientSslSocketFactory ,
183+ null );
184+
185+ assertNotNull (tunnelSocket );
186+ assertTrue (testProxy .getConnectRequestReceived ().await (5 , TimeUnit .SECONDS ));
187+
188+ assertEquals (1 , testProxy .getAuthorizationHeaderReceived ().getCount ());
189+
190+ tunnelSocket .close ();
191+ }
192+
193+ @ Test
194+ public void establishTunnelWithNullBearerTokenDoesNotAddAuthHeader () throws Exception {
195+ SslProxyTunnelEstablisher establisher = new SslProxyTunnelEstablisher ();
196+
197+ Socket tunnelSocket = establisher .establishTunnel (
198+ "localhost" ,
199+ testProxy .getPort (),
200+ "example.com" ,
201+ 443 ,
202+ clientSslSocketFactory ,
203+ () -> null );
204+
205+ assertNotNull (tunnelSocket );
206+ assertTrue (testProxy .getConnectRequestReceived ().await (5 , TimeUnit .SECONDS ));
207+
208+ assertEquals (1 , testProxy .getAuthorizationHeaderReceived ().getCount ());
209+
210+ tunnelSocket .close ();
211+ }
212+
213+ @ Test
214+ public void establishTunnelWithEmptyBearerTokenDoesNotAddAuthHeader () throws Exception {
215+ SslProxyTunnelEstablisher establisher = new SslProxyTunnelEstablisher ();
216+
217+ Socket tunnelSocket = establisher .establishTunnel (
218+ "localhost" ,
219+ testProxy .getPort (),
220+ "example.com" ,
221+ 443 ,
222+ clientSslSocketFactory ,
223+ () -> " " );
224+
225+ assertNotNull (tunnelSocket );
226+ assertTrue (testProxy .getConnectRequestReceived ().await (5 , TimeUnit .SECONDS ));
227+
228+ assertEquals (1 , testProxy .getAuthorizationHeaderReceived ().getCount ());
229+
230+ tunnelSocket .close ();
231+ }
232+
233+ @ Test
234+ public void establishTunnelWithNullStatusLineThrowsIOException () {
235+ testProxy .setConnectResponse (null );
236+ SslProxyTunnelEstablisher establisher = new SslProxyTunnelEstablisher ();
237+
238+ IOException exception = assertThrows (IOException .class , () -> establisher .establishTunnel (
239+ "localhost" ,
240+ testProxy .getPort (),
241+ "example.com" ,
242+ 443 ,
243+ clientSslSocketFactory ,
244+ null ));
245+
246+ assertNotNull (exception );
247+ }
248+
249+ @ Test
250+ public void establishTunnelWithMalformedStatusLineThrowsIOException () {
251+ testProxy .setConnectResponse ("HTTP/1.1" ); // Malformed, missing status code
252+ SslProxyTunnelEstablisher establisher = new SslProxyTunnelEstablisher ();
253+
254+ IOException exception = assertThrows (IOException .class , () -> establisher .establishTunnel (
255+ "localhost" ,
256+ testProxy .getPort (),
257+ "example.com" ,
258+ 443 ,
259+ clientSslSocketFactory ,
260+ null ));
261+
262+ assertNotNull (exception );
263+ }
264+
265+ @ Test
266+ public void establishTunnelWithProxyAuthRequiredThrowsHttpRetryException () {
267+ testProxy .setConnectResponse ("HTTP/1.1 407 Proxy Authentication Required" );
268+ SslProxyTunnelEstablisher establisher = new SslProxyTunnelEstablisher ();
269+
270+ HttpRetryException exception = assertThrows (HttpRetryException .class , () -> establisher .establishTunnel (
271+ "localhost" ,
272+ testProxy .getPort (),
273+ "example.com" ,
274+ 443 ,
275+ clientSslSocketFactory ,
276+ null ));
277+
278+ assertEquals (407 , exception .responseCode ());
172279 }
173280
174281 /**
@@ -182,6 +289,7 @@ private static class TestSslProxy extends Thread {
182289 private final CountDownLatch mConnectRequestReceived = new CountDownLatch (1 );
183290 private final CountDownLatch mAuthorizationHeaderReceived = new CountDownLatch (1 );
184291 private final AtomicReference <String > mReceivedConnectLine = new AtomicReference <>();
292+ private final AtomicReference <String > mConnectResponse = new AtomicReference <>("HTTP/1.1 200 Connection established" );
185293
186294 public TestSslProxy (int port , HeldCertificate serverCert ) {
187295 mPort = port ;
@@ -237,10 +345,13 @@ private void handleClient(Socket client) {
237345 }
238346 }
239347
240- // Send successful CONNECT response
241- writer .println ("HTTP/1.1 200 Connection established" );
242- writer .println ();
243- writer .flush ();
348+ // Send configured CONNECT response
349+ String response = mConnectResponse .get ();
350+ if (response != null ) {
351+ writer .println (response );
352+ writer .println ();
353+ writer .flush ();
354+ }
244355
245356 // Keep connection open for tunnel
246357 Thread .sleep (100 );
@@ -271,5 +382,9 @@ public CountDownLatch getAuthorizationHeaderReceived() {
271382 public String getReceivedConnectLine () {
272383 return mReceivedConnectLine .get ();
273384 }
385+
386+ public void setConnectResponse (String connectResponse ) {
387+ mConnectResponse .set (connectResponse );
388+ }
274389 }
275390}
0 commit comments