diff options
Diffstat (limited to 'Utilities/cmcurl/http.c')
-rw-r--r-- | Utilities/cmcurl/http.c | 1200 |
1 files changed, 813 insertions, 387 deletions
diff --git a/Utilities/cmcurl/http.c b/Utilities/cmcurl/http.c index 7cf543b..c07053b 100644 --- a/Utilities/cmcurl/http.c +++ b/Utilities/cmcurl/http.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -30,12 +30,14 @@ #include <stdarg.h> #include <stdlib.h> #include <ctype.h> +#ifdef HAVE_SYS_TYPES_H #include <sys/types.h> +#endif +#ifdef HAVE_SYS_STAT_H #include <sys/stat.h> +#endif -#include <errno.h> - -#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__) +#ifdef WIN32 #include <time.h> #include <io.h> #else @@ -45,7 +47,9 @@ #ifdef HAVE_NETINET_IN_H #include <netinet/in.h> #endif +#ifdef HAVE_SYS_TIME_H #include <sys/time.h> +#endif #ifdef HAVE_TIME_H #ifdef TIME_WITH_SYS_TIME @@ -70,22 +74,19 @@ #include <sys/param.h> #endif -#ifdef HAVE_SYS_SELECT_H -#include <sys/select.h> -#endif - #endif #include "urldata.h" #include <curl/curl.h> #include "transfer.h" #include "sendf.h" +#include "easyif.h" /* for Curl_convert_... prototypes */ #include "formdata.h" #include "progress.h" #include "base64.h" #include "cookie.h" #include "strequal.h" -#include "ssluse.h" +#include "sslgen.h" #include "http_digest.h" #include "http_ntlm.h" #include "http_negotiate.h" @@ -93,7 +94,11 @@ #include "share.h" #include "hostip.h" #include "http.h" -#include "curl_memory.h" +#include "memory.h" +#include "select.h" +#include "parsedate.h" /* for the week day and month names */ +#include "strtoofft.h" +#include "multiif.h" #define _MPRINTF_REPLACE /* use our functions only */ #include <curl/mprintf.h> @@ -145,12 +150,12 @@ static CURLcode Curl_output_basic(struct connectdata *conn, bool proxy) } snprintf(data->state.buffer, sizeof(data->state.buffer), "%s:%s", user, pwd); - if(Curl_base64_encode(data->state.buffer, + if(Curl_base64_encode(data, data->state.buffer, strlen(data->state.buffer), &authorization) > 0) { if(*userp) free(*userp); - *userp = aprintf( "%sAuthorization: Basic %s\015\012", + *userp = aprintf( "%sAuthorization: Basic %s\r\n", proxy?"Proxy-":"", authorization); free(authorization); @@ -192,6 +197,104 @@ static bool pickoneauth(struct auth *pick) } /* + * perhapsrewind() + * + * If we are doing POST or PUT { + * If we have more data to send { + * If we are doing NTLM { + * Keep sending since we must not disconnect + * } + * else { + * If there is more than just a little data left to send, close + * the current connection by force. + * } + * } + * If we have sent any data { + * If we don't have track of all the data { + * call app to tell it to rewind + * } + * else { + * rewind internally so that the operation can restart fine + * } + * } + * } + */ +static CURLcode perhapsrewind(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct HTTP *http = data->reqdata.proto.http; + struct Curl_transfer_keeper *k = &data->reqdata.keep; + curl_off_t bytessent; + curl_off_t expectsend = -1; /* default is unknown */ + + if(!http) + /* If this is still NULL, we have not reach very far and we can + safely skip this rewinding stuff */ + return CURLE_OK; + + bytessent = http->writebytecount; + + if(conn->bits.authneg) + /* This is a state where we are known to be negotiating and we don't send + any data then. */ + expectsend = 0; + else { + /* figure out how much data we are expected to send */ + switch(data->set.httpreq) { + case HTTPREQ_POST: + if(data->set.postfieldsize != -1) + expectsend = data->set.postfieldsize; + break; + case HTTPREQ_PUT: + if(data->set.infilesize != -1) + expectsend = data->set.infilesize; + break; + case HTTPREQ_POST_FORM: + expectsend = http->postsize; + break; + default: + break; + } + } + + conn->bits.rewindaftersend = FALSE; /* default */ + + if((expectsend == -1) || (expectsend > bytessent)) { + /* There is still data left to send */ + if((data->state.authproxy.picked == CURLAUTH_NTLM) || + (data->state.authhost.picked == CURLAUTH_NTLM)) { + if(((expectsend - bytessent) < 2000) || + (conn->ntlm.state != NTLMSTATE_NONE)) { + /* The NTLM-negotiation has started *OR* there is just a little (<2K) + data left to send, keep on sending. */ + + /* rewind data when completely done sending! */ + if(!conn->bits.authneg) + conn->bits.rewindaftersend = TRUE; + + return CURLE_OK; + } + if(conn->bits.close) + /* this is already marked to get closed */ + return CURLE_OK; + + infof(data, "NTLM send, close instead of sending %" FORMAT_OFF_T + " bytes\n", (curl_off_t)(expectsend - bytessent)); + } + + /* This is not NTLM or NTLM with many bytes left to send: close + */ + conn->bits.close = TRUE; + k->size = 0; /* don't download any more than 0 bytes */ + } + + if(bytessent) + return Curl_readrewind(conn); + + return CURLE_OK; +} + +/* * Curl_http_auth_act() gets called when a all HTTP headers have been received * and it checks what authentication methods that are available and decides * which one (if any) to use. It will set 'newurl' if an auth metod was @@ -205,43 +308,56 @@ CURLcode Curl_http_auth_act(struct connectdata *conn) bool pickproxy = FALSE; CURLcode code = CURLE_OK; + if(100 == data->reqdata.keep.httpcode) + /* this is a transient response code, ignore */ + return CURLE_OK; + if(data->state.authproblem) return data->set.http_fail_on_error?CURLE_HTTP_RETURNED_ERROR:CURLE_OK; if(conn->bits.user_passwd && - ((conn->keep.httpcode == 401) || - (conn->bits.authprobe && conn->keep.httpcode < 300))) { + ((data->reqdata.keep.httpcode == 401) || + (conn->bits.authneg && data->reqdata.keep.httpcode < 300))) { pickhost = pickoneauth(&data->state.authhost); if(!pickhost) data->state.authproblem = TRUE; } if(conn->bits.proxy_user_passwd && - ((conn->keep.httpcode == 407) || - (conn->bits.authprobe && conn->keep.httpcode < 300))) { + ((data->reqdata.keep.httpcode == 407) || + (conn->bits.authneg && data->reqdata.keep.httpcode < 300))) { pickproxy = pickoneauth(&data->state.authproxy); if(!pickproxy) data->state.authproblem = TRUE; } - if(pickhost || pickproxy) - conn->newurl = strdup(data->change.url); /* clone URL */ + if(pickhost || pickproxy) { + data->reqdata.newurl = strdup(data->change.url); /* clone URL */ + + if((data->set.httpreq != HTTPREQ_GET) && + (data->set.httpreq != HTTPREQ_HEAD) && + !conn->bits.rewindaftersend) { + code = perhapsrewind(conn); + if(code) + return code; + } + } - else if((conn->keep.httpcode < 300) && + else if((data->reqdata.keep.httpcode < 300) && (!data->state.authhost.done) && - conn->bits.authprobe) { + conn->bits.authneg) { /* no (known) authentication available, authentication is not "done" yet and no authentication seems to be required and we didn't try HEAD or GET */ if((data->set.httpreq != HTTPREQ_GET) && (data->set.httpreq != HTTPREQ_HEAD)) { - conn->newurl = strdup(data->change.url); /* clone URL */ + data->reqdata.newurl = strdup(data->change.url); /* clone URL */ data->state.authhost.done = TRUE; } } if (Curl_http_should_fail(conn)) { failf (data, "The requested URL returned error: %d", - conn->keep.httpcode); + data->reqdata.keep.httpcode); code = CURLE_HTTP_RETURNED_ERROR; } @@ -272,50 +388,48 @@ Curl_http_output_auth(struct connectdata *conn, CURLcode result = CURLE_OK; struct SessionHandle *data = conn->data; char *auth=NULL; + struct auth *authhost; + struct auth *authproxy; curlassert(data); + authhost = &data->state.authhost; + authproxy = &data->state.authproxy; + if((conn->bits.httpproxy && conn->bits.proxy_user_passwd) || conn->bits.user_passwd) /* continue please */ ; else { - data->state.authhost.done = TRUE; - data->state.authproxy.done = TRUE; + authhost->done = TRUE; + authproxy->done = TRUE; return CURLE_OK; /* no authentication with no user or password */ } - if(data->state.authhost.want && !data->state.authhost.picked) + if(authhost->want && !authhost->picked) /* The app has selected one or more methods, but none has been picked so far by a server round-trip. Then we set the picked one to the want one, and if this is one single bit it'll be used instantly. */ - data->state.authhost.picked = data->state.authhost.want; + authhost->picked = authhost->want; - if(data->state.authproxy.want && !data->state.authproxy.picked) + if(authproxy->want && !authproxy->picked) /* The app has selected one or more methods, but none has been picked so far by a proxy round-trip. Then we set the picked one to the want one, and if this is one single bit it'll be used instantly. */ - data->state.authproxy.picked = data->state.authproxy.want; - - /* To prevent the user+password to get sent to other than the original - host due to a location-follow, we do some weirdo checks here */ - if(!data->state.this_is_a_follow || - !data->state.auth_host || - curl_strequal(data->state.auth_host, conn->host.name) || - data->set.http_disable_hostname_check_before_authentication) { - - /* Send proxy authentication header if needed */ - if (conn->bits.httpproxy && - (conn->bits.tunnel_proxy == proxytunnel)) { -#ifdef USE_SSLEAY - if(data->state.authproxy.want == CURLAUTH_NTLM) { - auth=(char *)"NTLM"; - result = Curl_output_ntlm(conn, TRUE); - if(result) - return result; - } - else + authproxy->picked = authproxy->want; + + /* Send proxy authentication header if needed */ + if (conn->bits.httpproxy && + (conn->bits.tunnel_proxy == proxytunnel)) { +#ifdef USE_NTLM + if(authproxy->picked == CURLAUTH_NTLM) { + auth=(char *)"NTLM"; + result = Curl_output_ntlm(conn, TRUE); + if(result) + return result; + } + else #endif - if(data->state.authproxy.want == CURLAUTH_BASIC) { + if(authproxy->picked == CURLAUTH_BASIC) { /* Basic */ if(conn->bits.proxy_user_passwd && !checkheaders(data, "Proxy-authorization:")) { @@ -324,9 +438,12 @@ Curl_http_output_auth(struct connectdata *conn, if(result) return result; } - data->state.authproxy.done = TRUE; + /* NOTE: Curl_output_basic() should set 'done' TRUE, as the other auth + functions work that way */ + authproxy->done = TRUE; } - else if(data->state.authproxy.want == CURLAUTH_DIGEST) { +#ifndef CURL_DISABLE_CRYPTO_AUTH + else if(authproxy->picked == CURLAUTH_DIGEST) { auth=(char *)"Digest"; result = Curl_output_digest(conn, TRUE, /* proxy */ @@ -335,32 +452,45 @@ Curl_http_output_auth(struct connectdata *conn, if(result) return result; } - - infof(data, "Proxy auth using %s with user '%s'\n", - auth, conn->proxyuser?conn->proxyuser:""); +#endif + if(auth) { + infof(data, "Proxy auth using %s with user '%s'\n", + auth, conn->proxyuser?conn->proxyuser:""); + authproxy->multi = (bool)(!authproxy->done); + } + else + authproxy->multi = FALSE; } - else - /* we have no proxy so let's pretend we're done authenticating - with it */ - data->state.authproxy.done = TRUE; + else + /* we have no proxy so let's pretend we're done authenticating + with it */ + authproxy->done = TRUE; + + /* To prevent the user+password to get sent to other than the original + host due to a location-follow, we do some weirdo checks here */ + if(!data->state.this_is_a_follow || + conn->bits.netrc || + !data->state.first_host || + curl_strequal(data->state.first_host, conn->host.name) || + data->set.http_disable_hostname_check_before_authentication) { /* Send web authentication header if needed */ { auth = NULL; #ifdef HAVE_GSSAPI - if((data->state.authhost.want == CURLAUTH_GSSNEGOTIATE) && + if((authhost->picked == CURLAUTH_GSSNEGOTIATE) && data->state.negotiate.context && !GSS_ERROR(data->state.negotiate.status)) { auth=(char *)"GSS-Negotiate"; result = Curl_output_negotiate(conn); if (result) return result; - data->state.authhost.done = TRUE; + authhost->done = TRUE; } else #endif -#ifdef USE_SSLEAY - if(data->state.authhost.picked == CURLAUTH_NTLM) { +#ifdef USE_NTLM + if(authhost->picked == CURLAUTH_NTLM) { auth=(char *)"NTLM"; result = Curl_output_ntlm(conn, FALSE); if(result) @@ -369,7 +499,8 @@ Curl_http_output_auth(struct connectdata *conn, else #endif { - if(data->state.authhost.picked == CURLAUTH_DIGEST) { +#ifndef CURL_DISABLE_CRYPTO_AUTH + if(authhost->picked == CURLAUTH_DIGEST) { auth=(char *)"Digest"; result = Curl_output_digest(conn, FALSE, /* not a proxy */ @@ -377,8 +508,9 @@ Curl_http_output_auth(struct connectdata *conn, (unsigned char *)path); if(result) return result; - } - else if(data->state.authhost.picked == CURLAUTH_BASIC) { + } else +#endif + if(authhost->picked == CURLAUTH_BASIC) { if(conn->bits.user_passwd && !checkheaders(data, "Authorization:")) { auth=(char *)"Basic"; @@ -387,16 +519,21 @@ Curl_http_output_auth(struct connectdata *conn, return result; } /* basic is always ready */ - data->state.authhost.done = TRUE; + authhost->done = TRUE; } } - if(auth) + if(auth) { infof(data, "Server auth using %s with user '%s'\n", auth, conn->user); + + authhost->multi = (bool)(!authhost->done); + } + else + authhost->multi = FALSE; } } else - data->state.authhost.done = TRUE; + authhost->done = TRUE; return result; } @@ -433,15 +570,15 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, } /* pass all white spaces */ - while(*start && isspace((int)*start)) + while(*start && ISSPACE(*start)) start++; /* - * Here we check if we want the specific single authentiction (using ==) and + * Here we check if we want the specific single authentication (using ==) and * if we do, we initiate usage of it. * * If the provided authentication is wanted as one out of several accepted - * types (using &), we OR this authenticaion type to the authavail + * types (using &), we OR this authentication type to the authavail * variable. */ @@ -454,8 +591,8 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, /* if exactly this is wanted, go */ int neg = Curl_input_negotiate(conn, start); if (neg == 0) { - conn->newurl = strdup(data->change.url); - data->state.authproblem = (conn->newurl == NULL); + data->reqdata.newurl = strdup(data->change.url); + data->state.authproblem = (data->reqdata.newurl == NULL); } else { infof(data, "Authentication problem. Ignoring this.\n"); @@ -465,7 +602,7 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, } else #endif -#ifdef USE_SSLEAY +#ifdef USE_NTLM /* NTLM support requires the SSL crypto libs */ if(checkprefix("NTLM", start)) { *availp |= CURLAUTH_NTLM; @@ -485,22 +622,30 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, } else #endif +#ifndef CURL_DISABLE_CRYPTO_AUTH if(checkprefix("Digest", start)) { - CURLdigest dig; - *availp |= CURLAUTH_DIGEST; - authp->avail |= CURLAUTH_DIGEST; - - /* We call this function on input Digest headers even if Digest - * authentication isn't activated yet, as we need to store the - * incoming data from this header in case we are gonna use Digest. */ - dig = Curl_input_digest(conn, (bool)(httpcode == 407), start); - - if(CURLDIGEST_FINE != dig) { - infof(data, "Authentication problem. Ignoring this.\n"); - data->state.authproblem = TRUE; + if((authp->avail & CURLAUTH_DIGEST) != 0) { + infof(data, "Ignoring duplicate digest auth header.\n"); + } + else { + CURLdigest dig; + *availp |= CURLAUTH_DIGEST; + authp->avail |= CURLAUTH_DIGEST; + + /* We call this function on input Digest headers even if Digest + * authentication isn't activated yet, as we need to store the + * incoming data from this header in case we are gonna use Digest. */ + dig = Curl_input_digest(conn, (bool)(httpcode == 407), start); + + if(CURLDIGEST_FINE != dig) { + infof(data, "Authentication problem. Ignoring this.\n"); + data->state.authproblem = TRUE; + } } } - else if(checkprefix("Basic", start)) { + else +#endif + if(checkprefix("Basic", start)) { *availp |= CURLAUTH_BASIC; authp->avail |= CURLAUTH_BASIC; if(authp->picked == CURLAUTH_BASIC) { @@ -538,7 +683,7 @@ int Curl_http_should_fail(struct connectdata *conn) /* ** For readability */ - k = &conn->keep; + k = &data->reqdata.keep; /* ** If we haven't been asked to fail on error, @@ -553,6 +698,14 @@ int Curl_http_should_fail(struct connectdata *conn) if (k->httpcode < 400) return 0; + if (data->reqdata.resume_from && + (data->set.httpreq==HTTPREQ_GET) && + (k->httpcode == 416)) { + /* "Requested Range Not Satisfiable", just proceed and + pretend this is no error */ + return 0; + } + /* ** Any code >= 400 that's not 401 or 407 is always ** a terminal error @@ -585,7 +738,7 @@ int Curl_http_should_fail(struct connectdata *conn) infof(data,"%s: authavail = 0x%08x\n",__FUNCTION__,data->state.authavail); infof(data,"%s: httpcode = %d\n",__FUNCTION__,k->httpcode); infof(data,"%s: authdone = %d\n",__FUNCTION__,data->state.authdone); - infof(data,"%s: newurl = %s\n",__FUNCTION__,conn->newurl ? conn->newurl : "(null)"); + infof(data,"%s: newurl = %s\n",__FUNCTION__,data->reqdata.newurl ? data->reqdata.newurl : "(null)"); infof(data,"%s: authproblem = %d\n",__FUNCTION__,data->state.authproblem); #endif @@ -615,7 +768,7 @@ static size_t readmoredata(char *buffer, void *userp) { struct connectdata *conn = (struct connectdata *)userp; - struct HTTP *http = conn->proto.http; + struct HTTP *http = conn->data->reqdata.proto.http; size_t fullsize = size * nitems; if(0 == http->postsize) @@ -623,7 +776,7 @@ static size_t readmoredata(char *buffer, return 0; /* make sure that a HTTP request is never sent away chunked! */ - conn->bits.forbidchunk= (http->sending == HTTPSEND_REQUEST)?TRUE:FALSE; + conn->bits.forbidchunk = (bool)(http->sending == HTTPSEND_REQUEST); if(http->postsize <= (curl_off_t)fullsize) { memcpy(buffer, http->postdata, (size_t)http->postsize); @@ -667,6 +820,8 @@ struct send_buffer { }; typedef struct send_buffer send_buffer; +static CURLcode add_custom_headers(struct connectdata *conn, + send_buffer *req_buffer); static CURLcode add_buffer(send_buffer *in, const void *inptr, size_t size); @@ -686,23 +841,32 @@ send_buffer *add_buffer_init(void) } /* - * add_buffer_send() sends a buffer and frees all associated memory. + * add_buffer_send() sends a header buffer and frees all associated memory. + * Body data may be appended to the header data if desired. * * Returns CURLcode */ static CURLcode add_buffer_send(send_buffer *in, struct connectdata *conn, - long *bytes_written) /* add the number of sent + long *bytes_written, /* add the number of sent bytes to this counter */ + size_t included_body_bytes, /* how much of the buffer + contains body data (for log tracing) */ + int socketindex) + { ssize_t amount; CURLcode res; char *ptr; size_t size; - struct HTTP *http = conn->proto.http; + struct HTTP *http = conn->data->reqdata.proto.http; size_t sendsize; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + curl_socket_t sockfd; + + curlassert(socketindex <= SECONDARYSOCKET); + + sockfd = conn->sock[socketindex]; /* The looping below is required since we use non-blocking sockets, but due to the circumstances we will just loop and try again and again etc */ @@ -710,6 +874,20 @@ CURLcode add_buffer_send(send_buffer *in, ptr = in->buffer; size = in->size_used; +#ifdef CURL_DOES_CONVERSIONS + if(size - included_body_bytes > 0) { + res = Curl_convert_to_network(conn->data, ptr, size - included_body_bytes); + /* Curl_convert_to_network calls failf if unsuccessful */ + if(res != CURLE_OK) { + /* conversion failed, free memory and return to the caller */ + if(in->buffer) + free(in->buffer); + free(in); + return res; + } + } +#endif /* CURL_DOES_CONVERSIONS */ + if(conn->protocol & PROT_HTTPS) { /* We never send more than CURL_MAX_WRITE_SIZE bytes in one single chunk when we speak HTTPS, as if only a fraction of it is sent now, this data @@ -735,41 +913,61 @@ CURLcode add_buffer_send(send_buffer *in, if(CURLE_OK == res) { - if(conn->data->set.verbose) + if(conn->data->set.verbose) { /* this data _may_ contain binary stuff */ - Curl_debug(conn->data, CURLINFO_HEADER_OUT, ptr, amount, - conn->host.dispname); + Curl_debug(conn->data, CURLINFO_HEADER_OUT, ptr, + (size_t)(amount-included_body_bytes), conn); + if (included_body_bytes) + Curl_debug(conn->data, CURLINFO_DATA_OUT, + ptr+amount-included_body_bytes, + (size_t)included_body_bytes, conn); + } *bytes_written += amount; - if((size_t)amount != size) { - /* The whole request could not be sent in one system call. We must queue - it up and send it later when we get the chance. We must not loop here - and wait until it might work again. */ + if(http) { + if((size_t)amount != size) { + /* The whole request could not be sent in one system call. We must + queue it up and send it later when we get the chance. We must not + loop here and wait until it might work again. */ - size -= amount; + size -= amount; - ptr = in->buffer + amount; + ptr = in->buffer + amount; - /* backup the currently set pointers */ - http->backup.fread = conn->fread; - http->backup.fread_in = conn->fread_in; - http->backup.postdata = http->postdata; - http->backup.postsize = http->postsize; + /* backup the currently set pointers */ + http->backup.fread = conn->fread; + http->backup.fread_in = conn->fread_in; + http->backup.postdata = http->postdata; + http->backup.postsize = http->postsize; - /* set the new pointers for the request-sending */ - conn->fread = (curl_read_callback)readmoredata; - conn->fread_in = (void *)conn; - http->postdata = ptr; - http->postsize = (curl_off_t)size; + /* set the new pointers for the request-sending */ + conn->fread = (curl_read_callback)readmoredata; + conn->fread_in = (void *)conn; + http->postdata = ptr; + http->postsize = (curl_off_t)size; - http->send_buffer = in; - http->sending = HTTPSEND_REQUEST; + http->send_buffer = in; + http->sending = HTTPSEND_REQUEST; - return CURLE_OK; + return CURLE_OK; + } + http->sending = HTTPSEND_BODY; + /* the full buffer was sent, clean up and return */ + } + else { + if((size_t)amount != size) + /* We have no continue-send mechanism now, fail. This can only happen + when this function is used from the CONNECT sending function. We + currently (stupidly) assume that the whole request is always sent + away in the first single chunk. + + This needs FIXing. + */ + return CURLE_SEND_ERROR; + else + conn->writechannel_inuse = FALSE; } - http->sending = HTTPSEND_BODY; - /* the full buffer was sent, clean up and return */ } if(in->buffer) free(in->buffer); @@ -868,7 +1066,7 @@ Curl_compareheader(char *headerline, /* line to check */ start = &headerline[hlen]; /* pass all white spaces */ - while(*start && isspace((int)*start)) + while(*start && ISSPACE(*start)) start++; /* find the end of the header line */ @@ -895,34 +1093,41 @@ Curl_compareheader(char *headerline, /* line to check */ } /* - * ConnectHTTPProxyTunnel() requires that we're connected to a HTTP proxy. This + * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This * function will issue the necessary commands to get a seamless tunnel through * this proxy. After that, the socket can be used just as a normal socket. + * + * This badly needs to be rewritten. CONNECT should be sent and dealt with + * like any ordinary HTTP request, and not specially crafted like this. This + * function only remains here like this for now since the rewrite is a bit too + * much work to do at the moment. + * + * This function is BLOCKING which is nasty for all multi interface using apps. */ -CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, - int sockindex, - char *hostname, - int remote_port) +CURLcode Curl_proxyCONNECT(struct connectdata *conn, + int sockindex, + char *hostname, + int remote_port) { int subversion=0; struct SessionHandle *data=conn->data; - struct Curl_transfer_keeper *k = &conn->keep; + struct Curl_transfer_keeper *k = &data->reqdata.keep; CURLcode result; int res; - size_t nread; /* total size read */ int perline; /* count bytes per line */ - bool keepon=TRUE; + int keepon=TRUE; ssize_t gotbytes; char *ptr; - long timeout; /* default timeout in seconds */ - struct timeval interval; - fd_set rkeepfd; - fd_set readfd; + long timeout = + data->set.timeout?data->set.timeout:3600; /* in seconds */ char *line_start; char *host_port; curl_socket_t tunnelsocket = conn->sock[sockindex]; + send_buffer *req_buffer; + curl_off_t cl=0; + bool closeConnection = FALSE; #define SELECT_OK 0 #define SELECT_ERROR 1 @@ -930,36 +1135,78 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, int error = SELECT_OK; infof(data, "Establish HTTP proxy tunnel to %s:%d\n", hostname, remote_port); + conn->bits.proxy_connect_closed = FALSE; do { - if(conn->newurl) { + if(data->reqdata.newurl) { /* This only happens if we've looped here due to authentication reasons, and we don't really use the newly cloned URL here then. Just free() it. */ - free(conn->newurl); - conn->newurl = NULL; + free(data->reqdata.newurl); + data->reqdata.newurl = NULL; } + /* initialize a dynamic send-buffer */ + req_buffer = add_buffer_init(); + + if(!req_buffer) + return CURLE_OUT_OF_MEMORY; + host_port = aprintf("%s:%d", hostname, remote_port); if(!host_port) return CURLE_OUT_OF_MEMORY; /* Setup the proxy-authorization header, if any */ result = Curl_http_output_auth(conn, (char *)"CONNECT", host_port, TRUE); + if(CURLE_OK == result) { + char *host=(char *)""; + const char *proxyconn=""; + const char *useragent=""; + + if(!checkheaders(data, "Host:")) { + host = aprintf("Host: %s\r\n", host_port); + if(!host) + result = CURLE_OUT_OF_MEMORY; + } + if(!checkheaders(data, "Proxy-Connection:")) + proxyconn = "Proxy-Connection: Keep-Alive\r\n"; - /* OK, now send the connect request to the proxy */ - result = - Curl_sendf(tunnelsocket, conn, - "CONNECT %s:%d HTTP/1.0\015\012" - "%s" - "%s" - "\r\n", - hostname, remote_port, - conn->bits.proxy_user_passwd? - conn->allocptr.proxyuserpwd:"", - data->set.useragent?conn->allocptr.uagent:"" - ); + if(!checkheaders(data, "User-Agent:") && data->set.useragent) + useragent = conn->allocptr.uagent; + + if(CURLE_OK == result) { + /* Send the connect request to the proxy */ + /* BLOCKING */ + result = + add_bufferf(req_buffer, + "CONNECT %s:%d HTTP/1.0\r\n" + "%s" /* Host: */ + "%s" /* Proxy-Authorization */ + "%s" /* User-Agent */ + "%s", /* Proxy-Connection */ + hostname, remote_port, + host, + conn->allocptr.proxyuserpwd? + conn->allocptr.proxyuserpwd:"", + useragent, + proxyconn); + + if(CURLE_OK == result) + result = add_custom_headers(conn, req_buffer); + + if(host && *host) + free(host); + + if(CURLE_OK == result) + /* CRLF terminate the request */ + result = add_bufferf(req_buffer, "\r\n"); + + if(CURLE_OK == result) + /* Now send off the request */ + result = add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, sockindex); + } if(result) failf(data, "Failed sending CONNECT to proxy"); } @@ -967,36 +1214,26 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, if(result) return result; - FD_ZERO (&readfd); /* clear it */ - FD_SET (tunnelsocket, &readfd); /* read socket */ - - /* get this in a backup variable to be able to restore it on each lap in - the select() loop */ - rkeepfd = readfd; - ptr=data->state.buffer; line_start = ptr; nread=0; perline=0; + keepon=TRUE; while((nread<BUFSIZE) && (keepon && !error)) { - readfd = rkeepfd; /* set every lap */ - interval.tv_sec = 1; /* timeout each second and check the timeout */ - interval.tv_usec = 0; - - if(data->set.timeout) { - /* if timeout is requested, find out how much remaining time we have */ - timeout = data->set.timeout - /* timeout time */ - Curl_tvdiff(Curl_tvnow(), conn->now)/1000; /* spent time */ - if(timeout <=0 ) { - failf(data, "Proxy connection aborted due to timeout"); - error = SELECT_TIMEOUT; /* already too little time */ - break; - } + + /* if timeout is requested, find out how much remaining time we have */ + long check = timeout - /* timeout time */ + Curl_tvdiff(Curl_tvnow(), conn->now)/1000; /* spent time */ + if(check <=0 ) { + failf(data, "Proxy CONNECT aborted due to timeout"); + error = SELECT_TIMEOUT; /* already too little time */ + break; } - switch (select (tunnelsocket+1, &readfd, NULL, NULL, &interval)) { + /* timeout each second and check the timeout */ + switch (Curl_select(tunnelsocket, CURL_SOCKET_BAD, 1000)) { case -1: /* select() error, stop reading */ error = SELECT_ERROR; failf(data, "Proxy CONNECT aborted due to select() error"); @@ -1004,12 +1241,7 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, case 0: /* timeout */ break; default: - /* - * This code previously didn't use the kerberos sec_read() code - * to read, but when we use Curl_read() it may do so. Do confirm - * that this is still ok and then remove this comment! - */ - res= Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread, &gotbytes); + res = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread, &gotbytes); if(res< 0) /* EWOULDBLOCK */ continue; /* go loop yourself */ @@ -1024,32 +1256,39 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, /* * We got a whole chunk of data, which can be anything from one byte * to a set of lines and possibly just a piece of the last line. - * - * TODO: To make this code work less error-prone, we need to make - * sure that we read and create full lines before we compare them, - * as there is really nothing that stops the proxy from delivering - * the response lines in multiple parts, each part consisting of - * only a little piece of the line(s). */ + */ int i; nread += gotbytes; + + if(keepon > TRUE) { + /* This means we are currently ignoring a response-body, so we + simply count down our counter and make sure to break out of the + loop when we're done! */ + cl -= gotbytes; + if(cl<=0) { + keepon = FALSE; + break; + } + } + else for(i = 0; i < gotbytes; ptr++, i++) { perline++; /* amount of bytes in this line so far */ if(*ptr=='\n') { char letter; int writetype; - /* output debug output if that is requested */ + /* output debug if that is requested */ if(data->set.verbose) - Curl_debug(data, CURLINFO_HEADER_IN, line_start, perline, - conn->host.dispname); + Curl_debug(data, CURLINFO_HEADER_IN, + line_start, (size_t)perline, conn); /* send the header to the callback */ writetype = CLIENTWRITE_HEADER; if(data->set.include_header) writetype |= CLIENTWRITE_BODY; - result = Curl_client_write(data, writetype, line_start, perline); + result = Curl_client_write(conn, writetype, line_start, perline); if(result) return result; @@ -1060,7 +1299,21 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, if(('\r' == line_start[0]) || ('\n' == line_start[0])) { /* end of response-headers from the proxy */ - keepon=FALSE; + if(cl && (407 == k->httpcode) && !data->state.authproblem) { + /* If we get a 407 response code with content length when we + * have no auth problem, we must ignore the whole + * response-body */ + keepon = 2; + infof(data, "Ignore %" FORMAT_OFF_T + " bytes of response-body\n", cl); + cl -= (gotbytes - i);/* remove the remaining chunk of what + we already read */ + if(cl<=0) + /* if the whole thing was already read, we are done! */ + keepon=FALSE; + } + else + keepon = FALSE; break; /* breaks out of for-loop, not switch() */ } @@ -1075,6 +1328,13 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, if(result) return result; } + else if(checkprefix("Content-Length:", line_start)) { + cl = curlx_strtoofft(line_start + strlen("Content-Length:"), + NULL, 10); + } + else if(Curl_compareheader(line_start, + "Connection:", "close")) + closeConnection = TRUE; else if(2 == sscanf(line_start, "HTTP/1.%d %d", &subversion, &k->httpcode)) { @@ -1101,11 +1361,21 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, headers. 'newurl' is set to a new URL if we must loop. */ Curl_http_auth_act(conn); - } while(conn->newurl); + if (closeConnection && data->reqdata.newurl) { + /* Connection closed by server. Don't use it anymore */ + sclose(conn->sock[sockindex]); + conn->sock[sockindex] = CURL_SOCKET_BAD; + break; + } + } while(data->reqdata.newurl); if(200 != k->httpcode) { failf(data, "Received HTTP code %d from proxy after CONNECT", k->httpcode); + + if (closeConnection && data->reqdata.newurl) + conn->bits.proxy_connect_closed = TRUE; + return CURLE_RECV_ERROR; } @@ -1125,7 +1395,7 @@ CURLcode Curl_ConnectHTTPProxyTunnel(struct connectdata *conn, * Curl_http_connect() performs HTTP stuff to do at connect-time, called from * the generic Curl_connect(). */ -CURLcode Curl_http_connect(struct connectdata *conn) +CURLcode Curl_http_connect(struct connectdata *conn, bool *done) { struct SessionHandle *data; CURLcode result; @@ -1135,53 +1405,117 @@ CURLcode Curl_http_connect(struct connectdata *conn) /* If we are not using a proxy and we want a secure connection, perform SSL * initialization & connection now. If using a proxy with https, then we * must tell the proxy to CONNECT to the host we want to talk to. Only - * after the connect has occured, can we start talking SSL + * after the connect has occurred, can we start talking SSL */ - if(conn->bits.tunnel_proxy) { + if(conn->bits.tunnel_proxy && conn->bits.httpproxy) { /* either SSL over proxy, or explicitly asked for */ - result = Curl_ConnectHTTPProxyTunnel(conn, FIRSTSOCKET, - conn->host.name, - conn->remote_port); + result = Curl_proxyCONNECT(conn, FIRSTSOCKET, + conn->host.name, + conn->remote_port); if(CURLE_OK != result) return result; } + if(!data->state.this_is_a_follow) { + /* this is not a followed location, get the original host name */ + if (data->state.first_host) + /* Free to avoid leaking memory on multiple requests*/ + free(data->state.first_host); + + data->state.first_host = strdup(conn->host.name); + if(!data->state.first_host) + return CURLE_OUT_OF_MEMORY; + } + if(conn->protocol & PROT_HTTPS) { - /* now, perform the SSL initialization for this socket */ - result = Curl_SSLConnect(conn, FIRSTSOCKET); - if(result) - return result; + /* perform SSL initialization */ + if(data->state.used_interface == Curl_if_multi) { + result = Curl_https_connecting(conn, done); + if(result) + return result; + } + else { + /* BLOCKING */ + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(result) + return result; + *done = TRUE; + } + } + else { + *done = TRUE; } - if(conn->bits.user_passwd && !data->state.this_is_a_follow) { - /* Authorization: is requested, this is not a followed location, get the - original host name */ - if (data->state.auth_host) - /* Free to avoid leaking memory on multiple requests*/ - free(data->state.auth_host); + return CURLE_OK; +} - data->state.auth_host = strdup(conn->host.name); - } +CURLcode Curl_https_connecting(struct connectdata *conn, bool *done) +{ + CURLcode result; + curlassert(conn->protocol & PROT_HTTPS); + + /* perform SSL initialization for this socket */ + result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, done); + if(result) + return result; return CURLE_OK; } +#ifdef USE_SSLEAY +/* This function is OpenSSL-specific. It should be made to query the generic + SSL layer instead. */ +int Curl_https_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + if (conn->protocol & PROT_HTTPS) { + struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; + + if(!numsocks) + return GETSOCK_BLANK; + + if (connssl->connecting_state == ssl_connect_2_writing) { + /* write mode */ + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_WRITESOCK(0); + } + else if (connssl->connecting_state == ssl_connect_2_reading) { + /* read mode */ + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_READSOCK(0); + } + } + return CURLE_OK; +} +#else +#ifdef USE_GNUTLS +int Curl_https_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + (void)conn; + (void)socks; + (void)numsocks; + return GETSOCK_BLANK; +} +#endif +#endif + /* * Curl_http_done() gets called from Curl_done() after a single HTTP request * has been performed. */ CURLcode Curl_http_done(struct connectdata *conn, - CURLcode status) + CURLcode status, bool premature) { - struct SessionHandle *data; - struct HTTP *http; - (void)status; /* no use for us */ - - data=conn->data; - http=conn->proto.http; + struct SessionHandle *data = conn->data; + struct HTTP *http =data->reqdata.proto.http; + struct Curl_transfer_keeper *k = &data->reqdata.keep; + (void)premature; /* not used */ /* set the proper values (possibly modified on POST) */ conn->fread = data->set.fread; /* restore */ @@ -1195,16 +1529,24 @@ CURLcode Curl_http_done(struct connectdata *conn, free(buff->buffer); free(buff); - http->send_buffer = NULL; /* cleaer the pointer */ + http->send_buffer = NULL; /* clear the pointer */ } if(HTTPREQ_POST_FORM == data->set.httpreq) { - conn->bytecount = http->readbytecount + http->writebytecount; + k->bytecount = http->readbytecount + http->writebytecount; - Curl_formclean(http->sendit); /* Now free that whole lot */ + Curl_formclean(&http->sendit); /* Now free that whole lot */ + if(http->form.fp) { + /* a file being uploaded was left opened, close it! */ + fclose(http->form.fp); + http->form.fp = NULL; + } } else if(HTTPREQ_PUT == data->set.httpreq) - conn->bytecount = http->readbytecount + http->writebytecount; + k->bytecount = http->readbytecount + http->writebytecount; + + if (status != CURLE_OK) + return (status); if(!conn->bits.retry && ((http->readbytecount + @@ -1220,38 +1562,106 @@ CURLcode Curl_http_done(struct connectdata *conn, return CURLE_OK; } +/* check and possibly add an Expect: header */ +static CURLcode expect100(struct SessionHandle *data, + send_buffer *req_buffer) +{ + CURLcode result = CURLE_OK; + data->state.expect100header = FALSE; /* default to false unless it is set + to TRUE below */ + if((data->set.httpversion != CURL_HTTP_VERSION_1_0) && + !checkheaders(data, "Expect:")) { + /* if not doing HTTP 1.0 or disabled explicitly, we add a Expect: + 100-continue to the headers which actually speeds up post + operations (as there is one packet coming back from the web + server) */ + result = add_bufferf(req_buffer, + "Expect: 100-continue\r\n"); + if(result == CURLE_OK) + data->state.expect100header = TRUE; + } + return result; +} + +static CURLcode add_custom_headers(struct connectdata *conn, + send_buffer *req_buffer) +{ + CURLcode result = CURLE_OK; + char *ptr; + struct curl_slist *headers=conn->data->set.headers; + + while(headers) { + ptr = strchr(headers->data, ':'); + if(ptr) { + /* we require a colon for this to be a true header */ + + ptr++; /* pass the colon */ + while(*ptr && ISSPACE(*ptr)) + ptr++; + + if(*ptr) { + /* only send this if the contents was non-blank */ + + if(conn->allocptr.host && + /* a Host: header was sent already, don't pass on any custom Host: + header as that will produce *two* in the same request! */ + curl_strnequal("Host:", headers->data, 5)) + ; + else if(conn->data->set.httpreq == HTTPREQ_POST_FORM && + /* this header (extended by formdata.c) is sent later */ + curl_strnequal("Content-Type:", headers->data, + strlen("Content-Type:"))) + ; + else { + result = add_bufferf(req_buffer, "%s\r\n", headers->data); + if(result) + return result; + } + } + } + headers = headers->next; + } + return result; +} + /* * Curl_http() gets called from the generic Curl_do() function when a HTTP * request is to be performed. This creates and sends a properly constructed * HTTP request. */ -CURLcode Curl_http(struct connectdata *conn) +CURLcode Curl_http(struct connectdata *conn, bool *done) { struct SessionHandle *data=conn->data; char *buf = data->state.buffer; /* this is a short cut to the buffer */ - CURLcode result; + CURLcode result=CURLE_OK; struct HTTP *http; - char *ppath = conn->path; + char *ppath = data->reqdata.path; char *host = conn->host.name; - const char *te = ""; /* tranfer-encoding */ + const char *te = ""; /* transfer-encoding */ char *ptr; char *request; Curl_HttpReq httpreq = data->set.httpreq; char *addcookies = NULL; + curl_off_t included_body = 0; - if(!conn->proto.http) { + /* Always consider the DO phase done after this function call, even if there + may be parts of the request that is not yet sent, since we can deal with + the rest of the request in the PERFORM phase. */ + *done = TRUE; + + if(!data->reqdata.proto.http) { /* Only allocate this struct if we don't already have it! */ http = (struct HTTP *)malloc(sizeof(struct HTTP)); if(!http) return CURLE_OUT_OF_MEMORY; memset(http, 0, sizeof(struct HTTP)); - conn->proto.http = http; + data->reqdata.proto.http = http; } else - http = conn->proto.http; + http = data->reqdata.proto.http; - /* We default to persistant connections */ + /* We default to persistent connections */ conn->bits.close = FALSE; if ( (conn->protocol&(PROT_HTTP|PROT_FTP)) && @@ -1300,81 +1710,77 @@ CURLcode Curl_http(struct connectdata *conn) if(result) return result; - if((!data->state.authhost.done || !data->state.authproxy.done ) && - (httpreq != HTTPREQ_GET)) { - /* Until we are authenticated, we switch over to HEAD. Unless its a GET - we want to do. The explanation for this is rather long and boring, but - the point is that it can't be done otherwise without risking having to - send the POST or PUT data multiple times. */ - httpreq = HTTPREQ_HEAD; - request = (char *)"HEAD"; - conn->bits.no_body = TRUE; - conn->bits.authprobe = TRUE; /* this is a request done to probe for - authentication methods */ + if((data->state.authhost.multi || data->state.authproxy.multi) && + (httpreq != HTTPREQ_GET) && + (httpreq != HTTPREQ_HEAD)) { + /* Auth is required and we are not authenticated yet. Make a PUT or POST + with content-length zero as a "probe". */ + conn->bits.authneg = TRUE; } else - conn->bits.authprobe = FALSE; + conn->bits.authneg = FALSE; Curl_safefree(conn->allocptr.ref); if(data->change.referer && !checkheaders(data, "Referer:")) - conn->allocptr.ref = aprintf("Referer: %s\015\012", data->change.referer); + conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); else conn->allocptr.ref = NULL; if(data->set.cookie && !checkheaders(data, "Cookie:")) addcookies = data->set.cookie; - if(!conn->bits.upload_chunky && (httpreq != HTTPREQ_GET)) { - /* not a chunky transfer yet, but data is to be sent */ - ptr = checkheaders(data, "Transfer-Encoding:"); - if(ptr) { - /* Some kind of TE is requested, check if 'chunked' is chosen */ - conn->bits.upload_chunky = - Curl_compareheader(ptr, "Transfer-Encoding:", "chunked"); - te = ""; - } + if(!checkheaders(data, "Accept-Encoding:") && + data->set.encoding) { + Curl_safefree(conn->allocptr.accept_encoding); + conn->allocptr.accept_encoding = + aprintf("Accept-Encoding: %s\r\n", data->set.encoding); + if(!conn->allocptr.accept_encoding) + return CURLE_OUT_OF_MEMORY; } - else if(conn->bits.upload_chunky) { - /* RFC2616 section 4.4: - Messages MUST NOT include both a Content-Length header field and a - non-identity transfer-coding. If the message does include a non- - identity transfer-coding, the Content-Length MUST be ignored. */ - if(!checkheaders(data, "Transfer-Encoding:")) { + ptr = checkheaders(data, "Transfer-Encoding:"); + if(ptr) { + /* Some kind of TE is requested, check if 'chunked' is chosen */ + conn->bits.upload_chunky = + Curl_compareheader(ptr, "Transfer-Encoding:", "chunked"); + } + else { + if (httpreq == HTTPREQ_GET) + conn->bits.upload_chunky = FALSE; + if(conn->bits.upload_chunky) te = "Transfer-Encoding: chunked\r\n"; - } - else { - te = ""; - conn->bits.upload_chunky = FALSE; /* transfer-encoding was disabled, - so don't chunkify this! */ - } } Curl_safefree(conn->allocptr.host); ptr = checkheaders(data, "Host:"); - if(ptr && !data->state.this_is_a_follow) { + if(ptr && (!data->state.this_is_a_follow || + curl_strequal(data->state.first_host, conn->host.name))) { +#if !defined(CURL_DISABLE_COOKIES) /* If we have a given custom Host: header, we extract the host name in order to possibly use it for cookie reasons later on. We only allow the custom Host: header if this is NOT a redirect, as setting Host: in the - redirected request is being out on thin ice. */ + redirected request is being out on thin ice. Except if the host name + is the same as the first one! */ char *start = ptr+strlen("Host:"); - while(*start && isspace((int)*start )) + while(*start && ISSPACE(*start )) start++; ptr = start; /* start host-scanning here */ /* scan through the string to find the end (space or colon) */ - while(*ptr && !isspace((int)*ptr) && !(':'==*ptr)) + while(*ptr && !ISSPACE(*ptr) && !(':'==*ptr)) ptr++; if(ptr != start) { size_t len=ptr-start; + Curl_safefree(conn->allocptr.cookiehost); conn->allocptr.cookiehost = malloc(len+1); if(!conn->allocptr.cookiehost) return CURLE_OUT_OF_MEMORY; memcpy(conn->allocptr.cookiehost, start, len); conn->allocptr.cookiehost[len]=0; } +#endif conn->allocptr.host = NULL; } @@ -1410,8 +1816,8 @@ CURLcode Curl_http(struct connectdata *conn) uses the encoded host name! */ if(conn->host.dispname != conn->host.name) { char *url = data->change.url; - char *iPtr = strstr(url, conn->host.dispname); - if(iPtr) { + ptr = strstr(url, conn->host.dispname); + if(ptr) { /* This is where the display name starts in the URL, now replace this part with the encoded name. TODO: This method of replacing the host name is rather crude as I believe there's a slight risk that the @@ -1426,13 +1832,13 @@ CURLcode Curl_http(struct connectdata *conn) newurl = malloc(urllen + newlen - currlen + 1); if(newurl) { /* copy the part before the host name */ - memcpy(newurl, url, iPtr - url); + memcpy(newurl, url, ptr - url); /* append the new host name instead of the old */ - memcpy(newurl + (iPtr - url), conn->host.name, newlen); + memcpy(newurl + (ptr - url), conn->host.name, newlen); /* append the piece after the host name */ - memcpy(newurl + newlen + (iPtr - url), - iPtr + currlen, /* copy the trailing zero byte too */ - urllen - (iPtr-url) - currlen + 1); + memcpy(newurl + newlen + (ptr - url), + ptr + currlen, /* copy the trailing zero byte too */ + urllen - (ptr-url) - currlen + 1); if(data->change.url_alloc) free(data->change.url); data->change.url = newurl; @@ -1448,6 +1854,7 @@ CURLcode Curl_http(struct connectdata *conn) /* we must build the whole darned post sequence first, so that we have a size of the whole shebang before we start to send it */ result = Curl_getFormData(&http->sendit, data->set.httppost, + checkheaders(data, "Content-Type:"), &http->postsize); if(CURLE_OK != result) { /* Curl_getFormData() doesn't use failf() */ @@ -1457,8 +1864,10 @@ CURLcode Curl_http(struct connectdata *conn) } - if(!checkheaders(data, "Pragma:")) - http->p_pragma = "Pragma: no-cache\r\n"; + http->p_pragma = + (!checkheaders(data, "Pragma:") && + (conn->bits.httpproxy && !conn->bits.tunnel_proxy) )? + "Pragma: no-cache\r\n":NULL; if(!checkheaders(data, "Accept:")) http->p_accept = "Accept: */*\r\n"; @@ -1466,7 +1875,7 @@ CURLcode Curl_http(struct connectdata *conn) if(( (HTTPREQ_POST == httpreq) || (HTTPREQ_POST_FORM == httpreq) || (HTTPREQ_PUT == httpreq) ) && - conn->resume_from) { + data->reqdata.resume_from) { /********************************************************************** * Resuming upload in HTTP means that we PUT or POST and that we have * got a resume_from value set. The resume value has already created @@ -1475,15 +1884,15 @@ CURLcode Curl_http(struct connectdata *conn) * file size before we continue this venture in the dark lands of HTTP. *********************************************************************/ - if(conn->resume_from < 0 ) { + if(data->reqdata.resume_from < 0 ) { /* * This is meant to get the size of the present remote-file by itself. * We don't support this now. Bail out! */ - conn->resume_from = 0; + data->reqdata.resume_from = 0; } - if(conn->resume_from) { + if(data->reqdata.resume_from) { /* do we still game? */ curl_off_t passed=0; @@ -1491,7 +1900,7 @@ CURLcode Curl_http(struct connectdata *conn) input. If we knew it was a proper file we could've just fseek()ed but we only have a stream here */ do { - size_t readthisamountnow = (size_t)(conn->resume_from - passed); + size_t readthisamountnow = (size_t)(data->reqdata.resume_from - passed); size_t actuallyread; if(readthisamountnow > BUFSIZE) @@ -1508,11 +1917,11 @@ CURLcode Curl_http(struct connectdata *conn) passed); return CURLE_READ_ERROR; } - } while(passed != conn->resume_from); /* loop until done */ + } while(passed != data->reqdata.resume_from); /* loop until done */ /* now, decrease the size of the read */ if(data->set.infilesize>0) { - data->set.infilesize -= conn->resume_from; + data->set.infilesize -= data->reqdata.resume_from; if(data->set.infilesize <= 0) { failf(data, "File already completely uploaded"); @@ -1522,7 +1931,7 @@ CURLcode Curl_http(struct connectdata *conn) /* we've passed, proceed as normal */ } } - if(conn->bits.use_range) { + if(data->reqdata.use_range) { /* * A range is selected. We use different headers whether we're downloading * or uploading and we always let customized headers override our internal @@ -1533,19 +1942,19 @@ CURLcode Curl_http(struct connectdata *conn) /* if a line like this was already allocated, free the previous one */ if(conn->allocptr.rangeline) free(conn->allocptr.rangeline); - conn->allocptr.rangeline = aprintf("Range: bytes=%s\r\n", conn->range); + conn->allocptr.rangeline = aprintf("Range: bytes=%s\r\n", data->reqdata.range); } else if((httpreq != HTTPREQ_GET) && !checkheaders(data, "Content-Range:")) { - if(conn->resume_from) { + if(data->reqdata.resume_from) { /* This is because "resume" was selected */ curl_off_t total_expected_size= - conn->resume_from + data->set.infilesize; + data->reqdata.resume_from + data->set.infilesize; conn->allocptr.rangeline = aprintf("Content-Range: bytes %s%" FORMAT_OFF_T "/%" FORMAT_OFF_T "\r\n", - conn->range, total_expected_size-1, + data->reqdata.range, total_expected_size-1, total_expected_size); } else { @@ -1553,7 +1962,7 @@ CURLcode Curl_http(struct connectdata *conn) append total size */ conn->allocptr.rangeline = aprintf("Content-Range: bytes %s/%" FORMAT_OFF_T "\r\n", - conn->range, data->set.infilesize); + data->reqdata.range, data->set.infilesize); } } } @@ -1564,7 +1973,6 @@ CURLcode Curl_http(struct connectdata *conn) data->set.httpversion==CURL_HTTP_VERSION_1_0?"1.0":"1.1"; send_buffer *req_buffer; - struct curl_slist *headers=data->set.headers; curl_off_t postsize; /* off_t type to be able to hold a large file size */ /* initialize a dynamic send-buffer */ @@ -1587,6 +1995,7 @@ CURLcode Curl_http(struct connectdata *conn) "%s" /* accept */ "%s" /* accept-encoding */ "%s" /* referer */ + "%s" /* Proxy-Connection */ "%s",/* transfer-encoding */ request, @@ -1595,7 +2004,7 @@ CURLcode Curl_http(struct connectdata *conn) conn->allocptr.proxyuserpwd? conn->allocptr.proxyuserpwd:"", conn->allocptr.userpwd?conn->allocptr.userpwd:"", - (conn->bits.use_range && conn->allocptr.rangeline)? + (data->reqdata.use_range && conn->allocptr.rangeline)? conn->allocptr.rangeline:"", (data->set.useragent && *data->set.useragent && conn->allocptr.uagent)? conn->allocptr.uagent:"", @@ -1605,12 +2014,17 @@ CURLcode Curl_http(struct connectdata *conn) (data->set.encoding && *data->set.encoding && conn->allocptr.accept_encoding)? conn->allocptr.accept_encoding:"", (data->change.referer && conn->allocptr.ref)?conn->allocptr.ref:"" /* Referer: <data> */, + (conn->bits.httpproxy && + !conn->bits.tunnel_proxy && + !checkheaders(data, "Proxy-Connection:"))? + "Proxy-Connection: Keep-Alive\r\n":"", te ); if(result) return result; +#if !defined(CURL_DISABLE_COOKIES) if(data->cookies || addcookies) { struct Cookie *co=NULL; /* no cookies from start */ int count=0; @@ -1619,7 +2033,7 @@ CURLcode Curl_http(struct connectdata *conn) Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); co = Curl_cookie_getlist(data->cookies, conn->allocptr.cookiehost? - conn->allocptr.cookiehost:host, ppath, + conn->allocptr.cookiehost:host, data->reqdata.path, (bool)(conn->protocol&PROT_HTTPS?TRUE:FALSE)); Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); } @@ -1660,9 +2074,10 @@ CURLcode Curl_http(struct connectdata *conn) if(result) return result; } +#endif if(data->set.timecondition) { - struct tm *thistime; + struct tm *tm; /* Phil Karn (Fri, 13 Apr 2001) pointed out that the If-Modified-Since * header family should have their times set in GMT as RFC2616 defines: @@ -1674,18 +2089,22 @@ CURLcode Curl_http(struct connectdata *conn) #ifdef HAVE_GMTIME_R /* thread-safe version */ struct tm keeptime; - thistime = (struct tm *)gmtime_r(&data->set.timevalue, &keeptime); + tm = (struct tm *)gmtime_r(&data->set.timevalue, &keeptime); #else - thistime = gmtime(&data->set.timevalue); + tm = gmtime(&data->set.timevalue); #endif -#ifdef HAVE_STRFTIME /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ - strftime(buf, BUFSIZE-1, "%a, %d %b %Y %H:%M:%S GMT", thistime); -#else - /* TODO: Right, we *could* write a replacement here */ - strcpy(buf, "no strftime() support"); -#endif + snprintf(buf, BUFSIZE-1, + "%s, %02d %s %4d %02d:%02d:%02d GMT", + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + switch(data->set.timecondition) { case CURL_TIMECOND_IFMODSINCE: default: @@ -1705,25 +2124,9 @@ CURLcode Curl_http(struct connectdata *conn) return result; } - while(headers) { - ptr = strchr(headers->data, ':'); - if(ptr) { - /* we require a colon for this to be a true header */ - - ptr++; /* pass the colon */ - while(*ptr && isspace((int)*ptr)) - ptr++; - - if(*ptr) { - /* only send this if the contents was non-blank */ - - result = add_bufferf(req_buffer, "%s\r\n", headers->data); - if(result) - return result; - } - } - headers = headers->next; - } + result = add_custom_headers(conn, req_buffer); + if(result) + return result; http->postdata = NULL; /* nothing to post at this point */ Curl_pgrsSetUploadSize(data, 0); /* upload size is 0 atm */ @@ -1735,6 +2138,24 @@ CURLcode Curl_http(struct connectdata *conn) switch(httpreq) { case HTTPREQ_POST_FORM: + if(!http->sendit || conn->bits.authneg) { + /* nothing to post! */ + result = add_bufferf(req_buffer, "Content-Length: 0\r\n\r\n"); + if(result) + return result; + + result = add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, FIRSTSOCKET); + if(result) + failf(data, "Failed sending POST request"); + else + /* setup variables for the upcoming transfer */ + result = Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, + &http->readbytecount, + -1, NULL); + break; + } + if(Curl_FormInit(&http->form, http->sendit)) { failf(data, "Internal HTTP POST error!"); return CURLE_HTTP_POST_ERROR; @@ -1755,23 +2176,13 @@ CURLcode Curl_http(struct connectdata *conn) return result; } - if(!checkheaders(data, "Expect:")) { - /* if not disabled explicitly we add a Expect: 100-continue - to the headers which actually speeds up post operations (as - there is one packet coming back from the web server) */ - result = add_bufferf(req_buffer, - "Expect: 100-continue\r\n"); - if(result) - return result; - data->set.expect100header = TRUE; - } + result = expect100(data, req_buffer); + if(result) + return result; - if(!checkheaders(data, "Content-Type:")) { - /* Get Content-Type: line from Curl_formpostheader. + { - The Content-Type header line also contains the MIME boundary - string etc why disabling this header is likely to not make things - work, but we support disabling it anyway. + /* Get Content-Type: line from Curl_formpostheader. */ char *contentType; size_t linelength=0; @@ -1781,6 +2192,7 @@ CURLcode Curl_http(struct connectdata *conn) failf(data, "Could not get Content-Type header line!"); return CURLE_HTTP_POST_ERROR; } + result = add_buffer(req_buffer, contentType, linelength); if(result) return result; @@ -1796,61 +2208,68 @@ CURLcode Curl_http(struct connectdata *conn) /* fire away the whole request to the server */ result = add_buffer_send(req_buffer, conn, - &data->info.request_size); + &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending POST request"); else /* setup variables for the upcoming transfer */ - result = Curl_Transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, - FIRSTSOCKET, - &http->writebytecount); + result = Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, + &http->readbytecount, + FIRSTSOCKET, + &http->writebytecount); + if(result) { - Curl_formclean(http->sendit); /* free that whole lot */ + Curl_formclean(&http->sendit); /* free that whole lot */ return result; } +#ifdef CURL_DOES_CONVERSIONS +/* time to convert the form data... */ + result = Curl_formconvert(data, http->sendit); + if(result) { + Curl_formclean(&http->sendit); /* free that whole lot */ + return result; + } +#endif /* CURL_DOES_CONVERSIONS */ break; case HTTPREQ_PUT: /* Let's PUT the data to the server! */ - if((data->set.infilesize>0) && !conn->bits.upload_chunky) { + if(conn->bits.authneg) + postsize = 0; + else + postsize = data->set.infilesize; + + if((postsize != -1) && !conn->bits.upload_chunky) { /* only add Content-Length if not uploading chunked */ result = add_bufferf(req_buffer, - "Content-Length: %" FORMAT_OFF_T "\r\n", /* size */ - data->set.infilesize ); + "Content-Length: %" FORMAT_OFF_T "\r\n", + postsize ); if(result) return result; } - if(!checkheaders(data, "Expect:")) { - /* if not disabled explicitly we add a Expect: 100-continue - to the headers which actually speeds up post operations (as - there is one packet coming back from the web server) */ - result = add_bufferf(req_buffer, - "Expect: 100-continue\r\n"); - if(result) - return result; - data->set.expect100header = TRUE; - } + result = expect100(data, req_buffer); + if(result) + return result; result = add_buffer(req_buffer, "\r\n", 2); /* end of headers */ if(result) return result; /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, data->set.infilesize); + Curl_pgrsSetUploadSize(data, postsize); /* this sends the buffer and frees all the buffer resources */ result = add_buffer_send(req_buffer, conn, - &data->info.request_size); + &data->info.request_size, 0, FIRSTSOCKET); if(result) - failf(data, "Failed sending POST request"); + failf(data, "Failed sending PUT request"); else /* prepare for transfer */ - result = Curl_Transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, - FIRSTSOCKET, - &http->writebytecount); + result = Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, + &http->readbytecount, + postsize?FIRSTSOCKET:-1, + postsize?&http->writebytecount:NULL); if(result) return result; break; @@ -1858,10 +2277,13 @@ CURLcode Curl_http(struct connectdata *conn) case HTTPREQ_POST: /* this is the simple POST, using x-www-form-urlencoded style */ - /* store the size of the postfields */ - postsize = data->set.postfieldsize? - data->set.postfieldsize: - (data->set.postfields?(curl_off_t)strlen(data->set.postfields):0); + if(conn->bits.authneg) + postsize = 0; + else + /* figure out the size of the postfields */ + postsize = (data->set.postfieldsize != -1)? + data->set.postfieldsize: + (data->set.postfields?(curl_off_t)strlen(data->set.postfields):0); if(!conn->bits.upload_chunky) { /* We only set Content-Length and allow a custom Content-Length if @@ -1888,15 +2310,24 @@ CURLcode Curl_http(struct connectdata *conn) if(data->set.postfields) { - if((data->state.authhost.done || data->state.authproxy.done ) - && (postsize < (100*1024))) { - /* If we're not done with the authentication phase, we don't expect - to actually send off any data yet. Hence, we delay the sending of - the body until we receive that friendly 100-continue response */ + /* for really small posts we don't use Expect: headers at all, and for + the somewhat bigger ones we allow the app to disable it */ + if(postsize > TINY_INITIAL_POST_SIZE) { + result = expect100(data, req_buffer); + if(result) + return result; + } + else + data->state.expect100header = FALSE; - /* The post data is less than 100K, then append it to the header. - This limit is no magic limit but only set to prevent really huge - POSTs to get the data duplicated with malloc() and family. */ + if(!data->state.expect100header && + (postsize < MAX_INITIAL_POST_SIZE)) { + /* if we don't use expect:-100 AND + postsize is less than MAX_INITIAL_POST_SIZE + + then append the post data to the HTTP request header. This limit + is no magic limit but only set to prevent really huge POSTs to + get the data duplicated with malloc() and family. */ result = add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ if(result) @@ -1907,6 +2338,7 @@ CURLcode Curl_http(struct connectdata *conn) already now to reduce the number if send() calls */ result = add_buffer(req_buffer, data->set.postfields, (size_t)postsize); + included_body = postsize; } else { /* Append the POST data chunky-style */ @@ -1916,8 +2348,9 @@ CURLcode Curl_http(struct connectdata *conn) (size_t)postsize); if(CURLE_OK == result) result = add_buffer(req_buffer, - "\r\n0\r\n\r\n", 7); /* end of a chunked - transfer stream */ + "\x0d\x0a\x30\x0d\x0a\x0d\x0a", 7); + /* CR LF 0 CR LF CR LF */ + included_body = postsize + 7; } if(result) return result; @@ -1935,37 +2368,30 @@ CURLcode Curl_http(struct connectdata *conn) /* set the upload size to the progress meter */ Curl_pgrsSetUploadSize(data, http->postsize); - if(!checkheaders(data, "Expect:")) { - /* if not disabled explicitly we add a Expect: 100-continue to the - headers which actually speeds up post operations (as there is - one packet coming back from the web server) */ - add_bufferf(req_buffer, - "Expect: 100-continue\r\n"); - data->set.expect100header = TRUE; - } - add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ } } else { add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, data->set.infilesize); + if(data->set.postfieldsize) { + /* set the upload size to the progress meter */ + Curl_pgrsSetUploadSize(data, postsize?postsize:-1); - /* set the pointer to mark that we will send the post body using - the read callback */ - http->postdata = (char *)&http->postdata; + /* set the pointer to mark that we will send the post body using + the read callback */ + http->postdata = (char *)&http->postdata; + } } /* issue the request */ - result = add_buffer_send(req_buffer, conn, - &data->info.request_size); + result = add_buffer_send(req_buffer, conn, &data->info.request_size, + (size_t)included_body, FIRSTSOCKET); if(result) failf(data, "Failed sending HTTP POST request"); else result = - Curl_Transfer(conn, FIRSTSOCKET, -1, TRUE, + Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, http->postdata?FIRSTSOCKET:-1, http->postdata?&http->writebytecount:NULL); @@ -1976,13 +2402,13 @@ CURLcode Curl_http(struct connectdata *conn) /* issue the request */ result = add_buffer_send(req_buffer, conn, - &data->info.request_size); + &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending HTTP request"); else /* HTTP GET/HEAD download: */ - result = Curl_Transfer(conn, FIRSTSOCKET, -1, TRUE, + result = Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, http->postdata?FIRSTSOCKET:-1, http->postdata?&http->writebytecount:NULL); |