* Remove obsolete warning in Extension Manager
[Packages/TYPO3.CMS.git] / typo3 / mod / tools / em / class.nusoap.php
1 <?php
2 /*
3 $Id$
4
5 NuSOAP - Web Services Toolkit for PHP
6
7 Copyright (c) 2002 NuSphere Corporation
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Lesser General Public
11 License as published by the Free Software Foundation; either
12 version 2.1 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 §§§§§
23 If you have any questions or comments, please email:
24
25 Dietrich Ayala
26 dietrich@ganx4.com
27 http://dietrich.ganx4.com/nusoap
28
29 NuSphere Corporation
30 http://www.nusphere.com
31
32 */
33
34 /* load classes
35
36 // necessary classes
37 require_once('class.soapclient.php');
38 require_once('class.soap_val.php');
39 require_once('class.soap_parser.php');
40 require_once('class.soap_fault.php');
41
42 // transport classes
43 require_once('class.soap_transport_http.php');
44
45 // optional add-on classes
46 require_once('class.xmlschema.php');
47 require_once('class.wsdl.php');
48 */
49
50 // class variable emulation
51 // cf. http://www.webkreator.com/php/techniques/php-static-class-variables.html
52 $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel = 9;
53
54 /**
55 *
56 * nusoap_base
57 *
58 * @author Dietrich Ayala <dietrich@ganx4.com>
59 * @version $Id$
60 * @access public
61 */
62 class nusoap_base {
63 /**
64 * Identification for HTTP headers.
65 *
66 * @var string
67 * @access private
68 */
69 var $title = 'NuSOAP';
70 /**
71 * Version for HTTP headers.
72 *
73 * @var string
74 * @access private
75 */
76 var $version = '0.7.2';
77 /**
78 * CVS revision for HTTP headers.
79 *
80 * @var string
81 * @access private
82 */
83 var $revision = '$Revision$';
84 /**
85 * Current error string (manipulated by getError/setError)
86 *
87 * @var string
88 * @access private
89 */
90 var $error_str = '';
91 /**
92 * Current debug string (manipulated by debug/appendDebug/clearDebug/getDebug/getDebugAsXMLComment)
93 *
94 * @var string
95 * @access private
96 */
97 var $debug_str = '';
98 /**
99 * toggles automatic encoding of special characters as entities
100 * (should always be true, I think)
101 *
102 * @var boolean
103 * @access private
104 */
105 var $charencoding = true;
106 /**
107 * the debug level for this instance
108 *
109 * @var integer
110 * @access private
111 */
112 var $debugLevel;
113
114 /**
115 * set schema version
116 *
117 * @var string
118 * @access public
119 */
120 var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema';
121
122 /**
123 * charset encoding for outgoing messages
124 *
125 * @var string
126 * @access public
127 */
128 var $soap_defencoding = 'ISO-8859-1';
129 //var $soap_defencoding = 'UTF-8';
130
131 /**
132 * namespaces in an array of prefix => uri
133 *
134 * this is "seeded" by a set of constants, but it may be altered by code
135 *
136 * @var array
137 * @access public
138 */
139 var $namespaces = array(
140 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
141 'xsd' => 'http://www.w3.org/2001/XMLSchema',
142 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
143 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/'
144 );
145
146 /**
147 * namespaces used in the current context, e.g. during serialization
148 *
149 * @var array
150 * @access private
151 */
152 var $usedNamespaces = array();
153
154 /**
155 * XML Schema types in an array of uri => (array of xml type => php type)
156 * is this legacy yet?
157 * no, this is used by the xmlschema class to verify type => namespace mappings.
158 * @var array
159 * @access public
160 */
161 var $typemap = array(
162 'http://www.w3.org/2001/XMLSchema' => array(
163 'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
164 'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
165 'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string',
166 // abstract "any" types
167 'anyType'=>'string','anySimpleType'=>'string',
168 // derived datatypes
169 'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'',
170 'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer',
171 'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer',
172 'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''),
173 'http://www.w3.org/2000/10/XMLSchema' => array(
174 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
175 'float'=>'double','dateTime'=>'string',
176 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
177 'http://www.w3.org/1999/XMLSchema' => array(
178 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
179 'float'=>'double','dateTime'=>'string',
180 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
181 'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'),
182 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'),
183 'http://xml.apache.org/xml-soap' => array('Map')
184 );
185
186 /**
187 * XML entities to convert
188 *
189 * @var array
190 * @access public
191 * @deprecated
192 * @see expandEntities
193 */
194 var $xmlEntities = array('quot' => '"','amp' => '&',
195 'lt' => '<','gt' => '>','apos' => "'");
196
197 /**
198 * constructor
199 *
200 * @access public
201 */
202 function nusoap_base() {
203 $this->debugLevel = $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel;
204 }
205
206 /**
207 * gets the global debug level, which applies to future instances
208 *
209 * @return integer Debug level 0-9, where 0 turns off
210 * @access public
211 */
212 function getGlobalDebugLevel() {
213 return $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel;
214 }
215
216 /**
217 * sets the global debug level, which applies to future instances
218 *
219 * @param int $level Debug level 0-9, where 0 turns off
220 * @access public
221 */
222 function setGlobalDebugLevel($level) {
223 $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel = $level;
224 }
225
226 /**
227 * gets the debug level for this instance
228 *
229 * @return int Debug level 0-9, where 0 turns off
230 * @access public
231 */
232 function getDebugLevel() {
233 return $this->debugLevel;
234 }
235
236 /**
237 * sets the debug level for this instance
238 *
239 * @param int $level Debug level 0-9, where 0 turns off
240 * @access public
241 */
242 function setDebugLevel($level) {
243 $this->debugLevel = $level;
244 }
245
246 /**
247 * adds debug data to the instance debug string with formatting
248 *
249 * @param string $string debug data
250 * @access private
251 */
252 function debug($string){
253 if ($this->debugLevel > 0) {
254 $this->appendDebug($this->getmicrotime().' '.get_class($this).": $string\n");
255 }
256 }
257
258 /**
259 * adds debug data to the instance debug string without formatting
260 *
261 * @param string $string debug data
262 * @access public
263 */
264 function appendDebug($string){
265 if ($this->debugLevel > 0) {
266 // it would be nice to use a memory stream here to use
267 // memory more efficiently
268 $this->debug_str .= $string;
269 }
270 }
271
272 /**
273 * clears the current debug data for this instance
274 *
275 * @access public
276 */
277 function clearDebug() {
278 // it would be nice to use a memory stream here to use
279 // memory more efficiently
280 $this->debug_str = '';
281 }
282
283 /**
284 * gets the current debug data for this instance
285 *
286 * @return debug data
287 * @access public
288 */
289 function &getDebug() {
290 // it would be nice to use a memory stream here to use
291 // memory more efficiently
292 return $this->debug_str;
293 }
294
295 /**
296 * gets the current debug data for this instance as an XML comment
297 * this may change the contents of the debug data
298 *
299 * @return debug data as an XML comment
300 * @access public
301 */
302 function &getDebugAsXMLComment() {
303 // it would be nice to use a memory stream here to use
304 // memory more efficiently
305 while (strpos($this->debug_str, '--')) {
306 $this->debug_str = str_replace('--', '- -', $this->debug_str);
307 }
308 return "<!--\n" . $this->debug_str . "\n-->";
309 }
310
311 /**
312 * expands entities, e.g. changes '<' to '&lt;'.
313 *
314 * @param string $val The string in which to expand entities.
315 * @access private
316 */
317 function expandEntities($val) {
318 if ($this->charencoding) {
319 $val = str_replace('&', '&amp;', $val);
320 $val = str_replace("'", '&apos;', $val);
321 $val = str_replace('"', '&quot;', $val);
322 $val = str_replace('<', '&lt;', $val);
323 $val = str_replace('>', '&gt;', $val);
324 }
325 return $val;
326 }
327
328 /**
329 * returns error string if present
330 *
331 * @return mixed error string or false
332 * @access public
333 */
334 function getError(){
335 if($this->error_str != ''){
336 return $this->error_str;
337 }
338 return false;
339 }
340
341 /**
342 * sets error string
343 *
344 * @return boolean $string error string
345 * @access private
346 */
347 function setError($str){
348 $this->error_str = $str;
349 }
350
351 /**
352 * detect if array is a simple array or a struct (associative array)
353 *
354 * @param mixed $val The PHP array
355 * @return string (arraySimple|arrayStruct)
356 * @access private
357 */
358 function isArraySimpleOrStruct($val) {
359 $keyList = array_keys($val);
360 foreach ($keyList as $keyListValue) {
361 if (!is_int($keyListValue)) {
362 return 'arrayStruct';
363 }
364 }
365 return 'arraySimple';
366 }
367
368 /**
369 * serializes PHP values in accordance w/ section 5. Type information is
370 * not serialized if $use == 'literal'.
371 *
372 * @param mixed $val The value to serialize
373 * @param string $name The name (local part) of the XML element
374 * @param string $type The XML schema type (local part) for the element
375 * @param string $name_ns The namespace for the name of the XML element
376 * @param string $type_ns The namespace for the type of the element
377 * @param array $attributes The attributes to serialize as name=>value pairs
378 * @param string $use The WSDL "use" (encoded|literal)
379 * @return string The serialized element, possibly with child elements
380 * @access public
381 */
382 function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded'){
383 $this->debug("in serialize_val: name=$name, type=$type, name_ns=$name_ns, type_ns=$type_ns, use=$use");
384 $this->appendDebug('value=' . $this->varDump($val));
385 $this->appendDebug('attributes=' . $this->varDump($attributes));
386
387 if(is_object($val) && get_class($val) == 'soapval'){
388 return $val->serialize($use);
389 }
390 // force valid name if necessary
391 if (is_numeric($name)) {
392 $name = '__numeric_' . $name;
393 } elseif (! $name) {
394 $name = 'noname';
395 }
396 // if name has ns, add ns prefix to name
397 $xmlns = '';
398 if($name_ns){
399 $prefix = 'nu'.rand(1000,9999);
400 $name = $prefix.':'.$name;
401 $xmlns .= " xmlns:$prefix=\"$name_ns\"";
402 }
403 // if type is prefixed, create type prefix
404 if($type_ns != '' && $type_ns == $this->namespaces['xsd']){
405 // need to fix this. shouldn't default to xsd if no ns specified
406 // w/o checking against typemap
407 $type_prefix = 'xsd';
408 } elseif($type_ns){
409 $type_prefix = 'ns'.rand(1000,9999);
410 $xmlns .= " xmlns:$type_prefix=\"$type_ns\"";
411 }
412 // serialize attributes if present
413 $atts = '';
414 if($attributes){
415 foreach($attributes as $k => $v){
416 $atts .= " $k=\"".$this->expandEntities($v).'"';
417 }
418 }
419 // serialize null value
420 if (is_null($val)) {
421 if ($use == 'literal') {
422 // TODO: depends on minOccurs
423 return "<$name$xmlns $atts/>";
424 } else {
425 if (isset($type) && isset($type_prefix)) {
426 $type_str = " xsi:type=\"$type_prefix:$type\"";
427 } else {
428 $type_str = '';
429 }
430 return "<$name$xmlns$type_str $atts xsi:nil=\"true\"/>";
431 }
432 }
433 // serialize if an xsd built-in primitive type
434 if($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){
435 if (is_bool($val)) {
436 if ($type == 'boolean') {
437 $val = $val ? 'true' : 'false';
438 } elseif (! $val) {
439 $val = 0;
440 }
441 } else if (is_string($val)) {
442 $val = $this->expandEntities($val);
443 }
444 if ($use == 'literal') {
445 return "<$name$xmlns $atts>$val</$name>";
446 } else {
447 return "<$name$xmlns $atts xsi:type=\"xsd:$type\">$val</$name>";
448 }
449 }
450 // detect type and serialize
451 $xml = '';
452 switch(true) {
453 case (is_bool($val) || $type == 'boolean'):
454 if ($type == 'boolean') {
455 $val = $val ? 'true' : 'false';
456 } elseif (! $val) {
457 $val = 0;
458 }
459 if ($use == 'literal') {
460 $xml .= "<$name$xmlns $atts>$val</$name>";
461 } else {
462 $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val</$name>";
463 }
464 break;
465 case (is_int($val) || is_long($val) || $type == 'int'):
466 if ($use == 'literal') {
467 $xml .= "<$name$xmlns $atts>$val</$name>";
468 } else {
469 $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val</$name>";
470 }
471 break;
472 case (is_float($val)|| is_double($val) || $type == 'float'):
473 if ($use == 'literal') {
474 $xml .= "<$name$xmlns $atts>$val</$name>";
475 } else {
476 $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val</$name>";
477 }
478 break;
479 case (is_string($val) || $type == 'string'):
480 $val = $this->expandEntities($val);
481 if ($use == 'literal') {
482 $xml .= "<$name$xmlns $atts>$val</$name>";
483 } else {
484 $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val</$name>";
485 }
486 break;
487 case is_object($val):
488 if (! $name) {
489 $name = get_class($val);
490 $this->debug("In serialize_val, used class name $name as element name");
491 } else {
492 $this->debug("In serialize_val, do not override name $name for element name for class " . get_class($val));
493 }
494 foreach(get_object_vars($val) as $k => $v){
495 $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use);
496 }
497 $xml .= '<'.$name.'>'.$pXml.'</'.$name.'>';
498 break;
499 break;
500 case (is_array($val) || $type):
501 // detect if struct or array
502 $valueType = $this->isArraySimpleOrStruct($val);
503 if($valueType=='arraySimple' || ereg('^ArrayOf',$type)){
504 $i = 0;
505 if(is_array($val) && count($val)> 0){
506 foreach($val as $v){
507 if(is_object($v) && get_class($v) == 'soapval'){
508 $tt_ns = $v->type_ns;
509 $tt = $v->type;
510 } elseif (is_array($v)) {
511 $tt = $this->isArraySimpleOrStruct($v);
512 } else {
513 $tt = gettype($v);
514 }
515 $array_types[$tt] = 1;
516 // TODO: for literal, the name should be $name
517 $xml .= $this->serialize_val($v,'item',false,false,false,false,$use);
518 ++$i;
519 }
520 if(count($array_types) > 1){
521 $array_typename = 'xsd:anyType';
522 } elseif(isset($tt) && isset($this->typemap[$this->XMLSchemaVersion][$tt])) {
523 if ($tt == 'integer') {
524 $tt = 'int';
525 }
526 $array_typename = 'xsd:'.$tt;
527 } elseif(isset($tt) && $tt == 'arraySimple'){
528 $array_typename = 'SOAP-ENC:Array';
529 } elseif(isset($tt) && $tt == 'arrayStruct'){
530 $array_typename = 'unnamed_struct_use_soapval';
531 } else {
532 // if type is prefixed, create type prefix
533 if ($tt_ns != '' && $tt_ns == $this->namespaces['xsd']){
534 $array_typename = 'xsd:' . $tt;
535 } elseif ($tt_ns) {
536 $tt_prefix = 'ns' . rand(1000, 9999);
537 $array_typename = "$tt_prefix:$tt";
538 $xmlns .= " xmlns:$tt_prefix=\"$tt_ns\"";
539 } else {
540 $array_typename = $tt;
541 }
542 }
543 $array_type = $i;
544 if ($use == 'literal') {
545 $type_str = '';
546 } else if (isset($type) && isset($type_prefix)) {
547 $type_str = " xsi:type=\"$type_prefix:$type\"";
548 } else {
549 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\"";
550 }
551 // empty array
552 } else {
553 if ($use == 'literal') {
554 $type_str = '';
555 } else if (isset($type) && isset($type_prefix)) {
556 $type_str = " xsi:type=\"$type_prefix:$type\"";
557 } else {
558 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"xsd:anyType[0]\"";
559 }
560 }
561 // TODO: for array in literal, there is no wrapper here
562 $xml = "<$name$xmlns$type_str$atts>".$xml."</$name>";
563 } else {
564 // got a struct
565 if(isset($type) && isset($type_prefix)){
566 $type_str = " xsi:type=\"$type_prefix:$type\"";
567 } else {
568 $type_str = '';
569 }
570 if ($use == 'literal') {
571 $xml .= "<$name$xmlns $atts>";
572 } else {
573 $xml .= "<$name$xmlns$type_str$atts>";
574 }
575 foreach($val as $k => $v){
576 // Apache Map
577 if ($type == 'Map' && $type_ns == 'http://xml.apache.org/xml-soap') {
578 $xml .= '<item>';
579 $xml .= $this->serialize_val($k,'key',false,false,false,false,$use);
580 $xml .= $this->serialize_val($v,'value',false,false,false,false,$use);
581 $xml .= '</item>';
582 } else {
583 $xml .= $this->serialize_val($v,$k,false,false,false,false,$use);
584 }
585 }
586 $xml .= "</$name>";
587 }
588 break;
589 default:
590 $xml .= 'not detected, got '.gettype($val).' for '.$val;
591 break;
592 }
593 return $xml;
594 }
595
596 /**
597 * serializes a message
598 *
599 * @param string $body the XML of the SOAP body
600 * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers
601 * @param array $namespaces optional the namespaces used in generating the body and headers
602 * @param string $style optional (rpc|document)
603 * @param string $use optional (encoded|literal)
604 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
605 * @return string the message
606 * @access public
607 */
608 function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc',$use='encoded',$encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'){
609 // TODO: add an option to automatically run utf8_encode on $body and $headers
610 // if $this->soap_defencoding is UTF-8. Not doing this automatically allows
611 // one to send arbitrary UTF-8 characters, not just characters that map to ISO-8859-1
612
613 $this->debug("In serializeEnvelope length=" . strlen($body) . " body (max 1000 characters)=" . substr($body, 0, 1000) . " style=$style use=$use encodingStyle=$encodingStyle");
614 $this->debug("headers:");
615 $this->appendDebug($this->varDump($headers));
616 $this->debug("namespaces:");
617 $this->appendDebug($this->varDump($namespaces));
618
619 // serialize namespaces
620 $ns_string = '';
621 foreach(array_merge($this->namespaces,$namespaces) as $k => $v){
622 $ns_string .= " xmlns:$k=\"$v\"";
623 }
624 if($encodingStyle) {
625 $ns_string = " SOAP-ENV:encodingStyle=\"$encodingStyle\"$ns_string";
626 }
627
628 // serialize headers
629 if($headers){
630 if (is_array($headers)) {
631 $xml = '';
632 foreach ($headers as $header) {
633 $xml .= $this->serialize_val($header, false, false, false, false, false, $use);
634 }
635 $headers = $xml;
636 $this->debug("In serializeEnvelope, serialzied array of headers to $headers");
637 }
638 $headers = "<SOAP-ENV:Header>".$headers."</SOAP-ENV:Header>";
639 }
640 // serialize envelope
641 return
642 '<?xml version="1.0" encoding="'.$this->soap_defencoding .'"?'.">".
643 '<SOAP-ENV:Envelope'.$ns_string.">".
644 $headers.
645 "<SOAP-ENV:Body>".
646 $body.
647 "</SOAP-ENV:Body>".
648 "</SOAP-ENV:Envelope>";
649 }
650
651 /**
652 * formats a string to be inserted into an HTML stream
653 *
654 * @param string $str The string to format
655 * @return string The formatted string
656 * @access public
657 * @deprecated
658 */
659 function formatDump($str){
660 $str = htmlspecialchars($str);
661 return nl2br($str);
662 }
663
664 /**
665 * contracts (changes namespace to prefix) a qualified name
666 *
667 * @param string $qname qname
668 * @return string contracted qname
669 * @access private
670 */
671 function contractQname($qname){
672 // get element namespace
673 //$this->xdebug("Contract $qname");
674 if (strrpos($qname, ':')) {
675 // get unqualified name
676 $name = substr($qname, strrpos($qname, ':') + 1);
677 // get ns
678 $ns = substr($qname, 0, strrpos($qname, ':'));
679 $p = $this->getPrefixFromNamespace($ns);
680 if ($p) {
681 return $p . ':' . $name;
682 }
683 return $qname;
684 } else {
685 return $qname;
686 }
687 }
688
689 /**
690 * expands (changes prefix to namespace) a qualified name
691 *
692 * @param string $string qname
693 * @return string expanded qname
694 * @access private
695 */
696 function expandQname($qname){
697 // get element prefix
698 if(strpos($qname,':') && !ereg('^http://',$qname)){
699 // get unqualified name
700 $name = substr(strstr($qname,':'),1);
701 // get ns prefix
702 $prefix = substr($qname,0,strpos($qname,':'));
703 if(isset($this->namespaces[$prefix])){
704 return $this->namespaces[$prefix].':'.$name;
705 } else {
706 return $qname;
707 }
708 } else {
709 return $qname;
710 }
711 }
712
713 /**
714 * returns the local part of a prefixed string
715 * returns the original string, if not prefixed
716 *
717 * @param string $str The prefixed string
718 * @return string The local part
719 * @access public
720 */
721 function getLocalPart($str){
722 if($sstr = strrchr($str,':')){
723 // get unqualified name
724 return substr( $sstr, 1 );
725 } else {
726 return $str;
727 }
728 }
729
730 /**
731 * returns the prefix part of a prefixed string
732 * returns false, if not prefixed
733 *
734 * @param string $str The prefixed string
735 * @return mixed The prefix or false if there is no prefix
736 * @access public
737 */
738 function getPrefix($str){
739 if($pos = strrpos($str,':')){
740 // get prefix
741 return substr($str,0,$pos);
742 }
743 return false;
744 }
745
746 /**
747 * pass it a prefix, it returns a namespace
748 *
749 * @param string $prefix The prefix
750 * @return mixed The namespace, false if no namespace has the specified prefix
751 * @access public
752 */
753 function getNamespaceFromPrefix($prefix){
754 if (isset($this->namespaces[$prefix])) {
755 return $this->namespaces[$prefix];
756 }
757 //$this->setError("No namespace registered for prefix '$prefix'");
758 return false;
759 }
760
761 /**
762 * returns the prefix for a given namespace (or prefix)
763 * or false if no prefixes registered for the given namespace
764 *
765 * @param string $ns The namespace
766 * @return mixed The prefix, false if the namespace has no prefixes
767 * @access public
768 */
769 function getPrefixFromNamespace($ns) {
770 foreach ($this->namespaces as $p => $n) {
771 if ($ns == $n || $ns == $p) {
772 $this->usedNamespaces[$p] = $n;
773 return $p;
774 }
775 }
776 return false;
777 }
778
779 /**
780 * returns the time in ODBC canonical form with microseconds
781 *
782 * @return string The time in ODBC canonical form with microseconds
783 * @access public
784 */
785 function getmicrotime() {
786 if (function_exists('gettimeofday')) {
787 $tod = gettimeofday();
788 $sec = $tod['sec'];
789 $usec = $tod['usec'];
790 } else {
791 $sec = time();
792 $usec = 0;
793 }
794 return strftime('%Y-%m-%d %H:%M:%S', $sec) . '.' . sprintf('%06d', $usec);
795 }
796
797 /**
798 * Returns a string with the output of var_dump
799 *
800 * @param mixed $data The variable to var_dump
801 * @return string The output of var_dump
802 * @access public
803 */
804 function varDump($data) {
805 ob_start();
806 var_dump($data);
807 $ret_val = ob_get_contents();
808 ob_end_clean();
809 return $ret_val;
810 }
811 }
812
813 // XML Schema Datatype Helper Functions
814
815 //xsd:dateTime helpers
816
817 /**
818 * convert unix timestamp to ISO 8601 compliant date string
819 *
820 * @param string $timestamp Unix time stamp
821 * @access public
822 */
823 function timestamp_to_iso8601($timestamp,$utc=true){
824 $datestr = date('Y-m-d\TH:i:sO',$timestamp);
825 if($utc){
826 $eregStr =
827 '([0-9]{4})-'. // centuries & years CCYY-
828 '([0-9]{2})-'. // months MM-
829 '([0-9]{2})'. // days DD
830 'T'. // separator T
831 '([0-9]{2}):'. // hours hh:
832 '([0-9]{2}):'. // minutes mm:
833 '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss...
834 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
835
836 if(ereg($eregStr,$datestr,$regs)){
837 return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]);
838 }
839 return false;
840 } else {
841 return $datestr;
842 }
843 }
844
845 /**
846 * convert ISO 8601 compliant date string to unix timestamp
847 *
848 * @param string $datestr ISO 8601 compliant date string
849 * @access public
850 */
851 function iso8601_to_timestamp($datestr){
852 $eregStr =
853 '([0-9]{4})-'. // centuries & years CCYY-
854 '([0-9]{2})-'. // months MM-
855 '([0-9]{2})'. // days DD
856 'T'. // separator T
857 '([0-9]{2}):'. // hours hh:
858 '([0-9]{2}):'. // minutes mm:
859 '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss...
860 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
861 if(ereg($eregStr,$datestr,$regs)){
862 // not utc
863 if($regs[8] != 'Z'){
864 $op = substr($regs[8],0,1);
865 $h = substr($regs[8],1,2);
866 $m = substr($regs[8],strlen($regs[8])-2,2);
867 if($op == '-'){
868 $regs[4] = $regs[4] + $h;
869 $regs[5] = $regs[5] + $m;
870 } elseif($op == '+'){
871 $regs[4] = $regs[4] - $h;
872 $regs[5] = $regs[5] - $m;
873 }
874 }
875 return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z");
876 } else {
877 return false;
878 }
879 }
880
881 /**
882 * sleeps some number of microseconds
883 *
884 * @param string $usec the number of microseconds to sleep
885 * @access public
886 * @deprecated
887 */
888 function usleepWindows($usec)
889 {
890 $start = gettimeofday();
891
892 do
893 {
894 $stop = gettimeofday();
895 $timePassed = 1000000 * ($stop['sec'] - $start['sec'])
896 + $stop['usec'] - $start['usec'];
897 }
898 while ($timePassed < $usec);
899 }
900
901
902
903 /**
904 * Contains information for a SOAP fault.
905 * Mainly used for returning faults from deployed functions
906 * in a server instance.
907 * @author Dietrich Ayala <dietrich@ganx4.com>
908 * @version $Id$
909 * @access public
910 */
911 class soap_fault extends nusoap_base {
912 /**
913 * The fault code (client|server)
914 * @var string
915 * @access private
916 */
917 var $faultcode;
918 /**
919 * The fault actor
920 * @var string
921 * @access private
922 */
923 var $faultactor;
924 /**
925 * The fault string, a description of the fault
926 * @var string
927 * @access private
928 */
929 var $faultstring;
930 /**
931 * The fault detail, typically a string or array of string
932 * @var mixed
933 * @access private
934 */
935 var $faultdetail;
936
937 /**
938 * constructor
939 *
940 * @param string $faultcode (client | server)
941 * @param string $faultactor only used when msg routed between multiple actors
942 * @param string $faultstring human readable error message
943 * @param mixed $faultdetail detail, typically a string or array of string
944 */
945 function soap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){
946 parent::nusoap_base();
947 $this->faultcode = $faultcode;
948 $this->faultactor = $faultactor;
949 $this->faultstring = $faultstring;
950 $this->faultdetail = $faultdetail;
951 }
952
953 /**
954 * serialize a fault
955 *
956 * @return string The serialization of the fault instance.
957 * @access public
958 */
959 function serialize(){
960 $ns_string = '';
961 foreach($this->namespaces as $k => $v){
962 $ns_string .= "\n xmlns:$k=\"$v\"";
963 }
964 $return_msg =
965 '<?xml version="1.0" encoding="'.$this->soap_defencoding.'"?>'.
966 '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"'.$ns_string.">\n".
967 '<SOAP-ENV:Body>'.
968 '<SOAP-ENV:Fault>'.
969 $this->serialize_val($this->faultcode, 'faultcode').
970 $this->serialize_val($this->faultactor, 'faultactor').
971 $this->serialize_val($this->faultstring, 'faultstring').
972 $this->serialize_val($this->faultdetail, 'detail').
973 '</SOAP-ENV:Fault>'.
974 '</SOAP-ENV:Body>'.
975 '</SOAP-ENV:Envelope>';
976 return $return_msg;
977 }
978 }
979
980
981
982 /**
983 * parses an XML Schema, allows access to it's data, other utility methods
984 * no validation... yet.
985 * very experimental and limited. As is discussed on XML-DEV, I'm one of the people
986 * that just doesn't have time to read the spec(s) thoroughly, and just have a couple of trusty
987 * tutorials I refer to :)
988 *
989 * @author Dietrich Ayala <dietrich@ganx4.com>
990 * @version $Id$
991 * @access public
992 */
993 class XMLSchema extends nusoap_base {
994
995 // files
996 var $schema = '';
997 var $xml = '';
998 // namespaces
999 var $enclosingNamespaces;
1000 // schema info
1001 var $schemaInfo = array();
1002 var $schemaTargetNamespace = '';
1003 // types, elements, attributes defined by the schema
1004 var $attributes = array();
1005 var $complexTypes = array();
1006 var $complexTypeStack = array();
1007 var $currentComplexType = null;
1008 var $elements = array();
1009 var $elementStack = array();
1010 var $currentElement = null;
1011 var $simpleTypes = array();
1012 var $simpleTypeStack = array();
1013 var $currentSimpleType = null;
1014 // imports
1015 var $imports = array();
1016 // parser vars
1017 var $parser;
1018 var $position = 0;
1019 var $depth = 0;
1020 var $depth_array = array();
1021 var $message = array();
1022 var $defaultNamespace = array();
1023
1024 /**
1025 * constructor
1026 *
1027 * @param string $schema schema document URI
1028 * @param string $xml xml document URI
1029 * @param string $namespaces namespaces defined in enclosing XML
1030 * @access public
1031 */
1032 function XMLSchema($schema='',$xml='',$namespaces=array()){
1033 parent::nusoap_base();
1034 $this->debug('xmlschema class instantiated, inside constructor');
1035 // files
1036 $this->schema = $schema;
1037 $this->xml = $xml;
1038
1039 // namespaces
1040 $this->enclosingNamespaces = $namespaces;
1041 $this->namespaces = array_merge($this->namespaces, $namespaces);
1042
1043 // parse schema file
1044 if($schema != ''){
1045 $this->debug('initial schema file: '.$schema);
1046 $this->parseFile($schema, 'schema');
1047 }
1048
1049 // parse xml file
1050 if($xml != ''){
1051 $this->debug('initial xml file: '.$xml);
1052 $this->parseFile($xml, 'xml');
1053 }
1054
1055 }
1056
1057 /**
1058 * parse an XML file
1059 *
1060 * @param string $xml, path/URL to XML file
1061 * @param string $type, (schema | xml)
1062 * @return boolean
1063 * @access public
1064 */
1065 function parseFile($xml,$type){
1066 // parse xml file
1067 if($xml != ""){
1068 $xmlStr = @join("",@file($xml));
1069 if($xmlStr == ""){
1070 $msg = 'Error reading XML from '.$xml;
1071 $this->setError($msg);
1072 $this->debug($msg);
1073 return false;
1074 } else {
1075 $this->debug("parsing $xml");
1076 $this->parseString($xmlStr,$type);
1077 $this->debug("done parsing $xml");
1078 return true;
1079 }
1080 }
1081 return false;
1082 }
1083
1084 /**
1085 * parse an XML string
1086 *
1087 * @param string $xml path or URL
1088 * @param string $type, (schema|xml)
1089 * @access private
1090 */
1091 function parseString($xml,$type){
1092 // parse xml string
1093 if($xml != ""){
1094
1095 // Create an XML parser.
1096 $this->parser = xml_parser_create();
1097 // Set the options for parsing the XML data.
1098 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
1099
1100 // Set the object for the parser.
1101 xml_set_object($this->parser, $this);
1102
1103 // Set the element handlers for the parser.
1104 if($type == "schema"){
1105 xml_set_element_handler($this->parser, 'schemaStartElement','schemaEndElement');
1106 xml_set_character_data_handler($this->parser,'schemaCharacterData');
1107 } elseif($type == "xml"){
1108 xml_set_element_handler($this->parser, 'xmlStartElement','xmlEndElement');
1109 xml_set_character_data_handler($this->parser,'xmlCharacterData');
1110 }
1111
1112 // Parse the XML file.
1113 if(!xml_parse($this->parser,$xml,true)){
1114 // Display an error message.
1115 $errstr = sprintf('XML error parsing XML schema on line %d: %s',
1116 xml_get_current_line_number($this->parser),
1117 xml_error_string(xml_get_error_code($this->parser))
1118 );
1119 $this->debug($errstr);
1120 $this->debug("XML payload:\n" . $xml);
1121 $this->setError($errstr);
1122 }
1123
1124 xml_parser_free($this->parser);
1125 } else{
1126 $this->debug('no xml passed to parseString()!!');
1127 $this->setError('no xml passed to parseString()!!');
1128 }
1129 }
1130
1131 /**
1132 * start-element handler
1133 *
1134 * @param string $parser XML parser object
1135 * @param string $name element name
1136 * @param string $attrs associative array of attributes
1137 * @access private
1138 */
1139 function schemaStartElement($parser, $name, $attrs) {
1140
1141 // position in the total number of elements, starting from 0
1142 $pos = $this->position++;
1143 $depth = $this->depth++;
1144 // set self as current value for this depth
1145 $this->depth_array[$depth] = $pos;
1146 $this->message[$pos] = array('cdata' => '');
1147 if ($depth > 0) {
1148 $this->defaultNamespace[$pos] = $this->defaultNamespace[$this->depth_array[$depth - 1]];
1149 } else {
1150 $this->defaultNamespace[$pos] = false;
1151 }
1152
1153 // get element prefix
1154 if($prefix = $this->getPrefix($name)){
1155 // get unqualified name
1156 $name = $this->getLocalPart($name);
1157 } else {
1158 $prefix = '';
1159 }
1160
1161 // loop thru attributes, expanding, and registering namespace declarations
1162 if(count($attrs) > 0){
1163 foreach($attrs as $k => $v){
1164 // if ns declarations, add to class level array of valid namespaces
1165 if(ereg("^xmlns",$k)){
1166 //$this->xdebug("$k: $v");
1167 //$this->xdebug('ns_prefix: '.$this->getPrefix($k));
1168 if($ns_prefix = substr(strrchr($k,':'),1)){
1169 //$this->xdebug("Add namespace[$ns_prefix] = $v");
1170 $this->namespaces[$ns_prefix] = $v;
1171 } else {
1172 $this->defaultNamespace[$pos] = $v;
1173 if (! $this->getPrefixFromNamespace($v)) {
1174 $this->namespaces['ns'.(count($this->namespaces)+1)] = $v;
1175 }
1176 }
1177 if($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema'){
1178 $this->XMLSchemaVersion = $v;
1179 $this->namespaces['xsi'] = $v.'-instance';
1180 }
1181 }
1182 }
1183 foreach($attrs as $k => $v){
1184 // expand each attribute
1185 $k = strpos($k,':') ? $this->expandQname($k) : $k;
1186 $v = strpos($v,':') ? $this->expandQname($v) : $v;
1187 $eAttrs[$k] = $v;
1188 }
1189 $attrs = $eAttrs;
1190 } else {
1191 $attrs = array();
1192 }
1193 // find status, register data
1194 switch($name){
1195 case 'all': // (optional) compositor content for a complexType
1196 case 'choice':
1197 case 'group':
1198 case 'sequence':
1199 //$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
1200 $this->complexTypes[$this->currentComplexType]['compositor'] = $name;
1201 //if($name == 'all' || $name == 'sequence'){
1202 // $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1203 //}
1204 break;
1205 case 'attribute': // complexType attribute
1206 //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
1207 $this->xdebug("parsing attribute:");
1208 $this->appendDebug($this->varDump($attrs));
1209 if (!isset($attrs['form'])) {
1210 $attrs['form'] = $this->schemaInfo['attributeFormDefault'];
1211 }
1212 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1213 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1214 if (!strpos($v, ':')) {
1215 // no namespace in arrayType attribute value...
1216 if ($this->defaultNamespace[$pos]) {
1217 // ...so use the default
1218 $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1219 }
1220 }
1221 }
1222 if(isset($attrs['name'])){
1223 $this->attributes[$attrs['name']] = $attrs;
1224 $aname = $attrs['name'];
1225 } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
1226 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1227 $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1228 } else {
1229 $aname = '';
1230 }
1231 } elseif(isset($attrs['ref'])){
1232 $aname = $attrs['ref'];
1233 $this->attributes[$attrs['ref']] = $attrs;
1234 }
1235
1236 if($this->currentComplexType){ // This should *always* be
1237 $this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs;
1238 }
1239 // arrayType attribute
1240 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType'){
1241 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1242 $prefix = $this->getPrefix($aname);
1243 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
1244 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1245 } else {
1246 $v = '';
1247 }
1248 if(strpos($v,'[,]')){
1249 $this->complexTypes[$this->currentComplexType]['multidimensional'] = true;
1250 }
1251 $v = substr($v,0,strpos($v,'[')); // clip the []
1252 if(!strpos($v,':') && isset($this->typemap[$this->XMLSchemaVersion][$v])){
1253 $v = $this->XMLSchemaVersion.':'.$v;
1254 }
1255 $this->complexTypes[$this->currentComplexType]['arrayType'] = $v;
1256 }
1257 break;
1258 case 'complexContent': // (optional) content for a complexType
1259 break;
1260 case 'complexType':
1261 array_push($this->complexTypeStack, $this->currentComplexType);
1262 if(isset($attrs['name'])){
1263 $this->xdebug('processing named complexType '.$attrs['name']);
1264 //$this->currentElement = false;
1265 $this->currentComplexType = $attrs['name'];
1266 $this->complexTypes[$this->currentComplexType] = $attrs;
1267 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
1268 // This is for constructs like
1269 // <complexType name="ListOfString" base="soap:Array">
1270 // <sequence>
1271 // <element name="string" type="xsd:string"
1272 // minOccurs="0" maxOccurs="unbounded" />
1273 // </sequence>
1274 // </complexType>
1275 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1276 $this->xdebug('complexType is unusual array');
1277 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1278 } else {
1279 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1280 }
1281 }else{
1282 $this->xdebug('processing unnamed complexType for element '.$this->currentElement);
1283 $this->currentComplexType = $this->currentElement . '_ContainedType';
1284 //$this->currentElement = false;
1285 $this->complexTypes[$this->currentComplexType] = $attrs;
1286 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
1287 // This is for constructs like
1288 // <complexType name="ListOfString" base="soap:Array">
1289 // <sequence>
1290 // <element name="string" type="xsd:string"
1291 // minOccurs="0" maxOccurs="unbounded" />
1292 // </sequence>
1293 // </complexType>
1294 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1295 $this->xdebug('complexType is unusual array');
1296 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1297 } else {
1298 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1299 }
1300 }
1301 break;
1302 case 'element':
1303 array_push($this->elementStack, $this->currentElement);
1304 // elements defined as part of a complex type should
1305 // not really be added to $this->elements, but for some
1306 // reason, they are
1307 if (!isset($attrs['form'])) {
1308 $attrs['form'] = $this->schemaInfo['elementFormDefault'];
1309 }
1310 if(isset($attrs['type'])){
1311 $this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']);
1312 if (! $this->getPrefix($attrs['type'])) {
1313 if ($this->defaultNamespace[$pos]) {
1314 $attrs['type'] = $this->defaultNamespace[$pos] . ':' . $attrs['type'];
1315 $this->xdebug('used default namespace to make type ' . $attrs['type']);
1316 }
1317 }
1318 // This is for constructs like
1319 // <complexType name="ListOfString" base="soap:Array">
1320 // <sequence>
1321 // <element name="string" type="xsd:string"
1322 // minOccurs="0" maxOccurs="unbounded" />
1323 // </sequence>
1324 // </complexType>
1325 if ($this->currentComplexType && $this->complexTypes[$this->currentComplexType]['phpType'] == 'array') {
1326 $this->xdebug('arrayType for unusual array is ' . $attrs['type']);
1327 $this->complexTypes[$this->currentComplexType]['arrayType'] = $attrs['type'];
1328 }
1329 $this->currentElement = $attrs['name'];
1330 $this->elements[ $attrs['name'] ] = $attrs;
1331 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1332 $ename = $attrs['name'];
1333 } elseif(isset($attrs['ref'])){
1334 $this->xdebug("processing element as ref to ".$attrs['ref']);
1335 $this->currentElement = "ref to ".$attrs['ref'];
1336 $ename = $this->getLocalPart($attrs['ref']);
1337 } else {
1338 $this->xdebug("processing untyped element ".$attrs['name']);
1339 $this->currentElement = $attrs['name'];
1340 $this->elements[ $attrs['name'] ] = $attrs;
1341 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1342 $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['name'] . '_ContainedType';
1343 $this->elements[ $attrs['name'] ]['type'] = $attrs['type'];
1344 $ename = $attrs['name'];
1345 }
1346 if(isset($ename) && $this->currentComplexType){
1347 $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs;
1348 }
1349 break;
1350 case 'enumeration': // restriction value list member
1351 $this->xdebug('enumeration ' . $attrs['value']);
1352 if ($this->currentSimpleType) {
1353 $this->simpleTypes[$this->currentSimpleType]['enumeration'][] = $attrs['value'];
1354 } elseif ($this->currentComplexType) {
1355 $this->complexTypes[$this->currentComplexType]['enumeration'][] = $attrs['value'];
1356 }
1357 break;
1358 case 'extension': // simpleContent or complexContent type extension
1359 $this->xdebug('extension ' . $attrs['base']);
1360 if ($this->currentComplexType) {
1361 $this->complexTypes[$this->currentComplexType]['extensionBase'] = $attrs['base'];
1362 }
1363 break;
1364 case 'import':
1365 if (isset($attrs['schemaLocation'])) {
1366 //$this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']);
1367 $this->imports[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
1368 } else {
1369 //$this->xdebug('import namespace ' . $attrs['namespace']);
1370 $this->imports[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
1371 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
1372 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
1373 }
1374 }
1375 break;
1376 case 'list': // simpleType value list
1377 break;
1378 case 'restriction': // simpleType, simpleContent or complexContent value restriction
1379 $this->xdebug('restriction ' . $attrs['base']);
1380 if($this->currentSimpleType){
1381 $this->simpleTypes[$this->currentSimpleType]['type'] = $attrs['base'];
1382 } elseif($this->currentComplexType){
1383 $this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base'];
1384 if(strstr($attrs['base'],':') == ':Array'){
1385 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1386 }
1387 }
1388 break;
1389 case 'schema':
1390 $this->schemaInfo = $attrs;
1391 $this->schemaInfo['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
1392 if (isset($attrs['targetNamespace'])) {
1393 $this->schemaTargetNamespace = $attrs['targetNamespace'];
1394 }
1395 if (!isset($attrs['elementFormDefault'])) {
1396 $this->schemaInfo['elementFormDefault'] = 'unqualified';
1397 }
1398 if (!isset($attrs['attributeFormDefault'])) {
1399 $this->schemaInfo['attributeFormDefault'] = 'unqualified';
1400 }
1401 break;
1402 case 'simpleContent': // (optional) content for a complexType
1403 break;
1404 case 'simpleType':
1405 array_push($this->simpleTypeStack, $this->currentSimpleType);
1406 if(isset($attrs['name'])){
1407 $this->xdebug("processing simpleType for name " . $attrs['name']);
1408 $this->currentSimpleType = $attrs['name'];
1409 $this->simpleTypes[ $attrs['name'] ] = $attrs;
1410 $this->simpleTypes[ $attrs['name'] ]['typeClass'] = 'simpleType';
1411 $this->simpleTypes[ $attrs['name'] ]['phpType'] = 'scalar';
1412 } else {
1413 $this->xdebug('processing unnamed simpleType for element '.$this->currentElement);
1414 $this->currentSimpleType = $this->currentElement . '_ContainedType';
1415 //$this->currentElement = false;
1416 $this->simpleTypes[$this->currentSimpleType] = $attrs;
1417 $this->simpleTypes[$this->currentSimpleType]['phpType'] = 'scalar';
1418 }
1419 break;
1420 case 'union': // simpleType type list
1421 break;
1422 default:
1423 //$this->xdebug("do not have anything to do for element $name");
1424 }
1425 }
1426
1427 /**
1428 * end-element handler
1429 *
1430 * @param string $parser XML parser object
1431 * @param string $name element name
1432 * @access private
1433 */
1434 function schemaEndElement($parser, $name) {
1435 // bring depth down a notch
1436 $this->depth--;
1437 // position of current element is equal to the last value left in depth_array for my depth
1438 if(isset($this->depth_array[$this->depth])){
1439 $pos = $this->depth_array[$this->depth];
1440 }
1441 // get element prefix
1442 if ($prefix = $this->getPrefix($name)){
1443 // get unqualified name
1444 $name = $this->getLocalPart($name);
1445 } else {
1446 $prefix = '';
1447 }
1448 // move on...
1449 if($name == 'complexType'){
1450 $this->xdebug('done processing complexType ' . ($this->currentComplexType ? $this->currentComplexType : '(unknown)'));
1451 $this->currentComplexType = array_pop($this->complexTypeStack);
1452 //$this->currentElement = false;
1453 }
1454 if($name == 'element'){
1455 $this->xdebug('done processing element ' . ($this->currentElement ? $this->currentElement : '(unknown)'));
1456 $this->currentElement = array_pop($this->elementStack);
1457 }
1458 if($name == 'simpleType'){
1459 $this->xdebug('done processing simpleType ' . ($this->currentSimpleType ? $this->currentSimpleType : '(unknown)'));
1460 $this->currentSimpleType = array_pop($this->simpleTypeStack);
1461 }
1462 }
1463
1464 /**
1465 * element content handler
1466 *
1467 * @param string $parser XML parser object
1468 * @param string $data element content
1469 * @access private
1470 */
1471 function schemaCharacterData($parser, $data){
1472 $pos = $this->depth_array[$this->depth - 1];
1473 $this->message[$pos]['cdata'] .= $data;
1474 }
1475
1476 /**
1477 * serialize the schema
1478 *
1479 * @access public
1480 */
1481 function serializeSchema(){
1482
1483 $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion);
1484 $xml = '';
1485 // imports
1486 if (sizeof($this->imports) > 0) {
1487 foreach($this->imports as $ns => $list) {
1488 foreach ($list as $ii) {
1489 if ($ii['location'] != '') {
1490 $xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n";
1491 } else {
1492 $xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n";
1493 }
1494 }
1495 }
1496 }
1497 // complex types
1498 foreach($this->complexTypes as $typeName => $attrs){
1499 $contentStr = '';
1500 // serialize child elements
1501 if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){
1502 foreach($attrs['elements'] as $element => $eParts){
1503 if(isset($eParts['ref'])){
1504 $contentStr .= " <$schemaPrefix:element ref=\"$element\"/>\n";
1505 } else {
1506 $contentStr .= " <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\"";
1507 foreach ($eParts as $aName => $aValue) {
1508 // handle, e.g., abstract, default, form, minOccurs, maxOccurs, nillable
1509 if ($aName != 'name' && $aName != 'type') {
1510 $contentStr .= " $aName=\"$aValue\"";
1511 }
1512 }
1513 $contentStr .= "/>\n";
1514 }
1515 }
1516 // compositor wraps elements
1517 if (isset($attrs['compositor']) && ($attrs['compositor'] != '')) {
1518 $contentStr = " <$schemaPrefix:$attrs[compositor]>\n".$contentStr." </$schemaPrefix:$attrs[compositor]>\n";
1519 }
1520 }
1521 // attributes
1522 if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){
1523 foreach($attrs['attrs'] as $attr => $aParts){
1524 $contentStr .= " <$schemaPrefix:attribute";
1525 foreach ($aParts as $a => $v) {
1526 if ($a == 'ref' || $a == 'type') {
1527 $contentStr .= " $a=\"".$this->contractQName($v).'"';
1528 } elseif ($a == 'http://schemas.xmlsoap.org/wsdl/:arrayType') {
1529 $this->usedNamespaces['wsdl'] = $this->namespaces['wsdl'];
1530 $contentStr .= ' wsdl:arrayType="'.$this->contractQName($v).'"';
1531 } else {
1532 $contentStr .= " $a=\"$v\"";
1533 }
1534 }
1535 $contentStr .= "/>\n";
1536 }
1537 }
1538 // if restriction
1539 if (isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
1540 $contentStr = " <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr." </$schemaPrefix:restriction>\n";
1541 // complex or simple content
1542 if ((isset($attrs['elements']) && count($attrs['elements']) > 0) || (isset($attrs['attrs']) && count($attrs['attrs']) > 0)){
1543 $contentStr = " <$schemaPrefix:complexContent>\n".$contentStr." </$schemaPrefix:complexContent>\n";
1544 }
1545 }
1546 // finalize complex type
1547 if($contentStr != ''){
1548 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." </$schemaPrefix:complexType>\n";
1549 } else {
1550 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n";
1551 }
1552 $xml .= $contentStr;
1553 }
1554 // simple types
1555 if(isset($this->simpleTypes) && count($this->simpleTypes) > 0){
1556 foreach($this->simpleTypes as $typeName => $eParts){
1557 $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\"/>\n";
1558 if (isset($eParts['enumeration'])) {
1559 foreach ($eParts['enumeration'] as $e) {
1560 $xml .= " <$schemaPrefix:enumeration value=\"$e\"/>\n";
1561 }
1562 }
1563 $xml .= " </$schemaPrefix:simpleType>";
1564 }
1565 }
1566 // elements
1567 if(isset($this->elements) && count($this->elements) > 0){
1568 foreach($this->elements as $element => $eParts){
1569 $xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n";
1570 }
1571 }
1572 // attributes
1573 if(isset($this->attributes) && count($this->attributes) > 0){
1574 foreach($this->attributes as $attr => $aParts){
1575 $xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>";
1576 }
1577 }
1578 // finish 'er up
1579 $el = "<$schemaPrefix:schema targetNamespace=\"$this->schemaTargetNamespace\"\n";
1580 foreach (array_diff($this->usedNamespaces, $this->enclosingNamespaces) as $nsp => $ns) {
1581 $el .= " xmlns:$nsp=\"$ns\"\n";
1582 }
1583 $xml = $el . ">\n".$xml."</$schemaPrefix:schema>\n";
1584 return $xml;
1585 }
1586
1587 /**
1588 * adds debug data to the clas level debug string
1589 *
1590 * @param string $string debug data
1591 * @access private
1592 */
1593 function xdebug($string){
1594 $this->debug('<' . $this->schemaTargetNamespace . '> '.$string);
1595 }
1596
1597 /**
1598 * get the PHP type of a user defined type in the schema
1599 * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
1600 * returns false if no type exists, or not w/ the given namespace
1601 * else returns a string that is either a native php type, or 'struct'
1602 *
1603 * @param string $type, name of defined type
1604 * @param string $ns, namespace of type
1605 * @return mixed
1606 * @access public
1607 * @deprecated
1608 */
1609 function getPHPType($type,$ns){
1610 if(isset($this->typemap[$ns][$type])){
1611 //print "found type '$type' and ns $ns in typemap<br>";
1612 return $this->typemap[$ns][$type];
1613 } elseif(isset($this->complexTypes[$type])){
1614 //print "getting type '$type' and ns $ns from complexTypes array<br>";
1615 return $this->complexTypes[$type]['phpType'];
1616 }
1617 return false;
1618 }
1619
1620 /**
1621 * returns an associative array of information about a given type
1622 * returns false if no type exists by the given name
1623 *
1624 * For a complexType typeDef = array(
1625 * 'restrictionBase' => '',
1626 * 'phpType' => '',
1627 * 'compositor' => '(sequence|all)',
1628 * 'elements' => array(), // refs to elements array
1629 * 'attrs' => array() // refs to attributes array
1630 * ... and so on (see addComplexType)
1631 * )
1632 *
1633 * For simpleType or element, the array has different keys.
1634 *
1635 * @param string
1636 * @return mixed
1637 * @access public
1638 * @see addComplexType
1639 * @see addSimpleType
1640 * @see addElement
1641 */
1642 function getTypeDef($type){
1643 //$this->debug("in getTypeDef for type $type");
1644 if(isset($this->complexTypes[$type])){
1645 $this->xdebug("in getTypeDef, found complexType $type");
1646 return $this->complexTypes[$type];
1647 } elseif(isset($this->simpleTypes[$type])){
1648 $this->xdebug("in getTypeDef, found simpleType $type");
1649 if (!isset($this->simpleTypes[$type]['phpType'])) {
1650 // get info for type to tack onto the simple type
1651 // TODO: can this ever really apply (i.e. what is a simpleType really?)
1652 $uqType = substr($this->simpleTypes[$type]['type'], strrpos($this->simpleTypes[$type]['type'], ':') + 1);
1653 $ns = substr($this->simpleTypes[$type]['type'], 0, strrpos($this->simpleTypes[$type]['type'], ':'));
1654 $etype = $this->getTypeDef($uqType);
1655 if ($etype) {
1656 $this->xdebug("in getTypeDef, found type for simpleType $type:");
1657 $this->xdebug($this->varDump($etype));
1658 if (isset($etype['phpType'])) {
1659 $this->simpleTypes[$type]['phpType'] = $etype['phpType'];
1660 }
1661 if (isset($etype['elements'])) {
1662 $this->simpleTypes[$type]['elements'] = $etype['elements'];
1663 }
1664 }
1665 }
1666 return $this->simpleTypes[$type];
1667 } elseif(isset($this->elements[$type])){
1668 $this->xdebug("in getTypeDef, found element $type");
1669 if (!isset($this->elements[$type]['phpType'])) {
1670 // get info for type to tack onto the element
1671 $uqType = substr($this->elements[$type]['type'], strrpos($this->elements[$type]['type'], ':') + 1);
1672 $ns = substr($this->elements[$type]['type'], 0, strrpos($this->elements[$type]['type'], ':'));
1673 $etype = $this->getTypeDef($uqType);
1674 if ($etype) {
1675 $this->xdebug("in getTypeDef, found type for element $type:");
1676 $this->xdebug($this->varDump($etype));
1677 if (isset($etype['phpType'])) {
1678 $this->elements[$type]['phpType'] = $etype['phpType'];
1679 }
1680 if (isset($etype['elements'])) {
1681 $this->elements[$type]['elements'] = $etype['elements'];
1682 }
1683 } elseif ($ns == 'http://www.w3.org/2001/XMLSchema') {
1684 $this->xdebug("in getTypeDef, element $type is an XSD type");
1685 $this->elements[$type]['phpType'] = 'scalar';
1686 }
1687 }
1688 return $this->elements[$type];
1689 } elseif(isset($this->attributes[$type])){
1690 $this->xdebug("in getTypeDef, found attribute $type");
1691 return $this->attributes[$type];
1692 } elseif (ereg('_ContainedType$', $type)) {
1693 $this->xdebug("in getTypeDef, have an untyped element $type");
1694 $typeDef['typeClass'] = 'simpleType';
1695 $typeDef['phpType'] = 'scalar';
1696 $typeDef['type'] = 'http://www.w3.org/2001/XMLSchema:string';
1697 return $typeDef;
1698 }
1699 $this->xdebug("in getTypeDef, did not find $type");
1700 return false;
1701 }
1702
1703 /**
1704 * returns a sample serialization of a given type, or false if no type by the given name
1705 *
1706 * @param string $type, name of type
1707 * @return mixed
1708 * @access public
1709 * @deprecated
1710 */
1711 function serializeTypeDef($type){
1712 //print "in sTD() for type $type<br>";
1713 if($typeDef = $this->getTypeDef($type)){
1714 $str .= '<'.$type;
1715 if(is_array($typeDef['attrs'])){
1716 foreach($attrs as $attName => $data){
1717 $str .= " $attName=\"{type = ".$data['type']."}\"";
1718 }
1719 }
1720 $str .= " xmlns=\"".$this->schema['targetNamespace']."\"";
1721 if(count($typeDef['elements']) > 0){
1722 $str .= ">";
1723 foreach($typeDef['elements'] as $element => $eData){
1724 $str .= $this->serializeTypeDef($element);
1725 }
1726 $str .= "</$type>";
1727 } elseif($typeDef['typeClass'] == 'element') {
1728 $str .= "></$type>";
1729 } else {
1730 $str .= "/>";
1731 }
1732 return $str;
1733 }
1734 return false;
1735 }
1736
1737 /**
1738 * returns HTML form elements that allow a user
1739 * to enter values for creating an instance of the given type.
1740 *
1741 * @param string $name, name for type instance
1742 * @param string $type, name of type
1743 * @return string
1744 * @access public
1745 * @deprecated
1746 */
1747 function typeToForm($name,$type){
1748 // get typedef
1749 if($typeDef = $this->getTypeDef($type)){
1750 // if struct
1751 if($typeDef['phpType'] == 'struct'){
1752 $buffer .= '<table>';
1753 foreach($typeDef['elements'] as $child => $childDef){
1754 $buffer .= "
1755 <tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>
1756 <td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
1757 }
1758 $buffer .= '</table>';
1759 // if array
1760 } elseif($typeDef['phpType'] == 'array'){
1761 $buffer .= '<table>';
1762 for($i=0;$i < 3; $i++){
1763 $buffer .= "
1764 <tr><td align='right'>array item (type: $typeDef[arrayType]):</td>
1765 <td><input type='text' name='parameters[".$name."][]'></td></tr>";
1766 }
1767 $buffer .= '</table>';
1768 // if scalar
1769 } else {
1770 $buffer .= "<input type='text' name='parameters[$name]'>";
1771 }
1772 } else {
1773 $buffer .= "<input type='text' name='parameters[$name]'>";
1774 }
1775 return $buffer;
1776 }
1777
1778 /**
1779 * adds a complex type to the schema
1780 *
1781 * example: array
1782 *
1783 * addType(
1784 * 'ArrayOfstring',
1785 * 'complexType',
1786 * 'array',
1787 * '',
1788 * 'SOAP-ENC:Array',
1789 * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
1790 * 'xsd:string'
1791 * );
1792 *
1793 * example: PHP associative array ( SOAP Struct )
1794 *
1795 * addType(
1796 * 'SOAPStruct',
1797 * 'complexType',
1798 * 'struct',
1799 * 'all',
1800 * array('myVar'=> array('name'=>'myVar','type'=>'string')
1801 * );
1802 *
1803 * @param name
1804 * @param typeClass (complexType|simpleType|attribute)
1805 * @param phpType: currently supported are array and struct (php assoc array)
1806 * @param compositor (all|sequence|choice)
1807 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1808 * @param elements = array ( name = array(name=>'',type=>'') )
1809 * @param attrs = array(
1810 * array(
1811 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
1812 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
1813 * )
1814 * )
1815 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
1816 * @access public
1817 * @see getTypeDef
1818 */
1819 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
1820 $this->complexTypes[$name] = array(
1821 'name' => $name,
1822 'typeClass' => $typeClass,
1823 'phpType' => $phpType,
1824 'compositor'=> $compositor,
1825 'restrictionBase' => $restrictionBase,
1826 'elements' => $elements,
1827 'attrs' => $attrs,
1828 'arrayType' => $arrayType
1829 );
1830
1831 $this->xdebug("addComplexType $name:");
1832 $this->appendDebug($this->varDump($this->complexTypes[$name]));
1833 }
1834
1835 /**
1836 * adds a simple type to the schema
1837 *
1838 * @param string $name
1839 * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1840 * @param string $typeClass (should always be simpleType)
1841 * @param string $phpType (should always be scalar)
1842 * @param array $enumeration array of values
1843 * @access public
1844 * @see xmlschema
1845 * @see getTypeDef
1846 */
1847 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
1848 $this->simpleTypes[$name] = array(
1849 'name' => $name,
1850 'typeClass' => $typeClass,
1851 'phpType' => $phpType,
1852 'type' => $restrictionBase,
1853 'enumeration' => $enumeration
1854 );
1855
1856 $this->xdebug("addSimpleType $name:");
1857 $this->appendDebug($this->varDump($this->simpleTypes[$name]));
1858 }
1859
1860 /**
1861 * adds an element to the schema
1862 *
1863 * @param array $attrs attributes that must include name and type
1864 * @see xmlschema
1865 * @access public
1866 */
1867 function addElement($attrs) {
1868 if (! $this->getPrefix($attrs['type'])) {
1869 $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['type'];
1870 }
1871 $this->elements[ $attrs['name'] ] = $attrs;
1872 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1873
1874 $this->xdebug("addElement " . $attrs['name']);
1875 $this->appendDebug($this->varDump($this->elements[ $attrs['name'] ]));
1876 }
1877 }
1878
1879
1880
1881 /**
1882 * For creating serializable abstractions of native PHP types. This class
1883 * allows element name/namespace, XSD type, and XML attributes to be
1884 * associated with a value. This is extremely useful when WSDL is not
1885 * used, but is also useful when WSDL is used with polymorphic types, including
1886 * xsd:anyType and user-defined types.
1887 *
1888 * @author Dietrich Ayala <dietrich@ganx4.com>
1889 * @version $Id$
1890 * @access public
1891 */
1892 class soapval extends nusoap_base {
1893 /**
1894 * The XML element name
1895 *
1896 * @var string
1897 * @access private
1898 */
1899 var $name;
1900 /**
1901 * The XML type name (string or false)
1902 *
1903 * @var mixed
1904 * @access private
1905 */
1906 var $type;
1907 /**
1908 * The PHP value
1909 *
1910 * @var mixed
1911 * @access private
1912 */
1913 var $value;
1914 /**
1915 * The XML element namespace (string or false)
1916 *
1917 * @var mixed
1918 * @access private
1919 */
1920 var $element_ns;
1921 /**
1922 * The XML type namespace (string or false)
1923 *
1924 * @var mixed
1925 * @access private
1926 */
1927 var $type_ns;
1928 /**
1929 * The XML element attributes (array or false)
1930 *
1931 * @var mixed
1932 * @access private
1933 */
1934 var $attributes;
1935
1936 /**
1937 * constructor
1938 *
1939 * @param string $name optional name
1940 * @param mixed $type optional type name
1941 * @param mixed $value optional value
1942 * @param mixed $element_ns optional namespace of value
1943 * @param mixed $type_ns optional namespace of type
1944 * @param mixed $attributes associative array of attributes to add to element serialization
1945 * @access public
1946 */
1947 function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) {
1948 parent::nusoap_base();
1949 $this->name = $name;
1950 $this->type = $type;
1951 $this->value = $value;
1952 $this->element_ns = $element_ns;
1953 $this->type_ns = $type_ns;
1954 $this->attributes = $attributes;
1955 }
1956
1957 /**
1958 * return serialized value
1959 *
1960 * @param string $use The WSDL use value (encoded|literal)
1961 * @return string XML data
1962 * @access public
1963 */
1964 function serialize($use='encoded') {
1965 return $this->serialize_val($this->value,$this->name,$this->type,$this->element_ns,$this->type_ns,$this->attributes,$use);
1966 }
1967
1968 /**
1969 * decodes a soapval object into a PHP native type
1970 *
1971 * @return mixed
1972 * @access public
1973 */
1974 function decode(){
1975 return $this->value;
1976 }
1977 }
1978
1979
1980
1981 /**
1982 * transport class for sending/receiving data via HTTP and HTTPS
1983 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
1984 *
1985 * @author Dietrich Ayala <dietrich@ganx4.com>
1986 * @version $Id$
1987 * @access public
1988 */
1989 class soap_transport_http extends nusoap_base {
1990
1991 var $url = '';
1992 var $uri = '';
1993 var $digest_uri = '';
1994 var $scheme = '';
1995 var $host = '';
1996 var $port = '';
1997 var $path = '';
1998 var $request_method = 'POST';
1999 var $protocol_version = '1.0';
2000 var $encoding = '';
2001 var $outgoing_headers = array();
2002 var $incoming_headers = array();
2003 var $incoming_cookies = array();
2004 var $outgoing_payload = '';
2005 var $incoming_payload = '';
2006 var $useSOAPAction = true;
2007 var $persistentConnection = false;
2008 var $ch = false; // cURL handle
2009 var $username = '';
2010 var $password = '';
2011 var $authtype = '';
2012 var $digestRequest = array();
2013 var $certRequest = array(); // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional)
2014 // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem'
2015 // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem'
2016 // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem'
2017 // passphrase: SSL key password/passphrase
2018 // verifypeer: default is 1
2019 // verifyhost: default is 1
2020
2021 /**
2022 * constructor
2023 */
2024 function soap_transport_http($url){
2025 parent::nusoap_base();
2026 $this->setURL($url);
2027 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
2028 $this->outgoing_headers['User-Agent'] = $this->title.'/'.$this->version.' ('.$rev[1].')';
2029 $this->debug('set User-Agent: ' . $this->outgoing_headers['User-Agent']);
2030 }
2031
2032 function setURL($url) {
2033 $this->url = $url;
2034
2035 $u = parse_url($url);
2036 foreach($u as $k => $v){
2037 $this->debug("$k = $v");
2038 $this->$k = $v;
2039 }
2040
2041 // add any GET params to path
2042 if(isset($u['query']) && $u['query'] != ''){
2043 $this->path .= '?' . $u['query'];
2044 }
2045
2046 // set default port
2047 if(!isset($u['port'])){
2048 if($u['scheme'] == 'https'){
2049 $this->port = 443;
2050 } else {
2051 $this->port = 80;
2052 }
2053 }
2054
2055 $this->uri = $this->path;
2056 $this->digest_uri = $this->uri;
2057
2058 // build headers
2059 if (!isset($u['port'])) {
2060 $this->outgoing_headers['Host'] = $this->host;
2061 } else {
2062 $this->outgoing_headers['Host'] = $this->host.':'.$this->port;
2063 }
2064 $this->debug('set Host: ' . $this->outgoing_headers['Host']);
2065
2066 if (isset($u['user']) && $u['user'] != '') {
2067 $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : '');
2068 }
2069 }
2070
2071 function connect($connection_timeout=0,$response_timeout=30){
2072 // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
2073 // "regular" socket.
2074 // TODO: disabled for now because OpenSSL must be *compiled* in (not just
2075 // loaded), and until PHP5 stream_get_wrappers is not available.
2076 // if ($this->scheme == 'https') {
2077 // if (version_compare(phpversion(), '4.3.0') >= 0) {
2078 // if (extension_loaded('openssl')) {
2079 // $this->scheme = 'ssl';
2080 // $this->debug('Using SSL over OpenSSL');
2081 // }
2082 // }
2083 // }
2084 $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port");
2085 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2086 // use persistent connection
2087 if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){
2088 if (!feof($this->fp)) {
2089 $this->debug('Re-use persistent connection');
2090 return true;
2091 }
2092 fclose($this->fp);
2093 $this->debug('Closed persistent connection at EOF');
2094 }
2095
2096 // munge host if using OpenSSL
2097 if ($this->scheme == 'ssl') {
2098 $host = 'ssl://' . $this->host;
2099 } else {
2100 $host = $this->host;
2101 }
2102 $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout);
2103
2104 // open socket
2105 if($connection_timeout > 0){
2106 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout);
2107 } else {
2108 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str);
2109 }
2110
2111 // test pointer
2112 if(!$this->fp) {
2113 $msg = 'Couldn\'t open socket connection to server ' . $this->url;
2114 if ($this->errno) {
2115 $msg .= ', Error ('.$this->errno.'): '.$this->error_str;
2116 } else {
2117 $msg .= ' prior to connect(). This is often a problem looking up the host name.';
2118 }
2119 $this->debug($msg);
2120 $this->setError($msg);
2121 return false;
2122 }
2123
2124 // set response timeout
2125 $this->debug('set response timeout to ' . $response_timeout);
2126 socket_set_timeout( $this->fp, $response_timeout);
2127
2128 $this->debug('socket connected');
2129 return true;
2130 } else if ($this->scheme == 'https') {
2131 if (!extension_loaded('curl')) {
2132 $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
2133 return false;
2134 }
2135 $this->debug('connect using https');
2136 // init CURL
2137 $this->ch = curl_init();
2138 // set url
2139 $hostURL = ($this->port != '') ? "https://$this->host:$this->port" : "https://$this->host";
2140 // add path
2141 $hostURL .= $this->path;
2142 curl_setopt($this->ch, CURLOPT_URL, $hostURL);
2143 // follow location headers (re-directs)
2144 curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1);
2145 // ask for headers in the response output
2146 curl_setopt($this->ch, CURLOPT_HEADER, 1);
2147 // ask for the response output as the return value
2148 curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
2149 // encode
2150 // We manage this ourselves through headers and encoding
2151 // if(function_exists('gzuncompress')){
2152 // curl_setopt($this->ch, CURLOPT_ENCODING, 'deflate');
2153 // }
2154 // persistent connection
2155 if ($this->persistentConnection) {
2156 // The way we send data, we cannot use persistent connections, since
2157 // there will be some "junk" at the end of our request.
2158 //curl_setopt($this->ch, CURL_HTTP_VERSION_1_1, true);
2159 $this->persistentConnection = false;
2160 $this->outgoing_headers['Connection'] = 'close';
2161 $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
2162 }
2163 // set timeout
2164 if ($connection_timeout != 0) {
2165 curl_setopt($this->ch, CURLOPT_TIMEOUT, $connection_timeout);
2166 }
2167 // TODO: cURL has added a connection timeout separate from the response timeout
2168 //if ($connection_timeout != 0) {
2169 // curl_setopt($this->ch, CURLOPT_CONNECTIONTIMEOUT, $connection_timeout);
2170 //}
2171 //if ($response_timeout != 0) {
2172 // curl_setopt($this->ch, CURLOPT_TIMEOUT, $response_timeout);
2173 //}
2174
2175 // recent versions of cURL turn on peer/host checking by default,
2176 // while PHP binaries are not compiled with a default location for the
2177 // CA cert bundle, so disable peer/host checking.
2178 //curl_setopt($this->ch, CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');
2179 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0);
2180 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0);
2181
2182 // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo)
2183 if ($this->authtype == 'certificate') {
2184 if (isset($this->certRequest['cainfofile'])) {
2185 curl_setopt($this->ch, CURLOPT_CAINFO, $this->certRequest['cainfofile']);
2186 }
2187 if (isset($this->certRequest['verifypeer'])) {
2188 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']);
2189 } else {
2190 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1);
2191 }
2192 if (isset($this->certRequest['verifyhost'])) {
2193 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']);
2194 } else {
2195 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1);
2196 }
2197 if (isset($this->certRequest['sslcertfile'])) {
2198 curl_setopt($this->ch, CURLOPT_SSLCERT, $this->certRequest['sslcertfile']);
2199 }
2200 if (isset($this->certRequest['sslkeyfile'])) {
2201 curl_setopt($this->ch, CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']);
2202 }
2203 if (isset($this->certRequest['passphrase'])) {
2204 curl_setopt($this->ch, CURLOPT_SSLKEYPASSWD , $this->certRequest['passphrase']);
2205 }
2206 }
2207 $this->debug('cURL connection set up');
2208 return true;
2209 } else {
2210 $this->setError('Unknown scheme ' . $this->scheme);
2211 $this->debug('Unknown scheme ' . $this->scheme);
2212 return false;
2213 }
2214 }
2215
2216 /**
2217 * send the SOAP message via HTTP
2218 *
2219 * @param string $data message data
2220 * @param integer $timeout set connection timeout in seconds
2221 * @param integer $response_timeout set response timeout in seconds
2222 * @param array $cookies cookies to send
2223 * @return string data
2224 * @access public
2225 */
2226 function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) {
2227
2228 $this->debug('entered send() with data of length: '.strlen($data));
2229
2230 $this->tryagain = true;
2231 $tries = 0;
2232 while ($this->tryagain) {
2233 $this->tryagain = false;
2234 if ($tries++ < 2) {
2235 // make connnection
2236 if (!$this->connect($timeout, $response_timeout)){
2237 return false;
2238 }
2239
2240 // send request
2241 if (!$this->sendRequest($data, $cookies)){
2242 return false;
2243 }
2244
2245 // get response
2246 $respdata = $this->getResponse();
2247 } else {
2248 $this->setError('Too many tries to get an OK response');
2249 }
2250 }
2251 $this->debug('end of send()');
2252 return $respdata;
2253 }
2254
2255
2256 /**
2257 * send the SOAP message via HTTPS 1.0 using CURL
2258 *
2259 * @param string $msg message data
2260 * @param integer $timeout set connection timeout in seconds
2261 * @param integer $response_timeout set response timeout in seconds
2262 * @param array $cookies cookies to send
2263 * @return string data
2264 * @access public
2265 */
2266 function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) {
2267 return $this->send($data, $timeout, $response_timeout, $cookies);
2268 }
2269
2270 /**
2271 * if authenticating, set user credentials here
2272 *
2273 * @param string $username
2274 * @param string $password
2275 * @param string $authtype (basic, digest, certificate)
2276 * @param array $digestRequest (keys must be nonce, nc, realm, qop)
2277 * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
2278 * @access public
2279 */
2280 function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) {
2281 $this->debug("Set credentials for authtype $authtype");
2282 // cf. RFC 2617
2283 if ($authtype == 'basic') {
2284 $this->outgoing_headers['Authorization'] = 'Basic '.base64_encode(str_replace(':','',$username).':'.$password);
2285 } elseif ($authtype == 'digest') {
2286 if (isset($digestRequest['nonce'])) {
2287 $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1;
2288
2289 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
2290
2291 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
2292 $A1 = $username. ':' . (isset($digestRequest['realm']) ? $digestRequest['realm'] : '') . ':' . $password;
2293
2294 // H(A1) = MD5(A1)
2295 $HA1 = md5($A1);
2296
2297 // A2 = Method ":" digest-uri-value
2298 $A2 = 'POST:' . $this->digest_uri;
2299
2300 // H(A2)
2301 $HA2 = md5($A2);
2302
2303 // KD(secret, data) = H(concat(secret, ":", data))
2304 // if qop == auth:
2305 // request-digest = <"> < KD ( H(A1), unq(nonce-value)
2306 // ":" nc-value
2307 // ":" unq(cnonce-value)
2308 // ":" unq(qop-value)
2309 // ":" H(A2)
2310 // ) <">
2311 // if qop is missing,
2312 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
2313
2314 $unhashedDigest = '';
2315 $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : '';
2316 $cnonce = $nonce;
2317 if ($digestRequest['qop'] != '') {
2318 $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
2319 } else {
2320 $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
2321 }
2322
2323 $hashedDigest = md5($unhashedDigest);
2324
2325 $this->outgoing_headers['Authorization'] = 'Digest username="' . $username . '", realm="' . $digestRequest['realm'] . '", nonce="' . $nonce . '", uri="' . $this->digest_uri . '", cnonce="' . $cnonce . '", nc=' . sprintf("%08x", $digestRequest['nc']) . ', qop="' . $digestRequest['qop'] . '", response="' . $hashedDigest . '"';
2326 }
2327 } elseif ($authtype == 'certificate') {
2328 $this->certRequest = $certRequest;
2329 }
2330 $this->username = $username;
2331 $this->password = $password;
2332 $this->authtype = $authtype;
2333 $this->digestRequest = $digestRequest;
2334
2335 if (isset($this->outgoing_headers['Authorization'])) {
2336 $this->debug('set Authorization: ' . substr($this->outgoing_headers['Authorization'], 0, 12) . '...');
2337 } else {
2338 $this->debug('Authorization header not set');
2339 }
2340 }
2341
2342 /**
2343 * set the soapaction value
2344 *
2345 * @param string $soapaction
2346 * @access public
2347 */
2348 function setSOAPAction($soapaction) {
2349 $this->outgoing_headers['SOAPAction'] = '"' . $soapaction . '"';
2350 $this->debug('set SOAPAction: ' . $this->outgoing_headers['SOAPAction']);
2351 }
2352
2353 /**
2354 * use http encoding
2355 *
2356 * @param string $enc encoding style. supported values: gzip, deflate, or both
2357 * @access public
2358 */
2359 function setEncoding($enc='gzip, deflate') {
2360 if (function_exists('gzdeflate')) {
2361 $this->protocol_version = '1.1';
2362 $this->outgoing_headers['Accept-Encoding'] = $enc;
2363 $this->debug('set Accept-Encoding: ' . $this->outgoing_headers['Accept-Encoding']);
2364 if (!isset($this->outgoing_headers['Connection'])) {
2365 $this->outgoing_headers['Connection'] = 'close';
2366 $this->persistentConnection = false;
2367 $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
2368 }
2369 set_magic_quotes_runtime(0);
2370 // deprecated
2371 $this->encoding = $enc;
2372 }
2373 }
2374
2375 /**
2376 * set proxy info here
2377 *
2378 * @param string $proxyhost
2379 * @param string $proxyport
2380 * @param string $proxyusername
2381 * @param string $proxypassword
2382 * @access public
2383 */
2384 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
2385 $this->uri = $this->url;
2386 $this->host = $proxyhost;
2387 $this->port = $proxyport;
2388 if ($proxyusername != '' && $proxypassword != '') {
2389 $this->outgoing_headers['Proxy-Authorization'] = ' Basic '.base64_encode($proxyusername.':'.$proxypassword);
2390 $this->debug('set Proxy-Authorization: ' . $this->outgoing_headers['Proxy-Authorization']);
2391 }
2392 }
2393
2394 /**
2395 * decode a string that is encoded w/ "chunked' transfer encoding
2396 * as defined in RFC2068 19.4.6
2397 *
2398 * @param string $buffer
2399 * @param string $lb
2400 * @returns string
2401 * @access public
2402 * @deprecated
2403 */
2404 function decodeChunked($buffer, $lb){
2405 // length := 0
2406 $length = 0;
2407 $new = '';
2408
2409 // read chunk-size, chunk-extension (if any) and CRLF
2410 // get the position of the linebreak
2411 $chunkend = strpos($buffer, $lb);
2412 if ($chunkend == FALSE) {
2413 $this->debug('no linebreak found in decodeChunked');
2414 return $new;
2415 }
2416 $temp = substr($buffer,0,$chunkend);
2417 $chunk_size = hexdec( trim($temp) );
2418 $chunkstart = $chunkend + strlen($lb);
2419 // while (chunk-size > 0) {
2420 while ($chunk_size > 0) {
2421 $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
2422 $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size);
2423
2424 // Just in case we got a broken connection
2425 if ($chunkend == FALSE) {
2426 $chunk = substr($buffer,$chunkstart);
2427 // append chunk-data to entity-body
2428 $new .= $chunk;
2429 $length += strlen($chunk);
2430 break;
2431 }
2432
2433 // read chunk-data and CRLF
2434 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2435 // append chunk-data to entity-body
2436 $new .= $chunk;
2437 // length := length + chunk-size
2438 $length += strlen($chunk);
2439 // read chunk-size and CRLF
2440 $chunkstart = $chunkend + strlen($lb);
2441
2442 $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb);
2443 if ($chunkend == FALSE) {
2444 break; //Just in case we got a broken connection
2445 }
2446 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2447 $chunk_size = hexdec( trim($temp) );
2448 $chunkstart = $chunkend;
2449 }
2450 return $new;
2451 }
2452
2453 /*
2454 * Writes payload, including HTTP headers, to $this->outgoing_payload.
2455 */
2456 function buildPayload($data, $cookie_str = '') {
2457 // add content-length header
2458 $this->outgoing_headers['Content-Length'] = strlen($data);
2459 $this->debug('set Content-Length: ' . $this->outgoing_headers['Content-Length']);
2460
2461 // start building outgoing payload:
2462 $req = "$this->request_method $this->uri HTTP/$this->protocol_version";
2463 $this->debug("HTTP request: $req");
2464 $this->outgoing_payload = "$req\r\n";
2465
2466 // loop thru headers, serializing
2467 foreach($this->outgoing_headers as $k => $v){
2468 $hdr = $k.': '.$v;
2469 $this->debug("HTTP header: $hdr");
2470 $this->outgoing_payload .= "$hdr\r\n";
2471 }
2472
2473 // add any cookies
2474 if ($cookie_str != '') {
2475 $hdr = 'Cookie: '.$cookie_str;
2476 $this->debug("HTTP header: $hdr");
2477 $this->outgoing_payload .= "$hdr\r\n";
2478 }
2479
2480 // header/body separator
2481 $this->outgoing_payload .= "\r\n";
2482
2483 // add data
2484 $this->outgoing_payload .= $data;
2485 }
2486
2487 function sendRequest($data, $cookies = NULL) {
2488 // build cookie string
2489 $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https')));
2490
2491 // build payload
2492 $this->buildPayload($data, $cookie_str);
2493
2494 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2495 // send payload
2496 if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
2497 $this->setError('couldn\'t write message data to socket');
2498 $this->debug('couldn\'t write message data to socket');
2499 return false;
2500 }
2501 $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload));
2502 return true;
2503 } else if ($this->scheme == 'https') {
2504 // set payload
2505 // TODO: cURL does say this should only be the verb, and in fact it
2506 // turns out that the URI and HTTP version are appended to this, which
2507 // some servers refuse to work with
2508 //curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
2509 foreach($this->outgoing_headers as $k => $v){
2510 $curl_headers[] = "$k: $v";
2511 }
2512 if ($cookie_str != '') {
2513 $curl_headers[] = 'Cookie: ' . $cookie_str;
2514 }
2515 curl_setopt($this->ch, CURLOPT_HTTPHEADER, $curl_headers);
2516 if ($this->request_method == "POST") {
2517 curl_setopt($this->ch, CURLOPT_POST, 1);
2518 curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data);
2519 } else {
2520 }
2521 $this->debug('set cURL payload');
2522 return true;
2523 }
2524 }
2525
2526 function getResponse(){
2527 $this->incoming_payload = '';
2528
2529 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2530 // loop until headers have been retrieved
2531 $data = '';
2532 while (!isset($lb)){
2533
2534 // We might EOF during header read.
2535 if(feof($this->fp)) {
2536 $this->incoming_payload = $data;
2537 $this->debug('found no headers before EOF after length ' . strlen($data));
2538 $this->debug("received before EOF:\n" . $data);
2539 $this->setError('server failed to send headers');
2540 return false;
2541 }
2542
2543 $tmp = fgets($this->fp, 256);
2544 $tmplen = strlen($tmp);
2545 $this->debug("read line of $tmplen bytes: " . trim($tmp));
2546
2547 if ($tmplen == 0) {
2548 $this->incoming_payload = $data;
2549 $this->debug('socket read of headers timed out after length ' . strlen($data));
2550 $this->debug("read before timeout: " . $data);
2551 $this->setError('socket read of headers timed out');
2552 return false;
2553 }
2554
2555 $data .= $tmp;
2556 $pos = strpos($data,"\r\n\r\n");
2557 if($pos > 1){
2558 $lb = "\r\n";
2559 } else {
2560 $pos = strpos($data,"\n\n");
2561 if($pos > 1){
2562 $lb = "\n";
2563 }
2564 }
2565 // remove 100 header
2566 if(isset($lb) && ereg('^HTTP/1.1 100',$data)){
2567 unset($lb);
2568 $data = '';
2569 }//
2570 }
2571 // store header data
2572 $this->incoming_payload .= $data;
2573 $this->debug('found end of headers after length ' . strlen($data));
2574 // process headers
2575 $header_data = trim(substr($data,0,$pos));
2576 $header_array = explode($lb,$header_data);
2577 $this->incoming_headers = array();
2578 $this->incoming_cookies = array();
2579 foreach($header_array as $header_line){
2580 $arr = explode(':',$header_line, 2);
2581 if(count($arr) > 1){
2582 $header_name = strtolower(trim($arr[0]));
2583 $this->incoming_headers[$header_name] = trim($arr[1]);
2584 if ($header_name == 'set-cookie') {
2585 // TODO: allow multiple cookies from parseCookie
2586 $cookie = $this->parseCookie(trim($arr[1]));
2587 if ($cookie) {
2588 $this->incoming_cookies[] = $cookie;
2589 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
2590 } else {
2591 $this->debug('did not find cookie in ' . trim($arr[1]));
2592 }
2593 }
2594 } else if (isset($header_name)) {
2595 // append continuation line to previous header
2596 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
2597 }
2598 }
2599
2600 // loop until msg has been received
2601 if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') {
2602 $content_length = 2147483647; // ignore any content-length header
2603 $chunked = true;
2604 $this->debug("want to read chunked content");
2605 } elseif (isset($this->incoming_headers['content-length'])) {
2606 $content_length = $this->incoming_headers['content-length'];
2607 $chunked = false;
2608 $this->debug("want to read content of length $content_length");
2609 } else {
2610 $content_length = 2147483647;
2611 $chunked = false;
2612 $this->debug("want to read content to EOF");
2613 }
2614 $data = '';
2615 do {
2616 if ($chunked) {
2617 $tmp = fgets($this->fp, 256);
2618 $tmplen = strlen($tmp);
2619 $this->debug("read chunk line of $tmplen bytes");
2620 if ($tmplen == 0) {
2621 $this->incoming_payload = $data;
2622 $this->debug('socket read of chunk length timed out after length ' . strlen($data));
2623 $this->debug("read before timeout:\n" . $data);
2624 $this->setError('socket read of chunk length timed out');
2625 return false;
2626 }
2627 $content_length = hexdec(trim($tmp));
2628 $this->debug("chunk length $content_length");
2629 }
2630 $strlen = 0;
2631 while (($strlen < $content_length) && (!feof($this->fp))) {
2632 $readlen = min(8192, $content_length - $strlen);
2633 $tmp = fread($this->fp, $readlen);
2634 $tmplen = strlen($tmp);
2635 $this->debug("read buffer of $tmplen bytes");
2636 if (($tmplen == 0) && (!feof($this->fp))) {
2637 $this->incoming_payload = $data;
2638 $this->debug('socket read of body timed out after length ' . strlen($data));
2639 $this->debug("read before timeout:\n" . $data);
2640 $this->setError('socket read of body timed out');
2641 return false;
2642 }
2643 $strlen += $tmplen;
2644 $data .= $tmp;
2645 }
2646 if ($chunked && ($content_length > 0)) {
2647 $tmp = fgets($this->fp, 256);
2648 $tmplen = strlen($tmp);
2649 $this->debug("read chunk terminator of $tmplen bytes");
2650 if ($tmplen == 0) {
2651 $this->incoming_payload = $data;
2652 $this->debug('socket read of chunk terminator timed out after length ' . strlen($data));
2653 $this->debug("read before timeout:\n" . $data);
2654 $this->setError('socket read of chunk terminator timed out');
2655 return false;
2656 }
2657 }
2658 } while ($chunked && ($content_length > 0) && (!feof($this->fp)));
2659 if (feof($this->fp)) {
2660 $this->debug('read to EOF');
2661 }
2662 $this->debug('read body of length ' . strlen($data));
2663 $this->incoming_payload .= $data;
2664 $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server');
2665
2666 // close filepointer
2667 if(
2668 (isset($this->incoming_headers['connection']) && strtolower($this->incoming_headers['connection']) == 'close') ||
2669 (! $this->persistentConnection) || feof($this->fp)){
2670 fclose($this->fp);
2671 $this->fp = false;
2672 $this->debug('closed socket');
2673 }
2674
2675 // connection was closed unexpectedly
2676 if($this->incoming_payload == ''){
2677 $this->setError('no response from server');
2678 return false;
2679 }
2680
2681 // decode transfer-encoding
2682 // if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
2683 // if(!$data = $this->decodeChunked($data, $lb)){
2684 // $this->setError('Decoding of chunked data failed');
2685 // return false;
2686 // }
2687 //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
2688 // set decoded payload
2689 // $this->incoming_payload = $header_data.$lb.$lb.$data;
2690 // }
2691
2692 } else if ($this->scheme == 'https') {
2693 // send and receive
2694 $this->debug('send and receive with cURL');
2695 $this->incoming_payload = curl_exec($this->ch);
2696 $data = $this->incoming_payload;
2697
2698 $cErr = curl_error($this->ch);
2699 if ($cErr != '') {
2700 $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'<br>';
2701 // TODO: there is a PHP bug that can cause this to SEGV for CURLINFO_CONTENT_TYPE
2702 foreach(curl_getinfo($this->ch) as $k => $v){
2703 $err .= "$k: $v<br>";
2704 }
2705 $this->debug($err);
2706 $this->setError($err);
2707 curl_close($this->ch);
2708 return false;
2709 } else {
2710 //echo '<pre>';
2711 //var_dump(curl_getinfo($this->ch));
2712 //echo '</pre>';
2713 }
2714 // close curl
2715 $this->debug('No cURL error, closing cURL');
2716 curl_close($this->ch);
2717
2718 // remove 100 header(s)
2719 while (ereg('^HTTP/1.1 100',$data)) {
2720 if ($pos = strpos($data,"\r\n\r\n")) {
2721 $data = ltrim(substr($data,$pos));
2722 } elseif($pos = strpos($data,"\n\n") ) {
2723 $data = ltrim(substr($data,$pos));
2724 }
2725 }
2726
2727 // separate content from HTTP headers
2728 if ($pos = strpos($data,"\r\n\r\n")) {
2729 $lb = "\r\n";
2730 } elseif( $pos = strpos($data,"\n\n")) {
2731 $lb = "\n";
2732 } else {
2733 $this->debug('no proper separation of headers and document');
2734 $this->setError('no proper separation of headers and document');
2735 return false;
2736 }
2737 $header_data = trim(substr($data,0,$pos));
2738 $header_array = explode($lb,$header_data);
2739 $data = ltrim(substr($data,$pos));
2740 $this->debug('found proper separation of headers and document');
2741 $this->debug('cleaned data, stringlen: '.strlen($data));
2742 // clean headers
2743 foreach ($header_array as $header_line) {
2744 $arr = explode(':',$header_line,2);
2745 if(count($arr) > 1){
2746 $header_name = strtolower(trim($arr[0]));
2747 $this->incoming_headers[$header_name] = trim($arr[1]);
2748 if ($header_name == 'set-cookie') {
2749 // TODO: allow multiple cookies from parseCookie
2750 $cookie = $this->parseCookie(trim($arr[1]));
2751 if ($cookie) {
2752 $this->incoming_cookies[] = $cookie;
2753 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
2754 } else {
2755 $this->debug('did not find cookie in ' . trim($arr[1]));
2756 }
2757 }
2758 } else if (isset($header_name)) {
2759 // append continuation line to previous header
2760 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
2761 }
2762 }
2763 }
2764
2765 $arr = explode(' ', $header_array[0], 3);
2766 $http_version = $arr[0];
2767 $http_status = intval($arr[1]);
2768 $http_reason = count($arr) > 2 ? $arr[2] : '';
2769
2770 // see if we need to resend the request with http digest authentication
2771 if (isset($this->incoming_headers['location']) && $http_status == 301) {
2772 $this->debug("Got 301 $http_reason with Location: " . $this->incoming_headers['location']);
2773 $this->setURL($this->incoming_headers['location']);
2774 $this->tryagain = true;
2775 return false;
2776 }
2777
2778 // see if we need to resend the request with http digest authentication
2779 if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) {
2780 $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']);
2781 if (strstr($this->incoming_headers['www-authenticate'], "Digest ")) {
2782 $this->debug('Server wants digest authentication');
2783 // remove "Digest " from our elements
2784 $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']);
2785
2786 // parse elements into array
2787 $digestElements = explode(',', $digestString);
2788 foreach ($digestElements as $val) {
2789 $tempElement = explode('=', trim($val), 2);
2790 $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
2791 }
2792
2793 // should have (at least) qop, realm, nonce
2794 if (isset($digestRequest['nonce'])) {
2795 $this->setCredentials($this->username, $this->password, 'digest', $digestRequest);
2796 $this->tryagain = true;
2797 return false;
2798 }
2799 }
2800 $this->debug('HTTP authentication failed');
2801 $this->setError('HTTP authentication failed');
2802 return false;
2803 }
2804
2805 if (
2806 ($http_status >= 300 && $http_status <= 307) ||
2807 ($http_status >= 400 && $http_status <= 417) ||
2808 ($http_status >= 501 && $http_status <= 505)
2809 ) {
2810 $this->setError("Unsupported HTTP response status $http_status $http_reason (soapclient->response has contents of the response)");
2811 return false;
2812 }
2813
2814 // decode content-encoding
2815 if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){
2816 if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){
2817 // if decoding works, use it. else assume data wasn't gzencoded
2818 if(function_exists('gzinflate')){
2819 //$timer->setMarker('starting decoding of gzip/deflated content');
2820 // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress)
2821 // this means there are no Zlib headers, although there should be
2822 $this->debug('The gzinflate function exists');
2823 $datalen = strlen($data);
2824 if ($this->incoming_headers['content-encoding'] == 'deflate') {
2825 if ($degzdata = @gzinflate($data)) {
2826 $data = $degzdata;
2827 $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes');
2828 if (strlen($data) < $datalen) {
2829 // test for the case that the payload has been compressed twice
2830 $this->debug('The inflated payload is smaller than the gzipped one; try again');
2831 if ($degzdata = @gzinflate($data)) {
2832 $data = $degzdata;
2833 $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes');
2834 }
2835 }
2836 } else {
2837 $this->debug('Error using gzinflate to inflate the payload');
2838 $this->setError('Error using gzinflate to inflate the payload');
2839 }
2840 } elseif ($this->incoming_headers['content-encoding'] == 'gzip') {
2841 if ($degzdata = @gzinflate(substr($data, 10))) { // do our best
2842 $data = $degzdata;
2843 $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes');
2844 if (strlen($data) < $datalen) {
2845 // test for the case that the payload has been compressed twice
2846 $this->debug('The un-gzipped payload is smaller than the gzipped one; try again');
2847 if ($degzdata = @gzinflate(substr($data, 10))) {
2848 $data = $degzdata;
2849 $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes');
2850 }
2851 }
2852 } else {
2853 $this->debug('Error using gzinflate to un-gzip the payload');
2854 $this->setError('Error using gzinflate to un-gzip the payload');
2855 }
2856 }
2857 //$timer->setMarker('finished decoding of gzip/deflated content');
2858 //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
2859 // set decoded payload
2860 $this->incoming_payload = $header_data.$lb.$lb.$data;
2861 } else {
2862 $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
2863 $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
2864 }
2865 } else {
2866 $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
2867 $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
2868 }
2869 } else {
2870 $this->debug('No Content-Encoding header');
2871 }
2872
2873 if(strlen($data) == 0){
2874 $this->debug('no data after headers!');
2875 $this->setError('no data present after HTTP headers');
2876 return false;
2877 }
2878
2879 return $data;
2880 }
2881
2882 function setContentType($type, $charset = false) {
2883 $this->outgoing_headers['Content-Type'] = $type . ($charset ? '; charset=' . $charset : '');
2884 $this->debug('set Content-Type: ' . $this->outgoing_headers['Content-Type']);
2885 }
2886
2887 function usePersistentConnection(){
2888 if (isset($this->outgoing_headers['Accept-Encoding'])) {
2889 return false;
2890 }
2891 $this->protocol_version = '1.1';
2892 $this->persistentConnection = true;
2893 $this->outgoing_headers['Connection'] = 'Keep-Alive';
2894 $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
2895 return true;
2896 }
2897
2898 /**
2899 * parse an incoming Cookie into it's parts
2900 *
2901 * @param string $cookie_str content of cookie
2902 * @return array with data of that cookie
2903 * @access private
2904 */
2905 /*
2906 * TODO: allow a Set-Cookie string to be parsed into multiple cookies
2907 */
2908 function parseCookie($cookie_str) {
2909 $cookie_str = str_replace('; ', ';', $cookie_str) . ';';
2910 $data = split(';', $cookie_str);
2911 $value_str = $data[0];
2912
2913 $cookie_param = 'domain=';
2914 $start = strpos($cookie_str, $cookie_param);
2915 if ($start > 0) {
2916 $domain = substr($cookie_str, $start + strlen($cookie_param));
2917 $domain = substr($domain, 0, strpos($domain, ';'));
2918 } else {
2919 $domain = '';
2920 }
2921
2922 $cookie_param = 'expires=';
2923 $start = strpos($cookie_str, $cookie_param);
2924 if ($start > 0) {
2925 $expires = substr($cookie_str, $start + strlen($cookie_param));
2926 $expires = substr($expires, 0, strpos($expires, ';'));
2927 } else {
2928 $expires = '';
2929 }
2930
2931 $cookie_param = 'path=';
2932 $start = strpos($cookie_str, $cookie_param);
2933 if ( $start > 0 ) {
2934 $path = substr($cookie_str, $start + strlen($cookie_param));
2935 $path = substr($path, 0, strpos($path, ';'));
2936 } else {
2937 $path = '/';
2938 }
2939
2940 $cookie_param = ';secure;';
2941 if (strpos($cookie_str, $cookie_param) !== FALSE) {
2942 $secure = true;
2943 } else {
2944 $secure = false;
2945 }
2946
2947 $sep_pos = strpos($value_str, '=');
2948
2949 if ($sep_pos) {
2950 $name = substr($value_str, 0, $sep_pos);
2951 $value = substr($value_str, $sep_pos + 1);
2952 $cookie= array( 'name' => $name,
2953 'value' => $value,
2954 'domain' => $domain,