]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/nusoap/class.soap_transport_http.php
Release 6.5.0
[Github/sugarcrm.git] / include / nusoap / class.soap_transport_http.php
1 <?php
2
3 /*
4
5 Modification information for LGPL compliance
6
7 r57813 - 2010-08-19 10:34:44 -0700 (Thu, 19 Aug 2010) - kjing - Author: John Mertic <jmertic@sugarcrm.com>
8     Bug 39085 - When loading the opposite search panel via ajax on the ListViews, call the index action instead of the ListView action to avoid touching pre-MVC code by accident.
9
10 r56990 - 2010-06-16 13:05:36 -0700 (Wed, 16 Jun 2010) - kjing - snapshot "Mango" svn branch to a new one for GitHub sync
11
12 r56989 - 2010-06-16 13:01:33 -0700 (Wed, 16 Jun 2010) - kjing - defunt "Mango" svn dev branch before github cutover
13
14 r55980 - 2010-04-19 13:31:28 -0700 (Mon, 19 Apr 2010) - kjing - create Mango (6.1) based on windex
15
16 r51719 - 2009-10-22 10:18:00 -0700 (Thu, 22 Oct 2009) - mitani - Converted to Build 3  tags and updated the build system 
17
18 r51634 - 2009-10-19 13:32:22 -0700 (Mon, 19 Oct 2009) - mitani - Windex is the branch for Sugar Sales 1.0 development
19
20 r51509 - 2009-10-14 07:47:28 -0700 (Wed, 14 Oct 2009) - jmertic - More fallout fixes from the PHP 5.3 ereg to preg changes.
21
22 r51491 - 2009-10-13 13:09:52 -0700 (Tue, 13 Oct 2009) - jmertic - Fixed lint errors found in this file.
23
24 r51443 - 2009-10-12 13:34:36 -0700 (Mon, 12 Oct 2009) - jmertic - Bug 33332 - Made application PHP 5.3 compliant with E_DEPRECATED warnings on by:
25 - Changing all ereg function to either preg or simple string based ones
26 - No more references to magic quotes.
27 - Change all the session_unregister() functions to just unset() the correct session variable instead.
28
29 r50375 - 2009-08-24 18:07:43 -0700 (Mon, 24 Aug 2009) - dwong - branch kobe2 from tokyo r50372
30
31 r42807 - 2008-12-29 11:16:59 -0800 (Mon, 29 Dec 2008) - dwong - Branch from trunk/sugarcrm r42806 to branches/tokyo/sugarcrm
32
33 r13782 - 2006-06-06 10:58:55 -0700 (Tue, 06 Jun 2006) - majed - changes entry point code
34
35 r11115 - 2006-01-17 14:54:45 -0800 (Tue, 17 Jan 2006) - majed - add entry point validation
36
37 r8846 - 2005-10-31 11:01:12 -0800 (Mon, 31 Oct 2005) - majed - new version of nusoap
38
39 r7452 - 2005-08-17 11:32:34 -0700 (Wed, 17 Aug 2005) - majed - changes soap to nusoap
40
41 r5462 - 2005-05-25 13:50:11 -0700 (Wed, 25 May 2005) - majed - upgraded nusoap to .6.9
42
43 r573 - 2004-09-04 13:03:32 -0700 (Sat, 04 Sep 2004) - sugarclint - undoing copyrights added in inadvertantly.  --clint
44
45 r546 - 2004-09-03 11:49:38 -0700 (Fri, 03 Sep 2004) - sugarmsi - removed echo count
46
47 r354 - 2004-08-02 23:00:37 -0700 (Mon, 02 Aug 2004) - sugarjacob - Adding Soap
48
49
50 */
51
52
53 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
54
55
56
57
58 /**
59 * transport class for sending/receiving data via HTTP and HTTPS
60 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
61 *
62 * @author   Dietrich Ayala <dietrich@ganx4.com>
63 * @author   Scott Nichol <snichol@users.sourceforge.net>
64
65 * @access public
66 */
67 class soap_transport_http extends nusoap_base {
68
69         var $url = '';
70         var $uri = '';
71         var $digest_uri = '';
72         var $scheme = '';
73         var $host = '';
74         var $port = '';
75         var $path = '';
76         var $request_method = 'POST';
77         var $protocol_version = '1.0';
78         var $encoding = '';
79         var $outgoing_headers = array();
80         var $incoming_headers = array();
81         var $incoming_cookies = array();
82         var $outgoing_payload = '';
83         var $incoming_payload = '';
84         var $response_status_line;      // HTTP response status line
85         var $useSOAPAction = true;
86         var $persistentConnection = false;
87         var $ch = false;        // cURL handle
88         var $ch_options = array();      // cURL custom options
89         var $use_curl = false;          // force cURL use
90         var $proxy = null;                      // proxy information (associative array)
91         var $username = '';
92         var $password = '';
93         var $authtype = '';
94         var $digestRequest = array();
95         var $certRequest = array();     // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional)
96                                                                 // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem'
97                                                                 // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem'
98                                                                 // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem'
99                                                                 // passphrase: SSL key password/passphrase
100                                                                 // certpassword: SSL certificate password
101                                                                 // verifypeer: default is 1
102                                                                 // verifyhost: default is 1
103
104         /**
105         * constructor
106         *
107         * @param string $url The URL to which to connect
108         * @param array $curl_options User-specified cURL options
109         * @param boolean $use_curl Whether to try to force cURL use
110         * @access public
111         */
112         function soap_transport_http($url, $curl_options = NULL, $use_curl = false){
113                 parent::nusoap_base();
114                 $this->debug("ctor url=$url use_curl=$use_curl curl_options:");
115                 $this->appendDebug($this->varDump($curl_options));
116                 $this->setURL($url);
117                 if (is_array($curl_options)) {
118                         $this->ch_options = $curl_options;
119                 }
120                 $this->use_curl = $use_curl;
121                 preg_match('/\$Revisio' . 'n: ([^ ]+)/', $this->revision, $rev);
122                 $this->setHeader('User-Agent', $this->title.'/'.$this->version.' ('.$rev[1].')');
123         }
124
125         /**
126         * sets a cURL option
127         *
128         * @param        mixed $option The cURL option (always integer?)
129         * @param        mixed $value The cURL option value
130         * @access   private
131         */
132         function setCurlOption($option, $value) {
133                 $this->debug("setCurlOption option=$option, value=");
134                 $this->appendDebug($this->varDump($value));
135                 curl_setopt($this->ch, $option, $value);
136         }
137
138         /**
139         * sets an HTTP header
140         *
141         * @param string $name The name of the header
142         * @param string $value The value of the header
143         * @access private
144         */
145         function setHeader($name, $value) {
146                 $this->outgoing_headers[$name] = $value;
147                 $this->debug("set header $name: $value");
148         }
149
150         /**
151         * unsets an HTTP header
152         *
153         * @param string $name The name of the header
154         * @access private
155         */
156         function unsetHeader($name) {
157                 if (isset($this->outgoing_headers[$name])) {
158                         $this->debug("unset header $name");
159                         unset($this->outgoing_headers[$name]);
160                 }
161         }
162
163         /**
164         * sets the URL to which to connect
165         *
166         * @param string $url The URL to which to connect
167         * @access private
168         */
169         function setURL($url) {
170                 $this->url = $url;
171
172                 $u = parse_url($url);
173                 foreach($u as $k => $v){
174                         $this->debug("parsed URL $k = $v");
175                         $this->$k = $v;
176                 }
177                 
178                 // add any GET params to path
179                 if(isset($u['query']) && $u['query'] != ''){
180             $this->path .= '?' . $u['query'];
181                 }
182                 
183                 // set default port
184                 if(!isset($u['port'])){
185                         if($u['scheme'] == 'https'){
186                                 $this->port = 443;
187                         } else {
188                                 $this->port = 80;
189                         }
190                 }
191                 
192                 $this->uri = $this->path;
193                 $this->digest_uri = $this->uri;
194                 
195                 // build headers
196                 if (!isset($u['port'])) {
197                         $this->setHeader('Host', $this->host);
198                 } else {
199                         $this->setHeader('Host', $this->host.':'.$this->port);
200                 }
201
202                 if (isset($u['user']) && $u['user'] != '') {
203                         $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : '');
204                 }
205         }
206
207         /**
208         * gets the I/O method to use
209         *
210         * @return       string  I/O method to use (socket|curl|unknown)
211         * @access       private
212         */
213         function io_method() {
214                 if ($this->use_curl || ($this->scheme == 'https') || ($this->scheme == 'http' && $this->authtype == 'ntlm') || ($this->scheme == 'http' && is_array($this->proxy) && $this->proxy['authtype'] == 'ntlm'))
215                         return 'curl';
216                 if (($this->scheme == 'http' || $this->scheme == 'ssl') && $this->authtype != 'ntlm' && (!is_array($this->proxy) || $this->proxy['authtype'] != 'ntlm'))
217                         return 'socket';
218                 return 'unknown';
219         }
220
221         /**
222         * establish an HTTP connection
223         *
224         * @param    integer $timeout set connection timeout in seconds
225         * @param        integer $response_timeout set response timeout in seconds
226         * @return       boolean true if connected, false if not
227         * @access   private
228         */
229         function connect($connection_timeout=0,$response_timeout=30){
230                 // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
231                 // "regular" socket.
232                 // TODO: disabled for now because OpenSSL must be *compiled* in (not just
233                 //       loaded), and until PHP5 stream_get_wrappers is not available.
234 //              if ($this->scheme == 'https') {
235 //                      if (version_compare(phpversion(), '4.3.0') >= 0) {
236 //                              if (extension_loaded('openssl')) {
237 //                                      $this->scheme = 'ssl';
238 //                                      $this->debug('Using SSL over OpenSSL');
239 //                              }
240 //                      }
241 //              }
242                 $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port");
243           if ($this->io_method() == 'socket') {
244                 if (!is_array($this->proxy)) {
245                         $host = $this->host;
246                         $port = $this->port;
247                 } else {
248                         $host = $this->proxy['host'];
249                         $port = $this->proxy['port'];
250                 }
251
252                 // use persistent connection
253                 if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){
254                         if (!feof($this->fp)) {
255                                 $this->debug('Re-use persistent connection');
256                                 return true;
257                         }
258                         fclose($this->fp);
259                         $this->debug('Closed persistent connection at EOF');
260                 }
261
262                 // munge host if using OpenSSL
263                 if ($this->scheme == 'ssl') {
264                         $host = 'ssl://' . $host;
265                 }
266                 $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout);
267
268                 // open socket
269                 if($connection_timeout > 0){
270                         $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout);
271                 } else {
272                         $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str);
273                 }
274                 
275                 // test pointer
276                 if(!$this->fp) {
277                         $msg = 'Couldn\'t open socket connection to server ' . $this->url;
278                         if ($this->errno) {
279                                 $msg .= ', Error ('.$this->errno.'): '.$this->error_str;
280                         } else {
281                                 $msg .= ' prior to connect().  This is often a problem looking up the host name.';
282                         }
283                         $this->debug($msg);
284                         $this->setError($msg);
285                         return false;
286                 }
287                 
288                 // set response timeout
289                 $this->debug('set response timeout to ' . $response_timeout);
290                 socket_set_timeout( $this->fp, $response_timeout);
291
292                 $this->debug('socket connected');
293                 return true;
294           } else if ($this->io_method() == 'curl') {
295                 if (!extension_loaded('curl')) {
296 //                      $this->setError('cURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
297                         $this->setError('The PHP cURL Extension is required for HTTPS or NLTM.  You will need to re-build or update your PHP to include cURL or change php.ini to load the PHP cURL extension.');
298                         return false;
299                 }
300                 // Avoid warnings when PHP does not have these options
301                 if (defined('CURLOPT_CONNECTIONTIMEOUT'))
302                         $CURLOPT_CONNECTIONTIMEOUT = CURLOPT_CONNECTIONTIMEOUT;
303                 else
304                         $CURLOPT_CONNECTIONTIMEOUT = 78;
305                 if (defined('CURLOPT_HTTPAUTH'))
306                         $CURLOPT_HTTPAUTH = CURLOPT_HTTPAUTH;
307                 else
308                         $CURLOPT_HTTPAUTH = 107;
309                 if (defined('CURLOPT_PROXYAUTH'))
310                         $CURLOPT_PROXYAUTH = CURLOPT_PROXYAUTH;
311                 else
312                         $CURLOPT_PROXYAUTH = 111;
313                 if (defined('CURLAUTH_BASIC'))
314                         $CURLAUTH_BASIC = CURLAUTH_BASIC;
315                 else
316                         $CURLAUTH_BASIC = 1;
317                 if (defined('CURLAUTH_DIGEST'))
318                         $CURLAUTH_DIGEST = CURLAUTH_DIGEST;
319                 else
320                         $CURLAUTH_DIGEST = 2;
321                 if (defined('CURLAUTH_NTLM'))
322                         $CURLAUTH_NTLM = CURLAUTH_NTLM;
323                 else
324                         $CURLAUTH_NTLM = 8;
325
326                 $this->debug('connect using cURL');
327                 // init CURL
328                 $this->ch = curl_init();
329                 // set url
330                 $hostURL = ($this->port != '') ? "$this->scheme://$this->host:$this->port" : "$this->scheme://$this->host";
331                 // add path
332                 $hostURL .= $this->path;
333                 $this->setCurlOption(CURLOPT_URL, $hostURL);
334                 // follow location headers (re-directs)
335                 if (ini_get('safe_mode') || ini_get('open_basedir')) {
336                         $this->debug('safe_mode or open_basedir set, so do not set CURLOPT_FOLLOWLOCATION');
337                         $this->debug('safe_mode = ');
338                         $this->appendDebug($this->varDump(ini_get('safe_mode')));
339                         $this->debug('open_basedir = ');
340                         $this->appendDebug($this->varDump(ini_get('open_basedir')));
341                 } else {
342                         $this->setCurlOption(CURLOPT_FOLLOWLOCATION, 1);
343                 }
344                 // ask for headers in the response output
345                 $this->setCurlOption(CURLOPT_HEADER, 1);
346                 // ask for the response output as the return value
347                 $this->setCurlOption(CURLOPT_RETURNTRANSFER, 1);
348                 // encode
349                 // We manage this ourselves through headers and encoding
350 //              if(function_exists('gzuncompress')){
351 //                      $this->setCurlOption(CURLOPT_ENCODING, 'deflate');
352 //              }
353                 // persistent connection
354                 if ($this->persistentConnection) {
355                         // I believe the following comment is now bogus, having applied to
356                         // the code when it used CURLOPT_CUSTOMREQUEST to send the request.
357                         // The way we send data, we cannot use persistent connections, since
358                         // there will be some "junk" at the end of our request.
359                         //$this->setCurlOption(CURL_HTTP_VERSION_1_1, true);
360                         $this->persistentConnection = false;
361                         $this->setHeader('Connection', 'close');
362                 }
363                 // set timeouts
364                 if ($connection_timeout != 0) {
365                         $this->setCurlOption($CURLOPT_CONNECTIONTIMEOUT, $connection_timeout);
366                 }
367                 if ($response_timeout != 0) {
368                         $this->setCurlOption(CURLOPT_TIMEOUT, $response_timeout);
369                 }
370
371                 if ($this->scheme == 'https') {
372                         $this->debug('set cURL SSL verify options');
373                         // recent versions of cURL turn on peer/host checking by default,
374                         // while PHP binaries are not compiled with a default location for the
375                         // CA cert bundle, so disable peer/host checking.
376                         //$this->setCurlOption(CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');             
377                         $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, 0);
378                         $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, 0);
379         
380                         // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo)
381                         if ($this->authtype == 'certificate') {
382                                 $this->debug('set cURL certificate options');
383                                 if (isset($this->certRequest['cainfofile'])) {
384                                         $this->setCurlOption(CURLOPT_CAINFO, $this->certRequest['cainfofile']);
385                                 }
386                                 if (isset($this->certRequest['verifypeer'])) {
387                                         $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']);
388                                 } else {
389                                         $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, 1);
390                                 }
391                                 if (isset($this->certRequest['verifyhost'])) {
392                                         $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']);
393                                 } else {
394                                         $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, 1);
395                                 }
396                                 if (isset($this->certRequest['sslcertfile'])) {
397                                         $this->setCurlOption(CURLOPT_SSLCERT, $this->certRequest['sslcertfile']);
398                                 }
399                                 if (isset($this->certRequest['sslkeyfile'])) {
400                                         $this->setCurlOption(CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']);
401                                 }
402                                 if (isset($this->certRequest['passphrase'])) {
403                                         $this->setCurlOption(CURLOPT_SSLKEYPASSWD, $this->certRequest['passphrase']);
404                                 }
405                                 if (isset($this->certRequest['certpassword'])) {
406                                         $this->setCurlOption(CURLOPT_SSLCERTPASSWD, $this->certRequest['certpassword']);
407                                 }
408                         }
409                 }
410                 if ($this->authtype && ($this->authtype != 'certificate')) {
411                         if ($this->username) {
412                                 $this->debug('set cURL username/password');
413                                 $this->setCurlOption(CURLOPT_USERPWD, "$this->username:$this->password");
414                         }
415                         if ($this->authtype == 'basic') {
416                                 $this->debug('set cURL for Basic authentication');
417                                 $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_BASIC);
418                         }
419                         if ($this->authtype == 'digest') {
420                                 $this->debug('set cURL for digest authentication');
421                                 $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_DIGEST);
422                         }
423                         if ($this->authtype == 'ntlm') {
424                                 $this->debug('set cURL for NTLM authentication');
425                                 $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_NTLM);
426                         }
427                 }
428                 if (is_array($this->proxy)) {
429                         $this->debug('set cURL proxy options');
430                         if ($this->proxy['port'] != '') {
431                                 $this->setCurlOption(CURLOPT_PROXY, $this->proxy['host'].':'.$this->proxy['port']);
432                         } else {
433                                 $this->setCurlOption(CURLOPT_PROXY, $this->proxy['host']);
434                         }
435                         if ($this->proxy['username'] || $this->proxy['password']) {
436                                 $this->debug('set cURL proxy authentication options');
437                                 $this->setCurlOption(CURLOPT_PROXYUSERPWD, $this->proxy['username'].':'.$this->proxy['password']);
438                                 if ($this->proxy['authtype'] == 'basic') {
439                                         $this->setCurlOption($CURLOPT_PROXYAUTH, $CURLAUTH_BASIC);
440                                 }
441                                 if ($this->proxy['authtype'] == 'ntlm') {
442                                         $this->setCurlOption($CURLOPT_PROXYAUTH, $CURLAUTH_NTLM);
443                                 }
444                         }
445                 }
446                 $this->debug('cURL connection set up');
447                 return true;
448           } else {
449                 $this->setError('Unknown scheme ' . $this->scheme);
450                 $this->debug('Unknown scheme ' . $this->scheme);
451                 return false;
452           }
453         }
454
455         /**
456         * sends the SOAP request and gets the SOAP response via HTTP[S]
457         *
458         * @param    string $data message data
459         * @param    integer $timeout set connection timeout in seconds
460         * @param        integer $response_timeout set response timeout in seconds
461         * @param        array $cookies cookies to send
462         * @return       string data
463         * @access   public
464         */
465         function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) {
466                 
467                 $this->debug('entered send() with data of length: '.strlen($data));
468
469                 $this->tryagain = true;
470                 $tries = 0;
471                 while ($this->tryagain) {
472                         $this->tryagain = false;
473                         if ($tries++ < 2) {
474                                 // make connnection
475                                 if (!$this->connect($timeout, $response_timeout)){
476                                         return false;
477                                 }
478                                 
479                                 // send request
480                                 if (!$this->sendRequest($data, $cookies)){
481                                         return false;
482                                 }
483                                 
484                                 // get response
485                                 $respdata = $this->getResponse();
486                         } else {
487                                 $this->setError("Too many tries to get an OK response ($this->response_status_line)");
488                         }
489                 }               
490                 $this->debug('end of send()');
491                 return $respdata;
492         }
493
494
495         /**
496         * sends the SOAP request and gets the SOAP response via HTTPS using CURL
497         *
498         * @param    string $data message data
499         * @param    integer $timeout set connection timeout in seconds
500         * @param        integer $response_timeout set response timeout in seconds
501         * @param        array $cookies cookies to send
502         * @return       string data
503         * @access   public
504         * @deprecated
505         */
506         function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) {
507                 return $this->send($data, $timeout, $response_timeout, $cookies);
508         }
509         
510         /**
511         * if authenticating, set user credentials here
512         *
513         * @param    string $username
514         * @param    string $password
515         * @param        string $authtype (basic|digest|certificate|ntlm)
516         * @param        array $digestRequest (keys must be nonce, nc, realm, qop)
517         * @param        array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
518         * @access   public
519         */
520         function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) {
521                 $this->debug("setCredentials username=$username authtype=$authtype digestRequest=");
522                 $this->appendDebug($this->varDump($digestRequest));
523                 $this->debug("certRequest=");
524                 $this->appendDebug($this->varDump($certRequest));
525                 // cf. RFC 2617
526                 if ($authtype == 'basic') {
527                         $this->setHeader('Authorization', 'Basic '.base64_encode(str_replace(':','',$username).':'.$password));
528                 } elseif ($authtype == 'digest') {
529                         if (isset($digestRequest['nonce'])) {
530                                 $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1;
531                                 
532                                 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
533         
534                                 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
535                                 $A1 = $username. ':' . (isset($digestRequest['realm']) ? $digestRequest['realm'] : '') . ':' . $password;
536         
537                                 // H(A1) = MD5(A1)
538                                 $HA1 = md5($A1);
539         
540                                 // A2 = Method ":" digest-uri-value
541                                 $A2 = $this->request_method . ':' . $this->digest_uri;
542         
543                                 // H(A2)
544                                 $HA2 =  md5($A2);
545         
546                                 // KD(secret, data) = H(concat(secret, ":", data))
547                                 // if qop == auth:
548                                 // request-digest  = <"> < KD ( H(A1),     unq(nonce-value)
549                                 //                              ":" nc-value
550                                 //                              ":" unq(cnonce-value)
551                                 //                              ":" unq(qop-value)
552                                 //                              ":" H(A2)
553                                 //                            ) <">
554                                 // if qop is missing,
555                                 // request-digest  = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
556         
557                                 $unhashedDigest = '';
558                                 $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : '';
559                                 $cnonce = $nonce;
560                                 if ($digestRequest['qop'] != '') {
561                                         $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
562                                 } else {
563                                         $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
564                                 }
565         
566                                 $hashedDigest = md5($unhashedDigest);
567         
568                                 $opaque = '';   
569                                 if (isset($digestRequest['opaque'])) {
570                                         $opaque = ', opaque="' . $digestRequest['opaque'] . '"';
571                                 }
572
573                                 $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 . '"');
574                         }
575                 } elseif ($authtype == 'certificate') {
576                         $this->certRequest = $certRequest;
577                         $this->debug('Authorization header not set for certificate');
578                 } elseif ($authtype == 'ntlm') {
579                         // do nothing
580                         $this->debug('Authorization header not set for ntlm');
581                 }
582                 $this->username = $username;
583                 $this->password = $password;
584                 $this->authtype = $authtype;
585                 $this->digestRequest = $digestRequest;
586         }
587         
588         /**
589         * set the soapaction value
590         *
591         * @param    string $soapaction
592         * @access   public
593         */
594         function setSOAPAction($soapaction) {
595                 $this->setHeader('SOAPAction', '"' . $soapaction . '"');
596         }
597         
598         /**
599         * use http encoding
600         *
601         * @param    string $enc encoding style. supported values: gzip, deflate, or both
602         * @access   public
603         */
604         function setEncoding($enc='gzip, deflate') {
605                 if (function_exists('gzdeflate')) {
606                         $this->protocol_version = '1.1';
607                         $this->setHeader('Accept-Encoding', $enc);
608                         if (!isset($this->outgoing_headers['Connection'])) {
609                                 $this->setHeader('Connection', 'close');
610                                 $this->persistentConnection = false;
611                         }
612                         // deprecated as of PHP 5.3.0
613                         //set_magic_quotes_runtime(0);
614                         $this->encoding = $enc;
615                 }
616         }
617         
618         /**
619         * set proxy info here
620         *
621         * @param    string $proxyhost use an empty string to remove proxy
622         * @param    string $proxyport
623         * @param        string $proxyusername
624         * @param        string $proxypassword
625         * @param        string $proxyauthtype (basic|ntlm)
626         * @access   public
627         */
628         function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 'basic') {
629                 if ($proxyhost) {
630                         $this->proxy = array(
631                                 'host' => $proxyhost,
632                                 'port' => $proxyport,
633                                 'username' => $proxyusername,
634                                 'password' => $proxypassword,
635                                 'authtype' => $proxyauthtype
636                         );
637                         if ($proxyusername != '' && $proxypassword != '' && $proxyauthtype = 'basic') {
638                                 $this->setHeader('Proxy-Authorization', ' Basic '.base64_encode($proxyusername.':'.$proxypassword));
639                         }
640                 } else {
641                         $this->debug('remove proxy');
642                         $proxy = null;
643                         unsetHeader('Proxy-Authorization');
644                 }
645         }
646         
647
648         /**
649          * Test if the given string starts with a header that is to be skipped.
650          * Skippable headers result from chunked transfer and proxy requests.
651          *
652          * @param       string $data The string to check.
653          * @returns     boolean Whether a skippable header was found.
654          * @access      private
655          */
656         function isSkippableCurlHeader(&$data) {
657                 $skipHeaders = array(   'HTTP/1.1 100',
658                                                                 'HTTP/1.0 301',
659                                                                 'HTTP/1.1 301',
660                                                                 'HTTP/1.0 302',
661                                                                 'HTTP/1.1 302',
662                                                                 'HTTP/1.0 401',
663                                                                 'HTTP/1.1 401',
664                                                                 'HTTP/1.0 200 Connection established');
665                 foreach ($skipHeaders as $hd) {
666                         $prefix = substr($data, 0, strlen($hd));
667                         if ($prefix == $hd) return true;
668                 }
669
670                 return false;
671         }
672
673         /**
674         * decode a string that is encoded w/ "chunked' transfer encoding
675         * as defined in RFC2068 19.4.6
676         *
677         * @param    string $buffer
678         * @param    string $lb
679         * @returns      string
680         * @access   public
681         * @deprecated
682         */
683         function decodeChunked($buffer, $lb){
684                 // length := 0
685                 $length = 0;
686                 $new = '';
687                 
688                 // read chunk-size, chunk-extension (if any) and CRLF
689                 // get the position of the linebreak
690                 $chunkend = strpos($buffer, $lb);
691                 if ($chunkend == FALSE) {
692                         $this->debug('no linebreak found in decodeChunked');
693                         return $new;
694                 }
695                 $temp = substr($buffer,0,$chunkend);
696                 $chunk_size = hexdec( trim($temp) );
697                 $chunkstart = $chunkend + strlen($lb);
698                 // while (chunk-size > 0) {
699                 while ($chunk_size > 0) {
700                         $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
701                         $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size);
702                         
703                         // Just in case we got a broken connection
704                         if ($chunkend == FALSE) {
705                             $chunk = substr($buffer,$chunkstart);
706                                 // append chunk-data to entity-body
707                         $new .= $chunk;
708                             $length += strlen($chunk);
709                             break;
710                         }
711                         
712                         // read chunk-data and CRLF
713                         $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
714                         // append chunk-data to entity-body
715                         $new .= $chunk;
716                         // length := length + chunk-size
717                         $length += strlen($chunk);
718                         // read chunk-size and CRLF
719                         $chunkstart = $chunkend + strlen($lb);
720                         
721                         $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb);
722                         if ($chunkend == FALSE) {
723                                 break; //Just in case we got a broken connection
724                         }
725                         $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
726                         $chunk_size = hexdec( trim($temp) );
727                         $chunkstart = $chunkend;
728                 }
729                 return $new;
730         }
731         
732         /**
733          * Writes the payload, including HTTP headers, to $this->outgoing_payload.
734          *
735          * @param       string $data HTTP body
736          * @param       string $cookie_str data for HTTP Cookie header
737          * @return      void
738          * @access      private
739          */
740         function buildPayload($data, $cookie_str = '') {
741                 // Note: for cURL connections, $this->outgoing_payload is ignored,
742                 // as is the Content-Length header, but these are still created as
743                 // debugging guides.
744
745                 // add content-length header
746                 if ($this->request_method != 'GET') {
747                         $this->setHeader('Content-Length', strlen($data));
748                 }
749
750                 // start building outgoing payload:
751                 if ($this->proxy) {
752                         $uri = $this->url;
753                 } else {
754                         $uri = $this->uri;
755                 }
756                 $req = "$this->request_method $uri HTTP/$this->protocol_version";
757                 $this->debug("HTTP request: $req");
758                 $this->outgoing_payload = "$req\r\n";
759
760                 // loop thru headers, serializing
761                 foreach($this->outgoing_headers as $k => $v){
762                         $hdr = $k.': '.$v;
763                         $this->debug("HTTP header: $hdr");
764                         $this->outgoing_payload .= "$hdr\r\n";
765                 }
766
767                 // add any cookies
768                 if ($cookie_str != '') {
769                         $hdr = 'Cookie: '.$cookie_str;
770                         $this->debug("HTTP header: $hdr");
771                         $this->outgoing_payload .= "$hdr\r\n";
772                 }
773
774                 // header/body separator
775                 $this->outgoing_payload .= "\r\n";
776                 
777                 // add data
778                 $this->outgoing_payload .= $data;
779         }
780
781         /**
782         * sends the SOAP request via HTTP[S]
783         *
784         * @param    string $data message data
785         * @param        array $cookies cookies to send
786         * @return       boolean true if OK, false if problem
787         * @access   private
788         */
789         function sendRequest($data, $cookies = NULL) {
790                 // build cookie string
791                 $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https')));
792
793                 // build payload
794                 $this->buildPayload($data, $cookie_str);
795
796           if ($this->io_method() == 'socket') {
797                 // send payload
798                 if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
799                         $this->setError('couldn\'t write message data to socket');
800                         $this->debug('couldn\'t write message data to socket');
801                         return false;
802                 }
803                 $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload));
804                 return true;
805           } else if ($this->io_method() == 'curl') {
806                 // set payload
807                 // cURL does say this should only be the verb, and in fact it
808                 // turns out that the URI and HTTP version are appended to this, which
809                 // some servers refuse to work with (so we no longer use this method!)
810                 //$this->setCurlOption(CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
811                 $curl_headers = array();
812                 foreach($this->outgoing_headers as $k => $v){
813                         if ($k == 'Connection' || $k == 'Content-Length' || $k == 'Host' || $k == 'Authorization' || $k == 'Proxy-Authorization') {
814                                 $this->debug("Skip cURL header $k: $v");
815                         } else {
816                                 $curl_headers[] = "$k: $v";
817                         }
818                 }
819                 if ($cookie_str != '') {
820                         $curl_headers[] = 'Cookie: ' . $cookie_str;
821                 }
822                 $this->setCurlOption(CURLOPT_HTTPHEADER, $curl_headers);
823                 $this->debug('set cURL HTTP headers');
824                 if ($this->request_method == "POST") {
825                         $this->setCurlOption(CURLOPT_POST, 1);
826                         $this->setCurlOption(CURLOPT_POSTFIELDS, $data);
827                         $this->debug('set cURL POST data');
828                 } else {
829                 }
830                 // insert custom user-set cURL options
831                 foreach ($this->ch_options as $key => $val) {
832                         $this->setCurlOption($key, $val);
833                 }
834
835                 $this->debug('set cURL payload');
836                 return true;
837           }
838         }
839
840         /**
841         * gets the SOAP response via HTTP[S]
842         *
843         * @return       string the response (also sets member variables like incoming_payload)
844         * @access   private
845         */
846         function getResponse(){
847                 $this->incoming_payload = '';
848             
849           if ($this->io_method() == 'socket') {
850             // loop until headers have been retrieved
851             $data = '';
852             while (!isset($lb)){
853
854                         // We might EOF during header read.
855                         if(feof($this->fp)) {
856                                 $this->incoming_payload = $data;
857                                 $this->debug('found no headers before EOF after length ' . strlen($data));
858                                 $this->debug("received before EOF:\n" . $data);
859                                 $this->setError('server failed to send headers');
860                                 return false;
861                         }
862
863                         $tmp = fgets($this->fp, 256);
864                         $tmplen = strlen($tmp);
865                         $this->debug("read line of $tmplen bytes: " . trim($tmp));
866
867                         if ($tmplen == 0) {
868                                 $this->incoming_payload = $data;
869                                 $this->debug('socket read of headers timed out after length ' . strlen($data));
870                                 $this->debug("read before timeout: " . $data);
871                                 $this->setError('socket read of headers timed out');
872                                 return false;
873                         }
874
875                         $data .= $tmp;
876                         $pos = strpos($data,"\r\n\r\n");
877                         if($pos > 1){
878                                 $lb = "\r\n";
879                         } else {
880                                 $pos = strpos($data,"\n\n");
881                                 if($pos > 1){
882                                         $lb = "\n";
883                                 }
884                         }
885                         // remove 100 headers
886                         if (isset($lb) && preg_match('/^HTTP\/1.1 100/',$data)) {
887                                 unset($lb);
888                                 $data = '';
889                         }//
890                 }
891                 // store header data
892                 $this->incoming_payload .= $data;
893                 $this->debug('found end of headers after length ' . strlen($data));
894                 // process headers
895                 $header_data = trim(substr($data,0,$pos));
896                 $header_array = explode($lb,$header_data);
897                 $this->incoming_headers = array();
898                 $this->incoming_cookies = array();
899                 foreach($header_array as $header_line){
900                         $arr = explode(':',$header_line, 2);
901                         if(count($arr) > 1){
902                                 $header_name = strtolower(trim($arr[0]));
903                                 $this->incoming_headers[$header_name] = trim($arr[1]);
904                                 if ($header_name == 'set-cookie') {
905                                         // TODO: allow multiple cookies from parseCookie
906                                         $cookie = $this->parseCookie(trim($arr[1]));
907                                         if ($cookie) {
908                                                 $this->incoming_cookies[] = $cookie;
909                                                 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
910                                         } else {
911                                                 $this->debug('did not find cookie in ' . trim($arr[1]));
912                                         }
913                         }
914                         } else if (isset($header_name)) {
915                                 // append continuation line to previous header
916                                 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
917                         }
918                 }
919                 
920                 // loop until msg has been received
921                 if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') {
922                         $content_length =  2147483647;  // ignore any content-length header
923                         $chunked = true;
924                         $this->debug("want to read chunked content");
925                 } elseif (isset($this->incoming_headers['content-length'])) {
926                         $content_length = $this->incoming_headers['content-length'];
927                         $chunked = false;
928                         $this->debug("want to read content of length $content_length");
929                 } else {
930                         $content_length =  2147483647;
931                         $chunked = false;
932                         $this->debug("want to read content to EOF");
933                 }
934                 $data = '';
935                 do {
936                         if ($chunked) {
937                                 $tmp = fgets($this->fp, 256);
938                                 $tmplen = strlen($tmp);
939                                 $this->debug("read chunk line of $tmplen bytes");
940                                 if ($tmplen == 0) {
941                                         $this->incoming_payload = $data;
942                                         $this->debug('socket read of chunk length timed out after length ' . strlen($data));
943                                         $this->debug("read before timeout:\n" . $data);
944                                         $this->setError('socket read of chunk length timed out');
945                                         return false;
946                                 }
947                                 $content_length = hexdec(trim($tmp));
948                                 $this->debug("chunk length $content_length");
949                         }
950                         $strlen = 0;
951                     while (($strlen < $content_length) && (!feof($this->fp))) {
952                         $readlen = min(8192, $content_length - $strlen);
953                                 $tmp = fread($this->fp, $readlen);
954                                 $tmplen = strlen($tmp);
955                                 $this->debug("read buffer of $tmplen bytes");
956                                 if (($tmplen == 0) && (!feof($this->fp))) {
957                                         $this->incoming_payload = $data;
958                                         $this->debug('socket read of body timed out after length ' . strlen($data));
959                                         $this->debug("read before timeout:\n" . $data);
960                                         $this->setError('socket read of body timed out');
961                                         return false;
962                                 }
963                                 $strlen += $tmplen;
964                                 $data .= $tmp;
965                         }
966                         if ($chunked && ($content_length > 0)) {
967                                 $tmp = fgets($this->fp, 256);
968                                 $tmplen = strlen($tmp);
969                                 $this->debug("read chunk terminator of $tmplen bytes");
970                                 if ($tmplen == 0) {
971                                         $this->incoming_payload = $data;
972                                         $this->debug('socket read of chunk terminator timed out after length ' . strlen($data));
973                                         $this->debug("read before timeout:\n" . $data);
974                                         $this->setError('socket read of chunk terminator timed out');
975                                         return false;
976                                 }
977                         }
978                 } while ($chunked && ($content_length > 0) && (!feof($this->fp)));
979                 if (feof($this->fp)) {
980                         $this->debug('read to EOF');
981                 }
982                 $this->debug('read body of length ' . strlen($data));
983                 $this->incoming_payload .= $data;
984                 $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server');
985                 
986                 // close filepointer
987                 if(
988                         (isset($this->incoming_headers['connection']) && strtolower($this->incoming_headers['connection']) == 'close') || 
989                         (! $this->persistentConnection) || feof($this->fp)){
990                         fclose($this->fp);
991                         $this->fp = false;
992                         $this->debug('closed socket');
993                 }
994                 
995                 // connection was closed unexpectedly
996                 if($this->incoming_payload == ''){
997                         $this->setError('no response from server');
998                         return false;
999                 }
1000                 
1001                 // decode transfer-encoding
1002 //              if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
1003 //                      if(!$data = $this->decodeChunked($data, $lb)){
1004 //                              $this->setError('Decoding of chunked data failed');
1005 //                              return false;
1006 //                      }
1007                         //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
1008                         // set decoded payload
1009 //                      $this->incoming_payload = $header_data.$lb.$lb.$data;
1010 //              }
1011         
1012           } else if ($this->io_method() == 'curl') {
1013                 // send and receive
1014                 $this->debug('send and receive with cURL');
1015                 $this->incoming_payload = curl_exec($this->ch);
1016                 $data = $this->incoming_payload;
1017
1018         $cErr = curl_error($this->ch);
1019                 if ($cErr != '') {
1020                 $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'<br>';
1021                 // TODO: there is a PHP bug that can cause this to SEGV for CURLINFO_CONTENT_TYPE
1022                         foreach(curl_getinfo($this->ch) as $k => $v){
1023                                 $err .= "$k: $v<br>";
1024                         }
1025                         $this->debug($err);
1026                         $this->setError($err);
1027                         curl_close($this->ch);
1028                 return false;
1029                 } else {
1030                         //echo '<pre>';
1031                         //var_dump(curl_getinfo($this->ch));
1032                         //echo '</pre>';
1033                 }
1034                 // close curl
1035                 $this->debug('No cURL error, closing cURL');
1036                 curl_close($this->ch);
1037                 
1038                 // try removing skippable headers
1039                 $savedata = $data;
1040                 while ($this->isSkippableCurlHeader($data)) {
1041                         $this->debug("Found HTTP header to skip");
1042                         if ($pos = strpos($data,"\r\n\r\n")) {
1043                                 $data = ltrim(substr($data,$pos));
1044                         } elseif($pos = strpos($data,"\n\n") ) {
1045                                 $data = ltrim(substr($data,$pos));
1046                         }
1047                 }
1048
1049                 if ($data == '') {
1050                         // have nothing left; just remove 100 header(s)
1051                         $data = $savedata;
1052                         while (preg_match('/^HTTP\/1.1 100/',$data)) {
1053                                 if ($pos = strpos($data,"\r\n\r\n")) {
1054                                         $data = ltrim(substr($data,$pos));
1055                                 } elseif($pos = strpos($data,"\n\n") ) {
1056                                         $data = ltrim(substr($data,$pos));
1057                                 }
1058                         }
1059                 }
1060                 
1061                 // separate content from HTTP headers
1062                 if ($pos = strpos($data,"\r\n\r\n")) {
1063                         $lb = "\r\n";
1064                 } elseif( $pos = strpos($data,"\n\n")) {
1065                         $lb = "\n";
1066                 } else {
1067                         $this->debug('no proper separation of headers and document');
1068                         $this->setError('no proper separation of headers and document');
1069                         return false;
1070                 }
1071                 $header_data = trim(substr($data,0,$pos));
1072                 $header_array = explode($lb,$header_data);
1073                 $data = ltrim(substr($data,$pos));
1074                 $this->debug('found proper separation of headers and document');
1075                 $this->debug('cleaned data, stringlen: '.strlen($data));
1076                 // clean headers
1077                 foreach ($header_array as $header_line) {
1078                         $arr = explode(':',$header_line,2);
1079                         if(count($arr) > 1){
1080                                 $header_name = strtolower(trim($arr[0]));
1081                                 $this->incoming_headers[$header_name] = trim($arr[1]);
1082                                 if ($header_name == 'set-cookie') {
1083                                         // TODO: allow multiple cookies from parseCookie
1084                                         $cookie = $this->parseCookie(trim($arr[1]));
1085                                         if ($cookie) {
1086                                                 $this->incoming_cookies[] = $cookie;
1087                                                 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
1088                                         } else {
1089                                                 $this->debug('did not find cookie in ' . trim($arr[1]));
1090                                         }
1091                         }
1092                         } else if (isset($header_name)) {
1093                                 // append continuation line to previous header
1094                                 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
1095                         }
1096                 }
1097           }
1098
1099                 $this->response_status_line = $header_array[0];
1100                 $arr = explode(' ', $this->response_status_line, 3);
1101                 $http_version = $arr[0];
1102                 $http_status = intval($arr[1]);
1103                 $http_reason = count($arr) > 2 ? $arr[2] : '';
1104
1105                 // see if we need to resend the request with http digest authentication
1106                 if (isset($this->incoming_headers['location']) && ($http_status == 301 || $http_status == 302)) {
1107                         $this->debug("Got $http_status $http_reason with Location: " . $this->incoming_headers['location']);
1108                         $this->setURL($this->incoming_headers['location']);
1109                         $this->tryagain = true;
1110                         return false;
1111                 }
1112
1113                 // see if we need to resend the request with http digest authentication
1114                 if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) {
1115                         $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']);
1116                         if (strstr($this->incoming_headers['www-authenticate'], "Digest ")) {
1117                                 $this->debug('Server wants digest authentication');
1118                                 // remove "Digest " from our elements
1119                                 $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']);
1120                                 
1121                                 // parse elements into array
1122                                 $digestElements = explode(',', $digestString);
1123                                 foreach ($digestElements as $val) {
1124                                         $tempElement = explode('=', trim($val), 2);
1125                                         $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
1126                                 }
1127
1128                                 // should have (at least) qop, realm, nonce
1129                                 if (isset($digestRequest['nonce'])) {
1130                                         $this->setCredentials($this->username, $this->password, 'digest', $digestRequest);
1131                                         $this->tryagain = true;
1132                                         return false;
1133                                 }
1134                         }
1135                         $this->debug('HTTP authentication failed');
1136                         $this->setError('HTTP authentication failed');
1137                         return false;
1138                 }
1139                 
1140                 if (
1141                         ($http_status >= 300 && $http_status <= 307) ||
1142                         ($http_status >= 400 && $http_status <= 417) ||
1143                         ($http_status >= 501 && $http_status <= 505)
1144                    ) {
1145                         $this->setError("Unsupported HTTP response status $http_status $http_reason (soapclient->response has contents of the response)");
1146                         return false;
1147                 }
1148
1149                 // decode content-encoding
1150                 if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){
1151                         if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){
1152                         // if decoding works, use it. else assume data wasn't gzencoded
1153                         if(function_exists('gzinflate')){
1154                                         //$timer->setMarker('starting decoding of gzip/deflated content');
1155                                         // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress)
1156                                         // this means there are no Zlib headers, although there should be
1157                                         $this->debug('The gzinflate function exists');
1158                                         $datalen = strlen($data);
1159                                         if ($this->incoming_headers['content-encoding'] == 'deflate') {
1160                                                 if ($degzdata = @gzinflate($data)) {
1161                                                 $data = $degzdata;
1162                                                 $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes');
1163                                                 if (strlen($data) < $datalen) {
1164                                                         // test for the case that the payload has been compressed twice
1165                                                         $this->debug('The inflated payload is smaller than the gzipped one; try again');
1166                                                                 if ($degzdata = @gzinflate($data)) {
1167                                                                 $data = $degzdata;
1168                                                                 $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes');
1169                                                                 }
1170                                                 }
1171                                         } else {
1172                                                 $this->debug('Error using gzinflate to inflate the payload');
1173                                                 $this->setError('Error using gzinflate to inflate the payload');
1174                                         }
1175                                         } elseif ($this->incoming_headers['content-encoding'] == 'gzip') {
1176                                                 if ($degzdata = @gzinflate(substr($data, 10))) {        // do our best
1177                                                         $data = $degzdata;
1178                                                 $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes');
1179                                                 if (strlen($data) < $datalen) {
1180                                                         // test for the case that the payload has been compressed twice
1181                                                         $this->debug('The un-gzipped payload is smaller than the gzipped one; try again');
1182                                                                 if ($degzdata = @gzinflate(substr($data, 10))) {
1183                                                                 $data = $degzdata;
1184                                                                 $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes');
1185                                                                 }
1186                                                 }
1187                                         } else {
1188                                                 $this->debug('Error using gzinflate to un-gzip the payload');
1189                                                         $this->setError('Error using gzinflate to un-gzip the payload');
1190                                         }
1191                                         }
1192                                         //$timer->setMarker('finished decoding of gzip/deflated content');
1193                                         //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
1194                                         // set decoded payload
1195                                         $this->incoming_payload = $header_data.$lb.$lb.$data;
1196                         } else {
1197                                         $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
1198                                         $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
1199                                 }
1200                         } else {
1201                                 $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
1202                                 $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
1203                         }
1204                 } else {
1205                         $this->debug('No Content-Encoding header');
1206                 }
1207                 
1208                 if(strlen($data) == 0){
1209                         $this->debug('no data after headers!');
1210                         $this->setError('no data present after HTTP headers');
1211                         return false;
1212                 }
1213                 
1214                 return $data;
1215         }
1216
1217         /**
1218          * sets the content-type for the SOAP message to be sent
1219          *
1220          * @param       string $type the content type, MIME style
1221          * @param       mixed $charset character set used for encoding (or false)
1222          * @access      public
1223          */
1224         function setContentType($type, $charset = false) {
1225                 $this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : ''));
1226         }
1227
1228         /**
1229          * specifies that an HTTP persistent connection should be used
1230          *
1231          * @return      boolean whether the request was honored by this method.
1232          * @access      public
1233          */
1234         function usePersistentConnection(){
1235                 if (isset($this->outgoing_headers['Accept-Encoding'])) {
1236                         return false;
1237                 }
1238                 $this->protocol_version = '1.1';
1239                 $this->persistentConnection = true;
1240                 $this->setHeader('Connection', 'Keep-Alive');
1241                 return true;
1242         }
1243
1244         /**
1245          * parse an incoming Cookie into it's parts
1246          *
1247          * @param       string $cookie_str content of cookie
1248          * @return      array with data of that cookie
1249          * @access      private
1250          */
1251         /*
1252          * TODO: allow a Set-Cookie string to be parsed into multiple cookies
1253          */
1254         function parseCookie($cookie_str) {
1255                 $cookie_str = str_replace('; ', ';', $cookie_str) . ';';
1256                 $data = preg_split('/;/', $cookie_str);
1257                 $value_str = $data[0];
1258
1259                 $cookie_param = 'domain=';
1260                 $start = strpos($cookie_str, $cookie_param);
1261                 if ($start > 0) {
1262                         $domain = substr($cookie_str, $start + strlen($cookie_param));
1263                         $domain = substr($domain, 0, strpos($domain, ';'));
1264                 } else {
1265                         $domain = '';
1266                 }
1267
1268                 $cookie_param = 'expires=';
1269                 $start = strpos($cookie_str, $cookie_param);
1270                 if ($start > 0) {
1271                         $expires = substr($cookie_str, $start + strlen($cookie_param));
1272                         $expires = substr($expires, 0, strpos($expires, ';'));
1273                 } else {
1274                         $expires = '';
1275                 }
1276
1277                 $cookie_param = 'path=';
1278                 $start = strpos($cookie_str, $cookie_param);
1279                 if ( $start > 0 ) {
1280                         $path = substr($cookie_str, $start + strlen($cookie_param));
1281                         $path = substr($path, 0, strpos($path, ';'));
1282                 } else {
1283                         $path = '/';
1284                 }
1285                                                 
1286                 $cookie_param = ';secure;';
1287                 if (strpos($cookie_str, $cookie_param) !== FALSE) {
1288                         $secure = true;
1289                 } else {
1290                         $secure = false;
1291                 }
1292
1293                 $sep_pos = strpos($value_str, '=');
1294
1295                 if ($sep_pos) {
1296                         $name = substr($value_str, 0, $sep_pos);
1297                         $value = substr($value_str, $sep_pos + 1);
1298                         $cookie= array( 'name' => $name,
1299                                         'value' => $value,
1300                                                         'domain' => $domain,
1301                                                         'path' => $path,
1302                                                         'expires' => $expires,
1303                                                         'secure' => $secure
1304                                                         );              
1305                         return $cookie;
1306                 }
1307                 return false;
1308         }
1309   
1310         /**
1311          * sort out cookies for the current request
1312          *
1313          * @param       array $cookies array with all cookies
1314          * @param       boolean $secure is the send-content secure or not?
1315          * @return      string for Cookie-HTTP-Header
1316          * @access      private
1317          */
1318         function getCookiesForRequest($cookies, $secure=false) {
1319                 $cookie_str = '';
1320                 if ((! is_null($cookies)) && (is_array($cookies))) {
1321                         foreach ($cookies as $cookie) {
1322                                 if (! is_array($cookie)) {
1323                                         continue;
1324                                 }
1325                         $this->debug("check cookie for validity: ".$cookie['name'].'='.$cookie['value']);
1326                                 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
1327                                         if (strtotime($cookie['expires']) <= time()) {
1328                                                 $this->debug('cookie has expired');
1329                                                 continue;
1330                                         }
1331                                 }
1332                                 if ((isset($cookie['domain'])) && (! empty($cookie['domain']))) {
1333                                         $domain = preg_quote($cookie['domain']);
1334                                         if (! preg_match("'.*$domain$'i", $this->host)) {
1335                                                 $this->debug('cookie has different domain');
1336                                                 continue;
1337                                         }
1338                                 }
1339                                 if ((isset($cookie['path'])) && (! empty($cookie['path']))) {
1340                                         $path = preg_quote($cookie['path']);
1341                                         if (! preg_match("'^$path.*'i", $this->path)) {
1342                                                 $this->debug('cookie is for a different path');
1343                                                 continue;
1344                                         }
1345                                 }
1346                                 if ((! $secure) && (isset($cookie['secure'])) && ($cookie['secure'])) {
1347                                         $this->debug('cookie is secure, transport is not');
1348                                         continue;
1349                                 }
1350                                 $cookie_str .= $cookie['name'] . '=' . $cookie['value'] . '; ';
1351                         $this->debug('add cookie to Cookie-String: ' . $cookie['name'] . '=' . $cookie['value']);
1352                         }
1353                 }
1354                 return $cookie_str;
1355   }
1356 }
1357
1358
1359 ?>