cURL / Mailing Lists / curl-library / Single Mail

curl-library

Re: [PATCH] Addition of trailer headers in HTTP requests generated by libcurl

From: Chrysovaladis Datsios <cdatsios_at_gmail.com>
Date: Thu, 24 Jan 2013 12:03:51 +0200

>> I welcome this improvement but I agree with Dan Fandrich that it would be
>> more useful if implemented as a callback. My use cases for request trailers
>> involve signing operations on the message body where it's expensive to
>> buffer the whole request before starting to send the body… especially as
>> parsing the trailer would be optional.
>
>
> I too agree with the idea of having this as a callback instead to allow
> applications to provide the data during or even post transfer. Seems to
> match the protocol idea better!
>
Hi this is a revised version of the patch using a callback function.

+-----------------------+
| documentation |
+-----------------------+

**** curl_easy_setopt() option: CURLOPT_HTTPTRAILERHEADER

Pass a pointer to a linked list of HTTP trailer headers to pass at the
end of the HTTP request. The HTTP request should be using chunked
Transfer-Encoding. The linked list of HTTP trailer headers should be a
fully valid list of struct curl_slist structs properly filled in. Use
curl_slist_append(3) to create the list and curl_slist_free_all(3) to
clean up an entire list.
The option is set before the curl_easy_perfom(1). The linked list of
HTTP trailer headers contains the initial values of those headers. The
initial values are not the values that will be sent finally at the end
of the transfer. The final values of the HTTP trailer headers can be
known or calculated after the transfer has been performed. For this, a
new callback function assigned in option CURLOPT_HTTPTRAILERFUNCTION
has to be defined and it will be called at the end of the transfer.

**** curl_easy_setopt() option: CURLOPT_HTTPTRAILERFUNCTION

Pass a pointer to a function that matches the following prototype: int
function(struct curl_slist *trailer_headers); This function gets
called by libcurl when it is to send the last chunk of (zero
payload)data to the peer. Chunked transfer-encoding it is assumed. The
pointer to the trailer_headers points to the linked list of trailer
headers whose values are to take their final values. The new function
curl_slist_replace(3) can be called from inside the callback function
to alter the initial values to the real ones.

**** new function: curl_slist_replace - replace an old string in an
slist with a new string
        struct curl_slist *curl_slist_replace(struct curl_slist *list, const
char *old_data, const char *new_data);
        
Function curl_slist_replace(3) replaces an old string in the linked
list with an new string. It returns the the address of the first
record in the list. If no list exists NULL is returned.
        
+----------------------------------+
| code example: |
| PUT request with trailer header |
+----------------------------------+
#include <stdio.h>
#include <curl/curl.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
  CURL *curl;
  CURLcode res;
  int filecode;
  FILE *fd;
  struct curl_slist *custom_http_hdrs=NULL;
  struct curl_slist *trailer_http_hdrs=NULL;

  fd = fopen("video_0001", "rb"); /* open file to upload */
  if(!fd) {

    return 1; /* can't continue */
  }

  /* Read callback function */
  size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream) {
    size_t retcode;

    retcode = fread(ptr, size, nmemb, stream);
    if(!retcode) {
      if(ferror(stream))
        filecode = 1;
      if(feof(stream))
        filecode = 0;
    }
    return retcode;
  }

  /* trailer header altering callback function */
  int trailerheader_callback(struct curl_slist *trailer_headers) {
    int *fcode;

    if(!filecode)
      trailer_headers = curl_slist_replace(trailer_headers,
"mytrailer: default", "mytrailer: EOF");
    else
      trailer_headers = curl_slist_replace(trailer_headers,
"mytrailer: default", "mytrailer: error");
    return 0;
   }

  curl = curl_easy_init();
  if(curl) {

    curl_easy_setopt(curl, CURLOPT_URL, "http://10.8.60.209/myfile");

    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    curl_easy_setopt(curl, CURLOPT_READDATA, fd);
    curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);

    custom_http_hdrs = curl_slist_append(custom_http_hdrs,
                                        "Transfer-Encoding: chunked");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, custom_http_hdrs);

    /* gives an initial value at the trailer header */
    trailer_http_hdrs = curl_slist_append(trailer_http_hdrs,
"mytrailer: default");
    curl_easy_setopt(curl, CURLOPT_HTTPTRAILERHEADER, trailer_http_hdrs);

    /* Initial value will be altered from inside
trailerheader_callback() function */
    curl_easy_setopt(curl, CURLOPT_HTTPTRAILERFUNCTION, trailerheader_callback);

    res = curl_easy_perform(curl);

    if(res != CURLE_OK) {
      fprintf(stderr, "curl_easy_perform() failed: %s\n",
              curl_easy_strerror(res));

    }
    fclose(fd);
    curl_slist_free_all(custom_http_hdrs);
    curl_slist_free_all(trailer_http_hdrs);
    curl_easy_cleanup(curl);
  }
  return 0;
}

