source: trunk/libtransmission/variant-json.c @ 13678

Last change on this file since 13678 was 13678, checked in by jordan, 8 years ago

(trunk libT) when seraializing to JSON, set the LC_NUMERIC locale once for the entire serialization pass, instead of N times

  • Property svn:keywords set to Date Rev Author Id
File size: 16.4 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2 (b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: variant-json.c 13678 2012-12-17 02:43:17Z jordan $
11 */
12
13#include <assert.h>
14#include <ctype.h>
15#include <math.h> /* fabs() */
16#include <stdio.h>
17#include <string.h>
18#include <errno.h> /* EILSEQ, EINVAL */
19
20#include <locale.h> /* setlocale() */
21
22#include <event2/buffer.h> /* evbuffer_add() */
23#include <event2/util.h> /* evutil_strtoll () */
24
25#define JSONSL_STATE_USER_FIELDS /* no fields */
26#include "jsonsl.h"
27#include "jsonsl.c"
28
29#define __LIBTRANSMISSION_VARIANT_MODULE___
30#include "transmission.h"
31#include "ConvertUTF.h"
32#include "list.h"
33#include "ptrarray.h"
34#include "utils.h"
35#include "variant.h"
36#include "variant-common.h"
37
38/* arbitrary value... this is much deeper than our code goes */
39#define MAX_DEPTH 64
40
41struct json_wrapper_data
42{
43  int error;
44  bool has_content;
45  tr_variant * top;
46  const char * key;
47  size_t keylen;
48  struct evbuffer * keybuf;
49  struct evbuffer * strbuf;
50  const char * source;
51  tr_ptrArray stack;
52};
53
54static tr_variant*
55get_node (struct jsonsl_st * jsn)
56{
57  tr_variant * parent;
58  tr_variant * node = NULL;
59  struct json_wrapper_data * data = jsn->data;
60
61  parent = tr_ptrArrayEmpty (&data->stack)
62         ? NULL
63         : tr_ptrArrayBack (&data->stack);
64
65  if (!parent)
66    {
67      node = data->top;
68    }
69  else if (tr_variantIsList (parent))
70    {
71      node = tr_variantListAdd (parent);
72    }
73  else if (tr_variantIsDict (parent) && (data->key!=NULL))
74    {
75      node = tr_variantDictAdd (parent, data->key, data->keylen);
76
77      data->key = NULL;
78      data->keylen = 0;
79    }
80
81  return node;
82}
83
84
85static void
86error_handler (jsonsl_t                  jsn,
87               jsonsl_error_t            error,
88               struct jsonsl_state_st  * state   UNUSED,
89               const jsonsl_char_t     * buf)
90{
91  struct json_wrapper_data * data = jsn->data;
92
93  if (data->source)
94    {
95      tr_err ("JSON parse failed in %s at pos %zu: %s -- remaining text \"%.16s\"",
96              data->source,
97              jsn->pos,
98              jsonsl_strerror (error),
99              buf);
100    }
101  else
102    {
103      tr_err ("JSON parse failed at pos %zu: %s -- remaining text \"%.16s\"",
104              jsn->pos,
105              jsonsl_strerror (error),
106              buf);
107    }
108
109  data->error = EILSEQ;
110}
111
112static int
113error_callback (jsonsl_t                  jsn,
114                jsonsl_error_t            error,
115                struct jsonsl_state_st  * state,
116                jsonsl_char_t           * at)
117{
118  error_handler (jsn, error, state, at);
119  return 0; /* bail */
120}
121
122static void
123action_callback_PUSH (jsonsl_t                  jsn,
124                      jsonsl_action_t           action  UNUSED,
125                      struct jsonsl_state_st  * state,
126                      const jsonsl_char_t     * buf     UNUSED)
127{
128  tr_variant * node;
129  struct json_wrapper_data * data = jsn->data;
130
131  switch (state->type)
132    {
133      case JSONSL_T_LIST:
134        data->has_content = true;
135        node = get_node (jsn);
136        tr_variantInitList (node, 0);
137        tr_ptrArrayAppend (&data->stack, node);
138        break;
139
140      case JSONSL_T_OBJECT:
141        data->has_content = true;
142        node = get_node (jsn);
143        tr_variantInitDict (node, 0);
144        tr_ptrArrayAppend (&data->stack, node);
145        break;
146
147      default:
148        /* nothing else interesting on push */
149        break;
150    }
151}
152
153/* like sscanf(in+2, "%4x", &val) but less slow */
154static bool
155decode_hex_string (const char * in, unsigned int * setme)
156{
157  bool success;
158  char buf[5];
159  char * end;
160
161  assert (in != NULL);
162  assert (in[0] == '\\');
163  assert (in[1] == 'u');
164
165  memcpy (buf, in+2, 4);
166  buf[4] = '\0';
167  *setme = strtoul (buf, &end, 16);
168  success = end == buf+4;
169
170  return success;
171}
172
173static char*
174extract_escaped_string (const char       * in,
175                        size_t             in_len,
176                        size_t           * len,
177                        struct evbuffer  * buf)
178{
179  const char * const in_end = in + in_len;
180
181  evbuffer_drain (buf, evbuffer_get_length (buf));
182
183  while (in < in_end)
184    {
185      bool unescaped = false;
186
187      if (*in=='\\' && in_end-in>=2)
188        {
189          switch (in[1])
190            {
191              case 'b' : evbuffer_add (buf, "\b", 1); in+=2; unescaped = true; break;
192              case 'f' : evbuffer_add (buf, "\f", 1); in+=2; unescaped = true; break;
193              case 'n' : evbuffer_add (buf, "\n", 1); in+=2; unescaped = true; break;
194              case 'r' : evbuffer_add (buf, "\r", 1); in+=2; unescaped = true; break;
195              case 't' : evbuffer_add (buf, "\t", 1); in+=2; unescaped = true; break;
196              case '/' : evbuffer_add (buf, "/" , 1); in+=2; unescaped = true; break;
197              case '"' : evbuffer_add (buf, "\"" , 1); in+=2; unescaped = true; break;
198              case '\\': evbuffer_add (buf, "\\", 1); in+=2; unescaped = true; break;
199              case 'u':
200                {
201                  if (in_end - in >= 6)
202                    {
203                      unsigned int val = 0;
204
205                      if (decode_hex_string (in, &val))
206                        {
207                          UTF32 str32_buf[2] = { val, 0 };
208                          const UTF32 * str32_walk = str32_buf;
209                          const UTF32 * str32_end = str32_buf + 1;
210                          UTF8 str8_buf[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
211                          UTF8 * str8_walk = str8_buf;
212                          UTF8 * str8_end = str8_buf + 8;
213   
214                          if (ConvertUTF32toUTF8 (&str32_walk, str32_end, &str8_walk, str8_end, 0) == 0)
215                            {
216                              const size_t len = str8_walk - str8_buf;
217                              evbuffer_add (buf, str8_buf, len);
218                              unescaped = true;
219                            }
220   
221                          in += 6;
222                          break;
223                        }
224                    }
225                }
226            }
227        }
228
229      if (!unescaped)
230        {
231          evbuffer_add (buf, in, 1);
232          ++in;
233        }
234    }
235
236  *len = evbuffer_get_length (buf);
237  return (char*) evbuffer_pullup (buf, -1);
238}
239
240static const char *
241extract_string (jsonsl_t                  jsn,
242                struct jsonsl_state_st  * state,
243                size_t                  * len,
244                struct evbuffer         * buf)
245{
246  const char * ret;
247  const char * in_begin;
248  const char * in_end;
249  size_t in_len;
250
251  /* figure out where the string is */
252  in_begin = jsn->base + state->pos_begin;
253  if (*in_begin == '"')
254    in_begin++;
255  in_end = jsn->base + state->pos_cur;
256  in_len = in_end - in_begin;
257
258  if (memchr (in_begin, '\\', in_len) == NULL)
259    {
260      /* it's not escaped */
261      ret = in_begin;
262      *len = in_len;
263    }
264  else
265    {
266      ret = extract_escaped_string (in_begin, in_len, len, buf);
267    }
268
269  return ret;
270}
271
272static void
273action_callback_POP (jsonsl_t                  jsn,
274                     jsonsl_action_t           action  UNUSED,
275                     struct jsonsl_state_st  * state,
276                     const jsonsl_char_t     * buf     UNUSED)
277{
278  struct json_wrapper_data * data = jsn->data;
279
280  if (state->type == JSONSL_T_STRING)
281    {
282      size_t len;
283      const char * str = extract_string (jsn, state, &len, data->strbuf);
284      tr_variantInitStr (get_node (jsn), str, len);
285      data->has_content = true;
286    }
287  else if (state->type == JSONSL_T_HKEY)
288    {
289      data->has_content = true;
290      data->key = extract_string (jsn, state, &data->keylen, data->keybuf);
291    }
292  else if ((state->type == JSONSL_T_LIST) || (state->type == JSONSL_T_OBJECT))
293    {
294      tr_ptrArrayPop (&data->stack);
295    }
296  else if (state->type == JSONSL_T_SPECIAL)
297    {
298      if (state->special_flags & JSONSL_SPECIALf_NUMNOINT)
299        {
300          const char * begin = jsn->base + state->pos_begin;
301          data->has_content = true;
302          tr_variantInitReal (get_node (jsn), strtod (begin, NULL));
303        }
304      else if (state->special_flags & JSONSL_SPECIALf_NUMERIC)
305        {
306          const char * begin = jsn->base + state->pos_begin;
307          data->has_content = true;
308          tr_variantInitInt (get_node (jsn), evutil_strtoll (begin, NULL, 10));
309        }
310      else if (state->special_flags & JSONSL_SPECIALf_BOOLEAN)
311        {
312          const bool b = (state->special_flags & JSONSL_SPECIALf_TRUE) != 0;
313          data->has_content = true;
314          tr_variantInitBool (get_node (jsn), b);
315        }
316      else if (state->special_flags & JSONSL_SPECIALf_NULL)
317        {
318          data->has_content = true;
319          tr_variantInitStr (get_node (jsn), "", 0);
320        }
321    }
322}
323
324int
325tr_jsonParse (const char     * source,
326              const void     * vbuf,
327              size_t           len,
328              tr_variant     * setme_variant,
329              const char    ** setme_end)
330{
331  int error;
332  jsonsl_t jsn;
333  struct json_wrapper_data data;
334
335  jsn = jsonsl_new (MAX_DEPTH);
336  jsn->action_callback_PUSH = action_callback_PUSH;
337  jsn->action_callback_POP = action_callback_POP;
338  jsn->error_callback = error_callback;
339  jsn->data = &data;
340  jsonsl_enable_all_callbacks (jsn);
341
342  data.error = 0;
343  data.has_content = false;
344  data.key = NULL;
345  data.top = setme_variant;
346  data.stack = TR_PTR_ARRAY_INIT;
347  data.source = source;
348  data.keybuf = evbuffer_new ();
349  data.strbuf = evbuffer_new ();
350
351  /* parse it */
352  jsonsl_feed (jsn, vbuf, len);
353
354  /* EINVAL if there was no content */
355  if (!data.error && !data.has_content)
356    data.error = EINVAL;
357
358  /* maybe set the end ptr */
359  if (setme_end)
360    *setme_end = ((const char*)vbuf) + jsn->pos;
361
362  /* cleanup */
363  error = data.error;
364  evbuffer_free (data.keybuf);
365  evbuffer_free (data.strbuf);
366  tr_ptrArrayDestruct (&data.stack, NULL);
367  jsonsl_destroy (jsn);
368  return error;
369}
370
371/****
372*****
373****/
374
375struct ParentState
376{
377  int variantType;
378  int childIndex;
379  int childCount;
380};
381
382struct jsonWalk
383{
384  bool doIndent;
385  tr_list * parents;
386  struct evbuffer *  out;
387};
388
389static void
390jsonIndent (struct jsonWalk * data)
391{
392  if (data->doIndent)
393    {
394      char buf[1024];
395      const int width = tr_list_size (data->parents) * 4;
396
397      buf[0] = '\n';
398      memset (buf+1, ' ', width);
399      evbuffer_add (data->out, buf, 1+width);
400    }
401}
402
403static void
404jsonChildFunc (struct jsonWalk * data)
405{
406  if (data->parents && data->parents->data)
407    {
408      struct ParentState * pstate = data->parents->data;
409
410      switch (pstate->variantType)
411        {
412          case TR_VARIANT_TYPE_DICT:
413            {
414              const int i = pstate->childIndex++;
415              if (! (i % 2))
416                {
417                  evbuffer_add (data->out, ": ", data->doIndent ? 2 : 1);
418                }
419              else
420                {
421                  const bool isLast = pstate->childIndex == pstate->childCount;
422
423                  if (!isLast)
424                    {
425                      evbuffer_add (data->out, ", ", data->doIndent ? 2 : 1);
426                      jsonIndent (data);
427                    }
428                }
429              break;
430            }
431
432          case TR_VARIANT_TYPE_LIST:
433            {
434              const bool isLast = ++pstate->childIndex == pstate->childCount;
435              if (!isLast)
436                {
437                  evbuffer_add (data->out, ", ", data->doIndent ? 2 : 1);
438                  jsonIndent (data);
439                }
440              break;
441            }
442
443          default:
444            break;
445        }
446    }
447}
448
449static void
450jsonPushParent (struct jsonWalk  * data,
451                const tr_variant * v)
452{
453  struct ParentState * pstate = tr_new (struct ParentState, 1);
454
455  pstate->variantType = v->type;
456  pstate->childIndex = 0;
457  pstate->childCount = v->val.l.count;
458  tr_list_prepend (&data->parents, pstate);
459}
460
461static void
462jsonPopParent (struct jsonWalk * data)
463{
464  tr_free (tr_list_pop_front (&data->parents));
465}
466
467static void
468jsonIntFunc (const tr_variant * val, void * vdata)
469{
470  struct jsonWalk * data = vdata;
471  evbuffer_add_printf (data->out, "%" PRId64, val->val.i);
472  jsonChildFunc (data);
473}
474
475static void
476jsonBoolFunc (const tr_variant * val,
477              void             * vdata)
478{
479  struct jsonWalk * data = vdata;
480
481  if (val->val.b)
482    evbuffer_add (data->out, "true", 4);
483  else
484    evbuffer_add (data->out, "false", 5);
485
486  jsonChildFunc (data);
487}
488
489static void
490jsonRealFunc (const tr_variant * val,
491              void             * vdata)
492{
493  struct jsonWalk * data = vdata;
494
495  if (fabs (val->val.d - (int)val->val.d) < 0.00001)
496    evbuffer_add_printf (data->out, "%d", (int)val->val.d);
497  else
498    evbuffer_add_printf (data->out, "%.4f", tr_truncd (val->val.d, 4));
499
500  jsonChildFunc (data);
501}
502
503static void
504jsonStringFunc (const tr_variant * val,
505                void             * vdata)
506{
507  char * out;
508  char * outwalk;
509  char * outend;
510  struct evbuffer_iovec vec[1];
511  struct jsonWalk * data = vdata;
512  const char * str;
513  size_t len;
514  const unsigned char * it;
515  const unsigned char * end;
516
517  tr_variantGetStr (val, &str, &len);
518  it = (const unsigned char *) str;
519  end = it + len;
520
521  evbuffer_reserve_space (data->out, len * 4, vec, 1);
522  out = vec[0].iov_base;
523  outend = out + vec[0].iov_len;
524
525  outwalk = out;
526  *outwalk++ = '"';
527
528  for (; it!=end; ++it)
529    {
530      switch (*it)
531        {
532          case '\b': *outwalk++ = '\\'; *outwalk++ = 'b'; break;
533          case '\f': *outwalk++ = '\\'; *outwalk++ = 'f'; break;
534          case '\n': *outwalk++ = '\\'; *outwalk++ = 'n'; break;
535          case '\r': *outwalk++ = '\\'; *outwalk++ = 'r'; break;
536          case '\t': *outwalk++ = '\\'; *outwalk++ = 't'; break;
537          case '"' : *outwalk++ = '\\'; *outwalk++ = '"'; break;
538          case '\\': *outwalk++ = '\\'; *outwalk++ = '\\'; break;
539
540          default:
541            if (isascii (*it))
542              {
543                *outwalk++ = *it;
544              }
545            else
546              {
547                const UTF8 * tmp = it;
548                UTF32 buf[1] = { 0 };
549                UTF32 * u32 = buf;
550                ConversionResult result = ConvertUTF8toUTF32 (&tmp, end, &u32, buf + 1, 0);
551                if (((result==conversionOK) || (result==targetExhausted)) && (tmp!=it))
552                  {
553                    outwalk += tr_snprintf (outwalk, outend-outwalk, "\\u%04x", (unsigned int)buf[0]);
554                    it = tmp - 1;
555                  }
556              }
557            break;
558        }
559    }
560
561  *outwalk++ = '"';
562  vec[0].iov_len = outwalk - out;
563  evbuffer_commit_space (data->out, vec, 1);
564
565  jsonChildFunc (data);
566}
567
568static void
569jsonDictBeginFunc (const tr_variant * val,
570                   void             * vdata)
571{
572  struct jsonWalk * data = vdata;
573
574  jsonPushParent (data, val);
575  evbuffer_add (data->out, "{", 1);
576  if (val->val.l.count)
577    jsonIndent (data);
578}
579
580static void
581jsonListBeginFunc (const tr_variant * val,
582                   void             * vdata)
583{
584  const size_t nChildren = tr_variantListSize (val);
585  struct jsonWalk * data = vdata;
586
587  jsonPushParent (data, val);
588  evbuffer_add (data->out, "[", 1);
589  if (nChildren)
590    jsonIndent (data);
591}
592
593static void
594jsonContainerEndFunc (const tr_variant * val,
595                      void             * vdata)
596{
597  struct jsonWalk * data = vdata;
598  int emptyContainer = false;
599
600  jsonPopParent (data);
601
602  if (!emptyContainer)
603    jsonIndent (data);
604  if (tr_variantIsDict (val))
605    evbuffer_add (data->out, "}", 1);
606  else /* list */
607    evbuffer_add (data->out, "]", 1);
608
609  jsonChildFunc (data);
610}
611
612static const struct VariantWalkFuncs walk_funcs = { jsonIntFunc,
613                                                    jsonBoolFunc,
614                                                    jsonRealFunc,
615                                                    jsonStringFunc,
616                                                    jsonDictBeginFunc,
617                                                    jsonListBeginFunc,
618                                                    jsonContainerEndFunc };
619
620void
621tr_variantToBufJson (const tr_variant * top, struct evbuffer * buf, bool lean)
622{
623  char lc_numeric[128];
624  struct jsonWalk data;
625
626  data.doIndent = !lean;
627  data.out = buf;
628  data.parents = NULL;
629
630  /* json requires a '.' decimal point regardless of locale */
631  tr_strlcpy (lc_numeric, setlocale (LC_NUMERIC, NULL), sizeof (lc_numeric));
632  setlocale (LC_NUMERIC, "POSIX");
633  tr_variantWalk (top, &walk_funcs, &data, true);
634  setlocale (LC_NUMERIC, lc_numeric);
635
636  if (evbuffer_get_length (buf))
637    evbuffer_add_printf (buf, "\n");
638}
Note: See TracBrowser for help on using the repository browser.