libmicrohttpd2

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

libtest_convenience_server_reply.c (26780B)


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