libmicrohttpd2

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

libtest_convenience_client_request.c (28107B)


      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_TIMEOUT_MS,
    271                          500))
    272       || (CURLE_OK !=
    273           curl_easy_setopt (c,
    274                             CURLOPT_CONNECTTIMEOUT_MS,
    275                             50)) ||
    276 #ifdef CURLOPT_SERVER_RESPONSE_TIMEOUT_MS
    277       (CURLE_OK !=
    278        curl_easy_setopt (c,
    279                          CURLOPT_SERVER_RESPONSE_TIMEOUT_MS,
    280                          250)) ||
    281 #else
    282       (CURLE_OK !=
    283        curl_easy_setopt (c,
    284                          CURLOPT_SERVER_RESPONSE_TIMEOUT,
    285                          1)) ||
    286 #endif
    287       (CURLE_OK !=
    288        curl_easy_setopt (c,
    289                          CURLOPT_FRESH_CONNECT,
    290                          1L))
    291       || (CURLE_OK !=
    292           curl_easy_setopt (c,
    293                             CURLOPT_FORBID_REUSE,
    294                             1L))
    295       || (CURLE_OK !=
    296           curl_easy_setopt (c,
    297                             CURLOPT_VERBOSE,
    298                             0)))
    299   {
    300     curl_easy_cleanup (c);
    301     return "Failed to set curl options";
    302   }
    303   {
    304     /* Force curl to do the request to 127.0.0.1 regardless of
    305        hostname */
    306     const char *host;
    307     const char *end;
    308     char ri[1024];
    309 
    310     if (0 == strncasecmp (base_url,
    311                           "https://",
    312                           strlen ("https://")))
    313       host = &base_url[strlen ("https://")];
    314     else
    315       host = &base_url[strlen ("http://")];
    316     end = strchr (host, '/');
    317     if (NULL == end)
    318       end = host + strlen (host);
    319     snprintf (ri,
    320               sizeof (ri),
    321               "%.*s:127.0.0.1",
    322               (int)(end - host),
    323               host);
    324     pc->hosts = curl_slist_append (NULL,
    325                                    ri);
    326     if (CURLE_OK !=
    327         curl_easy_setopt (c,
    328                           CURLOPT_RESOLVE,
    329                           pc->hosts))
    330     {
    331       curl_easy_cleanup (c);
    332       return "Failed to override DNS";
    333     }
    334   }
    335   if (0 == strncasecmp (base_url,
    336                         "https://",
    337                         strlen ("https://")))
    338   {
    339     struct MHDT_Phase *phase = pc->phase;
    340 
    341     if (phase->check_server_cert)
    342     {
    343       if (CURLE_OK !=
    344           curl_easy_setopt (c,
    345                             CURLOPT_CAINFO,
    346                             "data/root-ca.crt"))
    347       {
    348         curl_easy_cleanup (c);
    349         return "Failed to override root CA";
    350       }
    351     }
    352     else
    353     {
    354       /* disable certificate checking */
    355       if ((CURLE_OK !=
    356            curl_easy_setopt (c,
    357                              CURLOPT_SSL_VERIFYPEER,
    358                              0L))
    359           || (CURLE_OK !=
    360               curl_easy_setopt (c,
    361                                 CURLOPT_SSL_VERIFYHOST,
    362                                 0L)))
    363       {
    364         curl_easy_cleanup (c);
    365         return "Failed to disable X509 server certificate checks";
    366       }
    367     }
    368     if (NULL != phase->client_cert)
    369     {
    370       if (CURLE_OK !=
    371           curl_easy_setopt (c,
    372                             CURLOPT_SSLCERT,
    373                             phase->client_cert))
    374       {
    375         curl_easy_cleanup (c);
    376         return "Failed to set client certificate";
    377       }
    378     }
    379   }
    380   return NULL;
    381 }
    382 
    383 
    384 /**
    385  * Create a curl handle for the given @a pc.
    386  *
    387  * @param pc current phase context with options to use
    388  * @return NULL on error
    389  */
    390 static CURL *
    391 setup_curl (const struct MHDT_PhaseContext *pc)
    392 {
    393   struct MHDT_Phase *p = pc->phase;
    394   CURL *c;
    395 
    396   c = curl_easy_init ();
    397   if (NULL == c)
    398     return NULL;
    399   switch (p->http_version)
    400   {
    401   case 0: /* unset == any */
    402     break;
    403   case 1:
    404     if (CURLE_OK !=
    405         curl_easy_setopt (c,
    406                           CURLOPT_HTTP_VERSION,
    407                           CURL_HTTP_VERSION_1_1))
    408     {
    409       curl_easy_cleanup (c);
    410       fprintf (stderr,
    411                "HTTP/1 not supported by curl?\n");
    412       return NULL;
    413     }
    414     break;
    415   case 2: /* HTTP/2 */
    416     if (p->use_tls)
    417     {
    418       if (CURLE_OK !=
    419           curl_easy_setopt (c,
    420                             CURLOPT_HTTP_VERSION,
    421                             CURL_HTTP_VERSION_2TLS))
    422       {
    423         curl_easy_cleanup (c);
    424         fprintf (stderr,
    425                  "HTTP/2 not supported by curl?\n");
    426         return NULL;
    427       }
    428     }
    429     else
    430     {
    431       if (CURLE_OK !=
    432           curl_easy_setopt (c,
    433                             CURLOPT_HTTP_VERSION,
    434                             CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE))
    435       {
    436         curl_easy_cleanup (c);
    437         fprintf (stderr,
    438                  "HTTP/2 not supported by curl?\n");
    439         return NULL;
    440       }
    441     }
    442     break;
    443   case 3:
    444     abort (); // not yet supported
    445     break;
    446   default:
    447     abort ();
    448     break;
    449   }
    450   return c;
    451 }
    452 
    453 
    454 const char *
    455 MHDT_client_get_host (const void *cls,
    456                       struct MHDT_PhaseContext *pc)
    457 {
    458   const char *host = cls;
    459   const char *err;
    460   size_t alen = strlen (host);
    461   CURL *c;
    462   size_t blen = strlen (pc->base_url);
    463   char u[alen + blen + 1];
    464   const char *slash = strchr (pc->base_url,
    465                               '/');
    466   const char *colon;
    467 
    468   if (NULL == slash)
    469     return "'/' missing in base URL";
    470   colon = strchr (slash,
    471                   ':');
    472   if (NULL == colon)
    473     return "':' missing in base URL";
    474   snprintf (u,
    475             sizeof (u),
    476             "https://%s%s",
    477             host,
    478             colon);
    479   c = setup_curl (pc);
    480   if (NULL == c)
    481     return "Failed to initialize Curl handle";
    482   err = set_url (c,
    483                  u,
    484                  pc);
    485   if (NULL != err)
    486     return err;
    487   PERFORM_REQUEST (c);
    488   CHECK_STATUS (c,
    489                 MHD_HTTP_STATUS_OK);
    490   curl_easy_cleanup (c);
    491   return NULL;
    492 }
    493 
    494 
    495 const char *
    496 MHDT_client_get_root (
    497   const void *cls,
    498   struct MHDT_PhaseContext *pc)
    499 {
    500   const char *text = cls;
    501   CURL *c;
    502   const char *err;
    503   DECLARE_WB (text);
    504 
    505   c = setup_curl (pc);
    506   if (NULL == c)
    507     return "Failed to initialize Curl handle";
    508   err = set_url (c,
    509                  pc->base_url,
    510                  pc);
    511   if (NULL != err)
    512     return err;
    513   SETUP_WB (c);
    514   PERFORM_REQUEST (c);
    515   CHECK_STATUS (c,
    516                 MHD_HTTP_STATUS_OK);
    517   curl_easy_cleanup (c);
    518   CHECK_WB (text);
    519   return NULL;
    520 }
    521 
    522 
    523 const char *
    524 MHDT_client_get_with_query (
    525   const void *cls,
    526   struct MHDT_PhaseContext *pc)
    527 {
    528   const char *args = cls;
    529   const char *err;
    530   size_t alen = strlen (args);
    531   CURL *c;
    532   size_t blen = strlen (pc->base_url);
    533   char u[alen + blen + 1];
    534 
    535   memcpy (u,
    536           pc->base_url,
    537           blen);
    538   memcpy (u + blen,
    539           args,
    540           alen);
    541   u[alen + blen] = '\0';
    542   c = setup_curl (pc);
    543   if (NULL == c)
    544     return "Failed to initialize Curl handle";
    545   err = set_url (c,
    546                  u,
    547                  pc);
    548   if (NULL != err)
    549     return err;
    550   PERFORM_REQUEST (c);
    551   CHECK_STATUS (c,
    552                 MHD_HTTP_STATUS_NO_CONTENT);
    553   curl_easy_cleanup (c);
    554   return NULL;
    555 }
    556 
    557 
    558 const char *
    559 MHDT_client_set_header (
    560   const void *cls,
    561   struct MHDT_PhaseContext *pc)
    562 {
    563   const char *hdr = cls;
    564   const char *err;
    565   CURL *c;
    566   CURLcode res;
    567   struct curl_slist *slist;
    568 
    569   c = setup_curl (pc);
    570   if (NULL == c)
    571     return "Failed to initialize Curl handle";
    572   err = set_url (c,
    573                  pc->base_url,
    574                  pc);
    575   if (NULL != err)
    576     return err;
    577   slist = curl_slist_append (NULL,
    578                              hdr);
    579   if (CURLE_OK !=
    580       curl_easy_setopt (c,
    581                         CURLOPT_HTTPHEADER,
    582                         slist))
    583   {
    584     curl_easy_cleanup (c);
    585     curl_slist_free_all (slist);
    586     return "Failed to set custom header for curl request";
    587   }
    588   res = curl_easy_perform (c);
    589   curl_slist_free_all (slist);
    590   if (CURLE_OK != res)
    591   {
    592     curl_easy_cleanup (c);
    593     return "Failed to fetch URL";
    594   }
    595   CHECK_STATUS (c,
    596                 MHD_HTTP_STATUS_NO_CONTENT);
    597   curl_easy_cleanup (c);
    598   return NULL;
    599 }
    600 
    601 
    602 const char *
    603 MHDT_client_expect_header (const void *cls,
    604                            struct MHDT_PhaseContext *pc)
    605 {
    606 #ifdef HAVE_LIBCRUL_NEW_HDR_API
    607   const char *hdr = cls;
    608   const char *err;
    609   size_t hlen = strlen (hdr) + 1;
    610   char key[hlen];
    611   const char *colon = strchr (hdr, ':');
    612   const char *value;
    613   CURL *c;
    614   bool found = false;
    615 
    616   if (NULL == colon)
    617     return "Invalid expected header passed";
    618   memcpy (key,
    619           hdr,
    620           hlen);
    621   key[colon - hdr] = '\0';
    622   value = &key[colon - hdr + 1];
    623   c = setup_curl (pc);
    624   if (NULL == c)
    625     return "Failed to initialize Curl handle";
    626   err = set_url (c,
    627                  pc->base_url,
    628                  pc);
    629   if (NULL != err)
    630     return err;
    631   PERFORM_REQUEST (c);
    632   CHECK_STATUS (c,
    633                 MHD_HTTP_STATUS_NO_CONTENT);
    634   for (size_t index = 0; !found; index++)
    635   {
    636     CURLHcode rval;
    637     struct curl_header *hout;
    638 
    639     rval = curl_easy_header (c,
    640                              key,
    641                              index,
    642                              CURLH_HEADER,
    643                              -1 /* last request */,
    644                              &hout);
    645     if (CURLHE_OK != rval)
    646       break;
    647     found = (0 == strcmp (value,
    648                           hout->value));
    649   }
    650   if (!found)
    651   {
    652     curl_easy_cleanup (c);
    653     return "Expected HTTP response header not found";
    654   }
    655   curl_easy_cleanup (c);
    656   return NULL;
    657 #else  /* ! HAVE_LIBCRUL_NEW_HDR_API */
    658   (void)cls;
    659   (void)pc;
    660   return NULL;
    661 #endif /* ! HAVE_LIBCRUL_NEW_HDR_API */
    662 }
    663 
    664 
    665 /**
    666  * Closure for the read_cb().
    667  */
    668 struct ReadBuffer
    669 {
    670   /**
    671    * Origin of data to upload.
    672    */
    673   const char *buf;
    674 
    675   /**
    676    * Number of bytes in @e buf.
    677    */
    678   size_t len;
    679 
    680   /**
    681    * Current read offset in @e buf.
    682    */
    683   size_t pos;
    684 
    685   /**
    686    * Number of chunks to user when sending.
    687    */
    688   unsigned int chunks;
    689 
    690 };
    691 
    692 
    693 /**
    694  * Callback for CURLOPT_READFUNCTION for uploading
    695  * data to the HTTP server.
    696  *
    697  * @param ptr data uploaded
    698  * @param size size of a member
    699  * @param nmemb number of members
    700  * @param stream must be a `struct ReadBuffer`
    701  * @return bytes processed (size*nmemb) or error
    702  */
    703 static size_t
    704 read_cb (void *ptr,
    705          size_t size,
    706          size_t nmemb,
    707          void *stream)
    708 {
    709   struct ReadBuffer *rb = stream;
    710   size_t limit = size * nmemb;
    711 
    712   if (limit / size != nmemb)
    713     return CURLE_WRITE_ERROR;
    714   if (limit > rb->len - rb->pos)
    715     limit = rb->len - rb->pos;
    716   if ((rb->chunks > 1)
    717       && (limit > 1))
    718   {
    719     limit /= rb->chunks;
    720     rb->chunks--;
    721   }
    722   memcpy (ptr,
    723           rb->buf + rb->pos,
    724           limit);
    725   rb->pos += limit;
    726   return limit;
    727 }
    728 
    729 
    730 const char *
    731 MHDT_client_put_data (
    732   const void *cls,
    733   struct MHDT_PhaseContext *pc)
    734 {
    735   const char *text = cls;
    736   const char *err;
    737   struct ReadBuffer rb = {
    738     .buf = text,
    739     .len = strlen (text)
    740   };
    741   CURL *c;
    742 
    743   c = setup_curl (pc);
    744   if (NULL == c)
    745     return "Failed to initialize Curl handle";
    746   err = set_url (c,
    747                  pc->base_url,
    748                  pc);
    749   if (NULL != err)
    750     return err;
    751   if (CURLE_OK !=
    752       curl_easy_setopt (c,
    753                         CURLOPT_UPLOAD,
    754                         1L))
    755   {
    756     curl_easy_cleanup (c);
    757     return "Failed to set PUT method for curl request";
    758   }
    759   if (CURLE_OK !=
    760       curl_easy_setopt (c,
    761                         CURLOPT_READFUNCTION,
    762                         &read_cb))
    763   {
    764     curl_easy_cleanup (c);
    765     return "Failed to set READFUNCTION for curl request";
    766   }
    767   if (CURLE_OK !=
    768       curl_easy_setopt (c,
    769                         CURLOPT_READDATA,
    770                         &rb))
    771   {
    772     curl_easy_cleanup (c);
    773     return "Failed to set READFUNCTION for curl request";
    774   }
    775   if (CURLE_OK !=
    776       curl_easy_setopt (c,
    777                         CURLOPT_INFILESIZE_LARGE,
    778                         (curl_off_t)rb.len))
    779   {
    780     curl_easy_cleanup (c);
    781     return "Failed to set INFILESIZE_LARGE for curl request";
    782   }
    783   PERFORM_REQUEST (c);
    784   CHECK_STATUS (c,
    785                 MHD_HTTP_STATUS_NO_CONTENT);
    786   curl_easy_cleanup (c);
    787   return NULL;
    788 }
    789 
    790 
    791 const char *
    792 MHDT_client_chunk_data (
    793   const void *cls,
    794   struct MHDT_PhaseContext *pc)
    795 {
    796   const char *text = cls;
    797   const char *err;
    798   struct ReadBuffer rb = {
    799     .buf = text,
    800     .len = strlen (text),
    801     .chunks = 2
    802   };
    803   CURL *c;
    804 
    805   c = setup_curl (pc);
    806   if (NULL == c)
    807     return "Failed to initialize Curl handle";
    808   err = set_url (c,
    809                  pc->base_url,
    810                  pc);
    811   if (NULL != err)
    812     return err;
    813   if (CURLE_OK !=
    814       curl_easy_setopt (c,
    815                         CURLOPT_UPLOAD,
    816                         1L))
    817   {
    818     curl_easy_cleanup (c);
    819     return "Failed to set PUT method for curl request";
    820   }
    821   if (CURLE_OK !=
    822       curl_easy_setopt (c,
    823                         CURLOPT_READFUNCTION,
    824                         &read_cb))
    825   {
    826     curl_easy_cleanup (c);
    827     return "Failed to set READFUNCTION for curl request";
    828   }
    829   if (CURLE_OK !=
    830       curl_easy_setopt (c,
    831                         CURLOPT_READDATA,
    832                         &rb))
    833   {
    834     curl_easy_cleanup (c);
    835     return "Failed to set READFUNCTION for curl request";
    836   }
    837   PERFORM_REQUEST (c);
    838   CHECK_STATUS (c,
    839                 MHD_HTTP_STATUS_NO_CONTENT);
    840   curl_easy_cleanup (c);
    841   return NULL;
    842 }
    843 
    844 
    845 const char *
    846 MHDT_client_do_post (
    847   const void *cls,
    848   struct MHDT_PhaseContext *pc)
    849 {
    850   const struct MHDT_PostInstructions *pi = cls;
    851   const char *err;
    852   CURL *c;
    853   struct curl_slist *request_hdr = NULL;
    854 
    855   /* reset wants in case we re-use the array */
    856   if (NULL != pi->wants)
    857   {
    858     for (unsigned int i = 0; NULL != pi->wants[i].key; i++)
    859     {
    860       pi->wants[i].value_off = 0;
    861       pi->wants[i].satisfied = false;
    862     }
    863   }
    864   c = setup_curl (pc);
    865   if (NULL == c)
    866     return "Failed to initialize Curl handle";
    867   err = set_url (c,
    868                  pc->base_url,
    869                  pc);
    870   if (NULL != err)
    871     return err;
    872   if (CURLE_OK !=
    873       curl_easy_setopt (c,
    874                         CURLOPT_POST,
    875                         1L))
    876   {
    877     curl_easy_cleanup (c);
    878     return "Failed to set POST method for curl request";
    879   }
    880   if (CURLE_OK !=
    881       curl_easy_setopt (c,
    882                         CURLOPT_POSTFIELDS,
    883                         pi->postdata))
    884   {
    885     curl_easy_cleanup (c);
    886     return "Failed to set POSTFIELDS for curl request";
    887   }
    888   if (0 != pi->postdata_size)
    889   {
    890     if (CURLE_OK !=
    891         curl_easy_setopt (c,
    892                           CURLOPT_POSTFIELDSIZE_LARGE,
    893                           (curl_off_t)pi->postdata_size))
    894     {
    895       curl_easy_cleanup (c);
    896       return "Failed to set POSTFIELDS for curl request";
    897     }
    898   }
    899   if (NULL != pi->postheader)
    900   {
    901     request_hdr = curl_slist_append (request_hdr,
    902                                      pi->postheader);
    903   }
    904   if (CURLE_OK !=
    905       curl_easy_setopt (c,
    906                         CURLOPT_HTTPHEADER,
    907                         request_hdr))
    908   {
    909     curl_easy_cleanup (c);
    910     curl_slist_free_all (request_hdr);
    911     return "Failed to set HTTPHEADER for curl request";
    912   }
    913   PERFORM_REQUEST (c);
    914   CHECK_STATUS (c,
    915                 MHD_HTTP_STATUS_NO_CONTENT);
    916   curl_easy_cleanup (c);
    917   curl_slist_free_all (request_hdr);
    918   if (NULL != pi->wants)
    919   {
    920     for (unsigned int i = 0; NULL != pi->wants[i].key; i++)
    921     {
    922       if (!pi->wants[i].satisfied)
    923       {
    924         fprintf (stderr,
    925                  "Server did not correctly detect key '%s'\n",
    926                  pi->wants[i].key);
    927         return "key-value data not matched by server";
    928       }
    929     }
    930   }
    931   return NULL;
    932 }
    933 
    934 
    935 /**
    936  * Send HTTP request with basic authentication.
    937  *
    938  * @param cred $USERNAME:$PASSWORD to use
    939  * @param[in,out] phase context
    940  * @param[out] http_status set to HTTP status
    941  * @return error message, NULL on success
    942  */
    943 static const char *
    944 send_basic_auth (const char *cred,
    945                  struct MHDT_PhaseContext *pc,
    946                  unsigned int *http_status)
    947 {
    948   CURL *c;
    949   const char *err;
    950   long status;
    951   char *pass = strchr (cred, ':');
    952   char *user;
    953 
    954   if (NULL == pass)
    955     return "invalid credential given";
    956   user = strndup (cred,
    957                   pass - cred);
    958   pass++;
    959   c = setup_curl (pc);
    960   if (NULL == c)
    961   {
    962     free (user);
    963     return "Failed to initialize Curl handle";
    964   }
    965   err = set_url (c,
    966                  pc->base_url,
    967                  pc);
    968   if (NULL != err)
    969   {
    970     free (user);
    971     curl_easy_cleanup (c);
    972     return err;
    973   }
    974   if ((CURLE_OK !=
    975        curl_easy_setopt (c,
    976                          CURLOPT_HTTPAUTH,
    977                          (long)CURLAUTH_BASIC))
    978       || (CURLE_OK !=
    979           curl_easy_setopt (c,
    980                             CURLOPT_USERNAME,
    981                             user))
    982       || (CURLE_OK !=
    983           curl_easy_setopt (c,
    984                             CURLOPT_PASSWORD,
    985                             pass)))
    986   {
    987     curl_easy_cleanup (c);
    988     free (user);
    989     return "Failed to set basic authentication header for curl request";
    990   }
    991   free (user);
    992   PERFORM_REQUEST (c);
    993   if (CURLE_OK !=
    994       curl_easy_getinfo (c,
    995                          CURLINFO_RESPONSE_CODE,
    996                          &status))
    997   {
    998     return "Failed to get HTTP status";
    999   }
   1000   *http_status = (unsigned int)status;
   1001   curl_easy_cleanup (c);
   1002   return NULL;
   1003 }
   1004 
   1005 
   1006 const char *
   1007 MHDT_client_send_basic_auth (
   1008   const void *cls,
   1009   struct MHDT_PhaseContext *pc)
   1010 {
   1011   const char *cred = cls;
   1012   const char *ret;
   1013   unsigned int status;
   1014 
   1015   ret = send_basic_auth (cred,
   1016                          pc,
   1017                          &status);
   1018   if (NULL != ret)
   1019     return ret;
   1020   if (MHD_HTTP_STATUS_NO_CONTENT != status)
   1021     return "invalid HTTP response code";
   1022   return NULL;
   1023 }
   1024 
   1025 
   1026 const char *
   1027 MHDT_client_fail_basic_auth (
   1028   const void *cls,
   1029   struct MHDT_PhaseContext *pc)
   1030 {
   1031   const char *cred = cls;
   1032   const char *ret;
   1033   unsigned int status;
   1034 
   1035   ret = send_basic_auth (cred,
   1036                          pc,
   1037                          &status);
   1038   if (NULL != ret)
   1039     return ret;
   1040   if (MHD_HTTP_STATUS_UNAUTHORIZED != status)
   1041     return "invalid HTTP response code";
   1042   return NULL;
   1043 }
   1044 
   1045 
   1046 /**
   1047  * Send HTTP request with digest authentication.
   1048  *
   1049  * @param cred $USERNAME:$PASSWORD to use
   1050  * @param[in,out] phase context
   1051  * @param[out] http_status set to HTTP status
   1052  * @return error message, NULL on success
   1053  */
   1054 static const char *
   1055 send_digest_auth (const char *cred,
   1056                   struct MHDT_PhaseContext *pc,
   1057                   unsigned int *http_status)
   1058 {
   1059   CURL *c;
   1060   const char *err;
   1061   long status;
   1062   char *pass = strchr (cred, ':');
   1063   char *user;
   1064 
   1065   if (NULL == pass)
   1066     return "invalid credential given";
   1067   user = strndup (cred,
   1068                   pass - cred);
   1069   pass++;
   1070   c = setup_curl (pc);
   1071   if (NULL == c)
   1072   {
   1073     free (user);
   1074     return "Failed to initialize Curl handle";
   1075   }
   1076   err = set_url (c,
   1077                  pc->base_url,
   1078                  pc);
   1079   if (NULL != err)
   1080   {
   1081     free (user);
   1082     curl_easy_cleanup (c);
   1083     return err;
   1084   }
   1085   if ((CURLE_OK !=
   1086        curl_easy_setopt (c,
   1087                          CURLOPT_HTTPAUTH,
   1088                          (long)CURLAUTH_DIGEST))
   1089       || (CURLE_OK !=
   1090           curl_easy_setopt (c,
   1091                             CURLOPT_USERNAME,
   1092                             user))
   1093       || (CURLE_OK !=
   1094           curl_easy_setopt (c,
   1095                             CURLOPT_PASSWORD,
   1096                             pass)))
   1097   {
   1098     curl_easy_cleanup (c);
   1099     free (user);
   1100     return "Failed to set digest authentication header for curl request";
   1101   }
   1102   free (user);
   1103   PERFORM_REQUEST (c);
   1104   if (CURLE_OK !=
   1105       curl_easy_getinfo (c,
   1106                          CURLINFO_RESPONSE_CODE,
   1107                          &status))
   1108   {
   1109     return "Failed to get HTTP status";
   1110   }
   1111   *http_status = (unsigned int)status;
   1112   curl_easy_cleanup (c);
   1113   return NULL;
   1114 }
   1115 
   1116 
   1117 const char *
   1118 MHDT_client_send_digest_auth (
   1119   const void *cls,
   1120   struct MHDT_PhaseContext *pc)
   1121 {
   1122   const char *cred = cls;
   1123   const char *ret;
   1124   unsigned int status;
   1125 
   1126   ret = send_digest_auth (cred,
   1127                           pc,
   1128                           &status);
   1129   if (NULL != ret)
   1130     return ret;
   1131   if (MHD_HTTP_STATUS_NO_CONTENT != status)
   1132     return "invalid HTTP response code";
   1133   return NULL;
   1134 }
   1135 
   1136 
   1137 const char *
   1138 MHDT_client_fail_digest_auth (
   1139   const void *cls,
   1140   struct MHDT_PhaseContext *pc)
   1141 {
   1142   const char *cred = cls;
   1143   const char *ret;
   1144   unsigned int status;
   1145 
   1146   ret = send_digest_auth (cred,
   1147                           pc,
   1148                           &status);
   1149   if (NULL != ret)
   1150     return ret;
   1151   if (MHD_HTTP_STATUS_FORBIDDEN != status)
   1152     return "invalid HTTP response code";
   1153   return NULL;
   1154 }