diff --git a/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java
index ce66ba256..240cc2ca7 100644
--- a/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java
+++ b/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java
@@ -1,5 +1,5 @@
/**
- * OWASP Enterprise Security API (ESAPI)
+ * OWASP Enterprise Security API (ESAPI)
*
* This file is part of the Open Web Application Security Project (OWASP)
* Enterprise Security API (ESAPI) project. For details, please see
diff --git a/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java b/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java
index a754064bc..348cb4a4d 100644
--- a/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java
+++ b/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java
@@ -520,6 +520,9 @@ public byte[] decodeFromBase64(String input) throws IOException {
* This will extract each piece of a URI according to parse zone as specified in RFC-3986 section 3,
* and it will construct a canonicalized String representing a version of the URI that is safe to
* run regex against.
+ *
+ * NOTE: This method will obey the ESAPI.properties configurations for allowing
+ * Mixed and Multiple Encoding URLs.
*
* @param dirtyUri
* @return Canonicalized URI string.
@@ -548,7 +551,6 @@ public String getCanonicalizedURI(URI dirtyUri) throws IntrusionException{
parseMap.put(UriSegment.SCHEME, dirtyUri.getScheme());
//authority = [ userinfo "@" ] host [ ":" port ]
parseMap.put(UriSegment.AUTHORITY, dirtyUri.getRawAuthority());
- parseMap.put(UriSegment.SCHEMSPECIFICPART, dirtyUri.getRawSchemeSpecificPart());
parseMap.put(UriSegment.HOST, dirtyUri.getHost());
//if port is undefined, it will return -1
Integer port = new Integer(dirtyUri.getPort());
@@ -557,9 +559,6 @@ public String getCanonicalizedURI(URI dirtyUri) throws IntrusionException{
parseMap.put(UriSegment.QUERY, dirtyUri.getRawQuery());
parseMap.put(UriSegment.FRAGMENT, dirtyUri.getRawFragment());
- //Now we canonicalize each part and build our string.
- StringBuilder sb = new StringBuilder();
-
//Replace all the items in the map with canonicalized versions.
Set set = parseMap.keySet();
@@ -568,8 +567,7 @@ public String getCanonicalizedURI(URI dirtyUri) throws IntrusionException{
boolean allowMixed = sg.getBooleanProp("Encoder.AllowMixedEncoding");
boolean allowMultiple = sg.getBooleanProp("Encoder.AllowMultipleEncoding");
for(UriSegment seg: set){
- String value = canonicalize(parseMap.get(seg), allowMultiple, allowMixed);
- value = value == null ? "" : value;
+ String value = "";
//In the case of a uri query, we need to break up and canonicalize the internal parts of the query.
if(seg == UriSegment.QUERY && null != parseMap.get(seg)){
StringBuilder qBuilder = new StringBuilder();
@@ -597,6 +595,10 @@ public String getCanonicalizedURI(URI dirtyUri) throws IntrusionException{
} catch (UnsupportedEncodingException e) {
logger.debug(Logger.EVENT_FAILURE, "decoding error when parsing [" + dirtyUri.toString() + "]");
}
+ } else {
+ String extractedInput = parseMap.get(seg);
+ value = canonicalize(extractedInput, allowMultiple, allowMixed);
+ value = value == null ? "" : value;
}
//Check if the port is -1, if it is, omit it from the output.
if(seg == UriSegment.PORT){
@@ -618,11 +620,16 @@ public String getCanonicalizedURI(URI dirtyUri) throws IntrusionException{
*/
protected String buildUrl(Map parseMap){
StringBuilder sb = new StringBuilder();
- sb.append(parseMap.get(UriSegment.SCHEME))
- .append("://")
+ boolean schemePresent = parseMap.get(UriSegment.SCHEME).equals("") ? false : true;
+
+ if(schemePresent) {
+ sb.append(parseMap.get(UriSegment.SCHEME))
+ .append("://");
+ }
+
//can't use SCHEMESPECIFICPART for this, because we need to canonicalize all the parts of the query.
//USERINFO is also deprecated. So we technically have more than we need.
- .append(parseMap.get(UriSegment.AUTHORITY) == null || parseMap.get(UriSegment.AUTHORITY).equals("") ? "" : parseMap.get(UriSegment.AUTHORITY))
+ sb.append(parseMap.get(UriSegment.AUTHORITY) == null || parseMap.get(UriSegment.AUTHORITY).equals("") ? "" : parseMap.get(UriSegment.AUTHORITY))
.append(parseMap.get(UriSegment.PATH) == null || parseMap.get(UriSegment.PATH).equals("") ? "" : parseMap.get(UriSegment.PATH))
.append(parseMap.get(UriSegment.QUERY) == null || parseMap.get(UriSegment.QUERY).equals("")
? "" : "?" + parseMap.get(UriSegment.QUERY))
diff --git a/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java b/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java
index 070e28a26..5414d9ecf 100644
--- a/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java
+++ b/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java
@@ -2,6 +2,7 @@
import static org.junit.Assert.assertEquals;
+import org.junit.Ignore;
import org.junit.Test;
public class HTMLEntityCodecTest {
@@ -48,4 +49,23 @@ public void testMixedBmpAndNonBmp(){
String input = bmp + nonBMP;
assertEquals(expected, codec.encode(new char[0], input));
}
+
+ @Test
+ /**
+ * TODO: The following methods are unit tests I'm checking in for an issue to be worked and fixed.
+ */
+ @Ignore("Pre check-in for issue #827")
+ public void testIssue827() {
+ String input = "/webapp/ux/home?d=1705914006565&status=login&ticket=1705914090394_HzJpTROVfhW-JhRW0OqDbHu7tWXXlgrKSUmOzIMsZNCcUIiYGMXX_Q%3D%3D&newsess=false&roleid=DP010101/0007&origin=ourprogram";
+ String expected = input;
+ assertEquals(expected, codec.decode(input));
+ }
+
+ @Test
+ @Ignore("Pre check-in for issue #827")
+ public void testIssue827OnlyOR() {
+ String input = "&origin=ourprogram";
+ String expected = input;
+ assertEquals(expected, codec.decode(input));
+ }
}
diff --git a/src/test/java/org/owasp/esapi/reference/EncoderTest.java b/src/test/java/org/owasp/esapi/reference/EncoderTest.java
index 963939626..51f72d063 100644
--- a/src/test/java/org/owasp/esapi/reference/EncoderTest.java
+++ b/src/test/java/org/owasp/esapi/reference/EncoderTest.java
@@ -15,25 +15,21 @@
*/
package org.owasp.esapi.reference;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
-import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.List;
+import org.junit.Ignore;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.Encoder;
import org.owasp.esapi.EncoderConstants;
-import org.owasp.esapi.codecs.CSSCodec;
+import org.owasp.esapi.SecurityConfiguration;
+import org.owasp.esapi.SecurityConfigurationWrapper;
import org.owasp.esapi.codecs.Codec;
import org.owasp.esapi.codecs.HTMLEntityCodec;
import org.owasp.esapi.codecs.MySQLCodec;
@@ -45,8 +41,7 @@
import org.owasp.esapi.errors.EncodingException;
import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.Randomizer;
-import org.owasp.esapi.SecurityConfiguration;
-import org.owasp.esapi.SecurityConfigurationWrapper;
+
import junit.framework.Test;
import junit.framework.TestCase;
@@ -747,6 +742,7 @@ public void testDecodeFromURL() throws Exception {
fail();
}
try {
+ //FIXME: Rewrite this to use expected Exceptions.
instance.decodeFromURL( "%3xridiculous" );
fail();
} catch( Exception e ) {
@@ -985,6 +981,50 @@ public void testGetCanonicalizedUri() throws Exception {
assertEquals(expectedUri, e.getCanonicalizedURI(uri));
}
+
+ public void testGetCanonicalizedUriWithAnHTMLEntityCollision() throws Exception {
+ System.out.println("GetCanonicalizedUriWithAnHTMLEntityCollision");
+ Encoder e = ESAPI.encoder();
+
+ String expectedUri = "http://palpatine@foobar.com/path_to/resource?foo=bar¶1=test";
+ //Please note that section 3.2.1 of RFC-3986 explicitly states not to encode
+ //password information as in http://palpatine:password@foo.com, and this will
+ //not appear in the userinfo field.
+ String input = "http://palpatine@foobar.com/path_to/resource?foo=bar¶1=test";
+ URI uri = new URI(input);
+ System.out.println(uri.toString());
+ assertEquals(expectedUri, e.getCanonicalizedURI(uri));
+
+ }
+
+ @org.junit.Ignore("Pre-check in unit test for issue #826")
+ public void Issue826GetCanonicalizedUriWithMultipleEncoding() throws Exception {
+ System.out.println("GetCanonicalizedUriWithAnHTMLEntityCollision");
+ Encoder e = ESAPI.encoder();
+ String expectedUri = "http://palpatine@foobar.com/path_to/resource?foo=bar¶1=&test";
+ //Please note that section 3.2.1 of RFC-3986 explicitly states not to encode
+ //password information as in http://palpatine:password@foo.com, and this will
+ //not appear in the userinfo field.
+ String input = "http://palpatine@foobar.com/path_to/resource?foo=bar¶1=&test";
+ URI uri = new URI(input);
+ System.out.println(uri.toString());
+ assertEquals(expectedUri, e.getCanonicalizedURI(uri));
+
+ }
+ public void testGetCanonicalizedUriWithMultQueryParams() throws Exception {
+ System.out.println("getCanonicalizedUri");
+ Encoder e = ESAPI.encoder();
+
+ String expectedUri = "http://palpatine@foo bar.com/path_to/resource?foo=bar&bar=foo#frag";
+ //Please note that section 3.2.1 of RFC-3986 explicitly states not to encode
+ //password information as in http://palpatine:password@foo.com, and this will
+ //not appear in the userinfo field.
+ String input = "http://palpatine@foo%20bar.com/path_to/resource?foo=bar&bar=foo#frag";
+ URI uri = new URI(input);
+ System.out.println(uri.toString());
+ assertEquals(expectedUri, e.getCanonicalizedURI(uri));
+
+ }
public void testGetCanonicalizedUriPiazza() throws Exception {
System.out.println("getCanonicalizedUriPiazza");
@@ -1000,6 +1040,41 @@ public void testGetCanonicalizedUriPiazza() throws Exception {
assertEquals(expectedUri, e.getCanonicalizedURI(uri));
}
+
+ public void testIssue824() throws Exception {
+ System.out.println("getCanonicalizedUriPiazza");
+ Encoder e = ESAPI.encoder();
+
+ String expectedUri = "/webapp/ux/home?d=1705914006565&status=login&ticket=1705914090394_HzJpTROVfhW-JhRW0OqDbHu7tWXXlgrKSUmOzIMsZNCcUIiYGMXX_Q==&newsess=false&roleid=DP010101/0007&origin=ourprogram";
+ //Please note that section 3.2.1 of RFC-3986 explicitly states not to encode
+ //password information as in http://palpatine:password@foo.com, and this will
+ //not appear in the userinfo field.
+ String input = "/webapp/ux/home?d=1705914006565&status=login&ticket=1705914090394_HzJpTROVfhW-JhRW0OqDbHu7tWXXlgrKSUmOzIMsZNCcUIiYGMXX_Q%3D%3D&newsess=false&roleid=DP010101/0007&origin=ourprogram";
+ URI uri = new URI(input);
+ System.out.println(uri.toString());
+ assertEquals(expectedUri, e.getCanonicalizedURI(uri));
+
+ }
+
+ @org.junit.Ignore("Pre-check in unit test for issue #826")
+ public void Issue826GetCanonicalizedDoubleAmpersand() throws Exception {
+ System.out.println("getCanonicalizedDoubleAmpersand");
+ Encoder e = ESAPI.encoder();
+ String expectedUri = "http://127.0.0.1:3000/campaigns?goal=all§ion=active&sort-by=-id&status=Draft%2C&html=&contentLaunched";
+ //http://127.0.0.1:3000/campaigns?goal=all§ion=active&sort-by=-id&status=Draft,&html=null&=null&contentLaunched=null
+ /*
+ * In this case, the URI class should break up the HTML entity in the query so
+ */
+ String input = "http://127.0.0.1:3000/campaigns?goal=all§ion=active&sort-by=-id&status=Draft%2C&html=&&contentLaunched";
+ URI uri = new URI(input);
+ System.out.println(uri.toString());
+ try {
+ assertEquals(expectedUri, e.getCanonicalizedURI(uri));
+ fail();
+ } catch (Exception ex) {
+ //Expected
+ }
+ }
public void testGetCanonicalizedUriWithMailto() throws Exception {
System.out.println("getCanonicalizedUriWithMailto");