/*
     This file is part of libmicrohttpd
     Copyright (C) 2007 Christian Grothoff
     Copyright (C) 2014-2022 Evgeny Grin (Karlson2k)

     libmicrohttpd is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published
     by the Free Software Foundation; either version 2, or (at your
     option) any later version.

     libmicrohttpd is distributed in the hope that it will be useful, but
     WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with libmicrohttpd; see the file COPYING.  If not, write to the
     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     Boston, MA 02110-1301, USA.
*/

/**
 * @file test_get_chunked.c
 * @brief  Testcase for libmicrohttpd GET operations with chunked content encoding
 * @author Christian Grothoff
 * @author Karlson2k (Evgeny Grin)
 */

#include "MHD_config.h"
#include "platform.h"
#include <curl/curl.h>
#include <microhttpd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>

#ifndef WINDOWS
#include <unistd.h>
#endif

#include "mhd_has_in_name.h"

#if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2
#undef MHD_CPU_COUNT
#endif
#if ! defined(MHD_CPU_COUNT)
#define MHD_CPU_COUNT 2
#endif

#define HDR_CHUNKED_ENCODING MHD_HTTP_HEADER_TRANSFER_ENCODING ": chunked"
#define RESP_FOOTER_NAME "Footer"
#define RESP_FOOTER_VALUE "working"
#define RESP_FOOTER RESP_FOOTER_NAME ": " RESP_FOOTER_VALUE

#define RESP_BLOCK_SIZE 128
#define RESP_BLOCK_QUANTIY 10
#define RESP_SIZE (RESP_BLOCK_SIZE * RESP_BLOCK_QUANTIY)

/**
 * Use "Connection: close" header?
 */
static int conn_close;

/**
 * Use static string response instead of callback-generated?
 */
static int resp_string;

/**
 * Use response with known size?
 */
static int resp_sized;

/**
 * Use empty (zero-sized) response?
 */
static int resp_empty;

/**
 * Force chunked response by response header?
 */
static int chunked_forced;

/**
 * MHD port used for testing
 */
static uint16_t port_global;


struct headers_check_result
{
  int found_chunked;
  int found_footer;
};

static size_t
lcurl_hdr_callback (char *buffer, size_t size, size_t nitems,
                    void *userdata)
{
  const size_t data_size = size * nitems;
  struct headers_check_result *check_res =
    (struct headers_check_result *) userdata;

  if ((data_size == strlen (HDR_CHUNKED_ENCODING) + 2) &&
      (0 == memcmp (buffer, HDR_CHUNKED_ENCODING "\r\n", data_size)))
    check_res->found_chunked = 1;
  if ((data_size == strlen (RESP_FOOTER) + 2) &&
      (0 == memcmp (buffer, RESP_FOOTER "\r\n", data_size)))
    check_res->found_footer = 1;

  return data_size;
}


struct CBC
{
  char *buf;
  size_t pos;
  size_t size;
};


static size_t
copyBuffer (void *ptr,
            size_t size,
            size_t nmemb,
            void *ctx)
{
  struct CBC *cbc = ctx;

  if (cbc->pos + size * nmemb > cbc->size)
    return 0;                   /* overflow */
  memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
  cbc->pos += size * nmemb;
  return size * nmemb;
}


/**
 * MHD content reader callback that returns data in chunks.
 */
static ssize_t
crc (void *cls,
     uint64_t pos,
     char *buf,
     size_t max)
{
  struct MHD_Response **responseptr = cls;

  if (resp_empty || (pos == RESP_SIZE - RESP_BLOCK_SIZE))
  { /* Add footer with the last block */
    if (MHD_YES != MHD_add_response_footer (*responseptr,
                                            RESP_FOOTER_NAME,
                                            RESP_FOOTER_VALUE))
      abort ();
  }
  if (resp_empty || (pos == RESP_SIZE))
    return MHD_CONTENT_READER_END_OF_STREAM;

  if (max < RESP_BLOCK_SIZE)
    abort ();                   /* should not happen in this testcase... */
  memset (buf,
          'A' + (char) (unsigned char) (pos / RESP_BLOCK_SIZE),
          RESP_BLOCK_SIZE);
  return RESP_BLOCK_SIZE;
}


/**
 * Dummy function that frees the "responseptr".
 */
static void
crcf (void *ptr)
{
  free (ptr);
}


