/*
  This file is part of GNU libmicrohttpd
  Copyright (C) 2024 Christian Grothoff
  Copyright (C) 2024 Evgeny Grin (Karlson2k)

  GNU libmicrohttpd is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  GNU libmicrohttpd is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

/**
 * @file libtest_convenience_server_reply.c
 * @brief convenience functions that generate
 *   replies from the server for libtest users
 * @author Christian Grothoff
 */
#include "libtest.h"
#include <pthread.h>
#include <stdbool.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <curl/curl.h>
#include <assert.h>

#ifndef CURL_VERSION_BITS
#  define CURL_VERSION_BITS(x,y,z) ((x) << 16 | (y) << 8 | (z))
#endif
#ifndef CURL_AT_LEAST_VERSION
#  define CURL_AT_LEAST_VERSION(x,y,z) \
        (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS (x, y, z))
#endif

const struct MHD_Action *
MHDT_server_reply_text (
  void *cls,
  struct MHD_Request *MHD_RESTRICT request,
  const struct MHD_String *MHD_RESTRICT path,
  enum MHD_HTTP_Method method,
  uint_fast64_t upload_size)
{
  const char *text = cls;

  (void) path; (void) method; (void) upload_size; /* Unused */

  return MHD_action_from_response (
    request,
    MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK,
                                     strlen (text),
                                     text));
}


const struct MHD_Action *
MHDT_server_reply_file (
  void *cls,
  struct MHD_Request *MHD_RESTRICT request,
  const struct MHD_String *MHD_RESTRICT path,
  enum MHD_HTTP_Method method,
  uint_fast64_t upload_size)
{
  const char *text = cls;
  size_t tlen = strlen (text);
  char fn[] = "/tmp/mhd-test-XXXXXX";
  int fd;

  (void) path; (void) method; (void) upload_size; /* Unused */

  fd = mkstemp (fn);
  if (-1 == fd)
  {
    fprintf (stderr,
             "Failed to mkstemp() temporary file\n");
    return MHD_action_abort_request (request);
  }
  if (((ssize_t) tlen) != write (fd, text, tlen))
  {
    fprintf (stderr,
             "Failed to write() temporary file in one go: %s\n",
             strerror (errno));
    return MHD_action_abort_request (request);
  }
  fsync (fd);
  if (0 != remove (fn))
  {
    fprintf (stderr,
             "Failed to remove() temporary file %s: %s\n",
             fn,
             strerror (errno));
  }
  return MHD_action_from_response (
    request,
    MHD_response_from_fd (MHD_HTTP_STATUS_OK,
                          fd,
                          0 /* offset */,
                          tlen));
}


const struct MHD_Action *
MHDT_server_reply_with_header (
  void *cls,
  struct MHD_Request *MHD_RESTRICT request,
  const struct MHD_String *MHD_RESTRICT path,
  enum MHD_HTTP_Method method,
  uint_fast64_t upload_size)
{
  const char *header = cls;
  size_t hlen = strlen (header) + 1;
  char name[hlen];
  const char *colon = strchr (header, ':');
  const char *value;
  struct MHD_Response *resp;

  (void) path; (void) method; (void) upload_size; /* Unused */

  memcpy (name,
          header,
          hlen);
  name[colon - header] = '\0';
  value = &name[colon - header + 1];

  resp = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT);
  if (MHD_SC_OK !=
      MHD_response_add_header (resp,
                               name,
                               value))
  {
    MHD_response_destroy (resp);
    return MHD_action_abort_request (request);
  }
  return MHD_action_from_response (
    request,
    resp);
}


