Coverage Report

Created: 2026-08-13 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libplist/src/jsmn.c
Line
Count
Source
1
/*
2
 * jsmn.c
3
 * Simple JSON parser
4
 *
5
 * Copyright (c) 2010 Serge A. Zaitsev
6
 * Updated to use size_t for token offsets and harden against overflows.
7
 *   (Nikias Bassen, January 2026)
8
 *
9
 * Permission is hereby granted, free of charge, to any person obtaining a copy
10
 * of this software and associated documentation files (the "Software"), to deal
11
 * in the Software without restriction, including without limitation the rights
12
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
 * copies of the Software, and to permit persons to whom the Software is
14
 * furnished to do so, subject to the following conditions:
15
 *
16
 * The above copyright notice and this permission notice shall be included in
17
 * all copies or substantial portions of the Software.
18
 *
19
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
 * THE SOFTWARE.
26
 */
27
28
#include <stdlib.h>
29
#include <stdint.h>
30
#include <limits.h>
31
#include <assert.h>
32
33
#include "jsmn.h"
34
35
106M
#define JSMN_POS_INVALID ((size_t)SIZE_MAX)
36
37
/**
38
 * Allocates a fresh unused token from the token pull.
39
 */
40
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
41
326k
    jsmntok_t *tokens, unsigned int num_tokens) {
42
326k
  jsmntok_t *tok;
43
326k
  if ((unsigned int)parser->toknext >= num_tokens) {
44
11.0k
    return NULL;
45
11.0k
  }
46
315k
  tok = &tokens[parser->toknext++];
47
315k
  tok->start = tok->end = JSMN_POS_INVALID;
48
315k
  tok->size = 0;
49
#ifdef JSMN_PARENT_LINKS
50
  tok->parent = -1;
51
#endif
52
315k
  return tok;
53
326k
}
54
55
/**
56
 * Fills token type and boundaries.
57
 */
58
static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
59
144k
                            size_t start, size_t end) {
60
144k
  token->type = type;
61
144k
  token->start = start;
62
144k
  token->end = end;
63
144k
  token->size = 0;
64
144k
}
65
66
/**
67
 * Fills next available token with JSON primitive.
68
 */
69
static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
70
33.6k
    jsmntok_t *tokens, unsigned int num_tokens) {
71
33.6k
  jsmntok_t *token;
72
33.6k
  size_t start;
73
74
33.6k
  start = parser->pos;
75
76
103k
  for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) {
77
103k
    switch (js[parser->pos]) {
78
0
#ifndef JSMN_STRICT
79
      /* In strict mode primitive must be followed by "," or "}" or "]" */
80
7.42k
      case ':':
81
7.42k
#endif
82
20.1k
      case '\t' : case '\r' : case '\n' : case ' ' :
83
32.9k
      case ','  : case ']'  : case '}' :
84
32.9k
        goto found;
85
70.2k
      default:
86
70.2k
        break;
87
103k
    }
88
70.2k
    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
89
23
      parser->pos = start;
90
23
      return JSMN_ERROR_INVAL;
91
23
    }
92
70.2k
  }
93
#ifdef JSMN_STRICT
94
  /* In strict mode primitive must be followed by a comma/object/array */
95
  parser->pos = start;
96
  return JSMN_ERROR_PART;
97
#endif
98
99
33.5k
found:
100
33.5k
  token = jsmn_alloc_token(parser, tokens, num_tokens);
101
33.5k
  if (token == NULL) {
102
1.12k
    parser->pos = start;
103
1.12k
    return JSMN_ERROR_NOMEM;
104
1.12k
  }
105
32.4k
  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
106
#ifdef JSMN_PARENT_LINKS
107
  token->parent = parser->toksuper;
108
#endif
109
32.4k
  parser->pos--;
110
32.4k
  return JSMN_SUCCESS;
111
33.5k
}
112
113
/**
114
 * Fills next token with JSON string.
115
 */
116
static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
117
115k
    jsmntok_t *tokens, unsigned int num_tokens) {
118
115k
  jsmntok_t *token;
119
120
115k
  size_t start = parser->pos;
121
122
115k
  parser->pos++;
123
124
  /* Skip starting quote */
125
312k
  for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) {
126
312k
    char c = js[parser->pos];
127
128
    /* Quote: end of string */
129
312k
    if (c == '\"') {
130
114k
      token = jsmn_alloc_token(parser, tokens, num_tokens);
131
114k
      if (token == NULL) {
132
2.54k
        parser->pos = start;
133
2.54k
        return JSMN_ERROR_NOMEM;
134
2.54k
      }
135
112k
      jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
136
#ifdef JSMN_PARENT_LINKS
137
      token->parent = parser->toksuper;
138
#endif
139
112k
      return JSMN_SUCCESS;
140
114k
    }
141
142
    /* Backslash: Quoted symbol expected */
143
197k
    if (c == '\\') {
144
24.1k
      parser->pos++;
145
24.1k
      if (parser->end > 0 && parser->pos >= parser->end) {
146
2
        parser->pos = start;
147
2
        return JSMN_ERROR_INVAL;
148
2
      }
149
24.1k
      switch (js[parser->pos]) {
150
        /* Allowed escaped symbols */
151
6.86k
        case '\"': case '/' : case '\\' : case 'b' :
152
22.4k
        case 'f' : case 'r' : case 'n'  : case 't' :
153
22.4k
          break;
154
        /* Allows escaped symbol \uXXXX */
155
1.66k
        case 'u':
156
          /* TODO */
157
1.66k
          break;
158
        /* Unexpected symbol */
159
2
        default:
160
2
          parser->pos = start;
161
2
          return JSMN_ERROR_INVAL;
162
24.1k
      }
163
24.1k
    }
164
197k
  }
