From 4e82f236a31c6bcf0a653ddd47a0d7dd01dd9c5c Mon Sep 17 00:00:00 2001 From: PurHur Date: Sun, 26 Jul 2026 02:46:54 +0000 Subject: [PATCH] Stdlib: ldap_compare/parse_result/get_dn/attribute walk (#22177) Expose Zend compare + result-parse + DN/attribute helpers over existing libldap FFI (php-src ext/ldap/ldap.c); PHP-in-PHP only. Co-authored-by: Cursor --- docs/bootstrap-inventory.md | 134 ++++---- ext/ldap/Module.php | 8 + ext/ldap/VmLdapCore.php | 142 +++++++++ ext/ldap/VmLdapNative.php | 114 +++++++ ext/ldap/VmLdapResult.php | 20 +- ext/ldap/ldap_result_builtins.php | 293 ++++++++++++++++++ .../cases/ext/ldap_compare_parse.phpt | 59 ++++ ..._gap_ldap_compare_parse_result_missing.php | 41 +++ 8 files changed, 754 insertions(+), 57 deletions(-) create mode 100644 ext/ldap/ldap_result_builtins.php create mode 100644 test/compliance/cases/ext/ldap_compare_parse.phpt create mode 100644 test/repro/maintainer_gap_ldap_compare_parse_result_missing.php diff --git a/docs/bootstrap-inventory.md b/docs/bootstrap-inventory.md index 76d2002046..52f173432f 100644 --- a/docs/bootstrap-inventory.md +++ b/docs/bootstrap-inventory.md @@ -8,11 +8,11 @@ Regenerate: `php script/bootstrap-inventory.php` | Metric | Count | |--------|------:| -| PHP files on vm.php path | 6563 | -| Phase A inventory files (M2 ratio SSOT) | 6563 | +| PHP files on vm.php path | 6564 | +| Phase A inventory files (M2 ratio SSOT) | 6564 | | Phase A ratio-deferred paths | 0 | | Source constructs flagged (blockers) | 0 | -| Source constructs flagged (warnings) | 21140 | +| Source constructs flagged (warnings) | 21157 | ## Compiler CFG gaps (`lib/Compiler.php`) @@ -1180,7 +1180,7 @@ Rank live CFG gaps across inventory files: `php script/bootstrap-inventory-triag | `ext/ldap/LdapEscapeJitHelper.php` | 0 | 1 | | `ext/ldap/LdapExtensionPolicy.php` | 0 | 1 | | `ext/ldap/LdapSerializeDeny.php` | 0 | 3 | -| `ext/ldap/Module.php` | 0 | 47 | +| `ext/ldap/Module.php` | 0 | 54 | | `ext/ldap/VmLdapArg.php` | 0 | 1 | | `ext/ldap/VmLdapConnection.php` | 0 | 4 | | `ext/ldap/VmLdapCore.php` | 0 | 4 | @@ -1196,6 +1196,7 @@ Rank live CFG gaps across inventory files: `php script/bootstrap-inventory-triag | `ext/ldap/ldap_exop_builtins.php` | 0 | 6 | | `ext/ldap/ldap_link_builtins.php` | 0 | 11 | | `ext/ldap/ldap_modify_builtins.php` | 0 | 7 | +| `ext/ldap/ldap_result_builtins.php` | 0 | 10 | | `ext/ldap/ldap_search_builtins.php` | 0 | 8 | | `ext/libxml/BuiltinClasses.php` | 0 | 1 | | `ext/libxml/LibxmlConstants.php` | 0 | 1 | @@ -15802,51 +15803,58 @@ Rank live CFG gaps across inventory files: `php script/bootstrap-inventory-triag **Warnings** (review for bootstrap subset): - new VM\Variable (line 31) -- new ldap_escape (line 57) -- new ldap_dn2ufn (line 58) -- new ldap_explode_dn (line 59) -- new ldap_connect (line 60) -- new ldap_bind (line 61) -- new ldap_bind_ext (line 62) -- new ldap_unbind (line 63) -- new ldap_close (line 64) -- new ldap_errno (line 65) -- new ldap_error (line 66) -- new ldap_err2str (line 67) -- new ldap_set_option (line 68) -- new ldap_get_option (line 69) -- new ldap_start_tls (line 70) -- new ldap_search (line 71) -- new ldap_list (line 72) -- new ldap_read (line 73) -- new ldap_count_entries (line 74) -- new ldap_get_entries (line 75) -- new ldap_first_entry (line 76) -- new ldap_next_entry (line 77) -- new ldap_get_attributes (line 78) -- new ldap_free_result (line 79) -- new ldap_exop (line 80) -- new ldap_exop_sync (line 81) -- new ldap_parse_exop (line 82) -- new ldap_exop_whoami (line 83) -- new ldap_exop_refresh (line 84) -- new ldap_exop_passwd (line 85) -- new ldap_mod_add (line 86) -- new ldap_mod_replace (line 87) -- new ldap_mod_del (line 88) -- new ldap_modify (line 89) -- new ldap_add (line 90) -- new ldap_delete (line 91) -- new ldap_modify_batch (line 92) -- new ldap_mod_batch (line 93) -- new ldap_add_ext (line 94) -- new ldap_delete_ext (line 95) -- new ldap_rename_ext (line 96) -- new ldap_mod_add_ext (line 97) -- new ldap_mod_del_ext (line 98) -- new ldap_mod_replace_ext (line 99) -- new ldap_rename (line 100) -- new ldap_connect_wallet (line 104) +- new ldap_escape (line 58) +- new ldap_dn2ufn (line 59) +- new ldap_explode_dn (line 60) +- new ldap_connect (line 61) +- new ldap_bind (line 62) +- new ldap_bind_ext (line 63) +- new ldap_unbind (line 64) +- new ldap_close (line 65) +- new ldap_errno (line 66) +- new ldap_error (line 67) +- new ldap_err2str (line 68) +- new ldap_set_option (line 69) +- new ldap_get_option (line 70) +- new ldap_start_tls (line 71) +- new ldap_search (line 72) +- new ldap_list (line 73) +- new ldap_read (line 74) +- new ldap_count_entries (line 75) +- new ldap_get_entries (line 76) +- new ldap_first_entry (line 77) +- new ldap_next_entry (line 78) +- new ldap_get_attributes (line 79) +- new ldap_free_result (line 80) +- new ldap_compare (line 81) +- new ldap_parse_result (line 82) +- new ldap_get_dn (line 83) +- new ldap_first_attribute (line 84) +- new ldap_next_attribute (line 85) +- new ldap_get_values (line 86) +- new ldap_get_values_len (line 87) +- new ldap_exop (line 88) +- new ldap_exop_sync (line 89) +- new ldap_parse_exop (line 90) +- new ldap_exop_whoami (line 91) +- new ldap_exop_refresh (line 92) +- new ldap_exop_passwd (line 93) +- new ldap_mod_add (line 94) +- new ldap_mod_replace (line 95) +- new ldap_mod_del (line 96) +- new ldap_modify (line 97) +- new ldap_add (line 98) +- new ldap_delete (line 99) +- new ldap_modify_batch (line 100) +- new ldap_mod_batch (line 101) +- new ldap_add_ext (line 102) +- new ldap_delete_ext (line 103) +- new ldap_rename_ext (line 104) +- new ldap_mod_add_ext (line 105) +- new ldap_mod_del_ext (line 106) +- new ldap_mod_replace_ext (line 107) +- new ldap_rename (line 108) +- new ldap_connect_wallet (line 112) - 3 class method(s) ### `ext/ldap/VmLdapArg.php` @@ -15865,10 +15873,10 @@ Rank live CFG gaps across inventory files: `php script/bootstrap-inventory-triag ### `ext/ldap/VmLdapCore.php` **Warnings** (review for bootstrap subset): -- new HashTable (line 178) -- new Variable (line 190) -- new Variable (line 201) -- 9 class method(s) +- new HashTable (line 320) +- new Variable (line 332) +- new Variable (line 343) +- 15 class method(s) ### `ext/ldap/VmLdapDn.php` @@ -15892,7 +15900,7 @@ Rank live CFG gaps across inventory files: `php script/bootstrap-inventory-triag ### `ext/ldap/VmLdapNative.php` **Warnings** (review for bootstrap subset): -- 42 class method(s) +- 46 class method(s) ### `ext/ldap/VmLdapResult.php` @@ -15902,8 +15910,8 @@ Rank live CFG gaps across inventory files: `php script/bootstrap-inventory-triag - new ObjectEntry (line 48) - new Variable (line 55) - new ObjectEntry (line 64) -- new Variable (line 71) -- 9 class method(s) +- new Variable (line 72) +- 11 class method(s) ### `ext/ldap/ldap_connect.php` @@ -15966,6 +15974,20 @@ Rank live CFG gaps across inventory files: `php script/bootstrap-inventory-triag - new ArgumentCountError (line 467) - 43 class method(s) +### `ext/ldap/ldap_result_builtins.php` + +**Warnings** (review for bootstrap subset): +- new ArgumentCountError (line 31) +- new ArgumentCountError (line 69) +- new HashTable (line 99) +- new Variable (line 101) +- new ArgumentCountError (line 128) +- new ArgumentCountError (line 163) +- new ArgumentCountError (line 198) +- new ArgumentCountError (line 233) +- new ArgumentCountError (line 270) +- 14 class method(s) + ### `ext/ldap/ldap_search_builtins.php` **Warnings** (review for bootstrap subset): diff --git a/ext/ldap/Module.php b/ext/ldap/Module.php index c40f70cea1..6dcdc483ac 100644 --- a/ext/ldap/Module.php +++ b/ext/ldap/Module.php @@ -52,6 +52,7 @@ public function getFunctions(): array require_once __DIR__.'/ldap_exop_builtins.php'; require_once __DIR__.'/ldap_modify_builtins.php'; require_once __DIR__.'/ldap_dn_builtins.php'; + require_once __DIR__.'/ldap_result_builtins.php'; $fns = [ new ldap_escape(), @@ -77,6 +78,13 @@ public function getFunctions(): array new ldap_next_entry(), new ldap_get_attributes(), new ldap_free_result(), + new ldap_compare(), + new ldap_parse_result(), + new ldap_get_dn(), + new ldap_first_attribute(), + new ldap_next_attribute(), + new ldap_get_values(), + new ldap_get_values_len(), new ldap_exop(), new ldap_exop_sync(), new ldap_parse_exop(), diff --git a/ext/ldap/VmLdapCore.php b/ext/ldap/VmLdapCore.php index 74c43995ed..61db507956 100644 --- a/ext/ldap/VmLdapCore.php +++ b/ext/ldap/VmLdapCore.php @@ -170,6 +170,148 @@ public static function getAttributesMap(ObjectEntry $connection, ObjectEntry $en return self::importPhpArray($out); } + /** + * ldap_compare → true / false / -1 (php-src; #22177). + */ + public static function compare( + ObjectEntry $connection, + string $dn, + string $attribute, + string $value + ): bool|int { + $ld = VmLdapConnection::native($connection); + $rc = VmLdapNative::compareExtSync($ld, $dn, $attribute, $value); + VmLdapConnection::setErrno($connection, $rc); + if (VmLdapNative::LDAP_COMPARE_TRUE === $rc) { + return true; + } + if (VmLdapNative::LDAP_COMPARE_FALSE === $rc) { + return false; + } + @\trigger_error('ldap_compare(): Compare: '.VmLdapNative::err2string($rc), \E_USER_WARNING); + + return -1; + } + + /** + * @return string|false + */ + public static function getDn(ObjectEntry $connection, ObjectEntry $entry): string|false + { + $ld = VmLdapConnection::native($connection); + $dn = VmLdapNative::getDn($ld, VmLdapResult::entryNative($entry)); + if (null === $dn) { + return false; + } + + return $dn; + } + + /** + * php-src ldap_get_values / ldap_get_values_len array shape. + * + * @return Variable|false + */ + public static function getValues(ObjectEntry $connection, ObjectEntry $entry, string $attribute): Variable|false + { + $ld = VmLdapConnection::native($connection); + $values = VmLdapNative::getValuesLen($ld, VmLdapResult::entryNative($entry), $attribute); + // OpenLDAP returns NULL on missing attr — treat empty as failure like php-src when errno set. + if ([] === $values) { + $errno = VmLdapNative::getOptionInt($ld, VmLdapNative::LDAP_OPT_ERROR_NUMBER)['value']; + if (VmLdapNative::LDAP_SUCCESS !== $errno && 0 !== $errno) { + @\trigger_error( + 'ldap_get_values(): Cannot get the value(s) of attribute '.VmLdapNative::err2string($errno), + \E_USER_WARNING + ); + + return false; + } + } + $out = ['count' => \count($values)]; + foreach ($values as $i => $val) { + $out[$i] = $val; + } + + return self::importPhpArray($out); + } + + /** + * @return string|false + */ + public static function firstAttribute(ObjectEntry $connection, ObjectEntry $entry): string|false + { + $ld = VmLdapConnection::native($connection); + $info = VmLdapNative::firstAttribute($ld, VmLdapResult::entryNative($entry)); + VmLdapResult::setEntryBer($entry, $info['ber']); + if (null === $info['attr']) { + return false; + } + + return $info['attr']; + } + + /** + * @return string|false + */ + public static function nextAttribute(ObjectEntry $connection, ObjectEntry $entry): string|false + { + $ber = VmLdapResult::entryBer($entry); + if (null === $ber) { + @\trigger_error( + 'ldap_next_attribute(): Called before calling ldap_first_attribute() or no attributes found in result entry', + \E_USER_WARNING + ); + + return false; + } + $ld = VmLdapConnection::native($connection); + $attr = VmLdapNative::nextAttribute($ld, VmLdapResult::entryNative($entry), $ber); + if (null === $attr) { + return false; + } + + return $attr; + } + + /** + * @param list $referralsOut filled when wanted + * @return array{ok: bool, errcode: int, matched_dn: string, error_message: string, referrals: list}|false + */ + public static function parseResult( + ObjectEntry $connection, + ObjectEntry $result, + bool $wantMatched, + bool $wantErrmsg, + bool $wantReferrals + ): array|false { + $ld = VmLdapConnection::native($connection); + $parsed = VmLdapNative::parseResult( + $ld, + VmLdapResult::resultNative($result), + $wantMatched, + $wantErrmsg, + $wantReferrals + ); + VmLdapConnection::setErrno($connection, $parsed['errno']); + if (!$parsed['ok']) { + @\trigger_error( + 'ldap_parse_result(): Unable to parse result: '.VmLdapNative::err2string($parsed['errno']), + \E_USER_WARNING + ); + + return false; + } + + return [ + 'ok' => true, + 'errcode' => $parsed['errcode'], + 'matched_dn' => $parsed['matched_dn'], + 'error_message' => $parsed['error_message'], + 'referrals' => $parsed['referrals'], + ]; + } + /** * @param array $value */ diff --git a/ext/ldap/VmLdapNative.php b/ext/ldap/VmLdapNative.php index ee37efc9f7..a5110cd9f7 100644 --- a/ext/ldap/VmLdapNative.php +++ b/ext/ldap/VmLdapNative.php @@ -533,6 +533,119 @@ public static function getDn(\FFI\CData $ld, \FFI\CData $entry): ?string return $str; } + /** + * ldap_compare_ext_s (php-src ldap_compare; #22177). + * + * Returns LDAP_COMPARE_TRUE (0x06), LDAP_COMPARE_FALSE (0x05), or other errno. + */ + public static function compareExtSync(\FFI\CData $ld, string $dn, string $attribute, string $value): int + { + $ffi = self::requireFfi(); + try { + $bv = self::newBerValue($value); + + return (int) $ffi->ldap_compare_ext_s($ld, $dn, $attribute, \FFI::addr($bv), null, null); + } catch (\Throwable) { + return -1; + } + } + + public const LDAP_COMPARE_FALSE = 0x05; + + public const LDAP_COMPARE_TRUE = 0x06; + + /** + * @return array{ok: bool, errno: int, errcode: int, matched_dn: string, error_message: string, referrals: list} + */ + public static function parseResult(\FFI\CData $ld, \FFI\CData $res, bool $wantMatched, bool $wantErrmsg, bool $wantReferrals): array + { + $ffi = self::requireFfi(); + $errcode = $ffi->new('int'); + $matchedp = $wantMatched ? $ffi->new('char*[1]') : null; + $errmsgp = $wantErrmsg ? $ffi->new('char*[1]') : null; + $refsp = $wantReferrals ? $ffi->new('char**[1]') : null; + if (null !== $matchedp) { + $matchedp[0] = null; + } + if (null !== $errmsgp) { + $errmsgp[0] = null; + } + if (null !== $refsp) { + $refsp[0] = null; + } + $rc = (int) $ffi->ldap_parse_result( + $ld, + $res, + \FFI::addr($errcode), + null !== $matchedp ? $matchedp : null, + null !== $errmsgp ? $errmsgp : null, + null !== $refsp ? $refsp : null, + null, + 0 + ); + $matched = ''; + $errmsg = ''; + $referrals = []; + if (null !== $matchedp && null !== $matchedp[0]) { + $matched = self::ffiString($matchedp[0]); + $ffi->ldap_memfree($matchedp[0]); + } + if (null !== $errmsgp && null !== $errmsgp[0]) { + $errmsg = self::ffiString($errmsgp[0]); + $ffi->ldap_memfree($errmsgp[0]); + } + if (null !== $refsp && null !== $refsp[0]) { + $refs = $refsp[0]; + for ($i = 0; ; ++$i) { + $ptr = $refs[$i]; + if (null === $ptr) { + break; + } + $referrals[] = self::ffiString($ptr); + } + $ffi->ldap_memvfree($refs); + } + + return [ + 'ok' => self::LDAP_SUCCESS === $rc, + 'errno' => $rc, + 'errcode' => (int) $errcode->cdata, + 'matched_dn' => $matched, + 'error_message' => $errmsg, + 'referrals' => $referrals, + ]; + } + + /** + * @return array{attr: ?string, ber: \FFI\CData} ber is void* BerElement* + */ + public static function firstAttribute(\FFI\CData $ld, \FFI\CData $entry): array + { + $ffi = self::requireFfi(); + $ber = $ffi->new('void*'); + $attr = $ffi->ldap_first_attribute($ld, $entry, \FFI::addr($ber)); + if (null === $attr) { + return ['attr' => null, 'ber' => $ber]; + } + $str = self::ffiString($attr); + $ffi->ldap_memfree($attr); + + return ['attr' => $str, 'ber' => $ber]; + } + + public static function nextAttribute(\FFI\CData $ld, \FFI\CData $entry, \FFI\CData $ber): ?string + { + $ffi = self::requireFfi(); + $attr = $ffi->ldap_next_attribute($ld, $entry, $ber); + if (null === $attr) { + return null; + } + $str = self::ffiString($attr); + $ffi->ldap_memfree($attr); + + return $str; + } + /** * @return list */ @@ -915,6 +1028,7 @@ private static function ffi() int ldap_simple_bind_s(LDAP *ld, const char *who, const char *passwd); int ldap_sasl_bind(LDAP *ld, const char *dn, const char *mechanism, BerValue *cred, void *serverctrls, void *clientctrls, int *msgidp); int ldap_search_ext_s(LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly, void *serverctrls, void *clientctrls, timeval *timeout, int sizelimit, LDAPMessage **res); +int ldap_compare_ext_s(LDAP *ld, const char *dn, const char *attr, BerValue *bvalue, void *serverctrls, void *clientctrls); int ldap_count_entries(LDAP *ld, LDAPMessage *res); LDAPMessage *ldap_first_entry(LDAP *ld, LDAPMessage *res); LDAPMessage *ldap_next_entry(LDAP *ld, LDAPMessage *entry); diff --git a/ext/ldap/VmLdapResult.php b/ext/ldap/VmLdapResult.php index e14254b07d..4232451c0a 100644 --- a/ext/ldap/VmLdapResult.php +++ b/ext/ldap/VmLdapResult.php @@ -25,7 +25,7 @@ final class VmLdapResult /** @var array */ private static array $results = []; - /** @var array */ + /** @var array */ private static array $entries = []; public static function registerClasses(Context $ctx): void @@ -67,6 +67,7 @@ public static function wrapEntry(\FFI\CData $native, Context $ctx, ObjectEntry $ 'native' => $native, 'connection_id' => $connection->id, 'result_id' => $resultId, + 'ber' => null, ]; $var = new Variable(Variable::TYPE_OBJECT); $var->object($object); @@ -83,6 +84,23 @@ public static function entryResultId(ObjectEntry $entry): int return (int) self::$entries[$entry->id]['result_id']; } + public static function entryBer(ObjectEntry $entry): ?\FFI\CData + { + if (!isset(self::$entries[$entry->id])) { + return null; + } + + return self::$entries[$entry->id]['ber']; + } + + public static function setEntryBer(ObjectEntry $entry, ?\FFI\CData $ber): void + { + if (!isset(self::$entries[$entry->id])) { + return; + } + self::$entries[$entry->id]['ber'] = $ber; + } + public static function isLiveResult(ObjectEntry $object): bool { return isset(self::$results[$object->id]) && !self::$results[$object->id]['freed']; diff --git a/ext/ldap/ldap_result_builtins.php b/ext/ldap/ldap_result_builtins.php new file mode 100644 index 0000000000..512dfb4e2f --- /dev/null +++ b/ext/ldap/ldap_result_builtins.php @@ -0,0 +1,293 @@ +calledArgs); + if ($argc < 4 || $argc > 5) { + throw new \ArgumentCountError(\sprintf( + 'ldap_compare() expects between 4 and 5 arguments, %d given', + $argc + )); + } + $conn = VmLdapArg::requireConnection($frame->calledArgs[0], 'ldap_compare', 1); + $dn = VmString::coerceStringBuiltinArg($frame->calledArgs[1], 'ldap_compare', 1, 'dn'); + $attr = VmString::coerceStringBuiltinArg($frame->calledArgs[2], 'ldap_compare', 2, 'attribute'); + $value = VmString::coerceStringBuiltinArg($frame->calledArgs[3], 'ldap_compare', 3, 'value'); + // arg 5 ($controls) accepted; ignored in v1. + if (null === $frame->returnVar) { + return; + } + $r = VmLdapCore::compare($conn, $dn, $attr, $value); + if (\is_bool($r)) { + $frame->returnVar->bool($r); + } else { + $frame->returnVar->int($r); + } + } + + public function call(Context $context, JITVariable ...$args): Value + { + throw new \LogicException('ldap_compare() is not implemented for JIT in this compiler build (issue #22177)'); + } +} + +final class ldap_parse_result extends Internal +{ + public function __construct() + { + parent::__construct('ldap_parse_result'); + } + + public function execute(Frame $frame): void + { + $argc = \count($frame->calledArgs); + if ($argc < 3 || $argc > 7) { + throw new \ArgumentCountError(\sprintf( + 'ldap_parse_result() expects between 3 and 7 arguments, %d given', + $argc + )); + } + $conn = VmLdapArg::requireConnection($frame->calledArgs[0], 'ldap_parse_result', 1); + $result = VmLdapArg::requireResult($frame->calledArgs[1], 'ldap_parse_result', 2); + $parsed = VmLdapCore::parseResult( + $conn, + $result, + $argc > 3, + $argc > 4, + $argc > 5 + ); + if (null === $frame->returnVar) { + return; + } + if (false === $parsed) { + $frame->returnVar->bool(false); + + return; + } + $frame->calledArgs[2]->resolveIndirect()->int($parsed['errcode']); + if ($argc > 3) { + $frame->calledArgs[3]->resolveIndirect()->string($parsed['matched_dn']); + } + if ($argc > 4) { + $frame->calledArgs[4]->resolveIndirect()->string($parsed['error_message']); + } + if ($argc > 5) { + $ht = new HashTable(); + foreach ($parsed['referrals'] as $i => $ref) { + $slot = new Variable(); + $slot->string($ref); + $ht->addIndex($i, $slot); + } + $frame->calledArgs[5]->resolveIndirect()->array($ht); + } + // arg 7 ($controls by ref) accepted; population deferred. + $frame->returnVar->bool(true); + } + + public function call(Context $context, JITVariable ...$args): Value + { + throw new \LogicException('ldap_parse_result() is not implemented for JIT in this compiler build (issue #22177)'); + } +} + +final class ldap_get_dn extends Internal +{ + public function __construct() + { + parent::__construct('ldap_get_dn'); + } + + public function execute(Frame $frame): void + { + $argc = \count($frame->calledArgs); + if (2 !== $argc) { + throw new \ArgumentCountError(\sprintf( + 'ldap_get_dn() expects exactly 2 arguments, %d given', + $argc + )); + } + $conn = VmLdapArg::requireConnection($frame->calledArgs[0], 'ldap_get_dn', 1); + $entry = VmLdapArg::requireEntry($frame->calledArgs[1], 'ldap_get_dn', 2); + if (null === $frame->returnVar) { + return; + } + $dn = VmLdapCore::getDn($conn, $entry); + if (false === $dn) { + $frame->returnVar->bool(false); + } else { + $frame->returnVar->string($dn); + } + } + + public function call(Context $context, JITVariable ...$args): Value + { + throw new \LogicException('ldap_get_dn() is not implemented for JIT in this compiler build (issue #22177)'); + } +} + +final class ldap_first_attribute extends Internal +{ + public function __construct() + { + parent::__construct('ldap_first_attribute'); + } + + public function execute(Frame $frame): void + { + $argc = \count($frame->calledArgs); + if (2 !== $argc) { + throw new \ArgumentCountError(\sprintf( + 'ldap_first_attribute() expects exactly 2 arguments, %d given', + $argc + )); + } + $conn = VmLdapArg::requireConnection($frame->calledArgs[0], 'ldap_first_attribute', 1); + $entry = VmLdapArg::requireEntry($frame->calledArgs[1], 'ldap_first_attribute', 2); + if (null === $frame->returnVar) { + return; + } + $attr = VmLdapCore::firstAttribute($conn, $entry); + if (false === $attr) { + $frame->returnVar->bool(false); + } else { + $frame->returnVar->string($attr); + } + } + + public function call(Context $context, JITVariable ...$args): Value + { + throw new \LogicException('ldap_first_attribute() is not implemented for JIT in this compiler build (issue #22177)'); + } +} + +final class ldap_next_attribute extends Internal +{ + public function __construct() + { + parent::__construct('ldap_next_attribute'); + } + + public function execute(Frame $frame): void + { + $argc = \count($frame->calledArgs); + if (2 !== $argc) { + throw new \ArgumentCountError(\sprintf( + 'ldap_next_attribute() expects exactly 2 arguments, %d given', + $argc + )); + } + $conn = VmLdapArg::requireConnection($frame->calledArgs[0], 'ldap_next_attribute', 1); + $entry = VmLdapArg::requireEntry($frame->calledArgs[1], 'ldap_next_attribute', 2); + if (null === $frame->returnVar) { + return; + } + $attr = VmLdapCore::nextAttribute($conn, $entry); + if (false === $attr) { + $frame->returnVar->bool(false); + } else { + $frame->returnVar->string($attr); + } + } + + public function call(Context $context, JITVariable ...$args): Value + { + throw new \LogicException('ldap_next_attribute() is not implemented for JIT in this compiler build (issue #22177)'); + } +} + +final class ldap_get_values extends Internal +{ + public function __construct() + { + parent::__construct('ldap_get_values'); + } + + public function execute(Frame $frame): void + { + $argc = \count($frame->calledArgs); + if (3 !== $argc) { + throw new \ArgumentCountError(\sprintf( + 'ldap_get_values() expects exactly 3 arguments, %d given', + $argc + )); + } + $conn = VmLdapArg::requireConnection($frame->calledArgs[0], 'ldap_get_values', 1); + $entry = VmLdapArg::requireEntry($frame->calledArgs[1], 'ldap_get_values', 2); + $attr = VmString::coerceStringBuiltinArg($frame->calledArgs[2], 'ldap_get_values', 2, 'attribute'); + if (null === $frame->returnVar) { + return; + } + $values = VmLdapCore::getValues($conn, $entry, $attr); + if (false === $values) { + $frame->returnVar->bool(false); + } else { + $frame->returnVar->copyFrom($values); + } + } + + public function call(Context $context, JITVariable ...$args): Value + { + throw new \LogicException('ldap_get_values() is not implemented for JIT in this compiler build (issue #22177)'); + } +} + +/** Alias of ldap_get_values (php-src ldap.stub.php). */ +final class ldap_get_values_len extends Internal +{ + public function __construct() + { + parent::__construct('ldap_get_values_len'); + } + + public function execute(Frame $frame): void + { + $argc = \count($frame->calledArgs); + if (3 !== $argc) { + throw new \ArgumentCountError(\sprintf( + 'ldap_get_values_len() expects exactly 3 arguments, %d given', + $argc + )); + } + $conn = VmLdapArg::requireConnection($frame->calledArgs[0], 'ldap_get_values_len', 1); + $entry = VmLdapArg::requireEntry($frame->calledArgs[1], 'ldap_get_values_len', 2); + $attr = VmString::coerceStringBuiltinArg($frame->calledArgs[2], 'ldap_get_values_len', 2, 'attribute'); + if (null === $frame->returnVar) { + return; + } + $values = VmLdapCore::getValues($conn, $entry, $attr); + if (false === $values) { + $frame->returnVar->bool(false); + } else { + $frame->returnVar->copyFrom($values); + } + } + + public function call(Context $context, JITVariable ...$args): Value + { + throw new \LogicException('ldap_get_values_len() is not implemented for JIT in this compiler build (issue #22177)'); + } +} diff --git a/test/compliance/cases/ext/ldap_compare_parse.phpt b/test/compliance/cases/ext/ldap_compare_parse.phpt new file mode 100644 index 0000000000..99ae947f61 --- /dev/null +++ b/test/compliance/cases/ext/ldap_compare_parse.phpt @@ -0,0 +1,59 @@ +--TEST-- +stdlib ldap_compare/parse_result/get_dn/attribute helpers (#22177, ext/ldap/ldap.c) +--FILE-- + +--EXPECT-- +ldap_compare=1 +ldap_parse_result=1 +ldap_get_dn=1 +ldap_first_attribute=1 +ldap_next_attribute=1 +ldap_get_values=1 +ldap_get_values_len=1 +compare_bad_conn_typeerror +compare_error_int +get_dn_bad_entry_typeerror +next_attr_bad_entry_typeerror diff --git a/test/repro/maintainer_gap_ldap_compare_parse_result_missing.php b/test/repro/maintainer_gap_ldap_compare_parse_result_missing.php new file mode 100644 index 0000000000..c606b4311a --- /dev/null +++ b/test/repro/maintainer_gap_ldap_compare_parse_result_missing.php @@ -0,0 +1,41 @@ +