libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

libtest_convenience_client_request.c (27191B)


      1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
      2 /*
      3   This file is part of GNU libmicrohttpd.
      4   Copyright (C) 2024 Christian Grothoff
      5 
      6   GNU libmicrohttpd is free software; you can redistribute it and/or
      7   modify it under the terms of the GNU Lesser General Public
      8   License as published by the Free Software Foundation; either
      9   version 2.1 of the License, or (at your option) any later version.
     10 
     11   GNU libmicrohttpd is distributed in the hope that it will be useful,
     12   but WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14   Lesser General Public License for more details.
     15 
     16   Alternatively, you can redistribute GNU libmicrohttpd and/or
     17   modify it under the terms of the GNU General Public License as
     18   published by the Free Software Foundation; either version 2 of
     19   the License, or (at your option) any later version, together
     20   with the eCos exception, as follows:
     21 
     22     As a special exception, if other files instantiate templates or
     23     use macros or inline functions from this file, or you compile this
     24     file and link it with other works to produce a work based on this
     25     file, this file does not by itself cause the resulting work to be
     26     covered by the GNU General Public License. However the source code
     27     for this file must still be made available in accordance with
     28     section (3) of the GNU General Public License v2.
     29 
     30     This exception does not invalidate any other reasons why a work
     31     based on this file might be covered by the GNU General Public
     32     License.
     33 
     34   You should have received copies of the GNU Lesser General Public
     35   License and the GNU General Public License along with this library;
     36   if not, see <https://www.gnu.org/licenses/>.
     37 */
     38 
     39 /**
     40  * @file libtest_convenience_client_request.c
     41  * @brief convenience functions implementing clients making requests for libtest users
     42  * @author Christian Grothoff
     43  */
     44 #include "libtest.h"
     45 #include <pthread.h>
     46 #include <stdbool.h>
     47 #include <fcntl.h>
     48 #include <unistd.h>
     49 #include <errno.h>
     50 #include <curl/curl.h>
     51 
     52 
     53 #ifndef CURL_VERSION_BITS
     54 #  define CURL_VERSION_BITS(x,y,z) ((x) << 16 | (y) << 8 | (z))
     55 #endif
     56 #ifndef CURL_AT_LEAST_VERSION
     57 #  define CURL_AT_LEAST_VERSION(x,y,z) \
     58         (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS (x, y, z))
     59 #endif
     60 
     61 #if CURL_AT_LEAST_VERSION (7,83,0)
     62 #  define HAVE_LIBCRUL_NEW_HDR_API 1
     63 #endif
     64 
     65 
     66 /**
     67  * Closure for the write_cb().
     68  */
     69 struct WriteBuffer
     70 {
     71   /**
     72    * Where to store the response.
     73    */
     74   char *buf;
     75 
     76   /**
     77    * Number of bytes in @e buf.
     78    */
     79   size_t len;
     80 
     81   /**
     82    * Current write offset in @e buf.
     83    */
     84   size_t pos;
     85 
     86   /**
     87    * Set to non-zero on errors (buffer full).
     88    */
     89   int err;
     90 };
     91 
     92 
     93 /**
     94  * Callback for CURLOPT_WRITEFUNCTION processing
     95  * data downloaded from the HTTP server.
     96  *
     97  * @param ptr data uploaded
     98  * @param size size of a member
     99  * @param nmemb number of members
    100  * @param stream must be a `struct WriteBuffer`
    101  * @return bytes processed (size*nmemb) or error
    102  */
    103 static size_t
    104 write_cb (void *ptr,
    105           size_t size,
    106           size_t nmemb,
    107           void *stream)
    108 {
    109   struct WriteBuffer *wb = stream;
    110   size_t prod = size * nmemb;
    111 
    112   if ( (prod / size != nmemb) ||
    113        (wb->pos + prod < wb->pos) ||
    114        (wb->pos + prod > wb->len) )
    115   {
    116     wb->err = 1;
    117     return CURLE_WRITE_ERROR;
    118   }
    119   memcpy (wb->buf + wb->pos,
    120           ptr,
    121           prod);
    122   wb->pos += prod;
    123   return prod;
    124 }
    125 
    126 
    127 /**
    128  * Declare variables needed to check a download.
    129  *
    130  * @param text text data we expect to receive
    131  */
    132 #define DECLARE_WB(text) \
    133         size_t wb_tlen = strlen (text); \
    134         char wb_buf[wb_tlen];           \
    135         struct WriteBuffer wb = {       \
    136           .buf = wb_buf,                \
    137           .len = wb_tlen                \
    138         }
    139 
    140 
    141 /**
    142  * Set CURL options to the write_cb() and wb buffer
    143  * to check a download.
    144  *
    145  * @param c CURL handle
    146  */
    147 #define SETUP_WB(c) do {                       \
    148           if (CURLE_OK !=                              \
    149               curl_easy_setopt (c,                     \
    150                                 CURLOPT_WRITEFUNCTION, \
    151                                 &write_cb))            \
    152           {                                            \
    153             curl_easy_cleanup (c);                     \
    154             return "Failed to set write callback for curl request"; \
    155           }                                            \
    156           if (CURLE_OK !=                              \
    157               curl_easy_setopt (c,                     \
    158                                 CURLOPT_WRITEDATA,     \
    159                                 &wb))                  \
    160           {                                            \
    161             curl_easy_cleanup (c);                     \
    162             return "Failed to set write buffer for curl request"; \
    163           }                                                      \
    164 } while (0)
    165 
    166 /**
    167  * Check that we received the expected text.
    168  *
    169  * @param text text we expect to have downloaded
    170  */
    171 #define CHECK_WB(text) do {      \
    172           if ( (wb_tlen != wb.pos) ||    \
    173                (0 != wb.err) ||          \
    174                (0 != memcmp (text,       \
    175                              wb_buf,     \
    176                              wb_tlen)) ) \
    177           return "Downloaded data does not match expectations"; \
    178 } while (0)
    179 
    180 
    181 /**
    182  * Perform the curl request @a c and cleanup and
    183  * return an error if the request failed.
    184  *
    185  * @param c request to perform
    186  */
    187 #define PERFORM_REQUEST(c) do {                  \
    188           CURLcode res;                          \
    189           res = curl_easy_perform (c);           \
    190           if (CURLE_OK != res)                   \
    191           {                                      \
    192             curl_easy_cleanup (c);               \
    193             return "Failed to fetch URL";        \
    194           }                                      \
    195 } while (0)
    196 
    197 /**
    198  * Check that the curl request @a c completed
    199  * with the @a want status code.
    200  * Return an error if the status does not match.
    201  *
    202  * @param c request to check
    203  * @param want desired HTTP status code
    204  */
    205 #define CHECK_STATUS(c,want) do {                \
    206           if (! check_status (c, want))          \
    207           {                                      \
    208             curl_easy_cleanup (c);               \
    209             return "Unexpected HTTP status";     \
    210           }                                      \
    211 } while (0)
    212 
    213 /**
    214  * Chec that the HTTP status of @a c matches @a expected_status
    215  *
    216  * @param a completed CURL request
    217  * @param expected_status the expected HTTP response code
    218  * @return true if the status matches
    219  */
    220 static bool
    221 check_status (CURL *c,
    222               unsigned int expected_status)
    223 {
    224   long status;
    225 
    226   if (CURLE_OK !=
    227       curl_easy_getinfo (c,
    228                          CURLINFO_RESPONSE_CODE,
    229                          &status))
    230   {
    231     fprintf (stderr,
    232              "Failed to get HTTP status");
    233     return false;
    234   }
    235   if (((unsigned int) status) != expected_status)
    236   {
    237     fprintf (stderr,
    238              "Expected HTTP status %u, got %ld\n",
    239              expected_status,
    240              status);
    241     return false;
    242   }
    243   return true;
    244 }
    245 
    246 
    247 /**
    248  * Set the @a base_url for the @a c handle.
    249  *
    250  * @param[in,out] c curl handle to manipulate
    251  * @param base_url base URL to set
    252  * @param[in,out] pc phase context with further options
    253  * @return NULL on success, error message on failure (@a c will be cleaned up in this case)
    254  */
    255 static const char *
    256 set_url (CURL *c,
    257          const char *base_url,
    258          struct MHDT_PhaseContext *pc)
    259 {
    260   if (CURLE_OK !=
    261       curl_easy_setopt (c,
    262                         CURLOPT_URL,
    263                         base_url))
    264   {
    265     curl_easy_cleanup (c);
    266     return "Failed to set URL";
    267   }
    268   if (CURLE_OK !=
    269       curl_easy_setopt (c,
    270                         CURLOPT_VERBOSE,
    271                         1))
    272   {
    273     curl_easy_cleanup (c);
    274     return "Failed to set verbosity";
    275   }
    276   {
    277     /* Force curl to do the request to 127.0.0.1 regardless of
    278        hostname */
    279     const char *host;
    280     const char *end;
    281     char ri[1024];
    282 
    283     if (0 == strncasecmp (base_url,
    284                           "https://",
    285                           strlen ("https://")))
    286       host = &base_url[strlen ("https://")];
    287     else
    288       host = &base_url[strlen ("http://")];
    289     end = strchr (host, '/');
    290     if (NULL == end)
    291       end = host + strlen (host);
    292     snprintf (ri,
    293               sizeof (ri),
    294               "%.*s:127.0.0.1",
    295               (int) (end - host),
    296               host);
    297     pc->hosts = curl_slist_append (NULL,
    298                                    ri);
    299     if (CURLE_OK !=
    300         curl_easy_setopt (c,
    301                           CURLOPT_RESOLVE,
    302                           pc->hosts))
    303     {
    304       curl_easy_cleanup (c);
    305       return "Failed to override DNS";
    306     }
    307   }
    308   if (0 == strncasecmp (base_url,
    309                         "https://",
    310                         strlen ("https://")))
    311   {
    312     struct MHDT_Phase *phase = pc->phase;
    313 
    314     if (phase->check_server_cert)
    315     {
    316       if (CURLE_OK !=
    317           curl_easy_setopt (c,
    318                             CURLOPT_CAINFO,
    319                             "data/root-ca.crt"))
    320       {
    321         curl_easy_cleanup (c);
    322         return "Failed to override root CA";
    323       }
    324     }
    325     else
    326     {
    327       /* disable certificate checking */
    328       if ( (CURLE_OK !=
    329             curl_easy_setopt (c,
    330                               CURLOPT_SSL_VERIFYPEER,
    331                               0L)) ||
    332            (CURLE_OK !=
    333             curl_easy_setopt (c,
    334                               CURLOPT_SSL_VERIFYHOST,
    335                               0L)) )
    336       {
    337         curl_easy_cleanup (c);
    338         return "Failed to disable X509 server certificate checks";
    339       }
    340     }
    341     if (NULL != phase->client_cert)
    342     {
    343       if (CURLE_OK !=
    344           curl_easy_setopt (c,
    345                             CURLOPT_SSLCERT,
    346                             phase->client_cert))
    347       {
    348         curl_easy_cleanup (c);
    349         return "Failed to set client certificate";
    350       }
    351     }
    352   }
    353   return NULL;
    354 }
    355 
    356 
    357 /**
    358  * Create a curl handle for the given @a pc.
    359  *
    360  * @param pc current phase context with options to use
    361  * @return NULL on error
    362  */
    363 static CURL *
    364 setup_curl (const struct MHDT_PhaseContext *pc)
    365 {
    366   struct MHDT_Phase *p = pc->phase;
    367   CURL *c;
    368 
    369   c = curl_easy_init ();
    370   if (NULL == c)
    371     return NULL;
    372   switch (p->http_version)
    373   {
    374   case 0: /* unset == any */
    375     break;
    376   case 1:
    377     if (CURLE_OK !=
    378         curl_easy_setopt (c,
    379                           CURLOPT_HTTP_VERSION,
    380                           CURL_HTTP_VERSION_1_1))
    381     {
    382       curl_easy_cleanup (c);
    383       fprintf (stderr,
    384                "HTTP/1 not supported by curl?\n");
    385       return NULL;
    386     }
    387     break;
    388   case 2: /* HTTP/2 */
    389     if (p->use_tls)
    390     {
    391       if (CURLE_OK !=
    392           curl_easy_setopt (c,
    393                             CURLOPT_HTTP_VERSION,
    394                             CURL_HTTP_VERSION_2TLS))
    395       {
    396         curl_easy_cleanup (c);
    397         fprintf (stderr,
    398                  "HTTP/2 not supported by curl?\n");
    399         return NULL;
    400       }
    401     }
    402     else
    403     {
    404       if (CURLE_OK !=
    405           curl_easy_setopt (c,
    406                             CURLOPT_HTTP_VERSION,
    407                             CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE))
    408       {
    409         curl_easy_cleanup (c);
    410         fprintf (stderr,
    411                  "HTTP/2 not supported by curl?\n");
    412         return NULL;
    413       }
    414     }
    415     break;
    416   case 3:
    417     abort (); // not yet supported
    418     break;
    419   default:
    420     abort ();
    421     break;
    422   }
    423   return c;
    424 }
    425 
    426 
    427 const char *
    428 MHDT_client_get_host (const void *cls,
    429                       struct MHDT_PhaseContext *pc)
    430 {
    431   const char *host = cls;
    432   const char *err;
    433   size_t alen = strlen (host);
    434   CURL *c;
    435   size_t blen = strlen (pc->base_url);
    436   char u[alen + blen + 1];
    437   const char *slash = strchr (pc->base_url,
    438                               '/');
    439   const char *colon;
    440 
    441   if (NULL == slash)
    442     return "'/' missing in base URL";
    443   colon = strchr (slash,
    444                   ':');
    445   if (NULL == colon)
    446     return "':' missing in base URL";
    447   snprintf (u,
    448             sizeof (u),
    449             "https://%s%s",
    450             host,
    451             colon);
    452   c = setup_curl (pc);
    453   if (NULL == c)
    454     return "Failed to initialize Curl handle";
    455   err = set_url (c,
    456                  u,
    457                  pc);
    458   if (NULL != err)
    459     return err;
    460   PERFORM_REQUEST (c);
    461   CHECK_STATUS (c,
    462                 MHD_HTTP_STATUS_OK);
    463   curl_easy_cleanup (c);
    464   return NULL;
    465 }
    466 
    467 
    468 const char *
    469 MHDT_client_get_root (
    470   const void *cls,
    471   struct MHDT_PhaseContext *pc)
    472 {
    473   const char *text = cls;
    474   CURL *c;
    475   const char *err;
    476   DECLARE_WB (text);
    477 
    478   c = setup_curl (pc);
    479   if (NULL == c)
    480     return "Failed to initialize Curl handle";
    481   err = set_url (c,
    482                  pc->base_url,
    483                  pc);
    484   if (NULL != err)
    485     return err;
    486   SETUP_WB (c);
    487   PERFORM_REQUEST (c);
    488   CHECK_STATUS (c,
    489                 MHD_HTTP_STATUS_OK);
    490   curl_easy_cleanup (c);
    491   CHECK_WB (text);
    492   return NULL;
    493 }
    494 
    495 
    496 const char *
    497 MHDT_client_get_with_query (
    498   const void *cls,
    499   struct MHDT_PhaseContext *pc)
    500 {
    501   const char *args = cls;
    502   const char *err;
    503   size_t alen = strlen (args);
    504   CURL *c;
    505   size_t blen = strlen (pc->base_url);
    506   char u[alen + blen + 1];
    507 
    508   memcpy (u,
    509           pc->base_url,
    510           blen);
    511   memcpy (u + blen,
    512           args,
    513           alen);
    514   u[alen + blen] = '\0';
    515   c = setup_curl (pc);
    516   if (NULL == c)
    517     return "Failed to initialize Curl handle";
    518   err = set_url (c,
    519                  u,
    520                  pc);
    521   if (NULL != err)
    522     return err;
    523   PERFORM_REQUEST (c);
    524   CHECK_STATUS (c,
    525                 MHD_HTTP_STATUS_NO_CONTENT);
    526   curl_easy_cleanup (c);
    527   return NULL;
    528 }
    529 
    530 
    531 const char *
    532 MHDT_client_set_header (
    533   const void *cls,
    534   struct MHDT_PhaseContext *pc)
    535 {
    536   const char *hdr = cls;
    537   const char *err;
    538   CURL *c;
    539   CURLcode res;
    540   struct curl_slist *slist;
    541 
    542   c = setup_curl (pc);
    543   if (NULL == c)
    544     return "Failed to initialize Curl handle";
    545   err = set_url (c,
    546                  pc->base_url,
    547                  pc);
    548   if (NULL != err)
    549     return err;
    550   slist = curl_slist_append (NULL,
    551                              hdr);
    552   if (CURLE_OK !=
    553       curl_easy_setopt (c,
    554                         CURLOPT_HTTPHEADER,
    555                         slist))
    556   {
    557     curl_easy_cleanup (c);
    558     curl_slist_free_all (slist);
    559     return "Failed to set custom header for curl request";
    560   }
    561   res = curl_easy_perform (c);
    562   curl_slist_free_all (slist);
    563   if (CURLE_OK != res)
    564   {
    565     curl_easy_cleanup (c);
    566     return "Failed to fetch URL";
    567   }
    568   CHECK_STATUS (c,
    569                 MHD_HTTP_STATUS_NO_CONTENT);
    570   curl_easy_cleanup (c);
    571   return NULL;
    572 }
    573 
    574 
    575 const char *
    576 MHDT_client_expect_header (const void *cls,
    577                            struct MHDT_PhaseContext *pc)
    578 {
    579 #ifdef HAVE_LIBCRUL_NEW_HDR_API
    580   const char *hdr = cls;
    581   const char *err;
    582   size_t hlen = strlen (hdr) + 1;
    583   char key[hlen];
    584   const char *colon = strchr (hdr, ':');
    585   const char *value;
    586   CURL *c;
    587   bool found = false;
    588 
    589   if (NULL == colon)
    590     return "Invalid expected header passed";
    591   memcpy (key,
    592           hdr,
    593           hlen);
    594   key[colon - hdr] = '\0';
    595   value = &key[colon - hdr + 1];
    596   c = setup_curl (pc);
    597   if (NULL == c)
    598     return "Failed to initialize Curl handle";
    599   err = set_url (c,
    600                  pc->base_url,
    601                  pc);
    602   if (NULL != err)
    603     return err;
    604   PERFORM_REQUEST (c);
    605   CHECK_STATUS (c,
    606                 MHD_HTTP_STATUS_NO_CONTENT);
    607   for (size_t index = 0; ! found; index++)
    608   {
    609     CURLHcode rval;
    610     struct curl_header *hout;
    611 
    612     rval = curl_easy_header (c,
    613                              key,
    614                              index,
    615                              CURLH_HEADER,
    616                              -1 /* last request */,
    617                              &hout);
    618     if (CURLHE_BADINDEX == rval)
    619       break;
    620     found = (0 == strcmp (value,
    621                           hout->value));
    622   }
    623   if (! found)
    624   {
    625     curl_easy_cleanup (c);
    626     return "Expected HTTP response header not found";
    627   }
    628   curl_easy_cleanup (c);
    629   return NULL;
    630 #else  /* ! HAVE_LIBCRUL_NEW_HDR_API */
    631   (void) cls; (void) pc;
    632   return NULL;
    633 #endif /* ! HAVE_LIBCRUL_NEW_HDR_API */
    634 }
    635 
    636 
    637 /**
    638  * Closure for the read_cb().
    639  */
    640 struct ReadBuffer
    641 {
    642   /**
    643    * Origin of data to upload.
    644    */
    645   const char *buf;
    646 
    647   /**
    648    * Number of bytes in @e buf.
    649    */
    650   size_t len;
    651 
    652   /**
    653    * Current read offset in @e buf.
    654    */
    655   size_t pos;
    656 
    657   /**
    658    * Number of chunks to user when sending.
    659    */
    660   unsigned int chunks;
    661 
    662 };
    663 
    664 
    665 /**
    666  * Callback for CURLOPT_READFUNCTION for uploading
    667  * data to the HTTP server.
    668  *
    669  * @param ptr data uploaded
    670  * @param size size of a member
    671  * @param nmemb number of members
    672  * @param stream must be a `struct ReadBuffer`
    673  * @return bytes processed (size*nmemb) or error
    674  */
    675 static size_t
    676 read_cb (void *ptr,
    677          size_t size,
    678          size_t nmemb,
    679          void *stream)
    680 {
    681   struct ReadBuffer *rb = stream;
    682   size_t limit = size * nmemb;
    683 
    684   if (limit / size != nmemb)
    685     return CURLE_WRITE_ERROR;
    686   if (limit > rb->len - rb->pos)
    687     limit = rb->len - rb->pos;
    688   if ( (rb->chunks > 1) &&
    689        (limit > 1) )
    690   {
    691     limit /= rb->chunks;
    692     rb->chunks--;
    693   }
    694   memcpy (ptr,
    695           rb->buf + rb->pos,
    696           limit);
    697   rb->pos += limit;
    698   return limit;
    699 }
    700 
    701 
    702 const char *
    703 MHDT_client_put_data (
    704   const void *cls,
    705   struct MHDT_PhaseContext *pc)
    706 {
    707   const char *text = cls;
    708   const char *err;
    709   struct ReadBuffer rb = {
    710     .buf = text,
    711     .len = strlen (text)
    712   };
    713   CURL *c;
    714 
    715   c = setup_curl (pc);
    716   if (NULL == c)
    717     return "Failed to initialize Curl handle";
    718   err = set_url (c,
    719                  pc->base_url,
    720                  pc);
    721   if (NULL != err)
    722     return err;
    723   if (CURLE_OK !=
    724       curl_easy_setopt (c,
    725                         CURLOPT_UPLOAD,
    726                         1L))
    727   {
    728     curl_easy_cleanup (c);
    729     return "Failed to set PUT method for curl request";
    730   }
    731   if (CURLE_OK !=
    732       curl_easy_setopt (c,
    733                         CURLOPT_READFUNCTION,
    734                         &read_cb))
    735   {
    736     curl_easy_cleanup (c);
    737     return "Failed to set READFUNCTION for curl request";
    738   }
    739   if (CURLE_OK !=
    740       curl_easy_setopt (c,
    741                         CURLOPT_READDATA,
    742                         &rb))
    743   {
    744     curl_easy_cleanup (c);
    745     return "Failed to set READFUNCTION for curl request";
    746   }
    747   if (CURLE_OK !=
    748       curl_easy_setopt (c,
    749                         CURLOPT_INFILESIZE_LARGE,
    750                         (curl_off_t) rb.len))
    751   {
    752     curl_easy_cleanup (c);
    753     return "Failed to set INFILESIZE_LARGE for curl request";
    754   }
    755   PERFORM_REQUEST (c);
    756   CHECK_STATUS (c,
    757                 MHD_HTTP_STATUS_NO_CONTENT);
    758   curl_easy_cleanup (c);
    759   return NULL;
    760 }
    761 
    762 
    763 const char *
    764 MHDT_client_chunk_data (
    765   const void *cls,
    766   struct MHDT_PhaseContext *pc)
    767 {
    768   const char *text = cls;
    769   const char *err;
    770   struct ReadBuffer rb = {
    771     .buf = text,
    772     .len = strlen (text),
    773     .chunks = 2
    774   };
    775   CURL *c;
    776 
    777   c = setup_curl (pc);
    778   if (NULL == c)
    779     return "Failed to initialize Curl handle";
    780   err = set_url (c,
    781                  pc->base_url,
    782                  pc);
    783   if (NULL != err)
    784     return err;
    785   if (CURLE_OK !=
    786       curl_easy_setopt (c,
    787                         CURLOPT_UPLOAD,
    788                         1L))
    789   {
    790     curl_easy_cleanup (c);
    791     return "Failed to set PUT method for curl request";
    792   }
    793   if (CURLE_OK !=
    794       curl_easy_setopt (c,
    795                         CURLOPT_READFUNCTION,
    796                         &read_cb))
    797   {
    798     curl_easy_cleanup (c);
    799     return "Failed to set READFUNCTION for curl request";
    800   }
    801   if (CURLE_OK !=
    802       curl_easy_setopt (c,
    803                         CURLOPT_READDATA,
    804                         &rb))
    805   {
    806     curl_easy_cleanup (c);
    807     return "Failed to set READFUNCTION for curl request";
    808   }
    809   PERFORM_REQUEST (c);
    810   CHECK_STATUS (c,
    811                 MHD_HTTP_STATUS_NO_CONTENT);
    812   curl_easy_cleanup (c);
    813   return NULL;
    814 }
    815 
    816 
    817 const char *
    818 MHDT_client_do_post (
    819   const void *cls,
    820   struct MHDT_PhaseContext *pc)
    821 {
    822   const struct MHDT_PostInstructions *pi = cls;
    823   const char *err;
    824   CURL *c;
    825   struct curl_slist *request_hdr = NULL;
    826 
    827   /* reset wants in case we re-use the array */
    828   if (NULL != pi->wants)
    829   {
    830     for (unsigned int i = 0; NULL != pi->wants[i].key; i++)
    831     {
    832       pi->wants[i].value_off = 0;
    833       pi->wants[i].satisfied = false;
    834     }
    835   }
    836   c = setup_curl (pc);
    837   if (NULL == c)
    838     return "Failed to initialize Curl handle";
    839   err = set_url (c,
    840                  pc->base_url,
    841                  pc);
    842   if (NULL != err)
    843     return err;
    844   if (CURLE_OK !=
    845       curl_easy_setopt (c,
    846                         CURLOPT_POST,
    847                         1L))
    848   {
    849     curl_easy_cleanup (c);
    850     return "Failed to set POST method for curl request";
    851   }
    852   if (CURLE_OK !=
    853       curl_easy_setopt (c,
    854                         CURLOPT_POSTFIELDS,
    855                         pi->postdata))
    856   {
    857     curl_easy_cleanup (c);
    858     return "Failed to set POSTFIELDS for curl request";
    859   }
    860   if (0 != pi->postdata_size)
    861   {
    862     if (CURLE_OK !=
    863         curl_easy_setopt (c,
    864                           CURLOPT_POSTFIELDSIZE_LARGE,
    865                           (curl_off_t) pi->postdata_size))
    866     {
    867       curl_easy_cleanup (c);
    868       return "Failed to set POSTFIELDS for curl request";
    869     }
    870   }
    871   if (NULL != pi->postheader)
    872   {
    873     request_hdr = curl_slist_append (request_hdr,
    874                                      pi->postheader);
    875   }
    876   if (CURLE_OK !=
    877       curl_easy_setopt (c,
    878                         CURLOPT_HTTPHEADER,
    879                         request_hdr))
    880   {
    881     curl_easy_cleanup (c);
    882     curl_slist_free_all (request_hdr);
    883     return "Failed to set HTTPHEADER for curl request";
    884   }
    885   PERFORM_REQUEST (c);
    886   CHECK_STATUS (c,
    887                 MHD_HTTP_STATUS_NO_CONTENT);
    888   curl_easy_cleanup (c);
    889   curl_slist_free_all (request_hdr);
    890   if (NULL != pi->wants)
    891   {
    892     for (unsigned int i = 0; NULL != pi->wants[i].key; i++)
    893     {
    894       if (! pi->wants[i].satisfied)
    895       {
    896         fprintf (stderr,
    897                  "Server did not correctly detect key '%s'\n",
    898                  pi->wants[i].key);
    899         return "key-value data not matched by server";
    900       }
    901     }
    902   }
    903   return NULL;
    904 }
    905 
    906 
    907 /**
    908  * Send HTTP request with basic authentication.
    909  *
    910  * @param cred $USERNAME:$PASSWORD to use
    911  * @param[in,out] phase context
    912  * @param[out] http_status set to HTTP status
    913  * @return error message, NULL on success
    914  */
    915 static const char *
    916 send_basic_auth (const char *cred,
    917                  struct MHDT_PhaseContext *pc,
    918                  unsigned int *http_status)
    919 {
    920   CURL *c;
    921   const char *err;
    922   long status;
    923   char *pass = strchr (cred, ':');
    924   char *user;
    925 
    926   if (NULL == pass)
    927     return "invalid credential given";
    928   user = strndup (cred,
    929                   pass - cred);
    930   pass++;
    931   c = setup_curl (pc);
    932   if (NULL == c)
    933   {
    934     free (user);
    935     return "Failed to initialize Curl handle";
    936   }
    937   err = set_url (c,
    938                  pc->base_url,
    939                  pc);
    940   if (NULL != err)
    941   {
    942     free (user);
    943     curl_easy_cleanup (c);
    944     return err;
    945   }
    946   if ( (CURLE_OK !=
    947         curl_easy_setopt (c,
    948                           CURLOPT_HTTPAUTH,
    949                           (long) CURLAUTH_BASIC)) ||
    950        (CURLE_OK !=
    951         curl_easy_setopt (c,
    952                           CURLOPT_USERNAME,
    953                           user)) ||
    954        (CURLE_OK !=
    955         curl_easy_setopt (c,
    956                           CURLOPT_PASSWORD,
    957                           pass)) )
    958   {
    959     curl_easy_cleanup (c);
    960     free (user);
    961     return "Failed to set basic authentication header for curl request";
    962   }
    963   free (user);
    964   PERFORM_REQUEST (c);
    965   if (CURLE_OK !=
    966       curl_easy_getinfo (c,
    967                          CURLINFO_RESPONSE_CODE,
    968                          &status))
    969   {
    970     return "Failed to get HTTP status";
    971   }
    972   *http_status = (unsigned int) status;
    973   curl_easy_cleanup (c);
    974   return NULL;
    975 }
    976 
    977 
    978 const char *
    979 MHDT_client_send_basic_auth (
    980   const void *cls,
    981   struct MHDT_PhaseContext *pc)
    982 {
    983   const char *cred = cls;
    984   const char *ret;
    985   unsigned int status;
    986 
    987   ret = send_basic_auth (cred,
    988                          pc,
    989                          &status);
    990   if (NULL != ret)
    991     return ret;
    992   if (MHD_HTTP_STATUS_NO_CONTENT != status)
    993     return "invalid HTTP response code";
    994   return NULL;
    995 }
    996 
    997 
    998 const char *
    999 MHDT_client_fail_basic_auth (
   1000   const void *cls,
   1001   struct MHDT_PhaseContext *pc)
   1002 {
   1003   const char *cred = cls;
   1004   const char *ret;
   1005   unsigned int status;
   1006 
   1007   ret = send_basic_auth (cred,
   1008                          pc,
   1009                          &status);
   1010   if (NULL != ret)
   1011     return ret;
   1012   if (MHD_HTTP_STATUS_UNAUTHORIZED != status)
   1013     return "invalid HTTP response code";
   1014   return NULL;
   1015 }
   1016 
   1017 
   1018 /**
   1019  * Send HTTP request with digest authentication.
   1020  *
   1021  * @param cred $USERNAME:$PASSWORD to use
   1022  * @param[in,out] phase context
   1023  * @param[out] http_status set to HTTP status
   1024  * @return error message, NULL on success
   1025  */
   1026 static const char *
   1027 send_digest_auth (const char *cred,
   1028                   struct MHDT_PhaseContext *pc,
   1029                   unsigned int *http_status)
   1030 {
   1031   CURL *c;
   1032   const char *err;
   1033   long status;
   1034   char *pass = strchr (cred, ':');
   1035   char *user;
   1036 
   1037   if (NULL == pass)
   1038     return "invalid credential given";
   1039   user = strndup (cred,
   1040                   pass - cred);
   1041   pass++;
   1042   c = setup_curl (pc);
   1043   if (NULL == c)
   1044   {
   1045     free (user);
   1046     return "Failed to initialize Curl handle";
   1047   }
   1048   err = set_url (c,
   1049                  pc->base_url,
   1050                  pc);
   1051   if (NULL != err)
   1052   {
   1053     free (user);
   1054     curl_easy_cleanup (c);
   1055     return err;
   1056   }
   1057   if ( (CURLE_OK !=
   1058         curl_easy_setopt (c,
   1059                           CURLOPT_HTTPAUTH,
   1060                           (long) CURLAUTH_DIGEST)) ||
   1061        (CURLE_OK !=
   1062         curl_easy_setopt (c,
   1063                           CURLOPT_USERNAME,
   1064                           user)) ||
   1065        (CURLE_OK !=
   1066         curl_easy_setopt (c,
   1067                           CURLOPT_PASSWORD,
   1068                           pass)) )
   1069   {
   1070     curl_easy_cleanup (c);
   1071     free (user);
   1072     return "Failed to set digest authentication header for curl request";
   1073   }
   1074   free (user);
   1075   PERFORM_REQUEST (c);
   1076   if (CURLE_OK !=
   1077       curl_easy_getinfo (c,
   1078                          CURLINFO_RESPONSE_CODE,
   1079                          &status))
   1080   {
   1081     return "Failed to get HTTP status";
   1082   }
   1083   *http_status = (unsigned int) status;
   1084   curl_easy_cleanup (c);
   1085   return NULL;
   1086 }
   1087 
   1088 
   1089 const char *
   1090 MHDT_client_send_digest_auth (
   1091   const void *cls,
   1092   struct MHDT_PhaseContext *pc)
   1093 {
   1094   const char *cred = cls;
   1095   const char *ret;
   1096   unsigned int status;
   1097 
   1098   ret = send_digest_auth (cred,
   1099                           pc,
   1100                           &status);
   1101   if (NULL != ret)
   1102     return ret;
   1103   if (MHD_HTTP_STATUS_NO_CONTENT != status)
   1104     return "invalid HTTP response code";
   1105   return NULL;
   1106 }
   1107 
   1108 
   1109 const char *
   1110 MHDT_client_fail_digest_auth (
   1111   const void *cls,
   1112   struct MHDT_PhaseContext *pc)
   1113 {
   1114   const char *cred = cls;
   1115   const char *ret;
   1116   unsigned int status;
   1117 
   1118   ret = send_digest_auth (cred,
   1119                           pc,
   1120                           &status);
   1121   if (NULL != ret)
   1122     return ret;
   1123   if (MHD_HTTP_STATUS_FORBIDDEN != status)
   1124     return "invalid HTTP response code";
   1125   return NULL;
   1126 }