lib: enhance client reader resume + rewind

- update client reader documentation
- client reader, add rewind capabilities
    - tell creader to rewind on next start
    - Curl_client_reset() will keep reader for future rewind if requested
    - add Curl_client_cleanup() for freeing all resources independent of
      rewinds
    - add Curl_client_start() to trigger rewinds
    - move rewind code from multi.c to sendf.c and make part of
      "cr-in"'s implementation
- http, move the "resume_from" handling into the client readers
    - the setup of a HTTP request is reshuffled to follow:
      * determine method, target, auth negotiation
      * install the client reader(s) for the request, including crlf
        conversions and "chunked" encoding
      * apply ranges to client reader
      * concat request headers, upgrades, cookies, etc.
      * complete request by determining Content-Length of installed
        readers in combination with method
      * send
    - add methods for client readers to
      * return the overall length they will generate (or -1 when unknown)
      * return the amount of data on the CLIENT level, so that
        expect-100 can decide if it want to apply itself
      * set a "resume_from" offset or fail if unsupported
    - struct HTTP has become largely empty now
- rename `Client_reader_*` to `Curl_creader_*`

Closes #13026
This commit is contained in:
Stefan Eissing 2024-02-29 10:12:39 +01:00 committed by Daniel Stenberg
parent 9c7768cd88
commit 14bcea074a
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
15 changed files with 752 additions and 474 deletions

View file

@ -1816,93 +1816,6 @@ 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)
{
#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API)
curl_mimepart *mimepart = &data->set.mimepost;
#endif
DEBUGASSERT(data->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 !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_MIME)
if(data->conn->handler->protocol & PROTO_FAMILY_HTTP) {
if(data->state.mimepost)
mimepart = data->state.mimepost;
}
#endif
if(data->set.postfields ||
(data->state.httpreq == HTTPREQ_GET) ||
(data->state.httpreq == HTTPREQ_HEAD))
; /* no need to rewind */
#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API)
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;
}
}
#endif
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
@ -2169,9 +2082,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
break;
case MSTATE_PROTOCONNECT:
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