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