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

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

copyediting: whitespace & indentation

  • Property svn:keywords set to Date Rev Author Id
File size: 16.5 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 13675 2012-12-15 16:31:22Z 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  char locale[128];
495
496  if (fabs (val->val.d - (int)val->val.d) < 0.00001)
497    {
498      evbuffer_add_printf (data->out, "%d", (int)val->val.d);
499    }
500  else
501    {
502      /* json requires a '.' decimal point regardless of locale */
503      tr_strlcpy (locale, setlocale (LC_NUMERIC, NULL), sizeof (locale));
504      setlocale (LC_NUMERIC, "POSIX");
505      evbuffer_add_printf (data->out, "%.4f", tr_truncd (val->val.d, 4));
506      setlocale (LC_NUMERIC, locale);
507    }
508
509  jsonChildFunc (data);
510}
511
512static void
513jsonStringFunc (const tr_variant * val,
514                void             * vdata)
515{
516  char * out;
517  char * outwalk;
518  char * outend;
519  struct evbuffer_iovec vec[1];
520  struct jsonWalk * data = vdata;
521  const char * str;
522  size_t len;
523  const unsigned char * it;
524  const unsigned char * end;
525
526  tr_variantGetStr (val, &str, &len);
527  it = (const unsigned char *) str;
528  end = it + len;
529
530  evbuffer_reserve_space (data->out, len * 4, vec, 1);
531  out = vec[0].iov_base;
532  outend = out + vec[0].iov_len;
533
534  outwalk = out;
535  *outwalk++ = '"';
536
537  for (; it!=end; ++it)
538    {
539      switch (*it)
540        {
541          case '\b': *outwalk++ = '\\'; *outwalk++ = 'b'; break;
542          case '\f': *outwalk++ = '\\'; *outwalk++ = 'f'; break;
543          case '\n': *outwalk++ = '\\'; *outwalk++ = 'n'; break;
544          case '\r': *outwalk++ = '\\'; *outwalk++ = 'r'; break;
545          case '\t': *outwalk++ = '\\'; *outwalk++ = 't'; break;
546          case '"' : *outwalk++ = '\\'; *outwalk++ = '"'; break;
547          case '\\': *outwalk++ = '\\'; *outwalk++ = '\\'; break;
548
549          default:
550            if (isascii (*it))
551              {
552                *outwalk++ = *it;
553              }
554            else
555              {
556                const UTF8 * tmp = it;
557                UTF32 buf[1] = { 0 };
558                UTF32 * u32 = buf;
559                ConversionResult result = ConvertUTF8toUTF32 (&tmp, end, &u32, buf + 1, 0);
560                if (((result==conversionOK) || (result==targetExhausted)) && (tmp!=it))
561                  {
562                    outwalk += tr_snprintf (outwalk, outend-outwalk, "\\u%04x", (unsigned int)buf[0]);
563                    it = tmp - 1;
564                  }
565              }
566            break;
567        }
568    }
569
570  *outwalk++ = '"';
571  vec[0].iov_len = outwalk - out;
572  evbuffer_commit_space (data->out, vec, 1);
573
574  jsonChildFunc (data);
575}
576
577static void
578jsonDictBeginFunc (const tr_variant * val,
579                   void             * vdata)
580{
581  struct jsonWalk * data = vdata;
582
583  jsonPushParent (data, val);
584  evbuffer_add (data->out, "{", 1);
585  if (val->val.l.count)
586    jsonIndent (data);
587}
588
589static void
590jsonListBeginFunc (const tr_variant * val,
591                   void             * vdata)
592{
593  const size_t nChildren = tr_variantListSize (val);
594  struct jsonWalk * data = vdata;
595
596  jsonPushParent (data, val);
597  evbuffer_add (data->out, "[", 1);
598  if (nChildren)
599    jsonIndent (data);
600}
601
602static void
603jsonContainerEndFunc (const tr_variant * val,
604                      void             * vdata)
605{
606  struct jsonWalk * data = vdata;
607  int emptyContainer = false;
608
609  jsonPopParent (data);
610
611  if (!emptyContainer)
612    jsonIndent (data);
613  if (tr_variantIsDict (val))
614    evbuffer_add (data->out, "}", 1);
615  else /* list */
616    evbuffer_add (data->out, "]", 1);
617
618  jsonChildFunc (data);
619}
620
621static const struct VariantWalkFuncs walk_funcs = { jsonIntFunc,
622                                                    jsonBoolFunc,
623                                                    jsonRealFunc,
624                                                    jsonStringFunc,
625                                                    jsonDictBeginFunc,
626                                                    jsonListBeginFunc,
627                                                    jsonContainerEndFunc };
628
629void
630tr_variantToBufJson (const tr_variant * top, struct evbuffer * buf, bool lean)
631{
632  struct jsonWalk data;
633  data.doIndent = !lean;
634  data.out = buf;
635  data.parents = NULL;
636  tr_variantWalk (top, &walk_funcs, &data, true);
637  if (evbuffer_get_length (buf))
638    evbuffer_add_printf (buf, "\n");
639}
Note: See TracBrowser for help on using the repository browser.