Curl::setCookies()   B
last analyzed

Complexity

Conditions 6
Paths 10

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 18
c 1
b 0
f 0
nc 10
nop 1
dl 0
loc 28
ccs 0
cts 0
cp 0
crap 42
rs 8.439
1
<?php
2
/**
3
 * Created by rozbo at 2017/4/15 下午2:30
4
 */
5
6
namespace puck\helpers;
7
8
use puck\tools\Arr;
9
10
class Curl {
11
12
    const VERSION = '7.2.0';
13
    const DEFAULT_TIMEOUT = 20;
14
15
    public $retryCount = 3;
16
17
    public $curl;
18
    public $id = null;
19
20
    public $error = false;
21
    public $errorCode = 0;
22
    public $errorMessage = null;
23
24
    public $curlError = false;
25
    public $curlErrorCode = 0;
26
    public $curlErrorMessage = null;
27
28
    public $httpError = false;
29
    public $httpStatusCode = 0;
30
    public $httpErrorMessage = null;
31
32
    public $baseUrl = null;
33
    public $url = null;
34
    public $requestHeaders = null;
35
    public $responseHeaders = null;
36
    public $rawResponseHeaders = '';
37
    public $responseCookies = array();
38
    public $response = null;
39
    public $rawResponse = null;
40
41
    public $beforeSendFunction = null;
42
    public $downloadCompleteFunction = null;
43
    public $successFunction = null;
44
    public $errorFunction = null;
45
    public $completeFunction = null;
46
    public $fileHandle = null;
47
48
    private $cookies = array();
49
    private $headers = array();
50
    private $options = array();
51
52
    private $jsonDecoder = null;
53
    private $jsonPattern = '/^(?:application|text)\/(?:[a-z]+(?:[\.-][0-9a-z]+){0,}[\+\.]|x-)?json(?:-[a-z]+)?/i';
54
    private $xmlDecoder = null;
55
    private $xmlPattern = '~^(?:text/|application/(?:atom\+|rss\+)?)xml~i';
56
    private $defaultDecoder = null;
57
58
    public static $RFC2616 = array(
59
        // RFC2616: "any CHAR except CTLs or separators".
60
        // CHAR           = <any US-ASCII character (octets 0 - 127)>
61
        // CTL            = <any US-ASCII control character
62
        //                  (octets 0 - 31) and DEL (127)>
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
63
        // separators     = "(" | ")" | "<" | ">" | "@"
0 ignored issues
show
Unused Code Comprehensibility introduced by
41% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
64
        //                | "," | ";" | ":" | "\" | <">
65
        //                | "/" | "[" | "]" | "?" | "="
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
66
        //                | "{" | "}" | SP | HT
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
67
        // SP             = <US-ASCII SP, space (32)>
68
        // HT             = <US-ASCII HT, horizontal-tab (9)>
69
        // <">            = <US-ASCII double-quote mark (34)>
70
        '!', '#', '$', '%', '&', "'", '*', '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
71
        'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
72
        'Y', 'Z', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
73
        'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~',
74
    );
75
    public static $RFC6265 = array(
76
        // RFC6265: "US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash".
77
        // %x21
78
        '!',
79
        // %x23-2B
80
        '#', '$', '%', '&', "'", '(', ')', '*', '+',
81
        // %x2D-3A
82
        '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':',
83
        // %x3C-5B
84
        '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
85
        'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[',
86
        // %x5D-7E
87
        ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
88
        's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
89
    );
90
91
    private static $deferredProperties = array(
92
        'effectiveUrl',
93
        'rfc2616',
94
        'rfc6265',
95
        'totalTime',
96
    );
97
98
    /**
99
     * Construct
100
     *
101
     * @access public
102
     * @param  $base_url
103
     * @throws \ErrorException
104
     */
105 1
    public function __construct($base_url = null)
106
    {
107 1
        if (!extension_loaded('curl')) {
108
            throw new \ErrorException('cURL library is not loaded');
109
        }
110
111 1
        $this->curl = curl_init();
112 1
        $this->id = uniqid('', true);
113 1
        $this->setDefaultUserAgent();
114 1
        $this->setDefaultJsonDecoder();
115 1
        $this->setDefaultXmlDecoder();
116 1
        $this->setDefaultTimeout();
117 1
        $this->setOpt(CURLINFO_HEADER_OUT, true);
118 1
        $this->setOpt(CURLOPT_HEADERFUNCTION, array($this, 'headerCallback'));
119 1
        $this->setOpt(CURLOPT_RETURNTRANSFER, true);
120 1
        $this->headers = [];
121 1
        $this->setUrl($base_url);
122 1
    }
123
124
    /**
125
     * Before Send
126
     *
127
     * @access public
128
     * @param  $callback
129
     */
130
    public function beforeSend($callback)
131
    {
132
        $this->beforeSendFunction = $callback;
133
    }
134
135
    /**
136
     * Build Post Data
137
     *
138
     * @access public
139
     * @param  $data
140
     *
141
     * @return array|string
142
     */
143
    public function buildPostData($data)
144
    {
145
        $binary_data = false;
146
        if (is_array($data)) {
147
            // Return JSON-encoded string when the request's content-type is JSON.
148
            if (isset($this->headers['Content-Type']) &&
149
                preg_match($this->jsonPattern, $this->headers['Content-Type'])) {
150
                $json_str = json_encode($data);
151
                if (!($json_str === false)) {
152
                    $data = $json_str;
153
                }
154
            } else {
155
                // Manually build a single-dimensional array from a multi-dimensional array as using curl_setopt($ch,
156
                // CURLOPT_POSTFIELDS, $data) doesn't correctly handle multi-dimensional arrays when files are
157
                // referenced.
158
                if (Arr::isMultidim($data)) {
159
                    $data = Arr::flatten($data);
160
                }
161
162
                // Modify array values to ensure any referenced files are properly handled depending on the support of
163
                // the @filename API or CURLFile usage. This also fixes the warning "curl_setopt(): The usage of the
164
                // @filename API for file uploading is deprecated. Please use the CURLFile class instead". Ignore
165
                // non-file values prefixed with the @ character.
166
                foreach ($data as $key => $value) {
167
                    if (is_string($value) && strpos($value, '@') === 0 && is_file(substr($value, 1))) {
168
                        $binary_data = true;
169
                        if (class_exists('CURLFile')) {
170
                            $data[$key] = new \CURLFile(substr($value, 1));
171
                        }
172
                    } elseif ($value instanceof \CURLFile) {
173
                        $binary_data = true;
174
                    }
175
                }
176
            }
177
        }
178
179
        if (!$binary_data && (is_array($data) || is_object($data))) {
180
            $data = http_build_query($data, '', '&');
181
        }
182
183
        return $data;
184
    }
185
186
    /**
187
     * Call
188
     *
189
     * @access public
190
     */
191 3
    public function call()
192
    {
193 3
        $args = func_get_args();
194 3
        $function = array_shift($args);
195 3
        if (is_callable($function)) {
196
            array_unshift($args, $this);
197
            call_user_func_array($function, $args);
198
        }
199 3
    }
200
201
    /**
202
     * Close
203
     *
204
     * @access public
205
     */
206
    public function close()
207
    {
208
        if (is_resource($this->curl)) {
209
            curl_close($this->curl);
210
        }
211
        $this->options = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $options.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
212
        $this->jsonDecoder = null;
213
        $this->xmlDecoder = null;
214
        $this->defaultDecoder = null;
215
    }
216
217
    /**
218
     * Complete
219
     *
220
     * @access public
221
     * @param  $callback
222
     */
223
    public function complete($callback)
224
    {
225
        $this->completeFunction = $callback;
226
    }
227
228
    /**
229
     * Progress
230
     *
231
     * @access public
232
     * @param  $callback
233
     */
234
    public function progress($callback)
235
    {
236
        $this->setOpt(CURLOPT_PROGRESSFUNCTION, $callback);
237
        $this->setOpt(CURLOPT_NOPROGRESS, false);
238
    }
239
240
    /**
241
     * Delete
242
     *
243
     * @access public
244
     * @param  $url
245
     * @param  $query_parameters
246
     * @param  $data
247
     *
248
     * @return string
249
     */
250
    public function delete($url, $query_parameters = array(), $data = array())
251
    {
252
        if (is_array($url)) {
253
            $data = $query_parameters;
254
            $query_parameters = $url;
255
            $url = $this->baseUrl;
256
        }
257
258
        $this->setUrl($url, $query_parameters);
259
        $this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE');
260
        $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
261
        return $this->exec();
262
    }
263
264
    /**
265
     * Download Complete
266
     *
267
     * @access private
268
     * @param  $fh
269
     */
270
    private function downloadComplete($fh)
271
    {
272
        if (!$this->error && $this->downloadCompleteFunction) {
273
            rewind($fh);
274
            $this->call($this->downloadCompleteFunction, $fh);
275
            $this->downloadCompleteFunction = null;
276
        }
277
278
        if (is_resource($fh)) {
279
            fclose($fh);
280
        }
281
282
        // Fix "PHP Notice: Use of undefined constant STDOUT" when reading the
283
        // PHP script from stdin. Using null causes "Warning: curl_setopt():
284
        // supplied argument is not a valid File-Handle resource".
285
        if (!defined('STDOUT')) {
286
            define('STDOUT', fopen('php://stdout', 'w'));
287
        }
288
289
        // Reset CURLOPT_FILE with STDOUT to avoid: "curl_exec(): CURLOPT_FILE
290
        // resource has gone away, resetting to default".
291
        $this->setOpt(CURLOPT_FILE, STDOUT);
292
293
        // Reset CURLOPT_RETURNTRANSFER to tell cURL to return subsequent
294
        // responses as the return value of curl_exec(). Without this,
295
        // curl_exec() will revert to returning boolean values.
296
        $this->setOpt(CURLOPT_RETURNTRANSFER, true);
297
    }
298
299
    /**
300
     * Download
301
     *
302
     * @access public
303
     * @param  $url
304
     * @param  $mixed_filename
305
     *
306
     * @return boolean
307
     */
308
    public function download($url, $mixed_filename)
309
    {
310
        if (is_callable($mixed_filename)) {
311
            $this->downloadCompleteFunction = $mixed_filename;
312
            $this->fileHandle = tmpfile();
313
        } else {
314
            $filename = $mixed_filename;
315
316
            // Use a temporary file when downloading. Not using a temporary file can cause an error when an existing
317
            // file has already fully completed downloading and a new download is started with the same destination save
318
            // path. The download request will include header "Range: bytes=$filesize-" which is syntactically valid,
319
            // but unsatisfiable.
320
            $download_filename = $filename . '.pccdownload';
321
322
            $mode = 'wb';
323
            // Attempt to resume download only when a temporary download file exists and is not empty.
324
            if (file_exists($download_filename) && $filesize = filesize($download_filename)) {
325
                $mode = 'ab';
326
                $first_byte_position = $filesize;
327
                $range = $first_byte_position . '-';
328
                $this->setOpt(CURLOPT_RANGE, $range);
329
            }
330
            $this->fileHandle = fopen($download_filename, $mode);
331
332
            // Move the downloaded temporary file to the destination save path.
333
            $this->downloadCompleteFunction = function ($fh) use ($download_filename, $filename) {
0 ignored issues
show
Unused Code introduced by
The parameter $fh is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
334
                rename($download_filename, $filename);
335
            };
336
        }
337
338
        $this->setOpt(CURLOPT_FILE, $this->fileHandle);
339
        $this->get($url);
340
341
        return ! $this->error;
342
    }
343
344
    /**
345
     * Error
346
     *
347
     * @access public
348
     * @param  $callback
349
     */
350
    public function error($callback)
351
    {
352
        $this->errorFunction = $callback;
353
    }
354
355
    /**
356
     * Exec
357
     *
358
     * @access public
359
     * @param  $ch
360
     *
361
     * @return mixed Returns the value provided by parseResponse.
362
     */
363 3
    public function exec($ch = null)
364
    {
365 3
        if ($ch === null) {
366 3
            $this->responseCookies = array();
367 3
            $this->call($this->beforeSendFunction);
368 3
            $i=0;
369 3
            while(1){
370 3
                $this->rawResponse = curl_exec($this->curl);
371 3
                $this->curlErrorCode = curl_errno($this->curl);
372 3
                if(($this->curlErrorCode==28 ||$this->curlErrorCode==7)
373 3
                    && $i++ < $this->retryCount){
374
                    continue;
375
                }
376 3
                break;
377
            }
378 3
            $this->curlErrorMessage = curl_error($this->curl);
379
        } else {
380
            $this->rawResponse = curl_multi_getcontent($ch);
381
            $this->curlErrorMessage = curl_error($ch);
382
        }
383 3
        $this->curlError = !($this->curlErrorCode === 0);
384
        // Include additional error code information in error message when possible.
385 3
        if ($this->curlError && function_exists('curl_strerror')) {
386
            $this->curlErrorMessage =
387
                curl_strerror($this->curlErrorCode) . (
388
                empty($this->curlErrorMessage) ? '' : ': ' . $this->curlErrorMessage
389
                );
390
            print_r($this->rawResponse);
391
        }
392
393 3
        $this->httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE);
394 3
        $this->httpError = in_array(floor($this->httpStatusCode / 100), array(4, 5));
395 3
        $this->error = $this->curlError || $this->httpError;
396 3
        $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->error ? $this->cu...his->httpStatusCode : 0 can also be of type double. However, the property $errorCode is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
397
398
        // NOTE: CURLINFO_HEADER_OUT set to true is required for requestHeaders
399
        // to not be empty (e.g. $curl->setOpt(CURLINFO_HEADER_OUT, true);).
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
400 3
        if ($this->getOpt(CURLINFO_HEADER_OUT) === true) {
401 3
            $this->requestHeaders = $this->parseRequestHeaders($this->getInfo(CURLINFO_HEADER_OUT));
402
        }
403 3
        $this->responseHeaders = $this->parseResponseHeaders($this->rawResponseHeaders);
404 3
        $this->response = $this->parseResponse($this->responseHeaders, $this->rawResponse);
405
406 3
        $this->httpErrorMessage = '';
407 3
        if ($this->error) {
408
            if (isset($this->responseHeaders['Status-Line'])) {
409
                $this->httpErrorMessage = $this->responseHeaders['Status-Line'];
410
            }
411
        }
412 3
        $this->errorMessage = $this->curlError ? $this->curlErrorMessage : $this->httpErrorMessage;
413
414 3
        if (!$this->error) {
415 3
            $this->call($this->successFunction);
416
        } else {
417
            $this->call($this->errorFunction);
418
        }
419
420 3
        $this->call($this->completeFunction);
421
422
        // Close open file handles and reset the curl instance.
423 3
        if (!($this->fileHandle === null)) {
424
            $this->downloadComplete($this->fileHandle);
425
        }
426
427 3
        return $this->response;
428
    }
