PageRenderTime 53ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/include/nusoap/class.soap_transport_http.php

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