Skip to content

Commit 838c98e

Browse files
committed
Add more bounds checks to Substring
Otherwise it's possible to access more of the underlying string than the substring covers. Discovered in #24. Not sure if Substring is really worth it at this point.
1 parent bd54c34 commit 838c98e

2 files changed

Lines changed: 113 additions & 4 deletions

File tree

commonmark/src/main/java/org/commonmark/internal/util/Substring.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,21 @@ public static CharSequence of(String base, int beginIndex, int endIndex) {
1414
}
1515

1616
private Substring(String base, int beginIndex, int endIndex) {
17+
if (beginIndex < 0) {
18+
throw new StringIndexOutOfBoundsException("beginIndex must be at least 0");
19+
}
20+
if (endIndex < 0) {
21+
throw new StringIndexOutOfBoundsException("endIndex must be at least 0");
22+
}
23+
if (endIndex < beginIndex) {
24+
throw new StringIndexOutOfBoundsException("endIndex must not be less than beginIndex");
25+
}
26+
if (endIndex > base.length()) {
27+
throw new StringIndexOutOfBoundsException("endIndex must not be greater than length");
28+
}
1729
this.base = base;
1830
this.beginIndex = beginIndex;
1931
this.endIndex = endIndex;
20-
if (endIndex > base.length()) {
21-
throw new IndexOutOfBoundsException("endIndex must not be greater than length");
22-
}
2332
}
2433

2534
@Override
@@ -29,11 +38,20 @@ public int length() {
2938

3039
@Override
3140
public char charAt(int index) {
41+
if (index < 0 || beginIndex + index >= endIndex) {
42+
throw new StringIndexOutOfBoundsException("String index out of range: " + index);
43+
}
3244
return base.charAt(index + beginIndex);
3345
}
3446

3547
@Override
3648
public CharSequence subSequence(int start, int end) {
49+
if (start < 0 || beginIndex + start > endIndex) {
50+
throw new StringIndexOutOfBoundsException("String index out of range: " + start);
51+
}
52+
if (end < 0 || beginIndex + end > endIndex) {
53+
throw new StringIndexOutOfBoundsException("String index out of range: " + end);
54+
}
3755
return new Substring(base, beginIndex + start, beginIndex + end);
3856
}
3957

@@ -49,6 +67,6 @@ public int hashCode() {
4967

5068
@Override
5169
public boolean equals(Object obj) {
52-
return obj == this || (obj instanceof CharSequence && toString().equals(obj));
70+
return obj == this || (obj instanceof CharSequence && toString().equals(obj.toString()));
5371
}
5472
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.commonmark.test;
2+
3+
import org.commonmark.internal.util.Substring;
4+
import org.junit.Test;
5+
6+
import static junit.framework.TestCase.assertEquals;
7+
import static org.junit.Assert.assertNotEquals;
8+
9+
public class SubstringTest {
10+
11+
private final CharSequence substring = Substring.of("abcdefghi", 3, 6);
12+
13+
@Test
14+
public void testConstructEmpty() {
15+
assertEquals("", Substring.of("ab", 0, 0).toString());
16+
assertEquals("", Substring.of("ab", 1, 1).toString());
17+
assertEquals("", Substring.of("ab", 2, 2).toString());
18+
}
19+
20+
@Test(expected = StringIndexOutOfBoundsException.class)
21+
public void testConstructBeginIndexNegative() {
22+
Substring.of("abc", -1, 0);
23+
}
24+
25+
@Test(expected = StringIndexOutOfBoundsException.class)
26+
public void testConstructEndIndexNegative() {
27+
Substring.of("abc", 0, -1);
28+
}
29+
30+
@Test(expected = StringIndexOutOfBoundsException.class)
31+
public void testConstructEndIndexLessThanBegin() {
32+
Substring.of("abc", 1, 0);
33+
}
34+
35+
@Test(expected = StringIndexOutOfBoundsException.class)
36+
public void testConstructEndIndexGreaterThanLength() {
37+
Substring.of("abc", 1, 4);
38+
}
39+
40+
@Test
41+
public void testLength() {
42+
assertEquals(3, substring.length());
43+
}
44+
45+
@Test
46+
public void testCharAt() {
47+
assertEquals('d', substring.charAt(0));
48+
assertEquals('e', substring.charAt(1));
49+
assertEquals('f', substring.charAt(2));
50+
}
51+
52+
@Test(expected = StringIndexOutOfBoundsException.class)
53+
public void testCharAtOutOfBoundsLeft() {
54+
substring.charAt(-1);
55+
}
56+
57+
@Test(expected = StringIndexOutOfBoundsException.class)
58+
public void testCharAtOutOfBoundsRight() {
59+
substring.charAt(3);
60+
}
61+
62+
@Test
63+
public void testSubSequence() {
64+
assertEquals("d", substring.subSequence(0, 1).toString());
65+
assertEquals("e", substring.subSequence(1, 2).toString());
66+
assertEquals("f", substring.subSequence(2, 3).toString());
67+
assertEquals("def", substring.subSequence(0, 3).toString());
68+
}
69+
70+
@Test(expected = StringIndexOutOfBoundsException.class)
71+
public void testSubSequenceOutOfBoundsLeft() {
72+
substring.subSequence(-1, 2);
73+
}
74+
75+
@Test(expected = StringIndexOutOfBoundsException.class)
76+
public void testSubSequenceOutOfBoundsRight() {
77+
substring.subSequence(1, 4);
78+
}
79+
80+
@Test
81+
public void testHashCodeEquals() {
82+
CharSequence a = Substring.of("abcdefghi", 3, 6);
83+
CharSequence b = Substring.of("123def456", 3, 6);
84+
CharSequence other = Substring.of("123de", 3, 5);
85+
assertEquals(a, b);
86+
assertEquals(b, a);
87+
assertNotEquals(a, other);
88+
assertNotEquals(other, a);
89+
assertEquals(a.hashCode(), b.hashCode());
90+
}
91+
}

0 commit comments

Comments
 (0)