1050 lines
24 KiB
C
1050 lines
24 KiB
C
/*
|
|
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 <http://www.gnu.org/licenses/>
|
|
*/
|
|
/**
|
|
* @file json/json.c
|
|
* @brief helper functions for JSON processing using libjansson
|
|
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
|
|
* @author Christian Grothoff
|
|
*/
|
|
#include "platform.h"
|
|
#include <gnunet/gnunet_util_lib.h>
|
|
#include "taler_util.h"
|
|
#include "taler_json_lib.h"
|
|
#include <unistr.h>
|
|
|
|
|
|
/**
|
|
* 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_PrivateContractHashP *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_ExtensionContractHashP *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_ExtensionConfigHashP *ech)
|
|
{
|
|
return dump_and_hash (config,
|
|
"taler-extension-configuration",
|
|
&ech->hash);
|
|
}
|
|
|
|
|
|
/* End of json/json.c */
|