429
430
    /**
431
     * Get
432
     *
433
     * @access public
434
     * @param  $url
435
     * @param  $data
436
     *
437
     * @return mixed Returns the value provided by exec.
438
     */
439 3
    public function get($url, $data = array())
440
    {
441 3
        if (is_array($url)) {
442
            $data = $url;
443
            $url = $this->baseUrl;
444
        }
445 3
        $this->setUrl($url, $data);
446 3
        $this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET');
447 3
        $this->setOpt(CURLOPT_HTTPGET, true);
448 3
        return $this->exec();
449
    }
450
451
    /**
452
     * Get Info
453
     *
454
     * @access public
455
     * @param  $opt
456
     *
457
     * @return mixed
458
     */
459 3
    public function getInfo($opt = null)
460
    {
461 3
        $args = array();
462 3
        $args[] = $this->curl;
463
464 3
        if (func_num_args()) {
465 3
            $args[] = $opt;
466
        }
467
468 3
        return call_user_func_array('curl_getinfo', $args);
469
    }
470
471
    /**
472
     * Get Opt
473
     *
474
     * @access public
475
     * @param  $option
476
     *
477
     * @return mixed
478
     */
479 3
    public function getOpt($option)
480
    {
481 3
        return isset($this->options[$option]) ? $this->options[$option] : null;
482
    }
