diff options
Diffstat (limited to 'Utilities/cmcurl/lib/http.c')
-rw-r--r-- | Utilities/cmcurl/lib/http.c | 2456 |
1 files changed, 1328 insertions, 1128 deletions
diff --git a/Utilities/cmcurl/lib/http.c b/Utilities/cmcurl/lib/http.c index c232ed4..6f7f55d 100644 --- a/Utilities/cmcurl/lib/http.c +++ b/Utilities/cmcurl/lib/http.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2021, 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 @@ -45,6 +45,10 @@ #include <sys/param.h> #endif +#ifdef USE_HYPER +#include <hyper.h> +#endif + #include "urldata.h" #include <curl/curl.h> #include "transfer.h" @@ -60,6 +64,7 @@ #include "http_ntlm.h" #include "curl_ntlm_wb.h" #include "http_negotiate.h" +#include "http_aws_sigv4.h" #include "url.h" #include "share.h" #include "hostip.h" @@ -78,6 +83,7 @@ #include "strdup.h" #include "altsvc.h" #include "hsts.h" +#include "c-hyper.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -88,22 +94,25 @@ * Forward declarations. */ -static int http_getsock_do(struct connectdata *conn, +static int http_getsock_do(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); -static int http_should_fail(struct connectdata *conn); +static bool http_should_fail(struct Curl_easy *data); #ifndef CURL_DISABLE_PROXY -static CURLcode add_haproxy_protocol_header(struct connectdata *conn); +static CURLcode add_haproxy_protocol_header(struct Curl_easy *data); #endif #ifdef USE_SSL -static CURLcode https_connecting(struct connectdata *conn, bool *done); -static int https_getsock(struct connectdata *conn, +static CURLcode https_connecting(struct Curl_easy *data, bool *done); +static int https_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); #else #define https_connecting(x,y) CURLE_COULDNT_CONNECT #endif -static CURLcode http_setup_conn(struct connectdata *conn); +static CURLcode http_setup_conn(struct Curl_easy *data, + struct connectdata *conn); /* * HTTP handler interface. @@ -159,19 +168,19 @@ const struct Curl_handler Curl_handler_https = { }; #endif -static CURLcode http_setup_conn(struct connectdata *conn) +static CURLcode http_setup_conn(struct Curl_easy *data, + struct connectdata *conn) { /* allocate the HTTP-specific struct for the Curl_easy, only to survive during this request */ struct HTTP *http; - struct Curl_easy *data = conn->data; DEBUGASSERT(data->req.p.http == NULL); http = calloc(1, sizeof(struct HTTP)); if(!http) return CURLE_OUT_OF_MEMORY; - Curl_mime_initpart(&http->form, conn->data); + Curl_mime_initpart(&http->form, data); data->req.p.http = http; if(data->set.httpversion == CURL_HTTP_VERSION_3) { @@ -199,16 +208,16 @@ static CURLcode http_setup_conn(struct connectdata *conn) * if proxy headers are not available, then it will lookup into http header * link list * - * It takes a connectdata struct as input instead of the Curl_easy simply to - * know if this is a proxy request or not, as it then might check a different - * header list. Provide the header prefix without colon!. + * It takes a connectdata struct as input to see if this is a proxy request or + * not, as it then might check a different header list. Provide the header + * prefix without colon! */ -char *Curl_checkProxyheaders(const struct connectdata *conn, +char *Curl_checkProxyheaders(struct Curl_easy *data, + const struct connectdata *conn, const char *thisheader) { struct curl_slist *head; size_t thislen = strlen(thisheader); - struct Curl_easy *data = conn->data; for(head = (conn->bits.proxy && data->set.sep_headers) ? data->set.proxyheaders : data->set.headers; @@ -222,7 +231,7 @@ char *Curl_checkProxyheaders(const struct connectdata *conn, } #else /* disabled */ -#define Curl_checkProxyheaders(x,y) NULL +#define Curl_checkProxyheaders(x,y,z) NULL #endif /* @@ -285,11 +294,11 @@ char *Curl_copy_header_value(const char *header) * * Returns CURLcode. */ -static CURLcode http_output_basic(struct connectdata *conn, bool proxy) +static CURLcode http_output_basic(struct Curl_easy *data, bool proxy) { size_t size = 0; char *authorization = NULL; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; char **userp; const char *user; const char *pwd; @@ -345,16 +354,15 @@ static CURLcode http_output_basic(struct connectdata *conn, bool proxy) * * Returns CURLcode. */ -static CURLcode http_output_bearer(struct connectdata *conn) +static CURLcode http_output_bearer(struct Curl_easy *data) { char **userp; CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; userp = &data->state.aptr.userpwd; free(*userp); *userp = aprintf("Authorization: Bearer %s\r\n", - conn->data->set.str[STRING_BEARER]); + data->set.str[STRING_BEARER]); if(!*userp) { result = CURLE_OUT_OF_MEMORY; @@ -393,6 +401,8 @@ static bool pickoneauth(struct auth *pick, unsigned long mask) pick->picked = CURLAUTH_NTLM_WB; else if(avail & CURLAUTH_BASIC) pick->picked = CURLAUTH_BASIC; + else if(avail & CURLAUTH_AWS_SIGV4) + pick->picked = CURLAUTH_AWS_SIGV4; else { pick->picked = CURLAUTH_PICKNONE; /* we select to use nothing */ picked = FALSE; @@ -425,9 +435,9 @@ static bool pickoneauth(struct auth *pick, unsigned long mask) * } * } */ -static CURLcode http_perhapsrewind(struct connectdata *conn) +static CURLcode http_perhapsrewind(struct Curl_easy *data, + struct connectdata *conn) { - struct Curl_easy *data = conn->data; struct HTTP *http = data->req.p.http; curl_off_t bytessent; curl_off_t expectsend = -1; /* default is unknown */ @@ -545,7 +555,7 @@ static CURLcode http_perhapsrewind(struct connectdata *conn) if(bytessent) /* we rewind now at once since if we already sent something */ - return Curl_readrewind(conn); + return Curl_readrewind(data); return CURLE_OK; } @@ -557,9 +567,9 @@ static CURLcode http_perhapsrewind(struct connectdata *conn) * picked. */ -CURLcode Curl_http_auth_act(struct connectdata *conn) +CURLcode Curl_http_auth_act(struct Curl_easy *data) { - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; bool pickhost = FALSE; bool pickproxy = FALSE; CURLcode result = CURLE_OK; @@ -585,7 +595,7 @@ CURLcode Curl_http_auth_act(struct connectdata *conn) conn->httpversion > 11) { infof(data, "Forcing HTTP/1.1 for NTLM"); connclose(conn, "Force HTTP/1.1 connection"); - conn->data->set.httpversion = CURL_HTTP_VERSION_1_1; + data->set.httpversion = CURL_HTTP_VERSION_1_1; } } #ifndef CURL_DISABLE_PROXY @@ -603,7 +613,7 @@ CURLcode Curl_http_auth_act(struct connectdata *conn) if((data->state.httpreq != HTTPREQ_GET) && (data->state.httpreq != HTTPREQ_HEAD) && !conn->bits.rewindaftersend) { - result = http_perhapsrewind(conn); + result = http_perhapsrewind(data, conn); if(result) return result; } @@ -630,7 +640,7 @@ CURLcode Curl_http_auth_act(struct connectdata *conn) data->state.authhost.done = TRUE; } } - if(http_should_fail(conn)) { + if(http_should_fail(data)) { failf(data, "The requested URL returned error: %d", data->req.httpcode); result = CURLE_HTTP_RETURNED_ERROR; @@ -645,7 +655,8 @@ CURLcode Curl_http_auth_act(struct connectdata *conn) * and whether or not it is to a proxy. */ static CURLcode -output_auth_headers(struct connectdata *conn, +output_auth_headers(struct Curl_easy *data, + struct connectdata *conn, struct auth *authstatus, const char *request, const char *path, @@ -653,17 +664,24 @@ output_auth_headers(struct connectdata *conn, { const char *auth = NULL; CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; #ifdef CURL_DISABLE_CRYPTO_AUTH (void)request; (void)path; #endif - +#ifndef CURL_DISABLE_CRYPTO_AUTH + if(authstatus->picked == CURLAUTH_AWS_SIGV4) { + auth = "AWS_SIGV4"; + result = Curl_output_aws_sigv4(data, proxy); + if(result) + return result; + } + else +#endif #ifdef USE_SPNEGO if(authstatus->picked == CURLAUTH_NEGOTIATE) { auth = "Negotiate"; - result = Curl_output_negotiate(conn, proxy); + result = Curl_output_negotiate(data, conn, proxy); if(result) return result; } @@ -672,7 +690,7 @@ output_auth_headers(struct connectdata *conn, #ifdef USE_NTLM if(authstatus->picked == CURLAUTH_NTLM) { auth = "NTLM"; - result = Curl_output_ntlm(conn, proxy); + result = Curl_output_ntlm(data, proxy); if(result) return result; } @@ -681,7 +699,7 @@ output_auth_headers(struct connectdata *conn, #if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) if(authstatus->picked == CURLAUTH_NTLM_WB) { auth = "NTLM_WB"; - result = Curl_output_ntlm_wb(conn, proxy); + result = Curl_output_ntlm_wb(data, conn, proxy); if(result) return result; } @@ -690,7 +708,8 @@ output_auth_headers(struct connectdata *conn, #ifndef CURL_DISABLE_CRYPTO_AUTH if(authstatus->picked == CURLAUTH_DIGEST) { auth = "Digest"; - result = Curl_output_digest(conn, + result = Curl_output_digest(data, + conn, proxy, (const unsigned char *)request, (const unsigned char *)path); @@ -704,12 +723,12 @@ output_auth_headers(struct connectdata *conn, if( #ifndef CURL_DISABLE_PROXY (proxy && conn->bits.proxy_user_passwd && - !Curl_checkProxyheaders(conn, "Proxy-authorization")) || + !Curl_checkProxyheaders(data, conn, "Proxy-authorization")) || #endif (!proxy && conn->bits.user_passwd && - !Curl_checkheaders(conn, "Authorization"))) { + !Curl_checkheaders(data, "Authorization"))) { auth = "Basic"; - result = http_output_basic(conn, proxy); + result = http_output_basic(data, proxy); if(result) return result; } @@ -721,9 +740,9 @@ output_auth_headers(struct connectdata *conn, if(authstatus->picked == CURLAUTH_BEARER) { /* Bearer */ if((!proxy && data->set.str[STRING_BEARER] && - !Curl_checkheaders(conn, "Authorization:"))) { + !Curl_checkheaders(data, "Authorization:"))) { auth = "Bearer"; - result = http_output_bearer(conn); + result = http_output_bearer(data); if(result) return result; } @@ -754,7 +773,7 @@ output_auth_headers(struct connectdata *conn, /** * Curl_http_output_auth() setups the authentication headers for the * host/proxy and the correct authentication - * method. conn->data->state.authdone is set to TRUE when authentication is + * method. data->state.authdone is set to TRUE when authentication is * done. * * @param conn all information about the current connection @@ -766,14 +785,15 @@ output_auth_headers(struct connectdata *conn, * @returns CURLcode */ CURLcode -Curl_http_output_auth(struct connectdata *conn, +Curl_http_output_auth(struct Curl_easy *data, + struct connectdata *conn, const char *request, + Curl_HttpReq httpreq, const char *path, bool proxytunnel) /* TRUE if this is the request setting up the proxy tunnel */ { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; struct auth *authhost; struct auth *authproxy; @@ -810,7 +830,7 @@ Curl_http_output_auth(struct connectdata *conn, /* Send proxy authentication header if needed */ if(conn->bits.httpproxy && (conn->bits.tunnel_proxy == (bit)proxytunnel)) { - result = output_auth_headers(conn, authproxy, request, path, TRUE); + result = output_auth_headers(data, conn, authproxy, request, path, TRUE); if(result) return result; } @@ -829,11 +849,22 @@ Curl_http_output_auth(struct connectdata *conn, !data->state.first_host || data->set.allow_auth_to_other_hosts || strcasecompare(data->state.first_host, conn->host.name)) { - result = output_auth_headers(conn, authhost, request, path, FALSE); + result = output_auth_headers(data, conn, authhost, request, path, FALSE); } else authhost->done = TRUE; + if(((authhost->multipass && !authhost->done) || + (authproxy->multipass && !authproxy->done)) && + (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.authneg = FALSE; + return result; } @@ -859,14 +890,13 @@ Curl_http_output_auth(struct connectdata *conn, * proxy CONNECT loop. */ -CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, +CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth) /* the first non-space */ { /* * This resource requires authentication */ - struct Curl_easy *data = conn->data; - + struct connectdata *conn = data->conn; #ifdef USE_SPNEGO curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state : &conn->http_negotiate_state; @@ -874,6 +904,8 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, unsigned long *availp; struct auth *authp; + (void) conn; /* In case conditionals make it unused. */ + if(proxy) { availp = &data->info.proxyauthavail; authp = &data->state.authproxy; @@ -908,7 +940,7 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, authp->avail |= CURLAUTH_NEGOTIATE; if(authp->picked == CURLAUTH_NEGOTIATE) { - CURLcode result = Curl_input_negotiate(conn, proxy, auth); + CURLcode result = Curl_input_negotiate(data, conn, proxy, auth); if(!result) { DEBUGASSERT(!data->req.newurl); data->req.newurl = strdup(data->change.url); @@ -937,7 +969,7 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, if(authp->picked == CURLAUTH_NTLM || authp->picked == CURLAUTH_NTLM_WB) { /* NTLM authentication is picked and activated */ - CURLcode result = Curl_input_ntlm(conn, proxy, auth); + CURLcode result = Curl_input_ntlm(data, proxy, auth); if(!result) { data->state.authproblem = FALSE; #ifdef NTLM_WB_ENABLED @@ -947,7 +979,7 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, *availp |= CURLAUTH_NTLM_WB; authp->avail |= CURLAUTH_NTLM_WB; - result = Curl_input_ntlm_wb(conn, proxy, auth); + result = Curl_input_ntlm_wb(data, conn, proxy, auth); if(result) { infof(data, "Authentication problem. Ignoring this.\n"); data->state.authproblem = TRUE; @@ -978,7 +1010,7 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, * authentication isn't activated yet, as we need to store the * incoming data from this header in case we are going to use * Digest */ - result = Curl_input_digest(conn, proxy, auth); + result = Curl_input_digest(data, proxy, auth); if(result) { infof(data, "Authentication problem. Ignoring this.\n"); data->state.authproblem = TRUE; @@ -1030,18 +1062,15 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, * * @param conn all information about the current connection * - * @retval 0 communications should continue + * @retval FALSE communications should continue * - * @retval 1 communications should not continue + * @retval TRUE communications should not continue */ -static int http_should_fail(struct connectdata *conn) +static bool http_should_fail(struct Curl_easy *data) { - struct Curl_easy *data; int httpcode; - - DEBUGASSERT(conn); - data = conn->data; DEBUGASSERT(data); + DEBUGASSERT(data->conn); httpcode = data->req.httpcode; @@ -1050,20 +1079,20 @@ static int http_should_fail(struct connectdata *conn) ** don't fail. */ if(!data->set.http_fail_on_error) - return 0; + return FALSE; /* ** Any code < 400 is never terminal. */ if(httpcode < 400) - return 0; + return FALSE; /* ** Any code >= 400 that's not 401 or 407 is always ** a terminal error */ if((httpcode != 401) && (httpcode != 407)) - return 1; + return TRUE; /* ** All we have left to deal with is 401 and 407 @@ -1088,16 +1117,17 @@ static int http_should_fail(struct connectdata *conn) ** Either we're not authenticating, or we're supposed to ** be authenticating something else. This is an error. */ - if((httpcode == 401) && !conn->bits.user_passwd) + if((httpcode == 401) && !data->conn->bits.user_passwd) return TRUE; #ifndef CURL_DISABLE_PROXY - if((httpcode == 407) && !conn->bits.proxy_user_passwd) + if((httpcode == 407) && !data->conn->bits.proxy_user_passwd) return TRUE; #endif return data->state.authproblem; } +#ifndef USE_HYPER /* * readmoredata() is a "fread() emulation" to provide POST and/or request * data. It is used when a huge POST is to be made and the entire chunk wasn't @@ -1111,8 +1141,8 @@ static size_t readmoredata(char *buffer, size_t nitems, void *userp) { - struct connectdata *conn = (struct connectdata *)userp; - struct HTTP *http = conn->data->req.p.http; + struct Curl_easy *data = (struct Curl_easy *)userp; + struct HTTP *http = data->req.p.http; size_t fullsize = size * nitems; if(!http->postsize) @@ -1120,7 +1150,7 @@ static size_t readmoredata(char *buffer, return 0; /* make sure that a HTTP request is never sent away chunked! */ - conn->data->req.forbidchunk = (http->sending == HTTPSEND_REQUEST)?TRUE:FALSE; + data->req.forbidchunk = (http->sending == HTTPSEND_REQUEST)?TRUE:FALSE; if(http->postsize <= (curl_off_t)fullsize) { memcpy(buffer, http->postdata, (size_t)http->postsize); @@ -1130,8 +1160,8 @@ static size_t readmoredata(char *buffer, /* move backup data into focus and continue on that */ http->postdata = http->backup.postdata; http->postsize = http->backup.postsize; - conn->data->state.fread_func = http->backup.fread_func; - conn->data->state.in = http->backup.fread_in; + data->state.fread_func = http->backup.fread_func; + data->state.in = http->backup.fread_in; http->sending++; /* move one step up */ @@ -1157,7 +1187,7 @@ static size_t readmoredata(char *buffer, * Returns CURLcode */ CURLcode Curl_buffer_send(struct dynbuf *in, - struct connectdata *conn, + struct Curl_easy *data, /* add the number of sent bytes to this counter */ curl_off_t *bytes_written, @@ -1169,7 +1199,7 @@ CURLcode Curl_buffer_send(struct dynbuf *in, CURLcode result; char *ptr; size_t size; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; struct HTTP *http = data->req.p.http; size_t sendsize; curl_socket_t sockfd; @@ -1229,7 +1259,9 @@ CURLcode Curl_buffer_send(struct dynbuf *in, } else { #ifdef CURLDEBUG - /* Allow debug builds override this logic to force short initial sends */ + /* Allow debug builds to override this logic to force short initial + sends + */ char *p = getenv("CURL_SMALLREQSEND"); if(p) { size_t altsize = (size_t)strtoul(p, NULL, 10); @@ -1243,7 +1275,7 @@ CURLcode Curl_buffer_send(struct dynbuf *in, sendsize = size; } - result = Curl_write(conn, sockfd, ptr, sendsize, &amount); + result = Curl_write(data, sockfd, ptr, sendsize, &amount); if(!result) { /* @@ -1290,10 +1322,13 @@ CURLcode Curl_buffer_send(struct dynbuf *in, /* set the new pointers for the request-sending */ data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)conn; + data->state.in = (void *)data; http->postdata = ptr; http->postsize = (curl_off_t)size; + /* this much data is remaining header: */ + data->req.pendingheader = headersize - headlen; + http->send_buffer = *in; /* copy the whole struct */ http->sending = HTTPSEND_REQUEST; @@ -1316,9 +1351,13 @@ CURLcode Curl_buffer_send(struct dynbuf *in, } Curl_dyn_free(in); + /* no remaining header data */ + data->req.pendingheader = 0; return result; } +#endif + /* end of the add_buffer functions */ /* ------------------------------------------------------------------------- */ @@ -1383,9 +1422,10 @@ Curl_compareheader(const char *headerline, /* line to check */ * Curl_http_connect() performs HTTP stuff to do at connect-time, called from * the generic Curl_connect(). */ -CURLcode Curl_http_connect(struct connectdata *conn, bool *done) +CURLcode Curl_http_connect(struct Curl_easy *data, bool *done) { CURLcode result; + struct connectdata *conn = data->conn; /* We default to persistent connections. We set this already in this connect function to make the re-use checks properly be able to check this bit. */ @@ -1393,7 +1433,7 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done) #ifndef CURL_DISABLE_PROXY /* the CONNECT procedure might not have been completed */ - result = Curl_proxy_connect(conn, FIRSTSOCKET); + result = Curl_proxy_connect(data, FIRSTSOCKET); if(result) return result; @@ -1408,9 +1448,9 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done) /* nothing else to do except wait right now - we're not done here. */ return CURLE_OK; - if(conn->data->set.haproxyprotocol) { + if(data->set.haproxyprotocol) { /* add HAProxy PROXY protocol header */ - result = add_haproxy_protocol_header(conn); + result = add_haproxy_protocol_header(data); if(result) return result; } @@ -1418,7 +1458,7 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done) if(conn->given->protocol & CURLPROTO_HTTPS) { /* perform SSL initialization */ - result = https_connecting(conn, done); + result = https_connecting(data, done); if(result) return result; } @@ -1431,24 +1471,27 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done) /* this returns the socket to wait for in the DO and DOING state for the multi interface and then we're always _sending_ a request and thus we wait for the single socket to become writable only */ -static int http_getsock_do(struct connectdata *conn, +static int http_getsock_do(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) { /* write mode */ + (void)data; socks[0] = conn->sock[FIRSTSOCKET]; return GETSOCK_WRITESOCK(0); } #ifndef CURL_DISABLE_PROXY -static CURLcode add_haproxy_protocol_header(struct connectdata *conn) +static CURLcode add_haproxy_protocol_header(struct Curl_easy *data) { char proxy_header[128]; struct dynbuf req; CURLcode result; char tcp_version[5]; + DEBUGASSERT(data->conn); /* Emit the correct prefix for IPv6 */ - if(conn->bits.ipv6) { + if(data->conn->bits.ipv6) { strcpy(tcp_version, "TCP6"); } else { @@ -1459,10 +1502,10 @@ static CURLcode add_haproxy_protocol_header(struct connectdata *conn) sizeof(proxy_header), "PROXY %s %s %s %li %li\r\n", tcp_version, - conn->data->info.conn_local_ip, - conn->data->info.conn_primary_ip, - conn->data->info.conn_local_port, - conn->data->info.conn_primary_port); + data->info.conn_local_ip, + data->info.conn_primary_ip, + data->info.conn_local_port, + data->info.conn_primary_port); Curl_dyn_init(&req, DYN_HAXPROXY); @@ -1470,7 +1513,7 @@ static CURLcode add_haproxy_protocol_header(struct connectdata *conn) if(result) return result; - result = Curl_buffer_send(&req, conn, &conn->data->info.request_size, + result = Curl_buffer_send(&req, data, &data->info.request_size, 0, FIRSTSOCKET); return result; @@ -1478,10 +1521,11 @@ static CURLcode add_haproxy_protocol_header(struct connectdata *conn) #endif #ifdef USE_SSL -static CURLcode https_connecting(struct connectdata *conn, bool *done) +static CURLcode https_connecting(struct Curl_easy *data, bool *done) { CURLcode result; - DEBUGASSERT((conn) && (conn->handler->flags & PROTOPT_SSL)); + struct connectdata *conn = data->conn; + DEBUGASSERT((data) && (data->conn->handler->flags & PROTOPT_SSL)); #ifdef ENABLE_QUIC if(conn->transport == TRNSPRT_QUIC) { @@ -1491,16 +1535,18 @@ static CURLcode https_connecting(struct connectdata *conn, bool *done) #endif /* perform SSL initialization for this socket */ - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, done); + result = Curl_ssl_connect_nonblocking(data, conn, FIRSTSOCKET, done); if(result) connclose(conn, "Failed HTTPS connection"); return result; } -static int https_getsock(struct connectdata *conn, +static int https_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) { + (void)data; if(conn->handler->flags & PROTOPT_SSL) return Curl_ssl_getsock(conn, socks); return GETSOCK_BLANK; @@ -1512,10 +1558,10 @@ static int https_getsock(struct connectdata *conn, * performed. */ -CURLcode Curl_http_done(struct connectdata *conn, +CURLcode Curl_http_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; struct HTTP *http = data->req.p.http; /* Clear multipass flag. If authentication isn't done yet, then it will get @@ -1523,7 +1569,7 @@ CURLcode Curl_http_done(struct connectdata *conn, data->state.authhost.multipass = FALSE; data->state.authproxy.multipass = FALSE; - Curl_unencode_cleanup(conn); + Curl_unencode_cleanup(data); /* set the proper values (possibly modified on POST) */ conn->seek_func = data->set.seek_func; /* restore */ @@ -1537,6 +1583,7 @@ CURLcode Curl_http_done(struct connectdata *conn, Curl_quic_done(data, premature); Curl_mime_cleanpart(&http->form); Curl_dyn_reset(&data->state.headerb); + Curl_hyper_done(data); if(status) return status; @@ -1552,6 +1599,8 @@ CURLcode Curl_http_done(struct connectdata *conn, read from the HTTP server (that counts), this can't be right so we return an error here */ failf(data, "Empty reply from server"); + /* Mark it as closed to avoid the "left intact" message */ + streamclose(conn, "Empty reply from server"); return CURLE_GOT_NOTHING; } @@ -1579,6 +1628,7 @@ static bool use_http_1_1plus(const struct Curl_easy *data, (data->set.httpversion >= CURL_HTTP_VERSION_1_1)); } +#ifndef USE_HYPER static const char *get_http_string(const struct Curl_easy *data, const struct connectdata *conn) { @@ -1598,6 +1648,7 @@ static const char *get_http_string(const struct Curl_easy *data, return "1.0"; } +#endif /* check and possibly add an Expect: header */ static CURLcode expect100(struct Curl_easy *data, @@ -1612,7 +1663,7 @@ static CURLcode expect100(struct Curl_easy *data, /* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an Expect: 100-continue to the headers which actually speeds up post operations (as there is one packet coming back from the web server) */ - const char *ptr = Curl_checkheaders(conn, "Expect"); + const char *ptr = Curl_checkheaders(data, "Expect"); if(ptr) { data->state.expect100header = Curl_compareheader(ptr, "Expect:", "100-continue"); @@ -1678,15 +1729,20 @@ CURLcode Curl_http_compile_trailers(struct curl_slist *trailers, return result; } -CURLcode Curl_add_custom_headers(struct connectdata *conn, +CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect, - struct dynbuf *req) +#ifndef USE_HYPER + struct dynbuf *req +#else + void *req +#endif + ) { + struct connectdata *conn = data->conn; char *ptr; struct curl_slist *h[2]; struct curl_slist *headers; int numlists = 1; /* by default */ - struct Curl_easy *data = conn->data; int i; #ifndef CURL_DISABLE_PROXY @@ -1747,7 +1803,9 @@ CURLcode Curl_add_custom_headers(struct connectdata *conn, /* copy the source */ semicolonp = strdup(headers->data); if(!semicolonp) { +#ifndef USE_HYPER Curl_dyn_free(req); +#endif return CURLE_OUT_OF_MEMORY; } /* put a colon where the semicolon is */ @@ -1808,7 +1866,11 @@ CURLcode Curl_add_custom_headers(struct connectdata *conn, !strcasecompare(data->state.first_host, conn->host.name))) ; else { +#ifdef USE_HYPER + result = Curl_hyper_header(data, req, compare); +#else result = Curl_dyn_addf(req, "%s\r\n", compare); +#endif } if(semicolonp) free(semicolonp); @@ -1824,10 +1886,14 @@ CURLcode Curl_add_custom_headers(struct connectdata *conn, } #ifndef CURL_DISABLE_PARSEDATE -CURLcode Curl_add_timecondition(const struct connectdata *conn, - struct dynbuf *req) +CURLcode Curl_add_timecondition(struct Curl_easy *data, +#ifndef USE_HYPER + struct dynbuf *req +#else + void *req +#endif + ) { - struct Curl_easy *data = conn->data; const struct tm *tm; struct tm keeptime; CURLcode result; @@ -1860,7 +1926,7 @@ CURLcode Curl_add_timecondition(const struct connectdata *conn, break; } - if(Curl_checkheaders(conn, condp)) { + if(Curl_checkheaders(data, condp)) { /* A custom header was specified; it will be sent instead. */ return CURLE_OK; } @@ -1884,7 +1950,11 @@ CURLcode Curl_add_timecondition(const struct connectdata *conn, tm->tm_min, tm->tm_sec); +#ifndef USE_HYPER result = Curl_dyn_add(req, datestr); +#else + result = Curl_hyper_header(data, req, datestr); +#endif return result; } @@ -1899,102 +1969,14 @@ CURLcode Curl_add_timecondition(const struct connectdata *conn, } #endif -/* - * Curl_http() gets called from the generic multi_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, bool *done) +void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, + const char **method, Curl_HttpReq *reqp) { - struct Curl_easy *data = conn->data; - CURLcode result = CURLE_OK; - struct HTTP *http; - const char *path = data->state.up.path; - const char *query = data->state.up.query; - bool paste_ftp_userpwd = FALSE; - char ftp_typecode[sizeof("/;type=?")] = ""; - const char *host = conn->host.name; - const char *te = ""; /* transfer-encoding */ - const char *ptr; - const char *request; Curl_HttpReq httpreq = data->state.httpreq; -#if !defined(CURL_DISABLE_COOKIES) - char *addcookies = NULL; -#endif - curl_off_t included_body = 0; - const char *httpstring; - struct dynbuf req; - curl_off_t postsize = 0; /* curl_off_t to handle large file sizes */ - char *altused = NULL; - - /* 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(conn->transport != TRNSPRT_QUIC) { - if(conn->httpversion < 20) { /* unless the connection is re-used and - already http2 */ - switch(conn->negnpn) { - case CURL_HTTP_VERSION_2: - conn->httpversion = 20; /* we know we're on HTTP/2 now */ - - result = Curl_http2_switched(conn, NULL, 0); - if(result) - return result; - break; - case CURL_HTTP_VERSION_1_1: - /* continue with HTTP/1.1 when explicitly requested */ - break; - default: - /* Check if user wants to use HTTP/2 with clear TCP*/ -#ifdef USE_NGHTTP2 - if(conn->data->set.httpversion == - CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - /* We don't support HTTP/2 proxies yet. Also it's debatable - whether or not this setting should apply to HTTP/2 proxies. */ - infof(data, "Ignoring HTTP/2 prior knowledge due to proxy\n"); - break; - } -#endif - DEBUGF(infof(data, "HTTP/2 over clean TCP\n")); - conn->httpversion = 20; - - result = Curl_http2_switched(conn, NULL, 0); - if(result) - return result; - } -#endif - break; - } - } - else { - /* prepare for a http2 request */ - result = Curl_http2_setup(conn); - if(result) - return result; - } - } - http = data->req.p.http; - DEBUGASSERT(http); - - if(!data->state.this_is_a_follow) { - /* 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; - - data->state.first_remote_port = conn->remote_port; - } - + const char *request; if((conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_FTP)) && - data->set.upload) { + data->set.upload) httpreq = HTTPREQ_PUT; - } /* Now set the 'request' pointer to the proper request string */ if(data->set.str[STRING_CUSTOMREQUEST]) @@ -2003,7 +1985,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) if(data->set.opt_no_body) request = "HEAD"; else { - DEBUGASSERT((httpreq > HTTPREQ_NONE) && (httpreq < HTTPREQ_LAST)); + DEBUGASSERT((httpreq >= HTTPREQ_GET) && (httpreq <= HTTPREQ_HEAD)); switch(httpreq) { case HTTPREQ_POST: case HTTPREQ_POST_FORM: @@ -2023,180 +2005,40 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) } } } + *method = request; + *reqp = httpreq; +} +CURLcode Curl_http_useragent(struct Curl_easy *data) +{ /* The User-Agent string might have been allocated in url.c already, because it might have been used in the proxy connect, but if we have got a header with the user-agent string specified, we erase the previously made string here. */ - if(Curl_checkheaders(conn, "User-Agent")) { + if(Curl_checkheaders(data, "User-Agent")) { free(data->state.aptr.uagent); data->state.aptr.uagent = NULL; } + return CURLE_OK; +} - /* setup the authentication headers */ - { - char *pq = NULL; - if(query && *query) { - pq = aprintf("%s?%s", path, query); - if(!pq) - return CURLE_OUT_OF_MEMORY; - } - result = Curl_http_output_auth(conn, request, (pq ? pq : path), FALSE); - free(pq); - if(result) - return result; - } - - if(((data->state.authhost.multipass && !data->state.authhost.done) - || (data->state.authproxy.multipass && !data->state.authproxy.done)) && - (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.authneg = FALSE; - - Curl_safefree(data->state.aptr.ref); - if(data->change.referer && !Curl_checkheaders(conn, "Referer")) { - data->state.aptr.ref = aprintf("Referer: %s\r\n", data->change.referer); - if(!data->state.aptr.ref) - return CURLE_OUT_OF_MEMORY; - } - else - data->state.aptr.ref = NULL; - -#if !defined(CURL_DISABLE_COOKIES) - if(data->set.str[STRING_COOKIE] && !Curl_checkheaders(conn, "Cookie")) - addcookies = data->set.str[STRING_COOKIE]; -#endif - - if(!Curl_checkheaders(conn, "Accept-Encoding") && - data->set.str[STRING_ENCODING]) { - Curl_safefree(data->state.aptr.accept_encoding); - data->state.aptr.accept_encoding = - aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); - if(!data->state.aptr.accept_encoding) - return CURLE_OUT_OF_MEMORY; - } - else { - Curl_safefree(data->state.aptr.accept_encoding); - data->state.aptr.accept_encoding = NULL; - } - -#ifdef HAVE_LIBZ - /* we only consider transfer-encoding magic if libz support is built-in */ - - if(!Curl_checkheaders(conn, "TE") && - data->set.http_transfer_encoding) { - /* When we are to insert a TE: header in the request, we must also insert - TE in a Connection: header, so we need to merge the custom provided - Connection: header and prevent the original to get sent. Note that if - the user has inserted his/hers own TE: header we don't do this magic - but then assume that the user will handle it all! */ - char *cptr = Curl_checkheaders(conn, "Connection"); -#define TE_HEADER "TE: gzip\r\n" - - Curl_safefree(data->state.aptr.te); - if(cptr) { - cptr = Curl_copy_header_value(cptr); - if(!cptr) - return CURLE_OUT_OF_MEMORY; - } - - /* Create the (updated) Connection: header */ - data->state.aptr.te = aprintf("Connection: %s%sTE\r\n" TE_HEADER, - cptr ? cptr : "", (cptr && *cptr) ? ", ":""); +CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn) +{ + const char *ptr; + if(!data->state.this_is_a_follow) { + /* Free to avoid leaking memory on multiple requests*/ + free(data->state.first_host); - free(cptr); - if(!data->state.aptr.te) + data->state.first_host = strdup(conn->host.name); + if(!data->state.first_host) return CURLE_OUT_OF_MEMORY; - } -#endif - - switch(httpreq) { - case HTTPREQ_POST_MIME: - http->sendit = &data->set.mimepost; - break; - case HTTPREQ_POST_FORM: - /* Convert the form structure into a mime structure. */ - Curl_mime_cleanpart(&http->form); - result = Curl_getformdata(data, &http->form, data->set.httppost, - data->state.fread_func); - if(result) - return result; - http->sendit = &http->form; - break; - default: - http->sendit = NULL; - } - -#ifndef CURL_DISABLE_MIME - if(http->sendit) { - const char *cthdr = Curl_checkheaders(conn, "Content-Type"); - - /* Read and seek body only. */ - http->sendit->flags |= MIME_BODY_ONLY; - - /* Prepare the mime structure headers & set content type. */ - - if(cthdr) - for(cthdr += 13; *cthdr == ' '; cthdr++) - ; - else if(http->sendit->kind == MIMEKIND_MULTIPART) - cthdr = "multipart/form-data"; - - curl_mime_headers(http->sendit, data->set.headers, 0); - result = Curl_mime_prepare_headers(http->sendit, cthdr, - NULL, MIMESTRATEGY_FORM); - curl_mime_headers(http->sendit, NULL, 0); - if(!result) - result = Curl_mime_rewind(http->sendit); - if(result) - return result; - http->postsize = Curl_mime_size(http->sendit); - } -#endif - ptr = Curl_checkheaders(conn, "Transfer-Encoding"); - if(ptr) { - /* Some kind of TE is requested, check if 'chunked' is chosen */ - data->req.upload_chunky = - Curl_compareheader(ptr, "Transfer-Encoding:", "chunked"); - } - else { - if((conn->handler->protocol & PROTO_FAMILY_HTTP) && - (((httpreq == HTTPREQ_POST_MIME || httpreq == HTTPREQ_POST_FORM) && - http->postsize < 0) || - ((data->set.upload || httpreq == HTTPREQ_POST) && - data->state.infilesize == -1))) { - if(conn->bits.authneg) - /* don't enable chunked during auth neg */ - ; - else if(use_http_1_1plus(data, conn)) { - if(conn->httpversion < 20) - /* HTTP, upload, unknown file size and not HTTP 1.0 */ - data->req.upload_chunky = TRUE; - } - else { - failf(data, "Chunky upload is not supported by HTTP 1.0"); - return CURLE_UPLOAD_FAILED; - } - } - else { - /* else, no chunky upload */ - data->req.upload_chunky = FALSE; - } - - if(data->req.upload_chunky) - te = "Transfer-Encoding: chunked\r\n"; + data->state.first_remote_port = conn->remote_port; } - Curl_safefree(data->state.aptr.host); - ptr = Curl_checkheaders(conn, "Host"); + ptr = Curl_checkheaders(data, "Host"); if(ptr && (!data->state.this_is_a_follow || strcasecompare(data->state.first_host, conn->host.name))) { #if !defined(CURL_DISABLE_COOKIES) @@ -2246,6 +2088,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) else { /* When building Host: headers, we must put the host name within [brackets] if the host name is a plain IPv6-address. RFC2732-style. */ + const char *host = conn->host.name; if(((conn->given->protocol&CURLPROTO_HTTPS) && (conn->remote_port == PORT_HTTPS)) || @@ -2268,6 +2111,24 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) /* without Host: we can't make a nice request */ return CURLE_OUT_OF_MEMORY; } + return CURLE_OK; +} + +/* + * Append the request-target to the HTTP request + */ +CURLcode Curl_http_target(struct Curl_easy *data, + struct connectdata *conn, + struct dynbuf *r) +{ + CURLcode result = CURLE_OK; + const char *path = data->state.up.path; + const char *query = data->state.up.query; + + if(data->set.str[STRING_TARGET]) { + path = data->set.str[STRING_TARGET]; + query = NULL; + } #ifndef CURL_DISABLE_PROXY if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { @@ -2279,6 +2140,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) /* and no fragment part */ CURLUcode uc; + char *url; CURLU *h = curl_url_dup(data->state.uh); if(!h) return CURLE_OUT_OF_MEMORY; @@ -2312,7 +2174,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) /* Extract the URL to use in the request. Store in STRING_TEMP_URL for clean-up reasons if the function returns before the free() further down. */ - uc = curl_url_get(h, CURLUPART_URL, &data->set.str[STRING_TEMP_URL], 0); + uc = curl_url_get(h, CURLUPART_URL, &url, 0); if(uc) { curl_url_cleanup(h); return CURLE_OUT_OF_MEMORY; @@ -2320,6 +2182,13 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) curl_url_cleanup(h); + /* target or url */ + result = Curl_dyn_add(r, data->set.str[STRING_TARGET]? + data->set.str[STRING_TARGET]:url); + free(url); + if(result) + return (result); + if(strcasecompare("ftp", data->state.up.scheme)) { if(data->set.proxy_transfer_mode) { /* when doing ftp, append ;type=<a|i> if not present */ @@ -2335,325 +2204,128 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) } } if(!type) { - char *p = ftp_typecode; - /* avoid sending invalid URLs like ftp://example.com;type=i if the - * user specified ftp://example.com without the slash */ - if(!*data->state.up.path && path[strlen(path) - 1] != '/') { - *p++ = '/'; - } - msnprintf(p, sizeof(ftp_typecode) - 1, ";type=%c", - data->set.prefer_ascii ? 'a' : 'i'); - } - } - if(conn->bits.user_passwd) - paste_ftp_userpwd = TRUE; - } - } -#endif /* CURL_DISABLE_PROXY */ - - http->p_accept = Curl_checkheaders(conn, "Accept")?NULL:"Accept: */*\r\n"; - - if((HTTPREQ_POST == httpreq || HTTPREQ_PUT == httpreq) && - data->state.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 - * a Range: header that will be passed along. We need to "fast forward" - * the file the given number of bytes and decrease the assume upload - * file size before we continue this venture in the dark lands of HTTP. - * Resuming mime/form posting at an offset > 0 has no sense and is ignored. - *********************************************************************/ - - if(data->state.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! - */ - data->state.resume_from = 0; - } - - if(data->state.resume_from && !data->state.this_is_a_follow) { - /* do we still game? */ - - /* Now, let's read off the proper amount of bytes from the - input. */ - int seekerr = CURL_SEEKFUNC_CANTSEEK; - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - curl_off_t passed = 0; - - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_READ_ERROR; - } - /* when seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - do { - size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, readthisamountnow, - data->state.in); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Could only read %" CURL_FORMAT_CURL_OFF_T - " bytes from the input", passed); - return CURLE_READ_ERROR; - } - } while(passed < data->state.resume_from); - } - - /* now, decrease the size of the read */ - if(data->state.infilesize>0) { - data->state.infilesize -= data->state.resume_from; - - if(data->state.infilesize <= 0) { - failf(data, "File already completely uploaded"); - return CURLE_PARTIAL_FILE; + result = Curl_dyn_addf(r, ";type=%c", + data->set.prefer_ascii ? 'a' : 'i'); + if(result) + return result; } } - /* we've passed, proceed as normal */ } } - if(data->state.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 - * ones if any such are specified. - */ - if(((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) && - !Curl_checkheaders(conn, "Range")) { - /* if a line like this was already allocated, free the previous one */ - free(data->state.aptr.rangeline); - data->state.aptr.rangeline = aprintf("Range: bytes=%s\r\n", - data->state.range); - } - else if((httpreq == HTTPREQ_POST || httpreq == HTTPREQ_PUT) && - !Curl_checkheaders(conn, "Content-Range")) { - /* if a line like this was already allocated, free the previous one */ - free(data->state.aptr.rangeline); - - if(data->set.set_resume_from < 0) { - /* Upload resume was asked for, but we don't know the size of the - remote part so we tell the server (and act accordingly) that we - upload the whole file (again) */ - data->state.aptr.rangeline = - aprintf("Content-Range: bytes 0-%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.infilesize - 1, data->state.infilesize); - - } - else if(data->state.resume_from) { - /* This is because "resume" was selected */ - curl_off_t total_expected_size = - data->state.resume_from + data->state.infilesize; - data->state.aptr.rangeline = - aprintf("Content-Range: bytes %s%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.range, total_expected_size-1, - total_expected_size); - } - else { - /* Range was selected and then we just pass the incoming range and - append total size */ - data->state.aptr.rangeline = - aprintf("Content-Range: bytes %s/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.range, data->state.infilesize); - } - if(!data->state.aptr.rangeline) - return CURLE_OUT_OF_MEMORY; - } - } - - httpstring = get_http_string(data, conn); - - /* initialize a dynamic send-buffer */ - Curl_dyn_init(&req, DYN_HTTP_REQUEST); - - /* add the main request stuff */ - /* GET/HEAD/POST/PUT */ - result = Curl_dyn_addf(&req, "%s ", request); - if(result) - return result; - - if(data->set.str[STRING_TARGET]) { - path = data->set.str[STRING_TARGET]; - query = NULL; - } - -#ifndef CURL_DISABLE_PROXY - /* url */ - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - char *url = data->set.str[STRING_TEMP_URL]; - result = Curl_dyn_add(&req, url); - Curl_safefree(data->set.str[STRING_TEMP_URL]); - } else +#else + (void)conn; /* not used in disabled-proxy builds */ #endif - if(paste_ftp_userpwd) - result = Curl_dyn_addf(&req, "ftp://%s:%s@%s", conn->user, conn->passwd, - path + sizeof("ftp://") - 1); - else { - result = Curl_dyn_add(&req, path); + { + result = Curl_dyn_add(r, path); if(result) return result; if(query) - result = Curl_dyn_addf(&req, "?%s", query); + result = Curl_dyn_addf(r, "?%s", query); } - if(result) - return result; -#ifndef CURL_DISABLE_ALTSVC - if(conn->bits.altused && !Curl_checkheaders(conn, "Alt-Used")) { - altused = aprintf("Alt-Used: %s:%d\r\n", - conn->conn_to_host.name, conn->conn_to_port); - if(!altused) { - Curl_dyn_free(&req); - return CURLE_OUT_OF_MEMORY; - } - } -#endif - result = - Curl_dyn_addf(&req, - "%s" /* ftp typecode (;type=x) */ - " HTTP/%s\r\n" /* HTTP version */ - "%s" /* host */ - "%s" /* proxyuserpwd */ - "%s" /* userpwd */ - "%s" /* range */ - "%s" /* user agent */ - "%s" /* accept */ - "%s" /* TE: */ - "%s" /* accept-encoding */ - "%s" /* referer */ - "%s" /* Proxy-Connection */ - "%s" /* transfer-encoding */ - "%s",/* Alt-Used */ - - ftp_typecode, - httpstring, - (data->state.aptr.host?data->state.aptr.host:""), - data->state.aptr.proxyuserpwd? - data->state.aptr.proxyuserpwd:"", - data->state.aptr.userpwd?data->state.aptr.userpwd:"", - (data->state.use_range && data->state.aptr.rangeline)? - data->state.aptr.rangeline:"", - (data->set.str[STRING_USERAGENT] && - *data->set.str[STRING_USERAGENT] && - data->state.aptr.uagent)? - data->state.aptr.uagent:"", - http->p_accept?http->p_accept:"", - data->state.aptr.te?data->state.aptr.te:"", - (data->set.str[STRING_ENCODING] && - *data->set.str[STRING_ENCODING] && - data->state.aptr.accept_encoding)? - data->state.aptr.accept_encoding:"", - (data->change.referer && data->state.aptr.ref)? - data->state.aptr.ref:"" /* Referer: <data> */, -#ifndef CURL_DISABLE_PROXY - (conn->bits.httpproxy && - !conn->bits.tunnel_proxy && - !Curl_checkProxyheaders(conn, "Proxy-Connection"))? - "Proxy-Connection: Keep-Alive\r\n":"", -#else - "", -#endif - te, - altused ? altused : "" - ); - - /* clear userpwd and proxyuserpwd to avoid re-using old credentials - * from re-used connections */ - Curl_safefree(data->state.aptr.userpwd); - Curl_safefree(data->state.aptr.proxyuserpwd); - free(altused); + return result; +} - if(result) - return result; +CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn, + Curl_HttpReq httpreq, const char **tep) +{ + CURLcode result = CURLE_OK; + const char *ptr; + struct HTTP *http = data->req.p.http; + http->postsize = 0; - if(!(conn->handler->flags&PROTOPT_SSL) && - conn->httpversion != 20 && - (data->set.httpversion == CURL_HTTP_VERSION_2)) { - /* append HTTP2 upgrade magic stuff to the HTTP request if it isn't done - over SSL */ - result = Curl_http2_request_upgrade(&req, conn); + switch(httpreq) { + case HTTPREQ_POST_MIME: + http->sendit = &data->set.mimepost; + break; + case HTTPREQ_POST_FORM: + /* Convert the form structure into a mime structure. */ + Curl_mime_cleanpart(&http->form); + result = Curl_getformdata(data, &http->form, data->set.httppost, + data->state.fread_func); if(result) return result; + http->sendit = &http->form; + break; + default: + http->sendit = NULL; } -#if !defined(CURL_DISABLE_COOKIES) - if(data->cookies || addcookies) { - struct Cookie *co = NULL; /* no cookies from start */ - int count = 0; +#ifndef CURL_DISABLE_MIME + if(http->sendit) { + const char *cthdr = Curl_checkheaders(data, "Content-Type"); - if(data->cookies && data->state.cookie_engine) { - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - co = Curl_cookie_getlist(data->cookies, - data->state.aptr.cookiehost? - data->state.aptr.cookiehost:host, - data->state.up.path, - (conn->handler->protocol&CURLPROTO_HTTPS)? - TRUE:FALSE); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } - if(co) { - struct Cookie *store = co; - /* now loop through all cookies that matched */ - while(co) { - if(co->value) { - if(0 == count) { - result = Curl_dyn_add(&req, "Cookie: "); - if(result) - break; - } - result = Curl_dyn_addf(&req, "%s%s=%s", count?"; ":"", - co->name, co->value); - if(result) - break; - count++; - } - co = co->next; /* next cookie please */ - } - Curl_cookie_freelist(store); - } - if(addcookies && !result) { - if(!count) - result = Curl_dyn_add(&req, "Cookie: "); - if(!result) { - result = Curl_dyn_addf(&req, "%s%s", count?"; ":"", addcookies); - count++; - } - } - if(count && !result) - result = Curl_dyn_add(&req, "\r\n"); + /* Read and seek body only. */ + http->sendit->flags |= MIME_BODY_ONLY; + + /* Prepare the mime structure headers & set content type. */ + if(cthdr) + for(cthdr += 13; *cthdr == ' '; cthdr++) + ; + else if(http->sendit->kind == MIMEKIND_MULTIPART) + cthdr = "multipart/form-data"; + + curl_mime_headers(http->sendit, data->set.headers, 0); + result = Curl_mime_prepare_headers(http->sendit, cthdr, + NULL, MIMESTRATEGY_FORM); + curl_mime_headers(http->sendit, NULL, 0); + if(!result) + result = Curl_mime_rewind(http->sendit); if(result) return result; + http->postsize = Curl_mime_size(http->sendit); } #endif - result = Curl_add_timecondition(conn, &req); - if(result) - return result; + ptr = Curl_checkheaders(data, "Transfer-Encoding"); + if(ptr) { + /* Some kind of TE is requested, check if 'chunked' is chosen */ + data->req.upload_chunky = + Curl_compareheader(ptr, "Transfer-Encoding:", "chunked"); + } + else { + if((conn->handler->protocol & PROTO_FAMILY_HTTP) && + (((httpreq == HTTPREQ_POST_MIME || httpreq == HTTPREQ_POST_FORM) && + http->postsize < 0) || + ((data->set.upload || httpreq == HTTPREQ_POST) && + data->state.infilesize == -1))) { + if(conn->bits.authneg) + /* don't enable chunked during auth neg */ + ; + else if(use_http_1_1plus(data, conn)) { + if(conn->httpversion < 20) + /* HTTP, upload, unknown file size and not HTTP 1.0 */ + data->req.upload_chunky = TRUE; + } + else { + failf(data, "Chunky upload is not supported by HTTP 1.0"); + return CURLE_UPLOAD_FAILED; + } + } + else { + /* else, no chunky upload */ + data->req.upload_chunky = FALSE; + } - result = Curl_add_custom_headers(conn, FALSE, &req); - if(result) - return result; + if(data->req.upload_chunky) + *tep = "Transfer-Encoding: chunked\r\n"; + } + return result; +} - http->postdata = NULL; /* nothing to post at this point */ - Curl_pgrsSetUploadSize(data, -1); /* upload size is unknown atm */ +CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, + struct dynbuf *r, Curl_HttpReq httpreq) +{ +#ifndef USE_HYPER + /* Hyper always handles the body separately */ + curl_off_t included_body = 0; +#endif + CURLcode result = CURLE_OK; + struct HTTP *http = data->req.p.http; + const char *ptr; /* If 'authdone' is FALSE, we must not set the write socket index to the Curl_transfer() call below, as we're not ready to actually upload any @@ -2664,42 +2336,42 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) case HTTPREQ_PUT: /* Let's PUT the data to the server! */ if(conn->bits.authneg) - postsize = 0; + http->postsize = 0; else - postsize = data->state.infilesize; + http->postsize = data->state.infilesize; - if((postsize != -1) && !data->req.upload_chunky && - (conn->bits.authneg || !Curl_checkheaders(conn, "Content-Length"))) { + if((http->postsize != -1) && !data->req.upload_chunky && + (conn->bits.authneg || !Curl_checkheaders(data, "Content-Length"))) { /* only add Content-Length if not uploading chunked */ - result = Curl_dyn_addf(&req, "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", postsize); + result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T + "\r\n", http->postsize); if(result) return result; } - if(postsize != 0) { - result = expect100(data, conn, &req); + if(http->postsize) { + result = expect100(data, conn, r); if(result) return result; } /* end of headers */ - result = Curl_dyn_add(&req, "\r\n"); + result = Curl_dyn_add(r, "\r\n"); if(result) return result; /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, postsize); + Curl_pgrsSetUploadSize(data, http->postsize); /* this sends the buffer and frees all the buffer resources */ - result = Curl_buffer_send(&req, conn, &data->info.request_size, 0, + result = Curl_buffer_send(r, data, &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending PUT request"); else /* prepare for transfer */ Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - postsize?FIRSTSOCKET:-1); + http->postsize?FIRSTSOCKET:-1); if(result) return result; break; @@ -2709,11 +2381,11 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) /* This is form posting using mime data. */ if(conn->bits.authneg) { /* nothing to post! */ - result = Curl_dyn_add(&req, "Content-Length: 0\r\n\r\n"); + result = Curl_dyn_add(r, "Content-Length: 0\r\n\r\n"); if(result) return result; - result = Curl_buffer_send(&req, conn, &data->info.request_size, 0, + result = Curl_buffer_send(r, data, &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending POST request"); @@ -2723,18 +2395,18 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) break; } - data->state.infilesize = postsize = http->postsize; + data->state.infilesize = http->postsize; /* We only set Content-Length and allow a custom Content-Length if we don't upload data chunked, as RFC2616 forbids us to set both kinds of headers (Transfer-Encoding: chunked and Content-Length) */ - if(postsize != -1 && !data->req.upload_chunky && - (conn->bits.authneg || !Curl_checkheaders(conn, "Content-Length"))) { + if(http->postsize != -1 && !data->req.upload_chunky && + (conn->bits.authneg || !Curl_checkheaders(data, "Content-Length"))) { /* we allow replacing this header if not during auth negotiation, although it isn't very wise to actually set your own */ - result = Curl_dyn_addf(&req, + result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", postsize); + "\r\n", http->postsize); if(result) return result; } @@ -2745,7 +2417,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) struct curl_slist *hdr; for(hdr = http->sendit->curlheaders; hdr; hdr = hdr->next) { - result = Curl_dyn_addf(&req, "%s\r\n", hdr->data); + result = Curl_dyn_addf(r, "%s\r\n", hdr->data); if(result) return result; } @@ -2756,13 +2428,13 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) the somewhat bigger ones we allow the app to disable it. Just make sure that the expect100header is always set to the preferred value here. */ - ptr = Curl_checkheaders(conn, "Expect"); + ptr = Curl_checkheaders(data, "Expect"); if(ptr) { data->state.expect100header = Curl_compareheader(ptr, "Expect:", "100-continue"); } - else if(postsize > EXPECT_100_THRESHOLD || postsize < 0) { - result = expect100(data, conn, &req); + else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) { + result = expect100(data, conn, r); if(result) return result; } @@ -2770,12 +2442,12 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) data->state.expect100header = FALSE; /* make the request end in a true CRLF */ - result = Curl_dyn_add(&req, "\r\n"); + result = Curl_dyn_add(r, "\r\n"); if(result) return result; /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, postsize); + Curl_pgrsSetUploadSize(data, http->postsize); /* Read from mime structure. */ data->state.fread_func = (curl_read_callback) Curl_mime_read; @@ -2783,14 +2455,14 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) http->sending = HTTPSEND_BODY; /* this sends the buffer and frees all the buffer resources */ - result = Curl_buffer_send(&req, conn, &data->info.request_size, 0, + result = Curl_buffer_send(r, data, &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending POST request"); else /* prepare for transfer */ Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - postsize?FIRSTSOCKET:-1); + http->postsize?FIRSTSOCKET:-1); if(result) return result; @@ -2800,26 +2472,26 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) /* this is the simple POST, using x-www-form-urlencoded style */ if(conn->bits.authneg) - postsize = 0; + http->postsize = 0; else /* the size of the post body */ - postsize = data->state.infilesize; + http->postsize = data->state.infilesize; /* We only set Content-Length and allow a custom Content-Length if we don't upload data chunked, as RFC2616 forbids us to set both kinds of headers (Transfer-Encoding: chunked and Content-Length) */ - if((postsize != -1) && !data->req.upload_chunky && - (conn->bits.authneg || !Curl_checkheaders(conn, "Content-Length"))) { + if((http->postsize != -1) && !data->req.upload_chunky && + (conn->bits.authneg || !Curl_checkheaders(data, "Content-Length"))) { /* we allow replacing this header if not during auth negotiation, although it isn't very wise to actually set your own */ - result = Curl_dyn_addf(&req, "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", postsize); + result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T + "\r\n", http->postsize); if(result) return result; } - if(!Curl_checkheaders(conn, "Content-Type")) { - result = Curl_dyn_add(&req, "Content-Type: application/" + if(!Curl_checkheaders(data, "Content-Type")) { + result = Curl_dyn_add(r, "Content-Type: application/" "x-www-form-urlencoded\r\n"); if(result) return result; @@ -2829,26 +2501,28 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) the somewhat bigger ones we allow the app to disable it. Just make sure that the expect100header is always set to the preferred value here. */ - ptr = Curl_checkheaders(conn, "Expect"); + ptr = Curl_checkheaders(data, "Expect"); if(ptr) { data->state.expect100header = Curl_compareheader(ptr, "Expect:", "100-continue"); } - else if(postsize > EXPECT_100_THRESHOLD || postsize < 0) { - result = expect100(data, conn, &req); + else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) { + result = expect100(data, conn, r); if(result) return result; } else data->state.expect100header = FALSE; +#ifndef USE_HYPER + /* With Hyper the body is always passed on separately */ if(data->set.postfields) { /* In HTTP2, we send request body in DATA frame regardless of its size. */ if(conn->httpversion != 20 && !data->state.expect100header && - (postsize < MAX_INITIAL_POST_SIZE)) { + (http->postsize < MAX_INITIAL_POST_SIZE)) { /* if we don't use expect: 100 AND postsize is less than MAX_INITIAL_POST_SIZE @@ -2857,34 +2531,34 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) get the data duplicated with malloc() and family. */ /* end of headers! */ - result = Curl_dyn_add(&req, "\r\n"); + result = Curl_dyn_add(r, "\r\n"); if(result) return result; if(!data->req.upload_chunky) { /* We're not sending it 'chunked', append it to the request already now to reduce the number if send() calls */ - result = Curl_dyn_addn(&req, data->set.postfields, - (size_t)postsize); - included_body = postsize; + result = Curl_dyn_addn(r, data->set.postfields, + (size_t)http->postsize); + included_body = http->postsize; } else { - if(postsize) { + if(http->postsize) { char chunk[16]; /* Append the POST data chunky-style */ - msnprintf(chunk, sizeof(chunk), "%x\r\n", (int)postsize); - result = Curl_dyn_add(&req, chunk); + msnprintf(chunk, sizeof(chunk), "%x\r\n", (int)http->postsize); + result = Curl_dyn_add(r, chunk); if(!result) { - included_body = postsize + strlen(chunk); - result = Curl_dyn_addn(&req, data->set.postfields, - (size_t)postsize); + included_body = http->postsize + strlen(chunk); + result = Curl_dyn_addn(r, data->set.postfields, + (size_t)http->postsize); if(!result) - result = Curl_dyn_add(&req, "\r\n"); + result = Curl_dyn_add(r, "\r\n"); included_body += 2; } } if(!result) { - result = Curl_dyn_add(&req, "\x30\x0d\x0a\x0d\x0a"); + result = Curl_dyn_add(r, "\x30\x0d\x0a\x0d\x0a"); /* 0 CR LF CR LF */ included_body += 5; } @@ -2892,37 +2566,38 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) if(result) return result; /* Make sure the progress information is accurate */ - Curl_pgrsSetUploadSize(data, postsize); + Curl_pgrsSetUploadSize(data, http->postsize); } else { /* A huge POST coming up, do data separate from the request */ - http->postsize = postsize; http->postdata = data->set.postfields; http->sending = HTTPSEND_BODY; data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)conn; + data->state.in = (void *)data; /* set the upload size to the progress meter */ Curl_pgrsSetUploadSize(data, http->postsize); /* end of headers! */ - result = Curl_dyn_add(&req, "\r\n"); + result = Curl_dyn_add(r, "\r\n"); if(result) return result; } } - else { + else +#endif + { /* end of headers! */ - result = Curl_dyn_add(&req, "\r\n"); + result = Curl_dyn_add(r, "\r\n"); if(result) return result; if(data->req.upload_chunky && conn->bits.authneg) { /* Chunky upload is selected and we're negotiating auth still, send end-of-data only */ - result = Curl_dyn_add(&req, (char *)"\x30\x0d\x0a\x0d\x0a"); + result = Curl_dyn_add(r, (char *)"\x30\x0d\x0a\x0d\x0a"); /* 0 CR LF CR LF */ if(result) return result; @@ -2930,19 +2605,16 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) else if(data->state.infilesize) { /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, postsize?postsize:-1); + Curl_pgrsSetUploadSize(data, http->postsize?http->postsize:-1); /* set the pointer to mark that we will send the post body using the - read callback, but only if we're not in authenticate - negotiation */ - if(!conn->bits.authneg) { + read callback, but only if we're not in authenticate negotiation */ + if(!conn->bits.authneg) http->postdata = (char *)&http->postdata; - http->postsize = postsize; - } } } /* issue the request */ - result = Curl_buffer_send(&req, conn, &data->info.request_size, + result = Curl_buffer_send(r, data, &data->info.request_size, (size_t)included_body, FIRSTSOCKET); if(result) @@ -2953,12 +2625,12 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) break; default: - result = Curl_dyn_add(&req, "\r\n"); + result = Curl_dyn_add(r, "\r\n"); if(result) return result; /* issue the request */ - result = Curl_buffer_send(&req, conn, &data->info.request_size, 0, + result = Curl_buffer_send(r, data, &data->info.request_size, 0, FIRSTSOCKET); if(result) @@ -2967,24 +2639,567 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) /* HTTP GET/HEAD download: */ Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); } + + return result; +} + +#if !defined(CURL_DISABLE_COOKIES) +CURLcode Curl_http_cookies(struct Curl_easy *data, + struct connectdata *conn, + struct dynbuf *r) +{ + CURLcode result = CURLE_OK; + char *addcookies = NULL; + if(data->set.str[STRING_COOKIE] && !Curl_checkheaders(data, "Cookie")) + addcookies = data->set.str[STRING_COOKIE]; + + if(data->cookies || addcookies) { + struct Cookie *co = NULL; /* no cookies from start */ + int count = 0; + + if(data->cookies && data->state.cookie_engine) { + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + co = Curl_cookie_getlist(data->cookies, + data->state.aptr.cookiehost? + data->state.aptr.cookiehost: + conn->host.name, + data->state.up.path, + (conn->handler->protocol&CURLPROTO_HTTPS)? + TRUE:FALSE); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } + if(co) { + struct Cookie *store = co; + /* now loop through all cookies that matched */ + while(co) { + if(co->value) { + if(0 == count) { + result = Curl_dyn_add(r, "Cookie: "); + if(result) + break; + } + result = Curl_dyn_addf(r, "%s%s=%s", count?"; ":"", + co->name, co->value); + if(result) + break; + count++; + } + co = co->next; /* next cookie please */ + } + Curl_cookie_freelist(store); + } + if(addcookies && !result) { + if(!count) + result = Curl_dyn_add(r, "Cookie: "); + if(!result) { + result = Curl_dyn_addf(r, "%s%s", count?"; ":"", addcookies); + count++; + } + } + if(count && !result) + result = Curl_dyn_add(r, "\r\n"); + + if(result) + return result; + } + return result; +} +#endif + +CURLcode Curl_http_range(struct Curl_easy *data, + Curl_HttpReq httpreq) +{ + if(data->state.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 + * ones if any such are specified. + */ + if(((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) && + !Curl_checkheaders(data, "Range")) { + /* if a line like this was already allocated, free the previous one */ + free(data->state.aptr.rangeline); + data->state.aptr.rangeline = aprintf("Range: bytes=%s\r\n", + data->state.range); + } + else if((httpreq == HTTPREQ_POST || httpreq == HTTPREQ_PUT) && + !Curl_checkheaders(data, "Content-Range")) { + + /* if a line like this was already allocated, free the previous one */ + free(data->state.aptr.rangeline); + + if(data->set.set_resume_from < 0) { + /* Upload resume was asked for, but we don't know the size of the + remote part so we tell the server (and act accordingly) that we + upload the whole file (again) */ + data->state.aptr.rangeline = + aprintf("Content-Range: bytes 0-%" CURL_FORMAT_CURL_OFF_T + "/%" CURL_FORMAT_CURL_OFF_T "\r\n", + data->state.infilesize - 1, data->state.infilesize); + + } + else if(data->state.resume_from) { + /* This is because "resume" was selected */ + curl_off_t total_expected_size = + data->state.resume_from + data->state.infilesize; + data->state.aptr.rangeline = + aprintf("Content-Range: bytes %s%" CURL_FORMAT_CURL_OFF_T + "/%" CURL_FORMAT_CURL_OFF_T "\r\n", + data->state.range, total_expected_size-1, + total_expected_size); + } + else { + /* Range was selected and then we just pass the incoming range and + append total size */ + data->state.aptr.rangeline = + aprintf("Content-Range: bytes %s/%" CURL_FORMAT_CURL_OFF_T "\r\n", + data->state.range, data->state.infilesize); + } + if(!data->state.aptr.rangeline) + return CURLE_OUT_OF_MEMORY; + } + } + return CURLE_OK; +} + +CURLcode Curl_http_resume(struct Curl_easy *data, + struct connectdata *conn, + Curl_HttpReq httpreq) +{ + if((HTTPREQ_POST == httpreq || HTTPREQ_PUT == httpreq) && + data->state.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 + * a Range: header that will be passed along. We need to "fast forward" + * the file the given number of bytes and decrease the assume upload + * file size before we continue this venture in the dark lands of HTTP. + * Resuming mime/form posting at an offset > 0 has no sense and is ignored. + *********************************************************************/ + + if(data->state.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! + */ + data->state.resume_from = 0; + } + + if(data->state.resume_from && !data->state.this_is_a_follow) { + /* do we still game? */ + + /* Now, let's read off the proper amount of bytes from the + input. */ + int seekerr = CURL_SEEKFUNC_CANTSEEK; + if(conn->seek_func) { + Curl_set_in_callback(data, true); + seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, + SEEK_SET); + Curl_set_in_callback(data, false); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_READ_ERROR; + } + /* when seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + do { + size_t readthisamountnow = + (data->state.resume_from - passed > data->set.buffer_size) ? + (size_t)data->set.buffer_size : + curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread = + data->state.fread_func(data->state.buffer, 1, readthisamountnow, + data->state.in); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Could only read %" CURL_FORMAT_CURL_OFF_T + " bytes from the input", passed); + return CURLE_READ_ERROR; + } + } while(passed < data->state.resume_from); + } + + /* now, decrease the size of the read */ + if(data->state.infilesize>0) { + data->state.infilesize -= data->state.resume_from; + + if(data->state.infilesize <= 0) { + failf(data, "File already completely uploaded"); + return CURLE_PARTIAL_FILE; + } + } + /* we've passed, proceed as normal */ + } + } + return CURLE_OK; +} + +CURLcode Curl_http_firstwrite(struct Curl_easy *data, + struct connectdata *conn, + bool *done) +{ + struct SingleRequest *k = &data->req; + DEBUGASSERT(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)); + if(data->req.newurl) { + if(conn->bits.close) { + /* Abort after the headers if "follow Location" is set + and we're set to close anyway. */ + k->keepon &= ~KEEP_RECV; + *done = TRUE; + return CURLE_OK; + } + /* We have a new url to load, but since we want to be able to re-use this + connection properly, we read the full response in "ignore more" */ + k->ignorebody = TRUE; + infof(data, "Ignoring the response-body\n"); + } + if(data->state.resume_from && !k->content_range && + (data->state.httpreq == HTTPREQ_GET) && + !k->ignorebody) { + + if(k->size == data->state.resume_from) { + /* The resume point is at the end of file, consider this fine even if it + doesn't allow resume from here. */ + infof(data, "The entire document is already downloaded"); + connclose(conn, "already downloaded"); + /* Abort download */ + k->keepon &= ~KEEP_RECV; + *done = TRUE; + return CURLE_OK; + } + + /* we wanted to resume a download, although the server doesn't seem to + * support this and we did this with a GET (if it wasn't a GET we did a + * POST or PUT resume) */ + failf(data, "HTTP server doesn't seem to support " + "byte ranges. Cannot resume."); + return CURLE_RANGE_ERROR; + } + + if(data->set.timecondition && !data->state.range) { + /* A time condition has been set AND no ranges have been requested. This + seems to be what chapter 13.3.4 of RFC 2616 defines to be the correct + action for a HTTP/1.1 client */ + + if(!Curl_meets_timecondition(data, k->timeofdoc)) { + *done = TRUE; + /* We're simulating a http 304 from server so we return + what should have been returned from the server */ + data->info.httpcode = 304; + infof(data, "Simulate a HTTP 304 response!\n"); + /* we abort the transfer before it is completed == we ruin the + re-use ability. Close the connection */ + connclose(conn, "Simulated 304 handling"); + return CURLE_OK; + } + } /* we have a time condition */ + + return CURLE_OK; +} + +#ifndef USE_HYPER +/* + * Curl_http() gets called from the generic multi_do() function when a HTTP + * request is to be performed. This creates and sends a properly constructed + * HTTP request. + */ +CURLcode Curl_http(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; + struct HTTP *http; + Curl_HttpReq httpreq; + const char *te = ""; /* transfer-encoding */ + const char *request; + const char *httpstring; + struct dynbuf req; + char *altused = NULL; + const char *p_accept; /* Accept: string */ + + /* Always consider the DO phase done after this function call, even if there + may be parts of the request that are not yet sent, since we can deal with + the rest of the request in the PERFORM phase. */ + *done = TRUE; + + if(conn->transport != TRNSPRT_QUIC) { + if(conn->httpversion < 20) { /* unless the connection is re-used and + already http2 */ + switch(conn->negnpn) { + case CURL_HTTP_VERSION_2: + conn->httpversion = 20; /* we know we're on HTTP/2 now */ + + result = Curl_http2_switched(data, NULL, 0); + if(result) + return result; + break; + case CURL_HTTP_VERSION_1_1: + /* continue with HTTP/1.1 when explicitly requested */ + break; + default: + /* Check if user wants to use HTTP/2 with clear TCP*/ +#ifdef USE_NGHTTP2 + if(data->set.httpversion == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + /* We don't support HTTP/2 proxies yet. Also it's debatable + whether or not this setting should apply to HTTP/2 proxies. */ + infof(data, "Ignoring HTTP/2 prior knowledge due to proxy\n"); + break; + } +#endif + DEBUGF(infof(data, "HTTP/2 over clean TCP\n")); + conn->httpversion = 20; + + result = Curl_http2_switched(data, NULL, 0); + if(result) + return result; + } +#endif + break; + } + } + else { + /* prepare for a http2 request */ + result = Curl_http2_setup(data, conn); + if(result) + return result; + } + } + http = data->req.p.http; + DEBUGASSERT(http); + + result = Curl_http_host(data, conn); + if(result) + return result; + + result = Curl_http_useragent(data); if(result) return result; - if(!postsize && (http->sending != HTTPSEND_REQUEST)) + + Curl_http_method(data, conn, &request, &httpreq); + + /* setup the authentication headers */ + { + char *pq = NULL; + if(data->state.up.query) { + pq = aprintf("%s?%s", data->state.up.path, data->state.up.query); + if(!pq) + return CURLE_OUT_OF_MEMORY; + } + result = Curl_http_output_auth(data, conn, request, httpreq, + (pq ? pq : data->state.up.path), FALSE); + free(pq); + if(result) + return result; + } + + Curl_safefree(data->state.aptr.ref); + if(data->change.referer && !Curl_checkheaders(data, "Referer")) { + data->state.aptr.ref = aprintf("Referer: %s\r\n", data->change.referer); + if(!data->state.aptr.ref) + return CURLE_OUT_OF_MEMORY; + } + + if(!Curl_checkheaders(data, "Accept-Encoding") && + data->set.str[STRING_ENCODING]) { + Curl_safefree(data->state.aptr.accept_encoding); + data->state.aptr.accept_encoding = + aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); + if(!data->state.aptr.accept_encoding) + return CURLE_OUT_OF_MEMORY; + } + else { + Curl_safefree(data->state.aptr.accept_encoding); + data->state.aptr.accept_encoding = NULL; + } + +#ifdef HAVE_LIBZ + /* we only consider transfer-encoding magic if libz support is built-in */ + + if(!Curl_checkheaders(data, "TE") && + data->set.http_transfer_encoding) { + /* When we are to insert a TE: header in the request, we must also insert + TE in a Connection: header, so we need to merge the custom provided + Connection: header and prevent the original to get sent. Note that if + the user has inserted his/her own TE: header we don't do this magic + but then assume that the user will handle it all! */ + char *cptr = Curl_checkheaders(data, "Connection"); +#define TE_HEADER "TE: gzip\r\n" + + Curl_safefree(data->state.aptr.te); + + if(cptr) { + cptr = Curl_copy_header_value(cptr); + if(!cptr) + return CURLE_OUT_OF_MEMORY; + } + + /* Create the (updated) Connection: header */ + data->state.aptr.te = aprintf("Connection: %s%sTE\r\n" TE_HEADER, + cptr ? cptr : "", (cptr && *cptr) ? ", ":""); + + free(cptr); + if(!data->state.aptr.te) + return CURLE_OUT_OF_MEMORY; + } +#endif + + result = Curl_http_body(data, conn, httpreq, &te); + if(result) + return result; + + p_accept = Curl_checkheaders(data, "Accept")?NULL:"Accept: */*\r\n"; + + result = Curl_http_resume(data, conn, httpreq); + if(result) + return result; + + result = Curl_http_range(data, httpreq); + if(result) + return result; + + httpstring = get_http_string(data, conn); + + /* initialize a dynamic send-buffer */ + Curl_dyn_init(&req, DYN_HTTP_REQUEST); + + /* add the main request stuff */ + /* GET/HEAD/POST/PUT */ + result = Curl_dyn_addf(&req, "%s ", request); + if(!result) + result = Curl_http_target(data, conn, &req); + if(result) { + Curl_dyn_free(&req); + return result; + } + +#ifndef CURL_DISABLE_ALTSVC + if(conn->bits.altused && !Curl_checkheaders(data, "Alt-Used")) { + altused = aprintf("Alt-Used: %s:%d\r\n", + conn->conn_to_host.name, conn->conn_to_port); + if(!altused) { + Curl_dyn_free(&req); + return CURLE_OUT_OF_MEMORY; + } + } +#endif + result = + Curl_dyn_addf(&req, + " HTTP/%s\r\n" /* HTTP version */ + "%s" /* host */ + "%s" /* proxyuserpwd */ + "%s" /* userpwd */ + "%s" /* range */ + "%s" /* user agent */ + "%s" /* accept */ + "%s" /* TE: */ + "%s" /* accept-encoding */ + "%s" /* referer */ + "%s" /* Proxy-Connection */ + "%s" /* transfer-encoding */ + "%s",/* Alt-Used */ + + httpstring, + (data->state.aptr.host?data->state.aptr.host:""), + data->state.aptr.proxyuserpwd? + data->state.aptr.proxyuserpwd:"", + data->state.aptr.userpwd?data->state.aptr.userpwd:"", + (data->state.use_range && data->state.aptr.rangeline)? + data->state.aptr.rangeline:"", + (data->set.str[STRING_USERAGENT] && + *data->set.str[STRING_USERAGENT] && + data->state.aptr.uagent)? + data->state.aptr.uagent:"", + p_accept?p_accept:"", + data->state.aptr.te?data->state.aptr.te:"", + (data->set.str[STRING_ENCODING] && + *data->set.str[STRING_ENCODING] && + data->state.aptr.accept_encoding)? + data->state.aptr.accept_encoding:"", + (data->change.referer && data->state.aptr.ref)? + data->state.aptr.ref:"" /* Referer: <data> */, +#ifndef CURL_DISABLE_PROXY + (conn->bits.httpproxy && + !conn->bits.tunnel_proxy && + !Curl_checkheaders(data, "Proxy-Connection") && + !Curl_checkProxyheaders(data, conn, "Proxy-Connection"))? + "Proxy-Connection: Keep-Alive\r\n":"", +#else + "", +#endif + te, + altused ? altused : "" + ); + + /* clear userpwd and proxyuserpwd to avoid re-using old credentials + * from re-used connections */ + Curl_safefree(data->state.aptr.userpwd); + Curl_safefree(data->state.aptr.proxyuserpwd); + free(altused); + + if(result) { + Curl_dyn_free(&req); + return result; + } + + if(!(conn->handler->flags&PROTOPT_SSL) && + conn->httpversion != 20 && + (data->set.httpversion == CURL_HTTP_VERSION_2)) { + /* append HTTP2 upgrade magic stuff to the HTTP request if it isn't done + over SSL */ + result = Curl_http2_request_upgrade(&req, conn); + if(result) { + Curl_dyn_free(&req); + return result; + } + } + + result = Curl_http_cookies(data, conn, &req); + if(!result) + result = Curl_add_timecondition(data, &req); + if(!result) + result = Curl_add_custom_headers(data, FALSE, &req); + + if(!result) { + http->postdata = NULL; /* nothing to post at this point */ + if((httpreq == HTTPREQ_GET) || + (httpreq == HTTPREQ_HEAD)) + Curl_pgrsSetUploadSize(data, 0); /* nothing */ + + /* bodysend takes ownership of the 'req' memory on success */ + result = Curl_http_bodysend(data, conn, &req, httpreq); + } + if(result) { + Curl_dyn_free(&req); + return result; + } + + if((http->postsize > -1) && + (http->postsize <= data->req.writebytecount) && + (http->sending != HTTPSEND_REQUEST)) data->req.upload_done = TRUE; if(data->req.writebytecount) { /* if a request-body has been sent off, we make sure this progress is noted properly */ Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - if(Curl_pgrsUpdate(conn)) + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; - if(data->req.writebytecount >= postsize) { + if(!http->postsize) { /* already sent the entire request body, mark the "upload" as complete */ infof(data, "upload completely sent off: %" CURL_FORMAT_CURL_OFF_T " out of %" CURL_FORMAT_CURL_OFF_T " bytes\n", - data->req.writebytecount, postsize); + data->req.writebytecount, http->postsize); data->req.upload_done = TRUE; data->req.keepon &= ~KEEP_SEND; /* we're done writing */ data->req.exp100 = EXP100_SEND_DATA; /* already sent */ @@ -3000,6 +3215,8 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) return result; } +#endif /* USE_HYPER */ + typedef enum { STATUS_UNKNOWN, /* not enough data to tell yet */ STATUS_DONE, /* a status line was read */ @@ -3104,41 +3321,383 @@ checkprotoprefix(struct Curl_easy *data, struct connectdata *conn, return checkhttpprefix(data, s, len); } -static void print_http_error(struct Curl_easy *data) +/* + * Curl_http_header() parses a single response header. + */ +CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, + char *headp) { + CURLcode result; struct SingleRequest *k = &data->req; - char *beg = Curl_dyn_ptr(&data->state.headerb); - - /* make sure that data->req.p points to the HTTP status line */ - if(!strncmp(beg, "HTTP", 4)) { - - /* skip to HTTP status code */ - beg = strchr(beg, ' '); - if(beg && *++beg) { - - /* find trailing CR */ - char end_char = '\r'; - char *end = strchr(beg, end_char); - if(!end) { - /* try to find LF (workaround for non-compliant HTTP servers) */ - end_char = '\n'; - end = strchr(beg, end_char); + /* Check for Content-Length: header lines to get size */ + if(!k->http_bodyless && + !data->set.ignorecl && checkprefix("Content-Length:", headp)) { + curl_off_t contentlength; + CURLofft offt = curlx_strtoofft(headp + 15, NULL, 10, &contentlength); + + if(offt == CURL_OFFT_OK) { + if(data->set.max_filesize && + contentlength > data->set.max_filesize) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + k->size = contentlength; + k->maxdownload = k->size; + /* we set the progress download size already at this point + just to make it easier for apps/callbacks to extract this + info as soon as possible */ + Curl_pgrsSetDownloadSize(data, k->size); + } + else if(offt == CURL_OFFT_FLOW) { + /* out of range */ + if(data->set.max_filesize) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; } + streamclose(conn, "overflow content-length"); + infof(data, "Overflow Content-Length: value!\n"); + } + else { + /* negative or just rubbish - bad HTTP */ + failf(data, "Invalid Content-Length: value"); + return CURLE_WEIRD_SERVER_REPLY; + } + } + /* check for Content-Type: header lines to get the MIME-type */ + else if(checkprefix("Content-Type:", headp)) { + char *contenttype = Curl_copy_header_value(headp); + if(!contenttype) + return CURLE_OUT_OF_MEMORY; + if(!*contenttype) + /* ignore empty data */ + free(contenttype); + else { + Curl_safefree(data->info.contenttype); + data->info.contenttype = contenttype; + } + } +#ifndef CURL_DISABLE_PROXY + else if((conn->httpversion == 10) && + conn->bits.httpproxy && + Curl_compareheader(headp, "Proxy-Connection:", "keep-alive")) { + /* + * When a HTTP/1.0 reply comes when using a proxy, the + * 'Proxy-Connection: keep-alive' line tells us the + * connection will be kept alive for our pleasure. + * Default action for 1.0 is to close. + */ + connkeep(conn, "Proxy-Connection keep-alive"); /* don't close */ + infof(data, "HTTP/1.0 proxy connection set to keep alive!\n"); + } + else if((conn->httpversion == 11) && + conn->bits.httpproxy && + Curl_compareheader(headp, "Proxy-Connection:", "close")) { + /* + * We get a HTTP/1.1 response from a proxy and it says it'll + * close down after this transfer. + */ + connclose(conn, "Proxy-Connection: asked to close after done"); + infof(data, "HTTP/1.1 proxy connection set close!\n"); + } +#endif + else if((conn->httpversion == 10) && + Curl_compareheader(headp, "Connection:", "keep-alive")) { + /* + * A HTTP/1.0 reply with the 'Connection: keep-alive' line + * tells us the connection will be kept alive for our + * pleasure. Default action for 1.0 is to close. + * + * [RFC2068, section 19.7.1] */ + connkeep(conn, "Connection keep-alive"); + infof(data, "HTTP/1.0 connection set to keep alive!\n"); + } + else if(Curl_compareheader(headp, "Connection:", "close")) { + /* + * [RFC 2616, section 8.1.2.1] + * "Connection: close" is HTTP/1.1 language and means that + * the connection will close when this request has been + * served. + */ + streamclose(conn, "Connection: close used"); + } + else if(!k->http_bodyless && checkprefix("Transfer-Encoding:", headp)) { + /* One or more encodings. We check for chunked and/or a compression + algorithm. */ + /* + * [RFC 2616, section 3.6.1] A 'chunked' transfer encoding + * means that the server will send a series of "chunks". Each + * chunk starts with line with info (including size of the + * coming block) (terminated with CRLF), then a block of data + * with the previously mentioned size. There can be any amount + * of chunks, and a chunk-data set to zero signals the + * end-of-chunks. */ + + result = Curl_build_unencoding_stack(data, headp + 18, TRUE); + if(result) + return result; + } + else if(!k->http_bodyless && checkprefix("Content-Encoding:", headp) && + data->set.str[STRING_ENCODING]) { + /* + * Process Content-Encoding. Look for the values: identity, + * gzip, deflate, compress, x-gzip and x-compress. x-gzip and + * x-compress are the same as gzip and compress. (Sec 3.5 RFC + * 2616). zlib cannot handle compress. However, errors are + * handled further down when the response body is processed + */ + result = Curl_build_unencoding_stack(data, headp + 17, FALSE); + if(result) + return result; + } + else if(checkprefix("Retry-After:", headp)) { + /* Retry-After = HTTP-date / delay-seconds */ + curl_off_t retry_after = 0; /* zero for unknown or "now" */ + time_t date = Curl_getdate_capped(&headp[12]); + if(-1 == date) { + /* not a date, try it as a decimal number */ + (void)curlx_strtoofft(&headp[12], NULL, 10, &retry_after); + } + else + /* convert date to number of seconds into the future */ + retry_after = date - time(NULL); + data->info.retry_after = retry_after; /* store it */ + } + else if(!k->http_bodyless && checkprefix("Content-Range:", headp)) { + /* Content-Range: bytes [num]- + Content-Range: bytes: [num]- + Content-Range: [num]- + Content-Range: [asterisk]/[total] + + The second format was added since Sun's webserver + JavaWebServer/1.1.1 obviously sends the header this way! + The third added since some servers use that! + The forth means the requested range was unsatisfied. + */ + + char *ptr = headp + 14; + + /* Move forward until first digit or asterisk */ + while(*ptr && !ISDIGIT(*ptr) && *ptr != '*') + ptr++; + + /* if it truly stopped on a digit */ + if(ISDIGIT(*ptr)) { + if(!curlx_strtoofft(ptr, NULL, 10, &k->offset)) { + if(data->state.resume_from == k->offset) + /* we asked for a resume and we got it */ + k->content_range = TRUE; + } + } + else + data->state.resume_from = 0; /* get everything */ + } +#if !defined(CURL_DISABLE_COOKIES) + else if(data->cookies && data->state.cookie_engine && + checkprefix("Set-Cookie:", headp)) { + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, + CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_add(data, + data->cookies, TRUE, FALSE, headp + 11, + /* If there is a custom-set Host: name, use it + here, or else use real peer host name. */ + data->state.aptr.cookiehost? + data->state.aptr.cookiehost:conn->host.name, + data->state.up.path, + (conn->handler->protocol&CURLPROTO_HTTPS)? + TRUE:FALSE); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } +#endif + else if(!k->http_bodyless && checkprefix("Last-Modified:", headp) && + (data->set.timecondition || data->set.get_filetime) ) { + k->timeofdoc = Curl_getdate_capped(headp + strlen("Last-Modified:")); + if(data->set.get_filetime) + data->info.filetime = k->timeofdoc; + } + else if((checkprefix("WWW-Authenticate:", headp) && + (401 == k->httpcode)) || + (checkprefix("Proxy-authenticate:", headp) && + (407 == k->httpcode))) { + + bool proxy = (k->httpcode == 407) ? TRUE : FALSE; + char *auth = Curl_copy_header_value(headp); + if(!auth) + return CURLE_OUT_OF_MEMORY; - if(end) { - /* temporarily replace CR or LF by NUL and print the error message */ - *end = '\0'; - failf(data, "The requested URL returned error: %s", beg); + result = Curl_http_input_auth(data, proxy, auth); - /* restore the previously replaced CR or LF */ - *end = end_char; - return; + free(auth); + + if(result) + return result; + } +#ifdef USE_SPNEGO + else if(checkprefix("Persistent-Auth", headp)) { + struct negotiatedata *negdata = &conn->negotiate; + struct auth *authp = &data->state.authhost; + if(authp->picked == CURLAUTH_NEGOTIATE) { + char *persistentauth = Curl_copy_header_value(headp); + if(!persistentauth) + return CURLE_OUT_OF_MEMORY; + negdata->noauthpersist = checkprefix("false", persistentauth)? + TRUE:FALSE; + negdata->havenoauthpersist = TRUE; + infof(data, "Negotiate: noauthpersist -> %d, header part: %s", + negdata->noauthpersist, persistentauth); + free(persistentauth); + } + } +#endif + else if((k->httpcode >= 300 && k->httpcode < 400) && + checkprefix("Location:", headp) && + !data->req.location) { + /* this is the URL that the server advises us to use instead */ + char *location = Curl_copy_header_value(headp); + if(!location) + return CURLE_OUT_OF_MEMORY; + if(!*location) + /* ignore empty data */ + free(location); + else { + data->req.location = location; + + if(data->set.http_follow_location) { + DEBUGASSERT(!data->req.newurl); + data->req.newurl = strdup(data->req.location); /* clone */ + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + + /* some cases of POST and PUT etc needs to rewind the data + stream at this point */ + result = http_perhapsrewind(data, conn); + if(result) + return result; } } } - /* fall-back to printing the HTTP status code only */ - failf(data, "The requested URL returned error: %d", k->httpcode); +#ifdef USE_HSTS + /* If enabled, the header is incoming and this is over HTTPS */ + else if(data->hsts && checkprefix("Strict-Transport-Security:", headp) && + (conn->handler->flags & PROTOPT_SSL)) { + CURLcode check = + Curl_hsts_parse(data->hsts, data->state.up.hostname, + &headp[ sizeof("Strict-Transport-Security:") -1 ]); + if(check) + infof(data, "Illegal STS header skipped\n"); +#ifdef DEBUGBUILD + else + infof(data, "Parsed STS header fine (%zu entries)\n", + data->hsts->list.size); +#endif + } +#endif +#ifndef CURL_DISABLE_ALTSVC + /* If enabled, the header is incoming and this is over HTTPS */ + else if(data->asi && checkprefix("Alt-Svc:", headp) && + ((conn->handler->flags & PROTOPT_SSL) || +#ifdef CURLDEBUG + /* allow debug builds to circumvent the HTTPS restriction */ + getenv("CURL_ALTSVC_HTTP") +#else + 0 +#endif + )) { + /* the ALPN of the current request */ + enum alpnid id = (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; + result = Curl_altsvc_parse(data, data->asi, + &headp[ strlen("Alt-Svc:") ], + id, conn->host.name, + curlx_uitous(conn->remote_port)); + if(result) + return result; + } +#endif + else if(conn->handler->protocol & CURLPROTO_RTSP) { + result = Curl_rtsp_parseheader(data, headp); + if(result) + return result; + } + return CURLE_OK; +} + +/* + * Called after the first HTTP response line (the status line) has been + * received and parsed. + */ + +CURLcode Curl_http_statusline(struct Curl_easy *data, + struct connectdata *conn) +{ + struct SingleRequest *k = &data->req; + data->info.httpcode = k->httpcode; + + data->info.httpversion = conn->httpversion; + if(!data->state.httpversion || + data->state.httpversion > conn->httpversion) + /* store the lowest server version we encounter */ + data->state.httpversion = conn->httpversion; + + /* + * This code executes as part of processing the header. As a + * result, it's not totally clear how to interpret the + * response code yet as that depends on what other headers may + * be present. 401 and 407 may be errors, but may be OK + * depending on how authentication is working. Other codes + * are definitely errors, so give up here. + */ + if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET && + k->httpcode == 416) { + /* "Requested Range Not Satisfiable", just proceed and + pretend this is no error */ + k->ignorebody = TRUE; /* Avoid appending error msg to good data. */ + } + + if(conn->httpversion == 10) { + /* Default action for HTTP/1.0 must be to close, unless + we get one of those fancy headers that tell us the + server keeps it open for us! */ + infof(data, "HTTP 1.0, assume close after body\n"); + connclose(conn, "HTTP/1.0 close after body"); + } + else if(conn->httpversion == 20 || + (k->upgr101 == UPGR101_REQUESTED && k->httpcode == 101)) { + DEBUGF(infof(data, "HTTP/2 found, allow multiplexing\n")); + /* HTTP/2 cannot avoid multiplexing since it is a core functionality + of the protocol */ + conn->bundle->multiuse = BUNDLE_MULTIPLEX; + } + else if(conn->httpversion >= 11 && + !conn->bits.close) { + /* If HTTP version is >= 1.1 and connection is persistent */ + DEBUGF(infof(data, + "HTTP 1.1 or later with persistent connection\n")); + } + + k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200; + switch(k->httpcode) { + case 304: + /* (quote from RFC2616, section 10.3.5): The 304 response + * MUST NOT contain a message-body, and thus is always + * terminated by the first empty line after the header + * fields. */ + if(data->set.timecondition) + data->info.timecond = TRUE; + /* FALLTHROUGH */ + case 204: + /* (quote from RFC2616, section 10.2.5): The server has + * fulfilled the request but does not need to return an + * entity-body ... The 204 response MUST NOT include a + * message-body, and thus is always terminated by the first + * empty line after the header fields. */ + k->size = 0; + k->maxdownload = 0; + k->http_bodyless = TRUE; + break; + default: + break; + } + return CURLE_OK; } /* @@ -3189,7 +3748,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, k->badheader = HEADER_ALLBAD; streamclose(conn, "bad HTTP: No end-of-message indicator"); if(!data->set.http09_allowed) { - failf(data, "Received HTTP/0.9 when not allowed\n"); + failf(data, "Received HTTP/0.9 when not allowed"); return CURLE_UNSUPPORTED_PROTOCOL; } break; @@ -3224,7 +3783,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, streamclose(conn, "bad HTTP: No end-of-message indicator"); /* this is not the beginning of a protocol first header line */ if(!data->set.http09_allowed) { - failf(data, "Received HTTP/0.9 when not allowed\n"); + failf(data, "Received HTTP/0.9 when not allowed"); return CURLE_UNSUPPORTED_PROTOCOL; } k->header = FALSE; @@ -3299,7 +3858,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, /* switch to http2 now. The bytes after response headers are also processed here, otherwise they are lost. */ - result = Curl_http2_switched(conn, k->str, *nread); + result = Curl_http2_switched(data, k->str, *nread); if(result) return result; *nread = 0; @@ -3365,15 +3924,6 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, conn->proxy_negotiate_state = GSS_AUTHSUCC; } #endif - /* - * When all the headers have been parsed, see if we should give - * up and return an error. - */ - if(http_should_fail(conn)) { - failf(data, "The requested URL returned error: %d", - k->httpcode); - return CURLE_HTTP_RETURNED_ERROR; - } /* now, only output this if the header AND body are requested: */ @@ -3382,7 +3932,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, writetype |= CLIENTWRITE_BODY; headerlen = Curl_dyn_len(&data->state.headerb); - result = Curl_client_write(conn, writetype, + result = Curl_client_write(data, writetype, Curl_dyn_ptr(&data->state.headerb), headerlen); if(result) @@ -3391,13 +3941,23 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, data->info.header_size += (long)headerlen; data->req.headerbytecount += (long)headerlen; + /* + * When all the headers have been parsed, see if we should give + * up and return an error. + */ + if(http_should_fail(data)) { + failf(data, "The requested URL returned error: %d", + k->httpcode); + return CURLE_HTTP_RETURNED_ERROR; + } + data->req.deductheadercount = (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; /* Curl_http_auth_act() checks what authentication methods * that are available and decides which one (if any) to * use. It will set 'newurl' if an auth method was picked. */ - result = Curl_http_auth_act(conn); + result = Curl_http_auth_act(data); if(result) return result; @@ -3435,8 +3995,8 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, infof(data, "Got 417 while waiting for a 100\n"); data->state.disableexpect = TRUE; DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(conn->data->change.url); - Curl_done_sending(conn, k); + data->req.newurl = strdup(data->change.url); + Curl_done_sending(data, k); } else if(data->set.http_keep_sending_on_error) { infof(data, "HTTP error before end of send, keep sending\n"); @@ -3448,7 +4008,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, else { infof(data, "HTTP error before end of send, stop sending\n"); streamclose(conn, "Stop sending data before everything sent"); - result = Curl_done_sending(conn, k); + result = Curl_done_sending(data, k); if(result) return result; k->upload_done = TRUE; @@ -3656,83 +4216,9 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, } if(nc) { - data->info.httpcode = k->httpcode; - - data->info.httpversion = conn->httpversion; - if(!data->state.httpversion || - data->state.httpversion > conn->httpversion) - /* store the lowest server version we encounter */ - data->state.httpversion = conn->httpversion; - - /* - * This code executes as part of processing the header. As a - * result, it's not totally clear how to interpret the - * response code yet as that depends on what other headers may - * be present. 401 and 407 may be errors, but may be OK - * depending on how authentication is working. Other codes - * are definitely errors, so give up here. - */ - if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET && - k->httpcode == 416) { - /* "Requested Range Not Satisfiable", just proceed and - pretend this is no error */ - k->ignorebody = TRUE; /* Avoid appending error msg to good data. */ - } - else if(data->set.http_fail_on_error && (k->httpcode >= 400) && - ((k->httpcode != 401) || !conn->bits.user_passwd) -#ifndef CURL_DISABLE_PROXY - && ((k->httpcode != 407) || !conn->bits.proxy_user_passwd) -#endif - ) { - /* serious error, go home! */ - print_http_error(data); - return CURLE_HTTP_RETURNED_ERROR; - } - - if(conn->httpversion == 10) { - /* Default action for HTTP/1.0 must be to close, unless - we get one of those fancy headers that tell us the - server keeps it open for us! */ - infof(data, "HTTP 1.0, assume close after body\n"); - connclose(conn, "HTTP/1.0 close after body"); - } - else if(conn->httpversion == 20 || - (k->upgr101 == UPGR101_REQUESTED && k->httpcode == 101)) { - DEBUGF(infof(data, "HTTP/2 found, allow multiplexing\n")); - /* HTTP/2 cannot avoid multiplexing since it is a core functionality - of the protocol */ - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - } - else if(conn->httpversion >= 11 && - !conn->bits.close) { - /* If HTTP version is >= 1.1 and connection is persistent */ - DEBUGF(infof(data, - "HTTP 1.1 or later with persistent connection\n")); - } - - k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200; - switch(k->httpcode) { - case 304: - /* (quote from RFC2616, section 10.3.5): The 304 response - * MUST NOT contain a message-body, and thus is always - * terminated by the first empty line after the header - * fields. */ - if(data->set.timecondition) - data->info.timecond = TRUE; - /* FALLTHROUGH */ - case 204: - /* (quote from RFC2616, section 10.2.5): The server has - * fulfilled the request but does not need to return an - * entity-body ... The 204 response MUST NOT include a - * message-body, and thus is always terminated by the first - * empty line after the header fields. */ - k->size = 0; - k->maxdownload = 0; - k->http_bodyless = TRUE; - break; - default: - break; - } + result = Curl_http_statusline(data, conn); + if(result) + return result; } else { k->header = FALSE; /* this is not a header line */ @@ -3745,295 +4231,9 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, if(result) return result; - /* Check for Content-Length: header lines to get size */ - if(!k->http_bodyless && - !data->set.ignorecl && checkprefix("Content-Length:", headp)) { - curl_off_t contentlength; - CURLofft offt = curlx_strtoofft(headp + 15, NULL, 10, &contentlength); - - if(offt == CURL_OFFT_OK) { - if(data->set.max_filesize && - contentlength > data->set.max_filesize) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; - } - k->size = contentlength; - k->maxdownload = k->size; - /* we set the progress download size already at this point - just to make it easier for apps/callbacks to extract this - info as soon as possible */ - Curl_pgrsSetDownloadSize(data, k->size); - } - else if(offt == CURL_OFFT_FLOW) { - /* out of range */ - if(data->set.max_filesize) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; - } - streamclose(conn, "overflow content-length"); - infof(data, "Overflow Content-Length: value!\n"); - } - else { - /* negative or just rubbish - bad HTTP */ - failf(data, "Invalid Content-Length: value"); - return CURLE_WEIRD_SERVER_REPLY; - } - } - /* check for Content-Type: header lines to get the MIME-type */ - else if(checkprefix("Content-Type:", headp)) { - char *contenttype = Curl_copy_header_value(headp); - if(!contenttype) - return CURLE_OUT_OF_MEMORY; - if(!*contenttype) - /* ignore empty data */ - free(contenttype); - else { - Curl_safefree(data->info.contenttype); - data->info.contenttype = contenttype; - } - } -#ifndef CURL_DISABLE_PROXY - else if((conn->httpversion == 10) && - conn->bits.httpproxy && - Curl_compareheader(headp, "Proxy-Connection:", "keep-alive")) { - /* - * When a HTTP/1.0 reply comes when using a proxy, the - * 'Proxy-Connection: keep-alive' line tells us the - * connection will be kept alive for our pleasure. - * Default action for 1.0 is to close. - */ - connkeep(conn, "Proxy-Connection keep-alive"); /* don't close */ - infof(data, "HTTP/1.0 proxy connection set to keep alive!\n"); - } - else if((conn->httpversion == 11) && - conn->bits.httpproxy && - Curl_compareheader(headp, "Proxy-Connection:", "close")) { - /* - * We get a HTTP/1.1 response from a proxy and it says it'll - * close down after this transfer. - */ - connclose(conn, "Proxy-Connection: asked to close after done"); - infof(data, "HTTP/1.1 proxy connection set close!\n"); - } -#endif - else if((conn->httpversion == 10) && - Curl_compareheader(headp, "Connection:", "keep-alive")) { - /* - * A HTTP/1.0 reply with the 'Connection: keep-alive' line - * tells us the connection will be kept alive for our - * pleasure. Default action for 1.0 is to close. - * - * [RFC2068, section 19.7.1] */ - connkeep(conn, "Connection keep-alive"); - infof(data, "HTTP/1.0 connection set to keep alive!\n"); - } - else if(Curl_compareheader(headp, "Connection:", "close")) { - /* - * [RFC 2616, section 8.1.2.1] - * "Connection: close" is HTTP/1.1 language and means that - * the connection will close when this request has been - * served. - */ - streamclose(conn, "Connection: close used"); - } - else if(!k->http_bodyless && checkprefix("Transfer-Encoding:", headp)) { - /* One or more encodings. We check for chunked and/or a compression - algorithm. */ - /* - * [RFC 2616, section 3.6.1] A 'chunked' transfer encoding - * means that the server will send a series of "chunks". Each - * chunk starts with line with info (including size of the - * coming block) (terminated with CRLF), then a block of data - * with the previously mentioned size. There can be any amount - * of chunks, and a chunk-data set to zero signals the - * end-of-chunks. */ - - result = Curl_build_unencoding_stack(conn, headp + 18, TRUE); - if(result) - return result; - } - else if(!k->http_bodyless && checkprefix("Content-Encoding:", headp) && - data->set.str[STRING_ENCODING]) { - /* - * Process Content-Encoding. Look for the values: identity, - * gzip, deflate, compress, x-gzip and x-compress. x-gzip and - * x-compress are the same as gzip and compress. (Sec 3.5 RFC - * 2616). zlib cannot handle compress. However, errors are - * handled further down when the response body is processed - */ - result = Curl_build_unencoding_stack(conn, headp + 17, FALSE); - if(result) - return result; - } - else if(checkprefix("Retry-After:", headp)) { - /* Retry-After = HTTP-date / delay-seconds */ - curl_off_t retry_after = 0; /* zero for unknown or "now" */ - time_t date = Curl_getdate_capped(&headp[12]); - if(-1 == date) { - /* not a date, try it as a decimal number */ - (void)curlx_strtoofft(&headp[12], NULL, 10, &retry_after); - } - else - /* convert date to number of seconds into the future */ - retry_after = date - time(NULL); - data->info.retry_after = retry_after; /* store it */ - } - else if(!k->http_bodyless && checkprefix("Content-Range:", headp)) { - /* Content-Range: bytes [num]- - Content-Range: bytes: [num]- - Content-Range: [num]- - Content-Range: [asterisk]/[total] - - The second format was added since Sun's webserver - JavaWebServer/1.1.1 obviously sends the header this way! - The third added since some servers use that! - The forth means the requested range was unsatisfied. - */ - - char *ptr = headp + 14; - - /* Move forward until first digit or asterisk */ - while(*ptr && !ISDIGIT(*ptr) && *ptr != '*') - ptr++; - - /* if it truly stopped on a digit */ - if(ISDIGIT(*ptr)) { - if(!curlx_strtoofft(ptr, NULL, 10, &k->offset)) { - if(data->state.resume_from == k->offset) - /* we asked for a resume and we got it */ - k->content_range = TRUE; - } - } - else - data->state.resume_from = 0; /* get everything */ - } -#if !defined(CURL_DISABLE_COOKIES) - else if(data->cookies && data->state.cookie_engine && - checkprefix("Set-Cookie:", headp)) { - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, - CURL_LOCK_ACCESS_SINGLE); - Curl_cookie_add(data, - data->cookies, TRUE, FALSE, headp + 11, - /* If there is a custom-set Host: name, use it - here, or else use real peer host name. */ - data->state.aptr.cookiehost? - data->state.aptr.cookiehost:conn->host.name, - data->state.up.path, - (conn->handler->protocol&CURLPROTO_HTTPS)? - TRUE:FALSE); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } -#endif - else if(!k->http_bodyless && checkprefix("Last-Modified:", headp) && - (data->set.timecondition || data->set.get_filetime) ) { - k->timeofdoc = Curl_getdate_capped(headp + strlen("Last-Modified:")); - if(data->set.get_filetime) - data->info.filetime = k->timeofdoc; - } - else if((checkprefix("WWW-Authenticate:", headp) && - (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", headp) && - (407 == k->httpcode))) { - - bool proxy = (k->httpcode == 407) ? TRUE : FALSE; - char *auth = Curl_copy_header_value(headp); - if(!auth) - return CURLE_OUT_OF_MEMORY; - - result = Curl_http_input_auth(conn, proxy, auth); - - free(auth); - - if(result) - return result; - } -#ifdef USE_SPNEGO - else if(checkprefix("Persistent-Auth", headp)) { - struct negotiatedata *negdata = &conn->negotiate; - struct auth *authp = &data->state.authhost; - if(authp->picked == CURLAUTH_NEGOTIATE) { - char *persistentauth = Curl_copy_header_value(headp); - if(!persistentauth) - return CURLE_OUT_OF_MEMORY; - negdata->noauthpersist = checkprefix("false", persistentauth)? - TRUE:FALSE; - negdata->havenoauthpersist = TRUE; - infof(data, "Negotiate: noauthpersist -> %d, header part: %s", - negdata->noauthpersist, persistentauth); - free(persistentauth); - } - } -#endif - else if((k->httpcode >= 300 && k->httpcode < 400) && - checkprefix("Location:", headp) && - !data->req.location) { - /* this is the URL that the server advises us to use instead */ - char *location = Curl_copy_header_value(headp); - if(!location) - return CURLE_OUT_OF_MEMORY; - if(!*location) - /* ignore empty data */ - free(location); - else { - data->req.location = location; - - if(data->set.http_follow_location) { - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->req.location); /* clone */ - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - - /* some cases of POST and PUT etc needs to rewind the data - stream at this point */ - result = http_perhapsrewind(conn); - if(result) - return result; - } - } - } - -#ifdef USE_HSTS - /* If enabled, the header is incoming and this is over HTTPS */ - else if(data->hsts && checkprefix("Strict-Transport-Security:", headp) && - (conn->handler->flags & PROTOPT_SSL)) { - CURLcode check = - Curl_hsts_parse(data->hsts, data->state.up.hostname, - &headp[ sizeof("Strict-Transport-Security:") -1 ]); - if(check) - infof(data, "Illegal STS header skipped\n"); -#ifdef DEBUGBUILD - else - infof(data, "Parsed STS header fine (%zu entries)\n", - data->hsts->list.size); -#endif - } -#endif -#ifndef CURL_DISABLE_ALTSVC - /* If enabled, the header is incoming and this is over HTTPS */ - else if(data->asi && checkprefix("Alt-Svc:", headp) && - ((conn->handler->flags & PROTOPT_SSL) || -#ifdef CURLDEBUG - /* allow debug builds to circumvent the HTTPS restriction */ - getenv("CURL_ALTSVC_HTTP") -#else - 0 -#endif - )) { - /* the ALPN of the current request */ - enum alpnid id = (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; - result = Curl_altsvc_parse(data, data->asi, - &headp[ strlen("Alt-Svc:") ], - id, conn->host.name, - curlx_uitous(conn->remote_port)); - if(result) - return result; - } -#endif - else if(conn->handler->protocol & CURLPROTO_RTSP) { - result = Curl_rtsp_parseheader(conn, headp); - if(result) - return result; - } + result = Curl_http_header(data, conn, headp); + if(result) + return result; /* * End of header-checks. Write them to the client. @@ -4046,7 +4246,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, Curl_debug(data, CURLINFO_HEADER_IN, headp, Curl_dyn_len(&data->state.headerb)); - result = Curl_client_write(conn, writetype, headp, + result = Curl_client_write(data, writetype, headp, Curl_dyn_len(&data->state.headerb)); if(result) return result; |