/*
 * (c) COPYRIGHT 1995-1999 MIT, INRIA and Keio University. All Rights reserved.
 * W3C Intellectual Property Notice and Legal Disclaimers:
 *  http://www.w3.org/Consortium/Legal/
 *
 * HTTPURL.java
 * $Id$
 */
package org.w3c.css.util;

import org.w3c.css.servlet.CssValidator;
import org.w3c.www.mime.MimeType;
import org.w3c.www.mime.MimeTypeFormatException;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.GZIPInputStream;

/**
 * @author Philippe Le Hegaret
 * @version $Revision$
 */
public class HTTPURL {

    /**
     * Don't create this class
     */
    private HTTPURL() {
    }


    public static String getHTTPStatusCode(int status) {
        switch (status) {
            case 100:
                return "Continue";
            case 101:
                return "Switching Protocols";
            case 200:
                return "OK";
            case 201:
                return "Created";
            case 202:
                return "Accepted";
            case 203:
                return "Non-Authoritative Information";
            case 204:
                return "No Content";
            case 205:
                return "Reset Content";
            case 206:
                return "Partial Content";
            case 300:
                return "Multiple Choices";
            case 301:
                return "Moved Permanently";
            case 302:
                return "Found";
            case 303:
                return "See Other";
            case 304:
                return "Not Modified";
            case 305:
                return "Use Proxy";
            case 306:
                return "(Unused)";
            case 307:
                return "Temporary Redirect";
            case 308:
                return "Permanent Redirect";
            case 400:
                return "Bad Request";
            case 401:
                return "Unauthorized";
            case 402:
                return "Payment Required";
            case 403:
                return "Forbidden";
            case 404:
                return "Not Found";
            case 405:
                return "Method Not Allowed";
            case 406:
                return "Not Acceptable";
            case 407:
                return "Proxy Authentication Required";
            case 408:
                return "Request Timeout";
            case 409:
                return "Conflict";
            case 410:
                return "Gone";
            case 411:
                return "Length Required";
            case 412:
                return "Precondition Failed";
            case 413:
                return "Request Entity Too Large";
            case 414:
                return "Request-URI Too Long";
            case 415:
                return "Unsupported Media Type";
            case 416:
                return "Requested Range Not Satisfiable";
            case 417:
                return "Expectation Failed";
            case 500:
                return "Internal Server Error";
            case 501:
                return "Not Implemented";
            case 502:
                return "Bad Gateway";
            case 503:
                return "Service Unavailable";
            case 504:
                return "Gateway Timeout";
            case 505:
                return "HTTP Version Not Supported";
            default:
                return Integer.toString(status, 10);
        }
    }

    public static URL getURL(String url) throws IOException {
        // url = URLEncoder.encode(url);
        try {
            return new URL(url);
        } catch (MalformedURLException e) {
            //if (!url.startsWith("http:")) { // ook!? dkfj://wwww.3.org -> http://dkfj://www.w3.org
            if (url.indexOf("://") == -1) { // the protocol is missing
                return new URL("http://" + url);
            } else {
                throw (IOException) e.fillInStackTrace();
            }
        }
    }

    public static URL getURL(URL base, String url)
            throws MalformedURLException {
        //	url = URLEncoder.encode(url);
        return new URL(base, url);
    }

    private static URLConnection getConnection(URL url, int count)
            throws IOException {
        return getConnection(url, null, count, null);
    }


