/*
 * Copyright (c) 2002, 2013, 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
 * @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;
        }
    }
}