483
484
    /**
485
     * Head
486
     *
487
     * @access public
488
     * @param  $url
489
     * @param  $data
490
     *
491
     * @return string
492
     */
493
    public function head($url, $data = array())
494
    {
495
        if (is_array($url)) {
496
            $data = $url;
497
            $url = $this->baseUrl;
498
        }
499
        $this->setUrl($url, $data);
500
        $this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD');
501
        $this->setOpt(CURLOPT_NOBODY, true);
502
        return $this->exec();
503
    }
504
505
    /**
506
     * Header Callback
507
     *
508
     * @access public
509
     * @param  $ch
510
     * @param  $header
511
     *
512
     * @return integer
513
     */
514 3
    public function headerCallback($ch, $header)
515
    {
516 3
        if (preg_match('/^Set-Cookie:\s*([^=]+)=([^;]+)/mi', $header, $cookie) === 1) {
517 1
            $this->responseCookies[$cookie[1]] = trim($cookie[2], " \n\r\t\0\x0B");
518
        }
519 3
        $this->rawResponseHeaders .= $header;
520 3
        return strlen($header);
521
    }
522
523
    /**
524
     * Options
525
     *
526
     * @access public
527
     * @param  $url
528
     * @param  $data
529
     *
530
     * @return string
531
     */
532
    public function options($url, $data = array())
