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 private final String name; 93 HttpTestAuthenticator(String name, String realm, String username)94 public HttpTestAuthenticator(String name, String realm, String username) { 95 this.name = name; 96 this.realm = realm; 97 this.username = username; 98 } 99 100 @Override getPasswordAuthentication()101 protected PasswordAuthentication getPasswordAuthentication() { 102 if (skipCount.get() == null || skipCount.get().booleanValue() == false) { 103 System.out.println("Authenticator " + name + " 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. getPassword(String user)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 @Override toString()124 public String toString() { 125 return super.toString() + "[name=\"" + name + "\"]"; 126 } 127 getUserName()128 public final String getUserName() { 129 return username; 130 } getRealm()131 public final String getRealm() { 132 return realm; 133 } 134 135 } 136 public static final HttpTestAuthenticator AUTHENTICATOR; 137 static { 138 AUTHENTICATOR = new HttpTestAuthenticator("AUTHENTICATOR","dublin", "foox"); 139 Authenticator.setDefault(AUTHENTICATOR); 140 } 141 142 static { 143 try { HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } })144 HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { 145 public boolean verify(String hostname, SSLSession session) { 146 return true; 147 } 148 }); SSLContext.setDefault(new SimpleSSLContext().get())149 SSLContext.setDefault(new SimpleSSLContext().get()); 150 } catch (IOException ex) { 151 throw new ExceptionInInitializerError(ex); 152 } 153 } 154 155 static final Logger logger = Logger.getLogger ("com.sun.net.httpserver"); 156 static { 157 if (DEBUG) logger.setLevel(Level.ALL); 158 Stream.of(Logger.getLogger("").getHandlers()) 159 .forEach(h -> h.setLevel(Level.ALL)); 160 } 161 162 static final int EXPECTED_AUTH_CALLS_PER_TEST = 1; 163 main(String[] args)164 public static void main(String[] args) throws Exception { 165 // new HTTPTest().execute(HttpAuthType.SERVER.name()); 166 new HTTPTest().execute(args); 167 } 168 execute(String... args)169 public void execute(String... args) throws Exception { 170 Stream<HttpAuthType> modes; 171 if (args == null || args.length == 0) { 172 modes = Stream.of(HttpAuthType.values()); 173 } else { 174 modes = Stream.of(args).map(HttpAuthType::valueOf); 175 } 176 modes.forEach(this::test); 177 System.out.println("Test PASSED - Authenticator called: " 178 + expected(AUTHENTICATOR.count.get())); 179 } 180 test(HttpAuthType mode)181 public void test(HttpAuthType mode) { 182 for (HttpProtocolType type: HttpProtocolType.values()) { 183 test(type, mode); 184 } 185 } 186 getHttpSchemeType()187 public HttpSchemeType getHttpSchemeType() { 188 return DEFAULT_SCHEME_TYPE; 189 } 190 test(HttpProtocolType protocol, HttpAuthType mode)191 public void test(HttpProtocolType protocol, HttpAuthType mode) { 192 if (mode == HttpAuthType.PROXY305 && protocol == HttpProtocolType.HTTPS ) { 193 // silently skip unsupported test combination 194 return; 195 } 196 System.out.println("\n**** Testing " + protocol + " " 197 + mode + " mode ****\n"); 198 int authCount = AUTHENTICATOR.count.get(); 199 int expectedIncrement = 0; 200 try { 201 // Creates an HTTP server that echoes back whatever is in the 202 // request body. 203 HTTPTestServer server = 204 HTTPTestServer.create(protocol, 205 mode, 206 AUTHENTICATOR, 207 getHttpSchemeType()); 208 try { 209 expectedIncrement += run(server, protocol, mode); 210 } finally { 211 server.stop(); 212 } 213 } catch (IOException ex) { 214 ex.printStackTrace(System.err); 215 throw new UncheckedIOException(ex); 216 } 217 int count = AUTHENTICATOR.count.get(); 218 if (count != authCount + expectedIncrement) { 219 throw new AssertionError("Authenticator called " + count(count) 220 + " expected it to be called " 221 + expected(authCount + expectedIncrement)); 222 } 223 } 224 225 /** 226 * Runs the test with the given parameters. 227 * @param server The server 228 * @param protocol The protocol (HTTP/HTTPS) 229 * @param mode The mode (PROXY, SERVER, SERVER307...) 230 * @return The number of times the default authenticator should have been 231 * called. 232 * @throws IOException in case of connection or protocol issues 233 */ run(HTTPTestServer server, HttpProtocolType protocol, HttpAuthType mode)234 public int run(HTTPTestServer server, 235 HttpProtocolType protocol, 236 HttpAuthType mode) 237 throws IOException 238 { 239 // Connect to the server with a GET request, then with a 240 // POST that contains "Hello World!" 241 HTTPTestClient.connect(protocol, server, mode, null); 242 // return the number of times the default authenticator is supposed 243 // to have been called. 244 return EXPECTED_AUTH_CALLS_PER_TEST; 245 } 246 count(int count)247 public static String count(int count) { 248 switch(count) { 249 case 0: return "not even once"; 250 case 1: return "once"; 251 case 2: return "twice"; 252 default: return String.valueOf(count) + " times"; 253 } 254 } 255 expected(int count)256 public static String expected(int count) { 257 switch(count) { 258 default: return count(count); 259 } 260 } protocol(HttpProtocolType type)261 public static String protocol(HttpProtocolType type) { 262 return type.name().toLowerCase(Locale.US); 263 } 264 url(HttpProtocolType protocol, InetSocketAddress address, String path)265 public static URL url(HttpProtocolType protocol, InetSocketAddress address, 266 String path) throws MalformedURLException { 267 return new URL(protocol(protocol), 268 address.getHostString(), 269 address.getPort(), path); 270 } 271 proxy(HTTPTestServer server, HttpAuthType authType)272 public static Proxy proxy(HTTPTestServer server, HttpAuthType authType) { 273 return (authType == HttpAuthType.PROXY) 274 ? new Proxy(Proxy.Type.HTTP, server.getAddress()) 275 : null; 276 } 277 openConnection(URL url, HttpAuthType authType, Proxy proxy)278 public static HttpURLConnection openConnection(URL url, 279 HttpAuthType authType, 280 Proxy proxy) 281 throws IOException { 282 283 HttpURLConnection conn = (HttpURLConnection) 284 (authType == HttpAuthType.PROXY 285 ? url.openConnection(proxy) 286 : url.openConnection()); 287 return conn; 288 } 289 } 290