Completed
Push — master ( 0d41b7...b680fe )
by Haruaki
25s
created

CurlSoapClient::___isNotEmptyExtProperty()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 3
eloc 2
nc 3
nop 1
crap 12
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
14
/**
15
 * @see https://github.com/php/php-src/tree/master/ext/soap
16
 */
17
class CurlSoapClient extends SoapClient
18
{
19
    protected $curl = null; ///< cURL handle
20
    protected $redirect_max; ///< max redirect counts
21
    protected $curl_timeout; ///< cURL request time-out seconds
22
    private $redirect_count = 0;
23
24 12
    public function __construct($wsdl, array $options)
25
    {
26 12
        parent::__construct($wsdl, $options);
27 12
        $this->redirect_max = 5;
28 12
        if (isset($options['redirect_max'])) {
29 1
            $this->redirect_max = (int)$options['redirect_max'];
30 1
        }
31 12
        $this->curl_timeout = 30;
32 12
        if (isset($options['curl_timeout'])) {
33 1
            $this->curl_timeout = (int)$options['curl_timeout'];
34 1
        }
35 12
        $this->curl = curl_init();
36 12
        $this->_cookies = array();
37 12
    }
38
39 12
    public function __destruct()
40
    {
41 12
        if (isset($this->curl)) {
42 12
            curl_close($this->curl);
43 12
        }
44 12
    }
45
46 1
    public function ___curlSetOpt($option, $value)
47
    {
48 1
        curl_setopt($this->curl, $option, $value);
49 1
    }
50
51 1
    public function __getCookies()
52
    {
53 1
        return $this->_cookies;
54
    }
55
56 1
    public function __setCookie($name, $value = null)
57
    {
58 1
        if (!isset($value)) {
59
            unset($this->_cookies[$name]);
60
            return;
61
        }
62 1
        $this->_cookies[$name] = (array)$value;
63 1
    }
64
65
    /**
66
     * Execute SOAP requests.
67
     *
68
     * @param string $request SOAP request
69
     * @param string $location SOAP address
70
     * @param string $action SOAP action
71
     * @param int $version SOAP version
72
     * @param int $one_way
73
     * @throws \Exception
74
     * @throws \SoapFault
75
     * @return string|object (string) SOAP response / (object) SoapFault object
76
     */
77 12
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
78
    {
79 12
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
80 12
        curl_setopt($this->curl, CURLOPT_HEADER, true);
81 12
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $request);
82 12
        if (isset($this->trace) && $this->trace) {
83 3
            curl_setopt($this->curl, CURLINFO_HEADER_OUT, true);
84 3
        }
85
86 12
        $this->___configHeader($action, $version);
87 12
        $this->___configCompression();
88 12
        $this->___configTimeout();
89 12
        if ($this->___isNotEmptyExtProperty('_user_agent')) {
90 1
            curl_setopt($this->curl, CURLOPT_USERAGENT, $this->_user_agent);
91 1
        }
92 12
        $this->___configHttpAuthentication();
93 12
        $this->___configProxy();
94 12
        if (isset($this->_ssl_method) && is_int($this->_ssl_method)) {
95
            switch ($this->_ssl_method) {
96
                case SOAP_SSL_METHOD_SSLv2:
97
                    curl_setopt($this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv2);
98
                    break;
99
                case SOAP_SSL_METHOD_SSLv3:
100
                    curl_setopt($this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3);
101
                    break;
102
                default:
103
                    curl_setopt($this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT);
104
                    break;
105
            }
106
        }
107
108
        try {
109 12
            $response = $this->___curlCall($location);
110 12
        } catch (\SoapFault $fault) {
111 7
            if (isset($this->_exceptions) && empty($this->_exceptions)) {
112
                // if exceptions option is false, return \SoapFault object
113 1
                return $fault;
114
            }
115 6
            throw $fault;
116
        }
117
118 5
        if ($one_way) {
119
            return '';
120
        }
121
122 5
        return $response;
123
    }
