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", &timestamp, &dtfmt);
643 #else
644 	zend_call_method_with_1_params(data, clazz, NULL, "format", &timestamp, &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(&timestamp), Z_STRLEN_P(&timestamp),
651 			omit_tag, omit_tag, YAML_PLAIN_SCALAR_STYLE);
652 	zval_ptr_dtor(&timestamp);
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