// Matti Kariluoma Oct 14 2010
// <matti.kariluoma@gmail.com>
//
// gcc curl_post_gzip.c -lcurl -lz -o curl_post_gzip
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <curl/curl.h>
#include <zlib.h>

#ifndef OS_CODE
#  define OS_CODE  0x03  /* assume Unix */
#endif

#ifndef TRUE
#  define TRUE  1
#endif

#ifndef FALSE
#  define FALSE 0
#endif

#if MAX_MEM_LEVEL >= 8
#  define DEF_MEM_LEVEL 8
#else
#  define DEF_MEM_LEVEL  MAX_MEM_LEVEL
#endif

static int gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */

// http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTWRITEFUNCTION
//
// ptr      : a pointer to a set of memory (size * nmemb) large
// size     :
// nmemb    :
// userdata : an optional address pointer setup with the following call:
//              curl_easy_setopt(curl, CURLOPT_WRITEDATA, &dataAddress);
//
// this function must return the amount of data in ptr that was handled
size_t string_gunzip( void *ptr, size_t size, size_t nmemb, void *userdata)
{
  size_t processed = 0;
  
  char *ptr_use = (char*)ptr;
  puts((char*)ptr);
  
  while(ptr_use - (char*)ptr < nmemb*size)
  {
    //printf("%c\n", ptr_use[0]);
    ++ptr_use;
    processed += size;
  }
  //printf("\n%d\n", processed);
  
  return processed;
}
 
