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 "php_ini.h"
24 #include "ext/standard/info.h"
25 #include "ext/standard/html.h"
26 #include "zend_smart_str.h"
27 #include "php_json.h"
28 #include "php_json_encoder.h"
29 #include <zend_exceptions.h>
30 #include "zend_enum.h"
31
32 static const char digits[] = "0123456789abcdef";
33
php_json_determine_array_type(zval * val)34 static int php_json_determine_array_type(zval *val) /* {{{ */
35 {
36 zend_array *myht = Z_ARRVAL_P(val);
37
38 if (myht) {
39 return zend_array_is_list(myht) ? PHP_JSON_OUTPUT_ARRAY : PHP_JSON_OUTPUT_OBJECT;
40 }
41
42 return PHP_JSON_OUTPUT_ARRAY;
43 }
44 /* }}} */
45
46 /* {{{ Pretty printing support functions */
47
php_json_pretty_print_char(smart_str * buf,int options,char c)48 static inline void php_json_pretty_print_char(smart_str *buf, int options, char c) /* {{{ */
49 {
50 if (options & PHP_JSON_PRETTY_PRINT) {
51 smart_str_appendc(buf, c);
52 }
53 }
54 /* }}} */
55
php_json_pretty_print_indent(smart_str * buf,int options,php_json_encoder * encoder)56 static inline void php_json_pretty_print_indent(smart_str *buf, int options, php_json_encoder *encoder) /* {{{ */
57 {
58 int i;
59
60 if (options & PHP_JSON_PRETTY_PRINT) {
61 for (i = 0; i < encoder->depth; ++i) {
62 smart_str_appendl(buf, " ", 4);
63 }
64 }
65 }
66 /* }}} */
67
68 /* }}} */
69
php_json_is_valid_double(double d)70 static inline int php_json_is_valid_double(double d) /* {{{ */
71 {
72 return !zend_isinf(d) && !zend_isnan(d);
73 }
74 /* }}} */
75
php_json_encode_double(smart_str * buf,double d,int options)76 static inline void php_json_encode_double(smart_str *buf, double d, int options) /* {{{ */
77 {
78 size_t len;
79 char num[ZEND_DOUBLE_MAX_LENGTH];
80
81 zend_gcvt(d, (int)PG(serialize_precision), '.', 'e', num);
82 len = strlen(num);
83 if (options & PHP_JSON_PRESERVE_ZERO_FRACTION && strchr(num, '.') == NULL && len < ZEND_DOUBLE_MAX_LENGTH - 2) {
84 num[len++] = '.';
85 num[len++] = '0';
86 num[len] = '\0';
87 }
88 smart_str_appendl(buf, num, len);
89 }
90 /* }}} */
91
92 #define PHP_JSON_HASH_PROTECT_RECURSION(_tmp_ht) \
93 do { \
94 if (_tmp_ht) { \
95 GC_TRY_PROTECT_RECURSION(_tmp_ht); \
96 } \
97 } while (0)
98
99 #define PHP_JSON_HASH_UNPROTECT_RECURSION(_tmp_ht) \
100 do { \
101 if (_tmp_ht) { \
102 GC_TRY_UNPROTECT_RECURSION(_tmp_ht); \
103 } \
104 } while (0)
105
php_json_encode_array(smart_str * buf,zval * val,int options,php_json_encoder * encoder)106 static int php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
107 {
108 int i, r, need_comma = 0;
109 HashTable *myht, *prop_ht;
110
111 if (Z_TYPE_P(val) == IS_ARRAY) {
112 myht = Z_ARRVAL_P(val);
113 prop_ht = NULL;
114 r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val);
115 } else if (Z_OBJ_P(val)->properties == NULL
116 && Z_OBJ_HT_P(val)->get_properties_for == NULL
117 && Z_OBJ_HT_P(val)->get_properties == zend_std_get_properties) {
118 /* Optimized version without rebuilding properties HashTable */
119 zend_object *obj = Z_OBJ_P(val);
120 zend_class_entry *ce = obj->ce;
121 zend_property_info *prop_info;
122 zval *prop;
123 int i;
124
125 if (GC_IS_RECURSIVE(obj)) {
126 encoder->error_code = PHP_JSON_ERROR_RECURSION;
127 smart_str_appendl(buf, "null", 4);
128 return FAILURE;
129 }
130
131 PHP_JSON_HASH_PROTECT_RECURSION(obj);
132
133 smart_str_appendc(buf, '{');
134
135 ++encoder->depth;
136
137 for (i = 0; i < ce->default_properties_count; i++) {
138 prop_info = ce->properties_info_table[i];
139 if (!prop_info) {
140 continue;
141 }
142 if (ZSTR_VAL(prop_info->name)[0] == '\0' && ZSTR_LEN(prop_info->name) > 0) {
143 /* Skip protected and private members. */
144 continue;
145 }
146 prop = OBJ_PROP(obj, prop_info->offset);
147 if (Z_TYPE_P(prop) == IS_UNDEF) {
148 continue;
149 }
150
151 if (need_comma) {
152 smart_str_appendc(buf, ',');
153 } else {
154 need_comma = 1;
155 }
156
157 php_json_pretty_print_char(buf, options, '\n');
158 php_json_pretty_print_indent(buf, options, encoder);
159
160 if (php_json_escape_string(buf, ZSTR_VAL(prop_info->name), ZSTR_LEN(prop_info->name),
161 options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE &&
162 (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) &&
163 buf->s) {
164 ZSTR_LEN(buf->s) -= 4;
165 smart_str_appendl(buf, "\"\"", 2);
166 }
167
168 smart_str_appendc(buf, ':');
169 php_json_pretty_print_char(buf, options, ' ');
170
171 if (php_json_encode_zval(buf, prop, options, encoder) == FAILURE &&
172 !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
173 PHP_JSON_HASH_UNPROTECT_RECURSION(obj);
174 return FAILURE;
175 }
176 }
177
178 PHP_JSON_HASH_UNPROTECT_RECURSION(obj);
179 if (encoder->depth > encoder->max_depth) {
180 encoder->error_code = PHP_JSON_ERROR_DEPTH;
181 if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
182 return FAILURE;
183 }
184 }
185 --encoder->depth;
186
187 if (need_comma) {
188 php_json_pretty_print_char(buf, options, '\n');
189 php_json_pretty_print_indent(buf, options, encoder);
190 }
191 smart_str_appendc(buf, '}');
192 return SUCCESS;
193 } else {
194 prop_ht = myht = zend_get_properties_for(val, ZEND_PROP_PURPOSE_JSON);
195 r = PHP_JSON_OUTPUT_OBJECT;
196 }
197
198 if (myht && GC_IS_RECURSIVE(myht)) {
199 encoder->error_code = PHP_JSON_ERROR_RECURSION;
200 smart_str_appendl(buf, "null", 4);
201 zend_release_properties(prop_ht);
202 return FAILURE;
203 }
204
205 PHP_JSON_HASH_PROTECT_RECURSION(myht);
206
207 if (r == PHP_JSON_OUTPUT_ARRAY) {
208 smart_str_appendc(buf, '[');
209 } else {
210 smart_str_appendc(buf, '{');
211 }
212
213 ++encoder->depth;
214
215 i = myht ? zend_hash_num_elements(myht) : 0;
216
217 if (i > 0) {
218 zend_string *key;
219 zval *data;
220 zend_ulong index;
221
222 ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) {
223 if (r == PHP_JSON_OUTPUT_ARRAY) {
224 if (need_comma) {
225 smart_str_appendc(buf, ',');
226 } else {
227 need_comma = 1;
228 }
229
230 php_json_pretty_print_char(buf, options, '\n');
231 php_json_pretty_print_indent(buf, options, encoder);
232 } else if (r == PHP_JSON_OUTPUT_OBJECT) {
233 if (key) {
234 if (ZSTR_VAL(key)[0] == '\0' && ZSTR_LEN(key) > 0 && Z_TYPE_P(val) == IS_OBJECT) {
235 /* Skip protected and private members. */
236 continue;
237 }
238
239 if (need_comma) {
240 smart_str_appendc(buf, ',');
241 } else {
242 need_comma = 1;
243 }
244
245 php_json_pretty_print_char(buf, options, '\n');
246 php_json_pretty_print_indent(buf, options, encoder);
247
248 if (php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key),
249 options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE &&
250 (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) &&
251 buf->s) {
252 ZSTR_LEN(buf->s) -= 4;
253 smart_str_appendl(buf, "\"\"", 2);
254 }
255 } else {
256 if (need_comma) {
257 smart_str_appendc(buf, ',');
258 } else {
259 need_comma = 1;
260 }
261
262 php_json_pretty_print_char(buf, options, '\n');
263 php_json_pretty_print_indent(buf, options, encoder);
264
265 smart_str_appendc(buf, '"');
266 smart_str_append_long(buf, (zend_long) index);
267 smart_str_appendc(buf, '"');
268 }
269
270 smart_str_appendc(buf, ':');
271 php_json_pretty_print_char(buf, options, ' ');
272 }
273
274 if (php_json_encode_zval(buf, data, options, encoder) == FAILURE &&
275 !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
276 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
277 zend_release_properties(prop_ht);
278 return FAILURE;
279 }
280 } ZEND_HASH_FOREACH_END();
281 }
282
283 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
284
285 if (encoder->depth > encoder->max_depth) {
286 encoder->error_code = PHP_JSON_ERROR_DEPTH;
287 if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
288 zend_release_properties(prop_ht);
289 return FAILURE;
290 }
291 }
292 --encoder->depth;
293
294 /* Only keep closing bracket on same line for empty arrays/objects */
295 if (need_comma) {
296 php_json_pretty_print_char(buf, options, '\n');
297 php_json_pretty_print_indent(buf, options, encoder);
298 }
299
300 if (r == PHP_JSON_OUTPUT_ARRAY) {
301 smart_str_appendc(buf, ']');
302 } else {
303 smart_str_appendc(buf, '}');
304 }
305
306 zend_release_properties(prop_ht);
307 return SUCCESS;
308 }
309 /* }}} */
310
php_json_escape_string(smart_str * buf,const char * s,size_t len,int options,php_json_encoder * encoder)311 int php_json_escape_string(
312 smart_str *buf, const char *s, size_t len,
313 int options, php_json_encoder *encoder) /* {{{ */
314 {
315 int status;
316 unsigned int us;
317 size_t pos, checkpoint;
318 char *dst;
319
320 if (len == 0) {
321 smart_str_appendl(buf, "\"\"", 2);
322 return SUCCESS;
323 }
324
325 if (options & PHP_JSON_NUMERIC_CHECK) {
326 double d;
327 int type;
328 zend_long p;
329
330 if ((type = is_numeric_string(s, len, &p, &d, 0)) != 0) {
331 if (type == IS_LONG) {
332 smart_str_append_long(buf, p);
333 return SUCCESS;
334 } else if (type == IS_DOUBLE && php_json_is_valid_double(d)) {
335 php_json_encode_double(buf, d, options);
336 return SUCCESS;
337 }
338 }
339
340 }
341 checkpoint = buf->s ? ZSTR_LEN(buf->s) : 0;
342
343 /* pre-allocate for string length plus 2 quotes */
344 smart_str_alloc(buf, len+2, 0);
345 smart_str_appendc(buf, '"');
346
347 pos = 0;
348
349 do {
350 static const uint32_t charmap[8] = {
351 0xffffffff, 0x500080c4, 0x10000000, 0x00000000,
352 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff};
353
354 us = (unsigned char)s[pos];
355 if (EXPECTED(!ZEND_BIT_TEST(charmap, us))) {
356 pos++;
357 len--;
358 if (len == 0) {
359 smart_str_appendl(buf, s, pos);
360 break;
361 }
362 } else {
363 if (pos) {
364 smart_str_appendl(buf, s, pos);
365 s += pos;
366 pos = 0;
367 }
368 us = (unsigned char)s[0];
369 if (UNEXPECTED(us >= 0x80)) {
370
371 us = php_next_utf8_char((unsigned char *)s, len, &pos, &status);
372
373 /* check whether UTF8 character is correct */
374 if (UNEXPECTED(status != SUCCESS)) {
375 if (options & PHP_JSON_INVALID_UTF8_IGNORE) {
376 /* ignore invalid UTF8 character */
377 } else if (options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) {
378 /* Use Unicode character 'REPLACEMENT CHARACTER' (U+FFFD) */
379 if (options & PHP_JSON_UNESCAPED_UNICODE) {
380 smart_str_appendl(buf, "\xef\xbf\xbd", 3);
381 } else {
382 smart_str_appendl(buf, "\\ufffd", 6);
383 }
384 } else {
385 ZSTR_LEN(buf->s) = checkpoint;
386 encoder->error_code = PHP_JSON_ERROR_UTF8;
387 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
388 smart_str_appendl(buf, "null", 4);
389 }
390 return FAILURE;
391 }
392
393 /* Escape U+2028/U+2029 line terminators, UNLESS both
394 JSON_UNESCAPED_UNICODE and
395 JSON_UNESCAPED_LINE_TERMINATORS were provided */
396 } else if ((options & PHP_JSON_UNESCAPED_UNICODE)
397 && ((options & PHP_JSON_UNESCAPED_LINE_TERMINATORS)
398 || us < 0x2028 || us > 0x2029)) {
399 smart_str_appendl(buf, s, pos);
400 } else {
401 /* From http://en.wikipedia.org/wiki/UTF16 */
402 if (us >= 0x10000) {
403 unsigned int next_us;
404
405 us -= 0x10000;
406 next_us = (unsigned short)((us & 0x3ff) | 0xdc00);
407 us = (unsigned short)((us >> 10) | 0xd800);
408 dst = smart_str_extend(buf, 6);
409 dst[0] = '\\';
410 dst[1] = 'u';
411 dst[2] = digits[(us >> 12) & 0xf];
412 dst[3] = digits[(us >> 8) & 0xf];
413 dst[4] = digits[(us >> 4) & 0xf];
414 dst[5] = digits[us & 0xf];
415 us = next_us;
416 }
417 dst = smart_str_extend(buf, 6);
418 dst[0] = '\\';
419 dst[1] = 'u';
420 dst[2] = digits[(us >> 12) & 0xf];
421 dst[3] = digits[(us >> 8) & 0xf];
422 dst[4] = digits[(us >> 4) & 0xf];
423 dst[5] = digits[us & 0xf];
424 }
425 s += pos;
426 len -= pos;
427 pos = 0;
428 } else {
429 s++;
430 switch (us) {
431 case '"':
432 if (options & PHP_JSON_HEX_QUOT) {
433 smart_str_appendl(buf, "\\u0022", 6);
434 } else {
435 smart_str_appendl(buf, "\\\"", 2);
436 }
437 break;
438
439 case '\\':
440 smart_str_appendl(buf, "\\\\", 2);
441 break;
442
443 case '/':
444 if (options & PHP_JSON_UNESCAPED_SLASHES) {
445 smart_str_appendc(buf, '/');
446 } else {
447 smart_str_appendl(buf, "\\/", 2);
448 }
449 break;
450
451 case '\b':
452 smart_str_appendl(buf, "\\b", 2);
453 break;
454
455 case '\f':
456 smart_str_appendl(buf, "\\f", 2);
457 break;
458
459 case '\n':
460 smart_str_appendl(buf, "\\n", 2);
461 break;
462
463 case '\r':
464 smart_str_appendl(buf, "\\r", 2);
465 break;
466
467 case '\t':
468 smart_str_appendl(buf, "\\t", 2);
469 break;
470
471 case '<':
472 if (options & PHP_JSON_HEX_TAG) {
473 smart_str_appendl(buf, "\\u003C", 6);
474 } else {
475 smart_str_appendc(buf, '<');
476 }
477 break;
478
479 case '>':
480 if (options & PHP_JSON_HEX_TAG) {
481 smart_str_appendl(buf, "\\u003E", 6);
482 } else {
483 smart_str_appendc(buf, '>');
484 }
485 break;
486
487 case '&':
488 if (options & PHP_JSON_HEX_AMP) {
489 smart_str_appendl(buf, "\\u0026", 6);
490 } else {
491 smart_str_appendc(buf, '&');
492 }
493 break;
494
495 case '\'':
496 if (options & PHP_JSON_HEX_APOS) {
497 smart_str_appendl(buf, "\\u0027", 6);
498 } else {
499 smart_str_appendc(buf, '\'');
500 }
501 break;
502
503 default:
504 ZEND_ASSERT(us < ' ');
505 dst = smart_str_extend(buf, 6);
506 dst[0] = '\\';
507 dst[1] = 'u';
508 dst[2] = '0';
509 dst[3] = '0';
510 dst[4] = digits[(us >> 4) & 0xf];
511 dst[5] = digits[us & 0xf];
512 break;
513 }
514 len--;
515 }
516 }
517 } while (len);
518
519 smart_str_appendc(buf, '"');
520
521 return SUCCESS;
522 }
523 /* }}} */
524
php_json_encode_serializable_object(smart_str * buf,zval * val,int options,php_json_encoder * encoder)525 static int php_json_encode_serializable_object(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
526 {
527 zend_class_entry *ce = Z_OBJCE_P(val);
528 HashTable* myht = Z_OBJPROP_P(val);
529 zval retval, fname;
530 int return_code;
531
532 if (myht && GC_IS_RECURSIVE(myht)) {
533 encoder->error_code = PHP_JSON_ERROR_RECURSION;
534 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
535 smart_str_appendl(buf, "null", 4);
536 }
537 return FAILURE;
538 }
539
540 PHP_JSON_HASH_PROTECT_RECURSION(myht);
541
542 ZVAL_STRING(&fname, "jsonSerialize");
543
544 if (FAILURE == call_user_function(NULL, val, &fname, &retval, 0, NULL) || Z_TYPE(retval) == IS_UNDEF) {
545 if (!EG(exception)) {
546 zend_throw_exception_ex(NULL, 0, "Failed calling %s::jsonSerialize()", ZSTR_VAL(ce->name));
547 }
548 zval_ptr_dtor(&fname);
549
550 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
551 smart_str_appendl(buf, "null", 4);
552 }
553 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
554 return FAILURE;
555 }
556
557 if (EG(exception)) {
558 /* Error already raised */
559 zval_ptr_dtor(&retval);
560 zval_ptr_dtor(&fname);
561
562 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
563 smart_str_appendl(buf, "null", 4);
564 }
565 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
566 return FAILURE;
567 }
568
569 if ((Z_TYPE(retval) == IS_OBJECT) &&
570 (Z_OBJ(retval) == Z_OBJ_P(val))) {
571 /* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
572 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
573 return_code = php_json_encode_array(buf, &retval, options, encoder);
574 } else {
575 /* All other types, encode as normal */
576 return_code = php_json_encode_zval(buf, &retval, options, encoder);
577 PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
578 }
579
580 zval_ptr_dtor(&retval);
581 zval_ptr_dtor(&fname);
582
583 return return_code;
584 }
585 /* }}} */
586
php_json_encode_serializable_enum(smart_str * buf,zval * val,int options,php_json_encoder * encoder)587 static int php_json_encode_serializable_enum(smart_str *buf, zval *val, int options, php_json_encoder *encoder)
588 {
589 zend_class_entry *ce = Z_OBJCE_P(val);
590 if (ce->enum_backing_type == IS_UNDEF) {
591 encoder->error_code = PHP_JSON_ERROR_NON_BACKED_ENUM;
592 smart_str_appendc(buf, '0');
593 return FAILURE;
594 }
595
596 zval *value_zv = zend_enum_fetch_case_value(Z_OBJ_P(val));
597 return php_json_encode_zval(buf, value_zv, options, encoder);
598 }
599
php_json_encode_zval(smart_str * buf,zval * val,int options,php_json_encoder * encoder)600 int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
601 {
602 again:
603 switch (Z_TYPE_P(val))
604 {
605 case IS_NULL:
606 smart_str_appendl(buf, "null", 4);
607 break;
608
609 case IS_TRUE:
610 smart_str_appendl(buf, "true", 4);
611 break;
612 case IS_FALSE:
613 smart_str_appendl(buf, "false", 5);
614 break;
615
616 case IS_LONG:
617 smart_str_append_long(buf, Z_LVAL_P(val));
618 break;
619
620 case IS_DOUBLE:
621 if (php_json_is_valid_double(Z_DVAL_P(val))) {
622 php_json_encode_double(buf, Z_DVAL_P(val), options);
623 } else {
624 encoder->error_code = PHP_JSON_ERROR_INF_OR_NAN;
625 smart_str_appendc(buf, '0');
626 }
627 break;
628
629 case IS_STRING:
630 return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options, encoder);
631
632 case IS_OBJECT:
633 if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
634 return php_json_encode_serializable_object(buf, val, options, encoder);
635 }
636 if (Z_OBJCE_P(val)->ce_flags & ZEND_ACC_ENUM) {
637 return php_json_encode_serializable_enum(buf, val, options, encoder);
638 }
639 /* fallthrough -- Non-serializable object */
640 ZEND_FALLTHROUGH;
641 case IS_ARRAY: {
642 /* Avoid modifications (and potential freeing) of the array through a reference when a
643 * jsonSerialize() method is invoked. */
644 zval zv;
645 int res;
646 ZVAL_COPY(&zv, val);
647 res = php_json_encode_array(buf, &zv, options, encoder);
648 zval_ptr_dtor_nogc(&zv);
649 return res;
650 }
651
652 case IS_REFERENCE:
653 val = Z_REFVAL_P(val);
654 goto again;
655
656 default:
657 encoder->error_code = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
658 if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
659 smart_str_appendl(buf, "null", 4);
660 }
661 return FAILURE;
662 }
663
664 return SUCCESS;
665 }
666 /* }}} */
667