#include <stdio.h>
#include <string.h>
#include <curl/curl.h>

const char data[]="0123456789";

struct data {
  char trace_ascii; /* 1 or 0 */
};

static struct timeval prev;

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;
  struct timeval tv;
  long delta;

  gettimeofday(&tv, NULL);

  delta = ((tv.tv_sec - 1374786238)*1000000 +
           tv.tv_usec ) -
    ((prev.tv_sec - 1374786238)*1000000 +
     prev.tv_usec);

  prev = tv;

  if(nohex)
    /* without the hex output, we can fit more on screen */
    width = 0x40;

  fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n",
          text, (long)size, (long)size);

  for(i=0; i<size; i+= width) {

    fprintf(stream, "D(%ld) %4.4lx: ",
            delta,  (long)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)
{
  const char *text;

  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, 1);
  return 0;
}


struct WriteThis {
  const char *readptr;
  long sizeleft;
};

static size_t
write_callback(void *contents, size_t size, size_t nmemb, void *userp)
{
  return size*nmemb;
}

static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
{
  struct WriteThis *pooh = (struct WriteThis *)userp;

  if(size*nmemb < sizeof(data))
    return 0;

  if(pooh->sizeleft) {
    memcpy(ptr, data, sizeof(data));
    pooh->sizeleft -= 10;
    return 10;
  }

  return 0;                          /* no more data left to deliver */
}

static int runonce(CURL *curl,
                   struct WriteThis *p)
{
  CURLcode res;

  p->sizeleft = (long)strlen(data);

  /* First set the URL that is about to receive our POST. */
  curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1/index.html");

  /* Now specify we want to POST data */
  curl_easy_setopt(curl, CURLOPT_POST, 1L);

  curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace);

  /* we want to use our own read function */
  curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);

  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);

  /* pointer to pass to our read function */
  curl_easy_setopt(curl, CURLOPT_READDATA, p);

  /* get verbose debug output please */
  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

  /* Set the expected POST size. If you want to POST large amounts of data,
     consider CURLOPT_POSTFIELDSIZE_LARGE */
  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, p->sizeleft);

  /*
    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 */
  }

  /* Perform the request, res will get the return code */
  res = curl_easy_perform(curl);
  /* Check for errors */
  if(res != CURLE_OK)
    fprintf(stderr, "curl_easy_perform() failed: %s\n",
            curl_easy_strerror(res));
}


static int runfixed(CURL *curl,
                    struct WriteThis *p)
{
  CURLcode res;

  /* First set the URL that is about to receive our POST. */
  curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1/index.html");

  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "0123456789");

  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);

  curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace);

  /* get verbose debug output please */
  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

  /* Set the expected POST size. If you want to POST large amounts of data,
     consider CURLOPT_POSTFIELDSIZE_LARGE */
  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 10);

  /*
    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 */
  }

  /* Perform the request, res will get the return code */
  res = curl_easy_perform(curl);
  /* Check for errors */
  if(res != CURLE_OK)
    fprintf(stderr, "curl_easy_perform() failed: %s\n",
            curl_easy_strerror(res));
}

#define LOOPS 10000

int main(int argc, char **argv)
{
  CURL *curl;
  CURLcode res;
  struct WriteThis pooh;
  int alt=0;

  if(argc > 1) {
    alt = 1;
    printf("runs fixed string version\n");
  }

  pooh.readptr = data;

  /* In windows, this will init the winsock stuff */
  res = curl_global_init(CURL_GLOBAL_DEFAULT);
  /* Check for errors */
  if(res != CURLE_OK) {
    fprintf(stderr, "curl_global_init() failed: %s\n",
            curl_easy_strerror(res));
    return 1;
  }

  gettimeofday(&prev, NULL);

  /* get a curl handle */
  curl = curl_easy_init();
  if(curl) {
    int i;
    if(alt) {
      for(i=0; i< LOOPS; i++)
        runfixed(curl, &pooh);
    }
    else {
      for(i=0; i< LOOPS; i++)
        runonce(curl, &pooh);
    }

    /* always cleanup */
    curl_easy_cleanup(curl);
  }
  curl_global_cleanup();
  return 0;
}