+------------------------------------------------------------+
| code diff: |
| This are the diffs from the original library: curl-7.28.1 |
+------------------------------------------------------------+

diff -ur curl_orig/include/curl/curl.h curl_new/include/curl/curl.h
--- curl_orig/include/curl/curl.h 2012-09-26 12:46:15.000000000 +0300
+++ curl_new/include/curl/curl.h 2013-01-24 11:41:37.769760828 +0200
@@ -308,6 +308,10 @@
                                       size_t nitems,
                                       void *instream);

+/* pointer to function curl_trailerheaders_callback */
+typedef int (*curl_trailerheaders_callback)(
+ struct curl_slist *trailer_headers);
+
 typedef enum {
   CURLSOCKTYPE_IPCXN, /* socket created for a specific IP connection */
   CURLSOCKTYPE_ACCEPT, /* socket created by accept() call */
@@ -1536,6 +1540,12 @@
   /* set the SMTP auth originator */
   CINIT(MAIL_AUTH, OBJECTPOINT, 217),

+ /* points to a linked list of trailer headers, struct curl_slist kind */
+ CINIT(HTTPTRAILERHEADER, OBJECTPOINT, 218),
+
+ /* Function that will be called to set the final values to trailer headers */
+ CINIT(HTTPTRAILERFUNCTION, FUNCTIONPOINT, 219),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;

@@ -1918,6 +1928,17 @@
                                                  const char *);

 /*
+ * NAME curl_slist_replace()
+ *
+ * DESCRIPTION
+ *
+ * Replaces an old string in the linked list with a new string.
+ * Returns the address of the first record in the list.
+ * If no list exists NULL is returned.
+ */
+CURL_EXTERN struct curl_slist *curl_slist_replace(struct curl_slist *,
+ const char *, const char *);
+/*
  * NAME curl_slist_free_all()
  *
  * DESCRIPTION
diff -ur curl_orig/include/curl/typecheck-gcc.h
curl_new/include/curl/typecheck-gcc.h
--- curl_orig/include/curl/typecheck-gcc.h 2012-04-25 18:29:20.000000000 +0300
+++ curl_new/include/curl/typecheck-gcc.h 2013-01-24 11:43:05.948720822 +0200
@@ -57,6 +57,9 @@
     if((_curl_opt) == CURLOPT_READFUNCTION) \
       if(!_curl_is_read_cb(value)) \
         _curl_easy_setopt_err_read_cb(); \
+ if((_curl_opt) == CURLOPT_HTTPTRAILERFUNCTION) \
+ if(!_curl_is_trailerheaders_cb(value)) \
+ _curl_easy_setopt_err_trailerheaders_cb(); \
     if((_curl_opt) == CURLOPT_IOCTLFUNCTION) \
       if(!_curl_is_ioctl_cb(value)) \
         _curl_easy_setopt_err_ioctl_cb(); \
@@ -157,6 +160,9 @@
   "curl_easy_setopt expects a curl_write_callback argument for this option")
 _CURL_WARNING(_curl_easy_setopt_err_read_cb,
   "curl_easy_setopt expects a curl_read_callback argument for this option")
+_CURL_WARNING(_curl_easy_setopt_err_trailerheaders_cb,
+ "curl_easy_setopt expects a "
+ "curl_trailerheaders_callback argument for this option")
 _CURL_WARNING(_curl_easy_setopt_err_ioctl_cb,
   "curl_easy_setopt expects a curl_ioctl_callback argument for this option")
 _CURL_WARNING(_curl_easy_setopt_err_sockopt_cb,
@@ -311,6 +317,7 @@
    (option) == CURLOPT_PREQUOTE || \
    (option) == CURLOPT_TELNETOPTIONS || \
    (option) == CURLOPT_MAIL_RCPT || \
+ (option) == CURLOPT_HTTPTRAILERHEADER || \
    0)

 /* groups of curl_easy_getinfo infos that take the same type of argument */
@@ -426,6 +433,11 @@
   (__builtin_types_compatible_p(__typeof__(func), type) || \
    __builtin_types_compatible_p(__typeof__(func), type*))

