root/trunk/lib/class.soap_transport_http.php

Revision 61, 36.3 kB (checked in by yann, 5 years ago)

use nusoap library for SOAP access (can be included in plugin package)

Line 
1 <?php
2
3
4
5
6 /**
7 * transport class for sending/receiving data via HTTP and HTTPS
8 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
9 *
10 * @author   Dietrich Ayala <dietrich@ganx4.com>
11 * @version  $Id: class.soap_transport_http.php,v 1.57 2005/07/27 19:24:42 snichol Exp $
12 * @access public
13 */
14 class soap_transport_http extends nusoap_base {
15
16     var $url = '';
17     var $uri = '';
18     var $digest_uri = '';
19     var $scheme = '';
20     var $host = '';
21     var $port = '';
22     var $path = '';
23     var $request_method = 'POST';
24     var $protocol_version = '1.0';
25     var $encoding = '';
26     var $outgoing_headers = array();
27     var $incoming_headers = array();
28     var $incoming_cookies = array();
29     var $outgoing_payload = '';
30     var $incoming_payload = '';
31     var $useSOAPAction = true;
32     var $persistentConnection = false;
33     var $ch = false;    // cURL handle
34     var $username = '';
35     var $password = '';
36     var $authtype = '';
37     var $digestRequest = array();
38     var $certRequest = array();    // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional)
39                                 // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem'
40                                 // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem'
41                                 // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem'
42                                 // passphrase: SSL key password/passphrase
43                                 // verifypeer: default is 1
44                                 // verifyhost: default is 1
45
46     /**
47     * constructor
48     */
49     function soap_transport_http($url){
50         parent::nusoap_base();
51         $this->setURL($url);
52         ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
53         $this->outgoing_headers['User-Agent'] = $this->title.'/'.$this->version.' ('.$rev[1].')';
54         $this->debug('set User-Agent: ' . $this->outgoing_headers['User-Agent']);
55     }
56
57     function setURL($url) {
58         $this->url = $url;
59
60         $u = parse_url($url);
61         foreach($u as $k => $v){
62             $this->debug("$k = $v");
63             $this->$k = $v;
64         }
65         
66         // add any GET params to path
67         if(isset($u['query']) && $u['query'] != ''){
68             $this->path .= '?' . $u['query'];
69         }
70         
71         // set default port
72         if(!isset($u['port'])){
73             if($u['scheme'] == 'https'){
74                 $this->port = 443;
75             } else {
76                 $this->port = 80;
77             }
78         }
79         
80         $this->uri = $this->path;
81         $this->digest_uri = $this->uri;
82         
83         // build headers
84         if (!isset($u['port'])) {
85             $this->outgoing_headers['Host'] = $this->host;
86         } else {
87             $this->outgoing_headers['Host'] = $this->host.':'.$this->port;
88         }
89         $this->debug('set Host: ' . $this->outgoing_headers['Host']);
90
91         if (isset($u['user']) && $u['user'] != '') {
92             $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : '');
93         }
94     }
95     
96     function connect($connection_timeout=0,$response_timeout=30){
97           // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
98           // "regular" socket.
99           // TODO: disabled for now because OpenSSL must be *compiled* in (not just
100           //       loaded), and until PHP5 stream_get_wrappers is not available.
101 //          if ($this->scheme == 'https') {
102 //              if (version_compare(phpversion(), '4.3.0') >= 0) {
103 //                  if (extension_loaded('openssl')) {
104 //                      $this->scheme = 'ssl';
105 //                      $this->debug('Using SSL over OpenSSL');
106 //                  }
107 //              }
108 //        }
109         $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port");
110       if ($this->scheme == 'http' || $this->scheme == 'ssl') {
111         // use persistent connection
112         if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){
113             if (!feof($this->fp)) {
114                 $this->debug('Re-use persistent connection');
115                 return true;
116             }
117             fclose($this->fp);
118             $this->debug('Closed persistent connection at EOF');
119         }
120
121         // munge host if using OpenSSL
122         if ($this->scheme == 'ssl') {
123             $host = 'ssl://' . $this->host;
124         } else {
125             $host = $this->host;
126         }
127         $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout);
128
129         // open socket
130         if($connection_timeout > 0){
131             $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout);
132         } else {
133             $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str);
134         }
135         
136         // test pointer
137         if(!$this->fp) {
138             $msg = 'Couldn\'t open socket connection to server ' . $this->url;
139             if ($this->errno) {
140                 $msg .= ', Error ('.$this->errno.'): '.$this->error_str;
141             } else {
142                 $msg .= ' prior to connect().  This is often a problem looking up the host name.';
143             }
144             $this->debug($msg);
145             $this->setError($msg);
146             return false;
147         }
148         
149         // set response timeout
150         $this->debug('set response timeout to ' . $response_timeout);
151         socket_set_timeout( $this->fp, $response_timeout);
152
153         $this->debug('socket connected');
154         return true;
155       } else if ($this->scheme == 'https') {
156         if (!extension_loaded('curl')) {
157             $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
158             return false;
159         }
160         $this->debug('connect using https');
161         // init CURL
162         $this->ch = curl_init();
163         // set url
164         $hostURL = ($this->port != '') ? "https://$this->host:$this->port" : "https://$this->host";
165         // add path
166         $hostURL .= $this->path;
167         curl_setopt($this->ch, CURLOPT_URL, $hostURL);
168         // follow location headers (re-directs)
169         curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1);
170         // ask for headers in the response output
171         curl_setopt($this->ch, CURLOPT_HEADER, 1);
172         // ask for the response output as the return value
173         curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
174         // encode
175         // We manage this ourselves through headers and encoding
176 //        if(function_exists('gzuncompress')){
177 //            curl_setopt($this->ch, CURLOPT_ENCODING, 'deflate');
178 //        }
179         // persistent connection
180         if ($this->persistentConnection) {
181             // The way we send data, we cannot use persistent connections, since
182             // there will be some "junk" at the end of our request.
183             //curl_setopt($this->ch, CURL_HTTP_VERSION_1_1, true);
184             $this->persistentConnection = false;
185             $this->outgoing_headers['Connection'] = 'close';
186             $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
187         }
188         // set timeout
189         if ($connection_timeout != 0) {
190             curl_setopt($this->ch, CURLOPT_TIMEOUT, $connection_timeout);
191         }
192         // TODO: cURL has added a connection timeout separate from the response timeout
193         //if ($connection_timeout != 0) {
194         //    curl_setopt($this->ch, CURLOPT_CONNECTIONTIMEOUT, $connection_timeout);
195         //}
196         //if ($response_timeout != 0) {
197         //    curl_setopt($this->ch, CURLOPT_TIMEOUT, $response_timeout);
198         //}
199
200         // recent versions of cURL turn on peer/host checking by default,
201         // while PHP binaries are not compiled with a default location for the
202         // CA cert bundle, so disable peer/host checking.
203 //curl_setopt($this->ch, CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');       
204         curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0);
205         curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0);
206
207         // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo)
208         if ($this->authtype == 'certificate') {
209             if (isset($this->certRequest['cainfofile'])) {
210                 curl_setopt($this->ch, CURLOPT_CAINFO, $this->certRequest['cainfofile']);
211             }
212             if (isset($this->certRequest['verifypeer'])) {
213                 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']);
214             } else {
215                 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1);
216             }
217             if (isset($this->certRequest['verifyhost'])) {
218                 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']);
219             } else {
220                 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1);
221             }
222             if (isset($this->certRequest['sslcertfile'])) {
223                 curl_setopt($this->ch, CURLOPT_SSLCERT, $this->certRequest['sslcertfile']);
224             }
225             if (isset($this->certRequest['sslkeyfile'])) {
226                 curl_setopt($this->ch, CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']);
227             }
228             if (isset($this->certRequest['passphrase'])) {
229                 curl_setopt($this->ch, CURLOPT_SSLKEYPASSWD , $this->certRequest['passphrase']);
230             }
231         }
232         $this->debug('cURL connection set up');
233         return true;
234       } else {
235         $this->setError('Unknown scheme ' . $this->scheme);
236         $this->debug('Unknown scheme ' . $this->scheme);
237         return false;
238       }
239     }
240     
241     /**
242     * send the SOAP message via HTTP
243     *
244     * @param    string $data message data
245     * @param    integer $timeout set connection timeout in seconds
246     * @param    integer $response_timeout set response timeout in seconds
247     * @param    array $cookies cookies to send
248     * @return    string data
249     * @access   public
250     */
251     function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) {
252         
253         $this->debug('entered send() with data of length: '.strlen($data));
254
255         $this->tryagain = true;
256         $tries = 0;
257         while ($this->tryagain) {
258             $this->tryagain = false;
259             if ($tries++ < 2) {
260                 // make connnection
261                 if (!$this->connect($timeout, $response_timeout)){
262                     return false;
263                 }
264                 
265                 // send request
266                 if (!$this->sendRequest($data, $cookies)){
267                     return false;
268                 }
269                 
270                 // get response
271                 $respdata = $this->getResponse();
272             } else {
273                 $this->setError('Too many tries to get an OK response');
274             }
275         }       
276         $this->debug('end of send()');
277         return $respdata;
278     }
279
280
281     /**
282     * send the SOAP message via HTTPS 1.0 using CURL
283     *
284     * @param    string $msg message data
285     * @param    integer $timeout set connection timeout in seconds
286     * @param    integer $response_timeout set response timeout in seconds
287     * @param    array $cookies cookies to send
288     * @return    string data
289     * @access   public
290     */
291     function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) {
292         return $this->send($data, $timeout, $response_timeout, $cookies);
293     }
294     
295     /**
296     * if authenticating, set user credentials here
297     *
298     * @param    string $username
299     * @param    string $password
300     * @param    string $authtype (basic, digest, certificate)
301     * @param    array $digestRequest (keys must be nonce, nc, realm, qop)
302     * @param    array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
303     * @access   public
304     */
305     function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) {
306         $this->debug("Set credentials for authtype $authtype");
307         // cf. RFC 2617
308         if ($authtype == 'basic') {
309             $this->outgoing_headers['Authorization'] = 'Basic '.base64_encode(str_replace(':','',$username).':'.$password);
310         } elseif ($authtype == 'digest') {
311             if (isset($digestRequest['nonce'])) {
312                 $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1;
313                 
314                 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
315     
316                 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
317                 $A1 = $username. ':' . (isset($digestRequest['realm']) ? $digestRequest['realm'] : '') . ':' . $password;
318     
319                 // H(A1) = MD5(A1)
320                 $HA1 = md5($A1);
321     
322                 // A2 = Method ":" digest-uri-value
323                 $A2 = 'POST:' . $this->digest_uri;
324     
325                 // H(A2)
326                 $HA2 md5($A2);
327     
328                 // KD(secret, data) = H(concat(secret, ":", data))
329                 // if qop == auth:
330                 // request-digest  = <"> < KD ( H(A1),     unq(nonce-value)
331                 //                              ":" nc-value
332                 //                              ":" unq(cnonce-value)
333                 //                              ":" unq(qop-value)
334                 //                              ":" H(A2)
335                 //                            ) <">
336                 // if qop is missing,
337                 // request-digest  = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
338     
339                 $unhashedDigest = '';
340                 $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : '';
341                 $cnonce = $nonce;
342                 if ($digestRequest['qop'] != '') {
343                     $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
344                 } else {
345                     $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
346                 }
347     
348                 $hashedDigest = md5($unhashedDigest);
349     
350                 $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 . '"';
351             }
352         } elseif ($authtype == 'certificate') {
353             $this->certRequest = $certRequest;
354         }
355         $this->username = $username;
356         $this->password = $password;
357         $this->authtype = $authtype;
358         $this->digestRequest = $digestRequest;
359         
360         if (isset($this->outgoing_headers['Authorization'])) {
361             $this->debug('set Authorization: ' . substr($this->outgoing_headers['Authorization'], 0, 12) . '...');
362         } else {
363             $this->debug('Authorization header not set');
364         }
365     }
366     
367     /**
368     * set the soapaction value
369     *
370     * @param    string $soapaction
371     * @access   public
372     */
373     function setSOAPAction($soapaction) {
374         $this->outgoing_headers['SOAPAction'] = '"' . $soapaction . '"';
375         $this->debug('set SOAPAction: ' . $this->outgoing_headers['SOAPAction']);
376     }
377     
378     /**
379     * use http encoding
380     *
381     * @param    string $enc encoding style. supported values: gzip, deflate, or both
382     * @access   public
383     */
384     function setEncoding($enc='gzip, deflate') {
385         if (function_exists('gzdeflate')) {
386             $this->protocol_version = '1.1';
387             $this->outgoing_headers['Accept-Encoding'] = $enc;
388             $this->debug('set Accept-Encoding: ' . $this->outgoing_headers['Accept-Encoding']);
389             if (!isset($this->outgoing_headers['Connection'])) {
390                 $this->outgoing_headers['Connection'] = 'close';
391                 $this->persistentConnection = false;
392                 $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
393             }
394             set_magic_quotes_runtime(0);
395             // deprecated
396             $this->encoding = $enc;
397         }
398     }
399     
400     /**
401     * set proxy info here
402     *
403     * @param    string $proxyhost
404     * @param    string $proxyport
405     * @param    string $proxyusername
406     * @param    string $proxypassword
407     * @access   public
408     */
409     function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
410         $this->uri = $this->url;
411         $this->host = $proxyhost;
412         $this->port = $proxyport;
413         if ($proxyusername != '' && $proxypassword != '') {
414             $this->outgoing_headers['Proxy-Authorization'] = ' Basic '.base64_encode($proxyusername.':'.$proxypassword);
415             $this->debug('set Proxy-Authorization: ' . $this->outgoing_headers['Proxy-Authorization']);
416         }
417     }
418     
419     /**
420     * decode a string that is encoded w/ "chunked' transfer encoding
421      * as defined in RFC2068 19.4.6
422     *
423     * @param    string $buffer
424     * @param    string $lb
425     * @returns    string
426     * @access   public
427     * @deprecated
428     */
429     function decodeChunked($buffer, $lb){
430         // length := 0
431         $length = 0;
432         $new = '';
433         
434         // read chunk-size, chunk-extension (if any) and CRLF
435         // get the position of the linebreak
436         $chunkend = strpos($buffer, $lb);
437         if ($chunkend == FALSE) {
438             $this->debug('no linebreak found in decodeChunked');
439             return $new;
440         }
441         $temp = substr($buffer,0,$chunkend);
442         $chunk_size = hexdec( trim($temp) );
443         $chunkstart = $chunkend + strlen($lb);
444         // while (chunk-size > 0) {
445         while ($chunk_size > 0) {
446             $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
447             $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size);
448               
449             // Just in case we got a broken connection
450               if ($chunkend == FALSE) {
451                   $chunk = substr($buffer,$chunkstart);
452                 // append chunk-data to entity-body
453                 $new .= $chunk;
454                   $length += strlen($chunk);
455                   break;
456             }
457             
458               // read chunk-data and CRLF
459               $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
460               // append chunk-data to entity-body
461               $new .= $chunk;
462               // length := length + chunk-size
463               $length += strlen($chunk);
464               // read chunk-size and CRLF
465               $chunkstart = $chunkend + strlen($lb);
466             
467               $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb);
468             if ($chunkend == FALSE) {
469                 break; //Just in case we got a broken connection
470             }
471             $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
472             $chunk_size = hexdec( trim($temp) );
473             $chunkstart = $chunkend;
474         }
475         return $new;
476     }
477     
478     /*
479      *    Writes payload, including HTTP headers, to $this->outgoing_payload.
480      */
481     function buildPayload($data, $cookie_str = '') {
482         // add content-length header
483         $this->outgoing_headers['Content-Length'] = strlen($data);
484         $this->debug('set Content-Length: ' . $this->outgoing_headers['Content-Length']);
485
486         // start building outgoing payload:
487         $req = "$this->request_method $this->uri HTTP/$this->protocol_version";
488         $this->debug("HTTP request: $req");
489         $this->outgoing_payload = "$req\r\n";
490
491         // loop thru headers, serializing
492         foreach($this->outgoing_headers as $k => $v){
493             $hdr = $k.': '.$v;
494             $this->debug("HTTP header: $hdr");
495             $this->outgoing_payload .= "$hdr\r\n";
496         }
497
498         // add any cookies
499         if ($cookie_str != '') {
500             $hdr = 'Cookie: '.$cookie_str;
501             $this->debug("HTTP header: $hdr");
502             $this->outgoing_payload .= "$hdr\r\n";
503         }
504
505         // header/body separator
506         $this->outgoing_payload .= "\r\n";
507         
508         // add data
509         $this->outgoing_payload .= $data;
510     }
511
512     function sendRequest($data, $cookies = NULL) {
513         // build cookie string
514         $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https')));
515
516         // build payload
517         $this->buildPayload($data, $cookie_str);
518
519       if ($this->scheme == 'http' || $this->scheme == 'ssl') {
520         // send payload
521         if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
522             $this->setError('couldn\'t write message data to socket');
523             $this->debug('couldn\'t write message data to socket');
524             return false;
525         }
526         $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload));
527         return true;
528       } else if ($this->scheme == 'https') {
529         // set payload
530         // TODO: cURL does say this should only be the verb, and in fact it
531         // turns out that the URI and HTTP version are appended to this, which
532         // some servers refuse to work with
533         //curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
534         foreach($this->outgoing_headers as $k => $v){
535             $curl_headers[] = "$k: $v";
536         }
537         if ($cookie_str != '') {
538             $curl_headers[] = 'Cookie: ' . $cookie_str;
539         }
540         curl_setopt($this->ch, CURLOPT_HTTPHEADER, $curl_headers);
541         if ($this->request_method == "POST") {
542               curl_setopt($this->ch, CURLOPT_POST, 1);
543               curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data);
544           } else {
545           }
546         $this->debug('set cURL payload');
547         return true;
548       }
549     }
550
551     function getResponse(){
552         $this->incoming_payload = '';
553         
554       if ($this->scheme == 'http' || $this->scheme == 'ssl') {
555         // loop until headers have been retrieved
556         $data = '';
557         while (!isset($lb)){
558
559             // We might EOF during header read.
560             if(feof($this->fp)) {
561                 $this->incoming_payload = $data;
562                 $this->debug('found no headers before EOF after length ' . strlen($data));
563                 $this->debug("received before EOF:\n" . $data);
564                 $this->setError('server failed to send headers');
565                 return false;
566             }
567
568             $tmp = fgets($this->fp, 256);
569             $tmplen = strlen($tmp);
570             $this->debug("read line of $tmplen bytes: " . trim($tmp));
571
572             if ($tmplen == 0) {
573                 $this->incoming_payload = $data;
574                 $this->debug('socket read of headers timed out after length ' . strlen($data));
575                 $this->debug("read before timeout: " . $data);
576                 $this->setError('socket read of headers timed out');
577                 return false;
578             }
579
580             $data .= $tmp;
581             $pos = strpos($data,"\r\n\r\n");
582             if($pos > 1){
583                 $lb = "\r\n";
584             } else {
585                 $pos = strpos($data,"\n\n");
586                 if($pos > 1){
587                     $lb = "\n";
588                 }
589             }
590             // remove 100 header
591             if(isset($lb) && ereg('^HTTP/1.1 100',$data)){
592                 unset($lb);
593                 $data = '';
594             }//
595         }
596         // store header data
597         $this->incoming_payload .= $data;
598         $this->debug('found end of headers after length ' . strlen($data));
599         // process headers
600         $header_data = trim(substr($data,0,$pos));
601         $header_array = explode($lb,$header_data);
602         $this->incoming_headers = array();
603         $this->incoming_cookies = array();
604         foreach($header_array as $header_line){
605             $arr = explode(':',$header_line, 2);
606             if(count($arr) > 1){
607                 $header_name = strtolower(trim($arr[0]));
608                 $this->incoming_headers[$header_name] = trim($arr[1]);
609                 if ($header_name == 'set-cookie') {
610                     // TODO: allow multiple cookies from parseCookie
611                     $cookie = $this->parseCookie(trim($arr[1]));
612                     if ($cookie) {
613                         $this->incoming_cookies[] = $cookie;
614                         $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
615                     } else {
616                         $this->debug('did not find cookie in ' . trim($arr[1]));
617                     }
618                 }
619             } else if (isset($header_name)) {
620                 // append continuation line to previous header
621                 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
622             }
623         }
624         
625         // loop until msg has been received
626         if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') {
627             $content_length 2147483647;    // ignore any content-length header
628             $chunked = true;
629             $this->debug("want to read chunked content");
630         } elseif (isset($this->incoming_headers['content-length'])) {
631             $content_length = $this->incoming_headers['content-length'];
632             $chunked = false;
633             $this->debug("want to read content of length $content_length");
634         } else {
635             $content_length 2147483647;
636             $chunked = false;
637             $this->debug("want to read content to EOF");
638         }
639         $data = '';
640         do {
641             if ($chunked) {
642                 $tmp = fgets($this->fp, 256);
643                 $tmplen = strlen($tmp);
644                 $this->debug("read chunk line of $tmplen bytes");
645                 if ($tmplen == 0) {
646                     $this->incoming_payload = $data;
647                     $this->debug('socket read of chunk length timed out after length ' . strlen($data));
648                     $this->debug("read before timeout:\n" . $data);
649                     $this->setError('socket read of chunk length timed out');
650                     return false;
651                 }
652                 $content_length = hexdec(trim($tmp));
653                 $this->debug("chunk length $content_length");
654             }
655             $strlen = 0;
656             while (($strlen < $content_length) && (!feof($this->fp))) {
657                 $readlen = min(8192, $content_length - $strlen);
658                 $tmp = fread($this->fp, $readlen);
659                 $tmplen = strlen($tmp);
660                 $this->debug("read buffer of $tmplen bytes");
661                 if (($tmplen == 0) && (!feof($this->fp))) {
662                     $this->incoming_payload = $data;
663                     $this->debug('socket read of body timed out after length ' . strlen($data));
664                     $this->debug("read before timeout:\n" . $data);
665                     $this->setError('socket read of body timed out');
666                     return false;
667                 }
668                 $strlen += $tmplen;
669                 $data .= $tmp;
670             }
671             if ($chunked && ($content_length > 0)) {
672                 $tmp = fgets($this->fp, 256);
673                 $tmplen = strlen($tmp);
674                 $this->debug("read chunk terminator of $tmplen bytes");
675                 if ($tmplen == 0) {
676                     $this->incoming_payload = $data;
677                     $this->debug('socket read of chunk terminator timed out after length ' . strlen($data));
678                     $this->debug("read before timeout:\n" . $data);
679                     $this->setError('socket read of chunk terminator timed out');
680                     return false;
681                 }
682             }
683         } while ($chunked && ($content_length > 0) && (!feof($this->fp)));
684         if (feof($this->fp)) {
685             $this->debug('read to EOF');
686         }
687         $this->debug('read body of length ' . strlen($data));
688         $this->incoming_payload .= $data;
689         $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server');
690         
691         // close filepointer
692         if(
693             (isset($this->incoming_headers['connection']) && strtolower($this->incoming_headers['connection']) == 'close') ||
694             (! $this->persistentConnection) || feof($this->fp)){
695             fclose($this->fp);
696             $this->fp = false;
697             $this->debug('closed socket');
698         }
699         
700         // connection was closed unexpectedly
701         if($this->incoming_payload == ''){
702             $this->setError('no response from server');
703             return false;
704         }
705         
706         // decode transfer-encoding
707 //        if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
708 //            if(!$data = $this->decodeChunked($data, $lb)){
709 //                $this->setError('Decoding of chunked data failed');
710 //                return false;
711 //            }
712             //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
713             // set decoded payload
714 //            $this->incoming_payload = $header_data.$lb.$lb.$data;
715 //        }
716     
717       } else if ($this->scheme == 'https') {
718         // send and receive
719         $this->debug('send and receive with cURL');
720         $this->incoming_payload = curl_exec($this->ch);
721         $data = $this->incoming_payload;
722
723         $cErr = curl_error($this->ch);
724         if ($cErr != '') {
725             $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'<br>';
726             // TODO: there is a PHP bug that can cause this to SEGV for CURLINFO_CONTENT_TYPE
727             foreach(curl_getinfo($this->ch) as $k => $v){
728                 $err .= "$k: $v<br>";
729             }
730             $this->debug($err);
731             $this->setError($err);
732             curl_close($this->ch);
733             return false;
734         } else {
735             //echo '<pre>';
736             //var_dump(curl_getinfo($this->ch));
737             //echo '</pre>';
738         }
739         // close curl
740         $this->debug('No cURL error, closing cURL');
741         curl_close($this->ch);
742         
743         // remove 100 header(s)
744         while (ereg('^HTTP/1.1 100',$data)) {
745             if ($pos = strpos($data,"\r\n\r\n")) {
746                 $data = ltrim(substr($data,$pos));
747             } elseif($pos = strpos($data,"\n\n") ) {
748                 $data = ltrim(substr($data,$pos));
749             }
750         }
751         
752         // separate content from HTTP headers
753         if ($pos = strpos($data,"\r\n\r\n")) {
754             $lb = "\r\n";
755         } elseif( $pos = strpos($data,"\n\n")) {
756             $lb = "\n";
757         } else {
758             $this->debug('no proper separation of headers and document');
759             $this->setError('no proper separation of headers and document');
760             return false;
761         }
762         $header_data = trim(substr($data,0,$pos));
763         $header_array = explode($lb,$header_data);
764         $data = ltrim(substr($data,$pos));
765         $this->debug('found proper separation of headers and document');
766         $this->debug('cleaned data, stringlen: '.strlen($data));
767         // clean headers
768         foreach ($header_array as $header_line) {
769             $arr = explode(':',$header_line,2);
770             if(count($arr) > 1){
771                 $header_name = strtolower(trim($arr[0]));
772                 $this->incoming_headers[$header_name] = trim($arr[1]);
773                 if ($header_name == 'set-cookie') {
774                     // TODO: allow multiple cookies from parseCookie
775                     $cookie = $this->parseCookie(trim($arr[1]));
776                     if ($cookie) {
777                         $this->incoming_cookies[] = $cookie;
778                         $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
779                     } else {
780                         $this->debug('did not find cookie in ' . trim($arr[1]));
781                     }
782                 }
783             } else if (isset($header_name)) {
784                 // append continuation line to previous header
785                 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
786             }
787         }
788       }
789
790         $arr = explode(' ', $header_array[0], 3);
791         $http_version = $arr[0];
792         $http_status = intval($arr[1]);
793         $http_reason = count($arr) > 2 ? $arr[2] : '';
794
795          // see if we need to resend the request with http digest authentication
796          if (isset($this->incoming_headers['location']) && $http_status == 301) {
797              $this->debug("Got 301 $http_reason with Location: " . $this->incoming_headers['location']);
798              $this->setURL($this->incoming_headers['location']);
799             $this->tryagain = true;
800             return false;
801         }
802
803          // see if we need to resend the request with http digest authentication
804          if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) {
805              $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']);
806              if (strstr($this->incoming_headers['www-authenticate'], "Digest ")) {
807                  $this->debug('Server wants digest authentication');
808                  // remove "Digest " from our elements
809                  $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']);
810                 
811                  // parse elements into array
812                  $digestElements = explode(',', $digestString);
813                  foreach ($digestElements as $val) {
814                      $tempElement = explode('=', trim($val), 2);
815                      $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
816                  }
817
818                 // should have (at least) qop, realm, nonce
819                  if (isset($digestRequest['nonce'])) {
820                      $this->setCredentials($this->username, $this->password, 'digest', $digestRequest);
821                      $this->tryagain = true;
822                      return false;
823                  }
824              }
825             $this->debug('HTTP authentication failed');
826             $this->setError('HTTP authentication failed');
827             return false;
828          }
829         
830         if (
831             ($http_status >= 300 && $http_status <= 307) ||
832             ($http_status >= 400 && $http_status <= 417) ||
833             ($http_status >= 501 && $http_status <= 505)
834            ) {
835             $this->setError("Unsupported HTTP response status $http_status $http_reason (soapclient->response has contents of the response)");
836             return false;
837         }
838
839         // decode content-encoding
840         if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){
841             if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){
842                 // if decoding works, use it. else assume data wasn't gzencoded
843                 if(function_exists('gzinflate')){
844                     //$timer->setMarker('starting decoding of gzip/deflated content');
845                     // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress)
846                     // this means there are no Zlib headers, although there should be
847                     $this->debug('The gzinflate function exists');
848                     $datalen = strlen($data);
849                     if ($this->incoming_headers['content-encoding'] == 'deflate') {
850                         if ($degzdata = @gzinflate($data)) {
851                             $data = $degzdata;
852                             $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes');
853                             if (strlen($data) < $datalen) {
854                                 // test for the case that the payload has been compressed twice
855                                 $this->debug('The inflated payload is smaller than the gzipped one; try again');
856                                 if ($degzdata = @gzinflate($data)) {
857                                     $data = $degzdata;
858                                     $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes');
859                                 }
860                             }
861                         } else {
862                             $this->debug('Error using gzinflate to inflate the payload');
863                             $this->setError('Error using gzinflate to inflate the payload');
864                         }
865                     } elseif ($this->incoming_headers['content-encoding'] == 'gzip') {
866                         if ($degzdata = @gzinflate(substr($data, 10))) {    // do our best
867                             $data = $degzdata;
868                             $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes');
869                             if (strlen($data) < $datalen) {
870                                 // test for the case that the payload has been compressed twice
871                                 $this->debug('The un-gzipped payload is smaller than the gzipped one; try again');
872                                 if ($degzdata = @gzinflate(substr($data, 10))) {
873                                     $data = $degzdata;
874                                     $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes');
875                                 }
876                             }
877                         } else {
878                             $this->debug('Error using gzinflate to un-gzip the payload');
879                             $this->setError('Error using gzinflate to un-gzip the payload');
880                         }
881                     }
882                     //$timer->setMarker('finished decoding of gzip/deflated content');
883                     //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
884                     // set decoded payload
885                     $this->incoming_payload = $header_data.$lb.$lb.$data;
886                 } else {
887                     $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
888                     $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
889                 }
890             } else {
891                 $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
892                 $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
893             }
894         } else {
895             $this->debug('No Content-Encoding header');
896         }
897         
898         if(strlen($data) == 0){
899             $this->debug('no data after headers!');
900             $this->setError('no data present after HTTP headers');
901             return false;
902         }
903         
904         return $data;
905     }
906
907     function setContentType($type, $charset = false) {
908         $this->outgoing_headers['Content-Type'] = $type . ($charset ? '; charset=' . $charset : '');
909         $this->debug('set Content-Type: ' . $this->outgoing_headers['Content-Type']);
910     }
911
912     function usePersistentConnection(){
913         if (isset($this->outgoing_headers['Accept-Encoding'])) {
914             return false;
915         }
916         $this->protocol_version = '1.1';
917         $this->persistentConnection = true;
918         $this->outgoing_headers['Connection'] = 'Keep-Alive';
919         $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
920         return true;
921     }
922
923     /**
924      * parse an incoming Cookie into it's parts
925      *
926      * @param    string $cookie_str content of cookie
927      * @return    array with data of that cookie
928      * @access    private
929      */
930     /*
931      * TODO: allow a Set-Cookie string to be parsed into multiple cookies
932      */
933     function parseCookie($cookie_str) {
934         $cookie_str = str_replace('; ', ';', $cookie_str) . ';';
935         $data = split(';', $cookie_str);
936         $value_str = $data[0];
937
938         $cookie_param = 'domain=';
939         $start = strpos($cookie_str, $cookie_param);
940         if ($start > 0) {
941             $domain = substr($cookie_str, $start + strlen($cookie_param));
942             $domain = substr($domain, 0, strpos($domain, ';'));
943         } else {
944             $domain = '';
945         }
946
947         $cookie_param = 'expires=';
948         $start = strpos($cookie_str, $cookie_param);
949         if ($start > 0) {
950             $expires = substr($cookie_str, $start + strlen($cookie_param));
951             $expires = substr($expires, 0, strpos($expires, ';'));
952         } else {
953             $expires = '';
954         }
955
956         $cookie_param = 'path=';
957         $start = strpos($cookie_str, $cookie_param);
958         if ( $start > 0 ) {
959             $path = substr($cookie_str, $start + strlen($cookie_param));
960             $path = substr($path, 0, strpos($path, ';'));
961         } else {
962             $path = '/';
963         }
964                         
965         $cookie_param = ';secure;';
966         if (strpos($cookie_str, $cookie_param) !== FALSE) {
967             $secure = true;
968         } else {
969             $secure = false;
970         }
971
972         $sep_pos = strpos($value_str, '=');
973
974         if ($sep_pos) {
975             $name = substr($value_str, 0, $sep_pos);
976             $value = substr($value_str, $sep_pos + 1);
977             $cookie= array(    'name' => $name,
978                             'value' => $value,
979                             'domain' => $domain,
980                             'path' => $path,
981                             'expires' => $expires,
982                             'secure' => $secure
983                             );       
984             return $cookie;
985         }
986         return false;
987     }
988  
989     /**
990      * sort out cookies for the current request
991      *
992      * @param    array $cookies array with all cookies
993      * @param    boolean $secure is the send-content secure or not?
994      * @return    string for Cookie-HTTP-Header
995      * @access    private
996      */
997     function getCookiesForRequest($cookies, $secure=false) {
998         $cookie_str = '';
999         if ((! is_null($cookies)) && (is_array($cookies))) {
1000             foreach ($cookies as $cookie) {
1001                 if (! is_array($cookie)) {
1002                     continue;
1003                 }
1004                 $this->debug("check cookie for validity: ".$cookie['name'].'='.$cookie['value']);
1005                 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
1006                     if (strtotime($cookie['expires']) <= time()) {
1007                         $this->debug('cookie has expired');
1008                         continue;
1009                     }
1010                 }
1011                 if ((isset($cookie['domain'])) && (! empty($cookie['domain']))) {
1012                     $domain = preg_quote($cookie['domain']);
1013                     if (! preg_match("'.*$domain$'i", $this->host)) {
1014                         $this->debug('cookie has different domain');
1015                         continue;
1016                     }
1017                 }
1018                 if ((isset($cookie['path'])) && (! empty($cookie['path']))) {
1019                     $path = preg_quote($cookie['path']);
1020                     if (! preg_match("'^$path.*'i", $this->path)) {
1021                         $this->debug('cookie is for a different path');
1022                         continue;
1023                     }
1024                 }
1025                 if ((! $secure) && (isset($cookie['secure'])) && ($cookie['secure'])) {
1026                     $this->debug('cookie is secure, transport is not');
1027                     continue;
1028                 }
1029                 $cookie_str .= $cookie['name'] . '=' . $cookie['value'] . '; ';
1030                 $this->debug('add cookie to Cookie-String: ' . $cookie['name'] . '=' . $cookie['value']);
1031             }
1032         }
1033         return $cookie_str;
1034   }
1035 }
1036
1037
1038 ?>
Note: See TracBrowser for help on using the browser.