/*****************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * $Id: post-callback.c,v 1.7 2007-07-16 21:22:12 danf Exp $
 *
 * An example source code that issues a HTTP POST and we provide the actual
 * data through a read callback.
 *
 */
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <curl/curl.h>

#define FILE_BASED

struct data {
  char trace_ascii; /* 1 or 0 */
};

static
void dump(const char *text,
          FILE *stream, unsigned char *ptr, size_t size,
          char nohex)
{
  size_t i;
  size_t c;

  unsigned int width=0x10;

  if(nohex)
    /* without the hex output, we can fit more on screen */
    width = 0x40;

  fprintf(stream, "%s, %zd bytes (0x%zx)\n", text, size, size);

  for(i=0; i<size; i+= width) {

    fprintf(stream, "%04zx: ", i);

    if(!nohex) {
      /* hex not disabled, show it */
      for(c = 0; c < width; c++)
        if(i+c < size)
          fprintf(stream, "%02x ", ptr[i+c]);
        else
          fputs("   ", stream);
    }

    for(c = 0; (c < width) && (i+c < size); c++) {
      /* check for 0D0A; if found, skip past and start a new line of output */
      if (nohex && (i+c+1 < size) && ptr[i+c]==0x0D && ptr[i+c+1]==0x0A) {
        i+=(c+2-width);
        break;
      }
      fprintf(stream, "%c",
              (ptr[i+c]>=0x20) && (ptr[i+c]<0x80)?ptr[i+c]:'.');
      /* check again for 0D0A, to avoid an extra \n if it's at width */
      if (nohex && (i+c+2 < size) && ptr[i+c+1]==0x0D && ptr[i+c+2]==0x0A) {
        i+=(c+3-width);
        break;
      }
    }
    fputc('\n', stream); /* newline */
  }
  fflush(stream);
}

static
int my_trace(CURL *handle, curl_infotype type,
             char *data, size_t size,
             void *userp)
{
  struct data *config = (struct data *)userp;
  const char *text;
  (void)handle; /* prevent compiler warning */

  switch (type) {
  case CURLINFO_TEXT:
    fprintf(stderr, "== Info: %s", data);
  default: /* in case a new one is introduced to shock us */
    return 0;

  case CURLINFO_HEADER_OUT:
    text = "=> Send header";
    break;
  case CURLINFO_DATA_OUT:
    text = "=> Send data";
    break;
  case CURLINFO_SSL_DATA_OUT:
    text = "=> Send SSL data";
    break;
  case CURLINFO_HEADER_IN:
    text = "<= Recv header";
    break;
  case CURLINFO_DATA_IN:
    text = "<= Recv data";
    break;
  case CURLINFO_SSL_DATA_IN:
    text = "<= Recv SSL data";
    break;
  }

  dump(text, stderr, (unsigned char *)data, size, config->trace_ascii);
  return 0;
}

const char data[]="this is what we post to the silly web server";

struct WriteThis {
  int fd;
};

#ifdef FILE_BASED
static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
{
  struct WriteThis *in = (struct WriteThis *)userp;
  ssize_t rc;

  rc = read(in->fd, ptr, size*nmemb);
  if(rc < 0)
    /* since size_t is unsigned we can't return negative values fine */
    return 0;
  return (size_t)rc;
}
#endif

/* CURLOPT_PROGRESSFUNCTION */
int prog_cb(void *p, double dltotal, double dlnow, double ult, double uln)
{
  fprintf(stderr, "Progress: (%g/%g) (%g/%g)\n",
          dlnow, dltotal, uln, ult);
  (void)p;
  return 0;
}

int main(int argc, char *argv[])
{
  CURL *curl;
  CURLcode res;
  struct WriteThis pooh;
  struct data config;
  char error[CURL_ERROR_SIZE];
  struct stat info;
  long postsize;
  char *ptr;

  config.trace_ascii = 1; /* enable ascii tracing */

  if(argc < 2) {
    printf("Usage: moo [file]\n");
    return 1;
  }

  pooh.fd = open(argv[1], O_RDONLY);

  stat(argv[1], &info);
  postsize = info.st_size;

#ifndef FILE_BASED
  ptr= malloc(postsize);
  read(pooh.fd, ptr, postsize);
#endif

  curl = curl_easy_init();
  if(curl) {
    /* First set the URL that is about to receive our POST. */
    curl_easy_setopt(curl, CURLOPT_URL, "http://localhost/");
    /* Now specify we want to POST data */
    curl_easy_setopt(curl, CURLOPT_POST, 1);

#ifdef FILE_BASED
    /* we want to use our own read function */
    curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);

    /* pointer to pass to our read function */
    curl_easy_setopt(curl, CURLOPT_READDATA, &pooh);

    /* Set the expected POST size. If you want to POST large amounts of data,
       consider CURLOPT_POSTFIELDSIZE_LARGE */
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postsize);
#else
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ptr);
#endif

    /* get verbose debug output please */
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);

    curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace);
    curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &config);

    curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error);

    curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST );
    curl_easy_setopt(curl, CURLOPT_USERPWD, "fake:user" );

    curl_easy_setopt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t)5000);

    curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, prog_cb);
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);

#if 1 /*def DISABLE_EXPECT*/
    /*
      Using POST with HTTP 1.1 implies the use of a "Expect: 100-continue"
      header.  You can disable this header with CURLOPT_HTTPHEADER as usual.
      NOTE: if you want chunked transfer too, you need to combine these two
      since you can only set one list of headers with CURLOPT_HTTPHEADER. */

    /* A less good option would be to enforce HTTP 1.0, but that might also
       have other implications. */
    {
      struct curl_slist *chunk = NULL;

      chunk = curl_slist_append(chunk, "Expect:");
      res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
      /* use curl_slist_free_all() after the *perform() call to free this
         list again */
    }
#endif

    /* Perform the request, res will get the return code */
    res = curl_easy_perform(curl);

    if(res) {
      printf("(%d) %s\n", res, error);
    }

    /* always cleanup */
    curl_easy_cleanup(curl);
  }
  return 0;
}
