/* * Copyright (c) 2002, 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. * * 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 * @modules jdk.httpserver * @run main/othervm B4769350 server * @run main/othervm 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() { InetSocketAddress addr = new InetSocketAddress(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, 0); } 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", "localhost"); System.setProperty ("http.proxyPort", Integer.toString(server.getPort())); doProxyTests ("www.foo.com", server); } else { doServerTests ("localhost:"+server.getPort(), server); } } } 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; } } }