Completed
Push — master ( 336258...d5ac01 )
by Haruaki
01:52
created

CurlSoapClient::___configProxy()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6.1308

Importance

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