const struct MHD_Action *
MHDT_server_reply_check_query (
  void *cls,
  struct MHD_Request *MHD_RESTRICT request,
  const struct MHD_String *MHD_RESTRICT path,
  enum MHD_HTTP_Method method,
  uint_fast64_t upload_size)
{
  const char *equery = cls;
  size_t qlen = strlen (equery) + 1;
  char qc[qlen];

  (void) path; (void) method; (void) upload_size; /* Unused */

  memcpy (qc,
          equery,
          qlen);
  for (const char *tok = strtok (qc, "&");
       NULL != tok;
       tok = strtok (NULL, "&"))
  {
    const char *end;
    const struct MHD_StringNullable *sn;
    const char *val;

    end = strchr (tok, '=');
    if (NULL == end)
    {
      end = &tok[strlen (tok)];
      val = NULL;
    }
    else
    {
      val = end + 1;
    }
    {
      size_t alen = (size_t) (end - tok);
      char arg[alen + 1];

      memcpy (arg,
              tok,
              alen);
      arg[alen] = '\0';
      sn = MHD_request_get_value (request,
                                  MHD_VK_GET_ARGUMENT,
                                  arg);
      if (NULL == sn)
      {
        fprintf (stderr,
                 "NULL returned for query key %s\n",
                 arg);
        return MHD_action_abort_request (request);
      }
      if (NULL == val)
      {
        if (NULL != sn->cstr)
        {
          fprintf (stderr,
                   "NULL expected for value for query key %s, got %s\n",
                   arg,
                   sn->cstr);
          return MHD_action_abort_request (request);
        }
      }
      else
      {
        if (NULL == sn->cstr)
        {
          fprintf (stderr,
                   "%s expected for value for query key %s, got NULL\n",
                   val,
                   arg);
          return MHD_action_abort_request (request);
        }
        if (0 != strcmp (val,
                         sn->cstr))
        {
          fprintf (stderr,
                   "%s expected for value for query key %s, got %s\n",
                   val,
                   arg,
                   sn->cstr);
          return MHD_action_abort_request (request);
        }
      }
    }
  }

  return MHD_action_from_response (
    request,
    MHD_response_from_empty (
      MHD_HTTP_STATUS_NO_CONTENT));
}


const struct MHD_Action *
MHDT_server_reply_check_header (
  void *cls,
  struct MHD_Request *MHD_RESTRICT request,
  const struct MHD_String *MHD_RESTRICT path,
  enum MHD_HTTP_Method method,
  uint_fast64_t upload_size)
{
  const char *want = cls;
  size_t wlen = strlen (want) + 1;
  char key[wlen];
  const char *colon = strchr (want, ':');
  const struct MHD_StringNullable *have;
  const char *value;

  (void) path; (void) method; (void) upload_size; /* Unused */

  memcpy (key,
          want,
          wlen);
  if (NULL != colon)
  {
    key[colon - want] = '\0';
    value = &key[colon - want + 1];
  }
  else
  {
    value = NULL;
  }
  have = MHD_request_get_value (request,
                                MHD_VK_HEADER,
                                key);
  if (NULL == have)
  {
    fprintf (stderr,
             "Missing client header `%s'\n",
             want);
    return MHD_action_abort_request (request);
  }
  if (NULL == value)
  {
    if (NULL != have->cstr)
    {
      fprintf (stderr,
               "Have unexpected client header `%s': `%s'\n",
               key,
               have->cstr);
      return MHD_action_abort_request (request);
    }
  }
  else
  {
    if (NULL == have->cstr)
    {
      fprintf (stderr,
               "Missing value for client header `%s'\n",
               want);
      return MHD_action_abort_request (request);
    }
    if (0 != strcmp (have->cstr,
                     value))
    {
      fprintf (stderr,
               "Client HTTP header `%s' was expected to be `%s' but is `%s'\n",
               key,
               value,
               have->cstr);
      return MHD_action_abort_request (request);
    }
  }
  return MHD_action_from_response (
    request,
    MHD_response_from_empty (
      MHD_HTTP_STATUS_NO_CONTENT));
}


/**
 * Function to process data uploaded by a client.
 *
 * @param cls the payload we expect to be uploaded as a 0-terminated string
 * @param request the request is being processed
 * @param content_data_size the size of the @a content_data,
 *                          zero when all data have been processed
 * @param[in] content_data the uploaded content data,
 *                         may be modified in the callback,
 *                         valid only until return from the callback,
 *                         NULL when all data have been processed
 * @return action specifying how to proceed:
 *         #MHD_upload_action_continue() to continue upload (for incremental
 *         upload processing only),
 *         #MHD_upload_action_suspend() to stop reading the upload until
 *         the request is resumed,
 *         #MHD_upload_action_abort_request() to close the socket,
 *         or a response to discard the rest of the upload and transmit
 *         the response
 * @ingroup action
 */
