|
1
|
|
|
<?php |
|
2
|
|
|
namespace Rmccue\Requests\Transport; |
|
3
|
|
|
|
|
4
|
|
|
use Rmccue\Requests as Requests; |
|
5
|
|
|
use Rmccue\Requests\Exception as Exception; |
|
6
|
|
|
use Rmccue\Requests\Transport as Transport; |
|
7
|
|
|
use Rmccue\Requests\Exception\Transport\cURL as Exception_cURL; |
|
8
|
|
|
/** |
|
9
|
|
|
* cURL HTTP transport |
|
10
|
|
|
* |
|
11
|
|
|
* @package Rmccue\Requests |
|
12
|
|
|
* @subpackage Transport |
|
13
|
|
|
*/ |
|
14
|
|
|
|
|
15
|
|
|
/** |
|
16
|
|
|
* cURL HTTP transport |
|
17
|
|
|
* |
|
18
|
|
|
* @package Rmccue\Requests |
|
19
|
|
|
* @subpackage Transport |
|
20
|
|
|
*/ |
|
21
|
|
|
class cURL implements Transport { |
|
22
|
|
|
const CURL_7_10_5 = 0x070A05; |
|
23
|
|
|
const CURL_7_16_2 = 0x071002; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* Raw HTTP data |
|
27
|
|
|
* |
|
28
|
|
|
* @var string |
|
29
|
|
|
*/ |
|
30
|
|
|
public $headers = ''; |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* Raw body data |
|
34
|
|
|
* |
|
35
|
|
|
* @var string |
|
36
|
|
|
*/ |
|
37
|
|
|
public $response_data = ''; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* Information on the current request |
|
41
|
|
|
* |
|
42
|
|
|
* @var array cURL information array, see {@see https://secure.php.net/curl_getinfo} |
|
43
|
|
|
*/ |
|
44
|
|
|
public $info; |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* Version string |
|
48
|
|
|
* |
|
49
|
|
|
* @var long |
|
50
|
|
|
*/ |
|
51
|
|
|
public $version; |
|
52
|
|
|
|
|
53
|
|
|
/** |
|
54
|
|
|
* cURL handle |
|
55
|
|
|
* |
|
56
|
|
|
* @var resource |
|
57
|
|
|
*/ |
|
58
|
|
|
protected $handle; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* Hook dispatcher instance |
|
62
|
|
|
* |
|
63
|
|
|
* @var Rmccue\Requests\Hooks |
|
64
|
|
|
*/ |
|
65
|
|
|
protected $hooks; |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* Have we finished the headers yet? |
|
69
|
|
|
* |
|
70
|
|
|
* @var boolean |
|
71
|
|
|
*/ |
|
72
|
|
|
protected $done_headers = false; |
|
73
|
|
|
|
|
74
|
|
|
/** |
|
75
|
|
|
* If streaming to a file, keep the file pointer |
|
76
|
|
|
* |
|
77
|
|
|
* @var resource |
|
78
|
|
|
*/ |
|
79
|
|
|
protected $stream_handle; |
|
80
|
|
|
|
|
81
|
|
|
/** |
|
82
|
|
|
* How many bytes are in the response body? |
|
83
|
|
|
* |
|
84
|
|
|
* @var int |
|
85
|
|
|
*/ |
|
86
|
|
|
protected $response_bytes; |
|
87
|
|
|
|
|
88
|
|
|
/** |
|
89
|
|
|
* What's the maximum number of bytes we should keep? |
|
90
|
|
|
* |
|
91
|
|
|
* @var int|bool Byte count, or false if no limit. |
|
92
|
|
|
*/ |
|
93
|
|
|
protected $response_byte_limit; |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* Constructor |
|
97
|
|
|
*/ |
|
98
|
|
|
public function __construct() { |
|
99
|
|
|
$curl = curl_version(); |
|
100
|
|
|
$this->version = $curl['version_number']; |
|
101
|
|
|
$this->handle = curl_init(); |
|
102
|
|
|
|
|
103
|
|
|
curl_setopt($this->handle, CURLOPT_HEADER, false); |
|
104
|
|
|
curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1); |
|
105
|
|
|
if ($this->version >= self::CURL_7_10_5) { |
|
106
|
|
|
curl_setopt($this->handle, CURLOPT_ENCODING, ''); |
|
107
|
|
|
} |
|
108
|
|
|
if (defined('CURLOPT_PROTOCOLS')) { |
|
109
|
|
|
curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); |
|
110
|
|
|
} |
|
111
|
|
|
if (defined('CURLOPT_REDIR_PROTOCOLS')) { |
|
112
|
|
|
curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); |
|
113
|
|
|
} |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
/** |
|
117
|
|
|
* Destructor |
|
118
|
|
|
*/ |
|
119
|
|
|
public function __destruct() { |
|
120
|
|
|
if (is_resource($this->handle)) { |
|
121
|
|
|
curl_close($this->handle); |
|
122
|
|
|
} |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
/** |
|
126
|
|
|
* Perform a request |
|
127
|
|
|
* |
|
128
|
|
|
* @throws Rmccue\Requests\Exception On a cURL error (`curlerror`) |
|
129
|
|
|
* |
|
130
|
|
|
* @param string $url URL to request |
|
131
|
|
|
* @param array $headers Associative array of request headers |
|
132
|
|
|
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD |
|
133
|
|
|
* @param array $options Request options, see {@see Requests::response()} for documentation |
|
134
|
|
|
* @return string Raw HTTP result |
|
135
|
|
|
*/ |
|
136
|
|
|
public function request($url, $headers = array(), $data = array(), $options = array()) { |
|
137
|
|
|
$this->hooks = $options['hooks']; |
|
138
|
|
|
|
|
139
|
|
|
$this->setup_handle($url, $headers, $data, $options); |
|
140
|
|
|
|
|
141
|
|
|
$options['hooks']->dispatch('curl.before_send', array(&$this->handle)); |
|
142
|
|
|
|
|
143
|
|
View Code Duplication |
if ($options['filename'] !== false) { |
|
|
|
|
|
|
144
|
|
|
$this->stream_handle = fopen($options['filename'], 'wb'); |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
$this->response_data = ''; |
|
148
|
|
|
$this->response_bytes = 0; |
|
149
|
|
|
$this->response_byte_limit = false; |
|
150
|
|
|
if ($options['max_bytes'] !== false) { |
|
151
|
|
|
$this->response_byte_limit = $options['max_bytes']; |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
|
|
if (isset($options['verify'])) { |
|
155
|
|
|
if ($options['verify'] === false) { |
|
156
|
|
|
curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); |
|
157
|
|
|
curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0); |
|
158
|
|
|
} |
|
159
|
|
|
elseif (is_string($options['verify'])) { |
|
160
|
|
|
curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']); |
|
161
|
|
|
} |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
if (isset($options['verifyname']) && $options['verifyname'] === false) { |
|
165
|
|
|
curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
curl_exec($this->handle); |
|
169
|
|
|
$response = $this->response_data; |
|
170
|
|
|
|
|
171
|
|
|
$options['hooks']->dispatch('curl.after_send', array()); |
|
172
|
|
|
|
|
173
|
|
|
if (curl_errno($this->handle) === 23 || curl_errno($this->handle) === 61) { |
|
174
|
|
|
// Reset encoding and try again |
|
175
|
|
|
curl_setopt($this->handle, CURLOPT_ENCODING, 'none'); |
|
176
|
|
|
|
|
177
|
|
|
$this->response_data = ''; |
|
178
|
|
|
$this->response_bytes = 0; |
|
179
|
|
|
curl_exec($this->handle); |
|
180
|
|
|
$response = $this->response_data; |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
$this->process_response($response, $options); |
|
184
|
|
|
|
|
185
|
|
|
// Need to remove the $this reference from the curl handle. |
|
186
|
|
|
// Otherwise Rmccue\Requests\Transport_cURL wont be garbage collected and the curl_close() will never be called. |
|
187
|
|
|
curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null); |
|
188
|
|
|
curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null); |
|
189
|
|
|
|
|
190
|
|
|
return $this->headers; |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
/** |
|
194
|
|
|
* Send multiple requests simultaneously |
|
195
|
|
|
* |
|
196
|
|
|
* @param array $requests Request data |
|
197
|
|
|
* @param array $options Global options |
|
198
|
|
|
* @return array Array of Rmccue\Requests\Response objects (may contain Rmccue\Requests\Exception or string responses as well) |
|
|
|
|
|
|
199
|
|
|
*/ |
|
200
|
|
|
public function request_multiple($requests, $options) { |
|
201
|
|
|
// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯ |
|
202
|
|
|
if (empty($requests)) { |
|
203
|
|
|
return array(); |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
$multihandle = curl_multi_init(); |
|
207
|
|
|
$subrequests = array(); |
|
208
|
|
|
$subhandles = array(); |
|
209
|
|
|
|
|
210
|
|
|
$class = get_class($this); |
|
211
|
|
|
foreach ($requests as $id => $request) { |
|
212
|
|
|
$subrequests[$id] = new $class(); |
|
213
|
|
|
$subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']); |
|
|
|
|
|
|
214
|
|
|
$request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id])); |
|
215
|
|
|
curl_multi_add_handle($multihandle, $subhandles[$id]); |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
$completed = 0; |
|
219
|
|
|
$responses = array(); |
|
220
|
|
|
|
|
221
|
|
|
$request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle)); |
|
|
|
|
|
|
222
|
|
|
|
|
223
|
|
|
do { |
|
224
|
|
|
$active = false; |
|
225
|
|
|
|
|
226
|
|
|
do { |
|
227
|
|
|
$status = curl_multi_exec($multihandle, $active); |
|
228
|
|
|
} |
|
229
|
|
|
while ($status === CURLM_CALL_MULTI_PERFORM); |
|
230
|
|
|
|
|
231
|
|
|
$to_process = array(); |
|
232
|
|
|
|
|
233
|
|
|
// Read the information as needed |
|
234
|
|
|
while ($done = curl_multi_info_read($multihandle)) { |
|
235
|
|
|
$key = array_search($done['handle'], $subhandles, true); |
|
236
|
|
|
if (!isset($to_process[$key])) { |
|
237
|
|
|
$to_process[$key] = $done; |
|
238
|
|
|
} |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
// Parse the finished requests before we start getting the new ones |
|
242
|
|
|
foreach ($to_process as $key => $done) { |
|
243
|
|
|
$options = $requests[$key]['options']; |
|
244
|
|
|
if (CURLE_OK !== $done['result']) { |
|
245
|
|
|
//get error string for handle. |
|
246
|
|
|
$reason = curl_error($done['handle']); |
|
247
|
|
|
$exception = new Exception_cURL( |
|
248
|
|
|
$reason, |
|
249
|
|
|
Exception_cURL::EASY, |
|
250
|
|
|
$done['handle'], |
|
251
|
|
|
$done['result'] |
|
252
|
|
|
); |
|
253
|
|
|
$responses[$key] = $exception; |
|
254
|
|
|
$options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key])); |
|
255
|
|
|
} |
|
256
|
|
|
else { |
|
257
|
|
|
$responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options); |
|
258
|
|
|
|
|
259
|
|
|
$options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key])); |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
|
|
curl_multi_remove_handle($multihandle, $done['handle']); |
|
263
|
|
|
curl_close($done['handle']); |
|
264
|
|
|
|
|
265
|
|
View Code Duplication |
if (!is_string($responses[$key])) { |
|
266
|
|
|
$options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key)); |
|
267
|
|
|
} |
|
268
|
|
|
$completed++; |
|
269
|
|
|
} |
|
270
|
|
|
} |
|
271
|
|
|
while ($active || $completed < count($subrequests)); |
|
272
|
|
|
|
|
273
|
|
|
$request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle)); |
|
274
|
|
|
|
|
275
|
|
|
curl_multi_close($multihandle); |
|
276
|
|
|
|
|
277
|
|
|
return $responses; |
|
278
|
|
|
} |
|
279
|
|
|
|
|
280
|
|
|
/** |
|
281
|
|
|
* Get the cURL handle for use in a multi-request |
|
282
|
|
|
* |
|
283
|
|
|
* @param string $url URL to request |
|
284
|
|
|
* @param array $headers Associative array of request headers |
|
285
|
|
|
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD |
|
286
|
|
|
* @param array $options Request options, see {@see Requests::response()} for documentation |
|
287
|
|
|
* @return resource Subrequest's cURL handle |
|
288
|
|
|
*/ |
|
289
|
|
|
public function &get_subrequest_handle($url, $headers, $data, $options) { |
|
290
|
|
|
$this->setup_handle($url, $headers, $data, $options); |
|
291
|
|
|
|
|
292
|
|
View Code Duplication |
if ($options['filename'] !== false) { |
|
|
|
|
|
|
293
|
|
|
$this->stream_handle = fopen($options['filename'], 'wb'); |
|
294
|
|
|
} |
|
295
|
|
|
|
|
296
|
|
|
$this->response_data = ''; |
|
297
|
|
|
$this->response_bytes = 0; |
|
298
|
|
|
$this->response_byte_limit = false; |
|
299
|
|
|
if ($options['max_bytes'] !== false) { |
|
300
|
|
|
$this->response_byte_limit = $options['max_bytes']; |
|
301
|
|
|
} |
|
302
|
|
|
$this->hooks = $options['hooks']; |
|
303
|
|
|
|
|
304
|
|
|
return $this->handle; |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
|
|
/** |
|
308
|
|
|
* Setup the cURL handle for the given data |
|
309
|
|
|
* |
|
310
|
|
|
* @param string $url URL to request |
|
311
|
|
|
* @param array $headers Associative array of request headers |
|
312
|
|
|
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD |
|
313
|
|
|
* @param array $options Request options, see {@see Requests::response()} for documentation |
|
314
|
|
|
*/ |
|
315
|
|
|
protected function setup_handle($url, $headers, $data, $options) { |
|
316
|
|
|
$options['hooks']->dispatch('curl.before_request', array(&$this->handle)); |
|
317
|
|
|
|
|
318
|
|
|
// Force closing the connection for old versions of cURL (<7.22). |
|
319
|
|
|
if ( ! isset( $headers['Connection'] ) ) { |
|
320
|
|
|
$headers['Connection'] = 'close'; |
|
321
|
|
|
} |
|
322
|
|
|
|
|
323
|
|
|
$headers = Requests::flatten($headers); |
|
324
|
|
|
|
|
325
|
|
|
if (!empty($data)) { |
|
326
|
|
|
$data_format = $options['data_format']; |
|
327
|
|
|
|
|
328
|
|
|
if ($data_format === 'query') { |
|
329
|
|
|
$url = self::format_get($url, $data); |
|
|
|
|
|
|
330
|
|
|
$data = ''; |
|
331
|
|
|
} |
|
332
|
|
|
elseif (!is_string($data)) { |
|
333
|
|
|
$data = http_build_query($data, null, '&'); |
|
334
|
|
|
} |
|
335
|
|
|
} |
|
336
|
|
|
|
|
337
|
|
|
switch ($options['type']) { |
|
338
|
|
|
case Requests::POST: |
|
339
|
|
|
curl_setopt($this->handle, CURLOPT_POST, true); |
|
340
|
|
|
curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); |
|
341
|
|
|
break; |
|
342
|
|
|
case Requests::HEAD: |
|
343
|
|
|
curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); |
|
344
|
|
|
curl_setopt($this->handle, CURLOPT_NOBODY, true); |
|
345
|
|
|
break; |
|
346
|
|
|
case Requests::TRACE: |
|
347
|
|
|
curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); |
|
348
|
|
|
break; |
|
349
|
|
|
case Requests::PATCH: |
|
350
|
|
|
case Requests::PUT: |
|
351
|
|
|
case Requests::DELETE: |
|
352
|
|
|
case Requests::OPTIONS: |
|
353
|
|
|
default: |
|
354
|
|
|
curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); |
|
355
|
|
|
if (!empty($data)) { |
|
356
|
|
|
curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); |
|
357
|
|
|
} |
|
358
|
|
|
} |
|
359
|
|
|
|
|
360
|
|
|
// cURL requires a minimum timeout of 1 second when using the system |
|
361
|
|
|
// DNS resolver, as it uses `alarm()`, which is second resolution only. |
|
362
|
|
|
// There's no way to detect which DNS resolver is being used from our |
|
363
|
|
|
// end, so we need to round up regardless of the supplied timeout. |
|
364
|
|
|
// |
|
365
|
|
|
// https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609 |
|
366
|
|
|
$timeout = max($options['timeout'], 1); |
|
367
|
|
|
|
|
368
|
|
|
if (is_int($timeout) || $this->version < self::CURL_7_16_2) { |
|
369
|
|
|
curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout)); |
|
370
|
|
|
} |
|
371
|
|
|
else { |
|
372
|
|
|
curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000)); |
|
373
|
|
|
} |
|
374
|
|
|
|
|
375
|
|
|
if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) { |
|
376
|
|
|
curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout'])); |
|
377
|
|
|
} |
|
378
|
|
|
else { |
|
379
|
|
|
curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000)); |
|
380
|
|
|
} |
|
381
|
|
|
curl_setopt($this->handle, CURLOPT_URL, $url); |
|
382
|
|
|
curl_setopt($this->handle, CURLOPT_REFERER, $url); |
|
383
|
|
|
curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']); |
|
384
|
|
|
if (!empty($headers)) { |
|
385
|
|
|
curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers); |
|
386
|
|
|
} |
|
387
|
|
|
if ($options['protocol_version'] === 1.1) { |
|
388
|
|
|
curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); |
|
389
|
|
|
} |
|
390
|
|
|
else { |
|
391
|
|
|
curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); |
|
392
|
|
|
} |
|
393
|
|
|
|
|
394
|
|
|
if (true === $options['blocking']) { |
|
395
|
|
|
curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers')); |
|
396
|
|
|
curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, array(&$this, 'stream_body')); |
|
397
|
|
|
curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE); |
|
398
|
|
|
} |
|
399
|
|
|
} |
|
400
|
|
|
|
|
401
|
|
|
/** |
|
402
|
|
|
* Process a response |
|
403
|
|
|
* |
|
404
|
|
|
* @param string $response Response data from the body |
|
405
|
|
|
* @param array $options Request options |
|
406
|
|
|
* @return string HTTP response data including headers |
|
407
|
|
|
*/ |
|
408
|
|
|
public function process_response($response, $options) { |
|
409
|
|
|
if ($options['blocking'] === false) { |
|
410
|
|
|
$fake_headers = ''; |
|
411
|
|
|
$options['hooks']->dispatch('curl.after_request', array(&$fake_headers)); |
|
412
|
|
|
return false; |
|
413
|
|
|
} |
|
414
|
|
|
if ($options['filename'] !== false) { |
|
415
|
|
|
fclose($this->stream_handle); |
|
416
|
|
|
$this->headers = trim($this->headers); |
|
417
|
|
|
} |
|
418
|
|
|
else { |
|
419
|
|
|
$this->headers .= $response; |
|
420
|
|
|
} |
|
421
|
|
|
|
|
422
|
|
|
if (curl_errno($this->handle)) { |
|
423
|
|
|
$error = sprintf( |
|
424
|
|
|
'cURL error %s: %s', |
|
425
|
|
|
curl_errno($this->handle), |
|
426
|
|
|
curl_error($this->handle) |
|
427
|
|
|
); |
|
428
|
|
|
throw new Exception($error, 'curlerror', $this->handle); |
|
429
|
|
|
} |
|
430
|
|
|
$this->info = curl_getinfo($this->handle); |
|
431
|
|
|
|
|
432
|
|
|
$options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info)); |
|
433
|
|
|
return $this->headers; |
|
434
|
|
|
} |
|
435
|
|
|
|
|
436
|
|
|
/** |
|
437
|
|
|
* Collect the headers as they are received |
|
438
|
|
|
* |
|
439
|
|
|
* @param resource $handle cURL resource |
|
440
|
|
|
* @param string $headers Header string |
|
441
|
|
|
* @return integer Length of provided header |
|
442
|
|
|
*/ |
|
443
|
|
|
public function stream_headers($handle, $headers) { |
|
444
|
|
|
// Why do we do this? cURL will send both the final response and any |
|
445
|
|
|
// interim responses, such as a 100 Continue. We don't need that. |
|
446
|
|
|
// (We may want to keep this somewhere just in case) |
|
447
|
|
|
if ($this->done_headers) { |
|
448
|
|
|
$this->headers = ''; |
|
449
|
|
|
$this->done_headers = false; |
|
450
|
|
|
} |
|
451
|
|
|
$this->headers .= $headers; |
|
452
|
|
|
|
|
453
|
|
|
if ($headers === "\r\n") { |
|
454
|
|
|
$this->done_headers = true; |
|
455
|
|
|
} |
|
456
|
|
|
return strlen($headers); |
|
457
|
|
|
} |
|
458
|
|
|
|
|
459
|
|
|
/** |
|
460
|
|
|
* Collect data as it's received |
|
461
|
|
|
* |
|
462
|
|
|
* @since 1.6.1 |
|
463
|
|
|
* |
|
464
|
|
|
* @param resource $handle cURL resource |
|
465
|
|
|
* @param string $data Body data |
|
466
|
|
|
* @return integer Length of provided data |
|
467
|
|
|
*/ |
|
468
|
|
|
public function stream_body($handle, $data) { |
|
469
|
|
|
$this->hooks->dispatch('request.progress', array($data, $this->response_bytes, $this->response_byte_limit)); |
|
470
|
|
|
$data_length = strlen($data); |
|
471
|
|
|
|
|
472
|
|
|
// Are we limiting the response size? |
|
473
|
|
|
if ($this->response_byte_limit) { |
|
474
|
|
|
if ($this->response_bytes === $this->response_byte_limit) { |
|
475
|
|
|
// Already at maximum, move on |
|
476
|
|
|
return $data_length; |
|
477
|
|
|
} |
|
478
|
|
|
|
|
479
|
|
|
if (($this->response_bytes + $data_length) > $this->response_byte_limit) { |
|
480
|
|
|
// Limit the length |
|
481
|
|
|
$limited_length = ($this->response_byte_limit - $this->response_bytes); |
|
482
|
|
|
$data = substr($data, 0, $limited_length); |
|
483
|
|
|
} |
|
484
|
|
|
} |
|
485
|
|
|
|
|
486
|
|
|
if ($this->stream_handle) { |
|
487
|
|
|
fwrite($this->stream_handle, $data); |
|
488
|
|
|
} |
|
489
|
|
|
else { |
|
490
|
|
|
$this->response_data .= $data; |
|
491
|
|
|
} |
|
492
|
|
|
|
|
493
|
|
|
$this->response_bytes += strlen($data); |
|
494
|
|
|
return $data_length; |
|
495
|
|
|
} |
|
496
|
|
|
|
|
497
|
|
|
/** |
|
498
|
|
|
* Format a URL given GET data |
|
499
|
|
|
* |
|
500
|
|
|
* @param string $url |
|
501
|
|
|
* @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query} |
|
502
|
|
|
* @return string URL with data |
|
503
|
|
|
*/ |
|
504
|
|
|
protected static function format_get($url, $data) { |
|
505
|
|
|
if (!empty($data)) { |
|
506
|
|
|
$url_parts = parse_url($url); |
|
507
|
|
|
if (empty($url_parts['query'])) { |
|
508
|
|
|
$query = $url_parts['query'] = ''; |
|
509
|
|
|
} |
|
510
|
|
|
else { |
|
511
|
|
|
$query = $url_parts['query']; |
|
512
|
|
|
} |
|
513
|
|
|
|
|
514
|
|
|
$query .= '&' . http_build_query($data, null, '&'); |
|
515
|
|
|
$query = trim($query, '&'); |
|
516
|
|
|
|
|
517
|
|
|
if (empty($url_parts['query'])) { |
|
518
|
|
|
$url .= '?' . $query; |
|
519
|
|
|
} |
|
520
|
|
|
else { |
|
521
|
|
|
$url = str_replace($url_parts['query'], $query, $url); |
|
522
|
|
|
} |
|
523
|
|
|
} |
|
524
|
|
|
return $url; |
|
525
|
|
|
} |
|
526
|
|
|
|
|
527
|
|
|
/** |
|
528
|
|
|
* Whether this transport is valid |
|
529
|
|
|
* |
|
530
|
|
|
* @codeCoverageIgnore |
|
531
|
|
|
* @return boolean True if the transport is valid, false otherwise. |
|
532
|
|
|
*/ |
|
533
|
|
|
public static function test($capabilities = array()) { |
|
534
|
|
|
if (!function_exists('curl_init') || !function_exists('curl_exec')) { |
|
535
|
|
|
return false; |
|
536
|
|
|
} |
|
537
|
|
|
|
|
538
|
|
|
// If needed, check that our installed curl version supports SSL |
|
539
|
|
|
if (isset($capabilities['ssl']) && $capabilities['ssl']) { |
|
540
|
|
|
$curl_version = curl_version(); |
|
541
|
|
|
if (!(CURL_VERSION_SSL & $curl_version['features'])) { |
|
542
|
|
|
return false; |
|
543
|
|
|
} |
|
544
|
|
|
} |
|
545
|
|
|
|
|
546
|
|
|
return true; |
|
547
|
|
|
} |
|
548
|
|
|
} |
|
549
|
|
|
|
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.