+/* evaluates to true if expr is of type curl_trailerheaders_callback */
+#define _curl_is_trailerheaders_cb(expr) \
+ (_curl_callback_compatible(expr, _curl_trailerheaders_callback1))
+typedef int (_curl_trailerheaders_callback1)(struct curl_slist *);
+
 /* evaluates to true if expr is of type curl_read_callback or "similar" */
 #define _curl_is_read_cb(expr) \
   (_curl_is_NULL(expr) || \
diff -ur curl_orig/lib/http.c curl_new/lib/http.c
--- curl_orig/lib/http.c 2012-11-13 23:02:16.000000000 +0200
+++ curl_new/lib/http.c 2013-01-22 14:18:42.404391364 +0200
@@ -1590,6 +1590,34 @@
     }
     headers = headers->next;
   }
+
+ char *tptr;
+ struct curl_slist *trailer_headers=conn->data->set.trailer_headers;
+
+ while(trailer_headers) {
+ ptr = strchr(trailer_headers->data, ':');
+ if(ptr) {
+ tptr = --ptr; /* the point where the trailer header field ends */
+ ptr++; /* pass the colon */
+ while(*ptr && ISSPACE(*ptr))
+ ptr++;
+
+ if(*ptr) {
+ /* only send this if the contents was non-blank */
+
+ char *tfield = (char *)malloc(strlen(trailer_headers->data)+1);
+ strncpy(tfield, trailer_headers->data, tptr-trailer_headers->data+1);
+ tfield[tptr-trailer_headers->data+1] = '\0';
+ CURLcode result = Curl_add_bufferf(req_buffer, "Trailer: %s\r\n",
+ tfield);
+ tptr = NULL;
+ if(result)
+ return result;
+ }
+ }
+ trailer_headers = trailer_headers->next;
+ }
+
   return CURLE_OK;
 }

diff -ur curl_orig/lib/slist.c curl_new/lib/slist.c
--- curl_orig/lib/slist.c 2012-03-08 21:35:25.000000000 +0200
+++ curl_new/lib/slist.c 2013-01-18 14:48:07.000000000 +0200
@@ -84,6 +84,39 @@
 }

 /*
+ * curl_slist_replace() replaces an old string in the linked list
+ * with an new string. It returns the the address of the first
+ * record in the list. If no list exists NULL is returned.
+ */
+struct curl_slist *curl_slist_replace(struct curl_slist *list,
+ const char *old_data, const char *new_data)
+{
+ struct curl_slist *item;
+ int found = 0;
+
+ if(!list)
+ return NULL;
+
+ item = list;
+
+ while(item) {
+ if(!strcmp(item->data, old_data)) {
+ found = 1;
+ break;
+ }
+ item = item->next;
+ }
+
+ if(found) {
+ char *dupdata = strdup(new_data);
+ if(dupdata)
+ item->data = dupdata;
+ }
+ return list;
+
+}
+
+/*
  * Curl_slist_duplicate() duplicates a linked list. It always returns the
  * address of the first record of the cloned list or NULL in case of an
  * error (or if the input list was NULL).
diff -ur curl_orig/lib/transfer.c curl_new/lib/transfer.c
--- curl_orig/lib/transfer.c 2012-11-13 23:02:16.000000000 +0200
+++ curl_new/lib/transfer.c 2013-01-24 11:43:56.053131238 +0200
@@ -929,6 +929,41 @@
          that instead of reading more data */
     }

