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

Last change on this file since 13672 was 13672, checked in by jordan, 9 years ago

(trunk, libT) faster JSON parsing for tr_variant. This mostly helps the Qt client, which makes heavy use of the JSON-based RPC calls.

  • Property svn:keywords set to Date Rev Author Id
File size: 16.2 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 13672 2012-12-15 00:01:59Z 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, size_t in_len, size_t * len, struct evbuffer * buf)
175{
176  const char * const in_end = in + in_len;
177
178  evbuffer_drain (buf, evbuffer_get_length (buf));
179
180  while (in < in_end)
181    {
182      bool unescaped = false;
183
184      if (*in=='\\' && in_end-in>=2)
185        {
186          switch (in[1])
187            {
188              case 'b' : evbuffer_add (buf, "\b", 1); in+=2; unescaped = true; break;
189              case 'f' : evbuffer_add (buf, "\f", 1); in+=2; unescaped = true; break;
190              case 'n' : evbuffer_add (buf, "\n", 1); in+=2; unescaped = true; break;
191              case 'r' : evbuffer_add (buf, "\r", 1); in+=2; unescaped = true; break;
192              case 't' : evbuffer_add (buf, "\t", 1); in+=2; unescaped = true; break;
193              case '/' : evbuffer_add (buf, "/" , 1); in+=2; unescaped = true; break;
194              case '"' : evbuffer_add (buf, "\"" , 1); in+=2; unescaped = true; break;
195              case '\\': evbuffer_add (buf, "\\", 1); in+=2; unescaped = true; break;
196              case 'u':
197                {
198                  if (in_end - in >= 6)
199                    {
200                      unsigned int val = 0;
201
202                      if (decode_hex_string (in, &val))
203                        {
204                          UTF32 str32_buf[2] = { val, 0 };
205                          const UTF32 * str32_walk = str32_buf;
206                          const UTF32 * str32_end = str32_buf + 1;
207                          UTF8 str8_buf[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
208                          UTF8 * str8_walk = str8_buf;
209                          UTF8 * str8_end = str8_buf + 8;
210   
211                          if (ConvertUTF32toUTF8 (&str32_walk, str32_end, &str8_walk, str8_end, 0) == 0)
212                            {
213                              const size_t len = str8_walk - str8_buf;
214                              evbuffer_add (buf, str8_buf, len);
215                              unescaped = true;
216                            }
217   
218                          in += 6;
219                          break;
220                        }
221                    }
222                }
223            }
224        }
225
226      if (!unescaped)
227        {
228          evbuffer_add (buf, in, 1);
229          ++in;
230        }
231    }
232
233  *len = evbuffer_get_length (buf);
234  return (char*) evbuffer_pullup (buf, -1);
235}
236
237static const char*
238extract_string (jsonsl_t jsn, struct jsonsl_state_st * state, size_t * len, struct evbuffer * buf)
239{
240  const char * ret;
241  const char * in_begin;
242  const char * in_end;
243  size_t in_len;
244
245  /* figure out where the string is */
246  in_begin = jsn->base + state->pos_begin;
247  if (*in_begin == '"')
248    in_begin++;
249  in_end = jsn->base + state->pos_cur;
250  in_len = in_end - in_begin;
251
252  if (memchr (in_begin, '\\', in_len) == NULL)
253    {
254      /* it's not escaped */
255      ret = in_begin;
256      *len = in_len;
257    }
258  else
259    {
260      ret = extract_escaped_string (in_begin, in_len, len, buf);
261    }
262
263  return ret;
264}
265
266static void
267action_callback_POP (jsonsl_t                  jsn,
268                     jsonsl_action_t           action  UNUSED,
269                     struct jsonsl_state_st  * state,
270                     const jsonsl_char_t     * buf     UNUSED)
271{
272  struct json_wrapper_data * data = jsn->data;
273
274  if (state->type == JSONSL_T_STRING)
275    {
276      size_t len;
277      const char * str = extract_string (jsn, state, &len, data->strbuf);
278      tr_variantInitStr (get_node (jsn), str, len);
279      data->has_content = true;
280    }
281  else if (state->type == JSONSL_T_HKEY)
282    {
283      data->has_content = true;
284      data->key = extract_string (jsn, state, &data->keylen, data->keybuf);
285    }
286  else if ((state->type == JSONSL_T_LIST) || (state->type == JSONSL_T_OBJECT))
287    {
288      tr_ptrArrayPop (&data->stack);
289    }
290  else if (state->type == JSONSL_T_SPECIAL)
291    {
292      if (state->special_flags & JSONSL_SPECIALf_NUMNOINT)
293        {
294          const char * begin = jsn->base + state->pos_begin;
295          data->has_content = true;
296          tr_variantInitReal (get_node (jsn), strtod (begin, NULL));
297        }
298      else if (state->special_flags & JSONSL_SPECIALf_NUMERIC)
299        {
300          const char * begin = jsn->base + state->pos_begin;
301          data->has_content = true;
302          tr_variantInitInt (get_node (jsn), evutil_strtoll (begin, NULL, 10));
303        }
304      else if (state->special_flags & JSONSL_SPECIALf_BOOLEAN)
305        {
306          const bool b = (state->special_flags & JSONSL_SPECIALf_TRUE) != 0;
307          data->has_content = true;
308          tr_variantInitBool (get_node (jsn), b);
309        }
310      else if (state->special_flags & JSONSL_SPECIALf_NULL)
311        {
312          data->has_content = true;
313          tr_variantInitStr (get_node (jsn), "", 0);
314        }
315    }
316}
317
318int
319tr_jsonParse (const char     * source,
320              const void     * vbuf,
321              size_t           len,
322              tr_variant        * setme_benc,
323              const char ** setme_end)
324{
325  int error;
326  jsonsl_t jsn;
327  struct json_wrapper_data data;
328
329  jsn = jsonsl_new (MAX_DEPTH);
330  jsn->action_callback_PUSH = action_callback_PUSH;
331  jsn->action_callback_POP = action_callback_POP;
332  jsn->error_callback = error_callback;
333  jsn->data = &data;
334  jsonsl_enable_all_callbacks (jsn);
335
336  data.error = 0;
337  data.has_content = false;
338  data.key = NULL;
339  data.top = setme_benc;
340  data.stack = TR_PTR_ARRAY_INIT;
341  data.source = source;
342  data.keybuf = evbuffer_new ();
343  data.strbuf = evbuffer_new ();
344
345  /* parse it */
346  jsonsl_feed (jsn, vbuf, len);
347
348  /* EINVAL if there was no content */
349  if (!data.error && !data.has_content)
350    data.error = EINVAL;
351
352  /* maybe set the end ptr */
353  if (setme_end)
354    *setme_end = ((const char*)vbuf) + jsn->pos;
355
356  /* cleanup */
357  error = data.error;
358  evbuffer_free (data.keybuf);
359  evbuffer_free (data.strbuf);
360  tr_ptrArrayDestruct (&data.stack, NULL);
361  jsonsl_destroy (jsn);
362  return error;
363}
364
365/****
366*****
367****/
368
369struct ParentState
370{
371  int bencType;
372  int childIndex;
373  int childCount;
374};
375
376struct jsonWalk
377{
378  bool doIndent;
379  tr_list * parents;
380  struct evbuffer *  out;
381};
382
383static void
384jsonIndent (struct jsonWalk * data)
385{
386  if (data->doIndent)
387    {
388      char buf[1024];
389      const int width = tr_list_size (data->parents) * 4;
390
391      buf[0] = '\n';
392      memset (buf+1, ' ', width);
393      evbuffer_add (data->out, buf, 1+width);
394    }
395}
396
397static void
398jsonChildFunc (struct jsonWalk * data)
399{
400  if (data->parents && data->parents->data)
401    {
402      struct ParentState * pstate = data->parents->data;
403
404      switch (pstate->bencType)
405        {
406          case TR_VARIANT_TYPE_DICT:
407            {
408              const int i = pstate->childIndex++;
409              if (! (i % 2))
410                {
411                  evbuffer_add (data->out, ": ", data->doIndent ? 2 : 1);
412                }
413              else
414                {
415                  const bool isLast = pstate->childIndex == pstate->childCount;
416
417                  if (!isLast)
418                    {
419                      evbuffer_add (data->out, ", ", data->doIndent ? 2 : 1);
420                      jsonIndent (data);
421                    }
422                }
423              break;
424            }
425
426          case TR_VARIANT_TYPE_LIST:
427            {
428              const bool isLast = ++pstate->childIndex == pstate->childCount;
429              if (!isLast)
430                {
431                  evbuffer_add (data->out, ", ", data->doIndent ? 2 : 1);
432                  jsonIndent (data);
433                }
434              break;
435            }
436
437          default:
438            break;
439        }
440    }
441}
442
443static void
444jsonPushParent (struct jsonWalk  * data,
445                const tr_variant * benc)
446{
447  struct ParentState * pstate = tr_new (struct ParentState, 1);
448
449  pstate->bencType = benc->type;
450  pstate->childIndex = 0;
451  pstate->childCount = benc->val.l.count;
452  tr_list_prepend (&data->parents, pstate);
453}
454
455static void
456jsonPopParent (struct jsonWalk * data)
457{
458  tr_free (tr_list_pop_front (&data->parents));
459}
460
461static void
462jsonIntFunc (const tr_variant * val, void * vdata)
463{
464  struct jsonWalk * data = vdata;
465  evbuffer_add_printf (data->out, "%" PRId64, val->val.i);
466  jsonChildFunc (data);
467}
468
469static void
470jsonBoolFunc (const tr_variant * val, void * vdata)
471{
472  struct jsonWalk * data = vdata;
473
474  if (val->val.b)
475    evbuffer_add (data->out, "true", 4);
476  else
477    evbuffer_add (data->out, "false", 5);
478
479  jsonChildFunc (data);
480}
481
482static void
483jsonRealFunc (const tr_variant * val, void * vdata)
484{
485  struct jsonWalk * data = vdata;
486  char locale[128];
487
488  if (fabs (val->val.d - (int)val->val.d) < 0.00001)
489    {
490      evbuffer_add_printf (data->out, "%d", (int)val->val.d);
491    }
492  else
493    {
494      /* json requires a '.' decimal point regardless of locale */
495      tr_strlcpy (locale, setlocale (LC_NUMERIC, NULL), sizeof (locale));
496      setlocale (LC_NUMERIC, "POSIX");
497      evbuffer_add_printf (data->out, "%.4f", tr_truncd (val->val.d, 4));
498      setlocale (LC_NUMERIC, locale);
499    }
500
501  jsonChildFunc (data);
502}
503
504static void
505jsonStringFunc (const tr_variant * val, 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  struct jsonWalk data;
624  data.doIndent = !lean;
625  data.out = buf;
626  data.parents = NULL;
627  tr_variantWalk (top, &walk_funcs, &data, true);
628  if (evbuffer_get_length (buf))
629    evbuffer_add_printf (buf, "\n");
630}
Note: See TracBrowser for help on using the repository browser.