Skip to content

Commit

Permalink
Curl_client_write() & al.: chop long data, convert data only once.
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick Monnerat committed Dec 9, 2014
1 parent e63d18f commit 6ea4ee9
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 136 deletions.
70 changes: 6 additions & 64 deletions lib/easy.c
Expand Up @@ -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
since the receive pausing is lifted! */

/* get the pointer, type and length in local copies since the function may
return PAUSE again and then we'll get a new copy allocted and stored in
/* get the pointer in local copy since the function may return PAUSE
again and then we'll get a new copy allocted and stored in
the tempwrite variables */
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;

/* since the write callback API is define to never exceed
CURL_MAX_WRITE_SIZE bytes in a single call, and since we may in fact
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 */
data->state.tempwrite = NULL;
result = Curl_client_chop_write(data->easy_conn, data->state.tempwritetype,
tempwrite, data->state.tempwritesize);
free(tempwrite);
}

/* if there's no error and we're not pausing both directions, we want
Expand Down
169 changes: 97 additions & 72 deletions lib/sendf.c
Expand Up @@ -374,25 +374,21 @@ static CURLcode pausewrite(struct SessionHandle *data,
}


/* 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.
/* Curl_client_chop_write() writes chunks of data not larger than
* CURL_MAX_WRITE_SIZE via client write callback(s) and
* takes care of pause requests from the callbacks.
*/
CURLcode Curl_client_write(struct connectdata *conn,
int type,
char *ptr,
size_t len)
CURLcode Curl_client_chop_write(struct connectdata *conn,
int type,
char * ptr,
size_t len)
{
struct SessionHandle *data = conn->data;
size_t wrote;
curl_write_callback writeheader = NULL;
curl_write_callback writebody = NULL;

if(0 == len)
len = strlen(ptr);
if(!len)
return CURLE_OK;

/* 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
Expand All @@ -417,78 +413,107 @@ CURLcode Curl_client_write(struct connectdata *conn,
/* update the pointer and the size */
data->state.tempwrite = newptr;
data->state.tempwritesize = newlen;

return CURLE_OK;
}

if(type & CLIENTWRITE_BODY) {
if((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;
/* Determine the callback(s) to use. */
if(type & CLIENTWRITE_BODY)
writebody = data->set.fwrite_func;
if((type & CLIENTWRITE_HEADER) &&
(data->set.fwrite_header || data->set.writeheader)) {
/*
* Write headers to the same callback or to the especially setup
* header callback function (added after version 7.7.1).
*/
writeheader =
data->set.fwrite_header? data->set.fwrite_header: data->set.fwrite_func;
}

/* Chop data, write chunks. */
while(len) {
size_t chunklen = len <= CURL_MAX_WRITE_SIZE? len: CURL_MAX_WRITE_SIZE;

#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(writebody) {
size_t wrote = writebody(ptr, 1, chunklen, data->set.out);

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!");
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 != chunklen) {
failf(data, "Failed writing body (%zu != %zu)", wrote, chunklen);
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) &&
(data->set.fwrite_header || data->set.writeheader) ) {
/*
* Write headers to the same callback or to the especially setup
* header callback function (added after version 7.7.1).
*/
curl_write_callback writeit=
data->set.fwrite_header?data->set.fwrite_header:data->set.fwrite_func;

/* Note: The header is in the host encoding
regardless of the ftp transfer mode (ASCII/Image) */

wrote = writeit(ptr, 1, len, 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 != len) {
failf (data, "Failed writing header");
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;
}


/* 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,
char *buf,
size_t bytesfromsocket,
Expand Down
2 changes: 2 additions & 0 deletions lib/sendf.h
Expand Up @@ -51,6 +51,8 @@ void Curl_failf(struct SessionHandle *, const char *fmt, ...);
#define CLIENTWRITE_HEADER (1<<1)
#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,
size_t len) WARN_UNUSED_RESULT;

Expand Down

0 comments on commit 6ea4ee9

Please sign in to comment.