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