+ /* The last chunk has zero size of data i.e. 0\r\n
+ * If this is the last chunk and trailer headers do exist*/
+ if(k->upload_chunky == true && data->req.upload_present == 5 &&
+ !strncmp(data->req.upload_fromhere, "0\r\n", 3) &&
+ conn->data->set.trailer_headers ) {
+
+ /* There is set a calback function that alters trailer header values,
+ * now its time to be called. */
+ if(conn->data->set.is_trailerheaders_set)
+ conn->data->set.trailerheaders_func(conn->data->set.trailer_headers);
+
+ Curl_send_buffer *trailer_buffer = Curl_add_buffer_init();
+ result = Curl_add_bufferf(trailer_buffer, "0\r\n");
+ if(result)
+ return result;
+
+ char *ptr;
+ struct curl_slist *trailer_headers=conn->data->set.trailer_headers;
+ while(trailer_headers) {
+ ptr = strchr(trailer_headers->data, ':');
+ if(ptr) {
+ result = Curl_add_bufferf(trailer_buffer, "%s\r\n",
+ trailer_headers->data);
+ if(result)
+ return result;
+ }
+ trailer_headers = trailer_headers->next;
+ }
+ result = Curl_add_bufferf(trailer_buffer, "\r\n");
+ if(result)
+ return result;
+ data->req.upload_fromhere = trailer_buffer->buffer;
+ data->req.upload_present = trailer_buffer->size_used;
+ }
+
     /* write to socket (send away data) */
     result = Curl_write(conn,
                         conn->writesockfd, /* socket to send to */
diff -ur curl_orig/lib/url.c curl_new/lib/url.c
--- curl_orig/lib/url.c 2012-11-18 16:08:45.000000000 +0200
+++ curl_new/lib/url.c 2013-01-24 11:43:35.138942035 +0200
@@ -1261,6 +1261,25 @@
     data->set.headers = va_arg(param, struct curl_slist *);
     break;

+ case CURLOPT_HTTPTRAILERHEADER:
+ /*
+ * Set a list with HTTP trailer headers to use
+ */
+ data->set.trailer_headers = va_arg(param, struct curl_slist *);
+ break;
+
+ case CURLOPT_HTTPTRAILERFUNCTION:
+ /*
+ * Set final values of trailer headers callback
+ */
+ data->set.trailerheaders_func = va_arg(param,
+ curl_trailerheaders_callback);
+ if(!data->set.trailerheaders_func)
+ data->set.is_trailerheaders_set = 0;
+ else
+ data->set.is_trailerheaders_set = 1;
+ break;
+
   case CURLOPT_HTTP200ALIASES:
     /*
      * Set a list of aliases for HTTP 200 in response header
diff -ur curl_orig/lib/urldata.h curl_new/lib/urldata.h
--- curl_orig/lib/urldata.h 2012-11-13 23:02:16.000000000 +0200
+++ curl_new/lib/urldata.h 2013-01-24 11:43:45.167828018 +0200
@@ -1429,6 +1429,10 @@
   curl_read_callback fread_func; /* function that reads the input */
   int is_fread_set; /* boolean, has read callback been set to non-NULL? */
   int is_fwrite_set; /* boolean, has write callback been set to non-NULL? */
+ curl_trailerheaders_callback trailerheaders_func; /* function that sets
+ the final values at trailer headers */
+ int is_trailerheaders_set; /* boolean, has trailerheaders callback
+ set to non-NULL? */
   curl_progress_callback fprogress; /* function for progress information */
   curl_debug_callback fdebug; /* function that write informational data */
   curl_ioctl_callback ioctl_func; /* function for I/O control */
@@ -1466,6 +1470,7 @@
                                 download */
   curl_off_t set_resume_from; /* continue [ftp] transfer from here */
   struct curl_slist *headers; /* linked list of extra headers */
+ struct curl_slist *trailer_headers; /* linked list of trailer headers */
   struct curl_httppost *httppost; /* linked list of POST data */
   bool cookiesession; /* new cookie session? */
   bool crlf; /* convert crlf on ftp upload(?) */
diff -ur curl_orig/src/tool_cfgable.h curl_new/src/tool_cfgable.h
--- curl_orig/src/tool_cfgable.h 2012-08-08 23:45:18.000000000 +0300
+++ curl_new/src/tool_cfgable.h 2013-01-22 14:17:35.861227926 +0200
@@ -150,6 +150,7 @@
   curl_TimeCond timecond;
   time_t condtime;
   struct curl_slist *headers;
+ struct curl_slist *trailer_headers;
   struct curl_httppost *httppost;
   struct curl_httppost *last_post;
   struct curl_slist *telnet_options;
diff -ur curl_orig/src/tool_operate.c curl_new/src/tool_operate.c
--- curl_orig/src/tool_operate.c 2012-11-13 23:02:16.000000000 +0200
+++ curl_new/src/tool_operate.c 2013-01-24 11:55:22.067351183 +0200
@@ -978,6 +978,8 @@
           my_setopt(curl, CURLOPT_AUTOREFERER, config->autoreferer);
           my_setopt_str(curl, CURLOPT_USERAGENT, config->useragent);
           my_setopt_slist(curl, CURLOPT_HTTPHEADER, config->headers);
+ my_setopt_slist(curl, CURLOPT_HTTPTRAILERHEADER,
+ config->trailer_headers);

           /* new in libcurl 7.5 */
           my_setopt(curl, CURLOPT_MAXREDIRS, config->maxredirs);

-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette: http://curl.haxx.se/mail/etiquette.html
Received on 2013-01-24