static const struct MHD_UploadAction *
check_upload_cb (void *cls,
                 struct MHD_Request *request,
                 size_t content_data_size,
                 void *content_data)
{
  const char *want = cls;
  size_t wlen = strlen (want);

  if (content_data_size != wlen)
  {
    fprintf (stderr,
             "Invalid body size given to full upload callback\n");
    return MHD_upload_action_abort_request (request);
  }
  if (0 != memcmp (want,
                   content_data,
                   wlen))
  {
    fprintf (stderr,
             "Invalid body data given to full upload callback\n");
    return MHD_upload_action_abort_request (request);
  }
  /* success! */
  return MHD_upload_action_from_response (
    request,
    MHD_response_from_empty (
      MHD_HTTP_STATUS_NO_CONTENT));
}


const struct MHD_Action *
MHDT_server_reply_check_upload (
  void *cls,
  struct MHD_Request *MHD_RESTRICT request,
  const struct MHD_String *MHD_RESTRICT path,
  enum MHD_HTTP_Method method,
  uint_fast64_t upload_size)
{
  const char *want = cls;
  size_t wlen = strlen (want);

  (void) path; (void) method; (void) upload_size; /* Unused */

  return MHD_action_process_upload_full (request,
                                         wlen,
                                         &check_upload_cb,
                                         (void *) want);
}


/**
 * Closure for #chunk_return.
 */
struct ChunkContext
{
  /**
   * Where we are in the buffer.
   */
  const char *pos;
};


/**
 * Function that returns a string in chunks.
 *
 * @param dyn_cont_cls must be a `struct ChunkContext`
 * @param ctx the context to produce the action to return,
 *            the pointer is only valid until the callback returns
 * @param pos position in the datastream to access;
 *        note that if a `struct MHD_Response` object is re-used,
 *        it is possible for the same content reader to
 *        be queried multiple times for the same data;
 *        however, if a `struct MHD_Response` is not re-used,
 *        libmicrohttpd guarantees that "pos" will be
 *        the sum of all data sizes provided by this callback
 * @param[out] buf where to copy the data
 * @param max maximum number of bytes to copy to @a buf (size of @a buf)
 * @return action to use,
 *         NULL in case of any error (the response will be aborted)
 */
static const struct MHD_DynamicContentCreatorAction *
chunk_return (void *cls,
              struct MHD_DynamicContentCreatorContext *ctx,
              uint_fast64_t pos,
              void *buf,
              size_t max)
{
  struct ChunkContext *cc = cls;
  size_t imax = strlen (cc->pos);
  const char *space = strchr (cc->pos, ' ');

  (void) pos; // TODO: add check

  if (0 == imax)
    return MHD_DCC_action_finish (ctx);
  if (NULL != space)
    imax = (size_t) (space - cc->pos) + 1;
  if (imax > max)
    imax = max;
  memcpy (buf,
          cc->pos,
          imax);
  cc->pos += imax;
  return MHD_DCC_action_continue (ctx,
                                  imax);
}


const struct MHD_Action *
MHDT_server_reply_chunked_text (
  void *cls,
  struct MHD_Request *MHD_RESTRICT request,
  const struct MHD_String *MHD_RESTRICT path,
  enum MHD_HTTP_Method method,
  uint_fast64_t upload_size)
{
  const char *text = cls;
  struct ChunkContext *cc;

  (void) path; (void) method; (void) upload_size; /* Unused */

  cc = malloc (sizeof (struct ChunkContext));
  if (NULL == cc)
    return NULL;
  cc->pos = text;

  return MHD_action_from_response (
    request,
    MHD_response_from_callback (MHD_HTTP_STATUS_OK,
                                MHD_SIZE_UNKNOWN,
                                &chunk_return,
                                cc,
                                &free));
}


/**
 * Compare two strings, succeed if both are NULL.
 *
 * @param wants string we want
 * @param have string we have
 * @return true if what we @a want is what we @a have
 */
static bool
nstrcmp (const char *wants,
         const struct MHD_StringNullable *have)
{
  if ( (NULL == wants) &&
       (NULL == have->cstr) &&
       (0 == have->len) )
    return true;
  if ( (NULL == wants) ||
       (NULL == have->cstr) )
    return false;
  return (0 == strcmp (wants,
                       have->cstr));
}


