/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.luni.tests.internal.net.www.protocol.http;

import dalvik.annotation.SideEffect;
import dalvik.annotation.BrokenTest;
import dalvik.annotation.KnownFailure;
import dalvik.annotation.TestTargetClass;
import dalvik.annotation.TestTargets;
import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetNew;

import java.io.IOException;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;

import junit.framework.TestCase;


/**
 * Tests for <code>HTTPURLConnection</code> class constructors and methods.
 *
 */
@TestTargetClass(HttpURLConnection.class)
public class HttpURLConnectionTest extends TestCase {

    private final static Object bound = new Object();

    static class MockServer extends Thread {
        ServerSocket serverSocket;
        boolean accepted = false;
        boolean started = false;

        public MockServer(String name) throws IOException {
            super(name);
            serverSocket = new ServerSocket(0);
            serverSocket.setSoTimeout(1000);
        }

        public int port() {
            return serverSocket.getLocalPort();
        }

        @Override
        public void run() {
            try {
                synchronized (bound) {
                    started = true;
                    bound.notify();
                }
                try {
                    serverSocket.accept().close();
                    accepted = true;
                } catch (SocketTimeoutException ignore) {
                }
                serverSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    static class MockProxyServer extends MockServer {

        boolean acceptedAuthorizedRequest;

        public MockProxyServer(String name) throws Exception {
            super(name);
        }

        @Override
        public void run() {
            try {
                Socket socket = serverSocket.accept();
                socket.setSoTimeout(1000);
                byte[] buff = new byte[1024];
                int num = socket.getInputStream().read(buff);
                socket.getOutputStream().write((
                    "HTTP/1.0 407 Proxy authentication required\n"
                  + "Proxy-authenticate: Basic realm=\"remotehost\"\n\n")
                        .getBytes());
                num = socket.getInputStream().read(buff);
                if (num == -1) {
                    // this connection was closed, create new one:
                    socket = serverSocket.accept();
                    socket.setSoTimeout(1000);
                    num = socket.getInputStream().read(buff);
                }
                String request = new String(buff, 0, num);
                acceptedAuthorizedRequest =
                    request.toLowerCase().indexOf("proxy-authorization:") > 0;
                if (acceptedAuthorizedRequest) {
                    socket.getOutputStream().write((
                            "HTTP/1.1 200 OK\n\n").getBytes());
                }
            } catch (IOException e) {
            }
        }
    }

    /**
     * ProxySelector implementation used in the test.
     */
    static class TestProxySelector extends ProxySelector {
        // proxy port
        private int proxy_port;
        // server port
        private int server_port;

        /**
         * Creates proxy selector instance.
         * Selector will return the proxy, only if the connection
         * is made to localhost:server_port. Otherwise it will
         * return NO_PROXY.
         * Address of the returned proxy will be localhost:proxy_port.
         */
        public TestProxySelector(int server_port, int proxy_port) {
            this.server_port = server_port;
            this.proxy_port = proxy_port;
        }

        @Override
        public java.util.List<Proxy> select(URI uri) {
            Proxy proxy = Proxy.NO_PROXY;
            if (("localhost".equals(uri.getHost()))
                    && (server_port == uri.getPort())) {
                proxy = new Proxy(Proxy.Type.HTTP,
                            new InetSocketAddress("localhost", proxy_port));
            }
            ArrayList<Proxy> result = new ArrayList<Proxy>();
            result.add(proxy);
            return result;
        }

        @Override
        public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
            // do nothing
        }
    }

    /**
     * @tests org.apache.harmony.luni.internal.net.www.http.getOutputStream()
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "Regression test.",
        method = "getOutputStream",
        args = {}
    )
    public void testGetOutputStream() throws Exception {
        // Regression for HARMONY-482
        MockServer httpServer =
            new MockServer("ServerSocket for HttpURLConnectionTest");
        httpServer.start();
        synchronized(bound) {
            if (!httpServer.started) {
                bound.wait(5000);
            }
        }
        HttpURLConnection c = (HttpURLConnection)
            new URL("http://localhost:" + httpServer.port()).openConnection();
        c.setDoOutput(true);
        //use new String("POST") instead of simple "POST" to obtain other
        //object instances then those that are in HttpURLConnection classes
        c.setRequestMethod(new String("POST"));
        c.getOutputStream();
        httpServer.join();
    }


    /**
     * Test checks if the proxy specified in openConnection
     * method will be used for connection to the server
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "Verifies if the proxy specified in openConnection method will be used for connection to the server.",
        method = "usingProxy",
        args = {}
    )
    public void testUsingProxy() throws Exception {
        // Regression for HARMONY-570
        MockServer server = new MockServer("server");
        MockServer proxy = new MockServer("proxy");

        URL url = new URL("http://localhost:" + server.port());

        HttpURLConnection connection = (HttpURLConnection) url
                .openConnection(new Proxy(Proxy.Type.HTTP,
                        new InetSocketAddress("localhost",
                            proxy.port())));
        connection.setConnectTimeout(2000);
        connection.setReadTimeout(2000);

        server.start();
        synchronized(bound) {
            if (!server.started) bound.wait(5000);
        }
        proxy.start();
        synchronized(bound) {
            if (!proxy.started) bound.wait(5000);
        }

        connection.connect();

        // wait while server and proxy run
        server.join();
        proxy.join();

        assertTrue("Connection does not use proxy", connection.usingProxy());
        assertTrue("Proxy server was not used", proxy.accepted);

        HttpURLConnection huc = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
        assertFalse(huc.usingProxy());
    }

    /**
     * Test checks if the proxy provided by proxy selector
     * will be used for connection to the server
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "Verifies if the proxy provided by proxy selector will be used for connection to the server.",
        method = "usingProxy",
        args = {}
    )
    public void testUsingProxySelector() throws Exception {
        // Regression for HARMONY-570
        MockServer server = new MockServer("server");
        MockServer proxy = new MockServer("proxy");

        URL url = new URL("http://localhost:" + server.port());

        // keep default proxy selector
        ProxySelector defPS = ProxySelector.getDefault();
        // replace selector
        ProxySelector.setDefault(
                new TestProxySelector(server.port(), proxy.port()));

        try {
            HttpURLConnection connection =
                (HttpURLConnection) url.openConnection();
            connection.setConnectTimeout(2000);
            connection.setReadTimeout(2000);

            server.start();
            synchronized(bound) {
                if (!server.started) bound.wait(5000);
            }
            proxy.start();
            synchronized(bound) {
                if (!proxy.started) bound.wait(5000);
            }
            connection.connect();

            // wait while server and proxy run
            server.join();
            proxy.join();

            assertTrue("Connection does not use proxy",
                                            connection.usingProxy());
            assertTrue("Proxy server was not used", proxy.accepted);
        } finally {
            // restore default proxy selector
            ProxySelector.setDefault(defPS);
        }
    }
    @TestTargets({
        @TestTargetNew(
            level = TestLevel.PARTIAL,
            notes = "Regression test.",
            method = "getResponseCode",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.PARTIAL,
            notes = "Regression test.",
            method = "connect",
            args = {}
        )
    })
    @SideEffect("Suffers from side effect of other, currently unknown test")
    public void testProxyAuthorization() throws Exception {
        // Set up test Authenticator
        Authenticator.setDefault(new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(
                    "user", "password".toCharArray());
            }
        });

        try {
            MockProxyServer proxy = new MockProxyServer("ProxyServer");

            URL url = new URL("http://remotehost:55555/requested.data");
            HttpURLConnection connection =
                (HttpURLConnection) url.openConnection(
                        new Proxy(Proxy.Type.HTTP,
                            new InetSocketAddress("localhost", proxy.port())));
            connection.setConnectTimeout(1000);
            connection.setReadTimeout(1000);

            proxy.start();

            connection.connect();
            assertEquals("unexpected response code",
                    200, connection.getResponseCode());
            proxy.join();
            assertTrue("Connection did not send proxy authorization request",
                    proxy.acceptedAuthorizedRequest);
        } finally {
            // remove previously set authenticator
            Authenticator.setDefault(null);
        }
    }

}