165
131
  parser->pos = start;
166
131
  return JSMN_ERROR_PART;
167
115k
}
168
169
/**
170
 * Parse JSON string and fill tokens.
171
 */
172
jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t length, jsmntok_t *tokens,
173
13.3k
    unsigned int num_tokens) {
174
13.3k
  jsmnerr_t r;
175
13.3k
  int i;
176
13.3k
  jsmntok_t *token;
177
178
13.3k
  parser->end = length;
179
180
13.3k
  if (num_tokens >= INT_MAX) {
181
0
    return JSMN_ERROR_LIMIT;
182
0
  }
183
13.3k
  if (length > SIZE_MAX / 2) {
184
0
    return JSMN_ERROR_LIMIT;
185
0
  }
186
187
440k
  for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) {
188
438k
    char c;
189
438k
    jsmntype_t type;
190
191
438k
    c = js[parser->pos];
192
438k
    switch (c) {
193
178k
      case '{': case '[':
194
178k
        token = jsmn_alloc_token(parser, tokens, num_tokens);
195
178k
        if (token == NULL)
196
7.36k
          return JSMN_ERROR_NOMEM;
197
170k
        if (parser->toksuper != -1) {
198
164k
          tokens[parser->toksuper].size++;
199
#ifdef JSMN_PARENT_LINKS
200
          token->parent = parser->toksuper;
201
#endif
202
164k
        }
203
170k
        token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
204
170k
        token->start = parser->pos;
205
170k
        parser->toksuper = parser->toknext - 1;
206
170k
        break;
207
79.4k
      case '}': case ']':
208
79.4k
        type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
209
#ifdef JSMN_PARENT_LINKS
210
        if (parser->toknext < 1) {
211
          return JSMN_ERROR_INVAL;
212
        }
213
        token = &tokens[parser->toknext - 1];
214
        for (;;) {
215
          if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
216
            if (token->type != type) {
217
              return JSMN_ERROR_INVAL;
218
            }
219
            if (parser->pos == SIZE_MAX) {
220
              return JSMN_ERROR_INVAL;
221
            }
222
            token->end = parser->pos + 1;
223
            parser->toksuper = token->parent;
224
            break;
225
          }
226
          if (token->parent == -1) {
227
            break;
228
          }
229
          token = &tokens[token->parent];
230
        }
231
#else
232
7.86M
        for (i = parser->toknext - 1; i >= 0; i--) {
233
7.86M
          token = &tokens[i];
234
7.86M
          if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
235
79.4k
            if (token->type != type) {
236
2
              return JSMN_ERROR_INVAL;
237
2
            }
238
79.4k
            parser->toksuper = -1;
239
79.4k
            token->end = parser->pos + 1;
240
79.4k
            break;
241
79.4k
          }
242
7.86M
        }
243
        /* Error if unmatched closing bracket */
244
79.4k
        if (i == -1) return JSMN_ERROR_INVAL;
245
27.1M
        for (; i >= 0; i--) {
246
27.1M
          token = &tokens[i];
247
27.1M
          if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
248
73.2k
            parser->toksuper = i;
249
73.2k
            break;
250
73.2k
          }
251
27.1M
        }
252
79.4k
#endif
253
79.4k
        break;
254
115k
      case '\"':
255
115k
        r = jsmn_parse_string(parser, js, tokens, num_tokens);
256
115k
        if (r < 0) return r;
257
112k
        if (parser->toksuper != -1)
258
111k
          tokens[parser->toksuper].size++;
259
112k
        break;
260
31.9k
      case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ':
261
31.9k
        break;
262
#ifdef JSMN_STRICT
263
      /* In strict mode primitives are: numbers and booleans */
264
      case '-': case '0': case '1' : case '2': case '3' : case '4':
265
      case '5': case '6': case '7' : case '8': case '9':
266
      case 't': case 'f': case 'n' :
267
#else
268
      /* In non-strict mode every unquoted value is a primitive */
269
33.6k
      default:
270
33.6k
#endif
271
33.6k
        r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
272
33.6k
        if (r < 0) return r;
273
32.4k
        if (parser->toksuper != -1)
274
30.4k
          tokens[parser->toksuper].size++;
275
32.4k
        break;
276
277
#ifdef JSMN_STRICT
278
      /* Unexpected char in strict mode */
279
      default:
280
        return JSMN_ERROR_INVAL;
281
#endif
282
283
438k
    }
284
438k
  }
285
286
222k
  for (i = parser->toknext - 1; i >= 0; i--) {
287
    /* Unmatched opened object or array */
288
220k
    if (tokens[i].start != JSMN_POS_INVALID && tokens[i].end == JSMN_POS_INVALID) {
289
186
      return JSMN_ERROR_PART;
290
186
    }
291
220k
  }
292
293
1.97k
  return JSMN_SUCCESS;
294
2.16k
}
295
296
/**
297
 * Creates a new parser based over a given  buffer with an array of tokens
298
 * available.
299
 */
300
2.36k
void jsmn_init(jsmn_parser *parser) {
301
2.36k
  parser->pos = 0;
302
2.36k
  parser->end = 0;
303
2.36k
  parser->toknext = 0;
304
2.36k
  parser->toksuper = -1;
305
2.36k
}
306