Skip to content

Commit ea47c3e

Browse files
Pattern Replacement Utility #494
Support class that can handle the regex replace/restore operations for a codec's encoding process.
1 parent b6cf32e commit ea47c3e

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package org.owasp.esapi.codecs.ref;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.regex.Matcher;
6+
import java.util.regex.Pattern;
7+
8+
/**
9+
* String mutation utility which can be used to replace all occurrences of a
10+
* defined regular expression with a marker string, and also restore the
11+
* original string content.
12+
*
13+
*/
14+
public class EncodingPatternPreservation {
15+
/** Default replacement marker. */
16+
private static final String REPLACEMENT_MARKER = EncodingPatternPreservation.class.getSimpleName();
17+
/** Pattern that is used to identify which content should be replaced. */
18+
private final Pattern noEncodeContent;
19+
/** The Marker used to replace found Pattern references. */
20+
private String replacementMarker = REPLACEMENT_MARKER;
21+
22+
/**
23+
* The ordered-list of elements that were replaced in the last call to
24+
* {@link #captureAndReplaceMatches(String)}, and that will be used to replace
25+
* the {@link #replacementMarker} on the next call to
26+
* {@link #restoreOriginalContent(String)}
27+
*/
28+
private final List<String> replacedContentList = new ArrayList<>();
29+
30+
/**
31+
* Constructor.
32+
*
33+
* @param pattern Pattern identifying content being replaced.
34+
*/
35+
public EncodingPatternPreservation(Pattern pattern) {
36+
noEncodeContent = pattern;
37+
}
38+
39+
/**
40+
* Replaces each matching instance of this instance's Pattern with an
41+
* identifiable replacement marker. <br>
42+
*
43+
* <br>
44+
* After the encoding process is complete, use
45+
* {@link #restoreOriginalContent(String)} to re-insert the original data.
46+
*
47+
* @param input String to adjust
48+
* @return The adjusted String
49+
*/
50+
public String captureAndReplaceMatches(String input) {
51+
if (!replacedContentList.isEmpty()) {
52+
// This may seem odd, but this will prevent programmer error that would result
53+
// in being unable to restore a previously tokenized String.
54+
String message = "Previously captured state is still present in instance. Call PatternContentPreservation.reset() to clear out preserved state and to reuse the reference.";
55+
throw new IllegalStateException(message);
56+
}
57+
String inputCpy = input;
58+
Matcher matcher = noEncodeContent.matcher(input);
59+
60+
while (matcher.find()) {
61+
int groups = matcher.groupCount();
62+
for (int x = 0; x < groups; x++) {
63+
String replaceContent = matcher.group(x);
64+
replacedContentList.add(replaceContent);
65+
inputCpy = inputCpy.replaceFirst(noEncodeContent.pattern(), replacementMarker);
66+
}
67+
}
68+
69+
return inputCpy;
70+
}
71+
72+
/**
73+
* Replaces each instance of the {@link #replacementMarker} with the original
74+
* content, as captured by {@link #captureAndReplaceMatches(String)}
75+
*
76+
* @param input String to restore.
77+
* @return String reference with all values replaced.
78+
*/
79+
public String restoreOriginalContent(String input) {
80+
String result = input;
81+
while (replacedContentList.size() > 0) {
82+
String origValue = replacedContentList.remove(0);
83+
result = result.replaceFirst(replacementMarker, origValue);
84+
}
85+
86+
return result;
87+
88+
}
89+
90+
/**
91+
* Allows the marker used as a replacement to be altered.
92+
*
93+
* @param marker String replacment to use for regex matches.
94+
*/
95+
public void setReplacementMarker(String marker) {
96+
if (!replacedContentList.isEmpty()) {
97+
// This may seem odd, but this will prevent programmer error that would result
98+
// in being unable to restore a previously tokenized String.
99+
String message = "Previously captured state is still present in instance. Call PatternContentPreservation.reset() to clear out preserved state and to alter the marker.";
100+
throw new IllegalStateException(message);
101+
}
102+
this.replacementMarker = marker;
103+
}
104+
105+
/**
106+
* Clears any stored replacement values out of the instance.
107+
*/
108+
public void reset() {
109+
replacedContentList.clear();
110+
}
111+
112+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.owasp.esapi.codecs.ref;
2+
3+
import java.util.regex.Pattern;
4+
5+
import org.junit.Test;
6+
7+
import static org.junit.Assert.*;
8+
9+
public class EncodingPatternPreservationTest {
10+
11+
@Test
12+
public void testReplaceAndRestore() {
13+
Pattern numberRegex = Pattern.compile("(ABC)");
14+
EncodingPatternPreservation epp = new EncodingPatternPreservation(numberRegex);
15+
String origStr = "12 ABC 34 DEF 56 G 7";
16+
String replacedStr = epp.captureAndReplaceMatches(origStr);
17+
18+
assertEquals("12 EncodingPatternPreservation 34 DEF 56 G 7", replacedStr);
19+
20+
String restored = epp.restoreOriginalContent(replacedStr);
21+
assertEquals(origStr, restored);
22+
}
23+
24+
@Test
25+
public void testSetMarker() {
26+
Pattern numberRegex = Pattern.compile("(ABC)");
27+
EncodingPatternPreservation epp = new EncodingPatternPreservation(numberRegex);
28+
epp.setReplacementMarker(EncodingPatternPreservationTest.class.getSimpleName());
29+
30+
String origStr = "12 ABC 34 DEF 56 G 7";
31+
String replacedStr = epp.captureAndReplaceMatches(origStr);
32+
33+
assertEquals("12 EncodingPatternPreservationTest 34 DEF 56 G 7", replacedStr);
34+
35+
String restored = epp.restoreOriginalContent(replacedStr);
36+
assertEquals(origStr, restored);
37+
}
38+
39+
@Test (expected = IllegalStateException.class)
40+
public void testSetMarkerExceptionNoReset() {
41+
Pattern numberRegex = Pattern.compile("(ABC)");
42+
EncodingPatternPreservation epp = new EncodingPatternPreservation(numberRegex);
43+
String origStr = "12 ABC 34 DEF 56 G 7";
44+
epp.captureAndReplaceMatches(origStr);
45+
//This allows the + case to be illustrated
46+
epp.reset();
47+
48+
//And the exception case.
49+
epp.captureAndReplaceMatches(origStr);
50+
epp.setReplacementMarker(EncodingPatternPreservationTest.class.getSimpleName());
51+
}
52+
53+
@Test (expected = IllegalStateException.class)
54+
public void testReplaceExceptionNoReset() {
55+
Pattern numberRegex = Pattern.compile("(ABC)");
56+
EncodingPatternPreservation epp = new EncodingPatternPreservation(numberRegex);
57+
String origStr = "12 ABC 34 DEF 56 G 7";
58+
epp.captureAndReplaceMatches(origStr);
59+
//This allows the + case to be illustrated
60+
epp.reset();
61+
62+
//And the exception case.
63+
epp.captureAndReplaceMatches(origStr);
64+
epp.captureAndReplaceMatches(origStr);
65+
}
66+
}

0 commit comments

Comments
 (0)