/*
  This file is part of TALER
  Copyright (C) 2014, 2015 GNUnet e.V.

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

  TALER 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 Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License along with
  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/>
*/

/**
 * @file taler-mint-httpd_parsing.c
 * @brief functions to parse incoming requests (MHD arguments and JSON snippets)
 * @author Florian Dold
 * @author Benedikt Mueller
 * @author Christian Grothoff
 */

#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include "taler-mint-httpd_parsing.h"
#include "taler-mint-httpd_responses.h"


/**
 * Initial size for POST request buffer.
 */
#define REQUEST_BUFFER_INITIAL (2*1024)

/**
 * Maximum POST request size.
 */
#define REQUEST_BUFFER_MAX (1024*1024)


/**
 * Buffer for POST requests.
 */
struct Buffer
{
  /**
   * Allocated memory
   */
  char *data;

  /**
   * Number of valid bytes in buffer.
   */
  size_t fill;

  /**
   * Number of allocated bytes in buffer.
   */
  size_t alloc;
};


/**
 * Initialize a buffer.
 *
 * @param buf the buffer to initialize
 * @param data the initial data
 * @param data_size size of the initial data
 * @param alloc_size size of the buffer
 * @param max_size maximum size that the buffer can grow to
 * @return a GNUnet result code
 */
static int
buffer_init (struct Buffer *buf,
             const void *data,
             size_t data_size,
             size_t alloc_size,
             size_t max_size)
{
  if (data_size > max_size || alloc_size > max_size)
    return GNUNET_SYSERR;
  if (data_size > alloc_size)
    alloc_size = data_size;
  buf->data = GNUNET_malloc (alloc_size);
  memcpy (buf->data, data, data_size);
  return GNUNET_OK;
}


/**
 * Free the data in a buffer.  Does *not* free
 * the buffer object itself.
 *
 * @param buf buffer to de-initialize
 */
static void
buffer_deinit (struct Buffer *buf)
{
  GNUNET_free (buf->data);
  buf->data = NULL;
}


/**
 * Append data to a buffer, growing the buffer if necessary.
 *
 * @param buf the buffer to append to
 * @param data the data to append
 * @param data_size the size of @a data
 * @param max_size maximum size that the buffer can grow to
 * @return #GNUNET_OK on success,
 *         #GNUNET_NO if the buffer can't accomodate for the new data
 */
static int
buffer_append (struct Buffer *buf,
               const void *data,
               size_t data_size,
               size_t max_size)
{
  if (buf->fill + data_size > max_size)
    return GNUNET_NO;
  if (data_size + buf->fill > buf->alloc)
  {
    char *new_buf;
    size_t new_size = buf->alloc;
    while (new_size < buf->fill + data_size)
      new_size += 2;
    if (new_size > max_size)
      return GNUNET_NO;
    new_buf = GNUNET_malloc (new_size);
    memcpy (new_buf, buf->data, buf->fill);
    GNUNET_free (buf->data);
    buf->data = new_buf;
    buf->alloc = new_size;
  }
  memcpy (buf->data + buf->fill, data, data_size);
  buf->fill += data_size;
  return GNUNET_OK;
}


/**
 * Release all memory allocated for the variable-size fields in
 * the parser specification.
 *
 * @param spec specification to free
 * @param spec_len number of items in @a spec to look at
 */
