patch for vigercrm nusoap: -> 0.69

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

patch for vigercrm nusoap: -> 0.69

Michael Davey
Hi,

The following patch upgrades the version of nusoap library from 0.67 to
0.69.

We have been using nusoap 0.69 with recent versions of vtiger crm for
the last two months without problems - this should be a low-risk
change.  nusoap 0.69 offers better wsdl support and is self-documenting:
http://vtigerdemo/contactserialize.php

Patch created using 'cvs diff -u' in include/nusoap directory

--
Michael


? nusoap.patch
Index: class.nusoap_base.php
===================================================================
RCS file: /cvsroot/vtigercrm/vtiger_crm/include/nusoap/class.nusoap_base.php,v
retrieving revision 1.2
diff -u -r1.2 class.nusoap_base.php
--- class.nusoap_base.php 8 Sep 2004 14:20:17 -0000 1.2
+++ class.nusoap_base.php 2 Oct 2005 13:23:38 -0000
@@ -1,7 +1,7 @@
 <?php
 
 /*
-$Id: class.nusoap_base.php,v 1.1 2004/08/17 13:27:46 gjayakrishnan Exp $
+$Id: class.nusoap_base.php,v 1.2 2005/09/27 13:44:38 michael_davey Exp $
 
 NuSOAP - Web Services Toolkit for PHP
 
@@ -55,15 +55,15 @@
 * nusoap_base
 *
 * @author   Dietrich Ayala <[hidden email]>
-* @version  $Id: class.nusoap_base.php,v 1.1 2004/08/17 13:27:46 gjayakrishnan Exp $
+* @version  $Id: class.nusoap_base.php,v 1.2 2005/09/27 13:44:38 michael_davey Exp $
 * @access   public
 */
 class nusoap_base {
 
  var $title = 'NuSOAP';
- var $version = '0.6.7';
- var $revision = '$Revision: 1.1 $';
- var $error_str = false;
+ var $version = '0.6.9';
+ var $revision = '$Revision: 1.2 $';
+ var $error_str = '';
     var $debug_str = '';
  // toggles automatic encoding of special characters as entities
  // (should always be true, I think)
@@ -112,11 +112,17 @@
  'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
  'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
  'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string',
+ // abstract "any" types
+ 'anyType'=>'string','anySimpleType'=>'string',
  // derived datatypes
  'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'',
  'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer',
  'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer',
  'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''),
+ 'http://www.w3.org/2000/10/XMLSchema' => array(
+ 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
+ 'float'=>'double','dateTime'=>'string',
+ 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
  'http://www.w3.org/1999/XMLSchema' => array(
  'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
  'float'=>'double','dateTime'=>'string',
@@ -136,13 +142,64 @@
  'lt' => '<','gt' => '>','apos' => "'");
 
  /**
- * adds debug data to the class level debug string
+ * adds debug data to the instance debug string with formatting
  *
  * @param    string $string debug data
  * @access   private
  */
  function debug($string){
- $this->debug_str .= get_class($this).": $string\n";
+ $this->appendDebug($this->getmicrotime().' '.get_class($this).": $string\n");
+ }
+
+ /**
+ * adds debug data to the instance debug string without formatting
+ *
+ * @param    string $string debug data
+ * @access   private
+ */
+ function appendDebug($string){
+ // it would be nice to use a memory stream here to use
+ // memory more efficiently
+ $this->debug_str .= $string;
+ }
+
+ /**
+ * clears the current debug data for this instance
+ *
+ * @access   public
+ */
+ function clearDebug() {
+ // it would be nice to use a memory stream here to use
+ // memory more efficiently
+ $this->debug_str = '';
+ }
+
+ /**
+ * gets the current debug data for this instance
+ *
+ * @return   debug data
+ * @access   public
+ */
+ function &getDebug() {
+ // it would be nice to use a memory stream here to use
+ // memory more efficiently
+ return $this->debug_str;
+ }
+
+ /**
+ * gets the current debug data for this instance as an XML comment
+ * this may change the contents of the debug data
+ *
+ * @return   debug data as an XML comment
+ * @access   public
+ */
+ function &getDebugAsXMLComment() {
+ // it would be nice to use a memory stream here to use
+ // memory more efficiently
+ while (strpos($this->debug_str, '--')) {
+ $this->debug_str = str_replace('--', '- -', $this->debug_str);
+ }
+     return "<!--\n" . $this->debug_str . "\n-->";
  }
 
  /**
@@ -165,7 +222,7 @@
  /**
  * returns error string if present
  *
- * @return   boolean $string error string
+ * @return   mixed error string or false
  * @access   public
  */
  function getError(){
@@ -239,6 +296,15 @@
  $atts .= " $k=\"$v\"";
  }
  }
+ // serialize null value
+ if (is_null($val)) {
+ if ($use == 'literal') {
+ // TODO: depends on nillable
+         return "<$name$xmlns $atts/>";
+         } else {
+         return "<$name$xmlns $atts xsi:nil=\"true\"/>";
+         }
+ }
         // serialize if an xsd built-in primitive type
         if($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){
          if (is_bool($val)) {
@@ -251,22 +317,14 @@
  $val = $this->expandEntities($val);
  }
  if ($use == 'literal') {
-         return "<$name$xmlns>$val</$name>";
+         return "<$name$xmlns $atts>$val</$name>";
          } else {
-         return "<$name$xmlns xsi:type=\"xsd:$type\">$val</$name>";
+         return "<$name$xmlns $atts xsi:type=\"xsd:$type\">$val</$name>";
          }
         }
  // detect type and serialize
  $xml = '';
  switch(true) {
- case ($type == '' && is_null($val)):
- if ($use == 'literal') {
- // TODO: depends on nillable
- $xml .= "<$name$xmlns/>";
- } else {
- $xml .= "<$name$xmlns xsi:nil=\"true\"/>";
- }
- break;
  case (is_bool($val) || $type == 'boolean'):
          if ($type == 'boolean') {
          $val = $val ? 'true' : 'false';
@@ -564,6 +622,24 @@
  return false;
  }
 
+ /**
+    * returns the time in ODBC canonical form with microseconds
+    *
+    * @return string
+    * @access public
+    */
+ function getmicrotime() {
+ if (function_exists('gettimeofday')) {
+ $tod = gettimeofday();
+ $sec = $tod['sec'];
+ $usec = $tod['usec'];
+ } else {
+ $sec = time();
+ $usec = 0;
+ }
+ return strftime('%Y-%m-%d %H:%M:%S', $sec) . '.' . sprintf('%06d', $usec);
+ }
+
     function varDump($data) {
  ob_start();
  var_dump($data);
Index: class.soap_parser.php
===================================================================
RCS file: /cvsroot/vtigercrm/vtiger_crm/include/nusoap/class.soap_parser.php,v
retrieving revision 1.2
diff -u -r1.2 class.soap_parser.php
--- class.soap_parser.php 8 Sep 2004 14:20:17 -0000 1.2
+++ class.soap_parser.php 2 Oct 2005 13:23:39 -0000
@@ -8,7 +8,7 @@
 * soap_parser class parses SOAP XML messages into native PHP values
 *
 * @author   Dietrich Ayala <[hidden email]>
-* @version  $Id: class.soap_parser.php,v 1.1 2004/08/17 13:27:46 gjayakrishnan Exp $
+* @version  $Id: class.soap_parser.php,v 1.2 2005/09/27 13:44:38 michael_davey Exp $
 * @access   public
 */
 class soap_parser extends nusoap_base {
@@ -63,6 +63,29 @@
 
  // Check whether content has been read.
  if(!empty($xml)){
+ // Check XML encoding
+ $pos_xml = strpos($xml, '<?xml');
+ if ($pos_xml !== FALSE) {
+ $xml_decl = substr($xml, $pos_xml, strpos($xml, '?>', $pos_xml + 2) - $pos_xml + 1);
+ if (preg_match("/encoding=[\"']([^\"']*)[\"']/", $xml_decl, $res)) {
+ $xml_encoding = $res[1];
+ if (strtoupper($xml_encoding) != $encoding) {
+ $err = "Charset from HTTP Content-Type '" . $encoding . "' does not match encoding from XML declaration '" . $xml_encoding . "'";
+ $this->debug($err);
+ if ($encoding != 'ISO-8859-1' || strtoupper($xml_encoding) != 'UTF-8') {
+ $this->setError($err);
+ return;
+ }
+ // when HTTP says ISO-8859-1 (the default) and XML says UTF-8 (the typical), assume the other endpoint is just sloppy and proceed
+ } else {
+ $this->debug('Charset from HTTP Content-Type matches encoding from XML declaration');
+ }
+ } else {
+ $this->debug('No encoding specified in XML declaration');
+ }
+ } else {
+ $this->debug('No XML declaration');
+ }
  $this->debug('Entering soap_parser(), length='.strlen($xml).', encoding='.$encoding);
  // Create an XML parser - why not xml_parser_create_ns?
  $this->parser = xml_parser_create($this->xml_encoding);
@@ -98,6 +121,7 @@
  foreach($this->multirefs as $id => $hrefs){
  $this->debug('resolving multirefs for id: '.$id);
  $idVal = $this->buildVal($this->ids[$id]);
+ unset($idVal['!id']);
  foreach($hrefs as $refPos => $ref){
  $this->debug('resolving href at pos '.$refPos);
  $this->multirefs[$id][$refPos] = $idVal;
@@ -223,6 +247,16 @@
  $this->message[$pos]['arraySize'] = $regs[3];
  $this->message[$pos]['arrayCols'] = $regs[4];
  }
+ // specifies nil value (or not)
+ } elseif ($key_localpart == 'nil'){
+ $this->message[$pos]['nil'] = ($value == 'true' || $value == '1');
+ // some other attribute
+ } elseif ($key != 'href' && $key != 'xmlns' && $key_localpart != 'encodingStyle' && $key_localpart != 'root') {
+ $this->message[$pos]['xattrs']['!' . $key] = $value;
+ }
+
+ if ($key == 'xmlns') {
+ $this->default_namespace = $value;
  }
  // log id
  if($key == 'id'){
@@ -283,18 +317,35 @@
  $this->multirefs[$id][$pos] = 'placeholder';
  // add set a reference to it as the result value
  $this->message[$pos]['result'] =& $this->multirefs[$id][$pos];
-            // build complex values
+            // build complexType values
  } elseif($this->message[$pos]['children'] != ''){
-
- // if result has already been generated (struct/array
+ // if result has already been generated (struct/array)
  if(!isset($this->message[$pos]['result'])){
  $this->message[$pos]['result'] = $this->buildVal($pos);
  }
-
- // set value of simple type
+ // build complexType values of attributes and possibly simpleContent
+ } elseif (isset($this->message[$pos]['xattrs'])) {
+ if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) {
+ $this->message[$pos]['xattrs']['!'] = null;
+ } elseif (isset($this->message[$pos]['cdata']) && $this->message[$pos]['cdata'] != '') {
+             if (isset($this->message[$pos]['type'])) {
+ $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
+ } else {
+ $parent = $this->message[$pos]['parent'];
+ if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
+ $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
+ } else {
+ $this->message[$pos]['xattrs']['!'] = $this->message[$pos]['cdata'];
+ }
+ }
+ }
+ $this->message[$pos]['result'] = $this->message[$pos]['xattrs'];
+ // set value of simpleType (or nil complexType)
  } else {
              //$this->debug('adding data for scalar value '.$this->message[$pos]['name'].' of value '.$this->message[$pos]['cdata']);
-             if (isset($this->message[$pos]['type'])) {
+ if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) {
+ $this->message[$pos]['xattrs']['!'] = null;
+ } elseif (isset($this->message[$pos]['type'])) {
  $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
  } else {
  $parent = $this->message[$pos]['parent'];
@@ -486,23 +537,16 @@
  if ($this->message[$pos]['type'] == 'Vector' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap') {
  $notstruct = 1;
  } else {
-             // is array or struct?
-             foreach($children as $child_pos){
-             if(isset($keys) && isset($keys[$this->message[$child_pos]['name']])){
-             $notstruct = 1;
-             break;
-             }
-             $keys[$this->message[$child_pos]['name']] = 1;
-             }
+ $notstruct = 0;
             }
              //
              foreach($children as $child_pos){
-             if(isset($notstruct)){
+             if($notstruct){
              $params[] = &$this->message[$child_pos]['result'];
              } else {
              if (isset($params[$this->message[$child_pos]['name']])) {
              // de-serialize repeated element name into an array
-             if (!is_array($params[$this->message[$child_pos]['name']])) {
+             if ((!is_array($params[$this->message[$child_pos]['name']])) || (!isset($params[$this->message[$child_pos]['name']][0]))) {
              $params[$this->message[$child_pos]['name']] = array($params[$this->message[$child_pos]['name']]);
              }
              $params[$this->message[$child_pos]['name']][] = &$this->message[$child_pos]['result'];
@@ -512,6 +556,11 @@
                  }
                 }
  }
+ if (isset($this->message[$pos]['xattrs'])) {
+ foreach ($this->message[$pos]['xattrs'] as $n => $v) {
+ $params[$n] = $v;
+ }
+ }
  return is_array($params) ? $params : array();
  } else {
          $this->debug('no children');
Index: class.soap_server.php
===================================================================
RCS file: /cvsroot/vtigercrm/vtiger_crm/include/nusoap/class.soap_server.php,v
retrieving revision 1.2
diff -u -r1.2 class.soap_server.php
--- class.soap_server.php 8 Sep 2004 14:20:17 -0000 1.2
+++ class.soap_server.php 2 Oct 2005 13:23:40 -0000
@@ -11,20 +11,21 @@
 * NOTE: WSDL functionality is experimental
 *
 * @author   Dietrich Ayala <[hidden email]>
-* @version  $Id: class.soap_server.php,v 1.1 2004/08/17 13:27:46 gjayakrishnan Exp $
+* @version  $Id: class.soap_server.php,v 1.2 2005/09/27 13:44:38 michael_davey Exp $
 * @access   public
 */
 class soap_server extends nusoap_base {
  var $headers = array(); // HTTP headers of request
  var $request = ''; // HTTP request
- var $requestHeaders = ''; // SOAP headers from request (incomplete namespace resolution) (text)
- var $document = ''; // SOAP body request portion (incomplete namespace resolution) (text)
+ var $requestHeaders = ''; // SOAP headers from request (incomplete namespace resolution; special characters not escaped) (text)
+ var $document = ''; // SOAP body request portion (incomplete namespace resolution; special characters not escaped) (text)
  var $requestSOAP = ''; // SOAP payload for request (text)
  var $methodURI = ''; // requested method namespace URI
  var $methodname = ''; // name of method requested
  var $methodparams = array(); // method parameters from request
  var $xml_encoding = ''; // character set encoding of incoming (request) messages
  var $SOAPAction = ''; // SOAP Action from request
+    var $decode_utf8 = true; // toggles whether the parser decodes element content w/ utf8_decode()
 
  var $outgoing_headers = array();// HTTP headers of response
  var $response = ''; // HTTP response
@@ -39,7 +40,8 @@
  var $wsdl = false; // wsdl instance
  var $externalWSDLURL = false; // URL for WSDL
  var $debug_flag = false; // whether to append debug to response as XML comment
-
+
+
  /**
  * constructor
     * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to.
@@ -86,8 +88,8 @@
  $this->wsdl = new wsdl($wsdl);
  $this->externalWSDLURL = $wsdl;
  }
- $this->debug("wsdl...\n" . $this->wsdl->debug_str);
- $this->wsdl->debug_str = '';
+ $this->appendDebug($this->wsdl->getDebug());
+ $this->wsdl->clearDebug();
  if($err = $this->wsdl->getError()){
  die('WSDL ERROR: '.$err);
  }
@@ -102,6 +104,8 @@
  */
  function service($data){
  global $QUERY_STRING;
+ global $_SERVER;
+
  if(isset($_SERVER['QUERY_STRING'])){
  $qs = $_SERVER['QUERY_STRING'];
  } elseif(isset($GLOBALS['QUERY_STRING'])){
@@ -120,13 +124,16 @@
                 $fp = fopen($this->externalWSDLURL, 'r');
                 fpassthru($fp);
               }
- } else {
+ } elseif ($this->wsdl) {
  header("Content-Type: text/xml; charset=ISO-8859-1\r\n");
- print $this->wsdl->serialize();
+ print $this->wsdl->serialize($this->debug_flag);
+ } else {
+ header("Content-Type: text/html; charset=ISO-8859-1\r\n");
+ print "This service does not provide WSDL";
  }
- } elseif($data == '' && $this->wsdl){
+ } elseif ($data == '' && $this->wsdl) {
  // print web interface
- print $this->webDescription();
+ print $this->wsdl->webDescription();
  } else {
  // handle the request
  $this->parse_request($data);
@@ -168,7 +175,7 @@
  $this->SOAPAction = str_replace('"','',$this->headers['SOAPAction']);
  }
  // get the character encoding of the incoming request
- if(strpos($this->headers['Content-Type'],'=')){
+ if(isset($this->headers['Content-Type']) && strpos($this->headers['Content-Type'],'=')){
  $enc = str_replace('"','',substr(strstr($this->headers["Content-Type"],'='),1));
  if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
  $this->xml_encoding = strtoupper($enc);
@@ -176,8 +183,8 @@
  $this->xml_encoding = 'US-ASCII';
  }
  } else {
- // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
- $this->xml_encoding = 'UTF-8';
+ // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
+ $this->xml_encoding = 'ISO-8859-1';
  }
  } elseif(isset($_SERVER) && is_array($_SERVER)){
  foreach ($_SERVER as $k => $v) {
@@ -204,8 +211,8 @@
  $this->xml_encoding = 'US-ASCII';
  }
  } else {
- // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
- $this->xml_encoding = 'UTF-8';
+ // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
+ $this->xml_encoding = 'ISO-8859-1';
  }
  }
  $this->headers[$k] = $v;
@@ -234,8 +241,8 @@
  $this->xml_encoding = 'US-ASCII';
  }
  } else {
- // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
- $this->xml_encoding = 'UTF-8';
+ // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
+ $this->xml_encoding = 'ISO-8859-1';
  }
  }
  $this->headers[$k] = $v;
@@ -269,7 +276,7 @@
  * @access   private
  */
  function parse_request($data='') {
- $this->debug('entering parse_request() on '.date('H:i Y-m-d'));
+ $this->debug('entering parse_request()');
  $this->parse_http_headers();
  $this->debug('got character encoding: '.$this->xml_encoding);
  // uncompress if necessary
@@ -283,11 +290,11 @@
  } elseif ($this->headers['Content-Encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) {
  $data = $degzdata;
  } else {
- $this->fault('Server', 'Errors occurred when trying to decode the data');
+ $this->fault('Client', 'Errors occurred when trying to decode the data');
  return;
  }
  } else {
- $this->fault('Server', 'This Server does not support compressed data');
+ $this->fault('Client', 'This Server does not support compressed data');
  return;
  }
  }
@@ -295,19 +302,19 @@
  $this->request .= "\r\n".$data;
  $this->requestSOAP = $data;
  // parse response, get soap parser obj
- $parser = new soap_parser($data,$this->xml_encoding);
+ $parser = new soap_parser($data,$this->xml_encoding,'',$this->decode_utf8);
  // parser debug
- $this->debug("parser debug: \n".$parser->debug_str);
+ $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('Server',"error in msg parsing:\n".$err);
+ $this->fault('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('method name: '.$this->methodname);
+ $this->debug('methodname: '.$this->methodname.' methodURI: '.$this->methodURI);
  $this->debug('calling parser->get_response()');
  $this->methodparams = $parser->get_response();
  // get SOAP headers
@@ -315,7 +322,7 @@
             // add document for doclit support
             $this->document = $parser->document;
  }
- $this->debug('leaving parse_request() on '.date('H:i Y-m-d'));
+ $this->debug('leaving parse_request');
  }
 
  /**
@@ -336,22 +343,50 @@
  * @access   private
  */
  function invoke_method() {
- $this->debug('entering invoke_method');
+ $this->debug('entering invoke_method methodname: ' . $this->methodname . ' methodURI: ' . $this->methodURI);
+ // if a . is present in $this->methodname, we see if there is a class in scope,
+ // which could be referred to. We will also distinguish between two deliminators,
+ // to allow methods to be called a the class or an instance
+ $class = '';
+ $method = '';
+ if (strpos($this->methodname, '..') > 0) {
+ $delim = '..';
+ } else if (strpos($this->methodname, '.') > 0) {
+ $delim = '.';
+ } else {
+ $delim = '';
+ }
+
+ if (strlen($delim) > 0 && substr_count($this->methodname, $delim) == 1 &&
+ class_exists(substr($this->methodname, 0, strpos($this->methodname, $delim)))) {
+ // get the class and method name
+ $class = substr($this->methodname, 0, strpos($this->methodname, $delim));
+ $method = substr($this->methodname, strpos($this->methodname, $delim) + strlen($delim));
+ $this->debug("class: $class method: $method delim: $delim");
+ }
+
  // does method exist?
- if(!function_exists($this->methodname)){
- // "method not found" fault here
- $this->debug("method '$this->methodname' not found!");
+ if ($class == '' && !function_exists($this->methodname)) {
+ $this->debug("function '$this->methodname' not found!");
+ $this->result = 'fault: method not found';
+ $this->fault('Client',"method '$this->methodname' not defined in service");
+ return;
+ }
+ if ($class != '' && !in_array(strtolower($method), get_class_methods($class))) {
+ $this->debug("method '$this->methodname' not found in class '$class'!");
  $this->result = 'fault: method not found';
- $this->fault('Server',"method '$this->methodname' not defined in service");
+ $this->fault('Client',"method '$this->methodname' not defined in service");
  return;
  }
+
  if($this->wsdl){
  if(!$this->opData = $this->wsdl->getOperationData($this->methodname)){
  //if(
-     $this->fault('Server',"Operation '$this->methodname' is not defined in the WSDL for this service");
+ $this->fault('Client',"Operation '$this->methodname' is not defined in the WSDL for this service");
  return;
-    }
-    $this->debug('opData is ' . $this->varDump($this->opData));
+ }
+ $this->debug('opData:');
+ $this->appendDebug($this->varDump($this->opData));
  }
  $this->debug("method '$this->methodname' exists");
  // evaluate message, getting back parameters
@@ -361,34 +396,59 @@
  $this->debug('ERROR: request not verified against method signature');
  $this->result = 'fault: request failed validation against method signature';
  // return fault
- $this->fault('Server',"Operation '$this->methodname' not defined in service.");
+ $this->fault('Client',"Operation '$this->methodname' not defined in service.");
  return;
  }
 
  // if there are parameters to pass
