1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2015 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        ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(retval), idx, key, ent) {
302            if (key == NULL) {
303                key = zend_long_to_str(idx);
304            } else {
305                zend_string_addref(key);
306            }
307            if (php_set_session_var(key, ent, NULL)) {
308                if (Z_REFCOUNTED_P(ent)) Z_ADDREF_P(ent);
309            }
310            PS_ADD_VAR(key);
311            zend_string_release(key);
312        } ZEND_HASH_FOREACH_END();
313    }
314
315    zval_ptr_dtor(&retval);
316
317    return ret;
318}
319/* }}} */
320#endif
321
322/* {{{ PHP_MINIT_FUNCTION
323 */
324PHP_MINIT_FUNCTION(wddx)
325{
326    le_wddx = zend_register_list_destructors_ex(release_wddx_packet_rsrc, NULL, "wddx", module_number);
327
328#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION)
329    php_session_register_serializer("wddx",
330                                    PS_SERIALIZER_ENCODE_NAME(wddx),
331                                    PS_SERIALIZER_DECODE_NAME(wddx));
332#endif
333
334    return SUCCESS;
335}
336/* }}} */
337
338/* {{{ PHP_MINFO_FUNCTION
339 */
340PHP_MINFO_FUNCTION(wddx)
341{
342    php_info_print_table_start();
343#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION)
344    php_info_print_table_header(2, "WDDX Support", "enabled" );
345    php_info_print_table_row(2, "WDDX Session Serializer", "enabled" );
346#else
347    php_info_print_table_row(2, "WDDX Support", "enabled" );
348#endif
349    php_info_print_table_end();
350}
351/* }}} */
352
353/* {{{ php_wddx_packet_start
354 */
355void php_wddx_packet_start(wddx_packet *packet, char *comment, size_t comment_len)
356{
357    php_wddx_add_chunk_static(packet, WDDX_PACKET_S);
358    if (comment) {
359        php_wddx_add_chunk_static(packet, WDDX_HEADER_S);
360        php_wddx_add_chunk_static(packet, WDDX_COMMENT_S);
361        php_wddx_add_chunk_ex(packet, comment, comment_len);
362        php_wddx_add_chunk_static(packet, WDDX_COMMENT_E);
363        php_wddx_add_chunk_static(packet, WDDX_HEADER_E);
364    } else {
365        php_wddx_add_chunk_static(packet, WDDX_HEADER);
366    }
367    php_wddx_add_chunk_static(packet, WDDX_DATA_S);
368}
369/* }}} */
370
371/* {{{ php_wddx_packet_end
372 */
373void php_wddx_packet_end(wddx_packet *packet)
374{
375    php_wddx_add_chunk_static(packet, WDDX_DATA_E);
376    php_wddx_add_chunk_static(packet, WDDX_PACKET_E);
377}
378/* }}} */
379
380#define FLUSH_BUF()                               \
381    if (l > 0) {                                  \
382        php_wddx_add_chunk_ex(packet, buf, l);    \
383        l = 0;                                    \
384    }
385
386/* {{{ php_wddx_serialize_string
387 */
388static void php_wddx_serialize_string(wddx_packet *packet, zval *var)
389{
390    php_wddx_add_chunk_static(packet, WDDX_STRING_S);
391
392    if (Z_STRLEN_P(var) > 0) {
393        zend_string *buf = php_escape_html_entities(
394            (unsigned char *) Z_STRVAL_P(var), Z_STRLEN_P(var), 0, ENT_QUOTES, NULL);
395
396        php_wddx_add_chunk_ex(packet, ZSTR_VAL(buf), ZSTR_LEN(buf));
397
398        zend_string_release(buf);
399    }
400    php_wddx_add_chunk_static(packet, WDDX_STRING_E);
401}
402/* }}} */
403
404/* {{{ php_wddx_serialize_number
405 */
406static void php_wddx_serialize_number(wddx_packet *packet, zval *var)
407{
408    char tmp_buf[WDDX_BUF_LEN];
409    zend_string *str = zval_get_string(var);
410    snprintf(tmp_buf, sizeof(tmp_buf), WDDX_NUMBER, ZSTR_VAL(str));
411    zend_string_release(str);
412
413    php_wddx_add_chunk(packet, tmp_buf);
414}
415/* }}} */
416
417/* {{{ php_wddx_serialize_boolean
418 */
419static void php_wddx_serialize_boolean(wddx_packet *packet, zval *var)
420{
421    php_wddx_add_chunk(packet, Z_TYPE_P(var) == IS_TRUE ? WDDX_BOOLEAN_TRUE : WDDX_BOOLEAN_FALSE);
422}
423/* }}} */
424
425/* {{{ php_wddx_serialize_unset
426 */
427static void php_wddx_serialize_unset(wddx_packet *packet)
428{
429    php_wddx_add_chunk_static(packet, WDDX_NULL);
430}
431/* }}} */
432
433/* {{{ php_wddx_serialize_object
434 */
435static void php_wddx_serialize_object(wddx_packet *packet, zval *obj)
436{
437/* OBJECTS_FIXME */
438    zval *ent, fname, *varname;
439    zval retval;
440    zend_string *key;
441    zend_ulong idx;
442    char tmp_buf[WDDX_BUF_LEN];
443    HashTable *objhash, *sleephash;
444
445    ZVAL_STRING(&fname, "__sleep");
446    /*
447     * We try to call __sleep() method on object. It's supposed to return an
448     * array of property names to be serialized.
449     */
450    if (call_user_function_ex(CG(function_table), obj, &fname, &retval, 0, 0, 1, NULL) == SUCCESS) {
451        if (!Z_ISUNDEF(retval) && (sleephash = HASH_OF(&retval))) {
452            PHP_CLASS_ATTRIBUTES;
453
454            PHP_SET_CLASS_ATTRIBUTES(obj);
455
456            php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
457            snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR);
458            php_wddx_add_chunk(packet, tmp_buf);
459            php_wddx_add_chunk_static(packet, WDDX_STRING_S);
460            php_wddx_add_chunk_ex(packet, ZSTR_VAL(class_name), ZSTR_LEN(class_name));
461            php_wddx_add_chunk_static(packet, WDDX_STRING_E);
462            php_wddx_add_chunk_static(packet, WDDX_VAR_E);
463
464            PHP_CLEANUP_CLASS_ATTRIBUTES();
465
466            objhash = HASH_OF(obj);
467
468            ZEND_HASH_FOREACH_VAL(sleephash, varname) {
469                if (Z_TYPE_P(varname) != IS_STRING) {
470                    php_error_docref(NULL, E_NOTICE, "__sleep should return an array only containing the names of instance-variables to serialize.");
471                    continue;
472                }
473
474                if ((ent = zend_hash_find(objhash, Z_STR_P(varname))) != NULL) {
475                    php_wddx_serialize_var(packet, ent, Z_STR_P(varname));
476                }
477            } ZEND_HASH_FOREACH_END();
478
479            php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
480        }
481    } else {
482        PHP_CLASS_ATTRIBUTES;
483
484        PHP_SET_CLASS_ATTRIBUTES(obj);
485
486        php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
487        snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR);
488        php_wddx_add_chunk(packet, tmp_buf);
489        php_wddx_add_chunk_static(packet, WDDX_STRING_S);
490        php_wddx_add_chunk_ex(packet, ZSTR_VAL(class_name), ZSTR_LEN(class_name));
491        php_wddx_add_chunk_static(packet, WDDX_STRING_E);
492        php_wddx_add_chunk_static(packet, WDDX_VAR_E);
493
494        PHP_CLEANUP_CLASS_ATTRIBUTES();
495
496        objhash = HASH_OF(obj);
497        ZEND_HASH_FOREACH_KEY_VAL(objhash, idx, key, ent) {
498            if (ent == obj) {
499                continue;
500            }
501            if (key) {
502                const char *class_name, *prop_name;
503                size_t prop_name_len;
504                zend_string *tmp;
505
506                zend_unmangle_property_name_ex(key, &class_name, &prop_name, &prop_name_len);
507                tmp = zend_string_init(prop_name, prop_name_len, 0);
508                php_wddx_serialize_var(packet, ent, tmp);
509                zend_string_release(tmp);
510            } else {
511                key = zend_long_to_str(idx);
512                php_wddx_serialize_var(packet, ent, key);
513                zend_string_release(key);
514            }
515        } ZEND_HASH_FOREACH_END();
516        php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
517    }
518
519    zval_ptr_dtor(&fname);
520    zval_ptr_dtor(&retval);
521}
522/* }}} */
523
524/* {{{ php_wddx_serialize_array
525 */
526static void php_wddx_serialize_array(wddx_packet *packet, zval *arr)
527{
528    zval *ent;
529    zend_string *key;
530    int is_struct = 0;
531    zend_ulong idx;
532    HashTable *target_hash;
533    char tmp_buf[WDDX_BUF_LEN];
534    zend_ulong ind = 0;
535
536    target_hash = HASH_OF(arr);
537    ZEND_HASH_FOREACH_KEY(target_hash, idx, key) {
538        if (key) {
539            is_struct = 1;
540            break;
541        }
542
543        if (idx != ind) {
544            is_struct = 1;
545            break;
546        }
547        ind++;
548    } ZEND_HASH_FOREACH_END();
549
550    if (is_struct) {
551        php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
552    } else {
553        snprintf(tmp_buf, sizeof(tmp_buf), WDDX_ARRAY_S, zend_hash_num_elements(target_hash));
554        php_wddx_add_chunk(packet, tmp_buf);
555    }
556
557    ZEND_HASH_FOREACH_KEY_VAL(target_hash, idx, key, ent) {
558        if (ent == arr) {
559            continue;
560        }
561
562        if (is_struct) {
563            if (key) {
564                php_wddx_serialize_var(packet, ent, key);
565            } else {
566                key = zend_long_to_str(idx);
567                php_wddx_serialize_var(packet, ent, key);
568                zend_string_release(key);
569            }
570        } else {
571            php_wddx_serialize_var(packet, ent, NULL);
572        }
573    } ZEND_HASH_FOREACH_END();
574
575    if (is_struct) {
576        php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
577    } else {
578        php_wddx_add_chunk_static(packet, WDDX_ARRAY_E);
579    }
580}
581/* }}} */
582
583/* {{{ php_wddx_serialize_var
584 */
585void php_wddx_serialize_var(wddx_packet *packet, zval *var, zend_string *name)
586{
587    HashTable *ht;
588
589    if (name) {
590        char *tmp_buf;
591        zend_string *name_esc = php_escape_html_entities((unsigned char *) ZSTR_VAL(name), ZSTR_LEN(name), 0, ENT_QUOTES, NULL);
592        tmp_buf = emalloc(ZSTR_LEN(name_esc) + sizeof(WDDX_VAR_S));
593        snprintf(tmp_buf, ZSTR_LEN(name_esc) + sizeof(WDDX_VAR_S), WDDX_VAR_S, ZSTR_VAL(name_esc));
594        php_wddx_add_chunk(packet, tmp_buf);
595        efree(tmp_buf);
596        zend_string_release(name_esc);
597    }
598
599    if (Z_TYPE_P(var) == IS_INDIRECT) {
600        var = Z_INDIRECT_P(var);
601    }
602    ZVAL_DEREF(var);
603    switch (Z_TYPE_P(var)) {
604        case IS_STRING:
605            php_wddx_serialize_string(packet, var);
606            break;
607
608        case IS_LONG:
609        case IS_DOUBLE:
610            php_wddx_serialize_number(packet, var);
611            break;
612
613        case IS_TRUE:
614        case IS_FALSE:
615            php_wddx_serialize_boolean(packet, var);
616            break;
617
618        case IS_NULL:
619            php_wddx_serialize_unset(packet);
620            break;
621
622        case IS_ARRAY:
623            ht = Z_ARRVAL_P(var);
624            if (ht->u.v.nApplyCount > 1) {
625                php_error_docref(NULL, E_RECOVERABLE_ERROR, "WDDX doesn't support circular references");
626                return;
627            }
628            if (ZEND_HASH_APPLY_PROTECTION(ht)) {
629                ht->u.v.nApplyCount++;
630            }
631            php_wddx_serialize_array(packet, var);
632            if (ZEND_HASH_APPLY_PROTECTION(ht)) {
633                ht->u.v.nApplyCount--;
634            }
635            break;
636
637        case IS_OBJECT:
638            ht = Z_OBJPROP_P(var);
639            if (ht->u.v.nApplyCount > 1) {
640                php_error_docref(NULL, E_RECOVERABLE_ERROR, "WDDX doesn't support circular references");
641                return;
642            }
643            ht->u.v.nApplyCount++;
644            php_wddx_serialize_object(packet, var);
645            ht->u.v.nApplyCount--;
646            break;
647    }
648
649    if (name) {
650        php_wddx_add_chunk_static(packet, WDDX_VAR_E);
651    }
652}
653/* }}} */
654
655/* {{{ php_wddx_add_var
656 */
657static void php_wddx_add_var(wddx_packet *packet, zval *name_var)
658{
659    zval *val;
660    HashTable *target_hash;
661
662    if (Z_TYPE_P(name_var) == IS_STRING) {
663        zend_array *symbol_table = zend_rebuild_symbol_table();
664        if ((val = zend_hash_find(symbol_table, Z_STR_P(name_var))) != NULL) {
665            if (Z_TYPE_P(val) == IS_INDIRECT) {
666                val = Z_INDIRECT_P(val);
667            }
668            php_wddx_serialize_var(packet, val, Z_STR_P(name_var));
669        }
670    } else if (Z_TYPE_P(name_var) == IS_ARRAY || Z_TYPE_P(name_var) == IS_OBJECT)   {
671        int is_array = Z_TYPE_P(name_var) == IS_ARRAY;
672
673        target_hash = HASH_OF(name_var);
674
675        if (is_array && target_hash->u.v.nApplyCount > 1) {
676            php_error_docref(NULL, E_WARNING, "recursion detected");
677            return;
678        }
679
680        if (Z_IMMUTABLE_P(name_var)) {
681            ZEND_HASH_FOREACH_VAL(target_hash, val) {
682                php_wddx_add_var(packet, val);
683            } ZEND_HASH_FOREACH_END();
684        } else {
685            ZEND_HASH_FOREACH_VAL(target_hash, val) {
686                if (is_array) {
687                    target_hash->u.v.nApplyCount++;
688                }
689
690                ZVAL_DEREF(val);
691                php_wddx_add_var(packet, val);
692
693                if (is_array) {
694                    target_hash->u.v.nApplyCount--;
695                }
696            } ZEND_HASH_FOREACH_END();
697        }
698    }
699}
700/* }}} */
701
702/* {{{ php_wddx_push_element
703 */
704static void php_wddx_push_element(void *user_data, const XML_Char *name, const XML_Char **atts)
705{
706    st_entry ent;
707    wddx_stack *stack = (wddx_stack *)user_data;
708    if (!strcmp((char *)name, EL_PACKET)) {
709        int i;
710
711        if (atts) for (i=0; atts[i]; i++) {
712            if (!strcmp((char *)atts[i], EL_VERSION)) {
713                /* nothing for now */
714            }
715        }
716    } else if (!strcmp((char *)name, EL_STRING)) {
717        ent.type = ST_STRING;
718        SET_STACK_VARNAME;
719
720        ZVAL_STR(&ent.data, ZSTR_EMPTY_ALLOC());
721        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
722    } else if (!strcmp((char *)name, EL_BINARY)) {
723        ent.type = ST_BINARY;
724        SET_STACK_VARNAME;
725
726        ZVAL_STR(&ent.data, ZSTR_EMPTY_ALLOC());
727        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
728    } else if (!strcmp((char *)name, EL_CHAR)) {
729        int i;
730
731        if (atts) for (i = 0; atts[i]; i++) {
732            if (!strcmp((char *)atts[i], EL_CHAR_CODE) && atts[++i] && atts[i][0]) {
733                char tmp_buf[2];
734
735                snprintf(tmp_buf, sizeof(tmp_buf), "%c", (char)strtol((char *)atts[i], NULL, 16));
736                php_wddx_process_data(user_data, (XML_Char *) tmp_buf, strlen(tmp_buf));
737                break;
738            }
739        }
740    } else if (!strcmp((char *)name, EL_NUMBER)) {
741        ent.type = ST_NUMBER;
742        SET_STACK_VARNAME;
743
744        ZVAL_LONG(&ent.data, 0);
745        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
746    } else if (!strcmp((char *)name, EL_BOOLEAN)) {
747        int i;
748
749        if (atts) for (i = 0; atts[i]; i++) {
750            if (!strcmp((char *)atts[i], EL_VALUE) && atts[++i] && atts[i][0]) {
751                ent.type = ST_BOOLEAN;
752                SET_STACK_VARNAME;
753
754                ZVAL_TRUE(&ent.data);
755                wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
756                php_wddx_process_data(user_data, atts[i], strlen((char *)atts[i]));
757                break;
758            }
759        }
760    } else if (!strcmp((char *)name, EL_NULL)) {
761        ent.type = ST_NULL;
762        SET_STACK_VARNAME;
763
764        ZVAL_NULL(&ent.data);
765
766        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
767    } else if (!strcmp((char *)name, EL_ARRAY)) {
768        ent.type = ST_ARRAY;
769        SET_STACK_VARNAME;
770
771        array_init(&ent.data);
772        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
773    } else if (!strcmp((char *)name, EL_STRUCT)) {
774        ent.type = ST_STRUCT;
775        SET_STACK_VARNAME;
776        array_init(&ent.data);
777        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
778    } else if (!strcmp((char *)name, EL_VAR)) {
779        int i;
780
781        if (atts) for (i = 0; atts[i]; i++) {
782            if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
783                stack->varname = estrdup((char *)atts[i]);
784                break;
785            }
786        }
787    } else if (!strcmp((char *)name, EL_RECORDSET)) {
788        int i;
789
790        ent.type = ST_RECORDSET;
791        SET_STACK_VARNAME;
792        array_init(&ent.data);
793
794        if (atts) for (i = 0; atts[i]; i++) {
795            if (!strcmp((char *)atts[i], "fieldNames") && atts[++i] && atts[i][0]) {
796                zval tmp;
797                char *key;
798                const char *p1, *p2, *endp;
799
800                endp = (char *)atts[i] + strlen((char *)atts[i]);
801                p1 = (char *)atts[i];
802                while ((p2 = php_memnstr(p1, ",", sizeof(",")-1, endp)) != NULL) {
803                    key = estrndup(p1, p2 - p1);
804                    array_init(&tmp);
805                    add_assoc_zval_ex(&ent.data, key, p2 - p1, &tmp);
806                    p1 = p2 + sizeof(",")-1;
807                    efree(key);
808                }
809
810                if (p1 <= endp) {
811                    array_init(&tmp);
812                    add_assoc_zval_ex(&ent.data, p1, endp - p1, &tmp);
813                }
814
815                break;
816            }
817        }
818
819        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
820    } else if (!strcmp((char *)name, EL_FIELD)) {
821        int i;
822        st_entry ent;
823
824        ent.type = ST_FIELD;
825        ent.varname = NULL;
826        ZVAL_UNDEF(&ent.data);
827
828        if (atts) for (i = 0; atts[i]; i++) {
829            if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
830                st_entry *recordset;
831                zval *field;
832
833                if (wddx_stack_top(stack, (void**)&recordset) == SUCCESS &&
834                    recordset->type == ST_RECORDSET &&
835                    (field = zend_hash_str_find(Z_ARRVAL(recordset->data), (char*)atts[i], strlen((char *)atts[i]))) != NULL) {
836                    ZVAL_COPY_VALUE(&ent.data, field);
837                }
838
839                break;
840            }
841        }
842
843        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
844    } else if (!strcmp((char *)name, EL_DATETIME)) {
845        ent.type = ST_DATETIME;
846        SET_STACK_VARNAME;
847
848        ZVAL_LONG(&ent.data, 0);
849        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
850    }
851}
852/* }}} */
853
854/* {{{ php_wddx_pop_element
855 */
856static void php_wddx_pop_element(void *user_data, const XML_Char *name)
857{
858    st_entry            *ent1, *ent2;
859    wddx_stack          *stack = (wddx_stack *)user_data;
860    HashTable           *target_hash;
861    zend_class_entry    *pce;
862    zval                obj;
863
864/* OBJECTS_FIXME */
865    if (stack->top == 0) {
866        return;
867    }
868
869    if (!strcmp((char *)name, EL_STRING) || !strcmp((char *)name, EL_NUMBER) ||
870        !strcmp((char *)name, EL_BOOLEAN) || !strcmp((char *)name, EL_NULL) ||
871        !strcmp((char *)name, EL_ARRAY) || !strcmp((char *)name, EL_STRUCT) ||
872        !strcmp((char *)name, EL_RECORDSET) || !strcmp((char *)name, EL_BINARY) ||
873        !strcmp((char *)name, EL_DATETIME)) {
874        wddx_stack_top(stack, (void**)&ent1);
875
876        if (!strcmp((char *)name, EL_BINARY)) {
877            zend_string *new_str = php_base64_decode(
878                (unsigned char *)Z_STRVAL(ent1->data), Z_STRLEN(ent1->data));
879            zval_ptr_dtor(&ent1->data);
880            ZVAL_STR(&ent1->data, new_str);
881        }
882
883        /* Call __wakeup() method on the object. */
884        if (Z_TYPE(ent1->data) == IS_OBJECT) {
885            zval fname, retval;
886
887            ZVAL_STRING(&fname, "__wakeup");
888
889            call_user_function_ex(NULL, &ent1->data, &fname, &retval, 0, 0, 0, NULL);
890
891            zval_ptr_dtor(&fname);
892            zval_ptr_dtor(&retval);
893        }
894
895        if (stack->top > 1) {
896            stack->top--;
897            wddx_stack_top(stack, (void**)&ent2);
898
899            /* if non-existent field */
900            if (ent2->type == ST_FIELD && Z_ISUNDEF(ent2->data)) {
901                zval_ptr_dtor(&ent1->data);
902                efree(ent1);
903                return;
904            }
905
906            if (Z_TYPE(ent2->data) == IS_ARRAY || Z_TYPE(ent2->data) == IS_OBJECT) {
907                target_hash = HASH_OF(&ent2->data);
908
909                if (ent1->varname) {
910                    if (!strcmp(ent1->varname, PHP_CLASS_NAME_VAR) &&
911                        Z_TYPE(ent1->data) == IS_STRING && Z_STRLEN(ent1->data)) {
912                        zend_bool incomplete_class = 0;
913
914                        zend_str_tolower(Z_STRVAL(ent1->data), Z_STRLEN(ent1->data));
915                        zend_string_forget_hash_val(Z_STR(ent1->data));
916                        if ((pce = zend_hash_find_ptr(EG(class_table), Z_STR(ent1->data))) == NULL) {
917                            incomplete_class = 1;
918                            pce = PHP_IC_ENTRY;
919                        }
920
921                        /* Initialize target object */
922                        object_init_ex(&obj, pce);
923
924                        /* Merge current hashtable with object's default properties */
925                        zend_hash_merge(Z_OBJPROP(obj),
926                                        Z_ARRVAL(ent2->data),
927                                        zval_add_ref, 0);
928
929                        if (incomplete_class) {
930                            php_store_class_name(&obj, Z_STRVAL(ent1->data), Z_STRLEN(ent1->data));
931                        }
932
933                        /* Clean up old array entry */
934                        zval_ptr_dtor(&ent2->data);
935
936                        /* Set stack entry to point to the newly created object */
937                        ZVAL_COPY_VALUE(&ent2->data, &obj);
938
939                        /* Clean up class name var entry */
940                        zval_ptr_dtor(&ent1->data);
941                    } else if (Z_TYPE(ent2->data) == IS_OBJECT) {
942                        zend_class_entry *old_scope = EG(scope);
943
944                        EG(scope) = Z_OBJCE(ent2->data);
945                        add_property_zval(&ent2->data, ent1->varname, &ent1->data);
946                        if Z_REFCOUNTED(ent1->data) Z_DELREF(ent1->data);
947                        EG(scope) = old_scope;
948                    } else {
949                        zend_symtable_str_update(target_hash, ent1->varname, strlen(ent1->varname), &ent1->data);
950                    }
951                    efree(ent1->varname);
952                } else  {
953                    zend_hash_next_index_insert(target_hash, &ent1->data);
954                }
955            }
956            efree(ent1);
957        } else {
958            stack->done = 1;
959        }
960    } else if (!strcmp((char *)name, EL_VAR) && stack->varname) {
961        efree(stack->varname);
962    } else if (!strcmp((char *)name, EL_FIELD)) {
963        st_entry *ent;
964        wddx_stack_top(stack, (void **)&ent);
965        efree(ent);
966        stack->top--;
967    }
968}
969/* }}} */
970
971/* {{{ php_wddx_process_data
972 */
973static void php_wddx_process_data(void *user_data, const XML_Char *s, int len)
974{
975    st_entry *ent;
976    wddx_stack *stack = (wddx_stack *)user_data;
977
978    if (!wddx_stack_is_empty(stack) && !stack->done) {
979        wddx_stack_top(stack, (void**)&ent);
980        switch (ent->type) {
981            case ST_BINARY:
982            case ST_STRING:
983                if (Z_STRLEN(ent->data) == 0) {
984                    zval_ptr_dtor(&ent->data);
985                    ZVAL_STRINGL(&ent->data, (char *)s, len);
986                } else {
987                    Z_STR(ent->data) = zend_string_extend(Z_STR(ent->data), Z_STRLEN(ent->data) + len, 0);
988                    memcpy(Z_STRVAL(ent->data) + Z_STRLEN(ent->data) - len, (char *)s, len);
989                    Z_STRVAL(ent->data)[Z_STRLEN(ent->data)] = '\0';
990                }
991                break;
992            case ST_NUMBER:
993                ZVAL_STRINGL(&ent->data, (char *)s, len);
994                convert_scalar_to_number(&ent->data);
995                break;
996
997            case ST_BOOLEAN:
998                if (!strcmp((char *)s, "true")) {
999                    Z_LVAL(ent->data) = 1;
1000                } else if (!strcmp((char *)s, "false")) {
1001                    Z_LVAL(ent->data) = 0;
1002                } else {
1003                    stack->top--;
1004                    zval_ptr_dtor(&ent->data);
1005                    if (ent->varname)
1006                        efree(ent->varname);
1007                    efree(ent);
1008                }
1009                break;
1010
1011            case ST_DATETIME: {
1012                char *tmp;
1013
1014                tmp = emalloc(len + 1);
1015                memcpy(tmp, (char *)s, len);
1016                tmp[len] = '\0';
1017
1018                Z_LVAL(ent->data) = php_parse_date(tmp, NULL);
1019                /* date out of range < 1969 or > 2038 */
1020                if (Z_LVAL(ent->data) == -1) {
1021                    ZVAL_STRINGL(&ent->data, (char *)s, len);
1022                }
1023                efree(tmp);
1024            }
1025                break;
1026
1027            default:
1028                break;
1029        }
1030    }
1031}
1032/* }}} */
1033
1034/* {{{ php_wddx_deserialize_ex
1035 */
1036int php_wddx_deserialize_ex(const char *value, size_t vallen, zval *return_value)
1037{
1038    wddx_stack stack;
1039    XML_Parser parser;
1040    st_entry *ent;
1041    int retval;
1042
1043    wddx_stack_init(&stack);
1044    parser = XML_ParserCreate((XML_Char *) "UTF-8");
1045
1046    XML_SetUserData(parser, &stack);
1047    XML_SetElementHandler(parser, php_wddx_push_element, php_wddx_pop_element);
1048    XML_SetCharacterDataHandler(parser, php_wddx_process_data);
1049
1050    /* XXX value should be parsed in the loop to exhaust size_t */
1051    XML_Parse(parser, (const XML_Char *) value, (int)vallen, 1);
1052
1053    XML_ParserFree(parser);
1054
1055    if (stack.top == 1) {
1056        wddx_stack_top(&stack, (void**)&ent);
1057        ZVAL_COPY(return_value, &ent->data);
1058        retval = SUCCESS;
1059    } else {
1060        retval = FAILURE;
1061    }
1062
1063    wddx_stack_destroy(&stack);
1064
1065    return retval;
1066}
1067/* }}} */
1068
1069/* {{{ proto string wddx_serialize_value(mixed var [, string comment])
1070   Creates a new packet and serializes the given value */
1071PHP_FUNCTION(wddx_serialize_value)
1072{
1073    zval *var;
1074    char *comment = NULL;
1075    size_t comment_len = 0;
1076    wddx_packet *packet;
1077
1078    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|s", &var, &comment, &comment_len) == FAILURE) {
1079        return;
1080    }
1081
1082    packet = php_wddx_constructor();
1083
1084    php_wddx_packet_start(packet, comment, comment_len);
1085    php_wddx_serialize_var(packet, var, NULL);
1086    php_wddx_packet_end(packet);
1087    smart_str_0(packet);
1088
1089    RETVAL_STR_COPY(packet->s);
1090    php_wddx_destructor(packet);
1091}
1092/* }}} */
1093
1094/* {{{ proto string wddx_serialize_vars(mixed var_name [, mixed ...])
1095   Creates a new packet and serializes given variables into a struct */
1096PHP_FUNCTION(wddx_serialize_vars)
1097{
1098    int num_args, i;
1099    wddx_packet *packet;
1100    zval *args = NULL;
1101
1102    if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &num_args) == FAILURE) {
1103        return;
1104    }
1105
1106    packet = php_wddx_constructor();
1107
1108    php_wddx_packet_start(packet, NULL, 0);
1109    php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
1110
1111    for (i=0; i<num_args; i++) {
1112        zval *arg;
1113        if (!Z_ISREF(args[i])) {
1114            arg = &args[i];
1115        } else {
1116            arg = Z_REFVAL(args[i]);
1117        }
1118        if (Z_TYPE_P(arg) != IS_ARRAY && Z_TYPE_P(arg) != IS_OBJECT) {
1119            convert_to_string_ex(arg);
1120        }
1121        php_wddx_add_var(packet, arg);
1122    }
1123
1124    php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
1125    php_wddx_packet_end(packet);
1126    smart_str_0(packet);
1127
1128    RETVAL_STR_COPY(packet->s);
1129    php_wddx_destructor(packet);
1130}
1131/* }}} */
1132
1133/* {{{ php_wddx_constructor
1134 */
1135wddx_packet *php_wddx_constructor(void)
1136{
1137    smart_str *packet;
1138
1139    packet = ecalloc(1, sizeof(smart_str));
1140
1141    return packet;
1142}
1143/* }}} */
1144
1145/* {{{ php_wddx_destructor
1146 */
1147void php_wddx_destructor(wddx_packet *packet)
1148{
1149    smart_str_free(packet);
1150    efree(packet);
1151}
1152/* }}} */
1153
1154/* {{{ proto resource wddx_packet_start([string comment])
1155   Starts a WDDX packet with optional comment and returns the packet id */
1156PHP_FUNCTION(wddx_packet_start)
1157{
1158    char *comment = NULL;
1159    size_t comment_len = 0;
1160    wddx_packet *packet;
1161
1162    comment = NULL;
1163
1164    if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &comment, &comment_len) == FAILURE) {
1165        return;
1166    }
1167
1168    packet = php_wddx_constructor();
1169
1170    php_wddx_packet_start(packet, comment, comment_len);
1171    php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
1172
1173    RETURN_RES(zend_register_resource(packet, le_wddx));
1174}
1175/* }}} */
1176
1177/* {{{ proto string wddx_packet_end(resource packet_id)
1178   Ends specified WDDX packet and returns the string containing the packet */
1179PHP_FUNCTION(wddx_packet_end)
1180{
1181    zval *packet_id;
1182    wddx_packet *packet = NULL;
1183
1184    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &packet_id) == FAILURE) {
1185        return;
1186    }
1187
1188    if ((packet = (wddx_packet *)zend_fetch_resource(Z_RES_P(packet_id), "WDDX packet ID", le_wddx)) == NULL) {
1189        RETURN_FALSE;
1190    }
1191
1192    php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
1193
1194    php_wddx_packet_end(packet);
1195    smart_str_0(packet);
1196
1197    RETVAL_STR_COPY(packet->s);
1198
1199    zend_list_close(Z_RES_P(packet_id));
1200}
1201/* }}} */
1202
1203/* {{{ proto bool wddx_add_vars(resource packet_id,  mixed var_names [, mixed ...])
1204   Serializes given variables and adds them to packet given by packet_id */
1205PHP_FUNCTION(wddx_add_vars)
1206{
1207    int num_args, i;
1208    zval *args = NULL;
1209    zval *packet_id;
1210    wddx_packet *packet = NULL;
1211
1212    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r+", &packet_id, &args, &num_args) == FAILURE) {
1213        return;
1214    }
1215
1216    if ((packet = (wddx_packet *)zend_fetch_resource(Z_RES_P(packet_id), "WDDX packet ID", le_wddx)) == NULL) {
1217        RETURN_FALSE;
1218    }
1219
1220    for (i=0; i<num_args; i++) {
1221        zval *arg;
1222        if (!Z_ISREF(args[i])) {
1223            arg = &args[i];
1224        } else {
1225            arg = Z_REFVAL(args[i]);
1226        }
1227        if (Z_TYPE_P(arg) != IS_ARRAY && Z_TYPE_P(arg) != IS_OBJECT) {
1228            convert_to_string_ex(arg);
1229        }
1230        php_wddx_add_var(packet, arg);
1231    }
1232
1233    RETURN_TRUE;
1234}
1235/* }}} */
1236
1237/* {{{ proto mixed wddx_deserialize(mixed packet)
1238   Deserializes given packet and returns a PHP value */
1239PHP_FUNCTION(wddx_deserialize)
1240{
1241    zval *packet;
1242    php_stream *stream = NULL;
1243    zend_string *payload = NULL;
1244
1245    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &packet) == FAILURE) {
1246        return;
1247    }
1248
1249    if (Z_TYPE_P(packet) == IS_STRING) {
1250        payload = Z_STR_P(packet);
1251    } else if (Z_TYPE_P(packet) == IS_RESOURCE) {
1252        php_stream_from_zval(stream, packet);
1253        if (stream) {
1254            payload = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
1255        }
1256    } else {
1257        php_error_docref(NULL, E_WARNING, "Expecting parameter 1 to be a string or a stream");
1258        return;
1259    }
1260
1261    if (payload == NULL) {
1262        return;
1263    }
1264
1265    php_wddx_deserialize_ex(ZSTR_VAL(payload), ZSTR_LEN(payload), return_value);
1266
1267    if (stream) {
1268        efree(payload);
1269    }
1270}
1271/* }}} */
1272
1273#endif /* HAVE_LIBEXPAT */
1274
1275/*
1276 * Local variables:
1277 * tab-width: 4
1278 * c-basic-offset: 4
1279 * End:
1280 * vim600: sw=4 ts=4 fdm=marker
1281 * vim<600: sw=4 ts=4
1282 */
1283