From 2ae08dcf660281707dc2ffa3ab4f45c2018b6a0f Mon Sep 17 00:00:00 2001 From: Arian Faurtosh Date: Fri, 30 Jan 2026 16:37:14 -0800 Subject: [PATCH] Enforce size limit when paged searches are enabled When paging is enabled and the requested size exceeds 126, the client now correctly stops fetching additional pages once the size limit is reached. --- History.rdoc | 5 +++++ lib/net/ldap/connection.rb | 5 +++++ test/test_ldap_connection.rb | 38 ++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/History.rdoc b/History.rdoc index 919eaf67..d49cf6a1 100644 --- a/History.rdoc +++ b/History.rdoc @@ -1,3 +1,8 @@ +=== Net::LDAP (Unreleased) +* Enforce size limit when paged searches are enabled + When paging is enabled and the requested size exceeds 126, the client now + correctly stops fetching additional pages once the size limit is reached. + === Net::LDAP 0.20.0 * Update test.yml by @HarlemSquirrel in #433 * Add `ostruct` as a dependency to the gemspec by @Ivanov-Anton in #432 diff --git a/lib/net/ldap/connection.rb b/lib/net/ldap/connection.rb index f1a70b18..376cf848 100644 --- a/lib/net/ldap/connection.rb +++ b/lib/net/ldap/connection.rb @@ -510,6 +510,11 @@ def search(args = nil) end end + # Stop paging if we've reached the caller's requested size limit. + # When paging is enabled, we set query_limit=0 (no server-side limit) + # for efficiency, so we must enforce the limit client-side here. + break if size > 0 && n_results >= size + break unless more_pages end # loop diff --git a/test/test_ldap_connection.rb b/test/test_ldap_connection.rb index fdfa418c..74ab486c 100644 --- a/test/test_ldap_connection.rb +++ b/test/test_ldap_connection.rb @@ -353,6 +353,44 @@ def test_invalid_pdu_type Net::LDAP::PDU.new([0, ber]) end end + + # Test that size limit is enforced client-side when paging is enabled. + def test_search_size_limit_stops_paging_early + make_search_entry = lambda { |entry_dn| + ber = Net::BER::BerIdentifiedArray.new([1, [entry_dn, [["uid", ["user"]]]]]) + ber.ber_identifier = Net::LDAP::PDU::SearchReturnedData + [1, ber] + } + + make_search_result = lambda { |cookie = nil| + ber = Net::BER::BerIdentifiedArray.new([Net::LDAP::ResultCodeSuccess, "", ""]) + ber.ber_identifier = Net::LDAP::PDU::SearchResult + return [1, ber] if cookie.nil? + + paging_value = "\x30".b + (126.to_ber + cookie.to_ber).then { |s| s.length.chr + s } + controls = [[Net::LDAP::LDAPControls::PAGED_RESULTS, false, paging_value]] + [1, ber, controls] + } + + # Page 1: 5 entries + result with "more" cookie + # Page 2: 5 entries + result with empty cookie (shouldn't be reached) + page1 = 5.times.map { |i| make_search_entry.call("uid=user#{i}") } + [make_search_result.call("more")] + page2 = 5.times.map { |i| make_search_entry.call("uid=user#{i + 5}") } + [make_search_result.call] + + mock = flexmock("socket") + write_count = 0 + mock.should_receive(:write).and_return { write_count += 1 } + mock.should_receive(:read_ber).and_return(*(page1 + page2)) + + conn = Net::LDAP::Connection.new(socket: mock) + results = [] + conn.search(base: "dc=test", size: 3, paged_searches_supported: true) { |e| results << e } + + # With fix: 5 entries >= size 3, stops after page 1 + # Without fix: would fetch page 2 and get 10 entries + assert_equal 5, results.size + assert_equal 1, write_count, "Should stop paging when size limit reached" + end end class TestLDAPConnectionErrors < Test::Unit::TestCase