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((a)->value.str.val, (a)->value.str.len, (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    NO_VERSION_YET,
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;
394
395        buf = php_escape_html_entities(Z_STRVAL_P(var), Z_STRLEN_P(var), 0, ENT_QUOTES, NULL);
396
397        php_wddx_add_chunk_ex(packet, buf->val, buf->len);
398
399        zend_string_release(buf);
400    }
401    php_wddx_add_chunk_static(packet, WDDX_STRING_E);
402}
403/* }}} */
404
405/* {{{ php_wddx_serialize_number
406 */
407static void php_wddx_serialize_number(wddx_packet *packet, zval *var)
408{
409    char tmp_buf[WDDX_BUF_LEN];
410    zval tmp;
411
412    ZVAL_DUP(&tmp, var);
413    convert_to_string(&tmp);
414    snprintf(tmp_buf, sizeof(tmp_buf), WDDX_NUMBER, Z_STRVAL(tmp));
415    zval_ptr_dtor(&tmp);
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, class_name->val, class_name->len);
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 = HASH_OF(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, class_name->val, class_name->len);
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 = HASH_OF(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 = HASH_OF(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;
596
597        name_esc = php_escape_html_entities(name->val, name->len, 0, ENT_QUOTES, NULL);
598        tmp_buf = emalloc(name_esc->len + sizeof(WDDX_VAR_S));
599        snprintf(tmp_buf, name_esc->len + sizeof(WDDX_VAR_S), WDDX_VAR_S, name_esc->val);
600        php_wddx_add_chunk(packet, tmp_buf);
601        efree(tmp_buf);
602        zend_string_release(name_esc);
603    }
604
605    if (Z_TYPE_P(var) == IS_INDIRECT) {
606        var = Z_INDIRECT_P(var);
607    }
608    ZVAL_DEREF(var);
609    switch (Z_TYPE_P(var)) {
610        case IS_STRING:
611            php_wddx_serialize_string(packet, var);
612            break;
613
614        case IS_LONG:
615        case IS_DOUBLE:
616            php_wddx_serialize_number(packet, var);
617            break;
618
619        case IS_TRUE:
620        case IS_FALSE:
621            php_wddx_serialize_boolean(packet, var);
622            break;
623
624        case IS_NULL:
625            php_wddx_serialize_unset(packet);
626            break;
627
628        case IS_ARRAY:
629            ht = Z_ARRVAL_P(var);
630            if (ht->u.v.nApplyCount > 1) {
631                php_error_docref(NULL, E_RECOVERABLE_ERROR, "WDDX doesn't support circular references");
632                return;
633            }
634            if (ZEND_HASH_APPLY_PROTECTION(ht)) {
635                ht->u.v.nApplyCount++;
636            }
637            php_wddx_serialize_array(packet, var);
638            if (ZEND_HASH_APPLY_PROTECTION(ht)) {
639                ht->u.v.nApplyCount--;
640            }
641            break;
642
643        case IS_OBJECT:
644            ht = Z_OBJPROP_P(var);
645            if (ht->u.v.nApplyCount > 1) {
646                php_error_docref(NULL, E_RECOVERABLE_ERROR, "WDDX doesn't support circular references");
647                return;
648            }
649            ht->u.v.nApplyCount++;
650            php_wddx_serialize_object(packet, var);
651            ht->u.v.nApplyCount--;
652            break;
653    }
654
655    if (name) {
656        php_wddx_add_chunk_static(packet, WDDX_VAR_E);
657    }
658}
659/* }}} */
660
661/* {{{ php_wddx_add_var
662 */
663static void php_wddx_add_var(wddx_packet *packet, zval *name_var)
664{
665    zval *val;
666    HashTable *target_hash;
667
668    if (Z_TYPE_P(name_var) == IS_STRING) {
669        zend_array *symbol_table = zend_rebuild_symbol_table();
670        if ((val = zend_hash_find(&symbol_table->ht, Z_STR_P(name_var))) != NULL) {
671            if (Z_TYPE_P(val) == IS_INDIRECT) {
672                val = Z_INDIRECT_P(val);
673            }
674            php_wddx_serialize_var(packet, val, Z_STR_P(name_var));
675        }
676    } else if (Z_TYPE_P(name_var) == IS_ARRAY || Z_TYPE_P(name_var) == IS_OBJECT)   {
677        int is_array = Z_TYPE_P(name_var) == IS_ARRAY;
678
679        target_hash = HASH_OF(name_var);
680
681        if (is_array && target_hash->u.v.nApplyCount > 1) {
682            php_error_docref(NULL, E_WARNING, "recursion detected");
683            return;
684        }
685
686        if (Z_IMMUTABLE_P(name_var)) {
687            ZEND_HASH_FOREACH_VAL(target_hash, val) {
688                php_wddx_add_var(packet, val);
689            } ZEND_HASH_FOREACH_END();
690        } else {
691            ZEND_HASH_FOREACH_VAL(target_hash, val) {
692                if (is_array) {
693                    target_hash->u.v.nApplyCount++;
694                }
695
696                ZVAL_DEREF(val);
697                php_wddx_add_var(packet, val);
698
699                if (is_array) {
700                    target_hash->u.v.nApplyCount--;
701                }
702            } ZEND_HASH_FOREACH_END();
703        }
704    }
705}
706/* }}} */
707
708/* {{{ php_wddx_push_element
709 */
710static void php_wddx_push_element(void *user_data, const XML_Char *name, const XML_Char **atts)
711{
712    st_entry ent;
713    wddx_stack *stack = (wddx_stack *)user_data;
714    if (!strcmp((char *)name, EL_PACKET)) {
715        int i;
716
717        if (atts) for (i=0; atts[i]; i++) {
718            if (!strcmp((char *)atts[i], EL_VERSION)) {
719                /* nothing for now */
720            }
721        }
722    } else if (!strcmp((char *)name, EL_STRING)) {
723        ent.type = ST_STRING;
724        SET_STACK_VARNAME;
725
726        ZVAL_STR(&ent.data, STR_EMPTY_ALLOC());
727        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
728    } else if (!strcmp((char *)name, EL_BINARY)) {
729        ent.type = ST_BINARY;
730        SET_STACK_VARNAME;
731
732        ZVAL_STR(&ent.data, STR_EMPTY_ALLOC());
733        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
734    } else if (!strcmp((char *)name, EL_CHAR)) {
735        int i;
736
737        if (atts) for (i = 0; atts[i]; i++) {
738            if (!strcmp((char *)atts[i], EL_CHAR_CODE) && atts[++i] && atts[i][0]) {
739                char tmp_buf[2];
740
741                snprintf(tmp_buf, sizeof(tmp_buf), "%c", (char)strtol((char *)atts[i], NULL, 16));
742                php_wddx_process_data(user_data, tmp_buf, strlen(tmp_buf));
743                break;
744            }
745        }
746    } else if (!strcmp((char *)name, EL_NUMBER)) {
747        ent.type = ST_NUMBER;
748        SET_STACK_VARNAME;
749
750        ZVAL_LONG(&ent.data, 0);
751        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
752    } else if (!strcmp((char *)name, EL_BOOLEAN)) {
753        int i;
754
755        if (atts) for (i = 0; atts[i]; i++) {
756            if (!strcmp((char *)atts[i], EL_VALUE) && atts[++i] && atts[i][0]) {
757                ent.type = ST_BOOLEAN;
758                SET_STACK_VARNAME;
759
760                ZVAL_TRUE(&ent.data);
761                wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
762                php_wddx_process_data(user_data, atts[i], strlen((char *)atts[i]));
763                break;
764            }
765        }
766    } else if (!strcmp((char *)name, EL_NULL)) {
767        ent.type = ST_NULL;
768        SET_STACK_VARNAME;
769
770        ZVAL_NULL(&ent.data);
771
772        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
773    } else if (!strcmp((char *)name, EL_ARRAY)) {
774        ent.type = ST_ARRAY;
775        SET_STACK_VARNAME;
776
777        array_init(&ent.data);
778        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
779    } else if (!strcmp((char *)name, EL_STRUCT)) {
780        ent.type = ST_STRUCT;
781        SET_STACK_VARNAME;
782        array_init(&ent.data);
783        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
784    } else if (!strcmp((char *)name, EL_VAR)) {
785        int i;
786
787        if (atts) for (i = 0; atts[i]; i++) {
788            if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
789                stack->varname = estrdup(atts[i]);
790                break;
791            }
792        }
793    } else if (!strcmp((char *)name, EL_RECORDSET)) {
794        int i;
795
796        ent.type = ST_RECORDSET;
797        SET_STACK_VARNAME;
798        array_init(&ent.data);
799
800        if (atts) for (i = 0; atts[i]; i++) {
801            if (!strcmp((char *)atts[i], "fieldNames") && atts[++i] && atts[i][0]) {
802                zval tmp;
803                char *key;
804                char *p1, *p2, *endp;
805
806                endp = (char *)atts[i] + strlen(atts[i]);
807                p1 = (char *)atts[i];
808                while ((p2 = php_memnstr(p1, ",", sizeof(",")-1, endp)) != NULL) {
809                    key = estrndup(p1, p2 - p1);
810                    array_init(&tmp);
811                    add_assoc_zval_ex(&ent.data, key, p2 - p1, &tmp);
812                    p1 = p2 + sizeof(",")-1;
813                    efree(key);
814                }
815
816                if (p1 <= endp) {
817                    array_init(&tmp);
818                    add_assoc_zval_ex(&ent.data, p1, endp - p1, &tmp);
819                }
820
821                break;
822            }
823        }
824
825        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
826    } else if (!strcmp((char *)name, EL_FIELD)) {
827        int i;
828        st_entry ent;
829
830        ent.type = ST_FIELD;
831        ent.varname = NULL;
832        ZVAL_UNDEF(&ent.data);
833
834        if (atts) for (i = 0; atts[i]; i++) {
835            if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
836                st_entry *recordset;
837                zval *field;
838
839                if (wddx_stack_top(stack, (void**)&recordset) == SUCCESS &&
840                    recordset->type == ST_RECORDSET &&
841                    (field = zend_hash_str_find(Z_ARRVAL(recordset->data), (char*)atts[i], strlen(atts[i]))) != NULL) {
842                    ZVAL_COPY_VALUE(&ent.data, field);
843                }
844
845                break;
846            }
847        }
848
849        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
850    } else if (!strcmp((char *)name, EL_DATETIME)) {
851        ent.type = ST_DATETIME;
852        SET_STACK_VARNAME;
853
854        ZVAL_LONG(&ent.data, 0);
855        wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
856    }
857}
858/* }}} */
859
860/* {{{ php_wddx_pop_element
861 */
862static void php_wddx_pop_element(void *user_data, const XML_Char *name)
863{
864    st_entry            *ent1, *ent2;
865    wddx_stack          *stack = (wddx_stack *)user_data;
866    HashTable           *target_hash;
867    zend_class_entry    *pce;
868    zval                obj;
869
870/* OBJECTS_FIXME */
871    if (stack->top == 0) {
872        return;
873    }
874
875    if (!strcmp((char *)name, EL_STRING) || !strcmp((char *)name, EL_NUMBER) ||
876        !strcmp((char *)name, EL_BOOLEAN) || !strcmp((char *)name, EL_NULL) ||
877        !strcmp((char *)name, EL_ARRAY) || !strcmp((char *)name, EL_STRUCT) ||
878        !strcmp((char *)name, EL_RECORDSET) || !strcmp((char *)name, EL_BINARY) ||
879        !strcmp((char *)name, EL_DATETIME)) {
880        wddx_stack_top(stack, (void**)&ent1);
881
882        if (!strcmp((char *)name, EL_BINARY)) {
883            zend_string *new_str;
884
885            new_str = php_base64_decode(Z_STRVAL(ent1->data), Z_STRLEN(ent1->data));
886            zval_ptr_dtor(&ent1->data);
887            ZVAL_STR(&ent1->data, new_str);
888        }
889
890        /* Call __wakeup() method on the object. */
891        if (Z_TYPE(ent1->data) == IS_OBJECT) {
892            zval fname, retval;
893
894            ZVAL_STRING(&fname, "__wakeup");
895
896            call_user_function_ex(NULL, &ent1->data, &fname, &retval, 0, 0, 0, NULL);
897
898            zval_ptr_dtor(&fname);
899            zval_ptr_dtor(&retval);
900        }
901
902        if (stack->top > 1) {
903            stack->top--;
904            wddx_stack_top(stack, (void**)&ent2);
905
906            /* if non-existent field */
907            if (ent2->type == ST_FIELD && Z_ISUNDEF(ent2->data)) {
908                zval_ptr_dtor(&ent1->data);
909                efree(ent1);
910                return;
911            }
912
913            if (Z_TYPE(ent2->data) == IS_ARRAY || Z_TYPE(ent2->data) == IS_OBJECT) {
914                target_hash = HASH_OF(&ent2->data);
915
916                if (ent1->varname) {
917                    if (!strcmp(ent1->varname, PHP_CLASS_NAME_VAR) &&
918                        Z_TYPE(ent1->data) == IS_STRING && Z_STRLEN(ent1->data)) {
919                        zend_bool incomplete_class = 0;
920
921                        zend_str_tolower(Z_STRVAL(ent1->data), Z_STRLEN(ent1->data));
922                        zend_string_forget_hash_val(Z_STR(ent1->data));
923                        if ((pce = zend_hash_find_ptr(EG(class_table), Z_STR(ent1->data))) == NULL) {
924                            incomplete_class = 1;
925                            pce = PHP_IC_ENTRY;
926                        }
927
928                        /* Initialize target object */
929                        object_init_ex(&obj, pce);
930
931                        /* Merge current hashtable with object's default properties */
932                        zend_hash_merge(Z_OBJPROP(obj),
933                                        Z_ARRVAL(ent2->data),
934                                        zval_add_ref, 0);
935
936                        if (incomplete_class) {
937                            php_store_class_name(&obj, Z_STRVAL(ent1->data), Z_STRLEN(ent1->data));
938                        }
939
940                        /* Clean up old array entry */
941                        zval_ptr_dtor(&ent2->data);
942
943                        /* Set stack entry to point to the newly created object */
944                        ZVAL_COPY_VALUE(&ent2->data, &obj);
945
946                        /* Clean up class name var entry */
947                        zval_ptr_dtor(&ent1->data);
948                    } else if (Z_TYPE(ent2->data) == IS_OBJECT) {
949                        zend_class_entry *old_scope = EG(scope);
950
951                        EG(scope) = Z_OBJCE(ent2->data);
952                        add_property_zval(&ent2->data, ent1->varname, &ent1->data);
953                        if Z_REFCOUNTED(ent1->data) Z_DELREF(ent1->data);
954                        EG(scope) = old_scope;
955                    } else {
956                        zend_symtable_str_update(target_hash, ent1->varname, strlen(ent1->varname), &ent1->data);
957                    }
958                    efree(ent1->varname);
959                } else  {
960                    zend_hash_next_index_insert(target_hash, &ent1->data);
961                }
962            }
963            efree(ent1);
964        } else {
965            stack->done = 1;
966        }
967    } else if (!strcmp((char *)name, EL_VAR) && stack->varname) {
968        efree(stack->varname);
969    } else if (!strcmp((char *)name, EL_FIELD)) {
970        st_entry *ent;
971        wddx_stack_top(stack, (void **)&ent);
972        efree(ent);
973        stack->top--;
974    }
975}
976/* }}} */
977
978/* {{{ php_wddx_process_data
979 */
980static void php_wddx_process_data(void *user_data, const XML_Char *s, int len)
981{
982    st_entry *ent;
983    wddx_stack *stack = (wddx_stack *)user_data;
984
985    if (!wddx_stack_is_empty(stack) && !stack->done) {
986        wddx_stack_top(stack, (void**)&ent);
987        switch (ent->type) {
988            case ST_BINARY:
989            case ST_STRING:
990                if (Z_STRLEN(ent->data) == 0) {
991                    zval_ptr_dtor(&ent->data);
992                    ZVAL_STRINGL(&ent->data, (char *)s, len);
993                } else {
994                    Z_STR(ent->data) = zend_string_realloc(Z_STR(ent->data), Z_STRLEN(ent->data) + len, 0);
995                    memcpy(Z_STRVAL(ent->data) + Z_STRLEN(ent->data) - len, (char *)s, len);
996                    Z_STRVAL(ent->data)[Z_STRLEN(ent->data)] = '\0';
997                }
998                break;
999            case ST_NUMBER:
1000                ZVAL_STRINGL(&ent->data, (char *)s, len);
1001                convert_scalar_to_number(&ent->data);
1002                break;
1003
1004            case ST_BOOLEAN:
1005                if (!strcmp((char *)s, "true")) {
1006                    Z_LVAL(ent->data) = 1;
1007                } else if (!strcmp((char *)s, "false")) {
1008                    Z_LVAL(ent->data) = 0;
1009                } else {
1010                    stack->top--;
1011                    zval_ptr_dtor(&ent->data);
1012                    if (ent->varname)
1013                        efree(ent->varname);
1014                    efree(ent);
1015                }
1016                break;
1017
1018            case ST_DATETIME: {
1019                char *tmp;
1020
1021                tmp = emalloc(len + 1);
1022                memcpy(tmp, (char *)s, len);
1023                tmp[len] = '\0';
1024
1025                Z_LVAL(ent->data) = php_parse_date(tmp, NULL);
1026                /* date out of range < 1969 or > 2038 */
1027                if (Z_LVAL(ent->data) == -1) {
1028                    ZVAL_STRINGL(&ent->data, (char *)s, len);
1029                }
1030                efree(tmp);
1031            }
1032                break;
1033
1034            default:
1035                break;
1036        }
1037    }
1038}
1039/* }}} */
1040
1041/* {{{ php_wddx_deserialize_ex
1042 */
1043int php_wddx_deserialize_ex(const char *value, size_t vallen, zval *return_value)
1044{
1045    wddx_stack stack;
1046    XML_Parser parser;
1047    st_entry *ent;
1048    int retval;
1049
1050    wddx_stack_init(&stack);
1051    parser = XML_ParserCreate("UTF-8");
1052
1053    XML_SetUserData(parser, &stack);
1054    XML_SetElementHandler(parser, php_wddx_push_element, php_wddx_pop_element);
1055    XML_SetCharacterDataHandler(parser, php_wddx_process_data);
1056
1057    /* XXX value should be parsed in the loop to exhaust size_t */
1058    XML_Parse(parser, value, (int)vallen, 1);
1059
1060    XML_ParserFree(parser);
1061
1062    if (stack.top == 1) {
1063        wddx_stack_top(&stack, (void**)&ent);
1064        ZVAL_COPY(return_value, &ent->data);
1065        retval = SUCCESS;
1066    } else {
1067        retval = FAILURE;
1068    }
1069
1070    wddx_stack_destroy(&stack);
1071
1072    return retval;
1073}
1074/* }}} */
1075
1076/* {{{ proto string wddx_serialize_value(mixed var [, string comment])
1077   Creates a new packet and serializes the given value */
1078PHP_FUNCTION(wddx_serialize_value)
1079{
1080    zval *var;
1081    char *comment = NULL;
1082    size_t comment_len = 0;
1083    wddx_packet *packet;
1084
1085    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|s", &var, &comment, &comment_len) == FAILURE) {
1086        return;
1087    }
1088
1089    packet = php_wddx_constructor();
1090
1091    php_wddx_packet_start(packet, comment, comment_len);
1092    php_wddx_serialize_var(packet, var, NULL);
1093    php_wddx_packet_end(packet);
1094    smart_str_0(packet);
1095
1096    RETVAL_STR(zend_string_copy(packet->s));
1097    php_wddx_destructor(packet);
1098}
1099/* }}} */
1100
1101/* {{{ proto string wddx_serialize_vars(mixed var_name [, mixed ...])
1102   Creates a new packet and serializes given variables into a struct */
1103PHP_FUNCTION(wddx_serialize_vars)
1104{
1105    int num_args, i;
1106    wddx_packet *packet;
1107    zval *args = NULL;
1108
1109    if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &num_args) == FAILURE) {
1110        return;
1111    }
1112
1113    packet = php_wddx_constructor();
1114
1115    php_wddx_packet_start(packet, NULL, 0);
1116    php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
1117
1118    for (i=0; i<num_args; i++) {
1119        zval *arg;
1120        if (!Z_ISREF(args[i])) {
1121            arg = &args[i];
1122        } else {
1123            arg = Z_REFVAL(args[i]);
1124        }
1125        if (Z_TYPE_P(arg) != IS_ARRAY && Z_TYPE_P(arg) != IS_OBJECT) {
1126            convert_to_string_ex(arg);
1127        }
1128        php_wddx_add_var(packet, arg);
1129    }
1130
1131    php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
1132    php_wddx_packet_end(packet);
1133    smart_str_0(packet);
1134
1135    RETVAL_STR(zend_string_copy(packet->s));
1136    php_wddx_destructor(packet);
1137}
1138/* }}} */
1139
1140/* {{{ php_wddx_constructor
1141 */
1142wddx_packet *php_wddx_constructor(void)
1143{
1144    smart_str *packet;
1145
1146    packet = ecalloc(1, sizeof(smart_str));
1147
1148    return packet;
1149}
1150/* }}} */
1151
1152/* {{{ php_wddx_destructor
1153 */
1154void php_wddx_destructor(wddx_packet *packet)
1155{
1156    smart_str_free(packet);
1157    efree(packet);
1158}
1159/* }}} */
1160
1161/* {{{ proto resource wddx_packet_start([string comment])
1162   Starts a WDDX packet with optional comment and returns the packet id */
1163PHP_FUNCTION(wddx_packet_start)
1164{
1165    char *comment = NULL;
1166    size_t comment_len = 0;
1167    wddx_packet *packet;
1168
1169    comment = NULL;
1170
1171    if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &comment, &comment_len) == FAILURE) {
1172        return;
1173    }
1174
1175    packet = php_wddx_constructor();
1176
1177    php_wddx_packet_start(packet, comment, comment_len);
1178    php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
1179
1180    ZEND_REGISTER_RESOURCE(return_value, packet, le_wddx);
1181}
1182/* }}} */
1183
1184/* {{{ proto string wddx_packet_end(resource packet_id)
1185   Ends specified WDDX packet and returns the string containing the packet */
1186PHP_FUNCTION(wddx_packet_end)
1187{
1188    zval *packet_id;
1189    wddx_packet *packet = NULL;
1190
1191    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &packet_id) == FAILURE) {
1192        return;
1193    }
1194
1195    ZEND_FETCH_RESOURCE(packet, wddx_packet *, packet_id, -1, "WDDX packet ID", le_wddx);
1196
1197    php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
1198
1199    php_wddx_packet_end(packet);
1200    smart_str_0(packet);
1201
1202    RETVAL_STR(zend_string_copy(packet->s));
1203
1204    zend_list_close(Z_RES_P(packet_id));
1205}
1206/* }}} */
1207
1208/* {{{ proto int wddx_add_vars(resource packet_id,  mixed var_names [, mixed ...])
1209   Serializes given variables and adds them to packet given by packet_id */
1210PHP_FUNCTION(wddx_add_vars)
1211{
1212    int num_args, i;
1213    zval *args = NULL;
1214    zval *packet_id;
1215    wddx_packet *packet = NULL;
1216
1217    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r+", &packet_id, &args, &num_args) == FAILURE) {
1218        return;
1219    }
1220
1221    if (!ZEND_FETCH_RESOURCE_NO_RETURN(packet, wddx_packet *, packet_id, -1, "WDDX packet ID", le_wddx)) {
1222        RETURN_FALSE;
1223    }
1224
1225    if (!packet) {
1226        RETURN_FALSE;
1227    }
1228
1229    for (i=0; i<num_args; i++) {
1230        zval *arg;
1231        if (!Z_ISREF(args[i])) {
1232            arg = &args[i];
1233        } else {
1234            arg = Z_REFVAL(args[i]);
1235        }
1236        if (Z_TYPE_P(arg) != IS_ARRAY && Z_TYPE_P(arg) != IS_OBJECT) {
1237            convert_to_string_ex(arg);
1238        }
1239        php_wddx_add_var(packet, arg);
1240    }
1241
1242    RETURN_TRUE;
1243}
1244/* }}} */
1245
1246/* {{{ proto mixed wddx_deserialize(mixed packet)
1247   Deserializes given packet and returns a PHP value */
1248PHP_FUNCTION(wddx_deserialize)
1249{
1250    zval *packet;
1251    php_stream *stream = NULL;
1252    zend_string *payload = NULL;
1253
1254    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &packet) == FAILURE) {
1255        return;
1256    }
1257
1258    if (Z_TYPE_P(packet) == IS_STRING) {
1259        payload = Z_STR_P(packet);
1260    } else if (Z_TYPE_P(packet) == IS_RESOURCE) {
1261        php_stream_from_zval(stream, packet);
1262        if (stream) {
1263            payload = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
1264        }
1265    } else {
1266        php_error_docref(NULL, E_WARNING, "Expecting parameter 1 to be a string or a stream");
1267        return;
1268    }
1269
1270    if (payload == NULL) {
1271        return;
1272    }
1273
1274    php_wddx_deserialize_ex(payload->val, payload->len, return_value);
1275
1276    if (stream) {
1277        efree(payload);
1278    }
1279}
1280/* }}} */
1281
1282#endif /* HAVE_LIBEXPAT */
1283
1284/*
1285 * Local variables:
1286 * tab-width: 4
1287 * c-basic-offset: 4
1288 * End:
1289 * vim600: sw=4 ts=4 fdm=marker
1290 * vim<600: sw=4 ts=4
1291 */
1292