1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.text;
19
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertTrue;
23
24 import java.io.ByteArrayInputStream;
25 import java.io.IOException;
26 import java.io.ObjectInputStream;
27 import java.nio.charset.StandardCharsets;
28
29 import org.apache.commons.lang3.CharUtils;
30 import org.apache.commons.lang3.SerializationUtils;
31 import org.junit.jupiter.api.Test;
32
33
34
35
36
37
38
39
40
41
42
43 public class TextStringBuilderClearTest {
44
45
46 private static boolean containsUtf16Be(final byte[] haystack, final String needle) {
47 final byte[] needleBytes = needle.getBytes(StandardCharsets.UTF_16BE);
48 outer: for (int i = 0; i <= haystack.length - needleBytes.length; i++) {
49 for (int j = 0; j < needleBytes.length; j++) {
50 if (haystack[i + j] != needleBytes[j]) {
51 continue outer;
52 }
53 }
54 return true;
55 }
56 return false;
57 }
58
59 @Test
60 void testDeleteShrinkLeavesNoResidue() {
61 final String string = "SECRET_PASSWORD_DATA";
62 final int len = string.length();
63 final TextStringBuilder sb = new TextStringBuilder(string);
64 assertEquals(len, sb.length());
65 sb.delete(6, len);
66 assertEquals(6, sb.length());
67 assertEquals("SECRET", sb.toString());
68 final char[] buf = sb.getBuffer();
69 for (int i = 6; i < len; i++) {
70 assertEquals(CharUtils.NUL, buf[i]);
71 }
72 }
73
74 @Test
75 public void testDeserializedBuilderHasNoStaleBufferContent() throws Exception {
76 final TextStringBuilder sb = new TextStringBuilder("secret_password_xyzzy");
77 sb.clear();
78 sb.append("safe");
79 final byte[] serialized = SerializationUtils.serialize(sb);
80 final TextStringBuilder sb2;
81 try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serialized))) {
82 sb2 = (TextStringBuilder) ois.readObject();
83 }
84 final String bufContent = new String(sb2.getBuffer());
85 assertFalse(bufContent.contains("secret_password"), "Deserialized buffer must not contain stale chars: " + bufContent);
86 }
87
88 @Test
89 public void testReadFromReaderDoesNotExposeStaleInternalBuffer() throws IOException {
90 final TextStringBuilder sb = new TextStringBuilder();
91 sb.append("SECRET_DATA_SHOULD_NOT_LEAK_ABCDEFGHIJ");
92 sb.clear();
93 try (SpyReader spy = new SpyReader("hi")) {
94 sb.readFrom(spy);
95 assertFalse(spy.observedStaleChars("_DATA_SHOULD_NOT_LEAK"));
96 }
97 }
98
99 @Test
100 void testReplaceShrinkLeavesNoResidue() {
101 final String string = "SECRET_PASSWORD_DATA";
102 final TextStringBuilder sb = new TextStringBuilder(string);
103 assertEquals(20, sb.length());
104
105 sb.replace(0, 20, "X");
106 assertEquals(1, sb.length());
107 assertEquals("X", sb.toString());
108 final char[] buf = sb.getBuffer();
109 assertTrue(buf.length >= 20);
110 for (int i = 1; i < 20; i++) {
111 assertEquals(CharUtils.NUL, buf[i]);
112 }
113 }
114
115 @Test
116 void testSetLengthShrinkLeavesNoResidue() {
117 final String string = "CONFIDENTIAL_TOKEN_VALUE";
118 final int len = string.length();
119 final TextStringBuilder sb = new TextStringBuilder(string);
120 assertEquals(len, sb.length());
121 sb.setLength(5);
122 assertEquals(5, sb.length());
123 assertEquals("CONFI", sb.toString());
124 final char[] buf = sb.getBuffer();
125 assertTrue(buf.length >= len);
126 for (int i = 5; i < len; i++) {
127 assertEquals(CharUtils.NUL, buf[i]);
128 }
129 }
130
131 @Test
132 public void testStaleCharsNotLeakedAfterClear() {
133 final TextStringBuilder sb = new TextStringBuilder("secret_password_xyzzy_leak");
134 sb.clear();
135 sb.append("ok");
136 assertFalse(containsUtf16Be(SerializationUtils.serialize(sb), "xyzzy_leak"));
137 }
138
139 @Test
140 public void testStaleCharsNotLeakedAfterTruncate() {
141 final TextStringBuilder sb = new TextStringBuilder("top_secret_key_material");
142 sb.delete(6, sb.length());
143 assertFalse(containsUtf16Be(SerializationUtils.serialize(sb), "secret_key_material"));
144 }
145 }