/**
 * "Stream" reader for POST data.
 * This callback is called to incrementally process parsed POST data sent by
 * the client.
 *
 * @param req the request
 * @param cls user-specified closure
 * @param name the name of the POST field
 * @param filename the name of the uploaded file, @a cstr member is NULL if not
 *                 known / not provided
 * @param content_type the mime-type of the data, cstr member is NULL if not
 *                     known / not provided
 * @param encoding the encoding of the data, cstr member is NULL if not known /
 *                 not provided
 * @param size the number of bytes in @a data available, may be zero if
 *             the @a final_data is #MHD_YES
 * @param data the pointer to @a size bytes of data at the specified
 *             @a off offset, NOT zero-terminated
 * @param off the offset of @a data in the overall value, always equal to
 *            the sum of sizes of previous calls for the same field / file;
 *            client may provide more than one field with the same name and
 *            the same filename, the new filed (or file) is indicated by zero
 *            value of @a off (and the end is indicated by @a final_data)
 * @param final_data if set to #MHD_YES then full field data is provided,
 *                   if set to #MHD_NO then more field data may be provided
 * @return action specifying how to proceed:
 *         #MHD_upload_action_continue() if all is well,
 *         #MHD_upload_action_suspend() to stop reading the upload until
 *         the request is resumed,
 *         #MHD_upload_action_abort_request() to close the socket,
 *         or a response to discard the rest of the upload and transmit
 *         the response
 * @ingroup action
 */
static const struct MHD_UploadAction *
post_stream_reader (struct MHD_Request *req,
                    void *cls,
                    const struct MHD_String *name,
                    const struct MHD_StringNullable *filename,
                    const struct MHD_StringNullable *content_type,
                    const struct MHD_StringNullable *encoding,
                    size_t size,
                    const void *data,
                    uint_fast64_t off,
                    enum MHD_Bool final_data)
{
  struct MHDT_PostInstructions *pi = cls;
  struct MHDT_PostWant *wants = pi->wants;

  (void) encoding; // TODO: add check

  if (NULL != wants)
  {
    for (unsigned int i = 0; NULL != wants[i].key; i++)
    {
      struct MHDT_PostWant *want = &wants[i];

      if (want->satisfied)
        continue;
      if (0 != strcmp (want->key,
                       name->cstr))
        continue;
      if (! nstrcmp (want->filename,
                     filename))
        continue;
      if (! nstrcmp (want->content_type,
                     content_type))
        continue;
      if (! want->incremental)
        continue;
      if (want->value_off != off)
        continue;
      if (want->value_size < off + size)
        continue;
      if (0 != memcmp (data,
                       want->value + off,
                       size))
        continue;
      want->value_off += size;
      want->satisfied = (want->value_size == want->value_off) && final_data;
    }
  }

  return MHD_upload_action_continue (req);
}


/**
 * Iterator over name-value pairs.  This iterator can be used to
 * iterate over all of the cookies, headers, or POST-data fields of a
 * request, and also to iterate over the headers that have been added
 * to a response.
 *
 * The pointers to the strings in @a nvt are valid until the response
 * is queued. If the data is needed beyond this point, it should be copied.
 *
 * @param cls closure
 * @param nvt the name, the value and the kind of the element
 * @return #MHD_YES to continue iterating,
 *         #MHD_NO to abort the iteration
 * @ingroup request
 */
static enum MHD_Bool
check_complete_post_value (
  void *cls,
  enum MHD_ValueKind kind,
  const struct MHD_NameAndValue *nv)
{
  struct MHDT_PostInstructions *pi = cls;
  struct MHDT_PostWant *wants = pi->wants;

  if (NULL == wants)
    return MHD_NO;
  if (MHD_VK_POSTDATA != kind)
    return MHD_NO;
  for (unsigned int i = 0; NULL != wants[i].key; i++)
  {
    struct MHDT_PostWant *want = &wants[i];

    if (want->satisfied)
      continue;
    if (want->incremental)
      continue;
    if (0 != strcmp (want->key,
                     nv->name.cstr))
      continue;
    if (NULL == want->value)
    {
      if (NULL == nv->value.cstr)
        want->satisfied = true;
    }
    else if (NULL == nv->value.cstr)
      continue;
    else if (0 == want->value_size)
    {
      if (0 == strcmp (nv->value.cstr,
                       want->value))
        want->satisfied = true;
    }
    else
    {
      if ((want->value_size == nv->value.len) &&
          (0 == memcmp (nv->value.cstr,
                        want->value,
                        want->value_size)))
        want->satisfied = true;
    }
  }
  return MHD_YES;
}


