Curl_client_write() & al.: chop long data, convert data only once.
This commit is contained in:
parent
e63d18fbd1
commit
6ea4ee94f9
68
lib/easy.c
68
lib/easy.c
@ -1026,73 +1026,15 @@ CURLcode curl_easy_pause(CURL *curl, int action)
|
|||||||
/* we have a buffer for sending that we now seem to be able to deliver
|
/* we have a buffer for sending that we now seem to be able to deliver
|
||||||
since the receive pausing is lifted! */
|
since the receive pausing is lifted! */
|
||||||
|
|
||||||
/* get the pointer, type and length in local copies since the function may
|
/* get the pointer in local copy since the function may return PAUSE
|
||||||
return PAUSE again and then we'll get a new copy allocted and stored in
|
again and then we'll get a new copy allocted and stored in
|
||||||
the tempwrite variables */
|
the tempwrite variables */
|
||||||
char *tempwrite = data->state.tempwrite;
|
char *tempwrite = data->state.tempwrite;
|
||||||
char *freewrite = tempwrite; /* store this pointer to free it later */
|
|
||||||
size_t tempsize = data->state.tempwritesize;
|
|
||||||
int temptype = data->state.tempwritetype;
|
|
||||||
size_t chunklen;
|
|
||||||
|
|
||||||
/* clear tempwrite here just to make sure it gets cleared if there's no
|
|
||||||
further use of it, and make sure we don't clear it after the function
|
|
||||||
invoke as it may have been set to a new value by then */
|
|
||||||
data->state.tempwrite = NULL;
|
data->state.tempwrite = NULL;
|
||||||
|
result = Curl_client_chop_write(data->easy_conn, data->state.tempwritetype,
|
||||||
/* since the write callback API is define to never exceed
|
tempwrite, data->state.tempwritesize);
|
||||||
CURL_MAX_WRITE_SIZE bytes in a single call, and since we may in fact
|
free(tempwrite);
|
||||||
have more data than that in our buffer here, we must loop sending the
|
|
||||||
data in multiple calls until there's no data left or we get another
|
|
||||||
pause returned.
|
|
||||||
|
|
||||||
A tricky part is that the function we call will "buffer" the data
|
|
||||||
itself when it pauses on a particular buffer, so we may need to do some
|
|
||||||
extra trickery if we get a pause return here.
|
|
||||||
*/
|
|
||||||
do {
|
|
||||||
chunklen = (tempsize > CURL_MAX_WRITE_SIZE)?CURL_MAX_WRITE_SIZE:tempsize;
|
|
||||||
|
|
||||||
result = Curl_client_write(data->easy_conn,
|
|
||||||
temptype, tempwrite, chunklen);
|
|
||||||
if(result)
|
|
||||||
/* failures abort the loop at once */
|
|
||||||
break;
|
|
||||||
|
|
||||||
if(data->state.tempwrite && (tempsize - chunklen)) {
|
|
||||||
/* Ouch, the reading is again paused and the block we send is now
|
|
||||||
"cached". If this is the final chunk we can leave it like this, but
|
|
||||||
if we have more chunks that are cached after this, we need to free
|
|
||||||
the newly cached one and put back a version that is truly the entire
|
|
||||||
contents that is saved for later
|
|
||||||
*/
|
|
||||||
char *newptr;
|
|
||||||
|
|
||||||
/* note that tempsize is still the size as before the callback was
|
|
||||||
used, and thus the whole piece of data to keep */
|
|
||||||
newptr = realloc(data->state.tempwrite, tempsize);
|
|
||||||
|
|
||||||
if(!newptr) {
|
|
||||||
free(data->state.tempwrite); /* free old area */
|
|
||||||
data->state.tempwrite = NULL;
|
|
||||||
result = CURLE_OUT_OF_MEMORY;
|
|
||||||
/* tempwrite will be freed further down */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
data->state.tempwrite = newptr; /* store new pointer */
|
|
||||||
memcpy(newptr, tempwrite, tempsize);
|
|
||||||
data->state.tempwritesize = tempsize; /* store new size */
|
|
||||||
/* tempwrite will be freed further down */
|
|
||||||
break; /* go back to pausing until further notice */
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tempsize -= chunklen; /* left after the call above */
|
|
||||||
tempwrite += chunklen; /* advance the pointer */
|
|
||||||
}
|
|
||||||
|
|
||||||
} while(!result && tempsize);
|
|
||||||
|
|
||||||
free(freewrite); /* this is unconditionally no longer used */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if there's no error and we're not pausing both directions, we want
|
/* if there's no error and we're not pausing both directions, we want
|
||||||
|
165
lib/sendf.c
165
lib/sendf.c
@ -374,25 +374,21 @@ static CURLcode pausewrite(struct SessionHandle *data,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Curl_client_write() sends data to the write callback(s)
|
/* Curl_client_chop_write() writes chunks of data not larger than
|
||||||
|
* CURL_MAX_WRITE_SIZE via client write callback(s) and
|
||||||
The bit pattern defines to what "streams" to write to. Body and/or header.
|
* takes care of pause requests from the callbacks.
|
||||||
The defines are in sendf.h of course.
|
|
||||||
|
|
||||||
If CURL_DO_LINEEND_CONV is enabled, data is converted IN PLACE to the
|
|
||||||
local character encoding. This is a problem and should be changed in
|
|
||||||
the future to leave the original data alone.
|
|
||||||
*/
|
*/
|
||||||
CURLcode Curl_client_write(struct connectdata *conn,
|
CURLcode Curl_client_chop_write(struct connectdata *conn,
|
||||||
int type,
|
int type,
|
||||||
char *ptr,
|
char * ptr,
|
||||||
size_t len)
|
size_t len)
|
||||||
{
|
{
|
||||||
struct SessionHandle *data = conn->data;
|
struct SessionHandle *data = conn->data;
|
||||||
size_t wrote;
|
curl_write_callback writeheader = NULL;
|
||||||
|
curl_write_callback writebody = NULL;
|
||||||
|
|
||||||
if(0 == len)
|
if(!len)
|
||||||
len = strlen(ptr);
|
return CURLE_OK;
|
||||||
|
|
||||||
/* If reading is actually paused, we're forced to append this chunk of data
|
/* If reading is actually paused, we're forced to append this chunk of data
|
||||||
to the already held data, but only if it is the same type as otherwise it
|
to the already held data, but only if it is the same type as otherwise it
|
||||||
@ -417,78 +413,107 @@ CURLcode Curl_client_write(struct connectdata *conn,
|
|||||||
/* update the pointer and the size */
|
/* update the pointer and the size */
|
||||||
data->state.tempwrite = newptr;
|
data->state.tempwrite = newptr;
|
||||||
data->state.tempwritesize = newlen;
|
data->state.tempwritesize = newlen;
|
||||||
|
|
||||||
return CURLE_OK;
|
return CURLE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type & CLIENTWRITE_BODY) {
|
/* Determine the callback(s) to use. */
|
||||||
if((conn->handler->protocol&PROTO_FAMILY_FTP) &&
|
if(type & CLIENTWRITE_BODY)
|
||||||
conn->proto.ftpc.transfertype == 'A') {
|
writebody = data->set.fwrite_func;
|
||||||
/* convert from the network encoding */
|
|
||||||
CURLcode result = Curl_convert_from_network(data, ptr, len);
|
|
||||||
/* Curl_convert_from_network calls failf if unsuccessful */
|
|
||||||
if(result)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
#ifdef CURL_DO_LINEEND_CONV
|
|
||||||
/* convert end-of-line markers */
|
|
||||||
len = convert_lineends(data, ptr, len);
|
|
||||||
#endif /* CURL_DO_LINEEND_CONV */
|
|
||||||
}
|
|
||||||
/* If the previous block of data ended with CR and this block of data is
|
|
||||||
just a NL, then the length might be zero */
|
|
||||||
if(len) {
|
|
||||||
wrote = data->set.fwrite_func(ptr, 1, len, data->set.out);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
wrote = len;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(CURL_WRITEFUNC_PAUSE == wrote) {
|
|
||||||
if(conn->handler->flags & PROTOPT_NONETWORK) {
|
|
||||||
/* Protocols that work without network cannot be paused. This is
|
|
||||||
actually only FILE:// just now, and it can't pause since the
|
|
||||||
transfer isn't done using the "normal" procedure. */
|
|
||||||
failf(data, "Write callback asked for PAUSE when not supported!");
|
|
||||||
return CURLE_WRITE_ERROR;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return pausewrite(data, type, ptr, len);
|
|
||||||
}
|
|
||||||
else if(wrote != len) {
|
|
||||||
failf(data, "Failed writing body (%zu != %zu)", wrote, len);
|
|
||||||
return CURLE_WRITE_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if((type & CLIENTWRITE_HEADER) &&
|
if((type & CLIENTWRITE_HEADER) &&
|
||||||
(data->set.fwrite_header || data->set.writeheader) ) {
|
(data->set.fwrite_header || data->set.writeheader)) {
|
||||||
/*
|
/*
|
||||||
* Write headers to the same callback or to the especially setup
|
* Write headers to the same callback or to the especially setup
|
||||||
* header callback function (added after version 7.7.1).
|
* header callback function (added after version 7.7.1).
|
||||||
*/
|
*/
|
||||||
curl_write_callback writeit=
|
writeheader =
|
||||||
data->set.fwrite_header?data->set.fwrite_header:data->set.fwrite_func;
|
data->set.fwrite_header? data->set.fwrite_header: data->set.fwrite_func;
|
||||||
|
}
|
||||||
|
|
||||||
/* Note: The header is in the host encoding
|
/* Chop data, write chunks. */
|
||||||
regardless of the ftp transfer mode (ASCII/Image) */
|
while(len) {
|
||||||
|
size_t chunklen = len <= CURL_MAX_WRITE_SIZE? len: CURL_MAX_WRITE_SIZE;
|
||||||
|
|
||||||
wrote = writeit(ptr, 1, len, data->set.writeheader);
|
if(writebody) {
|
||||||
if(CURL_WRITEFUNC_PAUSE == wrote)
|
size_t wrote = writebody(ptr, 1, chunklen, data->set.out);
|
||||||
/* here we pass in the HEADER bit only since if this was body as well
|
|
||||||
then it was passed already and clearly that didn't trigger the pause,
|
|
||||||
so this is saved for later with the HEADER bit only */
|
|
||||||
return pausewrite(data, CLIENTWRITE_HEADER, ptr, len);
|
|
||||||
|
|
||||||
if(wrote != len) {
|
if(CURL_WRITEFUNC_PAUSE == wrote) {
|
||||||
failf (data, "Failed writing header");
|
if(conn->handler->flags & PROTOPT_NONETWORK) {
|
||||||
return CURLE_WRITE_ERROR;
|
/* Protocols that work without network cannot be paused. This is
|
||||||
|
actually only FILE:// just now, and it can't pause since the
|
||||||
|
transfer isn't done using the "normal" procedure. */
|
||||||
|
failf(data, "Write callback asked for PAUSE when not supported!");
|
||||||
|
return CURLE_WRITE_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return pausewrite(data, type, ptr, len);
|
||||||
|
}
|
||||||
|
else if(wrote != chunklen) {
|
||||||
|
failf(data, "Failed writing body (%zu != %zu)", wrote, chunklen);
|
||||||
|
return CURLE_WRITE_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(writeheader) {
|
||||||
|
size_t wrote = writeheader(ptr, 1, chunklen, data->set.writeheader);
|
||||||
|
|
||||||
|
if(CURL_WRITEFUNC_PAUSE == wrote)
|
||||||
|
/* here we pass in the HEADER bit only since if this was body as well
|
||||||
|
then it was passed already and clearly that didn't trigger the
|
||||||
|
pause, so this is saved for later with the HEADER bit only */
|
||||||
|
return pausewrite(data, CLIENTWRITE_HEADER, ptr, len);
|
||||||
|
|
||||||
|
if(wrote != chunklen) {
|
||||||
|
failf (data, "Failed writing header");
|
||||||
|
return CURLE_WRITE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += chunklen;
|
||||||
|
len -= chunklen;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CURLE_OK;
|
return CURLE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Curl_client_write() sends data to the write callback(s)
|
||||||
|
|
||||||
|
The bit pattern defines to what "streams" to write to. Body and/or header.
|
||||||
|
The defines are in sendf.h of course.
|
||||||
|
|
||||||
|
If CURL_DO_LINEEND_CONV is enabled, data is converted IN PLACE to the
|
||||||
|
local character encoding. This is a problem and should be changed in
|
||||||
|
the future to leave the original data alone.
|
||||||
|
*/
|
||||||
|
CURLcode Curl_client_write(struct connectdata *conn,
|
||||||
|
int type,
|
||||||
|
char *ptr,
|
||||||
|
size_t len)
|
||||||
|
{
|
||||||
|
struct SessionHandle *data = conn->data;
|
||||||
|
|
||||||
|
if(0 == len)
|
||||||
|
len = strlen(ptr);
|
||||||
|
|
||||||
|
/* FTP data may need conversion. */
|
||||||
|
if((type & CLIENTWRITE_BODY) &&
|
||||||
|
(conn->handler->protocol & PROTO_FAMILY_FTP) &&
|
||||||
|
conn->proto.ftpc.transfertype == 'A') {
|
||||||
|
/* convert from the network encoding */
|
||||||
|
CURLcode result = Curl_convert_from_network(data, ptr, len);
|
||||||
|
/* Curl_convert_from_network calls failf if unsuccessful */
|
||||||
|
if(result)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
#ifdef CURL_DO_LINEEND_CONV
|
||||||
|
/* convert end-of-line markers */
|
||||||
|
len = convert_lineends(data, ptr, len);
|
||||||
|
#endif /* CURL_DO_LINEEND_CONV */
|
||||||
|
}
|
||||||
|
|
||||||
|
return Curl_client_chop_write(conn, type, ptr, len);
|
||||||
|
}
|
||||||
|
|
||||||
CURLcode Curl_read_plain(curl_socket_t sockfd,
|
CURLcode Curl_read_plain(curl_socket_t sockfd,
|
||||||
char *buf,
|
char *buf,
|
||||||
size_t bytesfromsocket,
|
size_t bytesfromsocket,
|
||||||
|
@ -51,6 +51,8 @@ void Curl_failf(struct SessionHandle *, const char *fmt, ...);
|
|||||||
#define CLIENTWRITE_HEADER (1<<1)
|
#define CLIENTWRITE_HEADER (1<<1)
|
||||||
#define CLIENTWRITE_BOTH (CLIENTWRITE_BODY|CLIENTWRITE_HEADER)
|
#define CLIENTWRITE_BOTH (CLIENTWRITE_BODY|CLIENTWRITE_HEADER)
|
||||||
|
|
||||||
|
CURLcode Curl_client_chop_write(struct connectdata *conn, int type, char *ptr,
|
||||||
|
size_t len) WARN_UNUSED_RESULT;
|
||||||
CURLcode Curl_client_write(struct connectdata *conn, int type, char *ptr,
|
CURLcode Curl_client_write(struct connectdata *conn, int type, char *ptr,
|
||||||
size_t len) WARN_UNUSED_RESULT;
|
size_t len) WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user