Skip to content

Commit 0ec2b35

Browse files
authored
Merge pull request #519 from jeremiahjstacey/issue_494
Issue 494 CSSCodec RGB Triplets
2 parents e50ff98 + 377d377 commit 0ec2b35

File tree

5 files changed

+276
-11
lines changed

5 files changed

+276
-11
lines changed

src/main/java/org/owasp/esapi/codecs/CSSCodec.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616
package org.owasp.esapi.codecs;
1717

18+
import java.util.regex.Pattern;
19+
20+
import org.owasp.esapi.codecs.ref.EncodingPatternPreservation;
21+
1822
/**
1923
* Implementation of the Codec interface for backslash encoding used in CSS.
2024
*
@@ -26,8 +30,21 @@
2630
public class CSSCodec extends AbstractCharacterCodec
2731
{
2832
private static final Character REPLACEMENT = '\ufffd';
29-
30-
33+
//rgb (###,###,###) OR rgb(###%,###%,###%)
34+
//([rR][gG][bB])\s*\(\s*\d{1,3}\s*(\%)?\s*,\s*\d{1,3}\s*(\%)?\s*,\s*\d{1,3}\s*(\%)?\s*\)
35+
private static final String RGB_TRPLT = "([rR][gG][bB])\\s*\\(\\s*\\d{1,3}\\s*(\\%)?\\s*,\\s*\\d{1,3}\\s*(\\%)?\\s*,\\s*\\d{1,3}\\s*(\\%)?\\s*\\)";
36+
private static final Pattern RGB_TRPLT_PATTERN = Pattern.compile(RGB_TRPLT);
37+
38+
@Override
39+
public String encode(char[] immune, String input) {
40+
EncodingPatternPreservation tripletCheck = new EncodingPatternPreservation(RGB_TRPLT_PATTERN);
41+
42+
String inputChk = tripletCheck.captureAndReplaceMatches(input);
43+
44+
String result = super.encode(immune, inputChk);
45+
46+
return tripletCheck.restoreOriginalContent(result);
47+
}
3148
/**
3249
* {@inheritDoc}
3350
*
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
String replaceContent = matcher.group(0);
62+
if (replaceContent != null) {
63+
replacedContentList.add(replaceContent);
64+
inputCpy = inputCpy.replaceFirst(noEncodeContent.pattern(), replacementMarker);
65+
}
66+
}
67+
68+
return inputCpy;
69+
}
70+
71+
/**
72+
* Replaces each instance of the {@link #replacementMarker} with the original
73+
* content, as captured by {@link #captureAndReplaceMatches(String)}
74+
*
75+
* @param input String to restore.
76+
* @return String reference with all values replaced.
77+
*/
78+
public String restoreOriginalContent(String input) {
79+
String result = input;
80+
while (replacedContentList.size() > 0) {
81+
String origValue = replacedContentList.remove(0);
82+
result = result.replaceFirst(replacementMarker, origValue);
83+
}
84+
85+
return result;
86+
87+
}
88+
89+
/**
90+
* Allows the marker used as a replacement to be altered.
91+
*
92+
* @param marker String replacment to use for regex matches.
93+
*/
94+
public void setReplacementMarker(String marker) {
95+
if (!replacedContentList.isEmpty()) {
96+
// This may seem odd, but this will prevent programmer error that would result
97+
// in being unable to restore a previously tokenized String.
98+
String message = "Previously captured state is still present in instance. Call PatternContentPreservation.reset() to clear out preserved state and to alter the marker.";
99+
throw new IllegalStateException(message);
100+
}
101+
this.replacementMarker = marker;
102+
}
103+
104+
/**
105+
* Clears any stored replacement values out of the instance.
106+
*/
107+
public void reset() {
108+
replacedContentList.clear();
109+
}
110+
111+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.owasp.esapi.codecs;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import org.junit.Test;
6+
7+
public class CSSCodecTest {
8+
private static final char[] IMMUNE_STUB = new char[0];
9+
/** Unit In Test*/
10+
private CSSCodec uit = new CSSCodec();
11+
12+
@Test
13+
public void testCSSTripletLeadString() {
14+
assertEquals("rgb(255,255,255)\\21 ", uit.encode(IMMUNE_STUB, "rgb(255,255,255)!"));
15+
assertEquals("rgb(25%,25%,25%)\\21 ", uit.encode(IMMUNE_STUB, "rgb(25%,25%,25%)!"));
16+
}
17+
@Test
18+
public void testCSSTripletTailString() {
19+
assertEquals("\\24 field\\3d rgb(255,255,255)\\21 ", uit.encode(IMMUNE_STUB, "$field=rgb(255,255,255)!"));
20+
assertEquals("\\24 field\\3d rgb(25%,25%,25%)\\21 ", uit.encode(IMMUNE_STUB, "$field=rgb(25%,25%,25%)!"));
21+
}
22+
@Test
23+
public void testCSSTripletStringPart() {
24+
assertEquals("\\24 field\\3d rgb(255,255,255)\\21 ", uit.encode(IMMUNE_STUB, "$field=rgb(255,255,255)!"));
25+
assertEquals("\\24 field\\3d rgb(25%,25%,25%)\\21 ", uit.encode(IMMUNE_STUB, "$field=rgb(25%,25%,25%)!"));
26+
}
27+
@Test
28+
public void testCSSTripletStringMultiPart() {
29+
assertEquals("\\24 field\\3d rgb(255,255,255)\\21 \\20 \\24 field\\3d rgb(255,255,255)\\21 ", uit.encode(IMMUNE_STUB, "$field=rgb(255,255,255)! $field=rgb(255,255,255)!"));
30+
assertEquals("\\24 field\\3d rgb(25%,25%,25%)\\21 \\20 \\24 field\\3d rgb(25%,25%,25%)\\21 ", uit.encode(IMMUNE_STUB, "$field=rgb(25%,25%,25%)! $field=rgb(25%,25%,25%)!"));
31+
assertEquals("\\24 field\\3d rgb(255,255,255)\\21 \\20 \\24 field\\3d rgb(25%,25%,25%)\\21 ", uit.encode(IMMUNE_STUB, "$field=rgb(255,255,255)! $field=rgb(25%,25%,25%)!"));
32+
}
33+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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 testReplaceMultipleAndRestore() {
26+
Pattern numberRegex = Pattern.compile("(ABC)");
27+
EncodingPatternPreservation epp = new EncodingPatternPreservation(numberRegex);
28+
String origStr = "12 ABC 34 ABC 56 G 7 ABC8";
29+
String replacedStr = epp.captureAndReplaceMatches(origStr);
30+
31+
assertEquals("12 EncodingPatternPreservation 34 EncodingPatternPreservation 56 G 7 EncodingPatternPreservation8", replacedStr);
32+
33+
String restored = epp.restoreOriginalContent(replacedStr);
34+
assertEquals(origStr, restored);
35+
}
36+
37+
@Test
38+
public void testSetMarker() {
39+
Pattern numberRegex = Pattern.compile("(ABC)");
40+
EncodingPatternPreservation epp = new EncodingPatternPreservation(numberRegex);
41+
epp.setReplacementMarker(EncodingPatternPreservationTest.class.getSimpleName());
42+
43+
String origStr = "12 ABC 34 DEF 56 G 7";
44+
String replacedStr = epp.captureAndReplaceMatches(origStr);
45+
46+
assertEquals("12 EncodingPatternPreservationTest 34 DEF 56 G 7", replacedStr);
47+
48+
String restored = epp.restoreOriginalContent(replacedStr);
49+
assertEquals(origStr, restored);
50+
}
51+
52+
@Test (expected = IllegalStateException.class)
53+
public void testSetMarkerExceptionNoReset() {
54+
Pattern numberRegex = Pattern.compile("(ABC)");
55+
EncodingPatternPreservation epp = new EncodingPatternPreservation(numberRegex);
56+
String origStr = "12 ABC 34 DEF 56 G 7";
57+
epp.captureAndReplaceMatches(origStr);
58+
//This allows the + case to be illustrated
59+
epp.reset();
60+
61+
//And the exception case.
62+
epp.captureAndReplaceMatches(origStr);
63+
epp.setReplacementMarker(EncodingPatternPreservationTest.class.getSimpleName());
64+
}
65+
66+
@Test (expected = IllegalStateException.class)
67+
public void testReplaceExceptionNoReset() {
68+
Pattern numberRegex = Pattern.compile("(ABC)");
69+
EncodingPatternPreservation epp = new EncodingPatternPreservation(numberRegex);
70+
String origStr = "12 ABC 34 DEF 56 G 7";
71+
epp.captureAndReplaceMatches(origStr);
72+
//This allows the + case to be illustrated
73+
epp.reset();
74+
75+
//And the exception case.
76+
epp.captureAndReplaceMatches(origStr);
77+
epp.captureAndReplaceMatches(origStr);
78+
}
79+
}

