/*
 *  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.
 */

/**
* @author Alexander V. Astapchuk
* @version $Revision$
*/

package org.apache.harmony.security.tests.java.security;

import dalvik.annotation.TestTargetClass;
import dalvik.annotation.TestTargets;
import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetNew;

import java.io.File;
import java.net.URL;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.cert.CertPath;
import java.security.cert.Certificate;

import org.apache.harmony.security.tests.support.TestCertUtils;

import junit.framework.TestCase;

@TestTargetClass(CodeSource.class)
/**
 * Unit test for CodeSource.
 *
 */

public class CodeSourceTest extends TestCase {
    private java.security.cert.Certificate[] chain = null;

    /* Below are various URLs used during the testing */
    private static URL urlSite;

    private static URL urlDir; // must NOT end with '/'

    private static URL urlDirOtherSite; // same as urlDir, but another site

    private static URL urlDir_port80, urlDir_port81;

    /* must be exactly the same as urlDir, but with slash added */
    private static URL urlDirWithSlash;

    //private static URL urlDirFtp;
    private static URL urlDir_FileProtocol;

    private static URL urlDirIP;

    private static URL urlFile, urlFileWithAdditionalDirs, urlFileDirOtherDir;

    private static URL urlFileDirMinus;

    private static URL urlFileDirStar;

    private static URL urlRef1, urlRef2;

    private boolean init = false;

    private void init() {
        if (!init) {
            try {
                String siteName = "www.intel.com";
                InetAddress addr = InetAddress.getByName(siteName);
                String siteIP = addr.getHostAddress();

                urlSite = new URL("http://"+siteName+"");
                urlDir = new URL("http://"+siteName+"/drl_test");
                urlDirOtherSite = new URL("http://www.any-other-site-which-is-not-siteName.com/drl_test");

                urlDir_port80 = new URL("http://"+siteName+":80/drl_test");
                urlDir_port81 = new URL("http://"+siteName+":81/drl_test");
                urlDirWithSlash = new URL(urlDir + "/");

                //urlDirFtp = new URL("ftp://www.intel.com/drl_test");
                urlDir_FileProtocol = new URL("file://"+siteName+"/drl_test");

                urlDirIP = new URL("http://"+siteIP+"/drl_test");

                urlFile = new URL("http://"+siteName+"/drl_test/empty.jar");
                urlFileWithAdditionalDirs = new URL(
                        "http://"+siteName+"/drl_test/what/ever/here/empty.jar");

                urlFileDirMinus = new URL("http://"+siteName+"/drl_test/-");
                urlFileDirStar = new URL("http://"+siteName+"/drl_test/*");
                urlFileDirOtherDir = new URL("http://"+siteName+"/_test_drl_/*");

                urlRef1 = new URL("http://"+siteName+"/drl_test/index.html#ref1");
                urlRef2 = new URL("http://"+siteName+"/drl_test/index.html#ref2");
            } catch (MalformedURLException ex) {
                throw new Error(ex);
            } catch (UnknownHostException ex) {
                throw new Error(ex);
            } finally {
                init = true;
            }
        }
    }

    protected void setUp() throws Exception {
        super.setUp();
        init();
        chain = TestCertUtils.getCertChain();
    }

