1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2016 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Author: Andrei Zmievski <andrei@php.net>                             |
16   +----------------------------------------------------------------------+
17 */
18
19/* $Id$ */
20
21#ifdef HAVE_CONFIG_H
22#include "config.h"
23#endif
24
25#include "php.h"
26
27#if HAVE_WDDX
28
29#include "ext/xml/expat_compat.h"
30#include "php_wddx.h"
31#include "php_wddx_api.h"
32
33#define PHP_XML_INTERNAL
34#include "ext/xml/php_xml.h"
35#include "ext/standard/php_incomplete_class.h"
36#include "ext/standard/base64.h"
37#include "ext/standard/info.h"
38#include "zend_smart_str.h"
39#include "ext/standard/html.h"
40#include "ext/standard/php_string.h"
41#include "ext/date/php_date.h"
42#include "zend_globals.h"
43
44#define WDDX_BUF_LEN			256
45#define PHP_CLASS_NAME_VAR		"php_class_name"
46
47#define EL_ARRAY				"array"
48#define EL_BINARY				"binary"
49#define EL_BOOLEAN				"boolean"
50#define EL_CHAR					"char"
51#define EL_CHAR_CODE			"code"
52#define EL_NULL					"null"
53#define EL_NUMBER				"number"
54#define	EL_PACKET				"wddxPacket"
55#define	EL_STRING				"string"
56#define EL_STRUCT				"struct"
57#define EL_VALUE				"value"
58#define EL_VAR					"var"
59#define EL_NAME	    			"name"
60#define EL_VERSION				"version"
61#define EL_RECORDSET			"recordset"
62#define EL_FIELD				"field"
63#define EL_DATETIME				"dateTime"
64
65#define php_wddx_deserialize(a,b) \
66	php_wddx_deserialize_ex(Z_STRVAL_P(a), Z_STRLEN_P(a), (b))
67
68#define SET_STACK_VARNAME							\
69		if (stack->varname) {						\
70			ent.varname = estrdup(stack->varname);	\
71			efree(stack->varname);					\
72			stack->varname = NULL;					\
73		} else										\
74			ent.varname = NULL;						\
75
76static int le_wddx;
77
78typedef struct {
79	zval data;
80	enum {
81		ST_ARRAY,
82		ST_BOOLEAN,
83		ST_NULL,
84		ST_NUMBER,
85		ST_STRING,
86		ST_BINARY,
87		ST_STRUCT,
88		ST_RECORDSET,
89		ST_FIELD,
90		ST_DATETIME
91	} type;
92	char *varname;
93} st_entry;
94
95typedef struct {
96	int top, max;
97	char *varname;
98	zend_bool done;
99	void **elements;
100} wddx_stack;
101
102
103static void php_wddx_process_data(void *user_data, const XML_Char *s, int len);
104
105/* {{{ arginfo */
106ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_serialize_value, 0, 0, 1)
107	ZEND_ARG_INFO(0, var)
108	ZEND_ARG_INFO(0, comment)
109ZEND_END_ARG_INFO()
110
111ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_serialize_vars, 0, 0, 1)
112	ZEND_ARG_VARIADIC_INFO(0, var_names)
113ZEND_END_ARG_INFO()
114
115ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_serialize_start, 0, 0, 0)
116	ZEND_ARG_INFO(0, comment)
117ZEND_END_ARG_INFO()
118
119ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_packet_end, 0, 0, 1)
120	ZEND_ARG_INFO(0, packet_id)
121ZEND_END_ARG_INFO()
122
123ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_add_vars, 0, 0, 2)
124	ZEND_ARG_INFO(0, packet_id)
125	ZEND_ARG_VARIADIC_INFO(0, var_names)
126ZEND_END_ARG_INFO()
127
128ZEND_BEGIN_ARG_INFO_EX(arginfo_wddx_deserialize, 0, 0, 1)
129	ZEND_ARG_INFO(0, packet)
130ZEND_END_ARG_INFO()
131/* }}} */
132
133/* {{{ wddx_functions[]
134 */
135const zend_function_entry wddx_functions[] = {
136	PHP_FE(wddx_serialize_value, arginfo_wddx_serialize_value)
137	PHP_FE(wddx_serialize_vars,	arginfo_wddx_serialize_vars)
138	PHP_FE(wddx_packet_start,	arginfo_wddx_serialize_start)
139	PHP_FE(wddx_packet_end,		arginfo_wddx_packet_end)
140	PHP_FE(wddx_add_vars,		arginfo_wddx_add_vars)
141	PHP_FE(wddx_deserialize,	arginfo_wddx_deserialize)
142	PHP_FE_END
143};
144/* }}} */
145
146PHP_MINIT_FUNCTION(wddx);
147PHP_MINFO_FUNCTION(wddx);
148
149/* {{{ dynamically loadable module stuff */
150#ifdef COMPILE_DL_WDDX
151ZEND_GET_MODULE(wddx)
152#endif /* COMPILE_DL_WDDX */
153/* }}} */
154
155/* {{{ wddx_module_entry
156 */
157zend_module_entry wddx_module_entry = {
158	STANDARD_MODULE_HEADER,
159	"wddx",
160	wddx_functions,
161	PHP_MINIT(wddx),
162	NULL,
163	NULL,
164	NULL,
165	PHP_MINFO(wddx),
166    PHP_WDDX_VERSION,
167	STANDARD_MODULE_PROPERTIES
168};
169/* }}} */
170
171/* {{{ wddx_stack_init
172 */
173static int wddx_stack_init(wddx_stack *stack)
174{
175	stack->top = 0;
176	stack->elements = (void **) safe_emalloc(sizeof(void **), STACK_BLOCK_SIZE, 0);
177	stack->max = STACK_BLOCK_SIZE;
178	stack->varname = NULL;
179	stack->done = 0;
180
181	return SUCCESS;
182}
183/* }}} */
184
185/* {{{ wddx_stack_push
186 */
187static int wddx_stack_push(wddx_stack *stack, void *element, int size)
188{
189	if (stack->top >= stack->max) {		/* we need to allocate more memory */
190		stack->elements = (void **) erealloc(stack->elements,
191				   (sizeof(void **) * (stack->max += STACK_BLOCK_SIZE)));
192	}
193	stack->elements[stack->top] = (void *) emalloc(size);
194	memcpy(stack->elements[stack->top], element, size);
195	return stack->top++;
196}
197/* }}} */
198
199/* {{{ wddx_stack_top
200 */
201static int wddx_stack_top(wddx_stack *stack, void **element)
202{
203	if (stack->top > 0) {
204		*element = stack->elements[stack->top - 1];
205		return SUCCESS;
206	} else {
207		*element = NULL;
208		return FAILURE;
209	}
210}
211/* }}} */
212
213/* {{{ wddx_stack_is_empty
214 */
215static int wddx_stack_is_empty(wddx_stack *stack)
216{
217	if (stack->top == 0) {
218		return 1;
219	} else {
220		return 0;
221	}
222}
223/* }}} */
224
225/* {{{ wddx_stack_destroy
226 */
227static int wddx_stack_destroy(wddx_stack *stack)
228{
229	register int i;
230
231	if (stack->elements) {
232		for (i = 0; i < stack->top; i++) {
233			zval_ptr_dtor(&((st_entry *)stack->elements[i])->data);
234			if (((st_entry *)stack->elements[i])->varname) {
235				efree(((st_entry *)stack->elements[i])->varname);
236			}
237			efree(stack->elements[i]);
238		}
239		efree(stack->elements);
240	}
241	return SUCCESS;
242}
243/* }}} */
244
245/* {{{ release_wddx_packet_rsrc
246 */
247static void release_wddx_packet_rsrc(zend_resource *rsrc)
248{
249	smart_str *str = (smart_str *)rsrc->ptr;
250	smart_str_free(str);
251	efree(str);
252}
253/* }}} */
254
255#include "ext/session/php_session.h"
256
257#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION)
258/* {{{ PS_SERIALIZER_ENCODE_FUNC
259 */
260PS_SERIALIZER_ENCODE_FUNC(wddx)
261{
262	wddx_packet *packet;
263	zend_string *str;
264	PS_ENCODE_VARS;
265
266	packet = php_wddx_constructor();
267
268	php_wddx_packet_start(packet, NULL, 0);
269	php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
270
271	PS_ENCODE_LOOP(
272		php_wddx_serialize_var(packet, struc, key);
273	);
274
275	php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
276	php_wddx_packet_end(packet);
277	smart_str_0(packet);
278	str = zend_string_copy(packet->s);
279	php_wddx_destructor(packet);
280
281	return str;
282}
283/* }}} */
284
285/* {{{ PS_SERIALIZER_DECODE_FUNC
286 */
287PS_SERIALIZER_DECODE_FUNC(wddx)
288{
289	zval retval;
290	zval *ent;
291	zend_string *key;
292	zend_ulong idx;
293	int ret;
294
295	if (vallen == 0) {
296		return SUCCESS;
297	}
298
299	ZVAL_UNDEF(&retval);
300	if ((ret = php_wddx_deserialize_ex(val, vallen, &retval)) == SUCCESS) {
301		if (Z_TYPE(retval) != IS_ARRAY) {
302			zval_dtor(&retval);
303			return FAILURE;
304		}
305		ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(retval), idx, key, ent) {
306			if (key == NULL) {
307				key = zend_long_to_str(idx);
308			} else {
309				zend_string_addref(key);
310			}
311			if (php_set_session_var(key, ent, NULL)) {
312				if (Z_REFCOUNTED_P(ent)) Z_ADDREF_P(ent);
313			}
314			PS_ADD_VAR(key);
315			zend_string_release(key);
316		} ZEND_HASH_FOREACH_END();
317	}
318
319	zval_ptr_dtor(&retval);
320
321	return ret;
322}
323/* }}} */
324#endif
325
326/* {{{ PHP_MINIT_FUNCTION
327 */
328PHP_MINIT_FUNCTION(wddx)
329{
330	le_wddx = zend_register_list_destructors_ex(release_wddx_packet_rsrc, NULL, "wddx", module_number);
331
332#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION)
333	php_session_register_serializer("wddx",
334									PS_SERIALIZER_ENCODE_NAME(wddx),
335									PS_SERIALIZER_DECODE_NAME(wddx));
336#endif
337
338	return SUCCESS;
339}
340/* }}} */
341
342/* {{{ PHP_MINFO_FUNCTION
343 */
344PHP_MINFO_FUNCTION(wddx)
345{
346	php_info_print_table_start();
347#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION)
348	php_info_print_table_header(2, "WDDX Support", "enabled" );
349	php_info_print_table_row(2, "WDDX Session Serializer", "enabled" );
350#else
351	php_info_print_table_row(2, "WDDX Support", "enabled" );
352#endif
353	php_info_print_table_end();
354}
355/* }}} */
356
357/* {{{ php_wddx_packet_start
358 */
359void php_wddx_packet_start(wddx_packet *packet, char *comment, size_t comment_len)
360{
361	php_wddx_add_chunk_static(packet, WDDX_PACKET_S);
362	if (comment) {
363		php_wddx_add_chunk_static(packet, WDDX_HEADER_S);
364		php_wddx_add_chunk_static(packet, WDDX_COMMENT_S);
365		php_wddx_add_chunk_ex(packet, comment, comment_len);
366		php_wddx_add_chunk_static(packet, WDDX_COMMENT_E);
367		php_wddx_add_chunk_static(packet, WDDX_HEADER_E);
368	} else {
369		php_wddx_add_chunk_static(packet, WDDX_HEADER);
370	}
371	php_wddx_add_chunk_static(packet, WDDX_DATA_S);
372}
373/* }}} */
374
375/* {{{ php_wddx_packet_end
376 */
377void php_wddx_packet_end(wddx_packet *packet)
378{
379	php_wddx_add_chunk_static(packet, WDDX_DATA_E);
380	php_wddx_add_chunk_static(packet, WDDX_PACKET_E);
381}
382/* }}} */
383
384#define FLUSH_BUF()                               \
385	if (l > 0) {                                  \
386		php_wddx_add_chunk_ex(packet, buf, l);    \
387		l = 0;                                    \
388	}
389
390/* {{{ php_wddx_serialize_string
391 */
392static void php_wddx_serialize_string(wddx_packet *packet, zval *var)
393{
394	php_wddx_add_chunk_static(packet, WDDX_STRING_S);
395
396	if (Z_STRLEN_P(var) > 0) {
397		zend_string *buf = php_escape_html_entities(
398			(unsigned char *) Z_STRVAL_P(var), Z_STRLEN_P(var), 0, ENT_QUOTES, NULL);
399
400		php_wddx_add_chunk_ex(packet, ZSTR_VAL(buf), ZSTR_LEN(buf));
401
402		zend_string_release(buf);
403	}
404	php_wddx_add_chunk_static(packet, WDDX_STRING_E);
405}
406/* }}} */
407
408/* {{{ php_wddx_serialize_number
409 */
410static void php_wddx_serialize_number(wddx_packet *packet, zval *var)
411{
412	char tmp_buf[WDDX_BUF_LEN];
413	zend_string *str = zval_get_string(var);
414	snprintf(tmp_buf, sizeof(tmp_buf), WDDX_NUMBER, ZSTR_VAL(str));
415	zend_string_release(str);
416
417	php_wddx_add_chunk(packet, tmp_buf);
418}
419/* }}} */
420
421/* {{{ php_wddx_serialize_boolean
422 */
423static void php_wddx_serialize_boolean(wddx_packet *packet, zval *var)
424{
425	php_wddx_add_chunk(packet, Z_TYPE_P(var) == IS_TRUE ? WDDX_BOOLEAN_TRUE : WDDX_BOOLEAN_FALSE);
426}
427/* }}} */
428
429/* {{{ php_wddx_serialize_unset
430 */
431static void php_wddx_serialize_unset(wddx_packet *packet)
432{
433	php_wddx_add_chunk_static(packet, WDDX_NULL);
434}
435/* }}} */
436
437/* {{{ php_wddx_serialize_object
438 */
439static void php_wddx_serialize_object(wddx_packet *packet, zval *obj)
440{
441/* OBJECTS_FIXME */
442	zval *ent, fname, *varname;
443	zval retval;
444	zend_string *key;
445	zend_ulong idx;
446	char tmp_buf[WDDX_BUF_LEN];
447	HashTable *objhash, *sleephash;
448
449	ZVAL_STRING(&fname, "__sleep");
450	/*
451	 * We try to call __sleep() method on object. It's supposed to return an
452	 * array of property names to be serialized.
453	 */
454	if (call_user_function_ex(CG(function_table), obj, &fname, &retval, 0, 0, 1, NULL) == SUCCESS) {
455		if (!Z_ISUNDEF(retval) && (sleephash = HASH_OF(&retval))) {
456			PHP_CLASS_ATTRIBUTES;
457
458			PHP_SET_CLASS_ATTRIBUTES(obj);
459
460			php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
461			snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR);
462			php_wddx_add_chunk(packet, tmp_buf);
463			php_wddx_add_chunk_static(packet, WDDX_STRING_S);
464			php_wddx_add_chunk_ex(packet, ZSTR_VAL(class_name), ZSTR_LEN(class_name));
465			php_wddx_add_chunk_static(packet, WDDX_STRING_E);
466			php_wddx_add_chunk_static(packet, WDDX_VAR_E);
467
468			PHP_CLEANUP_CLASS_ATTRIBUTES();
469
470			objhash = Z_OBJPROP_P(obj);
471
472			ZEND_HASH_FOREACH_VAL(sleephash, varname) {
473				if (Z_TYPE_P(varname) != IS_STRING) {
474					php_error_docref(NULL, E_NOTICE, "__sleep should return an array only containing the names of instance-variables to serialize.");
475					continue;
476				}
477
478				if ((ent = zend_hash_find(objhash, Z_STR_P(varname))) != NULL) {
479					php_wddx_serialize_var(packet, ent, Z_STR_P(varname));
480				}
481			} ZEND_HASH_FOREACH_END();
482
483			php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
484		}
485	} else {
486		PHP_CLASS_ATTRIBUTES;
487
488		PHP_SET_CLASS_ATTRIBUTES(obj);
489
490		php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
491		snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR);
492		php_wddx_add_chunk(packet, tmp_buf);
493		php_wddx_add_chunk_static(packet, WDDX_STRING_S);
494		php_wddx_add_chunk_ex(packet, ZSTR_VAL(class_name), ZSTR_LEN(class_name));
495		php_wddx_add_chunk_static(packet, WDDX_STRING_E);
496		php_wddx_add_chunk_static(packet, WDDX_VAR_E);
497
498		PHP_CLEANUP_CLASS_ATTRIBUTES();
499
500		objhash = Z_OBJPROP_P(obj);
501		ZEND_HASH_FOREACH_KEY_VAL(objhash, idx, key, ent) {
502			if (ent == obj) {
503				continue;
504			}
505			if (key) {
506				const char *class_name, *prop_name;
507				size_t prop_name_len;
508				zend_string *tmp;
509
510				zend_unmangle_property_name_ex(key, &class_name, &prop_name, &prop_name_len);
511				tmp = zend_string_init(prop_name, prop_name_len, 0);
512				php_wddx_serialize_var(packet, ent, tmp);
513				zend_string_release(tmp);
514			} else {
515				key = zend_long_to_str(idx);
516				php_wddx_serialize_var(packet, ent, key);
517				zend_string_release(key);
518			}
519		} ZEND_HASH_FOREACH_END();
520		php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
521	}
522
523	zval_ptr_dtor(&fname);
524	zval_ptr_dtor(&retval);
525}
526/* }}} */
527
528/* {{{ php_wddx_serialize_array
529 */
530static void php_wddx_serialize_array(wddx_packet *packet, zval *arr)
531{
532	zval *ent;
533	zend_string *key;
534	int is_struct = 0;
535	zend_ulong idx;
536	HashTable *target_hash;
537	char tmp_buf[WDDX_BUF_LEN];
538	zend_ulong ind = 0;
539
540	target_hash = Z_ARRVAL_P(arr);
541	ZEND_HASH_FOREACH_KEY(target_hash, idx, key) {
542		if (key) {
543			is_struct = 1;
544			break;
545		}
546
547		if (idx != ind) {
548			is_struct = 1;
549			break;
550		}
551		ind++;
552	} ZEND_HASH_FOREACH_END();
553
554	if (is_struct) {
555		php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
556	} else {
557		snprintf(tmp_buf, sizeof(tmp_buf), WDDX_ARRAY_S, zend_hash_num_elements(target_hash));
558		php_wddx_add_chunk(packet, tmp_buf);
559	}
560
561	ZEND_HASH_FOREACH_KEY_VAL(target_hash, idx, key, ent) {
562		if (ent == arr) {
563			continue;
564		}
565
566		if (is_struct) {
567			if (key) {
568				php_wddx_serialize_var(packet, ent, key);
569			} else {
570				key = zend_long_to_str(idx);
571				php_wddx_serialize_var(packet, ent, key);
572				zend_string_release(key);
573			}
574		} else {
575			php_wddx_serialize_var(packet, ent, NULL);
576		}
577	} ZEND_HASH_FOREACH_END();
578
579	if (is_struct) {
580		php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
581	} else {
582		php_wddx_add_chunk_static(packet, WDDX_ARRAY_E);
583	}
584}
585/* }}} */
586
587/* {{{ php_wddx_serialize_var
588 */
589void php_wddx_serialize_var(wddx_packet *packet, zval *var, zend_string *name)
590{
591	HashTable *ht;
592
593	if (name) {
594		char *tmp_buf;
595		zend_string *name_esc = php_escape_html_entities((unsigned char *) ZSTR_VAL(name), ZSTR_LEN(name), 0, ENT_QUOTES, NULL);
596		tmp_buf = emalloc(ZSTR_LEN(name_esc) + sizeof(WDDX_VAR_S));
597		snprintf(tmp_buf, ZSTR_LEN(name_esc) + sizeof(WDDX_VAR_S), WDDX_VAR_S, ZSTR_VAL(name_esc));
598		php_wddx_add_chunk(packet, tmp_buf);
599		efree(tmp_buf);
600		zend_string_release(name_esc);
601	}
602
603	if (Z_TYPE_P(var) == IS_INDIRECT) {
604		var = Z_INDIRECT_P(var);
605	}
606	ZVAL_DEREF(var);
607	switch (Z_TYPE_P(var)) {
608		case IS_STRING:
609			php_wddx_serialize_string(packet, var);
610			break;
611
612		case IS_LONG:
613		case IS_DOUBLE:
614			php_wddx_serialize_number(packet, var);
615			break;
616
617		case IS_TRUE:
618		case IS_FALSE:
619			php_wddx_serialize_boolean(packet, var);
620			break;
621
622		case IS_NULL:
623			php_wddx_serialize_unset(packet);
624			break;
625
626		case IS_ARRAY:
627			ht = Z_ARRVAL_P(var);
628			if (ht->u.v.nApplyCount > 1) {
629				php_error_docref(NULL, E_RECOVERABLE_ERROR, "WDDX doesn't support circular references");
630				return;
631			}
632			if (ZEND_HASH_APPLY_PROTECTION(ht)) {
633				ht->u.v.nApplyCount++;
634			}
635			php_wddx_serialize_array(packet, var);
636			if (ZEND_HASH_APPLY_PROTECTION(ht)) {
637				ht->u.v.nApplyCount--;
638			}
639			break;
640
641		case IS_OBJECT:
642			ht = Z_OBJPROP_P(var);
643			if (ht->u.v.nApplyCount > 1) {
644				php_error_docref(NULL, E_RECOVERABLE_ERROR, "WDDX doesn't support circular references");
645				return;
646			}
647			ht->u.v.nApplyCount++;
648 			php_wddx_serialize_object(packet, var);
649			ht->u.v.nApplyCount--;
650			break;
651	}
652
653	if (name) {
654		php_wddx_add_chunk_static(packet, WDDX_VAR_E);
655	}
656}
657/* }}} */
658
659/* {{{ php_wddx_add_var
660 */
661static void php_wddx_add_var(wddx_packet *packet, zval *name_var)
662{
663	zval *val;
664	HashTable *target_hash;
665
666	if (Z_TYPE_P(name_var) == IS_STRING) {
667		zend_array *symbol_table = zend_rebuild_symbol_table();
668		if ((val = zend_hash_find(symbol_table, Z_STR_P(name_var))) != NULL) {
669			if (Z_TYPE_P(val) == IS_INDIRECT) {
670				val = Z_INDIRECT_P(val);
671			}
672			php_wddx_serialize_var(packet, val, Z_STR_P(name_var));
673		}
674	} else if (Z_TYPE_P(name_var) == IS_ARRAY || Z_TYPE_P(name_var) == IS_OBJECT)	{
675		int is_array = Z_TYPE_P(name_var) == IS_ARRAY;
676
677		target_hash = HASH_OF(name_var);
678
679		if (is_array && target_hash->u.v.nApplyCount > 1) {
680			php_error_docref(NULL, E_WARNING, "recursion detected");
681			return;
682		}
683
684		if (Z_IMMUTABLE_P(name_var)) {
685			ZEND_HASH_FOREACH_VAL(target_hash, val) {
686				php_wddx_add_var(packet, val);
687			} ZEND_HASH_FOREACH_END();
688		} else {
689			ZEND_HASH_FOREACH_VAL(target_hash, val) {
690				if (is_array) {
691					target_hash->u.v.nApplyCount++;
692				}
693
694				ZVAL_DEREF(val);
695				php_wddx_add_var(packet, val);
696
697				if (is_array) {
698					target_hash->u.v.nApplyCount--;
699				}
700			} ZEND_HASH_FOREACH_END();
701		}
702	}
703}
704/* }}} */
705
706/* {{{ php_wddx_push_element
707 */
708static void php_wddx_push_element(void *user_data, const XML_Char *name, const XML_Char **atts)
709{
710	st_entry ent;
711	wddx_stack *stack = (wddx_stack *)user_data;
712	if (!strcmp((char *)name, EL_PACKET)) {
713		int i;
714
715		if (atts) for (i=0; atts[i]; i++) {
716			if (!strcmp((char *)atts[i], EL_VERSION)) {
717				/* nothing for now */
718			}
719		}
720	} else if (!strcmp((char *)name, EL_STRING)) {
721		ent.type = ST_STRING;
722		SET_STACK_VARNAME;
723
724		ZVAL_STR(&ent.data, ZSTR_EMPTY_ALLOC());
725		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
726	} else if (!strcmp((char *)name, EL_BINARY)) {
727		ent.type = ST_BINARY;
728		SET_STACK_VARNAME;
729
730		ZVAL_STR(&ent.data, ZSTR_EMPTY_ALLOC());
731		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
732	} else if (!strcmp((char *)name, EL_CHAR)) {
733		int i;
734
735		if (atts) for (i = 0; atts[i]; i++) {
736			if (!strcmp((char *)atts[i], EL_CHAR_CODE) && atts[++i] && atts[i][0]) {
737				char tmp_buf[2];
738
739				snprintf(tmp_buf, sizeof(tmp_buf), "%c", (char)strtol((char *)atts[i], NULL, 16));
740				php_wddx_process_data(user_data, (XML_Char *) tmp_buf, strlen(tmp_buf));
741				break;
742			}
743		}
744	} else if (!strcmp((char *)name, EL_NUMBER)) {
745		ent.type = ST_NUMBER;
746		SET_STACK_VARNAME;
747
748		ZVAL_LONG(&ent.data, 0);
749		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
750	} else if (!strcmp((char *)name, EL_BOOLEAN)) {
751		int i;
752
753		if (atts) for (i = 0; atts[i]; i++) {
754			if (!strcmp((char *)atts[i], EL_VALUE) && atts[++i] && atts[i][0]) {
755				ent.type = ST_BOOLEAN;
756				SET_STACK_VARNAME;
757
758				ZVAL_TRUE(&ent.data);
759				wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
760				php_wddx_process_data(user_data, atts[i], strlen((char *)atts[i]));
761				break;
762			}
763		}
764	} else if (!strcmp((char *)name, EL_NULL)) {
765		ent.type = ST_NULL;
766		SET_STACK_VARNAME;
767
768		ZVAL_NULL(&ent.data);
769
770		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
771	} else if (!strcmp((char *)name, EL_ARRAY)) {
772		ent.type = ST_ARRAY;
773		SET_STACK_VARNAME;
774
775		array_init(&ent.data);
776		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
777	} else if (!strcmp((char *)name, EL_STRUCT)) {
778		ent.type = ST_STRUCT;
779		SET_STACK_VARNAME;
780		array_init(&ent.data);
781		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
782	} else if (!strcmp((char *)name, EL_VAR)) {
783		int i;
784
785		if (atts) for (i = 0; atts[i]; i++) {
786			if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
787				stack->varname = estrdup((char *)atts[i]);
788				break;
789			}
790		}
791	} else if (!strcmp((char *)name, EL_RECORDSET)) {
792		int i;
793
794		ent.type = ST_RECORDSET;
795		SET_STACK_VARNAME;
796		array_init(&ent.data);
797
798		if (atts) for (i = 0; atts[i]; i++) {
799			if (!strcmp((char *)atts[i], "fieldNames") && atts[++i] && atts[i][0]) {
800				zval tmp;
801				char *key;
802				const char *p1, *p2, *endp;
803
804				endp = (char *)atts[i] + strlen((char *)atts[i]);
805				p1 = (char *)atts[i];
806				while ((p2 = php_memnstr(p1, ",", sizeof(",")-1, endp)) != NULL) {
807					key = estrndup(p1, p2 - p1);
808					array_init(&tmp);
809					add_assoc_zval_ex(&ent.data, key, p2 - p1, &tmp);
810					p1 = p2 + sizeof(",")-1;
811					efree(key);
812				}
813
814				if (p1 <= endp) {
815					array_init(&tmp);
816					add_assoc_zval_ex(&ent.data, p1, endp - p1, &tmp);
817				}
818
819				break;
820			}
821		}
822
823		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
824	} else if (!strcmp((char *)name, EL_FIELD)) {
825		int i;
826		st_entry ent;
827
828		ent.type = ST_FIELD;
829		ent.varname = NULL;
830		ZVAL_UNDEF(&ent.data);
831
832		if (atts) for (i = 0; atts[i]; i++) {
833			if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
834				st_entry *recordset;
835				zval *field;
836
837				if (wddx_stack_top(stack, (void**)&recordset) == SUCCESS &&
838					recordset->type == ST_RECORDSET &&
839					(field = zend_hash_str_find(Z_ARRVAL(recordset->data), (char*)atts[i], strlen((char *)atts[i]))) != NULL) {
840					ZVAL_COPY_VALUE(&ent.data, field);
841				}
842
843				break;
844			}
845		}
846
847		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
848	} else if (!strcmp((char *)name, EL_DATETIME)) {
849		ent.type = ST_DATETIME;
850		SET_STACK_VARNAME;
851
852		ZVAL_LONG(&ent.data, 0);
853		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
854	}
855}
856/* }}} */
857
858/* {{{ php_wddx_pop_element
859 */
860static void php_wddx_pop_element(void *user_data, const XML_Char *name)
861{
862	st_entry 			*ent1, *ent2;
863	wddx_stack 			*stack = (wddx_stack *)user_data;
864	HashTable 			*target_hash;
865	zend_class_entry 	*pce;
866	zval				obj;
867
868/* OBJECTS_FIXME */
869	if (stack->top == 0) {
870		return;
871	}
872
873	if (!strcmp((char *)name, EL_STRING) || !strcmp((char *)name, EL_NUMBER) ||
874		!strcmp((char *)name, EL_BOOLEAN) || !strcmp((char *)name, EL_NULL) ||
875	  	!strcmp((char *)name, EL_ARRAY) || !strcmp((char *)name, EL_STRUCT) ||
876		!strcmp((char *)name, EL_RECORDSET) || !strcmp((char *)name, EL_BINARY) ||
877		!strcmp((char *)name, EL_DATETIME)) {
878		wddx_stack_top(stack, (void**)&ent1);
879
880		if (Z_TYPE(ent1->data) == IS_UNDEF) {
881			if (stack->top > 1) {
882				stack->top--;
883			} else {
884				stack->done = 1;
885			}
886			efree(ent1);
887			return;
888		}
889
890		if (!strcmp((char *)name, EL_BINARY)) {
891			zend_string *new_str = php_base64_decode(
892				(unsigned char *)Z_STRVAL(ent1->data), Z_STRLEN(ent1->data));
893			zval_ptr_dtor(&ent1->data);
894			ZVAL_STR(&ent1->data, new_str);
895		}
896
897		/* Call __wakeup() method on the object. */
898		if (Z_TYPE(ent1->data) == IS_OBJECT) {
899			zval fname, retval;
900
901			ZVAL_STRING(&fname, "__wakeup");
902
903			call_user_function_ex(NULL, &ent1->data, &fname, &retval, 0, 0, 0, NULL);
904
905			zval_ptr_dtor(&fname);
906			zval_ptr_dtor(&retval);
907		}
908
909		if (stack->top > 1) {
910			stack->top--;
911			wddx_stack_top(stack, (void**)&ent2);
912
913			/* if non-existent field */
914			if (ent2->type == ST_FIELD && Z_ISUNDEF(ent2->data)) {
915				zval_ptr_dtor(&ent1->data);
916				efree(ent1);
917				return;
918			}
919
920			if (Z_TYPE(ent2->data) == IS_ARRAY || Z_TYPE(ent2->data) == IS_OBJECT) {
921				target_hash = HASH_OF(&ent2->data);
922
923				if (ent1->varname) {
924					if (!strcmp(ent1->varname, PHP_CLASS_NAME_VAR) &&
925						Z_TYPE(ent1->data) == IS_STRING && Z_STRLEN(ent1->data) &&
926						ent2->type == ST_STRUCT && Z_TYPE(ent2->data) == IS_ARRAY) {
927						zend_bool incomplete_class = 0;
928
929						zend_str_tolower(Z_STRVAL(ent1->data), Z_STRLEN(ent1->data));
930						zend_string_forget_hash_val(Z_STR(ent1->data));
931						if ((pce = zend_hash_find_ptr(EG(class_table), Z_STR(ent1->data))) == NULL) {
932							incomplete_class = 1;
933							pce = PHP_IC_ENTRY;
934						}
935
936						/* Initialize target object */
937						object_init_ex(&obj, pce);
938
939						/* Merge current hashtable with object's default properties */
940						zend_hash_merge(Z_OBJPROP(obj),
941										Z_ARRVAL(ent2->data),
942										zval_add_ref, 0);
943
944						if (incomplete_class) {
945							php_store_class_name(&obj, Z_STRVAL(ent1->data), Z_STRLEN(ent1->data));
946						}
947
948						/* Clean up old array entry */
949						zval_ptr_dtor(&ent2->data);
950
951						/* Set stack entry to point to the newly created object */
952						ZVAL_COPY_VALUE(&ent2->data, &obj);
953
954						/* Clean up class name var entry */
955						zval_ptr_dtor(&ent1->data);
956					} else if (Z_TYPE(ent2->data) == IS_OBJECT) {
957						zend_update_property(Z_OBJCE(ent2->data), &ent2->data, ent1->varname, strlen(ent1->varname), &ent1->data);
958						if Z_REFCOUNTED(ent1->data) Z_DELREF(ent1->data);
959					} else {
960						zend_symtable_str_update(target_hash, ent1->varname, strlen(ent1->varname), &ent1->data);
961					}
962					efree(ent1->varname);
963				} else	{
964					zend_hash_next_index_insert(target_hash, &ent1->data);
965				}
966			}
967			efree(ent1);
968		} else {
969			stack->done = 1;
970		}
971	} else if (!strcmp((char *)name, EL_VAR) && stack->varname) {
972		efree(stack->varname);
973		stack->varname = NULL;
974	} else if (!strcmp((char *)name, EL_FIELD)) {
975		st_entry *ent;
976		wddx_stack_top(stack, (void **)&ent);
977		efree(ent);
978		stack->top--;
979	}
980}
981/* }}} */
982
983/* {{{ php_wddx_process_data
984 */
985static void php_wddx_process_data(void *user_data, const XML_Char *s, int len)
986{
987	st_entry *ent;
988	wddx_stack *stack = (wddx_stack *)user_data;
989
990	if (!wddx_stack_is_empty(stack) && !stack->done) {
991		wddx_stack_top(stack, (void**)&ent);
992		switch (ent->type) {
993			case ST_BINARY:
994			case ST_STRING:
995				if (Z_STRLEN(ent->data) == 0) {
996					zval_ptr_dtor(&ent->data);
997					ZVAL_STRINGL(&ent->data, (char *)s, len);
998				} else {
999					Z_STR(ent->data) = zend_string_extend(Z_STR(ent->data), Z_STRLEN(ent->data) + len, 0);
1000					memcpy(Z_STRVAL(ent->data) + Z_STRLEN(ent->data) - len, (char *)s, len);
1001					Z_STRVAL(ent->data)[Z_STRLEN(ent->data)] = '\0';
1002				}
1003				break;
1004			case ST_NUMBER:
1005				ZVAL_STRINGL(&ent->data, (char *)s, len);
1006				convert_scalar_to_number(&ent->data);
1007				break;
1008
1009			case ST_BOOLEAN:
1010				if (!strcmp((char *)s, "true")) {
1011					Z_LVAL(ent->data) = 1;
1012				} else if (!strcmp((char *)s, "false")) {
1013					Z_LVAL(ent->data) = 0;
1014				} else {
1015					zval_ptr_dtor(&ent->data);
1016					if (ent->varname) {
1017						efree(ent->varname);
1018					}
1019					ZVAL_UNDEF(&ent->data);
1020				}
1021				break;
1022
1023			case ST_DATETIME: {
1024				char *tmp;
1025
1026				tmp = emalloc(len + 1);
1027				memcpy(tmp, (char *)s, len);
1028				tmp[len] = '\0';
1029
1030				Z_LVAL(ent->data) = php_parse_date(tmp, NULL);
1031				/* date out of range < 1969 or > 2038 */
1032				if (Z_LVAL(ent->data) == -1) {
1033					ZVAL_STRINGL(&ent->data, (char *)s, len);
1034				}
1035				efree(tmp);
1036			}
1037				break;
1038
1039			default:
1040				break;
1041		}
1042	}
1043}
1044/* }}} */
1045
1046/* {{{ php_wddx_deserialize_ex
1047 */
1048int php_wddx_deserialize_ex(const char *value, size_t vallen, zval *return_value)
1049{
1050	wddx_stack stack;
1051	XML_Parser parser;
1052	st_entry *ent;
1053	int retval;
1054
1055	wddx_stack_init(&stack);
1056	parser = XML_ParserCreate((XML_Char *) "UTF-8");
1057
1058	XML_SetUserData(parser, &stack);
1059	XML_SetElementHandler(parser, php_wddx_push_element, php_wddx_pop_element);
1060	XML_SetCharacterDataHandler(parser, php_wddx_process_data);
1061
1062	/* XXX value should be parsed in the loop to exhaust size_t */
1063	XML_Parse(parser, (const XML_Char *) value, (int)vallen, 1);
1064
1065	XML_ParserFree(parser);
1066
1067	if (stack.top == 1) {
1068		wddx_stack_top(&stack, (void**)&ent);
1069		ZVAL_COPY(return_value, &ent->data);
1070		retval = SUCCESS;
1071	} else {
1072		retval = FAILURE;
1073	}
1074
1075	wddx_stack_destroy(&stack);
1076
1077	return retval;
1078}
1079/* }}} */
1080
1081/* {{{ proto string wddx_serialize_value(mixed var [, string comment])
1082   Creates a new packet and serializes the given value */
1083PHP_FUNCTION(wddx_serialize_value)
1084{
1085	zval *var;
1086	char *comment = NULL;
1087	size_t comment_len = 0;
1088	wddx_packet *packet;
1089
1090	if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|s", &var, &comment, &comment_len) == FAILURE) {
1091		return;
1092	}
1093
1094	packet = php_wddx_constructor();
1095
1096	php_wddx_packet_start(packet, comment, comment_len);
1097	php_wddx_serialize_var(packet, var, NULL);
1098	php_wddx_packet_end(packet);
1099	smart_str_0(packet);
1100
1101	RETVAL_STR_COPY(packet->s);
1102	php_wddx_destructor(packet);
1103}
1104/* }}} */
1105
1106/* {{{ proto string wddx_serialize_vars(mixed var_name [, mixed ...])
1107   Creates a new packet and serializes given variables into a struct */
1108PHP_FUNCTION(wddx_serialize_vars)
1109{
1110	int num_args, i;
1111	wddx_packet *packet;
1112	zval *args = NULL;
1113
1114	if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &num_args) == FAILURE) {
1115		return;
1116	}
1117
1118	packet = php_wddx_constructor();
1119
1120	php_wddx_packet_start(packet, NULL, 0);
1121	php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
1122
1123	for (i=0; i<num_args; i++) {
1124		zval *arg;
1125		if (!Z_ISREF(args[i])) {
1126			arg = &args[i];
1127		} else {
1128			arg = Z_REFVAL(args[i]);
1129		}
1130		if (Z_TYPE_P(arg) != IS_ARRAY && Z_TYPE_P(arg) != IS_OBJECT) {
1131			convert_to_string_ex(arg);
1132		}
1133		php_wddx_add_var(packet, arg);
1134	}
1135
1136	php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
1137	php_wddx_packet_end(packet);
1138	smart_str_0(packet);
1139
1140	RETVAL_STR_COPY(packet->s);
1141	php_wddx_destructor(packet);
1142}
1143/* }}} */
1144
1145/* {{{ php_wddx_constructor
1146 */
1147wddx_packet *php_wddx_constructor(void)
1148{
1149	smart_str *packet;
1150
1151	packet = ecalloc(1, sizeof(smart_str));
1152
1153	return packet;
1154}
1155/* }}} */
1156
1157/* {{{ php_wddx_destructor
1158 */
1159void php_wddx_destructor(wddx_packet *packet)
1160{
1161	smart_str_free(packet);
1162	efree(packet);
1163}
1164/* }}} */
1165
1166/* {{{ proto resource wddx_packet_start([string comment])
1167   Starts a WDDX packet with optional comment and returns the packet id */
1168PHP_FUNCTION(wddx_packet_start)
1169{
1170	char *comment = NULL;
1171	size_t comment_len = 0;
1172	wddx_packet *packet;
1173
1174	comment = NULL;
1175
1176	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &comment, &comment_len) == FAILURE) {
1177		return;
1178	}
1179
1180	packet = php_wddx_constructor();
1181
1182	php_wddx_packet_start(packet, comment, comment_len);
1183	php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
1184
1185	RETURN_RES(zend_register_resource(packet, le_wddx));
1186}
1187/* }}} */
1188
1189/* {{{ proto string wddx_packet_end(resource packet_id)
1190   Ends specified WDDX packet and returns the string containing the packet */
1191PHP_FUNCTION(wddx_packet_end)
1192{
1193	zval *packet_id;
1194	wddx_packet *packet = NULL;
1195
1196	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &packet_id) == FAILURE) {
1197		return;
1198	}
1199
1200	if ((packet = (wddx_packet *)zend_fetch_resource(Z_RES_P(packet_id), "WDDX packet ID", le_wddx)) == NULL) {
1201		RETURN_FALSE;
1202	}
1203
1204	php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
1205
1206	php_wddx_packet_end(packet);
1207	smart_str_0(packet);
1208
1209	RETVAL_STR_COPY(packet->s);
1210
1211	zend_list_close(Z_RES_P(packet_id));
1212}
1213/* }}} */
1214
1215/* {{{ proto bool wddx_add_vars(resource packet_id,  mixed var_names [, mixed ...])
1216   Serializes given variables and adds them to packet given by packet_id */
1217PHP_FUNCTION(wddx_add_vars)
1218{
1219	int num_args, i;
1220	zval *args = NULL;
1221	zval *packet_id;
1222	wddx_packet *packet = NULL;
1223
1224	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r+", &packet_id, &args, &num_args) == FAILURE) {
1225		return;
1226	}
1227
1228	if ((packet = (wddx_packet *)zend_fetch_resource(Z_RES_P(packet_id), "WDDX packet ID", le_wddx)) == NULL) {
1229		RETURN_FALSE;
1230	}
1231
1232	for (i=0; i<num_args; i++) {
1233		zval *arg;
1234		if (!Z_ISREF(args[i])) {
1235			arg = &args[i];
1236		} else {
1237			arg = Z_REFVAL(args[i]);
1238		}
1239		if (Z_TYPE_P(arg) != IS_ARRAY && Z_TYPE_P(arg) != IS_OBJECT) {
1240			convert_to_string_ex(arg);
1241		}
1242		php_wddx_add_var(packet, arg);
1243	}
1244
1245	RETURN_TRUE;
1246}
1247/* }}} */
1248
1249/* {{{ proto mixed wddx_deserialize(mixed packet)
1250   Deserializes given packet and returns a PHP value */
1251PHP_FUNCTION(wddx_deserialize)
1252{
1253	zval *packet;
1254	php_stream *stream = NULL;
1255	zend_string *payload = NULL;
1256
1257	if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &packet) == FAILURE) {
1258		return;
1259	}
1260
1261	if (Z_TYPE_P(packet) == IS_STRING) {
1262		payload = Z_STR_P(packet);
1263	} else if (Z_TYPE_P(packet) == IS_RESOURCE) {
1264		php_stream_from_zval(stream, packet);
1265		if (stream) {
1266			payload = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
1267		}
1268	} else {
1269		php_error_docref(NULL, E_WARNING, "Expecting parameter 1 to be a string or a stream");
1270		return;
1271	}
1272
1273	if (payload == NULL) {
1274		return;
1275	}
1276
1277	php_wddx_deserialize_ex(ZSTR_VAL(payload), ZSTR_LEN(payload), return_value);
1278
1279	if (stream) {
1280		efree(payload);
1281	}
1282}
1283/* }}} */
1284
1285#endif /* HAVE_LIBEXPAT */
1286
1287/*
1288 * Local variables:
1289 * tab-width: 4
1290 * c-basic-offset: 4
1291 * End:
1292 * vim600: sw=4 ts=4 fdm=marker
1293 * vim<600: sw=4 ts=4
1294 */
1295