diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 9 | ||||
| -rw-r--r-- | src/cJSON.c | 1404 | ||||
| -rw-r--r-- | src/cJSON.h | 187 | ||||
| -rw-r--r-- | src/cJSON.readme | 270 | ||||
| -rw-r--r-- | src/gpgme-json.c | 1327 | 
5 files changed, 3195 insertions, 2 deletions
| diff --git a/src/Makefile.am b/src/Makefile.am index ce6f1d4e..c2d4a843 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,7 +26,7 @@ m4datadir = $(datadir)/aclocal  m4data_DATA = gpgme.m4  nodist_include_HEADERS = gpgme.h -bin_PROGRAMS = gpgme-tool +bin_PROGRAMS = gpgme-tool gpgme-json  if BUILD_W32_GLIB  ltlib_gpgme_glib = libgpgme-glib.la @@ -95,13 +95,18 @@ if BUILD_W32_GLIB  libgpgme_glib_la_SOURCES = $(main_sources) w32-glib-io.c  endif -# We use a global CFLAGS setting for all library +# We use a global CFLAGS setting for all libraries  # versions, because then every object file is only compiled once.  AM_CFLAGS = @LIBASSUAN_CFLAGS@ @GLIB_CFLAGS@  gpgme_tool_SOURCES = gpgme-tool.c argparse.c argparse.h  gpgme_tool_LDADD = libgpgme.la @LIBASSUAN_LIBS@ +gpgme_json_SOURCES = gpgme-json.c cJSON.c cJSON.h +gpgme_json_LDADD = -lm libgpgme.la $(GPG_ERROR_LIBS) +# We use -no-install temporary during development. +gpgme_json_LDFLAGS = -no-install +  if HAVE_W32_SYSTEM  # Windows provides us with an endless stream of Tough Love.  To spawn diff --git a/src/cJSON.c b/src/cJSON.c new file mode 100644 index 00000000..cf0cb132 --- /dev/null +++ b/src/cJSON.c @@ -0,0 +1,1404 @@ +/* cJSON.c - JSON parser in C. + * Copyright (c) 2009 Dave Gamble + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + * + * Note that this code has been modified from the original code taken + * from cjson-code-58.zip. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <stdlib.h> +#include <float.h> +#include <limits.h> +#include <ctype.h> +#include <errno.h> + +#include "cJSON.h" + +/* We use malloc function wrappers from gpgrt (aka libgpg-error).  */ +#if 1 +# include <gpgrt.h> +# define xtrymalloc(a)   gpgrt_malloc ((a)) +# define xtrycalloc(a,b) gpgrt_calloc ((a), (b)) +# define xtrystrdup(a)   gpgrt_strdup ((a)) +# define xfree(a)        gpgrt_free ((a)) +#else +# define xtrymalloc(a)   malloc ((a)) +# define xtrycalloc(a,b) calloc ((a), (b)) +# define xtrystrdup(a)   strdup ((a)) +# define xfree(a)        free ((a)) +#endif + + +static int +cJSON_strcasecmp (const char *s1, const char *s2) +{ +  if (!s1) +    return (s1 == s2) ? 0 : 1; +  if (!s2) +    return 1; +  for (; tolower (*(const unsigned char *)s1) +         == tolower (*(const unsigned char *) s2); ++s1, ++s2) +    if (*s1 == 0) +      return 0; +  return tolower (*(const unsigned char *) s1) - +    tolower (*(const unsigned char *) s2); +} + +/* Internal constructor. */ +static cJSON * +cJSON_New_Item (void) +{ +  return xtrycalloc (1, sizeof (cJSON)); +} + +/* Delete a cJSON structure.  (Does not clobber ERRNO). */ +void +cJSON_Delete (cJSON * c) +{ +  cJSON *next; +  int save_errno; + +  if (!c) +    return; + +  save_errno = errno; +  while (c) +    { +      next = c->next; +      if (!(c->type & cJSON_IsReference) && c->child) +	cJSON_Delete (c->child); +      if (!(c->type & cJSON_IsReference) && c->valuestring) +	xfree (c->valuestring); +      if (c->string) +	xfree (c->string); +      xfree (c); +      c = next; +    } +  errno = save_errno; +} + +/* Parse the input text to generate a number, and populate the result + * into item. */ +static const char * +parse_number (cJSON * item, const char *num) +{ +  double n = 0, sign = 1, scale = 0; +  int subscale = 0, signsubscale = 1; + +  if (*num == '-') +    sign = -1, num++;		/* Has sign? */ +  if (*num == '0') +    num++;			/* is zero */ +  if (*num >= '1' && *num <= '9') +    do +      n = (n * 10.0) + (*num++ - '0'); +    while (*num >= '0' && *num <= '9');	/* Number? */ +  if (*num == '.' && num[1] >= '0' && num[1] <= '9') +    { +      num++; +      do +	n = (n * 10.0) + (*num++ - '0'), scale--; +      while (*num >= '0' && *num <= '9'); +    }				/* Fractional part? */ +  if (*num == 'e' || *num == 'E')	/* Exponent? */ +    { +      num++; +      if (*num == '+') +	num++; +      else if (*num == '-') +	signsubscale = -1, num++;	/* With sign? */ +      while (*num >= '0' && *num <= '9') +	subscale = (subscale * 10) + (*num++ - '0');	/* Number? */ +    } + +  /* number = +/- number.fraction * 10^+/- exponent */ +  n = sign * n * pow (10.0, (scale + subscale * signsubscale)); + +  item->valuedouble = n; +  item->valueint = (int) n; +  item->type = cJSON_Number; +  return num; +} + +/* Render the number nicely from the given item into a string. */ +static char * +print_number (cJSON * item) +{ +  char *str; +  double d = item->valuedouble; +  if (fabs (((double) item->valueint) - d) <= DBL_EPSILON && d <= INT_MAX +      && d >= INT_MIN) +    { +      /* 2^64+1 can be represented in 21 chars. */ +      str = xtrymalloc (21); +      if (str) +	sprintf (str, "%d", item->valueint); +    } +  else +    { +      str = xtrymalloc (64);	/* This is a nice tradeoff. */ +      if (str) +	{ +	  if (fabs (floor (d) - d) <= DBL_EPSILON && fabs (d) < 1.0e60) +	    sprintf (str, "%.0f", d); +	  else if (fabs (d) < 1.0e-6 || fabs (d) > 1.0e9) +	    sprintf (str, "%e", d); +	  else +	    sprintf (str, "%f", d); +	} +    } +  return str; +} + +static unsigned +parse_hex4 (const char *str) +{ +  unsigned h = 0; +  if (*str >= '0' && *str <= '9') +    h += (*str) - '0'; +  else if (*str >= 'A' && *str <= 'F') +    h += 10 + (*str) - 'A'; +  else if (*str >= 'a' && *str <= 'f') +    h += 10 + (*str) - 'a'; +  else +    return 0; +  h = h << 4; +  str++; +  if (*str >= '0' && *str <= '9') +    h += (*str) - '0'; +  else if (*str >= 'A' && *str <= 'F') +    h += 10 + (*str) - 'A'; +  else if (*str >= 'a' && *str <= 'f') +    h += 10 + (*str) - 'a'; +  else +    return 0; +  h = h << 4; +  str++; +  if (*str >= '0' && *str <= '9') +    h += (*str) - '0'; +  else if (*str >= 'A' && *str <= 'F') +    h += 10 + (*str) - 'A'; +  else if (*str >= 'a' && *str <= 'f') +    h += 10 + (*str) - 'a'; +  else +    return 0; +  h = h << 4; +  str++; +  if (*str >= '0' && *str <= '9') +    h += (*str) - '0'; +  else if (*str >= 'A' && *str <= 'F') +    h += 10 + (*str) - 'A'; +  else if (*str >= 'a' && *str <= 'f') +    h += 10 + (*str) - 'a'; +  else +    return 0; +  return h; +} + +/* Parse the input text into an unescaped cstring, and populate item. */ +static const unsigned char firstByteMark[7] = +  { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; +static const char * +parse_string (cJSON * item, const char *str, const char **ep) +{ +  const char *ptr = str + 1; +  char *ptr2; +  char *out; +  int len = 0; +  unsigned uc, uc2; +  if (*str != '\"') +    { +      *ep = str; +      return 0; +    }				/* not a string! */ + +  while (*ptr != '\"' && *ptr && ++len) +    if (*ptr++ == '\\') +      ptr++;			/* Skip escaped quotes. */ + +  out = xtrymalloc (len + 1);	/* This is how long we need for the +                                   string, roughly. */ +  if (!out) +    return 0; + +  ptr = str + 1; +  ptr2 = out; +  while (*ptr != '\"' && *ptr) +    { +      if (*ptr != '\\') +	*ptr2++ = *ptr++; +      else +	{ +	  ptr++; +	  switch (*ptr) +	    { +	    case 'b': +	      *ptr2++ = '\b'; +	      break; +	    case 'f': +	      *ptr2++ = '\f'; +	      break; +	    case 'n': +	      *ptr2++ = '\n'; +	      break; +	    case 'r': +	      *ptr2++ = '\r'; +	      break; +	    case 't': +	      *ptr2++ = '\t'; +	      break; +	    case 'u':		/* transcode utf16 to utf8. */ +	      uc = parse_hex4 (ptr + 1); +	      ptr += 4;		/* get the unicode char. */ + +	      if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) +		break;		/* check for invalid.   */ + +	      if (uc >= 0xD800 && uc <= 0xDBFF)	/* UTF16 surrogate pairs. */ +		{ +		  if (ptr[1] != '\\' || ptr[2] != 'u') +		    break;	/* missing second-half of surrogate.    */ +		  uc2 = parse_hex4 (ptr + 3); +		  ptr += 6; +		  if (uc2 < 0xDC00 || uc2 > 0xDFFF) +		    break;	/* invalid second-half of surrogate.    */ +		  uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF)); +		} + +	      len = 4; +	      if (uc < 0x80) +		len = 1; +	      else if (uc < 0x800) +		len = 2; +	      else if (uc < 0x10000) +		len = 3; +	      ptr2 += len; + +	      switch (len) +		{ +		case 4: +		  *--ptr2 = ((uc | 0x80) & 0xBF); +		  uc >>= 6; +		case 3: +		  *--ptr2 = ((uc | 0x80) & 0xBF); +		  uc >>= 6; +		case 2: +		  *--ptr2 = ((uc | 0x80) & 0xBF); +		  uc >>= 6; +		case 1: +		  *--ptr2 = (uc | firstByteMark[len]); +		} +	      ptr2 += len; +	      break; +	    default: +	      *ptr2++ = *ptr; +	      break; +	    } +	  ptr++; +	} +    } +  *ptr2 = 0; +  if (*ptr == '\"') +    ptr++; +  item->valuestring = out; +  item->type = cJSON_String; +  return ptr; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static char * +print_string_ptr (const char *str) +{ +  const char *ptr; +  char *ptr2, *out; +  int len = 0; +  unsigned char token; + +  if (!str) +    return xtrystrdup (""); +  ptr = str; +  while ((token = *ptr) && ++len) +    { +      if (strchr ("\"\\\b\f\n\r\t", token)) +	len++; +      else if (token < 32) +	len += 5; +      ptr++; +    } + +  out = xtrymalloc (len + 3); +  if (!out) +    return 0; + +  ptr2 = out; +  ptr = str; +  *ptr2++ = '\"'; +  while (*ptr) +    { +      if ((unsigned char) *ptr > 31 && *ptr != '\"' && *ptr != '\\') +	*ptr2++ = *ptr++; +      else +	{ +	  *ptr2++ = '\\'; +	  switch (token = *ptr++) +	    { +	    case '\\': +	      *ptr2++ = '\\'; +	      break; +	    case '\"': +	      *ptr2++ = '\"'; +	      break; +	    case '\b': +	      *ptr2++ = 'b'; +	      break; +	    case '\f': +	      *ptr2++ = 'f'; +	      break; +	    case '\n': +	      *ptr2++ = 'n'; +	      break; +	    case '\r': +	      *ptr2++ = 'r'; +	      break; +	    case '\t': +	      *ptr2++ = 't'; +	      break; +	    default: +	      sprintf (ptr2, "u%04x", token); +	      ptr2 += 5; +	      break;		/* escape and print */ +	    } +	} +    } +  *ptr2++ = '\"'; +  *ptr2++ = 0; +  return out; +} + +/* Invote print_string_ptr (which is useful) on an item. */ +static char * +print_string (cJSON * item) +{ +  return print_string_ptr (item->valuestring); +} + +/* Predeclare these prototypes. */ +static const char *parse_value (cJSON * item, const char *value, +                                const char **ep); +static char *print_value (cJSON * item, int depth, int fmt); +static const char *parse_array (cJSON * item, const char *value, +                                const char **ep); +static char *print_array (cJSON * item, int depth, int fmt); +static const char *parse_object (cJSON * item, const char *value, +                                 const char **ep); +static char *print_object (cJSON * item, int depth, int fmt); + +/* Utility to jump whitespace and cr/lf */ +static const char * +skip (const char *in) +{ +  while (in && *in && (unsigned char) *in <= 32) +    in++; +  return in; +} + +/* Parse an object - create a new root, and populate. */ +cJSON * +cJSON_ParseWithOpts (const char *value, const char **return_parse_end, +		     int require_null_terminated, size_t *r_erroff) +{ +  const char *end = 0; +  const char *ep = 0; +  cJSON *c; + +  if (r_erroff) +    *r_erroff = 0; + +  c = cJSON_New_Item (); +  if (!c) +    return NULL; /* memory fail */ + +  end = parse_value (c, skip (value), &ep); +  if (!end) +    { +      cJSON_Delete (c); +      errno = EINVAL; +      if (r_erroff) +        *r_erroff = ep - value; +      return 0; +    }				/* parse failure. ep is set. */ + +  /* if we require null-terminated JSON without appended garbage, skip +     and then check for a null terminator */ +  if (require_null_terminated) +    { +      end = skip (end); +      if (*end) +	{ +	  cJSON_Delete (c); +	  ep = end; +          errno = EINVAL; +          if (r_erroff) +            *r_erroff = ep - value; +	  return 0; +	} +    } +  if (return_parse_end) +    *return_parse_end = end; +  return c; +} + +/* Default options for cJSON_Parse */ +cJSON * +cJSON_Parse (const char *value, size_t *r_erroff) +{ +  return cJSON_ParseWithOpts (value, 0, 0, r_erroff); +} + +/* Render a cJSON item/entity/structure to text. */ +char * +cJSON_Print (cJSON * item) +{ +  return print_value (item, 0, 1); +} + +char * +cJSON_PrintUnformatted (cJSON * item) +{ +  return print_value (item, 0, 0); +} + +/* Parser core - when encountering text, process appropriately. */ +static const char * +parse_value (cJSON * item, const char *value, const char **ep) +{ +  if (!value) +    return 0;			/* Fail on null. */ +  if (!strncmp (value, "null", 4)) +    { +      item->type = cJSON_NULL; +      return value + 4; +    } +  if (!strncmp (value, "false", 5)) +    { +      item->type = cJSON_False; +      return value + 5; +    } +  if (!strncmp (value, "true", 4)) +    { +      item->type = cJSON_True; +      item->valueint = 1; +      return value + 4; +    } +  if (*value == '\"') +    { +      return parse_string (item, value, ep); +    } +  if (*value == '-' || (*value >= '0' && *value <= '9')) +    { +      return parse_number (item, value); +    } +  if (*value == '[') +    { +      return parse_array (item, value, ep); +    } +  if (*value == '{') +    { +      return parse_object (item, value, ep); +    } + +  *ep = value; +  return 0;			/* failure. */ +} + +/* Render a value to text. */ +static char * +print_value (cJSON * item, int depth, int fmt) +{ +  char *out = 0; +  if (!item) +    return 0; +  switch ((item->type) & 255) +    { +    case cJSON_NULL: +      out = xtrystrdup ("null"); +      break; +    case cJSON_False: +      out = xtrystrdup ("false"); +      break; +    case cJSON_True: +      out = xtrystrdup ("true"); +      break; +    case cJSON_Number: +      out = print_number (item); +      break; +    case cJSON_String: +      out = print_string (item); +      break; +    case cJSON_Array: +      out = print_array (item, depth, fmt); +      break; +    case cJSON_Object: +      out = print_object (item, depth, fmt); +      break; +    } +  return out; +} + +/* Build an array from input text. */ +static const char * +parse_array (cJSON * item, const char *value, const char **ep) +{ +  cJSON *child; +  if (*value != '[') +    { +      *ep = value; +      return 0; +    }				/* not an array! */ + +  item->type = cJSON_Array; +  value = skip (value + 1); +  if (*value == ']') +    return value + 1;		/* empty array. */ + +  item->child = child = cJSON_New_Item (); +  if (!item->child) +    return 0;			/* memory fail */ +  /* skip any spacing, get the value. */ +  value = skip (parse_value (child, skip (value), ep)); +  if (!value) +    return 0; + +  while (*value == ',') +    { +      cJSON *new_item; +      if (!(new_item = cJSON_New_Item ())) +	return 0;		/* memory fail */ +      child->next = new_item; +      new_item->prev = child; +      child = new_item; +      value = skip (parse_value (child, skip (value + 1), ep)); +      if (!value) +	return 0;		/* memory fail */ +    } + +  if (*value == ']') +    return value + 1;		/* end of array */ +  *ep = value; +  return 0;			/* malformed. */ +} + +/* Render an array to text */ +static char * +print_array (cJSON * item, int depth, int fmt) +{ +  char **entries; +  char *out = 0, *ptr, *ret; +  int len = 5; +  cJSON *child = item->child; +  int numentries = 0, i = 0, fail = 0; + +  /* How many entries in the array? */ +  while (child) +    numentries++, child = child->next; +  /* Explicitly handle numentries==0 */ +  if (!numentries) +    { +      out = xtrymalloc (3); +      if (out) +	strcpy (out, "[]"); +      return out; +    } +  /* Allocate an array to hold the values for each */ +  entries = xtrymalloc (numentries * sizeof (char *)); +  if (!entries) +    return 0; +  memset (entries, 0, numentries * sizeof (char *)); +  /* Retrieve all the results: */ +  child = item->child; +  while (child && !fail) +    { +      ret = print_value (child, depth + 1, fmt); +      entries[i++] = ret; +      if (ret) +	len += strlen (ret) + 2 + (fmt ? 1 : 0); +      else +	fail = 1; +      child = child->next; +    } + +  /* If we didn't fail, try to xtrymalloc the output string */ +  if (!fail) +    out = xtrymalloc (len); +  /* If that fails, we fail. */ +  if (!out) +    fail = 1; + +  /* Handle failure. */ +  if (fail) +    { +      for (i = 0; i < numentries; i++) +	if (entries[i]) +	  xfree (entries[i]); +      xfree (entries); +      return 0; +    } + +  /* Compose the output array. */ +  *out = '['; +  ptr = out + 1; +  *ptr = 0; +  for (i = 0; i < numentries; i++) +    { +      strcpy (ptr, entries[i]); +      ptr += strlen (entries[i]); +      if (i != numentries - 1) +	{ +	  *ptr++ = ','; +	  if (fmt) +	    *ptr++ = ' '; +	  *ptr = 0; +	} +      xfree (entries[i]); +    } +  xfree (entries); +  *ptr++ = ']'; +  *ptr++ = 0; +  return out; +} + +/* Build an object from the text. */ +static const char * +parse_object (cJSON * item, const char *value, const char **ep) +{ +  cJSON *child; +  if (*value != '{') +    { +      *ep = value; +      return 0; +    }				/* not an object! */ + +  item->type = cJSON_Object; +  value = skip (value + 1); +  if (*value == '}') +    return value + 1;		/* empty array. */ + +  item->child = child = cJSON_New_Item (); +  if (!item->child) +    return 0; +  value = skip (parse_string (child, skip (value), ep)); +  if (!value) +    return 0; +  child->string = child->valuestring; +  child->valuestring = 0; +  if (*value != ':') +    { +      *ep = value; +      return 0; +    }				/* fail! */ +  /* skip any spacing, get the value. */ +  value = skip (parse_value (child, skip (value + 1), ep)); +  if (!value) +    return 0; + +  while (*value == ',') +    { +      cJSON *new_item; +      if (!(new_item = cJSON_New_Item ())) +	return 0;		/* memory fail */ +      child->next = new_item; +      new_item->prev = child; +      child = new_item; +      value = skip (parse_string (child, skip (value + 1), ep)); +      if (!value) +	return 0; +      child->string = child->valuestring; +      child->valuestring = 0; +      if (*value != ':') +	{ +	  *ep = value; +	  return 0; +	}			/* fail! */ +      /* skip any spacing, get the value. */ +      value = skip (parse_value (child, skip (value + 1), ep)); +      if (!value) +	return 0; +    } + +  if (*value == '}') +    return value + 1;		/* end of array */ +  *ep = value; +  return 0;			/* malformed. */ +} + +/* Render an object to text. */ +static char * +print_object (cJSON * item, int depth, int fmt) +{ +  char **entries = 0, **names = 0; +  char *out = 0, *ptr, *ret, *str; +  int len = 7, i = 0, j; +  cJSON *child = item->child; +  int numentries = 0, fail = 0; +  /* Count the number of entries. */ +  while (child) +    numentries++, child = child->next; +  /* Explicitly handle empty object case */ +  if (!numentries) +    { +      out = xtrymalloc (fmt ? depth + 4 : 3); +      if (!out) +	return 0; +      ptr = out; +      *ptr++ = '{'; +      if (fmt) +	{ +	  *ptr++ = '\n'; +	  for (i = 0; i < depth - 1; i++) +	    *ptr++ = '\t'; +	} +      *ptr++ = '}'; +      *ptr++ = 0; +      return out; +    } +  /* Allocate space for the names and the objects */ +  entries = xtrymalloc (numentries * sizeof (char *)); +  if (!entries) +    return 0; +  names = xtrymalloc (numentries * sizeof (char *)); +  if (!names) +    { +      xfree (entries); +      return 0; +    } +  memset (entries, 0, sizeof (char *) * numentries); +  memset (names, 0, sizeof (char *) * numentries); + +  /* Collect all the results into our arrays: */ +  child = item->child; +  depth++; +  if (fmt) +    len += depth; +  while (child) +    { +      names[i] = str = print_string_ptr (child->string); +      entries[i++] = ret = print_value (child, depth, fmt); +      if (str && ret) +	len += strlen (ret) + strlen (str) + 2 + (fmt ? 2 + depth : 0); +      else +	fail = 1; +      child = child->next; +    } + +  /* Try to allocate the output string */ +  if (!fail) +    out = xtrymalloc (len); +  if (!out) +    fail = 1; + +  /* Handle failure */ +  if (fail) +    { +      for (i = 0; i < numentries; i++) +	{ +	  if (names[i]) +	    xfree (names[i]); +	  if (entries[i]) +	    xfree (entries[i]); +	} +      xfree (names); +      xfree (entries); +      return 0; +    } + +  /* Compose the output: */ +  *out = '{'; +  ptr = out + 1; +  if (fmt) +    *ptr++ = '\n'; +  *ptr = 0; +  for (i = 0; i < numentries; i++) +    { +      if (fmt) +	for (j = 0; j < depth; j++) +	  *ptr++ = '\t'; +      strcpy (ptr, names[i]); +      ptr += strlen (names[i]); +      *ptr++ = ':'; +      if (fmt) +	*ptr++ = '\t'; +      strcpy (ptr, entries[i]); +      ptr += strlen (entries[i]); +      if (i != numentries - 1) +	*ptr++ = ','; +      if (fmt) +	*ptr++ = '\n'; +      *ptr = 0; +      xfree (names[i]); +      xfree (entries[i]); +    } + +  xfree (names); +  xfree (entries); +  if (fmt) +    for (i = 0; i < depth - 1; i++) +      *ptr++ = '\t'; +  *ptr++ = '}'; +  *ptr++ = 0; +  return out; +} + +/* Get Array size/item / object item. */ +int +cJSON_GetArraySize (cJSON * array) +{ +  cJSON *c = array->child; +  int i = 0; +  while (c) +    i++, c = c->next; +  return i; +} + +cJSON * +cJSON_GetArrayItem (cJSON * array, int item) +{ +  cJSON *c = array->child; +  while (c && item > 0) +    item--, c = c->next; +  return c; +} + +cJSON * +cJSON_GetObjectItem (cJSON * object, const char *string) +{ +  cJSON *c = object->child; +  while (c && cJSON_strcasecmp (c->string, string)) +    c = c->next; +  return c; +} + +/* Utility for array list handling. */ +static void +suffix_object (cJSON * prev, cJSON * item) +{ +  prev->next = item; +  item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON * +create_reference (cJSON * item) +{ +  cJSON *ref = cJSON_New_Item (); +  if (!ref) +    return 0; +  memcpy (ref, item, sizeof (cJSON)); +  ref->string = 0; +  ref->type |= cJSON_IsReference; +  ref->next = ref->prev = 0; +  return ref; +} + +/* Add item to array/object. */ +void +cJSON_AddItemToArray (cJSON * array, cJSON * item) +{ +  cJSON *c = array->child; +  if (!item) +    return; +  if (!c) +    { +      array->child = item; +    } +  else +    { +      while (c && c->next) +	c = c->next; +      suffix_object (c, item); +    } +} + +cJSON * +cJSON_AddItemToObject (cJSON * object, const char *string, cJSON * item) +{ +  char *tmp; + +  if (!item) +    return 0; +  tmp = xtrystrdup (string); +  if (!tmp) +    return NULL; + +  if (item->string) +    xfree (item->string); +  item->string = tmp; +  cJSON_AddItemToArray (object, item); +  return object; +} + +cJSON * +cJSON_AddNullToObject (cJSON *object, const char *name) +{ +  cJSON *obj, *tmp; + +  tmp = cJSON_CreateNull (); +  if (!tmp) +    return NULL; +  obj = cJSON_AddItemToObject(object, name, tmp); +  if (!obj) +    cJSON_Delete (tmp); +  return obj; +} + +cJSON * +cJSON_AddTrueToObject (cJSON *object, const char *name) +{ +  cJSON *obj, *tmp; + +  tmp = cJSON_CreateTrue (); +  if (!tmp) +    return NULL; +  obj = cJSON_AddItemToObject(object, name, tmp); +  if (!obj) +    cJSON_Delete (tmp); +  return obj; +} + +cJSON * +cJSON_AddFalseToObject (cJSON *object, const char *name) +{ +  cJSON *obj, *tmp; + +  tmp = cJSON_CreateFalse (); +  if (!tmp) +    return NULL; +  obj = cJSON_AddItemToObject(object, name, tmp); +  if (!obj) +    cJSON_Delete (tmp); +  return obj; +} + +cJSON * +cJSON_AddBoolToObject (cJSON *object, const char *name, int b) +{ +  cJSON *obj, *tmp; + +  tmp = cJSON_CreateBool (b); +  if (!tmp) +    return NULL; +  obj = cJSON_AddItemToObject(object, name, tmp); +  if (!obj) +    cJSON_Delete (tmp); +  return obj; +} + +cJSON * +cJSON_AddNumberToObject (cJSON *object, const char *name, double num) +{ +  cJSON *obj, *tmp; + +  tmp = cJSON_CreateNumber (num); +  if (!tmp) +    return NULL; +  obj = cJSON_AddItemToObject(object, name, tmp); +  if (!obj) +    cJSON_Delete (tmp); +  return obj; +} + +cJSON * +cJSON_AddStringToObject (cJSON *object, const char *name, const char *string) +{ +  cJSON *obj, *tmp; + +  tmp = cJSON_CreateString (string); +  if (!tmp) +    return NULL; +  obj = cJSON_AddItemToObject(object, name, tmp); +  if (!obj) +    cJSON_Delete (tmp); +  return obj; +} + +void +cJSON_AddItemReferenceToArray (cJSON * array, cJSON * item) +{ +  cJSON_AddItemToArray (array, create_reference (item)); +} + +void +cJSON_AddItemReferenceToObject (cJSON * object, const char *string, +				cJSON * item) +{ +  cJSON_AddItemToObject (object, string, create_reference (item)); +} + +cJSON * +cJSON_DetachItemFromArray (cJSON * array, int which) +{ +  cJSON *c = array->child; +  while (c && which > 0) +    c = c->next, which--; +  if (!c) +    return 0; +  if (c->prev) +    c->prev->next = c->next; +  if (c->next) +    c->next->prev = c->prev; +  if (c == array->child) +    array->child = c->next; +  c->prev = c->next = 0; +  return c; +} + +void +cJSON_DeleteItemFromArray (cJSON * array, int which) +{ +  cJSON_Delete (cJSON_DetachItemFromArray (array, which)); +} + +cJSON * +cJSON_DetachItemFromObject (cJSON * object, const char *string) +{ +  int i = 0; +  cJSON *c = object->child; +  while (c && cJSON_strcasecmp (c->string, string)) +    i++, c = c->next; +  if (c) +    return cJSON_DetachItemFromArray (object, i); +  return 0; +} + +void +cJSON_DeleteItemFromObject (cJSON * object, const char *string) +{ +  cJSON_Delete (cJSON_DetachItemFromObject (object, string)); +} + +/* Replace array/object items with new ones. */ +void +cJSON_ReplaceItemInArray (cJSON * array, int which, cJSON * newitem) +{ +  cJSON *c = array->child; +  while (c && which > 0) +    c = c->next, which--; +  if (!c) +    return; +  newitem->next = c->next; +  newitem->prev = c->prev; +  if (newitem->next) +    newitem->next->prev = newitem; +  if (c == array->child) +    array->child = newitem; +  else +    newitem->prev->next = newitem; +  c->next = c->prev = 0; +  cJSON_Delete (c); +} + +void +cJSON_ReplaceItemInObject (cJSON * object, const char *string, +			   cJSON * newitem) +{ +  int i = 0; +  cJSON *c = object->child; +  while (c && cJSON_strcasecmp (c->string, string)) +    i++, c = c->next; +  if (c) +    { +      newitem->string = xtrystrdup (string); +      cJSON_ReplaceItemInArray (object, i, newitem); +    } +} + +/* Create basic types: */ +cJSON * +cJSON_CreateNull (void) +{ +  cJSON *item = cJSON_New_Item (); +  if (item) +    item->type = cJSON_NULL; +  return item; +} + +cJSON * +cJSON_CreateTrue (void) +{ +  cJSON *item = cJSON_New_Item (); +  if (item) +    item->type = cJSON_True; +  return item; +} + +cJSON * +cJSON_CreateFalse (void) +{ +  cJSON *item = cJSON_New_Item (); +  if (item) +    item->type = cJSON_False; +  return item; +} + +cJSON * +cJSON_CreateBool (int b) +{ +  cJSON *item = cJSON_New_Item (); +  if (item) +    item->type = b ? cJSON_True : cJSON_False; +  return item; +} + +cJSON * +cJSON_CreateNumber (double num) +{ +  cJSON *item = cJSON_New_Item (); +  if (item) +    { +      item->type = cJSON_Number; +      item->valuedouble = num; +      item->valueint = (int) num; +    } +  return item; +} + +cJSON * +cJSON_CreateString (const char *string) +{ +  cJSON *item = cJSON_New_Item (); +  if (item) +    { +      item->type = cJSON_String; +      item->valuestring = xtrystrdup (string); +    } +  return item; +} + +cJSON * +cJSON_CreateStringConvey (char *string) +{ +  cJSON *item = cJSON_New_Item (); +  if (item) +    { +      item->type = cJSON_String; +      item->valuestring = string; +    } +  return item; +} + +cJSON * +cJSON_CreateArray (void) +{ +  cJSON *item = cJSON_New_Item (); +  if (item) +    item->type = cJSON_Array; +  return item; +} + +cJSON * +cJSON_CreateObject (void) +{ +  cJSON *item = cJSON_New_Item (); +  if (item) +    item->type = cJSON_Object; +  return item; +} + +/* Create Arrays: */ +cJSON * +cJSON_CreateIntArray (const int *numbers, int count) +{ +  int i; +  cJSON *n = 0, *p = 0, *a = cJSON_CreateArray (); +  for (i = 0; a && i < count; i++) +    { +      n = cJSON_CreateNumber (numbers[i]); +      if (!i) +	a->child = n; +      else +	suffix_object (p, n); +      p = n; +    } +  return a; +} + +cJSON * +cJSON_CreateFloatArray (const float *numbers, int count) +{ +  int i; +  cJSON *n = 0, *p = 0, *a = cJSON_CreateArray (); +  for (i = 0; a && i < count; i++) +    { +      n = cJSON_CreateNumber (numbers[i]); +      if (!i) +	a->child = n; +      else +	suffix_object (p, n); +      p = n; +    } +  return a; +} + +cJSON * +cJSON_CreateDoubleArray (const double *numbers, int count) +{ +  int i; +  cJSON *n = 0, *p = 0, *a = cJSON_CreateArray (); +  for (i = 0; a && i < count; i++) +    { +      n = cJSON_CreateNumber (numbers[i]); +      if (!i) +	a->child = n; +      else +	suffix_object (p, n); +      p = n; +    } +  return a; +} + +cJSON * +cJSON_CreateStringArray (const char **strings, int count) +{ +  int i; +  cJSON *n = 0, *p = 0, *a = cJSON_CreateArray (); +  for (i = 0; a && i < count; i++) +    { +      n = cJSON_CreateString (strings[i]); +      if (!i) +	a->child = n; +      else +	suffix_object (p, n); +      p = n; +    } +  return a; +} + +/* Duplication */ +cJSON * +cJSON_Duplicate (cJSON * item, int recurse) +{ +  cJSON *newitem, *cptr, *nptr = 0, *newchild; +  /* Bail on bad ptr */ +  if (!item) +    return 0; +  /* Create new item */ +  newitem = cJSON_New_Item (); +  if (!newitem) +    return 0; +  /* Copy over all vars */ +  newitem->type = item->type & (~cJSON_IsReference), newitem->valueint = +    item->valueint, newitem->valuedouble = item->valuedouble; +  if (item->valuestring) +    { +      newitem->valuestring = xtrystrdup (item->valuestring); +      if (!newitem->valuestring) +	{ +	  cJSON_Delete (newitem); +	  return 0; +	} +    } +  if (item->string) +    { +      newitem->string = xtrystrdup (item->string); +      if (!newitem->string) +	{ +	  cJSON_Delete (newitem); +	  return 0; +	} +    } +  /* If non-recursive, then we're done! */ +  if (!recurse) +    return newitem; +  /* Walk the ->next chain for the child. */ +  cptr = item->child; +  while (cptr) +    { +      /* Duplicate (with recurse) each item in the ->next chain */ +      newchild = cJSON_Duplicate (cptr, 1); +      if (!newchild) +	{ +	  cJSON_Delete (newitem); +	  return 0; +	} +      if (nptr) +	{ +          /* If newitem->child already set, +           * then crosswire ->prev and ->next and move on.  */ +	  nptr->next = newchild, newchild->prev = nptr; +	  nptr = newchild; +	} +      else +	{ +          /* Set newitem->child and move to it.  */ +	  newitem->child = newchild; +	  nptr = newchild; +	} +      cptr = cptr->next; +    } +  return newitem; +} + +void +cJSON_Minify (char *json) +{ +  char *into = json; +  while (*json) +    { +      if (*json == ' ') +	json++; +      else if (*json == '\t') +	json++;			/* Whitespace characters.  */ +      else if (*json == '\r') +	json++; +      else if (*json == '\n') +	json++; +      else if (*json == '/' && json[1] == '/') +	while (*json && *json != '\n') +	  json++;		/* Double-slash comments, to end of line.  */ +      else if (*json == '/' && json[1] == '*') +	{ +	  while (*json && !(*json == '*' && json[1] == '/')) +	    json++; +	  json += 2; +	}			/* Multiline comments.  */ +      else if (*json == '\"') +	{ +	  *into++ = *json++; +	  while (*json && *json != '\"') +	    { +	      if (*json == '\\') +		*into++ = *json++; +	      *into++ = *json++; +	    } +	  *into++ = *json++; +	}			/* String literals, which are \" sensitive.  */ +      else +	*into++ = *json++;	/* All other characters.  */ +    } +  *into = 0;			/* and null-terminate.  */ +} diff --git a/src/cJSON.h b/src/cJSON.h new file mode 100644 index 00000000..a200c318 --- /dev/null +++ b/src/cJSON.h @@ -0,0 +1,187 @@ +/* cJSON.h + * Copyright (c) 2009 Dave Gamble + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + * + * Note that this code has been modified from the original code taken + * from cjson-code-58.zip. + */ + +#ifndef cJSON_h +#define cJSON_h + +#ifdef __cplusplus +extern "C" +{ +#if 0 /*(to make Emacs auto-indent happy)*/ +} +#endif +#endif + +/* cJSON Types: */ +#define cJSON_False  0 +#define cJSON_True   1 +#define cJSON_NULL   2 +#define cJSON_Number 3 +#define cJSON_String 4 +#define cJSON_Array  5 +#define cJSON_Object 6 + +#define cJSON_IsReference 256 + +/* The cJSON structure: */ +typedef struct cJSON +{ +  /* next/prev allow you to walk array/object chains. Alternatively, +     use GetArraySize/GetArrayItem/GetObjectItem */ +  struct cJSON *next, *prev; + +  /* An array or object item will have a child pointer pointing to a +     chain of the items in the array/object. */ +  struct cJSON *child; + +  int type;		/* The type of the item, as above. */ + +  char *valuestring;	/* The item's string, if type==cJSON_String */ +  int valueint;		/* The item's number, if type==cJSON_Number */ +  double valuedouble;	/* The item's number, if type==cJSON_Number */ + +  /* The item's name string, if this item is the child of, or is in +     the list of subitems of an object. */ +  char *string; +} cJSON; + +typedef struct cJSON *cjson_t; + +/* Macros to test the type of an object.  */ +#define cjson_is_boolean(a) (!((a)->type & ~1)) +#define cjson_is_false(a)   ((a)->type == cJSON_False) +#define cjson_is_true(a)    ((a)->type == cJSON_True) +#define cjson_is_null(a)    ((a)->type == cJSON_NULL) +#define cjson_is_number(a)  ((a)->type == cJSON_Number) +#define cjson_is_string(a)  ((a)->type == cJSON_String) +#define cjson_is_array(a)   ((a)->type == cJSON_Array) +#define cjson_is_object(a)  ((a)->type == cJSON_Object) + +/* Supply a block of JSON, and this returns a cJSON object you can +   interrogate. Call cJSON_Delete when finished. */ +extern cJSON *cJSON_Parse(const char *value, size_t *r_erroff); + +/* Render a cJSON entity to text for transfer/storage. Free the char* +   when finished. */ +extern char  *cJSON_Print(cJSON *item); + +/* Render a cJSON entity to text for transfer/storage without any +   formatting. Free the char* when finished. */ +extern char  *cJSON_PrintUnformatted(cJSON *item); + +/* Delete a cJSON entity and all subentities. */ +extern void   cJSON_Delete(cJSON *c); + +/* Returns the number of items in an array (or object). */ +extern int    cJSON_GetArraySize(cJSON *array); + +/* Retrieve item number "item" from array "array". Returns NULL if +   unsuccessful. */ +extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); + +/* Get item "string" from object. Case insensitive. */ +extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); + +/* These calls create a cJSON item of the appropriate type. */ +extern cJSON *cJSON_CreateNull(void); +extern cJSON *cJSON_CreateTrue(void); +extern cJSON *cJSON_CreateFalse(void); +extern cJSON *cJSON_CreateBool(int b); +extern cJSON *cJSON_CreateNumber(double num); +extern cJSON *cJSON_CreateString(const char *string); +extern cJSON *cJSON_CreateStringConvey (char *string); +extern cJSON *cJSON_CreateArray(void); +extern cJSON *cJSON_CreateObject(void); + +/* These utilities create an Array of count items. */ +extern cJSON *cJSON_CreateIntArray(const int *numbers,int count); +extern cJSON *cJSON_CreateFloatArray(const float *numbers,int count); +extern cJSON *cJSON_CreateDoubleArray(const double *numbers,int count); +extern cJSON *cJSON_CreateStringArray(const char **strings,int count); + +/* Append item to the specified array. */ +extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); + +/* Append item to the specified object. */ +extern cJSON *cJSON_AddItemToObject(cJSON *object, const char *name, +                                    cJSON *item); +extern cJSON *cJSON_AddNullToObject (cJSON *object, const char *name); +extern cJSON *cJSON_AddTrueToObject (cJSON *object, const char *name); +extern cJSON *cJSON_AddFalseToObject (cJSON *object, const char *name); +extern cJSON *cJSON_AddBoolToObject (cJSON *object, const char *name, int b); +extern cJSON *cJSON_AddNumberToObject (cJSON *object, const char *name, +                                       double num); +extern cJSON *cJSON_AddStringToObject (cJSON *object, const char *name, +                                       const char *string); + +/* Append reference to item to the specified array/object. Use this +   when you want to add an existing cJSON to a new cJSON, but don't +   want to corrupt your existing cJSON. */ +extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +extern void cJSON_AddItemReferenceToObject(cJSON *object, +                                           const char *string,cJSON *item); + +/* Remove/Detatch items from Arrays/Objects. */ +extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which); +extern void   cJSON_DeleteItemFromArray(cJSON *array,int which); +extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string); +extern void   cJSON_DeleteItemFromObject(cJSON *object,const char *string); + +/* Update array items. */ +extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem); +extern void cJSON_ReplaceItemInObject(cJSON *object, +                                      const char *string, cJSON *newitem); + +/* Duplicate a cJSON item */ +extern cJSON *cJSON_Duplicate(cJSON *item,int recurse); + +/* Duplicate will create a new, identical cJSON item to the one you +   pass, in new memory that will need to be released. With recurse!=0, +   it will duplicate any children connected to the item.  The +   item->next and ->prev pointers are always zero on return from +   Duplicate. */ + +/* ParseWithOpts allows you to require (and check) that the JSON is +   null terminated, and to retrieve the pointer to the final byte +   parsed. */ +extern cJSON *cJSON_ParseWithOpts(const char *value, +                                  const char **return_parse_end, +                                  int require_null_terminated, +                                  size_t *r_erroff); + +extern void cJSON_Minify(char *json); + +/* When assigning an integer value, it needs to be propagated to +   valuedouble too. */ +#define cJSON_SetIntValue(object,val) \ +  ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) + +#ifdef __cplusplus +} +#endif + +#endif /* cJSON_h */ diff --git a/src/cJSON.readme b/src/cJSON.readme new file mode 100644 index 00000000..61623b4f --- /dev/null +++ b/src/cJSON.readme @@ -0,0 +1,270 @@ +/* +  Copyright (c) 2009 Dave Gamble + +  Permission is hereby granted, free of charge, to any person obtaining a copy +  of this software and associated documentation files (the "Software"), to deal +  in the Software without restriction, including without limitation the rights +  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +  copies of the Software, and to permit persons to whom the Software is +  furnished to do so, subject to the following conditions: + +  The above copyright notice and this permission notice shall be included in +  all copies or substantial portions of the Software. + +  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +  THE SOFTWARE. +*/ + +Welcome to cJSON. + +cJSON aims to be the dumbest possible parser that you can get your job +done with.  It's a single file of C, and a single header file. + +JSON is described best here: http://www.json.org/ It's like XML, but +fat-free. You use it to move data around, store things, or just +generally represent your program's state. + + +First up, how do I build?  Add cJSON.c to your project, and put +cJSON.h somewhere in the header search path.  For example, to build +the test app: + +gcc cJSON.c test.c -o test -lm +./test + + +As a library, cJSON exists to take away as much legwork as it can, but +not get in your way.  As a point of pragmatism (i.e. ignoring the +truth), I'm going to say that you can use it in one of two modes: Auto +and Manual. Let's have a quick run-through. + + +I lifted some JSON from this page: http://www.json.org/fatfree.html +That page inspired me to write cJSON, which is a parser that tries to +share the same philosophy as JSON itself. Simple, dumb, out of the +way. + +Some JSON: +{ +    "name": "Jack (\"Bee\") Nimble", +    "format": { +        "type":       "rect", +        "width":      1920, +        "height":     1080, +        "interlace":  false, +        "frame rate": 24 +    } +} + +Assume that you got this from a file, a webserver, or magic JSON +elves, whatever, you have a char * to it. Everything is a cJSON +struct.  Get it parsed: + +         cJSON *root = cJSON_Parse(my_json_string); + +This is an object. We're in C. We don't have objects. But we do have +structs.  What's the framerate? + +	cJSON *format = cJSON_GetObjectItem(root,"format"); +	int framerate = cJSON_GetObjectItem(format,"frame rate")->valueint; + +Want to change the framerate? + +	cJSON_GetObjectItem(format,"frame rate")->valueint=25; + +Back to disk? + +	char *rendered=cJSON_Print(root); + +Finished? Delete the root (this takes care of everything else). + +	cJSON_Delete(root); + +That's AUTO mode. If you're going to use Auto mode, you really ought +to check pointers before you dereference them. If you want to see how +you'd build this struct in code? + +	cJSON *root,*fmt; +	root=cJSON_CreateObject(); +	cJSON_AddItemToObject(root, "name", +                              cJSON_CreateString("Jack (\"Bee\") Nimble")); +	cJSON_AddItemToObject(root, "format", fmt=cJSON_CreateObject()); +	cJSON_AddStringToObject(fmt,"type",		"rect"); +	cJSON_AddNumberToObject(fmt,"width",		1920); +	cJSON_AddNumberToObject(fmt,"height",		1080); +	cJSON_AddFalseToObject (fmt,"interlace"); +	cJSON_AddNumberToObject(fmt,"frame rate",	24); + +Hopefully we can agree that's not a lot of code? There's no overhead, +no unnecessary setup.  Look at test.c for a bunch of nice examples, +mostly all ripped off the json.org site, and a few from elsewhere. + +What about manual mode? First up you need some detail.  Let's cover +how the cJSON objects represent the JSON data.  cJSON doesn't +distinguish arrays from objects in handling; just type.  Each cJSON +has, potentially, a child, siblings, value, a name. + +- The root object has: Object Type and a Child +- The Child has name "name", with value "Jack ("Bee") Nimble", and a sibling: +- Sibling has type Object, name "format", and a child. +- That child has type String, name "type", value "rect", and a sibling: +- Sibling has type Number, name "width", value 1920, and a sibling: +- Sibling has type Number, name "height", value 1080, and a sibling: +- Sibling hs type False, name "interlace", and a sibling: +- Sibling has type Number, name "frame rate", value 24 + +Here's the structure: + +typedef struct cJSON { +	struct cJSON *next,*prev; +	struct cJSON *child; + +	int type; + +	char *valuestring; +	int valueint; +	double valuedouble; + +	char *string; +} cJSON; + +By default all values are 0 unless set by virtue of being meaningful. + +next/prev is a doubly linked list of siblings. next takes you to your sibling, +prev takes you back from your sibling to you. + +Only objects and arrays have a "child", and it's the head of the +doubly linked list. + +A "child" entry will have prev==0, but next potentially points on. The +last sibling has next=0. + +The type expresses Null/True/False/Number/String/Array/Object, all of +which are #defined in cJSON.h + +A Number has valueint and valuedouble. If you're expecting an int, +read valueint, if not read valuedouble. + +Any entry which is in the linked list which is the child of an object +will have a "string" which is the "name" of the entry. When I said +"name" in the above example, that's "string".  "string" is the JSON +name for the 'variable name' if you will. + +Now you can trivially walk the lists, recursively, and parse as you +please.  You can invoke cJSON_Parse to get cJSON to parse for you, and +then you can take the root object, and traverse the structure (which +is, formally, an N-tree), and tokenise as you please. If you wanted to +build a callback style parser, this is how you'd do it (just an +example, since these things are very specific): + +void parse_and_callback(cJSON *item,const char *prefix) +{ +        while (item) +	{ +		char *newprefix=malloc(strlen(prefix)+strlen(item->name)+2); +		sprintf(newprefix,"%s/%s",prefix,item->name); +		int dorecurse=callback(newprefix, item->type, item); +		if (item->child && dorecurse) +                    parse_and_callback(item->child,newprefix); +		item=item->next; +		free(newprefix); +	} +} + +The prefix process will build you a separated list, to simplify your +callback handling. + +The 'dorecurse' flag would let the callback decide to handle +sub-arrays on it's own, or let you invoke it per-item. For the item +above, your callback might look like this: + +int callback(const char *name,int type,cJSON *item) +{ +	if (!strcmp(name,"name"))	{ /* populate name */ } +	else if (!strcmp(name,"format/type")	{ /* handle "rect" */ } +	else if (!strcmp(name,"format/width")	{ /* 800 */ } +	else if (!strcmp(name,"format/height")	{ /* 600 */ } +	else if (!strcmp(name,"format/interlace")	{ /* false */ } +	else if (!strcmp(name,"format/frame rate")	{ /* 24 */ } +	return 1; +} + +Alternatively, you might like to parse iteratively. +You'd use: + +void parse_object(cJSON *item) +{ +	int i; for (i=0;i<cJSON_GetArraySize(item);i++) +	{ +		cJSON *subitem=cJSON_GetArrayItem(item,i); +		// handle subitem. +	} +} + +Or, for PROPER manual mode: + +void parse_object(cJSON *item) +{ +	cJSON *subitem=item->child; +	while (subitem) +	{ +		// handle subitem +		if (subitem->child) parse_object(subitem->child); + +		subitem=subitem->next; +	} +} + +Of course, this should look familiar, since this is just a +stripped-down version of the callback-parser. + +This should cover most uses you'll find for parsing. The rest should +be possible to infer.. and if in doubt, read the source! There's not a +lot of it! ;) + + +In terms of constructing JSON data, the example code above is the +right way to do it.  You can, of course, hand your sub-objects to +other functions to populate.  Also, if you find a use for it, you can +manually build the objects.  For instance, suppose you wanted to build +an array of objects? + +cJSON *objects[24]; + +cJSON *Create_array_of_anything(cJSON **items,int num) +{ +	int i;cJSON *prev, *root=cJSON_CreateArray(); +	for (i=0;i<24;i++) +	{ +		if (!i)	root->child=objects[i]; +		else	prev->next=objects[i], objects[i]->prev=prev; +		prev=objects[i]; +	} +	return root; +} + +and simply: Create_array_of_anything(objects,24); + +cJSON doesn't make any assumptions about what order you create things +in.  You can attach the objects, as above, and later add children to +each of those objects. + +As soon as you call cJSON_Print, it renders the structure to text. + + + +The test.c code shows how to handle a bunch of typical cases. If you +uncomment the code, it'll load, parse and print a bunch of test files, +also from json.org, which are more complex than I'd care to try and +stash into a const char array[]. + + +Enjoy cJSON! + + +- Dave Gamble, Aug 2009 diff --git a/src/gpgme-json.c b/src/gpgme-json.c new file mode 100644 index 00000000..6ba79e5a --- /dev/null +++ b/src/gpgme-json.c @@ -0,0 +1,1327 @@ +/* gpgme-json.c - JSON based interface to gpgme (server) + * Copyright (C) 2018 g10 Code GmbH + * + * This file is part of GPGME. + * + * GPGME 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. + * + * GPGME 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 program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +/* This is tool implements the Native Messaging protocol of web + * browsers and provides the server part of it.  A Javascript based + * client can be found in lang/javascript.  The used data format is + * similar to the API of openpgpjs. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif +#include <stdint.h> + +#define GPGRT_ENABLE_ES_MACROS 1 +#define GPGRT_ENABLE_LOG_MACROS 1 +#define GPGRT_ENABLE_ARGPARSE_MACROS 1 +#include "gpgme.h" +#include "cJSON.h" + + +/* We don't allow a request with more than 64 MiB.  */ +#define MAX_REQUEST_SIZE (64 * 1024 * 1024) + + +static void xoutofcore (const char *type) GPGRT_ATTR_NORETURN; +static cjson_t error_object_v (cjson_t json, const char *message, +                              va_list arg_ptr) GPGRT_ATTR_PRINTF(2,0); +static cjson_t error_object (cjson_t json, const char *message, +                            ...) GPGRT_ATTR_PRINTF(2,3); +static char *error_object_string (const char *message, +                                  ...) GPGRT_ATTR_PRINTF(1,2); + + +/* True if interactive mode is active.  */ +static int opt_interactive; + + + +/* + * Helper functions and macros + */ + +#define xtrymalloc(a)  gpgrt_malloc ((a)) +#define xtrystrdup(a)  gpgrt_strdup ((a)) +#define xmalloc(a) ({                           \ +      void *_r = gpgrt_malloc ((a));            \ +      if (!_r)                                  \ +        xoutofcore ("malloc");                  \ +      _r; }) +#define xcalloc(a,b) ({                         \ +      void *_r = gpgrt_calloc ((a), (b));       \ +      if (!_r)                                  \ +        xoutofcore ("calloc");                  \ +      _r; }) +#define xstrdup(a) ({                           \ +      char *_r = gpgrt_strdup ((a));            \ +      if (!_r)                                  \ +        xoutofcore ("strdup");                  \ +      _r; }) +#define xstrconcat(a, ...) ({                           \ +      char *_r = gpgrt_strconcat ((a), __VA_ARGS__);    \ +      if (!_r)                                          \ +        xoutofcore ("strconcat");                       \ +      _r; }) +#define xfree(a) gpgrt_free ((a)) + +#define spacep(p)   (*(p) == ' ' || *(p) == '\t') + + +static void +xoutofcore (const char *type) +{ +  gpg_error_t err = gpg_error_from_syserror (); +  log_error ("%s failed: %s\n", type, gpg_strerror (err)); +  exit (2); +} + + +/* Call cJSON_CreateObject but terminate in case of an error.  */ +static cjson_t +xjson_CreateObject (void) +{ +  cjson_t json = cJSON_CreateObject (); +  if (!json) +    xoutofcore ("cJSON_CreateObject"); +  return json; +} + + +/* Wrapper around cJSON_AddStringToObject which returns an gpg-error + * code instead of the NULL or the new object.  */ +static gpg_error_t +cjson_AddStringToObject (cjson_t object, const char *name, const char *string) +{ +  if (!cJSON_AddStringToObject (object, name, string)) +    return gpg_error_from_syserror (); +  return 0; +} + + +/* Same as cjson_AddStringToObject but prints an error message and + * terminates the process.  */ +static void +xjson_AddStringToObject (cjson_t object, const char *name, const char *string) +{ +  if (!cJSON_AddStringToObject (object, name, string)) +    xoutofcore ("cJSON_AddStringToObject"); +} + + +/* Wrapper around cJSON_AddBoolToObject which terminates the process + * in case of an error.  */ +static void +xjson_AddBoolToObject (cjson_t object, const char *name, int abool) +{ +  if (!cJSON_AddBoolToObject (object, name, abool)) +    xoutofcore ("cJSON_AddStringToObject"); +  return ; +} + +/* This is similar to cJSON_AddStringToObject but takes a gpgme DATA + * object and adds it under NAME as a base 64 encoded string to + * OBJECT.  Ownership of DATA is transferred to this function.  */ +static gpg_error_t +add_base64_to_object (cjson_t object, const char *name, gpgme_data_t data) +{ +  gpg_err_code_t err; +  estream_t fp = NULL; +  gpgrt_b64state_t state = NULL; +  cjson_t j_str = NULL; +  void *buffer = NULL; +  size_t buflen; + +  fp = es_fopenmem (0, "rwb"); +  if (!fp) +    { +      err = gpg_err_code_from_syserror (); +      goto leave; +    } +  state = gpgrt_b64enc_start (fp, ""); +  if (!state) +    { +      err = gpg_err_code_from_syserror (); +      goto leave; +    } + +  gpgme_data_write (data, "", 1); /* Make sure we have  a string.  */ +  buffer = gpgme_data_release_and_get_mem (data, &buflen); +  data = NULL; +  if (!buffer) +    { +      err = gpg_error_from_syserror (); +      goto leave; +    } + +  err = gpgrt_b64enc_write (state, buffer, buflen); +  if (err) +    goto leave; +  xfree (buffer); +  buffer = NULL; + +  err = gpgrt_b64enc_finish (state); +  state = NULL; +  if (err) +    return err; + +  es_fputc (0, fp); +  if (es_fclose_snatch (fp, &buffer, NULL)) +    { +      fp = NULL; +      err = gpg_error_from_syserror (); +      goto leave; +    } +  fp = NULL; + +  j_str = cJSON_CreateStringConvey (buffer); +  if (!j_str) +    { +      err = gpg_error_from_syserror (); +      goto leave; +    } +  buffer = NULL; + +  if (!cJSON_AddItemToObject (object, name, j_str)) +    { +      err = gpg_error_from_syserror (); +      cJSON_Delete (j_str); +      j_str = NULL; +      goto leave; +    } +  j_str = NULL; + + leave: +  xfree (buffer); +  cJSON_Delete (j_str); +  gpgrt_b64enc_finish (state); +  es_fclose (fp); +  gpgme_data_release (data); +  return err; +} + + +/* Create a JSON error object.  If JSON is not NULL the error message + * is appended to that object.  An existing "type" item will be replaced. */ +static cjson_t +error_object_v (cjson_t json, const char *message, va_list arg_ptr) +{ +  cjson_t response, j_tmp; +  char *msg; + +  msg = gpgrt_vbsprintf (message, arg_ptr); +  if (!msg) +    xoutofcore ("error_object"); + +  response = json? json : xjson_CreateObject (); + +  if (!(j_tmp = cJSON_GetObjectItem (response, "type"))) +    xjson_AddStringToObject (response, "type", "error"); +  else /* Replace existing "type".  */ +    { +      j_tmp = cJSON_CreateString ("error"); +      if (!j_tmp) +        xoutofcore ("cJSON_CreateString"); +      cJSON_ReplaceItemInObject (response, "type", j_tmp); +     } +  xjson_AddStringToObject (response, "msg", msg); + +  xfree (msg); +  return response; +} + + +/* Call cJSON_Print but terminate in case of an error.  */ +static char * +xjson_Print (cjson_t object) +{ +  char *buf; +  buf = cJSON_Print (object); +  if (!buf) +    xoutofcore ("cJSON_Print"); +  return buf; +} + + +static cjson_t +error_object (cjson_t json, const char *message, ...) +{ +  cjson_t response; +  va_list arg_ptr; + +  va_start (arg_ptr, message); +  response = error_object_v (json, message, arg_ptr); +  va_end (arg_ptr); +  return response; +} + + +static char * +error_object_string (const char *message, ...) +{ +  cjson_t response; +  va_list arg_ptr; +  char *msg; + +  va_start (arg_ptr, message); +  response = error_object_v (NULL, message, arg_ptr); +  va_end (arg_ptr); + +  msg = xjson_Print (response); +  cJSON_Delete (response); +  return msg; +} + + +/* Get the boolean property NAME from the JSON object and store true + * or valse at R_VALUE.  If the name is unknown the value of DEF_VALUE + * is returned.  If the type of the value is not boolean, + * GPG_ERR_INV_VALUE is returned and R_VALUE set to DEF_VALUE.  */ +static gpg_error_t +get_boolean_flag (cjson_t json, const char *name, int def_value, int *r_value) +{ +  cjson_t j_item; + +  j_item = cJSON_GetObjectItem (json, name); +  if (!j_item) +    *r_value = def_value; +  else if (cjson_is_true (j_item)) +    *r_value = 1; +  else if (cjson_is_false (j_item)) +    *r_value = 0; +  else +    { +      *r_value = def_value; +      return gpg_error (GPG_ERR_INV_VALUE); +    } + +  return 0; +} + + +/* Get the boolean property PROTOCOL from the JSON object and store + * its value at R_PROTOCOL.  The default is OpenPGP.  */ +static gpg_error_t +get_protocol (cjson_t json, gpgme_protocol_t *r_protocol) +{ +  cjson_t j_item; + +  *r_protocol = GPGME_PROTOCOL_OpenPGP; +  j_item = cJSON_GetObjectItem (json, "protocol"); +  if (!j_item) +    ; +  else if (!cjson_is_string (j_item)) +    return gpg_error (GPG_ERR_INV_VALUE); +  else if (!strcmp(j_item->valuestring, "openpgp")) +    ; +  else if (!strcmp(j_item->valuestring, "cms")) +    *r_protocol = GPGME_PROTOCOL_CMS; +  else +    return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); + +  return 0; +} + + +/* Extract the keys from the KEYS array in the JSON object.  CTX is a + * GPGME context object.  On success an array with the keys is stored + * at R_KEYS.  In failure an error code is returned.  */ +static gpg_error_t +get_keys (gpgme_ctx_t ctx, cjson_t json, gpgme_key_t **r_keys) +{ +  gpg_error_t err; +  cjson_t j_keys, j_item; +  int i, nkeys; +  gpgme_key_t *keys; + +  *r_keys = NULL; + +  j_keys = cJSON_GetObjectItem (json, "keys"); +  if (!j_keys) +    return gpg_error (GPG_ERR_NO_KEY); +  if (!cjson_is_array (j_keys) && !cjson_is_string (j_keys)) +    return gpg_error (GPG_ERR_INV_VALUE); + +  if (cjson_is_string (j_keys)) +    nkeys = 1; +  else +    { +      nkeys = cJSON_GetArraySize (j_keys); +      if (!nkeys) +        return gpg_error (GPG_ERR_NO_KEY); +      for (i=0; i < nkeys; i++) +        { +          j_item = cJSON_GetArrayItem (j_keys, i); +          if (!j_item || !cjson_is_string (j_item)) +            return gpg_error (GPG_ERR_INV_VALUE); +        } +    } + +  /* Now allocate an array to store the gpgme key objects.  */ +  keys = xcalloc (nkeys + 1, sizeof *keys); + +  if (cjson_is_string (j_keys)) +    { +      err = gpgme_get_key (ctx, j_keys->valuestring, &keys[0], 0); +      if (err) +        goto leave; +    } +  else +    { +      for (i=0; i < nkeys; i++) +        { +          j_item = cJSON_GetArrayItem (j_keys, i); +          err = gpgme_get_key (ctx, j_item->valuestring, &keys[i], 0); +          if (err) +            goto leave; +        } +    } +  err = 0; +  *r_keys = keys; +  keys = NULL; + + leave: +  if (keys) +    { +      for (i=0; keys[i]; i++) +        gpgme_key_unref (keys[i]); +      xfree (keys); +    } +  return err; +} + + + +/* + *  GPGME support functions. + */ + +/* Helper for get_context.  */ +static gpgme_ctx_t +_create_new_context (gpgme_protocol_t proto) +{ +  gpg_error_t err; +  gpgme_ctx_t ctx; + +  err = gpgme_new (&ctx); +  if (err) +    log_fatal ("error creating GPGME context: %s\n", gpg_strerror (err)); +  gpgme_set_protocol (ctx, proto); +  return ctx; +} + + +/* Return a context object for protocol PROTO.  This is currently a + * statuically allocated context initialized for PROTO.  Termnates + * process on failure.  */ +static gpgme_ctx_t +get_context (gpgme_protocol_t proto) +{ +  static gpgme_ctx_t ctx_openpgp, ctx_cms; + +  if (proto == GPGME_PROTOCOL_OpenPGP) +    { +      if (!ctx_openpgp) +        ctx_openpgp = _create_new_context (proto); +      return ctx_openpgp; +    } +  else if (proto == GPGME_PROTOCOL_CMS) +    { +      if (!ctx_cms) +        ctx_cms = _create_new_context (proto); +      return ctx_cms; +    } +  else +    log_bug ("invalid protocol %d requested\n", proto); +} + + + +/* Free context object retrieved by get_context.  */ +static void +release_context (gpgme_ctx_t ctx) +{ +  /* Nothing to do right now.  */ +  (void)ctx; +} + + + +/* Given a Base-64 encoded string object in JSON return a gpgme data + * object at R_DATA.  */ +static gpg_error_t +data_from_base64_string (gpgme_data_t *r_data, cjson_t json) +{ +#if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */ +  *r_data = NULL; +  return gpg_error (GPG_ERR_NOT_SUPPORTED); +#else +  gpg_error_t err; +  size_t len; +  char *buf = NULL; +  gpgrt_b64state_t state = NULL; +  gpgme_data_t data = NULL; + +  *r_data = NULL; + +  /* A quick check on the JSON.  */ +  if (!cjson_is_string (json)) +    { +      err = gpg_error (GPG_ERR_INV_VALUE); +      goto leave; +    } + +  state = gpgrt_b64dec_start (NULL); +  if (!state) +    { +      err = gpg_err_code_from_syserror (); +      goto leave; +    } + +  /* Fixme: Data duplication - we should see how to snatch the memory +   * from the json object.  */ +  len = strlen (json->valuestring); +  buf = xtrystrdup (json->valuestring); +  if (!buf) +    { +      err = gpg_error_from_syserror (); +      goto leave; +    } + +  err = gpgrt_b64dec_proc (state, buf, len, &len); +  if (err) +    goto leave; + +  err = gpgrt_b64dec_finish (state); +  state = NULL; +  if (err) +    goto leave; + +  err = gpgme_data_new_from_mem (&data, buf, len, 1); +  if (err) +    goto leave; +  *r_data = data; +  data = NULL; + + leave: +  xfree (data); +  xfree (buf); +  gpgrt_b64dec_finish (state); +  return err; +#endif +} + + + +/* + * Implementaion of the commands. + */ + + +static const char hlp_encrypt[] = +  "op:     \"encrypt\"\n" +  "keys:   Array of strings with the fingerprints or user-ids\n" +  "        of the keys to encrypt the data.  For a single key\n" +  "        a String may be used instead of an array.\n" +  "data:   Input data. \n" +  "\n" +  "Optional parameters:\n" +  "protocol:      Either \"openpgp\" (default) or \"cms\".\n" +  "\n" +  "Optional boolean flags (default is false):\n" +  "base64:        Input data is base64 encoded.\n" +  "armor:         Request output in armored format.\n" +  "always-trust:  Request --always-trust option.\n" +  "no-encrypt-to: Do not use a default recipient.\n" +  "no-compress:   Do not compress the plaintext first.\n" +  "throw-keyids:  Request the --throw-keyids option.\n" +  "wrap:          Assume the input is an OpenPGP message.\n" +  "\n" +  "Response on success:\n" +  "type:   \"ciphertext\"\n" +  "data:   Unless armor mode is used a Base64 encoded binary\n" +  "        ciphertext.  In armor mode a string with an armored\n" +  "        OpenPGP or a PEM message.\n" +  "base64: Boolean indicating whether data is base64 encoded."; +static gpg_error_t +op_encrypt (cjson_t request, cjson_t result) +{ +  gpg_error_t err; +  gpgme_ctx_t ctx = NULL; +  gpgme_protocol_t protocol; +  int opt_base64; +  gpgme_key_t *keys = NULL; +  cjson_t j_input; +  gpgme_data_t input = NULL; +  gpgme_data_t output = NULL; +  int abool, i; +  gpgme_encrypt_flags_t encrypt_flags = 0; + +  if ((err = get_protocol (request, &protocol))) +    goto leave; +  ctx = get_context (protocol); + +  if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) +    goto leave; + +  if ((err = get_boolean_flag (request, "armor", 0, &abool))) +    goto leave; +  gpgme_set_armor (ctx, abool); +  if ((err = get_boolean_flag (request, "always-trust", 0, &abool))) +    goto leave; +  if (abool) +    encrypt_flags |= GPGME_ENCRYPT_ALWAYS_TRUST; +  if ((err = get_boolean_flag (request, "no-encrypt-to", 0,&abool))) +    goto leave; +  if (abool) +    encrypt_flags |= GPGME_ENCRYPT_NO_ENCRYPT_TO; +  if ((err = get_boolean_flag (request, "no-compress", 0, &abool))) +    goto leave; +  if (abool) +    encrypt_flags |= GPGME_ENCRYPT_NO_COMPRESS; +  if ((err = get_boolean_flag (request, "throw-keyids", 0, &abool))) +    goto leave; +  if (abool) +    encrypt_flags |= GPGME_ENCRYPT_THROW_KEYIDS; +  if ((err = get_boolean_flag (request, "wrap", 0, &abool))) +    goto leave; +  if (abool) +    encrypt_flags |= GPGME_ENCRYPT_WRAP; + + +  /* Get the keys.  */ +  err = get_keys (ctx, request, &keys); +  if (err) +    { +      /* Provide a custom error response.  */ +      error_object (result, "Error getting keys: %s", gpg_strerror (err)); +      goto leave; +    } + +  /* Get the data.  Note that INPUT is a shallow data object with the +   * storage hold in REQUEST.  */ +  j_input = cJSON_GetObjectItem (request, "data"); +  if (!j_input) +    { +      err = gpg_error (GPG_ERR_NO_DATA); +      goto leave; +    } +  if (!cjson_is_string (j_input)) +    { +      err = gpg_error (GPG_ERR_INV_VALUE); +      goto leave; +    } +  if (opt_base64) +    { +      err = data_from_base64_string (&input, j_input); +      if (err) +        { +          error_object (result, "Error decoding Base-64 encoded 'data': %s", +                        gpg_strerror (err)); +          goto leave; +        } +    } +  else +    { +      err = gpgme_data_new_from_mem (&input, j_input->valuestring, +                                     strlen (j_input->valuestring), 0); +      if (err) +        { +          error_object (result, "Error getting 'data': %s", gpg_strerror (err)); +          goto leave; +        } +    } + +  /* Create an output data object.  */ +  err = gpgme_data_new (&output); +  if (err) +    { +      error_object (result, "Error creating output data object: %s", +                    gpg_strerror (err)); +      goto leave; +    } + +  /* Encrypt.  */ +  err = gpgme_op_encrypt (ctx, keys, encrypt_flags, input, output); +  /* encrypt_result = gpgme_op_encrypt_result (ctx); */ +  if (err) +    { +      error_object (result, "Encryption failed: %s", gpg_strerror (err)); +      goto leave; +    } +  gpgme_data_release (input); +  input = NULL; + +  xjson_AddStringToObject (result, "type", "ciphertext"); +  /* If armoring is used we do not need to base64 the output.  */ +  xjson_AddBoolToObject (result, "base64", !gpgme_get_armor (ctx)); +  if (gpgme_get_armor (ctx)) +    { +      char *buffer; + +      /* Make sure that we really have a string.  */ +      gpgme_data_write (output, "", 1); +      buffer = gpgme_data_release_and_get_mem (output, NULL); +      if (!buffer) +        { +          err = gpg_error_from_syserror (); +          goto leave; +        } +      err = cjson_AddStringToObject (result, "data", buffer); +      gpgme_free (buffer); +      if (err) +        goto leave; +    } +  else +    { +      err = add_base64_to_object (result, "data", output); +      output = NULL; +      if (err) +        goto leave; +    } + + leave: +  if (keys) +    { +      for (i=0; keys[i]; i++) +        gpgme_key_unref (keys[i]); +      xfree (keys); +    } +  release_context (ctx); +  gpgme_data_release (input); +  return err; +} + + + +static const char hlp_help[] = +  "The tool expects a JSON object with the request and responds with\n" +  "another JSON object.  Even on error a JSON object is returned.  The\n" +  "property \"op\" is mandatory and its string value selects the\n" +  "operation; if the property \"help\" with the value \"true\" exists, the\n" +  "operation is not performned but a string with the documentation\n" +  "returned.  To list all operations it is allowed to leave out \"op\" in\n" +  "help mode.  Supported values for \"op\" are:\n\n" +  "  encrypt     Encrypt data.\n" +  "  help        Help overview."; +static gpg_error_t +op_help (cjson_t request, cjson_t result) +{ +  cjson_t j_tmp; +  char *buffer = NULL; +  const char *msg; + +  j_tmp = cJSON_GetObjectItem (request, "interactive_help"); +  if (opt_interactive && j_tmp && cjson_is_string (j_tmp)) +    msg = buffer = xstrconcat (hlp_help, "\n", j_tmp->valuestring, NULL); +  else +    msg = hlp_help; + +  xjson_AddStringToObject (result, "type", "help"); +  xjson_AddStringToObject (result, "msg", msg); + +  xfree (buffer); +  return 0; +} + + + +/* Process a request and return the response.  The response is a newly + * allocated staring or NULL in case of an error.  */ +static char * +process_request (const char *request) +{ +  static struct { +    const char *op; +    gpg_error_t (*handler)(cjson_t request, cjson_t result); +    const char * const helpstr; +  } optbl[] = { +    { "encrypt", op_encrypt, hlp_encrypt }, + + +    { "help",    op_help,    hlp_help }, +    { NULL } +  }; +  size_t erroff; +  cjson_t json; +  cjson_t j_tmp, j_op; +  cjson_t response; +  int helpmode; +  const char *op; +  char *res; +  int idx; + +  response = xjson_CreateObject (); + +  json = cJSON_Parse (request, &erroff); +  if (!json) +    { +      log_string (GPGRT_LOGLVL_INFO, request); +      log_info ("invalid JSON object at offset %zu\n", erroff); +      error_object (response, "invalid JSON object at offset %zu\n", erroff); +      goto leave; +    } + +  j_tmp = cJSON_GetObjectItem (json, "help"); +  helpmode = (j_tmp && cjson_is_true (j_tmp)); + +  j_op = cJSON_GetObjectItem (json, "op"); +  if (!j_op || !cjson_is_string (j_op)) +    { +      if (!helpmode) +        { +          error_object (response, "Property \"op\" missing"); +          goto leave; +        } +      op = "help";  /* Help summary.  */ +    } +  else +    op = j_op->valuestring; + +  for (idx=0; optbl[idx].op; idx++) +    if (!strcmp (op, optbl[idx].op)) +      break; +  if (optbl[idx].op) +    { +      if (helpmode && strcmp (op, "help")) +        { +          xjson_AddStringToObject (response, "type", "help"); +          xjson_AddStringToObject (response, "op", op); +          xjson_AddStringToObject (response, "msg", optbl[idx].helpstr); +        } +      else +        { +          gpg_error_t err; + +          err = optbl[idx].handler (json, response); +          if (err) +            { +              if (!(j_tmp = cJSON_GetObjectItem (response, "type")) +                  || !cjson_is_string (j_tmp) +                  || strcmp (j_tmp->valuestring, "error")) +                { +                  /* No error type response - provide a generic one.  */ +                  error_object (response, "Operation failed: %s", +                                gpg_strerror (err)); +                } + +              xjson_AddStringToObject (response, "op", op); +            } + +        } +    } +  else  /* Operation not supported.  */ +    { +      error_object (response, "Unknown operation '%s'", op); +      xjson_AddStringToObject (response, "op", op); +    } + + leave: +  cJSON_Delete (json); +  json = NULL; +  if (opt_interactive) +    res = cJSON_Print (response); +  else +    res = cJSON_PrintUnformatted (response); +  if (!res) +    log_error ("Printing JSON data failed\n"); +  cJSON_Delete (response); +  return res; +} + + + +/* + *  Driver code + */ + +/* Return a malloced line or NULL on EOF.  Terminate on read + * error.  */ +static char * +get_line (void) +{ +  char *line = NULL; +  size_t linesize = 0; +  gpg_error_t err; +  size_t maxlength = 2048; +  int n; +  const char *s; +  char *p; + + again: +  n = es_read_line (es_stdin, &line, &linesize, &maxlength); +  if (n < 0) +    { +      err = gpg_error_from_syserror (); +      log_error ("error reading line: %s\n", gpg_strerror (err)); +      exit (1); +    } +  if (!n) +    { +      xfree (line); +      line = NULL; +      return NULL;  /* EOF */ +    } +  if (!maxlength) +    { +      log_info ("line too long - skipped\n"); +      goto again; +    } +  if (memchr (line, 0, n)) +    log_info ("warning: line shortened due to embedded Nul character\n"); + +  if (line[n-1] == '\n') +    line[n-1] = 0; + +  /* Trim leading spaces.  */ +  for (s=line; spacep (s); s++) +    ; +  if (s != line) +    { +      for (p=line; *s;) +        *p++ = *s++; +      *p = 0; +      n = p - line; +    } + +  return line; +} + + +/* Process meta commands used with the standard REPL.  */ +static char * +process_meta_commands (const char *request) +{ +  char *result = NULL; + +  while (spacep (request)) +    request++; + +  if (!strncmp (request, "help", 4) && (spacep (request+4) || !request[4])) +    result = process_request ("{ \"op\": \"help\"," +                              " \"interactive_help\": " +                              "\"\\nMeta commands:\\n" +                              "  ,help       This help\\n" +                              "  ,quit       Terminate process\"" +                              "}"); +  else if (!strncmp (request, "quit", 4) && (spacep (request+4) || !request[4])) +    exit (0); +  else +    log_info ("invalid meta command\n"); + +  return result; +} + + +/* If STRING has a help response, return the MSG property in a human + * readable format.  */ +static char * +get_help_msg (const char *string) +{ +  cjson_t json, j_type, j_msg; +  const char *msg; +  char *buffer = NULL; +  char *p; + +  json = cJSON_Parse (string, NULL); +  if (json) +    { +      j_type = cJSON_GetObjectItem (json, "type"); +      if (j_type && cjson_is_string (j_type) +          && !strcmp (j_type->valuestring, "help")) +        { +          j_msg = cJSON_GetObjectItem (json, "msg"); +          if (j_msg || cjson_is_string (j_msg)) +            { +              msg = j_msg->valuestring; +              buffer = malloc (strlen (msg)+1); +              if (buffer) +                { +                  for (p=buffer; *msg; msg++) +                    { +                      if (*msg == '\\' && msg[1] == '\n') +                        *p++ = '\n'; +                      else +                        *p++ = *msg; +                    } +                  *p = 0; +                } +            } +        } +      cJSON_Delete (json); +    } +  return buffer; +} + + +/* An interactive standard REPL.  */ +static void +interactive_repl (void) +{ +  char *line = NULL; +  char *request = NULL; +  char *response = NULL; +  char *p; +  int first; + +  es_setvbuf (es_stdin, NULL, _IONBF, 0); +#if GPGRT_VERSION_NUMBER >= 0x011d00 /* 1.29 */ +  es_fprintf (es_stderr, "%s %s ready (enter \",help\" for help)\n", +              gpgrt_strusage (11), gpgrt_strusage (13)); +#endif +  do +    { +      es_fputs ("> ", es_stderr); +      es_fflush (es_stderr); +      es_fflush (es_stdout); +      xfree (line); +      line = get_line (); +      es_fflush (es_stderr); +      es_fflush (es_stdout); + +      first = !request; +      if (line && *line) +        { +          if (!request) +            request = xstrdup (line); +          else +            request = xstrconcat (request, "\n", line, NULL); +        } + +      if (!line) +        es_fputs ("\n", es_stderr); + +      if (!line || !*line || (first && *request == ',')) +        { +          /* Process the input.  */ +          xfree (response); +          response = NULL; +          if (request && *request == ',') +            { +              response = process_meta_commands (request+1); +            } +          else if (request) +            { +              response = process_request (request); +            } +          xfree (request); +          request = NULL; + +          if (response) +            { +              if (opt_interactive) +                { +                  char *msg = get_help_msg (response); +                  if (msg) +                    { +                      xfree (response); +                      response = msg; +                    } +                } + +              es_fputs ("===> ", es_stderr); +              es_fflush (es_stderr); +              for (p=response; *p; p++) +                { +                  if (*p == '\n') +                    { +                      es_fflush (es_stdout); +                      es_fputs ("\n===> ", es_stderr); +                      es_fflush (es_stderr); +                    } +                  else +                    es_putc (*p, es_stdout); +                } +              es_fflush (es_stdout); +              es_fputs ("\n", es_stderr); +            } +        } +    } +  while (line); + +  xfree (request); +  xfree (response); +  xfree (line); +} + + +/* Read and process  asingle request.  */ +static void +read_and_process_single_request (void) +{ +  char *line = NULL; +  char *request = NULL; +  char *response = NULL; +  size_t n; + +  for (;;) +    { +      xfree (line); +      line = get_line (); +      if (line && *line) +        request = (request? xstrconcat (request, "\n", line, NULL) +                   /**/   : xstrdup (line)); +      if (!line) +        { +          if (request) +            { +              xfree (response); +              response = process_request (request); +              if (response) +                { +                  es_fputs (response, es_stdout); +                  if ((n = strlen (response)) && response[n-1] != '\n') +                    es_fputc ('\n', es_stdout); +                } +              es_fflush (es_stdout); +            } +          break; +        } +    } + +  xfree (response); +  xfree (request); +  xfree (line); +} + + +/* The Native Messaging processing loop.  */ +static void +native_messaging_repl (void) +{ +  gpg_error_t err; +  uint32_t nrequest, nresponse; +  char *request = NULL; +  char *response = NULL; +  size_t n; + +  /* Due to the length octets we need to switch the I/O stream into +   * binary mode.  */ +  es_set_binary (es_stdin); +  es_set_binary (es_stdout); + +  for (;;) +    { +      /* Read length.  Note that the protocol uses native endianess. +       * Is it allowed to call such a thing a well thought out +       * protocol?  */ +      if (es_read (es_stdin, &nrequest, sizeof nrequest, &n)) +        { +          err = gpg_error_from_syserror (); +          log_error ("error reading request header: %s\n", gpg_strerror (err)); +          break; +        } +      if (!n) +        break;  /* EOF */ +      if (n != sizeof nrequest) +        { +          log_error ("error reading request header: short read\n"); +          break; +        } +      if (nrequest > MAX_REQUEST_SIZE) +        { +          log_error ("error reading request: request too long (%zu MiB)\n", +                     (size_t)nrequest / (1024*1024)); +          /* Fixme: Shall we read the request t the bit bucket and +           * return an error reponse or just return an error reponse +           * and terminate?  Needs some testing.  */ +          break; +        } + +      /* Read request.  */ +      request = xtrymalloc (nrequest); +      if (!request) +        { +          err = gpg_error_from_syserror (); +          log_error ("error reading request: Not enough memory for %zu MiB)\n", +                     (size_t)nrequest / (1024*1024)); +          /* FIXME: See comment above.  */ +          break; +        } +      if (es_read (es_stdin, request, nrequest, &n)) +        { +          err = gpg_error_from_syserror (); +          log_error ("error reading request: %s\n", gpg_strerror (err)); +          break; +        } +      if (n != nrequest) +        { +          /* That is a protocol violation.  */ +          xfree (response); +          response = error_object_string ("Invalid request:" +                                          " short read (%zu of %zu bytes)\n", +                                          n, (size_t)nrequest); +        } +      else /* Process request  */ +        { +          xfree (response); +          response = process_request (request); +        } +      nresponse = strlen (response); + +      /* Write response */ +      if (es_write (es_stdout, &nresponse, sizeof nresponse, &n)) +        { +          err = gpg_error_from_syserror (); +          log_error ("error writing request header: %s\n", gpg_strerror (err)); +          break; +        } +      if (n != sizeof nrequest) +        { +          log_error ("error writing request header: short write\n"); +          break; +        } +      if (es_write (es_stdout, response, nresponse, &n)) +        { +          err = gpg_error_from_syserror (); +          log_error ("error writing request: %s\n", gpg_strerror (err)); +          break; +        } +      if (n != nresponse) +        { +          log_error ("error writing request: short write\n"); +          break; +        } +      if (es_fflush (es_stdout) || es_ferror (es_stdout)) +        { +          err = gpg_error_from_syserror (); +          log_error ("error writing request: %s\n", gpg_strerror (err)); +          break; +        } +    } + +  xfree (response); +  xfree (request); +} + + + +static const char * +my_strusage( int level ) +{ +  const char *p; + +  switch (level) +    { +    case  9: p = "LGPL-2.1-or-later"; break; +    case 11: p = "gpgme-json"; break; +    case 13: p = PACKAGE_VERSION; break; +    case 14: p = "Copyright (C) 2018 g10 Code GmbH"; break; +    case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break; +    case 1: +    case 40: +      p = "Usage: gpgme-json [OPTIONS]"; +      break; +    case 41: +      p = "Native messaging based GPGME operations.\n"; +      break; +    case 42: +      p = "1"; /* Flag print 40 as part of 41. */ +      break; +    default: p = NULL; break; +    } +  return p; +} + +int +main (int argc, char *argv[]) +{ +#if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */ + +  fprintf (stderr, "WARNING: Old libgpg-error - using limited mode\n"); +  native_messaging_repl (); + +#else /* This is a modern libgp-error.  */ + +  enum { CMD_DEFAULT     = 0, +         CMD_INTERACTIVE = 'i', +         CMD_SINGLE      = 's', +         CMD_LIBVERSION  = 501 +  } cmd = CMD_DEFAULT; +  static gpgrt_opt_t opts[] = { +    ARGPARSE_c  (CMD_INTERACTIVE, "interactive", "Interactive REPL"), +    ARGPARSE_c  (CMD_SINGLE,      "single",      "Single request mode"), +    ARGPARSE_c  (CMD_LIBVERSION,  "lib-version", "Show library version"), +    ARGPARSE_end() +  }; +  gpgrt_argparse_t pargs = { &argc, &argv}; + +  gpgrt_set_strusage (my_strusage); + +#ifdef HAVE_SETLOCALE +  setlocale (LC_ALL, ""); +#endif +  gpgme_check_version (NULL); +#ifdef LC_CTYPE +  gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL)); +#endif +#ifdef LC_MESSAGES +  gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL)); +#endif + +  while (gpgrt_argparse (NULL, &pargs, opts)) +    { +      switch (pargs.r_opt) +        { +        case CMD_INTERACTIVE: +          opt_interactive = 1; +          /* Fall trough.  */ +        case CMD_SINGLE: +        case CMD_LIBVERSION: +          cmd = pargs.r_opt; +          break; + +        default: +          pargs.err = ARGPARSE_PRINT_WARNING; +	  break; +        } +    } +  gpgrt_argparse (NULL, &pargs, NULL); + +  switch (cmd) +    { +    case CMD_DEFAULT: +      native_messaging_repl (); +      break; + +    case CMD_SINGLE: +      read_and_process_single_request (); +      break; + +    case CMD_INTERACTIVE: +      interactive_repl (); +      break; + +    case CMD_LIBVERSION: +      printf ("Version from header: %s (0x%06x)\n", +              GPGME_VERSION, GPGME_VERSION_NUMBER); +      printf ("Version from binary: %s\n", gpgme_check_version (NULL)); +      printf ("Copyright blurb ...:%s\n", gpgme_check_version ("\x01\x01")); +      break; +    } + +#endif /* This is a modern libgp-error.  */ +  return 0; +} | 