// see zlib's compress.c:compress() function for the original of this
// modified function
//
// dest      : destination buffer, malloc'd already
// destLen   : size of the malloc'd buffer
// source    : the uncompressed text
// sourceLen : the size of the uncompressed text
//
// this function returns an error code from the zlib library.
// upon return, dest contains the compressed output, and destLen 
// contains the size of dest.
int string_gzip (char *dest, unsigned long *destLen, char *source, unsigned long sourceLen)
{
  char *header;
  
  sprintf(dest, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1],
          Z_DEFLATED, 0       , 0,0,0,0      , 0        , OS_CODE);
        //          , flags   , time         , xflags   ,        );
  
  header = dest;
  dest = &dest[10]; // skip ahead of the header
  *destLen -= 10; // update our available length
  
  z_stream stream;
  int err;

  stream.next_in = source;
  stream.avail_in = sourceLen;
  #ifdef MAXSEG_64K
    /* Check for source > 64K on 16-bit machine: */
    if (stream.avail_in != sourceLen)
      return Z_BUF_ERROR;
  #endif
  stream.next_out = dest;
  stream.avail_out = *destLen;
  if (stream.avail_out != *destLen)
    return Z_BUF_ERROR;

  stream.zalloc = Z_NULL;
  stream.zfree = Z_NULL;
  stream.opaque = Z_NULL;

  // instructs zlib not to write a zlib header
  err = deflateInit2( &stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 
                      -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY );
  if (err != Z_OK)
    return err;

  err = deflate(&stream, Z_FINISH); // Z_FINISH or Z_NO_FLUSH if we have
                                    // more input still (we don't)
  if (err != Z_STREAM_END) {
      deflateEnd(&stream);
      return err == Z_OK ? Z_BUF_ERROR : err;
  }
  *destLen = stream.total_out;

  err = deflateEnd(&stream);
  
  dest = header; // put the header back on
  *destLen += 10; // update length of our data block
  
  return err;
}

 
int main(int argc, char **argv)
{
  int compressed = TRUE;
  int verbose = TRUE;
  const char *host = "127.0.0.1:8080/xmlrpc";
  const char *data =
"<?xml version=\"1.0\"?>\n"
"<methodCall>\n"
"<methodName>Login.add</methodName>\n"
"<params>\n"
"  <param>\n"
"  <value>\n"
"   <int>2</int>\n"
"  </value>\n"
" </param>\n"
"  <param>\n"
"  <value>\n"
"   <int>4</int>\n"
"  </value>\n"
" </param>\n"
"</params>\n"
"</methodCall>\n";
  
  //puts(data);
  unsigned long dataLength = strlen(data);
  unsigned long compressedDataLength = sizeof(char)*dataLength*1.1 + 22;
  char *compressedData = calloc(compressedDataLength,1);
  
  if (compressed)
  {
    int returnCode = string_gzip(compressedData, &compressedDataLength, (char*)data, dataLength);
    
    if(returnCode != Z_OK)
    {
      switch(returnCode)
      {
        case Z_MEM_ERROR:
          fprintf(stderr, "zlib: Error allocating memory.\n");
          break;
        case Z_DATA_ERROR:
          fprintf(stderr, "zlib: Error, deflate data is invalid or incomplete.");
          break;
        case Z_VERSION_ERROR:
          fprintf(stderr, "zlib: Error, version mismatch between \"zlib.h\" and \"libz.so\".\n");
          break;
        case Z_ERRNO:
          fprintf(stderr, "zlib: Error reading/writing to files.\n");
          break;
        default:
          fprintf(stderr, "zlib: Error, unspecified.\n");
      }
    puts("Abandoning attempt to compress xml-rpc request. Making plain-text xml-rpc request...");
    if(compressedData)
      free(compressedData);
    compressedData = (char*)data;
    compressedDataLength = dataLength;
    compressed = FALSE;
    }
  }
  else
  {
    if(compressedData)
      free(compressedData);
    compressedData = (char*)data;
    compressedDataLength = dataLength;
  }
  
  CURL *curl;
  CURLcode res;
  struct curl_slist *header_list=NULL;
  char contentLengthBuf[32];
  
  curl = curl_easy_init();
  if(curl) 
  {
    // First set the URL that is about to receive our POST. This URL can
    // just as well be a https:// URL if that is what should receive the
    // data.  
    curl_easy_setopt(curl, CURLOPT_URL, host);
    
    // set the "Accept-Encoding: " header
    curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");
    
    // set the "Content-Encoding: ", "Content-Length: ", "Content-Type: " headers
    if (compressed)
      header_list = curl_slist_append(header_list, "Content-Encoding: gzip");
    header_list = curl_slist_append(header_list, "Content-Type: text/xml");
    sprintf(contentLengthBuf, "Content-Length: %d", compressedDataLength);
    header_list = curl_slist_append(header_list, contentLengthBuf);
    
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);

    // Now specify the POST data 
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, compressedData);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, compressedDataLength);
 
    if (verbose)
      // increase verbosity
      curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
    else
      // reduce verbosity
      curl_easy_setopt(curl, CURLOPT_VERBOSE, 0);
    
    // our callback to pipe output to
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &string_gunzip);
    
    // Perform the request, res will get the return code
    res = curl_easy_perform(curl);
    
    // always cleanup
    curl_slist_free_all(header_list);
    curl_easy_cleanup(curl);
  }
  
  if(compressedData && compressed)
    free(compressedData);
    
  return 0;
}

  /*
  int returnCode, i;
  unsigned long dataLength = strlen(data);
  
  //attempt to write the gzip stream manually
  
  unsigned long compressedDataLength = sizeof(char)*dataLength*1.1 + 22;
  //unsigned long compressedDataLength = sizeof(char)*dataLength*4;
  char *compressedData = calloc(compressedDataLength,1);
  char *headerBegin;
  
  // write a simple gzip header, 10 chars
  // == {31, -117, 8, 0, 0, 0, 0, 0, 0, 3}
  sprintf(compressedData, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1],
          Z_DEFLATED, 0       , 0,0,0,0      , 0        , OS_CODE);
        //          , flags   , time         , xflags   ,        );
  
  headerBegin = compressedData;
  compressedData = &compressedData[10];
  compressedDataLength -= 10;
  
  z_stream strm;
  
  strm.zalloc = Z_NULL;
  strm.zfree = Z_NULL;
  strm.opaque = Z_NULL;
  returnCode = deflateInit2( &strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 
                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY );
  if (returnCode != Z_OK)
    puts("Error in zlib.");
  
  strm.avail_in = dataLength;
  strm.next_in = (char*)data;
  
  strm.next_out = compressedData;
  strm.avail_out = compressedDataLength;
  if ((unsigned long)strm.avail_out != compressedDataLength) 
    //return Z_BUF_ERROR
    puts("Z_BUF_ERROR in zlib.");
    
  returnCode = deflate(&strm, Z_FINISH); // Z_FINISH or Z_NO_FLUSH if getting 
                                         // more input later
  
  //if(returnCode != Z_OK)
  if(returnCode != Z_STREAM_END)
  {
    switch(returnCode)
    {
      case Z_MEM_ERROR:
        fprintf(stderr, "zlib: Error allocating memory.\n");
        break;
      case Z_DATA_ERROR:
        fprintf(stderr, "zlib: Error, deflate data is invalid or incomplete.");
        break;
      case Z_VERSION_ERROR:
        fprintf(stderr, "zlib: Error, version mismatch between \"zlib.h\" and \"libz.so\".\n");
        break;
      case Z_ERRNO:
        fprintf(stderr, "zlib: Error reading/writing to files.\n");
        break;
      default:
        fprintf(stderr, "zlib: Error, unspecified.\n");
    }
    puts("Abandoning attempt to compress xml-rpc request. Making plain-text xml-rpc request...");
    deflateEnd(&strm);
  }
  
  compressedDataLength = strm.total_out;
  deflateEnd(&strm);
  
  compressedData = headerBegin;
  compressedDataLength += 10;
  */
  /*
  int returnCode, i;
  unsigned long dataLength = strlen(data);
  char *compressedData;
  int compressedDataLength = 0;
  int fd[2];
  
  pipe(fd);
   
  //gzFile tempFile = gzdopen(fd[1], "wb9"); //compression level 9
  gzFile tempFile = gzdopen(fd[1], "wb");
  if(tempFile == NULL)
    returnCode = Z_MEM_ERROR;
  else
  {
    compressedDataLength = gzwrite(tempFile, data, dataLength);
    returnCode = gzclose(tempFile);
  }
  
  if(returnCode != Z_OK)
  {
    switch(returnCode)
    {
      case Z_MEM_ERROR:
        fprintf(stderr, "zlib: Error allocating memory.\n");
        break;
      case Z_DATA_ERROR:
        fprintf(stderr, "zlib: Error, deflate data is invalid or incomplete.");
        break;
      case Z_VERSION_ERROR:
        fprintf(stderr, "zlib: Error, version mismatch between \"zlib.h\" and \"libz.so\".\n");
        break;
      case Z_ERRNO:
        fprintf(stderr, "zlib: Error reading/writing to files.\n");
        break;
      default:
        fprintf(stderr, "zlib: Error, unspecified.\n");
    }
    puts("Abandoning attempt to compress xml-rpc request. Making plain-text xml-rpc request...");
  }
  
  if(compressedDataLength > 0)
  {  
    compressedData = (char*)calloc(compressedDataLength, sizeof(char));
    compressedDataLength = read(fd[0], compressedData, compressedDataLength);
    //printf("%d\n", compressedDataLength);
    close(fd[0]);
  }
  
  
  FILE *sanity = fopen("sanity.gz", "wb");
  fwrite(compressedData, sizeof(char), sizeof(char)*compressedDataLength, sanity);
  fflush(sanity);
  */
  

