• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

voku / httpful / 5623107679

pending completion
5623107679

push

github

voku
[+]: fix test for the new release

1596 of 2486 relevant lines covered (64.2%)

81.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.96
/src/Httpful/Curl/Curl.php
1
<?php declare(strict_types=1);
2

3
namespace Httpful\Curl;
4

5
use Httpful\Request;
6
use Httpful\Uri;
7
use Httpful\UriResolver;
8
use Psr\Http\Message\RequestInterface;
9
use Psr\Http\Message\UriInterface;
10

11
/**
12
 * @internal
13
 */
14
final class Curl
15
{
16
    const DEFAULT_TIMEOUT = 30;
17

18
    /**
19
     * @var bool
20
     */
21
    public $error = false;
22

23
    /**
24
     * @var int
25
     */
26
    public $errorCode = 0;
27

28
    /**
29
     * @var string|null
30
     */
31
    public $errorMessage;
32

33
    /**
34
     * @var bool
35
     */
36
    public $curlError = false;
37

38
    /**
39
     * @var int
40
     */
41
    public $curlErrorCode = 0;
42

43
    /**
44
     * @var string|null
45
     */
46
    public $curlErrorMessage;
47

48
    /**
49
     * @var bool
50
     */
51
    public $httpError = false;
52

53
    /**
54
     * @var int
55
     */
56
    public $httpStatusCode = 0;
57

58
    /**
59
     * @var null|bool|string
60
     */
61
    public $rawResponse;
62

63
    /**
64
     * @var callable|null
65
     */
66
    public $beforeSendCallback;
67

68
    /**
69
     * @var callable|null
70
     */
71
    public $downloadCompleteCallback;
72

73
    /**
74
     * @var string|null
75
     */
76
    public $downloadFileName;
77

78
    /**
79
     * @var callable|null
80
     */
81
    public $successCallback;
82

83
    /**
84
     * @var callable|null
85
     */
86
    public $errorCallback;
87

88
    /**
89
     * @var callable|null
90
     */
91
    public $completeCallback;
92

93
    /**
94
     * @var false|resource|null
95
     */
96
    public $fileHandle;
97

98
    /**
99
     * @var int
100
     */
101
    public $attempts = 0;
102

103
    /**
104
     * @var int
105
     */
106
    public $retries = 0;
107

108
    /**
109
     * @var RequestInterface|null
110
     */
111
    public $request;
112

113
    /**
114
     * @var false|resource|\CurlHandle
115
     */
116
    private $curl;
117

118
    /**
119
     * @var int|string|null
120
     */
121
    private $id;
122

123
    /**
124
     * @var string
125
     */
126
    private $rawResponseHeaders = '';
127

128
    /**
129
     * @var array
130
     */
131
    private $responseCookies = [];
132

133
    /**
134
     * @var bool
135
     */
136
    private $childOfMultiCurl = false;
137

138
    /**
139
     * @var int
140
     */
141
    private $remainingRetries = 0;
142

143
    /**
144
     * @var callable|null
145
     */
146
    private $retryDecider;
147

148
    /**
149
     * @var UriInterface|null
150
     */
151
    private $url;
152

153
    /**
154
     * @var array
155
     */
156
    private $cookies = [];
157

158
    /**
159
     * @var \stdClass|null
160
     */
161
    private $headerCallbackData;
162

163
    /**
164
     * @param string $base_url
165
     */
166
    public function __construct($base_url = '')
167
    {
168
        if (!\extension_loaded('curl')) {
460✔
169
            throw new \ErrorException('cURL library is not loaded');
×
170
        }
171

172
        $this->curl = \curl_init();
460✔
173
        $this->initialize($base_url);
460✔
174
    }
175

176
    public function __destruct()
177
    {
178
        $this->close();
460✔
179
    }
180

181
    /**
182
     * @return bool
183
     */
184
    public function attemptRetry()
185
    {
186
        // init
187
        $attempt_retry = false;
148✔
188

189
        if ($this->error) {
148✔
190
            if ($this->retryDecider === null) {
36✔
191
                $attempt_retry = $this->remainingRetries >= 1;
36✔
192
            } else {
193
                $attempt_retry = \call_user_func($this->retryDecider, $this);
×
194
            }
195
            if ($attempt_retry) {
36✔
196
                ++$this->retries;
×
197
                if ($this->remainingRetries) {
×
198
                    --$this->remainingRetries;
×
199
                }
200
            }
201
        }
202

203
        return $attempt_retry;
148✔
204
    }
205

206
    /**
207
     * @param callable $callback
208
     *
209
     * @return $this
210
     */
211
    public function beforeSend($callback)
212
    {
213
        $this->beforeSendCallback = $callback;
24✔
214

215
        return $this;
24✔
216
    }
217

218
    /**
219
     * @param mixed $function
220
     *
221
     * @return void
222
     */
223

224
    /**
225
     * @param callable|null $function
226
     * @param mixed         ...$args
227
     *
228
     * @return $this
229
     */
230
    public function call($function, ...$args)
231
    {
232
        if (\is_callable($function)) {
148✔
233
            \array_unshift($args, $this);
28✔
234
            \call_user_func_array($function, $args);
28✔
235
        }
236

237
        return $this;
148✔
238
    }
239

240
    /**
241
     * @return void
242
     */
243
    public function close()
244
    {
245
        if (
246
            \is_resource($this->curl)
460✔
247
            ||
248
            (\class_exists('CurlHandle') && $this->curl instanceof \CurlHandle)
460✔
249
        ) {
250
            \curl_close($this->curl);
460✔
251
        }
252
    }
253

254
    /**
255
     * @param callable $callback
256
     *
257
     * @return $this
258
     */
259
    public function complete($callback)
260
    {
261
        $this->completeCallback = $callback;
24✔
262

263
        return $this;
24✔
264
    }
265

266
    /**
267
     * @param callable|string $filename_or_callable
268
     *
269
     * @return $this
270
     */
271
    public function download($filename_or_callable)
272
    {
273
        // Use tmpfile() or php://temp to avoid "Too many open files" error.
274
        if (\is_callable($filename_or_callable)) {
4✔
275
            $this->downloadCompleteCallback = $filename_or_callable;
×
276
            $this->downloadFileName = null;
×
277
            $this->fileHandle = \tmpfile();
×
278
        } else {
279
            $filename = $filename_or_callable;
4✔
280

281
            // Use a temporary file when downloading. Not using a temporary file can cause an error when an existing
282
            // file has already fully completed downloading and a new download is started with the same destination save
283
            // path. The download request will include header "Range: bytes=$file_size-" which is syntactically valid,
284
            // but unsatisfiable.
285
            $download_filename = $filename . '.pccdownload';
4✔
286
            $this->downloadFileName = $download_filename;
4✔
287

288
            // Attempt to resume download only when a temporary download file exists and is not empty.
289
            if (
290
                \is_file($download_filename)
4✔
291
                &&
292
                $file_size = \filesize($download_filename)
4✔
293
            ) {
294
                $first_byte_position = $file_size;
×
295
                $range = $first_byte_position . '-';
×
296
                $this->setRange($range);
×
297
                $this->fileHandle = \fopen($download_filename, 'ab');
×
298
            } else {
299
                $this->fileHandle = \fopen($download_filename, 'wb');
4✔
300
            }
301

302
            // Move the downloaded temporary file to the destination save path.
303
            $this->downloadCompleteCallback = static function ($instance, $fh) use ($download_filename, $filename) {
4✔
304
                // Close the open file handle before renaming the file.
305
                if (\is_resource($fh)) {
4✔
306
                    \fclose($fh);
4✔
307
                }
308

309
                \rename($download_filename, $filename);
4✔
310
            };
4✔
311
        }
312

313
        if ($this->fileHandle === false) {
4✔
314
            throw new \Httpful\Exception\ClientErrorException('Unable to write to file:' . $this->downloadFileName);
×
315
        }
316

317
        $this->setFile($this->fileHandle);
4✔
318

319
        return $this;
4✔
320
    }
321

322
    /**
323
     * @param callable $callback
324
     *
325
     * @return $this
326
     */
327
    public function error($callback)
328
    {
329
        $this->errorCallback = $callback;
24✔
330

331
        return $this;
24✔
332
    }
333

334
    /**
335
     * @param false|\CurlHandle|resource|null $ch
336
     *
337
     * @return mixed returns the value provided by parseResponse
338
     */
339
    public function exec($ch = null)
340
    {
341
        ++$this->attempts;
148✔
342

343
        if ($ch === false || $ch === null) {
148✔
344
            $this->responseCookies = [];
128✔
345
            $this->call($this->beforeSendCallback);
128✔
346
            $this->rawResponse = \curl_exec($this->curl);
128✔
347
            $this->curlErrorCode = \curl_errno($this->curl);
128✔
348
            $this->curlErrorMessage = \curl_error($this->curl);
128✔
349
        } else {
350
            $this->rawResponse = \curl_multi_getcontent($ch);
24✔
351
            $this->curlErrorMessage = \curl_error($ch);
24✔
352
        }
353

354
        $this->curlError = $this->curlErrorCode !== 0;
148✔
355

356
        // Transfer the header callback data and release the temporary store to avoid memory leak.
357
        if ($this->headerCallbackData === null) {
148✔
358
            $this->headerCallbackData = new \stdClass();
×
359
        }
360
        $this->rawResponseHeaders = $this->headerCallbackData->rawResponseHeaders;
148✔
361
        $this->responseCookies = $this->headerCallbackData->responseCookies;
148✔
362
        $this->headerCallbackData->rawResponseHeaders = '';
148✔
363
        $this->headerCallbackData->responseCookies = [];
148✔
364

365
        // Include additional error code information in error message when possible.
366
        if ($this->curlError && \function_exists('curl_strerror')) {
148✔
367
            $this->curlErrorMessage = \curl_strerror($this->curlErrorCode) . (empty($this->curlErrorMessage) ? '' : ': ' . $this->curlErrorMessage);
28✔
368
        }
369

370
        $this->httpStatusCode = $this->getInfo(\CURLINFO_HTTP_CODE);
148✔
371
        $this->httpError = \in_array((int) \floor($this->httpStatusCode / 100), [4, 5], true);
148✔
372
        $this->error = $this->curlError || $this->httpError;
148✔
373
        /** @noinspection NestedTernaryOperatorInspection */
374
        $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0;
148✔
375
        $this->errorMessage = $this->curlError ? $this->curlErrorMessage : '';
148✔
376

377
        // Reset nobody setting possibly set from a HEAD request.
378
        $this->setOpt(\CURLOPT_NOBODY, false);
148✔
379

380
        // Allow multicurl to attempt retry as needed.
381
        if ($this->isChildOfMultiCurl()) {
148✔
382
            /** @noinspection PhpInconsistentReturnPointsInspection */
383
            return;
24✔
384
        }
385

386
        if ($this->attemptRetry()) {
128✔
387
            return $this->exec($ch);
×
388
        }
389

390
        $this->execDone();
128✔
391

392
        return $this->rawResponse;
128✔
393
    }
394

395
    /**
396
     * @return void
397
     */
398
    public function execDone()
399
    {
400
        if ($this->error) {
148✔
401
            $this->call($this->errorCallback);
36✔
402
        } else {
403
            $this->call($this->successCallback);
116✔
404
        }
405

406
        $this->call($this->completeCallback);
148✔
407

408
        // Close open file handles and reset the curl instance.
409
        if (\is_resource($this->fileHandle)) {
148✔
410
            $this->downloadComplete($this->fileHandle);
4✔
411
        }
412

413
        // Free some memory + help the GC to free some more memory.
414
        if ($this->request instanceof Request) {
148✔
415
            $this->request->clearHelperData();
24✔
416
        }
417

418
        $this->request = null;
148✔
419
    }
420

421
    /**
422
     * @return int
423
     */
424
    public function getAttempts()
425
    {
426
        return $this->attempts;
×
427
    }
428

429
    /**
430
     * @return callable|null
431
     */
432
    public function getBeforeSendCallback()
433
    {
434
        return $this->beforeSendCallback;
×
435
    }
436

437
    /**
438
     * @return callable|null
439
     */
440
    public function getCompleteCallback()
441
    {
442
        return $this->completeCallback;
×
443
    }
444

445
    /**
446
     * @param string $key
447
     *
448
     * @return mixed
449
     */
450
    public function getCookie($key)
451
    {
452
        return $this->getResponseCookie($key);
×
453
    }
454

455
    /**
456
     * @return false|resource|\CurlHandle
457
     */
458
    public function getCurl()
459
    {
460
        return $this->curl;
184✔
461
    }
462

463
    /**
464
     * @return int
465
     */
466
    public function getCurlErrorCode()
467
    {
468
        return $this->curlErrorCode;
×
469
    }
470

471
    /**
472
     * @return string|null
473
     */
474
    public function getCurlErrorMessage()
475
    {
476
        return $this->curlErrorMessage;
×
477
    }
478

479
    /**
480
     * @return callable|null
481
     */
482
    public function getDownloadCompleteCallback()
483
    {
484
        return $this->downloadCompleteCallback;
×
485
    }
486

487
    /**
488
     * @return string|null
489
     */
490
    public function getDownloadFileName()
491
    {
492
        return $this->downloadFileName;
×
493
    }
494

495
    /**
496
     * @return callable|null
497
     */
498
    public function getErrorCallback()
499
    {
500
        return $this->errorCallback;
×
501
    }
502

503
    /**
504
     * @return int
505
     */
506
    public function getErrorCode()
507
    {
508
        return $this->errorCode;
28✔
509
    }
510

511
    /**
512
     * @return string|null
513
     */
514
    public function getErrorMessage()
515
    {
516
        return $this->errorMessage;
28✔
517
    }
518

519
    /**
520
     * @return false|resource|null
521
     */
522
    public function getFileHandle()
523
    {
524
        return $this->fileHandle;
×
525
    }
526

527
    /**
528
     * @return int
529
     */
530
    public function getHttpStatusCode()
531
    {
532
        return $this->httpStatusCode;
×
533
    }
534

535
    /**
536
     * @return int|string|null
537
     */
538
    public function getId()
539
    {
540
        return $this->id;
24✔
541
    }
542

543
    /**
544
     * @param int|null $opt
545
     *
546
     * @return mixed
547
     */
548
    public function getInfo($opt = null)
549
    {
550
        $args = [];
148✔
551
        $args[] = $this->curl;
148✔
552

553
        if (\func_num_args()) {
148✔
554
            $args[] = $opt;
148✔
555
        }
556

557
        return \curl_getinfo(...$args);
148✔
558
    }
559

560
    /**
561
     * @return null|bool|string
562
     */
563
    public function getRawResponse()
564
    {
565
        return $this->rawResponse;
120✔
566
    }
567

568
    /**
569
     * @return string
570
     */
571
    public function getRawResponseHeaders()
572
    {
573
        return $this->rawResponseHeaders;
120✔
574
    }
575

576
    /**
577
     * @return int
578
     */
579
    public function getRemainingRetries()
580
    {
581
        return $this->remainingRetries;
×
582
    }
583

584
    /**
585
     * @param string $key
586
     *
587
     * @return mixed
588
     */
589
    public function getResponseCookie($key)
590
    {
591
        return $this->responseCookies[$key] ?? null;
×
592
    }
593

594
    /**
595
     * @return array
596
     */
597
    public function getResponseCookies()
598
    {
599
        return $this->responseCookies;
×
600
    }
601

602
    /**
603
     * @return int
604
     */
605
    public function getRetries()
606
    {
607
        return $this->retries;
×
608
    }
609

610
    /**
611
     * @return callable|null
612
     */
613
    public function getRetryDecider()
614
    {
615
        return $this->retryDecider;
×
616
    }
617

618
    /**
619
     * @return callable|null
620
     */
621
    public function getSuccessCallback()
622
    {
623
        return $this->successCallback;
×
624
    }
625

626
    /**
627
     * @return UriInterface|null
628
     */
629
    public function getUrl()
630
    {
631
        return $this->url;
×
632
    }
633

634
    /**
635
     * @return bool
636
     */
637
    public function isChildOfMultiCurl(): bool
638
    {
639
        return $this->childOfMultiCurl;
148✔
640
    }
641

642
    /**
643
     * @return bool
644
     */
645
    public function isCurlError(): bool
646
    {
647
        return $this->curlError;
×
648
    }
649

650
    /**
651
     * @return bool
652
     */
653
    public function isError(): bool
654
    {
655
        return $this->error;
×
656
    }
657

658
    /**
659
     * @return bool
660
     */
661
    public function isHttpError(): bool
662
    {
663
        return $this->httpError;
×
664
    }
665

666
    /**
667
     * @param callable $callback
668
     *
669
     * @return $this
670
     */
671
    public function progress($callback)
672
    {
673
        $this->setOpt(\CURLOPT_PROGRESSFUNCTION, $callback);
×
674
        $this->setOpt(\CURLOPT_NOPROGRESS, false);
×
675

676
        return $this;
×
677
    }
678

679
    /**
680
     * @return void
681
     */
682
    public function reset()
683
    {
684
        if (
685
            \function_exists('curl_reset')
×
686
            &&
687
            (
688
                \is_resource($this->curl)
×
689
                ||
×
690
                (\class_exists('CurlHandle') && $this->curl instanceof \CurlHandle)
×
691
            )
692
        ) {
693
            \curl_reset($this->curl);
×
694
        } else {
695
            $this->curl = \curl_init();
×
696
        }
697

698
        $this->initialize('');
×
699
    }
700

701
    /**
702
     * @param string $username
703
     * @param string $password
704
     *
705
     * @return $this
706
     */
707
    public function setBasicAuthentication($username, $password = '')
708
    {
709
        $this->setOpt(\CURLOPT_HTTPAUTH, \CURLAUTH_BASIC);
×
710
        $this->setOpt(\CURLOPT_USERPWD, $username . ':' . $password);
×
711

712
        return $this;
×
713
    }
714

715
    /**
716
     * @param bool $bool
717
     *
718
     * @return $this
719
     */
720
    public function setChildOfMultiCurl(bool $bool)
721
    {
722
        $this->childOfMultiCurl = $bool;
24✔
723

724
        return $this;
24✔
725
    }
726

727
    /**
728
     * @param int $seconds
729
     *
730
     * @return $this
731
     */
732
    public function setConnectTimeout($seconds)
733
    {
734
        $this->setOpt(\CURLOPT_CONNECTTIMEOUT, $seconds);
×
735

736
        return $this;
×
737
    }
738

739
    /**
740
     * @param string $key
741
     * @param mixed  $value
742
     *
743
     * @return $this
744
     */
745
    public function setCookie($key, $value)
746
    {
747
        $this->setEncodedCookie($key, $value);
×
748
        $this->buildCookies();
×
749

750
        return $this;
×
751
    }
752

753
    /**
754
     * @param string $cookie_file
755
     *
756
     * @return $this
757
     */
758
    public function setCookieFile($cookie_file)
759
    {
760
        $this->setOpt(\CURLOPT_COOKIEFILE, $cookie_file);
×
761

762
        return $this;
×
763
    }
764

765
    /**
766
     * @param string $cookie_jar
767
     *
768
     * @return $this
769
     */
770
    public function setCookieJar($cookie_jar)
771
    {
772
        $this->setOpt(\CURLOPT_COOKIEJAR, $cookie_jar);
×
773

774
        return $this;
×
775
    }
776

777
    /**
778
     * @param string $string
779
     *
780
     * @return $this
781
     */
782
    public function setCookieString($string)
783
    {
784
        $this->setOpt(\CURLOPT_COOKIE, $string);
×
785

786
        return $this;
×
787
    }
788

789
    /**
790
     * @param array $cookies
791
     *
792
     * @return $this
793
     */
794
    public function setCookies($cookies)
795
    {
796
        foreach ($cookies as $key => $value) {
24✔
797
            $this->setEncodedCookie($key, $value);
×
798
        }
799
        $this->buildCookies();
24✔
800

801
        return $this;
24✔
802
    }
803

804
    /**
805
     * @return $this
806
     */
807
    public function setDefaultTimeout()
808
    {
809
        $this->setTimeout(self::DEFAULT_TIMEOUT);
460✔
810

811
        return $this;
460✔
812
    }
813

814
    /**
815
     * @param string $username
816
     * @param string $password
817
     *
818
     * @return $this
819
     */
820
    public function setDigestAuthentication($username, $password = '')
821
    {
822
        $this->setOpt(\CURLOPT_HTTPAUTH, \CURLAUTH_DIGEST);
×
823
        $this->setOpt(\CURLOPT_USERPWD, $username . ':' . $password);
×
824

825
        return $this;
×
826
    }
827

828
    /**
829
     * @param int|string $id
830
     *
831
     * @return $this
832
     */
833
    public function setId($id)
834
    {
835
        $this->id = $id;
460✔
836

837
        return $this;
460✔
838
    }
839

840
    /**
841
     * @param int $bytes
842
     *
843
     * @return $this
844
     */
845
    public function setMaxFilesize($bytes)
846
    {
847
        $callback = static function ($resource, $download_size, $downloaded, $upload_size, $uploaded) use ($bytes) {
848
            // Abort the transfer when $downloaded bytes exceeds maximum $bytes by returning a non-zero value.
849
            return $downloaded > $bytes ? 1 : 0;
×
850
        };
851

852
        $this->progress($callback);
×
853

854
        return $this;
×
855
    }
856

857
    /**
858
     * @param int   $option
859
     * @param mixed $value
860
     *
861
     * @return bool
862
     */
863
    public function setOpt($option, $value)
864
    {
865
        return \curl_setopt($this->curl, $option, $value);
460✔
866
    }
867

868
    /**
869
     * @param resource $file
870
     *
871
     * @return $this
872
     */
873
    public function setFile($file)
874
    {
875
        $this->setOpt(\CURLOPT_FILE, $file);
4✔
876

877
        return $this;
4✔
878
    }
879

880
    /**
881
     * @param array $options
882
     *
883
     * @return bool
884
     *              <p>Returns true if all options were successfully set. If an option could not be successfully set,
885
     *              false is immediately returned, ignoring any future options in the options array. Similar to
886
     *              curl_setopt_array().</p>
887
     */
888
    public function setOpts($options)
889
    {
890
        foreach ($options as $option => $value) {
×
891
            if (!$this->setOpt($option, $value)) {
×
892
                return false;
×
893
            }
894
        }
895

896
        return true;
×
897
    }
898

899
    /**
900
     * @param int $port
901
     *
902
     * @return $this
903
     */
904
    public function setPort($port)
905
    {
906
        $this->setOpt(\CURLOPT_PORT, (int) $port);
×
907

908
        return $this;
×
909
    }
910

911
    /**
912
     * Set an HTTP proxy to tunnel requests through.
913
     *
914
     * @param string $proxy    - The HTTP proxy to tunnel requests through. May include port number.
915
     * @param int    $port     - The port number of the proxy to connect to. This port number can also be set in $proxy.
916
     * @param string $username - The username to use for the connection to the proxy
917
     * @param string $password - The password to use for the connection to the proxy
918
     *
919
     * @return $this
920
     */
921
    public function setProxy($proxy, $port = null, $username = null, $password = null)
922
    {
923
        $this->setOpt(\CURLOPT_PROXY, $proxy);
×
924

925
        if ($port !== null) {
×
926
            $this->setOpt(\CURLOPT_PROXYPORT, $port);
×
927
        }
928

929
        if ($username !== null && $password !== null) {
×
930
            $this->setOpt(\CURLOPT_PROXYUSERPWD, $username . ':' . $password);
×
931
        }
932

933
        return $this;
×
934
    }
935

936
    /**
937
     * Set the HTTP authentication method(s) to use for the proxy connection.
938
     *
939
     * @param int $auth
940
     *
941
     * @return $this
942
     */
943
    public function setProxyAuth($auth)
944
    {
945
        $this->setOpt(\CURLOPT_PROXYAUTH, $auth);
×
946

947
        return $this;
×
948
    }
949

950
    /**
951
     * Set the proxy to tunnel through HTTP proxy.
952
     *
953
     * @param bool $tunnel
954
     *
955
     * @return $this
956
     */
957
    public function setProxyTunnel($tunnel = true)
958
    {
959
        $this->setOpt(\CURLOPT_HTTPPROXYTUNNEL, $tunnel);
×
960

961
        return $this;
×
962
    }
963

964
    /**
965
     * Set the proxy protocol type.
966
     *
967
     * @param int $type
968
     *                  <p>CURLPROXY_*</p>
969
     *
970
     * @return $this
971
     */
972
    public function setProxyType($type)
973
    {
974
        $this->setOpt(\CURLOPT_PROXYTYPE, $type);
×
975

976
        return $this;
×
977
    }
978

979
    /**
980
     * @param string $range <p>e.g. "0-4096"</p>
981
     *
982
     * @return $this
983
     */
984
    public function setRange($range)
985
    {
986
        $this->setOpt(\CURLOPT_RANGE, $range);
×
987

988
        return $this;
×
989
    }
990

991
    /**
992
     * @param string $referer
993
     *
994
     * @return $this
995
     */
996
    public function setReferer($referer)
997
    {
998
        $this->setReferrer($referer);
×
999

1000
        return $this;
×
1001
    }
1002

1003
    /**
1004
     * @param string $referrer
1005
     *
1006
     * @return $this
1007
     */
1008
    public function setReferrer($referrer)
1009
    {
1010
        $this->setOpt(\CURLOPT_REFERER, $referrer);
×
1011

1012
        return $this;
×
1013
    }
1014

1015
    /**
1016
     * Number of retries to attempt or decider callable.
1017
     *
1018
     * When using a number of retries to attempt, the maximum number of attempts
1019
     * for the request is $maximum_number_of_retries + 1.
1020
     *
1021
     * When using a callable decider, the request will be retried until the
1022
     * function returns a value which evaluates to false.
1023
     *
1024
     * @param callable|int $retry
1025
     *
1026
     * @return $this
1027
     */
1028
    public function setRetry($retry)
1029
    {
1030
        if (\is_callable($retry)) {
24✔
1031
            $this->retryDecider = $retry;
×
1032
        } elseif (\is_int($retry)) {
24✔
1033
            $maximum_number_of_retries = $retry;
×
1034
            $this->remainingRetries = $maximum_number_of_retries;
×
1035
        }
1036

1037
        return $this;
24✔
1038
    }
1039

1040
    /**
1041
     * @param int $seconds
1042
     *
1043
     * @return $this
1044
     */
1045
    public function setTimeout($seconds)
1046
    {
1047
        $this->setOpt(\CURLOPT_TIMEOUT, $seconds);
460✔
1048

1049
        return $this;
460✔
1050
    }
1051

1052
    /**
1053
     * @param string $url
1054
     * @param scalar|array<array-key,scalar> $mixed_data
1055
     *
1056
     * @return $this
1057
     */
1058
    public function setUrl($url, $mixed_data = '')
1059
    {
1060
        $built_url = new Uri($this->buildUrl($url, $mixed_data));
460✔
1061

1062
        if ($this->url === null) {
460✔
1063
            $this->url = UriResolver::resolve($built_url, new Uri(''));
460✔
1064
        } else {
1065
            $this->url = UriResolver::resolve($this->url, $built_url);
184✔
1066
        }
1067

1068
        $this->setOpt(\CURLOPT_URL, (string) $this->url);
460✔
1069

1070
        return $this;
460✔
1071
    }
1072

1073
    /**
1074
     * @param string $user_agent
1075
     *
1076
     * @return $this
1077
     */
1078
    public function setUserAgent($user_agent)
1079
    {
1080
        $this->setOpt(\CURLOPT_USERAGENT, $user_agent);
×
1081

1082
        return $this;
×
1083
    }
1084

1085
    /**
1086
     * @param callable $callback
1087
     *
1088
     * @return $this
1089
     */
1090
    public function success($callback)
1091
    {
1092
        $this->successCallback = $callback;
24✔
1093

1094
        return $this;
24✔
1095
    }
1096

1097
    /**
1098
     * Disable use of the proxy.
1099
     *
1100
     * @return $this
1101
     */
1102
    public function unsetProxy()
1103
    {
1104
        $this->setOpt(\CURLOPT_PROXY, null);
×
1105

1106
        return $this;
×
1107
    }
1108

1109
    /**
1110
     * @param bool          $on
1111
     * @param resource|null $output
1112
     *
1113
     * @return $this
1114
     */
1115
    public function verbose($on = true, $output = null)
1116
    {
1117
        // fallback
1118
        if ($output === null) {
×
1119
            if (!\defined('STDERR')) {
×
1120
                \define('STDERR', \fopen('php://stderr', 'wb'));
×
1121
            }
1122
            $output = \STDERR;
×
1123
        }
1124

1125
        $this->setOpt(\CURLOPT_VERBOSE, $on);
×
1126
        $this->setOpt(\CURLOPT_STDERR, $output);
×
1127

1128
        return $this;
×
1129
    }
1130

1131
    /**
1132
     * Build Cookies
1133
     *
1134
     * @return void
1135
     */
1136
    private function buildCookies()
1137
    {
1138
        // Avoid using http_build_query() as unnecessary encoding is performed.
1139
        // http_build_query($this->cookies, '', '; ');
1140
        $this->setOpt(
24✔
1141
            \CURLOPT_COOKIE,
24✔
1142
            \implode(
24✔
1143
                '; ',
24✔
1144
                \array_map(
24✔
1145
                    static function ($k, $v) {
24✔
1146
                        return $k . '=' . $v;
×
1147
                    },
24✔
1148
                    \array_keys($this->cookies),
24✔
1149
                    \array_values($this->cookies)
24✔
1150
                )
24✔
1151
            )
24✔
1152
        );
24✔
1153
    }
1154

1155
    /**
1156
     * @param string $url
1157
     * @param scalar|array<array-key,scalar> $mixed_data
1158
     *
1159
     * @return string
1160
     */
1161
    private function buildUrl($url, $mixed_data = '')
1162
    {
1163
        // init
1164
        $query_string = '';
460✔
1165

1166
        if (!empty($mixed_data)) {
460✔
1167
            $query_mark = \strpos($url, '?') > 0 ? '&' : '?';
×
1168
            if (\is_scalar($mixed_data)) {
×
1169
                $query_string .= $query_mark . $mixed_data;
×
1170
            } elseif (\is_array($mixed_data)) {
×
1171
                $query_string .= $query_mark . \http_build_query($mixed_data, '', '&');
×
1172
            }
1173
        }
1174

1175
        return $url . $query_string;
460✔
1176
    }
1177

1178
    /**
1179
     * Create Header Callback
1180
     *
1181
     * Gather headers and parse cookies as response headers are received. Keep this function separate from the class so
1182
     * that unset($curl) automatically calls __destruct() as expected. Otherwise, manually calling $curl->close() will
1183
     * be necessary to prevent a memory leak.
1184
     *
1185
     * @param \stdClass $header_callback_data
1186
     *
1187
     * @return callable
1188
     */
1189
    private function createHeaderCallback($header_callback_data)
1190
    {
1191
        return static function ($ch, $header) use ($header_callback_data) {
460✔
1192
            if (\preg_match('/^Set-Cookie:\s*([^=]+)=([^;]+)/mi', $header, $cookie) === 1) {
120✔
1193
                $header_callback_data->responseCookies[$cookie[1]] = \trim($cookie[2], " \n\r\t\0\x0B");
80✔
1194
            }
1195
            $header_callback_data->rawResponseHeaders .= $header;
120✔
1196

1197
            return \strlen($header);
120✔
1198
        };
460✔
1199
    }
1200

1201
    /**
1202
     * @param resource $fh
1203
     *
1204
     * @return void
1205
     */
1206
    private function downloadComplete($fh)
1207
    {
1208
        if (
1209
            $this->error
4✔
1210
            &&
1211
            $this->downloadFileName
4✔
1212
            &&
1213
            \is_file($this->downloadFileName)
4✔
1214
        ) {
1215
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
1216
            @\unlink($this->downloadFileName);
×
1217
        } elseif (
1218
            !$this->error
4✔
1219
            &&
1220
            $this->downloadCompleteCallback
4✔
1221
        ) {
1222
            \rewind($fh);
4✔
1223
            $this->call($this->downloadCompleteCallback, $fh);
4✔
1224
            $this->downloadCompleteCallback = null;
4✔
1225
        }
1226

1227
        if (\is_resource($fh)) {
4✔
1228
            \fclose($fh);
×
1229
        }
1230

1231
        // Fix "PHP Notice: Use of undefined constant STDOUT" when reading the
1232
        // PHP script from stdin. Using null causes "Warning: curl_setopt():
1233
        // supplied argument is not a valid File-Handle resource".
1234
        if (!\defined('STDOUT')) {
4✔
1235
            \define('STDOUT', \fopen('php://stdout', 'wb'));
×
1236
        }
1237

1238
        // Reset CURLOPT_FILE with STDOUT to avoid: "curl_exec(): CURLOPT_FILE
1239
        // resource has gone away, resetting to default".
1240
        $this->setFile(\STDOUT);
4✔
1241

1242
        // Reset CURLOPT_RETURNTRANSFER to tell cURL to return subsequent
1243
        // responses as the return value of curl_exec(). Without this,
1244
        // curl_exec() will revert to returning boolean values.
1245
        $this->setOpt(\CURLOPT_RETURNTRANSFER, true);
4✔
1246
    }
1247

1248
    /**
1249
     * @param string $base_url
1250
     *
1251
     * @return void
1252
     */
1253
    private function initialize($base_url)
1254
    {
1255
        $this->setId(\uniqid('', true));
460✔
1256
        $this->setDefaultTimeout();
460✔
1257
        $this->setOpt(\CURLINFO_HEADER_OUT, true);
460✔
1258

1259
        // Create a placeholder to temporarily store the header callback data.
1260
        $header_callback_data = new \stdClass();
460✔
1261
        $header_callback_data->rawResponseHeaders = '';
460✔
1262
        $header_callback_data->responseCookies = [];
460✔
1263
        $this->headerCallbackData = $header_callback_data;
460✔
1264
        $this->setOpt(\CURLOPT_HEADERFUNCTION, $this->createHeaderCallback($header_callback_data));
460✔
1265

1266
        $this->setOpt(\CURLOPT_RETURNTRANSFER, true);
460✔
1267
        $this->setUrl($base_url);
460✔
1268
    }
1269

1270
    /**
1271
     * @param string $key
1272
     * @param mixed  $value
1273
     *
1274
     * @return $this
1275
     */
1276
    private function setEncodedCookie($key, $value)
1277
    {
1278
        $name_chars = [];
×
1279
        foreach (\str_split($key) as $name_char) {
×
1280
            $name_chars[] = \rawurlencode($name_char);
×
1281
        }
1282

1283
        $value_chars = [];
×
1284
        foreach (\str_split($value) as $value_char) {
×
1285
            $value_chars[] = \rawurlencode($value_char);
×
1286
        }
1287

1288
        $this->cookies[\implode('', $name_chars)] = \implode('', $value_chars);
×
1289

1290
        return $this;
×
1291
    }
1292
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc