/******************************************************************************
 libprozilla - a download accelerator library
 Copyright (C) 2001 Kalum Somaratna

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 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
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
******************************************************************************/

/* $Id: http-retr.c,v 1.20 2005/03/31 20:10:57 sean Exp $ */

#include "common.h"
#include "prozilla.h"
#include "connect.h"
#include "misc.h"
#include "url.h"
#include "netrc.h"
#include "debug.h"
#include "http.h"
#include "http-retr.h"

/* Will download a portion of/or the full file from the connection->url.
 */
uerr_t proz_http_get_file(connection_t * connection)
{
  uerr_t err;
  int remote_port_len;
  char *user, *passwd, *www_auth = NULL, *proxy_auth = NULL, *range =
      NULL, *location = NULL, *referer = NULL, *pragma_no_cache = NULL;
  char *request, *remote_port;
  netrc_entry *netrc_ent;
  char buffer[HTTP_BUFFER_SIZE];
  /*The http stats that were returned after the call with GET */
  http_stat_t hs_after_get;

  /* we want it to terminate immediately */
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

  assert(connection->localfile != NULL);
  assert(connection->file_mode != NULL);

  /*clear the socks */
  connection->data_sock = 0;
  memset(&hs_after_get, 0, sizeof(hs_after_get));

  /* if there is nothing to download then return  */
  if (connection->status == COMPLETED)
  {
    pthread_mutex_lock(&connection->access_mutex);
    gettimeofday(&connection->time_begin, NULL);
    pthread_mutex_unlock(&connection->access_mutex);
    return HOK;
  }

  connection_change_status(connection, CONNECTING);

  if (http_use_proxy(connection))
  {
    connection_show_message(connection, _("Connecting to %s"),
			    connection->http_proxy->proxy_url.host);
    err = connect_to_server(&connection->data_sock,
			    connection->http_proxy->proxy_url.host,
			    connection->http_proxy->proxy_url.port,
			    &connection->xfer_timeout);
    if (err != NOCONERROR)
    {
      proz_debug(_("Error connecting to %s"),
		 connection->http_proxy->proxy_url.host);
      connection_change_status(connection, REMOTEFATAL);
      return err;
    }
  } else
  {
    connection_show_message(connection, _("Connecting to %s"),
			    connection->u.host);

    err = connect_to_server(&connection->data_sock, connection->u.host,
			    connection->u.port, &connection->xfer_timeout);
    if (err != NOCONERROR)
    {
      proz_debug(_("Error connecting to %s"), connection->u.host);
      connection_change_status(connection, REMOTEFATAL);
      return err;
    }
  }

  user = connection->u.user;
  passwd = connection->u.passwd;

  /* Use .netrc if asked to do so. */
  if (connection->use_netrc == TRUE)
  {
    netrc_ent = search_netrc(libprozrtinfo.netrc_list, connection->u.host);

    if (netrc_ent != NULL)
    {
      user = netrc_ent->account;
      passwd = netrc_ent->password;
    }
  }

  user = user ? user : "";
  passwd = passwd ? passwd : "";

  if (strlen(user) || strlen(passwd))
  {
    /* Construct the necessary header. */
    www_auth = get_basic_auth_str(user, passwd, "Authorization");
    proz_debug(_("Authenticating as user %s password %s"), user, passwd);
    proz_debug(_("Authentification string=%s"), www_auth);
  } else
    www_auth = 0;

  if (http_use_proxy(connection))
  {
    if (strlen(connection->http_proxy->username)
	|| strlen(connection->http_proxy->passwd))
      proxy_auth =
	  get_basic_auth_str(connection->http_proxy->username,
			     connection->http_proxy->passwd,
			     "Proxy-Authorization");
  }

  if (connection->u.port == 80)
  {
    remote_port = NULL;
    remote_port_len = 0;
  } else
  {
    remote_port = (char *) alloca(64);
    remote_port_len = sprintf(remote_port, ":%d", connection->u.port);
  }

  if (connection->hs.accept_ranges == 1)
  {
    range = (char *) alloca(18 + 64);
    sprintf(range, "Range: bytes=%lld-\r\n", connection->remote_startpos);
	proz_debug("Range = %lld Range = %s",connection->remote_startpos, range);
  }

  if (connection->u.referer)
  {
    referer = (char *) alloca(13 + strlen(connection->u.referer));
    sprintf(referer, "Referer: %s\r\n", connection->u.referer);
  }

  /* If we go through a proxy the request for the URL is different */
  if (http_use_proxy(connection))
  {
    location = (char *) alloca(strlen(connection->u.url) + 1);
    strcpy(location, connection->u.url);
  } else
  {
    location = (char *) alloca(strlen(connection->u.path) + 1);
    strcpy(location, connection->u.path);
  }

  /*Use no-cache directive for proxy servers? */
  if (http_use_proxy(connection)
      && (connection->http_no_cache || connection->attempts > 0))
  {
    pragma_no_cache = (char *) alloca(21);
    sprintf(pragma_no_cache, "Pragma: no-cache\r\n");
  }

  request = (char *) alloca(strlen(location)
			    + strlen(connection->user_agent)
			    + strlen(connection->u.host) + remote_port_len
			    + (range ? strlen(range) : 0)
			    + (referer ? strlen(referer) : 0)
			    + (www_auth ? strlen(www_auth) : 0)
			    + (proxy_auth ? strlen(proxy_auth) : 0)
			    + 64
			    +
			    (pragma_no_cache ? strlen(pragma_no_cache) :
			     0));

  /* TODO Add referrer tag. */
  sprintf(request,
	  "GET %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s%s\r\nAccept: */*\r\n%s%s%s%s%s\r\n",
	  location, connection->user_agent, connection->u.host,
	  remote_port ? remote_port : "", range ? range : "",
	  referer ? referer : "",
	  www_auth ? www_auth : "", proxy_auth ? proxy_auth : "",
	  pragma_no_cache ? pragma_no_cache : "");

  proz_debug("2 HTTP request = %s", request);

  connection_show_message(connection, _("Sending HTTP request"));
  err = http_fetch_headers(connection, &hs_after_get, request);

/* What hapenned ? */
  if (err != HOK)
  {
    proz_debug("2 http_fetch_headers err != HOK %d", err);
    /*Check if we authenticated using any user or password and if we 
       were kicked out, if so return HAUTHFAIL */
    if (err == HAUTHREQ && (strlen(user) || strlen(passwd)))
      err = HAUTHFAIL;
    /*
     * a error occured druing the process 
     */
    close_sock(&connection->data_sock);
    connection_change_status(connection, REMOTEFATAL);
    return err;
  }

  /*Check for the server lying about it being able to handle ranges */
  if (hs_after_get.contlen != -1)
  {
    if (connection->resume_support == TRUE)
    {
      if (hs_after_get.contlen !=
	  connection->main_file_size - connection->remote_startpos)
      {
	proz_debug("Error contlen does not match the requested range!");
	close_sock(&connection->data_sock);
	connection_change_status(connection, REMOTEFATAL);
	return CANTRESUME;
      }
    }
  }

/* which routine to call */
  if (connection->main_file_size == -1)
    err =
	connection_retr_fsize_not_known(connection, buffer,
					sizeof(buffer));
  else
    err = connection_retr_fsize_known(connection, buffer, sizeof(buffer));

  close_sock(&connection->data_sock);

  if (err == FILEGETOK)
    return HOK;
  else
  {
	  proz_debug("err != FILEGETOK %d", err);
	  return err;
  }
}



