From 49baade6e8a2f26a88a2f2c6a5faac33293b1111 Mon Sep 17 00:00:00 2001 From: Andreas Brachold Date: Sat, 26 Jan 2008 17:53:19 +0000 Subject: * Update to nusoap 0.7.3 * Enhance memory limit to 16M --- contrib/popularity/popularity/nusoap.php | 1684 +++++++++++++++++++++--------- contrib/popularity/t10.php | 449 ++++---- 2 files changed, 1442 insertions(+), 691 deletions(-) diff --git a/contrib/popularity/popularity/nusoap.php b/contrib/popularity/popularity/nusoap.php index 60d870d..1f1dde6 100644 --- a/contrib/popularity/popularity/nusoap.php +++ b/contrib/popularity/popularity/nusoap.php @@ -1,7 +1,7 @@ globalDebugLevel = 9; * nusoap_base * * @author Dietrich Ayala -* @version $Id: nusoap.php,v 1.94 2005/08/04 01:27:42 snichol Exp $ +* @author Scott Nichol +* @version $Id: nusoap.php,v 1.114 2007/11/06 15:17:46 snichol Exp $ * @access public */ class nusoap_base { @@ -69,21 +90,21 @@ class nusoap_base { * @var string * @access private */ - var $title = 'xxvSOAP'; + var $title = 'NuSOAP'; /** * Version for HTTP headers. * * @var string * @access private */ - var $version = '0.7.2'; + var $version = '0.7.3'; /** * CVS revision for HTTP headers. * * @var string * @access private */ - var $revision = '$Revision: 1.94 $'; + var $revision = '$Revision: 1.114 $'; /** * Current error string (manipulated by getError/setError) * @@ -157,7 +178,7 @@ class nusoap_base { /** * XML Schema types in an array of uri => (array of xml type => php type) * is this legacy yet? - * no, this is used by the xmlschema class to verify type => namespace mappings. + * no, this is used by the nusoap_xmlschema class to verify type => namespace mappings. * @var array * @access public */ @@ -308,7 +329,8 @@ class nusoap_base { while (strpos($this->debug_str, '--')) { $this->debug_str = str_replace('--', '- -', $this->debug_str); } - return ""; + $ret = ""; + return $ret; } /** @@ -379,16 +401,22 @@ class nusoap_base { * @param string $type_ns The namespace for the type of the element * @param array $attributes The attributes to serialize as name=>value pairs * @param string $use The WSDL "use" (encoded|literal) + * @param boolean $soapval Whether this is called from soapval. * @return string The serialized element, possibly with child elements * @access public */ - function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded'){ - $this->debug("in serialize_val: name=$name, type=$type, name_ns=$name_ns, type_ns=$type_ns, use=$use"); + function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded',$soapval=false) { + $this->debug("in serialize_val: name=$name, type=$type, name_ns=$name_ns, type_ns=$type_ns, use=$use, soapval=$soapval"); $this->appendDebug('value=' . $this->varDump($val)); $this->appendDebug('attributes=' . $this->varDump($attributes)); - if(is_object($val) && get_class($val) == 'soapval'){ - return $val->serialize($use); + if (is_object($val) && get_class($val) == 'soapval' && (! $soapval)) { + $this->debug("serialize_val: serialize soapval"); + $xml = $val->serialize($use); + $this->appendDebug($val->getDebug()); + $val->clearDebug(); + $this->debug("serialize_val of soapval returning $xml"); + return $xml; } // force valid name if necessary if (is_numeric($name)) { @@ -421,20 +449,26 @@ class nusoap_base { } // serialize null value if (is_null($val)) { + $this->debug("serialize_val: serialize null"); if ($use == 'literal') { // TODO: depends on minOccurs - return "<$name$xmlns $atts/>"; + $xml = "<$name$xmlns$atts/>"; + $this->debug("serialize_val returning $xml"); + return $xml; } else { if (isset($type) && isset($type_prefix)) { $type_str = " xsi:type=\"$type_prefix:$type\""; } else { $type_str = ''; } - return "<$name$xmlns$type_str $atts xsi:nil=\"true\"/>"; + $xml = "<$name$xmlns$type_str$atts xsi:nil=\"true\"/>"; + $this->debug("serialize_val returning $xml"); + return $xml; } } // serialize if an xsd built-in primitive type if($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){ + $this->debug("serialize_val: serialize xsd built-in primitive type"); if (is_bool($val)) { if ($type == 'boolean') { $val = $val ? 'true' : 'false'; @@ -445,65 +479,91 @@ class nusoap_base { $val = $this->expandEntities($val); } if ($use == 'literal') { - return "<$name$xmlns $atts>$val"; + $xml = "<$name$xmlns$atts>$val"; + $this->debug("serialize_val returning $xml"); + return $xml; } else { - return "<$name$xmlns $atts xsi:type=\"xsd:$type\">$val"; + $xml = "<$name$xmlns xsi:type=\"xsd:$type\"$atts>$val"; + $this->debug("serialize_val returning $xml"); + return $xml; } } // detect type and serialize $xml = ''; switch(true) { case (is_bool($val) || $type == 'boolean'): + $this->debug("serialize_val: serialize boolean"); if ($type == 'boolean') { $val = $val ? 'true' : 'false'; } elseif (! $val) { $val = 0; } if ($use == 'literal') { - $xml .= "<$name$xmlns $atts>$val"; + $xml .= "<$name$xmlns$atts>$val"; } else { $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val"; } break; case (is_int($val) || is_long($val) || $type == 'int'): + $this->debug("serialize_val: serialize int"); if ($use == 'literal') { - $xml .= "<$name$xmlns $atts>$val"; + $xml .= "<$name$xmlns$atts>$val"; } else { $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val"; } break; case (is_float($val)|| is_double($val) || $type == 'float'): + $this->debug("serialize_val: serialize float"); if ($use == 'literal') { - $xml .= "<$name$xmlns $atts>$val"; + $xml .= "<$name$xmlns$atts>$val"; } else { $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val"; } break; case (is_string($val) || $type == 'string'): + $this->debug("serialize_val: serialize string"); $val = $this->expandEntities($val); if ($use == 'literal') { - $xml .= "<$name$xmlns $atts>$val"; + $xml .= "<$name$xmlns$atts>$val"; } else { $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val"; } break; case is_object($val): - if (! $name) { - $name = get_class($val); - $this->debug("In serialize_val, used class name $name as element name"); + $this->debug("serialize_val: serialize object"); + if (get_class($val) == 'soapval') { + $this->debug("serialize_val: serialize soapval object"); + $pXml = $val->serialize($use); + $this->appendDebug($val->getDebug()); + $val->clearDebug(); + } else { + if (! $name) { + $name = get_class($val); + $this->debug("In serialize_val, used class name $name as element name"); + } else { + $this->debug("In serialize_val, do not override name $name for element name for class " . get_class($val)); + } + foreach(get_object_vars($val) as $k => $v){ + $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use); + } + } + if(isset($type) && isset($type_prefix)){ + $type_str = " xsi:type=\"$type_prefix:$type\""; } else { - $this->debug("In serialize_val, do not override name $name for element name for class " . get_class($val)); + $type_str = ''; } - foreach(get_object_vars($val) as $k => $v){ - $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use); + if ($use == 'literal') { + $xml .= "<$name$xmlns$atts>$pXml"; + } else { + $xml .= "<$name$xmlns$type_str$atts>$pXml"; } - $xml .= '<'.$name.'>'.$pXml.''; break; break; case (is_array($val) || $type): // detect if struct or array $valueType = $this->isArraySimpleOrStruct($val); if($valueType=='arraySimple' || ereg('^ArrayOf',$type)){ + $this->debug("serialize_val: serialize array"); $i = 0; if(is_array($val) && count($val)> 0){ foreach($val as $v){ @@ -565,13 +625,14 @@ class nusoap_base { $xml = "<$name$xmlns$type_str$atts>".$xml.""; } else { // got a struct + $this->debug("serialize_val: serialize struct"); if(isset($type) && isset($type_prefix)){ $type_str = " xsi:type=\"$type_prefix:$type\""; } else { $type_str = ''; } if ($use == 'literal') { - $xml .= "<$name$xmlns $atts>"; + $xml .= "<$name$xmlns$atts>"; } else { $xml .= "<$name$xmlns$type_str$atts>"; } @@ -590,9 +651,11 @@ class nusoap_base { } break; default: + $this->debug("serialize_val: serialize unknown"); $xml .= 'not detected, got '.gettype($val).' for '.$val; break; } + $this->debug("serialize_val returning $xml"); return $xml; } @@ -600,7 +663,7 @@ class nusoap_base { * serializes a message * * @param string $body the XML of the SOAP body - * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers + * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers, or associative array * @param array $namespaces optional the namespaces used in generating the body and headers * @param string $style optional (rpc|document) * @param string $use optional (encoded|literal) @@ -632,11 +695,15 @@ class nusoap_base { if($headers){ if (is_array($headers)) { $xml = ''; - foreach ($headers as $header) { - $xml .= $this->serialize_val($header, false, false, false, false, false, $use); + foreach ($headers as $k => $v) { + if (is_object($v) && get_class($v) == 'soapval') { + $xml .= $this->serialize_val($v, false, false, false, false, false, $use); + } else { + $xml .= $this->serialize_val($v, $k, false, false, false, false, $use); + } } $headers = $xml; - $this->debug("In serializeEnvelope, serialzied array of headers to $headers"); + $this->debug("In serializeEnvelope, serialized array of headers to $headers"); } $headers = "".$headers.""; } @@ -692,7 +759,7 @@ class nusoap_base { /** * expands (changes prefix to namespace) a qualified name * - * @param string $string qname + * @param string $qname qname * @return string expanded qname * @access private */ @@ -811,6 +878,16 @@ class nusoap_base { ob_end_clean(); return $ret_val; } + + /** + * represents the object as a string + * + * @return string + * @access public + */ + function __toString() { + return $this->varDump($this); + } } // XML Schema Datatype Helper Functions @@ -821,6 +898,7 @@ class nusoap_base { * convert unix timestamp to ISO 8601 compliant date string * * @param string $timestamp Unix time stamp +* @param boolean $utc Whether the time stamp is UTC or local * @access public */ function timestamp_to_iso8601($timestamp,$utc=true){ @@ -875,7 +953,8 @@ function iso8601_to_timestamp($datestr){ $regs[5] = $regs[5] - $m; } } - return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z"); + return gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); +// return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z"); } else { return false; } @@ -910,10 +989,10 @@ function usleepWindows($usec) * Mainly used for returning faults from deployed functions * in a server instance. * @author Dietrich Ayala -* @version $Id: nusoap.php,v 1.94 2005/08/04 01:27:42 snichol Exp $ +* @version $Id: nusoap.php,v 1.114 2007/11/06 15:17:46 snichol Exp $ * @access public */ -class soap_fault extends nusoap_base { +class nusoap_fault extends nusoap_base { /** * The fault code (client|server) * @var string @@ -942,12 +1021,12 @@ class soap_fault extends nusoap_base { /** * constructor * - * @param string $faultcode (client | server) + * @param string $faultcode (SOAP-ENV:Client | SOAP-ENV:Server) * @param string $faultactor only used when msg routed between multiple actors * @param string $faultstring human readable error message * @param mixed $faultdetail detail, typically a string or array of string */ - function soap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){ + function nusoap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){ parent::nusoap_base(); $this->faultcode = $faultcode; $this->faultactor = $faultactor; @@ -982,24 +1061,26 @@ class soap_fault extends nusoap_base { } } - +/** + * Backward compatibility + */ +class soap_fault extends nusoap_fault { +} ?> -* @version $Id: nusoap.php,v 1.94 2005/08/04 01:27:42 snichol Exp $ +* @author Scott Nichol +* @version $Id: nusoap.php,v 1.114 2007/11/06 15:17:46 snichol Exp $ * @access public */ -class XMLSchema extends nusoap_base { +class nusoap_xmlschema extends nusoap_base { // files var $schema = ''; @@ -1038,9 +1119,9 @@ class XMLSchema extends nusoap_base { * @param string $namespaces namespaces defined in enclosing XML * @access public */ - function XMLSchema($schema='',$xml='',$namespaces=array()){ + function nusoap_xmlschema($schema='',$xml='',$namespaces=array()){ parent::nusoap_base(); - $this->debug('xmlschema class instantiated, inside constructor'); + $this->debug('nusoap_xmlschema class instantiated, inside constructor'); // files $this->schema = $schema; $this->xml = $xml; @@ -1066,8 +1147,8 @@ class XMLSchema extends nusoap_base { /** * parse an XML file * - * @param string $xml, path/URL to XML file - * @param string $type, (schema | xml) + * @param string $xml path/URL to XML file + * @param string $type (schema | xml) * @return boolean * @access public */ @@ -1094,7 +1175,7 @@ class XMLSchema extends nusoap_base { * parse an XML string * * @param string $xml path or URL - * @param string $type, (schema|xml) + * @param string $type (schema|xml) * @access private */ function parseString($xml,$type){ @@ -1137,6 +1218,21 @@ class XMLSchema extends nusoap_base { } } + /** + * gets a type name for an unnamed type + * + * @param string Element name + * @return string A type name for an unnamed type + * @access private + */ + function CreateTypeName($ename) { + $scope = ''; + for ($i = 0; $i < count($this->complexTypeStack); $i++) { + $scope .= $this->complexTypeStack[$i] . '_'; + } + return $scope . $ename . '_ContainedType'; + } + /** * start-element handler * @@ -1269,6 +1365,8 @@ class XMLSchema extends nusoap_base { case 'complexType': array_push($this->complexTypeStack, $this->currentComplexType); if(isset($attrs['name'])){ + // TODO: what is the scope of named complexTypes that appear + // nested within other c complexTypes? $this->xdebug('processing named complexType '.$attrs['name']); //$this->currentElement = false; $this->currentComplexType = $attrs['name']; @@ -1287,9 +1385,10 @@ class XMLSchema extends nusoap_base { } else { $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct'; } - }else{ - $this->xdebug('processing unnamed complexType for element '.$this->currentElement); - $this->currentComplexType = $this->currentElement . '_ContainedType'; + } else { + $name = $this->CreateTypeName($this->currentElement); + $this->xdebug('processing unnamed complexType for element ' . $this->currentElement . ' named ' . $name); + $this->currentComplexType = $name; //$this->currentElement = false; $this->complexTypes[$this->currentComplexType] = $attrs; $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType'; @@ -1310,9 +1409,6 @@ class XMLSchema extends nusoap_base { break; case 'element': array_push($this->elementStack, $this->currentElement); - // elements defined as part of a complex type should - // not really be added to $this->elements, but for some - // reason, they are if (!isset($attrs['form'])) { $attrs['form'] = $this->schemaInfo['elementFormDefault']; } @@ -1336,24 +1432,25 @@ class XMLSchema extends nusoap_base { $this->complexTypes[$this->currentComplexType]['arrayType'] = $attrs['type']; } $this->currentElement = $attrs['name']; - $this->elements[ $attrs['name'] ] = $attrs; - $this->elements[ $attrs['name'] ]['typeClass'] = 'element'; $ename = $attrs['name']; } elseif(isset($attrs['ref'])){ $this->xdebug("processing element as ref to ".$attrs['ref']); $this->currentElement = "ref to ".$attrs['ref']; $ename = $this->getLocalPart($attrs['ref']); } else { - $this->xdebug("processing untyped element ".$attrs['name']); + $type = $this->CreateTypeName($this->currentComplexType . '_' . $attrs['name']); + $this->xdebug("processing untyped element " . $attrs['name'] . ' type ' . $type); $this->currentElement = $attrs['name']; - $this->elements[ $attrs['name'] ] = $attrs; - $this->elements[ $attrs['name'] ]['typeClass'] = 'element'; - $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['name'] . '_ContainedType'; - $this->elements[ $attrs['name'] ]['type'] = $attrs['type']; + $attrs['type'] = $this->schemaTargetNamespace . ':' . $type; $ename = $attrs['name']; } - if(isset($ename) && $this->currentComplexType){ + if (isset($ename) && $this->currentComplexType) { + $this->xdebug("add element $ename to complexType $this->currentComplexType"); $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs; + } elseif (!isset($attrs['ref'])) { + $this->xdebug("add element $ename to elements array"); + $this->elements[ $attrs['name'] ] = $attrs; + $this->elements[ $attrs['name'] ]['typeClass'] = 'element'; } break; case 'enumeration': // restriction value list member @@ -1419,8 +1516,9 @@ class XMLSchema extends nusoap_base { $this->simpleTypes[ $attrs['name'] ]['typeClass'] = 'simpleType'; $this->simpleTypes[ $attrs['name'] ]['phpType'] = 'scalar'; } else { - $this->xdebug('processing unnamed simpleType for element '.$this->currentElement); - $this->currentSimpleType = $this->currentElement . '_ContainedType'; + $name = $this->CreateTypeName($this->currentComplexType . '_' . $this->currentElement); + $this->xdebug('processing unnamed simpleType for element ' . $this->currentElement . ' named ' . $name); + $this->currentSimpleType = $name; //$this->currentElement = false; $this->simpleTypes[$this->currentSimpleType] = $attrs; $this->simpleTypes[$this->currentSimpleType]['phpType'] = 'scalar'; @@ -1563,13 +1661,13 @@ class XMLSchema extends nusoap_base { // simple types if(isset($this->simpleTypes) && count($this->simpleTypes) > 0){ foreach($this->simpleTypes as $typeName => $eParts){ - $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\"/>\n"; + $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\">\n"; if (isset($eParts['enumeration'])) { foreach ($eParts['enumeration'] as $e) { $xml .= " <$schemaPrefix:enumeration value=\"$e\"/>\n"; } } - $xml .= " "; + $xml .= " \n "; } } // elements @@ -1585,9 +1683,15 @@ class XMLSchema extends nusoap_base { } } // finish 'er up - $el = "<$schemaPrefix:schema targetNamespace=\"$this->schemaTargetNamespace\"\n"; + $attr = ''; + foreach ($this->schemaInfo as $k => $v) { + if ($k == 'elementFormDefault' || $k == 'attributeFormDefault') { + $attr .= " $k=\"$v\""; + } + } + $el = "<$schemaPrefix:schema$attr targetNamespace=\"$this->schemaTargetNamespace\"\n"; foreach (array_diff($this->usedNamespaces, $this->enclosingNamespaces) as $nsp => $ns) { - $el .= " xmlns:$nsp=\"$ns\"\n"; + $el .= " xmlns:$nsp=\"$ns\""; } $xml = $el . ">\n".$xml."\n"; return $xml; @@ -1609,8 +1713,8 @@ class XMLSchema extends nusoap_base { * returns false if no type exists, or not w/ the given namespace * else returns a string that is either a native php type, or 'struct' * - * @param string $type, name of defined type - * @param string $ns, namespace of type + * @param string $type name of defined type + * @param string $ns namespace of type * @return mixed * @access public * @deprecated @@ -1641,7 +1745,7 @@ class XMLSchema extends nusoap_base { * * For simpleType or element, the array has different keys. * - * @param string + * @param string $type * @return mixed * @access public * @see addComplexType @@ -1650,10 +1754,17 @@ class XMLSchema extends nusoap_base { */ function getTypeDef($type){ //$this->debug("in getTypeDef for type $type"); - if(isset($this->complexTypes[$type])){ + if (substr($type, -1) == '^') { + $is_element = 1; + $type = substr($type, 0, -1); + } else { + $is_element = 0; + } + + if((! $is_element) && isset($this->complexTypes[$type])){ $this->xdebug("in getTypeDef, found complexType $type"); return $this->complexTypes[$type]; - } elseif(isset($this->simpleTypes[$type])){ + } elseif((! $is_element) && isset($this->simpleTypes[$type])){ $this->xdebug("in getTypeDef, found simpleType $type"); if (!isset($this->simpleTypes[$type]['phpType'])) { // get info for type to tack onto the simple type @@ -1712,7 +1823,7 @@ class XMLSchema extends nusoap_base { /** * returns a sample serialization of a given type, or false if no type by the given name * - * @param string $type, name of type + * @param string $type name of type * @return mixed * @access public * @deprecated @@ -1722,7 +1833,7 @@ class XMLSchema extends nusoap_base { if($typeDef = $this->getTypeDef($type)){ $str .= '<'.$type; if(is_array($typeDef['attrs'])){ - foreach($attrs as $attName => $data){ + foreach($typeDef['attrs'] as $attName => $data){ $str .= " $attName=\"{type = ".$data['type']."}\""; } } @@ -1747,8 +1858,8 @@ class XMLSchema extends nusoap_base { * returns HTML form elements that allow a user * to enter values for creating an instance of the given type. * - * @param string $name, name for type instance - * @param string $type, name of type + * @param string $name name for type instance + * @param string $type name of type * @return string * @access public * @deprecated @@ -1850,7 +1961,7 @@ class XMLSchema extends nusoap_base { * @param string $phpType (should always be scalar) * @param array $enumeration array of values * @access public - * @see xmlschema + * @see nusoap_xmlschema * @see getTypeDef */ function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) { @@ -1870,7 +1981,7 @@ class XMLSchema extends nusoap_base { * adds an element to the schema * * @param array $attrs attributes that must include name and type - * @see xmlschema + * @see nusoap_xmlschema * @access public */ function addElement($attrs) { @@ -1885,7 +1996,11 @@ class XMLSchema extends nusoap_base { } } - +/** + * Backward compatibility + */ +class XMLSchema extends nusoap_xmlschema { +} ?> -* @version $Id: nusoap.php,v 1.94 2005/08/04 01:27:42 snichol Exp $ +* @version $Id: nusoap.php,v 1.114 2007/11/06 15:17:46 snichol Exp $ * @access public */ class soapval extends nusoap_base { @@ -1975,7 +2090,7 @@ class soapval extends nusoap_base { * @access public */ function serialize($use='encoded') { - return $this->serialize_val($this->value,$this->name,$this->type,$this->element_ns,$this->type_ns,$this->attributes,$use); + return $this->serialize_val($this->value, $this->name, $this->type, $this->element_ns, $this->type_ns, $this->attributes, $use, true); } /** @@ -2000,7 +2115,8 @@ class soapval extends nusoap_base { * NOTE: PHP must be compiled with the CURL extension for HTTPS support * * @author Dietrich Ayala -* @version $Id: nusoap.php,v 1.94 2005/08/04 01:27:42 snichol Exp $ +* @author Scott Nichol +* @version $Id: nusoap.php,v 1.114 2007/11/06 15:17:46 snichol Exp $ * @access public */ class soap_transport_http extends nusoap_base { @@ -2020,38 +2136,97 @@ class soap_transport_http extends nusoap_base { var $incoming_cookies = array(); var $outgoing_payload = ''; var $incoming_payload = ''; + var $response_status_line; // HTTP response status line var $useSOAPAction = true; var $persistentConnection = false; var $ch = false; // cURL handle + var $ch_options = array(); // cURL custom options + var $use_curl = false; // force cURL use + var $proxy = null; // proxy information (associative array) var $username = ''; var $password = ''; var $authtype = ''; var $digestRequest = array(); - var $certRequest = array(); // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional) + var $certRequest = array(); // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional) // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem' // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem' // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem' // passphrase: SSL key password/passphrase + // certpassword: SSL certificate password // verifypeer: default is 1 // verifyhost: default is 1 /** * constructor + * + * @param string $url The URL to which to connect + * @param array $curl_options User-specified cURL options + * @param boolean $use_curl Whether to try to force cURL use + * @access public */ - function soap_transport_http($url){ + function soap_transport_http($url, $curl_options = NULL, $use_curl = false){ parent::nusoap_base(); + $this->debug("ctor url=$url use_curl=$use_curl curl_options:"); + $this->appendDebug($this->varDump($curl_options)); $this->setURL($url); + if (is_array($curl_options)) { + $this->ch_options = $curl_options; + } + $this->use_curl = $use_curl; ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev); - $this->outgoing_headers['User-Agent'] = $this->title.'/'.$this->version.' ('.$rev[1].')'; - $this->debug('set User-Agent: ' . $this->outgoing_headers['User-Agent']); + $this->setHeader('User-Agent', $this->title.'/'.$this->version.' ('.$rev[1].')'); } + /** + * sets a cURL option + * + * @param mixed $option The cURL option (always integer?) + * @param mixed $value The cURL option value + * @access private + */ + function setCurlOption($option, $value) { + $this->debug("setCurlOption option=$option, value="); + $this->appendDebug($this->varDump($value)); + curl_setopt($this->ch, $option, $value); + } + + /** + * sets an HTTP header + * + * @param string $name The name of the header + * @param string $value The value of the header + * @access private + */ + function setHeader($name, $value) { + $this->outgoing_headers[$name] = $value; + $this->debug("set header $name: $value"); + } + + /** + * unsets an HTTP header + * + * @param string $name The name of the header + * @access private + */ + function unsetHeader($name) { + if (isset($this->outgoing_headers[$name])) { + $this->debug("unset header $name"); + unset($this->outgoing_headers[$name]); + } + } + + /** + * sets the URL to which to connect + * + * @param string $url The URL to which to connect + * @access private + */ function setURL($url) { $this->url = $url; $u = parse_url($url); foreach($u as $k => $v){ - $this->debug("$k = $v"); + $this->debug("parsed URL $k = $v"); $this->$k = $v; } @@ -2074,17 +2249,38 @@ class soap_transport_http extends nusoap_base { // build headers if (!isset($u['port'])) { - $this->outgoing_headers['Host'] = $this->host; + $this->setHeader('Host', $this->host); } else { - $this->outgoing_headers['Host'] = $this->host.':'.$this->port; + $this->setHeader('Host', $this->host.':'.$this->port); } - $this->debug('set Host: ' . $this->outgoing_headers['Host']); if (isset($u['user']) && $u['user'] != '') { $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : ''); } } - + + /** + * gets the I/O method to use + * + * @return string I/O method to use (socket|curl|unknown) + * @access private + */ + function io_method() { + if ($this->use_curl || ($this->scheme == 'https') || ($this->scheme == 'http' && $this->authtype == 'ntlm') || ($this->scheme == 'http' && is_array($this->proxy) && $this->proxy['authtype'] == 'ntlm')) + return 'curl'; + if (($this->scheme == 'http' || $this->scheme == 'ssl') && $this->authtype != 'ntlm' && (!is_array($this->proxy) || $this->proxy['authtype'] != 'ntlm')) + return 'socket'; + return 'unknown'; + } + + /** + * establish an HTTP connection + * + * @param integer $timeout set connection timeout in seconds + * @param integer $response_timeout set response timeout in seconds + * @return boolean true if connected, false if not + * @access private + */ function connect($connection_timeout=0,$response_timeout=30){ // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like // "regular" socket. @@ -2099,7 +2295,15 @@ class soap_transport_http extends nusoap_base { // } // } $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port"); - if ($this->scheme == 'http' || $this->scheme == 'ssl') { + if ($this->io_method() == 'socket') { + if (!is_array($this->proxy)) { + $host = $this->host; + $port = $this->port; + } else { + $host = $this->proxy['host']; + $port = $this->proxy['port']; + } + // use persistent connection if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){ if (!feof($this->fp)) { @@ -2112,9 +2316,7 @@ class soap_transport_http extends nusoap_base { // munge host if using OpenSSL if ($this->scheme == 'ssl') { - $host = 'ssl://' . $this->host; - } else { - $host = $this->host; + $host = 'ssl://' . $host; } $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout); @@ -2144,81 +2346,156 @@ class soap_transport_http extends nusoap_base { $this->debug('socket connected'); return true; - } else if ($this->scheme == 'https') { + } else if ($this->io_method() == 'curl') { if (!extension_loaded('curl')) { - $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS'); +// $this->setError('cURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS'); + $this->setError('The PHP cURL Extension is required for HTTPS or NLTM. You will need to re-build or update your PHP to included cURL.'); return false; } - $this->debug('connect using https'); + // Avoid warnings when PHP does not have these options + if (defined('CURLOPT_CONNECTIONTIMEOUT')) + $CURLOPT_CONNECTIONTIMEOUT = CURLOPT_CONNECTIONTIMEOUT; + else + $CURLOPT_CONNECTIONTIMEOUT = 78; + if (defined('CURLOPT_HTTPAUTH')) + $CURLOPT_HTTPAUTH = CURLOPT_HTTPAUTH; + else + $CURLOPT_HTTPAUTH = 107; + if (defined('CURLOPT_PROXYAUTH')) + $CURLOPT_PROXYAUTH = CURLOPT_PROXYAUTH; + else + $CURLOPT_PROXYAUTH = 111; + if (defined('CURLAUTH_BASIC')) + $CURLAUTH_BASIC = CURLAUTH_BASIC; + else + $CURLAUTH_BASIC = 1; + if (defined('CURLAUTH_DIGEST')) + $CURLAUTH_DIGEST = CURLAUTH_DIGEST; + else + $CURLAUTH_DIGEST = 2; + if (defined('CURLAUTH_NTLM')) + $CURLAUTH_NTLM = CURLAUTH_NTLM; + else + $CURLAUTH_NTLM = 8; + + $this->debug('connect using cURL'); // init CURL $this->ch = curl_init(); // set url - $hostURL = ($this->port != '') ? "https://$this->host:$this->port" : "https://$this->host"; + $hostURL = ($this->port != '') ? "$this->scheme://$this->host:$this->port" : "$this->scheme://$this->host"; // add path $hostURL .= $this->path; - curl_setopt($this->ch, CURLOPT_URL, $hostURL); + $this->setCurlOption(CURLOPT_URL, $hostURL); // follow location headers (re-directs) - curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1); + if (ini_get('safe_mode') || ini_get('open_basedir')) { + $this->debug('safe_mode or open_basedir set, so do not set CURLOPT_FOLLOWLOCATION'); + $this->debug('safe_mode = '); + $this->appendDebug($this->varDump(ini_get('safe_mode'))); + $this->debug('open_basedir = '); + $this->appendDebug($this->varDump(ini_get('open_basedir'))); + } else { + $this->setCurlOption(CURLOPT_FOLLOWLOCATION, 1); + } // ask for headers in the response output - curl_setopt($this->ch, CURLOPT_HEADER, 1); + $this->setCurlOption(CURLOPT_HEADER, 1); // ask for the response output as the return value - curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1); + $this->setCurlOption(CURLOPT_RETURNTRANSFER, 1); // encode // We manage this ourselves through headers and encoding // if(function_exists('gzuncompress')){ -// curl_setopt($this->ch, CURLOPT_ENCODING, 'deflate'); +// $this->setCurlOption(CURLOPT_ENCODING, 'deflate'); // } // persistent connection if ($this->persistentConnection) { + // I believe the following comment is now bogus, having applied to + // the code when it used CURLOPT_CUSTOMREQUEST to send the request. // The way we send data, we cannot use persistent connections, since // there will be some "junk" at the end of our request. - //curl_setopt($this->ch, CURL_HTTP_VERSION_1_1, true); + //$this->setCurlOption(CURL_HTTP_VERSION_1_1, true); $this->persistentConnection = false; - $this->outgoing_headers['Connection'] = 'close'; - $this->debug('set Connection: ' . $this->outgoing_headers['Connection']); + $this->setHeader('Connection', 'close'); } - // set timeout + // set timeouts if ($connection_timeout != 0) { - curl_setopt($this->ch, CURLOPT_TIMEOUT, $connection_timeout); - } - // TODO: cURL has added a connection timeout separate from the response timeout - //if ($connection_timeout != 0) { - // curl_setopt($this->ch, CURLOPT_CONNECTIONTIMEOUT, $connection_timeout); - //} - //if ($response_timeout != 0) { - // curl_setopt($this->ch, CURLOPT_TIMEOUT, $response_timeout); - //} - - // recent versions of cURL turn on peer/host checking by default, - // while PHP binaries are not compiled with a default location for the - // CA cert bundle, so disable peer/host checking. -//curl_setopt($this->ch, CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt'); - curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0); - curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0); - - // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo) - if ($this->authtype == 'certificate') { - if (isset($this->certRequest['cainfofile'])) { - curl_setopt($this->ch, CURLOPT_CAINFO, $this->certRequest['cainfofile']); - } - if (isset($this->certRequest['verifypeer'])) { - curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']); - } else { - curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1); + $this->setCurlOption($CURLOPT_CONNECTIONTIMEOUT, $connection_timeout); + } + if ($response_timeout != 0) { + $this->setCurlOption(CURLOPT_TIMEOUT, $response_timeout); + } + + if ($this->scheme == 'https') { + $this->debug('set cURL SSL verify options'); + // recent versions of cURL turn on peer/host checking by default, + // while PHP binaries are not compiled with a default location for the + // CA cert bundle, so disable peer/host checking. + //$this->setCurlOption(CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt'); + $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, 0); + $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, 0); + + // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo) + if ($this->authtype == 'certificate') { + $this->debug('set cURL certificate options'); + if (isset($this->certRequest['cainfofile'])) { + $this->setCurlOption(CURLOPT_CAINFO, $this->certRequest['cainfofile']); + } + if (isset($this->certRequest['verifypeer'])) { + $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']); + } else { + $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, 1); + } + if (isset($this->certRequest['verifyhost'])) { + $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']); + } else { + $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, 1); + } + if (isset($this->certRequest['sslcertfile'])) { + $this->setCurlOption(CURLOPT_SSLCERT, $this->certRequest['sslcertfile']); + } + if (isset($this->certRequest['sslkeyfile'])) { + $this->setCurlOption(CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']); + } + if (isset($this->certRequest['passphrase'])) { + $this->setCurlOption(CURLOPT_SSLKEYPASSWD, $this->certRequest['passphrase']); + } + if (isset($this->certRequest['certpassword'])) { + $this->setCurlOption(CURLOPT_SSLCERTPASSWD, $this->certRequest['certpassword']); + } } - if (isset($this->certRequest['verifyhost'])) { - curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']); - } else { - curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1); + } + if ($this->authtype && ($this->authtype != 'certificate')) { + if ($this->username) { + $this->debug('set cURL username/password'); + $this->setCurlOption(CURLOPT_USERPWD, "$this->username:$this->password"); } - if (isset($this->certRequest['sslcertfile'])) { - curl_setopt($this->ch, CURLOPT_SSLCERT, $this->certRequest['sslcertfile']); + if ($this->authtype == 'basic') { + $this->debug('set cURL for Basic authentication'); + $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_BASIC); } - if (isset($this->certRequest['sslkeyfile'])) { - curl_setopt($this->ch, CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']); + if ($this->authtype == 'digest') { + $this->debug('set cURL for digest authentication'); + $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_DIGEST); } - if (isset($this->certRequest['passphrase'])) { - curl_setopt($this->ch, CURLOPT_SSLKEYPASSWD , $this->certRequest['passphrase']); + if ($this->authtype == 'ntlm') { + $this->debug('set cURL for NTLM authentication'); + $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_NTLM); + } + } + if (is_array($this->proxy)) { + $this->debug('set cURL proxy options'); + if ($this->proxy['port'] != '') { + $this->setCurlOption(CURLOPT_PROXY, $this->proxy['host'].':'.$this->proxy['port']); + } else { + $this->setCurlOption(CURLOPT_PROXY, $this->proxy['host']); + } + if ($this->proxy['username'] || $this->proxy['password']) { + $this->debug('set cURL proxy authentication options'); + $this->setCurlOption(CURLOPT_PROXYUSERPWD, $this->proxy['username'].':'.$this->proxy['password']); + if ($this->proxy['authtype'] == 'basic') { + $this->setCurlOption($CURLOPT_PROXYAUTH, $CURLAUTH_BASIC); + } + if ($this->proxy['authtype'] == 'ntlm') { + $this->setCurlOption($CURLOPT_PROXYAUTH, $CURLAUTH_NTLM); + } } } $this->debug('cURL connection set up'); @@ -2229,9 +2506,9 @@ class soap_transport_http extends nusoap_base { return false; } } - + /** - * send the SOAP message via HTTP + * sends the SOAP request and gets the SOAP response via HTTP[S] * * @param string $data message data * @param integer $timeout set connection timeout in seconds @@ -2262,7 +2539,7 @@ class soap_transport_http extends nusoap_base { // get response $respdata = $this->getResponse(); } else { - $this->setError('Too many tries to get an OK response'); + $this->setError("Too many tries to get an OK response ($this->response_status_line)"); } } $this->debug('end of send()'); @@ -2271,14 +2548,15 @@ class soap_transport_http extends nusoap_base { /** - * send the SOAP message via HTTPS 1.0 using CURL + * sends the SOAP request and gets the SOAP response via HTTPS using CURL * - * @param string $msg message data + * @param string $data message data * @param integer $timeout set connection timeout in seconds * @param integer $response_timeout set response timeout in seconds * @param array $cookies cookies to send * @return string data * @access public + * @deprecated */ function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) { return $this->send($data, $timeout, $response_timeout, $cookies); @@ -2289,16 +2567,19 @@ class soap_transport_http extends nusoap_base { * * @param string $username * @param string $password - * @param string $authtype (basic, digest, certificate) + * @param string $authtype (basic|digest|certificate|ntlm) * @param array $digestRequest (keys must be nonce, nc, realm, qop) - * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs) + * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs) * @access public */ function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) { - $this->debug("Set credentials for authtype $authtype"); + $this->debug("setCredentials username=$username authtype=$authtype digestRequest="); + $this->appendDebug($this->varDump($digestRequest)); + $this->debug("certRequest="); + $this->appendDebug($this->varDump($certRequest)); // cf. RFC 2617 if ($authtype == 'basic') { - $this->outgoing_headers['Authorization'] = 'Basic '.base64_encode(str_replace(':','',$username).':'.$password); + $this->setHeader('Authorization', 'Basic '.base64_encode(str_replace(':','',$username).':'.$password)); } elseif ($authtype == 'digest') { if (isset($digestRequest['nonce'])) { $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1; @@ -2312,7 +2593,7 @@ class soap_transport_http extends nusoap_base { $HA1 = md5($A1); // A2 = Method ":" digest-uri-value - $A2 = 'POST:' . $this->digest_uri; + $A2 = $this->request_method . ':' . $this->digest_uri; // H(A2) $HA2 = md5($A2); @@ -2339,21 +2620,24 @@ class soap_transport_http extends nusoap_base { $hashedDigest = md5($unhashedDigest); - $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 . '"'; + $opaque = ''; + if (isset($digestRequest['opaque'])) { + $opaque = ', opaque="' . $digestRequest['opaque'] . '"'; + } + + $this->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . $digestRequest['realm'] . '", nonce="' . $nonce . '", uri="' . $this->digest_uri . $opaque . '", cnonce="' . $cnonce . '", nc=' . sprintf("%08x", $digestRequest['nc']) . ', qop="' . $digestRequest['qop'] . '", response="' . $hashedDigest . '"'); } } elseif ($authtype == 'certificate') { $this->certRequest = $certRequest; + $this->debug('Authorization header not set for certificate'); + } elseif ($authtype == 'ntlm') { + // do nothing + $this->debug('Authorization header not set for ntlm'); } $this->username = $username; $this->password = $password; $this->authtype = $authtype; $this->digestRequest = $digestRequest; - - if (isset($this->outgoing_headers['Authorization'])) { - $this->debug('set Authorization: ' . substr($this->outgoing_headers['Authorization'], 0, 12) . '...'); - } else { - $this->debug('Authorization header not set'); - } } /** @@ -2363,8 +2647,7 @@ class soap_transport_http extends nusoap_base { * @access public */ function setSOAPAction($soapaction) { - $this->outgoing_headers['SOAPAction'] = '"' . $soapaction . '"'; - $this->debug('set SOAPAction: ' . $this->outgoing_headers['SOAPAction']); + $this->setHeader('SOAPAction', '"' . $soapaction . '"'); } /** @@ -2376,12 +2659,10 @@ class soap_transport_http extends nusoap_base { function setEncoding($enc='gzip, deflate') { if (function_exists('gzdeflate')) { $this->protocol_version = '1.1'; - $this->outgoing_headers['Accept-Encoding'] = $enc; - $this->debug('set Accept-Encoding: ' . $this->outgoing_headers['Accept-Encoding']); + $this->setHeader('Accept-Encoding', $enc); if (!isset($this->outgoing_headers['Connection'])) { - $this->outgoing_headers['Connection'] = 'close'; + $this->setHeader('Connection', 'close'); $this->persistentConnection = false; - $this->debug('set Connection: ' . $this->outgoing_headers['Connection']); } set_magic_quotes_runtime(0); // deprecated @@ -2392,22 +2673,58 @@ class soap_transport_http extends nusoap_base { /** * set proxy info here * - * @param string $proxyhost + * @param string $proxyhost use an empty string to remove proxy * @param string $proxyport * @param string $proxyusername * @param string $proxypassword + * @param string $proxyauthtype (basic|ntlm) * @access public */ - function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') { - $this->uri = $this->url; - $this->host = $proxyhost; - $this->port = $proxyport; - if ($proxyusername != '' && $proxypassword != '') { - $this->outgoing_headers['Proxy-Authorization'] = ' Basic '.base64_encode($proxyusername.':'.$proxypassword); - $this->debug('set Proxy-Authorization: ' . $this->outgoing_headers['Proxy-Authorization']); + function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 'basic') { + if ($proxyhost) { + $this->proxy = array( + 'host' => $proxyhost, + 'port' => $proxyport, + 'username' => $proxyusername, + 'password' => $proxypassword, + 'authtype' => $proxyauthtype + ); + if ($proxyusername != '' && $proxypassword != '' && $proxyauthtype = 'basic') { + $this->setHeader('Proxy-Authorization', ' Basic '.base64_encode($proxyusername.':'.$proxypassword)); + } + } else { + $this->debug('remove proxy'); + $proxy = null; + unsetHeader('Proxy-Authorization'); } } + + /** + * Test if the given string starts with a header that is to be skipped. + * Skippable headers result from chunked transfer and proxy requests. + * + * @param string $data The string to check. + * @returns boolean Whether a skippable header was found. + * @access private + */ + function isSkippableCurlHeader(&$data) { + $skipHeaders = array( 'HTTP/1.1 100', + 'HTTP/1.0 301', + 'HTTP/1.1 301', + 'HTTP/1.0 302', + 'HTTP/1.1 302', + 'HTTP/1.0 401', + 'HTTP/1.1 401', + 'HTTP/1.0 200 Connection established'); + foreach ($skipHeaders as $hd) { + $prefix = substr($data, 0, strlen($hd)); + if ($prefix == $hd) return true; + } + + return false; + } + /** * decode a string that is encoded w/ "chunked' transfer encoding * as defined in RFC2068 19.4.6 @@ -2467,16 +2784,29 @@ class soap_transport_http extends nusoap_base { return $new; } - /* - * Writes payload, including HTTP headers, to $this->outgoing_payload. + /** + * Writes the payload, including HTTP headers, to $this->outgoing_payload. + * + * @param string $data HTTP body + * @param string $cookie_str data for HTTP Cookie header + * @return void + * @access private */ function buildPayload($data, $cookie_str = '') { + // Note: for cURL connections, $this->outgoing_payload is ignored, + // as is the Content-Length header, but these are still created as + // debugging guides. + // add content-length header - $this->outgoing_headers['Content-Length'] = strlen($data); - $this->debug('set Content-Length: ' . $this->outgoing_headers['Content-Length']); + $this->setHeader('Content-Length', strlen($data)); // start building outgoing payload: - $req = "$this->request_method $this->uri HTTP/$this->protocol_version"; + if ($this->proxy) { + $uri = $this->url; + } else { + $uri = $this->uri; + } + $req = "$this->request_method $uri HTTP/$this->protocol_version"; $this->debug("HTTP request: $req"); $this->outgoing_payload = "$req\r\n"; @@ -2501,6 +2831,14 @@ class soap_transport_http extends nusoap_base { $this->outgoing_payload .= $data; } + /** + * sends the SOAP request via HTTP[S] + * + * @param string $data message data + * @param array $cookies cookies to send + * @return boolean true if OK, false if problem + * @access private + */ function sendRequest($data, $cookies = NULL) { // build cookie string $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https'))); @@ -2508,7 +2846,7 @@ class soap_transport_http extends nusoap_base { // build payload $this->buildPayload($data, $cookie_str); - if ($this->scheme == 'http' || $this->scheme == 'ssl') { + if ($this->io_method() == 'socket') { // send payload if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) { $this->setError('couldn\'t write message data to socket'); @@ -2517,33 +2855,51 @@ class soap_transport_http extends nusoap_base { } $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload)); return true; - } else if ($this->scheme == 'https') { + } else if ($this->io_method() == 'curl') { // set payload - // TODO: cURL does say this should only be the verb, and in fact it + // cURL does say this should only be the verb, and in fact it // turns out that the URI and HTTP version are appended to this, which - // some servers refuse to work with - //curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload); + // some servers refuse to work with (so we no longer use this method!) + //$this->setCurlOption(CURLOPT_CUSTOMREQUEST, $this->outgoing_payload); + $curl_headers = array(); foreach($this->outgoing_headers as $k => $v){ - $curl_headers[] = "$k: $v"; + if ($k == 'Connection' || $k == 'Content-Length' || $k == 'Host' || $k == 'Authorization' || $k == 'Proxy-Authorization') { + $this->debug("Skip cURL header $k: $v"); + } else { + $curl_headers[] = "$k: $v"; + } } if ($cookie_str != '') { $curl_headers[] = 'Cookie: ' . $cookie_str; } - curl_setopt($this->ch, CURLOPT_HTTPHEADER, $curl_headers); + $this->setCurlOption(CURLOPT_HTTPHEADER, $curl_headers); + $this->debug('set cURL HTTP headers'); if ($this->request_method == "POST") { - curl_setopt($this->ch, CURLOPT_POST, 1); - curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data); + $this->setCurlOption(CURLOPT_POST, 1); + $this->setCurlOption(CURLOPT_POSTFIELDS, $data); + $this->debug('set cURL POST data'); } else { } + // insert custom user-set cURL options + foreach ($this->ch_options as $key => $val) { + $this->setCurlOption($key, $val); + } + $this->debug('set cURL payload'); return true; } } + /** + * gets the SOAP response via HTTP[S] + * + * @return string the response (also sets member variables like incoming_payload) + * @access private + */ function getResponse(){ $this->incoming_payload = ''; - if ($this->scheme == 'http' || $this->scheme == 'ssl') { + if ($this->io_method() == 'socket') { // loop until headers have been retrieved $data = ''; while (!isset($lb)){ @@ -2579,8 +2935,8 @@ class soap_transport_http extends nusoap_base { $lb = "\n"; } } - // remove 100 header - if(isset($lb) && ereg('^HTTP/1.1 100',$data)){ + // remove 100 headers + if (isset($lb) && ereg('^HTTP/1.1 100',$data)) { unset($lb); $data = ''; }// @@ -2706,7 +3062,7 @@ class soap_transport_http extends nusoap_base { // $this->incoming_payload = $header_data.$lb.$lb.$data; // } - } else if ($this->scheme == 'https') { + } else if ($this->io_method() == 'curl') { // send and receive $this->debug('send and receive with cURL'); $this->incoming_payload = curl_exec($this->ch); @@ -2732,14 +3088,28 @@ class soap_transport_http extends nusoap_base { $this->debug('No cURL error, closing cURL'); curl_close($this->ch); - // remove 100 header(s) - while (ereg('^HTTP/1.1 100',$data)) { + // try removing skippable headers + $savedata = $data; + while ($this->isSkippableCurlHeader($data)) { + $this->debug("Found HTTP header to skip"); if ($pos = strpos($data,"\r\n\r\n")) { $data = ltrim(substr($data,$pos)); } elseif($pos = strpos($data,"\n\n") ) { $data = ltrim(substr($data,$pos)); } } + + if ($data == '') { + // have nothing left; just remove 100 header(s) + $data = $savedata; + while (ereg('^HTTP/1.1 100',$data)) { + if ($pos = strpos($data,"\r\n\r\n")) { + $data = ltrim(substr($data,$pos)); + } elseif($pos = strpos($data,"\n\n") ) { + $data = ltrim(substr($data,$pos)); + } + } + } // separate content from HTTP headers if ($pos = strpos($data,"\r\n\r\n")) { @@ -2779,14 +3149,15 @@ class soap_transport_http extends nusoap_base { } } - $arr = explode(' ', $header_array[0], 3); + $this->response_status_line = $header_array[0]; + $arr = explode(' ', $this->response_status_line, 3); $http_version = $arr[0]; $http_status = intval($arr[1]); $http_reason = count($arr) > 2 ? $arr[2] : ''; // see if we need to resend the request with http digest authentication - if (isset($this->incoming_headers['location']) && $http_status == 301) { - $this->debug("Got 301 $http_reason with Location: " . $this->incoming_headers['location']); + if (isset($this->incoming_headers['location']) && ($http_status == 301 || $http_status == 302)) { + $this->debug("Got $http_status $http_reason with Location: " . $this->incoming_headers['location']); $this->setURL($this->incoming_headers['location']); $this->tryagain = true; return false; @@ -2896,19 +3267,30 @@ class soap_transport_http extends nusoap_base { return $data; } + /** + * sets the content-type for the SOAP message to be sent + * + * @param string $type the content type, MIME style + * @param mixed $charset character set used for encoding (or false) + * @access public + */ function setContentType($type, $charset = false) { - $this->outgoing_headers['Content-Type'] = $type . ($charset ? '; charset=' . $charset : ''); - $this->debug('set Content-Type: ' . $this->outgoing_headers['Content-Type']); + $this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : '')); } + /** + * specifies that an HTTP persistent connection should be used + * + * @return boolean whether the request was honored by this method. + * @access public + */ function usePersistentConnection(){ if (isset($this->outgoing_headers['Accept-Encoding'])) { return false; } $this->protocol_version = '1.1'; $this->persistentConnection = true; - $this->outgoing_headers['Connection'] = 'Keep-Alive'; - $this->debug('set Connection: ' . $this->outgoing_headers['Connection']); + $this->setHeader('Connection', 'Keep-Alive'); return true; } @@ -3032,16 +3414,15 @@ class soap_transport_http extends nusoap_base { /** * -* soap_server allows the user to create a SOAP server +* nusoap_server allows the user to create a SOAP server * that is capable of receiving messages and returning responses * -* NOTE: WSDL functionality is experimental -* * @author Dietrich Ayala -* @version $Id: nusoap.php,v 1.94 2005/08/04 01:27:42 snichol Exp $ +* @author Scott Nichol +* @version $Id: nusoap.php,v 1.114 2007/11/06 15:17:46 snichol Exp $ * @access public */ -class soap_server extends nusoap_base { +class nusoap_server extends nusoap_base { /** * HTTP headers of request * @var array @@ -3060,6 +3441,12 @@ class soap_server extends nusoap_base { * @access public */ var $requestHeaders = ''; + /** + * SOAP Headers from request (parsed) + * @var mixed + * @access public + */ + var $requestHeader = NULL; /** * SOAP body request portion (incomplete namespace resolution; special characters not escaped) (text) * @var string @@ -3122,8 +3509,8 @@ class soap_server extends nusoap_base { */ var $response = ''; /** - * SOAP headers for response (text) - * @var string + * SOAP headers for response (text or array of soapval or associative array) + * @var mixed * @access public */ var $responseHeaders = ''; @@ -3192,7 +3579,7 @@ class soap_server extends nusoap_base { * @param mixed $wsdl file path or URL (string), or wsdl instance (object) * @access public */ - function soap_server($wsdl=false){ + function nusoap_server($wsdl=false){ parent::nusoap_base(); // turn on debugging? global $debug; @@ -3209,13 +3596,13 @@ class soap_server extends nusoap_base { } if (isset($debug)) { - $this->debug("In soap_server, set debug_flag=$debug based on global flag"); + $this->debug("In nusoap_server, set debug_flag=$debug based on global flag"); $this->debug_flag = $debug; } elseif (isset($_SERVER['QUERY_STRING'])) { $qs = explode('&', $_SERVER['QUERY_STRING']); foreach ($qs as $v) { if (substr($v, 0, 6) == 'debug=') { - $this->debug("In soap_server, set debug_flag=" . substr($v, 6) . " based on query string #1"); + $this->debug("In nusoap_server, set debug_flag=" . substr($v, 6) . " based on query string #1"); $this->debug_flag = substr($v, 6); } } @@ -3223,7 +3610,7 @@ class soap_server extends nusoap_base { $qs = explode('&', $HTTP_SERVER_VARS['QUERY_STRING']); foreach ($qs as $v) { if (substr($v, 0, 6) == 'debug=') { - $this->debug("In soap_server, set debug_flag=" . substr($v, 6) . " based on query string #2"); + $this->debug("In nusoap_server, set debug_flag=" . substr($v, 6) . " based on query string #2"); $this->debug_flag = substr($v, 6); } } @@ -3231,7 +3618,7 @@ class soap_server extends nusoap_base { // wsdl if($wsdl){ - $this->debug("In soap_server, WSDL is specified"); + $this->debug("In nusoap_server, WSDL is specified"); if (is_object($wsdl) && (get_class($wsdl) == 'wsdl')) { $this->wsdl = $wsdl; $this->externalWSDLURL = $this->wsdl->wsdl; @@ -3351,9 +3738,9 @@ class soap_server extends nusoap_base { $this->debug("In parse_http_headers, use _SERVER"); foreach ($_SERVER as $k => $v) { if (substr($k, 0, 5) == 'HTTP_') { - $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5)))); $k = strtolower(substr($k, 5)); + $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5)))); } else { - $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k))); $k = strtolower($k); + $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k))); } if ($k == 'soapaction') { // get SOAPAction header @@ -3458,11 +3845,11 @@ class soap_server extends nusoap_base { } elseif ($this->headers['content-encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) { $data = $degzdata; } else { - $this->fault('Client', 'Errors occurred when trying to decode the data'); + $this->fault('SOAP-ENV:Client', 'Errors occurred when trying to decode the data'); return; } } else { - $this->fault('Client', 'This Server does not support compressed data'); + $this->fault('SOAP-ENV:Client', 'This Server does not support compressed data'); return; } } @@ -3504,7 +3891,7 @@ class soap_server extends nusoap_base { $this->methodname = $this->opData['name']; } else { $this->debug('in invoke_method, no WSDL for operation=' . $this->methodname); - $this->fault('Client', "Operation '" . $this->methodname . "' is not defined in the WSDL for this service"); + $this->fault('SOAP-ENV:Client', "Operation '" . $this->methodname . "' is not defined in the WSDL for this service"); return; } } else { @@ -3537,7 +3924,7 @@ class soap_server extends nusoap_base { if (!function_exists($this->methodname)) { $this->debug("in invoke_method, function '$this->methodname' not found!"); $this->result = 'fault: method not found'; - $this->fault('Client',"method '$this->methodname' not defined in service"); + $this->fault('SOAP-ENV:Client',"method '$this->methodname' not defined in service"); return; } } else { @@ -3545,7 +3932,7 @@ class soap_server extends nusoap_base { if (!in_array($method_to_compare, get_class_methods($class))) { $this->debug("in invoke_method, method '$this->methodname' not found in class '$class'!"); $this->result = 'fault: method not found'; - $this->fault('Client',"method '$this->methodname' not defined in service"); + $this->fault('SOAP-ENV:Client',"method '$this->methodname' not defined in service"); return; } } @@ -3557,7 +3944,7 @@ class soap_server extends nusoap_base { $this->debug('ERROR: request not verified against method signature'); $this->result = 'fault: request failed validation against method signature'; // return fault - $this->fault('Client',"Operation '$this->methodname' not defined in service."); + $this->fault('SOAP-ENV:Client',"Operation '$this->methodname' not defined in service."); return; } @@ -3583,8 +3970,8 @@ class soap_server extends nusoap_base { } if ($this->methodparams) { foreach ($this->methodparams as $param) { - if (is_array($param)) { - $this->fault('Client', 'NuSOAP does not handle complexType parameters correctly when using eval; call_user_func_array must be available'); + if (is_array($param) || is_object($param)) { + $this->fault('SOAP-ENV:Client', 'NuSOAP does not handle complexType parameters correctly when using eval; call_user_func_array must be available'); return; } $funcCall .= "\"$param\","; @@ -3606,11 +3993,15 @@ class soap_server extends nusoap_base { $instance = new $class (); $call_arg = array(&$instance, $method); } - $this->methodreturn = call_user_func_array($call_arg, $this->methodparams); + if (is_array($this->methodparams)) { + $this->methodreturn = call_user_func_array($call_arg, array_values($this->methodparams)); + } else { + $this->methodreturn = call_user_func_array($call_arg, array()); + } } $this->debug('in invoke_method, methodreturn:'); $this->appendDebug($this->varDump($this->methodreturn)); - $this->debug("in invoke_method, called method $this->methodname, received $this->methodreturn of type ".gettype($this->methodreturn)); + $this->debug("in invoke_method, called method $this->methodname, received data of type ".gettype($this->methodreturn)); } /** @@ -3627,7 +4018,7 @@ class soap_server extends nusoap_base { function serialize_return() { $this->debug('Entering serialize_return methodname: ' . $this->methodname . ' methodURI: ' . $this->methodURI); // if fault - if (isset($this->methodreturn) && (get_class($this->methodreturn) == 'soap_fault')) { + if (isset($this->methodreturn) && ((get_class($this->methodreturn) == 'soap_fault') || (get_class($this->methodreturn) == 'nusoap_fault'))) { $this->debug('got a fault object from method'); $this->fault = $this->methodreturn; return; @@ -3638,11 +4029,15 @@ class soap_server extends nusoap_base { $this->debug('got a(n) '.gettype($this->methodreturn).' from method'); $this->debug('serializing return value'); if($this->wsdl){ - // weak attempt at supporting multiple output params - if(sizeof($this->opData['output']['parts']) > 1){ + if (sizeof($this->opData['output']['parts']) > 1) { + $this->debug('more than one output part, so use the method return unchanged'); $opParams = $this->methodreturn; - } else { - // TODO: is this really necessary? + } elseif (sizeof($this->opData['output']['parts']) == 1) { + $this->debug('exactly one output part, so wrap the method return in a simple array'); + // TODO: verify that it is not already wrapped! + //foreach ($this->opData['output']['parts'] as $name => $type) { + // $this->debug('wrap in element named ' . $name); + //} $opParams = array($this->methodreturn); } $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams); @@ -3650,7 +4045,7 @@ class soap_server extends nusoap_base { $this->wsdl->clearDebug(); if($errstr = $this->wsdl->getError()){ $this->debug('got wsdl error: '.$errstr); - $this->fault('Server', 'unable to serialize result'); + $this->fault('SOAP-ENV:Server', 'unable to serialize result'); return; } } else { @@ -3671,7 +4066,8 @@ class soap_server extends nusoap_base { if ($this->opData['style'] == 'rpc') { $this->debug('style is rpc for serialization: use is ' . $this->opData['output']['use']); if ($this->opData['output']['use'] == 'literal') { - $payload = '<'.$this->methodname.'Response xmlns="'.$this->methodURI.'">'.$return_val.'methodname."Response>"; + // http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html R2735 says rpc/literal accessor elements should not be in a namespace + $payload = 'methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'methodname."Response>"; } else { $payload = 'methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'methodname."Response>"; } @@ -3694,7 +4090,7 @@ class soap_server extends nusoap_base { $encodingStyle = ''; } // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces. - $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders,$this->wsdl->usedNamespaces,$this->opData['style'],$encodingStyle); + $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders,$this->wsdl->usedNamespaces,$this->opData['style'],$this->opData['output']['use'],$encodingStyle); } else { $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders); } @@ -3827,25 +4223,27 @@ class soap_server extends nusoap_base { // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1 $this->xml_encoding = 'ISO-8859-1'; } - $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating soap_parser'); + $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating nusoap_parser'); // parse response, get soap parser obj - $parser = new soap_parser($data,$this->xml_encoding,'',$this->decode_utf8); + $parser = new nusoap_parser($data,$this->xml_encoding,'',$this->decode_utf8); // parser debug $this->debug("parser debug: \n".$parser->getDebug()); // if fault occurred during message parsing if($err = $parser->getError()){ $this->result = 'fault: error in msg parsing: '.$err; - $this->fault('Client',"error in msg parsing:\n".$err); + $this->fault('SOAP-ENV:Client',"error in msg parsing:\n".$err); // else successfully parsed request into soapval object } else { // get/set methodname $this->methodURI = $parser->root_struct_namespace; $this->methodname = $parser->root_struct_name; $this->debug('methodname: '.$this->methodname.' methodURI: '.$this->methodURI); - $this->debug('calling parser->get_response()'); - $this->methodparams = $parser->get_response(); + $this->debug('calling parser->get_soapbody()'); + $this->methodparams = $parser->get_soapbody(); // get SOAP headers $this->requestHeaders = $parser->getHeaders(); + // get SOAP Header + $this->requestHeader = $parser->get_soapheader(); // add document for doclit support $this->document = $parser->document; } @@ -3935,13 +4333,20 @@ class soap_server extends nusoap_base { if (isset($_SERVER)) { $SERVER_NAME = $_SERVER['SERVER_NAME']; $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME']; + $HTTPS = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : (isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off'); } elseif (isset($HTTP_SERVER_VARS)) { $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME']; $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME']; + $HTTPS = isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off'; } else { $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available"); } - $soapaction = "http://$SERVER_NAME$SCRIPT_NAME/$name"; + if ($HTTPS == '1' || $HTTPS == 'on') { + $SCHEME = 'https'; + } else { + $SCHEME = 'http'; + } + $soapaction = "$SCHEME://$SERVER_NAME$SCRIPT_NAME/$name"; } if(false == $style) { $style = "rpc"; @@ -3980,7 +4385,7 @@ class soap_server extends nusoap_base { if ($faultdetail == '' && $this->debug_flag) { $faultdetail = $this->getDebug(); } - $this->fault = new soap_fault($faultcode,$faultactor,$faultstring,$faultdetail); + $this->fault = new nusoap_fault($faultcode,$faultactor,$faultstring,$faultdetail); $this->fault->soap_defencoding = $this->soap_defencoding; } @@ -4003,15 +4408,20 @@ class soap_server extends nusoap_base { $SERVER_NAME = $_SERVER['SERVER_NAME']; $SERVER_PORT = $_SERVER['SERVER_PORT']; $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME']; - $HTTPS = $_SERVER['HTTPS']; + $HTTPS = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : (isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off'); } elseif (isset($HTTP_SERVER_VARS)) { $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME']; $SERVER_PORT = $HTTP_SERVER_VARS['SERVER_PORT']; $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME']; - $HTTPS = $HTTP_SERVER_VARS['HTTPS']; + $HTTPS = isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off'; } else { $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available"); } + // If server name has port number attached then strip it (else port number gets duplicated in WSDL output) (occurred using lighttpd and FastCGI) + $colon = strpos($SERVER_NAME,":"); + if ($colon) { + $SERVER_NAME = substr($SERVER_NAME, 0, $colon); + } if ($SERVER_PORT == 80) { $SERVER_PORT = ''; } else { @@ -4043,7 +4453,10 @@ class soap_server extends nusoap_base { if ($schemaTargetNamespace != $namespace) { $this->wsdl->namespaces['types'] = $schemaTargetNamespace; } - $this->wsdl->schemas[$schemaTargetNamespace][0] = new xmlschema('', '', $this->wsdl->namespaces); + $this->wsdl->schemas[$schemaTargetNamespace][0] = new nusoap_xmlschema('', '', $this->wsdl->namespaces); + if ($style == 'document') { + $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaInfo['elementFormDefault'] = 'qualified'; + } $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaTargetNamespace = $schemaTargetNamespace; $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/soap/encoding/'][0] = array('location' => '', 'loaded' => true); $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/wsdl/'][0] = array('location' => '', 'loaded' => true); @@ -4059,17 +4472,23 @@ class soap_server extends nusoap_base { } } - +/** + * Backward compatibility + */ +class soap_server extends nusoap_server { +} ?> -* @version $Id: nusoap.php,v 1.94 2005/08/04 01:27:42 snichol Exp $ +* @author Scott Nichol +* @version $Id: nusoap.php,v 1.114 2007/11/06 15:17:46 snichol Exp $ * @access public */ class wsdl extends nusoap_base { @@ -4107,6 +4526,13 @@ class wsdl extends nusoap_base { var $proxypassword = ''; var $timeout = 0; var $response_timeout = 30; + var $curl_options = array(); // User-specified cURL options + var $use_curl = false; // whether to always try to use cURL + // for HTTP authentication + var $username = ''; // Username for HTTP authentication + var $password = ''; // Password for HTTP authentication + var $authtype = ''; // Type of HTTP authentication + var $certRequest = array(); // Certificate for HTTP SSL authentication /** * constructor @@ -4118,82 +4544,96 @@ class wsdl extends nusoap_base { * @param string $proxypassword * @param integer $timeout set the connection timeout * @param integer $response_timeout set the response timeout + * @param array $curl_options user-specified cURL options + * @param boolean $use_curl try to use cURL * @access public */ - function wsdl($wsdl = '',$proxyhost=false,$proxyport=false,$proxyusername=false,$proxypassword=false,$timeout=0,$response_timeout=30){ + function wsdl($wsdl = '',$proxyhost=false,$proxyport=false,$proxyusername=false,$proxypassword=false,$timeout=0,$response_timeout=30,$curl_options=null,$use_curl=false){ parent::nusoap_base(); - $this->wsdl = $wsdl; + $this->debug("ctor wsdl=$wsdl timeout=$timeout response_timeout=$response_timeout"); $this->proxyhost = $proxyhost; $this->proxyport = $proxyport; $this->proxyusername = $proxyusername; $this->proxypassword = $proxypassword; $this->timeout = $timeout; $this->response_timeout = $response_timeout; - + if (is_array($curl_options)) + $this->curl_options = $curl_options; + $this->use_curl = $use_curl; + $this->fetchWSDL($wsdl); + } + + /** + * fetches the WSDL document and parses it + * + * @access public + */ + function fetchWSDL($wsdl) { + $this->debug("parse and process WSDL path=$wsdl"); + $this->wsdl = $wsdl; // parse wsdl file - if ($wsdl != "") { - $this->debug('initial wsdl URL: ' . $wsdl); - $this->parseWSDL($wsdl); + if ($this->wsdl != "") { + $this->parseWSDL($this->wsdl); } // imports // TODO: handle imports more properly, grabbing them in-line and nesting them - $imported_urls = array(); - $imported = 1; - while ($imported > 0) { - $imported = 0; - // Schema imports - foreach ($this->schemas as $ns => $list) { - foreach ($list as $xs) { - $wsdlparts = parse_url($this->wsdl); // this is bogusly simple! - foreach ($xs->imports as $ns2 => $list2) { - for ($ii = 0; $ii < count($list2); $ii++) { - if (! $list2[$ii]['loaded']) { - $this->schemas[$ns]->imports[$ns2][$ii]['loaded'] = true; - $url = $list2[$ii]['location']; - if ($url != '') { - $urlparts = parse_url($url); - if (!isset($urlparts['host'])) { - $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' .$wsdlparts['port'] : '') . - substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path']; - } - if (! in_array($url, $imported_urls)) { - $this->parseWSDL($url); - $imported++; - $imported_urls[] = $url; - } - } else { - $this->debug("Unexpected scenario: empty URL for unloaded import"); + $imported_urls = array(); + $imported = 1; + while ($imported > 0) { + $imported = 0; + // Schema imports + foreach ($this->schemas as $ns => $list) { + foreach ($list as $xs) { + $wsdlparts = parse_url($this->wsdl); // this is bogusly simple! + foreach ($xs->imports as $ns2 => $list2) { + for ($ii = 0; $ii < count($list2); $ii++) { + if (! $list2[$ii]['loaded']) { + $this->schemas[$ns]->imports[$ns2][$ii]['loaded'] = true; + $url = $list2[$ii]['location']; + if ($url != '') { + $urlparts = parse_url($url); + if (!isset($urlparts['host'])) { + $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' .$wsdlparts['port'] : '') . + substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path']; } + if (! in_array($url, $imported_urls)) { + $this->parseWSDL($url); + $imported++; + $imported_urls[] = $url; + } + } else { + $this->debug("Unexpected scenario: empty URL for unloaded import"); } } - } - } - } - // WSDL imports - $wsdlparts = parse_url($this->wsdl); // this is bogusly simple! - foreach ($this->import as $ns => $list) { - for ($ii = 0; $ii < count($list); $ii++) { - if (! $list[$ii]['loaded']) { - $this->import[$ns][$ii]['loaded'] = true; - $url = $list[$ii]['location']; - if ($url != '') { - $urlparts = parse_url($url); - if (!isset($urlparts['host'])) { - $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' . $wsdlparts['port'] : '') . - substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path']; - } - if (! in_array($url, $imported_urls)) { - $this->parseWSDL($url); - $imported++; - $imported_urls[] = $url; - } - } else { - $this->debug("Unexpected scenario: empty URL for unloaded import"); + } + } + } + } + // WSDL imports + $wsdlparts = parse_url($this->wsdl); // this is bogusly simple! + foreach ($this->import as $ns => $list) { + for ($ii = 0; $ii < count($list); $ii++) { + if (! $list[$ii]['loaded']) { + $this->import[$ns][$ii]['loaded'] = true; + $url = $list[$ii]['location']; + if ($url != '') { + $urlparts = parse_url($url); + if (!isset($urlparts['host'])) { + $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' . $wsdlparts['port'] : '') . + substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path']; } + if (! in_array($url, $imported_urls)) { + $this->parseWSDL($url); + $imported++; + $imported_urls[] = $url; + } + } else { + $this->debug("Unexpected scenario: empty URL for unloaded import"); } } - } - } + } + } + } // add new data to operation data foreach($this->bindings as $binding => $bindingData) { if (isset($bindingData['operations']) && is_array($bindingData['operations'])) { @@ -4213,7 +4653,8 @@ class wsdl extends nusoap_base { if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ])){ $this->bindings[$binding]['operations'][$operation]['output']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ]; } - if (isset($bindingData['style'])) { + // Set operation style if necessary, but do not override one already provided + if (isset($bindingData['style']) && !isset($this->bindings[$binding]['operations'][$operation]['style'])) { $this->bindings[$binding]['operations'][$operation]['style'] = $bindingData['style']; } $this->bindings[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ? $bindingData['transport'] : ''; @@ -4222,7 +4663,7 @@ class wsdl extends nusoap_base { } } } - } + } /** * parses the wsdl document @@ -4230,8 +4671,9 @@ class wsdl extends nusoap_base { * @param string $wsdl path or URL * @access private */ - function parseWSDL($wsdl = '') - { + function parseWSDL($wsdl = '') { + $this->debug("parse WSDL at path=$wsdl"); + if ($wsdl == '') { $this->debug('no wsdl passed to parseWSDL()!!'); $this->setError('no wsdl passed to parseWSDL()!!'); @@ -4244,12 +4686,15 @@ class wsdl extends nusoap_base { if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'http' || $wsdl_props['scheme'] == 'https')) { $this->debug('getting WSDL http(s) URL ' . $wsdl); // get wsdl - $tr = new soap_transport_http($wsdl); + $tr = new soap_transport_http($wsdl, $this->curl_options, $this->use_curl); $tr->request_method = 'GET'; $tr->useSOAPAction = false; if($this->proxyhost && $this->proxyport){ $tr->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword); } + if ($this->authtype != '') { + $tr->setCredentials($this->username, $this->password, $this->authtype, array(), $this->certRequest); + } $tr->setEncoding('gzip, deflate'); $wsdl_string = $tr->send('', $this->timeout, $this->response_timeout); //$this->debug("WSDL request\n" . $tr->outgoing_payload); @@ -4340,7 +4785,7 @@ class wsdl extends nusoap_base { $this->debug('Parsing WSDL schema'); // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")"); $this->status = 'schema'; - $this->currentSchema = new xmlschema('', '', $this->namespaces); + $this->currentSchema = new nusoap_xmlschema('', '', $this->namespaces); $this->currentSchema->schemaStartElement($parser, $name, $attrs); $this->appendDebug($this->currentSchema->getDebug()); $this->currentSchema->clearDebug(); @@ -4394,12 +4839,12 @@ class wsdl extends nusoap_base { case 'message': if ($name == 'part') { if (isset($attrs['type'])) { - $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs)); + $this->debug("msg " . $this->currentMessage . ": found part (with type) $attrs[name]: " . implode(',', $attrs)); $this->messages[$this->currentMessage][$attrs['name']] = $attrs['type']; } if (isset($attrs['element'])) { - $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs)); - $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element']; + $this->debug("msg " . $this->currentMessage . ": found part (with element) $attrs[name]: " . implode(',', $attrs)); + $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element'] . '^'; } } break; @@ -4584,6 +5029,24 @@ class wsdl extends nusoap_base { } } + /** + * if authenticating, set user credentials here + * + * @param string $username + * @param string $password + * @param string $authtype (basic|digest|certificate|ntlm) + * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs) + * @access public + */ + function setCredentials($username, $password, $authtype = 'basic', $certRequest = array()) { + $this->debug("setCredentials username=$username authtype=$authtype certRequest="); + $this->appendDebug($this->varDump($certRequest)); + $this->username = $username; + $this->password = $password; + $this->authtype = $authtype; + $this->certRequest = $certRequest; + } + function getBindingData($binding) { if (is_array($this->bindings[$binding])) { @@ -4594,15 +5057,16 @@ class wsdl extends nusoap_base { /** * returns an assoc array of operation names => operation data * - * @param string $bindingType eg: soap, smtp, dime (only soap is currently supported) + * @param string $bindingType eg: soap, smtp, dime (only soap and soap12 are currently supported) * @return array * @access public */ - function getOperations($bindingType = 'soap') - { + function getOperations($bindingType = 'soap') { $ops = array(); if ($bindingType == 'soap') { $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/'; + } elseif ($bindingType == 'soap12') { + $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap12/'; } // loop thru ports foreach($this->ports as $port => $portData) { @@ -4623,8 +5087,8 @@ class wsdl extends nusoap_base { /** * returns an associative array of data necessary for calling an operation * - * @param string $operation , name of operation - * @param string $bindingType , type of binding eg: soap + * @param string $operation name of operation + * @param string $bindingType type of binding eg: soap, soap12 * @return array * @access public */ @@ -4632,6 +5096,8 @@ class wsdl extends nusoap_base { { if ($bindingType == 'soap') { $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/'; + } elseif ($bindingType == 'soap12') { + $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap12/'; } // loop thru ports foreach($this->ports as $port => $portData) { @@ -4654,13 +5120,15 @@ class wsdl extends nusoap_base { * returns an associative array of data necessary for calling an operation * * @param string $soapAction soapAction for operation - * @param string $bindingType type of binding eg: soap + * @param string $bindingType type of binding eg: soap, soap12 * @return array * @access public */ function getOperationDataForSoapAction($soapAction, $bindingType = 'soap') { if ($bindingType == 'soap') { $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/'; + } elseif ($bindingType == 'soap12') { + $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap12/'; } // loop thru ports foreach($this->ports as $port => $portData) { @@ -4688,11 +5156,11 @@ class wsdl extends nusoap_base { * 'attrs' => array() // refs to attributes array * ) * - * @param $type string the type - * @param $ns string namespace (not prefix) of the type + * @param string $type the type + * @param string $ns namespace (not prefix) of the type * @return mixed * @access public - * @see xmlschema + * @see nusoap_xmlschema */ function getTypeDef($type, $ns) { $this->debug("in getTypeDef: type=$type, ns=$ns"); @@ -4700,13 +5168,22 @@ class wsdl extends nusoap_base { $ns = $this->namespaces['tns']; $this->debug("in getTypeDef: type namespace forced to $ns"); } + if (!isset($this->schemas[$ns])) { + foreach ($this->schemas as $ns0 => $schema0) { + if (strcasecmp($ns, $ns0) == 0) { + $this->debug("in getTypeDef: replacing schema namespace $ns with $ns0"); + $ns = $ns0; + break; + } + } + } if (isset($this->schemas[$ns])) { $this->debug("in getTypeDef: have schema for namespace $ns"); for ($i = 0; $i < count($this->schemas[$ns]); $i++) { $xs = &$this->schemas[$ns][$i]; $t = $xs->getTypeDef($type); - $this->appendDebug($xs->getDebug()); - $xs->clearDebug(); + //$this->appendDebug($xs->getDebug()); + //$xs->clearDebug(); if ($t) { if (!isset($t['phpType'])) { // get info for type to tack onto the element @@ -4753,7 +5230,7 @@ class wsdl extends nusoap_base { } $b = ' - xxvSOAP: '.$this->serviceName.' + NuSOAP: '.$this->serviceName.'