/*
  This file is part of TALER
  Copyright (C) 2014, 2015, 2016, 2020, 2021 Taler Systems SA
  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU 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 General Public License for more details.
  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see 
*/
/**
 * @file json/json.c
 * @brief helper functions for JSON processing using libjansson
 * @author Sree Harsha Totakura 
 * @author Christian Grothoff
 */
#include "platform.h"
#include 
#include "taler_util.h"
#include "taler_json_lib.h"
#include 
/**
 * Check if @a json contains a 'real' value anywhere.
 *
 * @param json json to check
 * @return true if a real is in it somewhere
 */
static bool
contains_real (const json_t *json)
{
  if (json_is_real (json))
    return true;
  if (json_is_object (json))
  {
    json_t *member;
    const char *name;
    json_object_foreach ((json_t *) json, name, member)
    if (contains_real (member))
      return true;
    return false;
  }
  if (json_is_array (json))
  {
    json_t *member;
    size_t index;
    json_array_foreach ((json_t *) json, index, member)
    if (contains_real (member))
      return true;
    return false;
  }
  return false;
}
/**
 * Dump character in the low range into @a buf
 * following RFC 8785.
 *
 * @param[in,out] buf buffer to modify
 * @param val value to dump
 */
static void
lowdump (struct GNUNET_Buffer *buf,
         unsigned char val)
{
  char scratch[7];
  switch (val)
  {
  case 0x8:
    GNUNET_buffer_write (buf,
                         "\\b",
                         2);
    break;
  case 0x9:
    GNUNET_buffer_write (buf,
                         "\\t",
                         2);
    break;
  case 0xA:
    GNUNET_buffer_write (buf,
                         "\\n",
                         2);
    break;
  case 0xC:
    GNUNET_buffer_write (buf,
                         "\\f",
                         2);
    break;
  case 0xD:
    GNUNET_buffer_write (buf,
                         "\\r",
                         2);
    break;
  default:
    GNUNET_snprintf (scratch,
                     sizeof (scratch),
                     "\\u%04x",
                     (unsigned int) val);
    GNUNET_buffer_write (buf,
                         scratch,
                         6);
    break;
  }
}
/**
 * Re-encode string at @a inp to match RFC 8785 (section 3.2.2.2).
 *
 * @param[in,out] inp pointer to string to re-encode
 * @return number of bytes in resulting @a inp
 */
static size_t
rfc8785encode (char **inp)
{
  struct GNUNET_Buffer buf = { 0 };
  size_t left = strlen (*inp) + 1;
  size_t olen;
  char *in = *inp;
  const char *pos = in;
  GNUNET_buffer_prealloc (&buf,
                          left + 40);
  buf.warn_grow = 0; /* disable, + 40 is just a wild guess */
  while (1)
  {
    int mbl = u8_mblen ((unsigned char *) pos,
                        left);
    unsigned char val;
    if (0 == mbl)
      break;
    val = (unsigned char) *pos;
    if ( (1 == mbl) &&
         (val <= 0x1F) )
    {
      /* Should not happen, as input is produced by
       * JSON stringification */
      GNUNET_break (0);
      lowdump (&buf,
               val);
    }
    else if ( (1 == mbl) && ('\\' == *pos) )
    {
      switch (*(pos + 1))
      {
      case '\\':
        mbl = 2;
        GNUNET_buffer_write (&buf,
                             pos,
                             mbl);
        break;
      case 'u':
        {
          unsigned int num;
          uint32_t n32;
          unsigned char res[8];
          size_t rlen;
          GNUNET_assert ( (1 ==
                           sscanf (pos + 2,
                                   "%4x",
                                   &num)) ||
                          (1 ==
                           sscanf (pos + 2,
                                   "%4X",
                                   &num)) );
          mbl = 6;
          n32 = (uint32_t) num;
          rlen = sizeof (res);
          u32_to_u8 (&n32,
                     1,
                     res,
                     &rlen);
          if ( (1 == rlen) &&
               (res[0] <= 0x1F) )
          {
            lowdump (&buf,
                     res[0]);
          }
          else
          {
            GNUNET_buffer_write (&buf,
                                 (const char *) res,
                                 rlen);
          }
        }
        break;
      default:
        mbl = 2;
        GNUNET_buffer_write (&buf,
                             pos,
                             mbl);
        break;
      }
    }
    else
    {
      GNUNET_buffer_write (&buf,
                           pos,
                           mbl);
    }
    left -= mbl;
    pos += mbl;
  }
  /* 0-terminate buffer */
  GNUNET_buffer_write (&buf,
                       "",
                       1);
  GNUNET_free (in);
  *inp = GNUNET_buffer_reap (&buf,
                             &olen);
  return olen;
}
/**
 * Dump the @a json to a string and hash it.
 *
 * @param json value to hash
 * @param salt salt value to include when using HKDF,
 *        NULL to not use any salt and to use SHA512
 * @param[out] hc where to store the hash
 * @return #GNUNET_OK on success,
 *         #GNUNET_NO if @a json was not hash-able
 *         #GNUNET_SYSERR on failure
 */
