ILIAS  eassessment Revision 61809
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.ilBMFTransport_HTTP.php
Go to the documentation of this file.
1 <?php
32 require_once dirname(__FILE__).'/../class.ilBMFBase.php';
33 
42 {
43 
49  var $headers = array();
50 
56  var $cookies;
57 
63  var $timeout = 4;
64 
70  var $urlparts = null;
71 
77  var $url = '';
78 
85 
92 
99 
106  var $result_encoding = 'UTF-8';
107 
112 
113  var $result_headers = array();
114 
115  var $result_cookies = array();
116 
126  {
127  parent::ilBMFBase('HTTP');
128  $this->urlparts = @parse_url($url);
129  $this->url = $url;
130  $this->encoding = $encoding;
131  }
132 
142  function send($msg, $options = null)
143  {
144  if (!$this->_validateUrl()) {
145  return $this->fault;
146  }
147 
148  if (isset($options['timeout'])) {
149  $this->timeout = (int)$options['timeout'];
150  }
151 
152  if (strcasecmp($this->urlparts['scheme'], 'HTTP') == 0) {
153  return $this->_sendHTTP($msg, $options);
154  } elseif (strcasecmp($this->urlparts['scheme'], 'HTTPS') == 0) {
155  return $this->_sendHTTPS($msg, $options);
156  }
157 
158  return $this->_raiseSoapFault('Invalid url scheme ' . $this->url);
159  }
160 
169  function setCredentials($username, $password)
170  {
171  $this->headers['Authorization'] = 'Basic ' . base64_encode($username . ':' . $password);
172  }
173 
181  function addCookie($name, $value)
182  {
183  $this->cookies[$name] = $value;
184  }
185 
191  function _genCookieHeader()
192  {
193  foreach ($this->cookies as $name=>$value) {
194  $cookies = (isset($cookies) ? $cookies. '; ' : '') .
195  urlencode($name) . '=' . urlencode($value);
196  }
197  return $cookies;
198  }
199 
206  function _validateUrl()
207  {
208  if (!is_array($this->urlparts) ) {
209  $this->_raiseSoapFault('Unable to parse URL ' . $this->url);
210  return false;
211  }
212  if (!isset($this->urlparts['host'])) {
213  $this->_raiseSoapFault('No host in URL ' . $this->url);
214  return false;
215  }
216  if (!isset($this->urlparts['port'])) {
217  if (strcasecmp($this->urlparts['scheme'], 'HTTP') == 0) {
218  $this->urlparts['port'] = 80;
219  } elseif (strcasecmp($this->urlparts['scheme'], 'HTTPS') == 0) {
220  $this->urlparts['port'] = 443;
221  }
222 
223  }
224  if (isset($this->urlparts['user'])) {
225  $this->setCredentials(urldecode($this->urlparts['user']),
226  urldecode($this->urlparts['pass']));
227  }
228  if (!isset($this->urlparts['path']) || !$this->urlparts['path']) {
229  $this->urlparts['path'] = '/';
230  }
231 
232  return true;
233  }
234 
243  {
244  $h = stristr($headers, 'Content-Type');
245  preg_match_all('/^Content-Type:\s*(.*)$/im', $h, $ct, PREG_SET_ORDER);
246  $n = count($ct);
247  $ct = $ct[$n - 1];
248 
249  // Strip the string of \r.
250  $this->result_content_type = str_replace("\r", '', $ct[1]);
251 
252  if (preg_match('/(.*?)(?:;\s?charset=)(.*)/i',
253  $this->result_content_type,
254  $m)) {
255  $this->result_content_type = $m[1];
256  if (count($m) > 2) {
257  $enc = strtoupper(str_replace('"', '', $m[2]));
258  if (in_array($enc, $this->_encodings)) {
259  $this->result_encoding = $enc;
260  }
261  }
262  }
263 
264  // Deal with broken servers that don't set content type on faults.
265  if (!$this->result_content_type) {
266  $this->result_content_type = 'text/xml';
267  }
268  }
269 
276  {
277  /* Largely borrowed from HTTP_Request. */
278  $this->result_headers = array();
279  $headers = split("\r?\n", $headers);
280  foreach ($headers as $value) {
281  if (strpos($value,':') === false) {
282  $this->result_headers[0] = $value;
283  continue;
284  }
285  list($name, $value) = split(':', $value);
286  $headername = strtolower($name);
287  $headervalue = trim($value);
288  $this->result_headers[$headername] = $headervalue;
289 
290  if ($headername == 'set-cookie') {
291  // Parse a SetCookie header to fill _cookies array.
292  $cookie = array('expires' => null,
293  'domain' => $this->urlparts['host'],
294  'path' => null,
295  'secure' => false);
296 
297  if (!strpos($headervalue, ';')) {
298  // Only a name=value pair.
299  list($cookie['name'], $cookie['value']) = array_map('trim', explode('=', $headervalue));
300  $cookie['name'] = urldecode($cookie['name']);
301  $cookie['value'] = urldecode($cookie['value']);
302 
303  } else {
304  // Some optional parameters are supplied.
305  $elements = explode(';', $headervalue);
306  list($cookie['name'], $cookie['value']) = array_map('trim', explode('=', $elements[0]));
307  $cookie['name'] = urldecode($cookie['name']);
308  $cookie['value'] = urldecode($cookie['value']);
309 
310  for ($i = 1; $i < count($elements);$i++) {
311  list($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
312  if ('secure' == $elName) {
313  $cookie['secure'] = true;
314  } elseif ('expires' == $elName) {
315  $cookie['expires'] = str_replace('"', '', $elValue);
316  } elseif ('path' == $elName OR 'domain' == $elName) {
317  $cookie[$elName] = urldecode($elValue);
318  } else {
319  $cookie[$elName] = $elValue;
320  }
321  }
322  }
323  $this->result_cookies[] = $cookie;
324  }
325  }
326  }
327 
334  function _parseResponse()
335  {
336  if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s",
337  $this->incoming_payload,
338  $match)) {
339  $this->response = $match[2];
340  // Find the response error, some servers response with 500 for
341  // SOAP faults.
342  $this->_parseHeaders($match[1]);
343 
344  list($protocol, $code, $msg) = sscanf($this->result_headers[0],
345  '%s %s %s');
346  unset($this->result_headers[0]);
347 
348  switch($code) {
349  case 100: // Continue
350  $this->incoming_payload = $match[2];
351  return $this->_parseResponse();
352  case 400:
353  $this->_raiseSoapFault("HTTP Response $code Bad Request");
354  return false;
355  break;
356  case 401:
357  $this->_raiseSoapFault("HTTP Response $code Authentication Failed");
358  return false;
359  break;
360  case 403:
361  $this->_raiseSoapFault("HTTP Response $code Forbidden");
362  return false;
363  break;
364  case 404:
365  $this->_raiseSoapFault("HTTP Response $code Not Found");
366  return false;
367  break;
368  case 407:
369  $this->_raiseSoapFault("HTTP Response $code Proxy Authentication Required");
370  return false;
371  break;
372  case 408:
373  $this->_raiseSoapFault("HTTP Response $code Request Timeout");
374  return false;
375  break;
376  case 410:
377  $this->_raiseSoapFault("HTTP Response $code Gone");
378  return false;
379  break;
380  default:
381  if ($code >= 400 && $code < 500) {
382  $this->_raiseSoapFault("HTTP Response $code Not Found, Server message: $msg");
383  return false;
384  }
385  }
386 
387  $this->_parseEncoding($match[1]);
388 
389  if ($this->result_content_type == 'application/dime') {
390  // XXX quick hack insertion of DIME
391  if (PEAR::isError($this->_decodeDIMEMessage($this->response,$this->headers,$this->attachments))) {
392  // _decodeDIMEMessage already raised $this->fault
393  return false;
394  }
395  $this->result_content_type = $this->headers['content-type'];
396  } elseif (stristr($this->result_content_type,'multipart/related')) {
397  $this->response = $this->incoming_payload;
398  if (PEAR::isError($this->_decodeMimeMessage($this->response,$this->headers,$this->attachments))) {
399  // _decodeMimeMessage already raised $this->fault
400  return false;
401  }
402  } elseif ($this->result_content_type != 'text/xml') {
403  $this->_raiseSoapFault($this->response);
404  return false;
405  }
406  // if no content, return false
407  return strlen($this->response) > 0;
408  }
409  $this->_raiseSoapFault('Invalid HTTP Response');
410  return false;
411  }
412 
421  function _getRequest($msg, $options)
422  {
423  $this->headers = array();
424 
425  $action = isset($options['soapaction']) ? $options['soapaction'] : '';
426  $fullpath = $this->urlparts['path'];
427  if (isset($this->urlparts['query'])) {
428  $fullpath .= '?' . $this->urlparts['query'];
429  }
430  if (isset($this->urlparts['fragment'])) {
431  $fullpath .= '#' . $this->urlparts['fragment'];
432  }
433 
434  if (isset($options['proxy_host'])) {
435  $fullpath = 'http://' . $this->urlparts['host'] . ':' .
436  $this->urlparts['port'] . $fullpath;
437  }
438 
439  if (isset($options['proxy_user'])) {
440  $this->headers['Proxy-Authorization'] = 'Basic ' .
441  base64_encode($options['proxy_user'] . ':' .
442  $options['proxy_pass']);
443  }
444 
445  if (isset($options['user'])) {
446  $this->setCredentials($options['user'], $options['pass']);
447  }
448 
449  $this->headers['User-Agent'] = $this->_userAgent;
450  $this->headers['Host'] = $this->urlparts['host'];
451  $this->headers['Content-Type'] = "text/xml; charset=$this->encoding";
452  $this->headers['Content-Length'] = strlen($msg);
453  $this->headers['SOAPAction'] = '"' . $action . '"';
454  if (isset($options['headers'])) {
455  $this->headers = array_merge($this->headers, $options['headers']);
456  }
457 
458  $this->cookies = array();
459  if (!isset($options['nocookies']) || !$options['nocookies']) {
460  // Add the cookies we got from the last request.
461  if (isset($this->result_cookies)) {
462  foreach ($this->result_cookies as $cookie) {
463  if ($cookie['domain'] == $this->urlparts['host'])
464  $this->cookies[$cookie['name']] = $cookie['value'];
465  }
466  }
467  }
468  // Add cookies the user wants to set.
469  if (isset($options['cookies'])) {
470  foreach ($options['cookies'] as $cookie) {
471  if ($cookie['domain'] == $this->urlparts['host'])
472  $this->cookies[$cookie['name']] = $cookie['value'];
473  }
474  }
475  if (count($this->cookies)) {
476  $this->headers['Cookie'] = $this->_genCookieHeader();
477  }
478  $headers = '';
479  foreach ($this->headers as $k => $v) {
480  $headers .= "$k: $v\r\n";
481  }
482  $this->outgoing_payload = "POST $fullpath HTTP/1.0\r\n" . $headers .
483  "\r\n" . $msg;
484 
485  return $this->outgoing_payload;
486  }
487 
496  function _sendHTTP($msg, $options)
497  {
498  $this->incoming_payload = '';
499  $this->_getRequest($msg, $options);
500  $host = $this->urlparts['host'];
501  $port = $this->urlparts['port'];
502  if (isset($options['proxy_host'])) {
503  $host = $options['proxy_host'];
504  $port = isset($options['proxy_port']) ? $options['proxy_port'] : 8080;
505  }
506  // Send.
507  if ($this->timeout > 0) {
508  $fp = @fsockopen($host, $port, $this->errno, $this->errmsg, $this->timeout);
509  } else {
510  $fp = @fsockopen($host, $port, $this->errno, $this->errmsg);
511  }
512  if (!$fp) {
513  return $this->_raiseSoapFault("Connect Error to $host:$port");
514  }
515  if ($this->timeout > 0) {
516  // some builds of PHP do not support this, silence the warning
517  @socket_set_timeout($fp, $this->timeout);
518  }
519  if (!fputs($fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
520  return $this->_raiseSoapFault("Error POSTing Data to $host");
521  }
522 
523  // get reponse
524  // XXX time consumer
525  do {
526  $data = fread($fp, 4096);
527  $_tmp_status = socket_get_status($fp);
528  if ($_tmp_status['timed_out']) {
529  return $this->_raiseSoapFault("Timed out read from $host");
530  } else {
531  $this->incoming_payload .= $data;
532  }
533  } while (!$_tmp_status['eof']);
534 
535  fclose($fp);
536 
537  if (!$this->_parseResponse()) {
538  return $this->fault;
539  }
540  return $this->response;
541  }
542 
551  function _sendHTTPS($msg, $options)
552  {
553  /* NOTE This function uses the CURL functions
554  * Your php must be compiled with CURL
555  */
556  if (!extension_loaded('curl')) {
557  return $this->_raiseSoapFault('CURL Extension is required for HTTPS');
558  }
559 
560 /* Databay: Changes for BMF */
561  $this->_getRequest($msg, $options);
562 
563  $ch = curl_init();
564 
565  if (isset($options['proxy_host'])) {
566  // $options['http_proxy'] == 'hostname:port'
567  $host = $options['proxy_host'];
568  $port = isset($options['proxy_port']) ? $options['proxy_port'] : 8080;
569  curl_setopt($ch, CURLOPT_PROXY, $host . ":" . $port);
570  }
571 
572  if (isset($options['proxy_user'])) {
573  // $options['http_proxy_userpw'] == 'username:password'
574  curl_setopt($ch, CURLOPT_PROXYUSERPWD, $options['proxy_user'] . ':' . $options['proxy_pass']);
575  }
576 
577  if (isset($options['user'])) {
578  curl_setopt($ch, CURLOPT_USERPWD, $options['user'] . ':' . $options['pass']);
579  }
580 
581  if (!isset($options['soapaction'])) {
582  $options['soapaction'] = '';
583  }
584  curl_setopt($ch, CURLOPT_HTTPHEADER , array('Content-Type: text/xml;charset=' . $this->encoding, 'SOAPAction: "'.$options['soapaction'].'"'));
585  curl_setopt($ch, CURLOPT_USERAGENT , $this->_userAgent);
586 
587  if ($this->timeout) {
588  curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); //times out after 4s
589  }
590 
591  curl_setopt($ch, CURLOPT_POSTFIELDS, $msg);
592  curl_setopt($ch, CURLOPT_URL, $this->url);
593  curl_setopt($ch, CURLOPT_POST, 1);
594 /* Databay: Changes for BMF */
595 # curl_setopt($ch, CURLOPT_FAILONERROR, 0);
596  curl_setopt($ch, CURLOPT_FAILONERROR, 1);
597  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
598  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
599  curl_setopt($ch, CURLOPT_HEADER, 1);
600 /* Databay: Changes for BMF */
601  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
602 /* Databay: Changes for BMF */
603  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
604 /* Databay: Changes for BMF */
605  curl_setopt($ch, CURLOPT_VERBOSE, 1);
606  if (defined('CURLOPT_HTTP_VERSION')) {
607  curl_setopt($ch, CURLOPT_HTTP_VERSION, 1);
608  }
609 
610  if (isset($options['curl'])) {
611  foreach ($options['curl'] as $key => $val) {
612  curl_setopt($ch, $key, $val);
613  }
614  }
615 
616  // Save the outgoing XML. This doesn't quite match _sendHTTP as CURL
617  // generates the headers, but having the XML is usually the most
618  // important part for tracing/debugging.
619  $this->outgoing_payload = $msg;
620 
621  $this->incoming_payload = curl_exec($ch);
622  if (!$this->incoming_payload) {
623  $m = 'curl_exec error ' . curl_errno($ch) . ' ' . curl_error($ch);
624  curl_close($ch);
625  return $this->_raiseSoapFault($m);
626  }
627  curl_close($ch);
628 
629  if (!$this->_parseResponse()) {
630  return $this->fault;
631  }
632 
633  return $this->response;
634  }
635 
636 }