533
    {
534
        if (is_array($url)) {
535
            $data = $url;
536
            $url = $this->baseUrl;
537
        }
538
        $this->setUrl($url, $data);
539
        $this->removeHeader('Content-Length');
540
        $this->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS');
541
        return $this->exec();
542
    }
543
544
    /**
545
     * Patch
546
     *
547
     * @access public
548
     * @param  $url
549
     * @param  $data
550
     *
551
     * @return string
552
     */
553
    public function patch($url, $data = array())
554
    {
555
        if (is_array($url)) {
556
            $data = $url;
557
            $url = $this->baseUrl;
558
        }
559
560
        if (is_array($data) && empty($data)) {
561
            $this->removeHeader('Content-Length');
562
        }
563
564
        $this->setUrl($url);
565
        $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH');
566
        $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
567
        return $this->exec();
568
    }
569
570
    /**
571
     * Post
572
     *
573
     * @access public
574
     * @param  $url
575
     * @param  $data
576
     * @param  $follow_303_with_post
577
     *     If true, will cause 303 redirections to be followed using a POST request (default: false).
578
     *     Notes:
579
     *       - Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true.
580
     *       - According to the HTTP specs (see [1]), a 303 redirection should be followed using
581
     *         the GET method. 301 and 302 must not.
582
     *       - In order to force a 303 redirection to be performed using the same method, the
583
     *         underlying cURL object must be set in a special state (the CURLOPT_CURSTOMREQUEST
584
     *         option must be set to the method to use after the redirection). Due to a limitation
585
     *         of the cURL extension of PHP < 5.5.11 ([2], [3]) and of HHVM, it is not possible
586
     *         to reset this option. Using these PHP engines, it is therefore impossible to
587
     *         restore this behavior on an existing php-curl-class Curl object.
588
     *
589
     * @return string
590
     *
591
     * [1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2
592
     * [2] https://github.com/php/php-src/pull/531
593
     * [3] http://php.net/ChangeLog-5.php#5.5.11
594
     */
595
    public function post($url, $data = array(), $follow_303_with_post = false)
596
    {
597
        if (is_array($url)) {
598
            $follow_303_with_post = (bool)$data;
599
            $data = $url;
600
            $url = $this->baseUrl;
601
        }
602
603
        $this->setUrl($url);
604
605
        if ($follow_303_with_post) {
606
            $this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST');
607
        } else {
608
            if (isset($this->options[CURLOPT_CUSTOMREQUEST])) {
609
                if ((version_compare(PHP_VERSION, '5.5.11') < 0) || defined('HHVM_VERSION')) {
610
                    trigger_error(
611
                        'Due to technical limitations of PHP <= 5.5.11 and HHVM, it is not possible to '
612
                        . 'perform a post-redirect-get request using a php-curl-class Curl object that '
613
                        . 'has already been used to perform other types of requests. Either use a new '
614
                        . 'php-curl-class Curl object or upgrade your PHP engine.',
615
                        E_USER_ERROR
616
                    );
617
                } else {
618
                    $this->setOpt(CURLOPT_CUSTOMREQUEST, null);
619
                }
620
            }
621
        }
622
623
        $this->setOpt(CURLOPT_POST, true);
624
        $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
625
        return $this->exec();
626
    }
627
628
    /**
629
     * Put
630
     *
631
     * @access public
632
     * @param  $url
633
     * @param  $data
634
     *
635
     * @return string
636
     */
637
    public function put($url, $data = array())
638
    {
639
        if (is_array($url)) {
640
            $data = $url;
641
            $url = $this->baseUrl;
642
        }
643
        $this->setUrl($url);
644
        $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT');
645
        $put_data = $this->buildPostData($data);
646
        if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) {
647
            if (is_string($put_data)) {
648
                $this->setHeader('Content-Length', strlen($put_data));
649
            }
650
        }
651
        if (!empty($put_data)) {
652
            $this->setOpt(CURLOPT_POSTFIELDS, $put_data);
653
        }
654
        return $this->exec();
655
    }
656
657
    /**
658
     * Search
659
     *
660
     * @access public
661
     * @param  $url
662
     * @param  $data
663
     *
664
     * @return string
665
     */
666
    public function search($url, $data = array())