static void
release_data (struct TMH_PARSE_FieldSpecification *spec,
              unsigned int spec_len)
{
  unsigned int i;

  for (i=0; i < spec_len; i++)
  {
    switch (spec[i].command)
    {
    case TMH_PARSE_JNC_FIELD:
      GNUNET_break (0);
      return;
    case TMH_PARSE_JNC_INDEX:
      GNUNET_break (0);
      return;
    case TMH_PARSE_JNC_RET_DATA:
      break;
    case TMH_PARSE_JNC_RET_DATA_VAR:
      if (NULL != spec[i].destination)
      {
        GNUNET_free (* (void**) spec[i].destination);
        *(void**) spec[i].destination = NULL;
        *spec[i].destination_size_out = 0;
      }
      break;
    case TMH_PARSE_JNC_RET_TYPED_JSON:
      {
        json_t *json;

        json = *(json_t **) spec[i].destination;
        if (NULL != json)
        {
          json_decref (json);
          *(json_t**) spec[i].destination = NULL;
        }
      }
      break;
    case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY:
      {
        struct TALER_DenominationPublicKey *pk;

        pk = spec[i].destination;
        if (NULL != pk->rsa_public_key)
        {
          GNUNET_CRYPTO_rsa_public_key_free (pk->rsa_public_key);
          pk->rsa_public_key = NULL;
        }
      }
      break;
    case TMH_PARSE_JNC_RET_RSA_SIGNATURE:
      {
        struct TALER_DenominationSignature *sig;

        sig = spec[i].destination;
        if (NULL != sig->rsa_signature)
        {
          GNUNET_CRYPTO_rsa_signature_free (sig->rsa_signature);
          sig->rsa_signature = NULL;
        }
      }
      break;
    case TMH_PARSE_JNC_RET_AMOUNT:
      memset (spec[i].destination,
              0,
              sizeof (struct TALER_Amount));
      break;
    case TMH_PARSE_JNC_RET_TIME_ABSOLUTE:
      break;
    case TMH_PARSE_JNC_RET_UINT64:
      break;
    }
  }
}


/**
 * Process a POST request containing a JSON object.  This function
 * realizes an MHD POST processor that will (incrementally) process
 * JSON data uploaded to the HTTP server.  It will store the required
 * state in the @a con_cls, which must be cleaned up using
 * #TMH_PARSE_post_cleanup_callback().
 *
 * @param connection the MHD connection
 * @param con_cls the closure (points to a `struct Buffer *`)
 * @param upload_data the POST data
 * @param upload_data_size number of bytes in @a upload_data
 * @param json the JSON object for a completed request
 * @return
 *    #GNUNET_YES if json object was parsed or at least
 *               may be parsed in the future (call again);
 *               `*json` will be NULL if we need to be called again,
 *                and non-NULL if we are done.
 *    #GNUNET_NO is request incomplete or invalid
 *               (error message was generated)
 *    #GNUNET_SYSERR on internal error
 *               (we could not even queue an error message,
 *                close HTTP session with MHD_NO)
 */
int
TMH_PARSE_post_json (struct MHD_Connection *connection,
                     void **con_cls,
                     const char *upload_data,
                     size_t *upload_data_size,
                     json_t **json)
{
  struct Buffer *r = *con_cls;

  *json = NULL;
  if (NULL == *con_cls)
  {
    /* We are seeing a fresh POST request. */
    r = GNUNET_new (struct Buffer);
    if (GNUNET_OK !=
        buffer_init (r,
                     upload_data,
                     *upload_data_size,
                     REQUEST_BUFFER_INITIAL,
                     REQUEST_BUFFER_MAX))
    {
      *con_cls = NULL;
      buffer_deinit (r);
      GNUNET_free (r);
      return (MHD_NO ==
              TMH_RESPONSE_reply_internal_error (connection,
                                                 "out of memory"))
        ? GNUNET_SYSERR : GNUNET_NO;
    }
    /* everything OK, wait for more POST data */
    *upload_data_size = 0;
    *con_cls = r;
    return GNUNET_YES;
  }
  if (0 != *upload_data_size)
  {
    /* We are seeing an old request with more data available. */

    if (GNUNET_OK !=
        buffer_append (r,
                       upload_data,
                       *upload_data_size,
                       REQUEST_BUFFER_MAX))
    {
      /* Request too long */
      *con_cls = NULL;
      buffer_deinit (r);
      GNUNET_free (r);
      return (MHD_NO ==
              TMH_RESPONSE_reply_request_too_large (connection))
        ? GNUNET_SYSERR : GNUNET_NO;
    }
    /* everything OK, wait for more POST data */
    *upload_data_size = 0;
    return GNUNET_YES;
  }

  /* We have seen the whole request. */

  *json = json_loadb (r->data,
                      r->fill,
                      0,
                      NULL);
  if (NULL == *json)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Failed to parse JSON request body\n");
    return (MHD_YES ==
            TMH_RESPONSE_reply_invalid_json (connection))
      ? GNUNET_NO : GNUNET_SYSERR;
  }
  buffer_deinit (r);
  GNUNET_free (r);
  *con_cls = NULL;

  return GNUNET_YES;
}


/**
 * Function called whenever we are done with a request
 * to clean up our state.
 *
 * @param con_cls value as it was left by
 *        #TMH_PARSE_post_json(), to be cleaned up
 */
