/****************************************************************************** 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$ */ #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; }