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')) { |
|
|
|
|
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')) { |
|
|
|
|
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
|
|
|
|
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.