package org.apache.maven.util;

/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *    "Apache Maven" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache Maven", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * ====================================================================
 */

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;

import java.net.URL;
import java.net.URLConnection;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;

import java.util.Date;

/**
 * Http utils for retrieving files.
 *
 * @author costin@dnt.ro
 * @author gg@grtmail.com (Added Java 1.1 style HTTP basic auth)
 * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
 */
public class HttpUtils
{
    /**
     * Use a proxy to bypass the firewall with or without authentication
     *
     * @param proxyHost Proxy Host (if proxy is required), or null
     * @param proxyPort Proxy Port (if proxy is required), or null
     * @param proxyUserName Proxy Username (if authentification is required), or null
     * @param proxyPassword Proxy Password (if authentification is required), or null
     */
    public static void useProxyUser(final String proxyHost, final String proxyPort,
        final String proxyUserName, final String proxyPassword)
    {
        if (proxyHost != null && proxyPort != null)
        {
            System.getProperties().put("proxySet", "true");
            System.getProperties().put("proxyHost", proxyHost);
            System.getProperties().put("proxyPort", proxyPort);
            
            if (proxyUserName != null)
            {
                Authenticator.setDefault(new Authenticator() {
                    protected PasswordAuthentication getPasswordAuthentication() {
                        return new PasswordAuthentication(proxyUserName,
                            proxyPassword == null ? new char[0] : proxyPassword.toCharArray());
                    }
                });
            }
        }
    }
    
    /**
     * Retrieve a remote file.  Returns true if the file was successfully
     * retrieved or if it is up to date (when the useTimestamp flag is set).
     * @param source the {@link URL} of the file to retrieve
     * @param destinationFile where to store it
     * @param file <strong>doesn't appear to be used</strong>
     * @param verbose the amount of logging to be displayed
     * @param ignoreErrors whether to ignore errors during I/O or throw an
     *      exception when they happen
     * @param useTimestamp whether to check the modified timestamp on the
     *      <code>destinationFile</code> against the remote <code>source</code>
     * @param uname the user name to use for basic authentication
     * @param pword the password to use for basic authentication
     * @return true if the retrieval succeeded, false otherwise
     */
    public static boolean getFile(URL source, File destinationFile, String file,
        boolean verbose, boolean ignoreErrors, boolean useTimestamp, 
        String uname, String pword,
        String proxyHost, String proxyPort, String proxyUserName, String proxyPassword)
    {
        boolean retrievedFile = false;
        try
        {
            logx("Getting: " + source);

            //set the timestamp to the file date.
            long timestamp = 0;
            boolean hasTimestamp = false;
            if (useTimestamp && destinationFile.exists())
            {
                timestamp = destinationFile.lastModified();
                if (verbose)
                {
                    Date t = new Date(timestamp);
                    logx("local file date : " + t.toString());
                }

                hasTimestamp = true;
            }
            
            //set proxy connection
            useProxyUser(proxyHost, proxyPort, proxyUserName, proxyPassword);

            //set up the URL connection
            URLConnection connection = source.openConnection();
            //modify the headers
            //NB: things like user authentication could go in here too.
            if (useTimestamp && hasTimestamp)
            {
                connection.setIfModifiedSince(timestamp);
            }
            // prepare Java 1.1 style credentials
            if (uname != null || pword != null)
            {
                String up = uname + ":" + pword;
                String encoding = null;
                // check to see if sun's Base64 encoder is available.
                try
                {
                    sun.misc.BASE64Encoder encoder =
                        (sun.misc.BASE64Encoder) Class.forName(
                        "sun.misc.BASE64Encoder").newInstance();

                    encoding = encoder.encode(up.getBytes());
                }
                catch (Exception ex)
                {
                    // Do nothing, as for Maven we will never use
                    // auth and we will eventually move over httpclient
                    // in the commons.
                }
                connection.setRequestProperty("Authorization", "Basic " + 
                    encoding);
            }

            //connect to the remote site (may take some time)
            connection.connect();
            //next test for a 304 result (HTTP only)
            if (connection instanceof HttpURLConnection)
            {
                HttpURLConnection httpConnection = (HttpURLConnection) 
                    connection;
                if (httpConnection.getResponseCode() == 
                    HttpURLConnection.HTTP_NOT_MODIFIED)
                {
                    //not modified so no file download. just return instead
                    //and trace out something so the user doesn't think that the
                    //download happened when it didnt
                    logx("Not modified - so not downloaded");
                    return true;
                }
                // test for 401 result (HTTP only)
                if (httpConnection.getResponseCode() == 
                    HttpURLConnection.HTTP_UNAUTHORIZED)
                {
                    logx("Not authorized - check " + destinationFile + 
                        " for details");
                    return false;
                }
            }

            // REVISIT: at this point even non HTTP connections may support the 
            // if-modified-since behaviour - we just check the date of the 
            // content and skip the write if it is not newer. 
            // Some protocols (FTP) dont include dates, of course.
         
            InputStream is = null;
            for (int i = 0; i < 3; i++)
            {
                try
                {
                    is = connection.getInputStream();
                    break;
                }
                catch (IOException ex)
                {
                    logx("Error opening connection " + ex);
                }
            }
            if (is == null)
            {
                logx("Can't get " + file + " to " + destinationFile);
                if (ignoreErrors)
                {
                    return false;
                }
                throw new Exception(
                    "Can't get " + file + " to " + destinationFile);
            }

            FileOutputStream fos = new FileOutputStream(destinationFile);
            logx("Writing " + destinationFile);

            byte[] buffer = new byte[100 * 1024];
            int length;

            while ((length = is.read(buffer)) >= 0)
            {
                fos.write(buffer, 0, length);
                System.out.print(".");
            }
            
            System.out.println();
            fos.close();
            is.close();

            // if (and only if) the use file time option is set, then the
            // saved file now has its timestamp set to that of the downloaded 
            // file
            if (useTimestamp)
            {
                long remoteTimestamp = connection.getLastModified();
                if (verbose)
                {
                    Date t = new Date(remoteTimestamp);
                    logx("last modified = " + t.toString() +
                        ((remoteTimestamp == 0) ? 
                            " - using current time instead" : ""));
                }
                if (remoteTimestamp != 0)
                {
                    touchFile(destinationFile, remoteTimestamp);
                }
            }

            retrievedFile = true;
        }
        catch (FileNotFoundException fnfe)
        {
            logx("Error getting " + source + " ; it does not exist.");
        }
        catch (Exception e)
        {
            e.printStackTrace();
            logx("Error getting " + file + " to " + destinationFile);
        }

        return retrievedFile;
    }