static enum GNUNET_GenericReturnValue
dump_and_hash (const json_t *json,
               const char *salt,
               struct GNUNET_HashCode *hc)
{
  char *wire_enc;
  size_t len;
  if (NULL == json)
  {
    GNUNET_break_op (0);
    return GNUNET_NO;
  }
  if (contains_real (json))
  {
    GNUNET_break_op (0);
    return GNUNET_NO;
  }
  if (NULL == (wire_enc = json_dumps (json,
                                      JSON_ENCODE_ANY
                                      | JSON_COMPACT
                                      | JSON_SORT_KEYS)))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  len = rfc8785encode (&wire_enc);
  if (NULL == salt)
  {
    GNUNET_CRYPTO_hash (wire_enc,
                        len,
                        hc);
  }
  else
  {
    if (GNUNET_YES !=
        GNUNET_CRYPTO_kdf (hc,
                           sizeof (*hc),
                           salt,
                           strlen (salt) + 1,
                           wire_enc,
                           len,
                           NULL,
                           0))
    {
      GNUNET_break (0);
      free (wire_enc);
      return GNUNET_SYSERR;
    }
  }
  free (wire_enc);
  return GNUNET_OK;
}
/**
 * Replace "forgettable" parts of a JSON object with their salted hash.
 *
 * @param[in] in some JSON value
 * @param[out] out resulting JSON value
 * @return #GNUNET_OK on success,
 *         #GNUNET_NO if @a json was not hash-able
 *         #GNUNET_SYSERR on failure
 */
