/*
 *
 * Paros and its related class files.
 * 
 * Paros is an HTTP/HTTPS proxy for assessing web application security.
 * Copyright (C) 2003-2004 Chinotec Technologies Company
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Clarified Artistic License
 * as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Clarified Artistic License for more details.
 * 
 * You should have received a copy of the Clarified Artistic License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.parosproxy.paros.network;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;


/**
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
public class HttpSender {

    static {
	    ProtocolSocketFactory sslFactory = null;
	    try {
	        Protocol protocol = Protocol.getProtocol("https");
	        sslFactory = protocol.getSocketFactory();
	    } catch (Exception e) {}
	    // avoid init again if already initialized
	    if (sslFactory == null || !(sslFactory instanceof SSLConnector)) {
	        Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory) new SSLConnector(), 443));
	    }
    }
    
    private static HttpMethodHelper helper = new HttpMethodHelper();
    private static String userAgent = "";
    
    private HttpClient client = null;
    private HttpClient clientViaProxy = null;
    private ConnectionParam param = null;
    MultiThreadedHttpConnectionManager httpConnManager = null;
    MultiThreadedHttpConnectionManager httpConnManagerProxy = null;
    private boolean followRedirect = false;
    
    public HttpSender(ConnectionParam connectionParam) {
        this.param = connectionParam;
        client = createHttpClient();
        clientViaProxy = createHttpClientViaProxy();
        
        addAuth(client);
        addAuth(clientViaProxy);
    }
    
    private HttpClient createHttpClient() {
        
        httpConnManager = new MultiThreadedHttpConnectionManager(); 
        setCommonManagerParams(httpConnManager);
        return new HttpClient(httpConnManager);   
    }
    
    private HttpClient createHttpClientViaProxy() { 
    
        if (param.getProxyChainName().equals("") ) {
            return createHttpClient();
        }
        
        httpConnManagerProxy = new MultiThreadedHttpConnectionManager();
        setCommonManagerParams(httpConnManagerProxy);
        HttpClient clientProxy = new HttpClient(httpConnManagerProxy);
    	clientProxy.getHostConfiguration().setProxy(param.getProxyChainName(), param.getProxyChainPort());
		
    	if (!param.getProxyChainUserName().equals("")) {
    	    
    	    //NTCredentials credentials = new NTCredentials(
    	    //        param.getProxyChainUserName(), param.getProxyChainPassword(), param.getProxyChainName(), param.getProxyChainName());
    	    UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
    	            param.getProxyChainUserName(), param.getProxyChainPassword());
    	    AuthScope authScope = new AuthScope(param.getProxyChainName(), param.getProxyChainPort(), param.getProxyChainRealm().equals("") ? AuthScope.ANY_REALM : param.getProxyChainRealm());
            
    	    clientProxy.getState().setProxyCredentials(authScope,credentials);
    	}
    	
    	return clientProxy;
    }

    public int executeMethod(HttpMethod method) throws HttpException, IOException {
        String hostName;
        hostName = method.getURI().getHost();
        method.setDoAuthentication(true);
        if (param.isUseProxy(hostName)) {
            return clientViaProxy.executeMethod(method);
        } else {
            return client.executeMethod(method);
        }
    }
    
    public void shutdown() {
        if (httpConnManager != null) {
            httpConnManager.shutdown();
        }
        if (httpConnManagerProxy != null) {
            httpConnManagerProxy.shutdown();
        }
    }
    
    private void addAuth(HttpClient client) {
        List list = param.getListAuth();
        for (int i=0; i<list.size(); i++) {
            HostAuthentication auth = (HostAuthentication) list.get(i);
            AuthScope authScope = new AuthScope(auth.getHostName(), auth.getPort(), auth.getRealm().equals("") ? AuthScope.ANY_REALM : auth.getRealm());
            NTCredentials credentials= null;
            try {
                credentials = new NTCredentials(auth.getUserName(), auth.getPassword(), InetAddress.getLocalHost().getCanonicalHostName(), auth.getHostName());
                client.getState().setCredentials(authScope, credentials);
            } catch (UnknownHostException e1) {
                e1.printStackTrace();
            }
            //client.getState().setCredentials(
            //        new HttpAuthRealm(auth.getHostName(), auth.getPort(), (auth.getRealm().equals("") ? null : auth.getRealm())),
            //        new UsernamePasswordCredentials(auth.getUserName(), auth.getPassword())
            //        );
        }
        
        /*List authPrefs = new Vector(3);
        authPrefs.add(AuthPolicy.NTLM);
        authPrefs.add(AuthPolicy.BASIC);
        authPrefs.add(AuthPolicy.DIGEST);
        client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
        */
    }

    public void sendAndReceive(HttpMessage msg) throws HttpException, IOException {
        sendAndReceive(msg, followRedirect);
        
    }
    
    /**
     * Send and receive a HttpMessage.  
     * @param msg
     * @param isFollowRedirect
     * @throws HttpException
     * @throws IOException
     */
    public void sendAndReceive(HttpMessage msg, boolean isFollowRedirect) throws HttpException, IOException {

        msg.setTimeSentMillis(System.currentTimeMillis());

        try {
            if (!isFollowRedirect || !
                    (msg.getRequestHeader().getMethod().equalsIgnoreCase(HttpRequestHeader.POST)
                            || msg.getRequestHeader().getMethod().equalsIgnoreCase(HttpRequestHeader.PUT))
            ) {
                send(msg, isFollowRedirect);
                return;
            } else {
                send(msg, false);
            }
            
            HttpMessage temp = msg.cloneAll();
            // POST/PUT method cannot be redirected by library. Need to follow by code
            
            // loop 1 time only because httpclient can handle redirect itself after first GET.
            for (int i=0; i<1
            && (HttpStatusCode.isRedirection(temp.getResponseHeader().getStatusCode())
                    && temp.getResponseHeader().getStatusCode() != HttpStatusCode.NOT_MODIFIED); i++) {
                String location = temp.getResponseHeader().getHeader(HttpHeader.LOCATION);
                URI baseUri = temp.getRequestHeader().getURI();
                URI newLocation = new URI(baseUri, location, false);
                temp.getRequestHeader().setURI(newLocation);
                
                temp.getRequestHeader().setMethod(HttpRequestHeader.GET);
                temp.getRequestHeader().setContentLength(0);
                send(temp, true);
            }
            
            msg.setResponseHeader(temp.getResponseHeader());
            msg.setResponseBody(temp.getResponseBody());

        } finally {
            msg.setTimeElapsedMillis((int) (System.currentTimeMillis()-msg.getTimeSentMillis()));
        }
    }
    
    private void send(HttpMessage msg, boolean isFollowRedirect) throws HttpException, IOException {
        HttpMethod method = null;
        HttpResponseHeader resHeader = null;
        
        try {
            method = runMethod(msg, isFollowRedirect);
	        // successfully executed;
	        resHeader = HttpMethodHelper.getHttpResponseHeader(method);
	        resHeader.setHeader(HttpHeader.TRANSFER_ENCODING, null);	//	replaceAll("Transfer-Encoding: chunked\r\n", "");
	        msg.setResponseHeader(resHeader);
	        msg.getResponseBody().setCharset(resHeader.getCharset());
	        // process response for each listner
	        msg.getResponseBody().setLength(0);
	        msg.getResponseBody().append(method.getResponseBody());
        } finally {
	        if (method != null) {
	            method.releaseConnection();
	        }
        }

        
    }
    
	private HttpMethod runMethod(HttpMessage msg, boolean isFollowRedirect) throws HttpException, IOException {
	    int status = -1;
		HttpMethod method = null;
		// no more retry
		modifyUserAgent(msg);
        method = helper.createRequestMethod(msg.getRequestHeader(), msg.getRequestBody());
        method.setFollowRedirects(isFollowRedirect);
        status = this.executeMethod(method);

        return method;
	}
	
	public void setFollowRedirect(boolean followRedirect) {
	    this.followRedirect = followRedirect;
	}
	    
	private void modifyUserAgent(HttpMessage msg) {

	    try {
	        // no modification to user agent if empty
	        if (userAgent.equals("") || msg.getRequestHeader().isEmpty()) {
	            return;
	        }
	        
	        // append new user agent to existing user agent
	        String currentUserAgent = msg.getRequestHeader().getHeader(HttpHeader.USER_AGENT);
	        if (currentUserAgent == null) {
	            currentUserAgent = "";
	        }
	        
	        String delimiter = "";
	        if (!currentUserAgent.equals("") && !currentUserAgent.endsWith(" ")) {
	            delimiter = " ";
	        }
	        
	        currentUserAgent = currentUserAgent + delimiter + userAgent;
	        msg.getRequestHeader().setHeader(HttpHeader.USER_AGENT, currentUserAgent);
	    } catch (Exception e) {}
	}

    /**
     * @return Returns the userAgent.
     */
    public static String getUserAgent() {
        return userAgent;
    }
    
    /**
     * @param userAgent The userAgent to set.
     */
    public static void setUserAgent(String userAgent) {
        HttpSender.userAgent = userAgent;
    }
    
    private void setCommonManagerParams(MultiThreadedHttpConnectionManager mgr) {
        mgr.getParams().setSoTimeout(40000);
        
        //mgr.getParams().setDefaultMaxConnectionsPerHost((Constant.MAX_HOST_CONNECTION > 5) ? 5: Constant.MAX_HOST_CONNECTION);
        //mgr.getParams().setConnectionTimeout(60000);  // use default

    }
   
    
}