static enum MHD_Result
ahc_echo (void *cls,
          struct MHD_Connection *connection,
          const char *url,
          const char *method,
          const char *version,
          const char *upload_data, size_t *upload_data_size, void **req_cls)
{
  static int aptr;
  struct MHD_Response *response;
  enum MHD_Result ret;

  (void) cls;
  (void) url;
  (void) version;              /* Unused. Silent compiler warning. */
  (void) upload_data;
  (void) upload_data_size;     /* Unused. Silent compiler warning. */

  if (0 != strcmp (MHD_HTTP_METHOD_GET, method))
    return MHD_NO;              /* unexpected method */
  if (&aptr != *req_cls)
  {
    /* do never respond on first call */
    *req_cls = &aptr;
    return MHD_YES;
  }
  if (! resp_string)
  {
    struct MHD_Response **responseptr;
    responseptr = malloc (sizeof (struct MHD_Response *));
    if (NULL == responseptr)
      _exit (99);

    response = MHD_create_response_from_callback (resp_sized ?
                                                  RESP_SIZE : MHD_SIZE_UNKNOWN,
                                                  1024,
                                                  &crc,
                                                  responseptr,
                                                  &crcf);
    *responseptr = response;
  }
  else
  {
    if (! resp_empty)
    {
      size_t pos;
      static const size_t resp_size = RESP_SIZE;
      char *buf = malloc (resp_size);
      if (NULL == buf)
        _exit (99);
      for (pos = 0; pos < resp_size; pos += RESP_BLOCK_SIZE)
        memset (buf + pos,
                'A' + (char) (unsigned char) (pos / RESP_BLOCK_SIZE),
                RESP_BLOCK_SIZE);

      response = MHD_create_response_from_buffer_copy (resp_size, buf);
      free (buf);
    }
    else
      response = MHD_create_response_empty (MHD_RF_NONE);
  }
  if (NULL == response)
    abort ();
  if (chunked_forced)
  {
    if (MHD_NO == MHD_add_response_header (response,
                                           MHD_HTTP_HEADER_TRANSFER_ENCODING,
                                           "chunked"))
      abort ();
  }
  if (MHD_NO == MHD_add_response_header (response,
                                         MHD_HTTP_HEADER_TRAILER,
                                         RESP_FOOTER_NAME))
    abort ();

  if (resp_string || (resp_sized && resp_empty))
  {
    /* There is no chance to add footer later */
    if (MHD_YES != MHD_add_response_footer (response,
                                            RESP_FOOTER_NAME,
                                            RESP_FOOTER_VALUE))
      abort ();
  }

  ret = MHD_queue_response (connection,
                            MHD_HTTP_OK,
                            response);
  MHD_destroy_response (response);
  return ret;
}


static unsigned int
validate (struct CBC cbc, unsigned int ebase)
{
  int i;
  char buf[RESP_BLOCK_SIZE];

  if (resp_empty)
  {
    if (0 != cbc.pos)
    {
      fprintf (stderr,
               "Got %u bytes instead of zero!\n",
               (unsigned int) cbc.pos);
      return 1;
    }
    return 0;
  }

  if (cbc.pos != RESP_SIZE)
  {
    fprintf (stderr,
             "Got %u bytes instead of 1280!\n",
             (unsigned int) cbc.pos);
    return ebase;
  }

  for (i = 0; i < RESP_BLOCK_QUANTIY; i++)
  {
    memset (buf, 'A' + i, RESP_BLOCK_SIZE);
    if (0 != memcmp (buf, &cbc.buf[i * RESP_BLOCK_SIZE], RESP_BLOCK_SIZE))
    {
      fprintf (stderr,
               "Got  `%.*s'\nWant `%.*s'\n",
               RESP_BLOCK_SIZE, &cbc.buf[i * RESP_BLOCK_SIZE],
               RESP_BLOCK_SIZE, buf);
      return ebase * 2;
    }
  }
  return 0;
}