124
125
    /**
126
     * set CURLOPT_HTTPHEADER.
127
     *
128
     * @param string $action SOAP action
129
     * @param int $version SOAP version
130
     * @return void
131
     */
132 12
    private function ___configHeader($action, $version)
133
    {
134 12
        $header = array();
135 12
        if (isset($this->_keep_alive) && empty($this->_keep_alive)) {
136 1
            $header[] = 'Connection: close';
137 1
        } else {
138 11
            $header[] = 'Connection: Keep-Alive';
139
        }
140 12
        if ($version === SOAP_1_2) {
141 1
            $header[] = "Content-Type: application/soap+xml; charset=utf-8; action=\"{$action}\"";
142 1
        } else {
143 11
            $header[] = 'Content-Type: text/xml; charset=utf-8';
144 11
            $header[] = "SOAPAction: \"{$action}\"";
145
        }
146 12
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, $header);
147 12
    }
148
149
    /**
150
     * set CURLOPT_ENCODING.
151
     *
152
     * @return void
153
     */
154 12
    private function ___configCompression()
155
    {
156 12
        if (isset($this->compression)) {
157 3
            if ($this->compression & SOAP_COMPRESSION_ACCEPT) {
158 1
                curl_setopt($this->curl, CURLOPT_ENCODING, '');
159 3
            } elseif ($this->compression & SOAP_COMPRESSION_DEFLATE) {
160 1
                curl_setopt($this->curl, CURLOPT_ENCODING, 'deflate');
161 1
            } else {
162 1
                curl_setopt($this->curl, CURLOPT_ENCODING, 'gzip');
163
            }
164 3
        }
165 12
    }
166
167
    /**
168
     * set CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT.
169
     *
170
     * @return void
171
     */
172 12
    private function ___configTimeout()
173
    {
174 12
        $connection_timeout = 10; // default
175 12
        if (isset($this->_connection_timeout) && is_int($this->_connection_timeout)) {
176 1
            $connection_timeout = $this->_connection_timeout;
177 1
        }
178 12
        curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, $connection_timeout);
179 12
        curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->curl_timeout);
180 12
    }
181
182
    /**
183
     * set CURLOPT_HTTPAUTH.
184
     *
185
     * @return void
186
     */
187 12
    private function ___configHttpAuthentication()
188
    {
189 12 View Code Duplication
        if ($this->___isNotEmptyExtProperty('_login') && $this->___isNotEmptyExtProperty('_password')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
190 12
            curl_setopt($this->curl, CURLOPT_USERPWD, $this->_login . ':' . $this->_password);
191 2
            if (property_exists($this, '_digest')) {
192 2
                curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE);
193 1
            } else {
194 1
                curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
195 1
            }
196
        }
197 2
    }
198 12
199
    /**
200
     * set proxy options.
201
     *
202
     * @return void
203
     */
204
    private function ___configProxy()
