From c0fb784eebf9d36a67c736d0428fb3577f2e25bb Mon Sep 17 00:00:00 2001
From: "Neil A. Wilson"
Date: Thu, 12 Dec 2019 16:56:20 -0600
Subject: [PATCH] Fix an issue with percent-decoding of LDAP URLs
Fixed an issue in which LDAP URLs with consecutive percent-encoded
bytes were not decoded correctly.
---
docs/release-notes.html | 12 +-
src/com/unboundid/ldap/sdk/LDAPURL.java | 268 ++++++++----------
.../unboundid/ldap/sdk/LDAPURLTestCase.java | 23 ++
3 files changed, 158 insertions(+), 145 deletions(-)
diff --git a/docs/release-notes.html b/docs/release-notes.html
index e131f91ea..cf00b5e76 100644
--- a/docs/release-notes.html
+++ b/docs/release-notes.html
@@ -13,6 +13,12 @@ Version 4.0.14
+ -
+ Fixed an issue in which LDAP URLs with consecutive percent-encoded bytes were
+ not decoded correctly.
+
+
+
-
Fixed an issue that could cause the LDAP SDK to incorrectly handle data read
from a server when the communication is protected with SASL integrity or
@@ -46,8 +52,8 @@
Version 4.0.14
-
- Added a new a new non-final MockableLDAPConnection class that makes it
- easier to mock an LDAPConnection instance. It implements
+ Added a new non-final MockableLDAPConnection class that makes it easier
+ to mock an LDAPConnection instance. It implements
FullLDAPInterface and wraps a provided LDAPConnection instance.
If you create a MockableLDAPConnection subclass, then you may override
any of the FullLDAPInterface methods to implement your own behavior.
@@ -57,7 +63,7 @@
Version 4.0.14
-
- Fixed a minor typo in the ldapsearch usage information
+ Fixed a minor typo in the ldapsearch usage information.
diff --git a/src/com/unboundid/ldap/sdk/LDAPURL.java b/src/com/unboundid/ldap/sdk/LDAPURL.java
index 36b5936c3..0ab2a83a8 100644
--- a/src/com/unboundid/ldap/sdk/LDAPURL.java
+++ b/src/com/unboundid/ldap/sdk/LDAPURL.java
@@ -23,9 +23,9 @@
import java.io.Serializable;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
+import com.unboundid.util.ByteStringBuffer;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.StaticUtils;
@@ -909,7 +909,7 @@ public static String percentDecode(final String s)
}
int pos = firstPercentPos;
- final StringBuilder buffer = new StringBuilder(2 * length);
+ final ByteStringBuffer buffer = new ByteStringBuffer(2 * length);
buffer.append(s.substring(0, firstPercentPos));
while (pos < length)
@@ -923,152 +923,136 @@ public static String percentDecode(final String s)
ERR_LDAPURL_HEX_STRING_TOO_SHORT.get(s));
}
-
- final ByteBuffer byteBuffer = ByteBuffer.allocate(length - pos);
- while (pos < length)
+ final byte b;
+ switch (s.charAt(pos++))
{
- final byte b;
- switch (s.charAt(pos++))
- {
- case '0':
- b = 0x00;
- break;
- case '1':
- b = 0x10;
- break;
- case '2':
- b = 0x20;
- break;
- case '3':
- b = 0x30;
- break;
- case '4':
- b = 0x40;
- break;
- case '5':
- b = 0x50;
- break;
- case '6':
- b = 0x60;
- break;
- case '7':
- b = 0x70;
- break;
- case '8':
- b = (byte) 0x80;
- break;
- case '9':
- b = (byte) 0x90;
- break;
- case 'a':
- case 'A':
- b = (byte) 0xA0;
- break;
- case 'b':
- case 'B':
- b = (byte) 0xB0;
- break;
- case 'c':
- case 'C':
- b = (byte) 0xC0;
- break;
- case 'd':
- case 'D':
- b = (byte) 0xD0;
- break;
- case 'e':
- case 'E':
- b = (byte) 0xE0;
- break;
- case 'f':
- case 'F':
- b = (byte) 0xF0;
- break;
- default:
- throw new LDAPException(ResultCode.DECODING_ERROR,
- ERR_LDAPURL_INVALID_HEX_CHAR.get(
- s.charAt(pos-1)));
- }
-
- if (pos >= length)
- {
+ case '0':
+ b = 0x00;
+ break;
+ case '1':
+ b = 0x10;
+ break;
+ case '2':
+ b = 0x20;
+ break;
+ case '3':
+ b = 0x30;
+ break;
+ case '4':
+ b = 0x40;
+ break;
+ case '5':
+ b = 0x50;
+ break;
+ case '6':
+ b = 0x60;
+ break;
+ case '7':
+ b = 0x70;
+ break;
+ case '8':
+ b = (byte) 0x80;
+ break;
+ case '9':
+ b = (byte) 0x90;
+ break;
+ case 'a':
+ case 'A':
+ b = (byte) 0xA0;
+ break;
+ case 'b':
+ case 'B':
+ b = (byte) 0xB0;
+ break;
+ case 'c':
+ case 'C':
+ b = (byte) 0xC0;
+ break;
+ case 'd':
+ case 'D':
+ b = (byte) 0xD0;
+ break;
+ case 'e':
+ case 'E':
+ b = (byte) 0xE0;
+ break;
+ case 'f':
+ case 'F':
+ b = (byte) 0xF0;
+ break;
+ default:
throw new LDAPException(ResultCode.DECODING_ERROR,
- ERR_LDAPURL_HEX_STRING_TOO_SHORT.get(s));
- }
+ ERR_LDAPURL_INVALID_HEX_CHAR.get(
+ s.charAt(pos-1)));
+ }
- switch (s.charAt(pos++))
- {
- case '0':
- byteBuffer.put(b);
- break;
- case '1':
- byteBuffer.put((byte) (b | 0x01));
- break;
- case '2':
- byteBuffer.put((byte) (b | 0x02));
- break;
- case '3':
- byteBuffer.put((byte) (b | 0x03));
- break;
- case '4':
- byteBuffer.put((byte) (b | 0x04));
- break;
- case '5':
- byteBuffer.put((byte) (b | 0x05));
- break;
- case '6':
- byteBuffer.put((byte) (b | 0x06));
- break;
- case '7':
- byteBuffer.put((byte) (b | 0x07));
- break;
- case '8':
- byteBuffer.put((byte) (b | 0x08));
- break;
- case '9':
- byteBuffer.put((byte) (b | 0x09));
- break;
- case 'a':
- case 'A':
- byteBuffer.put((byte) (b | 0x0A));
- break;
- case 'b':
- case 'B':
- byteBuffer.put((byte) (b | 0x0B));
- break;
- case 'c':
- case 'C':
- byteBuffer.put((byte) (b | 0x0C));
- break;
- case 'd':
- case 'D':
- byteBuffer.put((byte) (b | 0x0D));
- break;
- case 'e':
- case 'E':
- byteBuffer.put((byte) (b | 0x0E));
- break;
- case 'f':
- case 'F':
- byteBuffer.put((byte) (b | 0x0F));
- break;
- default:
- throw new LDAPException(ResultCode.DECODING_ERROR,
- ERR_LDAPURL_INVALID_HEX_CHAR.get(
- s.charAt(pos-1)));
- }
+ if (pos >= length)
+ {
+ throw new LDAPException(ResultCode.DECODING_ERROR,
+ ERR_LDAPURL_HEX_STRING_TOO_SHORT.get(s));
+ }
- if ((pos < length) && (s.charAt(pos) != '%'))
- {
+ switch (s.charAt(pos++))
+ {
+ case '0':
+ buffer.append(b);
break;
- }
+ case '1':
+ buffer.append((byte) (b | 0x01));
+ break;
+ case '2':
+ buffer.append((byte) (b | 0x02));
+ break;
+ case '3':
+ buffer.append((byte) (b | 0x03));
+ break;
+ case '4':
+ buffer.append((byte) (b | 0x04));
+ break;
+ case '5':
+ buffer.append((byte) (b | 0x05));
+ break;
+ case '6':
+ buffer.append((byte) (b | 0x06));
+ break;
+ case '7':
+ buffer.append((byte) (b | 0x07));
+ break;
+ case '8':
+ buffer.append((byte) (b | 0x08));
+ break;
+ case '9':
+ buffer.append((byte) (b | 0x09));
+ break;
+ case 'a':
+ case 'A':
+ buffer.append((byte) (b | 0x0A));
+ break;
+ case 'b':
+ case 'B':
+ buffer.append((byte) (b | 0x0B));
+ break;
+ case 'c':
+ case 'C':
+ buffer.append((byte) (b | 0x0C));
+ break;
+ case 'd':
+ case 'D':
+ buffer.append((byte) (b | 0x0D));
+ break;
+ case 'e':
+ case 'E':
+ buffer.append((byte) (b | 0x0E));
+ break;
+ case 'f':
+ case 'F':
+ buffer.append((byte) (b | 0x0F));
+ break;
+ default:
+ throw new LDAPException(ResultCode.DECODING_ERROR,
+ ERR_LDAPURL_INVALID_HEX_CHAR.get(
+ s.charAt(pos-1)));
}
-
- byteBuffer.flip();
- final byte[] byteArray = new byte[byteBuffer.limit()];
- byteBuffer.get(byteArray);
-
- buffer.append(StaticUtils.toUTF8String(byteArray));
}
else
{
diff --git a/tests/unit/src/com/unboundid/ldap/sdk/LDAPURLTestCase.java b/tests/unit/src/com/unboundid/ldap/sdk/LDAPURLTestCase.java
index 3c25d79da..1ec234c8e 100644
--- a/tests/unit/src/com/unboundid/ldap/sdk/LDAPURLTestCase.java
+++ b/tests/unit/src/com/unboundid/ldap/sdk/LDAPURLTestCase.java
@@ -1012,6 +1012,29 @@ public Object[][] getTestValidURLs()
"ldap://server.example.com:389/a=b+c=d,dc=example,dc=com??sub?" +
"(cn=foo%20bar)"
},
+
+ new Object[]
+ {
+ "ldap://child.root.example.com/OU=%25%5E%25%5E%25%5E*,DC=child," +
+ "DC=root,DC=example,DC=com",
+ "ldap",
+ "child.root.example.com",
+ true,
+ 389,
+ false,
+ new DN(new RDN("OU", "%^%^%^*"), new RDN("DC", "child"),
+ new RDN("DC", "root"), new RDN("DC", "example"),
+ new RDN("dc", "com")),
+ true,
+ new String[0],
+ false,
+ SearchScope.BASE,
+ false,
+ Filter.create("(objectClass=*)"),
+ false,
+ "ldap://child.root.example.com:389/ou=%25%5e%25%5e%25%5e*,dc=child," +
+ "dc=root,dc=example,dc=com??base?(objectclass=*)"
+ }
};
}