Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
//
3
// +----------------------------------------------------------------------+
4
// | PHP Version 4                                                        |
5
// +----------------------------------------------------------------------+
6
// | Copyright (c) 1997-2003 The PHP Group                                |
7
// +----------------------------------------------------------------------+
8
// | This source file is subject to version 2.02 of the PHP license,      |
9
// | that is bundled with this package in the file LICENSE, and is        |
10
// | available at through the world-wide-web at                           |
11
// | http://www.php.net/license/2_02.txt.                                 |
12
// | If you did not receive a copy of the PHP license and are unable to   |
13
// | obtain it through the world-wide-web, please send a note to          |
14
// | license@php.net so we can mail you a copy immediately.               |
15
// +----------------------------------------------------------------------+
16
// | Authors: Shane Caraveo <Shane@Caraveo.com>   Port to PEAR and more   |
17
// | Authors: Dietrich Ayala <dietrich@ganx4.com> Original Author         |
18
// +----------------------------------------------------------------------+
19
//
20
// $Id: Parser.php 696 2011-09-08 09:08:23Z tiefland $
21
//
22
 
23
require_once 'PayPal/SOAP/Base.php';
24
require_once 'PayPal/SOAP/Value.php';
25
 
26
/**
27
 * SOAP Parser
28
 *
29
 * This class is used by SOAP::Message and SOAP::Server to parse soap
30
 * packets. Originally based on SOAPx4 by Dietrich Ayala
31
 * http://dietrich.ganx4.com/soapx4
32
 *
33
 * @access public
34
 * @package SOAP::Parser
35
 * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates
36
 * @author Dietrich Ayala <dietrich@ganx4.com> Original Author
37
 */
