From 096f9a4fbdaa3e2df477dc12a5619839aa0253de Mon Sep 17 00:00:00 2001
From: Chrysovaladis Datsios <cdatsios@gmail.com>
Date: Thu, 28 Feb 2013 10:22:37 +0200
Subject: [PATCH] CURLOPT_TRAILERFUNCTION: support chunked-encoding request
 trailers

curl_easy_setopt() option: CURLOPT_TRAILERFUNCTION

Pass a pointer to a function that matches the following prototype: 
struct curl_slist* function(CURL *handle, void *userdata); This function 
gets called by libcurl when it is to send the last chunk of 
(zero payload)data to the peer. Chunked transfer-encoding must be used. 
The trailer header names have to be set in advance as custom headers using 
the CURLOPT_HTTPHEADER option with "Trailer" as "header name_field" and 
the actual header name as "header value_field". Inside the callback function 
a linked list of type struct curl_slist that will contain the trailer headers 
have to be created using the curl_slist_append(3) function. The callback 
function returns a pointer to the trailer_headers list. The user also can 
pass in a custom pointer to the callback, this is done by the associated 
CURLOPT_TRAILERDATA option.

curl_easy_setopt() option: CURLOPT_TRAILERDATA

Data pointer to pass to the trailer function. 
If you use the CURLOPT_TRAILERFUNCTION option, this is the pointer 
you'll get as input.

---
 include/curl/curl.h          |   11 +++++++-
 include/curl/typecheck-gcc.h |   15 ++++++++++
 lib/transfer.c               |   63 ++++++++++++++++++++++++++++++++++++++++--
 lib/url.c                    |   15 ++++++++++
 lib/urldata.h                |    3 ++
 5 files changed, 104 insertions(+), 3 deletions(-)

diff --git a/include/curl/curl.h b/include/curl/curl.h
index 5b39a24..71d1076 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -308,6 +308,10 @@ typedef size_t (*curl_read_callback)(char *buffer,
                                       size_t nitems,
                                       void *instream);
 