    /**
     * Retrieve a remote file.  Returns true if the file was successfully
     * retrieved or if it is up to date (when the useTimestamp flag is set).
     * @param source the {@link URL} of the file to retrieve
     * @param destinationFile where to store it
     * @param file <strong>doesn't appear to be used</strong>
     * @param verbose the amount of logging to be displayed
     * @param ignoreErrors whether to ignore errors during I/O or throw an
     *      exception when they happen
     * @param useTimestamp whether to check the modified timestamp on the
     *      <code>destinationFile</code> against the remote <code>source</code>
     * @param uname the user name to use for basic authentication
     * @param pword the password to use for basic authentication
     * @return true if the retrieval succeeded, false otherwise
     * @deprecated 
     * @see #getFile(url,file,string,boolean,boolean,boolean,string,string,string,string,string,string)
     */
    public static boolean getFile(URL source, File destinationFile, String file,
        boolean verbose, boolean ignoreErrors, boolean useTimestamp, 
        String uname, String pword)
    {
        return getFile(source, destinationFile, file,
            verbose, ignoreErrors, useTimestamp, 
            uname, pword, null, null, null, null);
    }
        
    /**
     * set the timestamp of a named file to a specified time.
     *
     * @param file the file to touch
     * @param timemillis in milliseconds since the start of the era
     * @return true if it succeeded. False means that this is a java1.1 system
     *      and that file times can not be set
     * @exception Exception Thrown in unrecoverable error. Likely this
     *      comes from file access failures.
     */
    private static boolean touchFile(File file, long timemillis)
        throws Exception
    {
        long modifiedTime;

        if (timemillis < 0)
        {
            modifiedTime = System.currentTimeMillis();
        }
        else
        {
            modifiedTime = timemillis;
        }

        file.setLastModified(modifiedTime);
        return true;
    }

    /** We need to move to the commons-logging goodies here.
     * Log a message to System.out
     * @param message the message to log
     */
    private static void logx(String message)
    {
        System.out.println(message);
    }
}