/* A genuine loop ;) It willed be called by the main thread, and 
this will handle all possible errors itself, retrying until the number 
of maximum tries for the  connection is realised
*/

uerr_t http_loop(connection_t * connection)
{
  boolean retrying_from_loop = FALSE;
  assert(connection->max_attempts >= 0);
  assert(connection->attempts >= 0);

  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

  do
  {
    if (connection->attempts > 0)
    {

      if (retrying_from_loop == TRUE)
      {
	connection_show_message(connection,
				_("Retrying...Attempt %d in %d seconds"),
				connection->attempts,
				connection->retry_delay.tv_sec);
	delay_ms(connection->retry_delay.tv_sec * 1000);
      }

      if (connection->resume_support == TRUE)
      {
	if (connection_load_resume_info(connection) == -1)
	{
	  connection_show_message(connection,
				  _
				  ("Error while attemting to process download file "));
	}
      } else
      {
	/*If we cant resume then reset the connections bytesreceived to 0 */
	connection->remote_bytes_received = 0;
      }
    }

    /*Push the handler which will cleanup any sockets that are left open */
    pthread_cleanup_push(cleanup_socks, (void *) connection);
    connection->err = proz_http_get_file(connection);
    /*pop the handler */
    pthread_cleanup_pop(0);

    connection->attempts++;

    /*Should the error be handled at this level ? */
    if (!http_loop_handle_error(connection->err))
    {
      connection_show_message(connection, _("Will be handled in main "));
      return connection->err;	/*If not return and the main thread will handle it */
    }

    switch (connection->err)
    {
    case HOK:
      connection_show_message(connection, _("Successfully got download"));
      return connection->err;
      break;

      /*TODO : What should we do if the file is not found, well pass it up to the main routine */

    default:
      connection_show_message(connection, proz_strerror(connection->err));
      break;
    }

    retrying_from_loop = TRUE;

  }
  while ((connection->attempts < connection->max_attempts)
	 || connection->max_attempts == 0);


  connection_show_message(connection,
			  _
			  ("I have tried %d attempt(s) and have failed, aborting"),
			  connection->attempts);

  return connection->err;

}