/**
 * The callback to be called when finished with processing
 * of the postprocessor upload data.
 * @param req the request
 * @param cls the closure
 * @param parsing_result the result of POST data parsing
 * @return the action to proceed
 */
static const struct MHD_UploadAction *
post_stream_done (struct MHD_Request *req,
                  void *cls,
                  enum MHD_PostParseResult parsing_result)
{
  struct MHDT_PostInstructions *pi = cls;
  struct MHDT_PostWant *wants = pi->wants;

  if (MHD_POST_PARSE_RES_OK != parsing_result)
  {
    fprintf (stderr,
             "POST parsing was not successful. The result: %d\n",
             (int) parsing_result);
    return MHD_upload_action_abort_request (req);
  }

  MHD_request_get_values_cb (req,
                             MHD_VK_POSTDATA,
                             &check_complete_post_value,
                             pi);
  if (NULL != wants)
  {
    for (unsigned int i = 0; NULL != wants[i].key; i++)
    {
      struct MHDT_PostWant *want = &wants[i];

      if (want->satisfied)
        continue;
      fprintf (stderr,
               "Expected key-value pair `%s' missing\n",
               want->key);
      return MHD_upload_action_abort_request (req);
    }
  }
  return MHD_upload_action_from_response (
    req,
    MHD_response_from_empty (
      MHD_HTTP_STATUS_NO_CONTENT));
}


const struct MHD_Action *
MHDT_server_reply_check_post (
  void *cls,
  struct MHD_Request *MHD_RESTRICT request,
  const struct MHD_String *MHD_RESTRICT path,
  enum MHD_HTTP_Method method,
  uint_fast64_t upload_size)
{
  struct MHDT_PostInstructions *pi = cls;

  (void) path; /* Unused */
  (void) upload_size; // TODO: add check

  if (MHD_HTTP_METHOD_POST != method)
  {
    fprintf (stderr,
             "Reported HTTP method other then POST. Reported method: %u\n",
             (unsigned) method);
    return MHD_action_abort_request (req);
  }

  return MHD_action_parse_post (request,
                                pi->buffer_size,
                                pi->auto_stream_size,
                                pi->enc,
                                &post_stream_reader,
                                pi,
                                &post_stream_done,
                                pi);
}


const struct MHD_Action *
MHDT_server_reply_check_basic_auth (
  void *cls,
  struct MHD_Request *MHD_RESTRICT request,
  const struct MHD_String *MHD_RESTRICT path,
  enum MHD_HTTP_Method method,
  uint_fast64_t upload_size)
{
  const char *cred = cls;
  union MHD_RequestInfoDynamicData dd;
  enum MHD_StatusCode sc;
  const struct MHD_AuthBasicCreds *ba;

  /* should not be needed, except to make gcc happy */
  memset (&dd,
          0,
          sizeof (dd));
  sc = MHD_request_get_info_dynamic (request,
                                     MHD_REQUEST_INFO_DYNAMIC_AUTH_BASIC_CREDS,
                                     &dd);
  if (MHD_SC_OK != sc)
  {
    fprintf (stderr,
             "No credentials?\n");
    return MHD_action_basic_auth_challenge_p (
      request,
      "test-realm",
      MHD_YES,
      MHD_response_from_empty (
        MHD_HTTP_STATUS_UNAUTHORIZED));
  }
  ba = dd.v_auth_basic_creds;
  assert (NULL != ba);
  if ( (0 != strncmp (ba->username.cstr,
                      cred,
                      ba->username.len)) ||
       (':' != cred[ba->username.len]) ||
       (NULL == ba->password.cstr) ||
       (0 != strcmp (ba->password.cstr,
                     &cred[ba->username.len + 1])) )
  {
    fprintf (stderr,
             "Wrong credentials (Got: %s/%s Want: %s)!\n",
             ba->username.cstr,
             ba->password.cstr,
             cred);
    return MHD_action_basic_auth_challenge_p (
      request,
      "test-realm",
      MHD_YES,
      MHD_response_from_empty (
        MHD_HTTP_STATUS_UNAUTHORIZED));
  }
  return MHD_action_from_response (
    request,
    MHD_response_from_empty (
      MHD_HTTP_STATUS_NO_CONTENT));
}


