/* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.IOException; import java.io.UncheckedIOException; import java.net.Authenticator; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.URL; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import jdk.testlibrary.SimpleSSLContext; /** * @test * @bug 8169415 * @library /lib/testlibrary/ * @modules java.base/sun.net.www * jdk.httpserver/sun.net.httpserver * @build jdk.testlibrary.SimpleSSLContext HTTPTest HTTPTestServer HTTPTestClient * @summary A simple HTTP test that starts an echo server supporting Digest * authentication, then starts a regular HTTP client to invoke it. * The client first does a GET request on "/", then follows on * with a POST request that sends "Hello World!" to the server. * The client expects to receive "Hello World!" in return. * The test supports several execution modes: * SERVER: The server performs Digest Server authentication; * PROXY: The server pretends to be a proxy and performs * Digest Proxy authentication; * SERVER307: The server redirects the client (307) to another * server that perform Digest authentication; * PROXY305: The server attempts to redirect * the client to a proxy using 305 code; * @run main/othervm HTTPTest SERVER * @run main/othervm HTTPTest PROXY * @run main/othervm HTTPTest SERVER307 * @run main/othervm HTTPTest PROXY305 * * @author danielfuchs */ public class HTTPTest { public static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("test.debug", "false")); public static enum HttpAuthType { SERVER, PROXY, SERVER307, PROXY305 }; public static enum HttpProtocolType { HTTP, HTTPS }; public static enum HttpSchemeType { NONE, BASICSERVER, BASIC, DIGEST }; public static final HttpAuthType DEFAULT_HTTP_AUTH_TYPE = HttpAuthType.SERVER; public static final HttpProtocolType DEFAULT_PROTOCOL_TYPE = HttpProtocolType.HTTP; public static final HttpSchemeType DEFAULT_SCHEME_TYPE = HttpSchemeType.DIGEST; public static class HttpTestAuthenticator extends Authenticator { private final String realm; private final String username; // Used to prevent incrementation of 'count' when calling the // authenticator from the server side. private final ThreadLocal<Boolean> skipCount = new ThreadLocal<>(); // count will be incremented every time getPasswordAuthentication() // is called from the client side. final AtomicInteger count = new AtomicInteger(); public HttpTestAuthenticator(String realm, String username) { this.realm = realm; this.username = username; } @Override protected PasswordAuthentication getPasswordAuthentication() { if (skipCount.get() == null || skipCount.get().booleanValue() == false) { System.out.println("Authenticator called: " + count.incrementAndGet()); } return new PasswordAuthentication(getUserName(), new char[] {'b','a','r'}); } // Called by the server side to get the password of the user // being authentified. public final char[] getPassword(String user) { if (user.equals(username)) { skipCount.set(Boolean.TRUE); try { return getPasswordAuthentication().getPassword(); } finally { skipCount.set(Boolean.FALSE); } } throw new SecurityException("User unknown: " + user); } public final String getUserName() { return username; } public final String getRealm() { return realm; } } public static final HttpTestAuthenticator AUTHENTICATOR; static { AUTHENTICATOR = new HttpTestAuthenticator("dublin", "foox"); Authenticator.setDefault(AUTHENTICATOR); } static { try { HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }); SSLContext.setDefault(new SimpleSSLContext().get()); } catch (IOException ex) { throw new ExceptionInInitializerError(ex); } } static final Logger logger = Logger.getLogger ("com.sun.net.httpserver"); static { if (DEBUG) logger.setLevel(Level.ALL); Stream.of(Logger.getLogger("").getHandlers()) .forEach(h -> h.setLevel(Level.ALL)); } static final int EXPECTED_AUTH_CALLS_PER_TEST = 1; public static void main(String[] args) throws Exception { // new HTTPTest().execute(HttpAuthType.SERVER.name()); new HTTPTest().execute(args); } public void execute(String... args) throws Exception { Stream<HttpAuthType> modes; if (args == null || args.length == 0) { modes = Stream.of(HttpAuthType.values()); } else { modes = Stream.of(args).map(HttpAuthType::valueOf); } modes.forEach(this::test); System.out.println("Test PASSED - Authenticator called: " + expected(AUTHENTICATOR.count.get())); } public void test(HttpAuthType mode) { for (HttpProtocolType type: HttpProtocolType.values()) { test(type, mode); } } public HttpSchemeType getHttpSchemeType() { return DEFAULT_SCHEME_TYPE; } public void test(HttpProtocolType protocol, HttpAuthType mode) { if (mode == HttpAuthType.PROXY305 && protocol == HttpProtocolType.HTTPS ) { // silently skip unsupported test combination return; } System.out.println("\n**** Testing " + protocol + " " + mode + " mode ****\n"); int authCount = AUTHENTICATOR.count.get(); int expectedIncrement = 0; try { // Creates an HTTP server that echoes back whatever is in the // request body. HTTPTestServer server = HTTPTestServer.create(protocol, mode, AUTHENTICATOR, getHttpSchemeType()); try { expectedIncrement += run(server, protocol, mode); } finally { server.stop(); } } catch (IOException ex) { ex.printStackTrace(System.err); throw new UncheckedIOException(ex); } int count = AUTHENTICATOR.count.get(); if (count != authCount + expectedIncrement) { throw new AssertionError("Authenticator called " + count(count) + " expected it to be called " + expected(authCount + expectedIncrement)); } } /** * Runs the test with the given parameters. * @param server The server * @param protocol The protocol (HTTP/HTTPS) * @param mode The mode (PROXY, SERVER, SERVER307...) * @return The number of times the default authenticator should have been * called. * @throws IOException in case of connection or protocol issues */ public int run(HTTPTestServer server, HttpProtocolType protocol, HttpAuthType mode) throws IOException { // Connect to the server with a GET request, then with a // POST that contains "Hello World!" HTTPTestClient.connect(protocol, server, mode, null); // return the number of times the default authenticator is supposed // to have been called. return EXPECTED_AUTH_CALLS_PER_TEST; } public static String count(int count) { switch(count) { case 0: return "not even once"; case 1: return "once"; case 2: return "twice"; default: return String.valueOf(count) + " times"; } } public static String expected(int count) { switch(count) { default: return count(count); } } public static String protocol(HttpProtocolType type) { return type.name().toLowerCase(Locale.US); } public static URL url(HttpProtocolType protocol, InetSocketAddress address, String path) throws MalformedURLException { return new URL(protocol(protocol), address.getHostString(), address.getPort(), path); } public static Proxy proxy(HTTPTestServer server, HttpAuthType authType) { return (authType == HttpAuthType.PROXY) ? new Proxy(Proxy.Type.HTTP, server.getAddress()) : null; } public static HttpURLConnection openConnection(URL url, HttpAuthType authType, Proxy proxy) throws IOException { HttpURLConnection conn = (HttpURLConnection) (authType == HttpAuthType.PROXY ? url.openConnection(proxy) : url.openConnection()); return conn; } }