Skip to content

Commit 2c5d21c

Browse files
committed
refactor: simplify setCURLOptions by splitting into category helpers and optimizing
1 parent 28c2f53 commit 2c5d21c

3 files changed

Lines changed: 267 additions & 51 deletions

File tree

system/HTTP/CURLRequest.php

Lines changed: 143 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -526,19 +526,46 @@ protected function setResponseHeaders(array $headers = [])
526526
*/
527527
protected function setCURLOptions(array $curlOptions = [], array $config = [])
528528
{
529-
// Auth Headers
530-
if (! empty($config['auth'])) {
531-
$curlOptions[CURLOPT_USERPWD] = $config['auth'][0] . ':' . $config['auth'][1];
529+
$curlOptions = $this->applyAuthOptions($curlOptions, $config);
530+
$curlOptions = $this->applySslOptions($curlOptions, $config);
531+
$curlOptions = $this->applyProxyOptions($curlOptions, $config);
532+
$curlOptions = $this->applyDebugOptions($curlOptions, $config);
533+
$curlOptions = $this->applyRedirectOptions($curlOptions, $config);
534+
$curlOptions = $this->applyConnectionOptions($curlOptions, $config);
535+
$curlOptions = $this->applyPayloadOptions($curlOptions, $config);
536+
537+
return $this->applyMiscOptions($curlOptions, $config);
538+
}
532539

533-
if (! empty($config['auth'][2]) && strtolower($config['auth'][2]) === 'digest') {
534-
$curlOptions[CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
535-
} else {
536-
$curlOptions[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
537-
}
540+
/**
541+
* @param array<int, mixed> $curlOptions
542+
* @param array<string, mixed> $config
543+
*
544+
* @return array<int, mixed>
545+
*/
546+
private function applyAuthOptions(array $curlOptions, array $config): array
547+
{
548+
// Auth Headers
549+
if (isset($config['auth']) && $config['auth'] !== []) {
550+
$curlOptions[CURLOPT_USERPWD] = $config['auth'][0] . ':' . $config['auth'][1];
551+
$curlOptions[CURLOPT_HTTPAUTH] = (isset($config['auth'][2]) && $config['auth'][2] !== '' && strtolower($config['auth'][2]) === 'digest')
552+
? CURLAUTH_DIGEST
553+
: CURLAUTH_BASIC;
538554
}
539555

556+
return $curlOptions;
557+
}
558+
559+
/**
560+
* @param array<int, mixed> $curlOptions
561+
* @param array<string, mixed> $config
562+
*
563+
* @return array<int, mixed>
564+
*/
565+
private function applySslOptions(array $curlOptions, array $config): array
566+
{
540567
// Certificate
541-
if (! empty($config['cert'])) {
568+
if (isset($config['cert']) && $config['cert'] !== '' && $config['cert'] !== []) {
542569
$cert = $config['cert'];
543570

544571
if (is_array($cert)) {
@@ -571,30 +598,51 @@ protected function setCURLOptions(array $curlOptions = [], array $config = [])
571598
}
572599
}
573600

601+
return $curlOptions;
602+
}
603+
604+
/**
605+
* @param array<int, mixed> $curlOptions
606+
* @param array<string, mixed> $config
607+
*
608+
* @return array<int, mixed>
609+
*/
610+
private function applyProxyOptions(array $curlOptions, array $config): array
611+
{
574612
// Proxy
575613
if (isset($config['proxy'])) {
576614
$curlOptions[CURLOPT_HTTPPROXYTUNNEL] = true;
577615
$curlOptions[CURLOPT_PROXY] = $config['proxy'];
578616
}
579617

618+
return $curlOptions;
619+
}
620+
621+
/**
622+
* @param array<int, mixed> $curlOptions
623+
* @param array<string, mixed> $config
624+
*
625+
* @return array<int, mixed>
626+
*/
627+
private function applyDebugOptions(array $curlOptions, array $config): array
628+
{
580629
// Debug
581-
if ($config['debug']) {
630+
if (isset($config['debug']) && $config['debug'] !== false && $config['debug'] !== '') {
582631
$curlOptions[CURLOPT_VERBOSE] = 1;
583632
$curlOptions[CURLOPT_STDERR] = is_string($config['debug']) ? fopen($config['debug'], 'a+b') : fopen('php://stderr', 'wb');
584633
}
585634

586-
// Decode Content
587-
if (! empty($config['decode_content'])) {
588-
$accept = $this->getHeaderLine('Accept-Encoding');
589-
590-
if ($accept !== '') {
591-
$curlOptions[CURLOPT_ENCODING] = $accept;
592-
} else {
593-
$curlOptions[CURLOPT_ENCODING] = '';
594-
$curlOptions[CURLOPT_HTTPHEADER] = 'Accept-Encoding';
595-
}
596-
}
635+
return $curlOptions;
636+
}
597637

638+
/**
639+
* @param array<int, mixed> $curlOptions
640+
* @param array<string, mixed> $config
641+
*
642+
* @return array<int, mixed>
643+
*/
644+
private function applyRedirectOptions(array $curlOptions, array $config): array
645+
{
598646
// Allow Redirects
599647
if (array_key_exists('allow_redirects', $config)) {
600648
$settings = $this->redirectDefaults;
@@ -623,6 +671,17 @@ protected function setCURLOptions(array $curlOptions = [], array $config = [])
623671
}
624672
}
625673

674+
return $curlOptions;
675+
}
676+
677+
/**
678+
* @param array<int, mixed> $curlOptions
679+
* @param array<string, mixed> $config
680+
*
681+
* @return array<int, mixed>
682+
*/
683+
private function applyConnectionOptions(array $curlOptions, array $config): array
684+
{
626685
// DNS Cache Timeout
627686
if (isset($config['dns_cache_timeout']) && is_numeric($config['dns_cache_timeout']) && $config['dns_cache_timeout'] >= -1) {
628687
$curlOptions[CURLOPT_DNS_CACHE_TIMEOUT] = (int) $config['dns_cache_timeout'];
@@ -634,13 +693,45 @@ protected function setCURLOptions(array $curlOptions = [], array $config = [])
634693
: true;
635694

636695
// Timeout
637-
$curlOptions[CURLOPT_TIMEOUT_MS] = (float) $config['timeout'] * 1000;
696+
$curlOptions[CURLOPT_TIMEOUT_MS] = (float) ($config['timeout'] ?? 0) * 1000;
638697

639698
// Connection Timeout
640-
$curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = (float) $config['connect_timeout'] * 1000;
699+
$curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = (float) ($config['connect_timeout'] ?? 150) * 1000;
700+
701+
// Resolve IP
702+
if (array_key_exists('force_ip_resolve', $config)) {
703+
$curlOptions[CURLOPT_IPRESOLVE] = match ($config['force_ip_resolve']) {
704+
'v4' => CURL_IPRESOLVE_V4,
705+
'v6' => CURL_IPRESOLVE_V6,
706+
default => CURL_IPRESOLVE_WHATEVER,
707+
};
708+
}
709+
710+
return $curlOptions;
711+
}
712+
713+
/**
714+
* @param array<int, mixed> $curlOptions
715+
* @param array<string, mixed> $config
716+
*
717+
* @return array<int, mixed>
718+
*/
719+
private function applyPayloadOptions(array $curlOptions, array $config): array
720+
{
721+
// Decode Content
722+
if (isset($config['decode_content']) && $config['decode_content'] !== false) {
723+
$accept = $this->getHeaderLine('Accept-Encoding');
724+
725+
if ($accept !== '') {
726+
$curlOptions[CURLOPT_ENCODING] = $accept;
727+
} else {
728+
$curlOptions[CURLOPT_ENCODING] = '';
729+
$curlOptions[CURLOPT_HTTPHEADER] = 'Accept-Encoding';
730+
}
731+
}
641732

642733
// Post Data - application/x-www-form-urlencoded
643-
if (! empty($config['form_params']) && is_array($config['form_params'])) {
734+
if (isset($config['form_params']) && is_array($config['form_params']) && $config['form_params'] !== []) {
644735
$postFields = http_build_query($config['form_params']);
645736
$curlOptions[CURLOPT_POSTFIELDS] = $postFields;
646737

@@ -651,14 +742,11 @@ protected function setCURLOptions(array $curlOptions = [], array $config = [])
651742
}
652743

653744
// Post Data - multipart/form-data
654-
if (! empty($config['multipart']) && is_array($config['multipart'])) {
745+
if (isset($config['multipart']) && is_array($config['multipart']) && $config['multipart'] !== []) {
655746
// setting the POSTFIELDS option automatically sets multipart
656747
$curlOptions[CURLOPT_POSTFIELDS] = $config['multipart'];
657748
}
658749

659-
// HTTP Errors
660-
$curlOptions[CURLOPT_FAILONERROR] = array_key_exists('http_errors', $config) ? (bool) $config['http_errors'] : true;
661-
662750
// JSON
663751
if (isset($config['json'])) {
664752
// Will be set as the body in `applyBody()`
@@ -668,30 +756,37 @@ protected function setCURLOptions(array $curlOptions = [], array $config = [])
668756
$this->setHeader('Content-Length', (string) strlen($json));
669757
}
670758

671-
// Resolve IP
672-
if (array_key_exists('force_ip_resolve', $config)) {
673-
$curlOptions[CURLOPT_IPRESOLVE] = match ($config['force_ip_resolve']) {
674-
'v4' => CURL_IPRESOLVE_V4,
675-
'v6' => CURL_IPRESOLVE_V6,
676-
default => CURL_IPRESOLVE_WHATEVER,
677-
};
678-
}
759+
return $curlOptions;
760+
}
761+
762+
/**
763+
* @param array<int, mixed> $curlOptions
764+
* @param array<string, mixed> $config
765+
*
766+
* @return array<int, mixed>
767+
*/
768+
private function applyMiscOptions(array $curlOptions, array $config): array
769+
{
770+
// HTTP Errors
771+
$curlOptions[CURLOPT_FAILONERROR] = array_key_exists('http_errors', $config) ? (bool) $config['http_errors'] : true;
679772

680773
// version
681-
if (! empty($config['version'])) {
774+
if (isset($config['version']) && $config['version'] !== '') {
682775
$version = sprintf('%.1F', $config['version']);
683-
if ($version === '1.0') {
684-
$curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
685-
} elseif ($version === '1.1') {
686-
$curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
687-
} elseif ($version === '2.0') {
688-
$curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
689-
} elseif ($version === '3.0') {
690-
if (! defined('CURL_HTTP_VERSION_3')) {
691-
define('CURL_HTTP_VERSION_3', 30);
692-
}
693776

694-
$curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_3;
777+
if (! defined('CURL_HTTP_VERSION_3')) {
778+
define('CURL_HTTP_VERSION_3', 30);
779+
}
780+
781+
$versions = [
782+
'1.0' => CURL_HTTP_VERSION_1_0,
783+
'1.1' => CURL_HTTP_VERSION_1_1,
784+
'2.0' => CURL_HTTP_VERSION_2_0,
785+
'3.0' => CURL_HTTP_VERSION_3,
786+
];
787+
788+
if (isset($versions[$version])) {
789+
$curlOptions[CURLOPT_HTTP_VERSION] = $versions[$version];
695790
}
696791
}
697792

tests/system/HTTP/CURLRequestTest.php

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ class CURLRequestTest extends CIUnitTestCase
3939

4040
protected function setUp(): void
4141
{
42-
parent::setUp();
43-
4442
$this->resetServices();
4543
Services::injectMock('superglobals', new Superglobals());
4644
$this->request = $this->getRequest();
@@ -1566,4 +1564,127 @@ public function testProxyAndContinueResponses(): void
15661564

15671565
$this->assertSame($testBody, $response->getBody());
15681566
}
1567+
1568+
public function testApplyAuthOptionsDirect(): void
1569+
{
1570+
$invoker = self::getPrivateMethodInvoker($this->request, 'applyAuthOptions');
1571+
1572+
$options = $invoker([], ['auth' => ['user', 'pass', 'digest']]);
1573+
$this->assertSame('user:pass', $options[CURLOPT_USERPWD]);
1574+
$this->assertSame(CURLAUTH_DIGEST, $options[CURLOPT_HTTPAUTH]);
1575+
1576+
$options2 = $invoker([], ['auth' => ['user', 'pass', 'basic']]);
1577+
$this->assertSame(CURLAUTH_BASIC, $options2[CURLOPT_HTTPAUTH]);
1578+
}
1579+
1580+
public function testApplySslOptionsDirect(): void
1581+
{
1582+
$invoker = self::getPrivateMethodInvoker($this->request, 'applySslOptions');
1583+
1584+
$options = $invoker([], ['cert' => __FILE__]);
1585+
$this->assertSame(__FILE__, $options[CURLOPT_SSLCERT]);
1586+
1587+
$options2 = $invoker([], ['verify' => false]);
1588+
$this->assertFalse($options2[CURLOPT_SSL_VERIFYPEER]);
1589+
$this->assertSame(0, $options2[CURLOPT_SSL_VERIFYHOST]);
1590+
}
1591+
1592+
public function testApplyProxyOptionsDirect(): void
1593+
{
1594+
$invoker = self::getPrivateMethodInvoker($this->request, 'applyProxyOptions');
1595+
1596+
$options = $invoker([], ['proxy' => 'http://proxy.example.com']);
1597+
$this->assertTrue($options[CURLOPT_HTTPPROXYTUNNEL]);
1598+
$this->assertSame('http://proxy.example.com', $options[CURLOPT_PROXY]);
1599+
}
1600+
1601+
public function testApplyDebugOptionsDirect(): void
1602+
{
1603+
$invoker = self::getPrivateMethodInvoker($this->request, 'applyDebugOptions');
1604+
1605+
$options = $invoker([], ['debug' => true]);
1606+
$this->assertSame(1, $options[CURLOPT_VERBOSE]);
1607+
$this->assertIsResource($options[CURLOPT_STDERR]);
1608+
}
1609+
1610+
public function testApplyRedirectOptionsDirect(): void
1611+
{
1612+
$invoker = self::getPrivateMethodInvoker($this->request, 'applyRedirectOptions');
1613+
1614+
$options = $invoker([], ['allow_redirects' => false]);
1615+
$this->assertSame(0, $options[CURLOPT_FOLLOWLOCATION]);
1616+
1617+
$options2 = $invoker([], ['allow_redirects' => true]);
1618+
$this->assertSame(1, $options2[CURLOPT_FOLLOWLOCATION]);
1619+
$this->assertSame(5, $options2[CURLOPT_MAXREDIRS]);
1620+
}
1621+
1622+
public function testApplyConnectionOptionsDirect(): void
1623+
{
1624+
$invoker = self::getPrivateMethodInvoker($this->request, 'applyConnectionOptions');
1625+
1626+
$options = $invoker([], [
1627+
'dns_cache_timeout' => 120,
1628+
'fresh_connect' => false,
1629+
'timeout' => 10,
1630+
'connect_timeout' => 5,
1631+
'force_ip_resolve' => 'v4',
1632+
]);
1633+
1634+
$this->assertSame(120, $options[CURLOPT_DNS_CACHE_TIMEOUT]);
1635+
$this->assertFalse($options[CURLOPT_FRESH_CONNECT]);
1636+
$this->assertEqualsWithDelta(10000.0, $options[CURLOPT_TIMEOUT_MS], PHP_FLOAT_EPSILON);
1637+
$this->assertEqualsWithDelta(5000.0, $options[CURLOPT_CONNECTTIMEOUT_MS], PHP_FLOAT_EPSILON);
1638+
$this->assertSame(CURL_IPRESOLVE_V4, $options[CURLOPT_IPRESOLVE]);
1639+
}
1640+
1641+
public function testApplyPayloadOptionsDirect(): void
1642+
{
1643+
$invoker = self::getPrivateMethodInvoker($this->request, 'applyPayloadOptions');
1644+
1645+
$options = $invoker([], ['form_params' => ['foo' => 'bar']]);
1646+
$this->assertSame('foo=bar', $options[CURLOPT_POSTFIELDS]);
1647+
1648+
$options2 = $invoker([], ['multipart' => ['file' => 'data']]);
1649+
$this->assertSame(['file' => 'data'], $options2[CURLOPT_POSTFIELDS]);
1650+
}
1651+
1652+
public function testApplyMiscOptionsDirect(): void
1653+
{
1654+
$invoker = self::getPrivateMethodInvoker($this->request, 'applyMiscOptions');
1655+
1656+
$options = $invoker([], [
1657+
'http_errors' => false,
1658+
'version' => '2.0',
1659+
'cookie' => 'cookies.txt',
1660+
'user_agent' => 'TestAgent',
1661+
]);
1662+
1663+
$this->assertFalse($options[CURLOPT_FAILONERROR]);
1664+
$this->assertSame(CURL_HTTP_VERSION_2_0, $options[CURLOPT_HTTP_VERSION]);
1665+
$this->assertSame('cookies.txt', $options[CURLOPT_COOKIEJAR]);
1666+
$this->assertSame('cookies.txt', $options[CURLOPT_COOKIEFILE]);
1667+
$this->assertSame('TestAgent', $options[CURLOPT_USERAGENT]);
1668+
}
1669+
1670+
public function testCURLOptionsPreservesIntegerKeys(): void
1671+
{
1672+
// cURL options use integer constants as keys. This test ensures they are not re-indexed.
1673+
$request = $this->getRequest();
1674+
$method = self::getPrivateMethodInvoker($request, 'setCURLOptions');
1675+
1676+
$initialOptions = [
1677+
CURLOPT_RETURNTRANSFER => true,
1678+
];
1679+
1680+
$config = [
1681+
'auth' => ['user', 'pass'],
1682+
];
1683+
1684+
$options = $method($initialOptions, $config);
1685+
1686+
// Verify keys are preserved and not re-indexed to 0, 1...
1687+
$this->assertArrayHasKey(CURLOPT_RETURNTRANSFER, $options);
1688+
$this->assertArrayHasKey(CURLOPT_USERPWD, $options);
1689+
}
15691690
}

0 commit comments

Comments
 (0)