HttpsURLConnectionTest.java revision 7387da1059039afc7d8fd3e48e4d0712fff5dfaf
1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package org.apache.harmony.luni.tests.internal.net.www.protocol.https;
19
20import dalvik.annotation.AndroidOnly;
21import dalvik.annotation.KnownFailure;
22import dalvik.annotation.TestTargetClass;
23import dalvik.annotation.TestTargets;
24import dalvik.annotation.TestLevel;
25import dalvik.annotation.TestTargetNew;
26
27import java.io.BufferedInputStream;
28import java.io.File;
29import java.io.FileInputStream;
30import java.io.FileNotFoundException;
31import java.io.FileOutputStream;
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
35import java.io.PrintStream;
36import java.net.Authenticator;
37import java.net.HttpURLConnection;
38import java.net.InetSocketAddress;
39import java.net.PasswordAuthentication;
40import java.net.Proxy;
41import java.net.ServerSocket;
42import java.net.Socket;
43import java.net.SocketTimeoutException;
44import java.net.URL;
45import java.security.KeyStore;
46import java.security.cert.Certificate;
47import java.util.Arrays;
48import javax.net.ssl.HostnameVerifier;
49import javax.net.ssl.HttpsURLConnection;
50import javax.net.ssl.KeyManagerFactory;
51import javax.net.ssl.SSLContext;
52import javax.net.ssl.SSLServerSocket;
53import javax.net.ssl.SSLSession;
54import javax.net.ssl.SSLSocket;
55import javax.net.ssl.SSLSocketFactory;
56import javax.net.ssl.TrustManagerFactory;
57
58import junit.framework.TestCase;
59
60/**
61 * Implementation independent test for HttpsURLConnection.
62 * The test needs certstore file placed in system classpath
63 * and named as "key_store." + the type of the
64 * default KeyStore installed in the system in lower case.
65 * <br>
66 * For example: if default KeyStore type in the system is BKS
67 * (i.e. java.security file sets up the property keystore.type=BKS),
68 * thus classpath should point to the directory with "key_store.bks"
69 * file.
70 * <br>
71 * This certstore file should contain self-signed certificate
72 * generated by keytool utility in a usual way.
73 * <br>
74 * The password to the certstore should be "password" (without quotes).
75 */
76@TestTargetClass(HttpsURLConnection.class)
77public class HttpsURLConnectionTest extends TestCase {
78
79    // the password to the store
80    private static final String KS_PASSWORD = "password";
81
82    // turn on/off logging
83    private static final boolean DO_LOG = false;
84
85    // read/connection timeout value
86    private static final int TIMEOUT = 5000;
87
88    // OK response code
89    private static final int OK_CODE = 200;
90
91    // Not Found response code
92    private static final int NOT_FOUND_CODE = 404;
93
94    // Proxy authentication required response code
95    private static final int AUTHENTICATION_REQUIRED_CODE = 407;
96
97    // fields keeping the system values of corresponding properties
98    private static String systemKeyStoreType;
99
100    private static String systemKeyStore;
101
102    private static String systemKeyStorePassword;
103
104    private static String systemTrustStoreType;
105
106    private static String systemTrustStore;
107
108    private static String systemTrustStorePassword;
109
110    private static File store;
111
112    static {
113        try {
114            store = File.createTempFile("key_store", "bks");
115        } catch (Exception e) {
116            // ignore
117        }
118    }
119
120    /**
121     * Checks that HttpsURLConnection's default SSLSocketFactory is operable.
122     */
123    @TestTargetNew(
124        level = TestLevel.PARTIAL_COMPLETE,
125        notes = "Verifies that HttpsURLConnection's default SSLSocketFactory is operable.",
126        method = "getDefaultSSLSocketFactory",
127        args = {}
128    )
129    @AndroidOnly("we only have a .bks key store in the test resources")
130    public void testGetDefaultSSLSocketFactory() throws Exception {
131        // set up the properties defining the default values needed by SSL stuff
132        setUpStoreProperties();
133
134        try {
135            SSLSocketFactory defaultSSLSF = HttpsURLConnection
136                    .getDefaultSSLSocketFactory();
137            ServerSocket ss = new ServerSocket(0);
138            Socket s = defaultSSLSF
139                    .createSocket("localhost", ss.getLocalPort());
140            ss.accept();
141            s.close();
142            ss.close();
143        } finally {
144            // roll the properties back to system values
145            tearDownStoreProperties();
146        }
147    }
148
149    /**
150     * Checks if HTTPS connection performs initial SSL handshake with the
151     * server working over SSL, sends encrypted HTTP request,
152     * and receives expected HTTP response. After HTTPS session if finished
153     * test checks connection state parameters established by
154     * HttpsURLConnection.
155     */
156    @TestTargetNew(
157        level = TestLevel.PARTIAL_COMPLETE,
158        notes = "Verifies  if HTTPS connection performs initial SSL handshake with the server working over SSL, sends encrypted HTTP request, and receives expected HTTP response.",
159        method = "setDefaultHostnameVerifier",
160        args = {javax.net.ssl.HostnameVerifier.class}
161    )
162    @KnownFailure("VM aborts after more than one test was run.")
163    @AndroidOnly("we only have a .bks key store in the test resources")
164    public void testHttpsConnection() throws Throwable {
165        // set up the properties defining the default values needed by SSL stuff
166        setUpStoreProperties();
167
168        try {
169            // create the SSL server socket acting as a server
170            SSLContext ctx = getContext();
171            ServerSocket ss = ctx.getServerSocketFactory()
172                    .createServerSocket(0);
173
174            // create the HostnameVerifier to check hostname verification
175            TestHostnameVerifier hnv = new TestHostnameVerifier();
176            HttpsURLConnection.setDefaultHostnameVerifier(hnv);
177
178            // create url connection to be tested
179            URL url = new URL("https://localhost:" + ss.getLocalPort());
180            HttpsURLConnection connection = (HttpsURLConnection) url
181                    .openConnection();
182
183            // perform the interaction between the peers
184            SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
185
186            // check the connection state
187            checkConnectionStateParameters(connection, peerSocket);
188
189            // should silently exit
190            connection.connect();
191        } finally {
192            // roll the properties back to system values
193            tearDownStoreProperties();
194        }
195    }
196
197    /**
198     * Tests the behaviour of HTTPS connection in case of unavailability
199     * of requested resource.
200     */
201    @TestTargets({
202        @TestTargetNew(
203            level = TestLevel.PARTIAL,
204            notes = "Verifies the behaviour of HTTPS connection in case of unavailability of requested resource.",
205            method = "setDoInput",
206            args = {boolean.class}
207        ),
208        @TestTargetNew(
209            level = TestLevel.PARTIAL,
210            notes = "Verifies the behaviour of HTTPS connection in case of unavailability of requested resource.",
211            method = "setConnectTimeout",
212            args = {int.class}
213        ),
214        @TestTargetNew(
215            level = TestLevel.PARTIAL,
216            notes = "Verifies the behaviour of HTTPS connection in case of unavailability of requested resource.",
217            method = "setReadTimeout",
218            args = {int.class}
219        )
220    })
221    @KnownFailure("VM aborts after more than one test was run.")
222    @AndroidOnly("we only have a .bks key store in the test resources")
223    public void testHttpsConnection_Not_Found_Response() throws Throwable {
224        // set up the properties defining the default values needed by SSL stuff
225        setUpStoreProperties();
226
227        try {
228            // create the SSL server socket acting as a server
229            SSLContext ctx = getContext();
230            ServerSocket ss = ctx.getServerSocketFactory()
231                    .createServerSocket(0);
232
233            // create the HostnameVerifier to check hostname verification
234            TestHostnameVerifier hnv = new TestHostnameVerifier();
235            HttpsURLConnection.setDefaultHostnameVerifier(hnv);
236
237            // create url connection to be tested
238            URL url = new URL("https://localhost:" + ss.getLocalPort());
239            HttpsURLConnection connection = (HttpsURLConnection) url
240                    .openConnection();
241
242            try {
243                doInteraction(connection, ss, NOT_FOUND_CODE);
244                fail("Expected exception was not thrown.");
245            } catch (FileNotFoundException e) {
246                if (DO_LOG) {
247                    System.out.println("Expected exception was thrown: "
248                            + e.getMessage());
249                }
250            }
251
252            // should silently exit
253            connection.connect();
254        } finally {
255            // roll the properties back to system values
256            tearDownStoreProperties();
257        }
258    }
259
260    /**
261     * Tests possibility to set up the default SSLSocketFactory
262     * to be used by HttpsURLConnection.
263     */
264    @TestTargetNew(
265        level = TestLevel.PARTIAL_COMPLETE,
266        notes = "Verifies possibility to set up the default SSLSocketFactory to be used by HttpsURLConnection.",
267        method = "setDefaultSSLSocketFactory",
268        args = {javax.net.ssl.SSLSocketFactory.class}
269    )
270    @AndroidOnly("we only have a .bks key store in the test resources")
271    public void testSetDefaultSSLSocketFactory() throws Throwable {
272        // create the SSLServerSocket which will be used by server side
273        SSLContext ctx = getContext();
274        SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory()
275                .createServerSocket(0);
276
277        SSLSocketFactory socketFactory = (SSLSocketFactory) ctx
278                .getSocketFactory();
279        // set up the factory as default
280        HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory);
281        // check the result
282        assertSame("Default SSLSocketFactory differs from expected",
283                socketFactory, HttpsURLConnection.getDefaultSSLSocketFactory());
284
285        // create the HostnameVerifier to check hostname verification
286        TestHostnameVerifier hnv = new TestHostnameVerifier();
287        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
288
289        // create HttpsURLConnection to be tested
290        URL url = new URL("https://localhost:" + ss.getLocalPort());
291        HttpsURLConnection connection = (HttpsURLConnection) url
292                .openConnection();
293
294        TestHostnameVerifier hnv_late = new TestHostnameVerifier();
295        // late initialization: should not be used for created connection
296        HttpsURLConnection.setDefaultHostnameVerifier(hnv_late);
297
298        // perform the interaction between the peers
299        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
300        // check the connection state
301        checkConnectionStateParameters(connection, peerSocket);
302        // check the verification process
303        assertTrue("Hostname verification was not done", hnv.verified);
304        assertFalse(
305                "Hostname verification should not be done by this verifier",
306                hnv_late.verified);
307        // check the used SSLSocketFactory
308        assertSame("Default SSLSocketFactory should be used",
309                HttpsURLConnection.getDefaultSSLSocketFactory(), connection
310                        .getSSLSocketFactory());
311
312        // should silently exit
313        connection.connect();
314    }
315
316    /**
317     * Tests possibility to set up the SSLSocketFactory
318     * to be used by HttpsURLConnection.
319     */
320    @TestTargetNew(
321        level = TestLevel.PARTIAL_COMPLETE,
322        notes = "Verifies possibility to set up the SSLSocketFactory to be used by HttpsURLConnection.",
323        method = "setSSLSocketFactory",
324        args = {javax.net.ssl.SSLSocketFactory.class}
325    )
326    @AndroidOnly("we only have a .bks key store in the test resources")
327    public void testSetSSLSocketFactory() throws Throwable {
328        // create the SSLServerSocket which will be used by server side
329        SSLContext ctx = getContext();
330        SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory()
331                .createServerSocket(0);
332
333        // create the HostnameVerifier to check hostname verification
334        TestHostnameVerifier hnv = new TestHostnameVerifier();
335        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
336
337        // create HttpsURLConnection to be tested
338        URL url = new URL("https://localhost:" + ss.getLocalPort());
339        HttpsURLConnection connection = (HttpsURLConnection) url
340                .openConnection();
341
342        SSLSocketFactory socketFactory = (SSLSocketFactory) ctx
343                .getSocketFactory();
344        connection.setSSLSocketFactory(socketFactory);
345
346        TestHostnameVerifier hnv_late = new TestHostnameVerifier();
347        // late initialization: should not be used for created connection
348        HttpsURLConnection.setDefaultHostnameVerifier(hnv_late);
349
350        // perform the interaction between the peers
351        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
352        // check the connection state
353        checkConnectionStateParameters(connection, peerSocket);
354        // check the verification process
355        assertTrue("Hostname verification was not done", hnv.verified);
356        assertFalse(
357                "Hostname verification should not be done by this verifier",
358                hnv_late.verified);
359        // check the used SSLSocketFactory
360        assertNotSame("Default SSLSocketFactory should not be used",
361                HttpsURLConnection.getDefaultSSLSocketFactory(), connection
362                        .getSSLSocketFactory());
363        assertSame("Result differs from expected", socketFactory, connection
364                .getSSLSocketFactory());
365
366        // should silently exit
367        connection.connect();
368    }
369
370    /**
371     * Tests the behaviour of HttpsURLConnection in case of retrieving
372     * of the connection state parameters before connection has been made.
373     */
374    @TestTargets({
375        @TestTargetNew(
376            level = TestLevel.PARTIAL_COMPLETE,
377            notes = "Verifies the behaviour of HttpsURLConnection in case of retrieving of the connection state parameters before connection has been made.",
378            method = "getCipherSuite",
379            args = {}
380        ),
381        @TestTargetNew(
382            level = TestLevel.PARTIAL_COMPLETE,
383            notes = "Verifies the behaviour of HttpsURLConnection in case of retrieving of the connection state parameters before connection has been made.",
384            method = "getPeerPrincipal",
385            args = {}
386        ),
387        @TestTargetNew(
388            level = TestLevel.PARTIAL_COMPLETE,
389            notes = "Verifies the behaviour of HttpsURLConnection in case of retrieving of the connection state parameters before connection has been made.",
390            method = "getLocalPrincipal",
391            args = {}
392        ),
393        @TestTargetNew(
394            level = TestLevel.PARTIAL_COMPLETE,
395            notes = "Verifies the behaviour of HttpsURLConnection in case of retrieving of the connection state parameters before connection has been made.",
396            method = "getServerCertificates",
397            args = {}
398        ),
399        @TestTargetNew(
400            level = TestLevel.PARTIAL_COMPLETE,
401            notes = "Verifies the behaviour of HttpsURLConnection in case of retrieving of the connection state parameters before connection has been made.",
402            method = "getLocalCertificates",
403            args = {}
404        )
405    })
406    @AndroidOnly("we only have a .bks key store in the test resources")
407    public void testUnconnectedStateParameters() throws Throwable {
408        // create HttpsURLConnection to be tested
409        URL url = new URL("https://localhost:55555");
410        HttpsURLConnection connection = (HttpsURLConnection) url
411                .openConnection();
412
413        try {
414            connection.getCipherSuite();
415            fail("Expected IllegalStateException was not thrown");
416        } catch (IllegalStateException e) {}
417        try {
418            connection.getPeerPrincipal();
419            fail("Expected IllegalStateException was not thrown");
420        } catch (IllegalStateException e) {}
421        try {
422            connection.getLocalPrincipal();
423            fail("Expected IllegalStateException was not thrown");
424        } catch (IllegalStateException e) {}
425
426        try {
427            connection.getServerCertificates();
428            fail("Expected IllegalStateException was not thrown");
429        } catch (IllegalStateException e) {}
430        try {
431            connection.getLocalCertificates();
432            fail("Expected IllegalStateException was not thrown");
433        } catch (IllegalStateException e) {}
434    }
435
436    /**
437     * Tests if setHostnameVerifier() method replaces default verifier.
438     */
439    @TestTargetNew(
440        level = TestLevel.PARTIAL_COMPLETE,
441        notes = "Verifies if setHostnameVerifier() method replaces default verifier.",
442        method = "setHostnameVerifier",
443        args = {javax.net.ssl.HostnameVerifier.class}
444    )
445    @KnownFailure("VM aborts after more than one test was run.")
446    @AndroidOnly("we only have a .bks key store in the test resources")
447    public void testSetHostnameVerifier() throws Throwable {
448        // setting up the properties pointing to the key/trust stores
449        setUpStoreProperties();
450
451        try {
452            // create the SSLServerSocket which will be used by server side
453            SSLServerSocket ss = (SSLServerSocket) getContext()
454                    .getServerSocketFactory().createServerSocket(0);
455
456            // create the HostnameVerifier to check that Hostname verification
457            // is done
458            TestHostnameVerifier hnv = new TestHostnameVerifier();
459            HttpsURLConnection.setDefaultHostnameVerifier(hnv);
460
461            // create HttpsURLConnection to be tested
462            URL url = new URL("https://localhost:" + ss.getLocalPort());
463            HttpsURLConnection connection = (HttpsURLConnection) url
464                    .openConnection();
465
466            TestHostnameVerifier hnv_late = new TestHostnameVerifier();
467            // replace default verifier
468            connection.setHostnameVerifier(hnv_late);
469
470            // perform the interaction between the peers and check the results
471            SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
472            assertTrue("Hostname verification was not done", hnv_late.verified);
473            assertFalse(
474                    "Hostname verification should not be done by this verifier",
475                    hnv.verified);
476            checkConnectionStateParameters(connection, peerSocket);
477
478            // should silently exit
479            connection.connect();
480        } finally {
481            // roll the properties back to system values
482            tearDownStoreProperties();
483        }
484    }
485
486    /**
487     * Tests the behaviour in case of sending the data to the server.
488     */
489    @TestTargetNew(
490        level = TestLevel.PARTIAL,
491        notes = "Verifies the behaviour in case of sending the data to the server.",
492        method = "setDoOutput",
493        args = {boolean.class}
494    )
495    @KnownFailure("VM aborts after more than one test was run.")
496    @AndroidOnly("we only have a .bks key store in the test resources")
497    public void test_doOutput() throws Throwable {
498        // setting up the properties pointing to the key/trust stores
499        setUpStoreProperties();
500
501        try {
502            // create the SSLServerSocket which will be used by server side
503            SSLServerSocket ss = (SSLServerSocket) getContext()
504                    .getServerSocketFactory().createServerSocket(0);
505
506            // create the HostnameVerifier to check that Hostname verification
507            // is done
508            TestHostnameVerifier hnv = new TestHostnameVerifier();
509            HttpsURLConnection.setDefaultHostnameVerifier(hnv);
510
511            // create HttpsURLConnection to be tested
512            URL url = new URL("https://localhost:" + ss.getLocalPort());
513            HttpsURLConnection connection = (HttpsURLConnection) url
514                    .openConnection();
515            connection.setDoOutput(true);
516
517            // perform the interaction between the peers and check the results
518            SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
519            checkConnectionStateParameters(connection, peerSocket);
520
521            // should silently exit
522            connection.connect();
523        } finally {
524            // roll the properties back to system values
525            tearDownStoreProperties();
526        }
527    }
528
529    /**
530     * Tests HTTPS connection process made through the proxy server.
531     */
532    @TestTargets({
533        @TestTargetNew(
534            level = TestLevel.PARTIAL,
535            notes = "Verifies HTTPS connection process made through the proxy server.",
536            method = "setDoInput",
537            args = {boolean.class}
538        ),
539        @TestTargetNew(
540            level = TestLevel.PARTIAL,
541            notes = "Verifies HTTPS connection process made through the proxy server.",
542            method = "setConnectTimeout",
543            args = {int.class}
544        ),
545        @TestTargetNew(
546            level = TestLevel.PARTIAL,
547            notes = "Verifies HTTPS connection process made through the proxy server.",
548            method = "setReadTimeout",
549            args = {int.class}
550        )
551    })
552    @KnownFailure("VM aborts after more than one test was run.")
553    @AndroidOnly("we only have a .bks key store in the test resources")
554    public void testProxyConnection() throws Throwable {
555        // setting up the properties pointing to the key/trust stores
556        setUpStoreProperties();
557
558        try {
559            // create the SSLServerSocket which will be used by server side
560            ServerSocket ss = new ServerSocket(0);
561
562            // create the HostnameVerifier to check that Hostname verification
563            // is done
564            TestHostnameVerifier hnv = new TestHostnameVerifier();
565            HttpsURLConnection.setDefaultHostnameVerifier(hnv);
566
567            // create HttpsURLConnection to be tested
568            URL url = new URL("https://requested.host:55556/requested.data");
569            HttpsURLConnection connection = (HttpsURLConnection) url
570                    .openConnection(new Proxy(Proxy.Type.HTTP,
571                            new InetSocketAddress("localhost", ss
572                                    .getLocalPort())));
573
574            // perform the interaction between the peers and check the results
575            SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
576            checkConnectionStateParameters(connection, peerSocket);
577
578            // should silently exit
579            connection.connect();
580        } finally {
581            // roll the properties back to system values
582            tearDownStoreProperties();
583        }
584    }
585
586    /**
587     * Tests HTTPS connection process made through the proxy server.
588     * Proxy server needs authentication.
589     */
590    @TestTargets({
591        @TestTargetNew(
592            level = TestLevel.PARTIAL,
593            notes = "Verifies HTTPS connection process made through the proxy server. Proxy server needs authentication.",
594            method = "setDoInput",
595            args = {boolean.class}
596        ),
597        @TestTargetNew(
598            level = TestLevel.PARTIAL,
599            notes = "Verifies HTTPS connection process made through the proxy server. Proxy server needs authentication.",
600            method = "setConnectTimeout",
601            args = {int.class}
602        ),
603        @TestTargetNew(
604            level = TestLevel.PARTIAL,
605            notes = "Verifies HTTPS connection process made through the proxy server. Proxy server needs authentication.",
606            method = "setReadTimeout",
607            args = {int.class}
608        )
609    })
610    @KnownFailure("VM aborts after more than one test was run.")
611    @AndroidOnly("we only have a .bks key store in the test resources")
612    public void testProxyAuthConnection() throws Throwable {
613        // setting up the properties pointing to the key/trust stores
614        setUpStoreProperties();
615
616        try {
617            // create the SSLServerSocket which will be used by server side
618            ServerSocket ss = new ServerSocket(0);
619
620            // create the HostnameVerifier to check that Hostname verification
621            // is done
622            TestHostnameVerifier hnv = new TestHostnameVerifier();
623            HttpsURLConnection.setDefaultHostnameVerifier(hnv);
624
625            Authenticator.setDefault(new Authenticator() {
626
627                protected PasswordAuthentication getPasswordAuthentication() {
628                    return new PasswordAuthentication("user", "password"
629                            .toCharArray());
630                }
631            });
632
633            // create HttpsURLConnection to be tested
634            URL url = new URL("https://requested.host:55555/requested.data");
635            HttpsURLConnection connection = (HttpsURLConnection) url
636                    .openConnection(new Proxy(Proxy.Type.HTTP,
637                            new InetSocketAddress("localhost", ss
638                                    .getLocalPort())));
639
640            // perform the interaction between the peers and check the results
641            SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
642            checkConnectionStateParameters(connection, peerSocket);
643
644            // should silently exit
645            connection.connect();
646        } finally {
647            // roll the properties back to system values
648            tearDownStoreProperties();
649        }
650    }
651
652    /**
653     * Tests HTTPS connection process made through the proxy server.
654     * 2 HTTPS connections are opened for one URL. For the first time
655     * the connection is opened through one proxy,
656     * for the second time through another.
657     */
658    @TestTargets({
659        @TestTargetNew(
660            level = TestLevel.PARTIAL_COMPLETE,
661            notes = "Verifies HTTPS connection process made through the proxy server.",
662            method = "getCipherSuite",
663            args = {}
664        ),
665        @TestTargetNew(
666            level = TestLevel.PARTIAL_COMPLETE,
667            notes = "Verifies HTTPS connection process made through the proxy server.",
668            method = "getLocalPrincipal",
669            args = {}
670        ),
671        @TestTargetNew(
672            level = TestLevel.PARTIAL_COMPLETE,
673            notes = "Verifies HTTPS connection process made through the proxy server.",
674            method = "getPeerPrincipal",
675            args = {}
676        )
677    })
678    @KnownFailure("VM aborts after more than one test was run.")
679    @AndroidOnly("we only have a .bks key store in the test resources")
680    public void testConsequentProxyConnection() throws Throwable {
681        // setting up the properties pointing to the key/trust stores
682        setUpStoreProperties();
683
684        try {
685            // create the SSLServerSocket which will be used by server side
686            ServerSocket ss = new ServerSocket(0);
687
688            // create the HostnameVerifier to check that Hostname verification
689            // is done
690            TestHostnameVerifier hnv = new TestHostnameVerifier();
691            HttpsURLConnection.setDefaultHostnameVerifier(hnv);
692
693            // create HttpsURLConnection to be tested
694            URL url = new URL("https://requested.host:55555/requested.data");
695            HttpsURLConnection connection = (HttpsURLConnection) url
696                    .openConnection(new Proxy(Proxy.Type.HTTP,
697                            new InetSocketAddress("localhost", ss
698                                    .getLocalPort())));
699
700            // perform the interaction between the peers and check the results
701            SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
702            checkConnectionStateParameters(connection, peerSocket);
703
704            // create another SSLServerSocket which will be used by server side
705            ss = new ServerSocket(0);
706
707            connection = (HttpsURLConnection) url.openConnection(new Proxy(
708                    Proxy.Type.HTTP, new InetSocketAddress("localhost", ss
709                            .getLocalPort())));
710
711            // perform the interaction between the peers and check the results
712            peerSocket = (SSLSocket) doInteraction(connection, ss);
713            checkConnectionStateParameters(connection, peerSocket);
714        } finally {
715            // roll the properties back to system values
716            tearDownStoreProperties();
717        }
718    }
719
720    /**
721     * Tests HTTPS connection process made through the proxy server.
722     * Proxy server needs authentication.
723     * Client sends data to the server.
724     */
725    @TestTargets({
726        @TestTargetNew(
727            level = TestLevel.PARTIAL,
728            notes = "Verifies HTTPS connection process made through the proxy server. Proxy server needs authentication. Client sends data to the server.",
729            method = "setDoInput",
730            args = {boolean.class}
731        ),
732        @TestTargetNew(
733            level = TestLevel.PARTIAL,
734            notes = "Verifies HTTPS connection process made through the proxy server. Proxy server needs authentication. Client sends data to the server.",
735            method = "setConnectTimeout",
736            args = {int.class}
737        ),
738        @TestTargetNew(
739            level = TestLevel.PARTIAL,
740            notes = "Verifies HTTPS connection process made through the proxy server. Proxy server needs authentication. Client sends data to the server.",
741            method = "setReadTimeout",
742            args = {int.class}
743        ),
744        @TestTargetNew(
745            level = TestLevel.PARTIAL,
746            notes = "Verifies HTTPS connection process made through the proxy server. Proxy server needs authentication. Client sends data to the server.",
747            method = "setDoOutput",
748            args = {boolean.class}
749        )
750    })
751    @KnownFailure("VM aborts after more than one test was run.")
752    @AndroidOnly("we only have a .bks key store in the test resources")
753    public void testProxyAuthConnection_doOutput() throws Throwable {
754        // setting up the properties pointing to the key/trust stores
755        setUpStoreProperties();
756
757        try {
758            // create the SSLServerSocket which will be used by server side
759            ServerSocket ss = new ServerSocket(0);
760
761            // create the HostnameVerifier to check that Hostname verification
762            // is done
763            TestHostnameVerifier hnv = new TestHostnameVerifier();
764            HttpsURLConnection.setDefaultHostnameVerifier(hnv);
765
766            Authenticator.setDefault(new Authenticator() {
767
768                protected PasswordAuthentication getPasswordAuthentication() {
769                    return new PasswordAuthentication("user", "password"
770                            .toCharArray());
771                }
772            });
773
774            // create HttpsURLConnection to be tested
775            URL url = new URL("https://requested.host:55554/requested.data");
776            HttpsURLConnection connection = (HttpsURLConnection) url
777                    .openConnection(new Proxy(Proxy.Type.HTTP,
778                            new InetSocketAddress("localhost", ss
779                                    .getLocalPort())));
780            connection.setDoOutput(true);
781
782            // perform the interaction between the peers and check the results
783            SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss,
784                    OK_CODE, true);
785            checkConnectionStateParameters(connection, peerSocket);
786        } finally {
787            // roll the properties back to system values
788            tearDownStoreProperties();
789        }
790    }
791
792    /**
793     * Tests HTTPS connection process made through the proxy server.
794     * Proxy server needs authentication but client fails to authenticate
795     * (Authenticator was not set up in the system).
796     */
797    @TestTargets({
798        @TestTargetNew(
799            level = TestLevel.PARTIAL,
800            notes = "Verifies HTTPS connection process made through the proxy server. Proxy server needs authentication but client fails to authenticate (Authenticator was not set up in the system).",
801            method = "setDoInput",
802            args = {boolean.class}
803        ),
804        @TestTargetNew(
805            level = TestLevel.PARTIAL,
806            notes = "Verifies HTTPS connection process made through the proxy server. Proxy server needs authentication but client fails to authenticate (Authenticator was not set up in the system).",
807            method = "setConnectTimeout",
808            args = {int.class}
809        ),
810        @TestTargetNew(
811            level = TestLevel.PARTIAL,
812            notes = "Verifies HTTPS connection process made through the proxy server. Proxy server needs authentication but client fails to authenticate (Authenticator was not set up in the system).",
813            method = "setReadTimeout",
814            args = {int.class}
815        )
816    })
817    @KnownFailure("VM aborts after more than one test was run.")
818    @AndroidOnly("we only have a .bks key store in the test resources")
819    public void testProxyAuthConnectionFailed() throws Throwable {
820        // setting up the properties pointing to the key/trust stores
821        setUpStoreProperties();
822
823        try {
824            // create the SSLServerSocket which will be used by server side
825            ServerSocket ss = new ServerSocket(0);
826
827            // create the HostnameVerifier to check that Hostname verification
828            // is done
829            TestHostnameVerifier hnv = new TestHostnameVerifier();
830            HttpsURLConnection.setDefaultHostnameVerifier(hnv);
831
832            // create HttpsURLConnection to be tested
833            URL url = new URL("https://requested.host:55555/requested.data");
834            HttpURLConnection connection = (HttpURLConnection) url
835                    .openConnection(new Proxy(Proxy.Type.HTTP,
836                            new InetSocketAddress("localhost", ss
837                                    .getLocalPort())));
838
839            // perform the interaction between the peers and check the results
840            try {
841                doInteraction(connection, ss, AUTHENTICATION_REQUIRED_CODE,
842                        true);
843            } catch (IOException e) {
844                // SSL Tunnelling failed
845                if (DO_LOG) {
846                    System.out.println("Got expected IOException: "
847                            + e.getMessage());
848                }
849            }
850        } finally {
851            // roll the properties back to system values
852            tearDownStoreProperties();
853        }
854    }
855
856    /**
857     * Tests the behaviour of HTTPS connection in case of unavailability
858     * of requested resource.
859     */
860    @TestTargets({
861        @TestTargetNew(
862            level = TestLevel.PARTIAL,
863            notes = "Verifies the behaviour of HTTPS connection in case of unavailability of requested resource.",
864            method = "setDoInput",
865            args = {boolean.class}
866        ),
867        @TestTargetNew(
868            level = TestLevel.PARTIAL,
869            notes = "Verifies the behaviour of HTTPS connection in case of unavailability of requested resource.",
870            method = "setConnectTimeout",
871            args = {int.class}
872        ),
873        @TestTargetNew(
874            level = TestLevel.PARTIAL,
875            notes = "Verifies the behaviour of HTTPS connection in case of unavailability of requested resource.",
876            method = "setReadTimeout",
877            args = {int.class}
878        )
879    })
880    @KnownFailure("VM aborts after more than one test was run.")
881    @AndroidOnly("we only have a .bks key store in the test resources")
882    public void testProxyConnection_Not_Found_Response() throws Throwable {
883        // setting up the properties pointing to the key/trust stores
884        setUpStoreProperties();
885
886        try {
887            // create the SSLServerSocket which will be used by server side
888            ServerSocket ss = new ServerSocket(0);
889
890            // create the HostnameVerifier to check that Hostname verification
891            // is done
892            TestHostnameVerifier hnv = new TestHostnameVerifier();
893            HttpsURLConnection.setDefaultHostnameVerifier(hnv);
894
895            // create HttpsURLConnection to be tested
896            URL url = new URL("https://localhost:" + ss.getLocalPort());
897            HttpURLConnection connection = (HttpURLConnection) url
898                    .openConnection(new Proxy(Proxy.Type.HTTP,
899                            new InetSocketAddress("localhost", ss
900                                    .getLocalPort())));
901
902            try {
903                doInteraction(connection, ss, NOT_FOUND_CODE); // NOT FOUND
904                fail("Expected exception was not thrown.");
905            } catch (FileNotFoundException e) {
906                if (DO_LOG) {
907                    System.out.println("Expected exception was thrown: "
908                            + e.getMessage());
909                }
910            }
911        } finally {
912            // roll the properties back to system values
913            tearDownStoreProperties();
914        }
915    }
916
917    // ---------------------------------------------------------------------
918    // ------------------------ Staff Methods ------------------------------
919    // ---------------------------------------------------------------------
920
921    /**
922     * Log the name of the test case to be executed.
923     */
924    public void setUp() throws Exception {
925        if (DO_LOG) {
926            System.out.println();
927            System.out.println("------------------------");
928            System.out.println("------ " + getName());
929            System.out.println("------------------------");
930        }
931
932        if (store != null) {
933            String ksFileName = "org/apache/harmony/luni/tests/key_store."
934                    + KeyStore.getDefaultType().toLowerCase();
935            InputStream in = ClassLoader.getSystemClassLoader()
936                    .getResourceAsStream(ksFileName);
937            FileOutputStream out = new FileOutputStream(store);
938            BufferedInputStream bufIn = new BufferedInputStream(in, 8192);
939            while (bufIn.available() > 0) {
940                byte[] buf = new byte[128];
941                int read = bufIn.read(buf);
942                out.write(buf, 0, read);
943            }
944            bufIn.close();
945            out.close();
946        } else {
947            fail("couldn't set up key store");
948        }
949    }
950
951    public void tearDown() {
952        if (store != null) {
953            store.delete();
954        }
955    }
956
957    /**
958     * Checks the HttpsURLConnection getter's values and compares
959     * them with actual corresponding values of remote peer.
960     */
961    public static void checkConnectionStateParameters(
962            HttpsURLConnection clientConnection, SSLSocket serverPeer)
963            throws Exception {
964        SSLSession session = serverPeer.getSession();
965
966        assertEquals(session.getCipherSuite(), clientConnection
967                .getCipherSuite());
968
969        assertEquals(session.getLocalPrincipal(), clientConnection
970                .getPeerPrincipal());
971
972        assertEquals(session.getPeerPrincipal(), clientConnection
973                .getLocalPrincipal());
974
975        Certificate[] serverCertificates = clientConnection
976                .getServerCertificates();
977        Certificate[] localCertificates = session.getLocalCertificates();
978        assertTrue("Server certificates differ from expected", Arrays.equals(
979                serverCertificates, localCertificates));
980
981        localCertificates = clientConnection.getLocalCertificates();
982        serverCertificates = session.getPeerCertificates();
983        assertTrue("Local certificates differ from expected", Arrays.equals(
984                serverCertificates, localCertificates));
985    }
986
987    /**
988     * Returns the file name of the key/trust store. The key store file
989     * (named as "key_store." + extension equals to the default KeyStore
990     * type installed in the system in lower case) is searched in classpath.
991     * @throws AssertionFailedError if property was not set
992     * or file does not exist.
993     */
994    private static String getKeyStoreFileName() {
995        return store.getAbsolutePath();
996    }
997
998    /**
999     * Builds and returns the context used for secure socket creation.
1000     */
1001    private static SSLContext getContext() throws Exception {
1002        String type = KeyStore.getDefaultType();
1003        SSLContext ctx;
1004
1005        String keyStore = getKeyStoreFileName();
1006        File keyStoreFile = new File(keyStore);
1007
1008        FileInputStream fis = new FileInputStream(keyStoreFile);
1009
1010        KeyStore ks = KeyStore.getInstance(type);
1011        ks.load(fis, KS_PASSWORD.toCharArray());
1012
1013        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
1014                .getDefaultAlgorithm());
1015        kmf.init(ks, KS_PASSWORD.toCharArray());
1016
1017        TrustManagerFactory tmf = TrustManagerFactory
1018                .getInstance(TrustManagerFactory.getDefaultAlgorithm());
1019        tmf.init(ks);
1020
1021        ctx = SSLContext.getInstance("TLSv1");
1022        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
1023
1024        return ctx;
1025    }
1026
1027    /**
1028     * Sets up the properties pointing to the key store and trust store
1029     * and used as default values by JSSE staff. This is needed to test
1030     * HTTPS behaviour in the case of default SSL Socket Factories.
1031     */
1032    private static void setUpStoreProperties() throws Exception {
1033        String type = KeyStore.getDefaultType();
1034
1035        systemKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType");
1036        systemKeyStore = System.getProperty("javax.net.ssl.keyStore");
1037        systemKeyStorePassword = System
1038                .getProperty("javax.net.ssl.keyStorePassword");
1039
1040        systemTrustStoreType = System
1041                .getProperty("javax.net.ssl.trustStoreType");
1042        systemTrustStore = System.getProperty("javax.net.ssl.trustStore");
1043        systemTrustStorePassword = System
1044                .getProperty("javax.net.ssl.trustStorePassword");
1045
1046        System.setProperty("javax.net.ssl.keyStoreType", type);
1047        System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName());
1048        System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD);
1049
1050        System.setProperty("javax.net.ssl.trustStoreType", type);
1051        System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName());
1052        System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD);
1053    }
1054
1055    /**
1056     * Rolls back the values of system properties.
1057     */
1058    private static void tearDownStoreProperties() {
1059        if (systemKeyStoreType == null) {
1060            System.clearProperty("javax.net.ssl.keyStoreType");
1061        } else {
1062            System
1063                    .setProperty("javax.net.ssl.keyStoreType",
1064                            systemKeyStoreType);
1065        }
1066        if (systemKeyStore == null) {
1067            System.clearProperty("javax.net.ssl.keyStore");
1068        } else {
1069            System.setProperty("javax.net.ssl.keyStore", systemKeyStore);
1070        }
1071        if (systemKeyStorePassword == null) {
1072            System.clearProperty("javax.net.ssl.keyStorePassword");
1073        } else {
1074            System.setProperty("javax.net.ssl.keyStorePassword",
1075                    systemKeyStorePassword);
1076        }
1077
1078        if (systemTrustStoreType == null) {
1079            System.clearProperty("javax.net.ssl.trustStoreType");
1080        } else {
1081            System.setProperty("javax.net.ssl.trustStoreType",
1082                    systemTrustStoreType);
1083        }
1084        if (systemTrustStore == null) {
1085            System.clearProperty("javax.net.ssl.trustStore");
1086        } else {
1087            System.setProperty("javax.net.ssl.trustStore", systemTrustStore);
1088        }
1089        if (systemTrustStorePassword == null) {
1090            System.clearProperty("javax.net.ssl.trustStorePassword");
1091        } else {
1092            System.setProperty("javax.net.ssl.trustStorePassword",
1093                    systemTrustStorePassword);
1094        }
1095    }
1096
1097    /**
1098     * Performs interaction between client's HttpURLConnection and
1099     * servers side (ServerSocket).
1100     */
1101    public static Socket doInteraction(
1102            final HttpURLConnection clientConnection,
1103            final ServerSocket serverSocket) throws Throwable {
1104        return doInteraction(clientConnection, serverSocket, OK_CODE, false);
1105    }
1106
1107    /**
1108     * Performs interaction between client's HttpURLConnection and
1109     * servers side (ServerSocket). Server will response with specified
1110     * response code.
1111     */
1112    public static Socket doInteraction(
1113            final HttpURLConnection clientConnection,
1114            final ServerSocket serverSocket, final int responseCode)
1115            throws Throwable {
1116        return doInteraction(clientConnection, serverSocket, responseCode,
1117                false);
1118    }
1119
1120    /**
1121     * Performs interaction between client's HttpURLConnection and
1122     * servers side (ServerSocket). Server will response with specified
1123     * response code.
1124     * @param doAuthentication specifies
1125     * if the server needs client authentication.
1126     */
1127    public static Socket doInteraction(
1128            final HttpURLConnection clientConnection,
1129            final ServerSocket serverSocket, final int responseCode,
1130            final boolean doAuthentication) throws Throwable {
1131
1132        // set up the connection
1133        clientConnection.setDoInput(true);
1134        clientConnection.setConnectTimeout(TIMEOUT);
1135        clientConnection.setReadTimeout(TIMEOUT);
1136
1137        ServerWork server = new ServerWork(serverSocket, responseCode,
1138                doAuthentication);
1139
1140        ClientConnectionWork client = new ClientConnectionWork(clientConnection);
1141
1142        server.start();
1143        client.start();
1144
1145        client.join();
1146        server.join();
1147
1148        if (client.thrown != null) {
1149            if (responseCode != OK_CODE) { // not OK response expected
1150                // it is probably expected exception, keep it as is
1151                throw client.thrown;
1152            }
1153            if ((client.thrown instanceof SocketTimeoutException)
1154                    && (server.thrown != null)) {
1155                // server's exception is more informative in this case
1156                throw new Exception(server.thrown);
1157            } else {
1158                throw new Exception(client.thrown);
1159            }
1160        }
1161        if (server.thrown != null) {
1162            throw server.thrown;
1163        }
1164        return server.peerSocket;
1165    }
1166
1167    /**
1168     * The host name verifier used in test.
1169     */
1170    static class TestHostnameVerifier implements HostnameVerifier {
1171
1172        boolean verified = false;
1173
1174        public boolean verify(String hostname, SSLSession session) {
1175            if (DO_LOG) {
1176                System.out.println("***> verification " + hostname + " "
1177                        + session.getPeerHost());
1178            }
1179            verified = true;
1180            return true;
1181        }
1182    }
1183
1184    /**
1185     * The base class for mock Client and Server.
1186     */
1187    static class Work extends Thread {
1188
1189        /**
1190         * The header of OK HTTP response.
1191         */
1192        static String responseHead = "HTTP/1.1 200 OK\n";
1193
1194        /**
1195         * The content of the response.
1196         */
1197        static String plainResponseContent = "<HTML>\n"
1198                + "<HEAD><TITLE>Plain Response Content</TITLE></HEAD>\n"
1199                + "</HTML>";
1200
1201        /**
1202         * The tail of the response.
1203         */
1204        static String plainResponseTail = "Content-type: text/html\n"
1205                + "Content-length: " + plainResponseContent.length() + "\n\n"
1206                + plainResponseContent;
1207
1208        /**
1209         * The response message to be sent in plain (HTTP) format.
1210         */
1211        static String plainResponse = responseHead + plainResponseTail;
1212
1213        /**
1214         * The content of the response to be sent during HTTPS session.
1215         */
1216        static String httpsResponseContent = "<HTML>\n"
1217                + "<HEAD><TITLE>HTTPS Response Content</TITLE></HEAD>\n"
1218                + "</HTML>";
1219
1220        /**
1221         * The tail of the response to be sent during HTTPS session.
1222         */
1223        static String httpsResponseTail = "Content-type: text/html\n"
1224                + "Content-length: " + httpsResponseContent.length() + "\n\n"
1225                + httpsResponseContent;
1226
1227        /**
1228         * The response requiring client's proxy authentication.
1229         */
1230        static String respAuthenticationRequired = "HTTP/1.0 407 Proxy authentication required\n"
1231                + "Proxy-authenticate: Basic realm=\"localhost\"\n\n";
1232
1233        /**
1234         * The data to be posted by client to the server.
1235         */
1236        static String clientsData = "_.-^ Client's Data ^-._";
1237
1238        /**
1239         * The exception thrown during peers interaction.
1240         */
1241        protected Throwable thrown;
1242
1243        /**
1244         * The print stream used for debug log.
1245         * If it is null debug info will not be printed.
1246         */
1247        private PrintStream out = new PrintStream(System.out);
1248
1249        /**
1250         * Prints log message.
1251         */
1252        public synchronized void log(String message) {
1253            if (DO_LOG && (out != null)) {
1254                System.out.println("[" + getName() + "]: " + message);
1255            }
1256        }
1257    }
1258
1259    /**
1260     * The class used for server side works.
1261     */
1262    static class ServerWork extends Work {
1263
1264        // the server socket used for connection
1265        private ServerSocket serverSocket;
1266
1267        // the socket connected with client peer
1268        private Socket peerSocket;
1269
1270        // indicates if the server acts as proxy server
1271        private boolean actAsProxy;
1272
1273        // indicates if the server needs proxy authentication
1274        private boolean needProxyAuthentication;
1275
1276        // response code to be send to the client peer
1277        private int responseCode;
1278
1279        /**
1280         * Creates the thread acting as a server side.
1281         */
1282        public ServerWork(ServerSocket serverSocket) {
1283            // the server does not require proxy authentication
1284            // and sends OK_CODE (OK) response code
1285            this(serverSocket, OK_CODE, false);
1286        }
1287
1288        /**
1289         * Creates the thread acting as a server side.
1290         * @param serverSocket the server socket to be used during connection
1291         * @param responseCode the response code to be sent to the client
1292         * @param needProxyAuthentication
1293         * indicates if the server needs proxy authentication
1294         */
1295        public ServerWork(ServerSocket serverSocket, int responseCode,
1296                boolean needProxyAuthentication) {
1297            this.serverSocket = serverSocket;
1298            this.responseCode = responseCode;
1299            this.needProxyAuthentication = needProxyAuthentication;
1300            // will act as a proxy server if the specified server socket
1301            // is not a secure server socket
1302            if (serverSocket instanceof SSLServerSocket) {
1303                // demand client to send its certificate
1304                ((SSLServerSocket) serverSocket).setNeedClientAuth(true);
1305                // work as a HTTPS server, not as HTTP proxy
1306                this.actAsProxy = false;
1307            } else {
1308                this.actAsProxy = true;
1309            }
1310            this.actAsProxy = !(serverSocket instanceof SSLServerSocket);
1311            setName(this.actAsProxy ? "Proxy Server" : "Server");
1312        }
1313
1314        /**
1315         * Closes the connection.
1316         */
1317        public void closeSocket(Socket socket) {
1318            try {
1319                socket.getInputStream().close();
1320            } catch (IOException e) {}
1321            try {
1322                socket.getOutputStream().close();
1323            } catch (IOException e) {}
1324            try {
1325                socket.close();
1326            } catch (IOException e) {}
1327        }
1328
1329        /**
1330         * Performs the actual server work.
1331         * If some exception occurs during the work it will be
1332         * stored in the <code>thrown</code> field.
1333         */
1334        public void run() {
1335            // the buffer used for reading the messages
1336            byte[] buff = new byte[2048];
1337            // the number of bytes read into the buffer
1338            int num;
1339            try {
1340                // configure the server socket to avoid blocking
1341                serverSocket.setSoTimeout(TIMEOUT);
1342                // accept client connection
1343                peerSocket = serverSocket.accept();
1344                // configure the client connection to avoid blocking
1345                peerSocket.setSoTimeout(TIMEOUT);
1346                log("Client connection ACCEPTED");
1347
1348                InputStream is = peerSocket.getInputStream();
1349                OutputStream os = peerSocket.getOutputStream();
1350
1351                num = is.read(buff);
1352                String message = new String(buff, 0, num);
1353                log("Got request:\n" + message);
1354                log("------------------");
1355
1356                if (!actAsProxy) {
1357                    // Act as Server (not Proxy) side
1358                    if (message.startsWith("POST")) {
1359                        // client connection sent some data
1360                        log("try to read client data");
1361                        num = is.read(buff);
1362                        message = new String(buff, 0, num);
1363                        log("client's data: '" + message + "'");
1364                        // check the received data
1365                        assertEquals(clientsData, message);
1366                    }
1367                    // just send the response
1368                    os
1369                            .write(("HTTP/1.1 " + responseCode + "\n" + httpsResponseTail)
1370                                    .getBytes());
1371                    // and return
1372                    log("Work is DONE");
1373                    return;
1374                }
1375
1376                // Do proxy work
1377                if (needProxyAuthentication) {
1378                    log("Authentication required ...");
1379                    // send Authentication Request
1380                    os.write(respAuthenticationRequired.getBytes());
1381                    // read response
1382                    num = is.read(buff);
1383                    if (num == -1) {
1384                        // this connection was closed,
1385                        // do clean up and create new one:
1386                        closeSocket(peerSocket);
1387                        peerSocket = serverSocket.accept();
1388                        peerSocket.setSoTimeout(TIMEOUT);
1389                        log("New client connection ACCEPTED");
1390                        is = peerSocket.getInputStream();
1391                        os = peerSocket.getOutputStream();
1392                        num = is.read(buff);
1393                    }
1394                    message = new String(buff, 0, num);
1395                    log("Got authenticated request:\n" + message);
1396                    log("------------------");
1397                    // check provided authorization credentials
1398                    assertTrue("Received message does not contain "
1399                            + "authorization credentials", message
1400                            .toLowerCase().indexOf("proxy-authorization:") > 0);
1401                }
1402
1403                // The content of this response will reach proxied HTTPUC
1404                // but will not reach proxied HTTPSUC
1405                // In case of HTTP connection it will be the final message,
1406                // in case of HTTPS connection this message will just indicate
1407                // that connection with remote host has been done
1408                // (i.e. SSL tunnel has been established).
1409                os.write(plainResponse.getBytes());
1410                log("Sent OK RESPONSE");
1411
1412                if (message.startsWith("CONNECT")) { // request for SSL tunnel
1413                    log("Perform SSL Handshake...");
1414                    // create sslSocket acting as a remote server peer
1415                    SSLSocket sslSocket = (SSLSocket) getContext()
1416                            .getSocketFactory().createSocket(peerSocket,
1417                                    "localhost", peerSocket.getPort(), true); // do autoclose
1418                    sslSocket.setUseClientMode(false);
1419                    // demand client authentication
1420                    sslSocket.setNeedClientAuth(true);
1421                    sslSocket.startHandshake();
1422                    peerSocket = sslSocket;
1423                    is = peerSocket.getInputStream();
1424                    os = peerSocket.getOutputStream();
1425
1426                    // read the HTTP request sent by secure connection
1427                    // (HTTPS request)
1428                    num = is.read(buff);
1429                    message = new String(buff, 0, num);
1430                    log("[Remote Server] Request from SSL tunnel:\n" + message);
1431                    log("------------------");
1432
1433                    if (message.startsWith("POST")) {
1434                        // client connection sent some data
1435                        log("[Remote Server] try to read client data");
1436                        num = is.read(buff);
1437                        message = new String(buff, 0, num);
1438                        log("[Remote Server] client's data: '" + message + "'");
1439                        // check the received data
1440                        assertEquals(clientsData, message);
1441                    }
1442
1443                    log("[Remote Server] Sending the response by SSL tunnel..");
1444                    // send the response with specified response code
1445                    os
1446                            .write(("HTTP/1.1 " + responseCode + "\n" + httpsResponseTail)
1447                                    .getBytes());
1448                }
1449                log("Work is DONE");
1450            } catch (Throwable e) {
1451                if (DO_LOG) {
1452                    e.printStackTrace();
1453                }
1454                thrown = e;
1455            } finally {
1456                closeSocket(peerSocket);
1457                try {
1458                    serverSocket.close();
1459                } catch (IOException e) {}
1460            }
1461        }
1462    }
1463
1464    /**
1465     * The class used for client side works. It could be used to test
1466     * both HttpURLConnection and HttpsURLConnection.
1467     */
1468    static class ClientConnectionWork extends Work {
1469
1470        // connection to be used to contact the server side
1471        private HttpURLConnection connection;
1472
1473        /**
1474         * Creates the thread acting as a client side.
1475         * @param connection connection to be used to contact the server side
1476         */
1477        public ClientConnectionWork(HttpURLConnection connection) {
1478            this.connection = connection;
1479            setName("Client Connection");
1480            log("Created over connection: " + connection.getClass());
1481        }
1482
1483        /**
1484         * Performs the actual client work.
1485         * If some exception occurs during the work it will be
1486         * stored in the <code>thrown<code> field.
1487         */
1488        public void run() {
1489            try {
1490                log("Opening the connection..");
1491                connection.connect();
1492                log("Connection has been ESTABLISHED, using proxy: "
1493                        + connection.usingProxy());
1494                if (connection.getDoOutput()) {
1495                    // connection configured to post data, do so
1496                    connection.getOutputStream().write(clientsData.getBytes());
1497                }
1498                // read the content of HTTP(s) response
1499                InputStream is = connection.getInputStream();
1500                log("Input Stream obtained");
1501                byte[] buff = new byte[2048];
1502                int num = 0;
1503                int byt = 0;
1504                while ((num < buff.length) && (is.available() > 0)
1505                        && ((byt = is.read()) != -1)) {
1506                    buff[num++] = (byte) byt;
1507                }
1508                String message = new String(buff, 0, num);
1509                log("Got content:\n" + message);
1510                log("------------------");
1511                log("Response code: " + connection.getResponseCode());
1512
1513                if (connection instanceof HttpsURLConnection) {
1514                    assertEquals(httpsResponseContent, message);
1515                } else {
1516                    assertEquals(plainResponseContent, message);
1517                }
1518            } catch (Throwable e) {
1519                if (DO_LOG) {
1520                    e.printStackTrace();
1521                }
1522                thrown = e;
1523            }
1524        }
1525    }
1526}
1527