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