Coverage Report

Created: 2026-01-18 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/json/json.c
Line
Count
Source
1
/*
2
  +----------------------------------------------------------------------+
3
  | Copyright (c) The PHP Group                                          |
4
  +----------------------------------------------------------------------+
5
  | This source file is subject to version 3.01 of the PHP license,      |
6
  | that is bundled with this package in the file LICENSE, and is        |
7
  | available through the world-wide-web at the following url:           |
8
  | https://www.php.net/license/3_01.txt                                 |
9
  | If you did not receive a copy of the PHP license and are unable to   |
10
  | obtain it through the world-wide-web, please send a note to          |
11
  | license@php.net so we can mail you a copy immediately.               |
12
  +----------------------------------------------------------------------+
13
  | Author: Omar Kilani <omar@php.net>                                   |
14
  |         Jakub Zelenka <bukka@php.net>                                |
15
  +----------------------------------------------------------------------+
16
*/
17
18
#ifdef HAVE_CONFIG_H
19
#include <config.h>
20
#endif
21
22
#include "php.h"
23
#include "ext/standard/info.h"
24
#include "zend_smart_str.h"
25
#include "php_json.h"
26
#include "php_json_encoder.h"
27
#include "php_json_parser.h"
28
#include "json_arginfo.h"
29
#include <zend_exceptions.h>
30
31
static PHP_MINFO_FUNCTION(json);
32
33
PHP_JSON_API zend_class_entry *php_json_serializable_ce;
34
PHP_JSON_API zend_class_entry *php_json_exception_ce;
35
36
PHP_JSON_API ZEND_DECLARE_MODULE_GLOBALS(json)
37
38
static int php_json_implement_json_serializable(zend_class_entry *interface, zend_class_entry *class_type)
39
2
{
40
2
  class_type->ce_flags |= ZEND_ACC_USE_GUARDS;
41
2
  return SUCCESS;
42
2
}
43
44
/* {{{ MINIT */
45
static PHP_MINIT_FUNCTION(json)
46
2
{
47
2
  php_json_serializable_ce = register_class_JsonSerializable();
48
2
  php_json_serializable_ce->interface_gets_implemented = php_json_implement_json_serializable;
49
50
2
  php_json_exception_ce = register_class_JsonException(zend_ce_exception);
51
52
2
  register_json_symbols(module_number);
53
54
2
  return SUCCESS;
55
2
}
56
/* }}} */
57
58
/* {{{ PHP_GINIT_FUNCTION */
59
static PHP_GINIT_FUNCTION(json)
60
2
{
61
#if defined(COMPILE_DL_JSON) && defined(ZTS)
62
  ZEND_TSRMLS_CACHE_UPDATE();
63
#endif
64
2
  json_globals->encoder_depth = 0;
65
2
  json_globals->error_code = 0;
66
2
  json_globals->error_line = 0;
67
2
  json_globals->error_column = 0;
68
2
  json_globals->encode_max_depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
69
2
}
70
/* }}} */
71
72
static PHP_RINIT_FUNCTION(json)
73
57.6k
{
74
57.6k
  JSON_G(error_code) = 0;
75
57.6k
  JSON_G(error_line) = 0;
76
57.6k
  JSON_G(error_column) = 0;
77
57.6k
  return SUCCESS;
78
57.6k
}
79
80
/* {{{ json_module_entry */
81
zend_module_entry json_module_entry = {
82
  STANDARD_MODULE_HEADER,
83
  "json",
84
  ext_functions,
85
  PHP_MINIT(json),
86
  NULL,
87
  PHP_RINIT(json),
88
  NULL,
89
  PHP_MINFO(json),
90
  PHP_JSON_VERSION,
91
  PHP_MODULE_GLOBALS(json),
92
  PHP_GINIT(json),
93
  NULL,
94
  NULL,
95
  STANDARD_MODULE_PROPERTIES_EX
96
};
97
/* }}} */
98
99
#ifdef COMPILE_DL_JSON
100
#ifdef ZTS
101
ZEND_TSRMLS_CACHE_DEFINE()
102
#endif
103
ZEND_GET_MODULE(json)
104
#endif
105
106
/* {{{ PHP_MINFO_FUNCTION */
107
static PHP_MINFO_FUNCTION(json)
108
0
{
109
0
  php_info_print_table_start();
110
0
  php_info_print_table_row(2, "json support", "enabled");
111
0
  php_info_print_table_end();
112
0
}
113
/* }}} */
114
115
PHP_JSON_API zend_string *php_json_encode_string(const char *s, size_t len, int options)
116
0
{
117
0
  smart_str buf = {0};
118
0
  php_json_encoder encoder;
119
120
0
  php_json_encode_init(&encoder);
121
122
0
  if (php_json_escape_string(&buf, s, len, options, &encoder) == FAILURE) {
123
0
    smart_str_free(&buf);
124
0
    return NULL;
125
0
  }
126
127
0
  return smart_str_extract(&buf);
128
0
}
129
130
PHP_JSON_API zend_result php_json_encode_ex(smart_str *buf, zval *val, int options, zend_long depth) /* {{{ */
131
0
{
132
0
  php_json_encoder encoder;
133
0
  zend_result return_code;
134
135
0
  php_json_encode_init(&encoder);
136
0
  encoder.max_depth = depth;
137
138
0
  return_code = php_json_encode_zval(buf, val, options, &encoder);
139
0
  JSON_G(error_code) = encoder.error_code;
140
141
0
  return return_code;
142
0
}
143
/* }}} */
144
145
PHP_JSON_API zend_result php_json_encode(smart_str *buf, zval *val, int options) /* {{{ */
146
0
{
147
0
  return php_json_encode_ex(buf, val, options, JSON_G(encode_max_depth));
148
0
}
149
/* }}} */
150
151
static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ */
152
0
{
153
0
  switch(error_code) {
154
0
    case PHP_JSON_ERROR_NONE:
155
0
      return "No error";
156
0
    case PHP_JSON_ERROR_DEPTH:
157
0
      return "Maximum stack depth exceeded";
158
0
    case PHP_JSON_ERROR_STATE_MISMATCH:
159
0
      return "State mismatch (invalid or malformed JSON)";
160
0
    case PHP_JSON_ERROR_CTRL_CHAR:
161
0
      return "Control character error, possibly incorrectly encoded";
162
0
    case PHP_JSON_ERROR_SYNTAX:
163
0
      return "Syntax error";
164
0
    case PHP_JSON_ERROR_UTF8:
165
0
      return "Malformed UTF-8 characters, possibly incorrectly encoded";
166
0
    case PHP_JSON_ERROR_RECURSION:
167
0
      return "Recursion detected";
168
0
    case PHP_JSON_ERROR_INF_OR_NAN:
169
0
      return "Inf and NaN cannot be JSON encoded";
170
0
    case PHP_JSON_ERROR_UNSUPPORTED_TYPE:
171
0
      return "Type is not supported";
172
0
    case PHP_JSON_ERROR_INVALID_PROPERTY_NAME:
173
0
      return "The decoded property name is invalid";
174
0
    case PHP_JSON_ERROR_UTF16:
175
0
      return "Single unpaired UTF-16 surrogate in unicode escape";
176
0
    case PHP_JSON_ERROR_NON_BACKED_ENUM:
177
0
      return "Non-backed enums have no default serialization";
178
0
    default:
179
0
      return "Unknown error";
180
0
  }
181
0
}
182
/* }}} */
183
184
static zend_string *php_json_get_error_msg_with_location(php_json_error_code error_code, size_t line, size_t column) /* {{{ */
185
0
{
186
0
  const char *base_msg = php_json_get_error_msg(error_code);
187
  
188
0
  if (line > 0 && column > 0) {
189
0
    return zend_strpprintf(0, "%s near location %zu:%zu", base_msg, line, column);
190
0
  }
191
  
192
0
  return zend_string_init(base_msg, strlen(base_msg), 0);
193
0
}
194
/* }}} */
195
196
PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth) /* {{{ */
197
0
{
198
0
  php_json_parser parser;
199
200
0
  php_json_parser_init(&parser, return_value, str, str_len, (int)options, (int)depth);
201
202
0
  if (php_json_yyparse(&parser)) {
203
0
    php_json_error_code error_code = php_json_parser_error_code(&parser);
204
0
    size_t error_line = php_json_parser_error_line(&parser);
205
0
    size_t error_column = php_json_parser_error_column(&parser);
206
207
0
    if (!(options & PHP_JSON_THROW_ON_ERROR)) {
208
0
      JSON_G(error_code) = error_code;
209
0
      JSON_G(error_line) = error_line;
210
0
      JSON_G(error_column) = error_column;
211
0
    } else {
212
0
      zend_string *error_msg = php_json_get_error_msg_with_location(error_code, error_line, error_column);
213
0
      zend_throw_exception(php_json_exception_ce, ZSTR_VAL(error_msg), error_code);
214
0
      zend_string_release(error_msg);
215
0
    }
216
0
    RETVAL_NULL();
217
0
    return FAILURE;
218
0
  }
219
220
0
  return SUCCESS;
221
0
}
222
/* }}} */
223
224
/* {{{ */
225
PHP_JSON_API bool php_json_validate_ex(const char *str, size_t str_len, zend_long options, zend_long depth)
226
0
{
227
0
  php_json_parser parser;
228
0
  zval tmp;
229
0
  const php_json_parser_methods* parser_validate_methods = php_json_get_validate_methods();
230
0
  php_json_parser_init_ex(&parser, &tmp, str, str_len, (int)options, (int)depth, parser_validate_methods);
231
232
0
  if (php_json_yyparse(&parser)) {
233
0
    php_json_error_code error_code = php_json_parser_error_code(&parser);
234
0
    size_t error_line = php_json_parser_error_line(&parser);
235
0
    size_t error_column = php_json_parser_error_column(&parser);
236
237
0
    JSON_G(error_code) = error_code;
238
0
    JSON_G(error_line) = error_line;
239
0
    JSON_G(error_column) = error_column;
240
0
    return false;
241
0
  }
242
243
0
  return true;
244
0
}
245
/* }}} */
246
247
/* {{{ Returns the JSON representation of a value */
248
PHP_FUNCTION(json_encode)
249
0
{
250
0
  zval *parameter;
251
0
  php_json_encoder encoder;
252
0
  smart_str buf = {0};
253
0
  zend_long options = 0;
254
0
  zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
255
256
0
  ZEND_PARSE_PARAMETERS_START(1, 3)
257
0
    Z_PARAM_ZVAL(parameter)
258
0
    Z_PARAM_OPTIONAL
259
0
    Z_PARAM_LONG(options)
260
0
    Z_PARAM_LONG(depth)
261
0
  ZEND_PARSE_PARAMETERS_END();
262
263
0
  php_json_encode_init(&encoder);
264
0
  encoder.max_depth = (int)depth;
265
0
  php_json_encode_zval(&buf, parameter, (int)options, &encoder);
266
267
0
  if (!(options & PHP_JSON_THROW_ON_ERROR) || (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
268
0
    JSON_G(error_code) = encoder.error_code;
269
0
    if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
270
0
      smart_str_free(&buf);
271
0
      RETURN_FALSE;
272
0
    }
273
0
  } else {
274
0
    if (encoder.error_code != PHP_JSON_ERROR_NONE) {
275
0
      smart_str_free(&buf);
276
0
      zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(encoder.error_code), encoder.error_code);
277
0
      RETURN_THROWS();
278
0
    }
279
0
  }
