Completed
Pull Request — master (#8)
by
unknown
01:56
created

CurlSoapClient::___configProxy()   D

Complexity

Conditions 9
Paths 19

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 10.4768

Importance

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