205 12
    {
206
        if ($this->___isNotEmptyExtProperty('_proxy_host')) {
207 12
            curl_setopt($this->curl, CURLOPT_PROXY, $this->_proxy_host);
208
        }
209
        if (isset($this->_proxy_port) && is_int($this->_proxy_port)) {
210 12
            curl_setopt($this->curl, CURLOPT_PROXYPORT, $this->_proxy_port);
211
        }
212 View Code Duplication
        if ($this->___isNotEmptyExtProperty('_proxy_login') && $this->___isNotEmptyExtProperty('_proxy_password')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
213 12
            curl_setopt($this->curl, CURLOPT_PROXYUSERPWD, $this->_proxy_login . ':' . $this->_proxy_password);
214 12
            if (property_exists($this, '_digest')) {
215
                curl_setopt($this->curl, CURLOPT_PROXYAUTH, CURLAUTH_ANYSAFE);
216
            } else {
217
                curl_setopt($this->curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
218
            }
219
        }
220
    }
221
222 12
    /**
223
     * Request cURL.
224
     *
225
     * @param[in] string $location SOAP address
226
     * @param string $location
227
     * @throws \SoapFault
228
     * @return mixed response body
229
     */
230
    private function ___curlCall($location)
231 12
    {
232
        curl_setopt($this->curl, CURLOPT_URL, $location);
233 12
234
        if (!empty($this->_cookies)) {
235 12
            $cookies = array();
236 1
            foreach ($this->_cookies as $cookie_name => $cookie_value) {
237 1
                $cookies[] = $cookie_name . '=' . $cookie_value[0];
238 1
            }
239 1
            curl_setopt($this->curl, CURLOPT_COOKIE, implode('; ', $cookies));
240 1
        }
241 1
242
        $response = curl_exec($this->curl);
243 12
        if ($response === false) {
244 12
            throw new \SoapFault(
245 2
                'HTTP',
246 2
                'Error Fetching http, ' . curl_error($this->curl) . ' (' . curl_errno($this->curl) . ')'
247 2
            );
248 2
        }
249
250
        $header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE);
251 10
        $response_header = substr($response, 0, $header_size);
252 10
        $response_body = substr($response, $header_size);
253 10
        $http_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
254 10
255
        if (isset($this->trace) && $this->trace) {
256 10
            $this->__last_request_headers = curl_getinfo($this->curl, CURLINFO_HEADER_OUT);
257 3
            $this->__last_response_headers = $response_header;
258 3
        }
259 3
260
        if ($http_code >= 300 && $http_code < 400) {
261 10
            $tmp = stristr($response_header, 'Location:');
262 4
            $line_end = strpos($tmp, "\n"); // "\r" will be trimmed
263 4
            if ($line_end === false) {
264 4
                throw new \SoapFault('HTTP', 'Error Redirecting, No Location');
265 1
            }
266
            $new_location = trim(substr($tmp, 9, $line_end - 9));
267 3
            $url = parse_url($new_location);
268 3
            if ($url === false ||
269 3
                empty($url['scheme']) ||
270 3
                preg_match('/^https?$/i', $url['scheme']) !== 1
271 2
            ) {
272 3
                throw new \SoapFault('HTTP', 'Error Redirecting, Invalid Location');
273 1
            }
274
            if (++$this->redirect_count > $this->redirect_max) {
275 2
                throw new \SoapFault('HTTP', 'Redirection limit reached, aborting');
276 1
            }
277
            return $this->___curlCall($new_location);
278 2
        }
279
280
        if ($http_code >= 400) {
281 7
            $is_error = false;
282 3
            $response_length = strlen($response_body);
283 3
            if ($response_length === 0) {
284 3
                $is_error = true;
285 1
            } elseif ($response_length > 0) {
286 3
                $is_xml = false;
287 2
                $content_type = curl_getinfo($this->curl, CURLINFO_CONTENT_TYPE);
288 2
                if ($content_type !== null) {
289 2
                    $separator_position = strpos($content_type, ';');
290 2
                    if ($separator_position !== false) {
291 2
                        $content_type = substr($content_type, 0, $separator_position);
292 2
                    }
293 2
                    if ($content_type === 'text/xml' || $content_type === 'application/soap+xml') {
294 2
                        $is_xml = true;
295 1
                    }
296 1
                }
297 2
                if (!$is_xml) {
298 2
                    $str = ltrim($response_body);
299 1
                    if (strncmp($str, '<?xml', 5)) {
300 1
                        $is_error = true;
301 1
                    }
302 1
                }
303 1
            }
304 2
305
            if ($is_error) {
306 3
                $string_http_code = (string)$http_code;
307 2
                $code_position = strpos($response_header, $string_http_code);
308 2
                $tmp = substr($response_header, $code_position + strlen($string_http_code));
309 2
                $http_message = trim(strstr($tmp, "\n", true));
310 2
                throw new \SoapFault('HTTP', $http_message);
311 2
            }
312
        }
313 1
314
        return $response_body;
315 5
    }
316
317
318
    /**
319
     * @param string $property
320
     */
321
    private function ___isNotEmptyExtProperty($property)
322
    {
323
        return isset($this->{$property}) && is_string($this->{$property}) && strlen($this->{$property}) > 0;
324
    }
325
}
326