280
281
0
  RETURN_STR(smart_str_extract(&buf));
282
0
}
283
/* }}} */
284
285
/* {{{ Decodes the JSON representation into a PHP value */
286
PHP_FUNCTION(json_decode)
287
0
{
288
0
  char *str;
289
0
  size_t str_len;
290
0
  bool assoc = 0; /* return JS objects as PHP objects by default */
291
0
  bool assoc_null = 1;
292
0
  zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
293
0
  zend_long options = 0;
294
295
0
  ZEND_PARSE_PARAMETERS_START(1, 4)
296
0
    Z_PARAM_STRING(str, str_len)
297
0
    Z_PARAM_OPTIONAL
298
0
    Z_PARAM_BOOL_OR_NULL(assoc, assoc_null)
299
0
    Z_PARAM_LONG(depth)
300
0
    Z_PARAM_LONG(options)
301
0
  ZEND_PARSE_PARAMETERS_END();
302
303
0
  if (!(options & PHP_JSON_THROW_ON_ERROR)) {
304
0
    JSON_G(error_code) = PHP_JSON_ERROR_NONE;
305
0
    JSON_G(error_line) = 0;
306
0
    JSON_G(error_column) = 0;
307
0
  }
308
309
0
  if (!str_len) {
310
0
    if (!(options & PHP_JSON_THROW_ON_ERROR)) {
311
0
      JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX;
312
0
      JSON_G(error_line) = 0;
313
0
      JSON_G(error_column) = 0;
314
0
    } else {
315
0
      zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX);
316
0
    }
317
0
    RETURN_NULL();
318
0
  }
