diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index f4b89c2f8..1bec4c14d 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -327,6 +327,12 @@ Disable Content decoding. See \fICURLOPT_HTTP_CONTENT_DECODING(3)\fP Disable Transfer decoding. See \fICURLOPT_HTTP_TRANSFER_DECODING(3)\fP .IP CURLOPT_EXPECT_100_TIMEOUT_MS 100-continue timeout. See \fICURLOPT_EXPECT_100_TIMEOUT_MS(3)\fP +.IP CURLOPT_TRAILERFUNCTION +Set callback for sending trailing headers. See +\fICURLOPT_TRAILERFUNCTION(3)\fP +.IP CURLOPT_TRAILERDATA +Custom pointer passed to the trailing headers callback. See +\fICURLOPT_TRAILERDATA(3)\fP .IP CURLOPT_PIPEWAIT Wait on connection to pipeline on it. See \fICURLOPT_PIPEWAIT(3)\fP .IP CURLOPT_STREAM_DEPENDS diff --git a/docs/libcurl/opts/CURLOPT_TRAILERDATA.3 b/docs/libcurl/opts/CURLOPT_TRAILERDATA.3 new file mode 100644 index 000000000..32fadd4ac --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_TRAILERDATA.3 @@ -0,0 +1,57 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2018, Daniel Stenberg, , et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at https://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_TRAILERDATA 3 "14 Aug 2018" "libcurl 7.64.0" "curl_easy_setopt options" + +.SH NAME: +CURLOPT_TRAILERDATA \- Custom pointer passed to the trailing headers callback + +.SH SYNOPSIS: +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_TRAILERDATA, void *userdata); + +.SH DESCRIPTION: +Data pointer to be passed to the HTTP trailer callback function. + +.SH DEFAULT: +NULL + +.SH PROTOCOLS: +HTTP + +.SH EXAMPLE: +.nf +/* Assuming we have a CURL handle in the hndl variable. */ + +struct MyData data; + +curl_easy_setopt(hndl, CURLOPT_TRAILERDATA, &data); + +.fi + +A more complete example can be found in examples/http_trailers.html +.SH AVAILABILITY: +This option was added in curl 7.64.0 and is present if HTTP support is enabled + +.SH "SEE ALSO" +.BR CURLOPT_TRAILERFUNCTION "(3), " diff --git a/docs/libcurl/opts/CURLOPT_TRAILERFUNCTION.3 b/docs/libcurl/opts/CURLOPT_TRAILERFUNCTION.3 new file mode 100644 index 000000000..8ca132362 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_TRAILERFUNCTION.3 @@ -0,0 +1,112 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2018, Daniel Stenberg, , et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at https://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_TRAILERFUNCTION 3 "14 Aug 2018" "libcurl 7.64.0" "curl_easy_setopt options" + +.SH NAME: +CURLOPT_TRAILERFUNCTION \- Set callback for sending trailing headers + +.SH SYNOPSIS: +#include + +int curl_trailer_callback(struct curl_slist ** list, void *userdata); + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_TRAILERFUNCTION, curl_trailer_callback *func); + +.SH DESCRIPTION: +Pass a pointer to a callback function. + +This callback function will be called once right before sending the final +CR LF in an HTTP chunked transfer to fill a list of trailing headers to be +sent before finishing the HTTP transfer. + +You can set the userdata argument with the CURLOPT_TRAILERDATA option. + +The trailing headers included in the linked list must not be CRLF-terminated, +because libcurl will add the appropriate line termination characters after +each header item. + +If you use curl_slist_append to add trailing headers to the curl_slist then +libcurl will duplicate the strings, and will free the curl_slist and the +duplicates once the trailers have been sent. + +If one of the trailing headers is not formatted correctly +(i.e. HeaderName: headerdata) it will be ignored and an info message +will be emitted. + +The return value can either be CURL_TRAILERFUNC_OK or CURL_TRAILERFUNC_ABORT +which would respectively instruct libcurl to either continue with sending the +trailers or to abort the request. + +If you set this option to NULL, then the transfer proceeds as usual +without any interruptions. + +.SH DEFAULT: +NULL + +.SH PROTOCOLS: +HTTP + +.SH EXAMPLE: +#include + +static int trailer_cb(struct curl_slist **tr, void *data) +{ + /* libcurl will free the list */ + *tr = curl_slist_append(*tr, "My-super-awesome-trailer: trailer-stuff"); + (void)data; + return CURL_TRAILERFUNC_OK; +} + +CURL *curl = curl_easy_init(); +if(curl) { + + /* Set the URL of the request */ + curl_easy_setopt(curl, CURLOPT_URL, "http://example.com/"); + /* Now set it as a put */ + curl_easy_setopt(curl, CURLOPT_PUT, 1L); + + /* Assuming we have a function that will return the data to be pushed + Let that function be read_cb */ + curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_cb); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Trailer: My-super-awsome-trailer"); + res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + /* Set the trailers filling callback */ + curl_easy_setopt(curl, CURLOPT_TRAILERFUNCTION, (curl_trailer_callback)trailer_cb); + + /* Perform the request, res will get the return code */ + res = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + + curl_slist_free_all(headers); +} + + +.SH AVAILABILITY: +This option was added in curl 7.64.0 and is present if HTTP support is enabled + +.SH "SEE ALSO" +.BR CURLOPT_TRAILERDATA "(3), " diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc index e58db5f4a..9bfd555f1 100644 --- a/docs/libcurl/opts/Makefile.inc +++ b/docs/libcurl/opts/Makefile.inc @@ -163,6 +163,8 @@ man_MANS = \ CURLOPT_HTTP_CONTENT_DECODING.3 \ CURLOPT_HTTP_TRANSFER_DECODING.3 \ CURLOPT_HTTP_VERSION.3 \ + CURLOPT_TRAILERFUNCTION.3 \ + CURLOPT_TRAILERDATA.3 \ CURLOPT_IGNORE_CONTENT_LENGTH.3 \ CURLOPT_INFILESIZE.3 \ CURLOPT_INFILESIZE_LARGE.3 \ diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 6c17c384c..8659346ce 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -431,6 +431,8 @@ CURLOPT_HTTPREQUEST 7.1 - 7.15.5 CURLOPT_HTTP_CONTENT_DECODING 7.16.2 CURLOPT_HTTP_TRANSFER_DECODING 7.16.2 CURLOPT_HTTP_VERSION 7.9.1 +CURLOPT_TRAILERFUNCTION 7.64.0 +CURLOPT_TRAILERDATA 7.64.0 CURLOPT_IGNORE_CONTENT_LENGTH 7.14.1 CURLOPT_INFILE 7.1 7.9.7 CURLOPT_INFILESIZE 7.1 @@ -851,6 +853,8 @@ CURL_PUSH_DENY 7.44.0 CURL_PUSH_OK 7.44.0 CURL_READFUNC_ABORT 7.12.1 CURL_READFUNC_PAUSE 7.18.0 +CURL_TRAILERFUNC_OK 7.64.0 +CURL_TRAILERFUNC_ABORT 7.64.0 CURL_REDIR_GET_ALL 7.19.1 CURL_REDIR_POST_301 7.19.1 CURL_REDIR_POST_302 7.19.1 diff --git a/include/curl/curl.h b/include/curl/curl.h index 3c5ce709a..bd6183838 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -355,11 +355,21 @@ typedef int (*curl_seek_callback)(void *instream, signal libcurl to pause sending data on the current transfer. */ #define CURL_READFUNC_PAUSE 0x10000001 +/* Return code for when the trailing headers' callback has terminated + without any errors*/ +#define CURL_TRAILERFUNC_OK 0 +/* Return code for when was an error in the trailing header's list and we + want to abort the request */ +#define CURL_TRAILERFUNC_ABORT 1 + typedef size_t (*curl_read_callback)(char *buffer, size_t size, size_t nitems, void *instream); +typedef int (*curl_trailer_callback)(struct curl_slist **list, + void *userdata); + typedef enum { CURLSOCKTYPE_IPCXN, /* socket created for a specific IP connection */ CURLSOCKTYPE_ACCEPT, /* socket created by accept() call */ @@ -1875,6 +1885,12 @@ typedef enum { /* Specify URL using CURL URL API. */ CINIT(CURLU, OBJECTPOINT, 282), + /* add trailing data just after no more data is available */ + CINIT(TRAILERFUNCTION, FUNCTIONPOINT, 283), + + /* pointer to be passed to HTTP_TRAILER_FUNCTION */ + CINIT(TRAILERDATA, OBJECTPOINT, 284), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/lib/http.c b/lib/http.c index 0a3e46243..07665743c 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1681,6 +1681,52 @@ enum proxy_use { HEADER_CONNECT /* sending CONNECT to a proxy */ }; +/* used to compile the provided trailers into one buffer + will return an error code if one of the headers is + not formatted correctly */ +CURLcode Curl_http_compile_trailers(struct curl_slist *trailers, + Curl_send_buffer *buffer, + struct Curl_easy *handle) +{ + char *ptr = NULL; + CURLcode result = CURLE_OK; + const char *endofline_native = NULL; + const char *endofline_network = NULL; + + /* TODO: Maybe split Curl_add_custom_headers to make it reusable here */ + + if( +#ifdef CURL_DO_LINEEND_CONV + (handle->set.prefer_ascii) || +#endif + (handle->set.crlf)) { + /* \n will become \r\n later on */ + endofline_native = "\n"; + endofline_network = "\x0a"; + } + else { + endofline_native = "\r\n"; + endofline_network = "\x0d\x0a"; + } + + while(trailers) { + /* only add correctly formatted trailers */ + ptr = strchr(trailers->data, ':'); + if(ptr && *(ptr + 1) == ' ') { + result = Curl_add_bufferf(&buffer, "%s%s", trailers->data, + endofline_native); + if(result) + return result; + } + else + infof(handle, "Malformatted trailing header ! Skipping trailer."); + trailers = trailers->next; + } + result = Curl_add_buffer(&buffer, endofline_network, + strlen(endofline_network)); + return result; +} + CURLcode Curl_add_custom_headers(struct connectdata *conn, bool is_connect, Curl_send_buffer *req_buffer) diff --git a/lib/http.h b/lib/http.h index 21fa701ab..7fa0471ad 100644 --- a/lib/http.h +++ b/lib/http.h @@ -74,6 +74,9 @@ CURLcode Curl_add_timecondition(struct Curl_easy *data, CURLcode Curl_add_custom_headers(struct connectdata *conn, bool is_connect, Curl_send_buffer *req_buffer); +CURLcode Curl_http_compile_trailers(struct curl_slist *trailers, + Curl_send_buffer *buffer, + struct Curl_easy *handle); /* protocol-specific functions set up to be called by the main engine */ CURLcode Curl_http(struct connectdata *conn, bool *done); diff --git a/lib/setopt.c b/lib/setopt.c index 01a890b39..27046768c 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -2636,6 +2636,16 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, return CURLE_BAD_FUNCTION_ARGUMENT; data->set.upkeep_interval_ms = arg; break; + case CURLOPT_TRAILERFUNCTION: +#ifndef CURL_DISABLE_HTTP + data->set.trailer_callback = va_arg(param, curl_trailer_callback); +#endif + break; + case CURLOPT_TRAILERDATA: +#ifndef CURL_DISABLE_HTTP + data->set.trailer_data = va_arg(param, void *); +#endif + break; default: /* unknown tag and its companion, just ignore: */ result = CURLE_UNKNOWN_OPTION; diff --git a/lib/transfer.c b/lib/transfer.c index 6390821bb..7483cf1f8 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -117,6 +117,35 @@ CURLcode Curl_get_upload_buffer(struct Curl_easy *data) return CURLE_OK; } +#ifndef CURL_DISABLE_HTTP +/* + * This function will be called to loop through the trailers buffer + * until no more data is available for sending. + */ +static size_t Curl_trailers_read(char *buffer, size_t size, size_t nitems, + void *raw) +{ + struct Curl_easy *data = (struct Curl_easy *)raw; + Curl_send_buffer *trailers_buf = data->state.trailers_buf; + size_t bytes_left = trailers_buf->size_used-data->state.trailers_bytes_sent; + size_t to_copy = (size*nitems < bytes_left) ? size*nitems : bytes_left; + if(to_copy) { + memcpy(buffer, + &trailers_buf->buffer[data->state.trailers_bytes_sent], + to_copy); + data->state.trailers_bytes_sent += to_copy; + } + return to_copy; +} + +static size_t Curl_trailers_left(void *raw) +{ + struct Curl_easy *data = (struct Curl_easy *)raw; + Curl_send_buffer *trailers_buf = data->state.trailers_buf; + return trailers_buf->size_used - data->state.trailers_bytes_sent; +} +#endif + /* * This function will call the read callback to fill our buffer with data * to upload. @@ -127,6 +156,17 @@ CURLcode Curl_fillreadbuffer(struct connectdata *conn, size_t bytes, struct Curl_easy *data = conn->data; size_t buffersize = bytes; size_t nread; + +#ifndef CURL_DISABLE_HTTP + struct curl_slist *trailers = NULL; + CURLcode c; + int trailers_ret_code; +#endif + + curl_read_callback readfunc = NULL; + void *extra_data = NULL; + bool added_crlf = FALSE; + #ifdef CURL_DOES_CONVERSIONS bool sending_http_headers = FALSE; @@ -140,15 +180,71 @@ CURLcode Curl_fillreadbuffer(struct connectdata *conn, size_t bytes, } #endif - if(data->req.upload_chunky) { +#ifndef CURL_DISABLE_HTTP + if(data->state.trailers_state == TRAILERS_INITIALIZED) { + /* at this point we already verified that the callback exists + so we compile and store the trailers buffer, then proceed */ + infof(data, + "Moving trailers state machine from initialized to sending.\n"); + data->state.trailers_state = TRAILERS_SENDING; + data->state.trailers_buf = Curl_add_buffer_init(); + if(!data->state.trailers_buf) { + failf(data, "Unable to allocate trailing headers buffer !"); + return CURLE_OUT_OF_MEMORY; + } + data->state.trailers_bytes_sent = 0; + Curl_set_in_callback(data, true); + trailers_ret_code = data->set.trailer_callback(&trailers, + data->set.trailer_data); + Curl_set_in_callback(data, false); + if(trailers_ret_code == CURL_TRAILERFUNC_OK) { + c = Curl_http_compile_trailers(trailers, data->state.trailers_buf, data); + } + else { + failf(data, "operation aborted by trailing headers callback"); + *nreadp = 0; + c = CURLE_ABORTED_BY_CALLBACK; + } + if(c != CURLE_OK) { + Curl_add_buffer_free(&data->state.trailers_buf); + curl_slist_free_all(trailers); + return c; + } + infof(data, "Successfully compiled trailers.\r\n"); + curl_slist_free_all(trailers); + } +#endif + + /* if we are transmitting trailing data, we don't need to write + a chunk size so we skip this */ + if(data->req.upload_chunky && + data->state.trailers_state == TRAILERS_NONE) { /* if chunked Transfer-Encoding */ buffersize -= (8 + 2 + 2); /* 32bit hex + CRLF + CRLF */ data->req.upload_fromhere += (8 + 2); /* 32bit hex + CRLF */ } +#ifndef CURL_DISABLE_HTTP + if(data->state.trailers_state == TRAILERS_SENDING) { + /* if we're here then that means that we already sent the last empty chunk + but we didn't send a final CR LF, so we sent 0 CR LF. We then start + pulling trailing data until we ²have no more at which point we + simply return to the previous point in the state machine as if + nothing happened. + */ + readfunc = Curl_trailers_read; + extra_data = (void *)data; + } + else +#endif + { + readfunc = data->state.fread_func; + extra_data = data->state.in; + } + Curl_set_in_callback(data, true); - nread = data->state.fread_func(data->req.upload_fromhere, 1, - buffersize, data->state.in); + nread = readfunc(data->req.upload_fromhere, 1, + buffersize, extra_data); Curl_set_in_callback(data, false); if(nread == CURL_READFUNC_ABORT) { @@ -203,7 +299,7 @@ CURLcode Curl_fillreadbuffer(struct connectdata *conn, size_t bytes, char hexbuffer[11]; const char *endofline_native; const char *endofline_network; - int hexlen; + int hexlen = 0; if( #ifdef CURL_DO_LINEEND_CONV @@ -218,20 +314,36 @@ CURLcode Curl_fillreadbuffer(struct connectdata *conn, size_t bytes, endofline_native = "\r\n"; endofline_network = "\x0d\x0a"; } - hexlen = msnprintf(hexbuffer, sizeof(hexbuffer), - "%x%s", nread, endofline_native); - /* move buffer pointer */ - data->req.upload_fromhere -= hexlen; - nread += hexlen; + /* if we're not handling trailing data, proceed as usual */ + if(data->state.trailers_state != TRAILERS_SENDING) { + hexlen = msnprintf(hexbuffer, sizeof(hexbuffer), + "%x%s", nread, endofline_native); - /* copy the prefix to the buffer, leaving out the NUL */ - memcpy(data->req.upload_fromhere, hexbuffer, hexlen); + /* move buffer pointer */ + data->req.upload_fromhere -= hexlen; + nread += hexlen; - /* always append ASCII CRLF to the data */ - memcpy(data->req.upload_fromhere + nread, - endofline_network, - strlen(endofline_network)); + /* copy the prefix to the buffer, leaving out the NUL */ + memcpy(data->req.upload_fromhere, hexbuffer, hexlen); + + /* always append ASCII CRLF to the data unless + we have a valid trailer callback */ +#ifndef CURL_DISABLE_HTTP + if((nread-hexlen) == 0 && + data->set.trailer_callback != NULL && + data->state.trailers_state == TRAILERS_NONE) { + data->state.trailers_state = TRAILERS_INITIALIZED; + } + else +#endif + { + memcpy(data->req.upload_fromhere + nread, + endofline_network, + strlen(endofline_network)); + added_crlf = TRUE; + } + } #ifdef CURL_DOES_CONVERSIONS { @@ -251,13 +363,29 @@ CURLcode Curl_fillreadbuffer(struct connectdata *conn, size_t bytes, } #endif /* CURL_DOES_CONVERSIONS */ - if((nread - hexlen) == 0) { - /* mark this as done once this chunk is transferred */ +#ifndef CURL_DISABLE_HTTP + if(data->state.trailers_state == TRAILERS_SENDING && + !Curl_trailers_left(data)) { + Curl_add_buffer_free(&data->state.trailers_buf); + data->state.trailers_state = TRAILERS_DONE; + data->set.trailer_data = NULL; + data->set.trailer_callback = NULL; + /* mark the transfer as done */ data->req.upload_done = TRUE; - infof(data, "Signaling end of chunked upload via terminating chunk.\n"); + infof(data, "Signaling end of chunked upload after trailers.\n"); } + else +#endif + if((nread - hexlen) == 0 && + data->state.trailers_state != TRAILERS_INITIALIZED) { + /* mark this as done once this chunk is transferred */ + data->req.upload_done = TRUE; + infof(data, + "Signaling end of chunked upload via terminating chunk.\n"); + } - nread += strlen(endofline_native); /* for the added end of line */ + if(added_crlf) + nread += strlen(endofline_network); /* for the added end of line */ } #ifdef CURL_DOES_CONVERSIONS else if((data->set.prefer_ascii) && (!sending_http_headers)) { @@ -925,7 +1053,6 @@ static CURLcode readwrite_upload(struct Curl_easy *data, *didwhat |= KEEP_SEND; do { - /* only read more data if there's no upload data already present in the upload buffer */ if(0 == k->upload_present) { @@ -950,7 +1077,6 @@ static CURLcode readwrite_upload(struct Curl_easy *data, k->keepon &= ~KEEP_SEND; /* disable writing */ k->start100 = Curl_now(); /* timeout count starts now */ *didwhat &= ~KEEP_SEND; /* we didn't write anything actually */ - /* set a timeout for the multi interface */ Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); break; diff --git a/lib/urldata.h b/lib/urldata.h index 448437d2a..8fb2d2894 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1216,6 +1216,15 @@ typedef enum { EXPIRE_LAST /* not an actual timer, used as a marker only */ } expire_id; + +typedef enum { + TRAILERS_NONE, + TRAILERS_INITIALIZED, + TRAILERS_SENDING, + TRAILERS_DONE +} trailers_state; + + /* * One instance for each timeout an easy handle can set. */ @@ -1362,6 +1371,13 @@ struct UrlState { #endif CURLU *uh; /* URL handle for the current parsed URL */ struct urlpieces up; +#ifndef CURL_DISABLE_HTTP + size_t trailers_bytes_sent; + Curl_send_buffer *trailers_buf; /* a buffer containing the compiled trailing + headers */ +#endif + trailers_state trailers_state; /* whether we are sending trailers + and what stage are we at */ }; @@ -1730,6 +1746,8 @@ struct UserDefined { multidone_func fmultidone; struct Curl_easy *dohfor; /* this is a DoH request for that transfer */ CURLU *uh; /* URL handle for the current parsed URL */ + void *trailer_data; /* pointer to pass to trailer data callback */ + curl_trailer_callback trailer_callback; /* trailing data callback */ }; struct Names { diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index 250aa2004..52916c983 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -179,6 +179,7 @@ test1550 test1551 test1552 test1553 test1554 test1555 test1556 test1557 \ test1560 test1561 \ \ test1590 \ +test1591 \ test1600 test1601 test1602 test1603 test1604 test1605 test1606 test1607 \ test1608 test1609 test1620 \ \ diff --git a/tests/data/test1591 b/tests/data/test1591 new file mode 100644 index 000000000..e864fdbaa --- /dev/null +++ b/tests/data/test1591 @@ -0,0 +1,63 @@ + + + +HTTP +HTTP PUT +CURLOPT_HTTPTRAILER_FUNCTION +CURLOPT_HTTPTRAILER_DATA + + + +# Server-side + + +HTTP/1.0 200 OK swsclose +Date: Thu, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake + + +# Client-side + + +HTTP + + +http + + +HTTP PUT with trailers at the end + + +lib1591 + + +http://%HOSTIP:%HTTPPORT/bzz/1591 + + +more than one byte + + + +# Verify data after the test has been "shot" + + +^User-Agent:.* + + +PUT /bzz/1591 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* +Transfer-Encoding: chunked +Trailer: my-super-awesome-trailer, my-other-awesome-trailer +Expect: 100-continue + +e +Hello Cloud! + +0 +my-super-awesome-trailer: trail1 +my-other-awesome-trailer: trail2 + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 080421b26..4c41fe7a1 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -31,6 +31,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \ lib1540 \ lib1550 lib1551 lib1552 lib1553 lib1554 lib1555 lib1556 lib1557 \ lib1560 \ + lib1591 \ lib1900 \ lib2033 @@ -518,6 +519,10 @@ lib1557_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1557 lib1560_SOURCES = lib1560.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib1560_LDADD = $(TESTUTIL_LIBS) +lib1591_SOURCES = lib1591.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) +lib1591_LDADD = $(TESTUTIL_LIBS) +lib1591_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1591 + lib1900_SOURCES = lib1900.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib1900_LDADD = $(TESTUTIL_LIBS) lib1900_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/tests/libtest/lib1591.c b/tests/libtest/lib1591.c new file mode 100644 index 000000000..7aba22337 --- /dev/null +++ b/tests/libtest/lib1591.c @@ -0,0 +1,106 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2018, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* + * This unit test PUT http data over proxy. Proxy header will be different + * from server http header + */ + +#include "test.h" +#include +#include "memdebug.h" + +static char data [] = "Hello Cloud!\r\n"; +static size_t consumed = 0; + +static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream) +{ + size_t amount = nmemb * size; /* Total bytes curl wants */ + + if(consumed == strlen(data)) { + return 0; + } + + if(amount > strlen(data)-consumed) { + amount = strlen(data); + } + + consumed += amount; + (void)stream; + memcpy(ptr, data, amount); + return amount; +} + +static int trailers_callback(struct curl_slist **list, void *userdata) +{ + (void)userdata; + *list = curl_slist_append(*list, "my-super-awesome-trailer: trail1"); + *list = curl_slist_append(*list, "my-other-awesome-trailer: trail2"); + return CURL_TRAILERFUNC_OK; +} + +int test(char *URL) +{ + CURL *curl = NULL; + CURLcode res = CURLE_FAILED_INIT; + /* http and proxy header list*/ + struct curl_slist *hhl = NULL; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + fprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + + curl = curl_easy_init(); + if(!curl) { + fprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + hhl = curl_slist_append(hhl, "Trailer: my-super-awesome-trailer," + " my-other-awesome-trailer"); + if(!hhl) { + goto test_cleanup; + } + + test_setopt(curl, CURLOPT_URL, URL); + test_setopt(curl, CURLOPT_HTTPHEADER, hhl); + test_setopt(curl, CURLOPT_PUT, 1L); + test_setopt(curl, CURLOPT_READFUNCTION, read_callback); + test_setopt(curl, CURLOPT_TRAILERFUNCTION, trailers_callback); + test_setopt(curl, CURLOPT_TRAILERDATA, NULL); + + res = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + + curl_slist_free_all(hhl); + + curl_global_cleanup(); + + return (int)res; +} + diff --git a/tests/libtest/mk-lib1521.pl b/tests/libtest/mk-lib1521.pl index 9771cf57f..fb1401a53 100755 --- a/tests/libtest/mk-lib1521.pl +++ b/tests/libtest/mk-lib1521.pl @@ -121,6 +121,7 @@ static int geterr(const char *name, CURLcode val, int lineno) static curl_progress_callback progresscb; static curl_write_callback headercb; static curl_debug_callback debugcb; +static curl_trailer_callback trailercb; static curl_ssl_ctx_callback ssl_ctx_cb; static curl_ioctl_callback ioctlcb; static curl_sockopt_callback sockoptcb; diff --git a/tests/server/sws.c b/tests/server/sws.c index cf3d291d9..87c0204c9 100644 --- a/tests/server/sws.c +++ b/tests/server/sws.c @@ -253,6 +253,9 @@ SIG_ATOMIC_T got_exit_signal = 0; static volatile int exit_signal = 0; +/* work around for handling trailing headers */ +static int already_recv_zeroed_chunk = FALSE; + /* signal handler that will be triggered to indicate that the program should finish its execution in a controlled manner as soon as possible. The first time this is called it will set got_exit_signal to one and @@ -755,10 +758,27 @@ static int ProcessRequest(struct httprequest *req) chunked = TRUE; } + if(chunked) { - if(strstr(req->reqbuf, "\r\n0\r\n\r\n")) + if(strstr(req->reqbuf, "\r\n0\r\n\r\n")) { /* end of chunks reached */ return 1; /* done */ + } + else if(strstr(req->reqbuf, "\r\n0\r\n")) { + char *last_crlf_char = strstr(req->reqbuf, "\r\n\r\n"); + while(TRUE) { + if(!strstr(last_crlf_char + 4, "\r\n\r\n")) + break; + last_crlf_char = strstr(last_crlf_char + 4, "\r\n\r\n"); + } + if(last_crlf_char && + last_crlf_char > strstr(req->reqbuf, "\r\n0\r\n")) + return 1; + already_recv_zeroed_chunk = TRUE; + return 0; + } + else if(already_recv_zeroed_chunk && strstr(req->reqbuf, "\r\n\r\n")) + return 1; else return 0; /* not done */ }