const struct MHD_Action *
MHDT_server_reply_check_digest_auth (
  void *cls,
  struct MHD_Request *MHD_RESTRICT request,
  const struct MHD_String *MHD_RESTRICT path,
  enum MHD_HTTP_Method method,
  uint_fast64_t upload_size)
{
  const char *cred = cls;
  const char *colon = strchr (cred, ':');
  char *username;
  const char *password;
  enum MHD_DigestAuthResult dar;
  const char *realm = "test-realm";
#if CURL_AT_LEAST_VERSION (7,57,0)
  enum MHD_DigestAuthAlgo algo = MHD_DIGEST_AUTH_ALGO_SHA256;
#else
  enum MHD_DigestAuthAlgo algo = MHD_DIGEST_AUTH_ALGO_MD5;
#endif
  size_t digest_len = MHD_digest_get_hash_size (algo);

  if (0 == digest_len)
    return NULL;
  assert (NULL != colon);
  password = colon + 1;
  username = strndup (cred,
                      colon - cred);
  assert (NULL != username);
  {
    enum MHD_StatusCode sc;
    char digest[digest_len];

    // FIXME: why is this needed? We should not get a warning
    // even without this memset!
    memset (digest, 0, sizeof (digest));
    sc = MHD_digest_auth_calc_userdigest (algo,
                                          username,
                                          realm,
                                          password,
                                          sizeof (digest),
                                          digest);
    if (MHD_SC_OK != sc)
    {
      fprintf (stderr,
               "MHD_digest_auth_calc_userdigest: %d\n",
               (int) sc);
      return NULL;
    }
    dar = MHD_digest_auth_check_digest (request,
                                        realm,
                                        username,
                                        sizeof (digest),
                                        digest,
                                        0, /* maximum nonce counter; 0: default */
                                        MHD_DIGEST_AUTH_MULT_QOP_AUTH,
                                        (enum MHD_DigestAuthMultiAlgo) algo);
  }
  free (username);
  if ((MHD_DAUTH_HEADER_MISSING == dar)
      || (MHD_DAUTH_NONCE_STALE == dar))
  {
    struct MHD_Response *resp;
    enum MHD_StatusCode sc;

    resp = MHD_response_from_empty (
      MHD_HTTP_STATUS_UNAUTHORIZED);
    if (NULL == resp)
    {
      fprintf (stderr,
               "Failed to create response body\n");
      return NULL;
    }
    sc = MHD_response_add_auth_digest_challenge (
      resp,
      "test-realm",
      "opaque",
      NULL, /* domain */
      (MHD_DAUTH_NONCE_STALE == dar) ? MHD_YES : MHD_NO, /* indicate stale */
      MHD_DIGEST_AUTH_MULT_QOP_AUTH,
      MHD_DIGEST_AUTH_MULT_ALGO_SHA256,
      MHD_NO /* userhash_support */,
      MHD_YES /* prefer UTF8 */);
    if (MHD_SC_OK != sc)
    {
      fprintf (stderr,
               "MHD_response_add_auth_digest_challenge failed: %d\n",
               (int) sc);
      return NULL;
    }
    return MHD_action_from_response (
      request,
      resp);
  }
  if (MHD_DAUTH_RESPONSE_WRONG == dar)
    return MHD_action_from_response (
      request,
      MHD_response_from_empty (MHD_HTTP_STATUS_FORBIDDEN));

  if (MHD_DAUTH_OK == dar)
    return MHD_action_from_response (
      request,
      MHD_response_from_empty (
        MHD_HTTP_STATUS_NO_CONTENT));

  return MHD_action_abort_request (request);
}