void
TMH_PARSE_post_cleanup_callback (void *con_cls)
{
  struct Buffer *r = con_cls;

  if (NULL != r)
  {
    buffer_deinit (r);
    GNUNET_free (r);
  }
}


/**
 * Extract base32crockford encoded data from request.
 *
 * Queues an error response to the connection if the parameter is missing or
 * invalid.
 *
 * @param connection the MHD connection
 * @param param_name the name of the parameter with the key
 * @param[out] out_data pointer to store the result
 * @param out_size expected size of data
 * @return
 *   #GNUNET_YES if the the argument is present
 *   #GNUNET_NO if the argument is absent or malformed
 *   #GNUNET_SYSERR on internal error (error response could not be sent)
 */
int
TMH_PARSE_mhd_request_arg_data (struct MHD_Connection *connection,
                                const char *param_name,
                                void *out_data,
                                size_t out_size)
{
  const char *str;

  str = MHD_lookup_connection_value (connection,
                                     MHD_GET_ARGUMENT_KIND,
                                     param_name);
  if (NULL == str)
  {
    return (MHD_NO ==
            TMH_RESPONSE_reply_arg_missing (connection, param_name))
      ? GNUNET_SYSERR : GNUNET_NO;
  }
  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (str,
                                     strlen (str),
                                     out_data,
                                     out_size))
    return (MHD_NO ==
            TMH_RESPONSE_reply_arg_invalid (connection, param_name))
      ? GNUNET_SYSERR : GNUNET_NO;
  return GNUNET_OK;
}


/**
 * Extraxt variable-size base32crockford encoded data from request.
 *
 * Queues an error response to the connection if the parameter is missing
 * or the encoding is invalid.
 *
 * @param connection the MHD connection
 * @param param_name the name of the parameter with the key
 * @param[out] out_data pointer to allocate buffer and store the result
 * @param[out] out_size set to the size of the buffer allocated in @a out_data
 * @return
 *   #GNUNET_YES if the the argument is present
 *   #GNUNET_NO if the argument is absent or malformed
 *   #GNUNET_SYSERR on internal error (error response could not be sent)
 */
int
TMH_PARSE_mhd_request_var_arg_data (struct MHD_Connection *connection,
                                    const char *param_name,
                                    void **out_data,
                                    size_t *out_size)
{
  const char *str;
  size_t slen;
  size_t olen;
  void *out;

  str = MHD_lookup_connection_value (connection,
                                     MHD_GET_ARGUMENT_KIND,
                                     param_name);
  if (NULL == str)
  {
    return (MHD_NO ==
            TMH_RESPONSE_reply_arg_missing (connection, param_name))
      ? GNUNET_SYSERR : GNUNET_NO;
  }
  slen = strlen (str);
  olen = (slen * 5) / 8;
  out = GNUNET_malloc (olen);
  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (str,
                                     strlen (str),
                                     out,
                                     olen))
  {
    GNUNET_free (out);
    *out_size = 0;
    return (MHD_NO ==
            TMH_RESPONSE_reply_arg_invalid (connection, param_name))
      ? GNUNET_SYSERR : GNUNET_NO;
  }
  *out_data = out;
  *out_size = olen;
  return GNUNET_OK;
}


/**
 * Navigate through a JSON tree.
 *
 * Sends an error response if navigation is impossible (i.e.
 * the JSON object is invalid)
 *
 * @param connection the connection to send an error response to
 * @param root the JSON node to start the navigation at.
 * @param ... navigation specification (see `enum TMH_PARSE_JsonNavigationCommand`)
 * @return
 *    #GNUNET_YES if navigation was successful
 *    #GNUNET_NO if json is malformed, error response was generated
 *    #GNUNET_SYSERR on internal error (no response was generated,
 *                       connection must be closed)
 */
