|
Support Incident Tracker GIT4.x
|
00001 <?php 00002 00003 00004 00005 00015 class soap_transport_http extends nusoap_base { 00016 00017 var $url = ''; 00018 var $uri = ''; 00019 var $digest_uri = ''; 00020 var $scheme = ''; 00021 var $host = ''; 00022 var $port = ''; 00023 var $path = ''; 00024 var $request_method = 'POST'; 00025 var $protocol_version = '1.0'; 00026 var $encoding = ''; 00027 var $outgoing_headers = array(); 00028 var $incoming_headers = array(); 00029 var $incoming_cookies = array(); 00030 var $outgoing_payload = ''; 00031 var $incoming_payload = ''; 00032 var $response_status_line; // HTTP response status line 00033 var $useSOAPAction = true; 00034 var $persistentConnection = false; 00035 var $ch = false; // cURL handle 00036 var $ch_options = array(); // cURL custom options 00037 var $use_curl = false; // force cURL use 00038 var $proxy = null; // proxy information (associative array) 00039 var $username = ''; 00040 var $password = ''; 00041 var $authtype = ''; 00042 var $digestRequest = array(); 00043 var $certRequest = array(); // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional) 00044 // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem' 00045 // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem' 00046 // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem' 00047 // passphrase: SSL key password/passphrase 00048 // certpassword: SSL certificate password 00049 // verifypeer: default is 1 00050 // verifyhost: default is 1 00051 00060 function soap_transport_http($url, $curl_options = NULL, $use_curl = false){ 00061 parent::nusoap_base(); 00062 $this->debug("ctor url=$url use_curl=$use_curl curl_options:"); 00063 $this->appendDebug($this->varDump($curl_options)); 00064 $this->setURL($url); 00065 if (is_array($curl_options)) { 00066 $this->ch_options = $curl_options; 00067 } 00068 $this->use_curl = $use_curl; 00069 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev); 00070 $this->setHeader('User-Agent', $this->title.'/'.$this->version.' ('.$rev[1].')'); 00071 } 00072 00080 function setCurlOption($option, $value) { 00081 $this->debug("setCurlOption option=$option, value="); 00082 $this->appendDebug($this->varDump($value)); 00083 curl_setopt($this->ch, $option, $value); 00084 } 00085 00093 function setHeader($name, $value) { 00094 $this->outgoing_headers[$name] = $value; 00095 $this->debug("set header $name: $value"); 00096 } 00097 00104 function unsetHeader($name) { 00105 if (isset($this->outgoing_headers[$name])) { 00106 $this->debug("unset header $name"); 00107 unset($this->outgoing_headers[$name]); 00108 } 00109 } 00110 00117 function setURL($url) { 00118 $this->url = $url; 00119 00120 $u = parse_url($url); 00121 foreach($u as $k => $v){ 00122 $this->debug("parsed URL $k = $v"); 00123 $this->$k = $v; 00124 } 00125 00126 // add any GET params to path 00127 if(isset($u['query']) && $u['query'] != ''){ 00128 $this->path .= '?' . $u['query']; 00129 } 00130 00131 // set default port 00132 if(!isset($u['port'])){ 00133 if($u['scheme'] == 'https'){ 00134 $this->port = 443; 00135 } else { 00136 $this->port = 80; 00137 } 00138 } 00139 00140 $this->uri = $this->path; 00141 $this->digest_uri = $this->uri; 00142 00143 // build headers 00144 if (!isset($u['port'])) { 00145 $this->setHeader('Host', $this->host); 00146 } else { 00147 $this->setHeader('Host', $this->host.':'.$this->port); 00148 } 00149 00150 if (isset($u['user']) && $u['user'] != '') { 00151 $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : ''); 00152 } 00153 } 00154 00161 function io_method() { 00162 if ($this->use_curl || ($this->scheme == 'https') || ($this->scheme == 'http' && $this->authtype == 'ntlm') || ($this->scheme == 'http' && is_array($this->proxy) && $this->proxy['authtype'] == 'ntlm')) 00163 return 'curl'; 00164 if (($this->scheme == 'http' || $this->scheme == 'ssl') && $this->authtype != 'ntlm' && (!is_array($this->proxy) || $this->proxy['authtype'] != 'ntlm')) 00165 return 'socket'; 00166 return 'unknown'; 00167 } 00168 00177 function connect($connection_timeout=0,$response_timeout=30){ 00178 // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like 00179 // "regular" socket. 00180 // TODO: disabled for now because OpenSSL must be *compiled* in (not just 00181 // loaded), and until PHP5 stream_get_wrappers is not available. 00182 // if ($this->scheme == 'https') { 00183 // if (version_compare(phpversion(), '4.3.0') >= 0) { 00184 // if (extension_loaded('openssl')) { 00185 // $this->scheme = 'ssl'; 00186 // $this->debug('Using SSL over OpenSSL'); 00187 // } 00188 // } 00189 // } 00190 $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port"); 00191 if ($this->io_method() == 'socket') { 00192 if (!is_array($this->proxy)) { 00193 $host = $this->host; 00194 $port = $this->port; 00195 } else { 00196 $host = $this->proxy['host']; 00197 $port = $this->proxy['port']; 00198 } 00199 00200 // use persistent connection 00201 if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){ 00202 if (!feof($this->fp)) { 00203 $this->debug('Re-use persistent connection'); 00204 return true; 00205 } 00206 fclose($this->fp); 00207 $this->debug('Closed persistent connection at EOF'); 00208 } 00209 00210 // munge host if using OpenSSL 00211 if ($this->scheme == 'ssl') { 00212 $host = 'ssl://' . $host; 00213 } 00214 $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout); 00215 00216 // open socket 00217 if($connection_timeout > 0){ 00218 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout); 00219 } else { 00220 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str); 00221 } 00222 00223 // test pointer 00224 if(!$this->fp) { 00225 $msg = 'Couldn\'t open socket connection to server ' . $this->url; 00226 if ($this->errno) { 00227 $msg .= ', Error ('.$this->errno.'): '.$this->error_str; 00228 } else { 00229 $msg .= ' prior to connect(). This is often a problem looking up the host name.'; 00230 } 00231 $this->debug($msg); 00232 $this->setError($msg); 00233 return false; 00234 } 00235 00236 // set response timeout 00237 $this->debug('set response timeout to ' . $response_timeout); 00238 socket_set_timeout( $this->fp, $response_timeout); 00239 00240 $this->debug('socket connected'); 00241 return true; 00242 } else if ($this->io_method() == 'curl') { 00243 if (!extension_loaded('curl')) { 00244 // $this->setError('cURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS'); 00245 $this->setError('The PHP cURL Extension is required for HTTPS or NLTM. You will need to re-build or update your PHP to included cURL.'); 00246 return false; 00247 } 00248 // Avoid warnings when PHP does not have these options 00249 if (defined('CURLOPT_CONNECTIONTIMEOUT')) 00250 $CURLOPT_CONNECTIONTIMEOUT = CURLOPT_CONNECTIONTIMEOUT; 00251 else 00252 $CURLOPT_CONNECTIONTIMEOUT = 78; 00253 if (defined('CURLOPT_HTTPAUTH')) 00254 $CURLOPT_HTTPAUTH = CURLOPT_HTTPAUTH; 00255 else 00256 $CURLOPT_HTTPAUTH = 107; 00257 if (defined('CURLOPT_PROXYAUTH')) 00258 $CURLOPT_PROXYAUTH = CURLOPT_PROXYAUTH; 00259 else 00260 $CURLOPT_PROXYAUTH = 111; 00261 if (defined('CURLAUTH_BASIC')) 00262 $CURLAUTH_BASIC = CURLAUTH_BASIC; 00263 else 00264 $CURLAUTH_BASIC = 1; 00265 if (defined('CURLAUTH_DIGEST')) 00266 $CURLAUTH_DIGEST = CURLAUTH_DIGEST; 00267 else 00268 $CURLAUTH_DIGEST = 2; 00269 if (defined('CURLAUTH_NTLM')) 00270 $CURLAUTH_NTLM = CURLAUTH_NTLM; 00271 else 00272 $CURLAUTH_NTLM = 8; 00273 00274 $this->debug('connect using cURL'); 00275 // init CURL 00276 $this->ch = curl_init(); 00277 // set url 00278 $hostURL = ($this->port != '') ? "$this->scheme://$this->host:$this->port" : "$this->scheme://$this->host"; 00279 // add path 00280 $hostURL .= $this->path; 00281 $this->setCurlOption(CURLOPT_URL, $hostURL); 00282 // follow location headers (re-directs) 00283 if (ini_get('safe_mode') || ini_get('open_basedir')) { 00284 $this->debug('safe_mode or open_basedir set, so do not set CURLOPT_FOLLOWLOCATION'); 00285 $this->debug('safe_mode = '); 00286 $this->appendDebug($this->varDump(ini_get('safe_mode'))); 00287 $this->debug('open_basedir = '); 00288 $this->appendDebug($this->varDump(ini_get('open_basedir'))); 00289 } else { 00290 $this->setCurlOption(CURLOPT_FOLLOWLOCATION, 1); 00291 } 00292 // ask for headers in the response output 00293 $this->setCurlOption(CURLOPT_HEADER, 1); 00294 // ask for the response output as the return value 00295 $this->setCurlOption(CURLOPT_RETURNTRANSFER, 1); 00296 // encode 00297 // We manage this ourselves through headers and encoding 00298 // if(function_exists('gzuncompress')){ 00299 // $this->setCurlOption(CURLOPT_ENCODING, 'deflate'); 00300 // } 00301 // persistent connection 00302 if ($this->persistentConnection) { 00303 // I believe the following comment is now bogus, having applied to 00304 // the code when it used CURLOPT_CUSTOMREQUEST to send the request. 00305 // The way we send data, we cannot use persistent connections, since 00306 // there will be some "junk" at the end of our request. 00307 //$this->setCurlOption(CURL_HTTP_VERSION_1_1, true); 00308 $this->persistentConnection = false; 00309 $this->setHeader('Connection', 'close'); 00310 } 00311 // set timeouts 00312 if ($connection_timeout != 0) { 00313 $this->setCurlOption($CURLOPT_CONNECTIONTIMEOUT, $connection_timeout); 00314 } 00315 if ($response_timeout != 0) { 00316 $this->setCurlOption(CURLOPT_TIMEOUT, $response_timeout); 00317 } 00318 00319 if ($this->scheme == 'https') { 00320 $this->debug('set cURL SSL verify options'); 00321 // recent versions of cURL turn on peer/host checking by default, 00322 // while PHP binaries are not compiled with a default location for the 00323 // CA cert bundle, so disable peer/host checking. 00324 //$this->setCurlOption(CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt'); 00325 $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, 0); 00326 $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, 0); 00327 00328 // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo) 00329 if ($this->authtype == 'certificate') { 00330 $this->debug('set cURL certificate options'); 00331 if (isset($this->certRequest['cainfofile'])) { 00332 $this->setCurlOption(CURLOPT_CAINFO, $this->certRequest['cainfofile']); 00333 } 00334 if (isset($this->certRequest['verifypeer'])) { 00335 $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']); 00336 } else { 00337 $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, 1); 00338 } 00339 if (isset($this->certRequest['verifyhost'])) { 00340 $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']); 00341 } else { 00342 $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, 1); 00343 } 00344 if (isset($this->certRequest['sslcertfile'])) { 00345 $this->setCurlOption(CURLOPT_SSLCERT, $this->certRequest['sslcertfile']); 00346 } 00347 if (isset($this->certRequest['sslkeyfile'])) { 00348 $this->setCurlOption(CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']); 00349 } 00350 if (isset($this->certRequest['passphrase'])) { 00351 $this->setCurlOption(CURLOPT_SSLKEYPASSWD, $this->certRequest['passphrase']); 00352 } 00353 if (isset($this->certRequest['certpassword'])) { 00354 $this->setCurlOption(CURLOPT_SSLCERTPASSWD, $this->certRequest['certpassword']); 00355 } 00356 } 00357 } 00358 if ($this->authtype && ($this->authtype != 'certificate')) { 00359 if ($this->username) { 00360 $this->debug('set cURL username/password'); 00361 $this->setCurlOption(CURLOPT_USERPWD, "$this->username:$this->password"); 00362 } 00363 if ($this->authtype == 'basic') { 00364 $this->debug('set cURL for Basic authentication'); 00365 $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_BASIC); 00366 } 00367 if ($this->authtype == 'digest') { 00368 $this->debug('set cURL for digest authentication'); 00369 $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_DIGEST); 00370 } 00371 if ($this->authtype == 'ntlm') { 00372 $this->debug('set cURL for NTLM authentication'); 00373 $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_NTLM); 00374 } 00375 } 00376 if (is_array($this->proxy)) { 00377 $this->debug('set cURL proxy options'); 00378 if ($this->proxy['port'] != '') { 00379 $this->setCurlOption(CURLOPT_PROXY, $this->proxy['host'].':'.$this->proxy['port']); 00380 } else { 00381 $this->setCurlOption(CURLOPT_PROXY, $this->proxy['host']); 00382 } 00383 if ($this->proxy['username'] || $this->proxy['password']) { 00384 $this->debug('set cURL proxy authentication options'); 00385 $this->setCurlOption(CURLOPT_PROXYUSERPWD, $this->proxy['username'].':'.$this->proxy['password']); 00386 if ($this->proxy['authtype'] == 'basic') { 00387 $this->setCurlOption($CURLOPT_PROXYAUTH, $CURLAUTH_BASIC); 00388 } 00389 if ($this->proxy['authtype'] == 'ntlm') { 00390 $this->setCurlOption($CURLOPT_PROXYAUTH, $CURLAUTH_NTLM); 00391 } 00392 } 00393 } 00394 $this->debug('cURL connection set up'); 00395 return true; 00396 } else { 00397 $this->setError('Unknown scheme ' . $this->scheme); 00398 $this->debug('Unknown scheme ' . $this->scheme); 00399 return false; 00400 } 00401 } 00402 00413 function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) { 00414 00415 $this->debug('entered send() with data of length: '.strlen($data)); 00416 00417 $this->tryagain = true; 00418 $tries = 0; 00419 while ($this->tryagain) { 00420 $this->tryagain = false; 00421 if ($tries++ < 2) { 00422 // make connnection 00423 if (!$this->connect($timeout, $response_timeout)){ 00424 return false; 00425 } 00426 00427 // send request 00428 if (!$this->sendRequest($data, $cookies)){ 00429 return false; 00430 } 00431 00432 // get response 00433 $respdata = $this->getResponse(); 00434 } else { 00435 $this->setError("Too many tries to get an OK response ($this->response_status_line)"); 00436 } 00437 } 00438 $this->debug('end of send()'); 00439 return $respdata; 00440 } 00441 00442 00454 function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) { 00455 return $this->send($data, $timeout, $response_timeout, $cookies); 00456 } 00457 00468 function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) { 00469 $this->debug("setCredentials username=$username authtype=$authtype digestRequest="); 00470 $this->appendDebug($this->varDump($digestRequest)); 00471 $this->debug("certRequest="); 00472 $this->appendDebug($this->varDump($certRequest)); 00473 // cf. RFC 2617 00474 if ($authtype == 'basic') { 00475 $this->setHeader('Authorization', 'Basic '.base64_encode(str_replace(':','',$username).':'.$password)); 00476 } elseif ($authtype == 'digest') { 00477 if (isset($digestRequest['nonce'])) { 00478 $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1; 00479 00480 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html) 00481 00482 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd 00483 $A1 = $username. ':' . (isset($digestRequest['realm']) ? $digestRequest['realm'] : '') . ':' . $password; 00484 00485 // H(A1) = MD5(A1) 00486 $HA1 = md5($A1); 00487 00488 // A2 = Method ":" digest-uri-value 00489 $A2 = $this->request_method . ':' . $this->digest_uri; 00490 00491 // H(A2) 00492 $HA2 = md5($A2); 00493 00494 // KD(secret, data) = H(concat(secret, ":", data)) 00495 // if qop == auth: 00496 // request-digest = <"> < KD ( H(A1), unq(nonce-value) 00497 // ":" nc-value 00498 // ":" unq(cnonce-value) 00499 // ":" unq(qop-value) 00500 // ":" H(A2) 00501 // ) <"> 00502 // if qop is missing, 00503 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <"> 00504 00505 $unhashedDigest = ''; 00506 $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : ''; 00507 $cnonce = $nonce; 00508 if ($digestRequest['qop'] != '') { 00509 $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2; 00510 } else { 00511 $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2; 00512 } 00513 00514 $hashedDigest = md5($unhashedDigest); 00515 00516 $opaque = ''; 00517 if (isset($digestRequest['opaque'])) { 00518 $opaque = ', opaque="' . $digestRequest['opaque'] . '"'; 00519 } 00520 00521 $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 . '"'); 00522 } 00523 } elseif ($authtype == 'certificate') { 00524 $this->certRequest = $certRequest; 00525 $this->debug('Authorization header not set for certificate'); 00526 } elseif ($authtype == 'ntlm') { 00527 // do nothing 00528 $this->debug('Authorization header not set for ntlm'); 00529 } 00530 $this->username = $username; 00531 $this->password = $password; 00532 $this->authtype = $authtype; 00533 $this->digestRequest = $digestRequest; 00534 } 00535 00542 function setSOAPAction($soapaction) { 00543 $this->setHeader('SOAPAction', '"' . $soapaction . '"'); 00544 } 00545 00552 function setEncoding($enc='gzip, deflate') { 00553 if (function_exists('gzdeflate')) { 00554 $this->protocol_version = '1.1'; 00555 $this->setHeader('Accept-Encoding', $enc); 00556 if (!isset($this->outgoing_headers['Connection'])) { 00557 $this->setHeader('Connection', 'close'); 00558 $this->persistentConnection = false; 00559 } 00560 set_magic_quotes_runtime(0); 00561 // deprecated 00562 $this->encoding = $enc; 00563 } 00564 } 00565 00576 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 'basic') { 00577 if ($proxyhost) { 00578 $this->proxy = array( 00579 'host' => $proxyhost, 00580 'port' => $proxyport, 00581 'username' => $proxyusername, 00582 'password' => $proxypassword, 00583 'authtype' => $proxyauthtype 00584 ); 00585 if ($proxyusername != '' && $proxypassword != '' && $proxyauthtype = 'basic') { 00586 $this->setHeader('Proxy-Authorization', ' Basic '.base64_encode($proxyusername.':'.$proxypassword)); 00587 } 00588 } else { 00589 $this->debug('remove proxy'); 00590 $proxy = null; 00591 unsetHeader('Proxy-Authorization'); 00592 } 00593 } 00594 00595 00604 function isSkippableCurlHeader(&$data) { 00605 $skipHeaders = array( 'HTTP/1.1 100', 00606 'HTTP/1.0 301', 00607 'HTTP/1.1 301', 00608 'HTTP/1.0 302', 00609 'HTTP/1.1 302', 00610 'HTTP/1.0 401', 00611 'HTTP/1.1 401', 00612 'HTTP/1.0 200 Connection established'); 00613 foreach ($skipHeaders as $hd) { 00614 $prefix = substr($data, 0, strlen($hd)); 00615 if ($prefix == $hd) return true; 00616 } 00617 00618 return false; 00619 } 00620 00631 function decodeChunked($buffer, $lb){ 00632 // length := 0 00633 $length = 0; 00634 $new = ''; 00635 00636 // read chunk-size, chunk-extension (if any) and CRLF 00637 // get the position of the linebreak 00638 $chunkend = strpos($buffer, $lb); 00639 if ($chunkend == FALSE) { 00640 $this->debug('no linebreak found in decodeChunked'); 00641 return $new; 00642 } 00643 $temp = substr($buffer,0,$chunkend); 00644 $chunk_size = hexdec( trim($temp) ); 00645 $chunkstart = $chunkend + strlen($lb); 00646 // while (chunk-size > 0) { 00647 while ($chunk_size > 0) { 00648 $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size"); 00649 $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size); 00650 00651 // Just in case we got a broken connection 00652 if ($chunkend == FALSE) { 00653 $chunk = substr($buffer,$chunkstart); 00654 // append chunk-data to entity-body 00655 $new .= $chunk; 00656 $length += strlen($chunk); 00657 break; 00658 } 00659 00660 // read chunk-data and CRLF 00661 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart); 00662 // append chunk-data to entity-body 00663 $new .= $chunk; 00664 // length := length + chunk-size 00665 $length += strlen($chunk); 00666 // read chunk-size and CRLF 00667 $chunkstart = $chunkend + strlen($lb); 00668 00669 $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb); 00670 if ($chunkend == FALSE) { 00671 break; //Just in case we got a broken connection 00672 } 00673 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart); 00674 $chunk_size = hexdec( trim($temp) ); 00675 $chunkstart = $chunkend; 00676 } 00677 return $new; 00678 } 00679 00688 function buildPayload($data, $cookie_str = '') { 00689 // Note: for cURL connections, $this->outgoing_payload is ignored, 00690 // as is the Content-Length header, but these are still created as 00691 // debugging guides. 00692 00693 // add content-length header 00694 $this->setHeader('Content-Length', strlen($data)); 00695 00696 // start building outgoing payload: 00697 if ($this->proxy) { 00698 $uri = $this->url; 00699 } else { 00700 $uri = $this->uri; 00701 } 00702 $req = "$this->request_method $uri HTTP/$this->protocol_version"; 00703 $this->debug("HTTP request: $req"); 00704 $this->outgoing_payload = "$req\r\n"; 00705 00706 // loop thru headers, serializing 00707 foreach($this->outgoing_headers as $k => $v){ 00708 $hdr = $k.': '.$v; 00709 $this->debug("HTTP header: $hdr"); 00710 $this->outgoing_payload .= "$hdr\r\n"; 00711 } 00712 00713 // add any cookies 00714 if ($cookie_str != '') { 00715 $hdr = 'Cookie: '.$cookie_str; 00716 $this->debug("HTTP header: $hdr"); 00717 $this->outgoing_payload .= "$hdr\r\n"; 00718 } 00719 00720 // header/body separator 00721 $this->outgoing_payload .= "\r\n"; 00722 00723 // add data 00724 $this->outgoing_payload .= $data; 00725 } 00726 00735 function sendRequest($data, $cookies = NULL) { 00736 // build cookie string 00737 $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https'))); 00738 00739 // build payload 00740 $this->buildPayload($data, $cookie_str); 00741 00742 if ($this->io_method() == 'socket') { 00743 // send payload 00744 if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) { 00745 $this->setError('couldn\'t write message data to socket'); 00746 $this->debug('couldn\'t write message data to socket'); 00747 return false; 00748 } 00749 $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload)); 00750 return true; 00751 } else if ($this->io_method() == 'curl') { 00752 // set payload 00753 // cURL does say this should only be the verb, and in fact it 00754 // turns out that the URI and HTTP version are appended to this, which 00755 // some servers refuse to work with (so we no longer use this method!) 00756 //$this->setCurlOption(CURLOPT_CUSTOMREQUEST, $this->outgoing_payload); 00757 $curl_headers = array(); 00758 foreach($this->outgoing_headers as $k => $v){ 00759 if ($k == 'Connection' || $k == 'Content-Length' || $k == 'Host' || $k == 'Authorization' || $k == 'Proxy-Authorization') { 00760 $this->debug("Skip cURL header $k: $v"); 00761 } else { 00762 $curl_headers[] = "$k: $v"; 00763 } 00764 } 00765 if ($cookie_str != '') { 00766 $curl_headers[] = 'Cookie: ' . $cookie_str; 00767 } 00768 $this->setCurlOption(CURLOPT_HTTPHEADER, $curl_headers); 00769 $this->debug('set cURL HTTP headers'); 00770 if ($this->request_method == "POST") { 00771 $this->setCurlOption(CURLOPT_POST, 1); 00772 $this->setCurlOption(CURLOPT_POSTFIELDS, $data); 00773 $this->debug('set cURL POST data'); 00774 } else { 00775 } 00776 // insert custom user-set cURL options 00777 foreach ($this->ch_options as $key => $val) { 00778 $this->setCurlOption($key, $val); 00779 } 00780 00781 $this->debug('set cURL payload'); 00782 return true; 00783 } 00784 } 00785 00792 function getResponse(){ 00793 $this->incoming_payload = ''; 00794 00795 if ($this->io_method() == 'socket') { 00796 // loop until headers have been retrieved 00797 $data = ''; 00798 while (!isset($lb)){ 00799 00800 // We might EOF during header read. 00801 if(feof($this->fp)) { 00802 $this->incoming_payload = $data; 00803 $this->debug('found no headers before EOF after length ' . strlen($data)); 00804 $this->debug("received before EOF:\n" . $data); 00805 $this->setError('server failed to send headers'); 00806 return false; 00807 } 00808 00809 $tmp = fgets($this->fp, 256); 00810 $tmplen = strlen($tmp); 00811 $this->debug("read line of $tmplen bytes: " . trim($tmp)); 00812 00813 if ($tmplen == 0) { 00814 $this->incoming_payload = $data; 00815 $this->debug('socket read of headers timed out after length ' . strlen($data)); 00816 $this->debug("read before timeout: " . $data); 00817 $this->setError('socket read of headers timed out'); 00818 return false; 00819 } 00820 00821 $data .= $tmp; 00822 $pos = strpos($data,"\r\n\r\n"); 00823 if($pos > 1){ 00824 $lb = "\r\n"; 00825 } else { 00826 $pos = strpos($data,"\n\n"); 00827 if($pos > 1){ 00828 $lb = "\n"; 00829 } 00830 } 00831 // remove 100 headers 00832 if (isset($lb) && ereg('^HTTP/1.1 100',$data)) { 00833 unset($lb); 00834 $data = ''; 00835 }// 00836 } 00837 // store header data 00838 $this->incoming_payload .= $data; 00839 $this->debug('found end of headers after length ' . strlen($data)); 00840 // process headers 00841 $header_data = trim(substr($data,0,$pos)); 00842 $header_array = explode($lb,$header_data); 00843 $this->incoming_headers = array(); 00844 $this->incoming_cookies = array(); 00845 foreach($header_array as $header_line){ 00846 $arr = explode(':',$header_line, 2); 00847 if(count($arr) > 1){ 00848 $header_name = strtolower(trim($arr[0])); 00849 $this->incoming_headers[$header_name] = trim($arr[1]); 00850 if ($header_name == 'set-cookie') { 00851 // TODO: allow multiple cookies from parseCookie 00852 $cookie = $this->parseCookie(trim($arr[1])); 00853 if ($cookie) { 00854 $this->incoming_cookies[] = $cookie; 00855 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']); 00856 } else { 00857 $this->debug('did not find cookie in ' . trim($arr[1])); 00858 } 00859 } 00860 } else if (isset($header_name)) { 00861 // append continuation line to previous header 00862 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line; 00863 } 00864 } 00865 00866 // loop until msg has been received 00867 if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') { 00868 $content_length = 2147483647; // ignore any content-length header 00869 $chunked = true; 00870 $this->debug("want to read chunked content"); 00871 } elseif (isset($this->incoming_headers['content-length'])) { 00872 $content_length = $this->incoming_headers['content-length']; 00873 $chunked = false; 00874 $this->debug("want to read content of length $content_length"); 00875 } else { 00876 $content_length = 2147483647; 00877 $chunked = false; 00878 $this->debug("want to read content to EOF"); 00879 } 00880 $data = ''; 00881 do { 00882 if ($chunked) { 00883 $tmp = fgets($this->fp, 256); 00884 $tmplen = strlen($tmp); 00885 $this->debug("read chunk line of $tmplen bytes"); 00886 if ($tmplen == 0) { 00887 $this->incoming_payload = $data; 00888 $this->debug('socket read of chunk length timed out after length ' . strlen($data)); 00889 $this->debug("read before timeout:\n" . $data); 00890 $this->setError('socket read of chunk length timed out'); 00891 return false; 00892 } 00893 $content_length = hexdec(trim($tmp)); 00894 $this->debug("chunk length $content_length"); 00895 } 00896 $strlen = 0; 00897 while (($strlen < $content_length) && (!feof($this->fp))) { 00898 $readlen = min(8192, $content_length - $strlen); 00899 $tmp = fread($this->fp, $readlen); 00900 $tmplen = strlen($tmp); 00901 $this->debug("read buffer of $tmplen bytes"); 00902 if (($tmplen == 0) && (!feof($this->fp))) { 00903 $this->incoming_payload = $data; 00904 $this->debug('socket read of body timed out after length ' . strlen($data)); 00905 $this->debug("read before timeout:\n" . $data); 00906 $this->setError('socket read of body timed out'); 00907 return false; 00908 } 00909 $strlen += $tmplen; 00910 $data .= $tmp; 00911 } 00912 if ($chunked && ($content_length > 0)) { 00913 $tmp = fgets($this->fp, 256); 00914 $tmplen = strlen($tmp); 00915 $this->debug("read chunk terminator of $tmplen bytes"); 00916 if ($tmplen == 0) { 00917 $this->incoming_payload = $data; 00918 $this->debug('socket read of chunk terminator timed out after length ' . strlen($data)); 00919 $this->debug("read before timeout:\n" . $data); 00920 $this->setError('socket read of chunk terminator timed out'); 00921 return false; 00922 } 00923 } 00924 } while ($chunked && ($content_length > 0) && (!feof($this->fp))); 00925 if (feof($this->fp)) { 00926 $this->debug('read to EOF'); 00927 } 00928 $this->debug('read body of length ' . strlen($data)); 00929 $this->incoming_payload .= $data; 00930 $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server'); 00931 00932 // close filepointer 00933 if( 00934 (isset($this->incoming_headers['connection']) && strtolower($this->incoming_headers['connection']) == 'close') || 00935 (! $this->persistentConnection) || feof($this->fp)){ 00936 fclose($this->fp); 00937 $this->fp = false; 00938 $this->debug('closed socket'); 00939 } 00940 00941 // connection was closed unexpectedly 00942 if($this->incoming_payload == ''){ 00943 $this->setError('no response from server'); 00944 return false; 00945 } 00946 00947 // decode transfer-encoding 00948 // if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){ 00949 // if(!$data = $this->decodeChunked($data, $lb)){ 00950 // $this->setError('Decoding of chunked data failed'); 00951 // return false; 00952 // } 00953 //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>"; 00954 // set decoded payload 00955 // $this->incoming_payload = $header_data.$lb.$lb.$data; 00956 // } 00957 00958 } else if ($this->io_method() == 'curl') { 00959 // send and receive 00960 $this->debug('send and receive with cURL'); 00961 $this->incoming_payload = curl_exec($this->ch); 00962 $data = $this->incoming_payload; 00963 00964 $cErr = curl_error($this->ch); 00965 if ($cErr != '') { 00966 $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'<br>'; 00967 // TODO: there is a PHP bug that can cause this to SEGV for CURLINFO_CONTENT_TYPE 00968 foreach(curl_getinfo($this->ch) as $k => $v){ 00969 $err .= "$k: $v<br>"; 00970 } 00971 $this->debug($err); 00972 $this->setError($err); 00973 curl_close($this->ch); 00974 return false; 00975 } else { 00976 //echo '<pre>'; 00977 //var_dump(curl_getinfo($this->ch)); 00978 //echo '</pre>'; 00979 } 00980 // close curl 00981 $this->debug('No cURL error, closing cURL'); 00982 curl_close($this->ch); 00983 00984 // try removing skippable headers 00985 $savedata = $data; 00986 while ($this->isSkippableCurlHeader($data)) { 00987 $this->debug("Found HTTP header to skip"); 00988 if ($pos = strpos($data,"\r\n\r\n")) { 00989 $data = ltrim(substr($data,$pos)); 00990 } elseif($pos = strpos($data,"\n\n") ) { 00991 $data = ltrim(substr($data,$pos)); 00992 } 00993 } 00994 00995 if ($data == '') { 00996 // have nothing left; just remove 100 header(s) 00997 $data = $savedata; 00998 while (ereg('^HTTP/1.1 100',$data)) { 00999 if ($pos = strpos($data,"\r\n\r\n")) { 01000 $data = ltrim(substr($data,$pos)); 01001 } elseif($pos = strpos($data,"\n\n") ) { 01002 $data = ltrim(substr($data,$pos)); 01003 } 01004 } 01005 } 01006 01007 // separate content from HTTP headers 01008 if ($pos = strpos($data,"\r\n\r\n")) { 01009 $lb = "\r\n"; 01010 } elseif( $pos = strpos($data,"\n\n")) { 01011 $lb = "\n"; 01012 } else { 01013 $this->debug('no proper separation of headers and document'); 01014 $this->setError('no proper separation of headers and document'); 01015 return false; 01016 } 01017 $header_data = trim(substr($data,0,$pos)); 01018 $header_array = explode($lb,$header_data); 01019 $data = ltrim(substr($data,$pos)); 01020 $this->debug('found proper separation of headers and document'); 01021 $this->debug('cleaned data, stringlen: '.strlen($data)); 01022 // clean headers 01023 foreach ($header_array as $header_line) { 01024 $arr = explode(':',$header_line,2); 01025 if(count($arr) > 1){ 01026 $header_name = strtolower(trim($arr[0])); 01027 $this->incoming_headers[$header_name] = trim($arr[1]); 01028 if ($header_name == 'set-cookie') { 01029 // TODO: allow multiple cookies from parseCookie 01030 $cookie = $this->parseCookie(trim($arr[1])); 01031 if ($cookie) { 01032 $this->incoming_cookies[] = $cookie; 01033 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']); 01034 } else { 01035 $this->debug('did not find cookie in ' . trim($arr[1])); 01036 } 01037 } 01038 } else if (isset($header_name)) { 01039 // append continuation line to previous header 01040 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line; 01041 } 01042 } 01043 } 01044 01045 $this->response_status_line = $header_array[0]; 01046 $arr = explode(' ', $this->response_status_line, 3); 01047 $http_version = $arr[0]; 01048 $http_status = intval($arr[1]); 01049 $http_reason = count($arr) > 2 ? $arr[2] : ''; 01050 01051 // see if we need to resend the request with http digest authentication 01052 if (isset($this->incoming_headers['location']) && ($http_status == 301 || $http_status == 302)) { 01053 $this->debug("Got $http_status $http_reason with Location: " . $this->incoming_headers['location']); 01054 $this->setURL($this->incoming_headers['location']); 01055 $this->tryagain = true; 01056 return false; 01057 } 01058 01059 // see if we need to resend the request with http digest authentication 01060 if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) { 01061 $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']); 01062 if (strstr($this->incoming_headers['www-authenticate'], "Digest ")) { 01063 $this->debug('Server wants digest authentication'); 01064 // remove "Digest " from our elements 01065 $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']); 01066 01067 // parse elements into array 01068 $digestElements = explode(',', $digestString); 01069 foreach ($digestElements as $val) { 01070 $tempElement = explode('=', trim($val), 2); 01071 $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]); 01072 } 01073 01074 // should have (at least) qop, realm, nonce 01075 if (isset($digestRequest['nonce'])) { 01076 $this->setCredentials($this->username, $this->password, 'digest', $digestRequest); 01077 $this->tryagain = true; 01078 return false; 01079 } 01080 } 01081 $this->debug('HTTP authentication failed'); 01082 $this->setError('HTTP authentication failed'); 01083 return false; 01084 } 01085 01086 if ( 01087 ($http_status >= 300 && $http_status <= 307) || 01088 ($http_status >= 400 && $http_status <= 417) || 01089 ($http_status >= 501 && $http_status <= 505) 01090 ) { 01091 $this->setError("Unsupported HTTP response status $http_status $http_reason (soapclient->response has contents of the response)"); 01092 return false; 01093 } 01094 01095 // decode content-encoding 01096 if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){ 01097 if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){ 01098 // if decoding works, use it. else assume data wasn't gzencoded 01099 if(function_exists('gzinflate')){ 01100 //$timer->setMarker('starting decoding of gzip/deflated content'); 01101 // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress) 01102 // this means there are no Zlib headers, although there should be 01103 $this->debug('The gzinflate function exists'); 01104 $datalen = strlen($data); 01105 if ($this->incoming_headers['content-encoding'] == 'deflate') { 01106 if ($degzdata = @gzinflate($data)) { 01107 $data = $degzdata; 01108 $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes'); 01109 if (strlen($data) < $datalen) { 01110 // test for the case that the payload has been compressed twice 01111 $this->debug('The inflated payload is smaller than the gzipped one; try again'); 01112 if ($degzdata = @gzinflate($data)) { 01113 $data = $degzdata; 01114 $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes'); 01115 } 01116 } 01117 } else { 01118 $this->debug('Error using gzinflate to inflate the payload'); 01119 $this->setError('Error using gzinflate to inflate the payload'); 01120 } 01121 } elseif ($this->incoming_headers['content-encoding'] == 'gzip') { 01122 if ($degzdata = @gzinflate(substr($data, 10))) { // do our best 01123 $data = $degzdata; 01124 $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes'); 01125 if (strlen($data) < $datalen) { 01126 // test for the case that the payload has been compressed twice 01127 $this->debug('The un-gzipped payload is smaller than the gzipped one; try again'); 01128 if ($degzdata = @gzinflate(substr($data, 10))) { 01129 $data = $degzdata; 01130 $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes'); 01131 } 01132 } 01133 } else { 01134 $this->debug('Error using gzinflate to un-gzip the payload'); 01135 $this->setError('Error using gzinflate to un-gzip the payload'); 01136 } 01137 } 01138 //$timer->setMarker('finished decoding of gzip/deflated content'); 01139 //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>"; 01140 // set decoded payload 01141 $this->incoming_payload = $header_data.$lb.$lb.$data; 01142 } else { 01143 $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.'); 01144 $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.'); 01145 } 01146 } else { 01147 $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']); 01148 $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']); 01149 } 01150 } else { 01151 $this->debug('No Content-Encoding header'); 01152 } 01153 01154 if(strlen($data) == 0){ 01155 $this->debug('no data after headers!'); 01156 $this->setError('no data present after HTTP headers'); 01157 return false; 01158 } 01159 01160 return $data; 01161 } 01162 01170 function setContentType($type, $charset = false) { 01171 $this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : '')); 01172 } 01173 01180 function usePersistentConnection(){ 01181 if (isset($this->outgoing_headers['Accept-Encoding'])) { 01182 return false; 01183 } 01184 $this->protocol_version = '1.1'; 01185 $this->persistentConnection = true; 01186 $this->setHeader('Connection', 'Keep-Alive'); 01187 return true; 01188 } 01189 01197 /* 01198 * TODO: allow a Set-Cookie string to be parsed into multiple cookies 01199 */ 01200 function parseCookie($cookie_str) { 01201 $cookie_str = str_replace('; ', ';', $cookie_str) . ';'; 01202 $data = split(';', $cookie_str); 01203 $value_str = $data[0]; 01204 01205 $cookie_param = 'domain='; 01206 $start = strpos($cookie_str, $cookie_param); 01207 if ($start > 0) { 01208 $domain = substr($cookie_str, $start + strlen($cookie_param)); 01209 $domain = substr($domain, 0, strpos($domain, ';')); 01210 } else { 01211 $domain = ''; 01212 } 01213 01214 $cookie_param = 'expires='; 01215 $start = strpos($cookie_str, $cookie_param); 01216 if ($start > 0) { 01217 $expires = substr($cookie_str, $start + strlen($cookie_param)); 01218 $expires = substr($expires, 0, strpos($expires, ';')); 01219 } else { 01220 $expires = ''; 01221 } 01222 01223 $cookie_param = 'path='; 01224 $start = strpos($cookie_str, $cookie_param); 01225 if ( $start > 0 ) { 01226 $path = substr($cookie_str, $start + strlen($cookie_param)); 01227 $path = substr($path, 0, strpos($path, ';')); 01228 } else { 01229 $path = '/'; 01230 } 01231 01232 $cookie_param = ';secure;'; 01233 if (strpos($cookie_str, $cookie_param) !== FALSE) { 01234 $secure = true; 01235 } else { 01236 $secure = false; 01237 } 01238 01239 $sep_pos = strpos($value_str, '='); 01240 01241 if ($sep_pos) { 01242 $name = substr($value_str, 0, $sep_pos); 01243 $value = substr($value_str, $sep_pos + 1); 01244 $cookie= array( 'name' => $name, 01245 'value' => $value, 01246 'domain' => $domain, 01247 'path' => $path, 01248 'expires' => $expires, 01249 'secure' => $secure 01250 ); 01251 return $cookie; 01252 } 01253 return false; 01254 } 01255 01264 function getCookiesForRequest($cookies, $secure=false) { 01265 $cookie_str = ''; 01266 if ((! is_null($cookies)) && (is_array($cookies))) { 01267 foreach ($cookies as $cookie) { 01268 if (! is_array($cookie)) { 01269 continue; 01270 } 01271 $this->debug("check cookie for validity: ".$cookie['name'].'='.$cookie['value']); 01272 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) { 01273 if (strtotime($cookie['expires']) <= time()) { 01274 $this->debug('cookie has expired'); 01275 continue; 01276 } 01277 } 01278 if ((isset($cookie['domain'])) && (! empty($cookie['domain']))) { 01279 $domain = preg_quote($cookie['domain']); 01280 if (! preg_match("'.*$domain$'i", $this->host)) { 01281 $this->debug('cookie has different domain'); 01282 continue; 01283 } 01284 } 01285 if ((isset($cookie['path'])) && (! empty($cookie['path']))) { 01286 $path = preg_quote($cookie['path']); 01287 if (! preg_match("'^$path.*'i", $this->path)) { 01288 $this->debug('cookie is for a different path'); 01289 continue; 01290 } 01291 } 01292 if ((! $secure) && (isset($cookie['secure'])) && ($cookie['secure'])) { 01293 $this->debug('cookie is secure, transport is not'); 01294 continue; 01295 } 01296 $cookie_str .= $cookie['name'] . '=' . $cookie['value'] . '; '; 01297 $this->debug('add cookie to Cookie-String: ' . $cookie['name'] . '=' . $cookie['value']); 01298 } 01299 } 01300 return $cookie_str; 01301 } 01302 } 01303 01304 01305 ?>