    /**
     * Tests hashCode().<br>
     * javadoc says nothing, so test DRL-specific implementation.
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "hashCode",
        args = {}
    )
    public void testHashCode() {
        // when nothing is specified, then hashCode obviously must be 0.
        assertTrue(new CodeSource(null, (Certificate[]) null).hashCode() == 0);
        // only URL.hashCode is taken into account...
        assertTrue(new CodeSource(urlSite, (Certificate[]) null).hashCode() == urlSite
                .hashCode());
        // ... and certs[] does not affect it
        assertTrue(new CodeSource(urlSite, chain).hashCode() == urlSite
                .hashCode());
    }

    /**
     * Tests CodeSource(URL, Certificate[]).
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "CodeSource",
        args = {java.net.URL.class, java.security.cert.Certificate[].class}
    )
    public void testCodeSourceURLCertificateArray() {
        new CodeSource(null, (Certificate[]) null);
        new CodeSource(urlSite, (Certificate[]) null);
        new CodeSource(null, chain);
        new CodeSource(urlSite, chain);
    }

    /**
     * Tests CodeSource(URL, CodeSigner[]).
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "Verifies method with null parameters only",
        method = "CodeSource",
        args = {java.net.URL.class, java.security.CodeSigner[].class}
    )
    public void testCodeSourceURLCodeSignerArray() {
        if (!has_15_features()) {
            return;
        }
        new CodeSource(null, (CodeSigner[]) null);

    }

    /**
     * equals(Object) must return <code>false</code> for null
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Null parameter checked",
        method = "equals",
        args = {java.lang.Object.class}
    )
    public void testEqualsObject_00() {
        CodeSource thiz = new CodeSource(urlSite, (Certificate[]) null);
        assertFalse(thiz.equals(null));

    }

    /**
     * equals(Object) must return <code>true</code> for the same object
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Same objects checked",
        method = "equals",
        args = {java.lang.Object.class}
    )
    public void testEqualsObject_01() {
        CodeSource thiz = new CodeSource(urlSite, (Certificate[]) null);
        assertTrue(thiz.equals(thiz));
    }

    /**
     * Test for equals(Object)<br>
     * The signer certificate chain must contain the same set of certificates, but
     * the order of the certificates is not taken into account.
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "equals",
        args = {java.lang.Object.class}
    )
    public void testEqualsObject_02() {
        Certificate cert0 = new TestCertUtils.TestCertificate();
        Certificate cert1 = new TestCertUtils.TestCertificate();
        Certificate[] certs0 = new Certificate[] { cert0, cert1 };
        Certificate[] certs1 = new Certificate[] { cert1, cert0 };
        CodeSource thiz = new CodeSource(urlSite, certs0);
        CodeSource that = new CodeSource(urlSite, certs1);
        assertTrue(thiz.equals(that));
    }

    /**
     * Test for equals(Object)<br>
     * Checks that both 'null' and not-null URLs are taken into account - properly.
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "equals",
        args = {java.lang.Object.class}
    )
    public void testEqualsObject_04() {
        CodeSource thiz = new CodeSource(urlSite, (Certificate[]) null);
        CodeSource that = new CodeSource(null, (Certificate[]) null);
        assertFalse(thiz.equals(that));
        assertFalse(that.equals(thiz));

        that = new CodeSource(urlFile, (Certificate[]) null);
        assertFalse(thiz.equals(that));
        assertFalse(that.equals(thiz));
    }

    /**
     * Tests CodeSource.getCertificates().
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "getCertificates",
        args = {}
    )
    public void testGetCertificates_00() {
        assertNull(new CodeSource(null, (Certificate[]) null).getCertificates());
        java.security.cert.Certificate[] got = new CodeSource(null, chain)
                .getCertificates();
        // The returned array must be clone()-d ...
        assertNotSame(got, chain);
        // ... but must represent the same set of certificates
        assertTrue(checkEqual(got, chain));
    }

    /**
     * Tests whether the getCertificates() returns certificates obtained from
     * the signers.
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "getCertificates",
        args = {}
    )
    public void testGetCertificates_01() {
        if (!has_15_features()) {
            return;
        }
        CertPath cpath = TestCertUtils.getCertPath();
        Certificate[] certs = (Certificate[]) cpath.getCertificates().toArray();
        CodeSigner[] signers = { new CodeSigner(cpath, null) };
        CodeSource cs = new CodeSource(null, signers);
        Certificate[] got = cs.getCertificates();
        // The set of certificates must be exactly the same,
        // but the order is not specified
        assertTrue(presented(certs, got));
        assertTrue(presented(got, certs));
    }

    /**
     * Checks whether two arrays of certificates represent the same same set of
     * certificates - in the same order.
     * @param one first array
     * @param two second array
     * @return <code>true</code> if both arrays represent the same set of
     * certificates,
     * <code>false</code> otherwise.
     */
    private static boolean checkEqual(java.security.cert.Certificate[] one,
            java.security.cert.Certificate[] two) {

        if (one == null) {
            return two == null;
        }

        if (two == null) {
            return false;
        }

        if (one.length != two.length) {
            return false;
        }

        for (int i = 0; i < one.length; i++) {
            if (one[i] == null) {
                if (two[i] != null) {
                    return false;
                }
            } else {
                if (!one[i].equals(two[i])) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Performs a test whether the <code>what</code> certificates are all
     * presented in <code>where</code> certificates.
     *
     * @param what - first array of Certificates
     * @param where  - second array of Certificates
     * @return <code>true</code> if each and every certificate from 'what'
     * (including null) is presented in 'where' <code>false</code> otherwise
     */
    private static boolean presented(Certificate[] what, Certificate[] where) {
        boolean whereHasNull = false;
        for (int i = 0; i < what.length; i++) {
            if (what[i] == null) {
                if (whereHasNull) {
                    continue;
                }
                for (int j = 0; j < where.length; j++) {
                    if (where[j] == null) {
                        whereHasNull = true;
                        break;
                    }
                }
                if (!whereHasNull) {
                    return false;
                }
            } else {
                boolean found = false;
                for (int j = 0; j < where.length; j++) {
                    if (what[i].equals(where[j])) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    return false;
                }
            }

        }
        return true;
    }

    /**
     * Tests CodeSource.getCodeSigners().
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "getCodeSigners",
        args = {}
    )
    public void testGetCodeSigners_00() {
        if (!has_15_features()) {
            return;
        }
        CodeSigner[] signers = { new CodeSigner(TestCertUtils.getCertPath(),
                null) };
        CodeSource cs = new CodeSource(null, signers);
        CodeSigner[] got = cs.getCodeSigners();
        assertNotNull(got);
        assertTrue(signers.length == got.length);
        // not sure whether they must be in the same order
        for (int i = 0; i < signers.length; i++) {
            CodeSigner s = signers[i];
            boolean found = false;
            for (int j = 0; j < got.length; j++) {
                if (got[j] == s) {
                    found = true;
                    break;
                }
            }
            assertTrue(found);
        }
    }

    /**
     * Tests CodeSource.getCodeSigners() for null.
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "getCodeSigners",
        args = {}
    )
    public void testGetCoderSignersNull() throws Exception{
        assertNull(new CodeSource(new URL("http://url"), (Certificate[])null).getCodeSigners());
    }

    /**
     * Tests CodeSource.getLocation()
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "getLocation",
        args = {}
    )
    public void testGetLocation() {
        assertTrue(new CodeSource(urlSite, (Certificate[]) null).getLocation() == urlSite);
        assertTrue(new CodeSource(urlSite, chain).getLocation() == urlSite);
        assertNull(new CodeSource(null, (Certificate[]) null).getLocation());
        assertNull(new CodeSource(null, chain).getLocation());
    }

    /**
     * Tests CodeSource.toString()
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "toString",
        args = {}
    )
    public void testToString() {
        // Javadoc keeps silence about String's format,
        // just make sure it can be invoked.
        new CodeSource(urlSite, chain).toString();
        new CodeSource(null, chain).toString();
        new CodeSource(null, (Certificate[]) null).toString();
    }

    /**
     * Tests whether we are running with the 1.5 features.<br>
     * The test is preformed by looking for (via reflection) the CodeSource's
     * constructor  {@link CodeSource#CodeSource(URL, CodeSigner[])}.
     * @return <code>true</code> if 1.5 feature is presented, <code>false</code>
     * otherwise.
     */
    private static boolean has_15_features() {
        Class klass = CodeSource.class;
        Class[] ctorArgs = { URL.class, new CodeSigner[] {}.getClass() };
        try {
            klass.getConstructor(ctorArgs);
        } catch (NoSuchMethodException ex) {
            // NoSuchMethod == Not RI.v1.5 and not DRL
            return false;
        }
        return true;
    }

    /**
     * must not imply null CodeSource
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_00() {
        CodeSource cs0 = new CodeSource(null, (Certificate[]) null);
        assertFalse(cs0.implies(null));
    }

    /**
     * CodeSource with location=null && Certificate[] == null implies any other
     * CodeSource
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_01() throws Exception {
        CodeSource thizCS = new CodeSource(urlSite, (Certificate[]) null);
        CodeSource thatCS = new CodeSource(null, (Certificate[]) null);
        assertTrue(thatCS.implies(thizCS));
        assertTrue(thatCS.implies(thatCS));

        assertFalse(thizCS.implies(thatCS));
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_02() throws Exception {
        CodeSource thizCS = new CodeSource(urlSite, (Certificate[]) null);
        CodeSource thatCS = new CodeSource(thizCS.getLocation(),
                (Certificate[]) null);
        assertTrue(thizCS.implies(thatCS));
        assertTrue(thatCS.implies(thizCS));

    }

    /*
     * FIXME
     * commented out for temporary, as there is no FTP:// protocol supported yet.
     * to be uncommented back.
     public void testImplies_03() throws Exception {
     CodeSource thizCS = new CodeSource(urlDir, (Certificate[]) null);
     CodeSource thatCS = new CodeSource(urlDirFtp, (Certificate[]) null);
     assertFalse(thizCS.implies(thatCS));
     assertFalse(thatCS.implies(thizCS));
     }
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_03_tmp() throws Exception {
        CodeSource thizCS = new CodeSource(urlDir, (Certificate[]) null);
        CodeSource thatCS = new CodeSource(urlDir_FileProtocol,
                (Certificate[]) null);
        assertFalse(thizCS.implies(thatCS));
        assertFalse(thatCS.implies(thizCS));
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_04() throws Exception {
        CodeSource thizCS = new CodeSource(urlDir, (Certificate[]) null);
        CodeSource thatCS = new CodeSource(urlDirIP, (Certificate[]) null);

        assertTrue(thizCS.implies(thatCS));
        assertTrue(thatCS.implies(thizCS));

        //
        // Check for another site - force to create SocketPermission
        //
        thatCS = new CodeSource(urlDirOtherSite, (Certificate[]) null);
        assertFalse(thizCS.implies(thatCS));

        //
        // also check for getHost() == null
        //
        thizCS = new CodeSource(new URL("http", null, "file1"),
                (Certificate[]) null);
        thatCS = new CodeSource(new URL("http", "another.host.com", "file1"),
                (Certificate[]) null);
        // well, yes, this is accordint to the spec...
        assertTrue(thizCS.implies(thatCS));
        assertFalse(thatCS.implies(thizCS));
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_05() throws Exception {
        CodeSource thizCS = new CodeSource(urlDir_port80, (Certificate[]) null);
        CodeSource thatCS = new CodeSource(urlDir, (Certificate[]) null);

        assertTrue(thizCS.implies(thatCS));
        assertTrue(thatCS.implies(thizCS));

        thizCS = new CodeSource(urlDir, (Certificate[]) null);
        thatCS = new CodeSource(urlDir_port81, (Certificate[]) null);
        //assert*True* because thizCS has 'port=-1'
        assertTrue(thizCS.implies(thatCS));

        thizCS = new CodeSource(urlDir_port81, (Certificate[]) null);
        thatCS = new CodeSource(urlDir, (Certificate[]) null);
        assertFalse(thizCS.implies(thatCS));
        //
        thizCS = new CodeSource(urlDir_port80, (Certificate[]) null);
        thatCS = new CodeSource(urlDir_port81, (Certificate[]) null);
        assertFalse(thizCS.implies(thatCS));
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_06() throws Exception {
        CodeSource thizCS = new CodeSource(urlFile, (Certificate[]) null);
        CodeSource thatCS = new CodeSource(urlFile, (Certificate[]) null);
        assertTrue(thizCS.implies(thatCS));
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_07() throws Exception {
        CodeSource thiz = new CodeSource(urlFileDirMinus, (Certificate[]) null);
        CodeSource that = new CodeSource(urlFile, (Certificate[]) null);
        assertTrue(thiz.implies(that));

        that = new CodeSource(urlFileWithAdditionalDirs, (Certificate[]) null);
        assertTrue(thiz.implies(that));

        that = new CodeSource(urlFileDirOtherDir, (Certificate[]) null);
        assertFalse(thiz.implies(that));
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_08() throws Exception {
        CodeSource thiz = new CodeSource(urlFileDirStar, (Certificate[]) null);
        CodeSource that = new CodeSource(urlFile, (Certificate[]) null);
        assertTrue(thiz.implies(that));
        that = new CodeSource(urlFileWithAdditionalDirs, (Certificate[]) null);
        assertFalse(thiz.implies(that));
        //
        that = new CodeSource(urlFileDirOtherDir, (Certificate[]) null);
        assertFalse(thiz.implies(that));
        // must not have any further '/'
        that = new CodeSource(new URL(urlFile.toString() + "/"),
                (Certificate[]) null);
        assertFalse(thiz.implies(that));
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_09() throws Exception {
        CodeSource thizCS = new CodeSource(urlDir, (Certificate[]) null);
        CodeSource thatCS = new CodeSource(urlDirWithSlash,
                (Certificate[]) null);
        assertTrue(thizCS.implies(thatCS));
        assertFalse(thatCS.implies(thizCS));
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_0A() throws Exception {
        CodeSource thizCS = new CodeSource(urlRef1, (Certificate[]) null);
        CodeSource thatCS = new CodeSource(urlRef1, (Certificate[]) null);
        assertTrue(thizCS.implies(thatCS));

        thizCS = new CodeSource(urlRef1, (Certificate[]) null);
        thatCS = new CodeSource(urlRef2, (Certificate[]) null);
        assertFalse(thizCS.implies(thatCS));

    }

    /**
     * If this certificates are not null, then all of this certificates should
     * be presented in certificates of that codesource.
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_0B() {

        Certificate c0 = new TestCertUtils.TestCertificate("00");
        Certificate c1 = new TestCertUtils.TestCertificate("01");
        Certificate c2 = new TestCertUtils.TestCertificate("02");
        Certificate[] thizCerts = { c0, c1 };
        Certificate[] thatCerts = { c1, c0, c2 };

        CodeSource thiz = new CodeSource(urlSite, thizCerts);
        CodeSource that = new CodeSource(urlSite, thatCerts);
        // two CodeSource-s with different set of certificates
        assertTrue(thiz.implies(that));

        //
        that = new CodeSource(urlSite, (Certificate[]) null);
        // 'thiz' has set of certs, while 'that' has no certs. URL-s are the
        // same.
        assertFalse(thiz.implies(that));
        assertTrue(that.implies(thiz));
    }

    /**
     * Testing with special URLs like 'localhost', 'file://' scheme ...
     * These special URLs have a special processing in implies(),
     * so they need to be covered and performance need to be checked
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_0C() throws Exception {
        URL url0 = new URL("http://localhost/someDir");
        URL url1 = new URL("http://localhost/someOtherDir");

        CodeSource thizCS = new CodeSource(url0, (Certificate[]) null);
        CodeSource thatCS = new CodeSource(url1, (Certificate[]) null);
        assertFalse(thizCS.implies(thatCS));
        assertFalse(thatCS.implies(thizCS));
    }

    /**
     * Testing with special URLs like 'localhost', 'file://' scheme ...
     * These special URLs have a special processing in implies(),
     * so they need to be covered and performance need to be checked
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "implies",
        args = {java.security.CodeSource.class}
    )
    public void testImplies_0D() throws Exception {
        URL url0 = new URL("file:///" + System.getProperty("java.io.tmpdir")
                + File.separator + "someDir");
        URL url1 = new URL("file:///" + System.getProperty("java.io.tmpdir")
                + File.separator + "someOtherDir");
        CodeSource thizCS = new CodeSource(url0, (Certificate[]) null);
        CodeSource thatCS = new CodeSource(url1, (Certificate[]) null);
        assertFalse(thizCS.implies(thatCS));
        assertFalse(thatCS.implies(thizCS));
    }
}
