transfer: fix upload rate limiting, add test cases

- add test cases for rate limiting uploads for all
  http versions
- fix transfer loop handling of limits. Signal a re-receive
  attempt only on exhausting maxloops without an EAGAIN
- fix `data->state.selectbits` forcing re-receive to also
  set re-sending when transfer is doing this.

Reported-by: Karthikdasari0423 on github
Fixes #12559
Closes #12586
This commit is contained in:
Stefan Eissing 2023-12-22 12:27:59 +01:00 committed by Daniel Stenberg
parent 8b1d229835
commit e492c7c524
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
3 changed files with 60 additions and 6 deletions

View file

@ -428,7 +428,7 @@ static CURLcode readwrite_data(struct Curl_easy *data,
size_t blen;
size_t consumed;
int maxloops = 10;
curl_off_t max_recv = data->set.max_recv_speed ? 0 : CURL_OFF_T_MAX;
curl_off_t total_received = 0;
bool data_eof_handled = FALSE;
DEBUGASSERT(data->state.buffer);
@ -439,6 +439,7 @@ static CURLcode readwrite_data(struct Curl_easy *data,
do {
bool is_empty_data = FALSE;
size_t bytestoread = data->set.buffer_size;
/* For HTTP/2 and HTTP/3, read data without caring about the content
length. This is safe because body in HTTP/2 is always segmented
thanks to its framing layer. Meanwhile, we have to call Curl_read
@ -447,6 +448,15 @@ static CURLcode readwrite_data(struct Curl_easy *data,
bool is_http3 = Curl_conn_is_http3(data, conn, FIRSTSOCKET);
data_eof_handled = is_http3 || Curl_conn_is_http2(data, conn, FIRSTSOCKET);
if(data->set.max_recv_speed) {
/* Limit the amount we read here, break on reaching it */
curl_off_t net_limit = data->set.max_recv_speed - total_received;
if(net_limit <= 0)
break;
if((size_t)net_limit < bytestoread)
bytestoread = (size_t)net_limit;
}
/* Each loop iteration starts with a fresh buffer and handles
* all data read into it. */
buf = data->state.buffer;
@ -654,7 +664,7 @@ static CURLcode readwrite_data(struct Curl_easy *data,
}
#endif /* CURL_DISABLE_HTTP */
max_recv -= blen;
total_received += blen;
if(!k->chunk && (blen || k->badheader || is_empty_data)) {
/* If this is chunky transfer, it was already written */
@ -712,11 +722,13 @@ static CURLcode readwrite_data(struct Curl_easy *data,
break;
}
} while((max_recv > 0) && data_pending(data) && maxloops--);
} while(maxloops-- && data_pending(data));
if(maxloops <= 0 || max_recv <= 0) {
/* we mark it as read-again-please */
if(maxloops <= 0) {
/* did not read until EAGAIN, mark read-again-please */
data->state.select_bits = CURL_CSELECT_IN;
if((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
data->state.select_bits |= CURL_CSELECT_OUT;
}
if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) &&