Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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