From e3905de8196d67b89df1602feb84c1f993211b20 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 28 Feb 2024 14:51:53 +0100 Subject: [PATCH] lib: further send/upload handling polish - Move all the "upload_done" handling to request.c - add possibility to abort sending of a request - add `Curl_req_done_sending()` for checks - transfer.c: readwrite_upload() now clean - removing data->state.ulbuf and data->req.upload_fromhere - as well as data->req.upload_present - set data->req.upload_done on having read all from the client and completely flushed the send buffer - tftp, remove setting of data->req.upload_fromhere - serves no purpose as `upload_present` is not set and the data itself is directly `sendto()` anyway - smtp, make upload EOB conversion a client reader - xfer_ulbuf addition - add xfer_ulbuf for borrowing, similar to xfer_buf - use in file upload - use in c-hyper body sending - h1-proxy, remove init of data->state.uilbuf that is never used - smb, add own send_buf instead of using data->state.ulbuf Closes #13010 --- lib/c-hyper.c | 54 ++++---- lib/cf-h1-proxy.c | 6 - lib/file.c | 16 +-- lib/http.c | 58 +++------ lib/http.h | 17 --- lib/multi.c | 66 +++++++++- lib/multihandle.h | 4 + lib/multiif.h | 25 ++++ lib/request.c | 118 +++++++++++++----- lib/request.h | 34 ++--- lib/rtsp.c | 4 +- lib/sendf.c | 6 +- lib/setopt.c | 1 - lib/smb.c | 33 ++--- lib/smb.h | 1 + lib/smtp.c | 310 ++++++++++++++++++++++++---------------------- lib/smtp.h | 13 -- lib/tftp.c | 2 - lib/transfer.c | 202 ++++-------------------------- lib/transfer.h | 6 +- lib/url.c | 1 - lib/urldata.h | 1 - 22 files changed, 464 insertions(+), 514 deletions(-) diff --git a/lib/c-hyper.c b/lib/c-hyper.c index d12d6de153de3c..6bac1a357329ef 100644 --- a/lib/c-hyper.c +++ b/lib/c-hyper.c @@ -469,7 +469,7 @@ CURLcode Curl_hyper_stream(struct Curl_easy *data, infof(data, "Got 417 while waiting for a 100"); data->state.disableexpect = TRUE; data->req.newurl = strdup(data->state.url); - Curl_done_sending(data, k); + Curl_req_abort_sending(data); } result = status_line(data, conn, @@ -663,7 +663,10 @@ static int uploadstreamed(void *userdata, hyper_context *ctx, size_t fillcount; struct Curl_easy *data = (struct Curl_easy *)userdata; CURLcode result; + char *xfer_ulbuf; + size_t xfer_ulblen; bool eos; + int rc = HYPER_POLL_ERROR; (void)ctx; if(data->req.exp100 > EXP100_SEND_DATA) { @@ -677,39 +680,44 @@ static int uploadstreamed(void *userdata, hyper_context *ctx, return HYPER_POLL_PENDING; } - result = Curl_client_read(data, data->state.ulbuf, - data->set.upload_buffer_size, - &fillcount, &eos); - if(result) { - data->state.hresult = result; - return HYPER_POLL_ERROR; - } + result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen); + if(result) + goto out; + + result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &fillcount, &eos); + if(result) + goto out; if(fillcount) { - hyper_buf *copy = hyper_buf_copy((uint8_t *)data->state.ulbuf, fillcount); + hyper_buf *copy = hyper_buf_copy((uint8_t *)xfer_ulbuf, fillcount); if(copy) *chunk = copy; else { - data->state.hresult = CURLE_OUT_OF_MEMORY; - return HYPER_POLL_ERROR; + result = CURLE_OUT_OF_MEMORY; + goto out; } /* increasing the writebytecount here is a little premature but we don't know exactly when the body is sent */ data->req.writebytecount += fillcount; Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - return HYPER_POLL_READY; + rc = HYPER_POLL_READY; } else if(eos) { *chunk = NULL; - return HYPER_POLL_READY; + rc = HYPER_POLL_READY; } else { /* paused, save a waker */ if(data->hyp.send_body_waker) hyper_waker_free(data->hyp.send_body_waker); data->hyp.send_body_waker = hyper_context_waker(ctx); - return HYPER_POLL_PENDING; + rc = HYPER_POLL_PENDING; } + +out: + Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf); + data->state.hresult = result; + return rc; } /* @@ -722,7 +730,6 @@ static CURLcode bodysend(struct Curl_easy *data, hyper_request *hyperreq, Curl_HttpReq httpreq) { - struct HTTP *http = data->req.p.http; CURLcode result = CURLE_OK; struct dynbuf req; if((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) @@ -731,21 +738,21 @@ static CURLcode bodysend(struct Curl_easy *data, hyper_body *body; Curl_dyn_init(&req, DYN_HTTP_REQUEST); result = Curl_http_req_complete(data, &req, httpreq); + if(result) + return result; - if(!result) + /* if the "complete" above did produce more than the closing line, + parse the added headers */ + if(Curl_dyn_len(&req) != 2 || strcmp(Curl_dyn_ptr(&req), "\r\n")) { result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&req)); + if(result) + return result; + } Curl_dyn_free(&req); body = hyper_body_new(); hyper_body_set_userdata(body, data); - result = Curl_get_upload_buffer(data); - if(result) { - hyper_body_free(body); - return result; - } - /* init the "upload from here" pointer */ - data->req.upload_fromhere = data->state.ulbuf; hyper_body_set_data_func(body, uploadstreamed); if(HYPERE_OK != hyper_request_set_body(hyperreq, body)) { @@ -753,7 +760,6 @@ static CURLcode bodysend(struct Curl_easy *data, result = CURLE_OUT_OF_MEMORY; } } - http->sending = HTTPSEND_BODY; return result; } diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c index 280516061fc94a..deb94580bfb1f0 100644 --- a/lib/cf-h1-proxy.c +++ b/lib/cf-h1-proxy.c @@ -114,18 +114,12 @@ static CURLcode tunnel_init(struct Curl_cfilter *cf, struct h1_tunnel_state **pts) { struct h1_tunnel_state *ts; - CURLcode result; if(cf->conn->handler->flags & PROTOPT_NOTCPPROXY) { failf(data, "%s cannot be done over CONNECT", cf->conn->handler->scheme); return CURLE_UNSUPPORTED_PROTOCOL; } - /* we might need the upload buffer for streaming a partial request */ - result = Curl_get_upload_buffer(data); - if(result) - return result; - ts = calloc(1, sizeof(*ts)); if(!ts) return CURLE_OUT_OF_MEMORY; diff --git a/lib/file.c b/lib/file.c index d3b08a81f0e339..07d879dfd128f0 100644 --- a/lib/file.c +++ b/lib/file.c @@ -291,8 +291,8 @@ static CURLcode file_upload(struct Curl_easy *data) int fd; int mode; CURLcode result = CURLE_OK; - char *xfer_buf; - size_t xfer_blen; + char *xfer_ulbuf; + size_t xfer_ulblen; curl_off_t bytecount = 0; struct_stat file_stat; const char *sendbuf; @@ -340,7 +340,7 @@ static CURLcode file_upload(struct Curl_easy *data) data->state.resume_from = (curl_off_t)file_stat.st_size; } - result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); + result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen); if(result) goto out; @@ -349,7 +349,7 @@ static CURLcode file_upload(struct Curl_easy *data) ssize_t nwrite; size_t readcount; - result = Curl_client_read(data, xfer_buf, xfer_blen, &readcount, &eos); + result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos); if(result) break; @@ -363,16 +363,16 @@ static CURLcode file_upload(struct Curl_easy *data) if((curl_off_t)nread <= data->state.resume_from) { data->state.resume_from -= nread; nread = 0; - sendbuf = xfer_buf; + sendbuf = xfer_ulbuf; } else { - sendbuf = xfer_buf + data->state.resume_from; + sendbuf = xfer_ulbuf + data->state.resume_from; nread -= (size_t)data->state.resume_from; data->state.resume_from = 0; } } else - sendbuf = xfer_buf; + sendbuf = xfer_ulbuf; /* write the data to the target */ nwrite = write(fd, sendbuf, nread); @@ -395,7 +395,7 @@ static CURLcode file_upload(struct Curl_easy *data) out: close(fd); - Curl_multi_xfer_buf_release(data, xfer_buf); + Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf); return result; } diff --git a/lib/http.c b/lib/http.c index 3f17f7cd92fc14..65febe77f7ab55 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1283,7 +1283,6 @@ CURLcode Curl_http_done(struct Curl_easy *data, if(!http) return CURLE_OK; - Curl_dyn_free(&http->send_buffer); Curl_dyn_reset(&data->state.headerb); Curl_hyper_done(data); Curl_ws_done(data); @@ -2151,6 +2150,12 @@ CURLcode Curl_http_req_complete(struct Curl_easy *data, CURLcode result = CURLE_OK; struct HTTP *http = data->req.p.http; + if(data->req.upload_chunky) { + result = Curl_httpchunk_add_reader(data); + if(result) + return result; + } + DEBUGASSERT(data->conn); switch(httpreq) { case HTTPREQ_PUT: /* Let's PUT the data to the server! */ @@ -2252,7 +2257,6 @@ CURLcode Curl_http_req_complete(struct Curl_easy *data, data->state.in = (void *) data->state.mimepost; result = Client_reader_set_fread(data, data->state.infilesize); } - http->sending = HTTPSEND_BODY; break; #endif case HTTPREQ_POST: @@ -2294,19 +2298,21 @@ CURLcode Curl_http_req_complete(struct Curl_easy *data, goto out; if(!http->postsize) { - Curl_pgrsSetUploadSize(data, -1); + Curl_pgrsSetUploadSize(data, 0); result = Client_reader_set_null(data); } - else if(data->set.postfields) { /* we have the bytes */ + else if(data->set.postfields) { Curl_pgrsSetUploadSize(data, http->postsize); - result = Client_reader_set_buf(data, data->set.postfields, - (size_t)http->postsize); + if(http->postsize > 0) + result = Client_reader_set_buf(data, data->set.postfields, + (size_t)http->postsize); + else + result = Client_reader_set_null(data); } else { /* we read the bytes from the callback */ Curl_pgrsSetUploadSize(data, http->postsize); result = Client_reader_set_fread(data, http->postsize); } - http->sending = HTTPSEND_BODY; break; default: @@ -2647,7 +2653,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; - struct HTTP *http; Curl_HttpReq httpreq; const char *te = ""; /* transfer-encoding */ const char *request; @@ -2699,9 +2704,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(result) goto fail; - http = data->req.p.http; - DEBUGASSERT(http); - result = Curl_http_host(data, conn); if(result) goto fail; @@ -2880,7 +2882,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) result = Curl_add_custom_headers(data, FALSE, &req); if(!result) { - http->postdata = NULL; /* nothing to post at this point */ if((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) Curl_pgrsSetUploadSize(data, 0); /* nothing */ @@ -2896,29 +2897,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(result) goto fail; - if(data->req.writebytecount) { - /* if a request-body has been sent off, we make sure this progress is noted - properly */ - Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - if(Curl_pgrsUpdate(data)) - result = CURLE_ABORTED_BY_CALLBACK; - - if(!http->postsize) { - /* already sent the entire request body, mark the "upload" as - complete */ - infof(data, "upload completely sent off: %" CURL_FORMAT_CURL_OFF_T - " out of %" CURL_FORMAT_CURL_OFF_T " bytes", - data->req.writebytecount, http->postsize); - data->req.upload_done = TRUE; - data->req.keepon &= ~KEEP_SEND; /* we're done writing */ - data->req.exp100 = EXP100_SEND_DATA; /* already sent */ - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - } - } - - if(data->req.upload_done) - Curl_conn_ev_data_done_send(data); - if((conn->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 @@ -3782,7 +3760,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data, * connection for closure after we've read the entire response. */ Curl_expire_done(data, EXPIRE_100_TIMEOUT); - if(!k->upload_done) { + if(!Curl_req_done_sending(data)) { if((k->httpcode == 417) && data->state.expect100header) { /* 417 Expectation Failed - try again without the Expect header */ @@ -3801,7 +3779,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data, data->state.disableexpect = TRUE; DEBUGASSERT(!data->req.newurl); data->req.newurl = strdup(data->state.url); - Curl_done_sending(data, k); + Curl_req_abort_sending(data); } else if(data->set.http_keep_sending_on_error) { infof(data, "HTTP error before end of send, keep sending"); @@ -3813,10 +3791,9 @@ static CURLcode http_rw_headers(struct Curl_easy *data, else { infof(data, "HTTP error before end of send, stop sending"); streamclose(conn, "Stop sending data before everything sent"); - result = Curl_done_sending(data, k); + result = Curl_req_abort_sending(data); if(result) return result; - k->upload_done = TRUE; if(data->state.expect100header) k->exp100 = EXP100_FAILED; } @@ -3828,8 +3805,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data, } } - if(data->state.rewindbeforesend && - (conn->writesockfd != CURL_SOCKET_BAD)) { + if(data->state.rewindbeforesend && !Curl_req_done_sending(data)) { /* We rewind before next send, continue sending now */ infof(data, "Keep sending data to get tossed away"); k->keepon |= KEEP_SEND; diff --git a/lib/http.h b/lib/http.h index b7fb877e6334f7..fc058477d5f6b3 100644 --- a/lib/http.h +++ b/lib/http.h @@ -189,27 +189,10 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data); ***************************************************************************/ struct HTTP { curl_off_t postsize; /* off_t to handle large file sizes */ - const char *postdata; - struct back { - curl_read_callback fread_func; /* backup storage for fread pointer */ - void *fread_in; /* backup storage for fread_in pointer */ - const char *postdata; - curl_off_t postsize; - struct Curl_easy *data; - } backup; - - enum { - HTTPSEND_NADA, /* init */ - HTTPSEND_REQUEST, /* sending a request */ - HTTPSEND_BODY /* sending body */ - } sending; #ifndef CURL_DISABLE_HTTP void *h2_ctx; /* HTTP/2 implementation context */ void *h3_ctx; /* HTTP/3 implementation context */ - struct dynbuf send_buffer; /* used if the request couldn't be sent in one - chunk, points to an allocated send_buffer - struct */ #endif }; diff --git a/lib/multi.c b/lib/multi.c index e0e871fe679c06..3770ac607b57a2 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -94,7 +94,7 @@ static CURLMcode add_next_timeout(struct curltime now, static CURLMcode multi_timeout(struct Curl_multi *multi, long *timeout_ms); static void process_pending_handles(struct Curl_multi *multi); -static void multi_xfer_buf_free(struct Curl_multi *multi); +static void multi_xfer_bufs_free(struct Curl_multi *multi); #ifdef DEBUGBUILD static const char * const multi_statename[]={ @@ -192,7 +192,7 @@ static void mstate(struct Curl_easy *data, CURLMstate state data->multi->num_alive--; if(!data->multi->num_alive) { /* free the transfer buffer when we have no more active transfers */ - multi_xfer_buf_free(data->multi); + multi_xfer_bufs_free(data->multi); } } @@ -716,8 +716,6 @@ static CURLcode multi_done(struct Curl_easy *data, if(!result) result = Curl_req_done(&data->req, data, premature); - Curl_safefree(data->state.ulbuf); - CONNCACHE_LOCK(data); Curl_detach_connection(data); if(CONN_INUSE(conn)) { @@ -2900,7 +2898,7 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) Curl_free_multi_ssl_backend_data(multi->ssl_backend_data); #endif - multi_xfer_buf_free(multi); + multi_xfer_bufs_free(multi); free(multi); return CURLM_OK; @@ -3891,10 +3889,66 @@ void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf) data->multi->xfer_buf_borrowed = FALSE; } -static void multi_xfer_buf_free(struct Curl_multi *multi) +CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen) +{ + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + *pbuf = NULL; + *pbuflen = 0; + if(!data->multi) { + failf(data, "transfer has no multi handle"); + return CURLE_FAILED_INIT; + } + if(!data->set.upload_buffer_size) { + failf(data, "transfer upload buffer size is 0"); + return CURLE_FAILED_INIT; + } + if(data->multi->xfer_ulbuf_borrowed) { + failf(data, "attempt to borrow xfer_ulbuf when already borrowed"); + return CURLE_AGAIN; + } + + if(data->multi->xfer_ulbuf && + data->set.upload_buffer_size > data->multi->xfer_ulbuf_len) { + /* not large enough, get a new one */ + free(data->multi->xfer_ulbuf); + data->multi->xfer_ulbuf = NULL; + data->multi->xfer_ulbuf_len = 0; + } + + if(!data->multi->xfer_ulbuf) { + data->multi->xfer_ulbuf = malloc((size_t)data->set.upload_buffer_size); + if(!data->multi->xfer_ulbuf) { + failf(data, "could not allocate xfer_ulbuf of %zu bytes", + (size_t)data->set.upload_buffer_size); + return CURLE_OUT_OF_MEMORY; + } + data->multi->xfer_ulbuf_len = data->set.upload_buffer_size; + } + + data->multi->xfer_ulbuf_borrowed = TRUE; + *pbuf = data->multi->xfer_ulbuf; + *pbuflen = data->multi->xfer_ulbuf_len; + return CURLE_OK; +} + +void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf) +{ + (void)buf; + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + DEBUGASSERT(!buf || data->multi->xfer_ulbuf == buf); + data->multi->xfer_ulbuf_borrowed = FALSE; +} + +static void multi_xfer_bufs_free(struct Curl_multi *multi) { DEBUGASSERT(multi); Curl_safefree(multi->xfer_buf); multi->xfer_buf_len = 0; multi->xfer_buf_borrowed = FALSE; + Curl_safefree(multi->xfer_ulbuf); + multi->xfer_ulbuf_len = 0; + multi->xfer_ulbuf_borrowed = FALSE; } diff --git a/lib/multihandle.h b/lib/multihandle.h index 3cccd343fec488..3156c83ad347fe 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -127,6 +127,9 @@ struct Curl_multi { /* buffer used for transfer data, lazy initialized */ char *xfer_buf; /* the actual buffer */ size_t xfer_buf_len; /* the allocated length */ + /* buffer used for upload data, lazy initialized */ + char *xfer_ulbuf; /* the actual buffer */ + size_t xfer_ulbuf_len; /* the allocated length */ #if defined(USE_SSL) struct multi_ssl_backend_data *ssl_backend_data; @@ -176,6 +179,7 @@ struct Curl_multi { BIT(dead); /* a callback returned error, everything needs to crash and burn */ BIT(xfer_buf_borrowed); /* xfer_buf is currently being borrowed */ + BIT(xfer_ulbuf_borrowed); /* xfer_buf is currently being borrowed */ #ifdef DEBUGBUILD BIT(warned); /* true after user warned of DEBUGBUILD */ #endif diff --git a/lib/multiif.h b/lib/multiif.h index de2ffeb9f432fd..37f7a442623c3a 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -118,4 +118,29 @@ CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, */ void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf); +/** + * Borrow the upload buffer from the multi, suitable + * for the given transfer `data`. The buffer may only be used in one + * multi processing of the easy handle. It MUST be returned to the + * multi before it can be borrowed again. + * Pointers into the buffer remain only valid as long as it is borrowed. + * + * @param data the easy handle + * @param pbuf on return, the buffer to use or NULL on error + * @param pbuflen on return, the size of *pbuf or 0 on error + * @return CURLE_OK when buffer is available and is returned. + * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, + * CURLE_FAILED_INIT if the easy handle is without multi. + * CURLE_AGAIN if the buffer is borrowed already. + */ +CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen); + +/** + * Release the borrowed upload buffer. All references into the buffer become + * invalid after this. + * @param buf the upload buffer pointer borrowed for coding error checks. + */ +void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf); + #endif /* HEADER_CURL_MULTIIF_H */ diff --git a/lib/request.c b/lib/request.c index cd5170a3282cdf..dfd96a6c4910f8 100644 --- a/lib/request.c +++ b/lib/request.c @@ -25,6 +25,7 @@ #include "curl_setup.h" #include "urldata.h" +#include "cfilters.h" #include "dynbuf.h" #include "doh.h" #include "multiif.h" @@ -67,12 +68,14 @@ CURLcode Curl_req_start(struct SingleRequest *req, return CURLE_OK; } +static CURLcode req_flush(struct Curl_easy *data); + CURLcode Curl_req_done(struct SingleRequest *req, struct Curl_easy *data, bool aborted) { (void)req; if(!aborted) - (void)Curl_req_flush(data); + (void)req_flush(data); Curl_client_reset(data); return CURLE_OK; } @@ -129,9 +132,9 @@ void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data) #endif } -static CURLcode req_send(struct Curl_easy *data, - const char *buf, size_t blen, - size_t hds_len, size_t *pnwritten) +static CURLcode xfer_send(struct Curl_easy *data, + const char *buf, size_t blen, + size_t hds_len, size_t *pnwritten) { CURLcode result = CURLE_OK; @@ -180,7 +183,7 @@ static CURLcode req_send_buffer_flush(struct Curl_easy *data) while(Curl_bufq_peek(&data->req.sendbuf, &buf, &blen)) { size_t nwritten, hds_len = CURLMIN(data->req.sendbuf_hds_len, blen); - result = req_send(data, (const char *)buf, blen, hds_len, &nwritten); + result = xfer_send(data, (const char *)buf, blen, hds_len, &nwritten); if(result) break; @@ -206,7 +209,33 @@ static CURLcode req_send_buffer_flush(struct Curl_easy *data) return result; } -CURLcode Curl_req_flush(struct Curl_easy *data) +static CURLcode req_set_upload_done(struct Curl_easy *data) +{ + DEBUGASSERT(!data->req.upload_done); + data->req.upload_done = TRUE; + data->req.keepon &= ~KEEP_SEND; /* we're done sending */ + + /* FIXME: http specific stuff, need to go somewhere else */ + data->req.exp100 = EXP100_SEND_DATA; + Curl_expire_done(data, EXPIRE_100_TIMEOUT); + + if(data->req.upload_aborted) { + if(data->req.writebytecount) + infof(data, "abort upload after having sent %" CURL_FORMAT_CURL_OFF_T + " bytes", data->req.writebytecount); + else + infof(data, "abort upload"); + } + else if(data->req.writebytecount) + infof(data, "upload completely sent off: %" CURL_FORMAT_CURL_OFF_T + " bytes", data->req.writebytecount); + else + infof(data, "We are completely uploaded and fine"); + + return Curl_xfer_send_close(data); +} + +static CURLcode req_flush(struct Curl_easy *data) { CURLcode result; @@ -221,9 +250,30 @@ CURLcode Curl_req_flush(struct Curl_easy *data) return CURLE_AGAIN; } } + + if(!data->req.upload_done && data->req.eos_read && + Curl_bufq_is_empty(&data->req.sendbuf)) { + return req_set_upload_done(data); + } return CURLE_OK; } +static ssize_t add_from_client(void *reader_ctx, + unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct Curl_easy *data = reader_ctx; + size_t nread; + bool eos; + + *err = Curl_client_read(data, (char *)buf, buflen, &nread, &eos); + if(*err) + return -1; + if(eos) + data->req.eos_read = TRUE; + return (ssize_t)nread; +} + #ifndef USE_HYPER static CURLcode req_send_buffer_add(struct Curl_easy *data, @@ -242,22 +292,6 @@ static CURLcode req_send_buffer_add(struct Curl_easy *data, return CURLE_OK; } -static ssize_t add_from_client(void *reader_ctx, - unsigned char *buf, size_t buflen, - CURLcode *err) -{ - struct Curl_easy *data = reader_ctx; - size_t nread; - bool eos; - - *err = Curl_client_read(data, (char *)buf, buflen, &nread, &eos); - if(*err) - return -1; - if(eos) - data->req.eos_read = TRUE; - return (ssize_t)nread; -} - CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *buf) { CURLcode result; @@ -275,22 +309,50 @@ CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *buf) if(result) return result; - if((data->req.exp100 == EXP100_SEND_DATA) && - !Curl_bufq_is_full(&data->req.sendbuf)) { + return Curl_req_send_more(data); +} +#endif /* !USE_HYPER */ + +bool Curl_req_want_send(struct Curl_easy *data) +{ + return data->req.sendbuf_init && !Curl_bufq_is_empty(&data->req.sendbuf); +} + +bool Curl_req_done_sending(struct Curl_easy *data) +{ + if(data->req.upload_done) { + DEBUGASSERT(Curl_bufq_is_empty(&data->req.sendbuf)); + return TRUE; + } + return FALSE; +} + +CURLcode Curl_req_send_more(struct Curl_easy *data) +{ + CURLcode result; + + /* Fill our send buffer if more from client can be read and + * we are not in a "expect-100" situation. */ + if(!data->req.eos_read && !Curl_bufq_is_full(&data->req.sendbuf) && + (data->req.exp100 == EXP100_SEND_DATA)) { ssize_t nread = Curl_bufq_sipn(&data->req.sendbuf, 0, add_from_client, data, &result); if(nread < 0 && result != CURLE_AGAIN) return result; } - result = req_send_buffer_flush(data); + result = req_flush(data); if(result == CURLE_AGAIN) result = CURLE_OK; return result; } -#endif /* !USE_HYPER */ -bool Curl_req_want_send(struct Curl_easy *data) +CURLcode Curl_req_abort_sending(struct Curl_easy *data) { - return data->req.sendbuf_init && !Curl_bufq_is_empty(&data->req.sendbuf); + if(!data->req.upload_done) { + Curl_bufq_reset(&data->req.sendbuf); + data->req.upload_aborted = TRUE; + return req_set_upload_done(data); + } + return CURLE_OK; } diff --git a/lib/request.h b/lib/request.h index e7af4a8e537f28..91973d5dbc70ca 100644 --- a/lib/request.h +++ b/lib/request.h @@ -100,16 +100,6 @@ struct SingleRequest { char *newurl; /* Set to the new URL to use when a redirect or a retry is wanted */ - /* 'upload_present' is used to keep a byte counter of how much data there is - still left in the buffer, aimed for upload. */ - size_t upload_present; - - /* 'upload_fromhere' is used as a read-pointer when we uploaded parts of a - buffer, so the next read should read from where this pointer points to, - and the 'upload_present' contains the number of bytes available at this - position */ - char *upload_fromhere; - /* Allocated protocol-specific data. Each protocol handler makes sure this points to data it needs. */ union { @@ -139,8 +129,9 @@ struct SingleRequest { BIT(download_done); /* set to TRUE when download is complete */ BIT(eos_written); /* iff EOS has been written to client */ BIT(eos_read); /* iff EOS has been read from the client */ - BIT(upload_done); /* set to TRUE when doing chunked transfer-encoding - upload and we're uploading the last chunk */ + BIT(upload_done); /* set to TRUE when all request data has been sent */ + BIT(upload_aborted); /* set to TRUE when upload was aborted. Will also + * show `upload_done` as TRUE. */ BIT(ignorebody); /* we read a response-body but we ignore it! */ BIT(http_bodyless); /* HTTP response status code is between 100 and 199, 204 or 304 */ @@ -206,15 +197,26 @@ CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *buf); #endif /* !USE_HYPER */ /** - * Flush all buffered request bytes. - * @return CURLE_OK on success, CURLE_AGAIN if sending was blocked, - * or the error on the sending. + * TRUE iff the request has sent all request headers and data. + */ +bool Curl_req_done_sending(struct Curl_easy *data); + +/* + * Read more from client and flush all buffered request bytes. + * @return CURLE_OK on success or the error on the sending. + * Never returns CURLE_AGAIN. */ -CURLcode Curl_req_flush(struct Curl_easy *data); +CURLcode Curl_req_send_more(struct Curl_easy *data); /** * TRUE iff the request wants to send, e.g. has buffered bytes. */ bool Curl_req_want_send(struct Curl_easy *data); +/** + * Stop sending any more request data to the server. + * Will clear the send buffer and mark request sending as done. + */ +CURLcode Curl_req_abort_sending(struct Curl_easy *data); + #endif /* HEADER_CURL_REQUEST_H */ diff --git a/lib/rtsp.c b/lib/rtsp.c index a0da9f138af8a5..0d8009ad424ee7 100644 --- a/lib/rtsp.c +++ b/lib/rtsp.c @@ -577,6 +577,8 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) if(result) goto out; + Curl_xfer_setup(data, FIRSTSOCKET, -1, TRUE, FIRSTSOCKET); + /* issue the request */ result = Curl_req_send(data, &req_buffer); if(result) { @@ -584,8 +586,6 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) goto out; } - Curl_xfer_setup(data, FIRSTSOCKET, -1, TRUE, FIRSTSOCKET); - /* Increment the CSeq on success */ data->state.rtsp_next_client_CSeq++; diff --git a/lib/sendf.c b/lib/sendf.c index 8fe9c756143ef7..1b092f06b346b7 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -576,8 +576,10 @@ static CURLcode cr_in_read(struct Curl_easy *data, return CURLE_READ_ERROR; } ctx->read_len += nread; + if(ctx->total_len >= 0) + ctx->seen_eos = (ctx->read_len >= ctx->total_len); *pnread = nread; - *peos = FALSE; + *peos = ctx->seen_eos; break; } DEBUGF(infof(data, "cr_in_read(len=%zu, total=%"CURL_FORMAT_CURL_OFF_T @@ -743,7 +745,7 @@ static CURLcode cr_lc_add(struct Curl_easy *data) CURLcode result; result = Curl_creader_create(&reader, data, &cr_lc, - CURL_CR_TRANSFER_ENCODE); + CURL_CR_CONTENT_ENCODE); if(!result) result = Curl_creader_add(data, reader); diff --git a/lib/setopt.c b/lib/setopt.c index ce1321fc80be9d..8a5a5d7c33d21d 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -2239,7 +2239,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) arg = UPLOADBUFFER_MIN; data->set.upload_buffer_size = (unsigned int)arg; - Curl_safefree(data->state.ulbuf); /* force a realloc next opportunity */ break; case CURLOPT_NOSIGNAL: diff --git a/lib/smb.c b/lib/smb.c index 404dd84f43d18b..77d34e31c48395 100644 --- a/lib/smb.c +++ b/lib/smb.c @@ -456,6 +456,9 @@ static CURLcode smb_connect(struct Curl_easy *data, bool *done) smbc->recv_buf = malloc(MAX_MESSAGE_SIZE); if(!smbc->recv_buf) return CURLE_OUT_OF_MEMORY; + smbc->send_buf = malloc(MAX_MESSAGE_SIZE); + if(!smbc->send_buf) + return CURLE_OUT_OF_MEMORY; /* Multiple requests are allowed with this connection */ connkeep(conn, "SMB default"); @@ -567,7 +570,7 @@ static CURLcode smb_send(struct Curl_easy *data, size_t len, size_t bytes_written; CURLcode result; - result = Curl_xfer_send(data, data->state.ulbuf, len, &bytes_written); + result = Curl_xfer_send(data, smbc->send_buf, len, &bytes_written); if(result) return result; @@ -592,7 +595,7 @@ static CURLcode smb_flush(struct Curl_easy *data) if(!smbc->send_size) return CURLE_OK; - result = Curl_xfer_send(data, data->state.ulbuf + smbc->sent, len, + result = Curl_xfer_send(data, smbc->send_buf + smbc->sent, len, &bytes_written); if(result) return result; @@ -608,13 +611,13 @@ static CURLcode smb_flush(struct Curl_easy *data) static CURLcode smb_send_message(struct Curl_easy *data, unsigned char cmd, const void *msg, size_t msg_len) { - CURLcode result = Curl_get_upload_buffer(data); - if(result) - return result; - smb_format_message(data, (struct smb_header *)data->state.ulbuf, + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + + smb_format_message(data, (struct smb_header *)smbc->send_buf, cmd, msg_len); - memcpy(data->state.ulbuf + sizeof(struct smb_header), - msg, msg_len); + DEBUGASSERT((sizeof(struct smb_header) + msg_len) <= MAX_MESSAGE_SIZE); + memcpy(smbc->send_buf + sizeof(struct smb_header), msg, msg_len); return smb_send(data, sizeof(struct smb_header) + msg_len, 0); } @@ -772,15 +775,14 @@ static CURLcode smb_send_read(struct Curl_easy *data) static CURLcode smb_send_write(struct Curl_easy *data) { + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; struct smb_write *msg; struct smb_request *req = data->req.p.smb; curl_off_t offset = data->req.offset; curl_off_t upload_size = data->req.size - data->req.bytecount; - CURLcode result = Curl_get_upload_buffer(data); - if(result) - return result; - msg = (struct smb_write *)data->state.ulbuf; + msg = (struct smb_write *)smbc->send_buf; if(upload_size >= MAX_PAYLOAD_SIZE - 1) /* There is one byte of padding */ upload_size = MAX_PAYLOAD_SIZE - 1; @@ -809,11 +811,11 @@ static CURLcode smb_send_and_recv(struct Curl_easy *data, void **msg) /* Check if there is data in the transfer buffer */ if(!smbc->send_size && smbc->upload_size) { - size_t nread = smbc->upload_size > (size_t)data->set.upload_buffer_size ? - (size_t)data->set.upload_buffer_size : smbc->upload_size; + size_t nread = smbc->upload_size > (size_t)MAX_MESSAGE_SIZE ? + (size_t)MAX_MESSAGE_SIZE : smbc->upload_size; bool eos; - result = Curl_client_read(data, data->state.ulbuf, nread, &nread, &eos); + result = Curl_client_read(data, smbc->send_buf, nread, &nread, &eos); if(result && result != CURLE_AGAIN) return result; if(!nread) @@ -1131,6 +1133,7 @@ static CURLcode smb_disconnect(struct Curl_easy *data, Curl_safefree(smbc->share); Curl_safefree(smbc->domain); Curl_safefree(smbc->recv_buf); + Curl_safefree(smbc->send_buf); return CURLE_OK; } diff --git a/lib/smb.h b/lib/smb.h index 437f4a58a85d7d..9ea2a8cc31c8bc 100644 --- a/lib/smb.h +++ b/lib/smb.h @@ -42,6 +42,7 @@ struct smb_conn { unsigned int session_key; unsigned short uid; char *recv_buf; + char *send_buf; size_t upload_size; size_t send_size; size_t sent; diff --git a/lib/smtp.c b/lib/smtp.c index ddee2223ac48ca..605bb16eeb0221 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -111,6 +111,7 @@ static CURLcode smtp_continue_auth(struct Curl_easy *data, const char *mech, const struct bufref *resp); static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech); static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out); +static CURLcode cr_eob_add(struct Curl_easy *data); /* * SMTP protocol handler. @@ -618,7 +619,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) result = smtp_parse_address(data->set.str[STRING_MAIL_FROM], &address, &host); if(result) - return result; + goto out; /* Establish whether we should report SMTPUTF8 to the server for this mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ @@ -642,8 +643,10 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) /* Null reverse-path, RFC-5321, sect. 3.6.3 */ from = strdup("<>"); - if(!from) - return CURLE_OUT_OF_MEMORY; + if(!from) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } /* Calculate the optional AUTH parameter */ if(data->set.str[STRING_MAIL_AUTH] && conn->proto.smtpc.sasl.authused) { @@ -655,10 +658,8 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) converting the host name to an IDN A-label if necessary */ result = smtp_parse_address(data->set.str[STRING_MAIL_AUTH], &address, &host); - if(result) { - free(from); - return result; - } + if(result) + goto out; /* Establish whether we should report SMTPUTF8 to the server for this mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ @@ -676,17 +677,14 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) /* An invalid mailbox was provided but we'll simply let the server worry about it */ auth = aprintf("<%s>", address); - - free(address); } else /* Empty AUTH, RFC-2554, sect. 5 */ auth = strdup("<>"); if(!auth) { - free(from); - - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto out; } } @@ -710,12 +708,8 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) if(!result) result = Curl_mime_rewind(&data->set.mimepost); - if(result) { - free(from); - free(auth); - - return result; - } + if(result) + goto out; data->state.infilesize = Curl_mime_size(&data->set.mimepost); @@ -730,10 +724,8 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) size = aprintf("%" CURL_FORMAT_CURL_OFF_T, data->state.infilesize); if(!size) { - free(from); - free(auth); - - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto out; } } @@ -754,6 +746,15 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) } } + /* Setup client reader for size and EOB conversion */ + result = Client_reader_set_fread(data, data->state.infilesize); + if(result) + goto out; + /* Add the client reader doing STMP EOB escaping */ + result = cr_eob_add(data); + if(result) + goto out; + /* Send the MAIL command */ result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "MAIL FROM:%s%s%s%s%s%s", @@ -765,6 +766,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) utf8 ? " SMTPUTF8" /* Internationalised mailbox */ : ""); /* included in our envelope */ +out: free(from); free(auth); free(size); @@ -1393,9 +1395,6 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct SMTP *smtp = data->req.p.smtp; - struct pingpong *pp = &conn->proto.smtpc.pp; - char *eob; - size_t len, bytes_written; (void)premature; @@ -1411,46 +1410,6 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, } else if(!data->set.connect_only && data->set.mail_rcpt && (data->state.upload || IS_MIME_POST(data))) { - /* Calculate the EOB taking into account any terminating CRLF from the - previous line of the email or the CRLF of the DATA command when there - is "no mail data". RFC-5321, sect. 4.1.1.4. - - Note: As some SSL backends, such as OpenSSL, will cause Curl_write() to - fail when using a different pointer following a previous write, that - returned CURLE_AGAIN, we duplicate the EOB now rather than when the - bytes written doesn't equal len. */ - if(smtp->trailing_crlf || !data->state.infilesize) { - eob = strdup(&SMTP_EOB[2]); - len = SMTP_EOB_LEN - 2; - } - else { - eob = strdup(SMTP_EOB); - len = SMTP_EOB_LEN; - } - - if(!eob) - return CURLE_OUT_OF_MEMORY; - - /* Send the end of block data */ - result = Curl_xfer_send(data, eob, len, &bytes_written); - if(result) { - free(eob); - return result; - } - - if(bytes_written != len) { - /* The whole chunk was not sent so keep it around and adjust the - pingpong structure accordingly */ - pp->sendthis = eob; - pp->sendsize = len; - pp->sendleft = len - bytes_written; - } - else { - /* Successfully sent so adjust the response timeout relative to now */ - pp->response = Curl_now(); - - free(eob); - } smtp_state(data, SMTP_POSTDATA); @@ -1818,108 +1777,159 @@ static CURLcode smtp_parse_address(const char *fqma, char **address, return result; } -CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, - const ssize_t nread, - const ssize_t offset) +struct cr_eob_ctx { + struct Curl_creader super; + struct bufq buf; + size_t n_eob; /* how many EOB bytes we matched so far */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(eos); /* we have returned an EOS */ +}; + +static CURLcode cr_eob_init(struct Curl_easy *data, + struct Curl_creader *reader) { - /* When sending a SMTP payload we must detect CRLF. sequences making sure - they are sent as CRLF.. instead, as a . on the beginning of a line will - be deleted by the server when not part of an EOB terminator and a - genuine CRLF.CRLF which isn't escaped will wrongly be detected as end of - data by the server - */ - ssize_t i; - ssize_t si; - struct SMTP *smtp = data->req.p.smtp; - char *scratch = data->state.scratch; - char *newscratch = NULL; - char *oldscratch = NULL; - size_t eob_sent; + struct cr_eob_ctx *ctx = (struct cr_eob_ctx *)reader; + (void)data; + /* The first char we read is the first on a line, as if we had + * read CRLF just before */ + ctx->n_eob = 2; + Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} - /* Do we need to allocate a scratch buffer? */ - if(!scratch || data->set.crlf) { - oldscratch = scratch; +static void cr_eob_close(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_eob_ctx *ctx = (struct cr_eob_ctx *)reader; + (void)data; + Curl_bufq_free(&ctx->buf); +} - scratch = newscratch = malloc(2 * data->set.upload_buffer_size); - if(!newscratch) { - failf(data, "Failed to alloc scratch buffer"); +/* this is the 5-bytes End-Of-Body marker for SMTP */ +#define SMTP_EOB "\r\n.\r\n" +#define SMTP_EOB_FIND_LEN 3 - return CURLE_OUT_OF_MEMORY; - } - } - DEBUGASSERT((size_t)data->set.upload_buffer_size >= (size_t)nread); - - /* Have we already sent part of the EOB? */ - eob_sent = smtp->eob; - - /* This loop can be improved by some kind of Boyer-Moore style of - approach but that is saved for later... */ - if(offset) - memcpy(scratch, data->req.upload_fromhere, offset); - for(i = offset, si = offset; i < nread; i++) { - if(SMTP_EOB[smtp->eob] == data->req.upload_fromhere[i]) { - smtp->eob++; - - /* Is the EOB potentially the terminating CRLF? */ - if(2 == smtp->eob || SMTP_EOB_LEN == smtp->eob) - smtp->trailing_crlf = TRUE; - else - smtp->trailing_crlf = FALSE; - } - else if(smtp->eob) { - /* A previous substring matched so output that first */ - memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent); - si += smtp->eob - eob_sent; - - /* Then compare the first byte */ - if(SMTP_EOB[0] == data->req.upload_fromhere[i]) - smtp->eob = 1; - else - smtp->eob = 0; +/* client reader doing SMTP End-Of-Body escaping. */ +static CURLcode cr_eob_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_eob_ctx *ctx = (struct cr_eob_ctx *)reader; + CURLcode result = CURLE_OK; + size_t nread, i, start, n; + bool eos; + + if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* Get more and convert it when needed */ + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + if(result) + return result; + + ctx->read_eos = eos; + if(nread) { + if(!ctx->n_eob && !memchr(buf, SMTP_EOB[0], nread)) { + /* not in the middle of a match, no EOB start found, just pass */ + *pnread = nread; + *peos = FALSE; + return CURLE_OK; + } + /* scan for EOB (continuation) and convert */ + for(i = start = 0; i < nread; ++i) { + if(ctx->n_eob >= SMTP_EOB_FIND_LEN) { + /* matched the EOB prefix and seeing additional char, add '.' */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(result) + return result; + result = Curl_bufq_cwrite(&ctx->buf, ".", 1, &n); + if(result) + return result; + ctx->n_eob = 0; + start = i; + if(data->state.infilesize > 0) + data->state.infilesize++; + } + + if(buf[i] != SMTP_EOB[ctx->n_eob]) + ctx->n_eob = 0; - eob_sent = 0; + if(buf[i] == SMTP_EOB[ctx->n_eob]) { + /* matching another char of the EOB */ + ++ctx->n_eob; + } + } - /* Reset the trailing CRLF flag as there was more data */ - smtp->trailing_crlf = FALSE; + /* add any remainder to buf */ + if(start < nread) { + result = Curl_bufq_cwrite(&ctx->buf, buf + start, nread - start, &n); + if(result) + return result; + } } - /* Do we have a match for CRLF. as per RFC-5321, sect. 4.5.2 */ - if(SMTP_EOB_FIND_LEN == smtp->eob) { - /* Copy the replacement data to the target buffer */ - memcpy(&scratch[si], &SMTP_EOB_REPL[eob_sent], - SMTP_EOB_REPL_LEN - eob_sent); - si += SMTP_EOB_REPL_LEN - eob_sent; - smtp->eob = 0; - eob_sent = 0; + if(ctx->read_eos) { + /* if we last matched a CRLF or if the data was empty, add ".\r\n" + * to end the body. If we sent something and it did not end with "\r\n", + * add "\r\n.\r\n" to end the body */ + const char *eob = SMTP_EOB; + switch(ctx->n_eob) { + case 2: + /* seen a CRLF at the end, just add the remainder */ + eob = &SMTP_EOB[2]; + break; + case 3: + /* ended with '\r\n.', we should escpe the last '.' */ + eob = "." SMTP_EOB; + break; + default: + break; + } + result = Curl_bufq_cwrite(&ctx->buf, eob, strlen(eob), &n); + if(result) + return result; } - else if(!smtp->eob) - scratch[si++] = data->req.upload_fromhere[i]; } - if(smtp->eob - eob_sent) { - /* A substring matched before processing ended so output that now */ - memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent); - si += smtp->eob - eob_sent; + *peos = FALSE; + if(!Curl_bufq_is_empty(&ctx->buf)) { + result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread); } + else + *pnread = 0; - /* Only use the new buffer if we replaced something */ - if(si != nread) { - /* Upload from the new (replaced) buffer instead */ - data->req.upload_fromhere = scratch; + if(ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* no more data, read all, done. */ + ctx->eos = TRUE; + } + *peos = ctx->eos; + DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zd, %d", + blen, result, *pnread, *peos)); + return CURLE_OK; +} - /* Save the buffer so it can be freed later */ - data->state.scratch = scratch; +static const struct Curl_crtype cr_eob = { + "cr-smtp-eob", + cr_eob_init, + cr_eob_read, + cr_eob_close, + Curl_creader_def_needs_rewind, + sizeof(struct cr_eob_ctx) +}; - /* Free the old scratch buffer */ - free(oldscratch); +static CURLcode cr_eob_add(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; - /* Set the new amount too */ - data->req.upload_present = si; - } - else - free(newscratch); + result = Curl_creader_create(&reader, data, &cr_eob, + CURL_CR_CONTENT_ENCODE); + if(!result) + result = Curl_creader_add(data, reader); - return CURLE_OK; + if(result && reader) + Curl_creader_free(data, reader); + return result; } #endif /* CURL_DISABLE_SMTP */ diff --git a/lib/smtp.h b/lib/smtp.h index 7a04c215499f98..7c2af6807390f5 100644 --- a/lib/smtp.h +++ b/lib/smtp.h @@ -84,17 +84,4 @@ struct smtp_conn { extern const struct Curl_handler Curl_handler_smtp; extern const struct Curl_handler Curl_handler_smtps; -/* this is the 5-bytes End-Of-Body marker for SMTP */ -#define SMTP_EOB "\x0d\x0a\x2e\x0d\x0a" -#define SMTP_EOB_LEN 5 -#define SMTP_EOB_FIND_LEN 3 - -/* if found in data, replace it with this string instead */ -#define SMTP_EOB_REPL "\x0d\x0a\x2e\x2e" -#define SMTP_EOB_REPL_LEN 4 - -CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, - const ssize_t nread, - const ssize_t offset); - #endif /* HEADER_CURL_SMTP_H */ diff --git a/lib/tftp.c b/lib/tftp.c index a99fe1ddcf1039..ff2b7b7457b2e2 100644 --- a/lib/tftp.c +++ b/lib/tftp.c @@ -452,8 +452,6 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, if(data->state.upload) { /* If we are uploading, send an WRQ */ setpacketevent(&state->spacket, TFTP_EVENT_WRQ); - state->data->req.upload_fromhere = - (char *)state->spacket.data + 4; if(data->state.infilesize != -1) Curl_pgrsSetUploadSize(data, data->state.infilesize); } diff --git a/lib/transfer.c b/lib/transfer.c index cbda03a2e7a9aa..e6153c6bea369a 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -115,16 +115,6 @@ char *Curl_checkheaders(const struct Curl_easy *data, } #endif -CURLcode Curl_get_upload_buffer(struct Curl_easy *data) -{ - if(!data->state.ulbuf) { - data->state.ulbuf = malloc(data->set.upload_buffer_size); - if(!data->state.ulbuf) - return CURLE_OUT_OF_MEMORY; - } - return CURLE_OK; -} - static int data_pending(struct Curl_easy *data) { struct connectdata *conn = data->conn; @@ -330,17 +320,6 @@ static CURLcode readwrite_data(struct Curl_easy *data, return result; } -CURLcode Curl_done_sending(struct Curl_easy *data, - struct SingleRequest *k) -{ - k->keepon &= ~KEEP_SEND; /* we're done writing */ - - /* These functions should be moved into the handler struct! */ - Curl_conn_ev_data_done_send(data); - - return CURLE_OK; -} - #if defined(_WIN32) && defined(USE_WINSOCK) #ifndef SIO_IDEAL_SEND_BACKLOG_QUERY #define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747B @@ -368,174 +347,37 @@ static void win_update_buffer_size(curl_socket_t sockfd) /* * Send data to upload to the server, when the socket is writable. */ -static CURLcode readwrite_upload(struct Curl_easy *data, - struct connectdata *conn, - int *didwhat) +static CURLcode readwrite_upload(struct Curl_easy *data, int *didwhat) { - size_t bytes_written; - CURLcode result; - ssize_t nread; /* number of bytes read */ - struct SingleRequest *k = &data->req; - - (void)conn; - *didwhat |= KEEP_SEND; - - if(!(k->keepon & KEEP_SEND_PAUSE)) { - result = Curl_req_flush(data); - if(result == CURLE_AGAIN) /* unable to send all we have */ - return CURLE_OK; - else if(result) - return result; - } - - do { - curl_off_t nbody; - ssize_t offset = 0; - bool eos; - - if(0 != k->upload_present && - k->upload_present < curl_upload_refill_watermark(data) && - !k->upload_chunky &&/*(variable sized chunked header; append not safe)*/ - !k->upload_done && /*!(k->upload_done once k->upload_present sent)*/ - !(k->writebytecount + (curl_off_t)k->upload_present == - data->state.infilesize)) { - offset = k->upload_present; - } - - /* only read more data if there's no upload data already - present in the upload buffer, or if appending to upload buffer */ - if(0 == k->upload_present || offset) { - result = Curl_get_upload_buffer(data); - if(result) - return result; - if(offset && k->upload_fromhere != data->state.ulbuf) - memmove(data->state.ulbuf, k->upload_fromhere, offset); - /* init the "upload from here" pointer */ - k->upload_fromhere = data->state.ulbuf; - - if(!k->upload_done) { - /* HTTP pollution, this should be written nicer to become more - protocol agnostic. */ - size_t fillcount; - - if(k->exp100 == EXP100_SENDING_REQUEST) { - /* If this call is to send body data, we must take some action: - We have sent off the full HTTP 1.1 request, and we shall now - go into the Expect: 100 state and await such a header */ - k->exp100 = EXP100_AWAITING_CONTINUE; /* wait for the header */ - k->keepon &= ~KEEP_SEND; /* disable writing */ - k->start100 = Curl_now(); /* timeout count starts now */ - *didwhat &= ~KEEP_SEND; /* we didn't write anything actually */ - /* set a timeout for the multi interface */ - Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); - break; - } - - k->upload_fromhere += offset; - result = Curl_client_read(data, k->upload_fromhere, - data->set.upload_buffer_size-offset, - &fillcount, &eos); - k->upload_fromhere -= offset; - if(result) - return result; - - nread = offset + fillcount; - } - else - nread = 0; /* we're done uploading/reading */ - - if(!nread && (k->keepon & KEEP_SEND_PAUSE)) { - /* this is a paused transfer */ - break; - } - if(nread <= 0) { - result = Curl_done_sending(data, k); - if(result) - return result; - break; - } + CURLcode result = CURLE_OK; - /* store number of bytes available for upload */ - k->upload_present = nread; + if((data->req.keepon & KEEP_SEND_PAUSE)) + return CURLE_OK; -#ifndef CURL_DISABLE_SMTP - if(conn->handler->protocol & PROTO_FAMILY_SMTP) { - result = Curl_smtp_escape_eob(data, nread, offset); - if(result) - return result; - } -#endif /* CURL_DISABLE_SMTP */ - } /* if 0 == k->upload_present or appended to upload buffer */ - else { - /* We have a partial buffer left from a previous "round". Use - that instead of reading more data */ - } + /* We should not get here when the sending is already done. It + * probably means that someone set `data-req.keepon |= KEEP_SEND` + * when it should not. */ + DEBUGASSERT(!Curl_req_done_sending(data)); - /* write to socket (send away data) */ - result = Curl_xfer_send(data, - k->upload_fromhere, /* buffer pointer */ - k->upload_present, /* buffer size */ - &bytes_written); /* actually sent */ + if(!Curl_req_done_sending(data)) { + *didwhat |= KEEP_SEND; + result = Curl_req_send_more(data); if(result) return result; #if defined(_WIN32) && defined(USE_WINSOCK) + /* FIXME: this looks like it would fit better into cf-socket.c + * but then I do not know enough Windows to say... */ { struct curltime n = Curl_now(); - if(Curl_timediff(n, conn->last_sndbuf_update) > 1000) { - win_update_buffer_size(conn->writesockfd); - conn->last_sndbuf_update = n; + if(Curl_timediff(n, data->conn->last_sndbuf_update) > 1000) { + win_update_buffer_size(data->conn->writesockfd); + data->conn->last_sndbuf_update = n; } } #endif - - nbody = bytes_written; - if(nbody) { - /* show the data before we change the pointer upload_fromhere */ - Curl_debug(data, CURLINFO_DATA_OUT, - &k->upload_fromhere[bytes_written - nbody], - (size_t)nbody); - - k->writebytecount += nbody; - Curl_pgrsSetUploadCounter(data, k->writebytecount); - } - - if((!k->upload_chunky || k->forbidchunk) && - (k->writebytecount == data->state.infilesize)) { - /* we have sent all data we were supposed to */ - k->upload_done = TRUE; - infof(data, "We are completely uploaded and fine"); - } - - if(k->upload_present != bytes_written) { - /* we only wrote a part of the buffer (if anything), deal with it! */ - - /* store the amount of bytes left in the buffer to write */ - k->upload_present -= bytes_written; - - /* advance the pointer where to find the buffer when the next send - is to happen */ - k->upload_fromhere += bytes_written; - } - else { - /* we've uploaded that buffer now */ - result = Curl_get_upload_buffer(data); - if(result) - return result; - k->upload_fromhere = data->state.ulbuf; - k->upload_present = 0; /* no more bytes left */ - - if(k->upload_done) { - result = Curl_done_sending(data, k); - if(result) - return result; - } - } - - - } while(0); /* just to break out from! */ - - return CURLE_OK; + } + return result; } static int select_bits_paused(struct Curl_easy *data, int select_bits) @@ -626,7 +468,7 @@ CURLcode Curl_readwrite(struct Curl_easy *data, if((k->keepon & KEEP_SEND) && (select_bits & CURL_CSELECT_OUT)) { /* write */ - result = readwrite_upload(data, conn, &didwhat); + result = readwrite_upload(data, &didwhat); if(result) goto out; } @@ -1458,3 +1300,9 @@ CURLcode Curl_xfer_recv(struct Curl_easy *data, blen = (size_t)data->set.buffer_size; return Curl_conn_recv(data, sockindex, buf, blen, pnrcvd); } + +CURLcode Curl_xfer_send_close(struct Curl_easy *data) +{ + Curl_conn_ev_data_done_send(data); + return CURLE_OK; +} diff --git a/lib/transfer.h b/lib/transfer.h index a4af1899e51a58..96e69988ad93f1 100644 --- a/lib/transfer.h +++ b/lib/transfer.h @@ -50,10 +50,6 @@ int Curl_single_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); CURLcode Curl_retry_request(struct Curl_easy *data, char **url); bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc); -CURLcode Curl_get_upload_buffer(struct Curl_easy *data); - -CURLcode Curl_done_sending(struct Curl_easy *data, - struct SingleRequest *k); /** * Write the transfer raw response bytes, as received from the connection. @@ -106,4 +102,6 @@ CURLcode Curl_xfer_recv(struct Curl_easy *data, char *buf, size_t blen, ssize_t *pnrcvd); +CURLcode Curl_xfer_send_close(struct Curl_easy *data); + #endif /* HEADER_CURL_TRANSFER_H */ diff --git a/lib/url.c b/lib/url.c index d72dfc773bbd1e..b30576597642d4 100644 --- a/lib/url.c +++ b/lib/url.c @@ -277,7 +277,6 @@ CURLcode Curl_close(struct Curl_easy **datap) up_free(data); Curl_dyn_free(&data->state.headerb); - Curl_safefree(data->state.ulbuf); Curl_flush_cookies(data, TRUE); Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]); Curl_altsvc_cleanup(&data->asi); diff --git a/lib/urldata.h b/lib/urldata.h index 3d68a60563aec5..0871e1348d5f77 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1215,7 +1215,6 @@ struct UrlState { struct dynbuf headerb; /* buffer to store headers in */ struct curl_slist *hstslist; /* list of HSTS files set by curl_easy_setopt(HSTS) calls */ - char *ulbuf; /* allocated upload buffer or NULL */ curl_off_t current_speed; /* the ProgressShow() function sets this, bytes / second */