+/* pointer to function curl_trailerheaders_callback */
+typedef struct curl_slist * (*curl_trailerheaders_callback)(CURL *handle,
+                                      void *userdata);
+
 typedef enum  {
   CURLSOCKTYPE_IPCXN,  /* socket created for a specific IP connection */
   CURLSOCKTYPE_ACCEPT, /* socket created by accept() call */
@@ -1536,6 +1540,11 @@ typedef enum {
   /* set the SMTP auth originator */
   CINIT(MAIL_AUTH, OBJECTPOINT, 217),
 
+  /* Function that will be called to set the final values to trailer headers */
+  CINIT(TRAILERFUNCTION, FUNCTIONPOINT, 218),
+  /* Data passed to the trailer headers callback function */
+  CINIT(TRAILERDATA, OBJECTPOINT, 219),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
diff --git a/include/curl/typecheck-gcc.h b/include/curl/typecheck-gcc.h
index f8917e8..5259c69 100644
--- a/include/curl/typecheck-gcc.h
+++ b/include/curl/typecheck-gcc.h
@@ -57,6 +57,9 @@ __extension__ ({                                                              \
     if((_curl_opt) == CURLOPT_READFUNCTION)                                   \
       if(!_curl_is_read_cb(value))                                            \
         _curl_easy_setopt_err_read_cb();                                      \
+    if((_curl_opt) == CURLOPT_TRAILERFUNCTION)                                \
+      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_WARNING(_curl_easy_setopt_err_write_callback,
   "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,
@@ -294,6 +300,7 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist,
    (option) == CURLOPT_INTERLEAVEDATA ||                                      \
    (option) == CURLOPT_CHUNK_DATA ||                                          \
    (option) == CURLOPT_FNMATCH_DATA ||                                        \
+   (option) == CURLOPT_TRAILERDATA ||                                         \
    0)
 
 /* evaluates to true if option takes a POST data argument (void* or char*) */
@@ -426,6 +433,14 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist,
   (__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) ||         \
+   _curl_callback_compatible(expr, _curl_trailerheaders_callback2))
+typedef struct curl_slist * (_curl_trailerheaders_callback1)(CURL *, void*);
+typedef struct curl_slist * (_curl_trailerheaders_callback2)(CURL *,
+                                                               const void*);
+
 /* evaluates to true if expr is of type curl_read_callback or "similar" */
 #define _curl_is_read_cb(expr)                                          \
   (_curl_is_NULL(expr) ||                                                     \
diff --git a/lib/transfer.c b/lib/transfer.c
index 51b2f77..2cc0730 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -104,6 +104,7 @@ CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp)
   struct SessionHandle *data = conn->data;
   size_t buffersize = (size_t)bytes;
   int nread;
+  int trlen = 0;
 #ifdef CURL_DOES_CONVERSIONS
   bool sending_http_headers = FALSE;
 
@@ -193,8 +194,66 @@ CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp)
     /* copy the prefix to the buffer, leaving out the NUL */
     memcpy(data->req.upload_fromhere, hexbuffer, hexlen);
 
+    /* If is the last chunk, here put the trailer headers if any */
+    if((nread - hexlen) == 0) {
+      if(data->set.trailerheaders_func) {
+        struct curl_slist *trailer_headers;
+        struct curl_slist *list_head;
+
+        /* The callback function that adds trailer header values */
+        trailer_headers = (data->set.trailerheaders_func)(data,
+                                              data->set.trailer_udata);
+        list_head = trailer_headers;
+        if(trailer_headers) {
+          char *ptr;
+          size_t headers_buf_size;
+          size_t endl_len;
+          size_t data_len;
+          size_t headers_index;
+          char *trailer_headers_buf;
+
+          endl_len = strlen(endofline_network);
+          headers_buf_size = HEADERSIZE;
+          trailer_headers_buf = malloc(headers_buf_size);
+          if(trailer_headers_buf == NULL)
+            return CURLE_OUT_OF_MEMORY;
+          memcpy(trailer_headers_buf, hexbuffer, hexlen);
+          headers_index = hexlen;
+
+          while(trailer_headers) {
+            /* header name_field : header value_field */
+            ptr = strchr(trailer_headers->data, ':');
+            if(ptr) {
+              data_len = strlen(trailer_headers->data);
+              if(headers_index + data_len + 3*endl_len > headers_buf_size) {
+                char *newbuf;
+                size_t newsize = headers_index + data_len + 3*endl_len;
+                if(newsize > CURL_MAX_HTTP_HEADER)
+                  return CURLE_OUT_OF_MEMORY;
+                newbuf = realloc(trailer_headers_buf, newsize);
+                if(newbuf == NULL)
+                  return CURLE_OUT_OF_MEMORY;
+                headers_buf_size = newsize;
+                trailer_headers_buf = newbuf;
+              }
+              memcpy(trailer_headers_buf + headers_index,
+                                             trailer_headers->data, data_len);
+              headers_index += data_len;
+              memcpy(trailer_headers_buf + headers_index,
+                                                 endofline_network, endl_len);
+              headers_index += endl_len;
+            }
+            trailer_headers = trailer_headers->next;
+          }
+          trlen = (int)(headers_index - hexlen);
+          data->req.upload_fromhere = trailer_headers_buf;
+          curl_slist_free_all(list_head);
+        }
+      }
+    }
+
     /* always append ASCII CRLF to the data */
-    memcpy(data->req.upload_fromhere + nread,
+    memcpy(data->req.upload_fromhere + nread + trlen,
            endofline_network,
            strlen(endofline_network));
 
@@ -231,7 +290,7 @@ CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp)
   }
 #endif /* CURL_DOES_CONVERSIONS */
 
-  *nreadp = nread;
+  *nreadp = nread + trlen;
 
   return CURLE_OK;
 }
diff --git a/lib/url.c b/lib/url.c
index 601d8d3..d412a9a 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -1261,6 +1261,21 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
     data->set.headers = va_arg(param, struct curl_slist *);
     break;
 
+  case CURLOPT_TRAILERFUNCTION:
+    /*
+     * Set final values of trailer headers callback
+     */
+    data->set.trailerheaders_func = va_arg(param,
+                                     curl_trailerheaders_callback);
+    break;
+
+  case CURLOPT_TRAILERDATA:
+    /*
+     * Custom data to pass to the trailer headers callback
+     */
+    data->set.trailer_udata = va_arg(param, void *);
+    break;
+
   case CURLOPT_HTTP200ALIASES:
     /*
      * Set a list of aliases for HTTP 200 in response header
diff --git a/lib/urldata.h b/lib/urldata.h
index 4116c34..caaa801 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1429,6 +1429,9 @@ struct UserDefined {
   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 */
+  void* trailer_udata; /* pointer, pass user data to trailerheaders callback */
   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 */
-- 
1.7.9.5