667
    {
668
        if (is_array($url)) {
669
            $data = $url;
670
            $url = $this->baseUrl;
671
        }
672
        $this->setUrl($url);
673
        $this->setOpt(CURLOPT_CUSTOMREQUEST, 'SEARCH');
674
        $put_data = $this->buildPostData($data);
675
        if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) {
676
            if (is_string($put_data)) {
677
                $this->setHeader('Content-Length', strlen($put_data));
678
            }
679
        }
680
        if (!empty($put_data)) {
681
            $this->setOpt(CURLOPT_POSTFIELDS, $put_data);
682
        }
683
        return $this->exec();
684
    }
685
686
    /**
687
     * Set Basic Authentication
688
     *
689
     * @access public
690
     * @param  $username
691
     * @param  $password
692
     */
693
    public function setBasicAuthentication($username, $password = '')
694
    {
695
        $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
696
        $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password);
697
    }
698
699
    /**
700
     * Set Digest Authentication
701
     *
702
     * @access public
703
     * @param  $username
704
     * @param  $password
705
     */
706
    public function setDigestAuthentication($username, $password = '')
707
    {
708
        $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
709
        $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password);
710
    }
711
712
    /**
713
     * Set Cookie
714
     *
715
     * @access public
716
     * @param  $key
717
     * @param  $value
718
     */
719
    public function setCookie($key, $value)
720
    {
721
        $name_chars = array();
722
        foreach (str_split($key) as $name_char) {
723
            if (!isset($this->rfc2616[$name_char])) {
0 ignored issues
show
Bug introduced by
The property rfc2616 does not seem to exist. Did you mean RFC2616?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
724
                $name_chars[] = rawurlencode($name_char);
725
            } else {
726
                $name_chars[] = $name_char;
727
            }
728
        }
729
730
        $value_chars = array();
731
        foreach (str_split($value) as $value_char) {
732
            if (!isset($this->rfc6265[$value_char])) {
0 ignored issues
show
Bug introduced by
The property rfc6265 does not seem to exist. Did you mean RFC6265?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
733
                $value_chars[] = rawurlencode($value_char);
734
            } else {
735
                $value_chars[] = $value_char;
736
            }
737
        }
738
739
        $this->cookies[implode('', $name_chars)] = implode('', $value_chars);
740
        $this->setOpt(CURLOPT_COOKIE, implode('; ', array_map(function ($k, $v) {
741
            return $k . '=' . $v;
742
        }, array_keys($this->cookies), array_values($this->cookies))));
743
    }
744
745
    /**
746
     * Set Cookies
747
     *
748
     * @access public
749
     * @param  $cookies
750
     */
751
    public function setCookies($cookies)
752
    {
753
        foreach ($cookies as $key => $value) {
754
            $name_chars = array();
755
            foreach (str_split($key) as $name_char) {
756
                if (!isset($this->rfc2616[$name_char])) {
0 ignored issues
show
Bug introduced by
The property rfc2616 does not seem to exist. Did you mean RFC2616?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
757
                    $name_chars[] = rawurlencode($name_char);
758
                } else {
759
                    $name_chars[] = $name_char;
760
                }
761
            }
762
763
            $value_chars = array();
764
            foreach (str_split($value) as $value_char) {
765
                if (!isset($this->rfc6265[$value_char])) {
0 ignored issues
show
Bug introduced by
The property rfc6265 does not seem to exist. Did you mean RFC6265?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
766
                    $value_chars[] = rawurlencode($value_char);
767
                } else {
768
                    $value_chars[] = $value_char;
769
                }
770
            }
771
772
            $this->cookies[implode('', $name_chars)] = implode('', $value_chars);
773
        }
774
775
        $this->setOpt(CURLOPT_COOKIE, implode('; ', array_map(function ($k, $v) {
776
            return $k . '=' . $v;
777
        }, array_keys($this->cookies), array_values($this->cookies))));
778
    }
779
780
    /**
781
     * Get Cookie
782
     *
783
     * @access public
784
     * @param  $key
785
     *
786
     * @return mixed
787
     */
788
    public function getCookie($key)
789
    {
790
        return $this->getResponseCookie($key);
791
    }
792
793
    /**
794
     * Get Response Cookie
795
     *
796
     * @access public
797
     * @param  $key
798
     *
799
     * @return mixed
800
     */
801
    public function getResponseCookie($key)
802
    {
803
        return isset($this->responseCookies[$key]) ? $this->responseCookies[$key] : null;
804
    }
805
806
    /**
807
     * Set Max Filesize
808
     *
809
     * @access public
810
     * @param  $bytes
811
     */
812
    public function setMaxFilesize($bytes)
813
    {
814
        // Make compatible with PHP version both before and after 5.5.0. PHP 5.5.0 added the cURL resource as the first
815
        // argument to the CURLOPT_PROGRESSFUNCTION callback.
816
        $gte_v550 = version_compare(PHP_VERSION, '5.5.0') >= 0;
817
        if ($gte_v550) {
818
            $callback = function ($resource, $download_size, $downloaded, $upload_size, $uploaded) use ($bytes) {
0 ignored issues
show
Unused Code introduced by
The parameter $upload_size is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $uploaded is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
819
                // Abort the transfer when $downloaded bytes exceeds maximum $bytes by returning a non-zero value.
820
                return $downloaded > $bytes ? 1 : 0;
821
            };
822
        } else {
823
            $callback = function ($download_size, $downloaded, $upload_size, $uploaded) use ($bytes) {
0 ignored issues
show
Unused Code introduced by
The parameter $upload_size is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $uploaded is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
824
                return $downloaded > $bytes ? 1 : 0;
825
            };
826
        }
827
828
        $this->progress($callback);
829
    }
830
831
    /**
832
     * Set Port
833
     *
834
     * @access public
835
     * @param  $port
836
     */
