libmicrohttpd2

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

libtest_convenience_client_request.c (28069B)


      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; (void) pc;
    659   return NULL;
    660 #endif /* ! HAVE_LIBCRUL_NEW_HDR_API */
    661 }
    662 
    663 
    664 /**
    665  * Closure for the read_cb().
    666  */
    667 struct ReadBuffer
    668 {
    669   /**
    670    * Origin of data to upload.
    671    */
    672   const char *buf;
    673 
    674   /**
    675    * Number of bytes in @e buf.
    676    */
    677   size_t len;
    678 
    679   /**
    680    * Current read offset in @e buf.
    681    */
    682   size_t pos;
    683 
    684   /**
    685    * Number of chunks to user when sending.
    686    */
    687   unsigned int chunks;
    688 
    689 };
    690 
    691 
    692 /**
    693  * Callback for CURLOPT_READFUNCTION for uploading
    694  * data to the HTTP server.
    695  *
    696  * @param ptr data uploaded
    697  * @param size size of a member
    698  * @param nmemb number of members
    699  * @param stream must be a `struct ReadBuffer`
    700  * @return bytes processed (size*nmemb) or error
    701  */
    702 static size_t
    703 read_cb (void *ptr,
    704          size_t size,
    705          size_t nmemb,
    706          void *stream)
    707 {
    708   struct ReadBuffer *rb = stream;
    709   size_t limit = size * nmemb;
    710 
    711   if (limit / size != nmemb)
    712     return CURLE_WRITE_ERROR;
    713   if (limit > rb->len - rb->pos)
    714     limit = rb->len - rb->pos;
    715   if ( (rb->chunks > 1) &&
    716        (limit > 1) )
    717   {
    718     limit /= rb->chunks;
    719     rb->chunks--;
    720   }
    721   memcpy (ptr,
    722           rb->buf + rb->pos,
    723           limit);
    724   rb->pos += limit;
    725   return limit;
    726 }
    727 
    728 
    729 const char *
    730 MHDT_client_put_data (
    731   const void *cls,
    732   struct MHDT_PhaseContext *pc)
    733 {
    734   const char *text = cls;
    735   const char *err;
    736   struct ReadBuffer rb = {
    737     .buf = text,
    738     .len = strlen (text)
    739   };
    740   CURL *c;
    741 
    742   c = setup_curl (pc);
    743   if (NULL == c)
    744     return "Failed to initialize Curl handle";
    745   err = set_url (c,
    746                  pc->base_url,
    747                  pc);
    748   if (NULL != err)
    749     return err;
    750   if (CURLE_OK !=
    751       curl_easy_setopt (c,
    752                         CURLOPT_UPLOAD,
    753                         1L))
    754   {
    755     curl_easy_cleanup (c);
    756     return "Failed to set PUT method for curl request";
    757   }
    758   if (CURLE_OK !=
    759       curl_easy_setopt (c,
    760                         CURLOPT_READFUNCTION,
    761                         &read_cb))
    762   {
    763     curl_easy_cleanup (c);
    764     return "Failed to set READFUNCTION for curl request";
    765   }
    766   if (CURLE_OK !=
    767       curl_easy_setopt (c,
    768                         CURLOPT_READDATA,
    769                         &rb))
    770   {
    771     curl_easy_cleanup (c);
    772     return "Failed to set READFUNCTION for curl request";
    773   }
    774   if (CURLE_OK !=
    775       curl_easy_setopt (c,
    776                         CURLOPT_INFILESIZE_LARGE,
    777                         (curl_off_t) rb.len))
    778   {
    779     curl_easy_cleanup (c);
    780     return "Failed to set INFILESIZE_LARGE for curl request";
    781   }
    782   PERFORM_REQUEST (c);
    783   CHECK_STATUS (c,
    784                 MHD_HTTP_STATUS_NO_CONTENT);
    785   curl_easy_cleanup (c);
    786   return NULL;
    787 }
    788 
    789 
    790 const char *
    791 MHDT_client_chunk_data (
    792   const void *cls,
    793   struct MHDT_PhaseContext *pc)
    794 {
    795   const char *text = cls;
    796   const char *err;
    797   struct ReadBuffer rb = {
    798     .buf = text,
    799     .len = strlen (text),
    800     .chunks = 2
    801   };
    802   CURL *c;
    803 
    804   c = setup_curl (pc);
    805   if (NULL == c)
    806     return "Failed to initialize Curl handle";
    807   err = set_url (c,
    808                  pc->base_url,
    809                  pc);
    810   if (NULL != err)
    811     return err;
    812   if (CURLE_OK !=
    813       curl_easy_setopt (c,
    814                         CURLOPT_UPLOAD,
    815                         1L))
    816   {
    817     curl_easy_cleanup (c);
    818     return "Failed to set PUT method for curl request";
    819   }
    820   if (CURLE_OK !=
    821       curl_easy_setopt (c,
    822                         CURLOPT_READFUNCTION,
    823                         &read_cb))
    824   {
    825     curl_easy_cleanup (c);
    826     return "Failed to set READFUNCTION for curl request";
    827   }
    828   if (CURLE_OK !=
    829       curl_easy_setopt (c,
    830                         CURLOPT_READDATA,
    831                         &rb))
    832   {
    833     curl_easy_cleanup (c);
    834     return "Failed to set READFUNCTION for curl request";
    835   }
    836   PERFORM_REQUEST (c);
    837   CHECK_STATUS (c,
    838                 MHD_HTTP_STATUS_NO_CONTENT);
    839   curl_easy_cleanup (c);
    840   return NULL;
    841 }
    842 
    843 
    844 const char *
    845 MHDT_client_do_post (
    846   const void *cls,
    847   struct MHDT_PhaseContext *pc)
    848 {
    849   const struct MHDT_PostInstructions *pi = cls;
    850   const char *err;
    851   CURL *c;
    852   struct curl_slist *request_hdr = NULL;
    853 
    854   /* reset wants in case we re-use the array */
    855   if (NULL != pi->wants)
    856   {
    857     for (unsigned int i = 0; NULL != pi->wants[i].key; i++)
    858     {
    859       pi->wants[i].value_off = 0;
    860       pi->wants[i].satisfied = false;
    861     }
    862   }
    863   c = setup_curl (pc);
    864   if (NULL == c)
    865     return "Failed to initialize Curl handle";
    866   err = set_url (c,
    867                  pc->base_url,
    868                  pc);
    869   if (NULL != err)
    870     return err;
    871   if (CURLE_OK !=
    872       curl_easy_setopt (c,
    873                         CURLOPT_POST,
    874                         1L))
    875   {
    876     curl_easy_cleanup (c);
    877     return "Failed to set POST method for curl request";
    878   }
    879   if (CURLE_OK !=
    880       curl_easy_setopt (c,
    881                         CURLOPT_POSTFIELDS,
    882                         pi->postdata))
    883   {
    884     curl_easy_cleanup (c);
    885     return "Failed to set POSTFIELDS for curl request";
    886   }
    887   if (0 != pi->postdata_size)
    888   {
    889     if (CURLE_OK !=
    890         curl_easy_setopt (c,
    891                           CURLOPT_POSTFIELDSIZE_LARGE,
    892                           (curl_off_t) pi->postdata_size))
    893     {
    894       curl_easy_cleanup (c);
    895       return "Failed to set POSTFIELDS for curl request";
    896     }
    897   }
    898   if (NULL != pi->postheader)
    899   {
    900     request_hdr = curl_slist_append (request_hdr,
    901                                      pi->postheader);
    902   }
    903   if (CURLE_OK !=
    904       curl_easy_setopt (c,
    905                         CURLOPT_HTTPHEADER,
    906                         request_hdr))
    907   {
    908     curl_easy_cleanup (c);
    909     curl_slist_free_all (request_hdr);
    910     return "Failed to set HTTPHEADER for curl request";
    911   }
    912   PERFORM_REQUEST (c);
    913   CHECK_STATUS (c,
    914                 MHD_HTTP_STATUS_NO_CONTENT);
    915   curl_easy_cleanup (c);
    916   curl_slist_free_all (request_hdr);
    917   if (NULL != pi->wants)
    918   {
    919     for (unsigned int i = 0; NULL != pi->wants[i].key; i++)
    920     {
    921       if (! pi->wants[i].satisfied)
    922       {
    923         fprintf (stderr,
    924                  "Server did not correctly detect key '%s'\n",
    925                  pi->wants[i].key);
    926         return "key-value data not matched by server";
    927       }
    928     }
    929   }
    930   return NULL;
    931 }
    932 
    933 
    934 /**
    935  * Send HTTP request with basic authentication.
    936  *
    937  * @param cred $USERNAME:$PASSWORD to use
    938  * @param[in,out] phase context
    939  * @param[out] http_status set to HTTP status
    940  * @return error message, NULL on success
    941  */
    942 static const char *
    943 send_basic_auth (const char *cred,
    944                  struct MHDT_PhaseContext *pc,
    945                  unsigned int *http_status)
    946 {
    947   CURL *c;
    948   const char *err;
    949   long status;
    950   char *pass = strchr (cred, ':');
    951   char *user;
    952 
    953   if (NULL == pass)
    954     return "invalid credential given";
    955   user = strndup (cred,
    956                   pass - cred);
    957   pass++;
    958   c = setup_curl (pc);
    959   if (NULL == c)
    960   {
    961     free (user);
    962     return "Failed to initialize Curl handle";
    963   }
    964   err = set_url (c,
    965                  pc->base_url,
    966                  pc);
    967   if (NULL != err)
    968   {
    969     free (user);
    970     curl_easy_cleanup (c);
    971     return err;
    972   }
    973   if ( (CURLE_OK !=
    974         curl_easy_setopt (c,
    975                           CURLOPT_HTTPAUTH,
    976                           (long) CURLAUTH_BASIC)) ||
    977        (CURLE_OK !=
    978         curl_easy_setopt (c,
    979                           CURLOPT_USERNAME,
    980                           user)) ||
    981        (CURLE_OK !=
    982         curl_easy_setopt (c,
    983                           CURLOPT_PASSWORD,
    984                           pass)) )
    985   {
    986     curl_easy_cleanup (c);
    987     free (user);
    988     return "Failed to set basic authentication header for curl request";
    989   }
    990   free (user);
    991   PERFORM_REQUEST (c);
    992   if (CURLE_OK !=
    993       curl_easy_getinfo (c,
    994                          CURLINFO_RESPONSE_CODE,
    995                          &status))
    996   {
    997     return "Failed to get HTTP status";
    998   }
    999   *http_status = (unsigned int) status;
   1000   curl_easy_cleanup (c);
   1001   return NULL;
   1002 }
   1003 
   1004 
   1005 const char *
   1006 MHDT_client_send_basic_auth (
   1007   const void *cls,
   1008   struct MHDT_PhaseContext *pc)
   1009 {
   1010   const char *cred = cls;
   1011   const char *ret;
   1012   unsigned int status;
   1013 
   1014   ret = send_basic_auth (cred,
   1015                          pc,
   1016                          &status);
   1017   if (NULL != ret)
   1018     return ret;
   1019   if (MHD_HTTP_STATUS_NO_CONTENT != status)
   1020     return "invalid HTTP response code";
   1021   return NULL;
   1022 }
   1023 
   1024 
   1025 const char *
   1026 MHDT_client_fail_basic_auth (
   1027   const void *cls,
   1028   struct MHDT_PhaseContext *pc)
   1029 {
   1030   const char *cred = cls;
   1031   const char *ret;
   1032   unsigned int status;
   1033 
   1034   ret = send_basic_auth (cred,
   1035                          pc,
   1036                          &status);
   1037   if (NULL != ret)
   1038     return ret;
   1039   if (MHD_HTTP_STATUS_UNAUTHORIZED != status)
   1040     return "invalid HTTP response code";
   1041   return NULL;
   1042 }
   1043 
   1044 
   1045 /**
   1046  * Send HTTP request with digest authentication.
   1047  *
   1048  * @param cred $USERNAME:$PASSWORD to use
   1049  * @param[in,out] phase context
   1050  * @param[out] http_status set to HTTP status
   1051  * @return error message, NULL on success
   1052  */
   1053 static const char *
   1054 send_digest_auth (const char *cred,
   1055                   struct MHDT_PhaseContext *pc,
   1056                   unsigned int *http_status)
   1057 {
   1058   CURL *c;
   1059   const char *err;
   1060   long status;
   1061   char *pass = strchr (cred, ':');
   1062   char *user;
   1063 
   1064   if (NULL == pass)
   1065     return "invalid credential given";
   1066   user = strndup (cred,
   1067                   pass - cred);
   1068   pass++;
   1069   c = setup_curl (pc);
   1070   if (NULL == c)
   1071   {
   1072     free (user);
   1073     return "Failed to initialize Curl handle";
   1074   }
   1075   err = set_url (c,
   1076                  pc->base_url,
   1077                  pc);
   1078   if (NULL != err)
   1079   {
   1080     free (user);
   1081     curl_easy_cleanup (c);
   1082     return err;
   1083   }
   1084   if ( (CURLE_OK !=
   1085         curl_easy_setopt (c,
   1086                           CURLOPT_HTTPAUTH,
   1087                           (long) CURLAUTH_DIGEST)) ||
   1088        (CURLE_OK !=
   1089         curl_easy_setopt (c,
   1090                           CURLOPT_USERNAME,
   1091                           user)) ||
   1092        (CURLE_OK !=
   1093         curl_easy_setopt (c,
   1094                           CURLOPT_PASSWORD,
   1095                           pass)) )
   1096   {
   1097     curl_easy_cleanup (c);
   1098     free (user);
   1099     return "Failed to set digest authentication header for curl request";
   1100   }
   1101   free (user);
   1102   PERFORM_REQUEST (c);
   1103   if (CURLE_OK !=
   1104       curl_easy_getinfo (c,
   1105                          CURLINFO_RESPONSE_CODE,
   1106                          &status))
   1107   {
   1108     return "Failed to get HTTP status";
   1109   }
   1110   *http_status = (unsigned int) status;
   1111   curl_easy_cleanup (c);
   1112   return NULL;
   1113 }
   1114 
   1115 
   1116 const char *
   1117 MHDT_client_send_digest_auth (
   1118   const void *cls,
   1119   struct MHDT_PhaseContext *pc)
   1120 {
   1121   const char *cred = cls;
   1122   const char *ret;
   1123   unsigned int status;
   1124 
   1125   ret = send_digest_auth (cred,
   1126                           pc,
   1127                           &status);
   1128   if (NULL != ret)
   1129     return ret;
   1130   if (MHD_HTTP_STATUS_NO_CONTENT != status)
   1131     return "invalid HTTP response code";
   1132   return NULL;
   1133 }
   1134 
   1135 
   1136 const char *
   1137 MHDT_client_fail_digest_auth (
   1138   const void *cls,
   1139   struct MHDT_PhaseContext *pc)
   1140 {
   1141   const char *cred = cls;
   1142   const char *ret;
   1143   unsigned int status;
   1144 
   1145   ret = send_digest_auth (cred,
   1146                           pc,
   1147                           &status);
   1148   if (NULL != ret)
   1149     return ret;
   1150   if (MHD_HTTP_STATUS_FORBIDDEN != status)
   1151     return "invalid HTTP response code";
   1152   return NULL;
   1153 }