diff --git a/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml b/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml
index 24e41ecd4e7ea5..3580fab4ecfe6f 100644
--- a/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml
+++ b/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml
@@ -94,6 +94,7 @@ Note:
cn=admin,dc=example,dc=com
password
ServerBind,None
+ True
localhost
@@ -102,6 +103,7 @@ Note:
cn=admin,dc=example,dc=com
password
ServerBind,None
+ False
danmose-ldap.danmose-domain.com
@@ -110,6 +112,7 @@ Note:
danmose-domain\Administrator
%TESTPASSWORD%
ServerBind,None
+ True
ldap.local
@@ -119,6 +122,7 @@ Note:
password
ServerBind,None
true
+ False
\ No newline at end of file
diff --git a/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs b/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs
index a9156b8efd5bbe..beed82aabcfa62 100644
--- a/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs
+++ b/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs
@@ -10,7 +10,7 @@ namespace System.DirectoryServices.Tests
{
internal class LdapConfiguration
{
- private LdapConfiguration(string serverName, string searchDn, string userName, string password, string port, AuthenticationTypes at, bool useTls)
+ private LdapConfiguration(string serverName, string searchDn, string userName, string password, string port, AuthenticationTypes at, bool useTls, bool supportsServerSideSort)
{
ServerName = serverName;
SearchDn = searchDn;
@@ -19,6 +19,7 @@ private LdapConfiguration(string serverName, string searchDn, string userName, s
Port = port;
AuthenticationTypes = at;
UseTls = useTls;
+ SupportsServerSideSort = supportsServerSideSort;
}
private static LdapConfiguration s_ldapConfiguration = GetConfiguration("LDAP.Configuration.xml");
@@ -32,6 +33,7 @@ private LdapConfiguration(string serverName, string searchDn, string userName, s
internal string SearchDn { get; set; }
internal AuthenticationTypes AuthenticationTypes { get; set; }
internal bool UseTls { get; set; }
+ internal bool SupportsServerSideSort { get; set; }
internal string LdapPath => string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/{SearchDn}" : $"LDAP://{ServerName}:{Port}/{SearchDn}";
internal string RootDSEPath => string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/rootDSE" : $"LDAP://{ServerName}:{Port}/rootDSE";
internal string UserNameWithNoDomain
@@ -107,6 +109,7 @@ internal static LdapConfiguration GetConfiguration(string configFile)
string password = "";
AuthenticationTypes at = AuthenticationTypes.None;
bool useTls = false;
+ bool supportsServerSideSort = false;
XElement child = connection.Element("ServerName");
if (child != null)
@@ -141,6 +144,12 @@ internal static LdapConfiguration GetConfiguration(string configFile)
useTls = bool.Parse(child.Value);
}
+ child = connection.Element("SupportsServerSideSort");
+ if (child != null)
+ {
+ supportsServerSideSort = bool.Parse(child.Value);
+ }
+
child = connection.Element("AuthenticationTypes");
if (child != null)
{
@@ -170,7 +179,7 @@ internal static LdapConfiguration GetConfiguration(string configFile)
at |= AuthenticationTypes.Signing;
}
- ldapConfig = new LdapConfiguration(serverName, searchDn, user, password, port, at, useTls);
+ ldapConfig = new LdapConfiguration(serverName, searchDn, user, password, port, at, useTls, supportsServerSideSort);
}
}
catch (Exception ex)
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj b/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj
index 43c120b0a74a09..33bfe13efee4e1 100644
--- a/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj
@@ -42,6 +42,7 @@
+
Common\DisableRuntimeMarshalling.cs
@@ -58,6 +59,7 @@
+
@@ -76,6 +78,7 @@
+
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Linux.cs
new file mode 100644
index 00000000000000..6119ae0956500f
--- /dev/null
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Linux.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using System.Runtime.Versioning;
+
+namespace System.DirectoryServices.Protocols
+{
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
+ internal partial struct SortKeyInterop
+ {
+ }
+}
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Windows.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Windows.cs
new file mode 100644
index 00000000000000..42ada55602cc65
--- /dev/null
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Windows.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using System.Runtime.Versioning;
+
+namespace System.DirectoryServices.Protocols
+{
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal partial struct SortKeyInterop
+ {
+ }
+}
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.cs
new file mode 100644
index 00000000000000..29ceb3a005522b
--- /dev/null
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using System.Runtime.Versioning;
+
+namespace System.DirectoryServices.Protocols
+{
+ // Declared as partial in order to be able to set the different StructLayout
+ // attributes in the Windows and Linux specific files.
+ // This is a layout-controlled struct, do not alter property ordering.
+ internal partial struct SortKeyInterop
+ {
+ public SortKeyInterop(SortKey sortKey)
+ {
+ if (sortKey == null)
+ throw new ArgumentNullException(nameof(sortKey));
+
+ AttributeName = sortKey.AttributeName;
+ MatchingRule = sortKey.MatchingRule;
+ ReverseOrder = sortKey.ReverseOrder;
+ }
+
+ internal string AttributeName { get; set; }
+
+ internal string MatchingRule { get; set; }
+
+ internal bool ReverseOrder { get; set; }
+ }
+}
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/DirectoryControl.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/DirectoryControl.cs
index bdef26646fedb6..6592f09945136d 100644
--- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/DirectoryControl.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/DirectoryControl.cs
@@ -703,9 +703,15 @@ public SortKey[] SortKeys
public override byte[] GetValue()
{
+ SortKeyInterop[] nativeSortKeys = new SortKeyInterop[_keys.Length];
+ for (int i = 0; i < _keys.Length; ++i)
+ {
+ nativeSortKeys[i] = new SortKeyInterop(_keys[i]);
+ }
+
IntPtr control = IntPtr.Zero;
- int structSize = Marshal.SizeOf(typeof(SortKey));
- int keyCount = _keys.Length;
+ int structSize = Marshal.SizeOf(typeof(SortKeyInterop));
+ int keyCount = nativeSortKeys.Length;
IntPtr memHandle = Utility.AllocHGlobalIntPtrArray(keyCount + 1);
try
@@ -716,7 +722,7 @@ public override byte[] GetValue()
for (i = 0; i < keyCount; i++)
{
sortPtr = Marshal.AllocHGlobal(structSize);
- Marshal.StructureToPtr(_keys[i], sortPtr, false);
+ Marshal.StructureToPtr(nativeSortKeys[i], sortPtr, false);
tempPtr = (IntPtr)((long)memHandle + IntPtr.Size * i);
Marshal.WriteIntPtr(tempPtr, sortPtr);
}
diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs
index f13a864d000187..5e2356366ddc83 100644
--- a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs
@@ -15,6 +15,8 @@ public partial class DirectoryServicesProtocolsTests
internal static bool IsLdapConfigurationExist => LdapConfiguration.Configuration != null;
internal static bool IsActiveDirectoryServer => IsLdapConfigurationExist && LdapConfiguration.Configuration.IsActiveDirectoryServer;
+ internal static bool IsServerSideSortSupported => IsLdapConfigurationExist && LdapConfiguration.Configuration.SupportsServerSideSort;
+
[ConditionalFact(nameof(IsLdapConfigurationExist))]
public void TestInvalidFilter()
{
@@ -533,6 +535,58 @@ public void TestPageRequests()
}
}
+ [ConditionalFact(nameof(IsServerSideSortSupported))]
+ public void TestSortedSearch()
+ {
+ using (LdapConnection connection = GetConnection())
+ {
+ string ouName = "ProtocolsGroup10";
+ string dn = "ou=" + ouName;
+
+ try
+ {
+ for (int i=0; i<10; i++)
+ {
+ DeleteEntry(connection, "ou=ProtocolsSubGroup10." + i + "," + dn);
+ }
+ DeleteEntry(connection, dn);
+
+ AddOrganizationalUnit(connection, dn);
+ SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
+ Assert.NotNull(sre);
+
+ for (int i=0; i<10; i++)
+ {
+ AddOrganizationalUnit(connection, "ou=ProtocolsSubGroup10." + i + "," + dn);
+ }
+
+ string filter = "(objectClass=*)";
+ SearchRequest searchRequest = new SearchRequest(
+ dn + "," + LdapConfiguration.Configuration.SearchDn,
+ filter,
+ SearchScope.Subtree,
+ null);
+
+ var sortRequestControl = new SortRequestControl("ou", true);
+ searchRequest.Controls.Add(sortRequestControl);
+
+ SearchResponse searchResponse = (SearchResponse) connection.SendRequest(searchRequest);
+ Assert.Equal(1, searchResponse.Controls.Length);
+ Assert.True(searchResponse.Controls[0] is SortResponseControl);
+ Assert.True(searchResponse.Entries.Count > 0);
+ Assert.Equal("ou=ProtocolsSubGroup10.9," + dn + "," + LdapConfiguration.Configuration.SearchDn, searchResponse.Entries[0].DistinguishedName);
+ }
+ finally
+ {
+ for (int i=0; i<20; i++)
+ {
+ DeleteEntry(connection, "ou=ProtocolsSubGroup10." + i + "," + dn);
+ }
+ DeleteEntry(connection, dn);
+ }
+ }
+ }
+
private void DeleteAttribute(LdapConnection connection, string entryDn, string attributeName)
{
string dn = entryDn + "," + LdapConfiguration.Configuration.SearchDn;
diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs
index 26bc2ca73f17d3..480b537bab85b6 100644
--- a/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs
@@ -32,11 +32,15 @@ public void Ctor_SortKeys(bool critical)
control.IsCritical = critical;
var expected = (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) ?
+ // WLDAP formatted ASN.1
new byte[] { 48, 132, 0, 0, 0, 43, 48, 132, 0, 0, 0, 17, 4, 5,110,
97, 109, 101, 49, 128, 5, 114, 117, 108, 101, 49, 129,
1, 255, 48, 132, 0, 0, 0, 14, 4, 5, 110, 97, 109, 101,
- 50, 128, 5, 114, 117, 108, 101, 50} :
- new byte[] { 48, 19, 48, 9, 4, 1, 110, 128, 1, 114, 129, 1, 255, 48, 6, 4, 1, 110, 128, 1, 114 };
+ 50, 128, 5, 114, 117, 108, 101, 50 } :
+ // OpenLdap formatted ASN.1
+ new byte[] { 48, 35, 48, 17, 4, 5, 110, 97, 109, 101, 49, 128, 5,
+ 114, 117, 108, 101, 49, 129, 1, 255, 48, 14, 4, 5, 110, 97, 109,
+ 101, 50, 128, 5, 114, 117, 108, 101, 50 };
Assert.Equal(expected, control.GetValue());
}