    private static void setSSLVerifier(HttpsURLConnection uConn) {
        TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

                    public void checkClientTrusted(
                            java.security.cert.X509Certificate[] certs, String authType) {
                    }

                    public void checkServerTrusted(
                            java.security.cert.X509Certificate[] certs, String authType) {
                    }
                }
        };

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            uConn.setSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
        }

        // Step 2: hostname verifier
        HostnameVerifier hv = new HostnameVerifier() {
            public boolean verify(String urlHostName, SSLSession session) {
                return true;
            }
        };
        uConn.setHostnameVerifier(hv);
    }

    private static URLConnection getConnection(URL url, URL referrer, int count,
                                               ApplContext ac)
            throws IOException {
        if (count > 5) {
            throw new ProtocolException("Server redirected too many " +
                    "times (5)");
        }
        // add the referrer, if not the same as the target URL
        URL ref = (url.equals(referrer) ? null : referrer);

        if (Util.servlet) {
            String protocol = url.getProtocol();
            if (!(("https".equalsIgnoreCase(protocol)) || ("http".equalsIgnoreCase(protocol)))) {
                System.err.println("[WARNING] : someone is trying to get the file: "
                        + url);
                throw new FileNotFoundException("import " + url +
                        ": Operation not permitted");
            }
            if (url.getPort() >= 0 && url.getPort() != 80 && url.getPort() != 443 && url.getPort() <= 1024) {
                System.err.println("[WARNING] : someone is trying to access a forbidden port: "
                        + url);
                throw new FileNotFoundException("import " + url +
                        ": Operation not permitted");
            }
        }

        URLConnection urlC = url.openConnection();

        if (Util.onDebug) {
            System.err.println("Accessing " + url);
            if (ac.getCredential() != null) {
                System.err.println("with [" + ac.getCredential() + ']');
            }
        }
        // avoid all kind of caches
        urlC.setRequestProperty("Pragma", "no-cache");
        urlC.setRequestProperty("Cache-Control", "no-cache, no-store");
        // for the fun
        urlC.setRequestProperty("User-Agent", CssValidator.server_name);
        // referrer
        setReferrer(urlC, ref);
        // relay authorization information
        if (ac.getCredential() != null) {
            urlC.setRequestProperty("Authorization", ac.getCredential());
        }
        // relay languages
        if (ac.getLang() != null) {
            if (ac.getLang().indexOf('*') == -1) {
                urlC.setRequestProperty("Accept-Language", ac.getLang() + ",*");
            } else {
                urlC.setRequestProperty("Accept-Language", ac.getLang());
            }
        }
        // should I put an Accept header?
        urlC.setRequestProperty("Accept",
                "text/css,text/html,text/xml,"
                        + "application/xhtml+xml,application/xml,"
                        + "image/svg+xml,*/*;q=0.1");

        if (urlC instanceof HttpURLConnection) {
            HttpURLConnection httpURL = (HttpURLConnection) urlC;
            int status;

            httpURL.setInstanceFollowRedirects(false);
            if (urlC instanceof HttpsURLConnection) {
                try {
                    urlC.connect();
                } catch (IOException ioex) {
                    setSSLVerifier((HttpsURLConnection) urlC);
                    urlC.connect();
                }
            } else {
                urlC.connect();
            }

            try {
                status = httpURL.getResponseCode();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                throw new FileNotFoundException(url + ": " +
                        getHTTPStatusCode(404));
            }

            switch (status) {
                case HttpURLConnection.HTTP_OK:
                    // nothing to do
                    break;
                case HttpURLConnection.HTTP_MOVED_PERM:
                case HttpURLConnection.HTTP_MOVED_TEMP:
                case 307:
                case 308:
                    try {
                        URL u = getURL(url, httpURL.getHeaderField("Location"));
                        return getConnection(u, ref, count + 1, ac);
                    } catch (Exception ex) {
                        // usually a NPE when Location is absent on a redirect.
                        // in any case, we will count this as non existent result.
                        throw new FileNotFoundException(url + ":" + " Error in "+status);
                    } finally {
                        httpURL.disconnect();
                    }
                case HttpURLConnection.HTTP_UNAUTHORIZED:
                    String realm = httpURL.getHeaderField("WWW-Authenticate");
                    httpURL.disconnect();
                    if (realm != null) {
                        throw new ProtocolException(realm);
                    }
                default:
                    try {
                        if (httpURL.getResponseMessage() != null) {
                            throw new FileNotFoundException(url + ": " +
                                    httpURL.getResponseMessage());
                        } else {
                            throw new FileNotFoundException(url + ": " +
                                    getHTTPStatusCode(status));
                        }
                    } finally {
                        httpURL.disconnect();
                    }
            }
        } else {
            urlC.connect();
        }
        return urlC;
    }

    public static URLConnection getConnection(URL url)
            throws IOException {
        return getConnection(url, 0);
    }

    public static URLConnection getConnection(URL url, ApplContext ac)
            throws IOException {
        return getConnection(url, ac.getReferrer(), 0, ac);
    }

    /* more madness */
    public static InputStream getInputStream(ApplContext ac, URLConnection uco)
            throws IOException {
        InputStream orig_stream = uco.getInputStream();
        String charset;
        String encoding;
        if (orig_stream == null) {
            return orig_stream; // let it fail elsewhere
        }
        encoding = uco.getContentEncoding();
        // not set -> return
        if (encoding != null) {
            if (encoding.equalsIgnoreCase("gzip")) {
                orig_stream = new GZIPInputStream(orig_stream);
            }
        }
        charset = getCharacterEncoding(ac, uco);
        if ((charset == null) || (charset.regionMatches(true, 0, "utf", 0, 3))) {
            UnicodeInputStream is = new UnicodeInputStream(orig_stream);
            charset = is.getEncodingFromStream();
            if (charset != null) {
                ac.setCharsetForURL(uco.getURL(), charset, true);
            }
            return is;
        }
        return orig_stream;
    }

    public static String getCharacterEncoding(ApplContext ac,
                                              URLConnection uco) {
        String charset = ac.getCharsetForURL(uco.getURL());
        if (charset != null) {
            return charset;
        }
        String mtypestr = uco.getContentType();
        if (mtypestr == null) {
            return mtypestr;
        }
        MimeType mt;
        try {
            mt = new MimeType(mtypestr);
        } catch (MimeTypeFormatException mex) {
            return null;
        }
        charset = mt.getParameterValue("charset");
        if (charset != null) {
            ac.setCharsetForURL(uco.getURL(), charset, false);
        }
        return charset;
    }

    // used to set referrer
    private static void setReferrer(URLConnection connection, URL referrer) {
        if (referrer == null) {
            return;
        }
        URL current = connection.getURL();
        String curProtocol = current.getProtocol();
        String refProtocol = referrer.getProtocol();
        if ("https".equalsIgnoreCase(refProtocol)) {
            if (!"https".equalsIgnoreCase(curProtocol)) {
                // exit, we won't disclose information on non-https
                // connections  (ref using https, req using http)
                return;
            }
            // ok so we have https for both, avoid leaking information
            // so check that hosts are the same
            if (!current.getHost().equalsIgnoreCase(referrer.getHost())) {
                return;
            }
        }
        // ok good, let's do it
        connection.setRequestProperty("Referer", referrer.toExternalForm());
    }

    /**
     *
     */
    public static void main(String[] args)
            throws Exception {
        int c;
        InputStream in = HTTPURL.getConnection(
                getURL(args[0])).getInputStream();

        while ((c = in.read()) != -1) {
            System.err.print((char) c);
        }
        System.exit(0);
    }
}