src/test/java/org/owasp/esapi/reference/EncoderTest.java

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,23 @@
1515
*/
1616
package org.owasp.esapi.reference;
1717

18-
import java.io.ByteArrayOutputStream;
18+
import static org.junit.Assert.assertEquals;
19+
1920
import java.io.IOException;
20-
import java.io.ObjectOutputStream;
2121
import java.io.UnsupportedEncodingException;
2222
import java.net.URI;
2323
import java.util.ArrayList;
2424
import java.util.Arrays;
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
import java.util.Map.Entry;
28+
import java.util.regex.Matcher;
29+
import java.util.regex.Pattern;
2530

26-
import org.junit.Ignore;
2731
import org.owasp.esapi.ESAPI;
2832
import org.owasp.esapi.Encoder;
2933
import org.owasp.esapi.EncoderConstants;
30-
import org.owasp.esapi.codecs.Base64;
34+
import org.owasp.esapi.codecs.CSSCodec;
3135
import org.owasp.esapi.codecs.Codec;
3236
import org.owasp.esapi.codecs.HTMLEntityCodec;
3337
import org.owasp.esapi.codecs.MySQLCodec;
@@ -376,7 +380,7 @@ public void testEncodeForHTMLAttribute() {
376380
/**
377381
*
378382
*/
379-
public void testEncodeForCSS() {
383+
public void testencodeForCSS() {
380384
System.out.println("encodeForCSS");
381385
Encoder instance = ESAPI.encoder();
382386
assertEquals(null, instance.encodeForCSS(null));
@@ -385,11 +389,32 @@ public void testEncodeForCSS() {
385389
assertEquals("#f00", instance.encodeForCSS("#f00"));
386390
assertEquals("#123456", instance.encodeForCSS("#123456"));
387391
assertEquals("#abcdef", instance.encodeForCSS("#abcdef"));
388-
assertEquals("red", instance.encodeForCSS("red"));
392+
assertEquals("red", instance.encodeForCSS("red"));
389393
}
390-
391-
392-
394+
395+
public void testCSSTripletLeadString() {
396+
Encoder instance = ESAPI.encoder();
397+
assertEquals("rgb(255,255,255)\\21 ", instance.encodeForCSS("rgb(255,255,255)!"));
398+
assertEquals("rgb(25%,25%,25%)\\21 ", instance.encodeForCSS("rgb(25%,25%,25%)!"));
399+
}
400+
public void testCSSTripletTailString() {
401+
Encoder instance = ESAPI.encoder();
402+
assertEquals("\\24 field\\3d rgb(255,255,255)\\21 ", instance.encodeForCSS("$field=rgb(255,255,255)!"));
403+
assertEquals("\\24 field\\3d rgb(25%,25%,25%)\\21 ", instance.encodeForCSS("$field=rgb(25%,25%,25%)!"));
404+
}
405+
public void testCSSTripletStringPart() {
406+
Encoder instance = ESAPI.encoder();
407+
assertEquals("\\24 field\\3d rgb(255,255,255)\\21 ", instance.encodeForCSS("$field=rgb(255,255,255)!"));
408+
assertEquals("\\24 field\\3d rgb(25%,25%,25%)\\21 ", instance.encodeForCSS("$field=rgb(25%,25%,25%)!"));
409+
}
410+
public void testCSSTripletStringMultiPart() {
411+
Encoder instance = ESAPI.encoder();
412+
assertEquals("\\24 field\\3d rgb(255,255,255)\\21 \\20 \\24 field\\3d rgb(255,255,255)\\21 ", instance.encodeForCSS("$field=rgb(255,255,255)! $field=rgb(255,255,255)!"));
413+
assertEquals("\\24 field\\3d rgb(25%,25%,25%)\\21 \\20 \\24 field\\3d rgb(25%,25%,25%)\\21 ", instance.encodeForCSS("$field=rgb(25%,25%,25%)! $field=rgb(25%,25%,25%)!"));
414+
assertEquals("\\24 field\\3d rgb(255,255,255)\\21 \\20 \\24 field\\3d rgb(25%,25%,25%)\\21 ", instance.encodeForCSS("$field=rgb(255,255,255)! $field=rgb(25%,25%,25%)!"));
415+
}
416+
417+
393418
/**
394419
* Test of encodeForJavaScript method, of class org.owasp.esapi.Encoder.
395420
*/

0 commit comments

Comments
 (0)