-        $this->debug('params var dump '.$this->varDump($this->methodparams));
- if($this->methodparams){
- $this->debug("calling '$this->methodname' with params");
- if (! function_exists('call_user_func_array')) {
- $this->debug('calling method using eval()');
- $funcCall = $this->methodname.'(';
- foreach($this->methodparams as $param) {
+ $this->debug('params:');
+ $this->appendDebug($this->varDump($this->methodparams));
+ $this->debug("calling '$this->methodname'");
+ if (!function_exists('call_user_func_array')) {
+ if ($class == '') {
+ $this->debug('calling function using eval()');
+ $funcCall = "\$this->methodreturn = $this->methodname(";
+ } else {
+ if ($delim == '..') {
+ $this->debug('calling class method using eval()');
+ $funcCall = "\$this->methodreturn = ".$class."::".$method."(";
+ } else {
+ $this->debug('calling instance method using eval()');
+ // generate unique instance name
+ $instname = "\$inst_".time();
+ $funcCall = $instname." = new ".$class."(); ";
+ $funcCall .= "\$this->methodreturn = ".$instname."->".$method."(";
+ }
+ }
+ 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');
+ return;
+ }
  $funcCall .= "\"$param\",";
  }
- $funcCall = substr($funcCall, 0, -1).')';
- $this->debug('function call:<br>'.$funcCall);
- @eval("\$this->methodreturn = $funcCall;");
- } else {
- $this->debug('calling method using call_user_func_array()');
- $this->methodreturn = call_user_func_array("$this->methodname",$this->methodparams);
+ $funcCall = substr($funcCall, 0, -1);
  }
+ $funcCall .= ');';
+ $this->debug('function call: '.$funcCall);
+ @eval($funcCall);
  } else {
- // call method w/ no parameters
- $this->debug("calling $this->methodname w/ no params");
- $m = $this->methodname;
- $this->methodreturn = @$m();
+ if ($class == '') {
+ $this->debug('calling function using call_user_func_array()');
+ $call_arg = "$this->methodname"; // straight assignment changes $this->methodname to lower case after call_user_func_array()
+ } elseif ($delim == '..') {
+ $this->debug('calling class method using call_user_func_array()');
+ $call_arg = array ($class, $method);
+ } else {
+ $this->debug('calling instance method using call_user_func_array()');
+ $instance = new $class ();
+ $call_arg = array(&$instance, $method);
+ }
+ $this->methodreturn = call_user_func_array($call_arg, $this->methodparams);
  }