837
    public function setPort($port)
838
    {
839
        $this->setOpt(CURLOPT_PORT, intval($port));
840
    }
841
842
    /**
843
     * Set Connect Timeout
844
     *
845
     * @access public
846
     * @param  $seconds
847
     */
848
    public function setConnectTimeout($seconds)
849
    {
850
        $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds);
851
    }
852
853
    /**
854
     * Set Cookie String
855
     *
856
     * @access public
857
     * @param  $string
858
     *
859
     * @return bool
860
     */
861
    public function setCookieString($string)
862
    {
863
        return $this->setOpt(CURLOPT_COOKIE, $string);
864
    }
865
866
    /**
867
     * Set Cookie File
868
     *
869
     * @access public
870
     * @param  $cookie_file
871
     *
872
     * @return boolean
873
     */
874
    public function setCookieFile($cookie_file)
875
    {
876
        return $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file);
877
    }
878
879
    /**
880
     * Set Cookie Jar
881
     *
882
     * @access public
883
     * @param  $cookie_jar
884
     *
885
     * @return boolean
886
     */
887
    public function setCookieJar($cookie_jar)
888
    {
889
        return $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar);
890
    }
891
892
    /**
893
     * Set Default JSON Decoder
894
     *
895
     * @access public
896
     * @param  $assoc
897
     * @param  $depth
898
     * @param  $options
899
     */
900 1
    public function setDefaultJsonDecoder()
901
    {
902 1
        $args = func_get_args();
903
        $this->jsonDecoder = function ($response) use ($args) {
904
            array_unshift($args, $response);
905
906
            // Call json_decode() without the $options parameter in PHP
907
            // versions less than 5.4.0 as the $options parameter was added in
908
            // PHP version 5.4.0.
909
            if (version_compare(PHP_VERSION, '5.4.0', '<')) {
910
                $args = array_slice($args, 0, 3);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $args, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
911
            }
912
913
            $json_obj = call_user_func_array('json_decode', $args);
914
            if (!($json_obj === null)) {
915
                $response = $json_obj;
916
            }
917
            return $response;
918
        };
919 1
    }
920
921
    /**
922
     * Set Default XML Decoder
923
     *
924
     * @access public
925
     */
926
    public function setDefaultXmlDecoder()
927
    {
928
        $this->xmlDecoder = function ($response) {
929
            $xml_obj = @simplexml_load_string($response);
930
            if (!($xml_obj === false)) {
931
                $response = $xml_obj;
932
            }
933
            return $response;
934
        };
935 1
    }
936
937
    /**
938
     * Set Default Decoder
939
     *
940
     * @access public
941
     * @param  $decoder string|callable
942
     */
943
    public function setDefaultDecoder($decoder = 'json')
944
    {
945
        if (is_callable($decoder)) {
946
            $this->defaultDecoder = $decoder;
947
        } else {
948
            if ($decoder === 'json') {
949
                $this->defaultDecoder = $this->jsonDecoder;
950
            } elseif ($decoder === 'xml') {
951
                $this->defaultDecoder = $this->xmlDecoder;
952
            }
953
        }
954
    }
955
956
    /**
957
     * Set Default Timeout
958
     *
959
     * @access public
960
     */
961 1
    public function setDefaultTimeout()
962
    {
963 1
        $this->setTimeout(self::DEFAULT_TIMEOUT);
964 1
    }
965
966
    /**
967
     * Set Default User Agent
968
     *
969
     * @access public
970
     */
971 1
    public function setDefaultUserAgent()
972
    {
973 1
        $user_agent = 'puck client' . self::VERSION . ' mini';
974 1
        $user_agent .= ' PHP/' . PHP_VERSION;
975 1
        $curl_version = curl_version();
976 1
        $user_agent .= ' curl/' . $curl_version['version'];
977 1
        $this->setUserAgent($user_agent);
978 1
    }
979
980
    /**
981
     * Set Header
982
     *
983
     * Add extra header to include in the request.
984
     *
985
     * @access public
986
     * @param  $key
987
     * @param  $value
988
     */
989
    public function setHeader($key, $value)
990
    {
991
        $this->headers[$key] = $value;
992
        $headers = array();
993
        foreach ($this->headers as $key => $value) {
994
            $headers[] = $key . ': ' . $value;
995
        }
996
        $this->setOpt(CURLOPT_HTTPHEADER, $headers);
997
    }
998
999
    /**
1000
     * Set Headers
1001
     *
1002
     * Add extra headers to include in the request.
1003
     *
1004
     * @access public
1005
     * @param  $headers
1006
     */
1007
    public function setHeaders($headers)
1008
    {
1009
        foreach ($headers as $key => $value) {
1010
            $this->headers[$key] = $value;
1011
        }
1012
1013
        $headers = array();
1014
        foreach ($this->headers as $key => $value) {
1015
            $headers[] = $key . ': ' . $value;
1016
        }
1017
        $this->setOpt(CURLOPT_HTTPHEADER, $headers);
1018
    }
1019
1020
    /**
1021
     * Set JSON Decoder
1022
     *
1023
     * @access public
1024
     * @param  $function
1025
     */
1026
    public function setJsonDecoder($function)
1027
    {
1028
        if (is_callable($function)) {
1029
            $this->jsonDecoder = $function;
1030
        }
1031
    }
1032
1033
    /**
1034
     * Set XML Decoder
1035
     *
1036
     * @access public
1037
     * @param  $function
1038
     */
1039
    public function setXmlDecoder($function)
