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