This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new 86ed42da5 Fix RandomStringUtils.random() false rejection of letters
and digits (#1703)
86ed42da5 is described below
commit 86ed42da5c1ac09888dfe2481aaa06df13fb1a68
Author: alhuda <[email protected]>
AuthorDate: Mon Jun 15 17:27:39 2026 +0530
Fix RandomStringUtils.random() false rejection of letters and digits (#1703)
* fix RandomStringUtils.random false rejection of letters and digits
* reject empty letters && digits ASCII range with a clear error
When letters && digits clamps the range above the alphanumerics
(e.g. ['z'+1, 0x7f)) it collapses to start >= end, which made gap 0
and nextBits(0) throw the unrelated "number of bits must be between
1 and 32". Throw the range-validation IllegalArgumentException there
instead, matching the single-category reachability checks.
---
.../apache/commons/lang3/RandomStringUtils.java | 10 +++++++-
.../commons/lang3/RandomStringUtilsTest.java | 30 ++++++++++++++++++++++
2 files changed, 39 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/apache/commons/lang3/RandomStringUtils.java
b/src/main/java/org/apache/commons/lang3/RandomStringUtils.java
index 40a5dc744..ba2129e40 100644
--- a/src/main/java/org/apache/commons/lang3/RandomStringUtils.java
+++ b/src/main/java/org/apache/commons/lang3/RandomStringUtils.java
@@ -296,7 +296,9 @@ public static String random(int count, int start, int end,
final boolean letters
if (letters && digits && start <= ASCII_0 && end >= ASCII_z + 1) {
return random(count, 0, 0, false, false, ALPHANUMERICAL_CHARS,
random);
}
- if (digits && end <= ASCII_0 || letters && end <= ASCII_A) {
+ // Only reject when none of the requested categories is reachable;
otherwise a letters && digits
+ // request would throw on a range that holds one category but not
the other (e.g. [ASCII_0, ASCII_A)).
+ if ((!digits || end <= ASCII_0) && (!letters || end <= ASCII_A) &&
(digits || letters)) {
throw new IllegalArgumentException(
String.format("Parameter end (%,d) must be greater
than (%,d) for generating digits or greater than (%,d) for generating
letters.", end,
ASCII_0, ASCII_A));
@@ -312,6 +314,12 @@ public static String random(int count, int start, int end,
final boolean letters
if (letters && digits) {
start = Math.max(ASCII_0, start);
end = Math.min(ASCII_z + 1, end);
+ // The clamp can empty the range when it sits above the
alphanumerics (e.g. [ASCII_z + 1, 0x7f)),
+ // unlike the single-category branches below which are
validated by the reachability loops further
+ // down. Reject here so the caller gets a clear range error
instead of nextBits(0) failing later.
+ if (start >= end) {
+ throw new IllegalArgumentException(String.format("No
letters or digits exist between start %,d and end %,d.", start, end));
+ }
} else if (digits) {
// just numbers, no letters
start = Math.max(ASCII_0, start);
diff --git a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java
b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java
index 9afe8ad5f..2c6a29181 100644
--- a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java
@@ -119,6 +119,36 @@ public void testCustomLetterCharsArrayDoesNotThrowIAE() {
}, "RandomStringUtils.random() threw IAE for valid letter chars array
- pre-patch behavior");
}
+ /**
+ * Asking for {@code letters && digits} must never be stricter than asking
for {@code digits} alone. The range
+ * {@code ['0', 'A')} holds the digits but no letters, so {@code
random(count, '0', 'A', true, true, ...)} must
+ * generate digits like the digits-only call over the same range, not
throw IllegalArgumentException.
+ */
+ @Test
+ void testLettersAndDigitsOverDigitOnlyRange() {
+ final String both = RandomStringUtils.random(100, '0', 'A', true,
true, null, new Random(42));
+ assertEquals(100, both.length());
+ for (final char c : both.toCharArray()) {
+ assertTrue(c >= '0' && c <= '9', () -> "Expected a digit but got:
" + c);
+ }
+ // digits alone already works over this range, so letters && digits
must not reject it
+ assertDoesNotThrow(() -> RandomStringUtils.random(100, '0', 'A',
false, true, null, new Random(42)));
+ }
+
+ /**
+ * The {@code letters && digits} ASCII fast path clamps {@code start} up
to {@code '0'} and {@code end} down to
+ * {@code 'z' + 1}. A range sitting entirely above the alphanumerics, e.g.
{@code ['z' + 1, 0x7f)}, collapses to
+ * {@code start >= end} after that clamp. It must throw a clear range
IllegalArgumentException, not fall through to
+ * {@code nextBits(0)} which reports the unrelated "number of bits must be
between 1 and 32".
+ */
+ @Test
+ void testLettersAndDigitsOverEmptyAsciiRange() {
+ final IllegalArgumentException e =
assertThrows(IllegalArgumentException.class,
+ () -> RandomStringUtils.random(10, 'z' + 1, 0x7f, true, true,
null, new Random(42)));
+ assertTrue(e.getMessage() != null && !e.getMessage().contains("number
of bits"),
+ () -> "Expected a range-validation message but got: " +
e.getMessage());
+ }
+
@Test
void testExceptionsRandom() {
assertIllegalArgumentException(() -> RandomStringUtils.random(-1));