1040
    {
1041
        if (is_callable($function)) {
1042
            $this->xmlDecoder = $function;
1043
        }
1044
    }
1045
1046
    /**
1047
     * Set Opt
1048
     *
1049
     * @access public
1050
     * @param  $option
1051
     * @param  $value
1052
     *
1053
     * @return boolean
1054
     */
1055 3
    public function setOpt($option, $value)
1056
    {
1057
        $required_options = array(
1058 3
            CURLOPT_RETURNTRANSFER => 'CURLOPT_RETURNTRANSFER',
1059
        );
1060
1061 3
        if (in_array($option, array_keys($required_options), true) && !($value === true)) {
1062
            trigger_error($required_options[$option] . ' is a required option', E_USER_WARNING);
1063
        }
1064
1065 3
        $success = curl_setopt($this->curl, $option, $value);
1066 3
        if ($success) {
1067 3
            $this->options[$option] = $value;
1068
        }
1069 3
        return $success;
1070
    }
1071
1072
    /**
1073
     * Set Opts
1074
     *
1075
     * @access public
1076
     * @param  $options
1077
     *
1078
     * @return boolean
1079
     *   Returns true if all options were successfully set. If an option could not be successfully set, false is
1080
     *   immediately returned, ignoring any future options in the options array. Similar to curl_setopt_array().
1081
     */
1082
    public function setOpts($options)
1083
    {
1084
        foreach ($options as $option => $value) {
1085
            if (!$this->setOpt($option, $value)) {
1086
                return false;
1087
            }
1088
        }
1089
        return true;
1090
    }
1091
1092
    /**
1093
     * Set Referer
1094
     *
1095
     * @access public
1096
     * @param  $referer
1097
     */
1098
    public function setReferer($referer)
1099
    {
1100
        $this->setReferrer($referer);
1101
    }
1102
1103
    /**
1104
     * Set Referrer
1105
     *
1106
     * @access public
1107
     * @param  $referrer
1108
     */
1109
    public function setReferrer($referrer)
1110
    {
1111
        $this->setOpt(CURLOPT_REFERER, $referrer);
1112
    }
1113
1114
    /**
1115
     * Set Timeout
1116
     *
1117
     * @access public
1118
     * @param  $seconds
1119
     */
1120 3
    public function setTimeout($seconds)
1121
    {
1122 3
        $this->setOpt(CURLOPT_TIMEOUT, $seconds);
1123 3
    }
1124
1125
    /**
1126
     * Set Url
1127
     *
1128
     * @access public
1129
     * @param  $url
1130
     * @param  $data
1131
     */
1132 3
    public function setUrl($url, $data = array())
1133
    {
1134 3
        $this->baseUrl = $url;
1135 3
        $this->url = $this->buildURL($url, $data);
1136 3
        $this->setOpt(CURLOPT_URL, $this->url);
1137 3
    }
1138
1139
    /**
1140
     * Set User Agent
1141
     *
1142
     * @access public
1143
     * @param  $user_agent
1144
     */
1145 1
    public function setUserAgent($user_agent)
1146
    {
1147 1
        $this->setOpt(CURLOPT_USERAGENT, $user_agent);
1148 1
    }
1149
1150
    /**
1151
     * Success
1152
     *
1153
     * @access public
1154
     * @param  $callback
1155
     */
1156
    public function success($callback)
1157
    {
1158
        $this->successFunction = $callback;
1159
    }
1160
1161
    /**
1162
     * Unset Header
1163
     *
1164
     * Remove extra header previously set using Curl::setHeader().
1165
     *
1166
     * @access public
1167
     * @param  $key
1168
     */
1169
    public function unsetHeader($key)
1170
    {
1171
        unset($this->headers[$key]);
1172
        $headers = array();
1173
        foreach ($this->headers as $key => $value) {
1174
            $headers[] = $key . ': ' . $value;
1175
        }
1176
        $this->setOpt(CURLOPT_HTTPHEADER, $headers);
1177
    }
1178
1179
    /**
1180
     * Remove Header
1181
     *
1182
     * Remove an internal header from the request.
1183
     * Using `curl -H "Host:" ...' is equivalent to $curl->removeHeader('Host');.
1184
     *
1185
     * @access public
1186
     * @param  $key
1187
     */
1188
    public function removeHeader($key)
1189
    {
1190
        $this->setHeader($key, '');
1191
    }
1192
1193
    /**
1194
     * Verbose
1195
     *
1196
     * @access public
1197
     * @param  bool $on
1198
     * @param  resource $output
1199
     */
1200
    public function verbose($on = true, $output = STDERR)
1201
    {
1202
        // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side
1203
        // effect of causing Curl::requestHeaders to be empty.
1204
        if ($on) {
1205
            $this->setOpt(CURLINFO_HEADER_OUT, false);
1206
        }
1207
        $this->setOpt(CURLOPT_VERBOSE, $on);
1208
        $this->setOpt(CURLOPT_STDERR, $output);
1209
    }
1210
1211
    /**
1212
     * Destruct
1213
     *
1214
     * @access public
1215
     */
1216
    public function __destruct()
1217
    {
1218
        $this->close();
1219
    }
1220
1221
    public function __get($name)
1222
    {
1223
        $return = null;
1224
        if (in_array($name, self::$deferredProperties) && is_callable(array($this, $getter = '__get_' . $name))) {
1225
            $return = $this->$name = $this->$getter();
1226
        }
1227
        return $return;
1228
    }
1229
1230
    /**
1231
     * Get Effective Url
1232
     *
1233
     * @access private
1234
     */
