| /* | |
| * Copyright (c) 2002, 2024, 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. | |
| * | |
| * 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. | |
| */ | |
| /** | |
| * @test | |
| * @bug 4769350 8017779 8191169 | |
| * @modules jdk.httpserver | |
| * @run main/othervm B4769350 server | |
| * @run main/othervm B4769350 proxy | |
| * @run main/othervm -Djava.net.preferIPv6Addresses=true B4769350 server | |
| * @run main/othervm -Djava.net.preferIPv6Addresses=true B4769350 proxy | |
| * @summary proxy authentication username and password caching only works in serial case | |
| * Run in othervm since the test sets system properties that are read by the | |
| * networking stack and cached when the HTTP handler is invoked, and previous | |
| * tests may already have invoked the HTTP handler. | |
| */ | |
| import com.sun.net.httpserver.HttpExchange; | |
| import com.sun.net.httpserver.HttpHandler; | |
| import com.sun.net.httpserver.HttpServer; | |
| import java.io.*; | |
| import java.net.*; | |
| import java.util.concurrent.BrokenBarrierException; | |
| import java.util.concurrent.CountDownLatch; | |
| import java.util.concurrent.CyclicBarrier; | |
| import java.util.concurrent.Executor; | |
| import java.util.concurrent.ExecutorService; | |
| import java.util.concurrent.Executors; | |
| public class B4769350 { | |
| static int count = 0; | |
| static boolean error = false; | |
| static void read (InputStream is) throws IOException { | |
| while (is.read() != -1) { | |
| //System.out.write (c); | |
| } | |
| } | |
| static class Client extends Thread { | |
| String authority, path; | |
| boolean allowerror; | |
| Client (String authority, String path, boolean allowerror) { | |
| super("Thread-" + path); | |
| this.authority = authority; | |
| this.path = path; | |
| this.allowerror = allowerror; | |
| } | |
| @Override | |
| public void run () { | |
| try { | |
| URI u = new URI ("http", authority, path, null, null); | |
| URL url = u.toURL(); | |
| URLConnection urlc = url.openConnection(); | |
| try (InputStream is = urlc.getInputStream()) { | |
| read (is); | |
| } | |
| } catch (URISyntaxException e) { | |
| System.out.println (e); | |
| error = true; | |
| } catch (IOException e) { | |
| if (!allowerror) { | |
| System.out.println (Thread.currentThread().getName() | |
| + " " + e); | |
| e.printStackTrace(); | |
| error = true; | |
| } | |
| } | |
| } | |
| } | |
| class Server implements AutoCloseable { | |
| HttpServer server; | |
| Executor executor; | |
| public String getAddress() { | |
| return server.getAddress().getHostName(); | |
| } | |
| public void startServer() { | |
| InetAddress loopback = InetAddress.getLoopbackAddress(); | |
| InetSocketAddress addr = new InetSocketAddress(loopback, 0); | |
| try { | |
| server = HttpServer.create(addr, 0); | |
| } catch (IOException ioe) { | |
| throw new RuntimeException("Server could not be created"); | |
| } | |
| executor = Executors.newFixedThreadPool(10); | |
| server.setExecutor(executor); | |
| server.createContext("/test/realm1/t1a", | |
| new AuthenticationHandlerT1a() ); | |
| server.createContext("/test/realm2/t1b", | |
| new AuthenticationHandlerT1b()); | |
| server.createContext("/test/realm1/t1c", | |
| new AuthenticationHandlerT1c()); | |
| server.createContext("/test/realm2/t1d", | |
| new AuthenticationHandlerT1d()); | |
| server.createContext("/test/realm3/t2a", | |
| new AuthenticationHandlerT2a()); | |
| server.createContext("/test/realm3/t2b", | |
| new AuthenticationHandlerT2b()); | |
| server.createContext("/test/realm4/t3a", | |
| new AuthenticationHandlerT3a()); | |
| server.createContext("/test/realm4/t3b", | |
| new AuthenticationHandlerT3bc()); | |
| server.createContext("/test/realm4/t3c", | |
| new AuthenticationHandlerT3bc()); | |
| t1Cond1 = new CyclicBarrier(3); | |
| server.start(); | |
| } | |
| public int getPort() { | |
| return server.getAddress().getPort(); | |
| } | |
| @Override | |
| public void close() { | |
| if (executor != null) | |
| ((ExecutorService)executor).shutdownNow(); | |
| if (server != null) | |
| server.stop(0); | |
| } | |
| /* T1 tests the client by sending 4 requests to 2 different realms | |
| * in parallel. The client should recognise two pairs of dependent requests | |
| * and execute the first of each pair in parallel. When they both succeed | |
| * the second requests should be executed without calling the authenticator. | |
| * The test succeeds if the authenticator was only called twice. | |
| */ | |
| class AuthenticationHandlerT1a implements HttpHandler | |
| { | |
| volatile int count = -1; | |
| @Override | |
| public void handle(HttpExchange exchange) throws IOException { | |
| count++; | |
| try { | |
| switch(count) { | |
| case 0: | |
| AuthenticationHandler.errorReply(exchange, | |
| "Basic realm=\"realm1\""); | |
| break; | |
| case 1: | |
| t1Cond1.await(); | |
| AuthenticationHandler.okReply(exchange); | |
| break; | |
| default: | |
| System.out.println ("Unexpected request"); | |
| } | |
| } catch (InterruptedException | | |
| BrokenBarrierException e) | |
| { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| } | |
| class AuthenticationHandlerT1b implements HttpHandler | |
| { | |
| volatile int count = -1; | |
| @Override | |
| public void handle(HttpExchange exchange) throws IOException { | |
| count++; | |
| try { | |
| switch(count) { | |
| case 0: | |
| AuthenticationHandler.errorReply(exchange, | |
| "Basic realm=\"realm2\""); | |
| break; | |
| case 1: | |
| t1Cond1.await(); | |
| AuthenticationHandler.okReply(exchange); | |
| break; | |
| default: | |
| System.out.println ("Unexpected request"); | |
| } | |
| } catch (InterruptedException | BrokenBarrierException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| } | |
| class AuthenticationHandlerT1c implements HttpHandler | |
| { | |
| volatile int count = -1; | |
| @Override | |
| public void handle(HttpExchange exchange) throws IOException { | |
| count++; | |
| switch(count) { | |
| case 0: | |
| AuthenticationHandler.errorReply(exchange, | |
| "Basic realm=\"realm1\""); | |
| break; | |
| case 1: | |
| AuthenticationHandler.okReply(exchange); | |
| break; | |
| default: | |
| System.out.println ("Unexpected request"); | |
| } | |
| } | |
| } | |
| class AuthenticationHandlerT1d implements HttpHandler | |
| { | |
| volatile int count = -1; | |
| @Override | |
| public void handle(HttpExchange exchange) throws IOException { | |
| count++; | |
| switch(count) { | |
| case 0: | |
| AuthenticationHandler.errorReply(exchange, | |
| "Basic realm=\"realm2\""); | |
| break; | |
| case 1: | |
| AuthenticationHandler.okReply(exchange); | |
| break; | |
| default: | |
| System.out.println ("Unexpected request"); | |
| } | |
| } | |
| } | |
| /* T2 tests to check that if initial authentication fails, the second will | |
| * succeed, and the authenticator is called twice | |
| */ | |
| class AuthenticationHandlerT2a implements HttpHandler | |
| { | |
| volatile int count = -1; | |
| @Override | |
| public void handle(HttpExchange exchange) throws IOException { | |
| count++; | |
| if (count == 1) { | |
| t2condlatch.countDown(); | |
| } | |
| AuthenticationHandler.errorReply(exchange, | |
| "Basic realm=\"realm3\""); | |
| } | |
| } | |
| class AuthenticationHandlerT2b implements HttpHandler | |
| { | |
| volatile int count = -1; | |
| @Override | |
| public void handle(HttpExchange exchange) throws IOException { | |
| count++; | |
| switch(count) { | |
| case 0: | |
| AuthenticationHandler.errorReply(exchange, | |
| "Basic realm=\"realm3\""); | |
| break; | |
| case 1: | |
| AuthenticationHandler.okReply(exchange); | |
| break; | |
| default: | |
| System.out.println ("Unexpected request"); | |
| } | |
| } | |
| } | |
| /* T3 tests proxy and server authentication. three threads request same | |
| * resource at same time. Authenticator should be called once for server | |
| * and once for proxy | |
| */ | |
| class AuthenticationHandlerT3a implements HttpHandler | |
| { | |
| volatile int count = -1; | |
| @Override | |
| public void handle(HttpExchange exchange) throws IOException { | |
| count++; | |
| switch(count) { | |
| case 0: | |
| AuthenticationHandler.proxyReply(exchange, | |
| "Basic realm=\"proxy\""); | |
| break; | |
| case 1: | |
| t3cond1.countDown(); | |
| AuthenticationHandler.errorReply(exchange, | |
| "Basic realm=\"realm4\""); | |
| break; | |
| case 2: | |
| AuthenticationHandler.okReply(exchange); | |
| break; | |
| default: | |
| System.out.println ("Unexpected request"); | |
| } | |
| } | |
| } | |
| class AuthenticationHandlerT3bc implements HttpHandler | |
| { | |
| volatile int count = -1; | |
| @Override | |
| public void handle(HttpExchange exchange) throws IOException { | |
| count++; | |
| switch(count) { | |
| case 0: | |
| AuthenticationHandler.proxyReply(exchange, | |
| "Basic realm=\"proxy\""); | |
| break; | |
| case 1: | |
| AuthenticationHandler.okReply(exchange); | |
| break; | |
| default: | |
| System.out.println ("Unexpected request"); | |
| } | |
| } | |
| } | |
| } | |
| static class AuthenticationHandler { | |
| static void errorReply(HttpExchange exchange, String reply) | |
| throws IOException | |
| { | |
| exchange.getResponseHeaders().add("Connection", "close"); | |
| exchange.getResponseHeaders().add("WWW-Authenticate", reply); | |
| exchange.sendResponseHeaders(401, 0); | |
| exchange.close(); | |
| } | |
| static void proxyReply (HttpExchange exchange, String reply) | |
| throws IOException | |
| { | |
| exchange.getResponseHeaders().add("Proxy-Authenticate", reply); | |
| exchange.sendResponseHeaders(407, -1); | |
| } | |
| static void okReply (HttpExchange exchange) throws IOException { | |
| exchange.getResponseHeaders().add("Connection", "close"); | |
| String response = "Hello ."; | |
| exchange.sendResponseHeaders(200, response.getBytes().length); | |
| try (OutputStream os = exchange.getResponseBody()) { | |
| os.write(response.getBytes()); | |
| } | |
| exchange.close(); | |
| } | |
| } | |
| static Server server; | |
| static MyAuthenticator auth = new MyAuthenticator (); | |
| static int redirects = 4; | |
| static Client c1,c2,c3,c4,c5,c6,c7,c8,c9; | |
| static CountDownLatch t2condlatch; | |
| static CountDownLatch t3cond1; | |
| static CyclicBarrier t1Cond1; | |
| static void doServerTests (String authority, Server server) throws Exception | |
| { | |
| System.out.println ("Doing Server tests"); | |
| System.out.println ("T1"); | |
| c1 = new Client (authority, "/test/realm1/t1a", false); | |
| c2 = new Client (authority, "/test/realm2/t1b", false); | |
| c3 = new Client (authority, "/test/realm1/t1c", false); | |
| c4 = new Client (authority, "/test/realm2/t1d", false); | |
| c1.start(); c2.start(); | |
| t1Cond1.await(); | |
| c3.start(); c4.start(); | |
| c1.join(); c2.join(); c3.join(); c4.join(); | |
| int f = auth.getCount(); | |
| if (f != 2) { | |
| except ("Authenticator was called "+f+" times. Should be 2", | |
| server); | |
| } | |
| if (error) { | |
| except ("error occurred", server); | |
| } | |
| auth.resetCount(); | |
| System.out.println ("T2"); | |
| c5 = new Client (authority, "/test/realm3/t2a", true); | |
| c6 = new Client (authority, "/test/realm3/t2b", false); | |
| t2condlatch = new CountDownLatch(1); | |
| c5.start (); | |
| t2condlatch.await(); | |
| c6.start (); | |
| c5.join(); c6.join(); | |
| f = auth.getCount(); | |
| if (f != redirects+1) { | |
| except ("Authenticator was called "+f+" times. Should be: " | |
| + redirects+1, server); | |
| } | |
| if (error) { | |
| except ("error occurred", server); | |
| } | |
| } | |
| static void doProxyTests (String authority, Server server) throws Exception | |
| { | |
| System.out.println ("Doing Proxy tests"); | |
| c7 = new Client (authority, "/test/realm4/t3a", false); | |
| c8 = new Client (authority, "/test/realm4/t3b", false); | |
| c9 = new Client (authority, "/test/realm4/t3c", false); | |
| t3cond1 = new CountDownLatch(1); | |
| c7.start (); | |
| t3cond1.await(); | |
| c8.start (); | |
| c9.start (); | |
| c7.join(); c8.join(); c9.join(); | |
| int f = auth.getCount(); | |
| if (f != 2) { | |
| except ("Authenticator was called "+f+" times. Should be: " + 2, | |
| server); | |
| } | |
| if (error) { | |
| except ("error occurred", server); | |
| } | |
| } | |
| public static void main (String[] args) throws Exception { | |
| new B4769350().runTest(args[0].equals ("proxy")); | |
| } | |
| public void runTest(boolean proxy) throws Exception { | |
| System.setProperty ("http.maxRedirects", Integer.toString (redirects)); | |
| System.setProperty ("http.auth.serializeRequests", "true"); | |
| Authenticator.setDefault (auth); | |
| try (Server server = new Server()) { | |
| server.startServer(); | |
| System.out.println ("Server: listening on port: " | |
| + server.getPort()); | |
| if (proxy) { | |
| System.setProperty ("http.proxyHost", | |
| InetAddress.getLoopbackAddress().getHostAddress()); | |
| System.setProperty ("http.proxyPort", | |
| Integer.toString(server.getPort())); | |
| doProxyTests ("www.foo.com", server); | |
| } else { | |
| ProxySelector.setDefault(ProxySelector.of(null)); | |
| doServerTests (authority(server.getPort()), server); | |
| } | |
| } | |
| } | |
| static String authority(int port) { | |
| InetAddress loopback = InetAddress.getLoopbackAddress(); | |
| String hoststr = loopback.getHostAddress(); | |
| if (hoststr.indexOf(':') > -1) { | |
| hoststr = "[" + hoststr + "]"; | |
| } | |
| return hoststr + ":" + port; | |
| } | |
| public static void except (String s, Server server) { | |
| server.close(); | |
| throw new RuntimeException (s); | |
| } | |
| static class MyAuthenticator extends Authenticator { | |
| MyAuthenticator () { | |
| super (); | |
| } | |
| volatile int count = 0; | |
| @Override | |
| public PasswordAuthentication getPasswordAuthentication () { | |
| PasswordAuthentication pw; | |
| pw = new PasswordAuthentication ("user", "pass1".toCharArray()); | |
| count ++; | |
| return pw; | |
| } | |
| public void resetCount () { | |
| count = 0; | |
| } | |
| public int getCount () { | |
| return count; | |
| } | |
| } | |
| } |