1/*
2 * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26import java.io.IOException;
27import java.io.UncheckedIOException;
28import java.net.Authenticator;
29import java.net.HttpURLConnection;
30import java.net.InetSocketAddress;
31import java.net.MalformedURLException;
32import java.net.PasswordAuthentication;
33import java.net.Proxy;
34import java.net.URL;
35import java.util.Locale;
36import java.util.concurrent.atomic.AtomicInteger;
37import java.util.logging.Level;
38import java.util.logging.Logger;
39import java.util.stream.Stream;
40import javax.net.ssl.HostnameVerifier;
41import javax.net.ssl.HttpsURLConnection;
42import javax.net.ssl.SSLContext;
43import javax.net.ssl.SSLSession;
44import jdk.testlibrary.SimpleSSLContext;
45
46/*
47 * @test
48 * @bug 8169415
49 * @library /lib/testlibrary/
50 * @modules java.logging
51 *          java.base/sun.net.www
52 *          jdk.httpserver/sun.net.httpserver
53 * @build jdk.testlibrary.SimpleSSLContext HTTPTest HTTPTestServer HTTPTestClient
54 * @summary A simple HTTP test that starts an echo server supporting Digest
55 *          authentication, then starts a regular HTTP client to invoke it.
56 *          The client first does a GET request on "/", then follows on
57 *          with a POST request that sends "Hello World!" to the server.
58 *          The client expects to receive "Hello World!" in return.
59 *          The test supports several execution modes:
60 *            SERVER: The server performs Digest Server authentication;
61 *            PROXY:  The server pretends to be a proxy and performs
62 *                    Digest Proxy authentication;
63 *            SERVER307: The server redirects the client (307) to another
64 *                    server that perform Digest authentication;
65 *            PROXY305: The server attempts to redirect
66 *                    the client to a proxy using 305 code;
67 * @run main/othervm HTTPTest SERVER
68 * @run main/othervm HTTPTest PROXY
69 * @run main/othervm HTTPTest SERVER307
70 * @run main/othervm HTTPTest PROXY305
71 *
72 * @author danielfuchs
73 */
74public class HTTPTest {
75
76    public static final boolean DEBUG =
77         Boolean.parseBoolean(System.getProperty("test.debug", "false"));
78    public static enum HttpAuthType { SERVER, PROXY, SERVER307, PROXY305 };
79    public static enum HttpProtocolType { HTTP, HTTPS };
80    public static enum HttpSchemeType { NONE, BASICSERVER, BASIC, DIGEST };
81    public static final HttpAuthType DEFAULT_HTTP_AUTH_TYPE = HttpAuthType.SERVER;
82    public static final HttpProtocolType DEFAULT_PROTOCOL_TYPE = HttpProtocolType.HTTP;
83    public static final HttpSchemeType DEFAULT_SCHEME_TYPE = HttpSchemeType.DIGEST;
84
85    public static class HttpTestAuthenticator extends Authenticator {
86        private final String realm;
87        private final String username;
88        // Used to prevent incrementation of 'count' when calling the
89        // authenticator from the server side.
90        private final ThreadLocal<Boolean> skipCount = new ThreadLocal<>();
91        // count will be incremented every time getPasswordAuthentication()
92        // is called from the client side.
93        final AtomicInteger count = new AtomicInteger();
94
95        public HttpTestAuthenticator(String realm, String username) {
96            this.realm = realm;
97            this.username = username;
98        }
99
100        @Override
101        protected PasswordAuthentication getPasswordAuthentication() {
102            if (skipCount.get() == null || skipCount.get().booleanValue() == false) {
103                System.out.println("Authenticator called: " + count.incrementAndGet());
104            }
105            return new PasswordAuthentication(getUserName(),
106                    new char[] {'b','a','r'});
107        }
108
109        // Called by the server side to get the password of the user
110        // being authentified.
111        public final char[] getPassword(String user) {
112            if (user.equals(username)) {
113                skipCount.set(Boolean.TRUE);
114                try {
115                    return getPasswordAuthentication().getPassword();
116                } finally {
117                    skipCount.set(Boolean.FALSE);
118                }
119            }
120            throw new SecurityException("User unknown: " + user);
121        }
122
123        public final String getUserName() {
124            return username;
125        }
126        public final String getRealm() {
127            return realm;
128        }
129
130    }
131    public static final HttpTestAuthenticator AUTHENTICATOR;
132    static {
133        AUTHENTICATOR = new HttpTestAuthenticator("dublin", "foox");
134        Authenticator.setDefault(AUTHENTICATOR);
135    }
136
137    static {
138        try {
139            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
140                public boolean verify(String hostname, SSLSession session) {
141                    return true;
142                }
143            });
144            SSLContext.setDefault(new SimpleSSLContext().get());
145        } catch (IOException ex) {
146            throw new ExceptionInInitializerError(ex);
147        }
148    }
149
150    static final Logger logger = Logger.getLogger ("com.sun.net.httpserver");
151    static {
152        if (DEBUG) logger.setLevel(Level.ALL);
153        Stream.of(Logger.getLogger("").getHandlers())
154              .forEach(h -> h.setLevel(Level.ALL));
155    }
156
157    static final int EXPECTED_AUTH_CALLS_PER_TEST = 1;
158
159    public static void main(String[] args) throws Exception {
160        // new HTTPTest().execute(HttpAuthType.SERVER.name());
161        new HTTPTest().execute(args);
162    }
163
164    public void execute(String... args) throws Exception {
165        Stream<HttpAuthType> modes;
166        if (args == null || args.length == 0) {
167            modes = Stream.of(HttpAuthType.values());
168        } else {
169            modes = Stream.of(args).map(HttpAuthType::valueOf);
170        }
171        modes.forEach(this::test);
172        System.out.println("Test PASSED - Authenticator called: "
173                 + expected(AUTHENTICATOR.count.get()));
174    }
175
176    public void test(HttpAuthType mode) {
177        for (HttpProtocolType type: HttpProtocolType.values()) {
178            test(type, mode);
179        }
180    }
181
182    public HttpSchemeType getHttpSchemeType() {
183        return DEFAULT_SCHEME_TYPE;
184    }
185
186    public void test(HttpProtocolType protocol, HttpAuthType mode) {
187        if (mode == HttpAuthType.PROXY305 && protocol == HttpProtocolType.HTTPS ) {
188            // silently skip unsupported test combination
189            return;
190        }
191        System.out.println("\n**** Testing " + protocol + " "
192                           + mode + " mode ****\n");
193        int authCount = AUTHENTICATOR.count.get();
194        int expectedIncrement = 0;
195        try {
196            // Creates an HTTP server that echoes back whatever is in the
197            // request body.
198            HTTPTestServer server =
199                    HTTPTestServer.create(protocol,
200                                          mode,
201                                          AUTHENTICATOR,
202                                          getHttpSchemeType());
203            try {
204                expectedIncrement += run(server, protocol, mode);
205            } finally {
206                server.stop();
207            }
208        }  catch (IOException ex) {
209            ex.printStackTrace(System.err);
210            throw new UncheckedIOException(ex);
211        }
212        int count = AUTHENTICATOR.count.get();
213        if (count != authCount + expectedIncrement) {
214            throw new AssertionError("Authenticator called " + count(count)
215                        + " expected it to be called "
216                        + expected(authCount + expectedIncrement));
217        }
218    }
219
220    /**
221     * Runs the test with the given parameters.
222     * @param server    The server
223     * @param protocol  The protocol (HTTP/HTTPS)
224     * @param mode      The mode (PROXY, SERVER, SERVER307...)
225     * @return The number of times the default authenticator should have been
226     *         called.
227     * @throws IOException in case of connection or protocol issues
228     */
229    public int run(HTTPTestServer server,
230                   HttpProtocolType protocol,
231                   HttpAuthType mode)
232            throws IOException
233    {
234        // Connect to the server with a GET request, then with a
235        // POST that contains "Hello World!"
236        HTTPTestClient.connect(protocol, server, mode, null);
237        // return the number of times the default authenticator is supposed
238        // to have been called.
239        return EXPECTED_AUTH_CALLS_PER_TEST;
240    }
241
242    public static String count(int count) {
243        switch(count) {
244            case 0: return "not even once";
245            case 1: return "once";
246            case 2: return "twice";
247            default: return String.valueOf(count) + " times";
248        }
249    }
250
251    public static String expected(int count) {
252        switch(count) {
253            default: return count(count);
254        }
255    }
256    public static String protocol(HttpProtocolType type) {
257        return type.name().toLowerCase(Locale.US);
258    }
259
260    public static URL url(HttpProtocolType protocol, InetSocketAddress address,
261                          String path) throws MalformedURLException {
262        return new URL(protocol(protocol),
263                       address.getHostString(),
264                       address.getPort(), path);
265    }
266
267    public static Proxy proxy(HTTPTestServer server, HttpAuthType authType) {
268        return (authType == HttpAuthType.PROXY)
269               ? new Proxy(Proxy.Type.HTTP, server.getAddress())
270               : null;
271    }
272
273    public static HttpURLConnection openConnection(URL url,
274                                                   HttpAuthType authType,
275                                                   Proxy proxy)
276                                    throws IOException {
277
278        HttpURLConnection conn = (HttpURLConnection)
279                (authType == HttpAuthType.PROXY
280                    ? url.openConnection(proxy)
281                    : url.openConnection());
282        return conn;
283    }
284}
285