static unsigned int
testInternalGet (void)
{
  struct MHD_Daemon *d;
  CURL *c;
  char buf[2048];
  struct CBC cbc;
  CURLcode errornum;
  uint16_t port;
  struct curl_slist *h_list = NULL;
  struct headers_check_result hdr_check;

  port = port_global;
  cbc.buf = buf;
  cbc.size = 2048;
  cbc.pos = 0;
  d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG,
                        port, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
  if (d == NULL)
    return 1;
  if (0 == port)
  {
    const union MHD_DaemonInfo *dinfo;
    dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
    if ((NULL == dinfo) || (0 == dinfo->port) )
    {
      MHD_stop_daemon (d); return 32;
    }
    port = dinfo->port;
    if (0 == port_global)
      port_global = port; /* Re-use the same port for all checks */
  }
  hdr_check.found_chunked = 0;
  hdr_check.found_footer = 0;
  c = curl_easy_init ();
  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
  curl_easy_setopt (c, CURLOPT_PORT, (long) port);
  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_easy_setopt (c, CURLOPT_HEADERFUNCTION, lcurl_hdr_callback);
  curl_easy_setopt (c, CURLOPT_HEADERDATA, &hdr_check);
  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
  if (conn_close)
  {
    h_list = curl_slist_append (h_list, "Connection: close");
    if (NULL == h_list)
      abort ();
    curl_easy_setopt (c, CURLOPT_HTTPHEADER, h_list);
  }
  if (CURLE_OK != (errornum = curl_easy_perform (c)))
  {
    fprintf (stderr,
             "curl_easy_perform failed: `%s'\n",
             curl_easy_strerror (errornum));
    curl_easy_cleanup (c);
    curl_slist_free_all (h_list);
    MHD_stop_daemon (d);
    return 2;
  }
  curl_easy_cleanup (c);
  curl_slist_free_all (h_list);
  MHD_stop_daemon (d);
  if (1 != hdr_check.found_chunked)
  {
    fprintf (stderr,
             "Chunked encoding header was not found in the response\n");
    return 8;
  }
  if (1 != hdr_check.found_footer)
  {
    fprintf (stderr,
             "The specified footer was not found in the response\n");
    return 16;
  }
  return validate (cbc, 4);
}


static unsigned int
testMultithreadedGet (void)
{
  struct MHD_Daemon *d;
  CURL *c;
  char buf[2048];
  struct CBC cbc;
  CURLcode errornum;
  uint16_t port;
  struct curl_slist *h_list = NULL;
  struct headers_check_result hdr_check;

  port = port_global;
  cbc.buf = buf;
  cbc.size = 2048;
  cbc.pos = 0;
  d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
                        | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG,
                        port, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
  if (d == NULL)
    return 16;
  if (0 == port)
  {
    const union MHD_DaemonInfo *dinfo;
    dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
    if ((NULL == dinfo) || (0 == dinfo->port) )
    {
      MHD_stop_daemon (d); return 32;
    }
    port = dinfo->port;
    if (0 == port_global)
      port_global = port; /* Re-use the same port for all checks */
  }
  c = curl_easy_init ();
  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
  curl_easy_setopt (c, CURLOPT_PORT, (long) port);
  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
  hdr_check.found_chunked = 0;
  hdr_check.found_footer = 0;
  curl_easy_setopt (c, CURLOPT_HEADERFUNCTION, lcurl_hdr_callback);
  curl_easy_setopt (c, CURLOPT_HEADERDATA, &hdr_check);
  if (conn_close)
  {
    h_list = curl_slist_append (h_list, "Connection: close");
    if (NULL == h_list)
      abort ();
    curl_easy_setopt (c, CURLOPT_HTTPHEADER, h_list);
  }
  if (CURLE_OK != (errornum = curl_easy_perform (c)))
  {
    fprintf (stderr,
             "curl_easy_perform failed: `%s'\n",
             curl_easy_strerror (errornum));
    curl_easy_cleanup (c);
    curl_slist_free_all (h_list);
    MHD_stop_daemon (d);
    return 32;
  }
  curl_easy_cleanup (c);
  curl_slist_free_all (h_list);
  MHD_stop_daemon (d);
  if (1 != hdr_check.found_chunked)
  {
    fprintf (stderr,
             "Chunked encoding header was not found in the response\n");
    return 8;
  }
  if (1 != hdr_check.found_footer)
  {
    fprintf (stderr,
             "The specified footer was not found in the response\n");
    return 16;
  }
  return validate (cbc, 64);
}


