/*
 *  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 javax.net.ssl;

import java.net.HttpURLConnection;
import java.net.URL;
import java.security.Principal;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;

/**
 * An {@link HttpURLConnection} for HTTPS (<a
 * href="http://tools.ietf.org/html/rfc2818">RFC 2818</a>). A
 * connected {@code HttpsURLConnection} allows access to the
 * negotiated cipher suite, the server certificate chain, and the
 * client certificate chain if any.
 *
 * <h3>Providing an application specific X509TrustManager</h3>
 *
 * If an application wants to trust Certificate Authority (CA)
 * certificates that are not part of the system, it should specify its
 * own {@code X509TrustManager} via a {@code SSLSocketFactory} set on
 * the {@code HttpsURLConnection}. The {@code X509TrustManager} can be
 * created based on a {@code KeyStore} using a {@code
 * TrustManagerFactory} to supply trusted CA certificates. Note that
 * self-signed certificates are effectively their own CA and can be
 * trusted by including them in a {@code KeyStore}.
 *
 * <p>For example, to trust a set of certificates specified by a {@code KeyStore}:
 * <pre>   {@code
 *   KeyStore keyStore = ...;
 *   String algorithm = TrustManagerFactory.getDefaultAlgorithm();
 *   TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
 *   tmf.init(keyStore);
 *
 *   SSLContext context = SSLContext.getInstance("TLS");
 *   context.init(null, tmf.getTrustManagers(), null);
 *
 *   URL url = new URL("https://www.example.com/");
 *   HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
 *   urlConnection.setSSLSocketFactory(context.getSocketFactory());
 *   InputStream in = urlConnection.getInputStream();
 * }</pre>
 *
 * <p>It is possible to implement {@code X509TrustManager} directly
 * instead of using one created by a {@code
 * TrustManagerFactory}. While this is straightforward in the insecure
 * case of allowing all certificate chains to pass verification,
 * writing a proper implementation will usually want to take advantage
 * of {@link java.security.cert.CertPathValidator
 * CertPathValidator}. In general, it might be better to write a
 * custom {@code KeyStore} implementation to pass to the {@code
 * TrustManagerFactory} than to try and write a custom {@code
 * X509TrustManager}.
 *
 * <h3>Providing an application specific X509KeyManager</h3>
 *
 * A custom {@code X509KeyManager} can be used to supply a client
 * certificate and its associated private key to authenticate a
 * connection to the server. The {@code X509KeyManager} can be created
 * based on a {@code KeyStore} using a {@code KeyManagerFactory}.
 *
 * <p>For example, to supply client certificates from a {@code KeyStore}:
 * <pre>   {@code
 *   KeyStore keyStore = ...;
 *   String algorithm = KeyManagerFactory.getDefaultAlgorithm();
 *   KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
 *   kmf.init(keyStore);
 *
 *   SSLContext context = SSLContext.getInstance("TLS");
 *   context.init(kmf.getKeyManagers(), null, null);
 *
 *   URL url = new URL("https://www.example.com/");
 *   HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
 *   urlConnection.setSSLSocketFactory(context.getSocketFactory());
 *   InputStream in = urlConnection.getInputStream();
 * }</pre>
 *
 * <p>A {@code X509KeyManager} can also be implemented directly. This
 * can allow an application to return a certificate and private key
 * from a non-{@code KeyStore} source or to specify its own logic for
 * selecting a specific credential to use when many may be present in
 * a single {@code KeyStore}.
 *
 * <h3>TLS Intolerance Support</h3>
 *
 * This class attempts to create secure connections using common TLS
 * extensions and SSL deflate compression. Should that fail, the
 * connection will be retried with SSLv3 only.
 */
public abstract class HttpsURLConnection extends HttpURLConnection {
    /*
     * Holds default instances so class preloading doesn't create an instance of
     * it.
     */
    private static class NoPreloadHolder {
        public static HostnameVerifier defaultHostnameVerifier = new DefaultHostnameVerifier();

