Passed
Push — master ( 3a7230...8bfcda )
by Haruaki
34s
created

CurlSoapClient::__construct()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5.009

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 13
cts 14
cp 0.9286
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 13
nc 8
nop 2
crap 5.009
1
<?php
2
/**
3
 * curlsoapclient - SoapClient with ext-curl. -
4
 *
5
 * @author    aaharu
6
 * @copyright Copyright (c) 2014 aaharu
7
 * @license   MIT License
8
 */
9
10
namespace Aaharu\Soap;
11
12
use SoapClient;
13
use SoapFault;
14
15
/**
16
 * @see https://github.com/php/php-src/tree/master/ext/soap
17
 */
18
class CurlSoapClient extends SoapClient
19
{
20
    protected $curl = null; ///< cURL handle
21
    protected $redirect_max; ///< max redirect counts
22
    protected $curl_timeout; ///< cURL request time-out seconds
23
    protected $proxy_type = CURLPROXY_HTTP; ///< proxy_type such as http, socks4, socks5
24
    private $redirect_count = 0;
25
26 13
    public function __construct($wsdl, array $options)
27
    {
28 13
        parent::__construct($wsdl, $options);
29 13
        $this->redirect_max = 5;
30 13
        if (isset($options['redirect_max'])) {
31 1
            $this->redirect_max = (int)$options['redirect_max'];
32
        }
33 13
        $this->curl_timeout = 30;
34 13
        if (isset($options['curl_timeout'])) {
35 1
            $this->curl_timeout = (int)$options['curl_timeout'];
36
        }
37 13
        if (isset($options['proxy_type']) &&
38 13
            in_array($options['proxy_type'], array('http', 'socks4', 'socks5'), true)) {
39
            $this->proxy_type = $options['proxy_type'];
40
        }
41 13
        $this->curl = curl_init();
42 13
        $this->_cookies = array();
43 13
    }
44
45 13
    public function __destruct()
46
    {
47 13
        if (isset($this->curl)) {
48 13
            curl_close($this->curl);
49
        }
50 13
    }
51
52 1
    public function ___curlSetOpt($option, $value)
53
    {
54 1
        curl_setopt($this->curl, $option, $value);
55 1
    }
56
57 1
    public function __getCookies()
58
    {
59 1
        return $this->_cookies;
60
    }
61
62 1
    public function __setCookie($name, $value = null)
63
    {
64 1
        if (!isset($value)) {
65 1
            unset($this->_cookies[$name]);
66 1
            return;
67
        }
68 1
        $this->_cookies[$name] = (array)$value;
69 1
    }
70
71
    /**
72
     * Execute SOAP requests.
73
     *
74
     * @param string $request SOAP request
75
     * @param string $location SOAP address
76
     * @param string $action SOAP action
77
     * @param int $version SOAP version
78
     * @param int $one_way
79
     * @throws \Exception
80
     * @throws \SoapFault
81
     * @return string|object (string) SOAP response / (object) SoapFault object
82
     */
83 13
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
84
    {
85 13
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
86 13
        curl_setopt($this->curl, CURLOPT_HEADER, true);
87 13
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $request);
88 13
        if (isset($this->trace) && $this->trace) {
89 3
            curl_setopt($this->curl, CURLINFO_HEADER_OUT, true);
90
        }
91
92 13
        $this->___configHeader($action, $version);
93 13
        $this->___configCompression();
94 13
        $this->___configTimeout();
95 13
        if (!$this->___isEmptyExtProperty('_user_agent')) {
96 1
            curl_setopt($this->curl, CURLOPT_USERAGENT, $this->_user_agent);
97
        }
98 13
        $this->___configHttpAuthentication();
99 13
        $this->___configProxy();
100 13
        if (!$this->___isEmptyExtProperty('_ssl_method')) {
101 1
            switch ($this->_ssl_method) {
102 1
                case SOAP_SSL_METHOD_SSLv2:
103
                    curl_setopt($this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv2);
104
                    break;
105 1
                case SOAP_SSL_METHOD_SSLv3:
106
                    curl_setopt($this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3);
107
                    break;
108
                default:
109 1
                    curl_setopt($this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT);
110 1
                    break;
111
            }
112
        }
113
114
        try {
115 13
            $response = $this->___curlCall($location);
116 7
        } catch (SoapFault $fault) {
117 7
            if (isset($this->_exceptions) && empty($this->_exceptions)) {
118
                // if exceptions option is false, return SoapFault object
119 1
                return $fault;
120
            }
121 6
            throw $fault;
122
        }
123
124 6
        if ($one_way) {
125
            return '';
126
        }
127
128 6
        return $response;
129
    }
