diff options
Diffstat (limited to 'lib')
44 files changed, 1541 insertions, 1274 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index f6ea5807d..973876f50 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -174,7 +174,7 @@ $(UNITPROTOS): $(CSOURCES) $(UNIT_V)(cd $(srcdir) && @PERL@ ../scripts/extract-unit-protos $(CSOURCES) > $(top_builddir)/lib/$(UNITPROTOS)) # disable the tests that are mostly causing false positives -TIDYFLAGS := -checks=-clang-analyzer-security.insecureAPI.bzero,-clang-analyzer-security.insecureAPI.strcpy,-clang-analyzer-optin.performance.Padding,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling -quiet +TIDYFLAGS := -checks=-clang-analyzer-security.insecureAPI.bzero,-clang-analyzer-security.insecureAPI.strcpy,-clang-analyzer-optin.performance.Padding,-clang-analyzer-security.ArrayBound,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling -quiet if CURL_WERROR TIDYFLAGS += --warnings-as-errors=* endif diff --git a/lib/altsvc.c b/lib/altsvc.c index a06f40bc2..c7448692f 100644 --- a/lib/altsvc.c +++ b/lib/altsvc.c @@ -187,13 +187,13 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, const char *line) else { struct altsvc *as; char dbuf[MAX_ALTSVC_DATELEN + 1]; - time_t expires; + time_t expires = 0; /* The date parser works on a null-terminated string. The maximum length is upheld by curlx_str_quotedword(). */ memcpy(dbuf, curlx_str(&date), curlx_strlen(&date)); dbuf[curlx_strlen(&date)] = 0; - expires = Curl_getdate_capped(dbuf); + Curl_getdate_capped(dbuf, &expires); as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn, (size_t)srcport, (size_t)dstport); if(as) { diff --git a/lib/cf-h2-proxy.c b/lib/cf-h2-proxy.c index 18803ed50..d67bbd55a 100644 --- a/lib/cf-h2-proxy.c +++ b/lib/cf-h2-proxy.c @@ -474,7 +474,7 @@ static CURLcode proxy_h2_progress_ingress(struct Curl_cfilter *cf, Curl_bufq_len(&ctx->inbufq), result, nread); if(result) { if(result != CURLE_AGAIN) { - failf(data, "Failed receiving HTTP2 data"); + failf(data, "Failed receiving HTTP2 proxy data"); return result; } break; @@ -541,7 +541,7 @@ static ssize_t on_session_send(nghttp2_session *h2, if(!nwritten) return NGHTTP2_ERR_WOULDBLOCK; - return (nwritten > SSIZE_T_MAX) ? + return (nwritten > SSIZE_MAX) ? NGHTTP2_ERR_CALLBACK_FAILURE : (ssize_t)nwritten; } @@ -817,7 +817,7 @@ static ssize_t tunnel_send_callback(nghttp2_session *session, CURL_TRC_CF(data, cf, "[%d] tunnel_send_callback -> %zd", ts->stream_id, nread); - return (nread > SSIZE_T_MAX) ? + return (nread > SSIZE_MAX) ? NGHTTP2_ERR_CALLBACK_FAILURE : (ssize_t)nread; } diff --git a/lib/cookie.c b/lib/cookie.c index 99b5e43d6..35d33268f 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -107,7 +107,7 @@ static void strstore(char **str, const char *newstr, size_t len); */ static void cap_expires(time_t now, struct Cookie *co) { - if((TIME_T_MAX - COOKIES_MAXAGE - 30) > now) { + if(co->expires && (TIME_T_MAX - COOKIES_MAXAGE - 30) > now) { timediff_t cap = now + COOKIES_MAXAGE; if(co->expires > cap) { cap += 30; @@ -388,17 +388,17 @@ static void remove_expired(struct CookieInfo *ci) for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) { co = Curl_node_elem(n); e = Curl_node_next(n); - if(co->expires && co->expires < now) { - Curl_node_remove(n); - freecookie(co); - ci->numcookies--; - } - else { - /* - * If this cookie has an expiration timestamp earlier than what we - * have seen so far then record it for the next round of expirations. - */ - if(co->expires && co->expires < ci->next_expiration) + if(co->expires) { + if(co->expires < now) { + Curl_node_remove(n); + freecookie(co); + ci->numcookies--; + } + else if(co->expires < ci->next_expiration) + /* + * If this cookie has an expiration timestamp earlier than what we + * have seen so far then record it for the next round of expirations. + */ ci->next_expiration = co->expires; } } @@ -666,7 +666,6 @@ parse_cookie_header(struct Curl_easy *data, if(*maxage == '\"') maxage++; rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX); - switch(rc) { case STRE_OVERFLOW: /* overflow, used max value */ @@ -678,8 +677,7 @@ parse_cookie_header(struct Curl_easy *data, break; case STRE_OK: if(!co->expires) - /* already expired */ - co->expires = 1; + co->expires = 1; /* expire now */ else if(CURL_OFF_T_MAX - now < co->expires) /* would overflow */ co->expires = CURL_OFF_T_MAX; @@ -698,18 +696,15 @@ parse_cookie_header(struct Curl_easy *data, * will be treated as a session cookie */ char dbuf[MAX_DATE_LENGTH + 1]; + time_t date = 0; memcpy(dbuf, curlx_str(&val), curlx_strlen(&val)); dbuf[curlx_strlen(&val)] = 0; - co->expires = Curl_getdate_capped(dbuf); - - /* - * Session cookies have expires set to 0 so if we get that back - * from the date parser let's add a second to make it a - * non-session cookie - */ - if(co->expires == 0) - co->expires = 1; - else if(co->expires < 0) + if(!Curl_getdate_capped(dbuf, &date)) { + if(!date) + date++; + co->expires = (curl_off_t)date; + } + else co->expires = 0; cap_expires(now, co); } @@ -1103,7 +1098,7 @@ Curl_cookie_add(struct Curl_easy *data, if(!ci->running && /* read from a file */ ci->newsession && /* clean session cookies */ - !co->expires) /* this is a session cookie since it does not expire */ + !co->expires) /* this is a session cookie */ goto fail; co->livecookie = ci->running; @@ -1294,6 +1289,14 @@ static int cookie_sort_ct(const void *p1, const void *p2) return (c2->creationtime > c1->creationtime) ? 1 : -1; } +bool Curl_secure_context(struct connectdata *conn, const char *host) +{ + return conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || + curl_strequal("localhost", host) || + !strcmp(host, "127.0.0.1") || + !strcmp(host, "::1"); +} + /* * Curl_cookie_getlist * @@ -1306,15 +1309,17 @@ static int cookie_sort_ct(const void *p1, const void *p2) * Returns 0 when there is a list returned. Otherwise non-zero. */ int Curl_cookie_getlist(struct Curl_easy *data, - struct CookieInfo *ci, - const char *host, const char *path, - bool secure, + struct connectdata *conn, + const char *host, struct Curl_llist *list) { size_t matches = 0; - bool is_ip; + const bool is_ip = Curl_host_is_ipnum(host); const size_t myhash = cookiehash(host); struct Curl_llist_node *n; + const bool secure = Curl_secure_context(conn, host); + struct CookieInfo *ci = data->cookies; + const char *path = data->state.up.path; Curl_llist_init(list, NULL); @@ -1324,9 +1329,6 @@ int Curl_cookie_getlist(struct Curl_easy *data, /* at first, remove expired cookies */ remove_expired(ci); - /* check if host is an IP(v4|v6) address */ - is_ip = Curl_host_is_ipnum(host); - for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) { struct Cookie *co = Curl_node_elem(n); diff --git a/lib/cookie.h b/lib/cookie.h index 7af65073c..99aa20af7 100644 --- a/lib/cookie.h +++ b/lib/cookie.h @@ -105,21 +105,21 @@ struct CookieInfo { #define MAX_COOKIE_SEND_AMOUNT 150 struct Curl_easy; +struct connectdata; + /* * Add a cookie to the internal list of cookies. The domain and path arguments * are only used if the header boolean is TRUE. */ +bool Curl_secure_context(struct connectdata *conn, const char *host); struct Cookie *Curl_cookie_add(struct Curl_easy *data, struct CookieInfo *c, bool header, bool noexpiry, const char *lineptr, const char *domain, const char *path, bool secure); - -int Curl_cookie_getlist(struct Curl_easy *data, - struct CookieInfo *c, const char *host, - const char *path, bool secure, - struct Curl_llist *list); +int Curl_cookie_getlist(struct Curl_easy *data, struct connectdata *conn, + const char *host, struct Curl_llist *list); void Curl_cookie_clearall(struct CookieInfo *cookies); void Curl_cookie_clearsess(struct CookieInfo *cookies); diff --git a/lib/curl_ntlm_core.c b/lib/curl_ntlm_core.c index fc0a97b55..e040db2ab 100644 --- a/lib/curl_ntlm_core.c +++ b/lib/curl_ntlm_core.c @@ -399,7 +399,7 @@ CURLcode Curl_ntlm_core_mk_nt_hash(const char *password, size_t len = strlen(password); unsigned char *pw; CURLcode result; - if(len > SIZE_T_MAX/2) /* avoid integer overflow */ + if(len > SIZE_MAX/2) /* avoid integer overflow */ return CURLE_OUT_OF_MEMORY; pw = len ? malloc(len * 2) : (unsigned char *)strdup(""); if(!pw) diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 12108e6df..bc3fbf28d 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -475,6 +475,10 @@ #include <curl/stdcheaders.h> #endif +#if defined(HAVE_STDINT_H) || defined(USE_WOLFSSL) +#include <stdint.h> +#endif + #ifdef _WIN32 # ifdef HAVE_IO_H # include <io.h> @@ -618,21 +622,21 @@ # endif #endif -#ifndef SIZE_T_MAX +#ifndef SIZE_MAX /* some limits.h headers have this defined, some do not */ #if defined(SIZEOF_SIZE_T) && (SIZEOF_SIZE_T > 4) -#define SIZE_T_MAX 18446744073709551615U +#define SIZE_MAX 18446744073709551615U #else -#define SIZE_T_MAX 4294967295U +#define SIZE_MAX 4294967295U #endif #endif -#ifndef SSIZE_T_MAX +#ifndef SSIZE_MAX /* some limits.h headers have this defined, some do not */ #if defined(SIZEOF_SIZE_T) && (SIZEOF_SIZE_T > 4) -#define SSIZE_T_MAX 9223372036854775807 +#define SSIZE_MAX 9223372036854775807 #else -#define SSIZE_T_MAX 2147483647 +#define SSIZE_MAX 2147483647 #endif #endif diff --git a/lib/curl_setup_once.h b/lib/curl_setup_once.h index 642e1487c..7a54760e1 100644 --- a/lib/curl_setup_once.h +++ b/lib/curl_setup_once.h @@ -63,10 +63,6 @@ #include <unistd.h> #endif -#if defined(HAVE_STDINT_H) || defined(USE_WOLFSSL) -#include <stdint.h> -#endif - /* Macro to strip 'const' without triggering a compiler warning. Use it for APIs that do not or cannot support the const qualifier. */ #ifdef HAVE_STDINT_H diff --git a/lib/curl_sha512_256.c b/lib/curl_sha512_256.c index 7258e3f41..e11b2d6a9 100644 --- a/lib/curl_sha512_256.c +++ b/lib/curl_sha512_256.c @@ -41,7 +41,6 @@ #ifdef USE_OPENSSL # include <openssl/opensslv.h> # if (!defined(LIBRESSL_VERSION_NUMBER) && \ - defined(OPENSSL_VERSION_NUMBER) && \ OPENSSL_VERSION_NUMBER >= 0x10101000L) || \ (defined(LIBRESSL_VERSION_NUMBER) && \ LIBRESSL_VERSION_NUMBER >= 0x3080000fL) diff --git a/lib/curlx/dynbuf.h b/lib/curlx/dynbuf.h index 27335a6fb..00ca04789 100644 --- a/lib/curlx/dynbuf.h +++ b/lib/curlx/dynbuf.h @@ -62,7 +62,7 @@ int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save); char *curlx_dyn_take(struct dynbuf *s, size_t *plen); /* Dynamic buffer max sizes */ -#define MAX_DYNBUF_SIZE (SIZE_T_MAX/2) +#define MAX_DYNBUF_SIZE (SIZE_MAX/2) #define DYN_DOH_RESPONSE 3000 #define DYN_DOH_CNAME 256 diff --git a/lib/curlx/strparse.c b/lib/curlx/strparse.c index ecdf5b961..a29d8be2f 100644 --- a/lib/curlx/strparse.c +++ b/lib/curlx/strparse.c @@ -165,7 +165,7 @@ static int str_num_base(const char **linep, curl_off_t *nump, curl_off_t max, (base == 16) ? 'f' : '7'; DEBUGASSERT(linep && *linep && nump); DEBUGASSERT((base == 8) || (base == 10) || (base == 16)); - DEBUGASSERT(max >= 0); /* mostly to catch SIZE_T_MAX, which is too large */ + DEBUGASSERT(max >= 0); /* mostly to catch SIZE_MAX, which is too large */ *nump = 0; p = *linep; if(!valid_digit(*p, m)) diff --git a/lib/curlx/warnless.c b/lib/curlx/warnless.c index c6cb7c6e5..bb636a932 100644 --- a/lib/curlx/warnless.c +++ b/lib/curlx/warnless.c @@ -98,7 +98,7 @@ unsigned long curlx_uztoul(size_t uznum) # pragma warning(disable:810) /* conversion may lose significant bits */ #endif -#if ULONG_MAX < SIZE_T_MAX +#if ULONG_MAX < SIZE_MAX DEBUGASSERT(uznum <= (size_t) CURL_MASK_ULONG); #endif return (unsigned long)(uznum & (size_t) CURL_MASK_ULONG); @@ -119,7 +119,7 @@ unsigned int curlx_uztoui(size_t uznum) # pragma warning(disable:810) /* conversion may lose significant bits */ #endif -#if UINT_MAX < SIZE_T_MAX +#if UINT_MAX < SIZE_MAX DEBUGASSERT(uznum <= (size_t) CURL_MASK_UINT); #endif return (unsigned int)(uznum & (size_t) CURL_MASK_UINT); @@ -243,7 +243,7 @@ int curlx_sztosi(ssize_t sznum) #endif DEBUGASSERT(sznum >= 0); -#if INT_MAX < SSIZE_T_MAX +#if INT_MAX < SSIZE_MAX DEBUGASSERT((size_t) sznum <= (size_t) CURL_MASK_SINT); #endif return (int)(sznum & (ssize_t) CURL_MASK_SINT); diff --git a/lib/cw-out.c b/lib/cw-out.c index ee7dc65df..6d988a00b 100644 --- a/lib/cw-out.c +++ b/lib/cw-out.c @@ -74,6 +74,7 @@ typedef enum { CW_OUT_NONE, CW_OUT_BODY, + CW_OUT_BODY_0LEN, CW_OUT_HDS } cw_out_type; @@ -170,6 +171,7 @@ static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype, { switch(otype) { case CW_OUT_BODY: + case CW_OUT_BODY_0LEN: *pwcb = data->set.fwrite_func; *pwcb_data = data->set.out; *pmax_write = CURL_MAX_WRITE_SIZE; @@ -193,6 +195,51 @@ static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype, } } +static CURLcode cw_out_cb_write(struct cw_out_ctx *ctx, + struct Curl_easy *data, + curl_write_callback wcb, + void *wcb_data, + cw_out_type otype, + const char *buf, size_t blen, + size_t *pnwritten) +{ + size_t nwritten; + CURLcode result; + + DEBUGASSERT(data->conn); + *pnwritten = 0; + Curl_set_in_callback(data, TRUE); + nwritten = wcb((char *)CURL_UNCONST(buf), 1, blen, wcb_data); + Curl_set_in_callback(data, FALSE); + CURL_TRC_WRITE(data, "[OUT] wrote %zu %s bytes -> %zu", + blen, (otype == CW_OUT_HDS) ? "header" : "body", + nwritten); + if(CURL_WRITEFUNC_PAUSE == nwritten) { + if(data->conn->handler->flags & PROTOPT_NONETWORK) { + /* Protocols that work without network cannot be paused. This is + actually only FILE:// just now, and it cannot pause since the + transfer is not done using the "normal" procedure. */ + failf(data, "Write callback asked for PAUSE when not supported"); + return CURLE_WRITE_ERROR; + } + ctx->paused = TRUE; + CURL_TRC_WRITE(data, "[OUT] PAUSE requested by client"); + result = Curl_xfer_pause_recv(data, TRUE); + return result ? result : CURLE_AGAIN; + } + else if(CURL_WRITEFUNC_ERROR == nwritten) { + failf(data, "client returned ERROR on write of %zu bytes", blen); + return CURLE_WRITE_ERROR; + } + else if(nwritten != blen) { + failf(data, "Failure writing output to destination, " + "passed %zu returned %zd", blen, nwritten); + return CURLE_WRITE_ERROR; + } + *pnwritten = nwritten; + return CURLE_OK; +} + static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, struct Curl_easy *data, cw_out_type otype, @@ -204,6 +251,7 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, void *wcb_data; size_t max_write, min_write; size_t wlen, nwritten; + CURLcode result; /* If we errored once, we do not invoke the client callback again */ if(ctx->errored) @@ -217,40 +265,24 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, } *pconsumed = 0; - while(blen && !ctx->paused) { - if(!flush_all && blen < min_write) - break; - wlen = max_write ? CURLMIN(blen, max_write) : blen; - Curl_set_in_callback(data, TRUE); - nwritten = wcb((char *)CURL_UNCONST(buf), 1, wlen, wcb_data); - Curl_set_in_callback(data, FALSE); - CURL_TRC_WRITE(data, "[OUT] wrote %zu %s bytes -> %zu", - wlen, (otype == CW_OUT_BODY) ? "body" : "header", - nwritten); - if(CURL_WRITEFUNC_PAUSE == nwritten) { - if(data->conn && data->conn->handler->flags & PROTOPT_NONETWORK) { - /* Protocols that work without network cannot be paused. This is - actually only FILE:// just now, and it cannot pause since the - transfer is not done using the "normal" procedure. */ - failf(data, "Write callback asked for PAUSE when not supported"); - return CURLE_WRITE_ERROR; - } - ctx->paused = TRUE; - CURL_TRC_WRITE(data, "[OUT] PAUSE requested by client"); - return Curl_xfer_pause_recv(data, TRUE); - } - else if(CURL_WRITEFUNC_ERROR == nwritten) { - failf(data, "client returned ERROR on write of %zu bytes", wlen); - return CURLE_WRITE_ERROR; - } - else if(nwritten != wlen) { - failf(data, "Failure writing output to destination, " - "passed %zu returned %zd", wlen, nwritten); - return CURLE_WRITE_ERROR; + if(otype == CW_OUT_BODY_0LEN) { + DEBUGASSERT(!blen); + return cw_out_cb_write(ctx, data, wcb, wcb_data, otype, + buf, blen, &nwritten); + } + else { + while(blen && !ctx->paused) { + if(!flush_all && blen < min_write) + break; + wlen = max_write ? CURLMIN(blen, max_write) : blen; + result = cw_out_cb_write(ctx, data, wcb, wcb_data, otype, + buf, wlen, &nwritten); + if(result) + return result; + *pconsumed += nwritten; + blen -= nwritten; + buf += nwritten; } - *pconsumed += nwritten; - blen -= nwritten; - buf += nwritten; } return CURLE_OK; } @@ -262,14 +294,14 @@ static CURLcode cw_out_buf_flush(struct cw_out_ctx *ctx, { CURLcode result = CURLE_OK; - if(curlx_dyn_len(&cwbuf->b)) { + if(curlx_dyn_len(&cwbuf->b) || (cwbuf->type == CW_OUT_BODY_0LEN)) { size_t consumed; result = cw_out_ptr_flush(ctx, data, cwbuf->type, flush_all, curlx_dyn_ptr(&cwbuf->b), curlx_dyn_len(&cwbuf->b), &consumed); - if(result) + if(result && (result != CURLE_AGAIN)) return result; if(consumed) { @@ -382,8 +414,9 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, size_t consumed; result = cw_out_ptr_flush(ctx, data, otype, flush_all, buf, blen, &consumed); - if(result) + if(result && (result != CURLE_AGAIN)) return result; + result = CURLE_OK; if(consumed < blen) { /* did not write all, append the rest */ result = cw_out_append(ctx, data, otype, @@ -413,7 +446,9 @@ static CURLcode cw_out_write(struct Curl_easy *data, if((type & CLIENTWRITE_BODY) || ((type & CLIENTWRITE_HEADER) && data->set.include_header)) { - result = cw_out_do_write(ctx, data, CW_OUT_BODY, flush_all, buf, blen); + cw_out_type otype = (!blen && (type & CLIENTWRITE_0LEN)) ? + CW_OUT_BODY_0LEN : CW_OUT_BODY; + result = cw_out_do_write(ctx, data, otype, flush_all, buf, blen); if(result) return result; } @@ -571,12 +571,12 @@ static CURLcode ftp_readresp(struct Curl_easy *data, struct ftp_conn *ftpc, int sockindex, struct pingpong *pp, - int *ftpcode, /* return the ftp-code if done */ + int *ftpcodep, /* return the ftp-code if done */ size_t *size) /* size of the response */ { int code; CURLcode result = Curl_pp_readresp(data, sockindex, pp, &code, size); - + DEBUGASSERT(ftpcodep); #ifdef HAVE_GSSAPI { struct connectdata *conn = data->conn; @@ -604,8 +604,7 @@ static CURLcode ftp_readresp(struct Curl_easy *data, if(!ftpc->shutdown) data->info.httpcode = code; - if(ftpcode) - *ftpcode = code; + *ftpcodep = code; if(code == 421) { /* 421 means "Service not available, closing control connection." and FTP @@ -633,7 +632,7 @@ static CURLcode ftp_readresp(struct Curl_easy *data, CURLcode Curl_GetFTPResponse(struct Curl_easy *data, ssize_t *nreadp, /* return number of bytes read */ - int *ftpcode) /* return the ftp-code */ + int *ftpcodep) /* return the ftp-code */ { /* * We cannot read just one byte per read() and then go back to select() as @@ -649,20 +648,16 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, struct pingpong *pp = &ftpc->pp; size_t nread; int cache_skip = 0; - int value_to_be_ignored = 0; + DEBUGASSERT(ftpcodep); CURL_TRC_FTP(data, "getFTPResponse start"); *nreadp = 0; - if(ftpcode) - *ftpcode = 0; /* 0 for errors */ - else - /* make the pointer point to something for the rest of this function */ - ftpcode = &value_to_be_ignored; + *ftpcodep = 0; /* 0 for errors */ if(!ftpc) return CURLE_FAILED_INIT; - while(!*ftpcode && !result) { + while(!*ftpcodep && !result) { /* check and reset timeout value every lap */ timediff_t timeout = Curl_pp_state_timeout(data, pp, FALSE); timediff_t interval_ms; @@ -720,7 +715,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, break; } - result = ftp_readresp(data, ftpc, FIRSTSOCKET, pp, ftpcode, &nread); + result = ftp_readresp(data, ftpc, FIRSTSOCKET, pp, ftpcodep, &nread); if(result) break; @@ -739,7 +734,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, pp->pending_resp = FALSE; CURL_TRC_FTP(data, "getFTPResponse -> result=%d, nread=%zd, ftpcode=%d", - result, *nreadp, *ftpcode); + result, *nreadp, *ftpcodep); return result; } @@ -2107,6 +2102,7 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, int year, month, day, hour, minute, second; struct pingpong *pp = &ftpc->pp; char *resp = curlx_dyn_ptr(&pp->recvbuf) + 4; + bool showtime = FALSE; if(ftp_213_date(resp, &year, &month, &day, &hour, &minute, &second)) { /* we have a time, reformat it */ char timebuf[24]; @@ -2114,7 +2110,8 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, "%04d%02d%02d %02d:%02d:%02d GMT", year, month, day, hour, minute, second); /* now, convert this into a time() value: */ - data->info.filetime = Curl_getdate_capped(timebuf); + if(!Curl_getdate_capped(timebuf, &data->info.filetime)) + showtime = TRUE; } #ifdef CURL_FTP_HTTPSTYLE_HEAD @@ -2127,10 +2124,8 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, warning: comparison of unsigned expression in '>= 0' is always true */ #pragma GCC diagnostic ignored "-Wtype-limits" #endif - if(data->req.no_body && - ftpc->file && - data->set.get_filetime && - (data->info.filetime >= 0) ) { + if(data->req.no_body && ftpc->file && + data->set.get_filetime && showtime) { #if defined(__GNUC__) && (defined(__DJGPP__) || defined(__AMIGA__)) #pragma GCC diagnostic pop #endif @@ -2696,6 +2691,175 @@ static CURLcode ftp_state_acct_resp(struct Curl_easy *data, return result; } +static CURLcode ftp_pwd_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + int ftpcode) +{ + struct pingpong *pp = &ftpc->pp; + CURLcode result; + + if(ftpcode == 257) { + char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ + bool entry_extracted = FALSE; + struct dynbuf out; + curlx_dyn_init(&out, 1000); + + /* Reply format is like + 257<space>[rubbish]"<directory-name>"<space><commentary> and the + RFC959 says + + The directory name can contain any character; embedded + double-quotes should be escaped by double-quotes (the + "quote-doubling" convention). + */ + + /* scan for the first double-quote for non-standard responses */ + while(*ptr != '\n' && *ptr != '\0' && *ptr != '"') + ptr++; + + if('\"' == *ptr) { + /* it started good */ + for(ptr++; *ptr; ptr++) { + if('\"' == *ptr) { + if('\"' == ptr[1]) { + /* "quote-doubling" */ + result = curlx_dyn_addn(&out, &ptr[1], 1); + ptr++; + } + else { + /* end of path */ + if(curlx_dyn_len(&out)) + entry_extracted = TRUE; + break; /* get out of this loop */ + } + } + else + result = curlx_dyn_addn(&out, ptr, 1); + if(result) + return result; + } + } + if(entry_extracted) { + /* If the path name does not look like an absolute path (i.e.: it + does not start with a '/'), we probably need some server-dependent + adjustments. For example, this is the case when connecting to + an OS400 FTP server: this server supports two name syntaxes, + the default one being incompatible with standard paths. In + addition, this server switches automatically to the regular path + syntax when one is encountered in a command: this results in + having an entrypath in the wrong syntax when later used in CWD. + The method used here is to check the server OS: we do it only + if the path name looks strange to minimize overhead on other + systems. */ + char *dir = curlx_dyn_ptr(&out); + + if(!ftpc->server_os && dir[0] != '/') { + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST"); + if(result) { + free(dir); + return result; + } + free(ftpc->entrypath); + ftpc->entrypath = dir; /* remember this */ + infof(data, "Entry path is '%s'", ftpc->entrypath); + /* also save it where getinfo can access it: */ + free(data->state.most_recent_ftp_entrypath); + data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath); + if(!data->state.most_recent_ftp_entrypath) + return CURLE_OUT_OF_MEMORY; + ftp_state(data, ftpc, FTP_SYST); + return result; + } + + free(ftpc->entrypath); + ftpc->entrypath = dir; /* remember this */ + infof(data, "Entry path is '%s'", ftpc->entrypath); + /* also save it where getinfo can access it: */ + free(data->state.most_recent_ftp_entrypath); + data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath); + if(!data->state.most_recent_ftp_entrypath) + return CURLE_OUT_OF_MEMORY; + } + else { + /* could not get the path */ + curlx_dyn_free(&out); + infof(data, "Failed to figure out path"); + } + } + ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); + return CURLE_OK; +} + +static const char * const ftpauth[] = { "SSL", "TLS" }; + +static CURLcode ftp_wait_resp(struct Curl_easy *data, + struct connectdata *conn, + struct ftp_conn *ftpc, + int ftpcode) +{ + CURLcode result = CURLE_OK; + if(ftpcode == 230) { + /* 230 User logged in - already! Take as 220 if TLS required. */ + if(data->set.use_ssl <= CURLUSESSL_TRY || + conn->bits.ftp_use_control_ssl) + return ftp_state_user_resp(data, ftpc, ftpcode); + } + else if(ftpcode != 220) { + failf(data, "Got a %03d ftp-server response when 220 was expected", + ftpcode); + return CURLE_WEIRD_SERVER_REPLY; + } + + /* We have received a 220 response fine, now we proceed. */ +#ifdef HAVE_GSSAPI + if(data->set.krb) { + /* If not anonymous login, try a secure login. Note that this + procedure is still BLOCKING. */ + + Curl_sec_request_prot(conn, "private"); + /* We set private first as default, in case the line below fails to + set a valid level */ + Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]); + + if(Curl_sec_login(data, conn)) { + failf(data, "secure login failed"); + return CURLE_WEIRD_SERVER_REPLY; + } + infof(data, "Authentication successful"); + } +#endif + + if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) { + /* We do not have an SSL/TLS control connection yet, but FTPS is + requested. Try an FTPS connection now */ + + ftpc->count3 = 0; + switch((long)data->set.ftpsslauth) { + case CURLFTPAUTH_DEFAULT: + case CURLFTPAUTH_SSL: + ftpc->count2 = 1; /* add one to get next */ + ftpc->count1 = 0; + break; + case CURLFTPAUTH_TLS: + ftpc->count2 = -1; /* subtract one to get next */ + ftpc->count1 = 1; + break; + default: + failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d", + (int)data->set.ftpsslauth); + return CURLE_UNKNOWN_OPTION; /* we do not know what to do */ + } + result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", + ftpauth[ftpc->count1]); + if(!result) + ftp_state(data, ftpc, FTP_AUTH); + } + else + result = ftp_state_user(data, ftpc, conn); + return result; +} static CURLcode ftp_pp_statemachine(struct Curl_easy *data, struct connectdata *conn) @@ -2705,7 +2869,6 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); struct pingpong *pp; - static const char * const ftpauth[] = { "SSL", "TLS" }; size_t nread = 0; if(!ftpc || !ftp) @@ -2715,455 +2878,306 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, return Curl_pp_flushsend(data, pp); result = ftp_readresp(data, ftpc, FIRSTSOCKET, pp, &ftpcode, &nread); - if(result) + if(result || !ftpcode) return result; - if(ftpcode) { - /* we have now received a full FTP server response */ - switch(ftpc->state) { - case FTP_WAIT220: - if(ftpcode == 230) { - /* 230 User logged in - already! Take as 220 if TLS required. */ - if(data->set.use_ssl <= CURLUSESSL_TRY || - conn->bits.ftp_use_control_ssl) - return ftp_state_user_resp(data, ftpc, ftpcode); - } - else if(ftpcode != 220) { - failf(data, "Got a %03d ftp-server response when 220 was expected", - ftpcode); - return CURLE_WEIRD_SERVER_REPLY; - } - - /* We have received a 220 response fine, now we proceed. */ -#ifdef HAVE_GSSAPI - if(data->set.krb) { - /* If not anonymous login, try a secure login. Note that this - procedure is still BLOCKING. */ - - Curl_sec_request_prot(conn, "private"); - /* We set private first as default, in case the line below fails to - set a valid level */ - Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]); - - if(Curl_sec_login(data, conn)) { - failf(data, "secure login failed"); - return CURLE_WEIRD_SERVER_REPLY; - } - infof(data, "Authentication successful"); - } -#endif - - if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) { - /* We do not have an SSL/TLS control connection yet, but FTPS is - requested. Try an FTPS connection now */ - - ftpc->count3 = 0; - switch((long)data->set.ftpsslauth) { - case CURLFTPAUTH_DEFAULT: - case CURLFTPAUTH_SSL: - ftpc->count2 = 1; /* add one to get next */ - ftpc->count1 = 0; - break; - case CURLFTPAUTH_TLS: - ftpc->count2 = -1; /* subtract one to get next */ - ftpc->count1 = 1; - break; - default: - failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d", - (int)data->set.ftpsslauth); - return CURLE_UNKNOWN_OPTION; /* we do not know what to do */ - } - result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", - ftpauth[ftpc->count1]); - if(!result) - ftp_state(data, ftpc, FTP_AUTH); - } - else - result = ftp_state_user(data, ftpc, conn); - break; + /* we have now received a full FTP server response */ + switch(ftpc->state) { + case FTP_WAIT220: + result = ftp_wait_resp(data, conn, ftpc, ftpcode); + break; - case FTP_AUTH: - /* we have gotten the response to a previous AUTH command */ + case FTP_AUTH: + /* we have gotten the response to a previous AUTH command */ - if(pp->overflow) - return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */ + if(pp->overflow) + return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */ - /* RFC2228 (page 5) says: - * - * If the server is willing to accept the named security mechanism, - * and does not require any security data, it must respond with - * reply code 234/334. - */ + /* RFC2228 (page 5) says: + * + * If the server is willing to accept the named security mechanism, + * and does not require any security data, it must respond with + * reply code 234/334. + */ - if((ftpcode == 234) || (ftpcode == 334)) { - /* this was BLOCKING, keep it so for now */ - bool done; - if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); - if(result) { - /* we failed and bail out */ - return CURLE_USE_SSL_FAILED; - } - } - result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, &done); - if(!result) { - conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */ - conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */ - result = ftp_state_user(data, ftpc, conn); + if((ftpcode == 234) || (ftpcode == 334)) { + /* this was BLOCKING, keep it so for now */ + bool done; + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + if(result) { + /* we failed and bail out */ + return CURLE_USE_SSL_FAILED; } } - else if(ftpc->count3 < 1) { - ftpc->count3++; - ftpc->count1 += ftpc->count2; /* get next attempt */ - result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", - ftpauth[ftpc->count1]); - /* remain in this same state */ - } - else { - if(data->set.use_ssl > CURLUSESSL_TRY) - /* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */ - result = CURLE_USE_SSL_FAILED; - else - /* ignore the failure and continue */ - result = ftp_state_user(data, ftpc, conn); - } - break; - - case FTP_USER: - case FTP_PASS: - result = ftp_state_user_resp(data, ftpc, ftpcode); - break; - - case FTP_ACCT: - result = ftp_state_acct_resp(data, ftpc, ftpcode); - break; - - case FTP_PBSZ: - result = - Curl_pp_sendf(data, &ftpc->pp, "PROT %c", - data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); - if(!result) - ftp_state(data, ftpc, FTP_PROT); - break; - - case FTP_PROT: - if(ftpcode/100 == 2) - /* We have enabled SSL for the data connection! */ - conn->bits.ftp_use_data_ssl = - (data->set.use_ssl != CURLUSESSL_CONTROL); - /* FTP servers typically responds with 500 if they decide to reject - our 'P' request */ - else if(data->set.use_ssl > CURLUSESSL_CONTROL) - /* we failed and bails out */ - return CURLE_USE_SSL_FAILED; - - if(data->set.ftp_ccc) { - /* CCC - Clear Command Channel - */ - result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC"); - if(!result) - ftp_state(data, ftpc, FTP_CCC); + result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, &done); + if(!result) { + conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */ + conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */ + result = ftp_state_user(data, ftpc, conn); } + } + else if(ftpc->count3 < 1) { + ftpc->count3++; + ftpc->count1 += ftpc->count2; /* get next attempt */ + result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", + ftpauth[ftpc->count1]); + /* remain in this same state */ + } + else { + if(data->set.use_ssl > CURLUSESSL_TRY) + /* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */ + result = CURLE_USE_SSL_FAILED; else - result = ftp_state_pwd(data, ftpc); - break; - - case FTP_CCC: - if(ftpcode < 500) { - /* First shut down the SSL layer (note: this call will block) */ - /* This has only been tested on the proftpd server, and the mod_tls - * code sends a close notify alert without waiting for a close notify - * alert in response. Thus we wait for a close notify alert from the - * server, but we do not send one. Let's hope other servers do - * the same... */ - result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET, - (data->set.ftp_ccc == (unsigned char)CURLFTPSSL_CCC_ACTIVE)); + /* ignore the failure and continue */ + result = ftp_state_user(data, ftpc, conn); + } + break; - if(result) - failf(data, "Failed to clear the command channel (CCC)"); - } - if(!result) - /* Then continue as normal */ - result = ftp_state_pwd(data, ftpc); - break; + case FTP_USER: + case FTP_PASS: + result = ftp_state_user_resp(data, ftpc, ftpcode); + break; - case FTP_PWD: - if(ftpcode == 257) { - char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first - letter */ - bool entry_extracted = FALSE; - struct dynbuf out; - curlx_dyn_init(&out, 1000); + case FTP_ACCT: + result = ftp_state_acct_resp(data, ftpc, ftpcode); + break; - /* Reply format is like - 257<space>[rubbish]"<directory-name>"<space><commentary> and the - RFC959 says + case FTP_PBSZ: + result = + Curl_pp_sendf(data, &ftpc->pp, "PROT %c", + data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); + if(!result) + ftp_state(data, ftpc, FTP_PROT); + break; - The directory name can contain any character; embedded - double-quotes should be escaped by double-quotes (the - "quote-doubling" convention). - */ + case FTP_PROT: + if(ftpcode/100 == 2) + /* We have enabled SSL for the data connection! */ + conn->bits.ftp_use_data_ssl = + (data->set.use_ssl != CURLUSESSL_CONTROL); + /* FTP servers typically responds with 500 if they decide to reject + our 'P' request */ + else if(data->set.use_ssl > CURLUSESSL_CONTROL) + /* we failed and bails out */ + return CURLE_USE_SSL_FAILED; + + if(data->set.ftp_ccc) { + /* CCC - Clear Command Channel + */ + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC"); + if(!result) + ftp_state(data, ftpc, FTP_CCC); + } + else + result = ftp_state_pwd(data, ftpc); + break; - /* scan for the first double-quote for non-standard responses */ - while(*ptr != '\n' && *ptr != '\0' && *ptr != '"') - ptr++; + case FTP_CCC: + if(ftpcode < 500) { + /* First shut down the SSL layer (note: this call will block) */ + /* This has only been tested on the proftpd server, and the mod_tls + * code sends a close notify alert without waiting for a close notify + * alert in response. Thus we wait for a close notify alert from the + * server, but we do not send one. Let's hope other servers do + * the same... */ + result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET, + (data->set.ftp_ccc == + (unsigned char)CURLFTPSSL_CCC_ACTIVE)); + if(result) + failf(data, "Failed to clear the command channel (CCC)"); + } + if(!result) + /* Then continue as normal */ + result = ftp_state_pwd(data, ftpc); + break; - if('\"' == *ptr) { - /* it started good */ - for(ptr++; *ptr; ptr++) { - if('\"' == *ptr) { - if('\"' == ptr[1]) { - /* "quote-doubling" */ - result = curlx_dyn_addn(&out, &ptr[1], 1); - ptr++; - } - else { - /* end of path */ - if(curlx_dyn_len(&out)) - entry_extracted = TRUE; - break; /* get out of this loop */ - } - } - else - result = curlx_dyn_addn(&out, ptr, 1); - if(result) - return result; - } - } - if(entry_extracted) { - /* If the path name does not look like an absolute path (i.e.: it - does not start with a '/'), we probably need some server-dependent - adjustments. For example, this is the case when connecting to - an OS400 FTP server: this server supports two name syntaxes, - the default one being incompatible with standard paths. In - addition, this server switches automatically to the regular path - syntax when one is encountered in a command: this results in - having an entrypath in the wrong syntax when later used in CWD. - The method used here is to check the server OS: we do it only - if the path name looks strange to minimize overhead on other - systems. */ - char *dir = curlx_dyn_ptr(&out); - - if(!ftpc->server_os && dir[0] != '/') { - result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST"); - if(result) { - free(dir); - return result; - } - free(ftpc->entrypath); - ftpc->entrypath = dir; /* remember this */ - infof(data, "Entry path is '%s'", ftpc->entrypath); - /* also save it where getinfo can access it: */ - free(data->state.most_recent_ftp_entrypath); - data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath); - if(!data->state.most_recent_ftp_entrypath) - return CURLE_OUT_OF_MEMORY; - ftp_state(data, ftpc, FTP_SYST); - break; - } + case FTP_PWD: + result = ftp_pwd_resp(data, ftpc, ftpcode); + break; - free(ftpc->entrypath); - ftpc->entrypath = dir; /* remember this */ - infof(data, "Entry path is '%s'", ftpc->entrypath); - /* also save it where getinfo can access it: */ - free(data->state.most_recent_ftp_entrypath); - data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath); - if(!data->state.most_recent_ftp_entrypath) - return CURLE_OUT_OF_MEMORY; - } - else { - /* could not get the path */ - curlx_dyn_free(&out); - infof(data, "Failed to figure out path"); - } - } - ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ - CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); - break; + case FTP_SYST: + if(ftpcode == 215) { + char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ + char *os; + char *start; - case FTP_SYST: - if(ftpcode == 215) { - char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first - letter */ - char *os; - char *start; - - /* Reply format is like - 215<space><OS-name><space><commentary> - */ - while(*ptr == ' ') - ptr++; - for(start = ptr; *ptr && *ptr != ' '; ptr++) - ; - os = Curl_memdup0(start, ptr - start); - if(!os) - return CURLE_OUT_OF_MEMORY; + /* Reply format is like + 215<space><OS-name><space><commentary> + */ + while(*ptr == ' ') + ptr++; + for(start = ptr; *ptr && *ptr != ' '; ptr++) + ; + os = Curl_memdup0(start, ptr - start); + if(!os) + return CURLE_OUT_OF_MEMORY; - /* Check for special servers here. */ - if(curl_strequal(os, "OS/400")) { - /* Force OS400 name format 1. */ - result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1"); - if(result) { - free(os); - return result; - } - /* remember target server OS */ - free(ftpc->server_os); - ftpc->server_os = os; - ftp_state(data, ftpc, FTP_NAMEFMT); - break; + /* Check for special servers here. */ + if(curl_strequal(os, "OS/400")) { + /* Force OS400 name format 1. */ + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1"); + if(result) { + free(os); + return result; } - /* Nothing special for the target server. */ /* remember target server OS */ free(ftpc->server_os); ftpc->server_os = os; + ftp_state(data, ftpc, FTP_NAMEFMT); + break; } - else { - /* Cannot identify server OS. Continue anyway and cross fingers. */ - } + /* Nothing special for the target server. */ + /* remember target server OS */ + free(ftpc->server_os); + ftpc->server_os = os; + } + else { + /* Cannot identify server OS. Continue anyway and cross fingers. */ + } + + ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); + break; - ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ - CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); + case FTP_NAMEFMT: + if(ftpcode == 250) { + /* Name format change successful: reload initial path. */ + ftp_state_pwd(data, ftpc); break; + } - case FTP_NAMEFMT: - if(ftpcode == 250) { - /* Name format change successful: reload initial path. */ - ftp_state_pwd(data, ftpc); - break; - } + ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); + break; - ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ - CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); - break; + case FTP_QUOTE: + case FTP_POSTQUOTE: + case FTP_RETR_PREQUOTE: + case FTP_STOR_PREQUOTE: + case FTP_LIST_PREQUOTE: + if((ftpcode >= 400) && !ftpc->count2) { + /* failure response code, and not allowed to fail */ + failf(data, "QUOT command failed with %03d", ftpcode); + result = CURLE_QUOTE_ERROR; + } + else + result = ftp_state_quote(data, ftpc, ftp, FALSE, ftpc->state); + break; - case FTP_QUOTE: - case FTP_POSTQUOTE: - case FTP_RETR_PREQUOTE: - case FTP_STOR_PREQUOTE: - case FTP_LIST_PREQUOTE: - if((ftpcode >= 400) && !ftpc->count2) { - /* failure response code, and not allowed to fail */ - failf(data, "QUOT command failed with %03d", ftpcode); - result = CURLE_QUOTE_ERROR; - } - else - result = ftp_state_quote(data, ftpc, ftp, FALSE, ftpc->state); - break; + case FTP_CWD: + if(ftpcode/100 != 2) { + /* failure to CWD there */ + if(data->set.ftp_create_missing_dirs && + ftpc->cwdcount && !ftpc->count2) { + /* try making it */ + ftpc->count2++; /* counter to prevent CWD-MKD loops */ - case FTP_CWD: - if(ftpcode/100 != 2) { - /* failure to CWD there */ - if(data->set.ftp_create_missing_dirs && - ftpc->cwdcount && !ftpc->count2) { - /* try making it */ - ftpc->count2++; /* counter to prevent CWD-MKD loops */ - - /* count3 is set to allow MKD to fail once per dir. In the case when - CWD fails and then MKD fails (due to another session raced it to - create the dir) this then allows for a second try to CWD to it. */ - ftpc->count3 = (data->set.ftp_create_missing_dirs == 2) ? 1 : 0; - - result = Curl_pp_sendf(data, &ftpc->pp, "MKD %.*s", - pathlen(ftpc, ftpc->cwdcount - 1), - pathpiece(ftpc, ftpc->cwdcount - 1)); - if(!result) - ftp_state(data, ftpc, FTP_MKD); - } - else { - /* return failure */ - failf(data, "Server denied you to change to the given directory"); - ftpc->cwdfail = TRUE; /* do not remember this path as we failed - to enter it */ - result = CURLE_REMOTE_ACCESS_DENIED; - } + /* count3 is set to allow MKD to fail once per dir. In the case when + CWD fails and then MKD fails (due to another session raced it to + create the dir) this then allows for a second try to CWD to it. */ + ftpc->count3 = (data->set.ftp_create_missing_dirs == 2) ? 1 : 0; + + result = Curl_pp_sendf(data, &ftpc->pp, "MKD %.*s", + pathlen(ftpc, ftpc->cwdcount - 1), + pathpiece(ftpc, ftpc->cwdcount - 1)); + if(!result) + ftp_state(data, ftpc, FTP_MKD); } else { - /* success */ - ftpc->count2 = 0; - if(ftpc->cwdcount >= ftpc->dirdepth) - result = ftp_state_mdtm(data, ftpc, ftp); - else { - ftpc->cwdcount++; - /* send next CWD */ - result = Curl_pp_sendf(data, &ftpc->pp, "CWD %.*s", - pathlen(ftpc, ftpc->cwdcount - 1), - pathpiece(ftpc, ftpc->cwdcount - 1)); - } - } - break; - - case FTP_MKD: - if((ftpcode/100 != 2) && !ftpc->count3--) { - /* failure to MKD the dir */ - failf(data, "Failed to MKD dir: %03d", ftpcode); + /* return failure */ + failf(data, "Server denied you to change to the given directory"); + ftpc->cwdfail = TRUE; /* do not remember this path as we failed + to enter it */ result = CURLE_REMOTE_ACCESS_DENIED; } + } + else { + /* success */ + ftpc->count2 = 0; + if(ftpc->cwdcount >= ftpc->dirdepth) + result = ftp_state_mdtm(data, ftpc, ftp); else { - ftp_state(data, ftpc, FTP_CWD); - /* send CWD */ + ftpc->cwdcount++; + /* send next CWD */ result = Curl_pp_sendf(data, &ftpc->pp, "CWD %.*s", pathlen(ftpc, ftpc->cwdcount - 1), pathpiece(ftpc, ftpc->cwdcount - 1)); } - break; + } + break; - case FTP_MDTM: - result = ftp_state_mdtm_resp(data, ftpc, ftp, ftpcode); - break; + case FTP_MKD: + if((ftpcode/100 != 2) && !ftpc->count3--) { + /* failure to MKD the dir */ + failf(data, "Failed to MKD dir: %03d", ftpcode); + result = CURLE_REMOTE_ACCESS_DENIED; + } + else { + ftp_state(data, ftpc, FTP_CWD); + /* send CWD */ + result = Curl_pp_sendf(data, &ftpc->pp, "CWD %.*s", + pathlen(ftpc, ftpc->cwdcount - 1), + pathpiece(ftpc, ftpc->cwdcount - 1)); + } + break; - case FTP_TYPE: - case FTP_LIST_TYPE: - case FTP_RETR_TYPE: - case FTP_STOR_TYPE: - case FTP_RETR_LIST_TYPE: - result = ftp_state_type_resp(data, ftpc, ftp, ftpcode, ftpc->state); - break; + case FTP_MDTM: + result = ftp_state_mdtm_resp(data, ftpc, ftp, ftpcode); + break; - case FTP_SIZE: - case FTP_RETR_SIZE: - case FTP_STOR_SIZE: - result = ftp_state_size_resp(data, ftpc, ftp, ftpcode, ftpc->state); - break; + case FTP_TYPE: + case FTP_LIST_TYPE: + case FTP_RETR_TYPE: + case FTP_STOR_TYPE: + case FTP_RETR_LIST_TYPE: + result = ftp_state_type_resp(data, ftpc, ftp, ftpcode, ftpc->state); + break; - case FTP_REST: - case FTP_RETR_REST: - result = ftp_state_rest_resp(data, ftpc, ftp, ftpcode, ftpc->state); - break; + case FTP_SIZE: + case FTP_RETR_SIZE: + case FTP_STOR_SIZE: + result = ftp_state_size_resp(data, ftpc, ftp, ftpcode, ftpc->state); + break; - case FTP_PRET: - if(ftpcode != 200) { - /* there only is this one standard OK return code. */ - failf(data, "PRET command not accepted: %03d", ftpcode); - return CURLE_FTP_PRET_FAILED; - } - result = ftp_state_use_pasv(data, ftpc, conn); - break; + case FTP_REST: + case FTP_RETR_REST: + result = ftp_state_rest_resp(data, ftpc, ftp, ftpcode, ftpc->state); + break; - case FTP_PASV: - result = ftp_state_pasv_resp(data, ftpc, ftpcode); - break; + case FTP_PRET: + if(ftpcode != 200) { + /* there only is this one standard OK return code. */ + failf(data, "PRET command not accepted: %03d", ftpcode); + return CURLE_FTP_PRET_FAILED; + } + result = ftp_state_use_pasv(data, ftpc, conn); + break; - case FTP_PORT: - result = ftp_state_port_resp(data, ftpc, ftp, ftpcode); - break; + case FTP_PASV: + result = ftp_state_pasv_resp(data, ftpc, ftpcode); + break; - case FTP_LIST: - case FTP_RETR: - result = ftp_state_get_resp(data, ftpc, ftp, ftpcode, ftpc->state); - break; + case FTP_PORT: + result = ftp_state_port_resp(data, ftpc, ftp, ftpcode); + break; - case FTP_STOR: - result = ftp_state_stor_resp(data, ftpc, ftpcode, ftpc->state); - break; + case FTP_LIST: + case FTP_RETR: + result = ftp_state_get_resp(data, ftpc, ftp, ftpcode, ftpc->state); + break; - case FTP_QUIT: - default: - /* internal error */ - ftp_state(data, ftpc, FTP_STOP); - break; - } - } /* if(ftpcode) */ + case FTP_STOR: + result = ftp_state_stor_resp(data, ftpc, ftpcode, ftpc->state); + break; + + case FTP_QUIT: + default: + /* internal error */ + ftp_state(data, ftpc, FTP_STOP); + break; + } return result; } diff --git a/lib/hsts.c b/lib/hsts.c index 91f32276f..9525158bc 100644 --- a/lib/hsts.c +++ b/lib/hsts.c @@ -426,7 +426,7 @@ static CURLcode hsts_add(struct hsts *h, const char *line) bool subdomain = FALSE; struct stsentry *e; char dbuf[MAX_HSTS_DATELEN + 1]; - time_t expires; + time_t expires = 0; const char *hp = curlx_str(&host); /* The date parser works on a null-terminated string. The maximum length @@ -434,8 +434,10 @@ static CURLcode hsts_add(struct hsts *h, const char *line) memcpy(dbuf, curlx_str(&date), curlx_strlen(&date)); dbuf[curlx_strlen(&date)] = 0; - expires = strcmp(dbuf, UNLIMITED) ? Curl_getdate_capped(dbuf) : - TIME_T_MAX; + if(!strcmp(dbuf, UNLIMITED)) + expires = TIME_T_MAX; + else + Curl_getdate_capped(dbuf, &expires); if(hp[0] == '.') { curlx_str_nudge(&host, 1); @@ -478,14 +480,14 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) e.name[0] = 0; /* just to make it clean */ sc = data->set.hsts_read(data, &e, data->set.hsts_read_userp); if(sc == CURLSTS_OK) { - time_t expires; + time_t expires = 0; CURLcode result; DEBUGASSERT(e.name[0]); if(!e.name[0]) /* bail out if no name was stored */ return CURLE_BAD_FUNCTION_ARGUMENT; if(e.expire[0]) - expires = Curl_getdate_capped(e.expire); + Curl_getdate_capped(e.expire, &expires); else expires = TIME_T_MAX; /* the end of time */ result = hsts_create(h, e.name, strlen(e.name), diff --git a/lib/http.c b/lib/http.c index 06ab3f164..bcbe6e9e7 100644 --- a/lib/http.c +++ b/lib/http.c @@ -105,24 +105,16 @@ static void http_exp100_got100(struct Curl_easy *data); static CURLcode http_firstwrite(struct Curl_easy *data); static CURLcode http_header(struct Curl_easy *data, const char *hd, size_t hdlen); -static CURLcode http_host(struct Curl_easy *data, struct connectdata *conn); static CURLcode http_range(struct Curl_easy *data, Curl_HttpReq httpreq); -static CURLcode http_req_complete(struct Curl_easy *data, - struct dynbuf *r, int httpversion, - Curl_HttpReq httpreq); -static CURLcode http_req_set_reader(struct Curl_easy *data, - Curl_HttpReq httpreq, int httpversion, - const char **tep); +static CURLcode http_req_set_TE(struct Curl_easy *data, + struct dynbuf *req, + int httpversion); static CURLcode http_size(struct Curl_easy *data); static CURLcode http_statusline(struct Curl_easy *data, struct connectdata *conn); -static CURLcode http_target(struct Curl_easy *data, struct connectdata *conn, - struct dynbuf *req); +static CURLcode http_target(struct Curl_easy *data, struct dynbuf *req); static CURLcode http_useragent(struct Curl_easy *data); -#ifdef HAVE_LIBZ -static CURLcode http_transferencode(struct Curl_easy *data); -#endif /* @@ -1709,10 +1701,8 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, we will force length zero then */ curlx_str_casecompare(&name, "Content-Length")) ; - else if(data->state.aptr.te && - /* when asking for Transfer-Encoding, do not pass on a custom - Connection: */ - curlx_str_casecompare(&name, "Connection")) + else if(curlx_str_casecompare(&name, "Connection")) + /* Normal Connection: header generation takes care of this */ ; else if((httpversion >= 20) && curlx_str_casecompare(&name, "Transfer-Encoding")) @@ -1817,14 +1807,14 @@ CURLcode Curl_add_timecondition(struct Curl_easy *data, } #endif -void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, +void Curl_http_method(struct Curl_easy *data, const char **method, Curl_HttpReq *reqp) { Curl_HttpReq httpreq = (Curl_HttpReq)data->state.httpreq; const char *request; - if(conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) + if(data->conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) httpreq = HTTPREQ_GET; - else if((conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_FTP)) && + else if((data->conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_FTP)) && data->state.upload) httpreq = HTTPREQ_PUT; @@ -1875,10 +1865,12 @@ static CURLcode http_useragent(struct Curl_easy *data) } -static CURLcode http_host(struct Curl_easy *data, struct connectdata *conn) +static CURLcode http_set_aptr_host(struct Curl_easy *data) { - const char *ptr; + struct connectdata *conn = data->conn; struct dynamically_allocated_data *aptr = &data->state.aptr; + const char *ptr; + if(!data->state.this_is_a_follow) { /* Free to avoid leaking memory on multiple requests */ free(data->state.first_host); @@ -1966,7 +1958,6 @@ static CURLcode http_host(struct Curl_easy *data, struct connectdata *conn) * Append the request-target to the HTTP request */ static CURLcode http_target(struct Curl_easy *data, - struct connectdata *conn, struct dynbuf *r) { CURLcode result = CURLE_OK; @@ -1979,7 +1970,7 @@ static CURLcode http_target(struct Curl_easy *data, } #ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + if(data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy) { /* Using a proxy but does not tunnel through it */ /* The path sent to the proxy is in fact the entire URL. But if the remote @@ -1993,8 +1984,8 @@ static CURLcode http_target(struct Curl_easy *data, if(!h) return CURLE_OUT_OF_MEMORY; - if(conn->host.dispname != conn->host.name) { - uc = curl_url_set(h, CURLUPART_HOST, conn->host.name, 0); + if(data->conn->host.dispname != data->conn->host.name) { + uc = curl_url_set(h, CURLUPART_HOST, data->conn->host.name, 0); if(uc) { curl_url_cleanup(h); return CURLE_OUT_OF_MEMORY; @@ -2060,8 +2051,6 @@ static CURLcode http_target(struct Curl_easy *data, } else -#else - (void)conn; /* not used in disabled-proxy builds */ #endif { result = curlx_dyn_add(r, path); @@ -2247,21 +2236,13 @@ static CURLcode http_resume(struct Curl_easy *data, Curl_HttpReq httpreq) return CURLE_OK; } -static CURLcode http_req_set_reader(struct Curl_easy *data, - Curl_HttpReq httpreq, int httpversion, - const char **tep) +static CURLcode http_req_set_TE(struct Curl_easy *data, + struct dynbuf *req, + int httpversion) { CURLcode result = CURLE_OK; const char *ptr; - result = set_reader(data, httpreq); - if(result) - return result; - - result = http_resume(data, httpreq); - if(result) - return result; - ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding")); if(ptr) { /* Some kind of TE is requested, check if 'chunked' is chosen */ @@ -2295,7 +2276,7 @@ static CURLcode http_req_set_reader(struct Curl_easy *data, } if(data->req.upload_chunky) - *tep = "Transfer-Encoding: chunked\r\n"; + result = curlx_dyn_add(req, "Transfer-Encoding: chunked\r\n"); } return result; } @@ -2335,9 +2316,10 @@ static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r, return CURLE_OK; } -static CURLcode http_req_complete(struct Curl_easy *data, - struct dynbuf *r, int httpversion, - Curl_HttpReq httpreq) +static CURLcode http_add_content_hds(struct Curl_easy *data, + struct dynbuf *r, + int httpversion, + Curl_HttpReq httpreq) { CURLcode result = CURLE_OK; curl_off_t req_clen; @@ -2405,26 +2387,17 @@ static CURLcode http_req_complete(struct Curl_easy *data, break; } - /* end of headers */ - result = curlx_dyn_addn(r, STRCONST("\r\n")); - if(!result) { - Curl_pgrsSetUploadSize(data, req_clen); - if(announced_exp100) - result = http_exp100_add_reader(data); - } + Curl_pgrsSetUploadSize(data, req_clen); + if(announced_exp100) + result = http_exp100_add_reader(data); out: - if(!result) { - /* setup variables for the upcoming transfer */ - Curl_xfer_setup_sendrecv(data, FIRSTSOCKET, -1); - } return result; } #ifndef CURL_DISABLE_COOKIES static CURLcode http_cookies(struct Curl_easy *data, - struct connectdata *conn, struct dynbuf *r) { CURLcode result = CURLE_OK; @@ -2441,15 +2414,9 @@ static CURLcode http_cookies(struct Curl_easy *data, if(data->cookies && data->state.cookie_engine) { const char *host = data->state.aptr.cookiehost ? - data->state.aptr.cookiehost : conn->host.name; - const bool secure_context = - conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || - curl_strequal("localhost", host) || - !strcmp(host, "127.0.0.1") || - !strcmp(host, "::1"); + data->state.aptr.cookiehost : data->conn->host.name; Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - rc = Curl_cookie_getlist(data, data->cookies, host, data->state.up.path, - secure_context, &list); + rc = Curl_cookie_getlist(data, data->conn, host, &list); Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); } if(!rc) { @@ -2500,7 +2467,7 @@ static CURLcode http_cookies(struct Curl_easy *data, return result; } #else -#define http_cookies(a,b,c) CURLE_OK +#define http_cookies(a,b) CURLE_OK #endif static CURLcode http_range(struct Curl_easy *data, @@ -2622,63 +2589,13 @@ static CURLcode http_firstwrite(struct Curl_easy *data) return CURLE_OK; } -#ifdef HAVE_LIBZ -static CURLcode http_transferencode(struct Curl_easy *data) -{ - if(!Curl_checkheaders(data, STRCONST("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 do not do this magic - but then assume that the user will handle it all! */ - char *cptr = Curl_checkheaders(data, STRCONST("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; - } - return CURLE_OK; -} -#endif - -/* - * Curl_http() gets called from the generic multi_do() function when an HTTP - * request is to be performed. This creates and sends a properly constructed - * HTTP request. - */ -CURLcode Curl_http(struct Curl_easy *data, bool *done) +static CURLcode http_check_new_conn(struct Curl_easy *data) { struct connectdata *conn = data->conn; - CURLcode result = CURLE_OK; - 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 */ - unsigned char httpversion; - const char *alpn; const char *info_version = NULL; + const char *alpn; + CURLcode result; - /* 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; alpn = Curl_conn_get_alpn_negotiated(data, conn); if(alpn && !strcmp("h3", alpn)) { DEBUGASSERT(Curl_conn_http_version(data, conn) == 30); @@ -2690,7 +2607,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) conn->bits.proxy && !conn->bits.tunnel_proxy) { result = Curl_http2_switch(data); if(result) - goto fail; + return result; } else #endif @@ -2703,7 +2620,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) DEBUGF(infof(data, "HTTP/2 over clean TCP")); result = Curl_http2_switch(data); if(result) - goto fail; + return result; info_version = "HTTP/2"; /* There is no ALPN here, but the connection is now definitely h2 */ conn->httpversion_seen = 20; @@ -2712,213 +2629,321 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) info_version = "HTTP/1.x"; } - if(info_version && !data->conn->bits.reuse) + if(info_version) infof(data, "using %s", info_version); + return CURLE_OK; +} - /* Add collecting of headers written to client. For a new connection, - * we might have done that already, but reuse - * or multiplex needs it here as well. */ - result = Curl_headers_init(data); - if(result) - goto fail; +static CURLcode http_add_connection_hd(struct Curl_easy *data, + struct dynbuf *req) +{ + char *custom = Curl_checkheaders(data, STRCONST("Connection")); + char *custom_val = custom ? Curl_copy_header_value(custom) : NULL; + const char *sep = (custom_val && *custom_val) ? ", " : "Connection: "; + CURLcode result = CURLE_OK; + size_t rlen = curlx_dyn_len(req); - result = http_host(data, conn); - if(result) - goto fail; + if(custom && !custom_val) + return CURLE_OUT_OF_MEMORY; - result = http_useragent(data); - if(result) - goto fail; + if(custom_val && *custom_val) + result = curlx_dyn_addf(req, "Connection: %s", custom_val); + if(!result && data->state.http_hd_te) { + result = curlx_dyn_addf(req, "%s%s", sep, "TE"); + sep = ", "; + } + if(!result && data->state.http_hd_upgrade) { + result = curlx_dyn_addf(req, "%s%s", sep, "Upgrade"); + sep = ", "; + } + if(!result && data->state.http_hd_h2_settings) { + result = curlx_dyn_addf(req, "%s%s", sep, "HTTP2-Settings"); + } + if(rlen < curlx_dyn_len(req)) + result = curlx_dyn_addn(req, STRCONST("\r\n")); - Curl_http_method(data, conn, &request, &httpreq); + free(custom_val); + return result; +} - /* 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) - goto fail; - } +/* Header identifier in order we send them by default */ +typedef enum { + H1_HD_REQUEST, + H1_HD_HOST, +#ifndef CURL_DISABLE_PROXY + H1_HD_PROXY_AUTH, +#endif + H1_HD_USER_AUTH, + H1_HD_RANGE, + H1_HD_USER_AGENT, + H1_HD_ACCEPT, + H1_HD_TE, + H1_HD_ACCEPT_ENCODING, + H1_HD_REFERER, +#ifndef CURL_DISABLE_PROXY + H1_HD_PROXY_CONNECTION, +#endif + H1_HD_TRANSFER_ENCODING, +#ifndef CURL_DISABLE_ALTSVC + H1_HD_ALT_USED, +#endif + H1_HD_UPGRADE, + H1_HD_COOKIES, + H1_HD_CONDITIONALS, + H1_HD_CUSTOM, + H1_HD_CONTENT, + H1_HD_CONNECTION, + H1_HD_LAST /* the last, empty header line */ +} http_hd_t; + +static CURLcode http_add_hd(struct Curl_easy *data, + struct dynbuf *req, + http_hd_t id, + unsigned char httpversion, + const char *method, + Curl_HttpReq httpreq) +{ + CURLcode result = CURLE_OK; + switch(id) { + case H1_HD_REQUEST: + /* add the main request stuff */ + /* GET/HEAD/POST/PUT */ + result = curlx_dyn_addf(req, "%s ", method); + if(!result) + result = http_target(data, req); + if(!result) + result = curlx_dyn_addf(req, " HTTP/%s\r\n", + get_http_string(httpversion)); + break; - Curl_safefree(data->state.aptr.ref); - if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer"))) { - data->state.aptr.ref = aprintf("Referer: %s\r\n", data->state.referer); - if(!data->state.aptr.ref) - return CURLE_OUT_OF_MEMORY; - } + case H1_HD_HOST: + if(data->state.aptr.host) + result = curlx_dyn_add(req, data->state.aptr.host); + break; - if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && - data->set.str[STRING_ENCODING]) { - free(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); +#ifndef CURL_DISABLE_PROXY + case H1_HD_PROXY_AUTH: + if(data->state.aptr.proxyuserpwd) + result = curlx_dyn_add(req, data->state.aptr.proxyuserpwd); + break; +#endif + + case H1_HD_USER_AUTH: + if(data->state.aptr.userpwd) + result = curlx_dyn_add(req, data->state.aptr.userpwd); + break; + + case H1_HD_RANGE: + if(data->state.use_range && data->state.aptr.rangeline) + result = curlx_dyn_add(req, data->state.aptr.rangeline); + break; + + case H1_HD_USER_AGENT: + if(data->set.str[STRING_USERAGENT] && /* User-Agent: */ + *data->set.str[STRING_USERAGENT] && + data->state.aptr.uagent) + result = curlx_dyn_add(req, data->state.aptr.uagent); + break; + + case H1_HD_ACCEPT: + if(!Curl_checkheaders(data, STRCONST("Accept"))) + result = curlx_dyn_add(req, "Accept: */*\r\n"); + break; + case H1_HD_TE: #ifdef HAVE_LIBZ - /* we only consider transfer-encoding magic if libz support is built-in */ - result = http_transferencode(data); - if(result) - goto fail; + if(!Curl_checkheaders(data, STRCONST("TE")) && + data->set.http_transfer_encoding) { + data->state.http_hd_te = TRUE; + result = curlx_dyn_add(req, "TE: gzip\r\n"); + } #endif + break; - httpversion = http_request_version(data); - httpstring = get_http_string(httpversion); + case H1_HD_ACCEPT_ENCODING: + Curl_safefree(data->state.aptr.accept_encoding); + if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && + data->set.str[STRING_ENCODING]) + result = curlx_dyn_addf(req, "Accept-Encoding: %s\r\n", + data->set.str[STRING_ENCODING]); + break; - result = http_req_set_reader(data, httpreq, httpversion, &te); - if(result) - goto fail; + case H1_HD_REFERER: + Curl_safefree(data->state.aptr.ref); + if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer"))) + result = curlx_dyn_addf(req, "Referer: %s\r\n", data->state.referer); + break; - p_accept = Curl_checkheaders(data, - STRCONST("Accept")) ? NULL : "Accept: */*\r\n"; +#ifndef CURL_DISABLE_PROXY + case H1_HD_PROXY_CONNECTION: + if(data->conn->bits.httpproxy && + !data->conn->bits.tunnel_proxy && + !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && + !Curl_checkProxyheaders(data, data->conn, STRCONST("Proxy-Connection"))) + result = curlx_dyn_add(req, "Proxy-Connection: Keep-Alive\r\n"); + break; +#endif - result = http_range(data, httpreq); - if(result) - goto fail; + case H1_HD_TRANSFER_ENCODING: + result = http_req_set_TE(data, req, httpversion); + break; + +#ifndef CURL_DISABLE_ALTSVC + case H1_HD_ALT_USED: + if(data->conn->bits.altused && + !Curl_checkheaders(data, STRCONST("Alt-Used"))) + result = curlx_dyn_addf(req, "Alt-Used: %s:%d\r\n", + data->conn->conn_to_host.name, + data->conn->conn_to_port); + break; +#endif + + case H1_HD_UPGRADE: + if(!Curl_conn_is_ssl(data->conn, FIRSTSOCKET) && (httpversion < 20) && + (data->state.http_neg.wanted & CURL_HTTP_V2x) && + data->state.http_neg.h2_upgrade) { + /* append HTTP2 upgrade magic stuff to the HTTP request if it is not done + over SSL */ + result = Curl_http2_request_upgrade(req, data); + } +#ifndef CURL_DISABLE_WEBSOCKETS + if(!result && data->conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) + result = Curl_ws_request(data, req); +#endif + break; + + case H1_HD_COOKIES: + result = http_cookies(data, req); + break; + + case H1_HD_CONDITIONALS: + result = Curl_add_timecondition(data, req); + break; + + case H1_HD_CUSTOM: + result = Curl_add_custom_headers(data, FALSE, httpversion, req); + break; + + case H1_HD_CONTENT: + result = http_add_content_hds(data, req, httpversion, httpreq); + break; + + case H1_HD_CONNECTION: { + result = http_add_connection_hd(data, req); + break; + } + + case H1_HD_LAST: + result = curlx_dyn_addn(req, STRCONST("\r\n")); + break; + } + return result; +} +/* + * Curl_http() gets called from the generic multi_do() function when an HTTP + * request is to be performed. This creates and sends a properly constructed + * HTTP request. + */ +CURLcode Curl_http(struct Curl_easy *data, bool *done) +{ + CURLcode result = CURLE_OK; + Curl_HttpReq httpreq; + const char *method; + struct dynbuf req; + unsigned char httpversion; + size_t hd_id; + + /* 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; /* initialize a dynamic send-buffer */ curlx_dyn_init(&req, DYN_HTTP_REQUEST); - /* make sure the header buffer is reset - if there are leftovers from a previous transfer */ curlx_dyn_reset(&data->state.headerb); - /* add the main request stuff */ - /* GET/HEAD/POST/PUT */ - result = curlx_dyn_addf(&req, "%s ", request); - if(!result) - result = http_target(data, conn, &req); - if(result) { - curlx_dyn_free(&req); - goto fail; + if(!data->conn->bits.reuse) { + result = http_check_new_conn(data); + if(result) + goto out; } -#ifndef CURL_DISABLE_ALTSVC - if(conn->bits.altused && !Curl_checkheaders(data, STRCONST("Alt-Used"))) { - altused = aprintf("Alt-Used: %s:%d\r\n", - conn->conn_to_host.name, conn->conn_to_port); - if(!altused) { - curlx_dyn_free(&req); - return CURLE_OUT_OF_MEMORY; - } - } -#endif - result = - curlx_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 : ""), -#ifndef CURL_DISABLE_PROXY - data->state.aptr.proxyuserpwd ? - data->state.aptr.proxyuserpwd : "", -#else - "", -#endif - 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->state.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, STRCONST("Proxy-Connection")) && - !Curl_checkProxyheaders(data, conn, - STRCONST("Proxy-Connection"))) ? - "Proxy-Connection: Keep-Alive\r\n":"", -#else - "", -#endif - te, - altused ? altused : "" - ); + /* Add collecting of headers written to client. For a new connection, + * we might have done that already, but reuse + * or multiplex needs it here as well. */ + result = Curl_headers_init(data); + if(result) + goto out; - /* clear userpwd and proxyuserpwd to avoid reusing old credentials - * from reused connections */ - Curl_safefree(data->state.aptr.userpwd); -#ifndef CURL_DISABLE_PROXY - Curl_safefree(data->state.aptr.proxyuserpwd); -#endif - free(altused); + data->state.http_hd_te = FALSE; + data->state.http_hd_upgrade = FALSE; + data->state.http_hd_h2_settings = FALSE; - if(result) { - curlx_dyn_free(&req); - goto fail; - } + /* what kind of request do we need to send? */ + Curl_http_method(data, &method, &httpreq); - if(!Curl_conn_is_ssl(conn, FIRSTSOCKET) && (httpversion < 20) && - (data->state.http_neg.wanted & CURL_HTTP_V2x) && - data->state.http_neg.h2_upgrade) { - /* append HTTP2 upgrade magic stuff to the HTTP request if it is not done - over SSL */ - result = Curl_http2_request_upgrade(&req, data); - if(result) { - curlx_dyn_free(&req); - return result; + /* select host to send */ + result = http_set_aptr_host(data); + if(!result) { + /* setup the authentication headers, how that method and host are known */ + 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, data->conn, method, httpreq, + (pq ? pq : data->state.up.path), FALSE); + free(pq); } + if(result) + goto out; - result = http_cookies(data, conn, &req); -#ifndef CURL_DISABLE_WEBSOCKETS - if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) - result = Curl_ws_request(data, &req); -#endif + result = http_useragent(data); + if(result) + goto out; + + /* Setup input reader, resume information and ranges */ + result = set_reader(data, httpreq); if(!result) - result = Curl_add_timecondition(data, &req); + result = http_resume(data, httpreq); if(!result) - result = Curl_add_custom_headers(data, FALSE, httpversion, &req); + result = http_range(data, httpreq); + if(result) + goto out; - if(!result) { - /* req_send takes ownership of the 'req' memory on success */ - result = http_req_complete(data, &req, httpversion, httpreq); - if(!result) - result = Curl_req_send(data, &req, httpversion); + httpversion = http_request_version(data); + /* Add request line and all headers to `req` */ + for(hd_id = 0; hd_id <= H1_HD_LAST; ++hd_id) { + result = http_add_hd(data, &req, (http_hd_t)hd_id, + httpversion, method, httpreq); + if(result) + goto out; } - curlx_dyn_free(&req); - if(result) - goto fail; + + /* setup variables for the upcoming transfer and send */ + Curl_xfer_setup_sendrecv(data, FIRSTSOCKET, -1); + result = Curl_req_send(data, &req, httpversion); if((httpversion >= 20) && data->req.upload_chunky) /* upload_chunky was set above to set up the request in a chunky fashion, but is disabled here again to avoid that the chunked encoded version is actually used when sending the request body over h2 */ data->req.upload_chunky = FALSE; -fail: + +out: if(CURLE_TOO_LARGE == result) failf(data, "HTTP request too large"); + + /* clear userpwd and proxyuserpwd to avoid reusing old credentials + * from reused connections */ + Curl_safefree(data->state.aptr.userpwd); +#ifndef CURL_DISABLE_PROXY + Curl_safefree(data->state.aptr.proxyuserpwd); +#endif + curlx_dyn_free(&req); return result; } @@ -3171,7 +3196,8 @@ static CURLcode http_header_l(struct Curl_easy *data, (data->set.timecondition || data->set.get_filetime)) ? HD_VAL(hd, hdlen, "Last-Modified:") : NULL; if(v) { - k->timeofdoc = Curl_getdate_capped(v); + if(Curl_getdate_capped(v, &k->timeofdoc)) + k->timeofdoc = 0; if(data->set.get_filetime) data->info.filetime = k->timeofdoc; return CURLE_OK; @@ -3286,14 +3312,12 @@ static CURLcode http_header_r(struct Curl_easy *data, if(v) { /* Retry-After = HTTP-date / delay-seconds */ curl_off_t retry_after = 0; /* zero for unknown or "now" */ - time_t date; + time_t date = 0; curlx_str_passblanks(&v); /* try it as a date first, because a date can otherwise start with and get treated as a number */ - date = Curl_getdate_capped(v); - - if((time_t)-1 != date) { + if(!Curl_getdate_capped(v, &date)) { time_t current = time(NULL); if(date >= current) /* convert date to number of seconds into the future */ @@ -3334,14 +3358,8 @@ static CURLcode http_header_s(struct Curl_easy *data, * real peer hostname. */ const char *host = data->state.aptr.cookiehost ? data->state.aptr.cookiehost : conn->host.name; - const bool secure_context = - conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || - curl_strequal("localhost", host) || - !strcmp(host, "127.0.0.1") || - !strcmp(host, "::1"); - - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, - CURL_LOCK_ACCESS_SINGLE); + const bool secure_context = Curl_secure_context(conn, host); + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); Curl_cookie_add(data, data->cookies, TRUE, FALSE, v, host, data->state.up.path, secure_context); Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); @@ -4836,8 +4854,7 @@ static const struct Curl_crtype cr_exp100 = { Curl_creader_def_needs_rewind, Curl_creader_def_total_length, Curl_creader_def_resume_from, - Curl_creader_def_rewind, - Curl_creader_def_unpause, + Curl_creader_def_cntrl, Curl_creader_def_is_paused, cr_exp100_done, sizeof(struct cr_exp100_ctx) diff --git a/lib/http.h b/lib/http.h index 50279aedf..67ef17f5b 100644 --- a/lib/http.h +++ b/lib/http.h @@ -106,7 +106,7 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect, CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, bool is_connect, struct dynhds *hds); -void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, +void Curl_http_method(struct Curl_easy *data, const char **method, Curl_HttpReq *); /* protocol-specific functions set up to be called by the main engine */ diff --git a/lib/http2.c b/lib/http2.c index e550abc8f..e2cdc9181 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -799,7 +799,7 @@ static ssize_t send_callback(nghttp2_session *h2, ctx->nw_out_blocked = 1; return NGHTTP2_ERR_WOULDBLOCK; } - return (nwritten > SSIZE_T_MAX) ? + return (nwritten > SSIZE_MAX) ? NGHTTP2_ERR_CALLBACK_FAILURE : (ssize_t)nwritten; } @@ -1820,8 +1820,9 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, return result; } + data->state.http_hd_upgrade = TRUE; + data->state.http_hd_h2_settings = TRUE; result = curlx_dyn_addf(req, - "Connection: Upgrade, HTTP2-Settings\r\n" "Upgrade: %s\r\n" "HTTP2-Settings: %s\r\n", NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64); diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c index 850b7ae11..c62db499c 100644 --- a/lib/http_aws_sigv4.c +++ b/lib/http_aws_sigv4.c @@ -775,7 +775,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) } } - Curl_http_method(data, conn, &method, &httpreq); + Curl_http_method(data, &method, &httpreq); payload_hash = parse_content_sha_hdr(data, curlx_str(&provider1), diff --git a/lib/http_chunks.c b/lib/http_chunks.c index f014a256a..f735a820c 100644 --- a/lib/http_chunks.c +++ b/lib/http_chunks.c @@ -656,8 +656,7 @@ const struct Curl_crtype Curl_httpchunk_encoder = { Curl_creader_def_needs_rewind, cr_chunked_total_length, Curl_creader_def_resume_from, - Curl_creader_def_rewind, - Curl_creader_def_unpause, + Curl_creader_def_cntrl, Curl_creader_def_is_paused, Curl_creader_def_done, sizeof(struct chunked_reader) diff --git a/lib/http_proxy.c b/lib/http_proxy.c index df51484eb..2d742856c 100644 --- a/lib/http_proxy.c +++ b/lib/http_proxy.c @@ -162,11 +162,6 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, we will force length zero then */ hd_name_eq(name, namelen, STRCONST("Content-Length:"))) ; - else if(data->state.aptr.te && - /* when asking for Transfer-Encoding, do not pass on a custom - Connection: */ - hd_name_eq(name, namelen, STRCONST("Connection:"))) - ; else if((httpversion >= 20) && hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:"))) /* HTTP/2 and HTTP/3 do not support chunked requests */ @@ -34,7 +34,7 @@ #ifdef USE_OPENSSL #include <openssl/opensslv.h> -#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) && !defined(USE_AMISSL) +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(USE_AMISSL) /* OpenSSL 3.0.0 marks the MD4 functions as deprecated */ #define OPENSSL_NO_MD4 #else diff --git a/lib/mime.c b/lib/mime.c index 3d4eef767..f480bc704 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -2150,22 +2150,27 @@ static CURLcode cr_mime_resume_from(struct Curl_easy *data, return CURLE_OK; } -static CURLcode cr_mime_rewind(struct Curl_easy *data, - struct Curl_creader *reader) +static CURLcode cr_mime_cntrl(struct Curl_easy *data, + struct Curl_creader *reader, + Curl_creader_cntrl opcode) { struct cr_mime_ctx *ctx = reader->ctx; - CURLcode result = mime_rewind(ctx->part); - if(result) - failf(data, "Cannot rewind mime/post data"); - return result; -} - -static CURLcode cr_mime_unpause(struct Curl_easy *data, - struct Curl_creader *reader) -{ - struct cr_mime_ctx *ctx = reader->ctx; - (void)data; - mime_unpause(ctx->part); + switch(opcode) { + case CURL_CRCNTRL_REWIND: { + CURLcode result = mime_rewind(ctx->part); + if(result) + failf(data, "Cannot rewind mime/post data"); + return result; + } + case CURL_CRCNTRL_UNPAUSE: + mime_unpause(ctx->part); + break; + case CURL_CRCNTRL_CLEAR_EOS: + ctx->seen_eos = FALSE; + break; + default: + break; + } return CURLE_OK; } @@ -2185,8 +2190,7 @@ static const struct Curl_crtype cr_mime = { cr_mime_needs_rewind, cr_mime_total_length, cr_mime_resume_from, - cr_mime_rewind, - cr_mime_unpause, + cr_mime_cntrl, cr_mime_is_paused, Curl_creader_def_done, sizeof(struct cr_mime_ctx) diff --git a/lib/parsedate.c b/lib/parsedate.c index eedab7c41..b9429db7f 100644 --- a/lib/parsedate.c +++ b/lib/parsedate.c @@ -589,26 +589,12 @@ time_t curl_getdate(const char *p, const time_t *now) /* Curl_getdate_capped() differs from curl_getdate() in that this will return TIME_T_MAX in case the parsed time value was too big, instead of an - error. */ + error. Returns non-zero on error. */ -time_t Curl_getdate_capped(const char *p) +int Curl_getdate_capped(const char *p, time_t *tp) { - time_t parsed = -1; - int rc = parsedate(p, &parsed); - - switch(rc) { - case PARSEDATE_OK: - if(parsed == (time_t)-1) - /* avoid returning -1 for a working scenario */ - parsed++; - return parsed; - case PARSEDATE_LATER: - /* this returns the maximum time value */ - return parsed; - default: - return -1; /* everything else is fail */ - } - /* UNREACHABLE */ + int rc = parsedate(p, tp); + return (rc == PARSEDATE_FAIL); } /* diff --git a/lib/parsedate.h b/lib/parsedate.h index 84c37f167..e5efb53f6 100644 --- a/lib/parsedate.h +++ b/lib/parsedate.h @@ -29,10 +29,10 @@ extern const char * const Curl_month[12]; CURLcode Curl_gmtime(time_t intime, struct tm *store); -/* Curl_getdate_capped() differs from curl_getdate() in that this will return +/* Curl_getdate_capped() differs from curl_getdate() in that this returns TIME_T_MAX in case the parsed time value was too big, instead of an error. */ -time_t Curl_getdate_capped(const char *p); +int Curl_getdate_capped(const char *p, time_t *store); #endif /* HEADER_CURL_PARSEDATE_H */ diff --git a/lib/request.c b/lib/request.c index 733e3e929..8751ada55 100644 --- a/lib/request.c +++ b/lib/request.c @@ -308,7 +308,7 @@ static CURLcode req_flush(struct Curl_easy *data) } if(data->req.eos_read && !data->req.eos_sent) { - char tmp; + char tmp = 0; size_t nwritten; result = xfer_send(data, &tmp, 0, 0, &nwritten); if(result) diff --git a/lib/sendf.c b/lib/sendf.c index 43b30ecc7..551bb5ca8 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -151,7 +151,7 @@ CURLcode Curl_client_start(struct Curl_easy *data) CURL_TRC_READ(data, "client start, rewind readers"); while(r) { - result = r->crt->rewind(data, r); + result = r->crt->cntrl(data, r, CURL_CRCNTRL_REWIND); if(result) { failf(data, "rewind of client reader '%s' failed: %d", r->crt->name, result); @@ -218,15 +218,15 @@ static size_t get_max_body_write_len(struct Curl_easy *data, curl_off_t limit) return 0; } #if SIZEOF_CURL_OFF_T > SIZEOF_SIZE_T - else if(remain_diff > SSIZE_T_MAX) { - return SIZE_T_MAX; + else if(remain_diff > SSIZE_MAX) { + return SIZE_MAX; } #endif else { return (size_t)remain_diff; } } - return SIZE_T_MAX; + return SIZE_MAX; } struct cw_download_ctx { @@ -543,6 +543,15 @@ CURLcode Curl_creader_read(struct Curl_easy *data, return reader->crt->do_read(data, reader, buf, blen, nread, eos); } +void Curl_creader_clear_eos(struct Curl_easy *data, + struct Curl_creader *reader) +{ + while(reader) { + (void)reader->crt->cntrl(data, reader, CURL_CRCNTRL_CLEAR_EOS); + reader = reader->next; + } +} + CURLcode Curl_creader_def_init(struct Curl_easy *data, struct Curl_creader *reader) { @@ -598,19 +607,13 @@ CURLcode Curl_creader_def_resume_from(struct Curl_easy *data, return CURLE_READ_ERROR; } -CURLcode Curl_creader_def_rewind(struct Curl_easy *data, - struct Curl_creader *reader) -{ - (void)data; - (void)reader; - return CURLE_OK; -} - -CURLcode Curl_creader_def_unpause(struct Curl_easy *data, - struct Curl_creader *reader) +CURLcode Curl_creader_def_cntrl(struct Curl_easy *data, + struct Curl_creader *reader, + Curl_creader_cntrl opcode) { (void)data; (void)reader; + (void)opcode; return CURLE_OK; } @@ -891,12 +894,24 @@ static CURLcode cr_in_rewind(struct Curl_easy *data, return CURLE_OK; } -static CURLcode cr_in_unpause(struct Curl_easy *data, - struct Curl_creader *reader) +static CURLcode cr_in_cntrl(struct Curl_easy *data, + struct Curl_creader *reader, + Curl_creader_cntrl opcode) { struct cr_in_ctx *ctx = reader->ctx; - (void)data; - ctx->is_paused = FALSE; + + switch(opcode) { + case CURL_CRCNTRL_REWIND: + return cr_in_rewind(data, reader); + case CURL_CRCNTRL_UNPAUSE: + ctx->is_paused = FALSE; + break; + case CURL_CRCNTRL_CLEAR_EOS: + ctx->seen_eos = FALSE; + break; + default: + break; + } return CURLE_OK; } @@ -916,8 +931,7 @@ static const struct Curl_crtype cr_in = { cr_in_needs_rewind, cr_in_total_length, cr_in_resume_from, - cr_in_rewind, - cr_in_unpause, + cr_in_cntrl, cr_in_is_paused, Curl_creader_def_done, sizeof(struct cr_in_ctx) @@ -1077,8 +1091,7 @@ static const struct Curl_crtype cr_lc = { Curl_creader_def_needs_rewind, cr_lc_total_length, Curl_creader_def_resume_from, - Curl_creader_def_rewind, - Curl_creader_def_unpause, + Curl_creader_def_cntrl, Curl_creader_def_is_paused, Curl_creader_def_done, sizeof(struct cr_lc_ctx) @@ -1251,8 +1264,7 @@ static const struct Curl_crtype cr_null = { Curl_creader_def_needs_rewind, cr_null_total_length, Curl_creader_def_resume_from, - Curl_creader_def_rewind, - Curl_creader_def_unpause, + Curl_creader_def_cntrl, Curl_creader_def_is_paused, Curl_creader_def_done, sizeof(struct Curl_creader) @@ -1312,12 +1324,19 @@ static bool cr_buf_needs_rewind(struct Curl_easy *data, return ctx->index > 0; } -static CURLcode cr_buf_rewind(struct Curl_easy *data, - struct Curl_creader *reader) +static CURLcode cr_buf_cntrl(struct Curl_easy *data, + struct Curl_creader *reader, + Curl_creader_cntrl opcode) { struct cr_buf_ctx *ctx = reader->ctx; (void)data; - ctx->index = 0; + switch(opcode) { + case CURL_CRCNTRL_REWIND: + ctx->index = 0; + break; + default: + break; + } return CURLE_OK; } @@ -1360,8 +1379,7 @@ static const struct Curl_crtype cr_buf = { cr_buf_needs_rewind, cr_buf_total_length, cr_buf_resume_from, - cr_buf_rewind, - Curl_creader_def_unpause, + cr_buf_cntrl, Curl_creader_def_is_paused, Curl_creader_def_done, sizeof(struct cr_buf_ctx) @@ -1417,7 +1435,7 @@ CURLcode Curl_creader_unpause(struct Curl_easy *data) CURLcode result = CURLE_OK; while(reader) { - result = reader->crt->unpause(data, reader); + result = reader->crt->cntrl(data, reader, CURL_CRCNTRL_UNPAUSE); if(result) break; reader = reader->next; diff --git a/lib/sendf.h b/lib/sendf.h index e5cc600bf..686744390 100644 --- a/lib/sendf.h +++ b/lib/sendf.h @@ -50,6 +50,7 @@ #define CLIENTWRITE_1XX (1<<5) /* a 1xx response related HEADER */ #define CLIENTWRITE_TRAILER (1<<6) /* a trailer HEADER */ #define CLIENTWRITE_EOS (1<<7) /* End Of transfer download Stream */ +#define CLIENTWRITE_0LEN (1<<8) /* write even 0-length buffers */ /** * Write `len` bytes at `prt` to the client. `type` indicates what @@ -202,6 +203,11 @@ void Curl_cwriter_def_close(struct Curl_easy *data, struct Curl_cwriter *writer); +typedef enum { + CURL_CRCNTRL_REWIND, + CURL_CRCNTRL_UNPAUSE, + CURL_CRCNTRL_CLEAR_EOS +} Curl_creader_cntrl; /* Client Reader Type, provides the implementation */ struct Curl_crtype { @@ -215,8 +221,8 @@ struct Curl_crtype { struct Curl_creader *reader); CURLcode (*resume_from)(struct Curl_easy *data, struct Curl_creader *reader, curl_off_t offset); - CURLcode (*rewind)(struct Curl_easy *data, struct Curl_creader *reader); - CURLcode (*unpause)(struct Curl_easy *data, struct Curl_creader *reader); + CURLcode (*cntrl)(struct Curl_easy *data, struct Curl_creader *reader, + Curl_creader_cntrl opcode); bool (*is_paused)(struct Curl_easy *data, struct Curl_creader *reader); void (*done)(struct Curl_easy *data, struct Curl_creader *reader, int premature); @@ -264,10 +270,9 @@ curl_off_t Curl_creader_def_total_length(struct Curl_easy *data, CURLcode Curl_creader_def_resume_from(struct Curl_easy *data, struct Curl_creader *reader, curl_off_t offset); -CURLcode Curl_creader_def_rewind(struct Curl_easy *data, - struct Curl_creader *reader); -CURLcode Curl_creader_def_unpause(struct Curl_easy *data, - struct Curl_creader *reader); +CURLcode Curl_creader_def_cntrl(struct Curl_easy *data, + struct Curl_creader *reader, + Curl_creader_cntrl opcode); bool Curl_creader_def_is_paused(struct Curl_easy *data, struct Curl_creader *reader); void Curl_creader_def_done(struct Curl_easy *data, @@ -281,6 +286,10 @@ CURLcode Curl_creader_read(struct Curl_easy *data, struct Curl_creader *reader, char *buf, size_t blen, size_t *nread, bool *eos); +/* Tell the reader and all below that any EOS state is to be cleared */ +void Curl_creader_clear_eos(struct Curl_easy *data, + struct Curl_creader *reader); + /** * Create a new creader instance with given type and phase. Is not * inserted into the writer chain by this call. diff --git a/lib/setopt.c b/lib/setopt.c index 168417738..5adfe4dbe 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -1683,7 +1683,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, /* * Check that requested length does not overflow the size_t type. */ - else if(s->postfieldsize > SIZE_T_MAX) + else if(s->postfieldsize > SIZE_MAX) return CURLE_OUT_OF_MEMORY; #endif else { diff --git a/lib/smtp.c b/lib/smtp.c index 97083e41f..c52cf0dc5 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -2069,8 +2069,7 @@ static const struct Curl_crtype cr_eob = { Curl_creader_def_needs_rewind, cr_eob_total_length, Curl_creader_def_resume_from, - Curl_creader_def_rewind, - Curl_creader_def_unpause, + Curl_creader_def_cntrl, Curl_creader_def_is_paused, Curl_creader_def_done, sizeof(struct cr_eob_ctx) diff --git a/lib/socks_sspi.c b/lib/socks_sspi.c index 1e80ee18d..49210585b 100644 --- a/lib/socks_sspi.c +++ b/lib/socks_sspi.c @@ -90,7 +90,6 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, unsigned char socksreq[4]; /* room for GSS-API exchange header only */ const char *service = data->set.str[STRING_PROXY_SERVICE_NAME] ? data->set.str[STRING_PROXY_SERVICE_NAME] : "rcmd"; - const size_t service_length = strlen(service); /* GSS-API request looks like * +----+------+-----+----------------+ @@ -101,20 +100,12 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, */ /* prepare service name */ - if(strchr(service, '/')) { + if(strchr(service, '/')) service_name = strdup(service); - if(!service_name) - return CURLE_OUT_OF_MEMORY; - } - else { - service_name = malloc(service_length + - strlen(conn->socks_proxy.host.name) + 2); - if(!service_name) - return CURLE_OUT_OF_MEMORY; - msnprintf(service_name, service_length + - strlen(conn->socks_proxy.host.name) + 2, "%s/%s", - service, conn->socks_proxy.host.name); - } + else + service_name = aprintf("%s/%s", service, conn->socks_proxy.host.name); + if(!service_name) + return CURLE_OUT_OF_MEMORY; input_desc.cBuffers = 1; input_desc.pBuffers = &sspi_recv_token; @@ -132,6 +123,10 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_send_token.cbBuffer = 0; sspi_send_token.pvBuffer = NULL; + sspi_w_token[0].pvBuffer = + sspi_w_token[1].pvBuffer = + sspi_w_token[2].pvBuffer = NULL; + wrap_desc.cBuffers = 3; wrap_desc.pBuffers = sspi_w_token; wrap_desc.ulVersion = SECBUFFER_VERSION; @@ -139,21 +134,19 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, cred_handle.dwLower = 0; cred_handle.dwUpper = 0; - status = Curl_pSecFn->AcquireCredentialsHandle(NULL, + names.sUserName = NULL; + + status = + Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)CURL_UNCONST(TEXT("Kerberos")), - SECPKG_CRED_OUTBOUND, - NULL, - NULL, - NULL, - NULL, - &cred_handle, - &expiry); + SECPKG_CRED_OUTBOUND, + NULL, NULL, NULL, NULL, + &cred_handle, &expiry); if(check_sspi_err(data, status, "AcquireCredentialsHandle")) { failf(data, "Failed to acquire credentials."); - free(service_name); - Curl_pSecFn->FreeCredentialsHandle(&cred_handle); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } (void)curlx_nonblock(sock, FALSE); @@ -164,24 +157,24 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, TCHAR *sname; sname = curlx_convert_UTF8_to_tchar(service_name); - if(!sname) - return CURLE_OUT_OF_MEMORY; - - status = Curl_pSecFn->InitializeSecurityContext(&cred_handle, - context_handle, - sname, - ISC_REQ_MUTUAL_AUTH | - ISC_REQ_ALLOCATE_MEMORY | - ISC_REQ_CONFIDENTIALITY | - ISC_REQ_REPLAY_DETECT, - 0, - SECURITY_NATIVE_DREP, - &input_desc, - 0, - &sspi_context, - &output_desc, - &sspi_ret_flags, - &expiry); + if(!sname) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + + status = + Curl_pSecFn->InitializeSecurityContext(&cred_handle, context_handle, + sname, + ISC_REQ_MUTUAL_AUTH | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_REPLAY_DETECT, + 0, SECURITY_NATIVE_DREP, + &input_desc, 0, + &sspi_context, + &output_desc, + &sspi_ret_flags, + &expiry); curlx_unicodefree(sname); @@ -192,13 +185,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, } if(check_sspi_err(data, status, "InitializeSecurityContext")) { - free(service_name); - Curl_pSecFn->FreeCredentialsHandle(&cred_handle); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - if(sspi_recv_token.pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); failf(data, "Failed to initialise security context."); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } if(sspi_send_token.cbBuffer) { @@ -211,14 +200,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, &written); if(code || (written != 4)) { failf(data, "Failed to send SSPI authentication request."); - free(service_name); - if(sspi_send_token.pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - if(sspi_recv_token.pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - Curl_pSecFn->FreeCredentialsHandle(&cred_handle); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } code = Curl_conn_cf_send(cf->next, data, @@ -226,16 +209,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_send_token.cbBuffer, FALSE, &written); if(code || (sspi_send_token.cbBuffer != written)) { failf(data, "Failed to send SSPI authentication token."); - free(service_name); - if(sspi_send_token.pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - if(sspi_recv_token.pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - Curl_pSecFn->FreeCredentialsHandle(&cred_handle); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } - } if(sspi_send_token.pvBuffer) { @@ -266,29 +242,23 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); if(result || (actualread != 4)) { failf(data, "Failed to receive SSPI authentication response."); - free(service_name); - Curl_pSecFn->FreeCredentialsHandle(&cred_handle); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } /* ignore the first (VER) byte */ if(socksreq[1] == 255) { /* status / message type */ failf(data, "User was rejected by the SOCKS5 server (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); - free(service_name); - Curl_pSecFn->FreeCredentialsHandle(&cred_handle); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } if(socksreq[1] != 1) { /* status / message type */ failf(data, "Invalid SSPI authentication response type (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); - free(service_name); - Curl_pSecFn->FreeCredentialsHandle(&cred_handle); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } memcpy(&us_length, socksreq + 2, sizeof(short)); @@ -298,39 +268,32 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_recv_token.pvBuffer = malloc(us_length); if(!sspi_recv_token.pvBuffer) { - free(service_name); - Curl_pSecFn->FreeCredentialsHandle(&cred_handle); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto error; } result = Curl_blockread_all(cf, data, (char *)sspi_recv_token.pvBuffer, sspi_recv_token.cbBuffer, &actualread); if(result || (actualread != us_length)) { failf(data, "Failed to receive SSPI authentication token."); - free(service_name); - if(sspi_recv_token.pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - Curl_pSecFn->FreeCredentialsHandle(&cred_handle); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } context_handle = &sspi_context; } - free(service_name); + Curl_safefree(service_name); /* Everything is good so far, user was authenticated! */ status = Curl_pSecFn->QueryCredentialsAttributes(&cred_handle, - SECPKG_CRED_ATTR_NAMES, - &names); + SECPKG_CRED_ATTR_NAMES, + &names); Curl_pSecFn->FreeCredentialsHandle(&cred_handle); if(check_sspi_err(data, status, "QueryCredentialAttributes")) { - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - Curl_pSecFn->FreeContextBuffer(names.sUserName); failf(data, "Failed to determine username."); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } else { #ifndef CURL_DISABLE_VERBOSE_STRINGS @@ -396,12 +359,12 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, } else { status = Curl_pSecFn->QueryContextAttributes(&sspi_context, - SECPKG_ATTR_SIZES, - &sspi_sizes); + SECPKG_ATTR_SIZES, + &sspi_sizes); if(check_sspi_err(data, status, "QueryContextAttributes")) { - Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } sspi_w_token[0].cbBuffer = sspi_sizes.cbSecurityTrailer; @@ -409,16 +372,15 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[0].pvBuffer = malloc(sspi_sizes.cbSecurityTrailer); if(!sspi_w_token[0].pvBuffer) { - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto error; } sspi_w_token[1].cbBuffer = 1; sspi_w_token[1].pvBuffer = malloc(1); if(!sspi_w_token[1].pvBuffer) { - Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto error; } memcpy(sspi_w_token[1].pvBuffer, &gss_enc, 1); @@ -426,42 +388,33 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[2].cbBuffer = sspi_sizes.cbBlockSize; sspi_w_token[2].pvBuffer = malloc(sspi_sizes.cbBlockSize); if(!sspi_w_token[2].pvBuffer) { - Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto error; } status = Curl_pSecFn->EncryptMessage(&sspi_context, - KERB_WRAP_NO_ENCRYPT, - &wrap_desc, - 0); + KERB_WRAP_NO_ENCRYPT, + &wrap_desc, 0); if(check_sspi_err(data, status, "EncryptMessage")) { - Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } - sspi_send_token.cbBuffer = sspi_w_token[0].cbBuffer - + sspi_w_token[1].cbBuffer - + sspi_w_token[2].cbBuffer; + sspi_send_token.cbBuffer = sspi_w_token[0].cbBuffer + + sspi_w_token[1].cbBuffer + + sspi_w_token[2].cbBuffer; sspi_send_token.pvBuffer = malloc(sspi_send_token.cbBuffer); if(!sspi_send_token.pvBuffer) { - Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto error; } memcpy(sspi_send_token.pvBuffer, sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer); memcpy((PUCHAR) sspi_send_token.pvBuffer +(int)sspi_w_token[0].cbBuffer, sspi_w_token[1].pvBuffer, sspi_w_token[1].cbBuffer); - memcpy((PUCHAR) sspi_send_token.pvBuffer - + sspi_w_token[0].cbBuffer - + sspi_w_token[1].cbBuffer, + memcpy((PUCHAR) sspi_send_token.pvBuffer + + sspi_w_token[0].cbBuffer + + sspi_w_token[1].cbBuffer, sspi_w_token[2].pvBuffer, sspi_w_token[2].cbBuffer); Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); @@ -482,10 +435,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, &written); if(code || (written != 4)) { failf(data, "Failed to send SSPI encryption request."); - if(sspi_send_token.pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } if(data->set.socks5_gssapi_nec) { @@ -494,8 +445,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, &written); if(code || (written != 1)) { failf(data, "Failed to send SSPI encryption type."); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } } else { @@ -504,10 +455,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_send_token.cbBuffer, FALSE, &written); if(code || (sspi_send_token.cbBuffer != (size_t)written)) { failf(data, "Failed to send SSPI encryption type."); - if(sspi_send_token.pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } if(sspi_send_token.pvBuffer) Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); @@ -516,23 +465,23 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); if(result || (actualread != 4)) { failf(data, "Failed to receive SSPI encryption response."); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } /* ignore the first (VER) byte */ if(socksreq[1] == 255) { /* status / message type */ failf(data, "User was rejected by the SOCKS5 server (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } if(socksreq[1] != 2) { /* status / message type */ failf(data, "Invalid SSPI encryption response type (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } memcpy(&us_length, socksreq + 2, sizeof(short)); @@ -541,8 +490,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[0].cbBuffer = us_length; sspi_w_token[0].pvBuffer = malloc(us_length); if(!sspi_w_token[0].pvBuffer) { - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto error; } result = Curl_blockread_all(cf, data, (char *)sspi_w_token[0].pvBuffer, @@ -550,9 +499,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(result || (actualread != us_length)) { failf(data, "Failed to receive SSPI encryption type."); - Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } @@ -563,30 +511,20 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[1].cbBuffer = 0; sspi_w_token[1].pvBuffer = NULL; - status = Curl_pSecFn->DecryptMessage(&sspi_context, - &wrap_desc, - 0, - &qop); + status = Curl_pSecFn->DecryptMessage(&sspi_context, &wrap_desc, + 0, &qop); if(check_sspi_err(data, status, "DecryptMessage")) { - if(sspi_w_token[0].pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - if(sspi_w_token[1].pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } if(sspi_w_token[1].cbBuffer != 1) { failf(data, "Invalid SSPI encryption response length (%lu).", (unsigned long)sspi_w_token[1].cbBuffer); - if(sspi_w_token[0].pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - if(sspi_w_token[1].pvBuffer) - Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } memcpy(socksreq, sspi_w_token[1].pvBuffer, sspi_w_token[1].cbBuffer); @@ -597,9 +535,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(sspi_w_token[0].cbBuffer != 1) { failf(data, "Invalid SSPI encryption response length (%lu).", (unsigned long)sspi_w_token[0].cbBuffer); - Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - Curl_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } memcpy(socksreq, sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer); Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); @@ -621,5 +558,22 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, } */ return CURLE_OK; +error: + free(service_name); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); + if(sspi_recv_token.pvBuffer) + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + if(sspi_send_token.pvBuffer) + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + if(names.sUserName) + Curl_pSecFn->FreeContextBuffer(names.sUserName); + if(sspi_w_token[0].pvBuffer) + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + if(sspi_w_token[1].pvBuffer) + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + if(sspi_w_token[2].pvBuffer) + Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + return result; } #endif diff --git a/lib/strdup.c b/lib/strdup.c index 69c41a5e2..66fd7c60e 100644 --- a/lib/strdup.c +++ b/lib/strdup.c @@ -71,7 +71,7 @@ wchar_t *Curl_wcsdup(const wchar_t *src) { size_t length = wcslen(src); - if(length > (SIZE_T_MAX / sizeof(wchar_t)) - 1) + if(length > (SIZE_MAX / sizeof(wchar_t)) - 1) return (wchar_t *)NULL; /* integer overflow */ return (wchar_t *)Curl_memdup(src, (length + 1) * sizeof(wchar_t)); @@ -320,7 +320,6 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_safefree(data->state.aptr.uagent); Curl_safefree(data->state.aptr.userpwd); Curl_safefree(data->state.aptr.accept_encoding); - Curl_safefree(data->state.aptr.te); Curl_safefree(data->state.aptr.rangeline); Curl_safefree(data->state.aptr.ref); Curl_safefree(data->state.aptr.host); diff --git a/lib/urldata.h b/lib/urldata.h index a35512cf7..cf181af64 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1122,7 +1122,6 @@ struct UrlState { #ifndef CURL_DISABLE_RTSP char *rtsp_transport; #endif - char *te; /* TE: request header */ /* transfer credentials */ char *user; @@ -1178,6 +1177,11 @@ struct UrlState { internal use and the user does not have ownership of the handle. */ BIT(http_ignorecustom); /* ignore custom method from now */ +#ifndef CURL_DISABLE_HTTP + BIT(http_hd_te); /* Added HTTP header TE: */ + BIT(http_hd_upgrade); /* Added HTTP header Upgrade: */ + BIT(http_hd_h2_settings); /* Added HTTP header H2Settings: */ +#endif }; /* diff --git a/lib/vauth/cleartext.c b/lib/vauth/cleartext.c index 8fc5d41b9..ebf026fcf 100644 --- a/lib/vauth/cleartext.c +++ b/lib/vauth/cleartext.c @@ -74,8 +74,8 @@ CURLcode Curl_auth_create_plain_message(const char *authzid, plen = strlen(passwd); /* Compute binary message length. Check for overflows. */ - if((zlen > SIZE_T_MAX/4) || (clen > SIZE_T_MAX/4) || - (plen > (SIZE_T_MAX/2 - 2))) + if((zlen > SIZE_MAX/4) || (clen > SIZE_MAX/4) || + (plen > (SIZE_MAX/2 - 2))) return CURLE_OUT_OF_MEMORY; plainlen = zlen + clen + plen + 2; diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/curl_ngtcp2.c index 575ebb902..6470f1506 100644 --- a/lib/vquic/curl_ngtcp2.c +++ b/lib/vquic/curl_ngtcp2.c @@ -431,9 +431,9 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx, s->log_printf = NULL; #endif - (void)data; s->initial_ts = pktx->ts; - s->handshake_timeout = QUIC_HANDSHAKE_TIMEOUT; + s->handshake_timeout = (data->set.connecttimeout > 0) ? + data->set.connecttimeout * NGTCP2_MILLISECONDS : QUIC_HANDSHAKE_TIMEOUT; s->max_window = 100 * ctx->max_stream_window; s->max_stream_window = 10 * ctx->max_stream_window; diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 8c366aafa..2898a70f0 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -1855,9 +1855,9 @@ static int myssh_in_SFTP_QUOTE_STAT(struct Curl_easy *data, } else if(!strncmp(cmd, "atime", 5) || !strncmp(cmd, "mtime", 5)) { - time_t date = Curl_getdate_capped(sshc->quote_path1); + time_t date; bool fail = FALSE; - if(date == -1) { + if(Curl_getdate_capped(sshc->quote_path1, &date)) { failf(data, "incorrect date format for %.*s", 5, cmd); fail = TRUE; } diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index ea7648239..13f374564 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -1390,16 +1390,16 @@ sftp_quote_stat(struct Curl_easy *data, } else if(!strncmp(cmd, "atime", 5) || !strncmp(cmd, "mtime", 5)) { - time_t date = Curl_getdate_capped(sshc->quote_path1); + time_t date; bool fail = FALSE; - if(date == -1) { + if(Curl_getdate_capped(sshc->quote_path1, &date)) { failf(data, "incorrect date format for %.*s", 5, cmd); fail = TRUE; } #if SIZEOF_TIME_T > SIZEOF_LONG if(date > 0xffffffff) { - /* if 'long' cannot old >32-bit, this date cannot be sent */ + /* if 'long' cannot hold >32-bit, this date cannot be sent */ failf(data, "date overflow"); fail = TRUE; } diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 3c572806e..20eae443d 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -426,7 +426,7 @@ CURLcode Curl_gtls_shared_creds_create(struct Curl_easy *data, CURLcode Curl_gtls_shared_creds_up_ref(struct gtls_shared_creds *creds) { DEBUGASSERT(creds); - if(creds->refcount < SIZE_T_MAX) { + if(creds->refcount < SIZE_MAX) { ++creds->refcount; return CURLE_OK; } diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 45469205c..5971d9a35 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -121,8 +121,14 @@ static void ossl_provider_cleanup(struct Curl_easy *data); #endif +/* + * AWS-LC has `SSL_CTX_set_default_read_buffer_len()?` but runs into + * decryption failures with large buffers. Sporadic failures in + * test_10_08 with h2 proxy uploads, increased frequency + * with CURL_DBG_SOCK_RBLOCK=50. Looks like a bug on their part. + */ #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ - !defined(LIBRESSL_VERSION_NUMBER) && !defined(OPENSSL_IS_BORINGSSL) + !defined(LIBRESSL_VERSION_NUMBER) && !defined(HAVE_BORINGSSL_LIKE) #define HAVE_SSL_CTX_SET_DEFAULT_READ_BUFFER_LEN 1 #endif @@ -4129,7 +4135,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, However using a large buffer (8 packets) actually decreases performance. 4 packets is better. */ - #ifdef HAVE_SSL_CTX_SET_DEFAULT_READ_BUFFER_LEN SSL_CTX_set_default_read_buffer_len(octx->ssl_ctx, 0x401e * 4); #endif diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 7fb4cb1fd..ea06d31ab 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -57,6 +57,7 @@ #include "../curlx/version_win32.h" #include "../rand.h" #include "../curlx/strparse.h" +#include "../progress.h" /* The last #include file should be: */ #include "../curl_memory.h" @@ -65,12 +66,15 @@ /* Some verbose debug messages are wrapped by SCH_DEV() instead of DEBUGF() * and only shown if CURL_SCHANNEL_DEV_DEBUG was defined at build time. These * messages are extra verbose and intended for curl developers debugging - * Schannel recv decryption. + * Schannel recv decryption and renegotiation. */ #ifdef CURL_SCHANNEL_DEV_DEBUG #define SCH_DEV(x) x +#define SCH_DEV_SHOWBOOL(x) \ + infof(data, "schannel: " #x " %s", (x) ? "TRUE" : "FALSE"); #else #define SCH_DEV(x) do { } while(0) +#define SCH_DEV_SHOWBOOL(x) do { } while(0) #endif /* Offered by mingw-w64 v8+. MS SDK 7.0A+. */ @@ -1099,6 +1103,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) backend->recv_sspi_close_notify = FALSE; backend->recv_connection_closed = FALSE; backend->recv_renegotiating = FALSE; + backend->renegotiate_state.started = FALSE; backend->encdata_is_incomplete = FALSE; /* continue to second handshake step */ @@ -1246,6 +1251,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) if(!SOCKET_WRITABLE(Curl_conn_cf_get_socket(cf, data), 0)) { SCH_DEV(infof(data, "schannel: handshake waiting for writeable socket")); connssl->io_need = CURL_SSL_IO_NEED_SEND; + free(inbuf[0].pvBuffer); return CURLE_OK; } @@ -1679,6 +1685,205 @@ static CURLcode schannel_connect(struct Curl_cfilter *cf, return CURLE_OK; } +enum schannel_renegotiate_caller_t { + SCH_RENEG_CALLER_IS_RECV, + SCH_RENEG_CALLER_IS_SEND +}; + +/* This function renegotiates the connection due to a server request received + by schannel_recv. This function returns CURLE_AGAIN if the renegotiation is + incomplete. In that case, we remain in the renegotiation (connecting) stage + and future calls to schannel_recv and schannel_send must call this function + first to complete the renegotiation. */ +static CURLcode +schannel_recv_renegotiate(struct Curl_cfilter *cf, struct Curl_easy *data, + enum schannel_renegotiate_caller_t caller) +{ + CURLcode result; + curl_socket_t sockfd; + const timediff_t max_renegotiate_ms = 5 * 60 * 1000; /* 5 minutes */ + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + struct schannel_renegotiate_state *rs = &backend->renegotiate_state; + + if(!backend || !backend->recv_renegotiating) { + failf(data, "schannel: unexpected call to schannel_recv_renegotiate"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(caller == SCH_RENEG_CALLER_IS_RECV) + SCH_DEV(infof(data, "schannel: renegotiation caller is schannel_recv")); + else if(caller == SCH_RENEG_CALLER_IS_SEND) + SCH_DEV(infof(data, "schannel: renegotiation caller is schannel_send")); + else { + failf(data, "schannel: unknown caller for schannel_recv_renegotiate"); + return CURLE_SSL_CONNECT_ERROR; + } + + sockfd = Curl_conn_cf_get_socket(cf, data); + + if(sockfd == CURL_SOCKET_BAD) { + failf(data, "schannel: renegotiation missing socket"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(!rs->started) { /* new renegotiation */ + infof(data, "schannel: renegotiating SSL/TLS connection"); + DEBUGASSERT(connssl->state == ssl_connection_complete); + DEBUGASSERT(connssl->connecting_state == ssl_connect_done); + connssl->state = ssl_connection_negotiating; + connssl->connecting_state = ssl_connect_2; + memset(rs, 0, sizeof(*rs)); + rs->io_need = CURL_SSL_IO_NEED_SEND; + rs->start_time = curlx_now(); + rs->started = TRUE; + } + + for(;;) { + bool block_read, block_write, blocking, done; + curl_socket_t readfd, writefd; + timediff_t elapsed; + + elapsed = curlx_timediff(curlx_now(), rs->start_time); + if(elapsed >= max_renegotiate_ms) { + failf(data, "schannel: renegotiation timeout"); + result = CURLE_SSL_CONNECT_ERROR; + break; + } + + /* the current io_need state may have been overwritten since the last time + this function was called. restore the io_need state needed to continue + the renegotiation. */ + + connssl->io_need = rs->io_need; + + result = schannel_connect(cf, data, &done); + + rs->io_need = connssl->io_need; + + if(!result && !done) + result = CURLE_AGAIN; + + if(result != CURLE_AGAIN) + break; + + readfd = (rs->io_need & CURL_SSL_IO_NEED_RECV) ? sockfd : CURL_SOCKET_BAD; + writefd = (rs->io_need & CURL_SSL_IO_NEED_SEND) ? sockfd : CURL_SOCKET_BAD; + + if(readfd == CURL_SOCKET_BAD && writefd == CURL_SOCKET_BAD) + continue; + + /* connect should not have requested io read and write together */ + DEBUGASSERT(readfd == CURL_SOCKET_BAD || writefd == CURL_SOCKET_BAD); + + /* This function is partially blocking to avoid a stoppage that would + * occur if the user is waiting on the socket only in one direction. + * + * For example, if the user has called recv then they may not be waiting + * for a writeable socket and vice versa, so we block to avoid that. + * + * In practice a wait is unlikely to occur. For caller recv if handshake + * data needs to be sent then we block for a writeable socket that should + * be writeable immediately except for OS resource constraints. For caller + * send if handshake data needs to be received then we block for a readable + * socket, which could take some time, but it's more likely the user has + * called recv since they had called it prior (only recv can start + * renegotiation and probably the user is going to call it again to get + * more of their data before calling send). + */ + + block_read = (caller == SCH_RENEG_CALLER_IS_SEND) ? TRUE : FALSE; + block_write = (caller == SCH_RENEG_CALLER_IS_RECV) ? TRUE : FALSE; + + blocking = (block_read && (readfd != CURL_SOCKET_BAD)) || + (block_write && (writefd != CURL_SOCKET_BAD)); + + SCH_DEV_SHOWBOOL(block_read); + SCH_DEV_SHOWBOOL(block_write); + SCH_DEV_SHOWBOOL(blocking); + + for(;;) { + int what; + timediff_t timeout, remaining; + + if(Curl_pgrsUpdate(data)) { + result = CURLE_ABORTED_BY_CALLBACK; + break; + } + + elapsed = curlx_timediff(curlx_now(), rs->start_time); + if(elapsed >= max_renegotiate_ms) { + failf(data, "schannel: renegotiation timeout"); + result = CURLE_SSL_CONNECT_ERROR; + break; + } + remaining = max_renegotiate_ms - elapsed; + + if(blocking) { + timeout = Curl_timeleft(data, NULL, FALSE); + + if(timeout < 0) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + + /* the blocking is in intervals so that the progress function can be + called every second */ + if(!timeout || timeout > 1000) + timeout = 1000; + + if(timeout > remaining) + timeout = remaining; + } + else + timeout = 0; + + SCH_DEV(infof(data, "schannel: renegotiation wait until socket is" + "%s%s for up to %" FMT_TIMEDIFF_T " ms", + ((readfd != CURL_SOCKET_BAD) ? " readable" : ""), + ((writefd != CURL_SOCKET_BAD) ? " writeable" : ""), + timeout)); + + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, timeout); + + if(what > 0 && (what & (CURL_CSELECT_IN | CURL_CSELECT_OUT))) { + SCH_DEV(infof(data, "schannel: renegotiation socket %s%s", + ((what & CURL_CSELECT_IN) ? "CURL_CSELECT_IN " : ""), + ((what & CURL_CSELECT_OUT) ? "CURL_CSELECT_OUT " : ""))); + result = CURLE_AGAIN; + break; + } + else if(!what) { + SCH_DEV(infof(data, "schannel: renegotiation socket timeout")); + if(blocking) + continue; + else + return CURLE_AGAIN; + } + + failf(data, "schannel: socket error during renegotiation"); + result = CURLE_SSL_CONNECT_ERROR; + break; + } + if(result != CURLE_AGAIN) + break; + } + + DEBUGASSERT(result != CURLE_AGAIN); + + rs->started = FALSE; + backend->recv_renegotiating = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + + if(result) + failf(data, "schannel: renegotiation failed"); + else + infof(data, "schannel: SSL/TLS connection renegotiated"); + + return result; +} + static CURLcode schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, size_t *pnwritten) @@ -1696,6 +1901,12 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGASSERT(backend); *pnwritten = 0; + if(backend->recv_renegotiating) { + result = schannel_recv_renegotiate(cf, data, SCH_RENEG_CALLER_IS_SEND); + if(result) + return result; + } + /* check if the maximum stream sizes were queried */ if(backend->stream_sizes.cbMaximumMessage == 0) { sspi_status = Curl_pSecFn->QueryContextAttributes( @@ -1827,7 +2038,6 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_connect_data *connssl = cf->ctx; unsigned char *reallocated_buffer; size_t reallocated_length; - bool done = FALSE; SecBuffer inbuf[4]; SecBufferDesc inbuf_desc; SECURITY_STATUS sspi_status = SEC_E_OK; @@ -1841,6 +2051,12 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGASSERT(backend); *pnread = 0; + if(backend->recv_renegotiating) { + result = schannel_recv_renegotiate(cf, data, SCH_RENEG_CALLER_IS_RECV); + if(result) + return result; + } + /**************************************************************************** * Do not return or set backend->recv_unrecoverable_err unless in the * cleanup. The pattern for return error is set *err, optional infof, goto @@ -2033,21 +2249,13 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, goto cleanup; } - /* begin renegotiation */ - infof(data, "schannel: renegotiating SSL/TLS connection"); - connssl->state = ssl_connection_negotiating; - connssl->connecting_state = ssl_connect_2; - connssl->io_need = CURL_SSL_IO_NEED_SEND; backend->recv_renegotiating = TRUE; - result = schannel_connect(cf, data, &done); - backend->recv_renegotiating = FALSE; - if(result) { - infof(data, "schannel: renegotiation failed"); + result = schannel_recv_renegotiate(cf, data, SCH_RENEG_CALLER_IS_RECV); + if(result) goto cleanup; - } + /* now retry receiving data */ sspi_status = SEC_E_OK; - infof(data, "schannel: SSL/TLS connection renegotiated"); continue; } /* check if the server closed the connection */ diff --git a/lib/vtls/schannel_int.h b/lib/vtls/schannel_int.h index 83be77e0f..5483d12b5 100644 --- a/lib/vtls/schannel_int.h +++ b/lib/vtls/schannel_int.h @@ -123,6 +123,11 @@ struct schannel_ssl_backend_data { more bytes into encdata then set this back to false. */ unsigned long req_flags, ret_flags; CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */ + struct schannel_renegotiate_state { + bool started; + struct curltime start_time; + int io_need; + } renegotiate_state; BIT(recv_sspi_close_notify); /* true if connection closed by close_notify */ BIT(recv_connection_closed); /* true if connection closed, regardless how */ BIT(recv_renegotiating); /* true if recv is doing renegotiation */ diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index d04396209..afbb9b821 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -938,6 +938,163 @@ wssl_legacy_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int version) #define wolfSSL_CTX_set_max_proto_version wssl_legacy_CTX_set_max_proto_version #endif +static CURLcode client_certificate(struct Curl_easy *data, + struct ssl_config_data *ssl_config, + struct wssl_ctx *wctx) +{ + /* Load the client certificate, and private key */ +#ifndef NO_FILESYSTEM + if(ssl_config->primary.cert_blob || ssl_config->primary.clientcert) { + const char *cert_file = ssl_config->primary.clientcert; + const char *key_file = ssl_config->key; + const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; + const struct curl_blob *key_blob = ssl_config->key_blob; + int file_type = wssl_do_file_type(ssl_config->cert_type); + int rc; + + switch(file_type) { + case WOLFSSL_FILETYPE_PEM: + rc = cert_blob ? + wolfSSL_CTX_use_certificate_chain_buffer(wctx->ssl_ctx, + cert_blob->data, + (long)cert_blob->len) : + wolfSSL_CTX_use_certificate_chain_file(wctx->ssl_ctx, cert_file); + break; + case WOLFSSL_FILETYPE_ASN1: + rc = cert_blob ? + wolfSSL_CTX_use_certificate_buffer(wctx->ssl_ctx, cert_blob->data, + (long)cert_blob->len, file_type) : + wolfSSL_CTX_use_certificate_file(wctx->ssl_ctx, cert_file, file_type); + break; + default: + failf(data, "unknown cert type"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(rc != 1) { + failf(data, "unable to use client certificate"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(!key_blob && !key_file) { + key_blob = cert_blob; + key_file = cert_file; + } + else + file_type = wssl_do_file_type(ssl_config->key_type); + + rc = key_blob ? + wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, + (long)key_blob->len, file_type) : + wolfSSL_CTX_use_PrivateKey_file(wctx->ssl_ctx, key_file, file_type); + if(rc != 1) { + failf(data, "unable to set private key"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#else /* NO_FILESYSTEM */ + if(ssl_config->primary.cert_blob) { + const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; + const struct curl_blob *key_blob = ssl_config->key_blob; + int file_type = wssl_do_file_type(ssl_config->cert_type); + int rc; + + switch(file_type) { + case WOLFSSL_FILETYPE_PEM: + rc = wolfSSL_CTX_use_certificate_chain_buffer(wctx->ssl_ctx, + cert_blob->data, + (long)cert_blob->len); + break; + case WOLFSSL_FILETYPE_ASN1: + rc = wolfSSL_CTX_use_certificate_buffer(wctx->ssl_ctx, cert_blob->data, + (long)cert_blob->len, file_type); + break; + default: + failf(data, "unknown cert type"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(rc != 1) { + failf(data, "unable to use client certificate"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(!key_blob) + key_blob = cert_blob; + else + file_type = wssl_do_file_type(ssl_config->key_type); + + if(wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, + (long)key_blob->len, + file_type) != 1) { + failf(data, "unable to set private key"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif /* !NO_FILESYSTEM */ + return CURLE_OK; +} + +static CURLcode ssl_version(struct Curl_easy *data, + struct ssl_primary_config *conn_config, + struct wssl_ctx *wctx) +{ + int res; + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_VERSION); + break; + case CURL_SSLVERSION_TLSv1_1: + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_1_VERSION); + break; + case CURL_SSLVERSION_TLSv1_2: + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_2_VERSION); + break; +#ifdef WOLFSSL_TLS13 + case CURL_SSLVERSION_TLSv1_3: + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_3_VERSION); + break; +#endif + default: + failf(data, "wolfSSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + if(res != WOLFSSL_SUCCESS) { + failf(data, "wolfSSL: failed set the minimum TLS version"); + return CURLE_SSL_CONNECT_ERROR; + } + + switch(conn_config->version_max) { +#ifdef WOLFSSL_TLS13 + case CURL_SSLVERSION_MAX_TLSv1_3: + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_3_VERSION); + break; +#endif + case CURL_SSLVERSION_MAX_TLSv1_2: + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_2_VERSION); + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_1_VERSION); + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_VERSION); + break; + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + res = WOLFSSL_SUCCESS; + break; + default: + failf(data, "wolfSSL: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + if(res != WOLFSSL_SUCCESS) { + failf(data, "wolfSSL: failed set the maximum TLS version"); + return CURLE_SSL_CONNECT_ERROR; + } + return CURLE_OK; +} + + #define QUIC_CIPHERS \ "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" @@ -957,7 +1114,6 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, struct ssl_primary_config *conn_config; WOLFSSL_METHOD* req_method = NULL; struct alpn_spec alpns; - int res; char *curves; #ifdef WOLFSSL_HAVE_KYBER word16 pqkem = 0; @@ -998,63 +1154,9 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, goto out; } - switch(conn_config->version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_VERSION); - break; - case CURL_SSLVERSION_TLSv1_1: - res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_1_VERSION); - break; - case CURL_SSLVERSION_TLSv1_2: - res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_2_VERSION); - break; -#ifdef WOLFSSL_TLS13 - case CURL_SSLVERSION_TLSv1_3: - res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_3_VERSION); - break; -#endif - default: - failf(data, "wolfSSL: unsupported minimum TLS version value"); - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - if(res != WOLFSSL_SUCCESS) { - failf(data, "wolfSSL: failed set the minimum TLS version"); - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - - switch(conn_config->version_max) { -#ifdef WOLFSSL_TLS13 - case CURL_SSLVERSION_MAX_TLSv1_3: - res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_3_VERSION); - break; -#endif - case CURL_SSLVERSION_MAX_TLSv1_2: - res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_2_VERSION); - break; - case CURL_SSLVERSION_MAX_TLSv1_1: - res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_1_VERSION); - break; - case CURL_SSLVERSION_MAX_TLSv1_0: - res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_VERSION); - break; - case CURL_SSLVERSION_MAX_DEFAULT: - case CURL_SSLVERSION_MAX_NONE: - res = WOLFSSL_SUCCESS; - break; - default: - failf(data, "wolfSSL: unsupported maximum TLS version value"); - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - if(res != WOLFSSL_SUCCESS) { - failf(data, "wolfSSL: failed set the maximum TLS version"); - result = CURLE_SSL_CONNECT_ERROR; + result = ssl_version(data, conn_config, wctx); + if(result) goto out; - } #ifndef WOLFSSL_TLS13 { @@ -1129,100 +1231,9 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, } } - /* Load the client certificate, and private key */ -#ifndef NO_FILESYSTEM - if(ssl_config->primary.cert_blob || ssl_config->primary.clientcert) { - const char *cert_file = ssl_config->primary.clientcert; - const char *key_file = ssl_config->key; - const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *key_blob = ssl_config->key_blob; - int file_type = wssl_do_file_type(ssl_config->cert_type); - int rc; - - switch(file_type) { - case WOLFSSL_FILETYPE_PEM: - rc = cert_blob ? - wolfSSL_CTX_use_certificate_chain_buffer(wctx->ssl_ctx, - cert_blob->data, - (long)cert_blob->len) : - wolfSSL_CTX_use_certificate_chain_file(wctx->ssl_ctx, cert_file); - break; - case WOLFSSL_FILETYPE_ASN1: - rc = cert_blob ? - wolfSSL_CTX_use_certificate_buffer(wctx->ssl_ctx, cert_blob->data, - (long)cert_blob->len, file_type) : - wolfSSL_CTX_use_certificate_file(wctx->ssl_ctx, cert_file, file_type); - break; - default: - failf(data, "unknown cert type"); - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto out; - } - if(rc != 1) { - failf(data, "unable to use client certificate"); - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - - if(!key_blob && !key_file) { - key_blob = cert_blob; - key_file = cert_file; - } - else - file_type = wssl_do_file_type(ssl_config->key_type); - - rc = key_blob ? - wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, - (long)key_blob->len, file_type) : - wolfSSL_CTX_use_PrivateKey_file(wctx->ssl_ctx, key_file, file_type); - if(rc != 1) { - failf(data, "unable to set private key"); - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - } -#else /* NO_FILESYSTEM */ - if(ssl_config->primary.cert_blob) { - const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *key_blob = ssl_config->key_blob; - int file_type = wssl_do_file_type(ssl_config->cert_type); - int rc; - - switch(file_type) { - case WOLFSSL_FILETYPE_PEM: - rc = wolfSSL_CTX_use_certificate_chain_buffer(wctx->ssl_ctx, - cert_blob->data, - (long)cert_blob->len); - break; - case WOLFSSL_FILETYPE_ASN1: - rc = wolfSSL_CTX_use_certificate_buffer(wctx->ssl_ctx, cert_blob->data, - (long)cert_blob->len, file_type); - break; - default: - failf(data, "unknown cert type"); - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto out; - } - if(rc != 1) { - failf(data, "unable to use client certificate"); - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - - if(!key_blob) - key_blob = cert_blob; - else - file_type = wssl_do_file_type(ssl_config->key_type); - - if(wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, - (long)key_blob->len, - file_type) != 1) { - failf(data, "unable to set private key"); - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - } -#endif /* !NO_FILESYSTEM */ + result = client_certificate(data, ssl_config, wctx); + if(result) + goto out; /* SSL always tries to verify the peer, this only says whether it should * fail to connect if the verification fails, or if it should continue @@ -474,7 +474,7 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, struct Curl_easy *data, struct bufq *inraw, - ws_write_payload *write_payload, + ws_write_payload *write_cb, void *write_ctx) { const unsigned char *inbuf; @@ -487,9 +487,9 @@ static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen)) { if((curl_off_t)inlen > remain) inlen = (size_t)remain; - nwritten = write_payload(inbuf, inlen, dec->frame_age, dec->frame_flags, - dec->payload_offset, dec->payload_len, - write_ctx, &result); + nwritten = write_cb(inbuf, inlen, dec->frame_age, dec->frame_flags, + dec->payload_offset, dec->payload_len, + write_ctx, &result); if(nwritten < 0) return result; Curl_bufq_skip(inraw, (size_t)nwritten); @@ -505,7 +505,7 @@ static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, static CURLcode ws_dec_pass(struct ws_decoder *dec, struct Curl_easy *data, struct bufq *inraw, - ws_write_payload *write_payload, + ws_write_payload *write_cb, void *write_ctx) { CURLcode result; @@ -535,8 +535,8 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, ssize_t nwritten; const unsigned char tmp = '\0'; /* special case of a 0 length frame, need to write once */ - nwritten = write_payload(&tmp, 0, dec->frame_age, dec->frame_flags, - 0, 0, write_ctx, &result); + nwritten = write_cb(&tmp, 0, dec->frame_age, dec->frame_flags, + 0, 0, write_ctx, &result); if(nwritten < 0) return result; dec->state = WS_DEC_INIT; @@ -544,7 +544,7 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, } FALLTHROUGH(); case WS_DEC_PAYLOAD: - result = ws_dec_pass_payload(dec, data, inraw, write_payload, write_ctx); + result = ws_dec_pass_payload(dec, data, inraw, write_cb, write_ctx); ws_dec_info(dec, data, "passing"); if(result) return result; @@ -631,7 +631,8 @@ static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen, update_meta(ws, frame_age, frame_flags, payload_offset, payload_len, buflen); - *err = Curl_cwriter_write(data, ctx->next_writer, ctx->cw_type, + *err = Curl_cwriter_write(data, ctx->next_writer, + (ctx->cw_type | CLIENTWRITE_0LEN), (const char *)buf, buflen); if(*err) return -1; @@ -943,7 +944,12 @@ static CURLcode cr_ws_read(struct Curl_easy *data, return result; ctx->read_eos = eos; - if(!nread) { + if(!Curl_bufq_is_empty(&ws->sendbuf)) { + /* client_read started a new frame, we disregard any eos reported */ + ctx->read_eos = FALSE; + Curl_creader_clear_eos(data, reader->next); + } + else if(!nread) { /* nothing to convert, return this right away */ if(ctx->read_eos) ctx->eos = TRUE; @@ -952,7 +958,7 @@ static CURLcode cr_ws_read(struct Curl_easy *data, goto out; } - if(!ws->enc.payload_remain) { + if(!ws->enc.payload_remain && Curl_bufq_is_empty(&ws->sendbuf)) { /* encode the data as a new BINARY frame */ result = ws_enc_write_head(data, &ws->enc, CURLWS_BINARY, nread, &ws->sendbuf); @@ -990,8 +996,7 @@ static const struct Curl_crtype ws_cr_encode = { Curl_creader_def_needs_rewind, Curl_creader_def_total_length, Curl_creader_def_resume_from, - Curl_creader_def_rewind, - Curl_creader_def_unpause, + Curl_creader_def_cntrl, Curl_creader_def_is_paused, Curl_creader_def_done, sizeof(struct cr_ws_ctx) @@ -1019,11 +1024,6 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) "Upgrade", "websocket" }, { - /* The request MUST contain a |Connection| header field whose value - MUST include the "Upgrade" token. */ - "Connection", "Upgrade", - }, - { /* The request MUST include a header field with the name |Sec-WebSocket-Version|. The value of this header field MUST be 13. */ @@ -1038,7 +1038,7 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) "Sec-WebSocket-Key", NULL, } }; - heads[3].val = &keyval[0]; + heads[2].val = &keyval[0]; /* 16 bytes random */ result = Curl_rand(data, (unsigned char *)rand, sizeof(rand)); @@ -1060,6 +1060,7 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) heads[i].val); } } + data->state.http_hd_upgrade = TRUE; k->upgr101 = UPGR101_WS; return result; } @@ -1732,7 +1733,7 @@ CURL_EXTERN CURLcode curl_ws_start_frame(CURL *d, return CURLE_FAILED_INIT; } - CURL_TRC_WS(data, "curl_start_frame(flags=%x, frame_len=%" FMT_OFF_T, + CURL_TRC_WS(data, "curl_ws_start_frame(flags=%x, frame_len=%" FMT_OFF_T, flags, frame_len); if(!data->conn) { |