-        $this->debug('methodreturn var dump'.$this->varDump($this->methodreturn));
+        $this->debug('methodreturn:');
+        $this->appendDebug($this->varDump($this->methodreturn));
  $this->debug("leaving invoke_method: called method $this->methodname, received $this->methodreturn of type ".gettype($this->methodreturn));
  }
 
@@ -404,57 +464,68 @@
  * @access   private
  */
  function serialize_return() {
- $this->debug("Entering serialize_return");
- // if we got nothing back. this might be ok (echoVoid)
- if(isset($this->methodreturn) && ($this->methodreturn != '' || is_bool($this->methodreturn))) {
- // if fault
- if(get_class($this->methodreturn) == 'soap_fault'){
- $this->debug('got a fault object from method');
- $this->fault = $this->methodreturn;
- return;
- } elseif ($this->methodreturnisliteralxml) {
- $return_val = $this->methodreturn;
- // returned value(s)
+ $this->debug('Entering serialize_return methodname: ' . $this->methodname . ' methodURI: ' . $this->methodURI);
+ // if fault
+ if (isset($this->methodreturn) && (get_class($this->methodreturn) == 'soap_fault')) {
+ $this->debug('got a fault object from method');
+ $this->fault = $this->methodreturn;
+ return;
+ } elseif ($this->methodreturnisliteralxml) {
+ $return_val = $this->methodreturn;
+ // returned value(s)
+ } else {
+ $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){
+     $opParams = $this->methodreturn;
+    } else {
+     // TODO: is this really necessary?
+     $opParams = array($this->methodreturn);
+    }
+    $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams);
+    $this->appendDebug($this->wsdl->getDebug());
+    $this->wsdl->clearDebug();
+ if($errstr = $this->wsdl->getError()){
+ $this->debug('got wsdl error: '.$errstr);
+ $this->fault('Server', 'unable to serialize result');
+ return;
+ }
  } else {
- $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){
-     $opParams = $this->methodreturn;
-    } else {
-     // TODO: is this really necessary?
-     $opParams = array($this->methodreturn);
-    }
-    $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams);
- if($errstr = $this->wsdl->getError()){
- $this->debug('got wsdl error: '.$errstr);
- $this->fault('Server', 'got wsdl error: '.$errstr);
- return;
- }
+ if (isset($this->methodreturn)) {
+ $return_val = $this->serialize_val($this->methodreturn, 'return');
  } else {
-    $return_val = $this->serialize_val($this->methodreturn, 'return');
+ $return_val = '';
+ $this->debug('in absence of WSDL, assume void return for backward compatibility');
  }
  }