static unsigned int
testMultithreadedPoolGet (void)
{
  struct MHD_Daemon *d;
  CURL *c;
  char buf[2048];
  struct CBC cbc;
  CURLcode errornum;
  uint16_t port;
  struct curl_slist *h_list = NULL;
  struct headers_check_result hdr_check;

  port = port_global;
  cbc.buf = buf;
  cbc.size = 2048;
  cbc.pos = 0;
  d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG,
                        port, NULL, NULL, &ahc_echo, NULL,
                        MHD_OPTION_THREAD_POOL_SIZE, MHD_CPU_COUNT,
                        MHD_OPTION_END);
  if (d == NULL)
    return 16;
  if (0 == port)
  {
    const union MHD_DaemonInfo *dinfo;
    dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
    if ((NULL == dinfo) || (0 == dinfo->port) )
    {
      MHD_stop_daemon (d); return 32;
    }
    port = dinfo->port;
    if (0 == port_global)
      port_global = port; /* Re-use the same port for all checks */
  }
  c = curl_easy_init ();
  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
  curl_easy_setopt (c, CURLOPT_PORT, (long) port);
  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
  hdr_check.found_chunked = 0;
  hdr_check.found_footer = 0;
  curl_easy_setopt (c, CURLOPT_HEADERFUNCTION, lcurl_hdr_callback);
  curl_easy_setopt (c, CURLOPT_HEADERDATA, &hdr_check);
  if (conn_close)
  {
    h_list = curl_slist_append (h_list, "Connection: close");
    if (NULL == h_list)
      abort ();
    curl_easy_setopt (c, CURLOPT_HTTPHEADER, h_list);
  }
  if (CURLE_OK != (errornum = curl_easy_perform (c)))
  {
    fprintf (stderr,
             "curl_easy_perform failed: `%s'\n",
             curl_easy_strerror (errornum));
    curl_easy_cleanup (c);
    curl_slist_free_all (h_list);
    MHD_stop_daemon (d);
    return 32;
  }
  curl_easy_cleanup (c);
  curl_slist_free_all (h_list);
  MHD_stop_daemon (d);
  if (1 != hdr_check.found_chunked)
  {
    fprintf (stderr,
             "Chunked encoding header was not found in the response\n");
    return 8;
  }
  if (1 != hdr_check.found_footer)
  {
    fprintf (stderr,
             "The specified footer was not found in the response\n");
    return 16;
  }
  return validate (cbc, 64);
}