int
TMH_PARSE_navigate_json (struct MHD_Connection *connection,
                         const json_t *root,
                         ...)
{
  va_list argp;
  int ret;
  json_t *path; /* what's our current path from 'root'? */

  path = json_array ();
  va_start (argp, root);
  ret = 2; /* just not any of the valid return values */
  while (2 == ret)
  {
    enum TMH_PARSE_JsonNavigationCommand command
      = va_arg (argp,
                enum TMH_PARSE_JsonNavigationCommand);

    switch (command)
    {
    case TMH_PARSE_JNC_FIELD:
      {
        const char *fname = va_arg(argp, const char *);

        json_array_append_new (path,
                               json_string (fname));
        root = json_object_get (root,
                                fname);
        if (NULL == root)
        {
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:s, s:O}",
                                               "error", "missing field in JSON",
                                               "field", fname,
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
      }
      break;

    case TMH_PARSE_JNC_INDEX:
      {
        int fnum = va_arg(argp, int);

        json_array_append_new (path,
                               json_integer (fnum));
        root = json_array_get (root,
                               fnum);
        if (NULL == root)
        {
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:O}",
                                               "error", "missing index in JSON",
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
      }
      break;

    case TMH_PARSE_JNC_RET_DATA:
      {
        void *where = va_arg (argp, void *);
        size_t len = va_arg (argp, size_t);
        const char *str;
        int res;

        str = json_string_value (root);
        if (NULL == str)
        {
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                             MHD_HTTP_BAD_REQUEST,
                                             "{s:s, s:O}",
                                             "error", "string expected",
                                             "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        res = GNUNET_STRINGS_string_to_data (str, strlen (str),
                                             where, len);
        if (GNUNET_OK != res)
        {
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:O}",
                                               "error", "malformed binary data in JSON",
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        ret = GNUNET_OK;
      }
      break;

    case TMH_PARSE_JNC_RET_DATA_VAR:
      {
        void **where = va_arg (argp, void **);
        size_t *len = va_arg (argp, size_t *);
        const char *str;
        int res;

        str = json_string_value (root);
        if (NULL == str)
        {
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_internal_error (connection,
                                                    "json_string_value() failed"))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        *len = (strlen (str) * 5) / 8;
        if (NULL != where)
        {
          *where = GNUNET_malloc (*len);
          res = GNUNET_STRINGS_string_to_data (str,
                                               strlen (str),
                                               *where,
                                               *len);
          if (GNUNET_OK != res)
          {
            GNUNET_free (*where);
            *where = NULL;
            *len = 0;
            ret = (MHD_YES ==
                   TMH_RESPONSE_reply_json_pack (connection,
                                                 MHD_HTTP_BAD_REQUEST,
                                                 "{s:s, s:O}",
                                                 "error", "malformed binary data in JSON",
                                                 "path", path))
              ? GNUNET_NO : GNUNET_SYSERR;
            break;
          }
        }
        ret = GNUNET_OK;
      }
      break;

    case TMH_PARSE_JNC_RET_TYPED_JSON:
      {
        int typ = va_arg (argp, int);
        const json_t **r_json = va_arg (argp, const json_t **);

        if ( (NULL == root) ||
             ( (-1 != typ) &&
               (json_typeof (root) != typ)) )
        {
          *r_json = NULL;
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:i, s:i, s:O}",
                                               "error", "wrong JSON field type",
                                               "type_expected", typ,
                                               "type_actual", json_typeof (root),
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        *r_json = root;
        json_incref ((json_t *) root);
        ret = GNUNET_OK;
      }
      break;

    case TMH_PARSE_JNC_RET_UINT64:
      {
        uint64_t *r_u64 = va_arg (argp, uint64_t *);

        if (json_typeof (root) != JSON_INTEGER)
        {
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:s, s:i, s:O}",
                                               "error", "wrong JSON field type",
                                               "type_expected", "integer",
                                               "type_actual", json_typeof (root),
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        *r_u64 = (uint64_t) json_integer_value (root);
        ret = GNUNET_OK;
      }
      break;

    case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY:
      {
        struct TALER_DenominationPublicKey *where;
        size_t len;
        const char *str;
        int res;
        void *buf;

        where = va_arg (argp,
                        struct TALER_DenominationPublicKey *);
        str = json_string_value (root);
        if (NULL == str)
        {
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:O}",
                                               "error", "string expected",
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        len = (strlen (str) * 5) / 8;
        buf = GNUNET_malloc (len);
        res = GNUNET_STRINGS_string_to_data (str,
                                             strlen (str),
                                             buf,
                                             len);
        if (GNUNET_OK != res)
        {
          GNUNET_free (buf);
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:O}",
                                               "error", "malformed binary data in JSON",
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        where->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_decode (buf,
                                                                     len);
        GNUNET_free (buf);
        if (NULL == where->rsa_public_key)
        {
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:O}",
                                               "error", "malformed RSA public key in JSON",
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        ret = GNUNET_OK;
        break;
      }

    case TMH_PARSE_JNC_RET_RSA_SIGNATURE:
      {
        struct TALER_DenominationSignature *where;
        size_t len;
        const char *str;
        int res;
        void *buf;

        where = va_arg (argp,
                        struct TALER_DenominationSignature *);
        str = json_string_value (root);
        if (NULL == str)
        {
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:O}",
                                               "error", "string expected",
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        len = (strlen (str) * 5) / 8;
        buf = GNUNET_malloc (len);
        res = GNUNET_STRINGS_string_to_data (str,
                                             strlen (str),
                                             buf,
                                             len);
        if (GNUNET_OK != res)
        {
          GNUNET_free (buf);
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:O}",
                                               "error", "malformed binary data in JSON",
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        where->rsa_signature = GNUNET_CRYPTO_rsa_signature_decode (buf,
                                                                   len);
        GNUNET_free (buf);
        if (NULL == where->rsa_signature)
        {
          ret = (MHD_YES ==
                 TMH_RESPONSE_reply_json_pack (connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               "{s:s, s:O}",
                                               "error", "malformed RSA signature in JSON",
                                               "path", path))
            ? GNUNET_NO : GNUNET_SYSERR;
          break;
        }
        ret = GNUNET_OK;
        break;
      }

    case TMH_PARSE_JNC_RET_AMOUNT:
      {
        struct TALER_Amount *where = va_arg (argp, void *);

        if (GNUNET_OK !=
            TALER_json_to_amount ((json_t *) root,
                                  where))
        {
          if (MHD_YES !=
              TMH_RESPONSE_reply_json_pack (connection,
                                            MHD_HTTP_BAD_REQUEST,
                                            "{s:s, s:O}",
                                            "error", "Bad format",
                                            "path", path))
            return GNUNET_SYSERR;
          return GNUNET_NO;
        }
        if (0 != strcmp (where->currency,
                         TMH_mint_currency_string))
        {
          if (MHD_YES !=
              TMH_RESPONSE_reply_json_pack (connection,
                                            MHD_HTTP_BAD_REQUEST,
                                            "{s:s, s:O, s:s}",
                                            "error", "Currency not supported",
                                            "path", path,
                                            "currency", where->currency))
          {
            memset (where, 0, sizeof (struct TALER_Amount));
            return GNUNET_SYSERR;
          }
          memset (where, 0, sizeof (struct TALER_Amount));
          return GNUNET_NO;
        }
        ret = GNUNET_OK;
        break;
      }

    case TMH_PARSE_JNC_RET_TIME_ABSOLUTE:
      {
        struct GNUNET_TIME_Absolute *where = va_arg (argp, void *);

        if (GNUNET_OK !=
            TALER_json_to_abs ((json_t *) root,
                               where))
        {
          if (MHD_YES !=
              TMH_RESPONSE_reply_json_pack (connection,
                                            MHD_HTTP_BAD_REQUEST,
                                            "{s:s, s:s, s:O}",
                                            "error", "Bad format",
                                            "hint", "expected absolute time",
                                            "path", path))
            return GNUNET_SYSERR;
          return GNUNET_NO;
        }
        ret = GNUNET_OK;
        break;
      }

    default:
      GNUNET_break (0);
      ret = (MHD_YES ==
             TMH_RESPONSE_reply_internal_error (connection,
                                                "unhandled value in switch"))
        ? GNUNET_NO : GNUNET_SYSERR;
      break;
    }
  }
  va_end (argp);
  json_decref (path);
  return ret;
}