        public static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory) SSLSocketFactory
                .getDefault();
    }

    /**
     * Sets the default hostname verifier to be used by new instances.
     *
     * @param v
     *            the new default hostname verifier
     * @throws IllegalArgumentException
     *             if the specified verifier is {@code null}.
     */
    public static void setDefaultHostnameVerifier(HostnameVerifier v) {
        if (v == null) {
            throw new IllegalArgumentException("HostnameVerifier is null");
        }
        NoPreloadHolder.defaultHostnameVerifier = v;
    }

    /**
     * Returns the default hostname verifier.
     *
     * @return the default hostname verifier.
     */
    public static HostnameVerifier getDefaultHostnameVerifier() {
        return NoPreloadHolder.defaultHostnameVerifier;
    }

    /**
     * Sets the default SSL socket factory to be used by new instances.
     *
     * @param sf
     *            the new default SSL socket factory.
     * @throws IllegalArgumentException
     *             if the specified socket factory is {@code null}.
     */
    public static void setDefaultSSLSocketFactory(SSLSocketFactory sf) {
        if (sf == null) {
            throw new IllegalArgumentException("SSLSocketFactory is null");
        }
        NoPreloadHolder.defaultSSLSocketFactory = sf;
    }

    /**
     * Returns the default SSL socket factory for new instances.
     *
     * @return the default SSL socket factory for new instances.
     */
    public static SSLSocketFactory getDefaultSSLSocketFactory() {
        return NoPreloadHolder.defaultSSLSocketFactory;
    }

    /**
     * The host name verifier used by this connection. It is initialized from
     * the default hostname verifier
     * {@link #setDefaultHostnameVerifier(HostnameVerifier)} or
     * {@link #getDefaultHostnameVerifier()}.
     */
    protected HostnameVerifier hostnameVerifier;

    private SSLSocketFactory sslSocketFactory;

    /**
     * Creates a new {@code HttpsURLConnection} with the specified {@code URL}.
     *
     * @param url
     *            the {@code URL} to connect to.
     */
    protected HttpsURLConnection(URL url) {
        super(url);
        hostnameVerifier = NoPreloadHolder.defaultHostnameVerifier;
        sslSocketFactory = NoPreloadHolder.defaultSSLSocketFactory;
    }

    /**
     * Returns the name of the cipher suite negotiated during the SSL handshake.
     *
     * @return the name of the cipher suite negotiated during the SSL handshake.
     * @throws IllegalStateException
     *             if no connection has been established yet.
     */
    public abstract String getCipherSuite();

    /**
     * Returns the list of local certificates used during the handshake. These
     * certificates were sent to the peer.
     *
     * @return Returns the list of certificates used during the handshake with
     *         the local identity certificate followed by CAs, or {@code null}
     *         if no certificates were used during the handshake.
     * @throws IllegalStateException
     *             if no connection has been established yet.
     */
    public abstract Certificate[] getLocalCertificates();

    /**
     * Return the list of certificates identifying the peer during the
     * handshake.
     *
     * @return the list of certificates identifying the peer with the peer's
     *         identity certificate followed by CAs.
     * @throws SSLPeerUnverifiedException
     *             if the identity of the peer has not been verified..
     * @throws IllegalStateException
     *             if no connection has been established yet.
     */
    public abstract Certificate[] getServerCertificates() throws SSLPeerUnverifiedException;

    /**
     * Returns the {@code Principal} identifying the peer.
     *
     * @return the {@code Principal} identifying the peer.
     * @throws SSLPeerUnverifiedException
     *             if the identity of the peer has not been verified.
     * @throws IllegalStateException
     *             if no connection has been established yet.
     */
    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
        Certificate[] certs = getServerCertificates();
        if (certs == null || certs.length == 0 || (!(certs[0] instanceof X509Certificate))) {
            throw new SSLPeerUnverifiedException("No server's end-entity certificate");
        }
        return ((X509Certificate) certs[0]).getSubjectX500Principal();
    }

    /**
     * Returns the {@code Principal} used to identify the local host during the handshake.
     *
     * @return the {@code Principal} used to identify the local host during the handshake, or
     *         {@code null} if none was used.
     * @throws IllegalStateException
     *             if no connection has been established yet.
     */
    public Principal getLocalPrincipal() {
        Certificate[] certs = getLocalCertificates();
        if (certs == null || certs.length == 0 || (!(certs[0] instanceof X509Certificate))) {
            return null;
        }
        return ((X509Certificate) certs[0]).getSubjectX500Principal();
    }

    /**
     * Sets the hostname verifier for this instance.
     *
     * @param v
     *            the hostname verifier for this instance.
     * @throws IllegalArgumentException
     *             if the specified verifier is {@code null}.
     */
    public void setHostnameVerifier(HostnameVerifier v) {
        if (v == null) {
            throw new IllegalArgumentException("HostnameVerifier is null");
        }
        hostnameVerifier = v;
    }

    /**
     * Returns the hostname verifier used by this instance.
     *
     * @return the hostname verifier used by this instance.
     */
    public HostnameVerifier getHostnameVerifier() {
        return hostnameVerifier;
    }

    /**
     * Sets the SSL socket factory for this instance.
     *
     * @param sf
     *            the SSL socket factory to be used by this instance.
     * @throws IllegalArgumentException
     *             if the specified socket factory is {@code null}.
     */
    public void setSSLSocketFactory(SSLSocketFactory sf) {
        if (sf == null) {
            throw new IllegalArgumentException("SSLSocketFactory is null");
        }
        sslSocketFactory = sf;
    }

    /**
     * Returns the SSL socket factory used by this instance.
     *
     * @return the SSL socket factory used by this instance.
     */
    public SSLSocketFactory getSSLSocketFactory() {
        return sslSocketFactory;
    }

}