/*Return true if it is a error which can be handled within the htp_loop,
or false if it should be passed upwards so that the main download thread can 
restart it when necessary after processing other threads status too */
boolean http_loop_handle_error(uerr_t err)
{
  proz_debug("Error encountered in http_loop is %d", err);
  if (err == HTTPNSFOD || err == FWRITEERR || err == FOPENERR
      || err == CANTRESUME)
    return FALSE;
  else
    return TRUE;
}


/*
  I am writing a seperate function to handle FTP proxying through HTTP, I
  MHO whoever thought of using HTTP to proxy FTP is a shithead, 
  its such a PITA ;)
 */
uerr_t ftp_get_file_from_http_proxy(connection_t * connection)
{

  uerr_t err;
  int remote_port_len;
  char *user, *passwd, *www_auth = NULL, *proxy_auth = NULL, *range =
      NULL, *pragma_no_cache = NULL;

  char *request, *remote_port;
  netrc_entry *netrc_ent;
  char buffer[HTTP_BUFFER_SIZE];
  /*The http stats that were returned after the call with GET */
  http_stat_t hs_after_get;

  /* we want it to terminate immediately */
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

  memset(&connection->hs, 0, sizeof(connection->hs));
  memset(&hs_after_get, 0, sizeof(hs_after_get));

  /* if there is nothing to download then return  */
  if (connection->status == COMPLETED)
  {
    pthread_mutex_lock(&connection->access_mutex);
    gettimeofday(&connection->time_begin, NULL);
    pthread_mutex_unlock(&connection->access_mutex);
    return FTPOK;
  }

  err = connect_to_server(&connection->data_sock,
			  connection->ftp_proxy->proxy_url.host,
			  connection->ftp_proxy->proxy_url.port,
			  &connection->xfer_timeout);

  if (err != NOCONERROR)
  {
    connection_show_message(connection, _("Error connecting to %s"),
			    connection->ftp_proxy->proxy_url.host);
    return err;
  }

  user = connection->u.user;
  passwd = connection->u.passwd;

  /* Use .netrc if asked to do so. */
  if (connection->use_netrc == TRUE)
  {
    netrc_ent = search_netrc(libprozrtinfo.netrc_list, connection->u.host);

    if (netrc_ent != NULL)
    {
      user = netrc_ent->account;
      passwd = netrc_ent->password;
    }
  }

  user = user ? user : "";
  passwd = passwd ? passwd : "";

  if (strlen(user) || strlen(passwd))
  {
    /* Construct the necessary header. */
    www_auth = get_basic_auth_str(user, passwd, "Authorization");
    proz_debug(_("Authenticating as user %s password %s"), user, passwd);
    proz_debug(_("Authentification string=%s"), www_auth);
  } else
    www_auth = 0;

  if (strlen(connection->ftp_proxy->username)
      || strlen(connection->ftp_proxy->passwd))
    proxy_auth =
	get_basic_auth_str(connection->ftp_proxy->username,
			   connection->ftp_proxy->passwd,
			   "Proxy-Authorization");

  remote_port = (char *) alloca(64);
  remote_port_len = sprintf(remote_port, ":%d", connection->u.port);

  if (connection->hs.accept_ranges == 1)
  {
    range = (char *) alloca(18 + 64);
    sprintf(range, "Range: bytes=%lld-\r\n", connection->remote_startpos);
  }


  /*Use no-cache directive for proxy servers? */
  if (http_use_proxy(connection)
      && (connection->http_no_cache || connection->attempts > 0))
  {
    pragma_no_cache = (char *) alloca(21);
    sprintf(pragma_no_cache, "Pragma: no-cache\r\n");
  }


  /*Referrer TAG should not be needed in FTP through HTTP proxy..right */


  request = (char *) alloca(strlen(connection->u.url)
			    + strlen(connection->user_agent)
			    + strlen(connection->u.host) + remote_port_len
			    + (range ? strlen(range) : 0)
			    + (www_auth ? strlen(www_auth) : 0)
			    + (proxy_auth ? strlen(proxy_auth) : 0)
			    + 64
			    +
			    (pragma_no_cache ? strlen(pragma_no_cache) :
			     0));


  /* TODO Add referrer tag. */
  sprintf(request,
	  "GET %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s%s\r\nAccept: */*\r\n%s%s%s%s\r\n",
	  connection->u.url, connection->user_agent, connection->u.host,
	  remote_port ? remote_port : "", range ? range : "",
	  www_auth ? www_auth : "", proxy_auth ? proxy_auth : "",
	  pragma_no_cache ? pragma_no_cache : "");

  proz_debug("HTTP request = %s", request);

  connection_show_message(connection, _("Sending HTTP request"));
  err = http_fetch_headers(connection, &hs_after_get, request);



  if (err == HAUTHREQ)
  {
    connection_change_status(connection, LOGINFAIL);
    return FTPLOGREFUSED;
  } else if (err == HTTPNSFOD)
  {
    connection_change_status(connection, REMOTEFATAL);
    return FTPNSFOD;
  } else if (err != HOK)
  {
    connection_change_status(connection, REMOTEFATAL);
    return FTPERR;
  }

  /*Check for the server lying about it being able to handle ranges */
  if (hs_after_get.contlen != -1)
  {
    if (connection->resume_support == TRUE)
    {
      if (hs_after_get.contlen !=
	  connection->main_file_size - connection->remote_startpos)
      {
	proz_debug("Error contlen does not match the requested range!");
	connection_change_status(connection, REMOTEFATAL);
	return CANTRESUME;
      }
    }
  }

/* which routine to call */
  if (connection->main_file_size == -1)
    err =
	connection_retr_fsize_not_known(connection, buffer,
					sizeof(buffer));
  else
    err = connection_retr_fsize_known(connection, buffer, sizeof(buffer));

  close_sock(&connection->data_sock);

  if (err == FILEGETOK)
    return FTPOK;
  else
    return err;

}