Completed
Pull Request — master (#7)
by Haruaki
01:51
created

CurlSoapClient::___isErrorResponse()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6

Importance

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