1235
    private function __get_effectiveUrl()
1236
    {
1237
        return $this->getInfo(CURLINFO_EFFECTIVE_URL);
1238
    }
1239
1240
    /**
1241
     * Get RFC 2616
1242
     *
1243
     * @access private
1244
     */
1245
    private function __get_rfc2616()
1246
    {
1247
        return array_fill_keys(self::$RFC2616, true);
1248
    }
1249
1250
    /**
1251
     * Get RFC 6265
1252
     *
1253
     * @access private
1254
     */
1255
    private function __get_rfc6265()
1256
    {
1257
        return array_fill_keys(self::$RFC6265, true);
1258
    }
1259
1260
    /**
1261
     * Get Total Time
1262
     *
1263
     * @access private
1264
     */
1265
    private function __get_totalTime()
1266
    {
1267
        return $this->getInfo(CURLINFO_TOTAL_TIME);
1268
    }
1269
1270
    /**
1271
     * Build Url
1272
     *
1273
     * @access private
1274
     * @param  $url
1275
     * @param  $data
1276
     *
1277
     * @return string
1278
     */
1279 3
    private function buildURL($url, $data = array())
1280
    {
1281 3
        return $url . (empty($data) ? '' : '?' . http_build_query($data, '', '&'));
1282
    }
1283
1284
    /**
1285
     * Parse Headers
1286
     *
1287
     * @access private
1288
     * @param  $raw_headers
1289
     *
1290
     * @return array
1291
     */
1292 3
    private function parseHeaders($raw_headers)
1293
    {
1294 3
        $raw_headers = preg_split('/\r\n/', $raw_headers, null, PREG_SPLIT_NO_EMPTY);
1295 3
        $http_headers = [];
1296
1297 3
        $raw_headers_count = count($raw_headers);
1298 3
        for ($i = 1; $i < $raw_headers_count; $i++) {
1299 3
            list($key, $value) = explode(':', $raw_headers[$i], 2);
1300 3
            $key = trim($key);
1301 3
            $value = trim($value);
1302
            // Use isset() as array_key_exists() and ArrayAccess are not compatible.
1303 3
            if (isset($http_headers[$key])) {
1304
                $http_headers[$key] .= ',' . $value;
1305
            } else {
1306 3
                $http_headers[$key] = $value;
1307
            }
1308
        }
1309
1310 3
        return array(isset($raw_headers['0']) ? $raw_headers['0'] : '', $http_headers);
1311
    }
1312
1313
    /**
1314
     * Parse Request Headers
1315
     *
1316
     * @access private
1317
     * @param  $raw_headers
1318
     *
1319
     * @return array
1320
     */
1321 3
    private function parseRequestHeaders($raw_headers)
1322
    {
1323 3
        $request_headers = [];
1324 3
        list($first_line, $headers) = $this->parseHeaders($raw_headers);
1325 3
        $request_headers['Request-Line'] = $first_line;
1326 3
        foreach ($headers as $key => $value) {
1327 3
            $request_headers[$key] = $value;
1328
        }
1329 3
        return $request_headers;
1330
    }
1331
1332
    /**
1333
     * Parse Response
1334
     *
1335
     * @access private
1336
     * @param  $response_headers
1337
     * @param  $raw_response
1338
     *
1339
     * @return mixed
1340
     *   Provided the content-type is determined to be json or xml:
1341
     *     Returns stdClass object when the default json decoder is used and the content-type is json.
1342
     *     Returns SimpleXMLElement object when the default xml decoder is used and the content-type is xml.
1343
     */
1344 3
    private function parseResponse($response_headers, $raw_response)
1345
    {
1346 3
        $response = $raw_response;
1347 3
        if (isset($response_headers['Content-Type'])) {
1348 3
            if (preg_match($this->jsonPattern, $response_headers['Content-Type'])) {
1349
                $json_decoder = $this->jsonDecoder;
1350
                if (is_callable($json_decoder)) {
1351
                    $response = $json_decoder($response);
1352
                }
1353 3
            } elseif (preg_match($this->xmlPattern, $response_headers['Content-Type'])) {
1354
                $xml_decoder = $this->xmlDecoder;
1355
                if (is_callable($xml_decoder)) {
1356
                    $response = $xml_decoder($response);
1357
                }
1358
            } else {
1359 3
                $decoder = $this->defaultDecoder;
1360 3
                if (is_callable($decoder)) {
1361
                    $response = $decoder($response);
1362
                }
1363
            }
1364
        }
1365
1366 3
        return $response;
1367
    }
1368
1369
    /**
1370
     * Parse Response Headers
1371
     *
1372
     * @access private
1373
     * @param  $raw_response_headers
1374
     *
1375
     * @return array
1376
     */
1377 3
    private function parseResponseHeaders($raw_response_headers)
1378
    {
1379 3
        $response_header_array = explode("\r\n\r\n", $raw_response_headers);
1380 3
        $response_header  = '';
1381 3
        for ($i = count($response_header_array) - 1; $i >= 0; $i--) {
1382 3
            if (stripos($response_header_array[$i], 'HTTP/') === 0) {
1383 3
                $response_header = $response_header_array[$i];
1384 3
                break;
1385
            }
1386
        }
1387
1388 3
        $response_headers = [];
1389 3
        list($first_line, $headers) = $this->parseHeaders($response_header);
1390 3
        $response_headers['Status-Line'] = $first_line;
1391 3
        foreach ($headers as $key => $value) {
1392 3
            $response_headers[$key] = $value;
1393
        }
1394 3
        return $response_headers;
1395
    }
1396
1397
}