/**
 * Parse JSON object into components based on the given field
 * specification.
 *
 * @param connection the connection to send an error response to
 * @param root the JSON node to start the navigation at.
 * @param spec field specification for the parser
 * @return
 *    #GNUNET_YES if navigation was successful (caller is responsible
 *                for freeing allocated variable-size data using
 *                #TMH_PARSE_release_data() when done)
 *    #GNUNET_NO if json is malformed, error response was generated
 *    #GNUNET_SYSERR on internal error
 */
int
TMH_PARSE_json_data (struct MHD_Connection *connection,
                     const json_t *root,
                     struct TMH_PARSE_FieldSpecification *spec)
{
  unsigned int i;
  int ret;

  ret = GNUNET_YES;
  for (i=0; NULL != spec[i].field_name; i++)
  {
    if (GNUNET_YES != ret)
      break;
    switch (spec[i].command)
    {
    case TMH_PARSE_JNC_FIELD:
      GNUNET_break (0);
      return GNUNET_SYSERR;
    case TMH_PARSE_JNC_INDEX:
      GNUNET_break (0);
      return GNUNET_SYSERR;
    case TMH_PARSE_JNC_RET_DATA:
      ret = TMH_PARSE_navigate_json (connection,
                                     root,
                                     TMH_PARSE_JNC_FIELD,
                                     spec[i].field_name,
                                     TMH_PARSE_JNC_RET_DATA,
                                     spec[i].destination,
                                     spec[i].destination_size_in);
      break;
    case TMH_PARSE_JNC_RET_DATA_VAR:
      ret = TMH_PARSE_navigate_json (connection,
                                     root,
                                     TMH_PARSE_JNC_FIELD,
                                     spec[i].field_name,
                                     TMH_PARSE_JNC_RET_DATA_VAR,
                                     (void **) spec[i].destination,
                                     spec[i].destination_size_out);
      break;
    case TMH_PARSE_JNC_RET_TYPED_JSON:
      ret = TMH_PARSE_navigate_json (connection,
                                     root,
                                     TMH_PARSE_JNC_FIELD,
                                     spec[i].field_name,
                                     TMH_PARSE_JNC_RET_TYPED_JSON,
                                     spec[i].type,
                                     spec[i].destination);
      break;
    case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY:
      ret = TMH_PARSE_navigate_json (connection,
                                     root,
                                     TMH_PARSE_JNC_FIELD,
                                     spec[i].field_name,
                                     TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY,
                                     spec[i].destination);
      break;
    case TMH_PARSE_JNC_RET_RSA_SIGNATURE:
      ret = TMH_PARSE_navigate_json (connection,
                                     root,
                                     TMH_PARSE_JNC_FIELD,
                                     spec[i].field_name,
                                     TMH_PARSE_JNC_RET_RSA_SIGNATURE,
                                     spec[i].destination);
      break;
    case TMH_PARSE_JNC_RET_AMOUNT:
      GNUNET_assert (sizeof (struct TALER_Amount) ==
                     spec[i].destination_size_in);
      ret = TMH_PARSE_navigate_json (connection,
                                     root,
                                     TMH_PARSE_JNC_FIELD,
                                     spec[i].field_name,
                                     TMH_PARSE_JNC_RET_AMOUNT,
                                     spec[i].destination);
      break;
    case TMH_PARSE_JNC_RET_TIME_ABSOLUTE:
      GNUNET_assert (sizeof (struct GNUNET_TIME_Absolute) ==
                     spec[i].destination_size_in);
      ret = TMH_PARSE_navigate_json (connection,
                                     root,
                                     TMH_PARSE_JNC_FIELD,
                                     spec[i].field_name,
                                     TMH_PARSE_JNC_RET_TIME_ABSOLUTE,
                                     spec[i].destination);
      break;
    case TMH_PARSE_JNC_RET_UINT64:
      GNUNET_assert (sizeof (uint64_t) ==
                     spec[i].destination_size_in);
      ret = TMH_PARSE_navigate_json (connection,
                                     root,
                                     TMH_PARSE_JNC_FIELD,
                                     spec[i].field_name,
                                     TMH_PARSE_JNC_RET_UINT64,
                                     spec[i].destination);
      break;
    }
  }
  if (GNUNET_YES != ret)
    release_data (spec,
                  i);
  return ret;
}