- $this->debug('return val: '.$this->varDump($return_val));
- } else {
- $return_val = '';
- $this->debug('got no response from method');
  }
+ $this->debug('return value:');
+ $this->appendDebug($this->varDump($return_val));
+
  $this->debug('serializing response');
  if ($this->wsdl) {
+ $this->debug('have WSDL for serialization: style is ' . $this->opData['style']);
  if ($this->opData['style'] == 'rpc') {
- $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
+ $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.'</'.$this->methodname."Response>";
+ } else {
+ $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
+ }
  } else {
+ $this->debug('style is not rpc for serialization: assume document');
  $payload = $return_val;
  }
  } else {
+ $this->debug('do not have WSDL for serialization: assume rpc/encoded');
  $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
  }
  $this->result = 'successful';
  if($this->wsdl){
  //if($this->debug_flag){
-             $this->debug("WSDL debug data:\n".$this->wsdl->debug_str);
+             $this->appendDebug($this->wsdl->getDebug());
             // }
  // 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']);
@@ -490,10 +561,7 @@
  }
         // add debug data if in debug mode
  if(isset($this->debug_flag) && $this->debug_flag){
- while (strpos($this->debug_str, '--')) {
- $this->debug_str = str_replace('--', '- -', $this->debug_str);
- }
-         $payload .= "<!--\n" . $this->debug_str . "\n-->";
+         $payload .= $this->getDebugAsXMLComment();
         }
  $this->outgoing_headers[] = "Server: $this->title Server v$this->version";
  ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