130
131
    /**
132
     * set CURLOPT_HTTPHEADER.
133
     *
134
     * @param string $action SOAP action
135
     * @param int $version SOAP version
136
     */
137 13
    private function ___configHeader($action, $version)
138
    {
139 13
        $header = array();
140 13
        if (isset($this->_keep_alive) && empty($this->_keep_alive)) {
141 1
            $header[] = 'Connection: close';
142
        } else {
143 12
            $header[] = 'Connection: Keep-Alive';
144
        }
145 13
        if ($version === SOAP_1_2) {
146 1
            $header[] = "Content-Type: application/soap+xml; charset=utf-8; action=\"{$action}\"";
147
        } else {
148 12
            $header[] = 'Content-Type: text/xml; charset=utf-8';
149 12
            $header[] = "SOAPAction: \"{$action}\"";
150
        }
151 13
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, $header);
152 13
    }
153
154
    /**
155
     * set CURLOPT_ENCODING.
156
     */
157 13
    private function ___configCompression()
158
    {
159 13
        if (!isset($this->compression)) {
160 10
            return;
161
        }
162 3
        if ($this->compression & SOAP_COMPRESSION_ACCEPT) {
163 1
            curl_setopt($this->curl, CURLOPT_ENCODING, '');
164 2
        } elseif ($this->compression & SOAP_COMPRESSION_DEFLATE) {
165 1
            curl_setopt($this->curl, CURLOPT_ENCODING, 'deflate');
166
        } else {
167 1
            curl_setopt($this->curl, CURLOPT_ENCODING, 'gzip');
168
        }
169 3
    }
170
171
    /**
172
     * set CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT.
173
     */
174 13
    private function ___configTimeout()
175
    {
176 13
        $connection_timeout = 10; // default
177 13
        if (!$this->___isEmptyExtProperty('_connection_timeout')) {
178 1
            $connection_timeout = $this->_connection_timeout;
179
        }
180 13
        curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, $connection_timeout);
181 13
        curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->curl_timeout);
182 13
    }
183
184
    /**
185
     * set CURLOPT_USERPWD and CURLOPT_HTTPAUTH.
186
     */
187 13
    private function ___configHttpAuthentication()
188
    {
189 13
        if ($this->___isEmptyExtProperty('_login') || $this->___isEmptyExtProperty('_password')) {
190 11
            return;
191
        }
192 2
        curl_setopt($this->curl, CURLOPT_USERPWD, $this->_login . ':' . $this->_password);
193 2
        if (property_exists($this, '_digest')) {
194 1
            curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE);
195
        } else {
196 1
            curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
197
        }
198 2
    }
199
200
    /**
201
     * set proxy options.
202
     */
203 13
    private function ___configProxy()
