SOAP
[ class tree: SOAP ] [ index: SOAP ] [ all elements ]

Source for file HTTP.php

Documentation is available at HTTP.php

  1. <?php
  2. /**
  3.  * This file contains the code for a HTTP transport layer.
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 2.02 of the PHP license,
  8.  * that is bundled with this package in the file LICENSE, and is available at
  9.  * through the world-wide-web at http://www.php.net/license/2_02.txt.  If you
  10.  * did not receive a copy of the PHP license and are unable to obtain it
  11.  * through the world-wide-web, please send a note to license@php.net so we can
  12.  * mail you a copy immediately.
  13.  *
  14.  * @category   Web Services
  15.  * @package    SOAP
  16.  * @author     Shane Caraveo <Shane@Caraveo.com>
  17.  * @author     Jan Schneider <jan@horde.org>
  18.  * @copyright  2003-2006 The PHP Group
  19.  * @license    http://www.php.net/license/2_02.txt  PHP License 2.02
  20.  * @link       http://pear.php.net/package/SOAP
  21.  */
  22.  
  23. /**
  24.  * HTTP Transport class
  25.  *
  26.  * @package  SOAP
  27.  * @category Web Services
  28.  */
  29.  
  30. /**
  31.  * Needed Classes
  32.  */
  33. require_once 'SOAP/Transport.php';
  34.  
  35. /**
  36.  *  HTTP Transport for SOAP
  37.  *
  38.  * @access public
  39.  * @package SOAP
  40.  * @author Shane Caraveo <shane@php.net>
  41.  * @author Jan Schneider <jan@horde.org>
  42.  */
  43. {
  44.     /**
  45.      * Basic Auth string.
  46.      *
  47.      * @var array 
  48.      */
  49.     var $headers = array();
  50.  
  51.     /**
  52.      * Cookies.
  53.      *
  54.      * @var array 
  55.      */
  56.     var $cookies;
  57.  
  58.     /**
  59.      * Connection timeout in seconds. 0 = none.
  60.      *
  61.      * @var integer 
  62.      */
  63.     var $timeout = 4;
  64.  
  65.     /**
  66.      * HTTP-Response Content-Type.
  67.      */
  68.     var $result_content_type;
  69.  
  70.     var $result_headers = array();
  71.  
  72.     var $result_cookies = array();
  73.  
  74.     /**
  75.      * SOAP_Transport_HTTP Constructor
  76.      *
  77.      * @access public
  78.      *
  79.      * @param string $url       HTTP url to SOAP endpoint.
  80.      * @param string $encoding  Encoding to use.
  81.      */
  82.     function SOAP_Transport_HTTP($url$encoding = SOAP_DEFAULT_ENCODING)
  83.     {
  84.         parent::SOAP_Base('HTTP');
  85.         $this->urlparts = @parse_url($url);
  86.         $this->url = $url;
  87.         $this->encoding = $encoding;
  88.     }
  89.  
  90.     /**
  91.      * Sends and receives SOAP data.
  92.      *
  93.      * @access public
  94.      *
  95.      * @param string  Outgoing SOAP data.
  96.      * @param array   Options.
  97.      *
  98.      * @return string|SOAP_Fault
  99.      */
  100.     function send($msg$options = array())
  101.     {
  102.         $this->fault = null;
  103.  
  104.         if (!$this->_validateUrl()) {
  105.             return $this->fault;
  106.         }
  107.  
  108.         if (isset($options['timeout'])) {
  109.             $this->timeout = (int)$options['timeout'];
  110.         }
  111.  
  112.         if (strcasecmp($this->urlparts['scheme']'HTTP'== 0{
  113.             return $this->_sendHTTP($msg$options);
  114.         elseif (strcasecmp($this->urlparts['scheme']'HTTPS'== 0{
  115.             return $this->_sendHTTPS($msg$options);
  116.         }
  117.  
  118.         return $this->_raiseSoapFault('Invalid url scheme ' $this->url);
  119.     }
  120.  
  121.     /**
  122.      * Sets data for HTTP authentication, creates authorization header.
  123.      *
  124.      * @param string $username   Username.
  125.      * @param string $password   Response data, minus HTTP headers.
  126.      *
  127.      * @access public
  128.      */
  129.     function setCredentials($username$password)
  130.     {
  131.         $this->headers['Authorization''Basic ' base64_encode($username ':' $password);
  132.     }
  133.  
  134.     /**
  135.      * Adds a cookie.
  136.      *
  137.      * @access public
  138.      * @param string $name  Cookie name.
  139.      * @param mixed $value  Cookie value.
  140.      */
  141.     function addCookie($name$value)
  142.     {
  143.         $this->cookies[$name$value;
  144.     }
  145.  
  146.     /**
  147.      * Generates the correct headers for the cookies.
  148.      *
  149.      * @access private
  150.      *
  151.      * @param array $options  Cookie options. If 'nocookies' is set and true
  152.      *                         the cookies from the last response are added
  153.      *                         automatically. 'cookies' is name-value-hash with
  154.      *                         a list of cookies to add.
  155.      *
  156.      * @return string  The cookie header value.
  157.      */
  158.     function _generateCookieHeader($options)
  159.     {
  160.         $this->cookies = array();
  161.  
  162.         if (empty($options['nocookies']&&
  163.             isset($this->result_cookies)) {
  164.             // Add the cookies we got from the last request.
  165.             foreach ($this->result_cookies as $cookie{
  166.                 if ($cookie['domain'== $this->urlparts['host']{
  167.                     $this->cookies[$cookie['name']] $cookie['value'];
  168.                 }
  169.             }
  170.         }
  171.  
  172.         // Add cookies the user wants to set.
  173.         if (isset($options['cookies'])) {
  174.             foreach ($options['cookies'as $cookie{
  175.                 if ($cookie['domain'== $this->urlparts['host']{
  176.                     $this->cookies[$cookie['name']] $cookie['value'];
  177.                 }
  178.             }
  179.         }
  180.  
  181.         $cookies '';
  182.         foreach ($this->cookies as $name => $value{
  183.             if (!empty($cookies)) {
  184.                 $cookies .= '; ';
  185.             }
  186.             $cookies .= urlencode($name'=' urlencode($value);
  187.         }
  188.  
  189.         return $cookies;
  190.     }
  191.  
  192.     /**
  193.      * Validate url data passed to constructor.
  194.      *
  195.      * @access private
  196.      * @return boolean 
  197.      */
  198.     function _validateUrl()
  199.     {
  200.         if (!is_array($this->urlparts) ) {
  201.             $this->_raiseSoapFault('Unable to parse URL ' $this->url);
  202.             return false;
  203.         }
  204.         if (!isset($this->urlparts['host'])) {
  205.             $this->_raiseSoapFault('No host in URL ' $this->url);
  206.             return false;
  207.         }
  208.         if (!isset($this->urlparts['port'])) {
  209.             if (strcasecmp($this->urlparts['scheme']'HTTP'== 0{
  210.                 $this->urlparts['port'= 80;
  211.             elseif (strcasecmp($this->urlparts['scheme']'HTTPS'== 0{
  212.                 $this->urlparts['port'= 443;
  213.             }
  214.  
  215.         }
  216.         if (isset($this->urlparts['user'])) {
  217.             $this->setCredentials(urldecode($this->urlparts['user']),
  218.                                   urldecode($this->urlparts['pass']));
  219.         }
  220.         if (!isset($this->urlparts['path']|| !$this->urlparts['path']{
  221.             $this->urlparts['path''/';
  222.         }
  223.  
  224.         return true;
  225.     }
  226.  
  227.     /**
  228.      * Finds out what the encoding is.
  229.      * Sets the object property accordingly.
  230.      *
  231.      * @access private
  232.      * @param array $headers  Headers.
  233.      */
  234.     function _parseEncoding($headers)
  235.     {
  236.         $h stristr($headers'Content-Type');
  237.         preg_match_all('/^Content-Type:\s*(.*)$/im'$h$ctPREG_SET_ORDER);
  238.         $n count($ct);
  239.         $ct $ct[$n - 1];
  240.  
  241.         // Strip the string of \r.
  242.         $this->result_content_type = str_replace("\r"''$ct[1]);
  243.  
  244.         if (preg_match('/(.*?)(?:;\s?charset=)(.*)/i',
  245.                        $this->result_content_type,
  246.                        $m)) {
  247.             $this->result_content_type = $m[1];
  248.             if (count($m> 2{
  249.                 $enc strtoupper(str_replace('"'''$m[2]));
  250.                 if (in_array($enc$this->_encodings)) {
  251.                     $this->result_encoding = $enc;
  252.                 }
  253.             }
  254.         }
  255.  
  256.         // Deal with broken servers that don't set content type on faults.
  257.         if (!$this->result_content_type{
  258.             $this->result_content_type = 'text/xml';
  259.         }
  260.     }
  261.  
  262.     /**
  263.      * Parses the headers.
  264.      *
  265.      * @param array $headers  The headers.
  266.      */
  267.     function _parseHeaders($headers)
  268.     {
  269.         /* Largely borrowed from HTTP_Request. */
  270.         $this->result_headers = array();
  271.         $headers split("\r?\n"$headers);
  272.         foreach ($headers as $value{
  273.             if (strpos($value,':'=== false{
  274.                 $this->result_headers[0$value;
  275.                 continue;
  276.             }
  277.             list($name$valuesplit(':'$value);
  278.             $headername strtolower($name);
  279.             $headervalue trim($value);
  280.             $this->result_headers[$headername$headervalue;
  281.  
  282.             if ($headername == 'set-cookie'{
  283.                 // Parse a SetCookie header to fill _cookies array.
  284.                 $cookie = array('expires' => null,
  285.                                 'domain'  => $this->urlparts['host'],
  286.                                 'path'    => null,
  287.                                 'secure'  => false);
  288.  
  289.                 if (!strpos($headervalue';')) {
  290.                     // Only a name=value pair.
  291.                     list($cookie['name']$cookie['value']array_map('trim'explode('='$headervalue));
  292.                     $cookie['name']  urldecode($cookie['name']);
  293.                     $cookie['value'urldecode($cookie['value']);
  294.  
  295.                 else {
  296.                     // Some optional parameters are supplied.
  297.                     $elements explode(';'$headervalue);
  298.                     list($cookie['name']$cookie['value']array_map('trim'explode('='$elements[0]));
  299.                     $cookie['name']  urldecode($cookie['name']);
  300.                     $cookie['value'urldecode($cookie['value']);
  301.  
  302.                     for ($i = 1; $i count($elements);$i++{
  303.                         list($elName$elValuearray_map('trim'explode('='$elements[$i]));
  304.                         if ('secure' == $elName{
  305.                             $cookie['secure'= true;
  306.                         elseif ('expires' == $elName{
  307.                             $cookie['expires'str_replace('"'''$elValue);
  308.                         elseif ('path' == $elName OR 'domain' == $elName{
  309.                             $cookie[$elNameurldecode($elValue);
  310.                         else {
  311.                             $cookie[$elName$elValue;
  312.                         }
  313.                     }
  314.                 }
  315.                 $this->result_cookies[$cookie;
  316.             }
  317.         }
  318.     }
  319.  
  320.     /**
  321.      * Removes HTTP headers from response.
  322.      *
  323.      * @return boolean 
  324.      * @access private
  325.      */
  326.     function _parseResponse()
  327.     {
  328.         if (!preg_match("/^(.*?)\r?\n\r?\n(.*)/s",
  329.                        $this->incoming_payload,
  330.                        $match)) {
  331.             $this->_raiseSoapFault('Invalid HTTP Response');
  332.             return false;
  333.         }
  334.  
  335.         $this->response $match[2];
  336.         // Find the response error, some servers response with 500 for
  337.         // SOAP faults.
  338.         $this->_parseHeaders($match[1]);
  339.  
  340.         list($code$msgsscanf($this->result_headers[0]'%s %s %s');
  341.         unset($this->result_headers[0]);
  342.  
  343.         switch($code{
  344.             case 100: // Continue
  345.                 $this->incoming_payload = $match[2];
  346.                 return $this->_parseResponse();
  347.             case 200:
  348.             case 202:
  349.                 $this->incoming_payload = trim($match[2]);
  350.                 if (!strlen($this->incoming_payload)) {
  351.                     /* Valid one-way message response. */
  352.                     return true;
  353.                 }
  354.                 break;
  355.             case 400:
  356.                 $this->_raiseSoapFault("HTTP Response $code Bad Request");
  357.                 return false;
  358.             case 401:
  359.                 $this->_raiseSoapFault("HTTP Response $code Authentication Failed");
  360.                 return false;
  361.             case 403:
  362.                 $this->_raiseSoapFault("HTTP Response $code Forbidden");
  363.                 return false;
  364.             case 404:
  365.                 $this->_raiseSoapFault("HTTP Response $code Not Found");
  366.                 return false;
  367.             case 407:
  368.                 $this->_raiseSoapFault("HTTP Response $code Proxy Authentication Required");
  369.                 return false;
  370.             case 408:
  371.                 $this->_raiseSoapFault("HTTP Response $code Request Timeout");
  372.                 return false;
  373.             case 410:
  374.                 $this->_raiseSoapFault("HTTP Response $code Gone");
  375.                 return false;
  376.             default:
  377.                 if ($code >= 400 && $code < 500{
  378.                     $this->_raiseSoapFault("HTTP Response $code Not Found, Server message: $msg");
  379.                     return false;
  380.                 }
  381.                 break;
  382.         }
  383.  
  384.         $this->_parseEncoding($match[1]);
  385.  
  386.         if ($this->result_content_type == 'application/dime'{
  387.             // XXX quick hack insertion of DIME
  388.             if (PEAR::isError($this->_decodeDIMEMessage($this->response$this->headers$this->attachments))) {
  389.                 // _decodeDIMEMessage already raised $this->fault
  390.                 return false;
  391.             }
  392.             $this->result_content_type = $this->headers['content-type'];
  393.         elseif (stristr($this->result_content_type'multipart/related')) {
  394.             $this->response $this->incoming_payload;
  395.             if (PEAR::isError($this->_decodeMimeMessage($this->response$this->headers$this->attachments))) {
  396.                 // _decodeMimeMessage already raised $this->fault
  397.                 return false;
  398.             }
  399.         elseif ($this->result_content_type != 'text/xml'{
  400.             $this->_raiseSoapFault($this->response);
  401.             return false;
  402.         }
  403.  
  404.         // if no content, return false
  405.         return strlen($this->response> 0;
  406.     }
  407.  
  408.     /**
  409.      * Creates an HTTP request, including headers, for the outgoing request.
  410.      *
  411.      * @access private
  412.      *
  413.      * @param string $msg     Outgoing SOAP package.
  414.      * @param array $options  Options.
  415.      *
  416.      * @return string  Outgoing payload.
  417.      */
  418.     function _getRequest($msg$options)
  419.     {
  420.         $this->headers = array();
  421.  
  422.         $action = isset($options['soapaction']$options['soapaction''';
  423.         $fullpath $this->urlparts['path'];
  424.         if (isset($this->urlparts['query'])) {
  425.             $fullpath .= '?' $this->urlparts['query'];
  426.         }
  427.         if (isset($this->urlparts['fragment'])) {
  428.             $fullpath .= '#' $this->urlparts['fragment'];
  429.         }
  430.  
  431.         if (isset($options['proxy_host'])) {
  432.             $fullpath 'http://' $this->urlparts['host'':' .
  433.                 $this->urlparts['port'$fullpath;
  434.         }
  435.  
  436.         if (isset($options['proxy_user'])) {
  437.             $this->headers['Proxy-Authorization''Basic ' .
  438.                 base64_encode($options['proxy_user'':' .
  439.                               $options['proxy_pass']);
  440.         }
  441.  
  442.         if (isset($options['user'])) {
  443.             $this->setCredentials($options['user']$options['pass']);
  444.         }
  445.  
  446.         $this->headers['User-Agent'$this->_userAgent;
  447.         $this->headers['Host'$this->urlparts['host'];
  448.         $this->headers['Content-Type'= "text/xml; charset=$this->encoding";
  449.         $this->headers['Content-Length'strlen($msg);
  450.         $this->headers['SOAPAction''"' $action '"';
  451.         $this->headers['Connection''close';
  452.  
  453.         if (isset($options['headers'])) {
  454.             $this->headers = array_merge($this->headers$options['headers']);
  455.         }
  456.  
  457.         $cookies $this->_generateCookieHeader($options);
  458.         if ($cookies{
  459.             $this->headers['Cookie'$cookies;
  460.         }
  461.  
  462.         $headers '';
  463.         foreach ($this->headers as $k => $v{
  464.             $headers .= "$k$v\r\n";
  465.         }
  466.         $this->outgoing_payload = "POST $fullpath HTTP/1.0\r\n" . $headers .
  467.             "\r\n" $msg;
  468.  
  469.         return $this->outgoing_payload;
  470.     }
  471.  
  472.     /**
  473.      * Sends the outgoing HTTP request and reads and parses the response.
  474.      *
  475.      * @access private
  476.      *
  477.      * @param string $msg     Outgoing SOAP package.
  478.      * @param array $options  Options.
  479.      *
  480.      * @return string  Response data without HTTP headers.
  481.      */
  482.     function _sendHTTP($msg$options)
  483.     {
  484.         $this->incoming_payload = '';
  485.         $this->_getRequest($msg$options);
  486.         $host $this->urlparts['host'];
  487.         $port $this->urlparts['port'];
  488.         if (isset($options['proxy_host'])) {
  489.             $host $options['proxy_host'];
  490.             $port = isset($options['proxy_port']$options['proxy_port': 8080;
  491.         }
  492.         // Send.
  493.         if ($this->timeout > 0{
  494.             $fp @fsockopen($host$port$this->errno$this->errmsg$this->timeout);
  495.         else {
  496.             $fp @fsockopen($host$port$this->errno$this->errmsg);
  497.         }
  498.         if (!$fp{
  499.             return $this->_raiseSoapFault("Connect Error to $host:$port");
  500.         }
  501.         if ($this->timeout > 0{
  502.             // some builds of PHP do not support this, silence the warning
  503.             @socket_set_timeout($fp$this->timeout);
  504.         }
  505.         if (!fputs($fp$this->outgoing_payloadstrlen($this->outgoing_payload))) {
  506.             return $this->_raiseSoapFault("Error POSTing Data to $host");
  507.         }
  508.  
  509.         // get reponse
  510.         // XXX time consumer
  511.         do {
  512.             $data fread($fp4096);
  513.             $_tmp_status socket_get_status($fp);
  514.             if ($_tmp_status['timed_out']{
  515.                 return $this->_raiseSoapFault("Timed out read from $host");
  516.             else {
  517.                 $this->incoming_payload .= $data;
  518.             }
  519.         while (!$_tmp_status['eof']);
  520.  
  521.         fclose($fp);
  522.  
  523.         if (!$this->_parseResponse()) {
  524.             return $this->fault;
  525.         }
  526.         return $this->response;
  527.     }
  528.  
  529.     /**
  530.      * Sends the outgoing HTTPS request and reads and parses the response.
  531.      *
  532.      * @access private
  533.      *
  534.      * @param string $msg     Outgoing SOAP package.
  535.      * @param array $options  Options.
  536.      *
  537.      * @return string  Response data without HTTP headers.
  538.      */
  539.     function _sendHTTPS($msg$options)
  540.     {
  541.         /* Check if the required curl extension is installed. */
  542.         if (!extension_loaded('curl')) {
  543.             return $this->_raiseSoapFault('CURL Extension is required for HTTPS');
  544.         }
  545.  
  546.         $ch curl_init();
  547.  
  548.         if (isset($options['proxy_host'])) {
  549.             $port = isset($options['proxy_port']$options['proxy_port': 8080;
  550.             curl_setopt($chCURLOPT_PROXY,
  551.                         $options['proxy_host'':' $port);
  552.         }
  553.         if (isset($options['proxy_user'])) {
  554.             curl_setopt($chCURLOPT_PROXYUSERPWD,
  555.                         $options['proxy_user'':' $options['proxy_pass']);
  556.         }
  557.  
  558.         if (isset($options['user'])) {
  559.             curl_setopt($chCURLOPT_USERPWD,
  560.                         $options['user'':' $options['pass']);
  561.         }
  562.  
  563.         $headers = array();
  564.         $action = isset($options['soapaction']$options['soapaction''';
  565.         $headers['Content-Type'= "text/xml; charset=$this->encoding";
  566.         $headers['SOAPAction''"' $action '"';
  567.         if (isset($options['headers'])) {
  568.             $headers array_merge($headers$options['headers']);
  569.         }
  570.         foreach ($headers as $header => $value{
  571.             $headers[$header$header ': ' $value;
  572.         }
  573.         curl_setopt($chCURLOPT_HTTPHEADER$headers);
  574.         curl_setopt($chCURLOPT_USERAGENT$this->_userAgent);
  575.  
  576.         if ($this->timeout{
  577.             curl_setopt($chCURLOPT_TIMEOUT$this->timeout);
  578.         }
  579.  
  580.         curl_setopt($chCURLOPT_POSTFIELDS$msg);
  581.         curl_setopt($chCURLOPT_URL$this->url);
  582.         curl_setopt($chCURLOPT_POST1);
  583.         curl_setopt($chCURLOPT_FAILONERROR0);
  584.         curl_setopt($chCURLOPT_RETURNTRANSFER1);
  585.         curl_setopt($chCURLOPT_HEADER1);
  586.         if (defined('CURLOPT_HTTP_VERSION')) {
  587.             curl_setopt($chCURLOPT_HTTP_VERSION1);
  588.         }
  589.         if (!ini_get('safe_mode'&& !ini_get('open_basedir')) {
  590.             curl_setopt($chCURLOPT_FOLLOWLOCATION1);
  591.         }
  592.         $cookies $this->_generateCookieHeader($options);
  593.         if ($cookies{
  594.             curl_setopt($chCURLOPT_COOKIE$cookies);
  595.         }
  596.  
  597.         if (isset($options['curl'])) {
  598.             foreach ($options['curl'as $key => $val{
  599.                 curl_setopt($ch$key$val);
  600.             }
  601.         }
  602.  
  603.         // Save the outgoing XML. This doesn't quite match _sendHTTP as CURL
  604.         // generates the headers, but having the XML is usually the most
  605.         // important part for tracing/debugging.
  606.         $this->outgoing_payload = $msg;
  607.  
  608.         $this->incoming_payload = curl_exec($ch);
  609.         if (!$this->incoming_payload{
  610.             $m 'curl_exec error ' curl_errno($ch' ' curl_error($ch);
  611.             curl_close($ch);
  612.             return $this->_raiseSoapFault($m);
  613.         }
  614.         curl_close($ch);
  615.  
  616.         if (!$this->_parseResponse()) {
  617.             return $this->fault;
  618.         }
  619.  
  620.         return $this->response;
  621.     }
  622.  
  623. }

Documentation generated on Mon, 04 Aug 2008 20:00:26 -0400 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.