Skip to content

Commit f66f39f

Browse files
committed
Merge branch 'master' of https://[email protected]/apache/commons-lang.git
2 parents 85f9052 + 8d36cab commit f66f39f

File tree

4 files changed

+132
-18
lines changed

4 files changed

+132
-18
lines changed

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ The <action> type attribute can be add,update,fix,remove.
6464
<action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.SystemUtils.IS_OS_NETWARE.</action>
6565
<action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.reflect.MethodUtils.getAccessibleMethod(Class, Method).</action>
6666
<action type="add" dev="ggregory" due-to="Gary Gregory">Add documentation to site for CVE-2025-48924 ClassUtils.getClass(...) can throw a StackOverflowError on very long inputs.</action>
67+
<action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.StringUtils.indexOfAny(CharSequence, int, char...).</action>
6768
<!-- UPDATE -->
6869
<action type="update" dev="ggregory" due-to="Gary Gregory">[test] Bump org.apache.commons:commons-text from 1.13.1 to 1.14.0.</action>
6970
</release>

src/main/java/org/apache/commons/lang3/RuntimeEnvironment.java

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,71 @@ private static boolean fileExists(final String path) {
3636

3737
/**
3838
* Tests whether we are running in a container like Docker or Podman.
39+
* <p>
40+
* <em>The following may change if we find better detection logic.</em>
41+
* </p>
42+
* <p>
43+
* We roughly follow the logic in SystemD:
44+
* </p>
45+
* <p>
46+
* <a href=
47+
* "https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692">https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692</a>
48+
* </p>
49+
* <p>
50+
* We check the `container` environment variable of process 1:
51+
* </p>
52+
* <ol>
53+
* <li>If the variable is empty, we return false. This includes the case, where the container developer wants to hide the fact that the application runs in
54+
* a container.</li>
55+
* <li>If the variable is not empty, we return true.</li>
56+
* <li>If the variable is absent, we continue.</li>
57+
* <li>We check files in the container. According to SystemD:/
58+
* <ol>
59+
* <li>/.dockerenv is used by Docker.</li>
60+
* <li>/run/.containerenv is used by PodMan.</li>
61+
* </ol>
62+
* </li>
63+
* </ol>
3964
*
40-
* @return whether we are running in a container like Docker or Podman. Never null
65+
* @return whether we are running in a container like Docker or Podman. Never null.
66+
* @see <a href="https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692">SystemD virt.c</a>
4167
*/
4268
public static Boolean inContainer() {
4369
return inContainer(StringUtils.EMPTY);
4470
}
4571

72+
/**
73+
* Tests whether we are running in a container like Docker or Podman.
74+
* <p>
75+
* <em>The following may change if we find better detection logic.</em>
76+
* </p>
77+
* <p>
78+
* We roughly follow the logic in SystemD:
79+
* </p>
80+
* <p>
81+
* <a href=
82+
* "https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692">https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692</a>
83+
* </p>
84+
* <p>
85+
* We check the `container` environment variable of process 1:
86+
* </p>
87+
* <ol>
88+
* <li>If the variable is empty, we return false. This includes the case, where the container developer wants to hide the fact that the application runs in
89+
* a container.</li>
90+
* <li>If the variable is not empty, we return true.</li>
91+
* <li>If the variable is absent, we continue.</li>
92+
* <li>We check files in the container. According to SystemD:/
93+
* <ol>
94+
* <li>/.dockerenv is used by Docker.</li>
95+
* <li>/run/.containerenv is used by PodMan.</li>
96+
* </ol>
97+
* </li>
98+
* </ol>
99+
*
100+
* @return Whether we are running in a container like Docker or Podman.
101+
* @see <a href="https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692">SystemD virt.c</a>
102+
*/
46103
static boolean inContainer(final String dirPrefix) {
47-
/*
48-
Roughly follow the logic in SystemD:
49-
https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692
50-
51-
We check the `container` environment variable of process 1:
52-
If the variable is empty, we return false. This includes the case, where the container developer wants to hide the fact that the application runs in a container.
53-
If the variable is not empty, we return true.
54-
If the variable is absent, we continue.
55-
56-
We check files in the container. According to SystemD:
57-
/.dockerenv is used by Docker.
58-
/run/.containerenv is used by PodMan.
59-
60-
*/
61104
final String value = readFile(dirPrefix + "/proc/1/environ", "container");
62105
if (value != null) {
63106
return !value.isEmpty();
@@ -79,12 +122,14 @@ private static String readFile(final String envVarFile, final String key) {
79122
// Split by null byte character
80123
final String[] lines = content.split(String.valueOf(CharUtils.NUL));
81124
final String prefix = key + "=";
125+
// @formatter:off
82126
return Arrays.stream(lines)
83127
.filter(line -> line.startsWith(prefix))
84128
.map(line -> line.split("=", 2))
85129
.map(keyValue -> keyValue[1])
86130
.findFirst()
87131
.orElse(null);
132+
// @formatter:on
88133
} catch (final IOException e) {
89134
return null;
90135
}

src/main/java/org/apache/commons/lang3/StringUtils.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2705,14 +2705,44 @@ public static int indexOf(final CharSequence seq, final int searchChar, final in
27052705
* @since 3.0 Changed signature from indexOfAny(String, char[]) to indexOfAny(CharSequence, char...)
27062706
*/
27072707
public static int indexOfAny(final CharSequence cs, final char... searchChars) {
2708+
return indexOfAny(cs, 0, searchChars);
2709+
}
2710+
2711+
/**
2712+
* Search a CharSequence to find the first index of any character in the given set of characters.
2713+
*
2714+
* <p>
2715+
* A {@code null} String will return {@code -1}. A {@code null} or zero length search array will return {@code -1}.
2716+
* </p>
2717+
* <p>
2718+
* The following is the same as {@code indexOfAny(cs, 0, searchChars)}.
2719+
* </p>
2720+
* <pre>
2721+
* StringUtils.indexOfAny(null, 0, *) = -1
2722+
* StringUtils.indexOfAny("", 0, *) = -1
2723+
* StringUtils.indexOfAny(*, 0, null) = -1
2724+
* StringUtils.indexOfAny(*, 0, []) = -1
2725+
* StringUtils.indexOfAny("zzabyycdxx", 0, ['z', 'a']) = 0
2726+
* StringUtils.indexOfAny("zzabyycdxx", 0, ['b', 'y']) = 3
2727+
* StringUtils.indexOfAny("aba", 0, ['z']) = -1
2728+
* </pre>
2729+
*
2730+
* @param cs the CharSequence to check, may be null.
2731+
* @param csStart Start searching the input {@code cs} at this index.
2732+
* @param searchChars the chars to search for, may be null.
2733+
* @return the index of any of the chars, -1 if no match or null input.
2734+
* @since 2.0
2735+
* @since 3.0 Changed signature from indexOfAny(String, char[]) to indexOfAny(CharSequence, char...)
2736+
*/
2737+
public static int indexOfAny(final CharSequence cs, final int csStart, final char... searchChars) {
27082738
if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) {
27092739
return INDEX_NOT_FOUND;
27102740
}
27112741
final int csLen = cs.length();
27122742
final int csLast = csLen - 1;
27132743
final int searchLen = searchChars.length;
27142744
final int searchLast = searchLen - 1;
2715-
for (int i = 0; i < csLen; i++) {
2745+
for (int i = csStart; i < csLen; i++) {
27162746
final char ch = cs.charAt(i);
27172747
for (int j = 0; j < searchLen; j++) {
27182748
if (searchChars[j] == ch) {

src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,16 +388,54 @@ void testIndexOfAny_StringCharArray() {
388388
assertEquals(-1, StringUtils.indexOfAny(null, (char[]) null));
389389
assertEquals(-1, StringUtils.indexOfAny(null, new char[0]));
390390
assertEquals(-1, StringUtils.indexOfAny(null, 'a', 'b'));
391-
392391
assertEquals(-1, StringUtils.indexOfAny("", (char[]) null));
393392
assertEquals(-1, StringUtils.indexOfAny("", new char[0]));
394393
assertEquals(-1, StringUtils.indexOfAny("", 'a', 'b'));
395-
396394
assertEquals(-1, StringUtils.indexOfAny("zzabyycdxx", (char[]) null));
397395
assertEquals(-1, StringUtils.indexOfAny("zzabyycdxx", new char[0]));
398396
assertEquals(0, StringUtils.indexOfAny("zzabyycdxx", 'z', 'a'));
399397
assertEquals(3, StringUtils.indexOfAny("zzabyycdxx", 'b', 'y'));
400398
assertEquals(-1, StringUtils.indexOfAny("ab", 'z'));
399+
// if more than one search char is present, the order of the search chars matters:
400+
assertEquals(0, StringUtils.indexOfAny("abcd", 'a', 'b', 'c', 'd'));
401+
assertEquals(0, StringUtils.indexOfAny("bcda", 'a', 'b', 'c', 'd'));
402+
assertEquals(0, StringUtils.indexOfAny("cbda", 'a', 'b', 'c', 'd'));
403+
}
404+
405+
@Test
406+
void testIndexOfAny_StringIntCharArray() {
407+
// default cases
408+
assertEquals(-1, StringUtils.indexOfAny(null, 0, (char[]) null));
409+
assertEquals(-1, StringUtils.indexOfAny(null, 0, new char[0]));
410+
assertEquals(-1, StringUtils.indexOfAny(null, 0, 'a', 'b'));
411+
assertEquals(-1, StringUtils.indexOfAny("", 0, (char[]) null));
412+
assertEquals(-1, StringUtils.indexOfAny("", 0, new char[0]));
413+
assertEquals(-1, StringUtils.indexOfAny("", 0, 'a', 'b'));
414+
assertEquals(-1, StringUtils.indexOfAny("zzabyycdxx", 0, (char[]) null));
415+
assertEquals(-1, StringUtils.indexOfAny("zzabyycdxx", 0, new char[0]));
416+
assertEquals(0, StringUtils.indexOfAny("zzabyycdxx", 0, 'z', 'a'));
417+
assertEquals(3, StringUtils.indexOfAny("zzabyycdxx", 0, 'b', 'y'));
418+
assertEquals(-1, StringUtils.indexOfAny("ab", 0, 'z'));
419+
// if more than one search char is present, the order of the search chars matters:
420+
assertEquals(0, StringUtils.indexOfAny("abcd", 0, 'a', 'b', 'c', 'd'));
421+
assertEquals(0, StringUtils.indexOfAny("bcda", 0, 'a', 'b', 'c', 'd'));
422+
assertEquals(0, StringUtils.indexOfAny("cbda", 0, 'a', 'b', 'c', 'd'));
423+
// Actually use the index
424+
assertEquals(-1, StringUtils.indexOfAny(null, 1, (char[]) null));
425+
assertEquals(-1, StringUtils.indexOfAny(null, 1, new char[0]));
426+
assertEquals(-1, StringUtils.indexOfAny(null, 1, 'a', 'b'));
427+
assertEquals(-1, StringUtils.indexOfAny("", 1, (char[]) null));
428+
assertEquals(-1, StringUtils.indexOfAny("", 1, new char[0]));
429+
assertEquals(-1, StringUtils.indexOfAny("", 1, 'a', 'b'));
430+
assertEquals(-1, StringUtils.indexOfAny("zzabyycdxx", 1, (char[]) null));
431+
assertEquals(-1, StringUtils.indexOfAny("zzabyycdxx", 1, new char[0]));
432+
assertEquals(1, StringUtils.indexOfAny("zzabyycdxx", 1, 'z', 'a'));
433+
assertEquals(3, StringUtils.indexOfAny("zzabyycdxx", 1, 'b', 'y'));
434+
assertEquals(-1, StringUtils.indexOfAny("ab", 1, 'z'));
435+
// if more than one search char is present, the order of the search chars matters:
436+
assertEquals(1, StringUtils.indexOfAny("abcd", 1, 'a', 'b', 'c', 'd'));
437+
assertEquals(1, StringUtils.indexOfAny("bcda", 1, 'a', 'b', 'c', 'd'));
438+
assertEquals(1, StringUtils.indexOfAny("cbda", 1, 'a', 'b', 'c', 'd'));
401439
}
402440

403441
/**

0 commit comments

Comments
 (0)