Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 106 additions & 32 deletions ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -1713,7 +1713,6 @@ static bool php_curl_set_callable_handler(zend_fcall_info_cache *const handler_f
static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool is_array_config) /* {{{ */
{
CURLcode error = CURLE_OK;
zend_long lval;

switch (option) {
/* Callable options */
Expand All @@ -1735,14 +1734,39 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
#endif

/* Long options */
case CURLOPT_SSL_VERIFYHOST:
lval = zval_get_long(zvalue);
case CURLOPT_PROTOCOLS:
case CURLOPT_REDIR_PROTOCOLS: {
bool failed = false;
zend_long lval = zval_try_get_long(zvalue, &failed);
if (UNEXPECTED(failed)) {
zend_argument_type_error(2 + !is_array_config,
"must be of type int for option CURLOPT_%s, %s given",
php_curl_option_get_name(option), zend_zval_type_name(zvalue));
return FAILURE;
}
if (PG(open_basedir) && *PG(open_basedir) && (lval & CURLPROTO_FILE)) {
php_error_docref(NULL, E_WARNING, "CURLPROTO_FILE cannot be activated when an open_basedir is set");
return FAILURE;
}
error = curl_easy_setopt(ch->cp, option, lval);
break;
}
case CURLOPT_SSL_VERIFYHOST: {
bool failed = false;
zend_long lval = zval_try_get_long(zvalue, &failed);
if (UNEXPECTED(failed)) {
zend_argument_type_error(2 + !is_array_config,
"must be of type int for option CURLOPT_%s, %s given",
php_curl_option_get_name(option), zend_zval_type_name(zvalue));
return FAILURE;
}
if (lval == 1) {
php_error_docref(NULL, E_NOTICE, "CURLOPT_SSL_VERIFYHOST no longer accepts the value 1, value 2 will be used instead");
error = curl_easy_setopt(ch->cp, option, 2L);
break;
lval = 2L;
}
ZEND_FALLTHROUGH;
error = curl_easy_setopt(ch->cp, option, lval);
break;
}
case CURLOPT_AUTOREFERER:
case CURLOPT_BUFFERSIZE:
case CURLOPT_CONNECTTIMEOUT:
Expand Down Expand Up @@ -1812,8 +1836,6 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
case CURLOPT_PROXY_TRANSFER_MODE:
case CURLOPT_ADDRESS_SCOPE:
case CURLOPT_CERTINFO:
case CURLOPT_PROTOCOLS:
case CURLOPT_REDIR_PROTOCOLS:
case CURLOPT_SOCKS5_GSSAPI_NEC:
case CURLOPT_TFTP_BLKSIZE:
case CURLOPT_FTP_USE_PRET:
Expand Down Expand Up @@ -1891,15 +1913,18 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
#if LIBCURL_VERSION_NUM >= 0x080900 /* Available since 8.9.0 */
case CURLOPT_TCP_KEEPCNT:
#endif
case CURLOPT_FOLLOWLOCATION:
lval = zval_get_long(zvalue);
if ((option == CURLOPT_PROTOCOLS || option == CURLOPT_REDIR_PROTOCOLS) &&
(PG(open_basedir) && *PG(open_basedir)) && (lval & CURLPROTO_FILE)) {
php_error_docref(NULL, E_WARNING, "CURLPROTO_FILE cannot be activated when an open_basedir is set");
return FAILURE;
case CURLOPT_FOLLOWLOCATION: {
bool failed = false;
zend_long lval = zval_try_get_long(zvalue, &failed);
if (UNEXPECTED(failed)) {
zend_argument_type_error(2 + !is_array_config,
"must be of type int for option CURLOPT_%s, %s given",
php_curl_option_get_name(option), zend_zval_type_name(zvalue));
return FAILURE;
}
error = curl_easy_setopt(ch->cp, option, lval);
break;
}
case CURLOPT_SAFE_UPLOAD:
if (!zend_is_true(zvalue)) {
zend_value_error("%s(): Disabling safe uploads is no longer supported", get_active_function_name());
Expand Down Expand Up @@ -1987,7 +2012,13 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
#endif
{
zend_string *tmp_str;
zend_string *str = zval_get_tmp_string(zvalue, &tmp_str);
zend_string *str = zval_try_get_tmp_string(zvalue, &tmp_str);
if (UNEXPECTED(str == NULL)) {
zend_argument_type_error(2 + !is_array_config,
"must be of type string for option CURLOPT_%s, %s given",
php_curl_option_get_name(option), zend_zval_type_name(zvalue));
return FAILURE;
}
#if LIBCURL_VERSION_NUM >= 0x075500 /* Available since 7.85.0 */
if ((option == CURLOPT_PROTOCOLS_STR || option == CURLOPT_REDIR_PROTOCOLS_STR) &&
(PG(open_basedir) && *PG(open_basedir))
Expand Down Expand Up @@ -2036,7 +2067,13 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
error = curl_easy_setopt(ch->cp, option, NULL);
} else {
zend_string *tmp_str;
zend_string *str = zval_get_tmp_string(zvalue, &tmp_str);
zend_string *str = zval_try_get_tmp_string(zvalue, &tmp_str);
if (UNEXPECTED(str == NULL)) {
zend_argument_type_error(2 + !is_array_config,
"must be of type string for option CURLOPT_%s, %s given",
php_curl_option_get_name(option), zend_zval_type_name(zvalue));
return FAILURE;
}
zend_result ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str));
zend_tmp_string_release(tmp_str);
return ret;
Expand All @@ -2056,7 +2093,11 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
case CURLOPT_URL:
{
zend_string *tmp_str;
zend_string *str = zval_get_tmp_string(zvalue, &tmp_str);
zend_string *str = zval_try_get_tmp_string(zvalue, &tmp_str);
if (UNEXPECTED(str == NULL)) {
zend_argument_type_error(2 + !is_array_config, "must be of type string for option CURLOPT_URl, %s given", zend_zval_type_name(zvalue));
return FAILURE;
}
zend_result ret = php_curl_option_url(ch, str);
zend_tmp_string_release(tmp_str);
return ret;
Expand Down Expand Up @@ -2173,18 +2214,24 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
{
zval *current;
HashTable *ph;
zend_string *val, *tmp_val;
struct curl_slist *slist = NULL;

if (Z_TYPE_P(zvalue) != IS_ARRAY) {
zend_type_error("%s(): The CURLOPT_%s option must have an array value", get_active_function_name(), php_curl_option_get_name(option));
zend_argument_type_error(2 + !is_array_config,
"must be of type array for option CURLOPT_%s, %s given",
php_curl_option_get_name(option), zend_zval_type_name(zvalue));
return FAILURE;
}

ph = Z_ARRVAL_P(zvalue);
ZEND_HASH_FOREACH_VAL(ph, current) {
ZVAL_DEREF(current);
val = zval_get_tmp_string(current, &tmp_val);
zend_string *tmp_val;
zend_string *val = zval_try_get_tmp_string(current, &tmp_val);
if (UNEXPECTED(val == NULL)) {
zend_argument_type_error(2 + !is_array_config, "entries for option CURLOPT_%s must be of type string, %s given", php_curl_option_get_name(option), zend_zval_type_name(zvalue));
return FAILURE;
}

if (zend_str_has_nul_byte(val)) {
curl_slist_free_all(slist);
Expand Down Expand Up @@ -2233,7 +2280,11 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
}
} else {
zend_string *tmp_str;
zend_string *str = zval_get_tmp_string(zvalue, &tmp_str);
zend_string *str = zval_try_get_tmp_string(zvalue, &tmp_str);
if (UNEXPECTED(str == NULL)) {
zend_argument_type_error(2 + !is_array_config, "must be of type string|array for option CURLOPT_POSTFIELDS, %s given", zend_zval_type_name(zvalue));
return FAILURE;
}
/* with curl 7.17.0 and later, we can use COPYPOSTFIELDS, but we have to provide size before */
error = curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) ZSTR_LEN(str));
error = curl_easy_setopt(ch->cp, CURLOPT_COPYPOSTFIELDS, ZSTR_VAL(str));
Expand All @@ -2254,15 +2305,32 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
case CURLOPT_MAX_RECV_SPEED_LARGE:
case CURLOPT_MAX_SEND_SPEED_LARGE:
case CURLOPT_MAXFILESIZE_LARGE:
case CURLOPT_TIMEVALUE_LARGE:
lval = zval_get_long(zvalue);
case CURLOPT_TIMEVALUE_LARGE: {
bool failed = false;
zend_long lval = zval_try_get_long(zvalue, &failed);
if (UNEXPECTED(failed)) {
zend_argument_type_error(2 + !is_array_config,
"must be of type int for option CURLOPT_%s, %s given",
php_curl_option_get_name(option), zend_zval_type_name(zvalue));
return FAILURE;
}
// TODO bound check that lval fits in curl_off_t?
error = curl_easy_setopt(ch->cp, option, (curl_off_t)lval);
break;
}

case CURLOPT_POSTREDIR:
lval = zval_get_long(zvalue);
case CURLOPT_POSTREDIR: {
bool failed = false;
zend_long lval = zval_try_get_long(zvalue, &failed);
if (UNEXPECTED(failed)) {
zend_argument_type_error(2 + !is_array_config,
"must be of type int for option CURLOPT_%s, %s given",
php_curl_option_get_name(option), zend_zval_type_name(zvalue));
return FAILURE;
}
error = curl_easy_setopt(ch->cp, CURLOPT_POSTREDIR, (long) (lval & CURL_REDIR_POST_ALL));
break;
}

/* the following options deal with files, therefore the open_basedir check
* is required.
Expand All @@ -2279,7 +2347,13 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
case CURLOPT_SSH_KNOWNHOSTS:
{
zend_string *tmp_str;
zend_string *str = zval_get_tmp_string(zvalue, &tmp_str);
zend_string *str = zval_try_get_tmp_string(zvalue, &tmp_str);
if (UNEXPECTED(str == NULL)) {
zend_argument_type_error(2 + !is_array_config,
"must be of type string for option CURLOPT_%s, %s given",
php_curl_option_get_name(option), zend_zval_type_name(zvalue));
return FAILURE;
}
zend_result ret;

if (ZSTR_LEN(str) && php_check_open_basedir(ZSTR_VAL(str))) {
Expand Down Expand Up @@ -2311,12 +2385,12 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue

case CURLOPT_SHARE:
{
if (Z_TYPE_P(zvalue) != IS_OBJECT) {
break;
}

if (Z_OBJCE_P(zvalue) != curl_share_ce && Z_OBJCE_P(zvalue) != curl_share_persistent_ce) {
break;
if (Z_TYPE_P(zvalue) != IS_OBJECT || (Z_OBJCE_P(zvalue) != curl_share_ce && Z_OBJCE_P(zvalue) != curl_share_persistent_ce)) {
zend_argument_type_error(2 + !is_array_config,
"must be of type CurlSharePersistentHandle|CurlShareHandle for option CURLOPT_SHARE, %s given",
zend_zval_type_name(zvalue)
);
return FAILURE;
}

php_curlsh *sh = Z_CURL_SHARE_P(zvalue);
Expand Down
2 changes: 1 addition & 1 deletion ext/curl/tests/curl_read_function_error_on_int.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ include 'server.inc';
$host = curl_cli_server_start();
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "{$host}/get.php?test=post");
curl_setopt($ch, CURLOPT_POST, ['f' => 'f']);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 2);
curl_setopt($ch, CURLOPT_READFUNCTION, "custom_readfunction" );

Expand Down
2 changes: 1 addition & 1 deletion ext/curl/tests/curl_setopt_basic003.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ var_dump( $curl_content );
?>
--EXPECT--
*** curl_setopt() call with CURLOPT_HTTPHEADER
TypeError: curl_setopt(): The CURLOPT_HTTPHEADER option must have an array value
TypeError: curl_setopt(): Argument #3 ($value) must be of type array for option CURLOPT_HTTPHEADER, int given
bool(false)
bool(true)
105 changes: 105 additions & 0 deletions ext/curl/tests/curl_setopt_option_value_errors.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
--TEST--
Check that curl_setopt() values are of the correct type for the given option
--EXTENSIONS--
curl
--FILE--
<?php

const KNOWNS_ERRORS = [
'curl_setopt(): Argument #2 ($option) is not a valid cURL option',
'curl_setopt(): supplied argument is not a valid File-Handle resource',
'Object of class stdClass could not be converted to string',
'curl_setopt(): Argument #3 ($value) must be of type CurlSharePersistentHandle|CurlShareHandle for option CURLOPT_SHARE, stdClass given',
];

function isKnownError($error): bool {
//return in_array($error, KNOWNS_ERRORS);
if (in_array($error, KNOWNS_ERRORS)) {
return true;
}
if (preg_match(
'/curl_setopt\(\): Argument #3 \(\$value\) must be a valid callback for option CURLOPT_([A-Z_])+, no array or string given/',
$error,
)) {
return true;
}
if (preg_match(
'/curl_setopt\(\): Argument #3 \(\$value\) must be of type array for option CURLOPT_([A-Z0-9_])+, stdClass given/',
$error,
)) {
return true;
}
if (preg_match(
'/curl_setopt\(\): Argument #3 \(\$value\) must be of type int for option CURLOPT_([A-Z0-9_])+, stdClass given/',
$error,
)) {
return true;
}
return false;
}

$ch = curl_init();
$obj = new stdClass();

$re = new ReflectionExtension('curl');
$constants = $re->getConstants();

foreach ($constants as $name => $constant) {
try {
$r = curl_setopt($ch, $constant, $obj);
if ($r === false) {
echo "Option $name failed\n";
} else {
echo "Option $name succeeded\n";
}
} catch (Throwable $e) {
if (!isKnownError($e->getMessage())) {
echo "Option $name failed with exception:\n";
echo $e::class, ': ', $e->getMessage(), "\n";
}
}
}

?>
--EXPECTF--
Option CURLOPT_BINARYTRANSFER succeeded
Option CURLOPT_DNS_USE_GLOBAL_CACHE succeeded
Option CURLOPT_PRIVATE succeeded
Option CURLOPT_RETURNTRANSFER succeeded
Option CURLFOLLOW_OBEYCODE succeeded
Option CURLE_FAILED_INIT succeeded
Option CURLINFO_HEADER_OUT succeeded
Option CURLM_BAD_EASY_HANDLE succeeded
Option CURLM_CALL_MULTI_PERFORM succeeded
Option CURLSHOPT_UNSHARE succeeded
Option CURL_HTTP_VERSION_1_1 succeeded
Option CURL_LOCK_DATA_COOKIE succeeded
Option CURL_NETRC_REQUIRED succeeded
Option CURL_SSLVERSION_SSLv2 succeeded
Option CURL_TIMECOND_IFUNMODSINCE succeeded
Option CURL_VERSION_KERBEROS4 succeeded
Option CURLAUTH_DIGEST succeeded
Option CURL_IPRESOLVE_V6 succeeded
Option CURLFTPSSL_CONTROL succeeded
Option CURLFTPAUTH_TLS succeeded
Option CURLFTPMETHOD_NOCWD succeeded
Option CURLFTPSSL_CCC_ACTIVE succeeded
Option CURLUSESSL_CONTROL succeeded
Option CURL_SEEKFUNC_CANTSEEK succeeded
Option CURLSSH_AUTH_ANY succeeded
Option CURLSSH_AUTH_DEFAULT succeeded
Option CURLSSH_AUTH_PASSWORD succeeded
Option CURL_REDIR_POST_302 succeeded
Option CURLPROTO_ALL succeeded
Option CURLPROTO_HTTPS succeeded
Option CURLFTP_CREATE_DIR_RETRY succeeded
Option CURLKHMATCH_MISSING succeeded
Option CURL_RTSPREQ_DESCRIBE succeeded
Option CURL_FNMATCHFUNC_FAIL succeeded
Option CURLGSSAPI_DELEGATION_FLAG succeeded
Option CURLPIPE_MULTIPLEX succeeded
Option CURLSSLOPT_NO_REVOKE succeeded
Option CURLPROXY_HTTPS succeeded
Option CURLPX_BAD_VERSION succeeded
Option CURLHSTS_READONLYFILE succeeded
Option CURLOPT_SAFE_UPLOAD succeeded
Loading