38
class SOAP_Parser extends SOAP_Base
39
{
40
    var $status = '';
41
    var $position = 0;
42
    var $pos_stat = 0;
43
    var $depth = 0;
44
    var $default_namespace = '';
45
    var $message = array();
46
    var $depth_array = array();
47
    var $previous_element = '';
48
    var $soapresponse = null;
49
    var $soapheaders = null;
50
    var $parent = 0;
51
    var $root_struct_name = array();
52
    var $header_struct_name = array();
53
    var $curent_root_struct_name = '';
54
    var $entities = array ( '&' => '&amp;', '<' => '&lt;', '>' => '&gt;', "'" => '&apos;', '"' => '&quot;' );
55
    var $root_struct = array();
56
    var $header_struct = array();
57
    var $curent_root_struct = 0;
58
    var $references = array();
59
    var $need_references = array();
60
    var $XMLSchemaVersion;
61
    var $bodyDepth; // used to handle non-root elements before root body element
62
 
63
    /**
64
     * SOAP_Parser constructor
65
     *
66
     * @param string xml content
67
     * @param string xml character encoding, defaults to 'UTF-8'
68
     */
69
    function SOAP_Parser(&$xml, $encoding = SOAP_DEFAULT_ENCODING, $attachments = null)
70
    {
71
        parent::SOAP_Base('Parser');
72
        $this->_setSchemaVersion(SOAP_XML_SCHEMA_VERSION);
73
 
74
        $this->attachments = $attachments;
75
 
76
        // Check the xml tag for encoding.
77
        if (preg_match('/<\?xml[^>]+encoding\s*?=\s*?(\'([^\']*)\'|"([^"]*)")[^>]*?[\?]>/', $xml, $m)) {
78
            $encoding = strtoupper($m[2] ? $m[2] : $m[3]);
79
        }
80
 
81
        // Determines where in the message we are
82
        // (envelope,header,body,method). Check whether content has
83
        // been read.
84
        if (!empty($xml)) {
85
            // Prepare the xml parser.
86
            $parser = xml_parser_create($encoding);
87
            xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
88
            xml_set_object($parser, $this);
89
            xml_set_element_handler($parser, 'startElement', 'endElement');
90
            xml_set_character_data_handler($parser, 'characterData');
91
 
92
            // Some lame soap implementations add null bytes at the
93
            // end of the soap stream, and expat choaks on that.
94
            if ($xml[strlen($xml) - 1] == 0) {
95
                $xml = trim($xml);
96
            }
97
 
98
            // Parse the XML file.
99
            if (!xml_parse($parser, $xml, true)) {
100
                $err = sprintf('XML error on line %d col %d byte %d %s',
101
                    xml_get_current_line_number($parser),
102
                    xml_get_current_column_number($parser),
103
                    xml_get_current_byte_index($parser),
104
                    xml_error_string(xml_get_error_code($parser)));
105
                $this->_raiseSoapFault($err,htmlspecialchars($xml));
106
            }
107
            xml_parser_free($parser);
108
        }
109
    }
110
 
111
 
112
    /**
113
     * domulti
114
     * recurse to build a multi-dim array, used by buildResponse
115
     *
116
     * @access private
117
     */
118
    function domulti($d, &$ar, &$r, &$v, $ad=0)
119
    {
120
        if ($d) {
121
            $this->domulti($d-1, $ar, $r[$ar[$ad]], $v, $ad+1);
122
        } else {
123
            $r = $v;
124
        }
125
    }
126
 
127
    /**
128
     * buildResponse
129
     * loop through msg, building response structures
130
     *
131
     * @param int position
132
     * @return SOAP_Value
133
     * @access private
134
     */
135
    function &buildResponse($pos)
136
    {
137
        $response = null;
138
 
139
        if (isset($this->message[$pos]['children'])) {
140
            $children = explode('|', $this->message[$pos]['children']);
141
 
142
            foreach ($children as $c => $child_pos) {
143
                if ($this->message[$child_pos]['type'] != null) {
144
                    $response[] =& $this->buildResponse($child_pos);
145
                }
146
            }
147
            if (array_key_exists('arraySize', $this->message[$pos])) {
148
                $ardepth = count($this->message[$pos]['arraySize']);
149
                if ($ardepth > 1) {
150
                    $ar = array_pad(array(), $ardepth, 0);
151
                    if (array_key_exists('arrayOffset', $this->message[$pos])) {
152
                        for ($i = 0; $i < $ardepth; $i++) {
153
                            $ar[$i] += $this->message[$pos]['arrayOffset'][$i];
154
                        }
155
                    }
156
                    $elc = count($response);
157
                    for ($i = 0; $i < $elc; $i++) {
158
                        // recurse to build a multi-dimensional array
159
                        $this->domulti($ardepth, $ar, $newresp, $response[$i]);
160
 
161
                        // increment our array pointers
162
                        $ad = $ardepth - 1;
163
                        $ar[$ad]++;
164
                        while ($ad > 0 && $ar[$ad] >= $this->message[$pos]['arraySize'][$ad]) {
165
                            $ar[$ad] = 0;
166
                            $ad--;
167
                            $ar[$ad]++;
168
                        }
169
                    }
170
                    $response = $newresp;
171
                } elseif (isset($this->message[$pos]['arrayOffset']) &&
172
                          $this->message[$pos]['arrayOffset'][0] > 0) {
173
                    // check for padding
174
                    $pad = $this->message[$pos]['arrayOffset'][0] + count($response) * -1;
175
                    $response = array_pad($response, $pad, null);
176
                }
177
            }
178
        }
179
 
180
        // Build attributes.
181
        $attrs = array();
182
        foreach ($this->message[$pos]['attrs'] as $atn => $atv) {
183
            if (!strstr($atn, 'xmlns') &&
184
                !strpos($atn, ':')) {
185
                $attrs[$atn] = $atv;
186
            }
187
        }
188
 
189
        // Add current node's value.
190
        if ($response) {
191
            $nqn =& new Qname($this->message[$pos]['name'], $this->message[$pos]['namespace']);
192
            $tqn =& new Qname($this->message[$pos]['type'], $this->message[$pos]['type_namespace']);
193
            $response =& new SOAP_Value($nqn->fqn(), $tqn->fqn(), $response, $attrs);
194
            if (isset($this->message[$pos]['arrayType'])) {
195
                $response->arrayType = $this->message[$pos]['arrayType'];
196
            }
197
        } else {
198
            $nqn =& new Qname($this->message[$pos]['name'], $this->message[$pos]['namespace']);
199
            $tqn =& new Qname($this->message[$pos]['type'], $this->message[$pos]['type_namespace']);
200
            $response =& new SOAP_Value($nqn->fqn(), $tqn->fqn(), $this->message[$pos]['cdata'], $attrs);
201
        }
202
 
203
        // handle header attribute that we need
204
        if (array_key_exists('actor', $this->message[$pos])) {
205
            $response->actor = $this->message[$pos]['actor'];
206
        }
207
        if (array_key_exists('mustUnderstand', $this->message[$pos])) {
208
            $response->mustunderstand = $this->message[$pos]['mustUnderstand'];
209
        }
210
        return $response;
211
    }
212
 
213
    /**
214
     * startElement
215
     * start-element handler used with xml parser
216
     *
217
     * @access private
218
     */
219
    function startElement($parser, $name, $attrs)
220
    {
221
        // position in a total number of elements, starting from 0
222
        // update class level pos
223
        $pos = $this->position++;
224
 
225
        // and set mine
226
        $this->message[$pos] = array();
227
        $this->message[$pos]['type'] = '';
228
        $this->message[$pos]['type_namespace'] = '';
229
        $this->message[$pos]['cdata'] = '';
230
        $this->message[$pos]['pos'] = $pos;
231
        $this->message[$pos]['id'] = '';
232
 
233
        // parent/child/depth determinations
234
 
235
        // depth = how many levels removed from root?
236
        // set mine as current global depth and increment global depth value
237
        $this->message[$pos]['depth'] = $this->depth++;
238
 
239
        // else add self as child to whoever the current parent is
240
        if ($pos != 0) {
241
            if (isset($this->message[$this->parent]['children']))
242
                $this->message[$this->parent]['children'] .= "|$pos";
243
            else
244
                $this->message[$this->parent]['children'] = $pos;
245
        }
246
 
247
        // set my parent
248
        $this->message[$pos]['parent'] = $this->parent;
249
 
250
        // set self as current value for this depth
251
        $this->depth_array[$this->depth] = $pos;
252
        // set self as current parent
253
        $this->parent = $pos;
254
        $qname =& new QName($name);
255
        // set status
256
        if (strcasecmp('envelope', $qname->name) == 0) {
257
            $this->status = 'envelope';
258
        } elseif (strcasecmp('header', $qname->name) == 0) {
259
            $this->status = 'header';
260
            $this->header_struct_name[] = $this->curent_root_struct_name = $qname->name;
261
            $this->header_struct[] = $this->curent_root_struct = $pos;
262
            $this->message[$pos]['type'] = 'Struct';
263
        } elseif (strcasecmp('body', $qname->name) == 0) {
264
            $this->status = 'body';
265
            $this->bodyDepth = $this->depth;
266
 
267
        // Set method
268
        } elseif ($this->status == 'body') {
269
            // Is this element allowed to be a root?
270
            // XXX this needs to be optimized, we loop through attrs twice now.
271
            $can_root = $this->depth == $this->bodyDepth + 1;
272
            if ($can_root) {
273
                foreach ($attrs as $key => $value) {
274
                    if (stristr($key, ':root') && !$value) {
275
                        $can_root = FALSE;
276
                    }
277
                }
278
            }
279
 
280
            if ($can_root) {
281
                $this->status = 'method';
282
                $this->root_struct_name[] = $this->curent_root_struct_name = $qname->name;
283
                $this->root_struct[] = $this->curent_root_struct = $pos;
284
                $this->message[$pos]['type'] = 'Struct';
285
            }
286
        }
287
 
288
        // Set my status.
289
        $this->message[$pos]['status'] = $this->status;
290
 
291
        // Set name.
292
        $this->message[$pos]['name'] = htmlspecialchars($qname->name);
293
 
294
        // Set attributes.
295
        $this->message[$pos]['attrs'] = $attrs;
296
 
297
        // Loop through attributes, logging ns and type declarations.
298
        foreach ($attrs as $key => $value) {
299
            // If ns declarations, add to class level array of valid
300
            // namespaces.
301
            $kqn =& new QName($key);
302
            if ($kqn->ns == 'xmlns') {
303
                $prefix = $kqn->name;
304
 
305
                if (in_array($value, $this->_XMLSchema)) {
306
                    $this->_setSchemaVersion($value);
307
                }
308
 
309
                $this->_namespaces[$value] = $prefix;
310
 
311
            // Set method namespace.
312
            } elseif ($key == 'xmlns') {
313
                $qname->ns = $this->_getNamespacePrefix($value);
314
                $qname->namespace = $value;
315
            } elseif ($kqn->name == 'actor') {
316
                $this->message[$pos]['actor'] = $value;
317
            } elseif ($kqn->name == 'mustUnderstand') {
318
                $this->message[$pos]['mustUnderstand'] = $value;
319
 
320
            // If it's a type declaration, set type.
321
            } elseif ($kqn->name == 'type') {
322
                $vqn =& new QName($value);
323
                $this->message[$pos]['type'] = $vqn->name;
324
                $this->message[$pos]['type_namespace'] = $this->_getNamespaceForPrefix($vqn->ns);
325
                // Should do something here with the namespace of
326
                // specified type?
327
 
328
            } elseif ($kqn->name == 'arrayType') {
329
                $vqn =& new QName($value);
330
                $this->message[$pos]['type'] = 'Array';
331
                if (isset($vqn->arraySize)) {
332
                    $this->message[$pos]['arraySize'] = $vqn->arraySize;
333
                }
334
                $this->message[$pos]['arrayType'] = $vqn->name;
335
 
336
            } elseif ($kqn->name == 'offset') {
337
                $this->message[$pos]['arrayOffset'] = split(',', substr($value, 1, strlen($value) - 2));
338
 
339
            } elseif ($kqn->name == 'id') {
340
                // Save id to reference array.
341
                $this->references[$value] = $pos;
342
                $this->message[$pos]['id'] = $value;
343
 
344
            } elseif ($kqn->name == 'href') {
345
                if ($value[0] == '#') {
346
                    $ref = substr($value, 1);
347
                    if (isset($this->references[$ref])) {
348
                        // cdata, type, inval.
349
                        $ref_pos = $this->references[$ref];
350
                        $this->message[$pos]['children'] = &$this->message[$ref_pos]['children'];
351
                        $this->message[$pos]['cdata'] = &$this->message[$ref_pos]['cdata'];
352
                        $this->message[$pos]['type'] = &$this->message[$ref_pos]['type'];
353
                        $this->message[$pos]['arraySize'] = &$this->message[$ref_pos]['arraySize'];
354
                        $this->message[$pos]['arrayType'] = &$this->message[$ref_pos]['arrayType'];
355
                    } else {
356
                        // Reverse reference, store in 'need reference'.
357
                        if (!isset($this->need_references[$ref])) {
358
                            $this->need_references[$ref] = array();
359
                        }
360
                        $this->need_references[$ref][] = $pos;
361
                    }
362
                } elseif (isset($this->attachments[$value])) {
363
                    $this->message[$pos]['cdata'] = $this->attachments[$value];
364
                }
365
            }
366
        }
367
        // See if namespace is defined in tag.
368
        if (array_key_exists('xmlns:' . $qname->ns, $attrs)) {
369
            $namespace = $attrs['xmlns:' . $qname->ns];
370
        } elseif ($qname->ns && !$qname->namespace) {
371
            $namespace = $this->_getNamespaceForPrefix($qname->ns);
372
        } else {
373
            // Get namespace.
374
            $namespace = $qname->namespace ? $qname->namespace : $this->default_namespace;
375
        }
376
        $this->message[$pos]['namespace'] = $namespace;
377
        $this->default_namespace = $namespace;
378
    }
379
 
380
    /**
381
     * endElement
382
     * end-element handler used with xml parser
383
     *
384
     * @access private
385
     */
386
    function endElement($parser, $name)
387
    {
388
        // Position of current element is equal to the last value left
389
        // in depth_array for my depth.
390
        $pos = $this->depth_array[$this->depth];
391
 
392
        // Bring depth down a notch.
393
        $this->depth--;
394
        $qname =& new QName($name);
395
 
396
        // Get type if not explicitly declared in an xsi:type attribute.
397
        // XXX check on integrating wsdl validation here
398
        if ($this->message[$pos]['type'] == '') {
399
            if (isset($this->message[$pos]['children'])) {
400
                /* this is slow, need to look at some faster method
401
                $children = explode('|', $this->message[$pos]['children']);
402
                if (count($children) > 2 &&
403
                    $this->message[$children[1]]['name'] == $this->message[$children[2]]['name']) {
404
                    $this->message[$pos]['type'] = 'Array';
405
                } else {
406
                    $this->message[$pos]['type'] = 'Struct';
407
                }*/
408
                $this->message[$pos]['type'] = 'Struct';
409
            } else {
410
                $parent = $this->message[$pos]['parent'];
411
                if ($this->message[$parent]['type'] == 'Array' &&
412
                  array_key_exists('arrayType', $this->message[$parent])) {
413
                    $this->message[$pos]['type'] = $this->message[$parent]['arrayType'];
414
                } else {
415
                    $this->message[$pos]['type'] = 'string';
416
                }
417
            }
418
        }
419
 
420
        // If tag we are currently closing is the method wrapper.
421
        if ($pos == $this->curent_root_struct) {
422
            $this->status = 'body';
423
        } elseif ($qname->name == 'Body' || $qname->name == 'Header') {
424
            $this->status = 'envelope';
425
        }
426
 
427
        // Set parent back to my parent.
428
        $this->parent = $this->message[$pos]['parent'];
429
 
430
        // Handle any reverse references now.
431
        $idref = $this->message[$pos]['id'];
432
 
433
        if ($idref != '' && array_key_exists($idref, $this->need_references)) {
434
            foreach ($this->need_references[$idref] as $ref_pos) {
435
                // XXX is this stuff there already?
436
                $this->message[$ref_pos]['children'] = &$this->message[$pos]['children'];
437
                $this->message[$ref_pos]['cdata'] = &$this->message[$pos]['cdata'];
438
                $this->message[$ref_pos]['type'] = &$this->message[$pos]['type'];
439
                $this->message[$ref_pos]['arraySize'] = &$this->message[$pos]['arraySize'];
440
                $this->message[$ref_pos]['arrayType'] = &$this->message[$pos]['arrayType'];
441
            }
442
        }
443
    }
444
 
445
    /**
446
     * characterData
447
     * element content handler used with xml parser
448
     *
449
     * @access private
450
     */
451
    function characterData($parser, $data)
452
    {
453
        $pos = $this->depth_array[$this->depth];
454
        if (isset($this->message[$pos]['cdata'])) {
455
            $this->message[$pos]['cdata'] .= $data;
456
        } else {
457
            $this->message[$pos]['cdata'] = $data;
458
        }
459
    }
460
 
461
    /**
462
     * getResponse
463
     *
464
     * returns an array of responses
465
     * after parsing a soap message, use this to get the response
466
     *
467
     * @return   array
468
     * @access public
469
     */
470
    function &getResponse()
471
    {
472
        if (isset($this->root_struct[0]) &&
473
            $this->root_struct[0]) {
474
            return $this->buildResponse($this->root_struct[0]);
475
        }
476
        return $this->_raiseSoapFault("couldn't build response");
477
    }
478
 
479
    /**
480
     * getHeaders
481
     *
482
     * returns an array of header responses
483
     * after parsing a soap message, use this to get the response
484
     *
485
     * @return   array
486
     * @access public
487
     */
488
    function &getHeaders()
489
    {
490
        if (isset($this->header_struct[0]) &&
491
            $this->header_struct[0]) {
492
            return $this->buildResponse($this->header_struct[0]);
493
        }
494
 
495
        // We don't fault if there are no headers that can be handled
496
        // by the app if necessary.
497
        return null;
498
    }
499
 
500
    /**
501
     * decodeEntities
502
     *
503
     * removes entities from text
504
     *
505
     * @param string
506
     * @return   string
507
     * @access private
508
     */
509
    function decodeEntities($text)
510
    {
511
        $trans_tbl = array_flip($this->entities);
512
        return strtr($text, $trans_tbl);
513
    }
514
 
515
}