source: branches/version-2_11-dev/data/module/SOAP/Transport/HTTP.php @ 21299

Revision 21299, 20.0 KB checked in by Seasoft, 12 years ago (diff)

#1521 (PEAR::SOAP 配布と異なる部分がある)

  • 新しいバージョンの配布ファイルを上書きすることで解決

#1522 (PEAR::SOAP をバージョンアップ)

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