libmicrohttpd2

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

libtest_convenience_server_reply.c (27113B)


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