From 8a907a9bd8bd2a5bee98f0a574e413874d12ff99 Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Fri, 27 Feb 2026 13:11:24 +0100 Subject: [PATCH 1/5] ext/sodium: Add support for libsodium 1.0.21 APIs (#20960) Add support for new functions present in recent libsodium versions: - Functions for IP address encryption: - sodium_crypto_ipcrypt_* - sodium_bin2ip/sodium_ip2bin helpers - Extendable output functions: - SHAKE128/SHAKE256 - TurboSHAKE128/TurboSHAKE256 --- NEWS | 3 + ext/sodium/libsodium.c | 914 ++++++++++++++++++++ ext/sodium/libsodium.stub.php | 166 ++++ ext/sodium/libsodium_arginfo.h | 201 ++++- ext/sodium/tests/crypto_ipcrypt.phpt | 63 ++ ext/sodium/tests/crypto_ipcrypt_nd.phpt | 98 +++ ext/sodium/tests/crypto_ipcrypt_pfx.phpt | 65 ++ ext/sodium/tests/crypto_xof_shake128.phpt | 104 +++ ext/sodium/tests/crypto_xof_shake256.phpt | 80 ++ ext/sodium/tests/crypto_xof_turboshake.phpt | 112 +++ ext/sodium/tests/sodium_bin2ip.phpt | 69 ++ 11 files changed, 1874 insertions(+), 1 deletion(-) create mode 100644 ext/sodium/tests/crypto_ipcrypt.phpt create mode 100644 ext/sodium/tests/crypto_ipcrypt_nd.phpt create mode 100644 ext/sodium/tests/crypto_ipcrypt_pfx.phpt create mode 100644 ext/sodium/tests/crypto_xof_shake128.phpt create mode 100644 ext/sodium/tests/crypto_xof_shake256.phpt create mode 100644 ext/sodium/tests/crypto_xof_turboshake.phpt create mode 100644 ext/sodium/tests/sodium_bin2ip.phpt diff --git a/NEWS b/NEWS index 3ea4407433664..fd2f2fa82ec80 100644 --- a/NEWS +++ b/NEWS @@ -104,6 +104,9 @@ PHP NEWS . Fixed GH-20532 (socket_addrinfo_lookup gives the error code with a new optional parameter). (David Carlier) +- Sodium: + . Added support for libsodium 1.0.21 IPcrypt and XOF APIs. (jedisct1) + - SPL: . DirectoryIterator key can now work better with filesystem supporting larger directory indexing. (David Carlier) diff --git a/ext/sodium/libsodium.c b/ext/sodium/libsodium.c index 7d36fbb892884..bb11a6ef9116f 100644 --- a/ext/sodium/libsodium.c +++ b/ext/sodium/libsodium.c @@ -3913,3 +3913,917 @@ PHP_FUNCTION(sodium_crypto_core_ristretto255_sub) RETURN_NEW_STR(r); } #endif + +#ifdef crypto_ipcrypt_KEYBYTES +PHP_FUNCTION(sodium_crypto_ipcrypt_keygen) +{ + unsigned char key[crypto_ipcrypt_KEYBYTES]; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + crypto_ipcrypt_keygen(key); + RETURN_STRINGL((const char *) key, sizeof key); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_encrypt) +{ + char *ip; + size_t ip_len; + unsigned char *key; + size_t key_len; + unsigned char bin[crypto_ipcrypt_BYTES]; + unsigned char encrypted[crypto_ipcrypt_BYTES]; + char ip_out[46]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", + &ip, &ip_len, &key, &key_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (key_len != crypto_ipcrypt_KEYBYTES) { + zend_argument_error(sodium_exception_ce, 2, "must be SODIUM_CRYPTO_IPCRYPT_KEYBYTES bytes long"); + RETURN_THROWS(); + } + if (sodium_ip2bin(bin, ip, ip_len) != 0) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid IP address"); + RETURN_THROWS(); + } + crypto_ipcrypt_encrypt(encrypted, bin, key); + if (sodium_bin2ip(ip_out, sizeof ip_out, encrypted) == NULL) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + RETURN_STRING(ip_out); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_decrypt) +{ + char *encrypted_ip; + size_t encrypted_ip_len; + unsigned char *key; + size_t key_len; + unsigned char bin[crypto_ipcrypt_BYTES]; + unsigned char decrypted[crypto_ipcrypt_BYTES]; + char ip_out[46]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", + &encrypted_ip, &encrypted_ip_len, &key, &key_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (key_len != crypto_ipcrypt_KEYBYTES) { + zend_argument_error(sodium_exception_ce, 2, "must be SODIUM_CRYPTO_IPCRYPT_KEYBYTES bytes long"); + RETURN_THROWS(); + } + if (sodium_ip2bin(bin, encrypted_ip, encrypted_ip_len) != 0) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid IP address"); + RETURN_THROWS(); + } + crypto_ipcrypt_decrypt(decrypted, bin, key); + if (sodium_bin2ip(ip_out, sizeof ip_out, decrypted) == NULL) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + RETURN_STRING(ip_out); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_nd_keygen) +{ + unsigned char key[crypto_ipcrypt_ND_KEYBYTES]; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + randombytes_buf(key, sizeof key); + RETURN_STRINGL((const char *) key, sizeof key); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_nd_encrypt) +{ + char *ip; + size_t ip_len; + unsigned char *key; + size_t key_len; + unsigned char bin[crypto_ipcrypt_ND_INPUTBYTES]; + unsigned char tweak[crypto_ipcrypt_ND_TWEAKBYTES]; + unsigned char encrypted[crypto_ipcrypt_ND_OUTPUTBYTES]; + char hex_out[crypto_ipcrypt_ND_OUTPUTBYTES * 2 + 1]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", + &ip, &ip_len, &key, &key_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (key_len != crypto_ipcrypt_ND_KEYBYTES) { + zend_argument_error(sodium_exception_ce, 2, "must be SODIUM_CRYPTO_IPCRYPT_ND_KEYBYTES bytes long"); + RETURN_THROWS(); + } + if (sodium_ip2bin(bin, ip, ip_len) != 0) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid IP address"); + RETURN_THROWS(); + } + randombytes_buf(tweak, sizeof tweak); + crypto_ipcrypt_nd_encrypt(encrypted, bin, tweak, key); + sodium_bin2hex(hex_out, sizeof hex_out, encrypted, sizeof encrypted); + RETURN_STRINGL(hex_out, crypto_ipcrypt_ND_OUTPUTBYTES * 2); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_nd_decrypt) +{ + char *ciphertext_hex; + size_t ciphertext_hex_len; + unsigned char *key; + size_t key_len; + unsigned char encrypted[crypto_ipcrypt_ND_OUTPUTBYTES]; + unsigned char decrypted[crypto_ipcrypt_ND_INPUTBYTES]; + char ip_out[46]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", + &ciphertext_hex, &ciphertext_hex_len, &key, &key_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (key_len != crypto_ipcrypt_ND_KEYBYTES) { + zend_argument_error(sodium_exception_ce, 2, "must be SODIUM_CRYPTO_IPCRYPT_ND_KEYBYTES bytes long"); + RETURN_THROWS(); + } + if (ciphertext_hex_len != crypto_ipcrypt_ND_OUTPUTBYTES * 2) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid hex-encoded ciphertext"); + RETURN_THROWS(); + } + if (sodium_hex2bin(encrypted, sizeof encrypted, ciphertext_hex, ciphertext_hex_len, + NULL, NULL, NULL) != 0) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid hex-encoded ciphertext"); + RETURN_THROWS(); + } + crypto_ipcrypt_nd_decrypt(decrypted, encrypted, key); + if (sodium_bin2ip(ip_out, sizeof ip_out, decrypted) == NULL) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + RETURN_STRING(ip_out); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_ndx_keygen) +{ + unsigned char key[crypto_ipcrypt_NDX_KEYBYTES]; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + crypto_ipcrypt_ndx_keygen(key); + RETURN_STRINGL((const char *) key, sizeof key); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_ndx_encrypt) +{ + char *ip; + size_t ip_len; + unsigned char *key; + size_t key_len; + unsigned char bin[crypto_ipcrypt_NDX_INPUTBYTES]; + unsigned char tweak[crypto_ipcrypt_NDX_TWEAKBYTES]; + unsigned char encrypted[crypto_ipcrypt_NDX_OUTPUTBYTES]; + char hex_out[crypto_ipcrypt_NDX_OUTPUTBYTES * 2 + 1]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", + &ip, &ip_len, &key, &key_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (key_len != crypto_ipcrypt_NDX_KEYBYTES) { + zend_argument_error(sodium_exception_ce, 2, "must be SODIUM_CRYPTO_IPCRYPT_NDX_KEYBYTES bytes long"); + RETURN_THROWS(); + } + if (sodium_ip2bin(bin, ip, ip_len) != 0) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid IP address"); + RETURN_THROWS(); + } + randombytes_buf(tweak, sizeof tweak); + crypto_ipcrypt_ndx_encrypt(encrypted, bin, tweak, key); + sodium_bin2hex(hex_out, sizeof hex_out, encrypted, sizeof encrypted); + RETURN_STRINGL(hex_out, crypto_ipcrypt_NDX_OUTPUTBYTES * 2); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_ndx_decrypt) +{ + char *ciphertext_hex; + size_t ciphertext_hex_len; + unsigned char *key; + size_t key_len; + unsigned char encrypted[crypto_ipcrypt_NDX_OUTPUTBYTES]; + unsigned char decrypted[crypto_ipcrypt_NDX_INPUTBYTES]; + char ip_out[46]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", + &ciphertext_hex, &ciphertext_hex_len, &key, &key_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (key_len != crypto_ipcrypt_NDX_KEYBYTES) { + zend_argument_error(sodium_exception_ce, 2, "must be SODIUM_CRYPTO_IPCRYPT_NDX_KEYBYTES bytes long"); + RETURN_THROWS(); + } + if (ciphertext_hex_len != crypto_ipcrypt_NDX_OUTPUTBYTES * 2) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid hex-encoded ciphertext"); + RETURN_THROWS(); + } + if (sodium_hex2bin(encrypted, sizeof encrypted, ciphertext_hex, ciphertext_hex_len, + NULL, NULL, NULL) != 0) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid hex-encoded ciphertext"); + RETURN_THROWS(); + } + crypto_ipcrypt_ndx_decrypt(decrypted, encrypted, key); + if (sodium_bin2ip(ip_out, sizeof ip_out, decrypted) == NULL) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + RETURN_STRING(ip_out); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_pfx_keygen) +{ + unsigned char key[crypto_ipcrypt_PFX_KEYBYTES]; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + crypto_ipcrypt_pfx_keygen(key); + RETURN_STRINGL((const char *) key, sizeof key); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_pfx_encrypt) +{ + char *ip; + size_t ip_len; + unsigned char *key; + size_t key_len; + unsigned char bin[crypto_ipcrypt_PFX_BYTES]; + unsigned char encrypted[crypto_ipcrypt_PFX_BYTES]; + char ip_out[46]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", + &ip, &ip_len, &key, &key_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (key_len != crypto_ipcrypt_PFX_KEYBYTES) { + zend_argument_error(sodium_exception_ce, 2, "must be SODIUM_CRYPTO_IPCRYPT_PFX_KEYBYTES bytes long"); + RETURN_THROWS(); + } + if (sodium_ip2bin(bin, ip, ip_len) != 0) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid IP address"); + RETURN_THROWS(); + } + crypto_ipcrypt_pfx_encrypt(encrypted, bin, key); + if (sodium_bin2ip(ip_out, sizeof ip_out, encrypted) == NULL) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + RETURN_STRING(ip_out); +} + +PHP_FUNCTION(sodium_crypto_ipcrypt_pfx_decrypt) +{ + char *encrypted_ip; + size_t encrypted_ip_len; + unsigned char *key; + size_t key_len; + unsigned char bin[crypto_ipcrypt_PFX_BYTES]; + unsigned char decrypted[crypto_ipcrypt_PFX_BYTES]; + char ip_out[46]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", + &encrypted_ip, &encrypted_ip_len, &key, &key_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (key_len != crypto_ipcrypt_PFX_KEYBYTES) { + zend_argument_error(sodium_exception_ce, 2, "must be SODIUM_CRYPTO_IPCRYPT_PFX_KEYBYTES bytes long"); + RETURN_THROWS(); + } + if (sodium_ip2bin(bin, encrypted_ip, encrypted_ip_len) != 0) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid IP address"); + RETURN_THROWS(); + } + crypto_ipcrypt_pfx_decrypt(decrypted, bin, key); + if (sodium_bin2ip(ip_out, sizeof ip_out, decrypted) == NULL) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + RETURN_STRING(ip_out); +} + +PHP_FUNCTION(sodium_bin2ip) +{ + unsigned char *bin; + size_t bin_len; + char ip_out[46]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", + &bin, &bin_len) == FAILURE) { + RETURN_THROWS(); + } + if (bin_len != 16) { + zend_argument_error(sodium_exception_ce, 1, "must be 16 bytes long"); + RETURN_THROWS(); + } + if (sodium_bin2ip(ip_out, sizeof ip_out, bin) == NULL) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + RETURN_STRING(ip_out); +} + +PHP_FUNCTION(sodium_ip2bin) +{ + char *ip; + size_t ip_len; + unsigned char bin[16]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", + &ip, &ip_len) == FAILURE) { + RETURN_THROWS(); + } + if (sodium_ip2bin(bin, ip, ip_len) != 0) { + zend_argument_error(sodium_exception_ce, 1, "must be a valid IP address"); + RETURN_THROWS(); + } + RETURN_STRINGL((const char *) bin, sizeof bin); +} +#endif + +#ifdef crypto_xof_shake128_STATEBYTES +PHP_FUNCTION(sodium_crypto_xof_shake128) +{ + zend_string *out; + unsigned char *msg; + zend_long out_len; + size_t msg_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls", + &out_len, &msg, &msg_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (out_len <= 0 || out_len > ZSTR_MAX_LEN) { + zend_argument_error(sodium_exception_ce, 1, "must be a positive integer"); + RETURN_THROWS(); + } + out = zend_string_alloc((size_t) out_len, 0); + if (crypto_xof_shake128((unsigned char *) ZSTR_VAL(out), (size_t) out_len, + msg, (unsigned long long) msg_len) != 0) { + zend_string_efree(out); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + ZSTR_VAL(out)[out_len] = 0; + RETURN_NEW_STR(out); +} + +PHP_FUNCTION(sodium_crypto_xof_shake128_init) +{ + crypto_xof_shake128_state state_tmp; + zend_string *state; + zend_long domain = -1; + bool domain_is_null = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(domain, domain_is_null) + ZEND_PARSE_PARAMETERS_END(); + + memset(&state_tmp, 0, sizeof state_tmp); + if (domain_is_null) { + if (crypto_xof_shake128_init(&state_tmp) != 0) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + } else { + if (domain < 0x01 || domain > 0x7f) { + zend_argument_error(sodium_exception_ce, 1, "must be between 0x01 and 0x7f"); + RETURN_THROWS(); + } + if (crypto_xof_shake128_init_with_domain(&state_tmp, (unsigned char) domain) != 0) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + } + state = zend_string_alloc(sizeof state_tmp, 0); + memcpy(ZSTR_VAL(state), &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + ZSTR_VAL(state)[sizeof state_tmp] = 0; + RETURN_STR(state); +} + +PHP_FUNCTION(sodium_crypto_xof_shake128_update) +{ + crypto_xof_shake128_state state_tmp; + zval *state_zv; + unsigned char *msg; + unsigned char *state; + size_t msg_len; + size_t state_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zs", + &state_zv, &msg, &msg_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + ZVAL_DEREF(state_zv); + if (Z_TYPE_P(state_zv) != IS_STRING) { + zend_argument_error(sodium_exception_ce, 1, "must be a reference to a state"); + RETURN_THROWS(); + } + sodium_separate_string(state_zv); + state = (unsigned char *) Z_STRVAL(*state_zv); + state_len = Z_STRLEN(*state_zv); + if (state_len != sizeof (crypto_xof_shake128_state)) { + zend_argument_error(sodium_exception_ce, 1, "must have a correct state length"); + RETURN_THROWS(); + } + memcpy(&state_tmp, state, sizeof state_tmp); + if (crypto_xof_shake128_update(&state_tmp, msg, (unsigned long long) msg_len) != 0) { + sodium_memzero(&state_tmp, sizeof state_tmp); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + memcpy(state, &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + RETURN_TRUE; +} + +PHP_FUNCTION(sodium_crypto_xof_shake128_squeeze) +{ + crypto_xof_shake128_state state_tmp; + zval *state_zv; + zend_string *out; + unsigned char *state; + zend_long out_len; + size_t state_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zl", + &state_zv, &out_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + ZVAL_DEREF(state_zv); + if (Z_TYPE_P(state_zv) != IS_STRING) { + zend_argument_error(sodium_exception_ce, 1, "must be a reference to a state"); + RETURN_THROWS(); + } + sodium_separate_string(state_zv); + state = (unsigned char *) Z_STRVAL(*state_zv); + state_len = Z_STRLEN(*state_zv); + if (state_len != sizeof (crypto_xof_shake128_state)) { + zend_argument_error(sodium_exception_ce, 1, "must have a correct state length"); + RETURN_THROWS(); + } + if (out_len <= 0 || out_len > ZSTR_MAX_LEN) { + zend_argument_error(sodium_exception_ce, 2, "must be a positive integer"); + RETURN_THROWS(); + } + out = zend_string_alloc((size_t) out_len, 0); + memcpy(&state_tmp, state, sizeof state_tmp); + if (crypto_xof_shake128_squeeze(&state_tmp, (unsigned char *) ZSTR_VAL(out), (size_t) out_len) != 0) { + zend_string_efree(out); + sodium_memzero(&state_tmp, sizeof state_tmp); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + memcpy(state, &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + ZSTR_VAL(out)[out_len] = 0; + RETURN_NEW_STR(out); +} + +PHP_FUNCTION(sodium_crypto_xof_shake256) +{ + zend_string *out; + unsigned char *msg; + zend_long out_len; + size_t msg_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls", + &out_len, &msg, &msg_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (out_len <= 0 || out_len > ZSTR_MAX_LEN) { + zend_argument_error(sodium_exception_ce, 1, "must be a positive integer"); + RETURN_THROWS(); + } + out = zend_string_alloc((size_t) out_len, 0); + if (crypto_xof_shake256((unsigned char *) ZSTR_VAL(out), (size_t) out_len, + msg, (unsigned long long) msg_len) != 0) { + zend_string_efree(out); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + ZSTR_VAL(out)[out_len] = 0; + RETURN_NEW_STR(out); +} + +PHP_FUNCTION(sodium_crypto_xof_shake256_init) +{ + crypto_xof_shake256_state state_tmp; + zend_string *state; + zend_long domain = -1; + bool domain_is_null = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(domain, domain_is_null) + ZEND_PARSE_PARAMETERS_END(); + + memset(&state_tmp, 0, sizeof state_tmp); + if (domain_is_null) { + if (crypto_xof_shake256_init(&state_tmp) != 0) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + } else { + if (domain < 0x01 || domain > 0x7f) { + zend_argument_error(sodium_exception_ce, 1, "must be between 0x01 and 0x7f"); + RETURN_THROWS(); + } + if (crypto_xof_shake256_init_with_domain(&state_tmp, (unsigned char) domain) != 0) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + } + state = zend_string_alloc(sizeof state_tmp, 0); + memcpy(ZSTR_VAL(state), &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + ZSTR_VAL(state)[sizeof state_tmp] = 0; + RETURN_STR(state); +} + +PHP_FUNCTION(sodium_crypto_xof_shake256_update) +{ + crypto_xof_shake256_state state_tmp; + zval *state_zv; + unsigned char *msg; + unsigned char *state; + size_t msg_len; + size_t state_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zs", + &state_zv, &msg, &msg_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + ZVAL_DEREF(state_zv); + if (Z_TYPE_P(state_zv) != IS_STRING) { + zend_argument_error(sodium_exception_ce, 1, "must be a reference to a state"); + RETURN_THROWS(); + } + sodium_separate_string(state_zv); + state = (unsigned char *) Z_STRVAL(*state_zv); + state_len = Z_STRLEN(*state_zv); + if (state_len != sizeof (crypto_xof_shake256_state)) { + zend_argument_error(sodium_exception_ce, 1, "must have a correct state length"); + RETURN_THROWS(); + } + memcpy(&state_tmp, state, sizeof state_tmp); + if (crypto_xof_shake256_update(&state_tmp, msg, (unsigned long long) msg_len) != 0) { + sodium_memzero(&state_tmp, sizeof state_tmp); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + memcpy(state, &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + RETURN_TRUE; +} + +PHP_FUNCTION(sodium_crypto_xof_shake256_squeeze) +{ + crypto_xof_shake256_state state_tmp; + zval *state_zv; + zend_string *out; + unsigned char *state; + zend_long out_len; + size_t state_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zl", + &state_zv, &out_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + ZVAL_DEREF(state_zv); + if (Z_TYPE_P(state_zv) != IS_STRING) { + zend_argument_error(sodium_exception_ce, 1, "must be a reference to a state"); + RETURN_THROWS(); + } + sodium_separate_string(state_zv); + state = (unsigned char *) Z_STRVAL(*state_zv); + state_len = Z_STRLEN(*state_zv); + if (state_len != sizeof (crypto_xof_shake256_state)) { + zend_argument_error(sodium_exception_ce, 1, "must have a correct state length"); + RETURN_THROWS(); + } + if (out_len <= 0 || out_len > ZSTR_MAX_LEN) { + zend_argument_error(sodium_exception_ce, 2, "must be a positive integer"); + RETURN_THROWS(); + } + out = zend_string_alloc((size_t) out_len, 0); + memcpy(&state_tmp, state, sizeof state_tmp); + if (crypto_xof_shake256_squeeze(&state_tmp, (unsigned char *) ZSTR_VAL(out), (size_t) out_len) != 0) { + zend_string_efree(out); + sodium_memzero(&state_tmp, sizeof state_tmp); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + memcpy(state, &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + ZSTR_VAL(out)[out_len] = 0; + RETURN_NEW_STR(out); +} + +PHP_FUNCTION(sodium_crypto_xof_turboshake128) +{ + zend_string *out; + unsigned char *msg; + zend_long out_len; + size_t msg_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls", + &out_len, &msg, &msg_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (out_len <= 0 || out_len > ZSTR_MAX_LEN) { + zend_argument_error(sodium_exception_ce, 1, "must be a positive integer"); + RETURN_THROWS(); + } + out = zend_string_alloc((size_t) out_len, 0); + if (crypto_xof_turboshake128((unsigned char *) ZSTR_VAL(out), (size_t) out_len, + msg, (unsigned long long) msg_len) != 0) { + zend_string_efree(out); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + ZSTR_VAL(out)[out_len] = 0; + RETURN_NEW_STR(out); +} + +PHP_FUNCTION(sodium_crypto_xof_turboshake128_init) +{ + crypto_xof_turboshake128_state state_tmp; + zend_string *state; + zend_long domain = -1; + bool domain_is_null = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(domain, domain_is_null) + ZEND_PARSE_PARAMETERS_END(); + + memset(&state_tmp, 0, sizeof state_tmp); + if (domain_is_null) { + if (crypto_xof_turboshake128_init(&state_tmp) != 0) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + } else { + if (domain < 0x01 || domain > 0x7f) { + zend_argument_error(sodium_exception_ce, 1, "must be between 0x01 and 0x7f"); + RETURN_THROWS(); + } + if (crypto_xof_turboshake128_init_with_domain(&state_tmp, (unsigned char) domain) != 0) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + } + state = zend_string_alloc(sizeof state_tmp, 0); + memcpy(ZSTR_VAL(state), &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + ZSTR_VAL(state)[sizeof state_tmp] = 0; + RETURN_STR(state); +} + +PHP_FUNCTION(sodium_crypto_xof_turboshake128_update) +{ + crypto_xof_turboshake128_state state_tmp; + zval *state_zv; + unsigned char *msg; + unsigned char *state; + size_t msg_len; + size_t state_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zs", + &state_zv, &msg, &msg_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + ZVAL_DEREF(state_zv); + if (Z_TYPE_P(state_zv) != IS_STRING) { + zend_argument_error(sodium_exception_ce, 1, "must be a reference to a state"); + RETURN_THROWS(); + } + sodium_separate_string(state_zv); + state = (unsigned char *) Z_STRVAL(*state_zv); + state_len = Z_STRLEN(*state_zv); + if (state_len != sizeof (crypto_xof_turboshake128_state)) { + zend_argument_error(sodium_exception_ce, 1, "must have a correct state length"); + RETURN_THROWS(); + } + memcpy(&state_tmp, state, sizeof state_tmp); + if (crypto_xof_turboshake128_update(&state_tmp, msg, (unsigned long long) msg_len) != 0) { + sodium_memzero(&state_tmp, sizeof state_tmp); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + memcpy(state, &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + RETURN_TRUE; +} + +PHP_FUNCTION(sodium_crypto_xof_turboshake128_squeeze) +{ + crypto_xof_turboshake128_state state_tmp; + zval *state_zv; + zend_string *out; + unsigned char *state; + zend_long out_len; + size_t state_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zl", + &state_zv, &out_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + ZVAL_DEREF(state_zv); + if (Z_TYPE_P(state_zv) != IS_STRING) { + zend_argument_error(sodium_exception_ce, 1, "must be a reference to a state"); + RETURN_THROWS(); + } + sodium_separate_string(state_zv); + state = (unsigned char *) Z_STRVAL(*state_zv); + state_len = Z_STRLEN(*state_zv); + if (state_len != sizeof (crypto_xof_turboshake128_state)) { + zend_argument_error(sodium_exception_ce, 1, "must have a correct state length"); + RETURN_THROWS(); + } + if (out_len <= 0 || out_len > ZSTR_MAX_LEN) { + zend_argument_error(sodium_exception_ce, 2, "must be a positive integer"); + RETURN_THROWS(); + } + out = zend_string_alloc((size_t) out_len, 0); + memcpy(&state_tmp, state, sizeof state_tmp); + if (crypto_xof_turboshake128_squeeze(&state_tmp, (unsigned char *) ZSTR_VAL(out), (size_t) out_len) != 0) { + zend_string_efree(out); + sodium_memzero(&state_tmp, sizeof state_tmp); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + memcpy(state, &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + ZSTR_VAL(out)[out_len] = 0; + RETURN_NEW_STR(out); +} + +PHP_FUNCTION(sodium_crypto_xof_turboshake256) +{ + zend_string *out; + unsigned char *msg; + zend_long out_len; + size_t msg_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls", + &out_len, &msg, &msg_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (out_len <= 0 || out_len > ZSTR_MAX_LEN) { + zend_argument_error(sodium_exception_ce, 1, "must be a positive integer"); + RETURN_THROWS(); + } + out = zend_string_alloc((size_t) out_len, 0); + if (crypto_xof_turboshake256((unsigned char *) ZSTR_VAL(out), (size_t) out_len, + msg, (unsigned long long) msg_len) != 0) { + zend_string_efree(out); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + ZSTR_VAL(out)[out_len] = 0; + RETURN_NEW_STR(out); +} + +PHP_FUNCTION(sodium_crypto_xof_turboshake256_init) +{ + crypto_xof_turboshake256_state state_tmp; + zend_string *state; + zend_long domain = -1; + bool domain_is_null = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(domain, domain_is_null) + ZEND_PARSE_PARAMETERS_END(); + + memset(&state_tmp, 0, sizeof state_tmp); + if (domain_is_null) { + if (crypto_xof_turboshake256_init(&state_tmp) != 0) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + } else { + if (domain < 0x01 || domain > 0x7f) { + zend_argument_error(sodium_exception_ce, 1, "must be between 0x01 and 0x7f"); + RETURN_THROWS(); + } + if (crypto_xof_turboshake256_init_with_domain(&state_tmp, (unsigned char) domain) != 0) { + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + } + state = zend_string_alloc(sizeof state_tmp, 0); + memcpy(ZSTR_VAL(state), &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + ZSTR_VAL(state)[sizeof state_tmp] = 0; + RETURN_STR(state); +} + +PHP_FUNCTION(sodium_crypto_xof_turboshake256_update) +{ + crypto_xof_turboshake256_state state_tmp; + zval *state_zv; + unsigned char *msg; + unsigned char *state; + size_t msg_len; + size_t state_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zs", + &state_zv, &msg, &msg_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + ZVAL_DEREF(state_zv); + if (Z_TYPE_P(state_zv) != IS_STRING) { + zend_argument_error(sodium_exception_ce, 1, "must be a reference to a state"); + RETURN_THROWS(); + } + sodium_separate_string(state_zv); + state = (unsigned char *) Z_STRVAL(*state_zv); + state_len = Z_STRLEN(*state_zv); + if (state_len != sizeof (crypto_xof_turboshake256_state)) { + zend_argument_error(sodium_exception_ce, 1, "must have a correct state length"); + RETURN_THROWS(); + } + memcpy(&state_tmp, state, sizeof state_tmp); + if (crypto_xof_turboshake256_update(&state_tmp, msg, (unsigned long long) msg_len) != 0) { + sodium_memzero(&state_tmp, sizeof state_tmp); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + memcpy(state, &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + RETURN_TRUE; +} + +PHP_FUNCTION(sodium_crypto_xof_turboshake256_squeeze) +{ + crypto_xof_turboshake256_state state_tmp; + zval *state_zv; + zend_string *out; + unsigned char *state; + zend_long out_len; + size_t state_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zl", + &state_zv, &out_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + ZVAL_DEREF(state_zv); + if (Z_TYPE_P(state_zv) != IS_STRING) { + zend_argument_error(sodium_exception_ce, 1, "must be a reference to a state"); + RETURN_THROWS(); + } + sodium_separate_string(state_zv); + state = (unsigned char *) Z_STRVAL(*state_zv); + state_len = Z_STRLEN(*state_zv); + if (state_len != sizeof (crypto_xof_turboshake256_state)) { + zend_argument_error(sodium_exception_ce, 1, "must have a correct state length"); + RETURN_THROWS(); + } + if (out_len <= 0 || out_len > ZSTR_MAX_LEN) { + zend_argument_error(sodium_exception_ce, 2, "must be a positive integer"); + RETURN_THROWS(); + } + out = zend_string_alloc((size_t) out_len, 0); + memcpy(&state_tmp, state, sizeof state_tmp); + if (crypto_xof_turboshake256_squeeze(&state_tmp, (unsigned char *) ZSTR_VAL(out), (size_t) out_len) != 0) { + zend_string_efree(out); + sodium_memzero(&state_tmp, sizeof state_tmp); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + memcpy(state, &state_tmp, sizeof state_tmp); + sodium_memzero(&state_tmp, sizeof state_tmp); + ZSTR_VAL(out)[out_len] = 0; + RETURN_NEW_STR(out); +} +#endif diff --git a/ext/sodium/libsodium.stub.php b/ext/sodium/libsodium.stub.php index 4bf6fade9136f..9fda5f4ddd73e 100644 --- a/ext/sodium/libsodium.stub.php +++ b/ext/sodium/libsodium.stub.php @@ -801,4 +801,170 @@ function sodium_base642bin(#[\SensitiveParameter] string $string, int $id, strin */ function sodium_crypto_scalarmult_base(#[\SensitiveParameter] string $secret_key): string {} +#ifdef crypto_ipcrypt_KEYBYTES +/** + * @var int + * @cvalue crypto_ipcrypt_BYTES + */ +const SODIUM_CRYPTO_IPCRYPT_BYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_KEYBYTES + */ +const SODIUM_CRYPTO_IPCRYPT_KEYBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_ND_KEYBYTES + */ +const SODIUM_CRYPTO_IPCRYPT_ND_KEYBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_ND_TWEAKBYTES + */ +const SODIUM_CRYPTO_IPCRYPT_ND_TWEAKBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_ND_INPUTBYTES + */ +const SODIUM_CRYPTO_IPCRYPT_ND_INPUTBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_ND_OUTPUTBYTES + */ +const SODIUM_CRYPTO_IPCRYPT_ND_OUTPUTBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_NDX_KEYBYTES + */ +const SODIUM_CRYPTO_IPCRYPT_NDX_KEYBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_NDX_TWEAKBYTES + */ +const SODIUM_CRYPTO_IPCRYPT_NDX_TWEAKBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_NDX_INPUTBYTES + */ +const SODIUM_CRYPTO_IPCRYPT_NDX_INPUTBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_NDX_OUTPUTBYTES + */ +const SODIUM_CRYPTO_IPCRYPT_NDX_OUTPUTBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_PFX_KEYBYTES + */ +const SODIUM_CRYPTO_IPCRYPT_PFX_KEYBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_ipcrypt_PFX_BYTES + */ +const SODIUM_CRYPTO_IPCRYPT_PFX_BYTES = UNKNOWN; + +function sodium_crypto_ipcrypt_keygen(): string {} + +function sodium_crypto_ipcrypt_encrypt(string $ip, #[\SensitiveParameter] string $key): string {} + +function sodium_crypto_ipcrypt_decrypt(string $encrypted_ip, #[\SensitiveParameter] string $key): string {} + +function sodium_crypto_ipcrypt_nd_keygen(): string {} + +function sodium_crypto_ipcrypt_nd_encrypt(string $ip, #[\SensitiveParameter] string $key): string {} + +function sodium_crypto_ipcrypt_nd_decrypt(string $ciphertext_hex, #[\SensitiveParameter] string $key): string {} + +function sodium_crypto_ipcrypt_ndx_keygen(): string {} + +function sodium_crypto_ipcrypt_ndx_encrypt(string $ip, #[\SensitiveParameter] string $key): string {} + +function sodium_crypto_ipcrypt_ndx_decrypt(string $ciphertext_hex, #[\SensitiveParameter] string $key): string {} + +function sodium_crypto_ipcrypt_pfx_keygen(): string {} + +function sodium_crypto_ipcrypt_pfx_encrypt(string $ip, #[\SensitiveParameter] string $key): string {} + +function sodium_crypto_ipcrypt_pfx_decrypt(string $encrypted_ip, #[\SensitiveParameter] string $key): string {} + +function sodium_bin2ip(string $bin): string {} + +function sodium_ip2bin(string $ip): string {} +#endif + +#ifdef crypto_xof_shake128_STATEBYTES +/** + * @var int + * @cvalue crypto_xof_shake128_BLOCKBYTES + */ +const SODIUM_CRYPTO_XOF_SHAKE128_BLOCKBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_xof_shake128_STATEBYTES + */ +const SODIUM_CRYPTO_XOF_SHAKE128_STATEBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_xof_shake256_BLOCKBYTES + */ +const SODIUM_CRYPTO_XOF_SHAKE256_BLOCKBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_xof_shake256_STATEBYTES + */ +const SODIUM_CRYPTO_XOF_SHAKE256_STATEBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_xof_turboshake128_BLOCKBYTES + */ +const SODIUM_CRYPTO_XOF_TURBOSHAKE128_BLOCKBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_xof_turboshake128_STATEBYTES + */ +const SODIUM_CRYPTO_XOF_TURBOSHAKE128_STATEBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_xof_turboshake256_BLOCKBYTES + */ +const SODIUM_CRYPTO_XOF_TURBOSHAKE256_BLOCKBYTES = UNKNOWN; +/** + * @var int + * @cvalue crypto_xof_turboshake256_STATEBYTES + */ +const SODIUM_CRYPTO_XOF_TURBOSHAKE256_STATEBYTES = UNKNOWN; + +function sodium_crypto_xof_shake128(int $length, string $message): string {} + +function sodium_crypto_xof_shake128_init(?int $domain = null): string {} + +function sodium_crypto_xof_shake128_update(string &$state, string $message): true {} + +function sodium_crypto_xof_shake128_squeeze(string &$state, int $length): string {} + +function sodium_crypto_xof_shake256(int $length, string $message): string {} + +function sodium_crypto_xof_shake256_init(?int $domain = null): string {} + +function sodium_crypto_xof_shake256_update(string &$state, string $message): true {} + +function sodium_crypto_xof_shake256_squeeze(string &$state, int $length): string {} + +function sodium_crypto_xof_turboshake128(int $length, string $message): string {} + +function sodium_crypto_xof_turboshake128_init(?int $domain = null): string {} + +function sodium_crypto_xof_turboshake128_update(string &$state, string $message): true {} + +function sodium_crypto_xof_turboshake128_squeeze(string &$state, int $length): string {} + +function sodium_crypto_xof_turboshake256(int $length, string $message): string {} + +function sodium_crypto_xof_turboshake256_init(?int $domain = null): string {} + +function sodium_crypto_xof_turboshake256_update(string &$state, string $message): true {} + +function sodium_crypto_xof_turboshake256_squeeze(string &$state, int $length): string {} +#endif + class SodiumException extends Exception {} diff --git a/ext/sodium/libsodium_arginfo.h b/ext/sodium/libsodium_arginfo.h index 0af7528eec72a..1b291e9a23300 100644 --- a/ext/sodium/libsodium_arginfo.h +++ b/ext/sodium/libsodium_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit libsodium.stub.php instead. - * Stub hash: 89cbb449ee6146dc8d50ba4bb1e76f83444a2db2 */ + * Stub hash: 7b337a297ae333dd8d0c232979b770332c9e7eb6 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_aead_aes256gcm_is_available, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -483,6 +483,95 @@ ZEND_END_ARG_INFO() #define arginfo_sodium_crypto_scalarmult_base arginfo_sodium_crypto_box_publickey_from_secretkey +#if defined(crypto_ipcrypt_KEYBYTES) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_ipcrypt_keygen, 0, 0, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_ipcrypt_encrypt, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, ip, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_ipcrypt_decrypt, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, encrypted_ip, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_sodium_crypto_ipcrypt_nd_keygen arginfo_sodium_crypto_ipcrypt_keygen + +#define arginfo_sodium_crypto_ipcrypt_nd_encrypt arginfo_sodium_crypto_ipcrypt_encrypt + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_ipcrypt_nd_decrypt, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, ciphertext_hex, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_sodium_crypto_ipcrypt_ndx_keygen arginfo_sodium_crypto_ipcrypt_keygen + +#define arginfo_sodium_crypto_ipcrypt_ndx_encrypt arginfo_sodium_crypto_ipcrypt_encrypt + +#define arginfo_sodium_crypto_ipcrypt_ndx_decrypt arginfo_sodium_crypto_ipcrypt_nd_decrypt + +#define arginfo_sodium_crypto_ipcrypt_pfx_keygen arginfo_sodium_crypto_ipcrypt_keygen + +#define arginfo_sodium_crypto_ipcrypt_pfx_encrypt arginfo_sodium_crypto_ipcrypt_encrypt + +#define arginfo_sodium_crypto_ipcrypt_pfx_decrypt arginfo_sodium_crypto_ipcrypt_decrypt + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_bin2ip, 0, 1, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, bin, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_ip2bin, 0, 1, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, ip, IS_STRING, 0) +ZEND_END_ARG_INFO() +#endif + +#if defined(crypto_xof_shake128_STATEBYTES) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_xof_shake128, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, message, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_xof_shake128_init, 0, 0, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, domain, IS_LONG, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_xof_shake128_update, 0, 2, IS_TRUE, 0) + ZEND_ARG_TYPE_INFO(1, state, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, message, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_xof_shake128_squeeze, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(1, state, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0) +ZEND_END_ARG_INFO() + +#define arginfo_sodium_crypto_xof_shake256 arginfo_sodium_crypto_xof_shake128 + +#define arginfo_sodium_crypto_xof_shake256_init arginfo_sodium_crypto_xof_shake128_init + +#define arginfo_sodium_crypto_xof_shake256_update arginfo_sodium_crypto_xof_shake128_update + +#define arginfo_sodium_crypto_xof_shake256_squeeze arginfo_sodium_crypto_xof_shake128_squeeze + +#define arginfo_sodium_crypto_xof_turboshake128 arginfo_sodium_crypto_xof_shake128 + +#define arginfo_sodium_crypto_xof_turboshake128_init arginfo_sodium_crypto_xof_shake128_init + +#define arginfo_sodium_crypto_xof_turboshake128_update arginfo_sodium_crypto_xof_shake128_update + +#define arginfo_sodium_crypto_xof_turboshake128_squeeze arginfo_sodium_crypto_xof_shake128_squeeze + +#define arginfo_sodium_crypto_xof_turboshake256 arginfo_sodium_crypto_xof_shake128 + +#define arginfo_sodium_crypto_xof_turboshake256_init arginfo_sodium_crypto_xof_shake128_init + +#define arginfo_sodium_crypto_xof_turboshake256_update arginfo_sodium_crypto_xof_shake128_update + +#define arginfo_sodium_crypto_xof_turboshake256_squeeze arginfo_sodium_crypto_xof_shake128_squeeze +#endif + ZEND_FUNCTION(sodium_crypto_aead_aes256gcm_is_available); #if defined(HAVE_AESGCM) ZEND_FUNCTION(sodium_crypto_aead_aes256gcm_decrypt); @@ -616,6 +705,40 @@ ZEND_FUNCTION(sodium_hex2bin); ZEND_FUNCTION(sodium_bin2base64); ZEND_FUNCTION(sodium_base642bin); #endif +#if defined(crypto_ipcrypt_KEYBYTES) +ZEND_FUNCTION(sodium_crypto_ipcrypt_keygen); +ZEND_FUNCTION(sodium_crypto_ipcrypt_encrypt); +ZEND_FUNCTION(sodium_crypto_ipcrypt_decrypt); +ZEND_FUNCTION(sodium_crypto_ipcrypt_nd_keygen); +ZEND_FUNCTION(sodium_crypto_ipcrypt_nd_encrypt); +ZEND_FUNCTION(sodium_crypto_ipcrypt_nd_decrypt); +ZEND_FUNCTION(sodium_crypto_ipcrypt_ndx_keygen); +ZEND_FUNCTION(sodium_crypto_ipcrypt_ndx_encrypt); +ZEND_FUNCTION(sodium_crypto_ipcrypt_ndx_decrypt); +ZEND_FUNCTION(sodium_crypto_ipcrypt_pfx_keygen); +ZEND_FUNCTION(sodium_crypto_ipcrypt_pfx_encrypt); +ZEND_FUNCTION(sodium_crypto_ipcrypt_pfx_decrypt); +ZEND_FUNCTION(sodium_bin2ip); +ZEND_FUNCTION(sodium_ip2bin); +#endif +#if defined(crypto_xof_shake128_STATEBYTES) +ZEND_FUNCTION(sodium_crypto_xof_shake128); +ZEND_FUNCTION(sodium_crypto_xof_shake128_init); +ZEND_FUNCTION(sodium_crypto_xof_shake128_update); +ZEND_FUNCTION(sodium_crypto_xof_shake128_squeeze); +ZEND_FUNCTION(sodium_crypto_xof_shake256); +ZEND_FUNCTION(sodium_crypto_xof_shake256_init); +ZEND_FUNCTION(sodium_crypto_xof_shake256_update); +ZEND_FUNCTION(sodium_crypto_xof_shake256_squeeze); +ZEND_FUNCTION(sodium_crypto_xof_turboshake128); +ZEND_FUNCTION(sodium_crypto_xof_turboshake128_init); +ZEND_FUNCTION(sodium_crypto_xof_turboshake128_update); +ZEND_FUNCTION(sodium_crypto_xof_turboshake128_squeeze); +ZEND_FUNCTION(sodium_crypto_xof_turboshake256); +ZEND_FUNCTION(sodium_crypto_xof_turboshake256_init); +ZEND_FUNCTION(sodium_crypto_xof_turboshake256_update); +ZEND_FUNCTION(sodium_crypto_xof_turboshake256_squeeze); +#endif static const zend_function_entry ext_functions[] = { ZEND_FE(sodium_crypto_aead_aes256gcm_is_available, arginfo_sodium_crypto_aead_aes256gcm_is_available) @@ -752,6 +875,40 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(sodium_base642bin, arginfo_sodium_base642bin) #endif ZEND_RAW_FENTRY("sodium_crypto_scalarmult_base", zif_sodium_crypto_box_publickey_from_secretkey, arginfo_sodium_crypto_scalarmult_base, 0, NULL, NULL) +#if defined(crypto_ipcrypt_KEYBYTES) + ZEND_FE(sodium_crypto_ipcrypt_keygen, arginfo_sodium_crypto_ipcrypt_keygen) + ZEND_FE(sodium_crypto_ipcrypt_encrypt, arginfo_sodium_crypto_ipcrypt_encrypt) + ZEND_FE(sodium_crypto_ipcrypt_decrypt, arginfo_sodium_crypto_ipcrypt_decrypt) + ZEND_FE(sodium_crypto_ipcrypt_nd_keygen, arginfo_sodium_crypto_ipcrypt_nd_keygen) + ZEND_FE(sodium_crypto_ipcrypt_nd_encrypt, arginfo_sodium_crypto_ipcrypt_nd_encrypt) + ZEND_FE(sodium_crypto_ipcrypt_nd_decrypt, arginfo_sodium_crypto_ipcrypt_nd_decrypt) + ZEND_FE(sodium_crypto_ipcrypt_ndx_keygen, arginfo_sodium_crypto_ipcrypt_ndx_keygen) + ZEND_FE(sodium_crypto_ipcrypt_ndx_encrypt, arginfo_sodium_crypto_ipcrypt_ndx_encrypt) + ZEND_FE(sodium_crypto_ipcrypt_ndx_decrypt, arginfo_sodium_crypto_ipcrypt_ndx_decrypt) + ZEND_FE(sodium_crypto_ipcrypt_pfx_keygen, arginfo_sodium_crypto_ipcrypt_pfx_keygen) + ZEND_FE(sodium_crypto_ipcrypt_pfx_encrypt, arginfo_sodium_crypto_ipcrypt_pfx_encrypt) + ZEND_FE(sodium_crypto_ipcrypt_pfx_decrypt, arginfo_sodium_crypto_ipcrypt_pfx_decrypt) + ZEND_FE(sodium_bin2ip, arginfo_sodium_bin2ip) + ZEND_FE(sodium_ip2bin, arginfo_sodium_ip2bin) +#endif +#if defined(crypto_xof_shake128_STATEBYTES) + ZEND_FE(sodium_crypto_xof_shake128, arginfo_sodium_crypto_xof_shake128) + ZEND_FE(sodium_crypto_xof_shake128_init, arginfo_sodium_crypto_xof_shake128_init) + ZEND_FE(sodium_crypto_xof_shake128_update, arginfo_sodium_crypto_xof_shake128_update) + ZEND_FE(sodium_crypto_xof_shake128_squeeze, arginfo_sodium_crypto_xof_shake128_squeeze) + ZEND_FE(sodium_crypto_xof_shake256, arginfo_sodium_crypto_xof_shake256) + ZEND_FE(sodium_crypto_xof_shake256_init, arginfo_sodium_crypto_xof_shake256_init) + ZEND_FE(sodium_crypto_xof_shake256_update, arginfo_sodium_crypto_xof_shake256_update) + ZEND_FE(sodium_crypto_xof_shake256_squeeze, arginfo_sodium_crypto_xof_shake256_squeeze) + ZEND_FE(sodium_crypto_xof_turboshake128, arginfo_sodium_crypto_xof_turboshake128) + ZEND_FE(sodium_crypto_xof_turboshake128_init, arginfo_sodium_crypto_xof_turboshake128_init) + ZEND_FE(sodium_crypto_xof_turboshake128_update, arginfo_sodium_crypto_xof_turboshake128_update) + ZEND_FE(sodium_crypto_xof_turboshake128_squeeze, arginfo_sodium_crypto_xof_turboshake128_squeeze) + ZEND_FE(sodium_crypto_xof_turboshake256, arginfo_sodium_crypto_xof_turboshake256) + ZEND_FE(sodium_crypto_xof_turboshake256_init, arginfo_sodium_crypto_xof_turboshake256_init) + ZEND_FE(sodium_crypto_xof_turboshake256_update, arginfo_sodium_crypto_xof_turboshake256_update) + ZEND_FE(sodium_crypto_xof_turboshake256_squeeze, arginfo_sodium_crypto_xof_turboshake256_squeeze) +#endif ZEND_FE_END }; @@ -883,6 +1040,30 @@ static void register_libsodium_symbols(int module_number) REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_CORE_RISTRETTO255_SCALARBYTES", crypto_core_ristretto255_SCALARBYTES, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_CORE_RISTRETTO255_NONREDUCEDSCALARBYTES", crypto_core_ristretto255_NONREDUCEDSCALARBYTES, CONST_PERSISTENT); #endif +#if defined(crypto_ipcrypt_KEYBYTES) + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_BYTES", crypto_ipcrypt_BYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_KEYBYTES", crypto_ipcrypt_KEYBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_ND_KEYBYTES", crypto_ipcrypt_ND_KEYBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_ND_TWEAKBYTES", crypto_ipcrypt_ND_TWEAKBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_ND_INPUTBYTES", crypto_ipcrypt_ND_INPUTBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_ND_OUTPUTBYTES", crypto_ipcrypt_ND_OUTPUTBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_NDX_KEYBYTES", crypto_ipcrypt_NDX_KEYBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_NDX_TWEAKBYTES", crypto_ipcrypt_NDX_TWEAKBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_NDX_INPUTBYTES", crypto_ipcrypt_NDX_INPUTBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_NDX_OUTPUTBYTES", crypto_ipcrypt_NDX_OUTPUTBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_PFX_KEYBYTES", crypto_ipcrypt_PFX_KEYBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_IPCRYPT_PFX_BYTES", crypto_ipcrypt_PFX_BYTES, CONST_PERSISTENT); +#endif +#if defined(crypto_xof_shake128_STATEBYTES) + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_XOF_SHAKE128_BLOCKBYTES", crypto_xof_shake128_BLOCKBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_XOF_SHAKE128_STATEBYTES", crypto_xof_shake128_STATEBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_XOF_SHAKE256_BLOCKBYTES", crypto_xof_shake256_BLOCKBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_XOF_SHAKE256_STATEBYTES", crypto_xof_shake256_STATEBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_XOF_TURBOSHAKE128_BLOCKBYTES", crypto_xof_turboshake128_BLOCKBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_XOF_TURBOSHAKE128_STATEBYTES", crypto_xof_turboshake128_STATEBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_XOF_TURBOSHAKE256_BLOCKBYTES", crypto_xof_turboshake256_BLOCKBYTES, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_XOF_TURBOSHAKE256_STATEBYTES", crypto_xof_turboshake256_STATEBYTES, CONST_PERSISTENT); +#endif #if defined(HAVE_AESGCM) @@ -1060,6 +1241,24 @@ static void register_libsodium_symbols(int module_number) #endif zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "sodium_crypto_scalarmult_base", sizeof("sodium_crypto_scalarmult_base") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); +#if defined(crypto_ipcrypt_KEYBYTES) + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "sodium_crypto_ipcrypt_encrypt", sizeof("sodium_crypto_ipcrypt_encrypt") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "sodium_crypto_ipcrypt_decrypt", sizeof("sodium_crypto_ipcrypt_decrypt") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "sodium_crypto_ipcrypt_nd_encrypt", sizeof("sodium_crypto_ipcrypt_nd_encrypt") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "sodium_crypto_ipcrypt_nd_decrypt", sizeof("sodium_crypto_ipcrypt_nd_decrypt") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "sodium_crypto_ipcrypt_ndx_encrypt", sizeof("sodium_crypto_ipcrypt_ndx_encrypt") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "sodium_crypto_ipcrypt_ndx_decrypt", sizeof("sodium_crypto_ipcrypt_ndx_decrypt") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "sodium_crypto_ipcrypt_pfx_encrypt", sizeof("sodium_crypto_ipcrypt_pfx_encrypt") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "sodium_crypto_ipcrypt_pfx_decrypt", sizeof("sodium_crypto_ipcrypt_pfx_decrypt") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); +#endif } static zend_class_entry *register_class_SodiumException(zend_class_entry *class_entry_Exception) diff --git a/ext/sodium/tests/crypto_ipcrypt.phpt b/ext/sodium/tests/crypto_ipcrypt.phpt new file mode 100644 index 0000000000000..f398d21c70fc1 --- /dev/null +++ b/ext/sodium/tests/crypto_ipcrypt.phpt @@ -0,0 +1,63 @@ +--TEST-- +Check for libsodium ipcrypt +--EXTENSIONS-- +sodium +--SKIPIF-- += 1.0.21)"; +?> +--FILE-- +getMessage() . "\n"; +} + +/* Error: invalid IP */ +try { + sodium_crypto_ipcrypt_encrypt("not_an_ip", $key); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* Error: wrong key length for decrypt */ +try { + sodium_crypto_ipcrypt_decrypt("::1", "short"); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} +?> +--EXPECT-- +string(11) "192.168.1.1" +bool(true) +bool(true) +string(3) "::1" +bool(true) +sodium_crypto_ipcrypt_encrypt(): Argument #2 ($key) must be SODIUM_CRYPTO_IPCRYPT_KEYBYTES bytes long +sodium_crypto_ipcrypt_encrypt(): Argument #1 ($ip) must be a valid IP address +sodium_crypto_ipcrypt_decrypt(): Argument #2 ($key) must be SODIUM_CRYPTO_IPCRYPT_KEYBYTES bytes long diff --git a/ext/sodium/tests/crypto_ipcrypt_nd.phpt b/ext/sodium/tests/crypto_ipcrypt_nd.phpt new file mode 100644 index 0000000000000..fed36bbda4447 --- /dev/null +++ b/ext/sodium/tests/crypto_ipcrypt_nd.phpt @@ -0,0 +1,98 @@ +--TEST-- +Check for libsodium ipcrypt non-deterministic (nd) and extended (ndx) +--EXTENSIONS-- +sodium +--SKIPIF-- += 1.0.21)"; +?> +--FILE-- +getMessage() . "\n"; +} + +/* ND error: invalid IP */ +try { + sodium_crypto_ipcrypt_nd_encrypt("bad", $nd_key); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* ND error: bad ciphertext hex length */ +try { + sodium_crypto_ipcrypt_nd_decrypt("tooshort", $nd_key); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* NDX: keygen, encrypt, decrypt roundtrip */ +$ndx_key = sodium_crypto_ipcrypt_ndx_keygen(); +var_dump(strlen($ndx_key) === SODIUM_CRYPTO_IPCRYPT_NDX_KEYBYTES); + +$ct_ndx = sodium_crypto_ipcrypt_ndx_encrypt("10.0.0.1", $ndx_key); +var_dump(strlen($ct_ndx) === SODIUM_CRYPTO_IPCRYPT_NDX_OUTPUTBYTES * 2); +$pt_ndx = sodium_crypto_ipcrypt_ndx_decrypt($ct_ndx, $ndx_key); +var_dump($pt_ndx); + +/* NDX is non-deterministic */ +$ct_ndx2 = sodium_crypto_ipcrypt_ndx_encrypt("10.0.0.1", $ndx_key); +var_dump($ct_ndx !== $ct_ndx2); + +/* NDX: IPv6 */ +$ct_ndx6 = sodium_crypto_ipcrypt_ndx_encrypt("fe80::1", $ndx_key); +var_dump(sodium_crypto_ipcrypt_ndx_decrypt($ct_ndx6, $ndx_key)); + +/* NDX error: wrong key length */ +try { + sodium_crypto_ipcrypt_ndx_encrypt("10.0.0.1", "short"); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* NDX error: bad ciphertext hex */ +try { + sodium_crypto_ipcrypt_ndx_decrypt("tooshort", $ndx_key); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} +?> +--EXPECT-- +bool(true) +bool(true) +string(11) "192.168.1.1" +bool(true) +string(11) "192.168.1.1" +string(3) "::1" +sodium_crypto_ipcrypt_nd_encrypt(): Argument #2 ($key) must be SODIUM_CRYPTO_IPCRYPT_ND_KEYBYTES bytes long +sodium_crypto_ipcrypt_nd_encrypt(): Argument #1 ($ip) must be a valid IP address +sodium_crypto_ipcrypt_nd_decrypt(): Argument #1 ($ciphertext_hex) must be a valid hex-encoded ciphertext +bool(true) +bool(true) +string(8) "10.0.0.1" +bool(true) +string(7) "fe80::1" +sodium_crypto_ipcrypt_ndx_encrypt(): Argument #2 ($key) must be SODIUM_CRYPTO_IPCRYPT_NDX_KEYBYTES bytes long +sodium_crypto_ipcrypt_ndx_decrypt(): Argument #1 ($ciphertext_hex) must be a valid hex-encoded ciphertext diff --git a/ext/sodium/tests/crypto_ipcrypt_pfx.phpt b/ext/sodium/tests/crypto_ipcrypt_pfx.phpt new file mode 100644 index 0000000000000..1ee51ef1f0e11 --- /dev/null +++ b/ext/sodium/tests/crypto_ipcrypt_pfx.phpt @@ -0,0 +1,65 @@ +--TEST-- +Check for libsodium ipcrypt prefix-preserving (pfx) +--EXTENSIONS-- +sodium +--SKIPIF-- += 1.0.21)"; +?> +--FILE-- +getMessage() . "\n"; +} + +/* PFX error: invalid IP */ +try { + sodium_crypto_ipcrypt_pfx_encrypt("not_an_ip", $key); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* PFX error: wrong key length for decrypt */ +try { + sodium_crypto_ipcrypt_pfx_decrypt("10.0.0.1", "short"); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} +?> +--EXPECT-- +string(8) "10.0.0.1" +bool(true) +bool(true) +string(3) "::1" +bool(true) +bool(true) +sodium_crypto_ipcrypt_pfx_encrypt(): Argument #2 ($key) must be SODIUM_CRYPTO_IPCRYPT_PFX_KEYBYTES bytes long +sodium_crypto_ipcrypt_pfx_encrypt(): Argument #1 ($ip) must be a valid IP address +sodium_crypto_ipcrypt_pfx_decrypt(): Argument #2 ($key) must be SODIUM_CRYPTO_IPCRYPT_PFX_KEYBYTES bytes long diff --git a/ext/sodium/tests/crypto_xof_shake128.phpt b/ext/sodium/tests/crypto_xof_shake128.phpt new file mode 100644 index 0000000000000..cb075b7ec2855 --- /dev/null +++ b/ext/sodium/tests/crypto_xof_shake128.phpt @@ -0,0 +1,104 @@ +--TEST-- +Check for libsodium XOF SHAKE128 +--EXTENSIONS-- +sodium +--SKIPIF-- += 1.0.21)"; +?> +--FILE-- +getMessage() . "\n"; +} + +/* Error: negative length */ +try { + sodium_crypto_xof_shake128(-1, "test"); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* Error: domain out of range (0x00) */ +try { + sodium_crypto_xof_shake128_init(0); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* Error: domain out of range (0x80) */ +try { + sodium_crypto_xof_shake128_init(0x80); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* Error: bad state */ +try { + $bad = "not_a_state"; + sodium_crypto_xof_shake128_update($bad, "test"); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} +?> +--EXPECT-- +string(64) "7f9c2ba4e88f827d616045507605853ed73b8093f6efbc88eb1a6eacfa66ef26" +string(64) "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8" +int(16) +int(64) +bool(true) +string(64) "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8" +string(64) "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8" +string(64) "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8" +string(64) "f3a3a89c329e644a7d2351744d9a28c953698b64102e912085ce1f6d79fa311e" +bool(true) +sodium_crypto_xof_shake128(): Argument #1 ($length) must be a positive integer +sodium_crypto_xof_shake128(): Argument #1 ($length) must be a positive integer +sodium_crypto_xof_shake128_init(): Argument #1 ($domain) must be between 0x01 and 0x7f +sodium_crypto_xof_shake128_init(): Argument #1 ($domain) must be between 0x01 and 0x7f +sodium_crypto_xof_shake128_update(): Argument #1 ($state) must have a correct state length diff --git a/ext/sodium/tests/crypto_xof_shake256.phpt b/ext/sodium/tests/crypto_xof_shake256.phpt new file mode 100644 index 0000000000000..3e69167133286 --- /dev/null +++ b/ext/sodium/tests/crypto_xof_shake256.phpt @@ -0,0 +1,80 @@ +--TEST-- +Check for libsodium XOF SHAKE256 +--EXTENSIONS-- +sodium +--SKIPIF-- += 1.0.21)"; +?> +--FILE-- +getMessage() . "\n"; +} + +/* Error: bad state */ +try { + $bad = "not_a_state"; + sodium_crypto_xof_shake256_update($bad, "test"); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* Error: squeeze bad length */ +try { + $state = sodium_crypto_xof_shake256_init(); + sodium_crypto_xof_shake256_update($state, "abc"); + sodium_crypto_xof_shake256_squeeze($state, 0); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} +?> +--EXPECT-- +string(64) "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f" +string(64) "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739" +int(16) +int(64) +bool(true) +string(64) "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739" +string(64) "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739" +string(64) "1259d63c872d50fee4500685419f489966971c0a77e2058fab0e48dfb12d24bc" +sodium_crypto_xof_shake256(): Argument #1 ($length) must be a positive integer +sodium_crypto_xof_shake256_update(): Argument #1 ($state) must have a correct state length +sodium_crypto_xof_shake256_squeeze(): Argument #2 ($length) must be a positive integer diff --git a/ext/sodium/tests/crypto_xof_turboshake.phpt b/ext/sodium/tests/crypto_xof_turboshake.phpt new file mode 100644 index 0000000000000..1f404140cc0ba --- /dev/null +++ b/ext/sodium/tests/crypto_xof_turboshake.phpt @@ -0,0 +1,112 @@ +--TEST-- +Check for libsodium XOF TurboSHAKE128 and TurboSHAKE256 +--EXTENSIONS-- +sodium +--SKIPIF-- += 1.0.21)"; +?> +--FILE-- +getMessage() . "\n"; +} + +/* TurboSHAKE256 error: bad state */ +try { + $bad = "not_a_state"; + sodium_crypto_xof_turboshake256_update($bad, "test"); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* TurboSHAKE128 error: domain out of range */ +try { + sodium_crypto_xof_turboshake128_init(0); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} +try { + sodium_crypto_xof_turboshake256_init(0x80); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} +?> +--EXPECT-- +string(64) "1e415f1c5983aff2169217277d17bb538cd945a397ddec541f1ce41af2c1b74c" +string(64) "dcf1646dfe993a8eb6b782d1faaca6d82416a5dcf1de98ee3c6dbc5e1dc63018" +string(64) "367a329dafea871c7802ec67f905ae13c57695dc2c6663c61035f59a18f8e7db" +string(64) "63824b1431a7372e85edc022c9d7afdd027472fcfa33c887d6f5aaf8dc5d4db6" +string(64) "dcf1646dfe993a8eb6b782d1faaca6d82416a5dcf1de98ee3c6dbc5e1dc63018" +string(64) "63824b1431a7372e85edc022c9d7afdd027472fcfa33c887d6f5aaf8dc5d4db6" +string(64) "dcf1646dfe993a8eb6b782d1faaca6d82416a5dcf1de98ee3c6dbc5e1dc63018" +string(64) "63824b1431a7372e85edc022c9d7afdd027472fcfa33c887d6f5aaf8dc5d4db6" +string(64) "3f7566fb02630888cba2af090aaf544ac6e85484d8662335b0c0fd6b3c83ea1b" +string(64) "0137a7e8cca0b99e9bdd557a7caac1f21d65bc2a2ccbde1e1f8c702352a2bf30" +int(16) +int(64) +bool(true) +sodium_crypto_xof_turboshake128(): Argument #1 ($length) must be a positive integer +sodium_crypto_xof_turboshake256_update(): Argument #1 ($state) must have a correct state length +sodium_crypto_xof_turboshake128_init(): Argument #1 ($domain) must be between 0x01 and 0x7f +sodium_crypto_xof_turboshake256_init(): Argument #1 ($domain) must be between 0x01 and 0x7f diff --git a/ext/sodium/tests/sodium_bin2ip.phpt b/ext/sodium/tests/sodium_bin2ip.phpt new file mode 100644 index 0000000000000..ab72b4b297acc --- /dev/null +++ b/ext/sodium/tests/sodium_bin2ip.phpt @@ -0,0 +1,69 @@ +--TEST-- +Check for libsodium bin2ip/ip2bin helpers +--EXTENSIONS-- +sodium +--SKIPIF-- += 1.0.21)"; +?> +--FILE-- + bin2ip roundtrip for various addresses */ +$addrs = ["0.0.0.0", "255.255.255.255", "127.0.0.1", "::ffff:192.168.0.1", "2001:db8::1"]; +foreach ($addrs as $addr) { + $result = sodium_bin2ip(sodium_ip2bin($addr)); + echo "$addr => $result\n"; +} + +/* Error: bin2ip wrong length */ +try { + sodium_bin2ip("short"); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} + +/* Error: ip2bin invalid address */ +try { + sodium_ip2bin("not_an_ip"); +} catch (SodiumException $e) { + echo $e->getMessage() . "\n"; +} +?> +--EXPECT-- +int(16) +string(32) "00000000000000000000ffffc0a80101" +string(11) "192.168.1.1" +string(32) "00000000000000000000000000000001" +string(3) "::1" +string(32) "00000000000000000000ffff0a000001" +string(8) "10.0.0.1" +string(32) "fe800000000000000000000000000001" +string(7) "fe80::1" +0.0.0.0 => 0.0.0.0 +255.255.255.255 => 255.255.255.255 +127.0.0.1 => 127.0.0.1 +::ffff:192.168.0.1 => 192.168.0.1 +2001:db8::1 => 2001:db8::1 +sodium_bin2ip(): Argument #1 ($bin) must be 16 bytes long +sodium_ip2bin(): Argument #1 ($ip) must be a valid IP address From b2d291546fc0bf50a9cdfeef93b474b6adcfe40f Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 26 Feb 2026 18:47:00 +0100 Subject: [PATCH 2/5] [CI][skip ci] Fix benchmarking summary Just compare against github.event.pull_request.base.sha. When GitHub creates hidden merge commits, it doesn't make sense to compare against the base anyway. Closes GH-21306 --- .github/workflows/test-suite.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 55a6f7dcdc186..2433bb321f89c 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -1074,8 +1074,8 @@ jobs: run: |- set -x php benchmark/generate_diff.php \ - ${{ github.event.pull_request.head.sha }} \ - $(git merge-base ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}) \ + ${{ github.sha }} \ + ${{ github.event.pull_request.base.sha }} \ > $GITHUB_STEP_SUMMARY - uses: actions/upload-artifact@v6 with: From a5a5e3acdde87c034af34e88f07b39d042c2355b Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 25 Feb 2026 16:51:22 +0100 Subject: [PATCH 3/5] Make one linux job run function jit on push We're currently only testing the tracing JIT on push. Having at least one function JIT job will catch some errors earlier. Closes GH-21294 --- .github/workflows/test-suite.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 2433bb321f89c..0fd95f67f9c41 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -187,6 +187,7 @@ jobs: ${{ matrix.variation && '-d zend_test.observer.enabled=1 -d zend_test.observer.show_output=0' || '' }} idleCpu: ${{ matrix.asan && 'true' || 'false' }} - name: Test Tracing JIT + if: ${{ inputs.all_variations || matrix.asan }} uses: ./.github/actions/test-linux with: enableOpcache: true @@ -208,7 +209,7 @@ jobs: - name: Test Function JIT # ASAN frequently timeouts. Each test run takes ~90 minutes, we can # avoid running into the 6 hour timeout by skipping the function JIT. - if: ${{ inputs.all_variations && !matrix.asan }} + if: ${{ !matrix.asan }} uses: ./.github/actions/test-linux with: enableOpcache: true From e4f727d61eb0ad04d158ef0f67cdabbc7427d6ac Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Fri, 4 Oct 2024 00:59:24 +0200 Subject: [PATCH 4/5] Implement ReflectionProperty::is{Readable,Writable}() RFC: https://wiki.php.net/rfc/isreadable-iswriteable Fixes GH-15309 Fixes GH-16175 Closes GH-16209 --- UPGRADING | 2 + ext/reflection/php_reflection.c | 236 ++++++++++++++++++ ext/reflection/php_reflection.stub.php | 4 + ext/reflection/php_reflection_arginfo.h | 13 +- ext/reflection/php_reflection_decl.h | 8 +- ...ReflectionProperty_isReadable_dynamic.phpt | 28 +++ .../ReflectionProperty_isReadable_hooks.phpt | 39 +++ .../ReflectionProperty_isReadable_init.phpt | 72 ++++++ ...tionProperty_isReadable_invalid_scope.phpt | 20 ++ .../ReflectionProperty_isReadable_lazy.phpt | 29 +++ ...ctionProperty_isReadable_lazy_dynamic.phpt | 25 ++ ...lectionProperty_isReadable_lazy_isset.phpt | 33 +++ .../ReflectionProperty_isReadable_static.phpt | 36 +++ ...nProperty_isReadable_unrelated_object.phpt | 36 +++ ...lectionProperty_isReadable_visibility.phpt | 67 +++++ ...ReflectionProperty_isWritable_dynamic.phpt | 32 +++ .../ReflectionProperty_isWritable_hooks.phpt | 39 +++ ...tionProperty_isWritable_invalid_scope.phpt | 20 ++ .../ReflectionProperty_isWritable_lazy.phpt | 29 +++ ...eflectionProperty_isWritable_readonly.phpt | 39 +++ .../ReflectionProperty_isWritable_static.phpt | 36 +++ ...nProperty_isWritable_unrelated_object.phpt | 36 +++ ...lectionProperty_isWritable_visibility.phpt | 68 +++++ 23 files changed, 942 insertions(+), 5 deletions(-) create mode 100644 ext/reflection/tests/ReflectionProperty_isReadable_dynamic.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isReadable_hooks.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isReadable_init.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isReadable_lazy.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isReadable_lazy_dynamic.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isReadable_lazy_isset.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isReadable_static.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isReadable_unrelated_object.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isReadable_visibility.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isWritable_dynamic.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isWritable_hooks.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isWritable_lazy.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isWritable_readonly.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isWritable_static.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isWritable_unrelated_object.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_isWritable_visibility.phpt diff --git a/UPGRADING b/UPGRADING index 9a1b37d98f7f4..151b27b226788 100644 --- a/UPGRADING +++ b/UPGRADING @@ -134,6 +134,8 @@ PHP 8.6 UPGRADE NOTES - Reflection: . ReflectionConstant::inNamespace() + . ReflectionProperty::isReadable() ReflectionProperty::isWritable() were + added. - Standard: . `clamp()` returns the given value if in range, else return the nearest diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 232c1e6b36a85..2a9b4776350bc 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6601,6 +6601,242 @@ ZEND_METHOD(ReflectionProperty, isFinal) _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL); } +static zend_result get_ce_from_scope_name(zend_class_entry **scope, zend_string *scope_name, zend_execute_data *execute_data) +{ + if (!scope_name) { + *scope = NULL; + return SUCCESS; + } + + *scope = zend_lookup_class(scope_name); + if (!*scope) { + zend_throw_error(NULL, "Class \"%s\" not found", ZSTR_VAL(scope_name)); + return FAILURE; + } + return SUCCESS; +} + +static zend_always_inline uint32_t set_visibility_to_visibility(uint32_t set_visibility) +{ + switch (set_visibility) { + case ZEND_ACC_PUBLIC_SET: + return ZEND_ACC_PUBLIC; + case ZEND_ACC_PROTECTED_SET: + return ZEND_ACC_PROTECTED; + case ZEND_ACC_PRIVATE_SET: + return ZEND_ACC_PRIVATE; + EMPTY_SWITCH_DEFAULT_CASE(); + } +} + +static bool check_visibility(uint32_t visibility, zend_class_entry *ce, zend_class_entry *scope) +{ + if (!(visibility & ZEND_ACC_PUBLIC) && (scope != ce)) { + if (!scope) { + return false; + } + if (visibility & ZEND_ACC_PRIVATE) { + return false; + } + ZEND_ASSERT(visibility & ZEND_ACC_PROTECTED); + if (!instanceof_function(scope, ce) && !instanceof_function(ce, scope)) { + return false; + } + } + return true; +} + +ZEND_METHOD(ReflectionProperty, isReadable) +{ + reflection_object *intern; + property_reference *ref; + zend_string *scope_name; + zend_object *obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR_OR_NULL(scope_name) + Z_PARAM_OPTIONAL + Z_PARAM_OBJ_OR_NULL(obj) + ZEND_PARSE_PARAMETERS_END(); + + GET_REFLECTION_OBJECT_PTR(ref); + + zend_property_info *prop = ref->prop; + if (prop && obj) { + if (prop->flags & ZEND_ACC_STATIC) { + _DO_THROW("null is expected as object argument for static properties"); + RETURN_THROWS(); + } + if (!instanceof_function(obj->ce, prop->ce)) { + _DO_THROW("Given object is not an instance of the class this property was declared in"); + RETURN_THROWS(); + } + prop = reflection_property_get_effective_prop(ref, intern->ce, obj); + } + + zend_class_entry *ce = obj ? obj->ce : intern->ce; + if (!prop) { + if (obj && obj->properties && zend_hash_find_ptr(obj->properties, ref->unmangled_name)) { + RETURN_TRUE; + } +handle_magic_get: + if (ce->__get) { + if (obj && ce->__isset) { + uint32_t *guard = zend_get_property_guard(obj, ref->unmangled_name); + if (!((*guard) & ZEND_GUARD_PROPERTY_ISSET)) { + GC_ADDREF(obj); + *guard |= ZEND_GUARD_PROPERTY_ISSET; + zval member; + ZVAL_STR(&member, ref->unmangled_name); + zend_call_known_instance_method_with_1_params(ce->__isset, obj, return_value, &member); + *guard &= ~ZEND_GUARD_PROPERTY_ISSET; + OBJ_RELEASE(obj); + return; + } + } + RETURN_TRUE; + } + if (obj && zend_lazy_object_must_init(obj)) { + obj = zend_lazy_object_init(obj); + if (!obj) { + RETURN_THROWS(); + } + if (obj->properties && zend_hash_find_ptr(obj->properties, ref->unmangled_name)) { + RETURN_TRUE; + } + } + RETURN_FALSE; + } + + zend_class_entry *scope; + if (get_ce_from_scope_name(&scope, scope_name, execute_data) == FAILURE) { + RETURN_THROWS(); + } + + if (!check_visibility(prop->flags & ZEND_ACC_PPP_MASK, prop->ce, scope)) { + if (!(prop->flags & ZEND_ACC_STATIC)) { + goto handle_magic_get; + } + RETURN_FALSE; + } + + if (prop->flags & ZEND_ACC_VIRTUAL) { + ZEND_ASSERT(prop->hooks); + if (!prop->hooks[ZEND_PROPERTY_HOOK_GET]) { + RETURN_FALSE; + } + } else if (obj && (!prop->hooks || !prop->hooks[ZEND_PROPERTY_HOOK_GET])) { +retry_declared:; + zval *prop_val = OBJ_PROP(obj, prop->offset); + if (Z_TYPE_P(prop_val) == IS_UNDEF) { + if (zend_lazy_object_must_init(obj) && (Z_PROP_FLAG_P(prop_val) & IS_PROP_LAZY)) { + obj = zend_lazy_object_init(obj); + if (!obj) { + RETURN_THROWS(); + } + goto retry_declared; + } + if (!(Z_PROP_FLAG_P(prop_val) & IS_PROP_UNINIT)) { + goto handle_magic_get; + } + RETURN_FALSE; + } + } else if (prop->flags & ZEND_ACC_STATIC) { + if (ce->default_static_members_count && !CE_STATIC_MEMBERS(ce)) { + zend_class_init_statics(ce); + } + zval *prop_val = CE_STATIC_MEMBERS(ce) + prop->offset; + RETURN_BOOL(!Z_ISUNDEF_P(prop_val)); + } + + RETURN_TRUE; +} + +ZEND_METHOD(ReflectionProperty, isWritable) +{ + reflection_object *intern; + property_reference *ref; + zend_string *scope_name; + zend_object *obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR_OR_NULL(scope_name) + Z_PARAM_OPTIONAL + Z_PARAM_OBJ_OR_NULL(obj) + ZEND_PARSE_PARAMETERS_END(); + + GET_REFLECTION_OBJECT_PTR(ref); + + zend_property_info *prop = ref->prop; + if (prop && obj) { + if (prop->flags & ZEND_ACC_STATIC) { + _DO_THROW("null is expected as object argument for static properties"); + RETURN_THROWS(); + } + if (!instanceof_function(obj->ce, prop->ce)) { + _DO_THROW("Given object is not an instance of the class this property was declared in"); + RETURN_THROWS(); + } + prop = reflection_property_get_effective_prop(ref, intern->ce, obj); + } + + zend_class_entry *ce = obj ? obj->ce : intern->ce; + if (!prop) { + if (!(ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) { + RETURN_TRUE; + } + /* This path is effectively unreachable, but theoretically possible for + * two internal classes where ZEND_ACC_NO_DYNAMIC_PROPERTIES is only + * added to the subclass, in which case a ReflectionProperty can be + * constructed on the parent class, and then tested on the subclass. */ +handle_magic_set: + RETURN_BOOL(ce->__set); + } + + zend_class_entry *scope; + if (get_ce_from_scope_name(&scope, scope_name, execute_data) == FAILURE) { + RETURN_THROWS(); + } + + if (!check_visibility(prop->flags & ZEND_ACC_PPP_MASK, prop->ce, scope)) { + if (!(prop->flags & ZEND_ACC_STATIC)) { + goto handle_magic_set; + } + RETURN_FALSE; + } + uint32_t set_visibility = prop->flags & ZEND_ACC_PPP_SET_MASK; + if (!set_visibility) { + set_visibility = zend_visibility_to_set_visibility(prop->flags & ZEND_ACC_PPP_MASK); + } + if (!check_visibility(set_visibility_to_visibility(set_visibility), prop->ce, scope)) { + RETURN_FALSE; + } + + if (prop->flags & ZEND_ACC_VIRTUAL) { + ZEND_ASSERT(prop->hooks); + if (!prop->hooks[ZEND_PROPERTY_HOOK_SET]) { + RETURN_FALSE; + } + } else if (obj && (prop->flags & ZEND_ACC_READONLY)) { +retry:; + zval *prop_val = OBJ_PROP(obj, prop->offset); + if (Z_TYPE_P(prop_val) == IS_UNDEF + && zend_lazy_object_must_init(obj) + && (Z_PROP_FLAG_P(prop_val) & IS_PROP_LAZY)) { + obj = zend_lazy_object_init(obj); + if (!obj) { + RETURN_THROWS(); + } + goto retry; + } + if (Z_TYPE_P(prop_val) != IS_UNDEF && !(Z_PROP_FLAG_P(prop_val) & IS_PROP_REINITABLE)) { + RETURN_FALSE; + } + } + + RETURN_TRUE; +} + /* {{{ Constructor. Throws an Exception in case the given extension does not exist */ ZEND_METHOD(ReflectionExtension, __construct) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 8f2c49460b4a5..b0273a3174f8e 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -574,6 +574,10 @@ public function hasHook(PropertyHookType $type): bool {} public function getHook(PropertyHookType $type): ?ReflectionMethod {} public function isFinal(): bool {} + + public function isReadable(?string $scope, ?object $object = null): bool {} + + public function isWritable(?string $scope, ?object $object = null): bool {} } /** @not-serializable */ diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index e16ea82b0e957..66605a22bbd66 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: b09497083efa7035dab6047f6d845ceaec81579e + * Stub hash: 267472e2b726ca5e788eb5cc3e946bc9aa7c9c41 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) @@ -473,6 +473,13 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isFinal arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_isReadable, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, scope, IS_STRING, 1) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, object, IS_OBJECT, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionProperty_isWritable arginfo_class_ReflectionProperty_isReadable + #define arginfo_class_ReflectionClassConstant___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionClassConstant___construct, 0, 0, 2) @@ -894,6 +901,8 @@ ZEND_METHOD(ReflectionProperty, getHooks); ZEND_METHOD(ReflectionProperty, hasHook); ZEND_METHOD(ReflectionProperty, getHook); ZEND_METHOD(ReflectionProperty, isFinal); +ZEND_METHOD(ReflectionProperty, isReadable); +ZEND_METHOD(ReflectionProperty, isWritable); ZEND_METHOD(ReflectionClassConstant, __construct); ZEND_METHOD(ReflectionClassConstant, __toString); ZEND_METHOD(ReflectionClassConstant, getName); @@ -1198,6 +1207,8 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_ME(ReflectionProperty, hasHook, arginfo_class_ReflectionProperty_hasHook, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getHook, arginfo_class_ReflectionProperty_getHook, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isFinal, arginfo_class_ReflectionProperty_isFinal, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isReadable, arginfo_class_ReflectionProperty_isReadable, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isWritable, arginfo_class_ReflectionProperty_isWritable, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/php_reflection_decl.h b/ext/reflection/php_reflection_decl.h index f93c1d0c887de..a5e8affd0beb1 100644 --- a/ext/reflection/php_reflection_decl.h +++ b/ext/reflection/php_reflection_decl.h @@ -1,12 +1,12 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: b09497083efa7035dab6047f6d845ceaec81579e */ + * Stub hash: 267472e2b726ca5e788eb5cc3e946bc9aa7c9c41 */ -#ifndef ZEND_PHP_REFLECTION_DECL_b09497083efa7035dab6047f6d845ceaec81579e_H -#define ZEND_PHP_REFLECTION_DECL_b09497083efa7035dab6047f6d845ceaec81579e_H +#ifndef ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H +#define ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H typedef enum zend_enum_PropertyHookType { ZEND_ENUM_PropertyHookType_Get = 1, ZEND_ENUM_PropertyHookType_Set = 2, } zend_enum_PropertyHookType; -#endif /* ZEND_PHP_REFLECTION_DECL_b09497083efa7035dab6047f6d845ceaec81579e_H */ +#endif /* ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H */ diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_dynamic.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_dynamic.phpt new file mode 100644 index 0000000000000..a4056a60555e8 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_dynamic.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test ReflectionProperty::isReadable() dynamic +--FILE-- +a = 'a'; +$r = new ReflectionProperty($a, 'a'); + +var_dump($r->isReadable(null, $a)); +unset($a->a); +var_dump($r->isReadable(null, $a)); + +$a = new A; +var_dump($r->isReadable(null, $a)); + +var_dump($r->isReadable(null, null)); + +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) +bool(false) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_hooks.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_hooks.phpt new file mode 100644 index 0000000000000..5fc31b2163eb4 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_hooks.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test ReflectionProperty::isReadable() hooks +--FILE-- + $this->a; } + public $b { get => 42; } + public $c { set => $value; } + public $d { set {} } + public $e { get => $this->e; set => $value; } + public $f { get {} set {} } +} + +function test($scope) { + $rc = new ReflectionClass(A::class); + foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': '; + var_dump($rp->isReadable($scope, null)); + } +} + +test('A'); +test(null); + +?> +--EXPECT-- +a from A: bool(true) +b from A: bool(true) +c from A: bool(true) +d from A: bool(false) +e from A: bool(true) +f from A: bool(true) +a from global: bool(true) +b from global: bool(true) +c from global: bool(true) +d from global: bool(false) +e from global: bool(true) +f from global: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_init.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_init.phpt new file mode 100644 index 0000000000000..242f28b57a821 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_init.phpt @@ -0,0 +1,72 @@ +--TEST-- +Test ReflectionProperty::isReadable() init +--FILE-- +e); + } +} + +class B { + public int $f; + public int $g; + public int $h; + + public function __construct() { + unset($this->g); + unset($this->h); + } + + public function __isset($name) { + return $name === 'h'; + } + + public function __get($name) {} +} + +class C { + public int $i; + public int $j; + public int $k; + + public function __construct() { + unset($this->j); + unset($this->k); + } + + public function __get($name) {} +} + +function test($class) { + $rc = new ReflectionClass($class); + foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from global: '; + var_dump($rp->isReadable(null, new $class)); + } +} + +test('A'); +test('B'); +test('C'); + +?> +--EXPECT-- +a from global: bool(true) +b from global: bool(false) +c from global: bool(true) +d from global: bool(false) +e from global: bool(false) +f from global: bool(false) +g from global: bool(false) +h from global: bool(true) +i from global: bool(false) +j from global: bool(true) +k from global: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt new file mode 100644 index 0000000000000..ae9f446c67719 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test ReflectionProperty::isReadable() invalid scope +--FILE-- +isReadable('B', null); +} catch (Error $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Error: Class "B" not found diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_lazy.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_lazy.phpt new file mode 100644 index 0000000000000..214708cda6a12 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_lazy.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test ReflectionProperty::isReadable() lazy +--CREDITS-- +Arnaud Le Blanc (arnaud-lb) +--FILE-- +a = 1; + } +} + +$rc = new ReflectionClass(A::class); +$obj = $rc->newLazyProxy(fn() => new A()); + +$rp = new ReflectionProperty(A::class, 'a'); +var_dump($rp->isReadable(null, $obj)); + +$rp = new ReflectionProperty(A::class, 'b'); +var_dump($rp->isReadable(null, $obj)); + +?> +--EXPECT-- +bool(true) +bool(false) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_lazy_dynamic.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_lazy_dynamic.phpt new file mode 100644 index 0000000000000..a40b32f0abd90 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_lazy_dynamic.phpt @@ -0,0 +1,25 @@ +--TEST-- +Test ReflectionProperty::isReadable() lazy dynamic +--CREDITS-- +Arnaud Le Blanc (arnaud-lb) +--FILE-- +prop = 1; + } +} + +$rc = new ReflectionClass(A::class); +$obj = $rc->newLazyProxy(fn() => new A()); + +$rp = new ReflectionProperty(new A, 'prop'); +var_dump($rp->isReadable(null, $obj)); + +?> +--EXPECT-- +bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_lazy_isset.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_lazy_isset.phpt new file mode 100644 index 0000000000000..398c3311e4995 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_lazy_isset.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test ReflectionProperty::isReadable() lazy isset +--CREDITS-- +Arnaud Le Blanc (arnaud-lb) +--FILE-- +prop = 1; + } + + public function __isset($name) { + return false; + } + + public function __get($name) { + return null; + } +} + +$rc = new ReflectionClass(A::class); +$obj = $rc->newLazyProxy(fn() => new A); + +$rp = new ReflectionProperty(new A, 'prop'); +var_dump($rp->isReadable(null, $obj)); + +?> +--EXPECT-- +bool(false) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_static.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_static.phpt new file mode 100644 index 0000000000000..e927f428a1f82 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_static.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test ReflectionProperty::isReadable() static +--FILE-- +isReadable(null, new A); +} catch (Exception $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +$rc = new ReflectionClass('A'); +foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from global: '; + var_dump($rp->isReadable(null)); +} + +?> +--EXPECT-- +ReflectionException: null is expected as object argument for static properties +a from global: bool(true) +b from global: bool(false) +c from global: bool(true) +d from global: bool(false) +e from global: bool(false) +f from global: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_unrelated_object.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_unrelated_object.phpt new file mode 100644 index 0000000000000..ce48958c28ce2 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_unrelated_object.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test ReflectionProperty::isReadable() unrelated object +--FILE-- +isReadable(null, $obj)); + } catch (Exception $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; + } +} + +test(new A); +test(new B); +test(new C); +test(new D); + +?> +--EXPECT-- +ReflectionException: Given object is not an instance of the class this property was declared in +bool(true) +bool(true) +ReflectionException: Given object is not an instance of the class this property was declared in diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_visibility.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_visibility.phpt new file mode 100644 index 0000000000000..205ff85293ff7 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_visibility.phpt @@ -0,0 +1,67 @@ +--TEST-- +Test ReflectionProperty::isReadable() visibility +--FILE-- +getProperties() as $rp) { + echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': '; + var_dump($rp->isReadable($scope, $scope && $scope !== 'A' ? new $scope : null)); + } +} + +foreach (['A', 'B', 'C', 'D', 'E'] as $scope) { + test($scope); +} +test(null); + +?> +--EXPECT-- +a from A: bool(true) +b from A: bool(true) +c from A: bool(false) +d from A: bool(true) +a from B: bool(true) +b from B: bool(true) +c from B: bool(true) +d from B: bool(true) +a from C: bool(true) +b from C: bool(true) +c from C: bool(false) +d from C: bool(true) +a from D: bool(true) +b from D: bool(true) +c from D: bool(true) +d from D: bool(true) +a from E: bool(true) +b from E: bool(true) +c from E: bool(false) +d from E: bool(true) +a from global: bool(true) +b from global: bool(false) +c from global: bool(false) +d from global: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_dynamic.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_dynamic.phpt new file mode 100644 index 0000000000000..e9da6675ddaef --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_dynamic.phpt @@ -0,0 +1,32 @@ +--TEST-- +Test ReflectionProperty::isWritable() dynamic +--FILE-- +isWritable(null, $a)); +var_dump($r->isWritable(null, null)); + +$a->b = 'b'; +$r = new ReflectionProperty($a, 'b'); +var_dump($r->isWritable(null, $a)); + +$a = new A; +var_dump($r->isWritable(null, $a)); + +var_dump($r->isWritable(null, null)); + +?> +--EXPECT-- +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_hooks.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_hooks.phpt new file mode 100644 index 0000000000000..2617ec65b9e71 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_hooks.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test ReflectionProperty::isWritable() visibility +--FILE-- + $this->a; } + public $b { get => 42; } + public $c { set => $value; } + public $d { set {} } + public $e { get => $this->e; set => $value; } + public $f { get {} set {} } +} + +function test($scope) { + $rc = new ReflectionClass(A::class); + foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': '; + var_dump($rp->isWritable($scope, null)); + } +} + +test('A'); +test(null); + +?> +--EXPECT-- +a from A: bool(true) +b from A: bool(false) +c from A: bool(true) +d from A: bool(true) +e from A: bool(true) +f from A: bool(true) +a from global: bool(true) +b from global: bool(false) +c from global: bool(true) +d from global: bool(true) +e from global: bool(true) +f from global: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt new file mode 100644 index 0000000000000..7474e521b35e4 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test ReflectionProperty::isWritable() invalid scope +--FILE-- +isWritable('B', null); +} catch (Error $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Error: Class "B" not found diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_lazy.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_lazy.phpt new file mode 100644 index 0000000000000..91f305fef8af2 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_lazy.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test ReflectionProperty::isWritable() lazy +--CREDITS-- +Arnaud Le Blanc (arnaud-lb) +--FILE-- +a = 1; + } +} + +$rc = new ReflectionClass(A::class); +$obj = $rc->newLazyProxy(fn() => new A()); + +$rp = new ReflectionProperty(A::class, 'a'); +var_dump($rp->isWritable(null, $obj)); + +$rp = new ReflectionProperty(A::class, 'b'); +var_dump($rp->isWritable(null, $obj)); + +?> +--EXPECT-- +bool(false) +bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_readonly.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_readonly.phpt new file mode 100644 index 0000000000000..256f14895908d --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_readonly.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test ReflectionProperty::isWritable() readonly +--FILE-- +a = 42; + } + + public function __clone() { + test($this); + $this->a = 43; + test($this); + } +} + +function test($instance) { + $rc = new ReflectionClass($instance); + foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from A: '; + var_dump($rp->isWritable($instance::class, $instance)); + } +} + +test(new A); +clone new A; + +?> +--EXPECT-- +a from A: bool(false) +b from A: bool(true) +a from A: bool(true) +b from A: bool(true) +a from A: bool(false) +b from A: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_static.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_static.phpt new file mode 100644 index 0000000000000..008d585c31530 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_static.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test ReflectionProperty::isWritable() static +--FILE-- +isWritable(null, new A); +} catch (Exception $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +$rc = new ReflectionClass('A'); +foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from global: '; + var_dump($rp->isWritable(null)); +} + +?> +--EXPECT-- +ReflectionException: null is expected as object argument for static properties +a from global: bool(true) +b from global: bool(true) +c from global: bool(true) +d from global: bool(false) +e from global: bool(false) +f from global: bool(false) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_unrelated_object.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_unrelated_object.phpt new file mode 100644 index 0000000000000..3e1e6a394c8f0 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_unrelated_object.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test ReflectionProperty::isWritable() unrelated object +--FILE-- +isWritable(null, $obj)); + } catch (Exception $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; + } +} + +test(new A); +test(new B); +test(new C); +test(new D); + +?> +--EXPECT-- +ReflectionException: Given object is not an instance of the class this property was declared in +bool(true) +bool(true) +ReflectionException: Given object is not an instance of the class this property was declared in diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_visibility.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_visibility.phpt new file mode 100644 index 0000000000000..1585f4cba531f --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_visibility.phpt @@ -0,0 +1,68 @@ +--TEST-- +Test ReflectionProperty::isWritable() visibility +--FILE-- +getProperties() as $rp) { + echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': '; + var_dump($rp->isWritable($scope, $scope && $scope !== 'A' ? new $scope : null)); + } +} + +test('A'); +test('B'); +test('C'); +test('D'); +test(null); + +?> +--EXPECT-- +a from A: bool(true) +b from A: bool(true) +c from A: bool(false) +d from A: bool(false) +e from A: bool(true) +f from A: bool(true) +a from B: bool(true) +b from B: bool(true) +c from B: bool(true) +d from B: bool(true) +e from B: bool(true) +f from B: bool(true) +a from C: bool(true) +b from C: bool(true) +c from C: bool(false) +d from C: bool(false) +e from C: bool(true) +f from C: bool(true) +a from D: bool(true) +b from D: bool(true) +c from D: bool(true) +d from D: bool(false) +e from D: bool(true) +f from D: bool(true) +a from global: bool(true) +b from global: bool(false) +c from global: bool(false) +d from global: bool(false) +e from global: bool(false) +f from global: bool(false) From 3a57b4ced5090f0b0bbe93b9789ae355c2f78be4 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 14 Jan 2026 15:35:48 +0100 Subject: [PATCH 5/5] Add RUN_RESOURCE_HEAVY_TESTS env var Disable resource-heavy tests by default (>1GB of memory usage), unless the RUN_RESOURCE_HEAVY_TESTS env variable is set. Fixes GH-20762 Closes GH-20935 --- .circleci/config.yml | 1 + .github/actions/freebsd/action.yml | 1 + .github/actions/test-gentoo/action.yml | 1 + .github/actions/test-libmysqlclient/action.yml | 1 + .github/actions/test-linux/action.yml | 1 + .github/actions/test-macos/action.yml | 1 + Zend/tests/bug55509.phpt | 1 + Zend/tests/bug74093.phpt | 1 + Zend/tests/bug78010.phpt | 5 +++++ ext/gd/tests/bug77270.phpt | 2 -- ext/gd/tests/bug77272.phpt | 1 + ext/gd/tests/bug77479.phpt | 1 + ext/gd/tests/gh16322.phpt | 2 -- ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt | 1 + ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt | 1 + ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt | 1 + ext/pdo_sqlite/tests/bug81740.phpt | 1 + ext/soap/tests/soap_qname_crash.phpt | 1 + .../tests/file/file_get_contents_file_put_contents_5gb.phpt | 1 + ext/standard/tests/strings/gh15613.phpt | 1 + ext/tidy/tests/parsing_file_too_large.phpt | 1 + sapi/fpm/tests/bug77023-pm-dynamic-blocking-sigquit.phpt | 2 +- sapi/fpm/tests/proc-idle-timeout.phpt | 2 +- 23 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index eb0c7e0643878..eebc3c729a9d3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -180,6 +180,7 @@ jobs: name: Test no_output_timeout: 30m command: | + export RUN_RESOURCE_HEAVY_TESTS=1 sapi/cli/php run-tests.php \ -d zend_extension=opcache.so \ -d opcache.enable_cli=1 \ diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml index 2a7a323da4179..588cb1fc0137c 100644 --- a/.github/actions/freebsd/action.yml +++ b/.github/actions/freebsd/action.yml @@ -106,6 +106,7 @@ runs: export SKIP_IO_CAPTURE_TESTS=1 export CI_NO_IPV6=1 export STACK_LIMIT_DEFAULTS_CHECK=1 + export RUN_RESOURCE_HEAVY_TESTS=1 sapi/cli/php run-tests.php \ -P -q -j2 \ -g FAIL,BORK,LEAK,XLEAK \ diff --git a/.github/actions/test-gentoo/action.yml b/.github/actions/test-gentoo/action.yml index ec9fb0b70c6a3..f717384218000 100644 --- a/.github/actions/test-gentoo/action.yml +++ b/.github/actions/test-gentoo/action.yml @@ -25,6 +25,7 @@ runs: # Slow tests criteron is doubled because this runner isn't as fast as others export SKIP_IO_CAPTURE_TESTS=1 export STACK_LIMIT_DEFAULTS_CHECK=1 + export RUN_RESOURCE_HEAVY_TESTS=1 sapi/cli/php run-tests.php -P -q ${{ inputs.runTestsParameters }} \ -j$(nproc) \ -g FAIL,BORK,LEAK,XLEAK \ diff --git a/.github/actions/test-libmysqlclient/action.yml b/.github/actions/test-libmysqlclient/action.yml index 185e909f96583..3ab4aeb43db9b 100644 --- a/.github/actions/test-libmysqlclient/action.yml +++ b/.github/actions/test-libmysqlclient/action.yml @@ -9,6 +9,7 @@ runs: export PDO_MYSQL_TEST_HOST=127.0.0.1 export PDO_MYSQL_TEST_USER=root export PDO_MYSQL_TEST_PASS=root + export RUN_RESOURCE_HEAVY_TESTS=1 sapi/cli/php run-tests.php -P -q \ -g FAIL,BORK,LEAK,XLEAK \ --no-progress --offline --show-diff --show-slow 1000 --set-timeout 120 \ diff --git a/.github/actions/test-linux/action.yml b/.github/actions/test-linux/action.yml index 840ad39f85443..337066ea0df7f 100644 --- a/.github/actions/test-linux/action.yml +++ b/.github/actions/test-linux/action.yml @@ -39,6 +39,7 @@ runs: export PDO_PGSQL_TEST_DSN="pgsql:host=localhost port=5432 dbname=test user=postgres password=postgres" fi export SKIP_IO_CAPTURE_TESTS=1 + export RUN_RESOURCE_HEAVY_TESTS=1 sapi/cli/php run-tests.php -P -q ${{ inputs.runTestsParameters }} \ ${{ inputs.enableOpcache == 'true' && '-d zend_extension=opcache.so -d opcache.enable_cli=1' || '' }} \ -d opcache.jit=${{ inputs.jitType }} \ diff --git a/.github/actions/test-macos/action.yml b/.github/actions/test-macos/action.yml index 9fd485f112ebb..e0e53a1beb99e 100644 --- a/.github/actions/test-macos/action.yml +++ b/.github/actions/test-macos/action.yml @@ -17,6 +17,7 @@ runs: set -x export SKIP_IO_CAPTURE_TESTS=1 export CI_NO_IPV6=1 + export RUN_RESOURCE_HEAVY_TESTS=1 sapi/cli/php run-tests.php -P -q ${{ inputs.runTestsParameters }} \ ${{ inputs.enableOpcache == 'true' && '-d zend_extension=opcache.so -d opcache.enable_cli=1' || '' }} \ -d opcache.jit=${{ inputs.jitType }} \ diff --git a/Zend/tests/bug55509.phpt b/Zend/tests/bug55509.phpt index 1ae5b04f8a0df..90c410752ec28 100644 --- a/Zend/tests/bug55509.phpt +++ b/Zend/tests/bug55509.phpt @@ -2,6 +2,7 @@ Bug #55509 (segfault on x86_64 using more than 2G memory) --SKIPIF-- --INI-- memory_limit=2G --FILE-- diff --git a/ext/gd/tests/bug77270.phpt b/ext/gd/tests/bug77270.phpt index 33246880c53d7..77608757f8f4b 100644 --- a/ext/gd/tests/bug77270.phpt +++ b/ext/gd/tests/bug77270.phpt @@ -1,7 +1,5 @@ --TEST-- Bug #77270 (imagecolormatch Out Of Bounds Write on Heap) ---INI-- -memory_limit=-1 --EXTENSIONS-- gd --SKIPIF-- diff --git a/ext/gd/tests/bug77272.phpt b/ext/gd/tests/bug77272.phpt index f2483f915c17b..11fd3b3ce5695 100644 --- a/ext/gd/tests/bug77272.phpt +++ b/ext/gd/tests/bug77272.phpt @@ -6,6 +6,7 @@ memory_limit=-1 gd --SKIPIF-- --INI-- diff --git a/ext/gd/tests/gh16322.phpt b/ext/gd/tests/gh16322.phpt index 1edc27285d2db..1e5ab2f3a6b12 100644 --- a/ext/gd/tests/gh16322.phpt +++ b/ext/gd/tests/gh16322.phpt @@ -2,8 +2,6 @@ GH-16322 (imageaffine overflow/underflow on affine matrix) --EXTENSIONS-- gd ---INI-- -memory_limit=-1 --FILE-- diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt index a69597084be6c..ef64e25b89c6a 100644 --- a/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt +++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt @@ -6,6 +6,7 @@ ldap memory_limit=-1 --SKIPIF-- diff --git a/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt b/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt index 431c61951ee2a..95b33ddb7f3cb 100644 --- a/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt +++ b/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt @@ -4,6 +4,7 @@ GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing OOB writes) pdo_dblib --SKIPIF-- diff --git a/ext/soap/tests/soap_qname_crash.phpt b/ext/soap/tests/soap_qname_crash.phpt index bcf01d574fab4..63c1851286af9 100644 --- a/ext/soap/tests/soap_qname_crash.phpt +++ b/ext/soap/tests/soap_qname_crash.phpt @@ -4,6 +4,7 @@ Test SoapClient with excessively large QName prefix in SoapVar soap --SKIPIF-- --INI-- diff --git a/ext/standard/tests/file/file_get_contents_file_put_contents_5gb.phpt b/ext/standard/tests/file/file_get_contents_file_put_contents_5gb.phpt index 854996a6481e7..dd16e885f6e15 100644 --- a/ext/standard/tests/file/file_get_contents_file_put_contents_5gb.phpt +++ b/ext/standard/tests/file/file_get_contents_file_put_contents_5gb.phpt @@ -2,6 +2,7 @@ Test file_put_contents() and file_get_contents() functions with 5GB string --SKIPIF-- --INI-- diff --git a/ext/tidy/tests/parsing_file_too_large.phpt b/ext/tidy/tests/parsing_file_too_large.phpt index a0118ba9ebdce..4db089fc939a4 100644 --- a/ext/tidy/tests/parsing_file_too_large.phpt +++ b/ext/tidy/tests/parsing_file_too_large.phpt @@ -4,6 +4,7 @@ Trying to parse a file that is too large (over 4GB) tidy --SKIPIF-- --FILE-- diff --git a/sapi/fpm/tests/proc-idle-timeout.phpt b/sapi/fpm/tests/proc-idle-timeout.phpt index 0c1d10cab8745..313b543b4cb7b 100644 --- a/sapi/fpm/tests/proc-idle-timeout.phpt +++ b/sapi/fpm/tests/proc-idle-timeout.phpt @@ -3,7 +3,7 @@ FPM: Process manager config pm.process_idle_timeout --SKIPIF-- --FILE--