319
320
0
  if (depth <= 0) {
321
0
    zend_argument_value_error(3, "must be greater than 0");
322
0
    RETURN_THROWS();
323
0
  }
324
325
0
  if (depth > INT_MAX) {
326
0
    zend_argument_value_error(3, "must be less than %d", INT_MAX);
327
0
    RETURN_THROWS();
328
0
  }
329
330
  /* For BC reasons, the bool $assoc overrides the long $options bit for PHP_JSON_OBJECT_AS_ARRAY */
331
0
  if (!assoc_null) {
332
0
    if (assoc) {
333
0
      options |=  PHP_JSON_OBJECT_AS_ARRAY;
334
0
    } else {
335
0
      options &= ~PHP_JSON_OBJECT_AS_ARRAY;
336
0
    }
337
0
  }
338
339
0
  php_json_decode_ex(return_value, str, str_len, options, depth);
340
0
}
341
/* }}} */
342
343
/* {{{ Validates if a string contains a valid json */
344
PHP_FUNCTION(json_validate)
345
0
{
346
0
  char *str;
347
0
  size_t str_len;
348
0
  zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
349
0
  zend_long options = 0;
350
351
0
  ZEND_PARSE_PARAMETERS_START(1, 3)
352
0
    Z_PARAM_STRING(str, str_len)
353
0
    Z_PARAM_OPTIONAL
354
0
    Z_PARAM_LONG(depth)
355
0
    Z_PARAM_LONG(options)
356
0
  ZEND_PARSE_PARAMETERS_END();
357
358
359
0
  if ((options != 0) && (options != PHP_JSON_INVALID_UTF8_IGNORE)) {
360
0
    zend_argument_value_error(3, "must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE)");
361
0
    RETURN_THROWS();
362
0
  }
363
364
0
  if (!str_len) {
365
0
    JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX;
366
0
    JSON_G(error_line) = 0;
367
0
    JSON_G(error_column) = 0;
368
0
    RETURN_FALSE;
369
0
  }
370
371
0
  JSON_G(error_code) = PHP_JSON_ERROR_NONE;
372
0
  JSON_G(error_line) = 0;
373
0
  JSON_G(error_column) = 0;
374
375
0
  if (depth <= 0) {
376
0
    zend_argument_value_error(2, "must be greater than 0");
377
0
    RETURN_THROWS();
378
0
  }
379
380
0
  if (depth > INT_MAX) {
381
0
    zend_argument_value_error(2, "must be less than %d", INT_MAX);
382
0
    RETURN_THROWS();
383
0
  }
384
385
0
  RETURN_BOOL(php_json_validate_ex(str, str_len, options, depth));
386
0
}
387
/* }}} */
388
389
/* {{{ Returns the error code of the last json_encode() or json_decode() call. */
390
PHP_FUNCTION(json_last_error)
391
0
{
392
0
  ZEND_PARSE_PARAMETERS_NONE();
393
394
0
  RETURN_LONG(JSON_G(error_code));
395
0
}
396
/* }}} */
397
398
/* {{{ Returns the error string of the last json_encode() or json_decode() call. */
399
PHP_FUNCTION(json_last_error_msg)
400
0
{
401
0
  ZEND_PARSE_PARAMETERS_NONE();
402
403
0
  RETVAL_STR(php_json_get_error_msg_with_location(
404
0
    JSON_G(error_code),
405
0
    JSON_G(error_line),
406
0
    JSON_G(error_column)
407
0
  ));
408
0
}
409
/* }}} */