1 /**
2 * YAML parser and emitter PHP extension
3 *
4 * Copyright (c) 2007 Ryusuke SEKIYAMA. All rights reserved.
5 * Copyright (c) 2009 Keynetics Inc. All rights reserved.
6 * Copyright (c) 2015 Bryan Davis and contributors. All rights reserved.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included
16 * in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 * @package php_yaml
27 * @author Ryusuke SEKIYAMA <rsky0711@gmail.com>
28 * @author Bryan Davis <bd808@bd808.com>
29 * @copyright 2007 Ryusuke SEKIYAMA
30 * @copyright 2009 Keynetics Inc
31 * @copyright 2015 Bryan Davis and contributors
32 * @license http://www.opensource.org/licenses/mit-license.php MIT License
33 */
34
35
36 #include "php_yaml.h"
37 #include "php_yaml_int.h"
38
39 /* {{{ local macros
40 */
41 #define y_event_init_failed(e) \
42 yaml_event_delete(e); \
43 php_error_docref(NULL, E_WARNING,\
44 "Memory error: Not enough memory for creating an event (libyaml)")
45
46 #define Y_ARRAY_SEQUENCE 1
47 #define Y_ARRAY_MAP 2
48
49 /* }}} */
50
51 /* {{{ local prototypes
52 */
53 static int y_event_emit(
54 const y_emit_state_t *state, yaml_event_t *event);
55 static void y_handle_emitter_error(const y_emit_state_t *state);
56 static int y_array_is_sequence(HashTable *ht);
57 static void y_scan_recursion(const y_emit_state_t *state, zval *data);
58 static zend_long y_search_recursive(
59 const y_emit_state_t *state, const zend_ulong addr);
60
61 static int y_write_zval(
62 const y_emit_state_t *state, zval *data, yaml_char_t *tag);
63 static int y_write_null(
64 const y_emit_state_t *state, yaml_char_t *tag);
65 static int y_write_bool(
66 const y_emit_state_t *state, zval *data, yaml_char_t *tag);
67 static int y_write_long(
68 const y_emit_state_t *state, zval *data, yaml_char_t *tag);
69 static int y_write_double(
70 const y_emit_state_t *state, zval *data, yaml_char_t *tag);
71 static int y_write_string(
72 const y_emit_state_t *state, zval *data, yaml_char_t *tag);
73 static int y_write_array(
74 const y_emit_state_t *state, zval *data, yaml_char_t *tag);
75 static int y_write_timestamp(
76 const y_emit_state_t *state, zval *data, yaml_char_t *tag);
77 static int y_write_object(
78 const y_emit_state_t *state, zval *data, yaml_char_t *tag);
79 static int y_write_object_callback (
80 const y_emit_state_t *state, zval *callback, zval *data,
81 const char *clazz_name);
82 static inline unsigned int get_next_char(
83 const unsigned char *str, size_t str_len, size_t *cursor, int *status);
84 /* }}} */
85
86
87 /* {{{ y_event_emit()
88 * send an event to the emitter
89 */
90 static int
y_event_emit(const y_emit_state_t * state,yaml_event_t * event)91 y_event_emit(const y_emit_state_t *state, yaml_event_t *event)
92 {
93 if (!yaml_emitter_emit(state->emitter, event)) {
94 yaml_event_delete(event);
95 y_handle_emitter_error(state);
96 return FAILURE;
97
98 } else {
99 return SUCCESS;
100 }
101 }
102 /* }}} */
103
104
105 /* {{{ y_handle_emitter_error()
106 * Emit a warning about an emitter error
107 */
y_handle_emitter_error(const y_emit_state_t * state)108 static void y_handle_emitter_error(const y_emit_state_t *state)
109 {
110 switch (state->emitter->error) {
111 case YAML_MEMORY_ERROR:
112 php_error_docref(NULL, E_WARNING,
113 "Memory error: Not enough memory for emitting");
114 break;
115
116 case YAML_WRITER_ERROR:
117 php_error_docref(NULL, E_WARNING,
118 "Writer error: %s", state->emitter->problem);
119 break;
120
121 case YAML_EMITTER_ERROR:
122 php_error_docref(NULL, E_WARNING,
123 "Emitter error: %s", state->emitter->problem);
124 break;
125
126 default:
127 php_error_docref(NULL, E_WARNING, "Internal error");
128 break;
129 }
130 }
131 /* }}} */
132
133
134 /* {{{ y_array_is_sequence()
135 * Does the array encode a sequence?
136 */
y_array_is_sequence(HashTable * ht)137 static int y_array_is_sequence(HashTable *ht)
138 {
139 zend_ulong kidx, idx;
140 zend_string *str_key;
141
142 idx = 0;
143 ZEND_HASH_FOREACH_KEY(ht, kidx, str_key) {
144 if (str_key) {
145 /* non-numeric key found */
146 return Y_ARRAY_MAP;
147 } else if (kidx != idx) {
148 /* gap in sequence found */
149 return Y_ARRAY_MAP;
150 }
151
152 idx++;
153 } ZEND_HASH_FOREACH_END();
154 return Y_ARRAY_SEQUENCE;
155 }
156 /* }}} */
157
158
159 /* {{{ y_scan_recursion()
160 * walk an object graph looking for recursive references
161 */
y_scan_recursion(const y_emit_state_t * state,zval * data)162 static void y_scan_recursion(const y_emit_state_t *state, zval *data)
163 {
164 HashTable *ht;
165 zval *elm;
166
167 ZVAL_DEREF(data);
168
169 ht = HASH_OF(data);
170
171 if (!ht) {
172 /* data isn't an array or object, so we're done */
173 return;
174 }
175
176 #if PHP_VERSION_ID >= 70300
177 if (!(GC_FLAGS(ht) & GC_IMMUTABLE) && GC_IS_RECURSIVE(ht)) {
178 #else
179 if (ZEND_HASH_APPLY_PROTECTION(ht) && ht->u.v.nApplyCount > 0) {
180 #endif
181 zval tmp;
182 ZVAL_LONG(&tmp, (zend_ulong) ht);
183
184 /* we've seen this before, so record address */
185 zend_hash_next_index_insert(state->recursive, &tmp);
186 return;
187 }
188
189 #if PHP_VERSION_ID >= 70300
190 if (!(GC_FLAGS(ht) & GC_IMMUTABLE)) {
191 GC_PROTECT_RECURSION(ht);
192 }
193 #else
194 if (ZEND_HASH_APPLY_PROTECTION(ht)) {
195 ht->u.v.nApplyCount++;
196 }
197 #endif
198
199 ZEND_HASH_FOREACH_VAL(ht, elm) {
200 y_scan_recursion(state, elm);
201 } ZEND_HASH_FOREACH_END();
202
203 #if PHP_VERSION_ID >= 70300
204 if (!(GC_FLAGS(ht) & GC_IMMUTABLE)) {
205 GC_UNPROTECT_RECURSION(ht);
206 }
207 #else
208 if (ZEND_HASH_APPLY_PROTECTION(ht)) {
209 ht->u.v.nApplyCount--;
210 }
211 #endif
212
213 return;
214 }
215 /* }}} */
216
217
218 /* {{{ y_search_recursive()
219 * Search the recursive state hash for an address
220 */
221 static zend_long y_search_recursive(
222 const y_emit_state_t *state, const zend_ulong addr)
223 {
224 zval *entry;
225 zend_ulong num_key;
226 zend_ulong found;
227
228 ZEND_HASH_FOREACH_NUM_KEY_VAL(state->recursive, num_key, entry) {
229 found = Z_LVAL_P(entry);
230 if (addr == found) {
231 return num_key;
232 }
233 } ZEND_HASH_FOREACH_END();
234 return -1;
235 }
236 /* }}} */
237
238
239 /* {{{ y_write_zval()
240 * Write a php zval to the emitter
241 */
242 static int y_write_zval(
243 const y_emit_state_t *state, zval *data, yaml_char_t *tag)
244 {
245 int status = FAILURE;
246
247 switch (Z_TYPE_P(data)) {
248 case IS_REFERENCE:
249 status = y_write_zval(state, Z_REFVAL_P(data), tag);
250 break;
251
252 case IS_NULL:
253 status = y_write_null(state, tag);
254 break;
255
256 case IS_TRUE:
257 case IS_FALSE:
258 status = y_write_bool(state, data, tag);
259 break;
260
261 case IS_LONG:
262 status = y_write_long(state, data, tag);
263 break;
264
265 case IS_DOUBLE:
266 status = y_write_double(state, data, tag);
267 break;
268
269 case IS_STRING:
270 status = y_write_string(state, data, tag);
271 break;
272
273 case IS_ARRAY:
274 status = y_write_array(state, data, tag);
275 break;
276
277 case IS_OBJECT:
278 status = y_write_object(state, data, tag);
279 break;
280
281 case IS_RESOURCE: /* unsupported object */
282 php_error_docref(NULL, E_NOTICE,
283 "Unable to emit PHP resources.");
284 break;
285
286 default: /* something we didn't think of */
287 php_error_docref(NULL, E_NOTICE,
288 "Unsupported php zval type %d.", Z_TYPE_P(data));
289 break;
290 }
291
292 return status;
293 }
294 /* }}} */
295
296
297 /* {{{ y_write_null()
298 */
299 static int y_write_null(const y_emit_state_t *state, yaml_char_t *tag)
300 {
301 yaml_event_t event;
302 int omit_tag = 0;
303 int status;
304
305 if (NULL == tag) {
306 tag = (yaml_char_t *) YAML_NULL_TAG;
307 omit_tag = 1;
308 }
309
310 status = yaml_scalar_event_initialize(&event, NULL, tag,
311 (yaml_char_t *) "~", strlen("~"),
312 omit_tag, omit_tag, YAML_PLAIN_SCALAR_STYLE);
313 if (!status) {
314 y_event_init_failed(&event);
315 return FAILURE;
316 }
317 return y_event_emit(state, &event);
318 }
319 /* }}} */
320
321
322 /* {{{ y_write_bool()
323 */
324 static int y_write_bool(
325 const y_emit_state_t *state, zval *data, yaml_char_t *tag)
326 {
327 yaml_event_t event;
328 int omit_tag = 0;
329 int status;
330 const char *res = Z_TYPE_P(data) == IS_TRUE ? "true" : "false";
331
332 if (NULL == tag) {
333 tag = (yaml_char_t *) YAML_BOOL_TAG;
334 omit_tag = 1;
335 }
336
337 status = yaml_scalar_event_initialize(&event, NULL, tag,
338 (yaml_char_t *) res, strlen(res),
339 omit_tag, omit_tag, YAML_PLAIN_SCALAR_STYLE);
340 if (!status) {
341 y_event_init_failed(&event);
342 return FAILURE;
343 }
344 return y_event_emit(state, &event);
345 }
346 /* }}} */
347
348
349 /* {{{ y_write_long()
350 */
351 static int y_write_long(
352 const y_emit_state_t *state, zval *data, yaml_char_t *tag)
353 {
354 yaml_event_t event;
355 int omit_tag = 0;
356 int status;
357 char *res = { 0 };
358 size_t res_size;
359
360 if (NULL == tag) {
361 tag = (yaml_char_t *) YAML_INT_TAG;
362 omit_tag = 1;
363 }
364
365 res_size = snprintf(res, 0, ZEND_LONG_FMT, Z_LVAL_P(data));
366 res = (char*) emalloc(res_size + 1);
367 snprintf(res, res_size + 1, ZEND_LONG_FMT, Z_LVAL_P(data));
368
369 status = yaml_scalar_event_initialize(&event, NULL, tag,
370 (yaml_char_t *) res, strlen(res),
371 omit_tag, omit_tag, YAML_PLAIN_SCALAR_STYLE);
372 efree(res);
373 if (!status) {
374 y_event_init_failed(&event);
375 return FAILURE;
376 }
377 return y_event_emit(state, &event);
378 }
379 /* }}} */
380
381
382 /* {{{ y_write_double()
383 */
384 static int y_write_double(
385 const y_emit_state_t *state, zval *data, yaml_char_t *tag)
386 {
387 yaml_event_t event;
388 int omit_tag = 0;
389 int status;
390 char res[PHP_DOUBLE_MAX_LENGTH];
391
392 if (NULL == tag) {
393 tag = (yaml_char_t *) YAML_FLOAT_TAG;
394 omit_tag = 1;
395 }
396
397 // Bug 79866: let PHP determine output precision
398 php_gcvt(Z_DVAL_P(data), (int)PG(serialize_precision), '.', 'E', res);
399
400 status = yaml_scalar_event_initialize(&event, NULL, tag,
401 (yaml_char_t *) res, strlen(res),
402 omit_tag, omit_tag, YAML_PLAIN_SCALAR_STYLE);
403
404 if (!status) {
405 y_event_init_failed(&event);
406 return FAILURE;
407 }
408 return y_event_emit(state, &event);
409 }
410 /* }}} */
411
412
413 /* {{{ y_write_string()
414 */
415 static int y_write_string(
416 const y_emit_state_t *state, zval *data, yaml_char_t *tag)
417 {
418 yaml_event_t event;
419 int omit_tag = 0;
420 int status;
421 yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE;
422
423 if (NULL == tag) {
424 tag = (yaml_char_t *) YAML_STR_TAG;
425 omit_tag = 1;
426 }
427
428 if (NULL != detect_scalar_type(Z_STRVAL_P(data), Z_STRLEN_P(data), NULL)) {
429 /* looks like some other type to us, make sure it's quoted */
430 style = YAML_DOUBLE_QUOTED_SCALAR_STYLE;
431
432 } else {
433 size_t pos = 0, us;
434 int j;
435 const unsigned char *s = (const unsigned char *)Z_STRVAL_P(data);
436 int len = Z_STRLEN_P(data);
437
438 for (j = 0; pos < len; j++) {
439 us = get_next_char(s, len, &pos, &status);
440 if (status != SUCCESS) {
441 /* invalid UTF-8 character found */
442 php_error_docref(NULL, E_WARNING,
443 "Invalid UTF-8 sequence in argument");
444 return FAILURE;
445
446 } else if ('\n' == us) {
447 /* has newline(s), make sure they are preserved */
448 style = YAML_LITERAL_SCALAR_STYLE;
449 }
450 }
451 }
452
453 status = yaml_scalar_event_initialize(&event, NULL, tag,
454 (yaml_char_t *) Z_STRVAL_P(data), Z_STRLEN_P(data),
455 omit_tag, omit_tag, style);
456 if (!status) {
457 y_event_init_failed(&event);
458 return FAILURE;
459 }
460 return y_event_emit(state, &event);
461 }
462 /* }}} */
463
464
465 /* {{{ y_write_array()
466 */
467 static int y_write_array(
468 const y_emit_state_t *state, zval *data, yaml_char_t *tag)
469 {
470 yaml_event_t event;
471 int omit_tag = 0;
472 int status;
473 HashTable *ht = Z_ARRVAL_P(data);
474 zval *elm;
475 int array_type;
476 zval key_zval;
477 zend_ulong kidx;
478 zend_string *kstr;
479 zend_long recursive_idx = -1;
480 char *anchor = { 0 };
481 size_t anchor_size;
482
483 array_type = y_array_is_sequence(ht);
484
485 if (NULL == tag) {
486 if (Y_ARRAY_SEQUENCE == array_type) {
487 tag = (yaml_char_t *) YAML_SEQ_TAG;
488 } else {
489 tag = (yaml_char_t *) YAML_MAP_TAG;
490 }
491 omit_tag = 1;
492 }
493
494 /* syck does a check to see if the array is small and all scalars
495 * if it is then it outputs in inline form
496 */
497
498 /* search state->recursive for this ht.
499 * if it exists:
500 * anchor = "id%04d" % index
501 * if ht->nApplyCount > 0:
502 * emit a ref
503 */
504 recursive_idx = y_search_recursive(state, (zend_ulong) ht);
505 if (-1 != recursive_idx) {
506 /* create anchor to refer to this structure */
507 anchor_size = snprintf(anchor, 0, "refid" ZEND_LONG_FMT, recursive_idx + 1);
508 anchor = (char*) emalloc(anchor_size + 1);
509 snprintf(anchor, anchor_size + 1, "refid" ZEND_LONG_FMT, recursive_idx + 1);
510
511 #if PHP_VERSION_ID >= 70300
512 if (!(GC_FLAGS(ht) & GC_IMMUTABLE) && GC_IS_RECURSIVE(ht)) {
513 #else
514 if (ZEND_HASH_APPLY_PROTECTION(ht) && ht->u.v.nApplyCount > 0) {
515 #endif
516 /* node has been visited before */
517 status = yaml_alias_event_initialize(
518 &event, (yaml_char_t *) anchor);
519 if (!status) {
520 y_event_init_failed(&event);
521 efree(anchor);
522 return FAILURE;
523 }
524
525 status = y_event_emit(state, &event);
526 efree(anchor);
527 return status;
528 }
529 }
530
531 if (Y_ARRAY_SEQUENCE == array_type) {
532 status = yaml_sequence_start_event_initialize(&event,
533 (yaml_char_t *) anchor, tag, omit_tag,
534 YAML_ANY_SEQUENCE_STYLE);
535 } else {
536 status = yaml_mapping_start_event_initialize(&event,
537 (yaml_char_t *) anchor, tag, omit_tag,
538 YAML_ANY_MAPPING_STYLE);
539 }
540
541 if (!status) {
542 y_event_init_failed(&event);
543 if (anchor) {
544 efree(anchor);
545 }
546 return FAILURE;
547 }
548 status = y_event_emit(state, &event);
549 if (anchor) {
550 efree(anchor);
551 }
552 if (FAILURE == status) {
553 return FAILURE;
554 }
555
556 #if PHP_VERSION_ID >= 70300
557 if (!(GC_FLAGS(ht) & GC_IMMUTABLE)) {
558 /* increment access count for hash */
559 GC_PROTECT_RECURSION(ht);
560 }
561 #else
562 if (ZEND_HASH_APPLY_PROTECTION(ht)) {
563 /* increment access count for hash */
564 ht->u.v.nApplyCount++;
565 }
566 #endif
567
568 /* emit array elements */
569 ZEND_HASH_FOREACH_KEY_VAL(ht, kidx, kstr, elm) {
570 ZVAL_DEREF(elm);
571
572 if (Y_ARRAY_MAP == array_type) {
573 /* create zval for key */
574 if (kstr) {
575 ZVAL_STR(&key_zval, kstr);
576 } else {
577 ZVAL_LONG(&key_zval, kidx);
578 }
579
580 /* emit key */
581 status = y_write_zval(state, &key_zval, NULL);
582 if (SUCCESS != status) {
583 return FAILURE;
584 }
585 }
586
587 status = y_write_zval(state, elm, NULL);
588
589
590 if (SUCCESS != status) {
591 return FAILURE;
592 }
593 } ZEND_HASH_FOREACH_END();
594
595 #if PHP_VERSION_ID >= 70300
596 if (!(GC_FLAGS(ht) & GC_IMMUTABLE)) {
597 GC_UNPROTECT_RECURSION(ht);
598 }
599 #else
600 if (ZEND_HASH_APPLY_PROTECTION(ht)) {
601 ht->u.v.nApplyCount--;
602 }
603 #endif
604
605 if (Y_ARRAY_SEQUENCE == array_type) {
606 status = yaml_sequence_end_event_initialize(&event);
607 } else {
608 status = yaml_mapping_end_event_initialize(&event);
609 }
610
611 if (!status) {
612 y_event_init_failed(&event);
613 return FAILURE;
614 }
615 return y_event_emit(state, &event);
616 }
617 /* }}} */
618
619
620 /* y_write_timestamp()
621 */
622 static int y_write_timestamp(
623 const y_emit_state_t *state, zval *data, yaml_char_t *tag)
624 {
625 yaml_event_t event;
626 int omit_tag = 0;
627 int status;
628 zend_class_entry *clazz = Z_OBJCE_P(data);
629 zval timestamp = {{0} };
630 zval dtfmt;
631
632 if (NULL == tag) {
633 tag = (yaml_char_t *) YAML_TIMESTAMP_TAG;
634 omit_tag = 1;
635 }
636
637 /* iso-8601 format specifier including milliseconds */
638 ZVAL_STRING(&dtfmt, "Y-m-d\\TH:i:s.uP");
639
640 /* format date as iso-8601 string */
641 #if PHP_VERSION_ID >= 80000
642 zend_call_method_with_1_params(Z_OBJ_P(data), clazz, NULL, "format", ×tamp, &dtfmt);
643 #else
644 zend_call_method_with_1_params(data, clazz, NULL, "format", ×tamp, &dtfmt);
645 #endif
646 zval_ptr_dtor(&dtfmt);
647
648 /* emit formatted date */
649 status = yaml_scalar_event_initialize(&event, NULL, tag,
650 (yaml_char_t *) Z_STRVAL_P(×tamp), Z_STRLEN_P(×tamp),
651 omit_tag, omit_tag, YAML_PLAIN_SCALAR_STYLE);
652 zval_ptr_dtor(×tamp);
653 if (!status) {
654 y_event_init_failed(&event);
655 return FAILURE;
656 }
657 return y_event_emit(state, &event);
658 }
659 /* }}} */
660
661
662 /* {{{ y_write_object()
663 */
664 static int y_write_object(
665 const y_emit_state_t *state, zval *data, yaml_char_t *tag)
666 {
667 yaml_event_t event;
668 int status;
669 zend_string *clazz_name;
670 zval *callback;
671
672 clazz_name = Z_OBJCE_P(data)->name;
673
674 /* TODO check for a "yamlSerialize()" instance method */
675 if (NULL != state->callbacks && (callback = zend_hash_find(
676 state->callbacks, clazz_name)) != NULL) {
677 /* found a registered callback for this class */
678 status = y_write_object_callback(
679 state, callback, data, clazz_name->val);
680
681 } else if (0 == strncmp(clazz_name->val, "DateTime", clazz_name->len)) {
682 status = y_write_timestamp(state, data, tag);
683 } else {
684 /* tag and emit serialized version of object */
685 php_serialize_data_t var_hash;
686 smart_str buf = { 0 };
687
688 PHP_VAR_SERIALIZE_INIT(var_hash);
689 php_var_serialize(&buf, data, &var_hash);
690 PHP_VAR_SERIALIZE_DESTROY(var_hash);
691
692 status = yaml_scalar_event_initialize(&event,
693 NULL, (yaml_char_t *) YAML_PHP_TAG, (yaml_char_t *) buf.s->val, buf.s->len,
694 0, 0, YAML_DOUBLE_QUOTED_SCALAR_STYLE);
695
696 if (!status) {
697 y_event_init_failed(&event);
698 status = FAILURE;
699 } else {
700 status = y_event_emit(state, &event);
701 }
702 smart_str_free(&buf);
703 }
704
705 return status;
706 }
707 /* }}} */
708
709 /* {{{ y_write_object_callback()
710 */
711 static int
712 y_write_object_callback (
713 const y_emit_state_t *state, zval *callback, zval *data,
714 const char *clazz_name) {
715 zval argv[1];
716 argv[0] = *data;
717 zval zret;
718 zval *ztag;
719 zval *zdata;
720 zend_string *str_key;
721 int result;
722
723 /* call the user function */
724 if (FAILURE == call_user_function(EG(function_table), NULL,
725 callback, &zret, 1, argv) ||
726 Z_TYPE_P(&zret) == IS_UNDEF) {
727 php_error_docref(NULL, E_WARNING,
728 "Failed to apply callback for class '%s'"
729 " with user defined function", clazz_name);
730 return FAILURE;
731 }
732
733 /* return val should be array */
734 if (IS_ARRAY != Z_TYPE_P(&zret)) {
735 php_error_docref(NULL, E_WARNING,
736 "Expected callback for class '%s'"
737 " to return an array", clazz_name);
738 zval_ptr_dtor(&zret);
739 return FAILURE;
740 }
741
742 /* pull out the tag and surrogate object */
743 str_key = zend_string_init("tag", sizeof("tag") - 1, 0);
744 if ((ztag = zend_hash_find(Z_ARRVAL_P(&zret), str_key)) == NULL || Z_TYPE_P(ztag) != IS_STRING) {
745 php_error_docref(NULL, E_WARNING,
746 "Expected callback result for class '%s'"
747 " to contain a key named 'tag' with a string value",
748 clazz_name);
749 zend_string_release(str_key);
750 return FAILURE;
751 }
752 zend_string_release(str_key);
753
754 str_key = zend_string_init("data", sizeof("data") - 1, 0);
755 if ((zdata = zend_hash_find(Z_ARRVAL_P(&zret), str_key)) == NULL) {
756 php_error_docref(NULL, E_WARNING,
757 "Expected callback result for class '%s'"
758 " to contain a key named 'data'",
759 clazz_name);
760 zend_string_release(str_key);
761 return FAILURE;
762 }
763 zend_string_release(str_key);
764
765 /* emit surrogate object and tag */
766 result = y_write_zval(
767 state, zdata, (yaml_char_t *) Z_STRVAL_P(ztag));
768
769 zval_ptr_dtor(&zret);
770 return result;
771 }
772 /* }}} */
773
774 /* {{{ php_yaml_write_impl()
775 * Common stream writing logic shared by yaml_emit and yaml_emit_file.
776 */
777 int
778 php_yaml_write_impl(
779 yaml_emitter_t *emitter, zval *data,
780 yaml_encoding_t encoding, HashTable *callbacks)
781 {
782 y_emit_state_t state;
783 yaml_event_t event;
784 int status;
785
786 state.emitter = emitter;
787 /* scan for recursive objects */
788 ALLOC_HASHTABLE(state.recursive);
789 zend_hash_init(state.recursive, 8, NULL, NULL, 0);
790 y_scan_recursion(&state, data);
791 state.callbacks = callbacks;
792
793
794 /* start stream */
795 status = yaml_stream_start_event_initialize(&event, encoding);
796 if (!status) {
797 y_event_init_failed(&event);
798 status = FAILURE;
799 goto cleanup;
800 }
801 if (FAILURE == y_event_emit(&state, &event)) {
802 status = FAILURE;
803 goto cleanup;
804 }
805
806 /* start document */
807 status = yaml_document_start_event_initialize(&event, NULL, NULL, NULL, 0);
808 if (!status) {
809 y_event_init_failed(&event);
810 status = FAILURE;
811 goto cleanup;
812 }
813 if (FAILURE == y_event_emit(&state, &event)) {
814 status = FAILURE;
815 goto cleanup;
816 }
817
818 /* output data */
819 if (FAILURE == y_write_zval(&state, data, NULL)) {
820 status = FAILURE;
821 goto cleanup;
822 }
823
824 /* end document */
825 status = yaml_document_end_event_initialize(&event, 0);
826 if (!status) {
827 y_event_init_failed(&event);
828 status = FAILURE;
829 goto cleanup;
830 }
831 if (FAILURE == y_event_emit(&state, &event)) {
832 status = FAILURE;
833 goto cleanup;
834 }
835
836 /* end stream */
837 status = yaml_stream_end_event_initialize(&event);
838 if (!status) {
839 y_event_init_failed(&event);
840 status = FAILURE;
841 goto cleanup;
842 }
843 if (FAILURE == y_event_emit(&state, &event)) {
844 status = FAILURE;
845 goto cleanup;
846 }
847
848 yaml_emitter_flush(state.emitter);
849 status = SUCCESS;
850
851 cleanup:
852 zend_hash_destroy(state.recursive);
853 FREE_HASHTABLE(state.recursive);
854
855 return status;
856 }
857 /* }}} */
858
859
860 /* {{{ php_yaml_write_to_buffer()
861 * Emitter string buffer
862 */
863 int
864 php_yaml_write_to_buffer(void *data, unsigned char *buffer, size_t size)
865 {
866 smart_string_appendl((smart_string *) data, (char *) buffer, size);
867 return 1;
868 }
869 /* }}} */
870
871
872 /* {{{ get_next_char
873 * Copied from ext/standard/html.c @ a37ff1fa4bb149dc81fc812d03cdf7685c499403
874 * and trimmed to include only utf8 code branch. I would have liked to use
875 * php_next_utf8_char() but it isn't available until php 5.4.
876 *
877 * Thank you cataphract@php.net!
878 */
879 #define CHECK_LEN(pos, chars_need) ((str_len - (pos)) >= (chars_need))
880 #define MB_FAILURE(pos, advance) do { \
881 *cursor = pos + (advance); \
882 *status = FAILURE; \
883 return 0; \
884 } while (0)
885 #define utf8_lead(c) ((c) < 0x80 || ((c) >= 0xC2 && (c) <= 0xF4))
886 #define utf8_trail(c) ((c) >= 0x80 && (c) <= 0xBF)
887
888 static inline unsigned int get_next_char(
889 const unsigned char *str,
890 size_t str_len,
891 size_t *cursor,
892 int *status)
893 {
894 size_t pos = *cursor;
895 unsigned int this_char = 0;
896 unsigned char c;
897
898 *status = SUCCESS;
899 assert(pos <= str_len);
900
901 if (!CHECK_LEN(pos, 1))
902 MB_FAILURE(pos, 1);
903
904 /* We'll follow strategy 2. from section 3.6.1 of UTR #36:
905 * "In a reported illegal byte sequence, do not include any
906 * non-initial byte that encodes a valid character or is a leading
907 * byte for a valid sequence." */
908 c = str[pos];
909 if (c < 0x80) {
910 this_char = c;
911 pos++;
912 } else if (c < 0xc2) {
913 MB_FAILURE(pos, 1);
914 } else if (c < 0xe0) {
915 if (!CHECK_LEN(pos, 2))
916 MB_FAILURE(pos, 1);
917
918 if (!utf8_trail(str[pos + 1])) {
919 MB_FAILURE(pos, utf8_lead(str[pos + 1]) ? 1 : 2);
920 }
921 this_char = ((c & 0x1f) << 6) | (str[pos + 1] & 0x3f);
922 if (this_char < 0x80) { /* non-shortest form */
923 MB_FAILURE(pos, 2);
924 }
925 pos += 2;
926 } else if (c < 0xf0) {
927 size_t avail = str_len - pos;
928
929 if (avail < 3 ||
930 !utf8_trail(str[pos + 1]) || !utf8_trail(str[pos + 2])) {
931 if (avail < 2 || utf8_lead(str[pos + 1]))
932 MB_FAILURE(pos, 1);
933 else if (avail < 3 || utf8_lead(str[pos + 2]))
934 MB_FAILURE(pos, 2);
935 else
936 MB_FAILURE(pos, 3);
937 }
938
939 this_char = ((c & 0x0f) << 12) | ((str[pos + 1] & 0x3f) << 6) |
940 (str[pos + 2] & 0x3f);
941 if (this_char < 0x800) {
942 /* non-shortest form */
943 MB_FAILURE(pos, 3);
944 } else if (this_char >= 0xd800 && this_char <= 0xdfff) {
945 /* surrogate */
946 MB_FAILURE(pos, 3);
947 }
948 pos += 3;
949 } else if (c < 0xf5) {
950 size_t avail = str_len - pos;
951
952 if (avail < 4 ||
953 !utf8_trail(str[pos + 1]) || !utf8_trail(str[pos + 2]) ||
954 !utf8_trail(str[pos + 3])) {
955 if (avail < 2 || utf8_lead(str[pos + 1]))
956 MB_FAILURE(pos, 1);
957 else if (avail < 3 || utf8_lead(str[pos + 2]))
958 MB_FAILURE(pos, 2);
959 else if (avail < 4 || utf8_lead(str[pos + 3]))
960 MB_FAILURE(pos, 3);
961 else
962 MB_FAILURE(pos, 4);
963 }
964
965 this_char = ((c & 0x07) << 18) | ((str[pos + 1] & 0x3f) << 12) |
966 ((str[pos + 2] & 0x3f) << 6) | (str[pos + 3] & 0x3f);
967 if (this_char < 0x10000 || this_char > 0x10FFFF) {
968 /* non-shortest form or outside range */
969 MB_FAILURE(pos, 4);
970 }
971 pos += 4;
972 } else {
973 MB_FAILURE(pos, 1);
974 }
975
976 *cursor = pos;
977 return this_char;
978 }
979 /* }}} */
980
981 /*
982 * Local variables:
983 * tab-width: 4
984 * c-basic-offset: 4
985 * End:
986 * vim600: noet sw=4 ts=4 fdm=marker
987 * vim<600: noet sw=4 ts=4
988 */
989