204
    {
205 13
        if ($this->___isEmptyExtProperty('_proxy_host')) {
206 12
            return;
207
        }
208 1
        curl_setopt($this->curl, CURLOPT_PROXY, $this->_proxy_host);
209 1
        if (!$this->___isEmptyExtProperty('_proxy_port')) {
210 1
            curl_setopt($this->curl, CURLOPT_PROXYPORT, $this->_proxy_port);
211
        }
212 1
        if (!$this->___isEmptyExtProperty('_proxy_login') && !$this->___isEmptyExtProperty('_proxy_password')) {
213 1
            curl_setopt($this->curl, CURLOPT_PROXYUSERPWD, $this->_proxy_login . ':' . $this->_proxy_password);
214
        }
215 1
        if (property_exists($this, '_digest')) {
216
            curl_setopt($this->curl, CURLOPT_PROXYAUTH, CURLAUTH_ANYSAFE);
217
        } else {
218 1
            curl_setopt($this->curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
219
        }
220 1
        if ($this->proxy_type === 'socks5') {
221
            curl_setopt($this->curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
222 1
        } elseif ($this->proxy_type === 'socks4') {
223
            curl_setopt($this->curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
224 1
        } elseif ($this->proxy_type === 'http') {
225
            // @see http://stackoverflow.com/questions/12288956/what-is-the-curl-option-curlopt-httpproxytunnel-means
226
            curl_setopt($this->curl, CURLOPT_HTTPPROXYTUNNEL, true);
227
        }
228 1
    }
229
230
    /**
231
     * Request cURL.
232
     *
233
     * @param string $location SOAP address
234
     * @param string $location
235
     * @throws \SoapFault
236
     * @return mixed response body
237
     */
238 13
    private function ___curlCall($location)
239
    {
240 13
        curl_setopt($this->curl, CURLOPT_URL, $location);
241
242 13
        if (!empty($this->_cookies)) {
243 1
            $cookies = array();
244 1
            foreach ($this->_cookies as $cookie_name => $cookie_value) {
245 1
                $cookies[] = $cookie_name . '=' . $cookie_value[0];
246
            }
247 1
            curl_setopt($this->curl, CURLOPT_COOKIE, implode('; ', $cookies));
248
        }
249
250 13
        $response = curl_exec($this->curl);
251 13
        if ($response === false) {
252 2
            throw new SoapFault(
253 2
                'HTTP',
254 2
                'Error Fetching http, ' . curl_error($this->curl) . ' (' . curl_errno($this->curl) . ')'
255
            );
256
        }
257
258 11
        $header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE);
259 11
        $response_header = substr($response, 0, $header_size);
260 11
        $response_body = substr($response, $header_size);
261 11
        $http_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
262
263 11
        if (isset($this->trace) && $this->trace) {
264 3
            $this->__last_request_headers = curl_getinfo($this->curl, CURLINFO_HEADER_OUT);
265 3
            $this->__last_response_headers = $response_header;
266
        }
267
268 11
        if ($http_code >= 300 && $http_code < 400) {
269 4
            $tmp = stristr($response_header, 'Location:');
270 4
            $line_end = strpos($tmp, "\n"); // "\r" will be trimmed
271 4
            if ($line_end === false) {
272 1
                throw new SoapFault('HTTP', 'Error Redirecting, No Location');
273
            }
274 3
            $new_location = trim(substr($tmp, 9, $line_end - 9));
275 3
            $url = parse_url($new_location);
276 3
            if ($url === false ||
277 3
                empty($url['scheme']) ||
278 3
                preg_match('/^https?$/i', $url['scheme']) !== 1
279
            ) {
280 1
                throw new SoapFault('HTTP', 'Error Redirecting, Invalid Location');
281
            }
282 2
            if (++$this->redirect_count > $this->redirect_max) {
283 1
                throw new SoapFault('HTTP', 'Redirection limit reached, aborting');
284
            }
285 2
            return $this->___curlCall($new_location);
286
        }
287
288 8
        if ($http_code >= 400 && $this->___isErrorResponse($response_body)) {
289 2
            $string_http_code = (string)$http_code;
290 2
            $code_position = strpos($response_header, $string_http_code);
291 2
            $tmp = substr($response_header, $code_position + strlen($string_http_code));
292 2
            $http_message = trim(strstr($tmp, "\n", true));
293 2
            throw new SoapFault('HTTP', $http_message);
294
        }
295
296 6
        return $response_body;
297
    }
298
299
    /**
300
     * check body is XML or not
301
     *
302
     * @param string $response_body server response body
303
     * @return boolean
304
     */
305 4
    private function ___isErrorResponse($response_body) {
306 4
        $response_length = strlen($response_body);
307 4
        if ($response_length === 0) {
308 1
            return true;
309
        }
310 3
        $content_type = curl_getinfo($this->curl, CURLINFO_CONTENT_TYPE);
311 3
        if (!empty($content_type)) {
312 3
            $tmp = explode(';', $content_type, 2);
313 3
            $content_type = $tmp[0];
314
        }
315 3
        if ($content_type === 'text/xml' || $content_type === 'application/soap+xml') {
316 1
            return false;
317
        }
318 2
        $str = ltrim($response_body);
319 2
        if (strncmp($str, '<?xml', 5)) {
320 1
            return true;
321
        }
322 1
        return false;
323
    }
324
325
326
    /**
327
     * SoapClient property util
328
     *
329
     * @param string $property property name
330
     * @return boolean
331
     */
332 13
    private function ___isEmptyExtProperty($property)
333
    {
334 13
        if (!isset($this->{$property})) {
335 13
            return true;
336
        }
337 4
        if (is_string($this->{$property})) {
338 3
            return strlen($this->{$property}) === 0;
339
        }
340
341 3
        return false;
342
    }
343
}
344