libmicrohttpd2

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

libtest_convenience_server_reply.c (28015B)


      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   Copyright (C) 2024 Evgeny Grin (Karlson2k)
      6 
      7   GNU libmicrohttpd is free software; you can redistribute it and/or
      8   modify it under the terms of the GNU Lesser General Public
      9   License as published by the Free Software Foundation; either
     10   version 2.1 of the License, or (at your option) any later version.
     11 
     12   GNU libmicrohttpd is distributed in the hope that it will be useful,
     13   but WITHOUT ANY WARRANTY; without even the implied warranty of
     14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     15   Lesser General Public License for more details.
     16 
     17   Alternatively, you can redistribute GNU libmicrohttpd and/or
     18   modify it under the terms of the GNU General Public License as
     19   published by the Free Software Foundation; either version 2 of
     20   the License, or (at your option) any later version, together
     21   with the eCos exception, as follows:
     22 
     23     As a special exception, if other files instantiate templates or
     24     use macros or inline functions from this file, or you compile this
     25     file and link it with other works to produce a work based on this
     26     file, this file does not by itself cause the resulting work to be
     27     covered by the GNU General Public License. However the source code
     28     for this file must still be made available in accordance with
     29     section (3) of the GNU General Public License v2.
     30 
     31     This exception does not invalidate any other reasons why a work
     32     based on this file might be covered by the GNU General Public
     33     License.
     34 
     35   You should have received copies of the GNU Lesser General Public
     36   License and the GNU General Public License along with this library;
     37   if not, see <https://www.gnu.org/licenses/>.
     38 */
     39 
     40 /**
     41  * @file libtest_convenience_server_reply.c
     42  * @brief convenience functions that generate
     43  *   replies from the server for libtest users
     44  * @author Christian Grothoff
     45  */
     46 #include "libtest.h"
     47 #include <pthread.h>
     48 #include <stdbool.h>
     49 #include <fcntl.h>
     50 #include <stdio.h>
     51 #include <unistd.h>
     52 #include <errno.h>
     53 #include <curl/curl.h>
     54 #include <assert.h>
     55 #include "mhdt_tmpfile.h"
     56 
     57 #ifndef CURL_VERSION_BITS
     58 #  define CURL_VERSION_BITS(x, y, z) ((x) << 16 | (y) << 8 | (z))
     59 #endif
     60 #ifndef CURL_AT_LEAST_VERSION
     61 #  define CURL_AT_LEAST_VERSION(x, y, z) \
     62           (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS (x, y, z))
     63 #endif
     64 
     65 const struct MHD_Action *
     66 MHDT_server_reply_text (
     67   void *cls,
     68   struct MHD_Request *MHD_RESTRICT request,
     69   const struct MHD_String *MHD_RESTRICT path,
     70   enum MHD_HTTP_Method method,
     71   uint_fast64_t upload_size)
     72 {
     73   const char *text = cls;
     74 
     75   (void)path;
     76   (void)method;
     77   (void)upload_size;                              /* Unused */
     78 
     79   return MHD_action_from_response (
     80     request,
     81     MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK,
     82                                      strlen (text),
     83                                      text));
     84 }
     85 
     86 
     87 /**
     88  * Create a temporary file and put the @a text to it.
     89  * Exit the test if the file cannot be created or written.
     90  *
     91  * @param text the text to put to the file
     92  * @return the FD of the file with the @a text
     93  */
     94 static int
     95 tmpfile_with_text (const char *text)
     96 {
     97   size_t written = 0u;
     98   size_t tlen = strlen (text);
     99   int fd;
    100 
    101   fd = MHDT_create_tmpfile ();
    102   if (-1 == fd)
    103   {
    104     fprintf (stderr,
    105              "Failed to create a temporary file\n");
    106     fflush (stderr);
    107     exit (99);
    108   }
    109 
    110   while (written < tlen)
    111   {
    112     ssize_t wr_chunk = write (fd,
    113                               text + written,
    114                               tlen - written);
    115     if (0 == wr_chunk)
    116       break;
    117     else if (0 > wr_chunk)
    118     {
    119       if (EINTR == errno)
    120         continue;
    121       break;
    122     }
    123     written += (size_t)wr_chunk;
    124   }
    125   if (written < tlen)
    126   {
    127     fprintf (stderr,
    128              "Failed to write() a temporary file: %s\n",
    129              strerror (errno));
    130     (void)close (fd);
    131     fflush (stderr);
    132     exit (99);
    133   }
    134   return fd;
    135 }
    136 
    137 
    138 const struct MHD_Action *
    139 MHDT_server_reply_file (
    140   void *cls,
    141   struct MHD_Request *MHD_RESTRICT request,
    142   const struct MHD_String *MHD_RESTRICT path,
    143   enum MHD_HTTP_Method method,
    144   uint_fast64_t upload_size)
    145 {
    146   const char *text = cls;
    147 
    148   (void)path;
    149   (void)method;
    150   (void)upload_size;                              /* Unused */
    151 
    152   return MHD_action_from_response (
    153     request,
    154     MHD_response_from_fd (MHD_HTTP_STATUS_OK,
    155                           tmpfile_with_text (text),
    156                           0 /* offset */,
    157                           strlen (text)));
    158 }
    159 
    160 
    161 const struct MHD_Action *
    162 MHDT_server_reply_file_unknown_size (
    163   void *cls,
    164   struct MHD_Request *MHD_RESTRICT request,
    165   const struct MHD_String *MHD_RESTRICT path,
    166   enum MHD_HTTP_Method method,
    167   uint_fast64_t upload_size)
    168 {
    169   const char *text = cls;
    170 
    171   (void)path;
    172   (void)method;
    173   (void)upload_size;                              /* Unused */
    174 
    175   return MHD_action_from_response (
    176     request,
    177     MHD_response_from_fd (MHD_HTTP_STATUS_OK,
    178                           tmpfile_with_text (text),
    179                           0 /* offset */,
    180                           MHD_SIZE_UNKNOWN));
    181 }
    182 
    183 
    184 const struct MHD_Action *
    185 MHDT_server_reply_with_header (
    186   void *cls,
    187   struct MHD_Request *MHD_RESTRICT request,
    188   const struct MHD_String *MHD_RESTRICT path,
    189   enum MHD_HTTP_Method method,
    190   uint_fast64_t upload_size)
    191 {
    192   const char *header = cls;
    193   size_t hlen = strlen (header) + 1;
    194   char name[hlen];
    195   const char *colon = strchr (header, ':');
    196   const char *value;
    197   struct MHD_Response *resp;
    198 
    199   (void)path;
    200   (void)method;
    201   (void)upload_size;                              /* Unused */
    202 
    203   memcpy (name,
    204           header,
    205           hlen);
    206   name[colon - header] = '\0';
    207   value = &name[colon - header + 1];
    208 
    209   resp = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT);
    210   if (MHD_SC_OK !=
    211       MHD_response_add_header (resp,
    212                                name,
    213                                value))
    214   {
    215     MHD_response_destroy (resp);
    216     return MHD_action_abort_request (request);
    217   }
    218   return MHD_action_from_response (
    219     request,
    220     resp);
    221 }
    222 
    223 
    224 const struct MHD_Action *
    225 MHDT_server_reply_check_query (
    226   void *cls,
    227   struct MHD_Request *MHD_RESTRICT request,
    228   const struct MHD_String *MHD_RESTRICT path,
    229   enum MHD_HTTP_Method method,
    230   uint_fast64_t upload_size)
    231 {
    232   const char *equery = cls;
    233   size_t qlen = strlen (equery) + 1;
    234   char qc[qlen];
    235 
    236   (void)path;
    237   (void)method;
    238   (void)upload_size;                              /* Unused */
    239 
    240   memcpy (qc,
    241           equery,
    242           qlen);
    243   for (const char *tok = strtok (qc, "&");
    244        NULL != tok;
    245        tok = strtok (NULL, "&"))
    246   {
    247     const char *end;
    248     struct MHD_StringNullable sn;
    249     const char *val;
    250 
    251     end = strchr (tok, '=');
    252     if (NULL == end)
    253     {
    254       end = &tok[strlen (tok)];
    255       val = NULL;
    256     }
    257     else
    258     {
    259       val = end + 1;
    260     }
    261     {
    262       size_t alen = (size_t)(end - tok);
    263       char arg[alen + 1];
    264 
    265       memcpy (arg,
    266               tok,
    267               alen);
    268       arg[alen] = '\0';
    269       if (MHD_NO ==
    270           MHD_request_get_value (request,
    271                                  MHD_VK_URI_QUERY_PARAM,
    272                                  arg,
    273                                  &sn))
    274       {
    275         fprintf (stderr,
    276                  "NULL returned for query key %s\n",
    277                  arg);
    278         return MHD_action_abort_request (request);
    279       }
    280       if (NULL == val)
    281       {
    282         if (NULL != sn.cstr)
    283         {
    284           fprintf (stderr,
    285                    "NULL expected for value for query key %s, got %s\n",
    286                    arg,
    287                    sn.cstr);
    288           return MHD_action_abort_request (request);
    289         }
    290       }
    291       else
    292       {
    293         if (NULL == sn.cstr)
    294         {
    295           fprintf (stderr,
    296                    "%s expected for value for query key %s, got NULL\n",
    297                    val,
    298                    arg);
    299           return MHD_action_abort_request (request);
    300         }
    301         if (0 != strcmp (val,
    302                          sn.cstr))
    303         {
    304           fprintf (stderr,
    305                    "%s expected for value for query key %s, got %s\n",
    306                    val,
    307                    arg,
    308                    sn.cstr);
    309           return MHD_action_abort_request (request);
    310         }
    311       }
    312     }
    313   }
    314 
    315   return MHD_action_from_response (
    316     request,
    317     MHD_response_from_empty (
    318       MHD_HTTP_STATUS_NO_CONTENT));
    319 }
    320 
    321 
    322 const struct MHD_Action *
    323 MHDT_server_reply_check_header (
    324   void *cls,
    325   struct MHD_Request *MHD_RESTRICT request,
    326   const struct MHD_String *MHD_RESTRICT path,
    327   enum MHD_HTTP_Method method,
    328   uint_fast64_t upload_size)
    329 {
    330   const char *want = cls;
    331   size_t wlen = strlen (want) + 1;
    332   char key[wlen];
    333   const char *colon = strchr (want, ':');
    334   struct MHD_StringNullable have;
    335   const char *value;
    336 
    337   (void)path;
    338   (void)method;
    339   (void)upload_size;                              /* Unused */
    340 
    341   memcpy (key,
    342           want,
    343           wlen);
    344   if (NULL != colon)
    345   {
    346     key[colon - want] = '\0';
    347     value = &key[colon - want + 1];
    348   }
    349   else
    350   {
    351     value = NULL;
    352   }
    353   if (MHD_NO ==
    354       MHD_request_get_value (request,
    355                              MHD_VK_HEADER,
    356                              key,
    357                              &have))
    358   {
    359     fprintf (stderr,
    360              "Missing client header `%s'\n",
    361              want);
    362     return MHD_action_abort_request (request);
    363   }
    364   if (NULL == value)
    365   {
    366     if (NULL != have.cstr)
    367     {
    368       fprintf (stderr,
    369                "Have unexpected client header `%s': `%s'\n",
    370                key,
    371                have.cstr);
    372       return MHD_action_abort_request (request);
    373     }
    374   }
    375   else
    376   {
    377     if (NULL == have.cstr)
    378     {
    379       fprintf (stderr,
    380                "Missing value for client header `%s'\n",
    381                want);
    382       return MHD_action_abort_request (request);
    383     }
    384     if (0 != strcmp (have.cstr,
    385                      value))
    386     {
    387       fprintf (stderr,
    388                "Client HTTP header `%s' was expected to be `%s' but is `%s'\n",
    389                key,
    390                value,
    391                have.cstr);
    392       return MHD_action_abort_request (request);
    393     }
    394   }
    395   return MHD_action_from_response (
    396     request,
    397     MHD_response_from_empty (
    398       MHD_HTTP_STATUS_NO_CONTENT));
    399 }
    400 
    401 
    402 /**
    403  * Function to process data uploaded by a client.
    404  *
    405  * @param cls the payload we expect to be uploaded as a 0-terminated string
    406  * @param request the request is being processed
    407  * @param content_data_size the size of the @a content_data,
    408  *                          zero when all data have been processed
    409  * @param[in] content_data the uploaded content data,
    410  *                         may be modified in the callback,
    411  *                         valid only until return from the callback,
    412  *                         NULL when all data have been processed
    413  * @return action specifying how to proceed:
    414  *         #MHD_upload_action_continue() to continue upload (for incremental
    415  *         upload processing only),
    416  *         #MHD_upload_action_suspend() to stop reading the upload until
    417  *         the request is resumed,
    418  *         #MHD_upload_action_abort_request() to close the socket,
    419  *         or a response to discard the rest of the upload and transmit
    420  *         the response
    421  * @ingroup action
    422  */
    423 static const struct MHD_UploadAction *
    424 check_upload_cb (void *cls,
    425                  struct MHD_Request *request,
    426                  size_t content_data_size,
    427                  void *content_data)
    428 {
    429   const char *want = cls;
    430   size_t wlen = strlen (want);
    431 
    432   if (content_data_size != wlen)
    433   {
    434     fprintf (stderr,
    435              "Invalid body size given to full upload callback\n");
    436     return MHD_upload_action_abort_request (request);
    437   }
    438   if (0 != memcmp (want,
    439                    content_data,
    440                    wlen))
    441   {
    442     fprintf (stderr,
    443              "Invalid body data given to full upload callback\n");
    444     return MHD_upload_action_abort_request (request);
    445   }
    446   /* success! */
    447   return MHD_upload_action_from_response (
    448     request,
    449     MHD_response_from_empty (
    450       MHD_HTTP_STATUS_NO_CONTENT));
    451 }
    452 
    453 
    454 const struct MHD_Action *
    455 MHDT_server_reply_check_upload (
    456   void *cls,
    457   struct MHD_Request *MHD_RESTRICT request,
    458   const struct MHD_String *MHD_RESTRICT path,
    459   enum MHD_HTTP_Method method,
    460   uint_fast64_t upload_size)
    461 {
    462   const char *want = cls;
    463   size_t wlen = strlen (want);
    464 
    465   (void)path;
    466   (void)method;
    467   (void)upload_size;                              /* Unused */
    468 
    469   return MHD_action_process_upload_full (request,
    470                                          wlen,
    471                                          &check_upload_cb,
    472                                          (void *)want);
    473 }
    474 
    475 
    476 /**
    477  * Closure for #chunk_return.
    478  */
    479 struct ChunkContext
    480 {
    481   /**
    482    * Where we are in the buffer.
    483    */
    484   const char *pos;
    485 };
    486 
    487 
    488 /**
    489  * Function that returns a string in chunks.
    490  *
    491  * @param dyn_cont_cls must be a `struct ChunkContext`
    492  * @param ctx the context to produce the action to return,
    493  *            the pointer is only valid until the callback returns
    494  * @param pos position in the datastream to access;
    495  *        note that if a `struct MHD_Response` object is re-used,
    496  *        it is possible for the same content reader to
    497  *        be queried multiple times for the same data;
    498  *        however, if a `struct MHD_Response` is not re-used,
    499  *        libmicrohttpd guarantees that "pos" will be
    500  *        the sum of all data sizes provided by this callback
    501  * @param[out] buf where to copy the data
    502  * @param max maximum number of bytes to copy to @a buf (size of @a buf)
    503  * @return action to use,
    504  *         NULL in case of any error (the response will be aborted)
    505  */
    506 static const struct MHD_DynamicContentCreatorAction *
    507 chunk_return (void *cls,
    508               struct MHD_DynamicContentCreatorContext *ctx,
    509               uint_fast64_t pos,
    510               void *buf,
    511               size_t max)
    512 {
    513   struct ChunkContext *cc = cls;
    514   size_t imax = strlen (cc->pos);
    515   const char *space = strchr (cc->pos, ' ');
    516 
    517   (void)pos;  // TODO: add check
    518 
    519   if (0 == imax)
    520     return MHD_DCC_action_finish (ctx);
    521   if (NULL != space)
    522     imax = (size_t)(space - cc->pos) + 1;
    523   if (imax > max)
    524     imax = max;
    525   memcpy (buf,
    526           cc->pos,
    527           imax);
    528   cc->pos += imax;
    529   return MHD_DCC_action_continue (ctx,
    530                                   imax);
    531 }
    532 
    533 
    534 const struct MHD_Action *
    535 MHDT_server_reply_chunked_text (
    536   void *cls,
    537   struct MHD_Request *MHD_RESTRICT request,
    538   const struct MHD_String *MHD_RESTRICT path,
    539   enum MHD_HTTP_Method method,
    540   uint_fast64_t upload_size)
    541 {
    542   const char *text = cls;
    543   struct ChunkContext *cc;
    544 
    545   (void)path;
    546   (void)method;
    547   (void)upload_size;                              /* Unused */
    548 
    549   cc = malloc (sizeof (struct ChunkContext));
    550   if (NULL == cc)
    551     return NULL;
    552   cc->pos = text;
    553 
    554   return MHD_action_from_response (
    555     request,
    556     MHD_response_from_callback (MHD_HTTP_STATUS_OK,
    557                                 MHD_SIZE_UNKNOWN,
    558                                 &chunk_return,
    559                                 cc,
    560                                 &free));
    561 }
    562 
    563 
    564 /**
    565  * Compare two strings, succeed if both are NULL.
    566  *
    567  * @param wants string we want
    568  * @param have string we have
    569  * @return true if what we @a want is what we @a have
    570  */
    571 static bool
    572 nstrcmp (const char *wants,
    573          const struct MHD_StringNullable *have)
    574 {
    575   if ((NULL == wants)
    576       && (NULL == have->cstr)
    577       && (0 == have->len))
    578     return true;
    579   if ((NULL == wants)
    580       || (NULL == have->cstr))
    581     return false;
    582   return (0 == strcmp (wants,
    583                        have->cstr));
    584 }
    585 
    586 
    587 /**
    588  * "Stream" reader for POST data.
    589  * This callback is called to incrementally process parsed POST data sent by
    590  * the client.
    591  *
    592  * @param req the request
    593  * @param cls user-specified closure
    594  * @param name the name of the POST field
    595  * @param filename the name of the uploaded file, @a cstr member is NULL if not
    596  *                 known / not provided
    597  * @param content_type the mime-type of the data, cstr member is NULL if not
    598  *                     known / not provided
    599  * @param encoding the encoding of the data, cstr member is NULL if not known /
    600  *                 not provided
    601  * @param size the number of bytes in @a data available, may be zero if
    602  *             the @a final_data is #MHD_YES
    603  * @param data the pointer to @a size bytes of data at the specified
    604  *             @a off offset, NOT zero-terminated
    605  * @param off the offset of @a data in the overall value, always equal to
    606  *            the sum of sizes of previous calls for the same field / file;
    607  *            client may provide more than one field with the same name and
    608  *            the same filename, the new filed (or file) is indicated by zero
    609  *            value of @a off (and the end is indicated by @a final_data)
    610  * @param final_data if set to #MHD_YES then full field data is provided,
    611  *                   if set to #MHD_NO then more field data may be provided
    612  * @return action specifying how to proceed:
    613  *         #MHD_upload_action_continue() if all is well,
    614  *         #MHD_upload_action_suspend() to stop reading the upload until
    615  *         the request is resumed,
    616  *         #MHD_upload_action_abort_request() to close the socket,
    617  *         or a response to discard the rest of the upload and transmit
    618  *         the response
    619  * @ingroup action
    620  */
    621 static const struct MHD_UploadAction *
    622 post_stream_reader (struct MHD_Request *req,
    623                     void *cls,
    624                     const struct MHD_String *name,
    625                     const struct MHD_StringNullable *filename,
    626                     const struct MHD_StringNullable *content_type,
    627                     const struct MHD_StringNullable *encoding,
    628                     size_t size,
    629                     const void *data,
    630                     uint_fast64_t off,
    631                     enum MHD_Bool final_data)
    632 {
    633   struct MHDT_PostInstructions *pi = cls;
    634   struct MHDT_PostWant *wants = pi->wants;
    635 
    636   (void)encoding;  // TODO: add check
    637 
    638   if (NULL != wants)
    639   {
    640     for (unsigned int i = 0; NULL != wants[i].key; i++)
    641     {
    642       struct MHDT_PostWant *want = &wants[i];
    643 
    644       if (want->satisfied)
    645         continue;
    646       if (0 != strcmp (want->key,
    647                        name->cstr))
    648         continue;
    649       if (!nstrcmp (want->filename,
    650                     filename))
    651         continue;
    652       if (!nstrcmp (want->content_type,
    653                     content_type))
    654         continue;
    655       if (!want->incremental)
    656         continue;
    657       if (want->value_off != off)
    658         continue;
    659       if (want->value_size < off + size)
    660         continue;
    661       if (0 != memcmp (data,
    662                        want->value + off,
    663                        size))
    664         continue;
    665       want->value_off += size;
    666       want->satisfied = (want->value_size == want->value_off) && final_data;
    667     }
    668   }
    669 
    670   return MHD_upload_action_continue (req);
    671 }
    672 
    673 
    674 /**
    675  * Iterator over name-value pairs.  This iterator can be used to
    676  * iterate over all of the cookies, headers, or POST-data fields of a
    677  * request, and also to iterate over the headers that have been added
    678  * to a response.
    679  *
    680  * The pointers to the strings in @a nvt are valid until the response
    681  * is queued. If the data is needed beyond this point, it should be copied.
    682  *
    683  * @param cls closure
    684  * @param nvt the name, the value and the kind of the element
    685  * @return #MHD_YES to continue iterating,
    686  *         #MHD_NO to abort the iteration
    687  * @ingroup request
    688  */
    689 static enum MHD_Bool
    690 check_complete_post_value (
    691   void *cls,
    692   enum MHD_ValueKind kind,
    693   const struct MHD_NameAndValue *nv)
    694 {
    695   struct MHDT_PostInstructions *pi = cls;
    696   struct MHDT_PostWant *wants = pi->wants;
    697 
    698   if (NULL == wants)
    699     return MHD_NO;
    700   if (MHD_VK_POSTDATA != kind)
    701     return MHD_NO;
    702   for (unsigned int i = 0; NULL != wants[i].key; i++)
    703   {
    704     struct MHDT_PostWant *want = &wants[i];
    705 
    706     if (want->satisfied)
    707       continue;
    708     if (want->incremental)
    709       continue;
    710     if (0 != strcmp (want->key,
    711                      nv->name.cstr))
    712       continue;
    713     if (NULL == want->value)
    714     {
    715       if (NULL == nv->value.cstr)
    716         want->satisfied = true;
    717     }
    718     else if (NULL == nv->value.cstr)
    719       continue;
    720     else if (0 == want->value_size)
    721     {
    722       if (0 == strcmp (nv->value.cstr,
    723                        want->value))
    724         want->satisfied = true;
    725     }
    726     else
    727     {
    728       if ((want->value_size == nv->value.len)
    729           && (0 == memcmp (nv->value.cstr,
    730                            want->value,
    731                            want->value_size)))
    732         want->satisfied = true;
    733     }
    734   }
    735   return MHD_YES;
    736 }
    737 
    738 
    739 /**
    740  * The callback to be called when finished with processing
    741  * of the postprocessor upload data.
    742  * @param req the request
    743  * @param cls the closure
    744  * @param parsing_result the result of POST data parsing
    745  * @return the action to proceed
    746  */
    747 static const struct MHD_UploadAction *
    748 post_stream_done (struct MHD_Request *req,
    749                   void *cls,
    750                   enum MHD_PostParseResult parsing_result)
    751 {
    752   struct MHDT_PostInstructions *pi = cls;
    753   struct MHDT_PostWant *wants = pi->wants;
    754 
    755   if (MHD_POST_PARSE_RES_OK != parsing_result)
    756   {
    757     fprintf (stderr,
    758              "POST parsing was not successful. The result: %d\n",
    759              (int)parsing_result);
    760     return MHD_upload_action_abort_request (req);
    761   }
    762 
    763   MHD_request_get_values_cb (req,
    764                              MHD_VK_POSTDATA,
    765                              &check_complete_post_value,
    766                              pi);
    767   if (NULL != wants)
    768   {
    769     for (unsigned int i = 0; NULL != wants[i].key; i++)
    770     {
    771       struct MHDT_PostWant *want = &wants[i];
    772 
    773       if (want->satisfied)
    774         continue;
    775       fprintf (stderr,
    776                "Expected key-value pair `%s' missing\n",
    777                want->key);
    778       return MHD_upload_action_abort_request (req);
    779     }
    780   }
    781   return MHD_upload_action_from_response (
    782     req,
    783     MHD_response_from_empty (
    784       MHD_HTTP_STATUS_NO_CONTENT));
    785 }
    786 
    787 
    788 const struct MHD_Action *
    789 MHDT_server_reply_check_post (
    790   void *cls,
    791   struct MHD_Request *MHD_RESTRICT request,
    792   const struct MHD_String *MHD_RESTRICT path,
    793   enum MHD_HTTP_Method method,
    794   uint_fast64_t upload_size)
    795 {
    796   struct MHDT_PostInstructions *pi = cls;
    797 
    798   (void)path;  /* Unused */
    799   (void)upload_size;  // TODO: add check
    800 
    801   if (MHD_HTTP_METHOD_POST != method)
    802   {
    803     fprintf (stderr,
    804              "Reported HTTP method other then POST. Reported method: %u\n",
    805              (unsigned)method);
    806     return MHD_action_abort_request (req);
    807   }
    808 
    809   return MHD_action_parse_post (request,
    810                                 pi->buffer_size,
    811                                 pi->auto_stream_size,
    812                                 pi->enc,
    813                                 &post_stream_reader,
    814                                 pi,
    815                                 &post_stream_done,
    816                                 pi);
    817 }
    818 
    819 
    820 const struct MHD_Action *
    821 MHDT_server_reply_check_basic_auth (
    822   void *cls,
    823   struct MHD_Request *MHD_RESTRICT request,
    824   const struct MHD_String *MHD_RESTRICT path,
    825   enum MHD_HTTP_Method method,
    826   uint_fast64_t upload_size)
    827 {
    828   const char *cred = cls;
    829   union MHD_RequestInfoDynamicData dd;
    830   enum MHD_StatusCode sc;
    831   const struct MHD_AuthBasicCreds *ba;
    832 
    833   /* should not be needed, except to make gcc happy */
    834   memset (&dd,
    835           0,
    836           sizeof (dd));
    837   sc = MHD_request_get_info_dynamic (request,
    838                                      MHD_REQUEST_INFO_DYNAMIC_AUTH_BASIC_CREDS,
    839                                      &dd);
    840   if (MHD_SC_OK != sc)
    841   {
    842     fprintf (stderr,
    843              "No credentials?\n");
    844     return MHD_action_basic_auth_challenge_p (
    845       request,
    846       "test-realm",
    847       MHD_YES,
    848       MHD_response_from_empty (
    849         MHD_HTTP_STATUS_UNAUTHORIZED));
    850   }
    851   ba = dd.v_auth_basic_creds;
    852   assert (NULL != ba);
    853   if ((0 != strncmp (ba->username.cstr,
    854                      cred,
    855                      ba->username.len))
    856       || (':' != cred[ba->username.len])
    857       || (NULL == ba->password.cstr)
    858       || (0 != strcmp (ba->password.cstr,
    859                        &cred[ba->username.len + 1])))
    860   {
    861     fprintf (stderr,
    862              "Wrong credentials (Got: %s/%s Want: %s)!\n",
    863              ba->username.cstr,
    864              ba->password.cstr,
    865              cred);
    866     return MHD_action_basic_auth_challenge_p (
    867       request,
    868       "test-realm",
    869       MHD_YES,
    870       MHD_response_from_empty (
    871         MHD_HTTP_STATUS_UNAUTHORIZED));
    872   }
    873   return MHD_action_from_response (
    874     request,
    875     MHD_response_from_empty (
    876       MHD_HTTP_STATUS_NO_CONTENT));
    877 }
    878 
    879 
    880 const struct MHD_Action *
    881 MHDT_server_reply_check_digest_auth (
    882   void *cls,
    883   struct MHD_Request *MHD_RESTRICT request,
    884   const struct MHD_String *MHD_RESTRICT path,
    885   enum MHD_HTTP_Method method,
    886   uint_fast64_t upload_size)
    887 {
    888   const char *cred = cls;
    889   const char *colon = strchr (cred, ':');
    890   char *username;
    891   const char *password;
    892   enum MHD_DigestAuthResult dar;
    893   const char *realm = "test-realm";
    894 #if CURL_AT_LEAST_VERSION (7, 57, 0)
    895   enum MHD_DigestAuthAlgo algo = MHD_DIGEST_AUTH_ALGO_SHA256;
    896 #else
    897   enum MHD_DigestAuthAlgo algo = MHD_DIGEST_AUTH_ALGO_MD5;
    898 #endif
    899   size_t digest_len = MHD_digest_get_hash_size (algo);
    900 
    901   (void)cls;  /* Unused, mute compiler warning */
    902 
    903   if (0 == digest_len)
    904     return NULL;
    905   assert (NULL != colon);
    906   password = colon + 1;
    907   username = strndup (cred,
    908                       colon - cred);
    909   assert (NULL != username);
    910   {
    911     enum MHD_StatusCode sc;
    912     char digest[digest_len];
    913 
    914     // FIXME: why is this needed? We should not get a warning
    915     // even without this memset!
    916     memset (digest, 0, sizeof (digest));
    917     sc = MHD_digest_auth_calc_userdigest (algo,
    918                                           username,
    919                                           realm,
    920                                           password,
    921                                           sizeof (digest),
    922                                           digest);
    923     if (MHD_SC_OK != sc)
    924     {
    925       fprintf (stderr,
    926                "MHD_digest_auth_calc_userdigest: %d\n",
    927                (int)sc);
    928       free (username);
    929       return NULL;
    930     }
    931     dar = MHD_digest_auth_check_digest (request,
    932                                         realm,
    933                                         username,
    934                                         sizeof (digest),
    935                                         digest,
    936                                         0, /* maximum nonce counter; 0: default */
    937                                         MHD_DIGEST_AUTH_MULT_QOP_AUTH,
    938                                         (enum MHD_DigestAuthMultiAlgo)algo);
    939   }
    940   free (username);
    941   if ((MHD_DAUTH_HEADER_MISSING == dar)
    942       || (MHD_DAUTH_NONCE_STALE == dar))
    943   {
    944     struct MHD_Response *resp;
    945     enum MHD_StatusCode sc;
    946 
    947     resp = MHD_response_from_empty (
    948       MHD_HTTP_STATUS_UNAUTHORIZED);
    949     if (NULL == resp)
    950     {
    951       fprintf (stderr,
    952                "Failed to create response body\n");
    953       return NULL;
    954     }
    955     sc = MHD_response_add_auth_digest_challenge (
    956       resp,
    957       "test-realm",
    958       "opaque",
    959       NULL, /* domain */
    960       (MHD_DAUTH_NONCE_STALE == dar) ? MHD_YES : MHD_NO, /* indicate stale */
    961       MHD_DIGEST_AUTH_MULT_QOP_AUTH,
    962       (enum MHD_DigestAuthMultiAlgo)algo,
    963       MHD_NO /* userhash_support */,
    964       MHD_YES /* prefer UTF8 */);
    965     if (MHD_SC_OK != sc)
    966     {
    967       fprintf (stderr,
    968                "MHD_response_add_auth_digest_challenge failed: %d\n",
    969                (int)sc);
    970       return NULL;
    971     }
    972     return MHD_action_from_response (
    973       request,
    974       resp);
    975   }
    976   if (MHD_DAUTH_RESPONSE_WRONG == dar)
    977     return MHD_action_from_response (
    978       request,
    979       MHD_response_from_empty (MHD_HTTP_STATUS_FORBIDDEN));
    980 
    981   if (MHD_DAUTH_OK == dar)
    982     return MHD_action_from_response (
    983       request,
    984       MHD_response_from_empty (
    985         MHD_HTTP_STATUS_NO_CONTENT));
    986 
    987   return MHD_action_abort_request (request);
    988 }