Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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
class 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
}