/**
 * Release all memory allocated for the variable-size fields in
 * the parser specification.
 *
 * @param spec specification to free
 */
void
TMH_PARSE_release_data (struct TMH_PARSE_FieldSpecification *spec)
{
  unsigned int i;

  for (i=0; NULL != spec[i].field_name; i++) ;
  release_data (spec, i);
}


/**
 * Generate line in parser specification for 64-bit integer
 * given as an integer in JSON.
 *
 * @param field name of the field
 * @param[out] u64 integer to initialize
 * @return corresponding field spec
 */
struct TMH_PARSE_FieldSpecification
TMH_PARSE_member_uint64 (const char *field,
                         uint64_t *u64)
{
  struct TMH_PARSE_FieldSpecification ret =
    { field, (void *) u64, sizeof (uint64_t), NULL, TMH_PARSE_JNC_RET_UINT64, 0 };
  return ret;
}


/**
 * Generate line in parser specification for JSON object value.
 *
 * @param field name of the field
 * @param[out] jsonp address of pointer to JSON to initialize
 * @return corresponding field spec
 */
struct TMH_PARSE_FieldSpecification
TMH_PARSE_member_object (const char *field,
                         json_t **jsonp)
{
  struct TMH_PARSE_FieldSpecification ret =
    { field, jsonp, 0, NULL, TMH_PARSE_JNC_RET_TYPED_JSON, JSON_OBJECT };
  *jsonp = NULL;
  return ret;
}


