summaryrefslogtreecommitdiffstats
path: root/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java
blob: 5258fd1e925ed741ac7b2a91b0a10dc2ff258230 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.luni.tests.internal.net.www.protocol.https;

import com.google.mockwebserver.Dispatcher;
import com.google.mockwebserver.MockResponse;
import com.google.mockwebserver.MockWebServer;
import com.google.mockwebserver.RecordedRequest;
import com.google.mockwebserver.SocketPolicy;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import junit.framework.TestCase;
import libcore.java.security.TestKeyStore;
import libcore.javax.net.ssl.TestTrustManager;

/**
 * Implementation independent test for HttpsURLConnection.
 */
public class HttpsURLConnectionTest extends TestCase {

    private static final String POST_METHOD = "POST";

    private static final String GET_METHOD = "GET";

    /**
     * Data to be posted by client to the server when the method is POST.
     */
    private static final String POST_DATA = "_.-^ Client's Data ^-._";

    /**
     * The content of the response to be sent during HTTPS session.
     */
    private static final String RESPONSE_CONTENT
            = "<HTML>\n"
            + "<HEAD><TITLE>HTTPS Response Content</TITLE></HEAD>\n"
            + "</HTML>";

    // the password to the store
    private static final String KS_PASSWORD = "password";

    // turn on/off logging
    private static final boolean DO_LOG = false;

    // read/connection timeout value
    private static final int TIMEOUT = 5000;

    // OK response code
    private static final int OK_CODE = 200;

    // Not Found response code
    private static final int NOT_FOUND_CODE = 404;

    // Proxy authentication required response code
    private static final int AUTHENTICATION_REQUIRED_CODE = 407;

    private static File store;

    static {
        try {
            store = File.createTempFile("key_store", "bks");
        } catch (Exception e) {
            // ignore
        }
    }

    /**
     * Checks that HttpsURLConnection's default SSLSocketFactory is operable.
     */
    public void testGetDefaultSSLSocketFactory() throws Exception {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        SSLSocketFactory defaultSSLSF = HttpsURLConnection.getDefaultSSLSocketFactory();
        ServerSocket ss = new ServerSocket(0);
        Socket s = defaultSSLSF.createSocket("localhost", ss.getLocalPort());
        ss.accept();
        s.close();
        ss.close();
    }

    public void testHttpsConnection() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        SSLContext ctx = getContext();

