lib: rewind BEFORE request instead of AFTER previous

This makes a big difference for cases when the rewind is not actually
necessary to perofm (for example HTTP response code 301 converts to GET)
and therefore the rewind can be avoided. In particular for situations
when that rewind fails, for example when reading from a pipe or similar.

Reported-by: Ali Utku Selen

Fixes #9735
Closes #9958
This commit is contained in:
Daniel Stenberg 2022-11-22 08:25:50 +01:00
parent 9ecebb5d00
commit 1b3973180b
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
7 changed files with 115 additions and 121 deletions

View file

@ -1729,6 +1729,90 @@ static CURLcode protocol_connect(struct Curl_easy *data,
return result; /* pass back status */
}
/*
* readrewind() rewinds the read stream. This is typically used for HTTP
* POST/PUT with multi-pass authentication when a sending was denied and a
* resend is necessary.
*/
static CURLcode readrewind(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
curl_mimepart *mimepart = &data->set.mimepost;
DEBUGASSERT(conn);
data->state.rewindbeforesend = FALSE; /* we rewind now */
/* explicitly switch off sending data on this connection now since we are
about to restart a new transfer and thus we want to avoid inadvertently
sending more data on the existing connection until the next transfer
starts */
data->req.keepon &= ~KEEP_SEND;
/* We have sent away data. If not using CURLOPT_POSTFIELDS or
CURLOPT_HTTPPOST, call app to rewind
*/
if(conn->handler->protocol & PROTO_FAMILY_HTTP) {
struct HTTP *http = data->req.p.http;
if(http->sendit)
mimepart = http->sendit;
}
if(data->set.postfields ||
(data->state.httpreq == HTTPREQ_GET) ||
(data->state.httpreq == HTTPREQ_HEAD))
; /* no need to rewind */
else if(data->state.httpreq == HTTPREQ_POST_MIME ||
data->state.httpreq == HTTPREQ_POST_FORM) {
CURLcode result = Curl_mime_rewind(mimepart);
if(result) {
failf(data, "Cannot rewind mime/post data");
return result;
}
}
else {
if(data->set.seek_func) {
int err;
Curl_set_in_callback(data, true);
err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET);
Curl_set_in_callback(data, false);
if(err) {
failf(data, "seek callback returned error %d", (int)err);
return CURLE_SEND_FAIL_REWIND;
}
}
else if(data->set.ioctl_func) {
curlioerr err;
Curl_set_in_callback(data, true);
err = (data->set.ioctl_func)(data, CURLIOCMD_RESTARTREAD,
data->set.ioctl_client);
Curl_set_in_callback(data, false);
infof(data, "the ioctl callback returned %d", (int)err);
if(err) {
failf(data, "ioctl callback returned error %d", (int)err);
return CURLE_SEND_FAIL_REWIND;
}
}
else {
/* If no CURLOPT_READFUNCTION is used, we know that we operate on a
given FILE * stream and we can actually attempt to rewind that
ourselves with fseek() */
if(data->state.fread_func == (curl_read_callback)fread) {
if(-1 != fseek(data->state.in, 0, SEEK_SET))
/* successful rewind */
return CURLE_OK;
}
/* no callback set or failure above, makes us fail at once */
failf(data, "necessary data rewind wasn't possible");
return CURLE_SEND_FAIL_REWIND;
}
}
return CURLE_OK;
}
/*
* Curl_preconnect() is called immediately before a connect starts. When a
* redirect is followed, this is then called multiple times during a single
@ -1741,6 +1825,7 @@ CURLcode Curl_preconnect(struct Curl_easy *data)
if(!data->state.buffer)
return CURLE_OUT_OF_MEMORY;
}
return CURLE_OK;
}
@ -1997,7 +2082,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
break;
case MSTATE_PROTOCONNECT:
if(data->conn->bits.reuse) {
if(data->state.rewindbeforesend)
result = readrewind(data);
if(!result && data->conn->bits.reuse) {
/* ftp seems to hang when protoconnect on reused connection
* since we handle PROTOCONNECT in general inside the filers, it
* seems wrong to restart this on a reused connection. */
@ -2005,7 +2093,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
rc = CURLM_CALL_MULTI_PERFORM;
break;
}
result = protocol_connect(data, &protocol_connected);
if(!result)
result = protocol_connect(data, &protocol_connected);
if(!result && !protocol_connected)
/* switch to waiting state */
multistate(data, MSTATE_PROTOCONNECTING);