/**
 * Generate line in parser specification for JSON array value.
 *
 * @param field name of the field
 * @param[out] jsonp address of JSON pointer to initialize
 * @return corresponding field spec
 */
struct TMH_PARSE_FieldSpecification
TMH_PARSE_member_array (const char *field,
                        json_t **jsonp)
{
  struct TMH_PARSE_FieldSpecification ret =
    { field, jsonp, 0, NULL, TMH_PARSE_JNC_RET_TYPED_JSON, JSON_ARRAY };
  *jsonp = NULL;
  return ret;
}


/**
 * Generate line in parser specification for an absolute time.
 *
 * @param field name of the field
 * @param[out] atime time to initialize
 */
struct TMH_PARSE_FieldSpecification
TMH_PARSE_member_time_abs (const char *field,
                           struct GNUNET_TIME_Absolute *atime)
{
  struct TMH_PARSE_FieldSpecification ret =
    { field, atime, sizeof(struct GNUNET_TIME_Absolute), NULL, TMH_PARSE_JNC_RET_TIME_ABSOLUTE, 0 };
  return ret;
}


/**
 * Generate line in parser specification for RSA public key.
 *
 * @param field name of the field
 * @param[out] pk key to initialize
 * @return corresponding field spec
 */
struct TMH_PARSE_FieldSpecification
TMH_PARSE_member_denomination_public_key (const char *field,
                                          struct TALER_DenominationPublicKey *pk)
{
  struct TMH_PARSE_FieldSpecification ret =
    { field, pk, 0, NULL, TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY, 0 };
  pk->rsa_public_key = NULL;
  return ret;
}


/**
 * Generate line in parser specification for RSA public key.
 *
 * @param field name of the field
 * @param sig the signature to initialize
 * @return corresponding field spec
 */
struct TMH_PARSE_FieldSpecification
TMH_PARSE_member_denomination_signature (const char *field,
                                         struct TALER_DenominationSignature *sig)
{
  struct TMH_PARSE_FieldSpecification ret =
    { field, sig, 0, NULL, TMH_PARSE_JNC_RET_RSA_SIGNATURE, 0 };
  sig->rsa_signature = NULL;
  return ret;
}


/**
 * Generate line in parser specification for an amount.
 *
 * @param field name of the field
 * @param amount a `struct TALER_Amount *` to initialize
 * @return corresponding field spec
 */
struct TMH_PARSE_FieldSpecification
TMH_PARSE_member_amount (const char *field,
                         struct TALER_Amount *amount)
{
  struct TMH_PARSE_FieldSpecification ret =
    { field, amount, sizeof(struct TALER_Amount), NULL, TMH_PARSE_JNC_RET_AMOUNT, 0 };
  memset (amount, 0, sizeof (struct TALER_Amount));
  return ret;
}


/**
 * Generate line in parser specification for variable-size value.
 *
 * @param field name of the field
 * @param[out] ptr pointer to initialize
 * @param[out] ptr_size size to initialize
 * @return corresponding field spec
 */
struct TMH_PARSE_FieldSpecification
TMH_PARSE_member_variable (const char *field,
                           void **ptr,
                           size_t *ptr_size)
{
  struct TMH_PARSE_FieldSpecification ret =
    { field, ptr, 0, ptr_size, TMH_PARSE_JNC_RET_DATA_VAR, 0 };
  *ptr = NULL;
  return ret;
}

/* end of taler-mint-httpd_parsing.c */