        // set the HostnameVerifier required to satisfy SSL - always returns "verified".
        HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());

        // create a webserver to check and respond to requests
        SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE);
        MockWebServer webServer = createWebServer(ctx, dispatcher);

        // create url connection to be tested
        URL url = webServer.getUrl("/");
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(ctx.getSocketFactory());

        // perform the interaction between the peers
        executeClientRequest(connection, false /* doOutput */);

        checkConnectionStateParameters(connection, dispatcher.getLastRequest());

        // should silently exit
        connection.connect();

        webServer.shutdown();
    }

    /**
     * Tests the behaviour of HTTPS connection in case of unavailability of requested resource.
     */
    public void testHttpsConnection_Not_Found_Response() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        SSLContext ctx = getContext();

        // set the HostnameVerifier required to satisfy SSL - always returns "verified".
        HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());

        // create a webserver to check and respond to requests
        SingleRequestDispatcher dispatcher =
                new SingleRequestDispatcher(GET_METHOD, NOT_FOUND_CODE);
        MockWebServer webServer = createWebServer(ctx, dispatcher);

        // create url connection to be tested
        URL url = webServer.getUrl("/");
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(ctx.getSocketFactory());

        try {
            executeClientRequest(connection, false /* doOutput */);
            fail("Expected exception was not thrown.");
        } catch (FileNotFoundException e) {
            if (DO_LOG) {
                System.out.println("Expected exception was thrown: " + e.getMessage());
                e.printStackTrace();
            }
        }

        // should silently exit
        connection.connect();

        webServer.shutdown();
    }

    /**
     * Tests possibility to set up the default SSLSocketFactory to be used by HttpsURLConnection.
     */
    public void testSetDefaultSSLSocketFactory() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        SSLContext ctx = getContext();

        SSLSocketFactory socketFactory = ctx.getSocketFactory();
        // set up the factory as default
        HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory);
        // check the result
        assertSame("Default SSLSocketFactory differs from expected",
                socketFactory, HttpsURLConnection.getDefaultSSLSocketFactory());

        // set the initial default host name verifier.
        TestHostnameVerifier initialHostnameVerifier = new TestHostnameVerifier();
        HttpsURLConnection.setDefaultHostnameVerifier(initialHostnameVerifier);

        // create a webserver to check and respond to requests
        SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE);
        MockWebServer webServer = createWebServer(ctx, dispatcher);

        // create HttpsURLConnection to be tested
        URL url = webServer.getUrl("/");
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();

        // late initialization: this HostnameVerifier should not be used for created connection
        TestHostnameVerifier lateHostnameVerifier = new TestHostnameVerifier();
        HttpsURLConnection.setDefaultHostnameVerifier(lateHostnameVerifier);

        // perform the interaction between the peers
        executeClientRequest(connection, false /* doOutput */);
        checkConnectionStateParameters(connection, dispatcher.getLastRequest());

        // check the verification process
        assertTrue("Hostname verification was not done", initialHostnameVerifier.verified);
        assertFalse("Hostname verification should not be done by this verifier",
                lateHostnameVerifier.verified);
        // check the used SSLSocketFactory
        assertSame("Default SSLSocketFactory should be used",
                HttpsURLConnection.getDefaultSSLSocketFactory(),
                connection.getSSLSocketFactory());

        webServer.shutdown();
    }

    /**
     * Tests
     * {@link javax.net.ssl.HttpsURLConnection#setSSLSocketFactory(javax.net.ssl.SSLSocketFactory)}.
     */
    public void testSetSSLSocketFactory() throws Throwable {
        // set up the properties pointing to the key/trust stores
        SSLContext ctx = getContext();

        // set the initial default host name verifier.
        TestHostnameVerifier hostnameVerifier = new TestHostnameVerifier();
        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

        // create a webserver to check and respond to requests
        SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE);
        MockWebServer webServer = createWebServer(ctx, dispatcher);

        // create HttpsURLConnection to be tested
        URL url = webServer.getUrl("/");
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();

        // late initialization: should not be used for the created connection.
        SSLSocketFactory socketFactory = ctx.getSocketFactory();
        connection.setSSLSocketFactory(socketFactory);

        // late initialization: should not be used for created connection
        TestHostnameVerifier lateHostnameVerifier = new TestHostnameVerifier();
        HttpsURLConnection.setDefaultHostnameVerifier(lateHostnameVerifier);

        // perform the interaction between the peers
        executeClientRequest(connection, false /* doOutput */);
        checkConnectionStateParameters(connection, dispatcher.getLastRequest());
        // check the verification process
        assertTrue("Hostname verification was not done", hostnameVerifier.verified);
        assertFalse("Hostname verification should not be done by this verifier",
                lateHostnameVerifier.verified);
        // check the used SSLSocketFactory
        assertNotSame("Default SSLSocketFactory should not be used",
                HttpsURLConnection.getDefaultSSLSocketFactory(),
                connection.getSSLSocketFactory());
        assertSame("Result differs from expected", socketFactory, connection.getSSLSocketFactory());

        webServer.shutdown();
    }

    /**
     * Tests the behaviour of HttpsURLConnection in case of retrieving
     * of the connection state parameters before connection has been made.
     */
    public void testUnconnectedStateParameters() throws Throwable {
        // create HttpsURLConnection to be tested
        URL url = new URL("https://localhost:55555");
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();

        try {
            connection.getCipherSuite();
            fail("Expected IllegalStateException was not thrown");
        } catch (IllegalStateException e) {}
        try {
            connection.getPeerPrincipal();
            fail("Expected IllegalStateException was not thrown");
        } catch (IllegalStateException e) {}
        try {
            connection.getLocalPrincipal();
            fail("Expected IllegalStateException was not thrown");
        } catch (IllegalStateException e) {}

        try {
            connection.getServerCertificates();
            fail("Expected IllegalStateException was not thrown");
        } catch (IllegalStateException e) {}
        try {
            connection.getLocalCertificates();
            fail("Expected IllegalStateException was not thrown");
        } catch (IllegalStateException e) {}
    }

    /**
     * Tests if setHostnameVerifier() method replaces default verifier.
     */
    public void testSetHostnameVerifier() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        SSLContext ctx = getContext();

        TestHostnameVerifier defaultHostnameVerifier = new TestHostnameVerifier();
        HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);

        // create a webserver to check and respond to requests
        SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE);
        MockWebServer webServer = createWebServer(ctx, dispatcher);

        // create HttpsURLConnection to be tested
        URL url = webServer.getUrl("/");
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(getContext().getSocketFactory());

        // replace the default verifier
        TestHostnameVerifier connectionHostnameVerifier = new TestHostnameVerifier();
        connection.setHostnameVerifier(connectionHostnameVerifier);

        // perform the interaction between the peers and check the results
        executeClientRequest(connection, false /* doOutput */);
        assertTrue("Hostname verification was not done", connectionHostnameVerifier.verified);
        assertFalse("Hostname verification should not be done by this verifier",
                defaultHostnameVerifier.verified);

        checkConnectionStateParameters(connection, dispatcher.getLastRequest());

        webServer.shutdown();
    }

    /**
     * Tests the behaviour in case of sending the data to the server.
     */
    public void test_doOutput() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        SSLContext ctx = getContext();

        // create a webserver to check and respond to requests
        SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(POST_METHOD, OK_CODE);
        MockWebServer webServer = createWebServer(ctx, dispatcher);

        // set the HostnameVerifier required to satisfy SSL - always returns "verified".
        HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());

        // create HttpsURLConnection to be tested
        URL url = webServer.getUrl("/");
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(getContext().getSocketFactory());

        // perform the interaction between the peers and check the results
        executeClientRequest(connection, true /* doOutput */);
        checkConnectionStateParameters(connection, dispatcher.getLastRequest());

        // should silently exit
        connection.connect();

        webServer.shutdown();
    }

    /**
     * Tests HTTPS connection process made through the proxy server.
     */
    public void testProxyConnection() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        SSLContext ctx = getContext();

        // set the HostnameVerifier required to satisfy SSL - always returns "verified".
        HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());

        // create a server that pretends to be both a proxy and then the webserver
        // request 1: proxy CONNECT, respond with OK
        ProxyConnectDispatcher proxyConnectDispatcher =
                new ProxyConnectDispatcher(false /* authenticationRequired */);
        // request 2: tunnelled GET, respond with OK
        SingleRequestDispatcher getDispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE);
        DelegatingDispatcher delegatingDispatcher =
                new DelegatingDispatcher(proxyConnectDispatcher, getDispatcher);
        MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher);

        // create HttpsURLConnection to be tested
        URL proxyUrl = proxyAndWebServer.getUrl("/");
        InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort());
        URL url = new URL("https://requested.host:55556/requested.data");
        HttpsURLConnection connection = (HttpsURLConnection)
                url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress));
        connection.setSSLSocketFactory(getContext().getSocketFactory());

        // perform the interaction between the peers and check the results
        executeClientRequest(connection, false /* doOutput */);
        checkConnectionStateParameters(connection, getDispatcher.getLastRequest());

        // should silently exit
        connection.connect();

        proxyAndWebServer.shutdown();
    }

    /**
     * Tests HTTPS connection process made through the proxy server.
     * Proxy server needs authentication.
     */
    public void testProxyAuthConnection() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        SSLContext ctx = getContext();

        // set the HostnameVerifier required to satisfy SSL - always returns "verified".
        HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());

        Authenticator.setDefault(new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication("user", "password".toCharArray());
            }
        });

        // create a server that pretends to be both a proxy and then the webserver
        // request 1: proxy CONNECT, respond with auth challenge
        ProxyConnectAuthFailDispatcher authFailDispatcher = new ProxyConnectAuthFailDispatcher();
        // request 2: proxy CONNECT, respond with OK
        ProxyConnectDispatcher proxyConnectDispatcher =
                new ProxyConnectDispatcher(true /* authenticationRequired */);
        // request 3: tunnelled GET, respond with OK
        SingleRequestDispatcher getDispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE);
        DelegatingDispatcher delegatingDispatcher = new DelegatingDispatcher(
                authFailDispatcher, proxyConnectDispatcher, getDispatcher);
        MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher);

        // create HttpsURLConnection to be tested
        URL proxyUrl = proxyAndWebServer.getUrl("/");
        InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort());
        URL url = new URL("https://requested.host:55555/requested.data");
        HttpsURLConnection connection = (HttpsURLConnection)
                url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress));
        connection.setSSLSocketFactory(getContext().getSocketFactory());

        // perform the interaction between the peers and check the results
        executeClientRequest(connection, false /* doOutput */);
        checkConnectionStateParameters(connection, getDispatcher.getLastRequest());

        // should silently exit
        connection.connect();

        proxyAndWebServer.shutdown();
    }

    /**
     * Tests HTTPS connection process made through the proxy server.
     * Two HTTPS connections are opened for one URL: the first time the connection is opened
     * through one proxy, the second time it is opened through another.
     */
    public void testConsequentProxyConnection() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        // set the HostnameVerifier required to satisfy SSL - always returns "verified".
        HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());

        // create a server that pretends to be both a proxy and then the webserver
        SingleRequestDispatcher getDispatcher1 = new SingleRequestDispatcher(GET_METHOD, OK_CODE);
        MockWebServer proxyAndWebServer1 = createProxiedServer(getDispatcher1);

        // create HttpsURLConnection to be tested
        URL proxyUrl1 = proxyAndWebServer1.getUrl("/");
        URL url = new URL("https://requested.host:55555/requested.data");
        InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl1.getPort());
        HttpsURLConnection connection = (HttpsURLConnection)
                url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress));
        connection.setSSLSocketFactory(getContext().getSocketFactory());
         executeClientRequest(connection, false /* doOutput */);
        checkConnectionStateParameters(connection, getDispatcher1.getLastRequest());

        proxyAndWebServer1.shutdown();

        // create another server
        SingleRequestDispatcher getDispatcher2 = new SingleRequestDispatcher(GET_METHOD, OK_CODE);
        MockWebServer proxyAndWebServer2 = createProxiedServer(getDispatcher2);

        // create another HttpsURLConnection to be tested
        URL proxyUrl2 = proxyAndWebServer2.getUrl("/");
        InetSocketAddress proxyAddress2 = new InetSocketAddress("localhost", proxyUrl2.getPort());
        HttpsURLConnection connection2 = (HttpsURLConnection) url.openConnection(
                new Proxy(Proxy.Type.HTTP, proxyAddress2));
        connection2.setSSLSocketFactory(getContext().getSocketFactory());

        // perform the interaction between the peers and check the results
        executeClientRequest(connection2, false /* doOutput */);
        checkConnectionStateParameters(connection2, getDispatcher2.getLastRequest());

        proxyAndWebServer2.shutdown();
    }

    private static MockWebServer createProxiedServer(Dispatcher getDispatcher)
            throws Exception {
        // request 1: proxy CONNECT, respond with OK
        ProxyConnectDispatcher proxyConnectDispatcher =
                new ProxyConnectDispatcher(false /* authenticationRequired */);
        // request 2: The get dispatcher.
        DelegatingDispatcher delegatingDispatcher1 =
                new DelegatingDispatcher(proxyConnectDispatcher, getDispatcher);
        return createProxyAndWebServer(getContext(), delegatingDispatcher1);
    }

    /**
     * Tests HTTPS connection process made through the proxy server.
     * Proxy server needs authentication.
     * Client sends data to the server.
     */
    public void testProxyAuthConnection_doOutput() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        SSLContext ctx = getContext();

        // set the HostnameVerifier required to satisfy SSL - always returns "verified".
        HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());

        Authenticator.setDefault(new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication("user", "password".toCharArray());
            }
        });

        // create a server that pretends to be both a proxy and then the webserver
        // request 1: proxy CONNECT, respond with auth challenge
        ProxyConnectAuthFailDispatcher authFailDispatcher = new ProxyConnectAuthFailDispatcher();
        // request 2: proxy CONNECT, respond with OK
        ProxyConnectDispatcher proxyConnectDispatcher =
                new ProxyConnectDispatcher(true /* authenticationRequired */);
        // request 3: tunnelled POST, respond with OK
        SingleRequestDispatcher postDispatcher = new SingleRequestDispatcher(POST_METHOD, OK_CODE);
        DelegatingDispatcher delegatingDispatcher = new DelegatingDispatcher(
                authFailDispatcher, proxyConnectDispatcher, postDispatcher);
        MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher);
        URL proxyUrl = proxyAndWebServer.getUrl("/");

        // create HttpsURLConnection to be tested
        InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort());
        HttpsURLConnection connection = (HttpsURLConnection)
                proxyUrl.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress));
        connection.setSSLSocketFactory(getContext().getSocketFactory());

        // perform the interaction between the peers and check the results
        executeClientRequest(connection, true /* doOutput */);
        checkConnectionStateParameters(connection, postDispatcher.getLastRequest());

        // should silently exit
        connection.connect();

        proxyAndWebServer.shutdown();
    }

    /**
     * Tests 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).
     */
    public void testProxyAuthConnectionFailed() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        // set the HostnameVerifier required to satisfy SSL - always returns "verified".
        HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());

        // create a server that pretends to be both a proxy that requests authentication.
        MockWebServer proxyAndWebServer = new MockWebServer();
        ProxyConnectAuthFailDispatcher authFailDispatcher = new ProxyConnectAuthFailDispatcher();
        proxyAndWebServer.setDispatcher(authFailDispatcher);
        proxyAndWebServer.play();

        // create HttpsURLConnection to be tested
        URL proxyUrl = proxyAndWebServer.getUrl("/");
        InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort());
        URL url = new URL("https://requested.host:55555/requested.data");
        HttpsURLConnection connection = (HttpsURLConnection)
                url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress));
        connection.setSSLSocketFactory(getContext().getSocketFactory());

        // perform the interaction between the peers and check the results
        try {
            executeClientRequest(connection, false);
        } catch (IOException e) {
            // SSL Tunnelling failed
            if (DO_LOG) {
                System.out.println("Got expected IOException: " + e.getMessage());
            }
        }
    }

    /**
     * Tests the behaviour of HTTPS connection in case of unavailability of requested resource (as
     * reported by the target web server).
     */
    public void testProxyConnection_Not_Found_Response() throws Throwable {
        // set up the properties pointing to the key/trust stores
        setUpStoreProperties();

        SSLContext ctx = getContext();

        // set the HostnameVerifier required to satisfy SSL - always returns "verified".
        HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());

        // create a server that pretends to be a proxy
        ProxyConnectDispatcher proxyConnectDispatcher =
                new ProxyConnectDispatcher(false /* authenticationRequired */);
        SingleRequestDispatcher notFoundDispatcher =
                new SingleRequestDispatcher(GET_METHOD, NOT_FOUND_CODE);
        DelegatingDispatcher delegatingDispatcher =
                new DelegatingDispatcher(proxyConnectDispatcher, notFoundDispatcher);
        MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher);

        // create HttpsURLConnection to be tested
        URL proxyUrl = proxyAndWebServer.getUrl("/");
        InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort());
        URL url = new URL("https://requested.host:55555/requested.data");
        HttpsURLConnection connection = (HttpsURLConnection)
                url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress));
        connection.setSSLSocketFactory(getContext().getSocketFactory());

        try {
            executeClientRequest(connection, false /* doOutput */);
            fail("Expected exception was not thrown.");
        } catch (FileNotFoundException e) {
            if (DO_LOG) {
                System.out.println("Expected exception was thrown: " + e.getMessage());
            }
        }
    }

    public void setUp() throws Exception {
        super.setUp();

        if (DO_LOG) {
            // Log the name of the test case to be executed.
            System.out.println();
            System.out.println("------------------------");
            System.out.println("------ " + getName());
            System.out.println("------------------------");
        }

        if (store != null) {
            String ksFileName = "org/apache/harmony/luni/tests/key_store." +
                    KeyStore.getDefaultType().toLowerCase();
            InputStream in = getClass().getClassLoader().getResourceAsStream(ksFileName);
            FileOutputStream out = new FileOutputStream(store);
            BufferedInputStream bufIn = new BufferedInputStream(in, 8192);
            while (bufIn.available() > 0) {
                byte[] buf = new byte[128];
                int read = bufIn.read(buf);
                out.write(buf, 0, read);
            }
            bufIn.close();
            out.close();
        } else {
            fail("couldn't set up key store");
        }
    }

    public void tearDown() {
        if (store != null) {
            store.delete();
        }
    }

    private static void checkConnectionStateParameters(
            HttpsURLConnection connection, RecordedRequest request) throws Exception {
        assertEquals(request.getSslCipherSuite(), connection.getCipherSuite());
        assertEquals(request.getSslLocalPrincipal(), connection.getPeerPrincipal());
        assertEquals(request.getSslPeerPrincipal(), connection.getLocalPrincipal());

        Certificate[] serverCertificates = connection.getServerCertificates();
        Certificate[] localCertificates = request.getSslLocalCertificates();
        assertTrue("Server certificates differ from expected",
                Arrays.equals(serverCertificates, localCertificates));

        localCertificates = connection.getLocalCertificates();
        serverCertificates = request.getSslPeerCertificates();
        assertTrue("Local certificates differ from expected",
                Arrays.equals(serverCertificates, localCertificates));
    }

    /**
     * Returns the file name of the key/trust store. The key store file
     * (named as "key_store." + extension equals to the default KeyStore
     * type installed in the system in lower case) is searched in classpath.
     * @throws junit.framework.AssertionFailedError if property was not set
     * or file does not exist.
     */
    private static String getKeyStoreFileName() {
        return store.getAbsolutePath();
    }

    /**
     * Builds and returns the context used for secure socket creation.
     */
    private static SSLContext getContext() throws Exception {
        String type = KeyStore.getDefaultType();
        String keyStore = getKeyStoreFileName();
        File keyStoreFile = new File(keyStore);
        FileInputStream fis = new FileInputStream(keyStoreFile);

        KeyStore ks = KeyStore.getInstance(type);
        ks.load(fis, KS_PASSWORD.toCharArray());
        fis.close();
        if (DO_LOG && false) {
            TestKeyStore.dump("HttpsURLConnection.getContext", ks, KS_PASSWORD.toCharArray());
        }

        String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm);
        kmf.init(ks, KS_PASSWORD.toCharArray());
        KeyManager[] keyManagers = kmf.getKeyManagers();

        String tmfAlgorthm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorthm);
        tmf.init(ks);
        TrustManager[] trustManagers = tmf.getTrustManagers();
        if (DO_LOG) {
            trustManagers = TestTrustManager.wrap(trustManagers);
        }

        SSLContext ctx = SSLContext.getInstance("TLSv1");
        ctx.init(keyManagers, trustManagers, null);
        return ctx;
    }

    /**
     * Sets up the properties pointing to the key store and trust store
     * and used as default values by JSSE staff. This is needed to test
     * HTTPS behaviour in the case of default SSL Socket Factories.
     */
    private static void setUpStoreProperties() throws Exception {
        String type = KeyStore.getDefaultType();

        System.setProperty("javax.net.ssl.keyStoreType", type);
        System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName());
        System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD);

        System.setProperty("javax.net.ssl.trustStoreType", type);
        System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName());
        System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD);
    }

    /**
     * The host name verifier used in test.
     */
    static class TestHostnameVerifier implements HostnameVerifier {

        boolean verified = false;

        public boolean verify(String hostname, SSLSession session) {
            if (DO_LOG) {
                System.out.println("***> verification " + hostname + " "
                                   + session.getPeerHost());
            }
            verified = true;
            return true;
        }
    }

    /**
     * Creates a {@link MockWebServer} that acts as both a proxy and then a web server with the
     * supplied {@link SSLContext} and {@link Dispatcher}. The dispatcher provided must handle the
     * CONNECT request/responses and {@link SocketPolicy} needed to simulate the hand-off from proxy
     * to web server. See {@link HttpsURLConnectionTest.ProxyConnectDispatcher}.
     */
    private static MockWebServer createProxyAndWebServer(SSLContext ctx, Dispatcher dispatcher)
            throws IOException {
        return createServer(ctx, dispatcher, true /* handleProxying */);
    }

    /**
     * Creates a {@link MockWebServer} that acts as (only) a web server with the supplied
     * {@link SSLContext} and {@link Dispatcher}.
     */
    private static MockWebServer createWebServer(SSLContext ctx, Dispatcher dispatcher)
            throws IOException {
        return createServer(ctx, dispatcher, false /* handleProxying */);
    }

    private static MockWebServer createServer(
            SSLContext ctx, Dispatcher dispatcher, boolean handleProxying)
            throws IOException {
        MockWebServer webServer = new MockWebServer();
        webServer.useHttps(ctx.getSocketFactory(), handleProxying /* tunnelProxy */);
        webServer.setDispatcher(dispatcher);
        webServer.play();
        return webServer;
    }

    /**
     * A {@link Dispatcher} that has a list of dispatchers to delegate to, each of which will be
     * used for one request and then discarded.
     */
    private static class DelegatingDispatcher extends Dispatcher {
        private LinkedList<Dispatcher> delegates = new LinkedList<Dispatcher>();

        public DelegatingDispatcher(Dispatcher... dispatchers) {
            addAll(dispatchers);
        }

        private void addAll(Dispatcher... dispatchers) {
            Collections.addAll(delegates, dispatchers);
        }

        @Override
        public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
            return delegates.removeFirst().dispatch(request);
        }

        @Override
        public SocketPolicy peekSocketPolicy() {
            return delegates.getFirst().peekSocketPolicy();
        }
    }

    /** Handles a request for SSL tunnel: Answers with a request to authenticate. */
    private static class ProxyConnectAuthFailDispatcher extends Dispatcher {

        @Override
        public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
            assertEquals("CONNECT", request.getMethod());

            MockResponse response = new MockResponse();
            response.setResponseCode(AUTHENTICATION_REQUIRED_CODE);
            response.addHeader("Proxy-authenticate: Basic realm=\"localhost\"");
            log("Authentication required. Sending response: " + response);
            return response;
        }

        private void log(String msg) {
            HttpsURLConnectionTest.log("ProxyConnectAuthFailDispatcher", msg);
        }
    }

    /**
     * Handles a request for SSL tunnel: Answers with a success and the socket is upgraded to SSL.
     */
    private static class ProxyConnectDispatcher extends Dispatcher {

        private final boolean authenticationRequired;

        private ProxyConnectDispatcher(boolean authenticationRequired) {
            this.authenticationRequired = authenticationRequired;
        }

        @Override
        public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
            if (authenticationRequired) {
                // check provided authorization credentials
                assertNotNull("no proxy-authorization credentials: " + request,
                        request.getHeader("proxy-authorization"));
                log("Got authenticated request:\n" + request);
                log("------------------");
            }

            assertEquals("CONNECT", request.getMethod());
            log("Send proxy response");
            MockResponse response = new MockResponse();
            response.setResponseCode(200);
            response.setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END);
            return response;
        }

        @Override
        public SocketPolicy peekSocketPolicy() {
            return SocketPolicy.UPGRADE_TO_SSL_AT_END;
        }

        private void log(String msg) {
            HttpsURLConnectionTest.log("ProxyConnectDispatcher", msg);
        }
    }

    /**
     * Handles a request: Answers with a response with a specified status code.
     * If the {@code expectedMethod} is {@code POST} a hardcoded response body {@link #POST_DATA}
     * will be included in the response.
     */
    private static class SingleRequestDispatcher extends Dispatcher {

        private final String expectedMethod;
        private final int responseCode;

        private RecordedRequest lastRequest;

        private SingleRequestDispatcher(String expectedMethod, int responseCode) {
            this.responseCode = responseCode;
            this.expectedMethod = expectedMethod;
        }

        @Override
        public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
            if (lastRequest != null) {
                fail("More than one request received");
            }
            log("Request received: " + request);
            lastRequest = request;
            assertEquals(expectedMethod, request.getMethod());
            if (POST_METHOD.equals(expectedMethod)) {
                assertEquals(POST_DATA, request.getUtf8Body());
            }

            MockResponse response = new MockResponse();
            response.setResponseCode(responseCode);
            response.setBody(RESPONSE_CONTENT);

            log("Responding with: " + response);
            return response;
        }

        public RecordedRequest getLastRequest() {
            return lastRequest;
        }

        @Override
        public SocketPolicy peekSocketPolicy() {
            return SocketPolicy.DISCONNECT_AT_END;
        }

        private void log(String msg) {
            HttpsURLConnectionTest.log("SingleRequestDispatcher", msg);
        }
    }

    /**
     * Executes an HTTP request using the supplied connection. If {@code doOutput} is {@code true}
     * the request made is a POST and the request body sent is {@link #POST_DATA}.
     * If {@code doOutput} is {@code false} the request made is a GET. The response must be a
     * success with a body {@link #RESPONSE_CONTENT}.
     */
    private static void executeClientRequest(
            HttpsURLConnection connection, boolean doOutput) throws IOException {

        // set up the connection
        connection.setDoInput(true);
        connection.setConnectTimeout(TIMEOUT);
        connection.setReadTimeout(TIMEOUT);
        connection.setDoOutput(doOutput);

        log("Client", "Opening the connection to " + connection.getURL());
        connection.connect();
        log("Client", "Connection has been ESTABLISHED, using proxy: " + connection.usingProxy());
        if (doOutput) {
            log("Client", "Posting data");
            // connection configured to post data, do so
            OutputStream os = connection.getOutputStream();
            os.write(POST_DATA.getBytes());
        }
        // read the content of HTTP(s) response
        InputStream is = connection.getInputStream();
        log("Client", "Input Stream obtained");
        byte[] buff = new byte[2048];
        int num = 0;
        int byt;
        while ((num < buff.length) && ((byt = is.read()) != -1)) {
            buff[num++] = (byte) byt;
        }
        String message = new String(buff, 0, num);
        log("Client", "Got content:\n" + message);
        log("Client", "------------------");
        log("Client", "Response code: " + connection.getResponseCode());
        assertEquals(RESPONSE_CONTENT, message);
    }

    /**
     * Prints log message.
     */
    public static synchronized void log(String origin, String message) {
        if (DO_LOG) {
            System.out.println("[" + origin + "]: " + message);
        }
    }
}