static enum GNUNET_GenericReturnValue
forget (const json_t *in,
        json_t **out)
{
  if (json_is_real (in))
  {
    /* floating point is not allowed! */
    GNUNET_break_op (0);
    return GNUNET_NO;
  }
  if (json_is_array (in))
  {
    /* array is a JSON array */
    size_t index;
    json_t *value;
    json_t *ret;
    ret = json_array ();
    if (NULL == ret)
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
    json_array_foreach (in, index, value) {
      enum GNUNET_GenericReturnValue iret;
      json_t *t;
      iret = forget (value,
                     &t);
      if (GNUNET_OK != iret)
      {
        json_decref (ret);
        return iret;
      }
      if (0 != json_array_append_new (ret,
                                      t))
      {
        GNUNET_break (0);
        json_decref (ret);
        return GNUNET_SYSERR;
      }
    }
    *out = ret;
    return GNUNET_OK;
  }
  if (json_is_object (in))
  {
    json_t *ret;
    const char *key;
    json_t *value;
    json_t *fg;
    json_t *rx;
    fg = json_object_get (in,
                          "$forgettable");
    rx = json_object_get (in,
                          "$forgotten");
    if (NULL != rx)
    {
      rx = json_deep_copy (rx); /* should be shallow
                                   by structure, but
                                   deep copy is safer */
      if (NULL == rx)
      {
        GNUNET_break (0);
        return GNUNET_SYSERR;
      }
    }
    ret = json_object ();
    if (NULL == ret)
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
    json_object_foreach ((json_t*) in, key, value) {
      json_t *t;
      json_t *salt;
      enum GNUNET_GenericReturnValue iret;
      if (fg == value)
        continue; /* skip! */
      if (rx == value)
        continue; /* skip! */
      if ( (NULL != rx) &&
           (NULL !=
            json_object_get (rx,
                             key)) )
      {
        (void) json_object_del (ret,
                                key);
        continue; /* already forgotten earlier */
      }
      iret = forget (value,
                     &t);
      if (GNUNET_OK != iret)
      {
        json_decref (ret);
        json_decref (rx);
        return iret;
      }
      if ( (NULL != fg) &&
           (NULL != (salt = json_object_get (fg,
                                             key))) )
      {
        /* 't' is to be forgotten! */
        struct GNUNET_HashCode hc;
        if (! json_is_string (salt))
        {
          GNUNET_break_op (0);
          json_decref (ret);
          json_decref (rx);
          json_decref (t);
          return GNUNET_NO;
        }
        iret = dump_and_hash (t,
                              json_string_value (salt),
                              &hc);
        if (GNUNET_OK != iret)
        {
          json_decref (ret);
          json_decref (rx);
          json_decref (t);
          return iret;
        }
        json_decref (t);
        /* scrub salt */
        if (0 !=
            json_object_del (fg,
                             key))
        {
          GNUNET_break_op (0);
          json_decref (ret);
          json_decref (rx);
          return GNUNET_NO;
        }
        if (NULL == rx)
          rx = json_object ();
        if (NULL == rx)
        {
          GNUNET_break (0);
          json_decref (ret);
          return GNUNET_SYSERR;
        }
        if (0 !=
            json_object_set_new (rx,
                                 key,
                                 GNUNET_JSON_from_data_auto (&hc)))
        {
          GNUNET_break (0);
          json_decref (ret);
          json_decref (rx);
          return GNUNET_SYSERR;
        }
      }
      else
      {
        /* 't' to be used without 'forgetting' */
        if (0 !=
            json_object_set_new (ret,
                                 key,
                                 t))
        {
          GNUNET_break (0);
          json_decref (ret);
          json_decref (rx);
          return GNUNET_SYSERR;
        }
      }
    } /* json_object_foreach */
    if ( (NULL != rx) &&
         (0 !=
          json_object_set_new (ret,
                               "$forgotten",
                               rx)) )
    {
      GNUNET_break (0);
      json_decref (ret);
      return GNUNET_SYSERR;
    }
    *out = ret;
    return GNUNET_OK;
  }
  *out = json_incref ((json_t *) in);
  return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
TALER_JSON_contract_hash (const json_t *json,
                          struct TALER_PrivateContractHash *hc)
{
  enum GNUNET_GenericReturnValue ret;
  json_t *cjson;
  json_t *dc;
  dc = json_deep_copy (json);
  ret = forget (dc,
                &cjson);
  json_decref (dc);
  if (GNUNET_OK != ret)
    return ret;
  ret = dump_and_hash (cjson,
                       NULL,
                       &hc->hash);
  json_decref (cjson);
  return ret;
}
enum GNUNET_GenericReturnValue
TALER_JSON_contract_mark_forgettable (json_t *json,
                                      const char *field)
{
  json_t *fg;
  struct GNUNET_ShortHashCode salt;
  if (! json_is_object (json))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  /* check field name is legal for forgettable field */
  for (const char *f = field; '\0' != *f; f++)
  {
    char c = *f;
    if ( (c >= 'a') && (c <= 'z') )
      continue;
    if ( (c >= 'A') && (c <= 'Z') )
      continue;
    if ( (c >= '0') && (c <= '9') )
      continue;
    if ('_' == c)
      continue;
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  if (NULL == json_object_get (json,
                               field))
  {
    /* field must exist */
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  fg = json_object_get (json,
                        "$forgettable");
  if (NULL == fg)
  {
    fg = json_object ();
    if (0 !=
        json_object_set_new (json,
                             "$forgettable",
                             fg))
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
  }
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                              &salt,
                              sizeof (salt));
  if (0 !=
      json_object_set_new (fg,
                           field,
                           GNUNET_JSON_from_data_auto (&salt)))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
TALER_JSON_contract_part_forget (json_t *json,
                                 const char *field)
{
  json_t *fg;
  const json_t *part;
  json_t *fp;
  json_t *rx;
  struct GNUNET_HashCode hc;
  const char *salt;
  enum GNUNET_GenericReturnValue ret;
  if (! json_is_object (json))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  if (NULL == (part = json_object_get (json,
                                       field)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Did not find field `%s' we were asked to forget\n",
                field);
    return GNUNET_SYSERR;
  }
  fg = json_object_get (json,
                        "$forgettable");
  if (NULL == fg)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Did not find '$forgettable' attribute trying to forget field `%s'\n",
                field);
    return GNUNET_SYSERR;
  }
  rx = json_object_get (json,
                        "$forgotten");
  if (NULL == rx)
  {
    rx = json_object ();
    if (0 !=
        json_object_set_new (json,
                             "$forgotten",
                             rx))
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
  }
  if (NULL !=
      json_object_get (rx,
                       field))
  {
    if (! json_is_null (json_object_get (json,
                                         field)))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Field `%s' market as forgotten, but still exists!\n",
                  field);
      return GNUNET_SYSERR;
    }
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Already forgot field `%s'\n",
                field);
    return GNUNET_NO;
  }
  salt = json_string_value (json_object_get (fg,
                                             field));
  if (NULL == salt)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Did not find required salt to forget field `%s'\n",
                field);
    return GNUNET_SYSERR;
  }
  /* need to recursively forget to compute 'hc' */
  ret = forget (part,
                &fp);
  if (GNUNET_OK != ret)
    return ret;
  if (GNUNET_OK !=
      dump_and_hash (fp,
                     salt,
                     &hc))
  {
    json_decref (fp);
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  json_decref (fp);
  /* drop salt */
  if (0 !=
      json_object_del (fg,
                       field))
  {
    json_decref (fp);
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  /* remember field as 'forgotten' */
  if (0 !=
      json_object_set_new (rx,
                           field,
                           GNUNET_JSON_from_data_auto (&hc)))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  /* finally, set 'forgotten' field to null */
  if (0 !=
      json_object_del (json,
                       field))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
/**
 * Look over all of the values of a '$forgettable' object.  Replace 'True'
 * values with proper random salts.  Fails if any forgettable values are
 * neither 'True' nor valid salts (strings).
 *
 * @param[in,out] f JSON to transform
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
seed_forgettable (json_t *f)
{
  const char *key;
  json_t *val;
  json_object_foreach (f,
                       key,
                       val)
  {
    if (json_is_string (val))
      continue;
    if (json_is_true (val))
    {
      struct GNUNET_ShortHashCode sh;
      GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                                  &sh,
                                  sizeof (sh));
      if (0 !=
          json_object_set_new (f,
                               key,
                               GNUNET_JSON_from_data_auto (&sh)))
      {
        GNUNET_break (0);
        return GNUNET_SYSERR;
      }
      continue;
    }
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Forgettable field `%s' has invalid value\n",
                key);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
/**
 * Take a given contract with "forgettable" fields marked
 * but with 'True' instead of a real salt. Replaces all
 * 'True' values with proper random salts.  Fails if any
 * forgettable markers are neither 'True' nor valid salts.
 *
 * @param[in,out] json JSON to transform
 * @return #GNUNET_OK on success
 */
enum GNUNET_GenericReturnValue
TALER_JSON_contract_seed_forgettable (json_t *json)
{
  if (json_is_object (json))
  {
    const char *key;
    json_t *val;
    json_object_foreach (json,
                         key,
                         val)
    {
      if (0 == strcmp ("$forgettable",
                       key))
      {
        if (GNUNET_OK !=
            seed_forgettable (val))
          return GNUNET_SYSERR;
        continue;
      }
      if (GNUNET_OK !=
          TALER_JSON_contract_seed_forgettable (val))
        return GNUNET_SYSERR;
    }
  }
  if (json_is_array (json))
  {
    size_t index;
    json_t *val;
    json_array_foreach (json,
                        index,
                        val)
    {
      if (GNUNET_OK !=
          TALER_JSON_contract_seed_forgettable (val))
        return GNUNET_SYSERR;
    }
  }
  return GNUNET_OK;
}
/**
 * Parse a json path.
 *
 * @param obj the object that the path is relative to.
 * @param prev the parent of @e obj.
 * @param path the path to parse.
 * @param cb the callback to call, if we get to the end of @e path.
 * @param cb_cls the closure for the callback.
 * @return #GNUNET_OK on success, #GNUNET_SYSERR if @e path is malformed.
 */
static enum GNUNET_GenericReturnValue
parse_path (json_t *obj,
            json_t *prev,
            const char *path,
            TALER_JSON_ExpandPathCallback cb,
            void *cb_cls)
{
  char *id = GNUNET_strdup (path);
  char *next_id = strchr (id,
                          '.');
  char *next_path;
  char *bracket;
  json_t *next_obj = NULL;
  char *next_dot;
  if (NULL == next_id)
  {
    cb (cb_cls,
        id,
        prev);
    GNUNET_free (id);
    return GNUNET_OK;
  }
  bracket = strchr (next_id,
                    '[');
  *next_id = '\0';
  next_id++;
  next_path = GNUNET_strdup (next_id);
  next_dot = strchr (next_id,
                     '.');
  if (NULL != next_dot)
    *next_dot = '\0';
  /* If this is the first time this is called, make sure id is "$" */
  if ( (NULL == prev) &&
       (0 != strcmp (id,
                     "$")))
  {
    GNUNET_free (id);
    GNUNET_free (next_path);
    return GNUNET_SYSERR;
  }
  /* Check for bracketed indices */
  if (NULL != bracket)
  {
    char *end_bracket = strchr (bracket,
                                ']');
    if (NULL == end_bracket)
    {
      GNUNET_free (id);
      GNUNET_free (next_path);
      return GNUNET_SYSERR;
    }
    *end_bracket = '\0';
    *bracket = '\0';
    bracket++;
    json_t *array = json_object_get (obj,
                                     next_id);
    if (0 == strcmp (bracket,
                     "*"))
    {
      size_t index;
      json_t *value;
      int ret = GNUNET_OK;
      json_array_foreach (array, index, value) {
        ret = parse_path (value,
                          obj,
                          next_path,
                          cb,
                          cb_cls);
        if (GNUNET_OK != ret)
        {
          GNUNET_free (id);
          GNUNET_free (next_path);
          return ret;
        }
      }
    }
    else
    {
      unsigned int index;
      char dummy;
      if (1 != sscanf (bracket,
                       "%u%c",
                       &index,
                       &dummy))
      {
        GNUNET_free (id);
        GNUNET_free (next_path);
        return GNUNET_SYSERR;
      }
      next_obj = json_array_get (array,
                                 index);
    }
  }
  else
  {
    /* No brackets, so just fetch the object by name */
    next_obj = json_object_get (obj,
                                next_id);
  }
  if (NULL != next_obj)
  {
    int ret = parse_path (next_obj,
                          obj,
                          next_path,
                          cb,
                          cb_cls);
    GNUNET_free (id);
    GNUNET_free (next_path);
    return ret;
  }
  GNUNET_free (id);
  GNUNET_free (next_path);
  return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
TALER_JSON_expand_path (json_t *json,
                        const char *path,
                        TALER_JSON_ExpandPathCallback cb,
                        void *cb_cls)
{
  return parse_path (json,
                     NULL,
                     path,
                     cb,
                     cb_cls);
}
enum TALER_ErrorCode
TALER_JSON_get_error_code (const json_t *json)
{
  const json_t *jc;
  if (NULL == json)
    return TALER_EC_GENERIC_INVALID_RESPONSE;
  jc = json_object_get (json, "code");
  /* The caller already knows that the JSON represents an error,
     so we are dealing with a missing error code here.  */
  if (NULL == jc)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Expected Taler error code `code' in JSON, but field does not exist!\n");
    return TALER_EC_INVALID;
  }
  if (json_is_integer (jc))
    return (enum TALER_ErrorCode) json_integer_value (jc);
  GNUNET_break_op (0);
  return TALER_EC_INVALID;
}
const char *
TALER_JSON_get_error_hint (const json_t *json)
{
  const json_t *jc;
  if (NULL == json)
    return NULL;
  jc = json_object_get (json,
                        "hint");
  if (NULL == jc)
    return NULL; /* no hint, is allowed */
  if (! json_is_string (jc))
  {
    /* Hints must be strings */
    GNUNET_break_op (0);
    return NULL;
  }
  return json_string_value (jc);
}
enum TALER_ErrorCode
TALER_JSON_get_error_code2 (const void *data,
                            size_t data_size)
{
  json_t *json;
  enum TALER_ErrorCode ec;
  json_error_t err;
  json = json_loadb (data,
                     data_size,
                     JSON_REJECT_DUPLICATES,
                     &err);
  if (NULL == json)
    return TALER_EC_INVALID;
  ec = TALER_JSON_get_error_code (json);
  json_decref (json);
  if (ec == TALER_EC_NONE)
    return TALER_EC_INVALID;
  return ec;
}
void
TALER_deposit_extension_hash (const json_t *extensions,
                              struct TALER_ExtensionContractHash *ech)
{
  GNUNET_assert (GNUNET_OK ==
                 dump_and_hash (extensions,
                                "taler-contract-extensions",
                                &ech->hash));
}
char *
TALER_JSON_canonicalize (const json_t *input)
{
  char *wire_enc;
  if (NULL == (wire_enc = json_dumps (input,
                                      JSON_ENCODE_ANY
                                      | JSON_COMPACT
                                      | JSON_SORT_KEYS)))
  {
    GNUNET_break (0);
    return NULL;
  }
  rfc8785encode (&wire_enc);
  return wire_enc;
}
enum GNUNET_GenericReturnValue
TALER_JSON_extensions_config_hash (const json_t *config,
                                   struct TALER_ExtensionConfigHash *ech)
{
  return dump_and_hash (config,
                        "taler-extension-configuration",
                        &ech->hash);
}
/* End of json/json.c */