static unsigned int
testExternalGet (void)
{
  struct MHD_Daemon *d;
  CURL *c;
  char buf[2048];
  struct CBC cbc;
  CURLM *multi;
  CURLMcode mret;
  fd_set rs;
  fd_set ws;
  fd_set es;
  MHD_socket maxsock;
#ifdef MHD_WINSOCK_SOCKETS
  int maxposixs; /* Max socket number unused on W32 */
#else  /* MHD_POSIX_SOCKETS */
#define maxposixs maxsock
#endif /* MHD_POSIX_SOCKETS */
  int running;
  struct CURLMsg *msg;
  time_t start;
  struct timeval tv;
  uint16_t port;
  struct curl_slist *h_list = NULL;
  struct headers_check_result hdr_check;

  port = port_global;
  multi = NULL;
  cbc.buf = buf;
  cbc.size = 2048;
  cbc.pos = 0;
  d = MHD_start_daemon (MHD_USE_ERROR_LOG | MHD_USE_NO_THREAD_SAFETY,
                        port, NULL, NULL, &ahc_echo, NULL,
                        MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE,
                        MHD_OPTION_END);
  if (d == NULL)
    return 256;
  if (0 == port)
  {
    const union MHD_DaemonInfo *dinfo;
    dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
    if ((NULL == dinfo) || (0 == dinfo->port) )
    {
      MHD_stop_daemon (d); return 32;
    }
    port = dinfo->port;
    if (0 == port_global)
      port_global = port; /* Re-use the same port for all checks */
  }
  c = curl_easy_init ();
  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
  curl_easy_setopt (c, CURLOPT_PORT, (long) port);
  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 5L);
  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
  hdr_check.found_chunked = 0;
  hdr_check.found_footer = 0;
  curl_easy_setopt (c, CURLOPT_HEADERFUNCTION, lcurl_hdr_callback);
  curl_easy_setopt (c, CURLOPT_HEADERDATA, &hdr_check);
  if (conn_close)
  {
    h_list = curl_slist_append (h_list, "Connection: close");
    if (NULL == h_list)
      abort ();
    curl_easy_setopt (c, CURLOPT_HTTPHEADER, h_list);
  }

  multi = curl_multi_init ();
  if (multi == NULL)
  {
    curl_easy_cleanup (c);
    curl_slist_free_all (h_list);
    MHD_stop_daemon (d);
    return 512;
  }
  mret = curl_multi_add_handle (multi, c);
  if (mret != CURLM_OK)
  {
    curl_multi_cleanup (multi);
    curl_easy_cleanup (c);
    curl_slist_free_all (h_list);
    MHD_stop_daemon (d);
    return 1024;
  }
  start = time (NULL);
  while ((time (NULL) - start < 5) && (multi != NULL))
  {
    maxsock = MHD_INVALID_SOCKET;
    maxposixs = -1;
    FD_ZERO (&rs);
    FD_ZERO (&ws);
    FD_ZERO (&es);
    curl_multi_perform (multi, &running);
    mret = curl_multi_fdset (multi, &rs, &ws, &es, &maxposixs);
    if (mret != CURLM_OK)
    {
      curl_multi_remove_handle (multi, c);
      curl_multi_cleanup (multi);
      curl_easy_cleanup (c);
      curl_slist_free_all (h_list);
      MHD_stop_daemon (d);
      return 2048;
    }
    if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &maxsock))
    {
      curl_multi_remove_handle (multi, c);
      curl_multi_cleanup (multi);
      curl_easy_cleanup (c);
      curl_slist_free_all (h_list);
      MHD_stop_daemon (d);
      return 4096;
    }
    tv.tv_sec = 0;
    tv.tv_usec = 1000;
    if (-1 == select (maxposixs + 1, &rs, &ws, &es, &tv))
    {
#ifdef MHD_POSIX_SOCKETS
      if (EINTR != errno)
      {
        fprintf (stderr, "Unexpected select() error: %d. Line: %d\n",
                 (int) errno, __LINE__);
        fflush (stderr);
        exit (99);
      }
#else
      if ((WSAEINVAL != WSAGetLastError ()) ||
          (0 != rs.fd_count) || (0 != ws.fd_count) || (0 != es.fd_count) )
      {
        fprintf (stderr, "Unexpected select() error: %d. Line: %d\n",
                 (int) WSAGetLastError (), __LINE__);
        fflush (stderr);
        exit (99);
      }
      Sleep (1);
#endif
    }
    curl_multi_perform (multi, &running);
    if (0 == running)
    {
      int pending;
      int curl_fine = 0;
      while (NULL != (msg = curl_multi_info_read (multi, &pending)))
      {
        if (msg->msg == CURLMSG_DONE)
        {
          if (msg->data.result == CURLE_OK)
            curl_fine = 1;
          else
          {
            fprintf (stderr,
                     "%s failed at %s:%d: `%s'\n",
                     "curl_multi_perform",
                     __FILE__,
                     __LINE__, curl_easy_strerror (msg->data.result));
            abort ();
          }
        }
      }
      if (! curl_fine)
      {
        fprintf (stderr, "libcurl haven't returned OK code\n");
        abort ();
      }
      curl_multi_remove_handle (multi, c);
      curl_multi_cleanup (multi);
      curl_easy_cleanup (c);
      curl_slist_free_all (h_list);
      h_list = NULL;
      c = NULL;
      multi = NULL;
    }
    MHD_run (d);
  }
  MHD_stop_daemon (d);
  if (multi != NULL)
  {
    curl_multi_remove_handle (multi, c);
    curl_easy_cleanup (c);
    curl_multi_cleanup (multi);
  }
  curl_slist_free_all (h_list);
  if (1 != hdr_check.found_chunked)
  {
    fprintf (stderr,
             "Chunked encoding header was not found in the response\n");
    return 8;
  }
  if (1 != hdr_check.found_footer)
  {
    fprintf (stderr,
             "The specified footer was not found in the response\n");
    return 16;
  }
  return validate (cbc, 8192);
}


int
main (int argc, char *const *argv)
{
  unsigned int errorCount = 0;
  (void) argc; (void) argv; /* Unused. Silent compiler warning. */

  if (0 != curl_global_init (CURL_GLOBAL_WIN32))
    return 2;
  conn_close = has_in_name (argv[0], "_close");
  resp_string = has_in_name (argv[0], "_string");
  resp_sized = has_in_name (argv[0], "_sized");
  resp_empty = has_in_name (argv[0], "_empty");
  chunked_forced = has_in_name (argv[0], "_forced");
  if (resp_string)
    resp_sized = ! 0;
  if (resp_sized)
    chunked_forced = ! 0;

  if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
    port_global = 0;
  else
  {
    port_global = 4100;
    if (conn_close)
      port_global += 1 << 0;
    if (resp_string)
      port_global += 1 << 1;
    if (resp_sized)
      port_global += 1 << 2;
    if (resp_empty)
      port_global += 1 << 3;
    if (chunked_forced)
      port_global += 1 << 4;
  }

  if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_THREADS))
  {
    errorCount += testInternalGet ();
    errorCount += testMultithreadedGet ();
    errorCount += testMultithreadedPoolGet ();
  }
  errorCount += testExternalGet ();
  if (errorCount != 0)
    fprintf (stderr, "Error (code: %u)\n", errorCount);
  curl_global_cleanup ();
  return (0 == errorCount) ? 0 : 1;       /* 0 == pass */
}