@@ -502,29 +570,34 @@
  //$this->outgoing_headers[] = "Connection: Close\r\n";
  $this->outgoing_headers[] = "Content-Type: text/xml; charset=$this->soap_defencoding";
  //begin code to compress payload - by John
+ // NOTE: there is no way to know whether the Web server will also compress
+ // this data.
  if (strlen($payload) > 1024 && isset($this->headers) && isset($this->headers['Accept-Encoding'])) {
-   if (strstr($this->headers['Accept-Encoding'], 'deflate')) {
- if (function_exists('gzcompress')) {
+ if (strstr($this->headers['Accept-Encoding'], 'gzip')) {
+ if (function_exists('gzencode')) {
  if (isset($this->debug_flag) && $this->debug_flag) {
- $payload .= "<!-- Content being deflated -->";
+ $payload .= "<!-- Content being gzipped -->";
  }
- $this->outgoing_headers[] = "Content-Encoding: deflate";
- $payload = gzcompress($payload);
+ $this->outgoing_headers[] = "Content-Encoding: gzip";
+ $payload = gzencode($payload);
  } else {
  if (isset($this->debug_flag) && $this->debug_flag) {
- $payload .= "<!-- Content will not be deflated: no gzcompress -->";
+ $payload .= "<!-- Content will not be gzipped: no gzencode -->";
  }
  }
-   } else if (strstr($this->headers['Accept-Encoding'], 'gzip')) {
- if (function_exists('gzencode')) {
+ } elseif (strstr($this->headers['Accept-Encoding'], 'deflate')) {
+ // Note: MSIE requires gzdeflate output (no Zlib header and checksum),
+ // instead of gzcompress output,
+ // which conflicts with HTTP 1.1 spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5)
+ if (function_exists('gzdeflate')) {
  if (isset($this->debug_flag) && $this->debug_flag) {
- $payload .= "<!-- Content being gzipped -->";
+ $payload .= "<!-- Content being deflated -->";
  }
- $this->outgoing_headers[] = "Content-Encoding: gzip";
- $payload = gzencode($payload);
+ $this->outgoing_headers[] = "Content-Encoding: deflate";
+ $payload = gzdeflate($payload);
  } else {
  if (isset($this->debug_flag) && $this->debug_flag) {
- $payload .= "<!-- Content will not be gzipped: no gzencode -->";
+ $payload .= "<!-- Content will not be deflated: no gzcompress -->";
  }
  }
  }
@@ -535,8 +608,8 @@
  foreach($this->outgoing_headers as $hdr){
  header($hdr, false);
  }
- $this->response = join("\r\n",$this->outgoing_headers)."\r\n".$payload;
  print $payload;
+ $this->response = join("\r\n",$this->outgoing_headers)."\r\n\r\n".$payload;
  }
 
  /**
@@ -583,14 +656,10 @@
  * @param string $documentation optional Description to include in WSDL
  * @access   public
  */
- function register($name,$in=false,$out=false,$namespace=false,$soapaction=false,$style=false,$use=false,$documentation=''){
+ function register($name,$in=array(),$out=array(),$namespace=false,$soapaction=false,$style=false,$use=false,$documentation=''){
  if($this->externalWSDLURL){
  die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.');
  }
-    if(false == $in) {
- }
- if(false == $out) {
- }
  if(false == $namespace) {
  }
  if(false == $soapaction) {
@@ -629,130 +698,10 @@
  */
  function fault($faultcode,$faultstring,$faultactor='',$faultdetail=''){
  $this->fault = new soap_fault($faultcode,$faultactor,$faultstring,$faultdetail);
+ $this->fault->soap_defencoding = $this->soap_defencoding;
  }
 
     /**
-    * prints html description of services
-    *
-    * @access private
-    */
-    function webDescription(){
- $b = '
- <html><head><title>NuSOAP: '.$this->wsdl->serviceName.'</title>
- <style type="text/css">
-    body    { font-family: arial; color: #000000; background-color: #ffffff; margin: 0px 0px 0px 0px; }
-    p       { font-family: arial; color: #000000; margin-top: 0px; margin-bottom: 12px; }
-    pre { background-color: silver; padding: 5px; font-family: Courier New; font-size: x-small; color: #000000;}
-    ul      { margin-top: 10px; margin-left: 20px; }
-    li      { list-style-type: none; margin-top: 10px; color: #000000; }
-    .content{
- margin-left: 0px; padding-bottom: 2em; }
-    .nav {
- padding-top: 10px; padding-bottom: 10px; padding-left: 15px; font-size: .70em;
- margin-top: 10px; margin-left: 0px; color: #000000;
- background-color: #ccccff; width: 20%; margin-left: 20px; margin-top: 20px; }
-    .title {
- font-family: arial; font-size: 26px; color: #ffffff;
- background-color: #999999; width: 105%; margin-left: 0px;
- padding-top: 10px; padding-bottom: 10px; padding-left: 15px;}
-    .hidden {
- position: absolute; visibility: hidden; z-index: 200; left: 250px; top: 100px;
- font-family: arial; overflow: hidden; width: 600;
- padding: 20px; font-size: 10px; background-color: #999999;
- layer-background-color:#FFFFFF; }
-    a,a:active  { color: charcoal; font-weight: bold; }
-    a:visited   { color: #666666; font-weight: bold; }
-    a:hover     { color: cc3300; font-weight: bold; }
- </style>
- <script language="JavaScript" type="text/javascript">
- <!--
- // POP-UP CAPTIONS...
- function lib_bwcheck(){ //Browsercheck (needed)
-    this.ver=navigator.appVersion
-    this.agent=navigator.userAgent
-    this.dom=document.getElementById?1:0
-    this.opera5=this.agent.indexOf("Opera 5")>-1
-    this.ie5=(this.ver.indexOf("MSIE 5")>-1 && this.dom && !this.opera5)?1:0;
-    this.ie6=(this.ver.indexOf("MSIE 6")>-1 && this.dom && !this.opera5)?1:0;
-    this.ie4=(document.all && !this.dom && !this.opera5)?1:0;
-    this.ie=this.ie4||this.ie5||this.ie6
-    this.mac=this.agent.indexOf("Mac")>-1
-    this.ns6=(this.dom && parseInt(this.ver) >= 5) ?1:0;
-    this.ns4=(document.layers && !this.dom)?1:0;
-    this.bw=(this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera5)
-    return this
- }
- var bw = new lib_bwcheck()
- //Makes crossbrowser object.
- function makeObj(obj){
-    this.evnt=bw.dom? document.getElementById(obj):bw.ie4?document.all[obj]:bw.ns4?document.layers[obj]:0;
-    if(!this.evnt) return false
-    this.css=bw.dom||bw.ie4?this.evnt.style:bw.ns4?this.evnt:0;
-    this.wref=bw.dom||bw.ie4?this.evnt:bw.ns4?this.css.document:0;
-    this.writeIt=b_writeIt;
-    return this
- }
- // A unit of measure that will be added when setting the position of a layer.
- //var px = bw.ns4||window.opera?"":"px";
- function b_writeIt(text){
-    if (bw.ns4){this.wref.write(text);this.wref.close()}
-    else this.wref.innerHTML = text
- }
- //Shows the messages
- var oDesc;
- function popup(divid){
-    if(oDesc = new makeObj(divid)){
- oDesc.css.visibility = "visible"
-    }
- }
- function popout(){ // Hides message
-    if(oDesc) oDesc.css.visibility = "hidden"
- }
- //-->
- </script>
- </head>
- <body>
- <div class=content>
- <br><br>
- <div class=title>'.$this->wsdl->serviceName.'</div>
- <div class=nav>
- <p>View the <a href="'.(isset($GLOBALS['PHP_SELF']) ? $GLOBALS['PHP_SELF'] : $_SERVER['PHP_SELF']).'?wsdl">WSDL</a> for the service.
- Click on an operation name to view it&apos;s details.</p>
- <ul>';
- foreach($this->wsdl->getOperations() as $op => $data){
-    $b .= "<li><a href='#' onclick=\"popup('$op')\">$op</a></li>";
-    // create hidden div
-    $b .= "<div id='$op' class='hidden'>
-    <a href='#' onclick='popout()'><font color='#ffffff'>Close</font></a><br><br>";
-    foreach($data as $donnie => $marie){ // loop through opdata
- if($donnie == 'input' || $donnie == 'output'){ // show input/output data
-    $b .= "<font color='white'>".ucfirst($donnie).':</font><br>';
-    foreach($marie as $captain => $tenille){ // loop through data
- if($captain == 'parts'){ // loop thru parts
-    $b .= "&nbsp;&nbsp;$captain:<br>";
-                //if(is_array($tenille)){
-     foreach($tenille as $joanie => $chachi){
- $b .= "&nbsp;&nbsp;&nbsp;&nbsp;$joanie: $chachi<br>";
-     }
-         //}
- } else {
-    $b .= "&nbsp;&nbsp;$captain: $tenille<br>";
- }
-    }
- } else {
-    $b .= "<font color='white'>".ucfirst($donnie).":</font> $marie<br>";
- }
-    }
- $b .= '</div>';
- }
- $b .= '
- <ul>
- </div>
- </div></body></html>';
- return $b;
-    }
-
-    /**
     * sets up wsdl object
     * this acts as a flag to enable internal WSDL generation
     *
Index: class.soap_transport_http.php
===================================================================
RCS file: /cvsroot/vtigercrm/vtiger_crm/include/nusoap/class.soap_transport_http.php,v
retrieving revision 1.2
diff -u -r1.2 class.soap_transport_http.php
--- class.soap_transport_http.php 8 Sep 2004 14:20:17 -0000 1.2
+++ class.soap_transport_http.php 2 Oct 2005 13:23:41 -0000
@@ -8,13 +8,14 @@
 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
 *
 * @author   Dietrich Ayala <[hidden email]>
-* @version  $Id: class.soap_transport_http.php,v 1.1 2004/08/17 13:27:46 gjayakrishnan Exp $
+* @version  $Id: class.soap_transport_http.php,v 1.2 2005/09/27 13:44:38 michael_davey Exp $
 * @access public
 */
 class soap_transport_http extends nusoap_base {
 
  var $url = '';
  var $uri = '';
+ var $digest_uri = '';
  var $scheme = '';
  var $host = '';
  var $port = '';
@@ -24,20 +25,34 @@
  var $encoding = '';
  var $outgoing_headers = array();
  var $incoming_headers = array();
+ var $incoming_cookies = array();
  var $outgoing_payload = '';
  var $incoming_payload = '';
  var $useSOAPAction = true;
  var $persistentConnection = false;
  var $ch = false; // cURL handle
- var $username;
- var $password;
-
+ var $username = '';
+ var $password = '';
+ var $authtype = '';
+ var $digestRequest = array();
+ var $certRequest = array(); // keys must be cainfofile, sslcertfile, sslkeyfile, passphrase
+ // 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
+
  /**
  * constructor
  */
  function soap_transport_http($url){
+ $this->setURL($url);
+ ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
+ $this->outgoing_headers['User-Agent'] = $this->title.'/'.$this->version.' ('.$rev[1].')';
+ }
+
+ function setURL($url) {
  $this->url = $url;
-
+
  $u = parse_url($url);
  foreach($u as $k => $v){
  $this->debug("$k = $v");
@@ -59,10 +74,9 @@
  }
 
  $this->uri = $this->path;
+ $this->digest_uri = $this->uri;
 
  // build headers
- ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
- $this->outgoing_headers['User-Agent'] = $this->title.'/'.$this->version.' ('.$rev[1].')';
  if (!isset($u['port'])) {
  $this->outgoing_headers['Host'] = $this->host;
  } else {
@@ -70,7 +84,7 @@
  }
 
  if (isset($u['user']) && $u['user'] != '') {
- $this->setCredentials($u['user'], isset($u['pass']) ? $u['pass'] : '');
+ $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : '');
  }
  }
 
@@ -145,6 +159,8 @@
  // add path
  $hostURL .= $this->path;
  curl_setopt($this->ch, CURLOPT_URL, $hostURL);
+ // follow location headers (re-directs)
+ curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1);
  // ask for headers in the response output
  curl_setopt($this->ch, CURLOPT_HEADER, 1);
  // ask for the response output as the return value
@@ -174,14 +190,15 @@
  curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0);
  curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0);
 
- /*
- TODO: support client certificates (thanks Tobias Boes)
-        curl_setopt($this->ch, CURLOPT_CAINFO, '$pathToPemFiles/rootca.pem');
-        curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1);
-        curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1);
-        curl_setopt($this->ch, CURLOPT_SSLCERT, '$pathToPemFiles/mycert.pem');
-        curl_setopt($this->ch, CURLOPT_SSLKEY, '$pathToPemFiles/mykey.pem');
- */
+ // support client certificates (thanks Tobias Boes)
+ if ($this->authtype == 'certificate') {
+        curl_setopt($this->ch, CURLOPT_CAINFO, $certRequest['cainfofile']);
+        curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1);
+        curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1);
+        curl_setopt($this->ch, CURLOPT_SSLCERT, $certRequest['sslcertfile']);
+        curl_setopt($this->ch, CURLOPT_SSLKEY, $certRequest['sslkeyfile']);
+    curl_setopt($this->ch, CURLOPT_SSLKEYPASSWD , $certRequest['passphrase']);
+ }
  $this->debug('cURL connection set up');
  return true;
   } else {
@@ -197,10 +214,11 @@
  * @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
  */
- function send($data, $timeout=0, $response_timeout=30) {
+ function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) {
 
  $this->debug('entered send() with data of length: '.strlen($data));
 
@@ -215,7 +233,7 @@
  }
 
  // send request
- if (!$this->sendRequest($data)){
+ if (!$this->sendRequest($data, $cookies)){
  return false;
  }
 
@@ -236,11 +254,12 @@
  * @param    string $msg 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
  */
- function sendHTTPS($data, $timeout=0, $response_timeout=30) {
- return $this->send($data, $timeout, $response_timeout);
+ function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) {
+ return $this->send($data, $timeout, $response_timeout, $cookies);
  }
 
  /**
@@ -248,17 +267,18 @@
  *
  * @param    string $username
  * @param    string $password
- * @param string $authtype
- * @param array $digestRequest
+ * @param string $authtype (basic, digest, certificate)
+ * @param array $digestRequest (keys must be nonce, nc, realm, qop)
+ * @param array $certRequest (keys must be cainfofile, sslcertfile, sslkeyfile, passphrase: see corresponding options in cURL docs)
  * @access   public
  */
- function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array()) {
+ function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) {
  global $_SERVER;
 
  $this->debug("Set credentials for authtype $authtype");
  // cf. RFC 2617
  if ($authtype == 'basic') {
- $this->outgoing_headers['Authorization'] = 'Basic '.base64_encode($username.':'.$password);
+ $this->outgoing_headers['Authorization'] = 'Basic '.base64_encode(str_replace(':','',$username).':'.$password);
  } elseif ($authtype == 'digest') {
  if (isset($digestRequest['nonce'])) {
  $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1;
@@ -272,7 +292,7 @@
  $HA1 = md5($A1);
 
  // A2 = Method ":" digest-uri-value
- $A2 = 'POST:' . $this->uri;
+ $A2 = 'POST:' . $this->digest_uri;
 
  // H(A2)
  $HA2 =  md5($A2);
@@ -299,8 +319,10 @@
 
  $hashedDigest = md5($unhashedDigest);
 
- $this->outgoing_headers['Authorization'] = 'Digest username="' . $username . '", realm="' . $digestRequest['realm'] . '", nonce="' . $nonce . '", uri="' . $this->uri . '", cnonce="' . $cnonce . '", nc=' . sprintf("%08x", $digestRequest['nc']) . ', qop="' . $digestRequest['qop'] . '", response="' . $hashedDigest . '"';
+ $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 . '"';
  }
+ } elseif ($authtype == 'certificate') {
+ $this->certRequest = $certRequest;
  }
  $this->username = $username;
  $this->password = $password;
@@ -333,8 +355,10 @@
  function setEncoding($enc='gzip, deflate'){
  $this->protocol_version = '1.1';
  $this->outgoing_headers['Accept-Encoding'] = $enc;
- $this->outgoing_headers['Connection'] = 'close';
- $this->persistentConnection = false;
+ if (!isset($this->outgoing_headers['Connection'])) {
+ $this->outgoing_headers['Connection'] = 'close';
+ $this->persistentConnection = false;
+ }
  set_magic_quotes_runtime(0);
  // deprecated
  $this->encoding = $enc;
@@ -366,6 +390,7 @@
  * @param    string $lb
  * @returns string
  * @access   public
+ * @deprecated
  */
  function decodeChunked($buffer, $lb){
  // length := 0
@@ -419,18 +444,29 @@
  /*
  * Writes payload, including HTTP headers, to $this->outgoing_payload.
  */
- function buildPayload($data) {
+ function buildPayload($data, $cookie_str = '') {
  // add content-length header
  $this->outgoing_headers['Content-Length'] = strlen($data);
 
  // start building outgoing payload:
- $this->outgoing_payload = "$this->request_method $this->uri HTTP/$this->protocol_version\r\n";
+ $req = "$this->request_method $this->uri HTTP/$this->protocol_version";
+ $this->debug("HTTP request: $req");
+ $this->outgoing_payload = "$req\r\n";
 
  // loop thru headers, serializing
  foreach($this->outgoing_headers as $k => $v){
- $this->outgoing_payload .= $k.': '.$v."\r\n";
+ $hdr = $k.': '.$v;
+ $this->debug("HTTP header: $hdr");
+ $this->outgoing_payload .= "$hdr\r\n";
  }
-
+
+ // add any cookies
+ if ($cookie_str != '') {
+ $hdr = 'Cookie: '.$cookie_str;
+ $this->debug("HTTP header: $hdr");
+ $this->outgoing_payload .= "$hdr\r\n";
+ }
+
  // header/body separator
  $this->outgoing_payload .= "\r\n";
 
@@ -438,9 +474,12 @@
  $this->outgoing_payload .= $data;
  }
 
- function sendRequest($data){
+ function sendRequest($data, $cookies = NULL) {
+ // build cookie string
+ $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https')));
+
  // build payload
- $this->buildPayload($data);
+ $this->buildPayload($data, $cookie_str);
 
   if ($this->scheme == 'http' || $this->scheme == 'ssl') {
  // send payload
@@ -460,6 +499,9 @@
  foreach($this->outgoing_headers as $k => $v){
  $curl_headers[] = "$k: $v";
  }
+ if ($cookie_str != '') {
+ $curl_headers[] = 'Cookie: ' . $cookie_str;
+ }
  curl_setopt($this->ch, CURLOPT_HTTPHEADER, $curl_headers);
  if ($this->request_method == "POST") {
    curl_setopt($this->ch, CURLOPT_POST, 1);
@@ -523,30 +565,41 @@
  $header_data = trim(substr($data,0,$pos));
  $header_array = explode($lb,$header_data);
  $this->incoming_headers = array();
+ $this->incoming_cookies = array();
  foreach($header_array as $header_line){
  $arr = explode(':',$header_line, 2);
  if(count($arr) > 1){
  $header_name = strtolower(trim($arr[0]));
  $this->incoming_headers[$header_name] = trim($arr[1]);
+ if ($header_name == 'set-cookie') {
+ // TODO: allow multiple cookies from parseCookie
+ $cookie = $this->parseCookie(trim($arr[1]));
+ if ($cookie) {
+ $this->incoming_cookies[] = $cookie;
+ $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
+ } else {
+ $this->debug('did not find cookie in ' . trim($arr[1]));
+ }
+     }
  } else if (isset($header_name)) {
+ // append continuation line to previous header
  $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
  }
  }
 
  // loop until msg has been received
- if (isset($this->incoming_headers['content-length'])) {
+ if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') {
+ $content_length =  2147483647; // ignore any content-length header
+ $chunked = true;
+ $this->debug("want to read chunked content");
+ } elseif (isset($this->incoming_headers['content-length'])) {
  $content_length = $this->incoming_headers['content-length'];
  $chunked = false;
  $this->debug("want to read content of length $content_length");
  } else {
  $content_length =  2147483647;
- if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') {
- $chunked = true;
- $this->debug("want to read chunked content");
- } else {
- $chunked = false;
- $this->debug("want to read content to EOF");
- }
+ $chunked = false;
+ $this->debug("want to read content to EOF");
  }
  $data = '';
  do {
@@ -678,15 +731,42 @@
  // clean headers
  foreach ($header_array as $header_line) {
  $arr = explode(':',$header_line,2);
- if (count($arr) > 1) {
- $this->incoming_headers[strtolower(trim($arr[0]))] = trim($arr[1]);
+ if(count($arr) > 1){
+ $header_name = strtolower(trim($arr[0]));
+ $this->incoming_headers[$header_name] = trim($arr[1]);
+ if ($header_name == 'set-cookie') {
+ // TODO: allow multiple cookies from parseCookie
+ $cookie = $this->parseCookie(trim($arr[1]));
+ if ($cookie) {
+ $this->incoming_cookies[] = $cookie;
+ $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
+ } else {
+ $this->debug('did not find cookie in ' . trim($arr[1]));
+ }
+     }
+ } else if (isset($header_name)) {
+ // append continuation line to previous header
+ $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
  }
  }
   }
 
+ $arr = explode(' ', $header_array[0], 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']);
+ $this->setURL($this->incoming_headers['location']);
+ $this->tryagain = true;
+ return false;
+ }
+
  // see if we need to resend the request with http digest authentication
- if (isset($this->incoming_headers['www-authenticate']) && strstr($header_array[0], '401 Unauthorized')) {
- $this->debug('Got 401 Unauthorized with WWW-Authenticate: ' . $this->incoming_headers['www-authenticate']);
+ if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) {
+ $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']);
  if (substr("Digest ", $this->incoming_headers['www-authenticate'])) {
  $this->debug('Server wants digest authentication');
  // remove "Digest " from our elements
@@ -695,7 +775,7 @@
  // parse elements into array
  $digestElements = explode(',', $digestString);
  foreach ($digestElements as $val) {
- $tempElement = explode('=', trim($val));
+ $tempElement = explode('=', trim($val), 2);
  $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
  }
 
@@ -711,27 +791,72 @@
  return false;
  }
 
+ if (
+ ($http_status >= 300 && $http_status <= 307) ||
+ ($http_status >= 400 && $http_status <= 417) ||
+ ($http_status >= 501 && $http_status <= 505)
+   ) {
+ $this->setError("Unsupported HTTP response status $http_status $http_reason (soapclient->response has contents of the response)");
+ return false;
+ }
+
  // decode content-encoding
  if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){
  if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){
      // if decoding works, use it. else assume data wasn't gzencoded
-     if(function_exists('gzuncompress')){
+     if(function_exists('gzinflate')){
  //$timer->setMarker('starting decoding of gzip/deflated content');
- if($this->incoming_headers['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)){
-     $data = $degzdata;
- } elseif($this->incoming_headers['content-encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))){ // do our best
- $data = $degzdata;
- } else {
- $this->setError('Errors occurred when trying to decode the data');
+ // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress)
+ // this means there are no Zlib headers, although there should be
+ $this->debug('The gzinflate function exists');
+ $datalen = strlen($data);
+ if ($this->incoming_headers['content-encoding'] == 'deflate') {
+ if ($degzdata = @gzinflate($data)) {
+     $data = $degzdata;
+     $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes');
+     if (strlen($data) < $datalen) {
+     // test for the case that the payload has been compressed twice
+     $this->debug('The inflated payload is smaller than the gzipped one; try again');
+ if ($degzdata = @gzinflate($data)) {
+     $data = $degzdata;
+     $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes');
+ }
+     }
+     } else {
+     $this->debug('Error using gzinflate to inflate the payload');
+     $this->setError('Error using gzinflate to inflate the payload');
+     }
+ } elseif ($this->incoming_headers['content-encoding'] == 'gzip') {
+ if ($degzdata = @gzinflate(substr($data, 10))) { // do our best
+ $data = $degzdata;
+     $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes');
+     if (strlen($data) < $datalen) {
+     // test for the case that the payload has been compressed twice
+     $this->debug('The un-gzipped payload is smaller than the gzipped one; try again');
+ if ($degzdata = @gzinflate(substr($data, 10))) {
+     $data = $degzdata;
+     $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes');
+ }
+     }
+     } else {
+     $this->debug('Error using gzinflate to un-gzip the payload');
+ $this->setError('Error using gzinflate to un-gzip the payload');
+     }
  }
  //$timer->setMarker('finished decoding of gzip/deflated content');
  //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
  // set decoded payload
  $this->incoming_payload = $header_data.$lb.$lb.$data;
      } else {
- $this->setError('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
+ $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
+ $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
  }
+ } else {
+ $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
+ $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
  }
+ } else {
+ $this->debug('No Content-Encoding header');
  }
 
  if(strlen($data) == 0){
@@ -756,6 +881,118 @@
  $this->outgoing_headers['Connection'] = 'Keep-Alive';
  return true;
  }
+
+ /**
+ * parse an incoming Cookie into it's parts
+ *
+ * @param string $cookie_str content of cookie
+ * @return array with data of that cookie
+ * @access private
+ *
+ * TODO: allow a Set-Cookie string to be parsed into multiple cookies
+ */
+ function parseCookie($cookie_str) {
+ $cookie_str = str_replace('; ', ';', $cookie_str) . ';';
+ $data = split(';', $cookie_str);
+ $value_str = $data[0];
+
+ $cookie_param = 'domain=';
+ $start = strpos($cookie_str, $cookie_param);
+ if ($start > 0) {
+ $domain = substr($cookie_str, $start + strlen($cookie_param));
+ $domain = substr($domain, 0, strpos($domain, ';'));
+ } else {
+ $domain = '';
+ }
+
+ $cookie_param = 'expires=';
+ $start = strpos($cookie_str, $cookie_param);
+ if ($start > 0) {
+ $expires = substr($cookie_str, $start + strlen($cookie_param));
+ $expires = substr($expires, 0, strpos($expires, ';'));
+ } else {
+ $expires = '';
+ }
+
+ $cookie_param = 'path=';
+ $start = strpos($cooki