From b496305cb7711166141c2b220560fa93352120cb Mon Sep 17 00:00:00 2001 From: Pavan Kumar Date: Wed, 8 Jun 2016 16:43:03 +0530 Subject: [PATCH 001/709] #302 HTMLEntityCodec Now decodes cased accented letters properly HTMLEntityCodec.decode incorrectly decodes upper-case accented letters as their lower-case counterparts. In this change i am doing an exact match for the possible string and if the match is not found falling back to the old technique of lowercasing the possible string & find a match for it. Please review this change --- .../org/owasp/esapi/codecs/HTMLEntityCodec.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java index b4337968a..cd72dc79c 100644 --- a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java @@ -250,12 +250,21 @@ private Character getNamedEntity( PushbackString input ) { // kludge around PushbackString.... len = Math.min(input.remainder().length(), entityToCharacterTrie.getMaxKeyLength()); for(int i=0;i exactEntry = entityToCharacterTrie.getLongestMatch(possibleStringLowerCase); + if(exactEntry != null) entry = exactEntry; + } + if(entry == null) return null; // no match, caller will reset input + } // fixup input input.reset(); From fd8826729fce7a7771c620a88b6f6dd080a3d149 Mon Sep 17 00:00:00 2001 From: mickilous Date: Tue, 21 Jun 2016 11:29:15 +0200 Subject: [PATCH 002/709] The DefaultHttpUtilities and SecurityWrapperResponse add systematically a Max-Age to the HTTP Header when adding a cookie. Even if the cookie has a negative maxAge value (which is the default value). So when adding a "session" cookie (without Max-Age), ESAPI add a Max-Age=-1 in the HTTP Header and the cookie will be discarded by the browser, because it is invalid (http://www.ietf.org/rfc/rfc2109.txt). Wen the maxAge field of the cookie is negative, it should not be specified at HTTP Header level. --- .../java/org/owasp/esapi/filters/SecurityWrapperResponse.java | 4 +++- .../java/org/owasp/esapi/reference/DefaultHTTPUtilities.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java index 9dd0632ce..c5bd2fb86 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java @@ -124,7 +124,9 @@ private String createCookieHeader(String name, String value, int maxAge, String // Set-Cookie:=[; =][; expires=][; // domain=][; path=][; secure][;HttpOnly String header = name + "=" + value; - header += "; Max-Age=" + maxAge; + if (maxAge >= 0) { + header += "; Max-Age=" + maxAge; + } if (domain != null) { header += "; Domain=" + domain; } diff --git a/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java b/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java index 3c460c872..aa39b152d 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java @@ -340,7 +340,9 @@ private String createCookieHeader(String name, String value, int maxAge, String // Set-Cookie:=[; =][; expires=][; // domain=][; path=][; secure][;HttpOnly] String header = name + "=" + value; - header += "; Max-Age=" + maxAge; + if (maxAge >= 0) { + header += "; Max-Age=" + maxAge; + } if (domain != null) { header += "; Domain=" + domain; } From 8e3b5736b2bc05a1d11c9d0054404ff76a718db1 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sun, 18 Jun 2017 15:49:54 -0700 Subject: [PATCH 003/709] Issue 316 -- updated code to account for httpOnly and Secure ccookie options. --- .../owasp/esapi/reference/DefaultHTTPUtilities.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java b/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java index 3c460c872..659e1cc4e 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java @@ -41,6 +41,7 @@ import org.owasp.esapi.ESAPI; import org.owasp.esapi.HTTPUtilities; import org.owasp.esapi.Logger; +import org.owasp.esapi.SecurityConfiguration; import org.owasp.esapi.StringUtilities; import org.owasp.esapi.User; import org.owasp.esapi.ValidationErrorList; @@ -929,6 +930,9 @@ public String setRememberToken( HttpServletRequest request, HttpServletResponse String clearToken = user.getAccountName() + "|" + password; long expiry = ESAPI.encryptor().getRelativeTimeStamp(maxAge * 1000); String cryptToken = ESAPI.encryptor().seal(clearToken, expiry); + SecurityConfiguration sg = ESAPI.securityConfiguration(); + boolean forceSecureCookies = sg.getBooleanProp("HttpUtilities.ForceSecureCookies"); + boolean forceHttpOnly = sg.getBooleanProp("HttpUtilities.ForceHttpOnlyCookies"); // Do NOT URLEncode cryptToken before creating cookie. See Google Issue # 144, // which was marked as "WontFix". @@ -937,6 +941,8 @@ public String setRememberToken( HttpServletRequest request, HttpServletResponse cookie.setMaxAge( maxAge ); cookie.setDomain( domain ); cookie.setPath( path ); + cookie.setHttpOnly(forceHttpOnly); + cookie.setSecure(forceSecureCookies); response.addCookie( cookie ); logger.info(Logger.SECURITY_SUCCESS, "Enabled remember me token for " + user.getAccountName() ); return cryptToken; @@ -957,7 +963,9 @@ public String setRememberToken(HttpServletRequest request, HttpServletResponse r String clearToken = user.getAccountName(); long expiry = ESAPI.encryptor().getRelativeTimeStamp(maxAge * 1000); String cryptToken = ESAPI.encryptor().seal(clearToken, expiry); - + SecurityConfiguration sg = ESAPI.securityConfiguration(); + boolean forceSecureCookies = sg.getBooleanProp("HttpUtilities.ForceSecureCookies"); + boolean forceHttpOnly = sg.getBooleanProp("HttpUtilities.ForceHttpOnlyCookies"); // Do NOT URLEncode cryptToken before creating cookie. See Google Issue # 144, // which was marked as "WontFix". @@ -965,6 +973,8 @@ public String setRememberToken(HttpServletRequest request, HttpServletResponse r cookie.setMaxAge( maxAge ); cookie.setDomain( domain ); cookie.setPath( path ); + cookie.setHttpOnly(forceHttpOnly); + cookie.setSecure(forceSecureCookies); response.addCookie( cookie ); logger.info(Logger.SECURITY_SUCCESS, "Enabled remember me token for " + user.getAccountName() ); } catch( IntegrityException e){ From 22663ed9ab5f17ddebe28e22d6ecaf6223763e21 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sun, 18 Jun 2017 16:22:19 -0700 Subject: [PATCH 004/709] Issue 291 -- Closed due to Issue #376 resolving the original problem. --- src/test/resources/urisForTest.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/resources/urisForTest.txt b/src/test/resources/urisForTest.txt index 43f795e3d..df34cbc8e 100644 --- a/src/test/resources/urisForTest.txt +++ b/src/test/resources/urisForTest.txt @@ -1,4 +1,5 @@ #Format is URI,Expected test value +http://www.google.com?connectid=68470072-44c2-417b-822b-d945dc0364f4&request=GetFeature&service=wfs&version=1.1.0&typeName=DigitalGlobe%3AFinishedFeature&bbox=37.5%2C41.5%2C37.8%2C41.7&PROPERTYNAME=source%2CsourceUnit%2CproductType,FALSE https://127.0.0.1:8080/foo/bar,TRUE http://shareasale.com:8080/sem/fusce.xml?sed=sodales&tristique=scelerisque,TRUE http://shareasale.com/sem/fusce.xml?sed=sodales&tristique=scelerisque,TRUE From e5ebcab94a60ecf8a9c513ac61d8e99ac91b74f5 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sun, 18 Jun 2017 16:42:05 -0700 Subject: [PATCH 005/709] Issue #394 -- Refactor the URI canonicalization into the Encoder class. --- src/main/java/org/owasp/esapi/Encoder.java | 12 ++ src/main/java/org/owasp/esapi/Validator.java | 11 -- .../owasp/esapi/reference/DefaultEncoder.java | 154 ++++++++++++++++++ .../esapi/reference/DefaultValidator.java | 150 +---------------- .../owasp/esapi/reference/EncoderTest.java | 28 ++++ .../owasp/esapi/reference/ValidatorTest.java | 28 ---- 6 files changed, 196 insertions(+), 187 deletions(-) diff --git a/src/main/java/org/owasp/esapi/Encoder.java b/src/main/java/org/owasp/esapi/Encoder.java index 2d2bfb7b9..cf83c472c 100644 --- a/src/main/java/org/owasp/esapi/Encoder.java +++ b/src/main/java/org/owasp/esapi/Encoder.java @@ -16,6 +16,7 @@ package org.owasp.esapi; import java.io.IOException; +import java.net.URI; import org.owasp.esapi.codecs.Codec; import org.owasp.esapi.errors.EncodingException; @@ -513,4 +514,15 @@ public interface Encoder { */ byte[] decodeFromBase64(String input) throws IOException; + /** + * + * Get a version of the input URI that will be safe to run regex and other validations against. + * It is not recommended to persist this value as it will transform user input. This method + * will not test to see if the URI is RFC-3986 compliant. + * + * @param input + * @return + */ + public String getCanonicalizedURI(URI dirtyUri); + } diff --git a/src/main/java/org/owasp/esapi/Validator.java b/src/main/java/org/owasp/esapi/Validator.java index c01dae247..12c87ea15 100644 --- a/src/main/java/org/owasp/esapi/Validator.java +++ b/src/main/java/org/owasp/esapi/Validator.java @@ -708,17 +708,6 @@ public interface Validator { */ boolean isValidURI(String context, String input, boolean allowNull); - /** - * - * Get a version of the input URI that will be safe to run regex and other validations against. - * It is not recommended to persist this value as it will transform user input. This method - * will not test to see if the URI is RFC-3986 compliant. - * - * @param input - * @return - */ - public String getCanonicalizedURI(URI dirtyUri); - /** * Will return a {@code URI} object that will represent a fully parsed and legal URI * as specified in RFC-3986. diff --git a/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java b/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java index c40c0d60b..c24fac4b5 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java @@ -17,15 +17,23 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; +import java.util.EnumMap; import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import org.owasp.esapi.ESAPI; import org.owasp.esapi.Encoder; import org.owasp.esapi.Logger; +import org.owasp.esapi.SecurityConfiguration; import org.owasp.esapi.codecs.Base64; import org.owasp.esapi.codecs.CSSCodec; import org.owasp.esapi.codecs.Codec; @@ -445,4 +453,150 @@ public byte[] decodeFromBase64(String input) throws IOException { } return Base64.decode( input ); } + + /** + * {@inheritDoc} + * + * 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. + * + * @param dirtyUri + * @return Canonicalized URI string. + * @throws IntrusionException + */ + public String getCanonicalizedURI(URI dirtyUri) throws IntrusionException{ + +// From RFC-3986 section 3 +// URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] +// +// hier-part = "//" authority path-abempty +// / path-absolute +// / path-rootless +// / path-empty + +// The following are two example URIs and their component parts: +// +// foo://example.com:8042/over/there?name=ferret#nose +// \_/ \______________/\_________/ \_________/ \__/ +// | | | | | +// scheme authority path query fragment +// | _____________________|__ +// / \ / \ +// urn:example:animal:ferret:nose + Map parseMap = new EnumMap(UriSegment.class); + 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()); + parseMap.put(UriSegment.PORT, port == -1 ? "": port.toString()); + parseMap.put(UriSegment.PATH, dirtyUri.getRawPath()); + 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(); + + SecurityConfiguration sg = ESAPI.securityConfiguration(); + 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; + //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(); + try { + Map> canonicalizedMap = this.splitQuery(dirtyUri); + Set>> query = canonicalizedMap.entrySet(); + Iterator>> i = query.iterator(); + while(i.hasNext()){ + Entry> e = i.next(); + String key = (String) e.getKey(); + String qVal = ""; + List list = (List) e.getValue(); + if(!list.isEmpty()){ + qVal = list.get(0); + } + qBuilder.append(key) + .append("=") + .append(qVal); + + if(i.hasNext()){ + qBuilder.append("&"); + } + } + value = qBuilder.toString(); + } catch (UnsupportedEncodingException e) { + logger.debug(Logger.EVENT_FAILURE, "decoding error when parsing [" + dirtyUri.toString() + "]"); + } + } + //Check if the port is -1, if it is, omit it from the output. + if(seg == UriSegment.PORT){ + if("-1" == parseMap.get(seg)){ + value = ""; + } + } + parseMap.put(seg, value ); + } + + return buildUrl(parseMap); + } + + /** + * All the parts should be canonicalized by this point. This is straightforward assembly. + * + * @param set + * @return + */ + protected String buildUrl(Map parseMap){ + StringBuilder sb = new StringBuilder(); + 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)) + .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)) + .append((parseMap.get(UriSegment.FRAGMENT) == null) || parseMap.get(UriSegment.FRAGMENT).equals("") + ? "": "#" + parseMap.get(UriSegment.FRAGMENT)) + ; + return sb.toString(); + } + + public enum UriSegment { + AUTHORITY, SCHEME, SCHEMSPECIFICPART, USERINFO, HOST, PORT, PATH, QUERY, FRAGMENT + } + + + /** + * The meat of this method was taken from StackOverflow: http://stackoverflow.com/a/13592567/557153 + * It has been modified to return a canonicalized key and value pairing. + * + * @param java URI + * @return a map of canonicalized query parameters. + * @throws UnsupportedEncodingException + */ + public Map> splitQuery(URI uri) throws UnsupportedEncodingException { + final Map> query_pairs = new LinkedHashMap>(); + final String[] pairs = uri.getQuery().split("&"); + for (String pair : pairs) { + final int idx = pair.indexOf("="); + final String key = idx > 0 ? canonicalize(pair.substring(0, idx)) : pair; + if (!query_pairs.containsKey(key)) { + query_pairs.put(key, new LinkedList()); + } + final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null; + query_pairs.get(key).add(canonicalize(value)); + } + return query_pairs; + } } diff --git a/src/main/java/org/owasp/esapi/reference/DefaultValidator.java b/src/main/java/org/owasp/esapi/reference/DefaultValidator.java index 9b5197824..7b45f1d26 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultValidator.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultValidator.java @@ -1210,11 +1210,11 @@ private final boolean isEmpty(char[] input) { public boolean isValidURI(String context, String input, boolean allowNull) { boolean isValid = false; boolean inputIsNullOrEmpty = input == null || "".equals(input); - + Encoder encoder = ESAPI.encoder(); try{ URI compliantURI = null == input ? new URI("") : this.getRfcCompliantURI(input); if(null != compliantURI && input != null){ - String canonicalizedURI = getCanonicalizedURI(compliantURI); + String canonicalizedURI = encoder.getCanonicalizedURI(compliantURI); //if getCanonicalizedURI doesn't throw an IntrusionException, then the URI contains no mixed or //double-encoding attacks. logger.debug(Logger.SECURITY_SUCCESS, "We did not detect any mixed or multiple encoding in the uri:[" + input + "]"); @@ -1259,150 +1259,4 @@ public URI getRfcCompliantURI(String input){ } return rval; } - - /** - * {@inheritDoc} - * - * 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. - * - * @param dirtyUri - * @return Canonicalized URI string. - * @throws IntrusionException - */ - public String getCanonicalizedURI(URI dirtyUri) throws IntrusionException{ - -// From RFC-3986 section 3 -// URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] -// -// hier-part = "//" authority path-abempty -// / path-absolute -// / path-rootless -// / path-empty - -// The following are two example URIs and their component parts: -// -// foo://example.com:8042/over/there?name=ferret#nose -// \_/ \______________/\_________/ \_________/ \__/ -// | | | | | -// scheme authority path query fragment -// | _____________________|__ -// / \ / \ -// urn:example:animal:ferret:nose - Map parseMap = new EnumMap(UriSegment.class); - 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()); - parseMap.put(UriSegment.PORT, port == -1 ? "": port.toString()); - parseMap.put(UriSegment.PATH, dirtyUri.getRawPath()); - 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(); - - SecurityConfiguration sg = ESAPI.securityConfiguration(); - boolean allowMixed = sg.getBooleanProp("Encoder.AllowMixedEncoding"); - boolean allowMultiple = sg.getBooleanProp("Encoder.AllowMultipleEncoding"); - for(UriSegment seg: set){ - String value = encoder.canonicalize(parseMap.get(seg), allowMultiple, allowMixed); - value = value == null ? "" : 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(); - try { - Map> canonicalizedMap = this.splitQuery(dirtyUri); - Set>> query = canonicalizedMap.entrySet(); - Iterator>> i = query.iterator(); - while(i.hasNext()){ - Entry> e = i.next(); - String key = (String) e.getKey(); - String qVal = ""; - List list = (List) e.getValue(); - if(!list.isEmpty()){ - qVal = list.get(0); - } - qBuilder.append(key) - .append("=") - .append(qVal); - - if(i.hasNext()){ - qBuilder.append("&"); - } - } - value = qBuilder.toString(); - } catch (UnsupportedEncodingException e) { - logger.debug(Logger.EVENT_FAILURE, "decoding error when parsing [" + dirtyUri.toString() + "]"); - } - } - //Check if the port is -1, if it is, omit it from the output. - if(seg == UriSegment.PORT){ - if("-1" == parseMap.get(seg)){ - value = ""; - } - } - parseMap.put(seg, value ); - } - - return buildUrl(parseMap); - } - -/** - * The meat of this method was taken from StackOverflow: http://stackoverflow.com/a/13592567/557153 - * It has been modified to return a canonicalized key and value pairing. - * - * @param java URI - * @return a map of canonicalized query parameters. - * @throws UnsupportedEncodingException - */ - public Map> splitQuery(URI uri) throws UnsupportedEncodingException { - final Map> query_pairs = new LinkedHashMap>(); - final String[] pairs = uri.getQuery().split("&"); - for (String pair : pairs) { - final int idx = pair.indexOf("="); - final String key = idx > 0 ? encoder.canonicalize(pair.substring(0, idx)) : pair; - if (!query_pairs.containsKey(key)) { - query_pairs.put(key, new LinkedList()); - } - final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null; - query_pairs.get(key).add(encoder.canonicalize(value)); - } - return query_pairs; - } - - public enum UriSegment { - AUTHORITY, SCHEME, SCHEMSPECIFICPART, USERINFO, HOST, PORT, PATH, QUERY, FRAGMENT - } - - /** - * All the parts should be canonicalized by this point. This is straightforward assembly. - * - * @param set - * @return - */ - protected String buildUrl(Map parseMap){ - StringBuilder sb = new StringBuilder(); - 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)) - .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)) - .append((parseMap.get(UriSegment.FRAGMENT) == null) || parseMap.get(UriSegment.FRAGMENT).equals("") - ? "": "#" + parseMap.get(UriSegment.FRAGMENT)) - ; - return sb.toString(); - } - } diff --git a/src/test/java/org/owasp/esapi/reference/EncoderTest.java b/src/test/java/org/owasp/esapi/reference/EncoderTest.java index bc2e1afde..e7979af4c 100644 --- a/src/test/java/org/owasp/esapi/reference/EncoderTest.java +++ b/src/test/java/org/owasp/esapi/reference/EncoderTest.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.io.ByteArrayOutputStream; @@ -854,5 +855,32 @@ public String javaScriptEncode(String str) { } } + public void testGetCanonicalizedUri() throws Exception { + Encoder e = ESAPI.encoder(); + + String expectedUri = "http://palpatine@foo bar.com/path_to/resource?foo=bar#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#frag"; + URI uri = new URI(input); + System.out.println(uri.toString()); + assertEquals(expectedUri, e.getCanonicalizedURI(uri)); + + } + + public void testGetCanonicalizedUriWithMailto() throws Exception { + Encoder e = ESAPI.encoder(); + + String expectedUri = "http://palpatine@foo bar.com/path_to/resource?foo=bar#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#frag"; + URI uri = new URI(input); + System.out.println(uri.toString()); + assertEquals(expectedUri, e.getCanonicalizedURI(uri)); + + } } diff --git a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java index 614d25f1f..7df1227b0 100644 --- a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java +++ b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java @@ -1161,33 +1161,5 @@ public void testGetValidUriNullInput(){ boolean isValid = v.isValidURI("test", null, true); assertTrue(isValid); } - - public void testGetCanonicalizedUri() throws Exception { - Validator v = ESAPI.validator(); - - String expectedUri = "http://palpatine@foo bar.com/path_to/resource?foo=bar#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#frag"; - URI uri = new URI(input); - System.out.println(uri.toString()); - assertEquals(expectedUri, v.getCanonicalizedURI(uri)); - - } - - public void testGetCanonicalizedUriWithMailto() throws Exception { - Validator v = ESAPI.validator(); - - String expectedUri = "http://palpatine@foo bar.com/path_to/resource?foo=bar#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#frag"; - URI uri = new URI(input); - System.out.println(uri.toString()); - assertEquals(expectedUri, v.getCanonicalizedURI(uri)); - - } } From c97f70735b30b41cf4f6147b4893acf3d61795c8 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sat, 1 Jul 2017 12:26:02 -0700 Subject: [PATCH 006/709] Issue #397 -- Restore search directory to maintain legacy search behavior. --- .../DefaultSecurityConfiguration.java | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java index 22b0f2067..1d59f2b24 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java @@ -15,6 +15,23 @@ */ package org.owasp.esapi.reference; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + import org.apache.commons.lang.text.StrTokenizer; import org.owasp.esapi.ESAPI; import org.owasp.esapi.Logger; @@ -22,12 +39,6 @@ import org.owasp.esapi.configuration.EsapiPropertyManager; import org.owasp.esapi.errors.ConfigurationException; -import java.io.*; -import java.net.URL; -import java.util.*; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - /** * The reference {@code SecurityConfiguration} manages all the settings used by the ESAPI in a single place. In this reference * implementation, resources can be put in several locations, which are searched in the following order: @@ -653,8 +664,14 @@ private Properties loadConfigurationFromClasspath(String fileName) throws Illega // try resources folder if (in == null) { - currentClasspathSearchLocation = "src/main/resources/"; - in = currentLoader.getResourceAsStream("src/main/resources/" + fileName); + currentClasspathSearchLocation = "resources/"; + in = currentLoader.getResourceAsStream("resources/" + fileName); + } + + // try src/main/resources folder + if (in == null) { + currentClasspathSearchLocation = "resources/"; + in = currentLoader.getResourceAsStream("resources/" + fileName); } // now load the properties From 603408f73173145258e8677d0e1364d2e60c876e Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sat, 15 Jul 2017 12:07:52 -0700 Subject: [PATCH 007/709] Issue #399 -- Fixed mvn site so that the build no longer breaks on legacy javadoc comments. --- pom.xml | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 224f1f5c7..fd8551117 100644 --- a/pom.xml +++ b/pom.xml @@ -309,7 +309,7 @@ org.codehaus.mojo findbugs-maven-plugin - 2.5.5 + 3.0.4 true true @@ -412,6 +412,39 @@ + + doclint-java8-disable + + [1.8,) + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + -Xdoclint:none + + + + org.apache.maven.plugins + maven-site-plugin + 3.4 + + + + org.apache.maven.plugins + maven-javadoc-plugin + + -Xdoclint:none + + + + + + + + dist From a607dba2112bffcf005fdb8c9b2d4e08391e05ca Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sat, 15 Jul 2017 12:24:19 -0700 Subject: [PATCH 008/709] Issue #316 -- Fixed error where I got rid of src/main/resources and also updated the unit test to use non-deprecated asserts. --- .../DefaultSecurityConfiguration.java | 4 +- .../DefaultSecurityConfigurationTest.java | 193 +++++++++--------- 2 files changed, 101 insertions(+), 96 deletions(-) diff --git a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java index 1d59f2b24..861fced2b 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java @@ -670,8 +670,8 @@ private Properties loadConfigurationFromClasspath(String fileName) throws Illega // try src/main/resources folder if (in == null) { - currentClasspathSearchLocation = "resources/"; - in = currentLoader.getResourceAsStream("resources/" + fileName); + currentClasspathSearchLocation = "src/main/resources/"; + in = currentLoader.getResourceAsStream("src/main/resources/" + fileName); } // now load the properties diff --git a/src/test/java/org/owasp/esapi/reference/DefaultSecurityConfigurationTest.java b/src/test/java/org/owasp/esapi/reference/DefaultSecurityConfigurationTest.java index 0a7654871..fed8647af 100644 --- a/src/test/java/org/owasp/esapi/reference/DefaultSecurityConfigurationTest.java +++ b/src/test/java/org/owasp/esapi/reference/DefaultSecurityConfigurationTest.java @@ -1,8 +1,13 @@ package org.owasp.esapi.reference; -import java.util.regex.Pattern; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -import junit.framework.Assert; +import java.util.regex.Pattern; import org.junit.Test; import org.owasp.esapi.ESAPI; @@ -21,139 +26,139 @@ private DefaultSecurityConfiguration createWithProperty(String key, String val) public void testGetApplicationName() { final String expected = "ESAPI_UnitTests"; DefaultSecurityConfiguration secConf = this.createWithProperty(DefaultSecurityConfiguration.APPLICATION_NAME, expected); - Assert.assertEquals(expected, secConf.getApplicationName()); + assertEquals(expected, secConf.getApplicationName()); } @Test public void testGetLogImplementation() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_LOG_IMPLEMENTATION, secConf.getLogImplementation()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_LOG_IMPLEMENTATION, secConf.getLogImplementation()); final String expected = "TestLogger"; secConf = this.createWithProperty(DefaultSecurityConfiguration.LOG_IMPLEMENTATION, expected); - Assert.assertEquals(expected, secConf.getLogImplementation()); + assertEquals(expected, secConf.getLogImplementation()); } @Test public void testAuthenticationImplementation() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_AUTHENTICATION_IMPLEMENTATION, secConf.getAuthenticationImplementation()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_AUTHENTICATION_IMPLEMENTATION, secConf.getAuthenticationImplementation()); final String expected = "TestAuthentication"; secConf = this.createWithProperty(DefaultSecurityConfiguration.AUTHENTICATION_IMPLEMENTATION, expected); - Assert.assertEquals(expected, secConf.getAuthenticationImplementation()); + assertEquals(expected, secConf.getAuthenticationImplementation()); } @Test public void testEncoderImplementation() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_ENCODER_IMPLEMENTATION, secConf.getEncoderImplementation()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_ENCODER_IMPLEMENTATION, secConf.getEncoderImplementation()); final String expected = "TestEncoder"; secConf = this.createWithProperty(DefaultSecurityConfiguration.ENCODER_IMPLEMENTATION, expected); - Assert.assertEquals(expected, secConf.getEncoderImplementation()); + assertEquals(expected, secConf.getEncoderImplementation()); } @Test public void testAccessControlImplementation() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_ACCESS_CONTROL_IMPLEMENTATION, secConf.getAccessControlImplementation()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_ACCESS_CONTROL_IMPLEMENTATION, secConf.getAccessControlImplementation()); final String expected = "TestAccessControl"; secConf = this.createWithProperty(DefaultSecurityConfiguration.ACCESS_CONTROL_IMPLEMENTATION, expected); - Assert.assertEquals(expected, secConf.getAccessControlImplementation()); + assertEquals(expected, secConf.getAccessControlImplementation()); } @Test public void testEncryptionImplementation() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_ENCRYPTION_IMPLEMENTATION, secConf.getEncryptionImplementation()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_ENCRYPTION_IMPLEMENTATION, secConf.getEncryptionImplementation()); final String expected = "TestEncryption"; secConf = this.createWithProperty(DefaultSecurityConfiguration.ENCRYPTION_IMPLEMENTATION, expected); - Assert.assertEquals(expected, secConf.getEncryptionImplementation()); + assertEquals(expected, secConf.getEncryptionImplementation()); } @Test public void testIntrusionDetectionImplementation() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_INTRUSION_DETECTION_IMPLEMENTATION, secConf.getIntrusionDetectionImplementation()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_INTRUSION_DETECTION_IMPLEMENTATION, secConf.getIntrusionDetectionImplementation()); final String expected = "TestIntrusionDetection"; secConf = this.createWithProperty(DefaultSecurityConfiguration.INTRUSION_DETECTION_IMPLEMENTATION, expected); - Assert.assertEquals(expected, secConf.getIntrusionDetectionImplementation()); + assertEquals(expected, secConf.getIntrusionDetectionImplementation()); } @Test public void testRandomizerImplementation() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_RANDOMIZER_IMPLEMENTATION, secConf.getRandomizerImplementation()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_RANDOMIZER_IMPLEMENTATION, secConf.getRandomizerImplementation()); final String expected = "TestRandomizer"; secConf = this.createWithProperty(DefaultSecurityConfiguration.RANDOMIZER_IMPLEMENTATION, expected); - Assert.assertEquals(expected, secConf.getRandomizerImplementation()); + assertEquals(expected, secConf.getRandomizerImplementation()); } @Test public void testExecutorImplementation() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_EXECUTOR_IMPLEMENTATION, secConf.getExecutorImplementation()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_EXECUTOR_IMPLEMENTATION, secConf.getExecutorImplementation()); final String expected = "TestExecutor"; secConf = this.createWithProperty(DefaultSecurityConfiguration.EXECUTOR_IMPLEMENTATION, expected); - Assert.assertEquals(expected, secConf.getExecutorImplementation()); + assertEquals(expected, secConf.getExecutorImplementation()); } @Test public void testHTTPUtilitiesImplementation() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_HTTP_UTILITIES_IMPLEMENTATION, secConf.getHTTPUtilitiesImplementation()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_HTTP_UTILITIES_IMPLEMENTATION, secConf.getHTTPUtilitiesImplementation()); final String expected = "TestHTTPUtilities"; secConf = this.createWithProperty(DefaultSecurityConfiguration.HTTP_UTILITIES_IMPLEMENTATION, expected); - Assert.assertEquals(expected, secConf.getHTTPUtilitiesImplementation()); + assertEquals(expected, secConf.getHTTPUtilitiesImplementation()); } @Test public void testValidationImplementation() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_VALIDATOR_IMPLEMENTATION, secConf.getValidationImplementation()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_VALIDATOR_IMPLEMENTATION, secConf.getValidationImplementation()); final String expected = "TestValidation"; secConf = this.createWithProperty(DefaultSecurityConfiguration.VALIDATOR_IMPLEMENTATION, expected); - Assert.assertEquals(expected, secConf.getValidationImplementation()); + assertEquals(expected, secConf.getValidationImplementation()); } @Test public void testGetEncryptionKeyLength() { // test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(128, secConf.getEncryptionKeyLength()); + assertEquals(128, secConf.getEncryptionKeyLength()); final int expected = 256; secConf = this.createWithProperty(DefaultSecurityConfiguration.KEY_LENGTH, String.valueOf(expected)); - Assert.assertEquals(expected, secConf.getEncryptionKeyLength()); + assertEquals(expected, secConf.getEncryptionKeyLength()); } @Test public void testGetKDFPseudoRandomFunction() { // test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals("HmacSHA256", secConf.getKDFPseudoRandomFunction()); + assertEquals("HmacSHA256", secConf.getKDFPseudoRandomFunction()); final String expected = "HmacSHA1"; secConf = this.createWithProperty(DefaultSecurityConfiguration.KDF_PRF_ALG, expected); - Assert.assertEquals(expected, secConf.getKDFPseudoRandomFunction()); + assertEquals(expected, secConf.getKDFPseudoRandomFunction()); } @Test @@ -161,10 +166,10 @@ public void testGetMasterSalt() { try { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); secConf.getMasterSalt(); - Assert.fail("Expected Exception not thrown"); + fail("Expected Exception not thrown"); } catch (ConfigurationException ce) { - Assert.assertNotNull(ce.getMessage()); + assertNotNull(ce.getMessage()); } final String salt = "53081"; @@ -172,7 +177,7 @@ public void testGetMasterSalt() { java.util.Properties properties = new java.util.Properties(); properties.setProperty(DefaultSecurityConfiguration.MASTER_SALT, property); DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(properties); - Assert.assertEquals(salt, new String(secConf.getMasterSalt())); + assertEquals(salt, new String(secConf.getMasterSalt())); } @Test @@ -181,21 +186,21 @@ public void testGetAllowedExecutables() { java.util.List allowedExecutables = secConf.getAllowedExecutables(); //is this really what should be returned? what about an empty list? - Assert.assertEquals(1, allowedExecutables.size()); - Assert.assertEquals("", allowedExecutables.get(0)); + assertEquals(1, allowedExecutables.size()); + assertEquals("", allowedExecutables.get(0)); java.util.Properties properties = new java.util.Properties(); properties.setProperty(DefaultSecurityConfiguration.APPROVED_EXECUTABLES, String.valueOf("/bin/bzip2,/bin/diff, /bin/cvs")); secConf = new DefaultSecurityConfiguration(properties); allowedExecutables = secConf.getAllowedExecutables(); - Assert.assertEquals(3, allowedExecutables.size()); - Assert.assertEquals("/bin/bzip2", allowedExecutables.get(0)); - Assert.assertEquals("/bin/diff", allowedExecutables.get(1)); + assertEquals(3, allowedExecutables.size()); + assertEquals("/bin/bzip2", allowedExecutables.get(0)); + assertEquals("/bin/diff", allowedExecutables.get(1)); //this seems less than optimal, maybe each value should have a trim() done to it //at least we know that this behavior exists, the property should'nt have spaces between values - Assert.assertEquals(" /bin/cvs", allowedExecutables.get(2)); + assertEquals(" /bin/cvs", allowedExecutables.get(2)); } @Test @@ -203,189 +208,189 @@ public void testGetAllowedFileExtensions() { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); java.util.List allowedFileExtensions = secConf.getAllowedFileExtensions(); - Assert.assertFalse(allowedFileExtensions.isEmpty()); + assertFalse(allowedFileExtensions.isEmpty()); java.util.Properties properties = new java.util.Properties(); properties.setProperty(DefaultSecurityConfiguration.APPROVED_UPLOAD_EXTENSIONS, String.valueOf(".txt,.xml,.html,.png")); secConf = new DefaultSecurityConfiguration(properties); allowedFileExtensions = secConf.getAllowedFileExtensions(); - Assert.assertEquals(4, allowedFileExtensions.size()); - Assert.assertEquals(".html", allowedFileExtensions.get(2)); + assertEquals(4, allowedFileExtensions.size()); + assertEquals(".html", allowedFileExtensions.get(2)); } @Test public void testGetAllowedFileUploadSize() { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); //assert that the default is of some reasonable size - Assert.assertTrue(secConf.getAllowedFileUploadSize() > (1024 * 100)); + assertTrue(secConf.getAllowedFileUploadSize() > (1024 * 100)); final int expected = (1024 * 1000); secConf = this.createWithProperty(DefaultSecurityConfiguration.MAX_UPLOAD_FILE_BYTES, String.valueOf(expected)); - Assert.assertEquals(expected, secConf.getAllowedFileUploadSize()); + assertEquals(expected, secConf.getAllowedFileUploadSize()); } @Test public void testGetParameterNames() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals("password", secConf.getPasswordParameterName()); - Assert.assertEquals("username", secConf.getUsernameParameterName()); + assertEquals("password", secConf.getPasswordParameterName()); + assertEquals("username", secConf.getUsernameParameterName()); java.util.Properties properties = new java.util.Properties(); properties.setProperty(DefaultSecurityConfiguration.PASSWORD_PARAMETER_NAME, "j_password"); properties.setProperty(DefaultSecurityConfiguration.USERNAME_PARAMETER_NAME, "j_username"); secConf = new DefaultSecurityConfiguration(properties); - Assert.assertEquals("j_password", secConf.getPasswordParameterName()); - Assert.assertEquals("j_username", secConf.getUsernameParameterName()); + assertEquals("j_password", secConf.getPasswordParameterName()); + assertEquals("j_username", secConf.getUsernameParameterName()); } @Test public void testGetEncryptionAlgorithm() { //test the default DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals("AES", secConf.getEncryptionAlgorithm()); + assertEquals("AES", secConf.getEncryptionAlgorithm()); secConf = this.createWithProperty(DefaultSecurityConfiguration.ENCRYPTION_ALGORITHM, "3DES"); - Assert.assertEquals("3DES", secConf.getEncryptionAlgorithm()); + assertEquals("3DES", secConf.getEncryptionAlgorithm()); } @Test public void testGetCipherXProperties() { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals("AES/CBC/PKCS5Padding", secConf.getCipherTransformation()); - //Assert.assertEquals("AES/CBC/PKCS5Padding", secConf.getC); + assertEquals("AES/CBC/PKCS5Padding", secConf.getCipherTransformation()); + //assertEquals("AES/CBC/PKCS5Padding", secConf.getC); java.util.Properties properties = new java.util.Properties(); properties.setProperty(DefaultSecurityConfiguration.CIPHER_TRANSFORMATION_IMPLEMENTATION, "Blowfish/CFB/ISO10126Padding"); secConf = new DefaultSecurityConfiguration(properties); - Assert.assertEquals("Blowfish/CFB/ISO10126Padding", secConf.getCipherTransformation()); + assertEquals("Blowfish/CFB/ISO10126Padding", secConf.getCipherTransformation()); secConf.setCipherTransformation("DESede/PCBC/PKCS5Padding"); - Assert.assertEquals("DESede/PCBC/PKCS5Padding", secConf.getCipherTransformation()); + assertEquals("DESede/PCBC/PKCS5Padding", secConf.getCipherTransformation()); secConf.setCipherTransformation(null);//sets it back to default - Assert.assertEquals("Blowfish/CFB/ISO10126Padding", secConf.getCipherTransformation()); + assertEquals("Blowfish/CFB/ISO10126Padding", secConf.getCipherTransformation()); } @Test public void testIV() { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals("random", secConf.getIVType()); + assertEquals("random", secConf.getIVType()); try { secConf.getFixedIV(); - Assert.fail(); + fail(); } catch (ConfigurationException ce) { - Assert.assertNotNull(ce.getMessage()); + assertNotNull(ce.getMessage()); } java.util.Properties properties = new java.util.Properties(); properties.setProperty(DefaultSecurityConfiguration.IV_TYPE, "fixed"); properties.setProperty(DefaultSecurityConfiguration.FIXED_IV, "ivValue"); secConf = new DefaultSecurityConfiguration(properties); - Assert.assertEquals("fixed", secConf.getIVType()); - Assert.assertEquals("ivValue", secConf.getFixedIV()); + assertEquals("fixed", secConf.getIVType()); + assertEquals("ivValue", secConf.getFixedIV()); properties.setProperty(DefaultSecurityConfiguration.IV_TYPE, "illegal"); secConf = new DefaultSecurityConfiguration(properties); try { secConf.getIVType(); - Assert.fail(); + fail(); } catch (ConfigurationException ce) { - Assert.assertNotNull(ce.getMessage()); + assertNotNull(ce.getMessage()); } try { secConf.getFixedIV(); - Assert.fail(); + fail(); } catch (ConfigurationException ce) { - Assert.assertNotNull(ce.getMessage()); + assertNotNull(ce.getMessage()); } } @Test public void testGetAllowMultipleEncoding() { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertFalse(secConf.getAllowMultipleEncoding()); + assertFalse(secConf.getAllowMultipleEncoding()); secConf = this.createWithProperty(DefaultSecurityConfiguration.ALLOW_MULTIPLE_ENCODING, "yes"); - Assert.assertTrue(secConf.getAllowMultipleEncoding()); + assertTrue(secConf.getAllowMultipleEncoding()); secConf = this.createWithProperty(DefaultSecurityConfiguration.ALLOW_MULTIPLE_ENCODING, "true"); - Assert.assertTrue(secConf.getAllowMultipleEncoding()); + assertTrue(secConf.getAllowMultipleEncoding()); secConf = this.createWithProperty(DefaultSecurityConfiguration.ALLOW_MULTIPLE_ENCODING, "no"); - Assert.assertFalse(secConf.getAllowMultipleEncoding()); + assertFalse(secConf.getAllowMultipleEncoding()); } @Test public void testGetDefaultCanonicalizationCodecs() { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertFalse(secConf.getDefaultCanonicalizationCodecs().isEmpty()); + assertFalse(secConf.getDefaultCanonicalizationCodecs().isEmpty()); String property = "org.owasp.esapi.codecs.TestCodec1,org.owasp.esapi.codecs.TestCodec2"; secConf = this.createWithProperty(DefaultSecurityConfiguration.CANONICALIZATION_CODECS, property); - Assert.assertTrue(secConf.getDefaultCanonicalizationCodecs().contains("org.owasp.esapi.codecs.TestCodec1")); + assertTrue(secConf.getDefaultCanonicalizationCodecs().contains("org.owasp.esapi.codecs.TestCodec1")); } @Test public void testGetDisableIntrusionDetection() { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertFalse(secConf.getDisableIntrusionDetection()); + assertFalse(secConf.getDisableIntrusionDetection()); secConf = this.createWithProperty(DefaultSecurityConfiguration.DISABLE_INTRUSION_DETECTION, "TRUE"); - Assert.assertTrue(secConf.getDisableIntrusionDetection()); + assertTrue(secConf.getDisableIntrusionDetection()); secConf = this.createWithProperty(DefaultSecurityConfiguration.DISABLE_INTRUSION_DETECTION, "true"); - Assert.assertTrue(secConf.getDisableIntrusionDetection()); + assertTrue(secConf.getDisableIntrusionDetection()); secConf = this.createWithProperty(DefaultSecurityConfiguration.DISABLE_INTRUSION_DETECTION, "false"); - Assert.assertFalse(secConf.getDisableIntrusionDetection()); + assertFalse(secConf.getDisableIntrusionDetection()); } @Test public void testGetLogLevel() { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(Logger.WARNING, secConf.getLogLevel()); + assertEquals(Logger.WARNING, secConf.getLogLevel()); secConf = this.createWithProperty(DefaultSecurityConfiguration.LOG_LEVEL, "trace"); - Assert.assertEquals(Logger.TRACE, secConf.getLogLevel()); + assertEquals(Logger.TRACE, secConf.getLogLevel()); secConf = this.createWithProperty(DefaultSecurityConfiguration.LOG_LEVEL, "Off"); - Assert.assertEquals(Logger.OFF, secConf.getLogLevel()); + assertEquals(Logger.OFF, secConf.getLogLevel()); secConf = this.createWithProperty(DefaultSecurityConfiguration.LOG_LEVEL, "all"); - Assert.assertEquals(Logger.ALL, secConf.getLogLevel()); + assertEquals(Logger.ALL, secConf.getLogLevel()); secConf = this.createWithProperty(DefaultSecurityConfiguration.LOG_LEVEL, "DEBUG"); - Assert.assertEquals(Logger.DEBUG, secConf.getLogLevel()); + assertEquals(Logger.DEBUG, secConf.getLogLevel()); secConf = this.createWithProperty(DefaultSecurityConfiguration.LOG_LEVEL, "info"); - Assert.assertEquals(Logger.INFO, secConf.getLogLevel()); + assertEquals(Logger.INFO, secConf.getLogLevel()); secConf = this.createWithProperty(DefaultSecurityConfiguration.LOG_LEVEL, "ERROR"); - Assert.assertEquals(Logger.ERROR, secConf.getLogLevel()); + assertEquals(Logger.ERROR, secConf.getLogLevel()); } @Test public void testGetLogFileName() { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals("ESAPI_logging_file", secConf.getLogFileName()); + assertEquals("ESAPI_logging_file", secConf.getLogFileName()); secConf = this.createWithProperty(DefaultSecurityConfiguration.LOG_FILE_NAME, "log.txt"); - Assert.assertEquals("log.txt", secConf.getLogFileName()); + assertEquals("log.txt", secConf.getLogFileName()); } @Test public void testGetMaxLogFileSize() { DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration(new java.util.Properties()); - Assert.assertEquals(DefaultSecurityConfiguration.DEFAULT_MAX_LOG_FILE_SIZE, secConf.getMaxLogFileSize()); + assertEquals(DefaultSecurityConfiguration.DEFAULT_MAX_LOG_FILE_SIZE, secConf.getMaxLogFileSize()); int maxLogSize = (1024 * 1000); secConf = this.createWithProperty(DefaultSecurityConfiguration.MAX_LOG_FILE_SIZE, String.valueOf(maxLogSize)); - Assert.assertEquals(maxLogSize, secConf.getMaxLogFileSize()); + assertEquals(maxLogSize, secConf.getMaxLogFileSize()); } private String patternOrNull(Pattern p){ @@ -395,24 +400,24 @@ private String patternOrNull(Pattern p){ @Test public void testValidationsPropertiesFileOptions(){ DefaultSecurityConfiguration secConf = new DefaultSecurityConfiguration("ESAPI-SingleValidatorFileChecker.properties"); - Assert.assertEquals(patternOrNull(secConf.getValidationPattern("Test1")), "ValueFromFile1"); - Assert.assertNull(secConf.getValidationPattern("Test2")); - Assert.assertNull(secConf.getValidationPattern("TestC")); + assertEquals(patternOrNull(secConf.getValidationPattern("Test1")), "ValueFromFile1"); + assertNull(secConf.getValidationPattern("Test2")); + assertNull(secConf.getValidationPattern("TestC")); secConf = new DefaultSecurityConfiguration("ESAPI-DualValidatorFileChecker.properties"); - Assert.assertEquals(patternOrNull(secConf.getValidationPattern("Test1")), "ValueFromFile1"); - Assert.assertEquals(patternOrNull(secConf.getValidationPattern("Test2")), "ValueFromFile2"); - Assert.assertNull(secConf.getValidationPattern("TestC")); + assertEquals(patternOrNull(secConf.getValidationPattern("Test1")), "ValueFromFile1"); + assertEquals(patternOrNull(secConf.getValidationPattern("Test2")), "ValueFromFile2"); + assertNull(secConf.getValidationPattern("TestC")); secConf = new DefaultSecurityConfiguration("ESAPI-CommaValidatorFileChecker.properties"); - Assert.assertEquals(patternOrNull(secConf.getValidationPattern("TestC")), "ValueFromCommaFile"); - Assert.assertNull(secConf.getValidationPattern("Test1")); - Assert.assertNull(secConf.getValidationPattern("Test2")); + assertEquals(patternOrNull(secConf.getValidationPattern("TestC")), "ValueFromCommaFile"); + assertNull(secConf.getValidationPattern("Test1")); + assertNull(secConf.getValidationPattern("Test2")); secConf = new DefaultSecurityConfiguration("ESAPI-QuotedValidatorFileChecker.properties"); - Assert.assertEquals(patternOrNull(secConf.getValidationPattern("Test1")), "ValueFromFile1"); - Assert.assertEquals(patternOrNull(secConf.getValidationPattern("Test2")), "ValueFromFile2"); - Assert.assertEquals(patternOrNull(secConf.getValidationPattern("TestC")), "ValueFromCommaFile"); + assertEquals(patternOrNull(secConf.getValidationPattern("Test1")), "ValueFromFile1"); + assertEquals(patternOrNull(secConf.getValidationPattern("Test2")), "ValueFromFile2"); + assertEquals(patternOrNull(secConf.getValidationPattern("TestC")), "ValueFromCommaFile"); } } From 283753841aa927eb535ddfddb5c899fd7721d39d Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sat, 15 Jul 2017 19:21:21 -0700 Subject: [PATCH 009/709] Issue #316 -- Added unit test to ensure search directory changes are adequately updated in the future, and upped JVM compliance level to 1.7 since we already decided that 1.6 is more than defunct. --- .../DefaultSecurityConfiguration.java | 34 +++++++++++++++---- .../DefaultSecurityConfigurationTest.java | 17 ++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java index 861fced2b..a1f551e7d 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java @@ -642,36 +642,36 @@ private Properties loadConfigurationFromClasspath(String fileName) throws Illega try { // try root String currentClasspathSearchLocation = "/ (root)"; - in = loaders[i].getResourceAsStream(fileName); + in = loaders[i].getResourceAsStream(DefaultSearchPath.ROOT.toString()); // try resourceDirectory folder if (in == null) { currentClasspathSearchLocation = resourceDirectory + "/"; - in = currentLoader.getResourceAsStream(resourceDirectory + "/" + fileName); + in = currentLoader.getResourceAsStream(DefaultSearchPath.RESOURCE_DIRECTORY + fileName); } // try .esapi folder. Look here first for backward compatibility. if (in == null) { currentClasspathSearchLocation = ".esapi/"; - in = currentLoader.getResourceAsStream(".esapi/" + fileName); + in = currentLoader.getResourceAsStream(DefaultSearchPath.DOT_ESAPI + fileName); } // try esapi folder (new directory) if (in == null) { currentClasspathSearchLocation = "esapi/"; - in = currentLoader.getResourceAsStream("esapi/" + fileName); + in = currentLoader.getResourceAsStream(DefaultSearchPath.ESAPI + fileName); } // try resources folder if (in == null) { currentClasspathSearchLocation = "resources/"; - in = currentLoader.getResourceAsStream("resources/" + fileName); + in = currentLoader.getResourceAsStream(DefaultSearchPath.RESOURCES + fileName); } // try src/main/resources folder if (in == null) { currentClasspathSearchLocation = "src/main/resources/"; - in = currentLoader.getResourceAsStream("src/main/resources/" + fileName); + in = currentLoader.getResourceAsStream(DefaultSearchPath.SRC_MAIN_RESOURCES + fileName); } // now load the properties @@ -1347,4 +1347,26 @@ protected boolean shouldPrintProperties() { protected Properties getESAPIProperties() { return properties; } + + public enum DefaultSearchPath { + + RESOURCE_DIRECTORY("resourceDirectory/"), + SRC_MAIN_RESOURCES("src/main/resources/"), + ROOT("/"), + DOT_ESAPI(".esapi/"), + ESAPI("esapi/"), + RESOURCES("resources/"); + + private final String path; + + + + private DefaultSearchPath(String s){ + this.path = s; + } + + public String value(){ + return path; + } + } } diff --git a/src/test/java/org/owasp/esapi/reference/DefaultSecurityConfigurationTest.java b/src/test/java/org/owasp/esapi/reference/DefaultSecurityConfigurationTest.java index fed8647af..5171da6a5 100644 --- a/src/test/java/org/owasp/esapi/reference/DefaultSecurityConfigurationTest.java +++ b/src/test/java/org/owasp/esapi/reference/DefaultSecurityConfigurationTest.java @@ -13,6 +13,7 @@ import org.owasp.esapi.ESAPI; import org.owasp.esapi.Logger; import org.owasp.esapi.errors.ConfigurationException; +import org.owasp.esapi.reference.DefaultSecurityConfiguration.DefaultSearchPath; public class DefaultSecurityConfigurationTest { @@ -420,4 +421,20 @@ public void testValidationsPropertiesFileOptions(){ assertEquals(patternOrNull(secConf.getValidationPattern("TestC")), "ValueFromCommaFile"); } + @Test + public void DefaultSearchPathTest(){ + assertEquals("/", DefaultSearchPath.ROOT.value()); + assertEquals("resourceDirectory/", DefaultSearchPath.RESOURCE_DIRECTORY.value()); + assertEquals(".esapi/", DefaultSearchPath.DOT_ESAPI.value()); + assertEquals("esapi/", DefaultSearchPath.ESAPI.value()); + assertEquals("resources/", DefaultSearchPath.RESOURCES.value()); + assertEquals("src/main/resources/", DefaultSearchPath.SRC_MAIN_RESOURCES.value()); + } + + @Test + public void DefaultSearchPathEnumChanges(){ + int expected = 6; + int testValue = DefaultSearchPath.values().length; + assertEquals(expected, testValue); + } } From 262c342723d9c043559ada1c440dc061bdec1161 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sun, 16 Jul 2017 09:27:46 -0700 Subject: [PATCH 010/709] Issue #399 -- Updated pom.xml so the base compile version is 1.7 instead of 1.6. This eliminates incompatibilities with mvn site that was preventing newer plugins from using items like java.util.Function. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index fd8551117..b66f5ee1a 100644 --- a/pom.xml +++ b/pom.xml @@ -219,8 +219,8 @@ maven-compiler-plugin 3.3 - 1.6 - 1.6 + 1.7 + 1.7 true true false From df2c9783b3e4112519af3722e21c3d7499eab101 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sun, 16 Jul 2017 13:13:43 -0700 Subject: [PATCH 011/709] Issue #398 -- Fixed hardcoded instance of HTTP header and made it configurable. --- .../org/owasp/esapi/filters/SecurityWrapperResponse.java | 6 ++++-- .../esapi/reference/DefaultSecurityConfiguration.java | 2 -- src/test/java/org/owasp/esapi/reference/ValidatorTest.java | 7 +++++++ src/test/resources/esapi/ESAPI.properties | 6 ++++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java index 9dd0632ce..63a403879 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java @@ -24,6 +24,7 @@ import org.owasp.esapi.ESAPI; import org.owasp.esapi.Logger; +import org.owasp.esapi.SecurityConfiguration; import org.owasp.esapi.StringUtilities; import org.owasp.esapi.ValidationErrorList; import org.owasp.esapi.errors.IntrusionException; @@ -168,10 +169,11 @@ public void addDateHeader(String name, long date) { public void addHeader(String name, String value) { try { // TODO: make stripping a global config + SecurityConfiguration sc = ESAPI.securityConfiguration(); String strippedName = StringUtilities.stripControls(name); String strippedValue = StringUtilities.stripControls(value); - String safeName = ESAPI.validator().getValidInput("addHeader", strippedName, "HTTPHeaderName", 20, false); - String safeValue = ESAPI.validator().getValidInput("addHeader", strippedValue, "HTTPHeaderValue", ESAPI.securityConfiguration().getMaxHttpHeaderSize(), false); + String safeName = ESAPI.validator().getValidInput("addHeader", strippedName, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); + String safeValue = ESAPI.validator().getValidInput("addHeader", strippedValue, "HTTPHeaderValue", sc.getIntProp("HttpUtilities.MaxHeaderValueSize"), false); getHttpServletResponse().addHeader(safeName, safeValue); } catch (ValidationException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to add invalid header denied", e); diff --git a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java index a1f551e7d..bf2a79696 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java @@ -1359,8 +1359,6 @@ public enum DefaultSearchPath { private final String path; - - private DefaultSearchPath(String s){ this.path = s; } diff --git a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java index 7df1227b0..e10b47e48 100644 --- a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java +++ b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java @@ -1087,6 +1087,13 @@ public void testGetHeader() { assertFalse(safeRequest.getHeader("f2").equals(request.getHeader("f2"))); assertNull(safeRequest.getHeader("p3")); } + + public void testHeaderLengthChecks(){ + Validator v = ESAPI.validator(); + SecurityConfiguration sc = ESAPI.securityConfiguration(); + assertFalse(v.isValidInput("addHeader", generateStringOfLength(257), "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false)); + assertFalse(v.isValidInput("addHeader", generateStringOfLength(4097), "HTTPHeaderValue", sc.getIntProp("HttpUtilities.MaxHeaderValueSize"), false)); + } public void testGetHeaderNames() { //testing Validator.HTTPHeaderName diff --git a/src/test/resources/esapi/ESAPI.properties b/src/test/resources/esapi/ESAPI.properties index 2727d4ada..becadfee7 100644 --- a/src/test/resources/esapi/ESAPI.properties +++ b/src/test/resources/esapi/ESAPI.properties @@ -331,8 +331,10 @@ HttpUtilities.ForceHttpOnlySession=false HttpUtilities.ForceSecureSession=false HttpUtilities.ForceHttpOnlyCookies=true HttpUtilities.ForceSecureCookies=true -# Maximum size of HTTP headers -HttpUtilities.MaxHeaderSize=4096 +# Maximum size of HTTP header key +HttpUtilities.MaxHeaderKeySize=256 +# Maximum size of HTTP header value +HttpUtilities.MaxHeaderValueSize=4096 # File upload configuration HttpUtilities.ApprovedUploadExtensions=.zip,.pdf,.doc,.docx,.ppt,.pptx,.tar,.gz,.tgz,.rar,.war,.jar,.ear,.xls,.rtf,.properties,.java,.class,.txt,.xml,.jsp,.jsf,.exe,.dll HttpUtilities.MaxUploadFileBytes=500000000 From 005173a2892f0a9bacd2b8edd98843751008cd19 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Fri, 21 Jul 2017 09:56:23 -0700 Subject: [PATCH 012/709] Added simple unit test to validate changes that made HTML entities always lower case. Associated with issues #285 and #302. --- src/test/java/org/owasp/esapi/reference/EncoderTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/org/owasp/esapi/reference/EncoderTest.java b/src/test/java/org/owasp/esapi/reference/EncoderTest.java index 6aad8c773..5bd553b25 100644 --- a/src/test/java/org/owasp/esapi/reference/EncoderTest.java +++ b/src/test/java/org/owasp/esapi/reference/EncoderTest.java @@ -469,6 +469,7 @@ public void testEncodeForLDAP() { assertEquals("No special characters to escape", "Hi This is a test #��", instance.encodeForLDAP("Hi This is a test #��")); assertEquals("Zeros", "Hi \\00", instance.encodeForLDAP("Hi \u0000")); assertEquals("LDAP Christams Tree", "Hi \\28This\\29 = is \\2a a \\5c test # � � �", instance.encodeForLDAP("Hi (This) = is * a \\ test # � � �")); + assertEquals("Hi \\28This\\29 =", instance.encodeForLDAP("Hi (This) =")); } /** @@ -499,6 +500,14 @@ public void testEncodeForDN() { assertEquals("Christmas Tree DN", "\\ Hello\\\\ \\+ \\, \\\"World\\\" \\;\\ ", instance.encodeForDN(" Hello\\ + , \"World\" ; ")); } + /** + * Longstanding issue of always lowercasing named HTML entities. This will be set right now. + */ + public void testNamedUpperCaseDecoding(){ + String input = "Ü"; + String expected = "Ü"; + assertEquals(expected, ESAPI.encoder().decodeForHTML(input)); + } public void testEncodeForXMLNull() { Encoder instance = ESAPI.encoder(); assertEquals(null, instance.encodeForXML(null)); From 4a7357d07ab46d7a659790200a22fd8085b5819e Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Fri, 21 Jul 2017 15:04:53 -0700 Subject: [PATCH 013/709] Issue #403 -- Converted SecurityWrapperResponse.java to get rid of magic numbers and to get header lengths from ESAPI.properties. --- .../filters/SecurityWrapperResponse.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java index 3d8796649..515bf7376 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java @@ -151,7 +151,8 @@ private String createCookieHeader(String name, String value, int maxAge, String */ public void addDateHeader(String name, long date) { try { - String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", 20, false); + SecurityConfiguration sc = ESAPI.securityConfiguration(); + String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); getHttpServletResponse().addDateHeader(safeName, date); } catch (ValidationException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid date header name denied", e); @@ -184,13 +185,14 @@ public void addHeader(String name, String value) { /** * Add an int header to the response after ensuring that there are no - * encoded or illegal characters in the name and name. + * encoded or illegal characters in the name and value. * @param name * @param value */ public void addIntHeader(String name, int value) { try { - String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", 20, false); + SecurityConfiguration sc = ESAPI.securityConfiguration(); + String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); getHttpServletResponse().addIntHeader(safeName, value); } catch (ValidationException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid int header name denied", e); @@ -428,7 +430,8 @@ public void setContentType(String type) { */ public void setDateHeader(String name, long date) { try { - String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", 20, false); + SecurityConfiguration sc = ESAPI.securityConfiguration(); + String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); getHttpServletResponse().setDateHeader(safeName, date); } catch (ValidationException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid date header name denied", e); @@ -448,8 +451,9 @@ public void setHeader(String name, String value) { try { String strippedName = StringUtilities.stripControls(name); String strippedValue = StringUtilities.stripControls(value); - String safeName = ESAPI.validator().getValidInput("setHeader", strippedName, "HTTPHeaderName", 50, false); - String safeValue = ESAPI.validator().getValidInput("setHeader", strippedValue, "HTTPHeaderValue", ESAPI.securityConfiguration().getMaxHttpHeaderSize(), false); + SecurityConfiguration sc = ESAPI.securityConfiguration(); + String safeName = ESAPI.validator().getValidInput("setHeader", strippedName, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); + String safeValue = ESAPI.validator().getValidInput("setHeader", strippedValue, "HTTPHeaderValue", sc.getIntProp("HttpUtilities.MaxHeaderValueSize"), false); getHttpServletResponse().setHeader(safeName, safeValue); } catch (ValidationException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid header denied", e); @@ -464,7 +468,8 @@ public void setHeader(String name, String value) { */ public void setIntHeader(String name, int value) { try { - String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", 20, false); + SecurityConfiguration sc = ESAPI.securityConfiguration(); + String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); getHttpServletResponse().setIntHeader(safeName, value); } catch (ValidationException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid int header name denied", e); From 86ffb3d33a2846b9b0a3e34a1ca0461f6a680247 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Fri, 21 Jul 2017 15:07:14 -0700 Subject: [PATCH 014/709] Issue #400 -- Added mocking framework to maven test baseline and completed first minor unit tests of SecurityWrapperResponse. --- pom.xml | 38 ++++++++++++++- .../filters/SecurityWrapperResponseTest.java | 46 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java diff --git a/pom.xml b/pom.xml index b66f5ee1a..7bc646277 100644 --- a/pom.xml +++ b/pom.xml @@ -205,12 +205,48 @@ batik-css 1.9 - + + + + org.powermock + powermock-api-mockito + 1.7.0 + test + + + org.powermock + powermock-module-junit4 + 1.7.0 + test + + + + + diff --git a/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java b/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java new file mode 100644 index 000000000..01eb5aca8 --- /dev/null +++ b/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java @@ -0,0 +1,46 @@ +package org.owasp.esapi.filters; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.owasp.esapi.util.TestUtils; +import org.powermock.modules.junit4.PowerMockRunner; + +//@PrepareForTest({SecurityWrapperResponse.class}) +@RunWith(PowerMockRunner.class) +public class SecurityWrapperResponseTest { + + @Test + public void testAddHeader(){ + HttpServletResponse servResp = mock(HttpServletResponse.class); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + resp.addHeader("Foo", "bar"); + verify(servResp, times(1)).addHeader("Foo", "bar"); + } + + @Test + public void testAddHeaderInvalidValueLength(){ + //refactor this to use a spy. + HttpServletResponse servResp = mock(HttpServletResponse.class); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + SecurityWrapperResponse spyResp = spy(resp); + Mockito.doCallRealMethod().when(spyResp).addHeader("Foo", TestUtils.generateStringOfLength(4097)); + resp.addHeader("Foo", TestUtils.generateStringOfLength(4097)); + verify(servResp, times(0)).addHeader("Foo", "bar"); + } + + @Test + public void testAddHeaderInvalidKeyLength(){ + HttpServletResponse servResp = mock(HttpServletResponse.class); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + resp.addHeader(TestUtils.generateStringOfLength(257), "bar"); + verify(servResp, times(0)).addHeader("Foo", "bar"); + } +} From 7c90eb797c52f0d070c2edeebcab2c9781f7f389 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sun, 23 Jul 2017 11:24:22 -0700 Subject: [PATCH 015/709] Issue #400 -- added unit tests for addCookie method, implicitly tests a private method within the class as well. I also converted the class under test to use configurable header length from securityConfiguration. --- .../filters/SecurityWrapperResponse.java | 9 +- .../filters/SecurityWrapperResponseTest.java | 84 ++++++++++++++++++- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java index 515bf7376..019b26c0a 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java @@ -82,11 +82,12 @@ public void addCookie(Cookie cookie) { String domain = cookie.getDomain(); String path = cookie.getPath(); boolean secure = cookie.getSecure(); + SecurityConfiguration sc = ESAPI.securityConfiguration(); // validate the name and value ValidationErrorList errors = new ValidationErrorList(); - String cookieName = ESAPI.validator().getValidInput("cookie name", name, "HTTPCookieName", 50, false, errors); - String cookieValue = ESAPI.validator().getValidInput("cookie value", value, "HTTPCookieValue", ESAPI.securityConfiguration().getMaxHttpHeaderSize(), false, errors); + String cookieName = ESAPI.validator().getValidInput("cookie name", name, "HTTPCookieName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false, errors); + String cookieValue = ESAPI.validator().getValidInput("cookie value", value, "HTTPCookieValue", sc.getIntProp("HttpUtilities.MaxHeaderValueSize"), false, errors); // if there are no errors, then just set a cookie header if (errors.size() == 0) { @@ -134,10 +135,10 @@ private String createCookieHeader(String name, String value, int maxAge, String if (path != null) { header += "; Path=" + path; } - if ( secure || ESAPI.securityConfiguration().getForceSecureCookies() ) { + if ( secure || ESAPI.securityConfiguration().getBooleanProp("HttpUtilities.ForceSecureCookies") ) { header += "; Secure"; } - if ( ESAPI.securityConfiguration().getForceHttpOnlyCookies() ) { + if ( ESAPI.securityConfiguration().getBooleanProp("HttpUtilities.ForceHttpOnlyCookies") ) { header += "; HttpOnly"; } return header; diff --git a/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java b/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java index 01eb5aca8..ecbbad138 100644 --- a/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java +++ b/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java @@ -1,15 +1,20 @@ package org.owasp.esapi.filters; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; + +import java.util.Collection; + +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import org.owasp.esapi.http.MockHttpServletResponse; import org.owasp.esapi.util.TestUtils; import org.powermock.modules.junit4.PowerMockRunner; @@ -25,6 +30,24 @@ public void testAddHeader(){ verify(servResp, times(1)).addHeader("Foo", "bar"); } + @Test + public void testAddDateHeader(){ + HttpServletResponse servResp = mock(HttpServletResponse.class); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + long currentTime = System.currentTimeMillis(); + resp.addDateHeader("Foo", currentTime); + verify(servResp, times(1)).addDateHeader("Foo", currentTime); + } + + @Test + public void testInvalidDateHeader(){ + HttpServletResponse servResp = mock(HttpServletResponse.class); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + long currentTime = System.currentTimeMillis(); + resp.addDateHeader("Foo\\r\\n", currentTime); + verify(servResp, times(0)).addDateHeader("Foo", currentTime); + } + @Test public void testAddHeaderInvalidValueLength(){ //refactor this to use a spy. @@ -43,4 +66,63 @@ public void testAddHeaderInvalidKeyLength(){ resp.addHeader(TestUtils.generateStringOfLength(257), "bar"); verify(servResp, times(0)).addHeader("Foo", "bar"); } + + @Test + public void testAddValidCookie(){ + HttpServletResponse servResp = new MockHttpServletResponse(); + servResp = spy(servResp); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + SecurityWrapperResponse spyResp = spy(resp); + Cookie cookie = new Cookie("Foo", TestUtils.generateStringOfLength(10)); + Mockito.doCallRealMethod().when(spyResp).addCookie(cookie); + spyResp.addCookie(cookie); + /* + * We're indirectly testing our class. Since it ultimately + * delegates to HttpServletResponse.addHeader, we're actually + * validating that our test method constructs a header with the + * expected properties. This implicitly tests the + * createCookieHeader method as well. + */ + verify(servResp, times(1)).addHeader("Set-Cookie", "Foo=aaaaaaaaaa; Secure; HttpOnly"); + } + + @Test + public void testAddValidCookieWithDomain(){ + HttpServletResponse servResp = new MockHttpServletResponse(); + servResp = spy(servResp); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + SecurityWrapperResponse spyResp = spy(resp); + Cookie cookie = new Cookie("Foo", TestUtils.generateStringOfLength(10)); + cookie.setDomain("evil.com"); + Mockito.doCallRealMethod().when(spyResp).addCookie(cookie); + spyResp.addCookie(cookie); + verify(servResp, times(1)).addHeader("Set-Cookie", "Foo=aaaaaaaaaa; Domain=evil.com; Secure; HttpOnly"); + } + + @Test + public void testAddValidCookieWithPath(){ + HttpServletResponse servResp = new MockHttpServletResponse(); + servResp = spy(servResp); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + SecurityWrapperResponse spyResp = spy(resp); + Cookie cookie = new Cookie("Foo", TestUtils.generateStringOfLength(10)); + cookie.setDomain("evil.com"); + cookie.setPath("/foo/bar"); + Mockito.doCallRealMethod().when(spyResp).addCookie(cookie); + spyResp.addCookie(cookie); + verify(servResp, times(1)).addHeader("Set-Cookie", "Foo=aaaaaaaaaa; Domain=evil.com; Path=/foo/bar; Secure; HttpOnly"); + } + + @Test + public void testAddInValidCookie(){ + HttpServletResponse servResp = new MockHttpServletResponse(); + servResp = spy(servResp); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + SecurityWrapperResponse spyResp = spy(resp); + Cookie cookie = new Cookie("Foo", TestUtils.generateStringOfLength(5000)); + Mockito.doCallRealMethod().when(spyResp).addCookie(cookie); + + spyResp.addCookie(cookie); + verify(servResp, times(0)).addHeader("Set-Cookie", "Foo=" + TestUtils.generateStringOfLength(5000) + "; Secure; HttpOnly"); + } } From f97a93ab7498579ce79488ee63f48e63b93ae4f2 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Mon, 24 Jul 2017 14:53:02 -0700 Subject: [PATCH 016/709] Issue #400 -- Added more unit tests. Also did work for #403, as well as #383. --- .../esapi/filters/SecurityWrapperRequest.java | 6 ++- .../filters/SecurityWrapperResponse.java | 42 ++++++++++++------ .../filters/SecurityWrapperResponseTest.java | 27 ++++++++++++ .../owasp/esapi/reference/EncoderTest.java | 1 + .../owasp/esapi/reference/ValidatorTest.java | 44 ++++++++----------- .../java/org/owasp/esapi/util/TestUtils.java | 14 ++++++ src/test/resources/esapi/ESAPI.properties | 9 ++-- 7 files changed, 98 insertions(+), 45 deletions(-) create mode 100644 src/test/java/org/owasp/esapi/util/TestUtils.java diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java index c267361c2..3f6a53f3c 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java @@ -35,8 +35,9 @@ import org.owasp.esapi.ESAPI; import org.owasp.esapi.Logger; -import org.owasp.esapi.errors.ValidationException; +import org.owasp.esapi.SecurityConfiguration; import org.owasp.esapi.errors.AccessControlException; +import org.owasp.esapi.errors.ValidationException; // TODO: Parameterize these various lengths in calls to ESAPI.validator().getValidInput() // so that they can be placed in ESAPI.properties file (or other property file, @@ -217,10 +218,11 @@ public String getHeader(String name) { public Enumeration getHeaderNames() { Vector v = new Vector(); Enumeration en = getHttpServletRequest().getHeaderNames(); + SecurityConfiguration sc = ESAPI.securityConfiguration(); while (en.hasMoreElements()) { try { String name = (String) en.nextElement(); - String clean = ESAPI.validator().getValidInput("HTTP header name: " + name, name, "HTTPHeaderName", 150, true); + String clean = ESAPI.validator().getValidInput("HTTP header name: " + name, name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderNameSize"), true); v.add(clean); } catch (ValidationException e) { // already logged diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java index 019b26c0a..82e629557 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java @@ -86,7 +86,7 @@ public void addCookie(Cookie cookie) { // validate the name and value ValidationErrorList errors = new ValidationErrorList(); - String cookieName = ESAPI.validator().getValidInput("cookie name", name, "HTTPCookieName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false, errors); + String cookieName = ESAPI.validator().getValidInput("cookie name", name, "HTTPCookieName", sc.getIntProp("HttpUtilities.MaxHeaderNameSize"), false, errors); String cookieValue = ESAPI.validator().getValidInput("cookie value", value, "HTTPCookieValue", sc.getIntProp("HttpUtilities.MaxHeaderValueSize"), false, errors); // if there are no errors, then just set a cookie header @@ -153,7 +153,7 @@ private String createCookieHeader(String name, String value, int maxAge, String public void addDateHeader(String name, long date) { try { SecurityConfiguration sc = ESAPI.securityConfiguration(); - String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); + String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderNameSize"), false); getHttpServletResponse().addDateHeader(safeName, date); } catch (ValidationException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid date header name denied", e); @@ -176,7 +176,7 @@ public void addHeader(String name, String value) { SecurityConfiguration sc = ESAPI.securityConfiguration(); String strippedName = StringUtilities.stripControls(name); String strippedValue = StringUtilities.stripControls(value); - String safeName = ESAPI.validator().getValidInput("addHeader", strippedName, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); + String safeName = ESAPI.validator().getValidInput("addHeader", strippedName, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderNameSize"), false); String safeValue = ESAPI.validator().getValidInput("addHeader", strippedValue, "HTTPHeaderValue", sc.getIntProp("HttpUtilities.MaxHeaderValueSize"), false); getHttpServletResponse().addHeader(safeName, safeValue); } catch (ValidationException e) { @@ -193,7 +193,7 @@ public void addHeader(String name, String value) { public void addIntHeader(String name, int value) { try { SecurityConfiguration sc = ESAPI.securityConfiguration(); - String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); + String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderNameSize"), false); getHttpServletResponse().addIntHeader(safeName, value); } catch (ValidationException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid int header name denied", e); @@ -353,24 +353,38 @@ public void resetBuffer() { /** * Override the error code with a 200 in order to confound attackers using - * automated scanners. - * @param sc + * automated scanners. Overwriting is controlled by {@code HttpUtilities.OverwriteStatusCodes} + * in ESAPI.properties. + * @param sc -- http status code * @throws IOException */ public void sendError(int sc) throws IOException { - getHttpServletResponse().sendError(HttpServletResponse.SC_OK, getHTTPMessage(sc)); + SecurityConfiguration config = ESAPI.securityConfiguration(); + if(config.getBooleanProp("HttpUtilities.OverwriteStatusCodes")){ + getHttpServletResponse().sendError(HttpServletResponse.SC_OK, getHTTPMessage(sc)); + }else{ + getHttpServletResponse().sendError(sc, getHTTPMessage(sc)); + } + } /** * Override the error code with a 200 in order to confound attackers using * automated scanners. The message is canonicalized and filtered for - * dangerous characters. - * @param sc - * @param msg + * dangerous characters. Overwriting is controlled by {@code HttpUtilities.OverwriteStatusCodes} + * in ESAPI.properties. + * @param sc -- http status code + * @param msg -- error message * @throws IOException */ public void sendError(int sc, String msg) throws IOException { - getHttpServletResponse().sendError(HttpServletResponse.SC_OK, ESAPI.encoder().encodeForHTML(msg)); + SecurityConfiguration config = ESAPI.securityConfiguration(); + if(config.getBooleanProp("HttpUtilities.OverwriteStatusCodes")){ + getHttpServletResponse().sendError(HttpServletResponse.SC_OK, ESAPI.encoder().encodeForHTML(msg)); + }else{ + getHttpServletResponse().sendError(sc, ESAPI.encoder().encodeForHTML(msg)); + } + } /** @@ -432,7 +446,7 @@ public void setContentType(String type) { public void setDateHeader(String name, long date) { try { SecurityConfiguration sc = ESAPI.securityConfiguration(); - String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); + String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderNameSize"), false); getHttpServletResponse().setDateHeader(safeName, date); } catch (ValidationException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid date header name denied", e); @@ -453,7 +467,7 @@ public void setHeader(String name, String value) { String strippedName = StringUtilities.stripControls(name); String strippedValue = StringUtilities.stripControls(value); SecurityConfiguration sc = ESAPI.securityConfiguration(); - String safeName = ESAPI.validator().getValidInput("setHeader", strippedName, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); + String safeName = ESAPI.validator().getValidInput("setHeader", strippedName, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderNameSize"), false); String safeValue = ESAPI.validator().getValidInput("setHeader", strippedValue, "HTTPHeaderValue", sc.getIntProp("HttpUtilities.MaxHeaderValueSize"), false); getHttpServletResponse().setHeader(safeName, safeValue); } catch (ValidationException e) { @@ -470,7 +484,7 @@ public void setHeader(String name, String value) { public void setIntHeader(String name, int value) { try { SecurityConfiguration sc = ESAPI.securityConfiguration(); - String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false); + String safeName = ESAPI.validator().getValidInput("safeSetDateHeader", name, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderNameSize"), false); getHttpServletResponse().setIntHeader(safeName, value); } catch (ValidationException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid int header name denied", e); diff --git a/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java b/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java index ecbbad138..c61a20a20 100644 --- a/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java +++ b/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java @@ -67,6 +67,33 @@ public void testAddHeaderInvalidKeyLength(){ verify(servResp, times(0)).addHeader("Foo", "bar"); } + @Test + public void testAddIntHeader(){ + HttpServletResponse servResp = mock(HttpServletResponse.class); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + resp.addIntHeader("aaaa", 4); + verify(servResp, times(1)).addIntHeader("aaaa", 4); + } + + @Test + public void testAddInvalidIntHeader(){ + HttpServletResponse servResp = mock(HttpServletResponse.class); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + resp.addIntHeader(TestUtils.generateStringOfLength(257), Integer.MIN_VALUE); + verify(servResp, times(0)).addIntHeader(TestUtils.generateStringOfLength(257), Integer.MIN_VALUE); + } + + @Test + public void testContainsHeader(){ + HttpServletResponse servResp = new MockHttpServletResponse(); + servResp = spy(servResp); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + resp = spy(resp); + resp.addIntHeader("aaaa", Integer.MIN_VALUE); + verify(servResp, times(1)).addIntHeader("aaaa", Integer.MIN_VALUE); + assertEquals(true, servResp.containsHeader("aaaa")); + } + @Test public void testAddValidCookie(){ HttpServletResponse servResp = new MockHttpServletResponse(); diff --git a/src/test/java/org/owasp/esapi/reference/EncoderTest.java b/src/test/java/org/owasp/esapi/reference/EncoderTest.java index 5bd553b25..45702ced4 100644 --- a/src/test/java/org/owasp/esapi/reference/EncoderTest.java +++ b/src/test/java/org/owasp/esapi/reference/EncoderTest.java @@ -508,6 +508,7 @@ public void testNamedUpperCaseDecoding(){ String expected = "Ü"; assertEquals(expected, ESAPI.encoder().decodeForHTML(input)); } + public void testEncodeForXMLNull() { Encoder instance = ESAPI.encoder(); assertEquals(null, instance.encodeForXML(null)); diff --git a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java index e10b47e48..c98620320 100644 --- a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java +++ b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java @@ -25,6 +25,7 @@ import org.owasp.esapi.http.MockHttpServletResponse; import org.owasp.esapi.reference.validation.HTMLValidationRule; import org.owasp.esapi.reference.validation.StringValidationRule; +import org.owasp.esapi.util.TestUtils; import javax.servlet.http.Cookie; import java.io.*; @@ -592,10 +593,11 @@ public void testisValidInput() { assertTrue(instance.isValidInput("test", "jeffWILLIAMS123", "HTTPParameterValue", 100, false)); assertTrue(instance.isValidInput("test", "jeff .-/+=@_ WILLIAMS", "HTTPParameterValue", 100, false)); // Removed per Issue 116 - The '*' character is valid as a parameter character -// assertFalse(instance.isValidInput("test", "jeff*WILLIAMS", "HTTPParameterValue", 100, false)); +// assertFalse(instance.isValidInput("test", "jeff*WILLIAMS", "HTTPParameterValue", 100, false)) + System.err.println(instance.isValidInput("test", "jeff\\WILLIAMS", "HTTPParameterValue", 100, false));; assertFalse(instance.isValidInput("test", "jeff^WILLIAMS", "HTTPParameterValue", 100, false)); assertFalse(instance.isValidInput("test", "jeff\\WILLIAMS", "HTTPParameterValue", 100, false)); - + assertTrue(instance.isValidInput("test", null, "Email", 100, true)); assertFalse(instance.isValidInput("test", null, "Email", 100, false)); @@ -1005,11 +1007,11 @@ public void testGetParameterMap() { //an example of a parameter from displaytag, should pass request.addParameter("d-49653-p", "pass"); request.addParameter("XSS"); - request.addHeader("p2", generateStringOfLength(200)); // Upper limit increased from 150 -> 200, GitHub issue #351 - request.addHeader("f2", generateStringOfLength(201)); + request.addHeader("p2", TestUtils.generateStringOfLength(200)); // Upper limit increased from 150 -> 200, GitHub issue #351 + request.addHeader("f2", TestUtils.generateStringOfLength(201)); assertEquals(safeRequest.getHeader("p1"), request.getHeader("p1")); assertEquals(safeRequest.getHeader("p2"), request.getHeader("p2")); assertFalse(safeRequest.getHeader("f1").equals(request.getHeader("f1"))); @@ -1091,8 +1093,8 @@ public void testGetHeader() { public void testHeaderLengthChecks(){ Validator v = ESAPI.validator(); SecurityConfiguration sc = ESAPI.securityConfiguration(); - assertFalse(v.isValidInput("addHeader", generateStringOfLength(257), "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderKeySize"), false)); - assertFalse(v.isValidInput("addHeader", generateStringOfLength(4097), "HTTPHeaderValue", sc.getIntProp("HttpUtilities.MaxHeaderValueSize"), false)); + assertFalse(v.isValidInput("addHeader", TestUtils.generateStringOfLength(257), "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderNameSize"), false)); + assertFalse(v.isValidInput("addHeader", TestUtils.generateStringOfLength(4097), "HTTPHeaderValue", sc.getIntProp("HttpUtilities.MaxHeaderValueSize"), false)); } public void testGetHeaderNames() { @@ -1102,11 +1104,12 @@ public void testGetHeaderNames() { request.addHeader("d-49653-p", "pass"); request.addHeader("= 0 : "length must be >= 0"; - StringBuilder longString = new StringBuilder(length); - for (int i = 0; i < length; i++) { - longString.append("a"); - } - return longString.toString(); - } - public void testGetContextPath() { // Root Context Path ("") assertTrue(ESAPI.validator().isValidInput("HTTPContextPath", "", "HTTPContextPath", 512, true)); diff --git a/src/test/java/org/owasp/esapi/util/TestUtils.java b/src/test/java/org/owasp/esapi/util/TestUtils.java new file mode 100644 index 000000000..f7f117fc5 --- /dev/null +++ b/src/test/java/org/owasp/esapi/util/TestUtils.java @@ -0,0 +1,14 @@ +package org.owasp.esapi.util; + +public class TestUtils { + + public static String generateStringOfLength(int length) { + assert length >= 0 : "length must be >= 0"; + StringBuilder longString = new StringBuilder(length); + for (int i = 0; i < length; i++) { + longString.append("a"); + } + return longString.toString(); + } + +} diff --git a/src/test/resources/esapi/ESAPI.properties b/src/test/resources/esapi/ESAPI.properties index becadfee7..1ff0c4e65 100644 --- a/src/test/resources/esapi/ESAPI.properties +++ b/src/test/resources/esapi/ESAPI.properties @@ -332,7 +332,7 @@ HttpUtilities.ForceSecureSession=false HttpUtilities.ForceHttpOnlyCookies=true HttpUtilities.ForceSecureCookies=true # Maximum size of HTTP header key -HttpUtilities.MaxHeaderKeySize=256 +HttpUtilities.MaxHeaderNameSize=256 # Maximum size of HTTP header value HttpUtilities.MaxHeaderValueSize=4096 # File upload configuration @@ -345,7 +345,8 @@ HttpUtilities.ResponseContentType=text/html; charset=UTF-8 # This is the name of the cookie used to represent the HTTP session # Typically this will be the default "JSESSIONID" HttpUtilities.HttpSessionIdName=JSESSIONID - +#Sets whether or not will will overwrite http status codes to 200. +HttpUtilities.OverwriteStatusCodes=true #=========================================================================== @@ -447,8 +448,8 @@ Validator.HTTPScheme=^(http|https)$ Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$ Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$ Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$ -# Note that max header name capped at 150 in SecurityRequestWrapper! -Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,50}$ +# Note that headerName and Value length is also configured in the HTTPUtilities section +Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,256}$ Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$ Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$ Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$ From 5c0e1de2ba09a2f606d26740a70a35fbe42a5090 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Mon, 24 Jul 2017 17:22:24 -0700 Subject: [PATCH 017/709] Issue #400 -- Added more unit tests for SecurityWrapperResponse. --- .../filters/SecurityWrapperResponse.java | 19 ++++++++-- .../filters/SecurityWrapperResponseTest.java | 37 +++++++++++++++++++ src/test/resources/esapi/ESAPI.properties | 3 +- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java index 82e629557..343a44396 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java @@ -418,7 +418,8 @@ public void setBufferSize(int size) { * @param charset */ public void setCharacterEncoding(String charset) { - getHttpServletResponse().setCharacterEncoding(ESAPI.securityConfiguration().getCharacterEncoding()); + SecurityConfiguration sc = ESAPI.securityConfiguration(); + getHttpServletResponse().setCharacterEncoding(sc.getStringProp("HttpUtilities.CharacterEncoding")); } /** @@ -506,7 +507,13 @@ public void setLocale(Locale loc) { * @param sc */ public void setStatus(int sc) { - getHttpServletResponse().setStatus(HttpServletResponse.SC_OK); + SecurityConfiguration config = ESAPI.securityConfiguration(); + if(config.getBooleanProp("HttpUtilities.OverwriteStatusCodes")){ + getHttpServletResponse().setStatus(HttpServletResponse.SC_OK); + }else{ + getHttpServletResponse().setStatus(sc); + } + } /** @@ -520,8 +527,12 @@ public void setStatus(int sc) { @Deprecated public void setStatus(int sc, String sm) { try { - // setStatus is deprecated so use sendError instead - sendError(HttpServletResponse.SC_OK, sm); + SecurityConfiguration config = ESAPI.securityConfiguration(); + if(config.getBooleanProp("HttpUtilities.OverwriteStatusCodes")){ + sendError(HttpServletResponse.SC_OK, sm); + }else{ + sendError(sc, sm); + } } catch (IOException e) { logger.warning(Logger.SECURITY_FAILURE, "Attempt to set response status failed", e); } diff --git a/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java b/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java index c61a20a20..8811f99f5 100644 --- a/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java +++ b/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java @@ -121,6 +121,7 @@ public void testAddValidCookieWithDomain(){ SecurityWrapperResponse spyResp = spy(resp); Cookie cookie = new Cookie("Foo", TestUtils.generateStringOfLength(10)); cookie.setDomain("evil.com"); + cookie.setMaxAge(-1); Mockito.doCallRealMethod().when(spyResp).addCookie(cookie); spyResp.addCookie(cookie); verify(servResp, times(1)).addHeader("Set-Cookie", "Foo=aaaaaaaaaa; Domain=evil.com; Secure; HttpOnly"); @@ -152,4 +153,40 @@ public void testAddInValidCookie(){ spyResp.addCookie(cookie); verify(servResp, times(0)).addHeader("Set-Cookie", "Foo=" + TestUtils.generateStringOfLength(5000) + "; Secure; HttpOnly"); } + + @Test + public void testSendError() throws Exception{ + HttpServletResponse servResp = new MockHttpServletResponse(); + servResp = spy(servResp); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + SecurityWrapperResponse spyResp = spy(resp); + Mockito.doCallRealMethod().when(spyResp).sendError(200); + spyResp.sendError(200); + + verify(servResp, times(1)).sendError(200, "HTTP error code: 200");; + } + + @Test + public void testSendStatus() throws Exception{ + HttpServletResponse servResp = new MockHttpServletResponse(); + servResp = spy(servResp); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + SecurityWrapperResponse spyResp = spy(resp); + Mockito.doCallRealMethod().when(spyResp).setStatus(200);; + spyResp.setStatus(200); + + verify(servResp, times(1)).setStatus(200);; + } + + @Test + public void testSendStatusWithString() throws Exception{ + HttpServletResponse servResp = new MockHttpServletResponse(); + servResp = spy(servResp); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + SecurityWrapperResponse spyResp = spy(resp); + Mockito.doCallRealMethod().when(spyResp).setStatus(200, "foo");; + spyResp.setStatus(200, "foo"); + + verify(servResp, times(1)).sendError(200, "foo");; + } } diff --git a/src/test/resources/esapi/ESAPI.properties b/src/test/resources/esapi/ESAPI.properties index 1ff0c4e65..6e4bc6246 100644 --- a/src/test/resources/esapi/ESAPI.properties +++ b/src/test/resources/esapi/ESAPI.properties @@ -347,7 +347,8 @@ HttpUtilities.ResponseContentType=text/html; charset=UTF-8 HttpUtilities.HttpSessionIdName=JSESSIONID #Sets whether or not will will overwrite http status codes to 200. HttpUtilities.OverwriteStatusCodes=true - +#Sets the application's base character encoding. This is forked from the Java Encryptor property. +HttpUtilities.CharacterEncoding=UTF-8 #=========================================================================== # ESAPI Executor From f73b9dbb7b1460b669f68231d8cefee9ac39881a Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Mon, 24 Jul 2017 17:41:32 -0700 Subject: [PATCH 018/709] Issue #400 -- Got unit test coverage up to 65.5% --- .../filters/SecurityWrapperResponse.java | 1 - .../filters/SecurityWrapperResponseTest.java | 38 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java index 343a44396..c1de913c4 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperResponse.java @@ -384,7 +384,6 @@ public void sendError(int sc, String msg) throws IOException { }else{ getHttpServletResponse().sendError(sc, ESAPI.encoder().encodeForHTML(msg)); } - } /** diff --git a/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java b/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java index 8811f99f5..478f0b050 100644 --- a/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java +++ b/src/test/java/org/owasp/esapi/filters/SecurityWrapperResponseTest.java @@ -39,6 +39,40 @@ public void testAddDateHeader(){ verify(servResp, times(1)).addDateHeader("Foo", currentTime); } + @Test + public void testSetDateHeader(){ + HttpServletResponse servResp = mock(HttpServletResponse.class); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + long currentTime = System.currentTimeMillis(); + resp.setDateHeader("Foo", currentTime); + verify(servResp, times(1)).setDateHeader("Foo", currentTime); + } + + @Test + public void testSetInvalidDateHeader(){ + HttpServletResponse servResp = mock(HttpServletResponse.class); + SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); + long currentTime = System.currentTimeMillis(); + resp.setDateHeader("alert"); + verify(servResp, times(0)).setHeader("foo", ""); + } + @Test public void testInvalidDateHeader(){ HttpServletResponse servResp = mock(HttpServletResponse.class); @@ -101,8 +135,10 @@ public void testAddValidCookie(){ SecurityWrapperResponse resp = new SecurityWrapperResponse(servResp); SecurityWrapperResponse spyResp = spy(resp); Cookie cookie = new Cookie("Foo", TestUtils.generateStringOfLength(10)); + cookie.setMaxAge(5000); Mockito.doCallRealMethod().when(spyResp).addCookie(cookie); spyResp.addCookie(cookie); + /* * We're indirectly testing our class. Since it ultimately * delegates to HttpServletResponse.addHeader, we're actually @@ -110,7 +146,7 @@ public void testAddValidCookie(){ * expected properties. This implicitly tests the * createCookieHeader method as well. */ - verify(servResp, times(1)).addHeader("Set-Cookie", "Foo=aaaaaaaaaa; Secure; HttpOnly"); + verify(servResp, times(1)).addHeader("Set-Cookie", "Foo=aaaaaaaaaa; Max-Age=5000; Secure; HttpOnly"); } @Test From 271284e3c407e2be47d43062680e1fba6b058128 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Mon, 24 Jul 2017 18:14:24 -0700 Subject: [PATCH 019/709] Issue #317 -- Fixed resource leak. Special thanks to eamonn. --- .../owasp/esapi/waf/rules/BeanShellRule.java | 71 ++++++++++--------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/owasp/esapi/waf/rules/BeanShellRule.java b/src/main/java/org/owasp/esapi/waf/rules/BeanShellRule.java index ea0caa786..f90da574e 100644 --- a/src/main/java/org/owasp/esapi/waf/rules/BeanShellRule.java +++ b/src/main/java/org/owasp/esapi/waf/rules/BeanShellRule.java @@ -34,6 +34,7 @@ /** * This is the Rule subclass executed for <bean-shell-script> rules. + * * @author Arshan Dabirsiaghi * */ @@ -42,43 +43,41 @@ public class BeanShellRule extends Rule { private Interpreter i; private String script; private Pattern path; - - public BeanShellRule(String fileLocation, String id, Pattern path) throws IOException, EvalError { + + public BeanShellRule(String fileLocation, String id, Pattern path) throws IOException, EvalError { i = new Interpreter(); i.set("logger", logger); - this.script = getFileContents( ESAPI.securityConfiguration().getResourceFile(fileLocation)); + this.script = getFileContents(ESAPI.securityConfiguration().getResourceFile(fileLocation)); this.id = id; this.path = path; } - - public Action check(HttpServletRequest request, - InterceptingHTTPServletResponse response, + + public Action check(HttpServletRequest request, InterceptingHTTPServletResponse response, HttpServletResponse httpResponse) { /* * Early fail: if the URL doesn't match one we're interested in. */ - - if ( path != null && ! path.matcher(request.getRequestURI()).matches() ) { + + if (path != null && !path.matcher(request.getRequestURI()).matches()) { return new DoNothingAction(); } - + /* - * Run the beanshell that we've already parsed - * and pre-compiled. Populate the "request" - * and "response" objects so the script has + * Run the beanshell that we've already parsed and pre-compiled. + * Populate the "request" and "response" objects so the script has * access to the same variables we do here. */ - + try { - + Action a = null; - + i.set("action", a); i.set("request", request); - - if ( response != null ) { - i.set("response", response); + + if (response != null) { + i.set("response", response); } else { i.set("response", httpResponse); } @@ -86,31 +85,35 @@ public Action check(HttpServletRequest request, i.set("session", request.getSession()); i.eval(script); - a = (Action)i.get("action"); - - if ( a != null ) { + a = (Action) i.get("action"); + + if (a != null) { return a; } - + } catch (EvalError e) { - log(request,"Error running custom beanshell rule (" + id + ") - " + e.getMessage()); + log(request, "Error running custom beanshell rule (" + id + ") - " + e.getMessage()); } - + return new DoNothingAction(); } - + private String getFileContents(File f) throws IOException { - - FileReader fr = new FileReader(f); StringBuffer sb = new StringBuffer(); - String line; - BufferedReader br = new BufferedReader(fr); - - while( (line=br.readLine()) != null ) { - sb.append(line + System.getProperty("line.separator")); + BufferedReader br = null; + + try { + br = new BufferedReader(new FileReader(f)); + String line; + while ((line = br.readLine()) != null) { + sb.append(line + System.getProperty("line.separator")); + } + + } finally { + if (br != null) { + br.close(); + } } - return sb.toString(); } - } From 9177ca3034b1af7fb7cd95a6433338b0b3a0ec03 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Tue, 25 Jul 2017 20:51:55 -0700 Subject: [PATCH 020/709] Issue #327 -- got rid of misleading HTML entity in URL regex. --- configuration/esapi/validation.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration/esapi/validation.properties b/configuration/esapi/validation.properties index 433fa0b6b..dd24e46c3 100644 --- a/configuration/esapi/validation.properties +++ b/configuration/esapi/validation.properties @@ -23,7 +23,7 @@ Validator.SafeString=^[.\\p{Alnum}\\p{Space}]{0,1024}$ Validator.Email=^[A-Za-z0-9._%'-]+@[A-Za-z0-9.-]+\\.[a-zA-Z]{2,4}$ Validator.IPAddress=^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ -Validator.URL=^(ht|f)tp(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\:\\'\\/\\\\\\+=&%\\$#_]*)?$ +Validator.URL=^(ht|f)tp(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\:\\'\\/\\\\\\+=&;%\\$#_]*)?$ Validator.CreditCard=^(\\d{4}[- ]?){3}\\d{4}$ Validator.SSN=^(?!000)([0-6]\\d{2}|7([0-6]\\d|7[012]))([ -]?)(?!00)\\d\\d\\3(?!0000)\\d{4}$ From 158c78924d624e26ea8a0110a50371420ad4e2ab Mon Sep 17 00:00:00 2001 From: adetlefsen-rms Date: Thu, 27 Jul 2017 12:32:27 -0700 Subject: [PATCH 021/709] Update AntiSamy and XOM versions --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 7bc646277..40386fbf6 100644 --- a/pom.xml +++ b/pom.xml @@ -174,9 +174,9 @@ jar - xom + com.io7m.xom xom - 1.2.5 + 1.2.10 org.beanshell @@ -186,7 +186,7 @@ org.owasp.antisamy antisamy - 1.5.5 + 1.5.6 + 8A2A524F From 5e4e201e8552f94785e02ecc0c33e7c8d24efee7 Mon Sep 17 00:00:00 2001 From: kwwall Date: Mon, 31 Jul 2017 22:21:05 -0400 Subject: [PATCH 033/709] Remove trailing tab on line 881. --- src/main/java/org/owasp/esapi/crypto/CipherText.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/owasp/esapi/crypto/CipherText.java b/src/main/java/org/owasp/esapi/crypto/CipherText.java index b327d1a2f..15a0b4675 100644 --- a/src/main/java/org/owasp/esapi/crypto/CipherText.java +++ b/src/main/java/org/owasp/esapi/crypto/CipherText.java @@ -878,7 +878,7 @@ private void received(EnumSet ctSet) { */ public int getKDFInfo() { final int unusedBit28 = 0x8000000; // 1000000000000000000000000000 - + // kdf version is bits 1-27, bit 28 (reserved) should be 0, and // bits 29-32 are the MAC algorithm indicating which PRF to use for the KDF. int kdfVers = this.getKDFVersion(); From 660054db9a8a366dee499928108fc9239894e84a Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Tue, 1 Aug 2017 21:27:57 -0700 Subject: [PATCH 034/709] Issue #300 -- Solved the root problem, now have many unit tests to clean up. --- .../java/org/owasp/esapi/codecs/Codec.java | 43 +++++++++++++++++-- .../owasp/esapi/codecs/HTMLEntityCodec.java | 36 ++++++++++++++++ .../owasp/esapi/reference/EncoderTest.java | 23 +++++++++- 3 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/owasp/esapi/codecs/Codec.java b/src/main/java/org/owasp/esapi/codecs/Codec.java index ec02a806a..b9f545e52 100644 --- a/src/main/java/org/owasp/esapi/codecs/Codec.java +++ b/src/main/java/org/owasp/esapi/codecs/Codec.java @@ -64,9 +64,15 @@ public Codec() { */ public String encode(char[] immune, String input) { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - sb.append(encodeCharacter(immune, c)); + for(int offset = 0; offset < input.length(); ){ + final int point = input.codePointAt(offset); + if(Character.isBmpCodePoint(point)){ + //We can then safely cast this to char and maintain legacy behavior. + sb.append(encodeCharacter(immune, (char) point)); + }else{ + sb.append(encodeCharacter(immune, point)); + } + offset += Character.charCount(point); } return sb.toString(); } @@ -83,6 +89,19 @@ public String encode(char[] immune, String input) { public String encodeCharacter( char[] immune, Character c ) { return ""+c; } + + /** + * Default codepoint implementation that should be overridden in specific codecs. + * + * @param immune + * @param codePoint + * the integer to encode + * @return + * the encoded Character + */ + public String encodeCharacter( char[] immune, int codePoint ) { + return new StringBuilder().appendCodePoint(codePoint).toString(); + } /** * Decode a String that was encoded using the encode method in this Class @@ -131,6 +150,19 @@ public static String getHexForNonAlphanumeric(char c) return hex[c]; return toHex(c); } + + /** + * Lookup the hex value of any character that is not alphanumeric. + * @param c The character to lookup. + * @return, return null if alphanumeric or the character code + * in hex. + */ + public static String getHexForNonAlphanumeric(int c) + { + if(c<0xFF) + return hex[c]; + return toHex(c); + } public static String toOctal(char c) { @@ -141,6 +173,11 @@ public static String toHex(char c) { return Integer.toHexString(c); } + + public static String toHex(int c) + { + return Integer.toHexString(c); + } /** * Utility to search a char[] for a specific char. diff --git a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java index cd72dc79c..c9ad38ca1 100644 --- a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java @@ -78,6 +78,42 @@ public String encodeCharacter( char[] immune, Character c ) { return "&#x" + hex + ";"; } + /** + * {@inheritDoc} + * + * Encodes a Character for safe use in an HTML entity field. + * @param immune + */ + public String encodeCharacter( char[] immune, int codePoint ) { + + // check for immune characters +// if ( containsCharacter(codePoint, immune ) ) { +// return ""+codePoint; +// } + +// // check for alphanumeric characters + String hex = Codec.getHexForNonAlphanumeric(codePoint); +// if ( hex == null ) { +// return ""+c; +// } +// +// // check for illegal characters +// if ( ( c <= 0x1f && c != '\t' && c != '\n' && c != '\r' ) || ( c >= 0x7f && c <= 0x9f ) ) +// { +// hex = REPLACEMENT_HEX; // Let's entity encode this instead of returning it +// c = REPLACEMENT_CHAR; +// } +// +// // check if there's a defined entity +// String entityName = (String) characterToEntityMap.get(c); +// if (entityName != null) { +// return "&" + entityName + ";"; +// } + + // return the hex entity as suggested in the spec + return "&#x" + hex + ";"; + } + /** * {@inheritDoc} * diff --git a/src/test/java/org/owasp/esapi/reference/EncoderTest.java b/src/test/java/org/owasp/esapi/reference/EncoderTest.java index 45702ced4..896a93661 100644 --- a/src/test/java/org/owasp/esapi/reference/EncoderTest.java +++ b/src/test/java/org/owasp/esapi/reference/EncoderTest.java @@ -32,6 +32,7 @@ import org.owasp.esapi.EncoderConstants; import org.owasp.esapi.codecs.Base64; import org.owasp.esapi.codecs.Codec; +import org.owasp.esapi.codecs.HTMLEntityCodec; import org.owasp.esapi.codecs.MySQLCodec; import org.owasp.esapi.codecs.OracleCodec; import org.owasp.esapi.codecs.PushbackString; @@ -902,7 +903,27 @@ public void testGetCanonicalizedUriWithMailto() throws Exception { URI uri = new URI(input); System.out.println(uri.toString()); assertEquals(expectedUri, e.getCanonicalizedURI(uri)); - + } + + public void testHtmlEncodeStrSurrogatePair() + { + Encoder enc = ESAPI.encoder(); + String inStr = new String (new int[]{0x2f804}, 0, 1); + assertEquals(false, Character.isBmpCodePoint(inStr.codePointAt(0))); + assertEquals(true, Character.isBmpCodePoint(new String(new int[] {0x0a}, 0, 1).codePointAt(0))); + String expected = "你"; + String result; + + result = enc.encodeForHTML(inStr); + assertEquals(expected, result); + } + + public void testHtmlDecodeHexEntititesSurrogatePair() + { + HTMLEntityCodec htmlCodec = new HTMLEntityCodec(); + String expected = new String (new int[]{0x2f804}, 0, 1); + assertEquals( expected, htmlCodec.decode("你") ); + assertEquals( expected, htmlCodec.decode("你") ); } } From 45b3d1680bf90133f1ac6fc2addafe9c5855de98 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Tue, 1 Aug 2017 23:00:10 -0700 Subject: [PATCH 035/709] Issue #300 -- Fixed most unit tests. chars are NOT autoboxed to Characters, make sure we update documentation! --- .../java/org/owasp/esapi/PreparedString.java | 1 + .../org/owasp/esapi/codecs/AbstractCodec.java | 173 ++++++++++++++++++ .../java/org/owasp/esapi/codecs/CSSCodec.java | 4 +- .../java/org/owasp/esapi/codecs/Codec.java | 104 ++--------- .../java/org/owasp/esapi/codecs/DB2Codec.java | 2 +- .../owasp/esapi/codecs/HTMLEntityCodec.java | 6 +- .../owasp/esapi/codecs/JavaScriptCodec.java | 4 +- .../org/owasp/esapi/codecs/MySQLCodec.java | 4 +- .../org/owasp/esapi/codecs/OracleCodec.java | 2 +- .../org/owasp/esapi/codecs/PercentCodec.java | 2 +- .../org/owasp/esapi/codecs/UnixCodec.java | 4 +- .../org/owasp/esapi/codecs/VBScriptCodec.java | 4 +- .../org/owasp/esapi/codecs/WindowsCodec.java | 4 +- .../owasp/esapi/codecs/XMLEntityCodec.java | 2 +- .../org/owasp/esapi/PreparedStringTest.java | 1 + ...{CodecTest.java => AbstractCodecTest.java} | 38 ++-- .../owasp/esapi/codecs/CodecImmunityTest.java | 4 +- .../owasp/esapi/reference/RandomizerTest.java | 2 +- 18 files changed, 228 insertions(+), 133 deletions(-) create mode 100644 src/main/java/org/owasp/esapi/codecs/AbstractCodec.java rename src/test/java/org/owasp/esapi/codecs/{CodecTest.java => AbstractCodecTest.java} (96%) diff --git a/src/main/java/org/owasp/esapi/PreparedString.java b/src/main/java/org/owasp/esapi/PreparedString.java index 3dccff055..176421347 100644 --- a/src/main/java/org/owasp/esapi/PreparedString.java +++ b/src/main/java/org/owasp/esapi/PreparedString.java @@ -16,6 +16,7 @@ package org.owasp.esapi; import java.util.ArrayList; + import org.owasp.esapi.codecs.Codec; import org.owasp.esapi.codecs.HTMLEntityCodec; diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java new file mode 100644 index 000000000..11d33f8a8 --- /dev/null +++ b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java @@ -0,0 +1,173 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2007 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + * @author Jeff Williams Aspect Security + * @created 2007 + */ +package org.owasp.esapi.codecs; + + +/** + * The Codec interface defines a set of methods for encoding and decoding application level encoding schemes, + * such as HTML entity encoding and percent encoding (aka URL encoding). Codecs are used in output encoding + * and canonicalization. The design of these codecs allows for character-by-character decoding, which is + * necessary to detect double-encoding and the use of multiple encoding schemes, both of which are techniques + * used by attackers to bypass validation and bury encoded attacks in data. + * + * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security + * @since June 1, 2007 + * @see org.owasp.esapi.Encoder + */ +public abstract class AbstractCodec implements Codec { + + /** + * Initialize an array to mark which characters are to be encoded. Store the hex + * string for that character to save time later. If the character shouldn't be + * encoded, then store null. + */ + private final String[] hex = new String[256]; + + /** + * Default constructor + */ + public AbstractCodec() { + for ( char c = 0; c < 0xFF; c++ ) { + if ( c >= 0x30 && c <= 0x39 || c >= 0x41 && c <= 0x5A || c >= 0x61 && c <= 0x7A ) { + hex[c] = null; + } else { + hex[c] = toHex(c).intern(); + } + } + } + + /* (non-Javadoc) + * @see org.owasp.esapi.codecs.Codec#encode(char[], java.lang.String) + */ + @Override + public String encode(char[] immune, String input) { + StringBuilder sb = new StringBuilder(); + for(int offset = 0; offset < input.length(); ){ + final int point = input.codePointAt(offset); + if(Character.isBmpCodePoint(point)){ + //We can then safely cast this to char and maintain legacy behavior. + sb.append(encodeCharacter(immune, new Character((char) point))); + }else{ + sb.append(encodeCharacter(immune, point)); + } + offset += Character.charCount(point); + } + return sb.toString(); + } + + /** + * WARNING!!!! Passing a standard char to this method will resolve to the + * @{code public String encodeCharacter( char[] immune, int codePoint )} method + * instead of this one!!! YOU HAVE BEEN WARNED!!!! + * + * @{Inherit} + */ + @Override + public String encodeCharacter( char[] immune, Character c ) { + return ""+c; + } + + /* (non-Javadoc) + * @see org.owasp.esapi.codecs.Codec#encodeCharacter(char[], int) + */ + @Override + public String encodeCharacter( char[] immune, int codePoint ) { + return new StringBuilder().appendCodePoint(codePoint).toString(); + } + + /* (non-Javadoc) + * @see org.owasp.esapi.codecs.Codec#decode(java.lang.String) + */ + @Override + public String decode(String input) { + StringBuilder sb = new StringBuilder(); + PushbackString pbs = new PushbackString(input); + while (pbs.hasNext()) { + Character c = decodeCharacter(pbs); + if (c != null) { + sb.append(c); + } else { + sb.append(pbs.next()); + } + } + return sb.toString(); + } + + /* (non-Javadoc) + * @see org.owasp.esapi.codecs.Codec#decodeCharacter(org.owasp.esapi.codecs.PushbackString) + */ + @Override + public Character decodeCharacter( PushbackString input ) { + return input.next(); + } + + /** + * Lookup the hex value of any character that is not alphanumeric. + * @param c The character to lookup. + * @return, return null if alphanumeric or the character code + * in hex. + */ + public String getHexForNonAlphanumeric(char c) + { + if(c<0xFF) + return hex[c]; + return toHex(c); + } + + /** + * Lookup the hex value of any character that is not alphanumeric. + * @param c The character to lookup. + * @return, return null if alphanumeric or the character code + * in hex. + */ + public String getHexForNonAlphanumeric(int c) + { + if(c<0xFF) + return hex[c]; + return toHex(c); + } + + public String toOctal(char c) + { + return Integer.toOctalString(c); + } + + public String toHex(char c) + { + return Integer.toHexString(c); + } + + public String toHex(int c) + { + return Integer.toHexString(c); + } + + /** + * Utility to search a char[] for a specific char. + * + * @param c + * @param array + * @return + */ + public boolean containsCharacter( char c, char[] array ) { + for (char ch : array) { + if (c == ch) return true; + } + return false; + } + +} diff --git a/src/main/java/org/owasp/esapi/codecs/CSSCodec.java b/src/main/java/org/owasp/esapi/codecs/CSSCodec.java index cee77ccaf..a44cebeed 100644 --- a/src/main/java/org/owasp/esapi/codecs/CSSCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/CSSCodec.java @@ -23,7 +23,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class CSSCodec extends Codec +public class CSSCodec extends AbstractCodec { private static final Character REPLACEMENT = '\ufffd'; @@ -42,7 +42,7 @@ public String encodeCharacter(char[] immune, Character c) { } // check for alphanumeric characters - String hex = Codec.getHexForNonAlphanumeric(c); + String hex = super.getHexForNonAlphanumeric(c); if ( hex == null ) { return ""+c; } diff --git a/src/main/java/org/owasp/esapi/codecs/Codec.java b/src/main/java/org/owasp/esapi/codecs/Codec.java index b9f545e52..3f7cd2554 100644 --- a/src/main/java/org/owasp/esapi/codecs/Codec.java +++ b/src/main/java/org/owasp/esapi/codecs/Codec.java @@ -28,32 +28,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public abstract class Codec { - - /** - * Initialize an array to mark which characters are to be encoded. Store the hex - * string for that character to save time later. If the character shouldn't be - * encoded, then store null. - */ - private static final String[] hex = new String[256]; - - static { - for ( char c = 0; c < 0xFF; c++ ) { - if ( c >= 0x30 && c <= 0x39 || c >= 0x41 && c <= 0x5A || c >= 0x61 && c <= 0x7A ) { - hex[c] = null; - } else { - hex[c] = toHex(c).intern(); - } - } - } - - - /** - * Default constructor - */ - public Codec() { - } - +public interface Codec { /** * Encode a String so that it can be safely used in a specific context. * @@ -62,20 +37,7 @@ public Codec() { * the String to encode * @return the encoded String */ - public String encode(char[] immune, String input) { - StringBuilder sb = new StringBuilder(); - for(int offset = 0; offset < input.length(); ){ - final int point = input.codePointAt(offset); - if(Character.isBmpCodePoint(point)){ - //We can then safely cast this to char and maintain legacy behavior. - sb.append(encodeCharacter(immune, (char) point)); - }else{ - sb.append(encodeCharacter(immune, point)); - } - offset += Character.charCount(point); - } - return sb.toString(); - } + public String encode(char[] immune, String input); /** * Default implementation that should be overridden in specific codecs. @@ -86,9 +48,7 @@ public String encode(char[] immune, String input) { * @return * the encoded Character */ - public String encodeCharacter( char[] immune, Character c ) { - return ""+c; - } + public String encodeCharacter( char[] immune, Character c ); /** * Default codepoint implementation that should be overridden in specific codecs. @@ -99,9 +59,7 @@ public String encodeCharacter( char[] immune, Character c ) { * @return * the encoded Character */ - public String encodeCharacter( char[] immune, int codePoint ) { - return new StringBuilder().appendCodePoint(codePoint).toString(); - } + public String encodeCharacter( char[] immune, int codePoint ); /** * Decode a String that was encoded using the encode method in this Class @@ -111,19 +69,7 @@ public String encodeCharacter( char[] immune, int codePoint ) { * @return * the decoded String */ - public String decode(String input) { - StringBuilder sb = new StringBuilder(); - PushbackString pbs = new PushbackString(input); - while (pbs.hasNext()) { - Character c = decodeCharacter(pbs); - if (c != null) { - sb.append(c); - } else { - sb.append(pbs.next()); - } - } - return sb.toString(); - } + public String decode(String input); /** * Returns the decoded version of the next character from the input string and advances the @@ -134,9 +80,7 @@ public String decode(String input) { * * @return the decoded Character */ - public Character decodeCharacter( PushbackString input ) { - return input.next(); - } + public Character decodeCharacter( PushbackString input ); /** * Lookup the hex value of any character that is not alphanumeric. @@ -144,12 +88,7 @@ public Character decodeCharacter( PushbackString input ) { * @return, return null if alphanumeric or the character code * in hex. */ - public static String getHexForNonAlphanumeric(char c) - { - if(c<0xFF) - return hex[c]; - return toHex(c); - } + public String getHexForNonAlphanumeric(char c); /** * Lookup the hex value of any character that is not alphanumeric. @@ -157,27 +96,13 @@ public static String getHexForNonAlphanumeric(char c) * @return, return null if alphanumeric or the character code * in hex. */ - public static String getHexForNonAlphanumeric(int c) - { - if(c<0xFF) - return hex[c]; - return toHex(c); - } + public String getHexForNonAlphanumeric(int c); - public static String toOctal(char c) - { - return Integer.toOctalString(c); - } + public String toOctal(char c); - public static String toHex(char c) - { - return Integer.toHexString(c); - } + public String toHex(char c); - public static String toHex(int c) - { - return Integer.toHexString(c); - } + public String toHex(int c); /** * Utility to search a char[] for a specific char. @@ -186,11 +111,6 @@ public static String toHex(int c) * @param array * @return */ - public static boolean containsCharacter( char c, char[] array ) { - for (char ch : array) { - if (c == ch) return true; - } - return false; - } + public boolean containsCharacter( char c, char[] array ); } diff --git a/src/main/java/org/owasp/esapi/codecs/DB2Codec.java b/src/main/java/org/owasp/esapi/codecs/DB2Codec.java index 4ff63ea1a..236bed78e 100644 --- a/src/main/java/org/owasp/esapi/codecs/DB2Codec.java +++ b/src/main/java/org/owasp/esapi/codecs/DB2Codec.java @@ -20,7 +20,7 @@ * @since October 26, 2010 * @see org.owasp.esapi.Encoder */ -public class DB2Codec extends Codec { +public class DB2Codec extends AbstractCodec { public String encodeCharacter(char[] immune, Character c) { diff --git a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java index c9ad38ca1..e3892d2f8 100644 --- a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java @@ -27,7 +27,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class HTMLEntityCodec extends Codec +public class HTMLEntityCodec extends AbstractCodec { private static final char REPLACEMENT_CHAR = '\ufffd'; private static final String REPLACEMENT_HEX = "fffd"; @@ -56,7 +56,7 @@ public String encodeCharacter( char[] immune, Character c ) { } // check for alphanumeric characters - String hex = Codec.getHexForNonAlphanumeric(c); + String hex = super.getHexForNonAlphanumeric(c); if ( hex == null ) { return ""+c; } @@ -92,7 +92,7 @@ public String encodeCharacter( char[] immune, int codePoint ) { // } // // check for alphanumeric characters - String hex = Codec.getHexForNonAlphanumeric(codePoint); + String hex = super.getHexForNonAlphanumeric(codePoint); // if ( hex == null ) { // return ""+c; // } diff --git a/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java b/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java index 7980155e1..98c38c7b2 100644 --- a/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java @@ -24,7 +24,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class JavaScriptCodec extends Codec { +public class JavaScriptCodec extends AbstractCodec { /** @@ -45,7 +45,7 @@ public String encodeCharacter( char[] immune, Character c ) { } // check for alphanumeric characters - String hex = Codec.getHexForNonAlphanumeric(c); + String hex = super.getHexForNonAlphanumeric(c); if ( hex == null ) { return ""+c; } diff --git a/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java b/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java index 7d2aacbaa..e43f13ca2 100644 --- a/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java @@ -26,7 +26,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class MySQLCodec extends Codec { +public class MySQLCodec extends AbstractCodec { /** * Specifies the SQL Mode the target MySQL Server is running with. For details about MySQL Server Modes * please see the Manual at {@link http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_ansi} @@ -95,7 +95,7 @@ public String encodeCharacter( char[] immune, Character c ) { } // check for alphanumeric characters - String hex = Codec.getHexForNonAlphanumeric( ch ); + String hex = super.getHexForNonAlphanumeric( ch ); if ( hex == null ) { return ""+ch; } diff --git a/src/main/java/org/owasp/esapi/codecs/OracleCodec.java b/src/main/java/org/owasp/esapi/codecs/OracleCodec.java index 540920d24..06cca0609 100644 --- a/src/main/java/org/owasp/esapi/codecs/OracleCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/OracleCodec.java @@ -30,7 +30,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class OracleCodec extends Codec { +public class OracleCodec extends AbstractCodec { /** diff --git a/src/main/java/org/owasp/esapi/codecs/PercentCodec.java b/src/main/java/org/owasp/esapi/codecs/PercentCodec.java index 42e1d040e..ed7e65dd8 100644 --- a/src/main/java/org/owasp/esapi/codecs/PercentCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/PercentCodec.java @@ -28,7 +28,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class PercentCodec extends Codec +public class PercentCodec extends AbstractCodec { private static final String ALPHA_NUMERIC_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @SuppressWarnings("unused") diff --git a/src/main/java/org/owasp/esapi/codecs/UnixCodec.java b/src/main/java/org/owasp/esapi/codecs/UnixCodec.java index 489cf073b..3381e82f0 100644 --- a/src/main/java/org/owasp/esapi/codecs/UnixCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/UnixCodec.java @@ -24,7 +24,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class UnixCodec extends Codec { +public class UnixCodec extends AbstractCodec { /** * {@inheritDoc} @@ -42,7 +42,7 @@ public String encodeCharacter( char[] immune, Character c ) { } // check for alphanumeric characters - String hex = Codec.getHexForNonAlphanumeric( ch ); + String hex = super.getHexForNonAlphanumeric( ch ); if ( hex == null ) { return ""+ch; } diff --git a/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java b/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java index 31442127a..85ce820e9 100644 --- a/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java @@ -26,7 +26,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class VBScriptCodec extends Codec { +public class VBScriptCodec extends AbstractCodec { /** * Encode a String so that it can be safely used in a specific context. @@ -78,7 +78,7 @@ public String encodeCharacter( char[] immune, Character c ) { } // check for alphanumeric characters - String hex = Codec.getHexForNonAlphanumeric( ch ); + String hex = super.getHexForNonAlphanumeric( ch ); if ( hex == null ) { return ""+ch; } diff --git a/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java b/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java index f7abb3a4b..bcc53f626 100644 --- a/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java @@ -24,7 +24,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class WindowsCodec extends Codec { +public class WindowsCodec extends AbstractCodec { /** @@ -43,7 +43,7 @@ public String encodeCharacter( char[] immune, Character c ) { } // check for alphanumeric characters - String hex = Codec.getHexForNonAlphanumeric( ch ); + String hex = super.getHexForNonAlphanumeric( ch ); if ( hex == null ) { return ""+ch; } diff --git a/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java index e248392ac..25b4ffbc4 100644 --- a/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java @@ -41,7 +41,7 @@ * of knowing about. Decoding is included for completeness but it's use * is not recommended. Use a XML parser instead! */ -public class XMLEntityCodec extends Codec +public class XMLEntityCodec extends AbstractCodec { private static final String ALPHA_NUMERIC_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; private static final String UNENCODED_STR = ALPHA_NUMERIC_STR + " \t"; diff --git a/src/test/java/org/owasp/esapi/PreparedStringTest.java b/src/test/java/org/owasp/esapi/PreparedStringTest.java index b288c1325..c3ddf8ece 100644 --- a/src/test/java/org/owasp/esapi/PreparedStringTest.java +++ b/src/test/java/org/owasp/esapi/PreparedStringTest.java @@ -17,6 +17,7 @@ package org.owasp.esapi; import junit.framework.TestCase; + import org.owasp.esapi.codecs.Codec; import org.owasp.esapi.codecs.HTMLEntityCodec; diff --git a/src/test/java/org/owasp/esapi/codecs/CodecTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java similarity index 96% rename from src/test/java/org/owasp/esapi/codecs/CodecTest.java rename to src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java index 8a7a225ba..1472e5dfb 100644 --- a/src/test/java/org/owasp/esapi/codecs/CodecTest.java +++ b/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java @@ -26,7 +26,7 @@ * href="http://www.aspectsecurity.com">Aspect Security * @since June 1, 2007 */ -public class CodecTest extends TestCase { +public class AbstractCodecTest extends TestCase { private static final char[] EMPTY_CHAR_ARRAY = new char[0]; private static final Character LESS_THAN = Character.valueOf('<'); @@ -48,7 +48,7 @@ public class CodecTest extends TestCase { * @param testName * the test name */ - public CodecTest(String testName) { + public AbstractCodecTest(String testName) { super(testName); } @@ -74,7 +74,7 @@ protected void tearDown() throws Exception { * @return the test */ public static Test suite() { - TestSuite suite = new TestSuite(CodecTest.class); + TestSuite suite = new TestSuite(AbstractCodecTest.class); return suite; } @@ -142,7 +142,7 @@ public void testHtmlEncodeChar() public void testHtmlEncodeChar0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "Ā"; String result; @@ -156,7 +156,7 @@ public void testHtmlEncodeChar0x100() public void testHtmlEncodeStr0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "Ā"; String result; @@ -175,7 +175,7 @@ public void testPercentEncodeChar() public void testPercentEncodeChar0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "%C4%80"; String result; @@ -189,7 +189,7 @@ public void testPercentEncodeChar0x100() public void testPercentEncodeStr0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "%C4%80"; String result; @@ -208,7 +208,7 @@ public void testJavaScriptEncodeChar() public void testJavaScriptEncodeChar0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "\\u0100"; String result; @@ -221,7 +221,7 @@ public void testJavaScriptEncodeChar0x100() public void testJavaScriptEncodeStr0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "\\u0100"; String result; @@ -239,7 +239,7 @@ public void testVBScriptEncodeChar() public void testVBScriptEncodeChar0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); // FIXME I don't know vb... // String expected = "\\u0100"; @@ -253,7 +253,7 @@ public void testVBScriptEncodeChar0x100() public void testVBScriptEncodeStr0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); // FIXME I don't know vb... // String expected = "chrw(0x100)"; @@ -272,7 +272,7 @@ public void testCSSEncodeChar() public void testCSSEncodeChar0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "\\100 "; String result; @@ -285,7 +285,7 @@ public void testCSSEncodeChar0x100() public void testCSSEncodeStr0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "\\100 "; String result; @@ -303,7 +303,7 @@ public void testMySQLANSIEncodeChar() public void testMySQLStandardEncodeChar0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "\\" + in; String result; @@ -316,7 +316,7 @@ public void testMySQLStandardEncodeChar0x100() public void testMySQLStandardEncodeStr0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "\\" + in; String result; @@ -344,7 +344,7 @@ public void testUnixEncodeChar() public void testUnixEncodeChar0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "\\" + in; String result; @@ -357,7 +357,7 @@ public void testUnixEncodeChar0x100() public void testUnixEncodeStr0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "\\" + in; String result; @@ -375,7 +375,7 @@ public void testWindowsEncodeChar() public void testWindowsEncodeChar0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "^" + in; String result; @@ -388,7 +388,7 @@ public void testWindowsEncodeChar0x100() public void testWindowsEncodeStr0x100() { - char in = 0x100; + Character in = 0x100; String inStr = Character.toString(in); String expected = "^" + in; String result; diff --git a/src/test/java/org/owasp/esapi/codecs/CodecImmunityTest.java b/src/test/java/org/owasp/esapi/codecs/CodecImmunityTest.java index 7d35dc168..9a3b8806f 100644 --- a/src/test/java/org/owasp/esapi/codecs/CodecImmunityTest.java +++ b/src/test/java/org/owasp/esapi/codecs/CodecImmunityTest.java @@ -43,7 +43,7 @@ public class CodecImmunityTest { @Parameters(name = "{0}") public static Collection getParams() { - Collection knownCodecs = new ArrayList(); + Collection knownCodecs = new ArrayList(); knownCodecs.add(new CSSCodec()); knownCodecs.add(new DB2Codec()); knownCodecs.add(new HTMLEntityCodec()); @@ -99,7 +99,7 @@ private static Collection buildImmunitiyValidation(Codec codec, char[] return params; } - private static Collection fullCharacterCodecValidation(Collection codecs) { + private static Collection fullCharacterCodecValidation(Collection codecs) { char[] holyCowTesting = StringUtilities.union(EncoderConstants.CHAR_ALPHANUMERICS, EncoderConstants.CHAR_SPECIALS); Collection params = new ArrayList(); for (Codec codec: codecs) { diff --git a/src/test/java/org/owasp/esapi/reference/RandomizerTest.java b/src/test/java/org/owasp/esapi/reference/RandomizerTest.java index b73a0241d..32dc9bd1f 100644 --- a/src/test/java/org/owasp/esapi/reference/RandomizerTest.java +++ b/src/test/java/org/owasp/esapi/reference/RandomizerTest.java @@ -26,7 +26,7 @@ import org.owasp.esapi.ESAPI; import org.owasp.esapi.EncoderConstants; import org.owasp.esapi.Randomizer; -import org.owasp.esapi.codecs.Codec; +import org.owasp.esapi.codecs.AbstractCodec; import org.owasp.esapi.errors.EncryptionException; /** From ca044492e3678f38323cd1c89c39dbfeed920a74 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Fri, 4 Aug 2017 15:40:50 -0700 Subject: [PATCH 036/709] Issue #300 -- Refactored PushbackString into a more generic class, and created a new Integer based impl to support codePoints. Refactored PusbhbackString to also use the same interface. --- .../codecs/AbstractPushbackSequence.java | 47 +++ .../esapi/codecs/PushBackSequenceImpl.java | 137 +++++++++ .../owasp/esapi/codecs/PushbackSequence.java | 64 +++++ .../owasp/esapi/codecs/PushbackString.java | 272 ++++++++++-------- .../esapi/codecs/PushBackStringTest.java | 41 +++ 5 files changed, 434 insertions(+), 127 deletions(-) create mode 100644 src/main/java/org/owasp/esapi/codecs/AbstractPushbackSequence.java create mode 100644 src/main/java/org/owasp/esapi/codecs/PushBackSequenceImpl.java create mode 100644 src/main/java/org/owasp/esapi/codecs/PushbackSequence.java create mode 100644 src/test/java/org/owasp/esapi/codecs/PushBackStringTest.java diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractPushbackSequence.java b/src/main/java/org/owasp/esapi/codecs/AbstractPushbackSequence.java new file mode 100644 index 000000000..ee35478c3 --- /dev/null +++ b/src/main/java/org/owasp/esapi/codecs/AbstractPushbackSequence.java @@ -0,0 +1,47 @@ +package org.owasp.esapi.codecs; + +public abstract class AbstractPushbackSequence implements PushbackSequence { + protected String input; + protected T pushback; + protected T temp; + protected int index = 0; + protected int mark = 0; + + public AbstractPushbackSequence(String input) { + this.input = input; + } + + /** + * + * @param c + */ + public void pushback(T c) { + pushback = c; + } + + /** + * Get the current index of the PushbackString. Typically used in error + * messages. + * + * @return The current index of the PushbackString. + */ + public int index() { + return index; + } + + /** + * + * @return + */ + public boolean hasNext() { + if (pushback != null) + return true; + if (input == null) + return false; + if (input.length() == 0) + return false; + if (index >= input.length()) + return false; + return true; + } +} diff --git a/src/main/java/org/owasp/esapi/codecs/PushBackSequenceImpl.java b/src/main/java/org/owasp/esapi/codecs/PushBackSequenceImpl.java new file mode 100644 index 000000000..ea31e88d6 --- /dev/null +++ b/src/main/java/org/owasp/esapi/codecs/PushBackSequenceImpl.java @@ -0,0 +1,137 @@ +package org.owasp.esapi.codecs; + + +/** + * The pushback string is used by Codecs to allow them to push decoded characters back onto a string + * for further decoding. This is necessary to detect double-encoding. + * + * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security + * @since June 1, 2007 + * @see org.owasp.esapi.Encoder + */ +public class PushBackSequenceImpl extends AbstractPushbackSequence{ + /** + * + * @param input + */ + public PushBackSequenceImpl( String input ) { + super(input); + } + + /** + * + * @return + */ + public Integer next() { + if ( pushback != null ) { + Integer save = pushback; + pushback = null; + return save; + } + if ( input == null ) return null; + if ( input.length() == 0 ) return null; + if ( index >= input.length() ) return null; + final Integer point = input.codePointAt(index); + index += Character.charCount(point); + return point; + } + + /** + * + * @return + */ + public Integer nextHex() { + Integer c = next(); + if ( c == null ) return null; + if ( isHexDigit( c ) ) return c; + return null; + } + + /** + * + * @return + */ + public Integer nextOctal() { + Integer c = next(); + if ( c == null ) return null; + if ( isOctalDigit( c ) ) return c; + return null; + } + + /** + * Returns true if the parameter character is a hexidecimal digit 0 through 9, a through f, or A through F. + * @param c + * @return + */ + public static boolean isHexDigit( Integer c ) { + if ( c == null ) return false; + Integer ch = Integer.valueOf(c); + return (ch >= '0' && ch <= '9' ) || (ch >= 'a' && ch <= 'f' ) || (ch >= 'A' && ch <= 'F' ); + } + + /** + * Returns true if the parameter character is an octal digit 0 through 7. + * @param c + * @return + */ + public static boolean isOctalDigit( Integer c ) { + if ( c == null ) return false; + Integer ch = Integer.valueOf(c); + return ch >= '0' && ch <= '7'; + } + + /** + * Return the next codePoint without affecting the current index. + * @return + */ + public Integer peek() { + if ( pushback != null ) return pushback; + if ( input == null ) return null; + if ( input.length() == 0 ) return null; + if ( index >= input.length() ) return null; + return input.codePointAt(index); + } + + /** + * Test to see if the next codePoint is a particular value without affecting the current index. + * @param c + * @return + */ + public boolean peek( Integer c ) { + if ( pushback != null && pushback.intValue() == c ) return true; + if ( input == null ) return false; + if ( input.length() == 0 ) return false; + if ( index >= input.length() ) return false; + return input.codePointAt(index) == c; + } + + /** + * + */ + public void mark() { + temp = pushback; + mark = index; + } + + /** + * + */ + public void reset() { + pushback = temp; + index = mark; + } + + /** + * + * @return + */ + protected String remainder() { + String output = input.substring( index ); + if ( pushback != null ) { + output = pushback + output; + } + return output; + } + +} diff --git a/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java b/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java new file mode 100644 index 000000000..58fe4a930 --- /dev/null +++ b/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java @@ -0,0 +1,64 @@ +package org.owasp.esapi.codecs; + +public interface PushbackSequence { + + /** + * + * @param c + */ + void pushback(T c); + + /** + * Get the current index of the PushbackString. Typically used in error messages. + * @return The current index of the PushbackString. + */ + int index(); + + /** + * + * @return + */ + boolean hasNext(); + + /** + * + * @return + */ + T next(); + + /** + * + * @return + */ + T nextHex(); + + /** + * + * @return + */ + T nextOctal(); + + /** + * Return the next character without affecting the current index. + * @return + */ + T peek(); + + /** + * Test to see if the next character is a particular value without affecting the current index. + * @param c + * @return + */ + boolean peek(T c); + + /** + * + */ + void mark(); + + /** + * + */ + void reset(); + +} \ No newline at end of file diff --git a/src/main/java/org/owasp/esapi/codecs/PushbackString.java b/src/main/java/org/owasp/esapi/codecs/PushbackString.java index 1119b223c..9982d48e2 100644 --- a/src/main/java/org/owasp/esapi/codecs/PushbackString.java +++ b/src/main/java/org/owasp/esapi/codecs/PushbackString.java @@ -15,169 +15,187 @@ */ package org.owasp.esapi.codecs; - /** - * The pushback string is used by Codecs to allow them to push decoded characters back onto a string - * for further decoding. This is necessary to detect double-encoding. + * The pushback string is used by Codecs to allow them to push decoded + * characters back onto a string for further decoding. This is necessary to + * detect double-encoding. * - * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security + * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) + * Aspect Security * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class PushbackString { - - private String input; - private Character pushback; - private Character temp; - private int index = 0; - private int mark = 0; - - /** - * - * @param input - */ - public PushbackString( String input ) { - this.input = input; - } - - /** - * - * @param c - */ - public void pushback( Character c ) { - pushback = c; +public class PushbackString extends AbstractPushbackSequence{ + /** + * + * @param input + */ + public PushbackString(String input) { + super(input); } - - /** - * Get the current index of the PushbackString. Typically used in error messages. - * @return The current index of the PushbackString. - */ - public int index() { + /* + * (non-Javadoc) + * + * @see org.owasp.esapi.codecs.PushbackSequence#index() + */ + public int index() { return index; } - - /** - * - * @return - */ - public boolean hasNext() { - if ( pushback != null ) return true; - if ( input == null ) return false; - if ( input.length() == 0 ) return false; - if ( index >= input.length() ) return false; - return true; + + /* + * (non-Javadoc) + * + * @see org.owasp.esapi.codecs.PushbackSequence#hasNext() + */ + public boolean hasNext() { + if (pushback != null) + return true; + if (input == null) + return false; + if (input.length() == 0) + return false; + if (index >= input.length()) + return false; + return true; } - - /** - * - * @return - */ - public Character next() { - if ( pushback != null ) { + + /* + * (non-Javadoc) + * + * @see org.owasp.esapi.codecs.PushbackSequence#next() + */ + public Character next() { + if (pushback != null) { Character save = pushback; pushback = null; return save; } - if ( input == null ) return null; - if ( input.length() == 0 ) return null; - if ( index >= input.length() ) return null; - return Character.valueOf( input.charAt(index++) ); + if (input == null) + return null; + if (input.length() == 0) + return null; + if (index >= input.length()) + return null; + return Character.valueOf(input.charAt(index++)); } - - /** - * - * @return - */ - public Character nextHex() { + + /* + * (non-Javadoc) + * + * @see org.owasp.esapi.codecs.PushbackSequence#nextHex() + */ + public Character nextHex() { Character c = next(); - if ( c == null ) return null; - if ( isHexDigit( c ) ) return c; + if (c == null) + return null; + if (isHexDigit(c)) + return c; return null; } - /** - * - * @return - */ - public Character nextOctal() { + /* + * (non-Javadoc) + * + * @see org.owasp.esapi.codecs.PushbackSequence#nextOctal() + */ + public Character nextOctal() { Character c = next(); - if ( c == null ) return null; - if ( isOctalDigit( c ) ) return c; + if (c == null) + return null; + if (isOctalDigit(c)) + return c; return null; } - /** - * Returns true if the parameter character is a hexidecimal digit 0 through 9, a through f, or A through F. - * @param c - * @return - */ - public static boolean isHexDigit( Character c ) { - if ( c == null ) return false; + /** + * Returns true if the parameter character is a hexidecimal digit 0 through + * 9, a through f, or A through F. + * + * @param c + * @return + */ + public static boolean isHexDigit(Character c) { + if (c == null) + return false; char ch = c.charValue(); - return (ch >= '0' && ch <= '9' ) || (ch >= 'a' && ch <= 'f' ) || (ch >= 'A' && ch <= 'F' ); + return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); } - /** - * Returns true if the parameter character is an octal digit 0 through 7. - * @param c - * @return - */ -public static boolean isOctalDigit( Character c ) { - if ( c == null ) return false; - char ch = c.charValue(); - return ch >= '0' && ch <= '7'; -} + /** + * Returns true if the parameter character is an octal digit 0 through 7. + * + * @param c + * @return + */ + public static boolean isOctalDigit(Character c) { + if (c == null) + return false; + char ch = c.charValue(); + return ch >= '0' && ch <= '7'; + } - /** - * Return the next character without affecting the current index. - * @return - */ - public Character peek() { - if ( pushback != null ) return pushback; - if ( input == null ) return null; - if ( input.length() == 0 ) return null; - if ( index >= input.length() ) return null; - return Character.valueOf( input.charAt(index) ); + /* + * (non-Javadoc) + * + * @see org.owasp.esapi.codecs.PushbackSequence#peek() + */ + public Character peek() { + if (pushback != null) + return pushback; + if (input == null) + return null; + if (input.length() == 0) + return null; + if (index >= input.length()) + return null; + return Character.valueOf(input.charAt(index)); } - - /** - * Test to see if the next character is a particular value without affecting the current index. - * @param c - * @return - */ - public boolean peek( char c ) { - if ( pushback != null && pushback.charValue() == c ) return true; - if ( input == null ) return false; - if ( input.length() == 0 ) return false; - if ( index >= input.length() ) return false; + + /* + * (non-Javadoc) + * + * @see org.owasp.esapi.codecs.PushbackSequence#peek(char) + */ + public boolean peek(Character c) { + if (pushback != null && pushback.charValue() == c) + return true; + if (input == null) + return false; + if (input.length() == 0) + return false; + if (index >= input.length()) + return false; return input.charAt(index) == c; - } - - /** - * - */ - public void mark() { + } + + /* + * (non-Javadoc) + * + * @see org.owasp.esapi.codecs.PushbackSequence#mark() + */ + public void mark() { temp = pushback; mark = index; } - /** - * - */ - public void reset() { + /* + * (non-Javadoc) + * + * @see org.owasp.esapi.codecs.PushbackSequence#reset() + */ + public void reset() { pushback = temp; index = mark; } - - /** - * - * @return - */ - protected String remainder() { - String output = input.substring( index ); - if ( pushback != null ) { + + /** + * + * @return + */ + protected String remainder() { + String output = input.substring(index); + if (pushback != null) { output = pushback + output; } return output; diff --git a/src/test/java/org/owasp/esapi/codecs/PushBackStringTest.java b/src/test/java/org/owasp/esapi/codecs/PushBackStringTest.java new file mode 100644 index 000000000..2a4ed84e2 --- /dev/null +++ b/src/test/java/org/owasp/esapi/codecs/PushBackStringTest.java @@ -0,0 +1,41 @@ +package org.owasp.esapi.codecs; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class PushBackStringTest { + + @Test + public void testPushbackString() { + PushbackSequence pbs = new PushbackString("012345"); + + pbs.mark(); + assertEquals(0, pbs.index()); + Character first = pbs.next(); + + System.out.println("0x" + Integer.toHexString(first)); + + assertEquals("0", new StringBuilder().appendCodePoint(first).toString()); + } + + @Test + public void testPushbackSequence() { + AbstractPushbackSequence pbs = new PushBackSequenceImpl("12345"); + + pbs.mark(); + assertEquals(0, pbs.index()); + Integer first = pbs.next(); + + System.out.println("0x" + Integer.toHexString(first)); + + assertEquals("&", new StringBuilder().appendCodePoint(first).toString()); + + Integer second = pbs.next(); + + if(second == '#'){ + System.out.printf("[%d]:[%d]\n", second, (int) '#'); + + } + } +} From 65646f174e42be2bd1a88706f53d6be56d94d4a7 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sat, 5 Aug 2017 15:34:50 -0700 Subject: [PATCH 037/709] Issue #300 -- Finally got the core issue whacked! Now we have to clean up the rest of the unit tests. --- .../esapi/codecs/AbstractCharacterCodec.java | 21 + .../org/owasp/esapi/codecs/AbstractCodec.java | 23 +- .../esapi/codecs/AbstractIntegerCodec.java | 22 + .../java/org/owasp/esapi/codecs/CSSCodec.java | 4 +- .../java/org/owasp/esapi/codecs/Codec.java | 6 +- .../java/org/owasp/esapi/codecs/DB2Codec.java | 2 +- .../owasp/esapi/codecs/HTMLEntityCodec.java | 565 +++++++++--------- .../owasp/esapi/codecs/JavaScriptCodec.java | 2 +- .../org/owasp/esapi/codecs/MySQLCodec.java | 2 +- .../org/owasp/esapi/codecs/OracleCodec.java | 2 +- .../org/owasp/esapi/codecs/PercentCodec.java | 2 +- .../esapi/codecs/PushBackSequenceImpl.java | 2 +- .../owasp/esapi/codecs/PushbackSequence.java | 8 + .../owasp/esapi/codecs/PushbackString.java | 2 +- .../org/owasp/esapi/codecs/UnixCodec.java | 2 +- .../org/owasp/esapi/codecs/VBScriptCodec.java | 2 +- .../org/owasp/esapi/codecs/WindowsCodec.java | 2 +- .../owasp/esapi/codecs/XMLEntityCodec.java | 2 +- .../owasp/esapi/codecs/AbstractCodecTest.java | 2 +- .../owasp/esapi/reference/EncoderTest.java | 19 +- 20 files changed, 366 insertions(+), 326 deletions(-) create mode 100644 src/main/java/org/owasp/esapi/codecs/AbstractCharacterCodec.java create mode 100644 src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractCharacterCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractCharacterCodec.java new file mode 100644 index 000000000..5868d2bc0 --- /dev/null +++ b/src/main/java/org/owasp/esapi/codecs/AbstractCharacterCodec.java @@ -0,0 +1,21 @@ +package org.owasp.esapi.codecs; + +public abstract class AbstractCharacterCodec extends AbstractCodec { + /* (non-Javadoc) + * @see org.owasp.esapi.codecs.Codec#decode(java.lang.String) + */ + @Override + public String decode(String input) { + StringBuilder sb = new StringBuilder(); + PushbackSequence pbs = new PushbackString(input); + while (pbs.hasNext()) { + Character c = decodeCharacter(pbs); + if (c != null) { + sb.append(c); + } else { + sb.append(pbs.next()); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java index 11d33f8a8..236125497 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java @@ -25,10 +25,11 @@ * * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security + * @param * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public abstract class AbstractCodec implements Codec { +public abstract class AbstractCodec implements Codec { /** * Initialize an array to mark which characters are to be encoded. Store the hex @@ -89,29 +90,13 @@ public String encodeCharacter( char[] immune, int codePoint ) { return new StringBuilder().appendCodePoint(codePoint).toString(); } - /* (non-Javadoc) - * @see org.owasp.esapi.codecs.Codec#decode(java.lang.String) - */ - @Override - public String decode(String input) { - StringBuilder sb = new StringBuilder(); - PushbackString pbs = new PushbackString(input); - while (pbs.hasNext()) { - Character c = decodeCharacter(pbs); - if (c != null) { - sb.append(c); - } else { - sb.append(pbs.next()); - } - } - return sb.toString(); - } + /* (non-Javadoc) * @see org.owasp.esapi.codecs.Codec#decodeCharacter(org.owasp.esapi.codecs.PushbackString) */ @Override - public Character decodeCharacter( PushbackString input ) { + public T decodeCharacter( PushbackSequence input ) { return input.next(); } diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java new file mode 100644 index 000000000..f43891481 --- /dev/null +++ b/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java @@ -0,0 +1,22 @@ +package org.owasp.esapi.codecs; + +public class AbstractIntegerCodec extends AbstractCodec { + + /** + * {@inheritDoc} + */ + @Override + public String decode(String input) { + StringBuilder sb = new StringBuilder(); + PushbackSequence pbs = new PushBackSequenceImpl(input); + while (pbs.hasNext()) { + Integer c = decodeCharacter(pbs); + if (c != null) { + sb.appendCodePoint(c); + } else { + sb.appendCodePoint(pbs.next()); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/org/owasp/esapi/codecs/CSSCodec.java b/src/main/java/org/owasp/esapi/codecs/CSSCodec.java index a44cebeed..088463889 100644 --- a/src/main/java/org/owasp/esapi/codecs/CSSCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/CSSCodec.java @@ -23,7 +23,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class CSSCodec extends AbstractCodec +public class CSSCodec extends AbstractCharacterCodec { private static final Character REPLACEMENT = '\ufffd'; @@ -58,7 +58,7 @@ public String encodeCharacter(char[] immune, Character c) { * Returns the decoded version of the character starting at index, * or null if no decoding is possible. */ - public Character decodeCharacter(PushbackString input) + public Character decodeCharacter(PushbackSequence input) { input.mark(); Character first = input.next(); diff --git a/src/main/java/org/owasp/esapi/codecs/Codec.java b/src/main/java/org/owasp/esapi/codecs/Codec.java index 3f7cd2554..c205d1b3f 100644 --- a/src/main/java/org/owasp/esapi/codecs/Codec.java +++ b/src/main/java/org/owasp/esapi/codecs/Codec.java @@ -28,7 +28,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public interface Codec { +public interface Codec { /** * Encode a String so that it can be safely used in a specific context. * @@ -73,14 +73,14 @@ public interface Codec { /** * Returns the decoded version of the next character from the input string and advances the - * current character in the PushbackString. If the current character is not encoded, this + * current character in the PushbackSequence. If the current character is not encoded, this * method MUST reset the PushbackString. * * @param input the Character to decode * * @return the decoded Character */ - public Character decodeCharacter( PushbackString input ); + public T decodeCharacter( PushbackSequence input ); /** * Lookup the hex value of any character that is not alphanumeric. diff --git a/src/main/java/org/owasp/esapi/codecs/DB2Codec.java b/src/main/java/org/owasp/esapi/codecs/DB2Codec.java index 236bed78e..850d9a6aa 100644 --- a/src/main/java/org/owasp/esapi/codecs/DB2Codec.java +++ b/src/main/java/org/owasp/esapi/codecs/DB2Codec.java @@ -20,7 +20,7 @@ * @since October 26, 2010 * @see org.owasp.esapi.Encoder */ -public class DB2Codec extends AbstractCodec { +public class DB2Codec extends AbstractCharacterCodec { public String encodeCharacter(char[] immune, Character c) { diff --git a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java index e3892d2f8..4adfdf84c 100644 --- a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.Collections; import java.util.Map; +import java.util.Map.Entry; /** * Implementation of the Codec interface for HTML entity encoding. @@ -27,14 +28,14 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class HTMLEntityCodec extends AbstractCodec +public class HTMLEntityCodec extends AbstractIntegerCodec { private static final char REPLACEMENT_CHAR = '\ufffd'; private static final String REPLACEMENT_HEX = "fffd"; private static final String REPLACEMENT_STR = "" + REPLACEMENT_CHAR; - private static final Map characterToEntityMap = mkCharacterToEntityMap(); + private static final Map characterToEntityMap = mkCharacterToEntityMap(); - private static final Trie entityToCharacterTrie = mkEntityToCharacterTrie(); + private static final Trie entityToCharacterTrie = mkEntityToCharacterTrie(); /** * @@ -69,7 +70,7 @@ public String encodeCharacter( char[] immune, Character c ) { } // check if there's a defined entity - String entityName = (String) characterToEntityMap.get(c); + String entityName = (String) characterToEntityMap.get(Integer.valueOf(c)); if (entityName != null) { return "&" + entityName + ";"; } @@ -125,9 +126,9 @@ public String encodeCharacter( char[] immune, int codePoint ) { * &#xhhhh; * &name; */ - public Character decodeCharacter( PushbackString input ) { + public Integer decodeCharacter( PushbackSequence input ) { input.mark(); - Character first = input.next(); + Integer first = input.next(); if ( first == null ) { input.reset(); return null; @@ -140,7 +141,7 @@ public Character decodeCharacter( PushbackString input ) { } // test for numeric encodings - Character second = input.next(); + Integer second = input.next(); if ( second == null ) { input.reset(); return null; @@ -148,12 +149,12 @@ public Character decodeCharacter( PushbackString input ) { if (second == '#' ) { // handle numbers - Character c = getNumericEntity( input ); + Integer c = getNumericEntity( input ); if ( c != null ) return c; - } else if ( Character.isLetter( second.charValue() ) ) { + } else if ( Character.isLetter( second ) ) { // handle entities input.pushback( second ); - Character c = getNamedEntity( input ); + Integer c = getNamedEntity( input ); if ( c != null ) return c; } input.reset(); @@ -169,8 +170,8 @@ public Character decodeCharacter( PushbackString input ) { * @return * null if input is null, the character of input after decoding */ - private Character getNumericEntity( PushbackString input ) { - Character first = input.peek(); + private Integer getNumericEntity( PushbackSequence input ) { + Integer first = input.peek(); if ( first == null ) return null; if (first == 'x' || first == 'X' ) { @@ -189,14 +190,14 @@ private Character getNumericEntity( PushbackString input ) { * character representation of this decimal value, e.g. A * @throws NumberFormatException */ - private Character parseNumber( PushbackString input ) { + private Integer parseNumber( PushbackSequence input ) { StringBuilder sb = new StringBuilder(); while( input.hasNext() ) { - Character c = input.peek(); + Integer c = input.peek(); // if character is a digit then add it on and keep going - if ( Character.isDigit( c.charValue() ) ) { - sb.append( c ); + if ( Character.isDigit( c ) ) { + sb.appendCodePoint( c ); input.next(); // if character is a semi-colon, eat it and quit @@ -212,7 +213,7 @@ private Character parseNumber( PushbackString input ) { try { int i = Integer.parseInt(sb.toString()); if (Character.isValidCodePoint(i)) { - return (char) i; + return i; } } catch( NumberFormatException e ) { // throw an exception for malformed entity? @@ -229,14 +230,14 @@ private Character parseNumber( PushbackString input ) { * A single character from the string * @throws NumberFormatException */ - private Character parseHex( PushbackString input ) { + private Integer parseHex( PushbackSequence input ) { StringBuilder sb = new StringBuilder(); while( input.hasNext() ) { - Character c = input.peek(); + Integer c = input.peek(); // if character is a hex digit then add it on and keep going if ( "0123456789ABCDEFabcdef".indexOf(c) != -1 ) { - sb.append( c ); + sb.appendCodePoint( c ); input.next(); // if character is a semi-colon, eat it and quit @@ -252,7 +253,7 @@ private Character parseHex( PushbackString input ) { try { int i = Integer.parseInt(sb.toString(), 16); if (Character.isValidCodePoint(i)) { - return (char) i; + return i; } } catch( NumberFormatException e ) { // throw an exception for malformed entity? @@ -278,9 +279,9 @@ private Character parseHex( PushbackString input ) { * @return * Returns the decoded version of the character starting at index, or null if no decoding is possible. */ - private Character getNamedEntity( PushbackString input ) { + private Integer getNamedEntity( PushbackSequence input ) { StringBuilder possible = new StringBuilder(); - Map.Entry entry; + Entry entry; int len; // kludge around PushbackString.... @@ -296,7 +297,7 @@ private Character getNamedEntity( PushbackString input ) { String possibleString = possible.toString(); String possibleStringLowerCase = possibleString.toLowerCase(); if(!possibleString.equals(possibleStringLowerCase)) { - Map.Entry exactEntry = entityToCharacterTrie.getLongestMatch(possibleStringLowerCase); + Map.Entry exactEntry = entityToCharacterTrie.getLongestMatch(possibleStringLowerCase); if(exactEntry != null) entry = exactEntry; } if(entry == null) return null; // no match, caller will reset input @@ -310,7 +311,7 @@ private Character getNamedEntity( PushbackString input ) { input.next(); // check for a trailing semicolen - if(input.peek(';')) + if(input.peek(Integer.valueOf(';'))) input.next(); return entry.getValue(); @@ -320,262 +321,262 @@ private Character getNamedEntity( PushbackString input ) { * Build a unmodifiable Map from entity Character to Name. * @return Unmodifiable map. */ - private static synchronized Map mkCharacterToEntityMap() + private static synchronized Map mkCharacterToEntityMap() { - Map map = new HashMap(252); + Map map = new HashMap(252); - map.put((char)34, "quot"); /* quotation mark */ - map.put((char)38, "amp"); /* ampersand */ - map.put((char)60, "lt"); /* less-than sign */ - map.put((char)62, "gt"); /* greater-than sign */ - map.put((char)160, "nbsp"); /* no-break space */ - map.put((char)161, "iexcl"); /* inverted exclamation mark */ - map.put((char)162, "cent"); /* cent sign */ - map.put((char)163, "pound"); /* pound sign */ - map.put((char)164, "curren"); /* currency sign */ - map.put((char)165, "yen"); /* yen sign */ - map.put((char)166, "brvbar"); /* broken bar */ - map.put((char)167, "sect"); /* section sign */ - map.put((char)168, "uml"); /* diaeresis */ - map.put((char)169, "copy"); /* copyright sign */ - map.put((char)170, "ordf"); /* feminine ordinal indicator */ - map.put((char)171, "laquo"); /* left-pointing double angle quotation mark */ - map.put((char)172, "not"); /* not sign */ - map.put((char)173, "shy"); /* soft hyphen */ - map.put((char)174, "reg"); /* registered sign */ - map.put((char)175, "macr"); /* macron */ - map.put((char)176, "deg"); /* degree sign */ - map.put((char)177, "plusmn"); /* plus-minus sign */ - map.put((char)178, "sup2"); /* superscript two */ - map.put((char)179, "sup3"); /* superscript three */ - map.put((char)180, "acute"); /* acute accent */ - map.put((char)181, "micro"); /* micro sign */ - map.put((char)182, "para"); /* pilcrow sign */ - map.put((char)183, "middot"); /* middle dot */ - map.put((char)184, "cedil"); /* cedilla */ - map.put((char)185, "sup1"); /* superscript one */ - map.put((char)186, "ordm"); /* masculine ordinal indicator */ - map.put((char)187, "raquo"); /* right-pointing double angle quotation mark */ - map.put((char)188, "frac14"); /* vulgar fraction one quarter */ - map.put((char)189, "frac12"); /* vulgar fraction one half */ - map.put((char)190, "frac34"); /* vulgar fraction three quarters */ - map.put((char)191, "iquest"); /* inverted question mark */ - map.put((char)192, "Agrave"); /* Latin capital letter a with grave */ - map.put((char)193, "Aacute"); /* Latin capital letter a with acute */ - map.put((char)194, "Acirc"); /* Latin capital letter a with circumflex */ - map.put((char)195, "Atilde"); /* Latin capital letter a with tilde */ - map.put((char)196, "Auml"); /* Latin capital letter a with diaeresis */ - map.put((char)197, "Aring"); /* Latin capital letter a with ring above */ - map.put((char)198, "AElig"); /* Latin capital letter ae */ - map.put((char)199, "Ccedil"); /* Latin capital letter c with cedilla */ - map.put((char)200, "Egrave"); /* Latin capital letter e with grave */ - map.put((char)201, "Eacute"); /* Latin capital letter e with acute */ - map.put((char)202, "Ecirc"); /* Latin capital letter e with circumflex */ - map.put((char)203, "Euml"); /* Latin capital letter e with diaeresis */ - map.put((char)204, "Igrave"); /* Latin capital letter i with grave */ - map.put((char)205, "Iacute"); /* Latin capital letter i with acute */ - map.put((char)206, "Icirc"); /* Latin capital letter i with circumflex */ - map.put((char)207, "Iuml"); /* Latin capital letter i with diaeresis */ - map.put((char)208, "ETH"); /* Latin capital letter eth */ - map.put((char)209, "Ntilde"); /* Latin capital letter n with tilde */ - map.put((char)210, "Ograve"); /* Latin capital letter o with grave */ - map.put((char)211, "Oacute"); /* Latin capital letter o with acute */ - map.put((char)212, "Ocirc"); /* Latin capital letter o with circumflex */ - map.put((char)213, "Otilde"); /* Latin capital letter o with tilde */ - map.put((char)214, "Ouml"); /* Latin capital letter o with diaeresis */ - map.put((char)215, "times"); /* multiplication sign */ - map.put((char)216, "Oslash"); /* Latin capital letter o with stroke */ - map.put((char)217, "Ugrave"); /* Latin capital letter u with grave */ - map.put((char)218, "Uacute"); /* Latin capital letter u with acute */ - map.put((char)219, "Ucirc"); /* Latin capital letter u with circumflex */ - map.put((char)220, "Uuml"); /* Latin capital letter u with diaeresis */ - map.put((char)221, "Yacute"); /* Latin capital letter y with acute */ - map.put((char)222, "THORN"); /* Latin capital letter thorn */ - map.put((char)223, "szlig"); /* Latin small letter sharp sXCOMMAX German Eszett */ - map.put((char)224, "agrave"); /* Latin small letter a with grave */ - map.put((char)225, "aacute"); /* Latin small letter a with acute */ - map.put((char)226, "acirc"); /* Latin small letter a with circumflex */ - map.put((char)227, "atilde"); /* Latin small letter a with tilde */ - map.put((char)228, "auml"); /* Latin small letter a with diaeresis */ - map.put((char)229, "aring"); /* Latin small letter a with ring above */ - map.put((char)230, "aelig"); /* Latin lowercase ligature ae */ - map.put((char)231, "ccedil"); /* Latin small letter c with cedilla */ - map.put((char)232, "egrave"); /* Latin small letter e with grave */ - map.put((char)233, "eacute"); /* Latin small letter e with acute */ - map.put((char)234, "ecirc"); /* Latin small letter e with circumflex */ - map.put((char)235, "euml"); /* Latin small letter e with diaeresis */ - map.put((char)236, "igrave"); /* Latin small letter i with grave */ - map.put((char)237, "iacute"); /* Latin small letter i with acute */ - map.put((char)238, "icirc"); /* Latin small letter i with circumflex */ - map.put((char)239, "iuml"); /* Latin small letter i with diaeresis */ - map.put((char)240, "eth"); /* Latin small letter eth */ - map.put((char)241, "ntilde"); /* Latin small letter n with tilde */ - map.put((char)242, "ograve"); /* Latin small letter o with grave */ - map.put((char)243, "oacute"); /* Latin small letter o with acute */ - map.put((char)244, "ocirc"); /* Latin small letter o with circumflex */ - map.put((char)245, "otilde"); /* Latin small letter o with tilde */ - map.put((char)246, "ouml"); /* Latin small letter o with diaeresis */ - map.put((char)247, "divide"); /* division sign */ - map.put((char)248, "oslash"); /* Latin small letter o with stroke */ - map.put((char)249, "ugrave"); /* Latin small letter u with grave */ - map.put((char)250, "uacute"); /* Latin small letter u with acute */ - map.put((char)251, "ucirc"); /* Latin small letter u with circumflex */ - map.put((char)252, "uuml"); /* Latin small letter u with diaeresis */ - map.put((char)253, "yacute"); /* Latin small letter y with acute */ - map.put((char)254, "thorn"); /* Latin small letter thorn */ - map.put((char)255, "yuml"); /* Latin small letter y with diaeresis */ - map.put((char)338, "OElig"); /* Latin capital ligature oe */ - map.put((char)339, "oelig"); /* Latin small ligature oe */ - map.put((char)352, "Scaron"); /* Latin capital letter s with caron */ - map.put((char)353, "scaron"); /* Latin small letter s with caron */ - map.put((char)376, "Yuml"); /* Latin capital letter y with diaeresis */ - map.put((char)402, "fnof"); /* Latin small letter f with hook */ - map.put((char)710, "circ"); /* modifier letter circumflex accent */ - map.put((char)732, "tilde"); /* small tilde */ - map.put((char)913, "Alpha"); /* Greek capital letter alpha */ - map.put((char)914, "Beta"); /* Greek capital letter beta */ - map.put((char)915, "Gamma"); /* Greek capital letter gamma */ - map.put((char)916, "Delta"); /* Greek capital letter delta */ - map.put((char)917, "Epsilon"); /* Greek capital letter epsilon */ - map.put((char)918, "Zeta"); /* Greek capital letter zeta */ - map.put((char)919, "Eta"); /* Greek capital letter eta */ - map.put((char)920, "Theta"); /* Greek capital letter theta */ - map.put((char)921, "Iota"); /* Greek capital letter iota */ - map.put((char)922, "Kappa"); /* Greek capital letter kappa */ - map.put((char)923, "Lambda"); /* Greek capital letter lambda */ - map.put((char)924, "Mu"); /* Greek capital letter mu */ - map.put((char)925, "Nu"); /* Greek capital letter nu */ - map.put((char)926, "Xi"); /* Greek capital letter xi */ - map.put((char)927, "Omicron"); /* Greek capital letter omicron */ - map.put((char)928, "Pi"); /* Greek capital letter pi */ - map.put((char)929, "Rho"); /* Greek capital letter rho */ - map.put((char)931, "Sigma"); /* Greek capital letter sigma */ - map.put((char)932, "Tau"); /* Greek capital letter tau */ - map.put((char)933, "Upsilon"); /* Greek capital letter upsilon */ - map.put((char)934, "Phi"); /* Greek capital letter phi */ - map.put((char)935, "Chi"); /* Greek capital letter chi */ - map.put((char)936, "Psi"); /* Greek capital letter psi */ - map.put((char)937, "Omega"); /* Greek capital letter omega */ - map.put((char)945, "alpha"); /* Greek small letter alpha */ - map.put((char)946, "beta"); /* Greek small letter beta */ - map.put((char)947, "gamma"); /* Greek small letter gamma */ - map.put((char)948, "delta"); /* Greek small letter delta */ - map.put((char)949, "epsilon"); /* Greek small letter epsilon */ - map.put((char)950, "zeta"); /* Greek small letter zeta */ - map.put((char)951, "eta"); /* Greek small letter eta */ - map.put((char)952, "theta"); /* Greek small letter theta */ - map.put((char)953, "iota"); /* Greek small letter iota */ - map.put((char)954, "kappa"); /* Greek small letter kappa */ - map.put((char)955, "lambda"); /* Greek small letter lambda */ - map.put((char)956, "mu"); /* Greek small letter mu */ - map.put((char)957, "nu"); /* Greek small letter nu */ - map.put((char)958, "xi"); /* Greek small letter xi */ - map.put((char)959, "omicron"); /* Greek small letter omicron */ - map.put((char)960, "pi"); /* Greek small letter pi */ - map.put((char)961, "rho"); /* Greek small letter rho */ - map.put((char)962, "sigmaf"); /* Greek small letter final sigma */ - map.put((char)963, "sigma"); /* Greek small letter sigma */ - map.put((char)964, "tau"); /* Greek small letter tau */ - map.put((char)965, "upsilon"); /* Greek small letter upsilon */ - map.put((char)966, "phi"); /* Greek small letter phi */ - map.put((char)967, "chi"); /* Greek small letter chi */ - map.put((char)968, "psi"); /* Greek small letter psi */ - map.put((char)969, "omega"); /* Greek small letter omega */ - map.put((char)977, "thetasym"); /* Greek theta symbol */ - map.put((char)978, "upsih"); /* Greek upsilon with hook symbol */ - map.put((char)982, "piv"); /* Greek pi symbol */ - map.put((char)8194, "ensp"); /* en space */ - map.put((char)8195, "emsp"); /* em space */ - map.put((char)8201, "thinsp"); /* thin space */ - map.put((char)8204, "zwnj"); /* zero width non-joiner */ - map.put((char)8205, "zwj"); /* zero width joiner */ - map.put((char)8206, "lrm"); /* left-to-right mark */ - map.put((char)8207, "rlm"); /* right-to-left mark */ - map.put((char)8211, "ndash"); /* en dash */ - map.put((char)8212, "mdash"); /* em dash */ - map.put((char)8216, "lsquo"); /* left single quotation mark */ - map.put((char)8217, "rsquo"); /* right single quotation mark */ - map.put((char)8218, "sbquo"); /* single low-9 quotation mark */ - map.put((char)8220, "ldquo"); /* left double quotation mark */ - map.put((char)8221, "rdquo"); /* right double quotation mark */ - map.put((char)8222, "bdquo"); /* double low-9 quotation mark */ - map.put((char)8224, "dagger"); /* dagger */ - map.put((char)8225, "Dagger"); /* double dagger */ - map.put((char)8226, "bull"); /* bullet */ - map.put((char)8230, "hellip"); /* horizontal ellipsis */ - map.put((char)8240, "permil"); /* per mille sign */ - map.put((char)8242, "prime"); /* prime */ - map.put((char)8243, "Prime"); /* double prime */ - map.put((char)8249, "lsaquo"); /* single left-pointing angle quotation mark */ - map.put((char)8250, "rsaquo"); /* single right-pointing angle quotation mark */ - map.put((char)8254, "oline"); /* overline */ - map.put((char)8260, "frasl"); /* fraction slash */ - map.put((char)8364, "euro"); /* euro sign */ - map.put((char)8465, "image"); /* black-letter capital i */ - map.put((char)8472, "weierp"); /* script capital pXCOMMAX Weierstrass p */ - map.put((char)8476, "real"); /* black-letter capital r */ - map.put((char)8482, "trade"); /* trademark sign */ - map.put((char)8501, "alefsym"); /* alef symbol */ - map.put((char)8592, "larr"); /* leftwards arrow */ - map.put((char)8593, "uarr"); /* upwards arrow */ - map.put((char)8594, "rarr"); /* rightwards arrow */ - map.put((char)8595, "darr"); /* downwards arrow */ - map.put((char)8596, "harr"); /* left right arrow */ - map.put((char)8629, "crarr"); /* downwards arrow with corner leftwards */ - map.put((char)8656, "lArr"); /* leftwards double arrow */ - map.put((char)8657, "uArr"); /* upwards double arrow */ - map.put((char)8658, "rArr"); /* rightwards double arrow */ - map.put((char)8659, "dArr"); /* downwards double arrow */ - map.put((char)8660, "hArr"); /* left right double arrow */ - map.put((char)8704, "forall"); /* for all */ - map.put((char)8706, "part"); /* partial differential */ - map.put((char)8707, "exist"); /* there exists */ - map.put((char)8709, "empty"); /* empty set */ - map.put((char)8711, "nabla"); /* nabla */ - map.put((char)8712, "isin"); /* element of */ - map.put((char)8713, "notin"); /* not an element of */ - map.put((char)8715, "ni"); /* contains as member */ - map.put((char)8719, "prod"); /* n-ary product */ - map.put((char)8721, "sum"); /* n-ary summation */ - map.put((char)8722, "minus"); /* minus sign */ - map.put((char)8727, "lowast"); /* asterisk operator */ - map.put((char)8730, "radic"); /* square root */ - map.put((char)8733, "prop"); /* proportional to */ - map.put((char)8734, "infin"); /* infinity */ - map.put((char)8736, "ang"); /* angle */ - map.put((char)8743, "and"); /* logical and */ - map.put((char)8744, "or"); /* logical or */ - map.put((char)8745, "cap"); /* intersection */ - map.put((char)8746, "cup"); /* union */ - map.put((char)8747, "int"); /* integral */ - map.put((char)8756, "there4"); /* therefore */ - map.put((char)8764, "sim"); /* tilde operator */ - map.put((char)8773, "cong"); /* congruent to */ - map.put((char)8776, "asymp"); /* almost equal to */ - map.put((char)8800, "ne"); /* not equal to */ - map.put((char)8801, "equiv"); /* identical toXCOMMAX equivalent to */ - map.put((char)8804, "le"); /* less-than or equal to */ - map.put((char)8805, "ge"); /* greater-than or equal to */ - map.put((char)8834, "sub"); /* subset of */ - map.put((char)8835, "sup"); /* superset of */ - map.put((char)8836, "nsub"); /* not a subset of */ - map.put((char)8838, "sube"); /* subset of or equal to */ - map.put((char)8839, "supe"); /* superset of or equal to */ - map.put((char)8853, "oplus"); /* circled plus */ - map.put((char)8855, "otimes"); /* circled times */ - map.put((char)8869, "perp"); /* up tack */ - map.put((char)8901, "sdot"); /* dot operator */ - map.put((char)8968, "lceil"); /* left ceiling */ - map.put((char)8969, "rceil"); /* right ceiling */ - map.put((char)8970, "lfloor"); /* left floor */ - map.put((char)8971, "rfloor"); /* right floor */ - map.put((char)9001, "lang"); /* left-pointing angle bracket */ - map.put((char)9002, "rang"); /* right-pointing angle bracket */ - map.put((char)9674, "loz"); /* lozenge */ - map.put((char)9824, "spades"); /* black spade suit */ - map.put((char)9827, "clubs"); /* black club suit */ - map.put((char)9829, "hearts"); /* black heart suit */ - map.put((char)9830, "diams"); /* black diamond suit */ + map.put(34, "quot"); /* quotation mark */ + map.put(38, "amp"); /* ampersand */ + map.put(60, "lt"); /* less-than sign */ + map.put(62, "gt"); /* greater-than sign */ + map.put(160, "nbsp"); /* no-break space */ + map.put(161, "iexcl"); /* inverted exclamation mark */ + map.put(162, "cent"); /* cent sign */ + map.put(163, "pound"); /* pound sign */ + map.put(164, "curren"); /* currency sign */ + map.put(165, "yen"); /* yen sign */ + map.put(166, "brvbar"); /* broken bar */ + map.put(167, "sect"); /* section sign */ + map.put(168, "uml"); /* diaeresis */ + map.put(169, "copy"); /* copyright sign */ + map.put(170, "ordf"); /* feminine ordinal indicator */ + map.put(171, "laquo"); /* left-pointing double angle quotation mark */ + map.put(172, "not"); /* not sign */ + map.put(173, "shy"); /* soft hyphen */ + map.put(174, "reg"); /* registered sign */ + map.put(175, "macr"); /* macron */ + map.put(176, "deg"); /* degree sign */ + map.put(177, "plusmn"); /* plus-minus sign */ + map.put(178, "sup2"); /* superscript two */ + map.put(179, "sup3"); /* superscript three */ + map.put(180, "acute"); /* acute accent */ + map.put(181, "micro"); /* micro sign */ + map.put(182, "para"); /* pilcrow sign */ + map.put(183, "middot"); /* middle dot */ + map.put(184, "cedil"); /* cedilla */ + map.put(185, "sup1"); /* superscript one */ + map.put(186, "ordm"); /* masculine ordinal indicator */ + map.put(187, "raquo"); /* right-pointing double angle quotation mark */ + map.put(188, "frac14"); /* vulgar fraction one quarter */ + map.put(189, "frac12"); /* vulgar fraction one half */ + map.put(190, "frac34"); /* vulgar fraction three quarters */ + map.put(191, "iquest"); /* inverted question mark */ + map.put(192, "Agrave"); /* Latin capital letter a with grave */ + map.put(193, "Aacute"); /* Latin capital letter a with acute */ + map.put(194, "Acirc"); /* Latin capital letter a with circumflex */ + map.put(195, "Atilde"); /* Latin capital letter a with tilde */ + map.put(196, "Auml"); /* Latin capital letter a with diaeresis */ + map.put(197, "Aring"); /* Latin capital letter a with ring above */ + map.put(198, "AElig"); /* Latin capital letter ae */ + map.put(199, "Ccedil"); /* Latin capital letter c with cedilla */ + map.put(200, "Egrave"); /* Latin capital letter e with grave */ + map.put(201, "Eacute"); /* Latin capital letter e with acute */ + map.put(202, "Ecirc"); /* Latin capital letter e with circumflex */ + map.put(203, "Euml"); /* Latin capital letter e with diaeresis */ + map.put(204, "Igrave"); /* Latin capital letter i with grave */ + map.put(205, "Iacute"); /* Latin capital letter i with acute */ + map.put(206, "Icirc"); /* Latin capital letter i with circumflex */ + map.put(207, "Iuml"); /* Latin capital letter i with diaeresis */ + map.put(208, "ETH"); /* Latin capital letter eth */ + map.put(209, "Ntilde"); /* Latin capital letter n with tilde */ + map.put(210, "Ograve"); /* Latin capital letter o with grave */ + map.put(211, "Oacute"); /* Latin capital letter o with acute */ + map.put(212, "Ocirc"); /* Latin capital letter o with circumflex */ + map.put(213, "Otilde"); /* Latin capital letter o with tilde */ + map.put(214, "Ouml"); /* Latin capital letter o with diaeresis */ + map.put(215, "times"); /* multiplication sign */ + map.put(216, "Oslash"); /* Latin capital letter o with stroke */ + map.put(217, "Ugrave"); /* Latin capital letter u with grave */ + map.put(218, "Uacute"); /* Latin capital letter u with acute */ + map.put(219, "Ucirc"); /* Latin capital letter u with circumflex */ + map.put(220, "Uuml"); /* Latin capital letter u with diaeresis */ + map.put(221, "Yacute"); /* Latin capital letter y with acute */ + map.put(222, "THORN"); /* Latin capital letter thorn */ + map.put(223, "szlig"); /* Latin small letter sharp sXCOMMAX German Eszett */ + map.put(224, "agrave"); /* Latin small letter a with grave */ + map.put(225, "aacute"); /* Latin small letter a with acute */ + map.put(226, "acirc"); /* Latin small letter a with circumflex */ + map.put(227, "atilde"); /* Latin small letter a with tilde */ + map.put(228, "auml"); /* Latin small letter a with diaeresis */ + map.put(229, "aring"); /* Latin small letter a with ring above */ + map.put(230, "aelig"); /* Latin lowercase ligature ae */ + map.put(231, "ccedil"); /* Latin small letter c with cedilla */ + map.put(232, "egrave"); /* Latin small letter e with grave */ + map.put(233, "eacute"); /* Latin small letter e with acute */ + map.put(234, "ecirc"); /* Latin small letter e with circumflex */ + map.put(235, "euml"); /* Latin small letter e with diaeresis */ + map.put(236, "igrave"); /* Latin small letter i with grave */ + map.put(237, "iacute"); /* Latin small letter i with acute */ + map.put(238, "icirc"); /* Latin small letter i with circumflex */ + map.put(239, "iuml"); /* Latin small letter i with diaeresis */ + map.put(240, "eth"); /* Latin small letter eth */ + map.put(241, "ntilde"); /* Latin small letter n with tilde */ + map.put(242, "ograve"); /* Latin small letter o with grave */ + map.put(243, "oacute"); /* Latin small letter o with acute */ + map.put(244, "ocirc"); /* Latin small letter o with circumflex */ + map.put(245, "otilde"); /* Latin small letter o with tilde */ + map.put(246, "ouml"); /* Latin small letter o with diaeresis */ + map.put(247, "divide"); /* division sign */ + map.put(248, "oslash"); /* Latin small letter o with stroke */ + map.put(249, "ugrave"); /* Latin small letter u with grave */ + map.put(250, "uacute"); /* Latin small letter u with acute */ + map.put(251, "ucirc"); /* Latin small letter u with circumflex */ + map.put(252, "uuml"); /* Latin small letter u with diaeresis */ + map.put(253, "yacute"); /* Latin small letter y with acute */ + map.put(254, "thorn"); /* Latin small letter thorn */ + map.put(255, "yuml"); /* Latin small letter y with diaeresis */ + map.put(338, "OElig"); /* Latin capital ligature oe */ + map.put(339, "oelig"); /* Latin small ligature oe */ + map.put(352, "Scaron"); /* Latin capital letter s with caron */ + map.put(353, "scaron"); /* Latin small letter s with caron */ + map.put(376, "Yuml"); /* Latin capital letter y with diaeresis */ + map.put(402, "fnof"); /* Latin small letter f with hook */ + map.put(710, "circ"); /* modifier letter circumflex accent */ + map.put(732, "tilde"); /* small tilde */ + map.put(913, "Alpha"); /* Greek capital letter alpha */ + map.put(914, "Beta"); /* Greek capital letter beta */ + map.put(915, "Gamma"); /* Greek capital letter gamma */ + map.put(916, "Delta"); /* Greek capital letter delta */ + map.put(917, "Epsilon"); /* Greek capital letter epsilon */ + map.put(918, "Zeta"); /* Greek capital letter zeta */ + map.put(919, "Eta"); /* Greek capital letter eta */ + map.put(920, "Theta"); /* Greek capital letter theta */ + map.put(921, "Iota"); /* Greek capital letter iota */ + map.put(922, "Kappa"); /* Greek capital letter kappa */ + map.put(923, "Lambda"); /* Greek capital letter lambda */ + map.put(924, "Mu"); /* Greek capital letter mu */ + map.put(925, "Nu"); /* Greek capital letter nu */ + map.put(926, "Xi"); /* Greek capital letter xi */ + map.put(927, "Omicron"); /* Greek capital letter omicron */ + map.put(928, "Pi"); /* Greek capital letter pi */ + map.put(929, "Rho"); /* Greek capital letter rho */ + map.put(931, "Sigma"); /* Greek capital letter sigma */ + map.put(932, "Tau"); /* Greek capital letter tau */ + map.put(933, "Upsilon"); /* Greek capital letter upsilon */ + map.put(934, "Phi"); /* Greek capital letter phi */ + map.put(935, "Chi"); /* Greek capital letter chi */ + map.put(936, "Psi"); /* Greek capital letter psi */ + map.put(937, "Omega"); /* Greek capital letter omega */ + map.put(945, "alpha"); /* Greek small letter alpha */ + map.put(946, "beta"); /* Greek small letter beta */ + map.put(947, "gamma"); /* Greek small letter gamma */ + map.put(948, "delta"); /* Greek small letter delta */ + map.put(949, "epsilon"); /* Greek small letter epsilon */ + map.put(950, "zeta"); /* Greek small letter zeta */ + map.put(951, "eta"); /* Greek small letter eta */ + map.put(952, "theta"); /* Greek small letter theta */ + map.put(953, "iota"); /* Greek small letter iota */ + map.put(954, "kappa"); /* Greek small letter kappa */ + map.put(955, "lambda"); /* Greek small letter lambda */ + map.put(956, "mu"); /* Greek small letter mu */ + map.put(957, "nu"); /* Greek small letter nu */ + map.put(958, "xi"); /* Greek small letter xi */ + map.put(959, "omicron"); /* Greek small letter omicron */ + map.put(960, "pi"); /* Greek small letter pi */ + map.put(961, "rho"); /* Greek small letter rho */ + map.put(962, "sigmaf"); /* Greek small letter final sigma */ + map.put(963, "sigma"); /* Greek small letter sigma */ + map.put(964, "tau"); /* Greek small letter tau */ + map.put(965, "upsilon"); /* Greek small letter upsilon */ + map.put(966, "phi"); /* Greek small letter phi */ + map.put(967, "chi"); /* Greek small letter chi */ + map.put(968, "psi"); /* Greek small letter psi */ + map.put(969, "omega"); /* Greek small letter omega */ + map.put(977, "thetasym"); /* Greek theta symbol */ + map.put(978, "upsih"); /* Greek upsilon with hook symbol */ + map.put(982, "piv"); /* Greek pi symbol */ + map.put(8194, "ensp"); /* en space */ + map.put(8195, "emsp"); /* em space */ + map.put(8201, "thinsp"); /* thin space */ + map.put(8204, "zwnj"); /* zero width non-joiner */ + map.put(8205, "zwj"); /* zero width joiner */ + map.put(8206, "lrm"); /* left-to-right mark */ + map.put(8207, "rlm"); /* right-to-left mark */ + map.put(8211, "ndash"); /* en dash */ + map.put(8212, "mdash"); /* em dash */ + map.put(8216, "lsquo"); /* left single quotation mark */ + map.put(8217, "rsquo"); /* right single quotation mark */ + map.put(8218, "sbquo"); /* single low-9 quotation mark */ + map.put(8220, "ldquo"); /* left double quotation mark */ + map.put(8221, "rdquo"); /* right double quotation mark */ + map.put(8222, "bdquo"); /* double low-9 quotation mark */ + map.put(8224, "dagger"); /* dagger */ + map.put(8225, "Dagger"); /* double dagger */ + map.put(8226, "bull"); /* bullet */ + map.put(8230, "hellip"); /* horizontal ellipsis */ + map.put(8240, "permil"); /* per mille sign */ + map.put(8242, "prime"); /* prime */ + map.put(8243, "Prime"); /* double prime */ + map.put(8249, "lsaquo"); /* single left-pointing angle quotation mark */ + map.put(8250, "rsaquo"); /* single right-pointing angle quotation mark */ + map.put(8254, "oline"); /* overline */ + map.put(8260, "frasl"); /* fraction slash */ + map.put(8364, "euro"); /* euro sign */ + map.put(8465, "image"); /* black-letter capital i */ + map.put(8472, "weierp"); /* script capital pXCOMMAX Weierstrass p */ + map.put(8476, "real"); /* black-letter capital r */ + map.put(8482, "trade"); /* trademark sign */ + map.put(8501, "alefsym"); /* alef symbol */ + map.put(8592, "larr"); /* leftwards arrow */ + map.put(8593, "uarr"); /* upwards arrow */ + map.put(8594, "rarr"); /* rightwards arrow */ + map.put(8595, "darr"); /* downwards arrow */ + map.put(8596, "harr"); /* left right arrow */ + map.put(8629, "crarr"); /* downwards arrow with corner leftwards */ + map.put(8656, "lArr"); /* leftwards double arrow */ + map.put(8657, "uArr"); /* upwards double arrow */ + map.put(8658, "rArr"); /* rightwards double arrow */ + map.put(8659, "dArr"); /* downwards double arrow */ + map.put(8660, "hArr"); /* left right double arrow */ + map.put(8704, "forall"); /* for all */ + map.put(8706, "part"); /* partial differential */ + map.put(8707, "exist"); /* there exists */ + map.put(8709, "empty"); /* empty set */ + map.put(8711, "nabla"); /* nabla */ + map.put(8712, "isin"); /* element of */ + map.put(8713, "notin"); /* not an element of */ + map.put(8715, "ni"); /* contains as member */ + map.put(8719, "prod"); /* n-ary product */ + map.put(8721, "sum"); /* n-ary summation */ + map.put(8722, "minus"); /* minus sign */ + map.put(8727, "lowast"); /* asterisk operator */ + map.put(8730, "radic"); /* square root */ + map.put(8733, "prop"); /* proportional to */ + map.put(8734, "infin"); /* infinity */ + map.put(8736, "ang"); /* angle */ + map.put(8743, "and"); /* logical and */ + map.put(8744, "or"); /* logical or */ + map.put(8745, "cap"); /* intersection */ + map.put(8746, "cup"); /* union */ + map.put(8747, "int"); /* integral */ + map.put(8756, "there4"); /* therefore */ + map.put(8764, "sim"); /* tilde operator */ + map.put(8773, "cong"); /* congruent to */ + map.put(8776, "asymp"); /* almost equal to */ + map.put(8800, "ne"); /* not equal to */ + map.put(8801, "equiv"); /* identical toXCOMMAX equivalent to */ + map.put(8804, "le"); /* less-than or equal to */ + map.put(8805, "ge"); /* greater-than or equal to */ + map.put(8834, "sub"); /* subset of */ + map.put(8835, "sup"); /* superset of */ + map.put(8836, "nsub"); /* not a subset of */ + map.put(8838, "sube"); /* subset of or equal to */ + map.put(8839, "supe"); /* superset of or equal to */ + map.put(8853, "oplus"); /* circled plus */ + map.put(8855, "otimes"); /* circled times */ + map.put(8869, "perp"); /* up tack */ + map.put(8901, "sdot"); /* dot operator */ + map.put(8968, "lceil"); /* left ceiling */ + map.put(8969, "rceil"); /* right ceiling */ + map.put(8970, "lfloor"); /* left floor */ + map.put(8971, "rfloor"); /* right floor */ + map.put(9001, "lang"); /* left-pointing angle bracket */ + map.put(9002, "rang"); /* right-pointing angle bracket */ + map.put(9674, "loz"); /* lozenge */ + map.put(9824, "spades"); /* black spade suit */ + map.put(9827, "clubs"); /* black club suit */ + map.put(9829, "hearts"); /* black heart suit */ + map.put(9830, "diams"); /* black diamond suit */ return Collections.unmodifiableMap(map); } @@ -584,11 +585,11 @@ private static synchronized Map mkCharacterToEntityMap() * Build a unmodifiable Trie from entitiy Name to Character * @return Unmodifiable trie. */ - private static synchronized Trie mkEntityToCharacterTrie() + private static synchronized Trie mkEntityToCharacterTrie() { - Trie trie = new HashTrie(); + Trie trie = new HashTrie(); - for(Map.Entry entry : characterToEntityMap.entrySet()) + for(Map.Entry entry : characterToEntityMap.entrySet()) trie.put(entry.getValue(),entry.getKey()); return Trie.Util.unmodifiable(trie); } diff --git a/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java b/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java index 98c38c7b2..a10d9b588 100644 --- a/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java @@ -24,7 +24,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class JavaScriptCodec extends AbstractCodec { +public class JavaScriptCodec extends AbstractCharacterCodec { /** diff --git a/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java b/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java index e43f13ca2..6552d089f 100644 --- a/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java @@ -26,7 +26,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class MySQLCodec extends AbstractCodec { +public class MySQLCodec extends AbstractCharacterCodec { /** * Specifies the SQL Mode the target MySQL Server is running with. For details about MySQL Server Modes * please see the Manual at {@link http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_ansi} diff --git a/src/main/java/org/owasp/esapi/codecs/OracleCodec.java b/src/main/java/org/owasp/esapi/codecs/OracleCodec.java index 06cca0609..ff87ea8c0 100644 --- a/src/main/java/org/owasp/esapi/codecs/OracleCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/OracleCodec.java @@ -30,7 +30,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class OracleCodec extends AbstractCodec { +public class OracleCodec extends AbstractCharacterCodec { /** diff --git a/src/main/java/org/owasp/esapi/codecs/PercentCodec.java b/src/main/java/org/owasp/esapi/codecs/PercentCodec.java index ed7e65dd8..6b3d5d0a5 100644 --- a/src/main/java/org/owasp/esapi/codecs/PercentCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/PercentCodec.java @@ -28,7 +28,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class PercentCodec extends AbstractCodec +public class PercentCodec extends AbstractCharacterCodec { private static final String ALPHA_NUMERIC_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @SuppressWarnings("unused") diff --git a/src/main/java/org/owasp/esapi/codecs/PushBackSequenceImpl.java b/src/main/java/org/owasp/esapi/codecs/PushBackSequenceImpl.java index ea31e88d6..c10f4de22 100644 --- a/src/main/java/org/owasp/esapi/codecs/PushBackSequenceImpl.java +++ b/src/main/java/org/owasp/esapi/codecs/PushBackSequenceImpl.java @@ -126,7 +126,7 @@ public void reset() { * * @return */ - protected String remainder() { + public String remainder() { String output = input.substring( index ); if ( pushback != null ) { output = pushback + output; diff --git a/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java b/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java index 58fe4a930..5588ca91f 100644 --- a/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java +++ b/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java @@ -61,4 +61,12 @@ public interface PushbackSequence { */ void reset(); + /** + * Not at all sure what this method is intended to do. There + * is a line in HTMLEntityCodec that said calling this method + * is a "kludge around PushbackString..." + * @return + */ + String remainder(); + } \ No newline at end of file diff --git a/src/main/java/org/owasp/esapi/codecs/PushbackString.java b/src/main/java/org/owasp/esapi/codecs/PushbackString.java index 9982d48e2..4255045f9 100644 --- a/src/main/java/org/owasp/esapi/codecs/PushbackString.java +++ b/src/main/java/org/owasp/esapi/codecs/PushbackString.java @@ -193,7 +193,7 @@ public void reset() { * * @return */ - protected String remainder() { + public String remainder() { String output = input.substring(index); if (pushback != null) { output = pushback + output; diff --git a/src/main/java/org/owasp/esapi/codecs/UnixCodec.java b/src/main/java/org/owasp/esapi/codecs/UnixCodec.java index 3381e82f0..da2dddc0b 100644 --- a/src/main/java/org/owasp/esapi/codecs/UnixCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/UnixCodec.java @@ -24,7 +24,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class UnixCodec extends AbstractCodec { +public class UnixCodec extends AbstractCharacterCodec { /** * {@inheritDoc} diff --git a/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java b/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java index 85ce820e9..ae7f1f6ac 100644 --- a/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java @@ -26,7 +26,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class VBScriptCodec extends AbstractCodec { +public class VBScriptCodec extends AbstractCharacterCodec { /** * Encode a String so that it can be safely used in a specific context. diff --git a/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java b/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java index bcc53f626..c0a5540ea 100644 --- a/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java @@ -24,7 +24,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class WindowsCodec extends AbstractCodec { +public class WindowsCodec extends AbstractCharacterCodec { /** diff --git a/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java index 25b4ffbc4..45482adae 100644 --- a/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java @@ -41,7 +41,7 @@ * of knowing about. Decoding is included for completeness but it's use * is not recommended. Use a XML parser instead! */ -public class XMLEntityCodec extends AbstractCodec +public class XMLEntityCodec extends AbstractCharacterCodec { private static final String ALPHA_NUMERIC_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; private static final String UNENCODED_STR = ALPHA_NUMERIC_STR + " \t"; diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java index 1472e5dfb..9e0a5477b 100644 --- a/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java +++ b/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java @@ -574,7 +574,7 @@ public void testWindowsDecode() public void testHtmlDecodeCharLessThan() { - assertEquals( LESS_THAN, htmlCodec.decodeCharacter(new PushbackString("<")) ); + assertEquals( LESS_THAN, htmlCodec.decodeCharacter(new PushBackSequenceImpl("<")) ); } public void testPercentDecodeChar() diff --git a/src/test/java/org/owasp/esapi/reference/EncoderTest.java b/src/test/java/org/owasp/esapi/reference/EncoderTest.java index 896a93661..125e8ed48 100644 --- a/src/test/java/org/owasp/esapi/reference/EncoderTest.java +++ b/src/test/java/org/owasp/esapi/reference/EncoderTest.java @@ -15,18 +15,15 @@ */ package org.owasp.esapi.reference; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.ObjectOutputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; -import java.io.ByteArrayOutputStream; -import java.io.ObjectOutputStream; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; +import org.junit.Ignore; import org.owasp.esapi.ESAPI; import org.owasp.esapi.Encoder; import org.owasp.esapi.EncoderConstants; @@ -41,6 +38,10 @@ import org.owasp.esapi.errors.EncodingException; import org.owasp.esapi.errors.IntrusionException; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + /** * The Class EncoderTest. * @@ -91,6 +92,8 @@ public static Test suite() { * * @throws EncodingException */ + //FIXME: Remove @Ignore + @Ignore public void testCanonicalize() throws EncodingException { System.out.println("canonicalize"); @@ -720,7 +723,7 @@ public void testWindowsCodec() { System.out.println("WindowsCodec"); Encoder instance = ESAPI.encoder(); - Codec win = new WindowsCodec(); + Codec win = new WindowsCodec(); char[] immune = new char[0]; assertEquals(null, instance.encodeForOS(win, null)); @@ -754,7 +757,7 @@ public void testUnixCodec() { System.out.println("UnixCodec"); Encoder instance = ESAPI.encoder(); - Codec unix = new UnixCodec(); + Codec unix = new UnixCodec(); char[] immune = new char[0]; assertEquals(null, instance.encodeForOS(unix, null)); From 914edd7584fa9bfa1e5a307787e64d82318a2aee Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sun, 6 Aug 2017 09:55:22 -0700 Subject: [PATCH 038/709] Issue #300 -- Fixed a tricky polymorphism bug with Jeremiah Stacey's help. Added a unit test for PercentCodec. --- .../org/owasp/esapi/codecs/JavaScriptCodec.java | 2 +- .../java/org/owasp/esapi/codecs/MySQLCodec.java | 2 +- .../java/org/owasp/esapi/codecs/OracleCodec.java | 2 +- .../org/owasp/esapi/codecs/PercentCodec.java | 2 +- .../java/org/owasp/esapi/codecs/UnixCodec.java | 2 +- .../org/owasp/esapi/codecs/VBScriptCodec.java | 2 +- .../org/owasp/esapi/codecs/WindowsCodec.java | 2 +- .../org/owasp/esapi/codecs/PercentCodecTest.java | 16 ++++++++++++++++ 8 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/owasp/esapi/codecs/PercentCodecTest.java diff --git a/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java b/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java index a10d9b588..a43cbd004 100644 --- a/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/JavaScriptCodec.java @@ -87,7 +87,7 @@ public String encodeCharacter( char[] immune, Character c ) { * \\uHHHH * \\OOO (1, 2, or 3 digits) */ - public Character decodeCharacter( PushbackString input ) { + public Character decodeCharacter( PushbackSequence input ) { input.mark(); Character first = input.next(); if ( first == null ) { diff --git a/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java b/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java index 6552d089f..1bc902342 100644 --- a/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/MySQLCodec.java @@ -162,7 +162,7 @@ private String encodeCharacterMySQL( Character c ) { * In ANSI_MODE '' decodes to ' * In MYSQL_MODE \x decodes to x (or a small list of specials) */ - public Character decodeCharacter( PushbackString input ) { + public Character decodeCharacter( PushbackSequence input ) { switch( mode ) { case ANSI: return decodeCharacterANSI( input ); case STANDARD: return decodeCharacterMySQL( input ); diff --git a/src/main/java/org/owasp/esapi/codecs/OracleCodec.java b/src/main/java/org/owasp/esapi/codecs/OracleCodec.java index ff87ea8c0..0021a8613 100644 --- a/src/main/java/org/owasp/esapi/codecs/OracleCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/OracleCodec.java @@ -59,7 +59,7 @@ public String encodeCharacter( char[] immune, Character c ) { * Formats all are legal * '' decodes to ' */ - public Character decodeCharacter( PushbackString input ) { + public Character decodeCharacter( PushbackSequence input ) { input.mark(); Character first = input.next(); if ( first == null ) { diff --git a/src/main/java/org/owasp/esapi/codecs/PercentCodec.java b/src/main/java/org/owasp/esapi/codecs/PercentCodec.java index 6b3d5d0a5..ac9e9a59e 100644 --- a/src/main/java/org/owasp/esapi/codecs/PercentCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/PercentCodec.java @@ -128,7 +128,7 @@ public String encodeCharacter( char[] immune, Character c ) * @param input * encoded character using percent characters (such as URL encoding) */ - public Character decodeCharacter( PushbackString input ) { + public Character decodeCharacter( PushbackSequence input ) { input.mark(); Character first = input.next(); if ( first == null ) { diff --git a/src/main/java/org/owasp/esapi/codecs/UnixCodec.java b/src/main/java/org/owasp/esapi/codecs/UnixCodec.java index da2dddc0b..faeebad65 100644 --- a/src/main/java/org/owasp/esapi/codecs/UnixCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/UnixCodec.java @@ -61,7 +61,7 @@ public String encodeCharacter( char[] immune, Character c ) { * \x - all special characters * */ - public Character decodeCharacter( PushbackString input ) { + public Character decodeCharacter( PushbackSequence input ) { input.mark(); Character first = input.next(); if ( first == null ) { diff --git a/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java b/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java index ae7f1f6ac..122e90aad 100644 --- a/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/VBScriptCodec.java @@ -96,7 +96,7 @@ public String encodeCharacter( char[] immune, Character c ) { * "x - all special characters * " + chr(x) + " - not supported yet */ - public Character decodeCharacter( PushbackString input ) { + public Character decodeCharacter( PushbackSequence input ) { input.mark(); Character first = input.next(); if ( first == null ) { diff --git a/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java b/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java index c0a5540ea..be22640ad 100644 --- a/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/WindowsCodec.java @@ -61,7 +61,7 @@ public String encodeCharacter( char[] immune, Character c ) { * Formats all are legal both upper/lower case: * ^x - all special characters */ - public Character decodeCharacter( PushbackString input ) { + public Character decodeCharacter( PushbackSequence input ) { input.mark(); Character first = input.next(); if ( first == null ) { diff --git a/src/test/java/org/owasp/esapi/codecs/PercentCodecTest.java b/src/test/java/org/owasp/esapi/codecs/PercentCodecTest.java new file mode 100644 index 000000000..c1b2b7ad8 --- /dev/null +++ b/src/test/java/org/owasp/esapi/codecs/PercentCodecTest.java @@ -0,0 +1,16 @@ +package org.owasp.esapi.codecs; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class PercentCodecTest { + + @Test + public void testPercentDecode(){ + Codec codec = new PercentCodec(); + + String expected = " "; + assertEquals(expected, codec.decode("%20")); + } +} From 946158b501d5d17c52cc5bbf88dedc7888fe59f2 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sun, 6 Aug 2017 11:04:45 -0700 Subject: [PATCH 039/709] Issue #300 -- We need to get more data driven unit tests, but this issue is now completely whacked! --- .../org/owasp/esapi/codecs/HTMLEntityCodec.java | 9 ++++++--- .../java/org/owasp/esapi/codecs/MySQLCodec.java | 4 ++-- .../org/owasp/esapi/codecs/XMLEntityCodec.java | 10 +++++----- .../owasp/esapi/codecs/AbstractCodecTest.java | 5 ++++- .../owasp/esapi/codecs/HTMLEntityCodecTest.java | 17 +++++++++++++++++ .../org/owasp/esapi/reference/EncoderTest.java | 2 -- 6 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java diff --git a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java index 4adfdf84c..14564372f 100644 --- a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java @@ -286,9 +286,12 @@ private Integer getNamedEntity( PushbackSequence input ) { // kludge around PushbackString.... len = Math.min(input.remainder().length(), entityToCharacterTrie.getMaxKeyLength()); - for(int i=0;i input ) { * @return * A single character, decoded */ - private Character decodeCharacterANSI( PushbackString input ) { + private Character decodeCharacterANSI( PushbackSequence input ) { input.mark(); Character first = input.next(); if ( first == null ) { @@ -214,7 +214,7 @@ private Character decodeCharacterANSI( PushbackString input ) { * @return * A single character from that string, decoded. */ - private Character decodeCharacterMySQL( PushbackString input ) { + private Character decodeCharacterMySQL( PushbackSequence input ) { input.mark(); Character first = input.next(); if ( first == null ) { diff --git a/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java index 45482adae..418f4c5a7 100644 --- a/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/XMLEntityCodec.java @@ -91,7 +91,7 @@ public String encodeCharacter(char[] immune, Character c) *
  • &name;
  • * */ - public Character decodeCharacter(PushbackString input) + public Character decodeCharacter(PushbackSequence input) { Character ret = null; Character first; @@ -137,7 +137,7 @@ else if(Character.isLetter(second.charValue())) * is positioned at the character after the &# * @return The character decoded or null on failure. */ - private static Character getNumericEntity(PushbackString input) + private static Character getNumericEntity(PushbackSequence input) { Character first = input.peek(); @@ -174,7 +174,7 @@ private static Character int2char(int i) * the next char is not a 'x' or 'X'. * @return The character decoded or null on failutre. */ - private static Character parseNumber(PushbackString input) + private static Character parseNumber(PushbackSequence input) { StringBuilder sb = new StringBuilder(); Character c; @@ -209,7 +209,7 @@ private static Character parseNumber(PushbackString input) * is positioned at the character after the &#[xX] * @return The character decoded or null on failutre. */ - private static Character parseHex(PushbackString input) + private static Character parseHex(PushbackSequence input) { Character c; StringBuilder sb = new StringBuilder(); @@ -268,7 +268,7 @@ private static Character parseHex(PushbackString input) * is positioned at the character after the &. * @return The character decoded or null on failutre. */ - private Character getNamedEntity(PushbackString input) + private Character getNamedEntity(PushbackSequence input) { StringBuilder possible = new StringBuilder(); Map.Entry entry; diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java index 9e0a5477b..1456058bd 100644 --- a/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java +++ b/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java @@ -574,7 +574,10 @@ public void testWindowsDecode() public void testHtmlDecodeCharLessThan() { - assertEquals( LESS_THAN, htmlCodec.decodeCharacter(new PushBackSequenceImpl("<")) ); + Integer value = htmlCodec.decodeCharacter(new PushBackSequenceImpl("<")); + assertEquals(new Integer(60), value); + StringBuilder sb = new StringBuilder().appendCodePoint(value); + assertEquals( LESS_THAN.toString(), sb.toString()); } public void testPercentDecodeChar() diff --git a/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java b/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java new file mode 100644 index 000000000..a12f287cd --- /dev/null +++ b/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java @@ -0,0 +1,17 @@ +package org.owasp.esapi.codecs; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class HTMLEntityCodecTest { + Codec codec = new HTMLEntityCodec(); + + @Test + public void testEntityDecoding(){ + assertEquals("<", codec.decode("<")); + assertEquals( "<", codec.decode("<")); + assertEquals( "<", codec.decode("<")); + assertEquals( "<", codec.decode("<")); + } +} diff --git a/src/test/java/org/owasp/esapi/reference/EncoderTest.java b/src/test/java/org/owasp/esapi/reference/EncoderTest.java index 125e8ed48..64bb88ac9 100644 --- a/src/test/java/org/owasp/esapi/reference/EncoderTest.java +++ b/src/test/java/org/owasp/esapi/reference/EncoderTest.java @@ -92,8 +92,6 @@ public static Test suite() { * * @throws EncodingException */ - //FIXME: Remove @Ignore - @Ignore public void testCanonicalize() throws EncodingException { System.out.println("canonicalize"); From 62c5100f4654eab3df7191c90e458120fff71133 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sun, 6 Aug 2017 11:29:49 -0700 Subject: [PATCH 040/709] Issue #303 -- Added unit test to prove that this issue is resolved. --- .../java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java b/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java index a12f287cd..bae3732dc 100644 --- a/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java +++ b/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java @@ -14,4 +14,13 @@ public void testEntityDecoding(){ assertEquals( "<", codec.decode("<")); assertEquals( "<", codec.decode("<")); } + + @Test + public void test32BitCJK(){ + String s = "𡘾𦴩𥻂"; + String expected = "𡘾𦴩𥻂"; + String bad = "������"; + assertEquals(false, expected.equals(bad)); + assertEquals(expected, codec.encode(new char[0], s)); + } } From 957a9c79f290b9d67dfa5cbdbfee78f6c2fbca4a Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Mon, 7 Aug 2017 19:50:44 -0700 Subject: [PATCH 041/709] Issue #300 -- Brought back a slightly modified version of the old HTMLEntityCodec for backwards compatability purposes. --- .../esapi/codecs/LegacyHTMLEntityCodec.java | 552 ++++++++++++++++++ 1 file changed, 552 insertions(+) create mode 100644 src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java diff --git a/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java new file mode 100644 index 000000000..e35296b62 --- /dev/null +++ b/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java @@ -0,0 +1,552 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2007 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + * @author Jeff Williams Aspect Security + * @created 2007 + */ +package org.owasp.esapi.codecs; + +import java.util.HashMap; +import java.util.Collections; +import java.util.Map; + +/** + * Implementation of the Codec interface for HTML entity encoding. + * + * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security + * @since June 1, 2007 + * @see org.owasp.esapi.Encoder + */ +public class LegacyHTMLEntityCodec extends AbstractCharacterCodec { + + private static final char REPLACEMENT_CHAR = '\ufffd'; + private static final String REPLACEMENT_HEX = "fffd"; + private static final String REPLACEMENT_STR = "" + REPLACEMENT_CHAR; + private static final Map characterToEntityMap = mkCharacterToEntityMap(); + private static final Trie entityToCharacterTrie = mkEntityToCharacterTrie(); + + /** + * {@inheritDoc} + * + * Encodes a Character for safe use in an HTML entity field. + * @param immune + */ + public String encodeCharacter( char[] immune, Character c ) { + + // check for immune characters + if ( containsCharacter(c, immune ) ) { + return ""+c; + } + + // check for alphanumeric characters + String hex = super.getHexForNonAlphanumeric(c); + if ( hex == null ) { + return ""+c; + } + + // check for illegal characters + if ( ( c <= 0x1f && c != '\t' && c != '\n' && c != '\r' ) || ( c >= 0x7f && c <= 0x9f ) ) + { + hex = REPLACEMENT_HEX; // Let's entity encode this instead of returning it + c = REPLACEMENT_CHAR; + } + + // check if there's a defined entity + String entityName = (String) characterToEntityMap.get(c); + if (entityName != null) { + return "&" + entityName + ";"; + } + + // return the hex entity as suggested in the spec + return "&#x" + hex + ";"; + } + + /** + * {@inheritDoc} + * + * Returns the decoded version of the character starting at index, or + * null if no decoding is possible. + * + * Formats all are legal both with and without semi-colon, upper/lower case: + * &#dddd; + * &#xhhhh; + * &name; + */ + public Character decodeCharacter( PushbackString input ) { + input.mark(); + Character first = input.next(); + if ( first == null ) { + input.reset(); + return null; + } + + // if this is not an encoded character, return null + if (first != '&' ) { + input.reset(); + return null; + } + + // test for numeric encodings + Character second = input.next(); + if ( second == null ) { + input.reset(); + return null; + } + + if (second == '#' ) { + // handle numbers + Character c = getNumericEntity( input ); + if ( c != null ) return c; + } else if ( Character.isLetter( second.charValue() ) ) { + // handle entities + input.pushback( second ); + Character c = getNamedEntity( input ); + if ( c != null ) return c; + } + input.reset(); + return null; + } + + /** + * getNumericEntry checks input to see if it is a numeric entity + * + * @param input + * The input to test for being a numeric entity + * + * @return + * null if input is null, the character of input after decoding + */ + private Character getNumericEntity( PushbackString input ) { + Character first = input.peek(); + if ( first == null ) return null; + + if (first == 'x' || first == 'X' ) { + input.next(); + return parseHex( input ); + } + return parseNumber( input ); + } + + /** + * Parse a decimal number, such as those from JavaScript's String.fromCharCode(value) + * + * @param input + * decimal encoded string, such as 65 + * @return + * character representation of this decimal value, e.g. A + * @throws NumberFormatException + */ + private Character parseNumber( PushbackString input ) { + StringBuilder sb = new StringBuilder(); + while( input.hasNext() ) { + Character c = input.peek(); + + // if character is a digit then add it on and keep going + if ( Character.isDigit( c.charValue() ) ) { + sb.append( c ); + input.next(); + + // if character is a semi-colon, eat it and quit + } else if (c == ';' ) { + input.next(); + break; + + // otherwise just quit + } else { + break; + } + } + try { + int i = Integer.parseInt(sb.toString()); + if (Character.isValidCodePoint(i)) { + return (char) i; + } + } catch( NumberFormatException e ) { + // throw an exception for malformed entity? + } + return null; + } + + /** + * Parse a hex encoded entity + * + * @param input + * Hex encoded input (such as 437ae;) + * @return + * A single character from the string + * @throws NumberFormatException + */ + private Character parseHex( PushbackString input ) { + StringBuilder sb = new StringBuilder(); + while( input.hasNext() ) { + Character c = input.peek(); + + // if character is a hex digit then add it on and keep going + if ( "0123456789ABCDEFabcdef".indexOf(c) != -1 ) { + sb.append( c ); + input.next(); + + // if character is a semi-colon, eat it and quit + } else if (c == ';' ) { + input.next(); + break; + + // otherwise just quit + } else { + break; + } + } + try { + int i = Integer.parseInt(sb.toString(), 16); + if (Character.isValidCodePoint(i)) { + return (char) i; + } + } catch( NumberFormatException e ) { + // throw an exception for malformed entity? + } + return null; + } + + /** + * + * Returns the decoded version of the character starting at index, or + * null if no decoding is possible. + * + * Formats all are legal both with and without semi-colon, upper/lower case: + * &aa; + * &aaa; + * &aaaa; + * &aaaaa; + * &aaaaaa; + * &aaaaaaa; + * + * @param input + * A string containing a named entity like " + * @return + * Returns the decoded version of the character starting at index, or null if no decoding is possible. + */ + private Character getNamedEntity( PushbackString input ) { + StringBuilder possible = new StringBuilder(); + Map.Entry entry; + int len; + + // kludge around PushbackString.... + len = Math.min(input.remainder().length(), entityToCharacterTrie.getMaxKeyLength()); + for(int i=0;i exactEntry = entityToCharacterTrie.getLongestMatch(possibleStringLowerCase); + if(exactEntry != null) entry = exactEntry; + } + if(entry == null) return null; // no match, caller will reset input + } + + // fixup input + input.reset(); + input.next(); // read & + len = entry.getKey().length(); // what matched's length + for(int i=0;i mkCharacterToEntityMap() + { + Map map = new HashMap(252); + + map.put((char)34, "quot"); /* quotation mark */ + map.put((char)38, "amp"); /* ampersand */ + map.put((char)60, "lt"); /* less-than sign */ + map.put((char)62, "gt"); /* greater-than sign */ + map.put((char)160, "nbsp"); /* no-break space */ + map.put((char)161, "iexcl"); /* inverted exclamation mark */ + map.put((char)162, "cent"); /* cent sign */ + map.put((char)163, "pound"); /* pound sign */ + map.put((char)164, "curren"); /* currency sign */ + map.put((char)165, "yen"); /* yen sign */ + map.put((char)166, "brvbar"); /* broken bar */ + map.put((char)167, "sect"); /* section sign */ + map.put((char)168, "uml"); /* diaeresis */ + map.put((char)169, "copy"); /* copyright sign */ + map.put((char)170, "ordf"); /* feminine ordinal indicator */ + map.put((char)171, "laquo"); /* left-pointing double angle quotation mark */ + map.put((char)172, "not"); /* not sign */ + map.put((char)173, "shy"); /* soft hyphen */ + map.put((char)174, "reg"); /* registered sign */ + map.put((char)175, "macr"); /* macron */ + map.put((char)176, "deg"); /* degree sign */ + map.put((char)177, "plusmn"); /* plus-minus sign */ + map.put((char)178, "sup2"); /* superscript two */ + map.put((char)179, "sup3"); /* superscript three */ + map.put((char)180, "acute"); /* acute accent */ + map.put((char)181, "micro"); /* micro sign */ + map.put((char)182, "para"); /* pilcrow sign */ + map.put((char)183, "middot"); /* middle dot */ + map.put((char)184, "cedil"); /* cedilla */ + map.put((char)185, "sup1"); /* superscript one */ + map.put((char)186, "ordm"); /* masculine ordinal indicator */ + map.put((char)187, "raquo"); /* right-pointing double angle quotation mark */ + map.put((char)188, "frac14"); /* vulgar fraction one quarter */ + map.put((char)189, "frac12"); /* vulgar fraction one half */ + map.put((char)190, "frac34"); /* vulgar fraction three quarters */ + map.put((char)191, "iquest"); /* inverted question mark */ + map.put((char)192, "Agrave"); /* Latin capital letter a with grave */ + map.put((char)193, "Aacute"); /* Latin capital letter a with acute */ + map.put((char)194, "Acirc"); /* Latin capital letter a with circumflex */ + map.put((char)195, "Atilde"); /* Latin capital letter a with tilde */ + map.put((char)196, "Auml"); /* Latin capital letter a with diaeresis */ + map.put((char)197, "Aring"); /* Latin capital letter a with ring above */ + map.put((char)198, "AElig"); /* Latin capital letter ae */ + map.put((char)199, "Ccedil"); /* Latin capital letter c with cedilla */ + map.put((char)200, "Egrave"); /* Latin capital letter e with grave */ + map.put((char)201, "Eacute"); /* Latin capital letter e with acute */ + map.put((char)202, "Ecirc"); /* Latin capital letter e with circumflex */ + map.put((char)203, "Euml"); /* Latin capital letter e with diaeresis */ + map.put((char)204, "Igrave"); /* Latin capital letter i with grave */ + map.put((char)205, "Iacute"); /* Latin capital letter i with acute */ + map.put((char)206, "Icirc"); /* Latin capital letter i with circumflex */ + map.put((char)207, "Iuml"); /* Latin capital letter i with diaeresis */ + map.put((char)208, "ETH"); /* Latin capital letter eth */ + map.put((char)209, "Ntilde"); /* Latin capital letter n with tilde */ + map.put((char)210, "Ograve"); /* Latin capital letter o with grave */ + map.put((char)211, "Oacute"); /* Latin capital letter o with acute */ + map.put((char)212, "Ocirc"); /* Latin capital letter o with circumflex */ + map.put((char)213, "Otilde"); /* Latin capital letter o with tilde */ + map.put((char)214, "Ouml"); /* Latin capital letter o with diaeresis */ + map.put((char)215, "times"); /* multiplication sign */ + map.put((char)216, "Oslash"); /* Latin capital letter o with stroke */ + map.put((char)217, "Ugrave"); /* Latin capital letter u with grave */ + map.put((char)218, "Uacute"); /* Latin capital letter u with acute */ + map.put((char)219, "Ucirc"); /* Latin capital letter u with circumflex */ + map.put((char)220, "Uuml"); /* Latin capital letter u with diaeresis */ + map.put((char)221, "Yacute"); /* Latin capital letter y with acute */ + map.put((char)222, "THORN"); /* Latin capital letter thorn */ + map.put((char)223, "szlig"); /* Latin small letter sharp sXCOMMAX German Eszett */ + map.put((char)224, "agrave"); /* Latin small letter a with grave */ + map.put((char)225, "aacute"); /* Latin small letter a with acute */ + map.put((char)226, "acirc"); /* Latin small letter a with circumflex */ + map.put((char)227, "atilde"); /* Latin small letter a with tilde */ + map.put((char)228, "auml"); /* Latin small letter a with diaeresis */ + map.put((char)229, "aring"); /* Latin small letter a with ring above */ + map.put((char)230, "aelig"); /* Latin lowercase ligature ae */ + map.put((char)231, "ccedil"); /* Latin small letter c with cedilla */ + map.put((char)232, "egrave"); /* Latin small letter e with grave */ + map.put((char)233, "eacute"); /* Latin small letter e with acute */ + map.put((char)234, "ecirc"); /* Latin small letter e with circumflex */ + map.put((char)235, "euml"); /* Latin small letter e with diaeresis */ + map.put((char)236, "igrave"); /* Latin small letter i with grave */ + map.put((char)237, "iacute"); /* Latin small letter i with acute */ + map.put((char)238, "icirc"); /* Latin small letter i with circumflex */ + map.put((char)239, "iuml"); /* Latin small letter i with diaeresis */ + map.put((char)240, "eth"); /* Latin small letter eth */ + map.put((char)241, "ntilde"); /* Latin small letter n with tilde */ + map.put((char)242, "ograve"); /* Latin small letter o with grave */ + map.put((char)243, "oacute"); /* Latin small letter o with acute */ + map.put((char)244, "ocirc"); /* Latin small letter o with circumflex */ + map.put((char)245, "otilde"); /* Latin small letter o with tilde */ + map.put((char)246, "ouml"); /* Latin small letter o with diaeresis */ + map.put((char)247, "divide"); /* division sign */ + map.put((char)248, "oslash"); /* Latin small letter o with stroke */ + map.put((char)249, "ugrave"); /* Latin small letter u with grave */ + map.put((char)250, "uacute"); /* Latin small letter u with acute */ + map.put((char)251, "ucirc"); /* Latin small letter u with circumflex */ + map.put((char)252, "uuml"); /* Latin small letter u with diaeresis */ + map.put((char)253, "yacute"); /* Latin small letter y with acute */ + map.put((char)254, "thorn"); /* Latin small letter thorn */ + map.put((char)255, "yuml"); /* Latin small letter y with diaeresis */ + map.put((char)338, "OElig"); /* Latin capital ligature oe */ + map.put((char)339, "oelig"); /* Latin small ligature oe */ + map.put((char)352, "Scaron"); /* Latin capital letter s with caron */ + map.put((char)353, "scaron"); /* Latin small letter s with caron */ + map.put((char)376, "Yuml"); /* Latin capital letter y with diaeresis */ + map.put((char)402, "fnof"); /* Latin small letter f with hook */ + map.put((char)710, "circ"); /* modifier letter circumflex accent */ + map.put((char)732, "tilde"); /* small tilde */ + map.put((char)913, "Alpha"); /* Greek capital letter alpha */ + map.put((char)914, "Beta"); /* Greek capital letter beta */ + map.put((char)915, "Gamma"); /* Greek capital letter gamma */ + map.put((char)916, "Delta"); /* Greek capital letter delta */ + map.put((char)917, "Epsilon"); /* Greek capital letter epsilon */ + map.put((char)918, "Zeta"); /* Greek capital letter zeta */ + map.put((char)919, "Eta"); /* Greek capital letter eta */ + map.put((char)920, "Theta"); /* Greek capital letter theta */ + map.put((char)921, "Iota"); /* Greek capital letter iota */ + map.put((char)922, "Kappa"); /* Greek capital letter kappa */ + map.put((char)923, "Lambda"); /* Greek capital letter lambda */ + map.put((char)924, "Mu"); /* Greek capital letter mu */ + map.put((char)925, "Nu"); /* Greek capital letter nu */ + map.put((char)926, "Xi"); /* Greek capital letter xi */ + map.put((char)927, "Omicron"); /* Greek capital letter omicron */ + map.put((char)928, "Pi"); /* Greek capital letter pi */ + map.put((char)929, "Rho"); /* Greek capital letter rho */ + map.put((char)931, "Sigma"); /* Greek capital letter sigma */ + map.put((char)932, "Tau"); /* Greek capital letter tau */ + map.put((char)933, "Upsilon"); /* Greek capital letter upsilon */ + map.put((char)934, "Phi"); /* Greek capital letter phi */ + map.put((char)935, "Chi"); /* Greek capital letter chi */ + map.put((char)936, "Psi"); /* Greek capital letter psi */ + map.put((char)937, "Omega"); /* Greek capital letter omega */ + map.put((char)945, "alpha"); /* Greek small letter alpha */ + map.put((char)946, "beta"); /* Greek small letter beta */ + map.put((char)947, "gamma"); /* Greek small letter gamma */ + map.put((char)948, "delta"); /* Greek small letter delta */ + map.put((char)949, "epsilon"); /* Greek small letter epsilon */ + map.put((char)950, "zeta"); /* Greek small letter zeta */ + map.put((char)951, "eta"); /* Greek small letter eta */ + map.put((char)952, "theta"); /* Greek small letter theta */ + map.put((char)953, "iota"); /* Greek small letter iota */ + map.put((char)954, "kappa"); /* Greek small letter kappa */ + map.put((char)955, "lambda"); /* Greek small letter lambda */ + map.put((char)956, "mu"); /* Greek small letter mu */ + map.put((char)957, "nu"); /* Greek small letter nu */ + map.put((char)958, "xi"); /* Greek small letter xi */ + map.put((char)959, "omicron"); /* Greek small letter omicron */ + map.put((char)960, "pi"); /* Greek small letter pi */ + map.put((char)961, "rho"); /* Greek small letter rho */ + map.put((char)962, "sigmaf"); /* Greek small letter final sigma */ + map.put((char)963, "sigma"); /* Greek small letter sigma */ + map.put((char)964, "tau"); /* Greek small letter tau */ + map.put((char)965, "upsilon"); /* Greek small letter upsilon */ + map.put((char)966, "phi"); /* Greek small letter phi */ + map.put((char)967, "chi"); /* Greek small letter chi */ + map.put((char)968, "psi"); /* Greek small letter psi */ + map.put((char)969, "omega"); /* Greek small letter omega */ + map.put((char)977, "thetasym"); /* Greek theta symbol */ + map.put((char)978, "upsih"); /* Greek upsilon with hook symbol */ + map.put((char)982, "piv"); /* Greek pi symbol */ + map.put((char)8194, "ensp"); /* en space */ + map.put((char)8195, "emsp"); /* em space */ + map.put((char)8201, "thinsp"); /* thin space */ + map.put((char)8204, "zwnj"); /* zero width non-joiner */ + map.put((char)8205, "zwj"); /* zero width joiner */ + map.put((char)8206, "lrm"); /* left-to-right mark */ + map.put((char)8207, "rlm"); /* right-to-left mark */ + map.put((char)8211, "ndash"); /* en dash */ + map.put((char)8212, "mdash"); /* em dash */ + map.put((char)8216, "lsquo"); /* left single quotation mark */ + map.put((char)8217, "rsquo"); /* right single quotation mark */ + map.put((char)8218, "sbquo"); /* single low-9 quotation mark */ + map.put((char)8220, "ldquo"); /* left double quotation mark */ + map.put((char)8221, "rdquo"); /* right double quotation mark */ + map.put((char)8222, "bdquo"); /* double low-9 quotation mark */ + map.put((char)8224, "dagger"); /* dagger */ + map.put((char)8225, "Dagger"); /* double dagger */ + map.put((char)8226, "bull"); /* bullet */ + map.put((char)8230, "hellip"); /* horizontal ellipsis */ + map.put((char)8240, "permil"); /* per mille sign */ + map.put((char)8242, "prime"); /* prime */ + map.put((char)8243, "Prime"); /* double prime */ + map.put((char)8249, "lsaquo"); /* single left-pointing angle quotation mark */ + map.put((char)8250, "rsaquo"); /* single right-pointing angle quotation mark */ + map.put((char)8254, "oline"); /* overline */ + map.put((char)8260, "frasl"); /* fraction slash */ + map.put((char)8364, "euro"); /* euro sign */ + map.put((char)8465, "image"); /* black-letter capital i */ + map.put((char)8472, "weierp"); /* script capital pXCOMMAX Weierstrass p */ + map.put((char)8476, "real"); /* black-letter capital r */ + map.put((char)8482, "trade"); /* trademark sign */ + map.put((char)8501, "alefsym"); /* alef symbol */ + map.put((char)8592, "larr"); /* leftwards arrow */ + map.put((char)8593, "uarr"); /* upwards arrow */ + map.put((char)8594, "rarr"); /* rightwards arrow */ + map.put((char)8595, "darr"); /* downwards arrow */ + map.put((char)8596, "harr"); /* left right arrow */ + map.put((char)8629, "crarr"); /* downwards arrow with corner leftwards */ + map.put((char)8656, "lArr"); /* leftwards double arrow */ + map.put((char)8657, "uArr"); /* upwards double arrow */ + map.put((char)8658, "rArr"); /* rightwards double arrow */ + map.put((char)8659, "dArr"); /* downwards double arrow */ + map.put((char)8660, "hArr"); /* left right double arrow */ + map.put((char)8704, "forall"); /* for all */ + map.put((char)8706, "part"); /* partial differential */ + map.put((char)8707, "exist"); /* there exists */ + map.put((char)8709, "empty"); /* empty set */ + map.put((char)8711, "nabla"); /* nabla */ + map.put((char)8712, "isin"); /* element of */ + map.put((char)8713, "notin"); /* not an element of */ + map.put((char)8715, "ni"); /* contains as member */ + map.put((char)8719, "prod"); /* n-ary product */ + map.put((char)8721, "sum"); /* n-ary summation */ + map.put((char)8722, "minus"); /* minus sign */ + map.put((char)8727, "lowast"); /* asterisk operator */ + map.put((char)8730, "radic"); /* square root */ + map.put((char)8733, "prop"); /* proportional to */ + map.put((char)8734, "infin"); /* infinity */ + map.put((char)8736, "ang"); /* angle */ + map.put((char)8743, "and"); /* logical and */ + map.put((char)8744, "or"); /* logical or */ + map.put((char)8745, "cap"); /* intersection */ + map.put((char)8746, "cup"); /* union */ + map.put((char)8747, "int"); /* integral */ + map.put((char)8756, "there4"); /* therefore */ + map.put((char)8764, "sim"); /* tilde operator */ + map.put((char)8773, "cong"); /* congruent to */ + map.put((char)8776, "asymp"); /* almost equal to */ + map.put((char)8800, "ne"); /* not equal to */ + map.put((char)8801, "equiv"); /* identical toXCOMMAX equivalent to */ + map.put((char)8804, "le"); /* less-than or equal to */ + map.put((char)8805, "ge"); /* greater-than or equal to */ + map.put((char)8834, "sub"); /* subset of */ + map.put((char)8835, "sup"); /* superset of */ + map.put((char)8836, "nsub"); /* not a subset of */ + map.put((char)8838, "sube"); /* subset of or equal to */ + map.put((char)8839, "supe"); /* superset of or equal to */ + map.put((char)8853, "oplus"); /* circled plus */ + map.put((char)8855, "otimes"); /* circled times */ + map.put((char)8869, "perp"); /* up tack */ + map.put((char)8901, "sdot"); /* dot operator */ + map.put((char)8968, "lceil"); /* left ceiling */ + map.put((char)8969, "rceil"); /* right ceiling */ + map.put((char)8970, "lfloor"); /* left floor */ + map.put((char)8971, "rfloor"); /* right floor */ + map.put((char)9001, "lang"); /* left-pointing angle bracket */ + map.put((char)9002, "rang"); /* right-pointing angle bracket */ + map.put((char)9674, "loz"); /* lozenge */ + map.put((char)9824, "spades"); /* black spade suit */ + map.put((char)9827, "clubs"); /* black club suit */ + map.put((char)9829, "hearts"); /* black heart suit */ + map.put((char)9830, "diams"); /* black diamond suit */ + + return Collections.unmodifiableMap(map); + } + + /** + * Build a unmodifiable Trie from entitiy Name to Character + * @return Unmodifiable trie. + */ + private static synchronized Trie mkEntityToCharacterTrie() + { + Trie trie = new HashTrie(); + + for(Map.Entry entry : characterToEntityMap.entrySet()) + trie.put(entry.getValue(),entry.getKey()); + return Trie.Util.unmodifiable(trie); + } +} From 524950f2eaa65dbadd1583c2ed84e14b921893e7 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Mon, 7 Aug 2017 19:51:26 -0700 Subject: [PATCH 042/709] Issue #300 -- Brought back a slightly modified version of the old HTMLEntityCodec for backwards compatability purposes. --- .../java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java index e35296b62..57e5cb6b8 100644 --- a/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/LegacyHTMLEntityCodec.java @@ -20,6 +20,12 @@ import java.util.Map; /** + * + * This class is DEPRECATED. It did not correctly handle encoding of non-BMP + * unicode code points. This class is provided solely for any fatal bugs + * not accounted for in the new version and will be removed entirely in + * a future release. + * * Implementation of the Codec interface for HTML entity encoding. * * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Date: Mon, 7 Aug 2017 23:22:53 -0700 Subject: [PATCH 043/709] Issue #300 -- Addressed a review comment. --- src/main/java/org/owasp/esapi/codecs/AbstractCodec.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java index 236125497..99188bc1b 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java @@ -121,9 +121,11 @@ public String getHexForNonAlphanumeric(char c) */ public String getHexForNonAlphanumeric(int c) { - if(c<0xFF) + if(c<0xFF){ return hex[c]; - return toHex(c); + }else{ + return toHex(c); + } } public String toOctal(char c) From 4c92b64ca3d64ca7b8801f52203689315ec803a6 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Tue, 8 Aug 2017 22:50:15 -0700 Subject: [PATCH 044/709] Issue #300 -- Added OWASP file headers, and did some lexical cleanup on if statements. --- .../esapi/codecs/AbstractCharacterCodec.java | 26 +++++++ .../org/owasp/esapi/codecs/AbstractCodec.java | 6 +- .../esapi/codecs/AbstractIntegerCodec.java | 28 +++++++ .../codecs/AbstractPushbackSequence.java | 29 ++++++++ .../java/org/owasp/esapi/codecs/Codec.java | 3 + .../owasp/esapi/codecs/HTMLEntityCodec.java | 11 ++- .../owasp/esapi/codecs/PushbackSequence.java | 18 ++++- .../owasp/esapi/codecs/PushbackString.java | 73 +++++++++++++------ 8 files changed, 164 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractCharacterCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractCharacterCodec.java index 5868d2bc0..ed73e4bd9 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractCharacterCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractCharacterCodec.java @@ -1,5 +1,31 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2017 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + * @author Matt Seil (mseil .at. owasp.org) + * @created 2017 + * + * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security + * @created 2007 + */ + package org.owasp.esapi.codecs; +/** + * + * This abstract Impl is broken off from the original {@code Codec} class and + * provides the {@code Character} parsing logic that has been with ESAPI from the beginning. + * + */ public abstract class AbstractCharacterCodec extends AbstractCodec { /* (non-Javadoc) * @see org.owasp.esapi.codecs.Codec#decode(java.lang.String) diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java index 99188bc1b..d6ee5ffcf 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java @@ -5,13 +5,13 @@ * Enterprise Security API (ESAPI) project. For details, please see * http://www.owasp.org/index.php/ESAPI. * - * Copyright (c) 2007 - The OWASP Foundation + * Copyright (c) 2017 - The OWASP Foundation * * The ESAPI is published by OWASP under the BSD license. You should read and accept the * LICENSE before you use, modify, and/or redistribute this software. * - * @author Jeff Williams Aspect Security - * @created 2007 + * @author Matt Seil (mseil .at. owasp.org) + * @created 2017 */ package org.owasp.esapi.codecs; diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java index f43891481..6e6a9c1a9 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java @@ -1,5 +1,33 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2017 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + * @author Matt Seil (mseil .at. owasp.org) + * @created 2017 + * + * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security + * @created 2007 + */ package org.owasp.esapi.codecs; +/** + * This class is intended to be an alternative Abstract Implementation for parsing encoding + * data by focusing on {@code int} as opposed to {@code Character}. Because non-BMP code + * points cannot be represented by a {@code char}, this class remedies that by parsing string + * data as codePoints as opposed to a stream of {@code char}s. + * + * @author Matt Seil (mseil .at. owasp.org) + * @Created 2017 -- Adapted from Jeff Williams' original {@code Codec} class. + */ public class AbstractIntegerCodec extends AbstractCodec { /** diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractPushbackSequence.java b/src/main/java/org/owasp/esapi/codecs/AbstractPushbackSequence.java index ee35478c3..bf9f78a8e 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractPushbackSequence.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractPushbackSequence.java @@ -1,5 +1,34 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2017 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + * @author Matt Seil (mseil .at. owasp.org) + * @created 2017 + * + */ + package org.owasp.esapi.codecs; +/** + * + * This Abstract class provides the generic logic for using a {@code PushbackSequence} + * in regards to iterating strings. The final Impl is intended for the user to supply + * a type {@code T} such that the pushback interface can be utilized for sequences + * of type {@code T}. Presently this generic class is limited by the fact that + * @{code input} is a {@code String}. + * + * @author Matt Seil + * + * @param + */ public abstract class AbstractPushbackSequence implements PushbackSequence { protected String input; protected T pushback; diff --git a/src/main/java/org/owasp/esapi/codecs/Codec.java b/src/main/java/org/owasp/esapi/codecs/Codec.java index c205d1b3f..5e914a4a0 100644 --- a/src/main/java/org/owasp/esapi/codecs/Codec.java +++ b/src/main/java/org/owasp/esapi/codecs/Codec.java @@ -26,6 +26,9 @@ * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security * @since June 1, 2007 + * + * @author Matt Seil (mseil .at. owasp.org) + * @since June 1, 2017 * @see org.owasp.esapi.Encoder */ public interface Codec { diff --git a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java index 14564372f..fd2c866cc 100644 --- a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java @@ -5,12 +5,16 @@ * Enterprise Security API (ESAPI) project. For details, please see * http://www.owasp.org/index.php/ESAPI. * - * Copyright (c) 2007 - The OWASP Foundation + * Copyright (c) 2017 - The OWASP Foundation * * The ESAPI is published by OWASP under the BSD license. You should read and accept the * LICENSE before you use, modify, and/or redistribute this software. * - * @author Jeff Williams Aspect Security + * @author Matt Seil (mseil .at. owasp.org) + * @created 2017 + * + * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security * @created 2007 */ package org.owasp.esapi.codecs; @@ -26,6 +30,9 @@ * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security * @since June 1, 2007 + * + * @author Matt Seil (mseil .at. owasp.org) (mseil .at. owasp.org) + * * @see org.owasp.esapi.Encoder */ public class HTMLEntityCodec extends AbstractIntegerCodec diff --git a/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java b/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java index 5588ca91f..bcdbff635 100644 --- a/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java +++ b/src/main/java/org/owasp/esapi/codecs/PushbackSequence.java @@ -1,3 +1,19 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2017 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + * @author Matt Seil (mseil .at. owasp.org) + * @created 2017 + * + */ package org.owasp.esapi.codecs; public interface PushbackSequence { @@ -10,7 +26,7 @@ public interface PushbackSequence { /** * Get the current index of the PushbackString. Typically used in error messages. - * @return The current index of the PushbackString. + * @return The current index of the PushbackSequence. */ int index(); diff --git a/src/main/java/org/owasp/esapi/codecs/PushbackString.java b/src/main/java/org/owasp/esapi/codecs/PushbackString.java index 4255045f9..103993c05 100644 --- a/src/main/java/org/owasp/esapi/codecs/PushbackString.java +++ b/src/main/java/org/owasp/esapi/codecs/PushbackString.java @@ -5,12 +5,16 @@ * Enterprise Security API (ESAPI) project. For details, please see * http://www.owasp.org/index.php/ESAPI. * - * Copyright (c) 2007 - The OWASP Foundation + * Copyright (c) 2017 - The OWASP Foundation * * The ESAPI is published by OWASP under the BSD license. You should read and accept the * LICENSE before you use, modify, and/or redistribute this software. * - * @author Jeff Williams Aspect Security + * @author Matt Seil (mseil .at. owasp.org) + * @updated 2017 + * + * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security * @created 2007 */ package org.owasp.esapi.codecs; @@ -25,7 +29,7 @@ * @since June 1, 2007 * @see org.owasp.esapi.Encoder */ -public class PushbackString extends AbstractPushbackSequence{ +public class PushbackString extends AbstractPushbackSequence { /** * * @param input @@ -49,14 +53,18 @@ public int index() { * @see org.owasp.esapi.codecs.PushbackSequence#hasNext() */ public boolean hasNext() { - if (pushback != null) + if (pushback != null){ return true; - if (input == null) + } + if (input == null){ return false; - if (input.length() == 0) + } + if (input.length() == 0){ return false; - if (index >= input.length()) + } + if (index >= input.length()){ return false; + } return true; } @@ -71,12 +79,15 @@ public Character next() { pushback = null; return save; } - if (input == null) + if (input == null){ return null; - if (input.length() == 0) + } + if (input.length() == 0){ return null; - if (index >= input.length()) + } + if (index >= input.length()){ return null; + } return Character.valueOf(input.charAt(index++)); } @@ -87,10 +98,12 @@ public Character next() { */ public Character nextHex() { Character c = next(); - if (c == null) + if (c == null){ return null; - if (isHexDigit(c)) + } + if (isHexDigit(c)){ return c; + } return null; } @@ -101,10 +114,12 @@ public Character nextHex() { */ public Character nextOctal() { Character c = next(); - if (c == null) + if (c == null){ return null; - if (isOctalDigit(c)) + } + if (isOctalDigit(c)){ return c; + } return null; } @@ -116,8 +131,9 @@ public Character nextOctal() { * @return */ public static boolean isHexDigit(Character c) { - if (c == null) + if (c == null){ return false; + } char ch = c.charValue(); return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); } @@ -129,8 +145,9 @@ public static boolean isHexDigit(Character c) { * @return */ public static boolean isOctalDigit(Character c) { - if (c == null) + if (c == null){ return false; + } char ch = c.charValue(); return ch >= '0' && ch <= '7'; } @@ -141,14 +158,18 @@ public static boolean isOctalDigit(Character c) { * @see org.owasp.esapi.codecs.PushbackSequence#peek() */ public Character peek() { - if (pushback != null) + if (pushback != null){ return pushback; - if (input == null) + } + if (input == null){ return null; - if (input.length() == 0) + } + if (input.length() == 0){ return null; - if (index >= input.length()) + } + if (index >= input.length()){ return null; + } return Character.valueOf(input.charAt(index)); } @@ -158,14 +179,18 @@ public Character peek() { * @see org.owasp.esapi.codecs.PushbackSequence#peek(char) */ public boolean peek(Character c) { - if (pushback != null && pushback.charValue() == c) + if (pushback != null && pushback.charValue() == c){ return true; - if (input == null) + } + if (input == null){ return false; - if (input.length() == 0) + } + if (input.length() == 0){ return false; - if (index >= input.length()) + } + if (index >= input.length()){ return false; + } return input.charAt(index) == c; } From 039040686faeeed649b1ed544417731dddc08aaa Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Thu, 10 Aug 2017 00:09:18 -0700 Subject: [PATCH 045/709] Issue #300 -- implemented HTMLEntityCodec and affected unit tests. --- src/main/java/org/owasp/esapi/codecs/AbstractCodec.java | 6 +++++- .../java/org/owasp/esapi/codecs/AbstractIntegerCodec.java | 5 ++--- src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java index d6ee5ffcf..2a71c1b1c 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java @@ -87,7 +87,11 @@ public String encodeCharacter( char[] immune, Character c ) { */ @Override public String encodeCharacter( char[] immune, int codePoint ) { - return new StringBuilder().appendCodePoint(codePoint).toString(); + String rval = ""; + if(Character.isValidCodePoint(codePoint)){ + rval = new StringBuilder().appendCodePoint(codePoint).toString(); + } + return rval; } diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java index 6e6a9c1a9..61dadbc76 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java @@ -39,10 +39,9 @@ public String decode(String input) { PushbackSequence pbs = new PushBackSequenceImpl(input); while (pbs.hasNext()) { Integer c = decodeCharacter(pbs); - if (c != null) { + boolean isValid = Character.isValidCodePoint(c); + if (c != null && isValid) { sb.appendCodePoint(c); - } else { - sb.appendCodePoint(pbs.next()); } } return sb.toString(); diff --git a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java index fd2c866cc..3976d9e14 100644 --- a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java @@ -203,7 +203,7 @@ private Integer parseNumber( PushbackSequence input ) { Integer c = input.peek(); // if character is a digit then add it on and keep going - if ( Character.isDigit( c ) ) { + if ( Character.isDigit( c ) && Character.isValidCodePoint(c) ) { sb.appendCodePoint( c ); input.next(); @@ -243,6 +243,7 @@ private Integer parseHex( PushbackSequence input ) { Integer c = input.peek(); // if character is a hex digit then add it on and keep going + //This statement implicitly tests for Character.isValidCodePoint(int) if ( "0123456789ABCDEFabcdef".indexOf(c) != -1 ) { sb.appendCodePoint( c ); input.next(); @@ -295,7 +296,7 @@ private Integer getNamedEntity( PushbackSequence input ) { len = Math.min(input.remainder().length(), entityToCharacterTrie.getMaxKeyLength()); for(int i=0;i Date: Thu, 10 Aug 2017 19:57:35 -0700 Subject: [PATCH 046/709] Issue #300 -- Added test cases with mixed BMP and non BMP data. --- .../org/owasp/esapi/codecs/AbstractCodec.java | 4 + .../esapi/codecs/AbstractIntegerCodec.java | 4 +- .../owasp/esapi/codecs/HTMLEntityCodec.java | 80 ++++++++----------- .../owasp/esapi/codecs/AbstractCodecTest.java | 16 ++-- .../esapi/codecs/HTMLEntityCodecTest.java | 25 ++++++ .../owasp/esapi/reference/EncoderTest.java | 3 +- 6 files changed, 76 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java index 2a71c1b1c..b9c0ac6c7 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java @@ -82,6 +82,10 @@ public String encodeCharacter( char[] immune, Character c ) { return ""+c; } + public String encodeCharacter(char[] immune, char c){ + throw new IllegalArgumentException("You tried to call encodeCharacter with a char. Nope. Use Character instead!"); + } + /* (non-Javadoc) * @see org.owasp.esapi.codecs.Codec#encodeCharacter(char[], int) */ diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java index 61dadbc76..635f2f1e0 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractIntegerCodec.java @@ -39,9 +39,11 @@ public String decode(String input) { PushbackSequence pbs = new PushBackSequenceImpl(input); while (pbs.hasNext()) { Integer c = decodeCharacter(pbs); - boolean isValid = Character.isValidCodePoint(c); + boolean isValid = null == c ? false:Character.isValidCodePoint(c); if (c != null && isValid) { sb.appendCodePoint(c); + }else{ + sb.appendCodePoint(pbs.next()); } } return sb.toString(); diff --git a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java index 3976d9e14..ee7139450 100644 --- a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java @@ -50,34 +50,56 @@ public class HTMLEntityCodec extends AbstractIntegerCodec public HTMLEntityCodec() { } + /** + * Overrides the AbstractImpl to keep this code performing entirely at the {@code int} + * level. + */ + @Override + public String encode(char[] immune, String input) { + StringBuilder sb = new StringBuilder(); + for(int offset = 0; offset < input.length(); ){ + final int point = input.codePointAt(offset); + if(Character.isValidCodePoint(point)){ + sb.append(encodeCharacter(immune, point)); + } + offset += Character.charCount(point); + } + return sb.toString(); + } + /** * {@inheritDoc} * - * Encodes a Character for safe use in an HTML entity field. + * Encodes a codePoint for safe use in an HTML entity field. * @param immune */ - public String encodeCharacter( char[] immune, Character c ) { + @Override + public String encodeCharacter( char[] immune, int codePoint ) { // check for immune characters - if ( containsCharacter(c, immune ) ) { - return ""+c; + // Cast the codePoint to a char because we want to limit immunity to the BMP field only. + if ( containsCharacter( (char) codePoint, immune ) && Character.isValidCodePoint(codePoint)) { + return new StringBuilder().appendCodePoint(codePoint).toString(); } // check for alphanumeric characters - String hex = super.getHexForNonAlphanumeric(c); - if ( hex == null ) { - return ""+c; + String hex = super.getHexForNonAlphanumeric(codePoint); + if ( hex == null && Character.isValidCodePoint(codePoint)) { + return new StringBuilder().appendCodePoint(codePoint).toString(); } - // check for illegal characters - if ( ( c <= 0x1f && c != '\t' && c != '\n' && c != '\r' ) || ( c >= 0x7f && c <= 0x9f ) ) + if ( ( codePoint <= 0x1f + && codePoint != '\t' + && codePoint != '\n' + && codePoint != '\r' ) + || ( codePoint >= 0x7f && codePoint <= 0x9f ) ) { hex = REPLACEMENT_HEX; // Let's entity encode this instead of returning it - c = REPLACEMENT_CHAR; + codePoint = REPLACEMENT_CHAR; } // check if there's a defined entity - String entityName = (String) characterToEntityMap.get(Integer.valueOf(c)); + String entityName = (String) characterToEntityMap.get(codePoint); if (entityName != null) { return "&" + entityName + ";"; } @@ -86,42 +108,6 @@ public String encodeCharacter( char[] immune, Character c ) { return "&#x" + hex + ";"; } - /** - * {@inheritDoc} - * - * Encodes a Character for safe use in an HTML entity field. - * @param immune - */ - public String encodeCharacter( char[] immune, int codePoint ) { - - // check for immune characters -// if ( containsCharacter(codePoint, immune ) ) { -// return ""+codePoint; -// } - -// // check for alphanumeric characters - String hex = super.getHexForNonAlphanumeric(codePoint); -// if ( hex == null ) { -// return ""+c; -// } -// -// // check for illegal characters -// if ( ( c <= 0x1f && c != '\t' && c != '\n' && c != '\r' ) || ( c >= 0x7f && c <= 0x9f ) ) -// { -// hex = REPLACEMENT_HEX; // Let's entity encode this instead of returning it -// c = REPLACEMENT_CHAR; -// } -// -// // check if there's a defined entity -// String entityName = (String) characterToEntityMap.get(c); -// if (entityName != null) { -// return "&" + entityName + ";"; -// } - - // return the hex entity as suggested in the spec - return "&#x" + hex + ";"; - } - /** * {@inheritDoc} * diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java index 1456058bd..813330ed6 100644 --- a/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java +++ b/src/test/java/org/owasp/esapi/codecs/AbstractCodecTest.java @@ -137,7 +137,8 @@ public void testWindowsEncode() public void testHtmlEncodeChar() { - assertEquals( "<", htmlCodec.encodeCharacter(EMPTY_CHAR_ARRAY, LESS_THAN) ); + + assertEquals( "<", htmlCodec.encodeCharacter(EMPTY_CHAR_ARRAY, (int) LESS_THAN) ); } public void testHtmlEncodeChar0x100() @@ -146,12 +147,13 @@ public void testHtmlEncodeChar0x100() String inStr = Character.toString(in); String expected = "Ā"; String result; - - result = htmlCodec.encodeCharacter(EMPTY_CHAR_ARRAY, in); - // this should be escaped - assertFalse(inStr.equals(result)); - // UTF-8 encoded and then percent escaped - assertEquals(expected, result); + //The new default for HTMLEntityCodec is ints/Integers. Use Character/char at your own risk! + //Characters destroy non-BMP codepoints. This Codec is now supposed surpass that. + result = htmlCodec.encodeCharacter(EMPTY_CHAR_ARRAY, (int) in); + // this should be escaped + assertFalse(inStr.equals(result)); + // UTF-8 encoded and then percent escaped + assertEquals(expected, result); } public void testHtmlEncodeStr0x100() diff --git a/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java b/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java index bae3732dc..c81031692 100644 --- a/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java +++ b/src/test/java/org/owasp/esapi/codecs/HTMLEntityCodecTest.java @@ -23,4 +23,29 @@ public void test32BitCJK(){ assertEquals(false, expected.equals(bad)); assertEquals(expected, codec.encode(new char[0], s)); } + + @Test + public void test32BitCJKMixedWithBmp(){ + String s = "𡘾𦴩<𥻂"; + String expected = "𡘾𦴩<𥻂"; + String bad = "������"; + assertEquals(false, expected.equals(bad)); + assertEquals(expected, codec.encode(new char[0], s)); + } + + @Test + public void testDecodeforChars(){ + String s = "!@$%()=+{}[]"; + String expected = "!@$%()=+{}[]"; + assertEquals(expected, codec.decode(s)); + } + + @Test + public void testMixedBmpAndNonBmp(){ + String nonBMP = new String(new int[]{0x2f804}, 0, 1); + String bmp = "")); assertEquals("&lt;script&gt;", instance.encodeForHTML("<script>")); assertEquals("!@$%()=+{}[]", instance.encodeForHTML("!@$%()=+{}[]")); - assertEquals("!@$%()=+{}[]", instance.encodeForHTML(instance.canonicalize("!@$%()=+{}[]") ) ); + String canonicalized = instance.canonicalize("!@$%()=+{}[]"); + assertEquals("!@$%()=+{}[]", instance.encodeForHTML( canonicalized ) ); assertEquals(",.-_ ", instance.encodeForHTML(",.-_ ")); assertEquals("dir&", instance.encodeForHTML("dir&")); assertEquals("one&two", instance.encodeForHTML("one&two")); From 3f9d208d8fe4ff5b6a03f78112dec5a6a0a909eb Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Thu, 10 Aug 2017 21:49:16 -0700 Subject: [PATCH 047/709] Issue #300 -- Added documentation warnings in regards to the destructive nature of the codecs on non-UTF data inserted into a string. --- .../java/org/owasp/esapi/codecs/AbstractCodec.java | 12 ++++++++++-- src/main/java/org/owasp/esapi/codecs/Codec.java | 3 ++- .../java/org/owasp/esapi/codecs/HTMLEntityCodec.java | 9 +++++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java index b9c0ac6c7..8660fb7a7 100644 --- a/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/AbstractCodec.java @@ -51,8 +51,16 @@ public AbstractCodec() { } } - /* (non-Javadoc) - * @see org.owasp.esapi.codecs.Codec#encode(char[], java.lang.String) + /** + * WARNING!! {@code Character} based Codecs will silently transform code points that are not + * legal UTF code points into garbage data as they will cast them to {@code char}s. + *

    + * If you are implementing an {@code Integer} based codec, these will be silently discarded + * based on the return from {@code Character.isValidCodePoint( int )}. This is the preferred + * behavior moving forward. + * + * + * {@inheritDoc} */ @Override public String encode(char[] immune, String input) { diff --git a/src/main/java/org/owasp/esapi/codecs/Codec.java b/src/main/java/org/owasp/esapi/codecs/Codec.java index 5e914a4a0..cd0b66f03 100644 --- a/src/main/java/org/owasp/esapi/codecs/Codec.java +++ b/src/main/java/org/owasp/esapi/codecs/Codec.java @@ -45,7 +45,8 @@ public interface Codec { /** * Default implementation that should be overridden in specific codecs. * - * @param immune + * @param immune + * array of chars to NOT encode. Use with caution. * @param c * the Character to encode * @return diff --git a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java index ee7139450..984a02105 100644 --- a/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java +++ b/src/main/java/org/owasp/esapi/codecs/HTMLEntityCodec.java @@ -51,8 +51,13 @@ public HTMLEntityCodec() { } /** - * Overrides the AbstractImpl to keep this code performing entirely at the {@code int} - * level. + * Given an array of {@code char}, scan the input {@code String} and encode unsafe + * codePoints, except for codePoints passed into the {@code char} array. + *

    + * WARNING: This method will silently discard any code point per the + * call to {@code Character.isValidCodePoint( int )} method. + * + * {@inheritDoc} */ @Override public String encode(char[] immune, String input) { From 3ed0a2fa4dfe43c14bbb282c0662ed6d8bbf35bd Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Thu, 10 Aug 2017 22:13:33 -0700 Subject: [PATCH 048/709] Issue #281 -- Updated unit tests with missing assertions. --- .../org/owasp/esapi/reference/SafeFileTest.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/test/java/org/owasp/esapi/reference/SafeFileTest.java b/src/test/java/org/owasp/esapi/reference/SafeFileTest.java index a946bdc7c..f2acd589c 100644 --- a/src/test/java/org/owasp/esapi/reference/SafeFileTest.java +++ b/src/test/java/org/owasp/esapi/reference/SafeFileTest.java @@ -80,21 +80,17 @@ public void testEscapeCharactersInFilename() { } File sf = new File(testDir, "test^.file" ); - if ( sf.exists() ) { - System.out.println( " Injection allowed "+ sf.getAbsolutePath() ); - } else { - System.out.println( " Injection didn't work "+ sf.getAbsolutePath() ); - } + assertFalse("Injection didn't work " + sf.getAbsolutePath(), + sf.exists()); + assertTrue(" Injection allowed " + sf.getAbsolutePath(), sf.exists()); } public void testEscapeCharacterInDirectoryInjection() { System.out.println("testEscapeCharacterInDirectoryInjection"); File sf = new File(testDir, "test\\^.^.\\file"); - if ( sf.exists() ) { - System.out.println( " Injection allowed "+ sf.getAbsolutePath() ); - } else { - System.out.println( " Injection didn't work "+ sf.getAbsolutePath() ); - } + assertFalse(" Injection didn't work " + sf.getAbsolutePath(), + sf.exists()); + assertTrue(" Injection allowed " + sf.getAbsolutePath(), sf.exists()); } public void testJavaFileInjectionGood() throws ValidationException @@ -275,5 +271,4 @@ public void testCreateParentPercentNull() // expected } } - } From 98aaaa06cb7ba37f932c7db26cb967d92695d23f Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Thu, 10 Aug 2017 22:20:00 -0700 Subject: [PATCH 049/709] Issue #278 -- Added default local to date test to avoid non-us unit test failures. --- src/test/java/org/owasp/esapi/reference/ValidatorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java index f5a8fb330..8fb0a2e8a 100644 --- a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java +++ b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java @@ -142,7 +142,7 @@ public void testGetValidDate() throws Exception { // TODO: This test case fails due to an apparent bug in SimpleDateFormat // Note: This seems to be fixed in JDK 6. Will leave it commented out since // we only require JDK 5. -kww - instance.getValidDate("test", "June 32, 2008", DateFormat.getDateInstance(), false, errors); + instance.getValidDate("test", "June 32, 2008", DateFormat.getDateInstance(DateFormat.DEFAULT, Locale.US), false, errors); // assertEquals( 2, errors.size() ); } From c27d5e06cd11034cf2f5529da023b4d2ca43c72a Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Fri, 11 Aug 2017 18:34:04 -0700 Subject: [PATCH 050/709] Issue #281 -- added windows escape char to blacklist in SafeFile to adhere to intended contract. --- src/main/java/org/owasp/esapi/SafeFile.java | 8 ++-- .../owasp/esapi/reference/SafeFileTest.java | 44 ++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/owasp/esapi/SafeFile.java b/src/main/java/org/owasp/esapi/SafeFile.java index 6a4f239c5..7df86bb38 100644 --- a/src/main/java/org/owasp/esapi/SafeFile.java +++ b/src/main/java/org/owasp/esapi/SafeFile.java @@ -32,8 +32,8 @@ public class SafeFile extends File { private static final long serialVersionUID = 1L; private static final Pattern PERCENTS_PAT = Pattern.compile("(%)([0-9a-fA-F])([0-9a-fA-F])"); - private static final Pattern FILE_BLACKLIST_PAT = Pattern.compile("([\\\\/:*?<>|])"); - private static final Pattern DIR_BLACKLIST_PAT = Pattern.compile("([*?<>|])"); + private static final Pattern FILE_BLACKLIST_PAT = Pattern.compile("([\\\\/:*?<>|^])"); + private static final Pattern DIR_BLACKLIST_PAT = Pattern.compile("([*?<>|^])"); public SafeFile(String path) throws ValidationException { super(path); @@ -62,12 +62,12 @@ public SafeFile(URI uri) throws ValidationException { private void doDirCheck(String path) throws ValidationException { Matcher m1 = DIR_BLACKLIST_PAT.matcher( path ); - if ( m1.find() ) { + if ( null != m1 && m1.find() ) { throw new ValidationException( "Invalid directory", "Directory path (" + path + ") contains illegal character: " + m1.group() ); } Matcher m2 = PERCENTS_PAT.matcher( path ); - if ( m2.find() ) { + if (null != m2 && m2.find() ) { throw new ValidationException( "Invalid directory", "Directory path (" + path + ") contains encoded characters: " + m2.group() ); } diff --git a/src/test/java/org/owasp/esapi/reference/SafeFileTest.java b/src/test/java/org/owasp/esapi/reference/SafeFileTest.java index f2acd589c..923b75ec0 100644 --- a/src/test/java/org/owasp/esapi/reference/SafeFileTest.java +++ b/src/test/java/org/owasp/esapi/reference/SafeFileTest.java @@ -16,19 +16,17 @@ package org.owasp.esapi.reference; import java.io.File; -import java.net.URI; -import java.net.URLDecoder; import java.util.Iterator; import java.util.Set; -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - import org.owasp.esapi.SafeFile; import org.owasp.esapi.errors.ValidationException; -import org.owasp.esapi.util.FileTestUtils; import org.owasp.esapi.util.CollectionsUtil; +import org.owasp.esapi.util.FileTestUtils; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; /** * @author Jeff Williams (jeff.williams@aspectsecurity.com) @@ -79,18 +77,21 @@ public void testEscapeCharactersInFilename() { System.out.println( "File is there: " + tf ); } - File sf = new File(testDir, "test^.file" ); - assertFalse("Injection didn't work " + sf.getAbsolutePath(), - sf.exists()); - assertTrue(" Injection allowed " + sf.getAbsolutePath(), sf.exists()); + try { + File sf = new SafeFile(testDir, "test^.file" ); + } catch (ValidationException e) { + assertEquals("Invalid directory", e.getMessage()); + } + } public void testEscapeCharacterInDirectoryInjection() { System.out.println("testEscapeCharacterInDirectoryInjection"); - File sf = new File(testDir, "test\\^.^.\\file"); - assertFalse(" Injection didn't work " + sf.getAbsolutePath(), - sf.exists()); - assertTrue(" Injection allowed " + sf.getAbsolutePath(), sf.exists()); + try { + File sf = new SafeFile(testDir, "test\\^.^.\\file"); + } catch (ValidationException e) { + assertEquals("Invalid directory", e.getMessage()); + } } public void testJavaFileInjectionGood() throws ValidationException @@ -271,4 +272,17 @@ public void testCreateParentPercentNull() // expected } } + + public final void testSafeFileShouldAcceptEmptyPath() throws ValidationException + { + String filename = "hello.txt"; + //API dictates that NPE should be thrown. + try{ + SafeFile file = new SafeFile(filename); + }catch(NullPointerException npe){ + assertNotNull(npe); + } + + + } } From a663a4be2ff75b05e283e1365c8812c71d0570af Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Thu, 17 Aug 2017 17:03:45 -0700 Subject: [PATCH 051/709] Catching up ESAPI.properties prod and test version. --- configuration/esapi/ESAPI.properties | 51 ++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/configuration/esapi/ESAPI.properties b/configuration/esapi/ESAPI.properties index f98cc5a49..82d7fad92 100644 --- a/configuration/esapi/ESAPI.properties +++ b/configuration/esapi/ESAPI.properties @@ -319,8 +319,30 @@ HttpUtilities.ForceHttpOnlySession=false HttpUtilities.ForceSecureSession=false HttpUtilities.ForceHttpOnlyCookies=true HttpUtilities.ForceSecureCookies=true -# Maximum size of HTTP headers -HttpUtilities.MaxHeaderSize=4096 +# Maximum size of HTTP header key--the validator regex may have additional values. +HttpUtilities.MaxHeaderNameSize=256 +# Maximum size of HTTP header value--the validator regex may have additional values. +HttpUtilities.MaxHeaderValueSize=4096 +# Maximum size of JSESSIONID for the application--the validator regex may have additional values. +HttpUtilities.HTTPJSESSIONIDLENGTH=50 +# Maximum length of a URL (see https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers) +HttpUtilities.URILENGTH=2000 +# Maximum length of a redirect +HttpUtilities.maxRedirectLength=512 +# Maximum length for an http scheme +HttpUtilities.HTTPSCHEMELENGTH=10 +# Maximum length for an http host +HttpUtilities.HTTPHOSTLENGTH=100 +# Maximum length for an http path +HttpUtilities.HTTPPATHLENGTH=150 +#Maximum length for a context path +HttpUtilities.contextPathLength=150 +#Maximum length for an httpServletPath +HttpUtilities.HTTPSERVLETPATHLENGTH=100 +#Maximum length for an http query parameter name +HttpUtilities.httpQueryParamNameLength=100 +#Maximum length for an http query parameter -- old default was 2000, but that's the max length for a URL... +HttpUtilities.httpQueryParamValueLength=500 # File upload configuration HttpUtilities.ApprovedUploadExtensions=.zip,.pdf,.doc,.docx,.ppt,.pptx,.tar,.gz,.tgz,.rar,.war,.jar,.ear,.xls,.rtf,.properties,.java,.class,.txt,.xml,.jsp,.jsf,.exe,.dll HttpUtilities.MaxUploadFileBytes=500000000 @@ -331,8 +353,10 @@ HttpUtilities.ResponseContentType=text/html; charset=UTF-8 # This is the name of the cookie used to represent the HTTP session # Typically this will be the default "JSESSIONID" HttpUtilities.HttpSessionIdName=JSESSIONID - - +#Sets whether or not will will overwrite http status codes to 200. +HttpUtilities.OverwriteStatusCodes=true +#Sets the application's base character encoding. This is forked from the Java Encryptor property. +HttpUtilities.CharacterEncoding=UTF-8 #=========================================================================== # ESAPI Executor @@ -441,20 +465,27 @@ Validator.Redirect=^\\/test.*$ # Values with Base64 encoded data (e.g. encrypted state) will need at least [a-zA-Z0-9\/+=] Validator.HTTPScheme=^(http|https)$ Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$ -Validator.HTTPParameterName=^[a-zA-Z0-9_]{1,32}$ -Validator.HTTPParameterValue=^[a-zA-Z0-9.\\-\\/+=@_ ]*$ Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$ Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$ -# Note that max header name capped at 150 in SecurityRequestWrapper! -Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,50}$ +# Note that headerName and Value length is also configured in the HTTPUtilities section +Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,256}$ Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$ -Validator.HTTPContextPath=^\\/?[a-zA-Z0-9.\\-\\/_]*$ Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$ Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$ Validator.HTTPQueryString=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ %]*$ Validator.HTTPURI=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$ Validator.HTTPURL=^.*$ -Validator.HTTPJSESSIONID=^[A-Z0-9]{10,30}$ +Validator.HTTPJSESSIONID=^[A-Z0-9]{10,32}$ + + +# Contributed by Fraenku@gmx.ch +# Googlecode Issue 116 (http://code.google.com/p/owasp-esapi-java/issues/detail?id=116) +Validator.HTTPParameterName=^[a-zA-Z0-9_\\-]{1,32}$ +Validator.HTTPParameterValue=^[\\p{L}\\p{N}.\\-/+=_ !$*?@]{0,1000}$ +Validator.HTTPContextPath=^/[a-zA-Z0-9.\\-_]*$ +Validator.HTTPQueryString=^([a-zA-Z0-9_\\-]{1,32}=[\\p{L}\\p{N}.\\-/+=_ !$*?@%]*&?)*$ +Validator.HTTPURI=^/([a-zA-Z0-9.\\-_]*/?)*$ + # Validation of file related input Validator.FileName=^[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$ From 97366649c5caf81c9671599c400bd70382bbe250 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sat, 19 Aug 2017 15:44:48 -0700 Subject: [PATCH 052/709] Fixing some minor issues of duplication and grammar. --- configuration/esapi/ESAPI.properties | 6 ++---- src/test/resources/esapi/ESAPI.properties | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/configuration/esapi/ESAPI.properties b/configuration/esapi/ESAPI.properties index 82d7fad92..6a7ef055c 100644 --- a/configuration/esapi/ESAPI.properties +++ b/configuration/esapi/ESAPI.properties @@ -353,7 +353,7 @@ HttpUtilities.ResponseContentType=text/html; charset=UTF-8 # This is the name of the cookie used to represent the HTTP session # Typically this will be the default "JSESSIONID" HttpUtilities.HttpSessionIdName=JSESSIONID -#Sets whether or not will will overwrite http status codes to 200. +#Sets whether or not we will overwrite http status codes to 200. HttpUtilities.OverwriteStatusCodes=true #Sets the application's base character encoding. This is forked from the Java Encryptor property. HttpUtilities.CharacterEncoding=UTF-8 @@ -472,14 +472,12 @@ Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,256}$ Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$ Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$ Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$ -Validator.HTTPQueryString=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ %]*$ -Validator.HTTPURI=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$ Validator.HTTPURL=^.*$ Validator.HTTPJSESSIONID=^[A-Z0-9]{10,32}$ # Contributed by Fraenku@gmx.ch -# Googlecode Issue 116 (http://code.google.com/p/owasp-esapi-java/issues/detail?id=116) +# Github Issue 126 https://github.com/ESAPI/esapi-java-legacy/issues/126 Validator.HTTPParameterName=^[a-zA-Z0-9_\\-]{1,32}$ Validator.HTTPParameterValue=^[\\p{L}\\p{N}.\\-/+=_ !$*?@]{0,1000}$ Validator.HTTPContextPath=^/[a-zA-Z0-9.\\-_]*$ diff --git a/src/test/resources/esapi/ESAPI.properties b/src/test/resources/esapi/ESAPI.properties index 9003ff97a..6e184a46e 100644 --- a/src/test/resources/esapi/ESAPI.properties +++ b/src/test/resources/esapi/ESAPI.properties @@ -365,7 +365,7 @@ HttpUtilities.ResponseContentType=text/html; charset=UTF-8 # This is the name of the cookie used to represent the HTTP session # Typically this will be the default "JSESSIONID" HttpUtilities.HttpSessionIdName=JSESSIONID -#Sets whether or not will will overwrite http status codes to 200. +#Sets whether or not we will overwrite http status codes to 200. HttpUtilities.OverwriteStatusCodes=true #Sets the application's base character encoding. This is forked from the Java Encryptor property. HttpUtilities.CharacterEncoding=UTF-8 From 8f531164201d05c3a2acd7f3e1eb43c1f36d7551 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sat, 19 Aug 2017 15:53:55 -0700 Subject: [PATCH 053/709] Cleaned up imports. --- .../java/org/owasp/esapi/reference/DefaultValidator.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/org/owasp/esapi/reference/DefaultValidator.java b/src/main/java/org/owasp/esapi/reference/DefaultValidator.java index 074b44dd1..d2ac1996a 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultValidator.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultValidator.java @@ -20,22 +20,16 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLDecoder; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; -import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.regex.Pattern; From a9849f1421617b5087685daeb7239015bd753930 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sat, 26 Aug 2017 16:13:21 -0700 Subject: [PATCH 054/709] Issue #284 -- Restored original canonicalization behavior due to issue #54 being an invalid fix for a legacy URL issue where query params would get flagged as HTML Entities. --- .../esapi/reference/DefaultValidator.java | 2 - .../validation/StringValidationRule.java | 50 +++---------------- .../owasp/esapi/reference/ValidatorTest.java | 2 +- 3 files changed, 8 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/owasp/esapi/reference/DefaultValidator.java b/src/main/java/org/owasp/esapi/reference/DefaultValidator.java index d2ac1996a..92bef36c6 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultValidator.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultValidator.java @@ -216,7 +216,6 @@ public String getValidInput(String context, String input, String type, int maxLe } rvr.setMaximumLength(maxLength); rvr.setAllowNull(allowNull); - rvr.setValidateInputAndCanonical(canonicalize); return rvr.getValid(context, input); } @@ -343,7 +342,6 @@ public String getValidSafeHTML( String context, String input, int maxLength, boo HTMLValidationRule hvr = new HTMLValidationRule( "safehtml", encoder ); hvr.setMaximumLength(maxLength); hvr.setAllowNull(allowNull); - hvr.setValidateInputAndCanonical(false); return hvr.getValid(context, input); } diff --git a/src/main/java/org/owasp/esapi/reference/validation/StringValidationRule.java b/src/main/java/org/owasp/esapi/reference/validation/StringValidationRule.java index c48286cf5..3163c532d 100644 --- a/src/main/java/org/owasp/esapi/reference/validation/StringValidationRule.java +++ b/src/main/java/org/owasp/esapi/reference/validation/StringValidationRule.java @@ -44,7 +44,6 @@ public class StringValidationRule extends BaseValidationRule { protected List blacklistPatterns = new ArrayList(); protected int minLength = 0; protected int maxLength = Integer.MAX_VALUE; - protected boolean validateInputAndCanonical = true; public StringValidationRule( String typeName ) { super( typeName ); @@ -116,16 +115,6 @@ public void setMaximumLength( int length ) { maxLength = length; } - /** - * Set the flag which determines whether the in input itself is - * checked as well as the canonical form of the input. - * @param flag The value to set - */ - public void setValidateInputAndCanonical(boolean flag) - { - validateInputAndCanonical = flag; - } - /** * checks input against whitelists. * @param context The context to include in exception messages @@ -267,47 +256,22 @@ public String getValid( String context, String input ) throws ValidationExceptio { String data = null; - // checks on input itself - // check for empty/null if(checkEmpty(context, input) == null) return null; - if (validateInputAndCanonical) - { - //first validate pre-canonicalized data - - // check length - checkLength(context, input); - - // check whitelist patterns - checkWhitelist(context, input); - - // check blacklist patterns - checkBlacklist(context, input); - - // canonicalize - data = encoder.canonicalize( input ); - - } else { - - //skip canonicalization - data = input; - } - - // check for empty/null - if(checkEmpty(context, data, input) == null) - return null; - // check length - checkLength(context, data, input); + checkLength(context, input); + + // canonicalize + data = encoder.canonicalize( input ); // check whitelist patterns - checkWhitelist(context, data, input); + checkWhitelist(context, input); // check blacklist patterns - checkBlacklist(context, data, input); - + checkBlacklist(context, input); + // validation passed return data; } diff --git a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java index 8fb0a2e8a..bcef424b4 100644 --- a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java +++ b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java @@ -232,7 +232,7 @@ public void testGetValidFileName() throws Exception { assertEquals("Percent encoding is not changed", testName, instance.getValidFileName("test", testName, ESAPI.securityConfiguration().getAllowedFileExtensions(), false, errors)); } - public void testGetValidInput() { + public void testGetValidInput() throws Exception { System.out.println("getValidInput"); Validator instance = ESAPI.validator(); ValidationErrorList errors = new ValidationErrorList(); From 66745aef119a661f35eb8ef9cbae2aa514926f59 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sat, 26 Aug 2017 16:15:42 -0700 Subject: [PATCH 055/709] Issue #284 -- Fixed code no longer needed for testing. --- src/test/java/org/owasp/esapi/reference/ValidatorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java index bcef424b4..6222ebeb2 100644 --- a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java +++ b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java @@ -232,7 +232,7 @@ public void testGetValidFileName() throws Exception { assertEquals("Percent encoding is not changed", testName, instance.getValidFileName("test", testName, ESAPI.securityConfiguration().getAllowedFileExtensions(), false, errors)); } - public void testGetValidInput() throws Exception { + public void testGetValidInput(){ System.out.println("getValidInput"); Validator instance = ESAPI.validator(); ValidationErrorList errors = new ValidationErrorList(); From f01719ad13ad19e7f6bb7e88e2d384f44dd32ef3 Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sat, 26 Aug 2017 18:10:32 -0700 Subject: [PATCH 056/709] Adding mocking frameworks for testing. --- pom.xml | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index 25b4cebff..ddf78c307 100644 --- a/pom.xml +++ b/pom.xml @@ -88,13 +88,6 @@ Project Inventor - - Chris Schmidt - Aspect Security - - Project Co-owner - - Kevin W. Wall Wells Fargo @@ -102,6 +95,13 @@ Project Co-owner + + Matt Seil + OWASP + + Project Co-owner + + @@ -119,6 +119,12 @@ + + + joda-time + joda-time + 2.9.9 + commons-configuration commons-configuration @@ -224,29 +230,29 @@ 1.7.0 test - - - - - + @@ -529,7 +535,7 @@
    - + org.apache.maven.plugins maven-jar-plugin @@ -561,7 +567,7 @@ - + From a47d8e70ec57e362ed58225fd97b12cb5b7cf2da Mon Sep 17 00:00:00 2001 From: NiklasMehner Date: Tue, 7 Nov 2017 03:52:51 +0100 Subject: [PATCH 057/709] Fix configuration loading: Use value instead of constants also update vulnerable dependencies (#426) * Fix configuration loading: Use value instead of constants * Update dependencies with vulnerabilities Close #426 --- pom.xml | 10 +++++----- .../esapi/reference/DefaultSecurityConfiguration.java | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index ddf78c307..44db96788 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,7 @@ commons-fileupload commons-fileupload - 1.3.2 + 1.3.3 compile @@ -185,14 +185,14 @@ 1.2.10 - org.beanshell - bsh-core - 2.0b4 + org.apache-extras.beanshell + bsh + 2.0b6 org.owasp.antisamy antisamy - 1.5.6 + 1.5.7 + -Xmaxwarns + 2000 + + + -Xlint:all,-deprecation,-rawtypes,-unchecked + + @@ -330,7 +362,7 @@ org.owasp dependency-check-maven - 1.4.4 + 2.1.0 5.9 ./suppressions.xml @@ -370,7 +402,7 @@ maven-pmd-plugin 3.6 - 1.5 + 1.7 utf-8 @@ -464,6 +496,7 @@ org.apache.maven.plugins maven-javadoc-plugin + 3.0.0-M1 -Xdoclint:none @@ -568,7 +601,7 @@ - org.apache.maven.plugins @@ -583,10 +616,10 @@ - 1.6 + 1.7 - ESAPI 2.1 uses the JDK1.6 for it's baseline. Please make sure that your - JAVA_HOME environment variable is pointed to a JDK1.6 distribution. + ESAPI 2.x now uses the JDK1.7 for it's baseline. Please make sure that your + JAVA_HOME environment variable is pointed to a JDK1.7 distribution. From f3cdc694e56f5bb5c248dc6294ec174df5bd6e20 Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Tue, 26 Dec 2017 11:37:14 -0500 Subject: [PATCH 061/709] Update README.md file to fix minor typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c5ccd2c0..f6f3e4721 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ In mid-2014 ESAPI Migrated all code to GitHub. This migration was completed in N ### What about the issues still located on Google Code? All issues from Google Code have been migrated to GitHub issues. We have a JIRA/Confluence instance allocated to us, but it has not be configured to synchronize with the GitHub issues, and thus is should not be used. JIRA is fine, but if we can't have it synchronized with GitHub issues (which is where the majority of our users report issues), it is not usuable. As developers, we do not want to spent time having to close issues from multiple bug-tracking sites. Therefore, until this synchronization happens (see GitHub issue #371), please ONLY use GitHub for reporting bugs. -When reporting an issue, please be clear and try to ensure that the ESAPI development team has sufficient information to be able to reproduce your results. If you have not already done son, this might be a good time to read Eric S. Raymond's classic "How to Ask Questions the Smart Way", at http://www.catb.org/esr/faqs/smart-questions.html before posting your issue. +When reporting an issue, please be clear and try to ensure that the ESAPI development team has sufficient information to be able to reproduce your results. If you have not already done so, this might be a good time to read Eric S. Raymond's classic "How to Ask Questions the Smart Way", at http://www.catb.org/esr/faqs/smart-questions.html before posting your issue. ### Find an Issue? If you have found a bug, then create an issue on the esapi-legacy-java repo: https://github.com/ESAPI/esapi-java-legacy/issues From 15b5b767f16cb590c5a6aba273a33da15bbb9730 Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Sat, 30 Dec 2017 15:05:04 -0500 Subject: [PATCH 062/709] Update README.md to refer to GitHub's suggested begiiner label for issues. Previously we were using the label 'FirstBug' to reflect issues appropriate for beginners to work on. That label was created prior to GitHub suggesting the use of 'help wanted' or 'good first issue' for that label. I have created the 'good first use' label (with the same color as suggested by GitHub) and replaced all the (now obsolete) 'FirstBug' labels with the label 'good first use' and then deleted the custom 'FirstBug' label. Updating this README.md file is the file step. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6f3e4721..27168a156 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ https://github.com/ESAPI/esapi-java ## How can I contribute or help with fix bugs? Fork and submit a pull request! Simple as pi! We generally only accept bug fixes, not new features because as a legacy project, we don't intend on adding new features, although we may make exceptions. If you wish to propose a new feature, the best place to discuss it is via the ESAPI-DEV mailing list mentioned below. Note that we vet all pull requests, including coding style of any contributions; use the same coding style found in the files you are already editing. -If you are new to ESAPI, a good place to start is to look for GitHub issues labled as 'FirstBug'. (E.g., https://github.com/ESAPI/esapi-java-legacy/labels/FirstBug) +If you are new to ESAPI, a good place to start is to look for GitHub issues labled as 'good first issue'. (E.g., to find all open issues with that label, use https://github.com/ESAPI/esapi-java-legacy/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22.) ### What happened to Google code? In mid-2014 ESAPI Migrated all code to GitHub. This migration was completed in November 2014. From b712af5a543e8428a44a363c96a4b0ee8d2e300b Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Fri, 5 Jan 2018 13:31:05 -0600 Subject: [PATCH 063/709] GITHUB #135 Updating SecurityWrapperRequest.getQueryString to return the original encoded string to the caller IFF that string can be converted into a 'safe' string by the ESAPI Validator instance/configuration. --- .../org/owasp/esapi/filters/SecurityWrapperRequest.java | 7 ++++++- .../java/org/owasp/esapi/filters/SafeRequestTest.java | 8 +++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java index 095069918..72f9f9bdc 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java @@ -513,7 +513,12 @@ public String getQueryString() { } catch (ValidationException e) { // already logged } - return clean; + /* GITHUB #135 + * as long as the original query can be cleaned then we assume it's safe. + * Returning the decoded 'clean' value changes how the string is interpreted, + * so we need to return the original query value. + */ + return clean == null || clean.isEmpty() ? clean : query; } /** diff --git a/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java b/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java index e4d55950d..e758fe02f 100644 --- a/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java +++ b/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java @@ -121,7 +121,7 @@ public void testGetQueryStringPercent() req.setQueryString("a=%62"); wrappedReq = new SecurityWrapperRequest(req); - assertEquals("a=b",wrappedReq.getQueryString()); + assertEquals("a=%62",wrappedReq.getQueryString()); } public void testGetQueryStringPercentNUL() @@ -131,11 +131,9 @@ public void testGetQueryStringPercentNUL() req.setQueryString("a=%00"); wrappedReq = new SecurityWrapperRequest(req); - assertEquals("",wrappedReq.getQueryString()); + assertEquals("a=%00", wrappedReq.getQueryString()); } - /* these tests need to be enabled&changed based on the decisions - * made regarding issue 125. Currently they fail. public void testGetQueryStringPercentEquals() { MockHttpServletRequest req = new MockHttpServletRequest(); @@ -155,7 +153,7 @@ public void testGetQueryStringPercentAmpersand() wrappedReq = new SecurityWrapperRequest(req); assertEquals("a=%26b",wrappedReq.getQueryString()); } - */ + // Test to ensure null-value contract defined by ServletRequest.getParameterNames(String) is met. public void testGetParameterValuesReturnsNullWhenParameterDoesNotExistInRequest() { From 2c199e5e84ebee32e1147440c8ef9b72ecf8e198 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Sat, 20 Jan 2018 09:19:24 -0600 Subject: [PATCH 064/709] Canonicalize SecurityWrapperRequest.getQueryString Reverts last change which removed canonicalization from the return value. Tests being updated to reflect the expected behavior, which includes both canonicalization as well as test-scoped whitelist functionality from validation. --- .../org/owasp/esapi/filters/SecurityWrapperRequest.java | 7 +------ .../java/org/owasp/esapi/filters/SafeRequestTest.java | 8 ++++---- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java index 72f9f9bdc..095069918 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java @@ -513,12 +513,7 @@ public String getQueryString() { } catch (ValidationException e) { // already logged } - /* GITHUB #135 - * as long as the original query can be cleaned then we assume it's safe. - * Returning the decoded 'clean' value changes how the string is interpreted, - * so we need to return the original query value. - */ - return clean == null || clean.isEmpty() ? clean : query; + return clean; } /** diff --git a/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java b/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java index e758fe02f..e404f8c46 100644 --- a/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java +++ b/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java @@ -121,7 +121,7 @@ public void testGetQueryStringPercent() req.setQueryString("a=%62"); wrappedReq = new SecurityWrapperRequest(req); - assertEquals("a=%62",wrappedReq.getQueryString()); + assertEquals("a=b",wrappedReq.getQueryString()); } public void testGetQueryStringPercentNUL() @@ -131,7 +131,7 @@ public void testGetQueryStringPercentNUL() req.setQueryString("a=%00"); wrappedReq = new SecurityWrapperRequest(req); - assertEquals("a=%00", wrappedReq.getQueryString()); + assertEquals("a="+Character.MIN_VALUE, wrappedReq.getQueryString()); } public void testGetQueryStringPercentEquals() @@ -141,7 +141,7 @@ public void testGetQueryStringPercentEquals() req.setQueryString("a=%3d"); wrappedReq = new SecurityWrapperRequest(req); - assertEquals("a=%3d",wrappedReq.getQueryString()); + assertEquals("a==",wrappedReq.getQueryString()); } public void testGetQueryStringPercentAmpersand() @@ -151,7 +151,7 @@ public void testGetQueryStringPercentAmpersand() req.setQueryString("a=%26b"); wrappedReq = new SecurityWrapperRequest(req); - assertEquals("a=%26b",wrappedReq.getQueryString()); + assertEquals("a=&b",wrappedReq.getQueryString()); } From f006b5308eb54e2ba316a4b55e24cc6430fdcfed Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Sat, 20 Jan 2018 09:26:35 -0600 Subject: [PATCH 065/709] SecurityWrapperRequest Workflow Test Using PowerMock to assert the happy-path and exception cases when requesting a QueryString reference. This approach removes the test environment from the unit and focuses on the behavior of the class in isolation. --- .../filters/SecurityWrapperRequestTest.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java diff --git a/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java b/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java new file mode 100644 index 000000000..3200700c1 --- /dev/null +++ b/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java @@ -0,0 +1,108 @@ +package org.owasp.esapi.filters; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.owasp.esapi.ESAPI; +import org.owasp.esapi.SecurityConfiguration; +import org.owasp.esapi.Validator; +import org.owasp.esapi.errors.IntrusionException; +import org.owasp.esapi.errors.ValidationException; +import org.owasp.esapi.filters.SecurityWrapperRequest; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. + * Why do people care this exists? + * + * @author Jeremiah + * @since Jan 3, 2018 + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest(ESAPI.class) +public class SecurityWrapperRequestTest { + + @Mock + private HttpServletRequest mockRequest; + @Mock + private Validator mockValidator; + @Mock + private SecurityConfiguration mockSecConfig; + + @Before + public void setup() throws Exception { + PowerMockito.mockStatic(ESAPI.class); + PowerMockito.when(ESAPI.class, "validator").thenReturn(mockValidator); + PowerMockito.when(ESAPI.class, "securityConfiguration").thenReturn(mockSecConfig); + } + + @Test + public void testGetQueryString() throws IntrusionException, ValidationException { + String queryString = "queryString"; + int maxLength = 255; + + ArgumentCaptor inputCapture = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCapture = ArgumentCaptor.forClass(String.class); + ArgumentCaptor lenghtCapture = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor allowNullCapture = ArgumentCaptor.forClass(Boolean.class); + + PowerMockito.when(mockValidator.getValidInput(Matchers.anyString(), inputCapture.capture(), typeCapture + .capture(), lenghtCapture.capture(), allowNullCapture.capture())).thenReturn("canonicalized"); + PowerMockito.when(mockSecConfig.getIntProp("HttpUtilities.URILENGTH")).thenReturn(maxLength); + PowerMockito.when(mockRequest.getQueryString()).thenReturn(queryString); + + SecurityWrapperRequest request = new SecurityWrapperRequest(mockRequest); + String rval = request.getQueryString(); + Assert.assertEquals("canonicalized", rval); + + Assert.assertEquals(queryString, inputCapture.getValue()); + Assert.assertEquals("HTTPQueryString", typeCapture.getValue()); + Assert.assertTrue(maxLength == lenghtCapture.getValue().intValue()); + Assert.assertEquals(true, allowNullCapture.getValue()); + + Mockito.verify(mockValidator, Mockito.times(1)).getValidInput(Matchers.anyString(), Matchers.anyString(), + Matchers.anyString(), Matchers.anyInt(), Matchers.anyBoolean()); + Mockito.verify(mockSecConfig, Mockito.times(1)).getIntProp("HttpUtilities.URILENGTH"); + Mockito.verify(mockRequest, Mockito.times(1)).getQueryString(); + } + + @SuppressWarnings("unchecked") + @Test + public void testGetQueryStringCanonicalizeException() throws IntrusionException, ValidationException { + String queryString = "queryString"; + int maxLength = 255; + + ArgumentCaptor inputCapture = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCapture = ArgumentCaptor.forClass(String.class); + ArgumentCaptor lenghtCapture = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor allowNullCapture = ArgumentCaptor.forClass(Boolean.class); + + PowerMockito.when(mockValidator.getValidInput(Matchers.anyString(), inputCapture.capture(), typeCapture + .capture(), lenghtCapture.capture(), allowNullCapture.capture())).thenThrow(ValidationException.class); + PowerMockito.when(mockSecConfig.getIntProp("HttpUtilities.URILENGTH")).thenReturn(maxLength); + PowerMockito.when(mockRequest.getQueryString()).thenReturn(queryString); + + SecurityWrapperRequest request = new SecurityWrapperRequest(mockRequest); + String rval = request.getQueryString(); + Assert.assertEquals("", rval); + + Assert.assertEquals(queryString, inputCapture.getValue()); + Assert.assertEquals("HTTPQueryString", typeCapture.getValue()); + Assert.assertTrue(maxLength == lenghtCapture.getValue().intValue()); + Assert.assertEquals(true, allowNullCapture.getValue()); + + Mockito.verify(mockValidator, Mockito.times(1)).getValidInput(Matchers.anyString(), Matchers.anyString(), + Matchers.anyString(), Matchers.anyInt(), Matchers.anyBoolean()); + Mockito.verify(mockSecConfig, Mockito.times(1)).getIntProp("HttpUtilities.URILENGTH"); + Mockito.verify(mockRequest, Mockito.times(1)).getQueryString(); + } +} From 5f91df0a6a454bc5df04e2e64918ebba59f4512a Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Sat, 20 Jan 2018 09:29:03 -0600 Subject: [PATCH 066/709] Whitelist Regex Validation Tests Providing a structure to assert that the enviroment configurations will provide expected responses when passed controlled values. Performing a preliminary whitelist test set on HttpQueryString regex from ESAPI.properties. The test implementation asserts that the regex being tested matches the test environment's regex for a given property. If the regex strings differ, the tests associated with that property will be skipped to prevent false-positives from being provided to a client. --- .../esapi/reference/AbstractPatternTest.java | 49 ++++++++++ ...EsapiWhitelistValidationPatternTester.java | 91 +++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java create mode 100644 src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java diff --git a/src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java b/src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java new file mode 100644 index 000000000..3fd72ebaf --- /dev/null +++ b/src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java @@ -0,0 +1,49 @@ +package org.owasp.esapi.reference; + +import java.util.regex.Pattern; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + + +/** + * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. + * Why do people care this exists? + * @author Jeremiah + * @since Jan 20, 2018 + * + */ +@RunWith (Parameterized.class) +public abstract class AbstractPatternTest { + + protected static class PatternTestTuple { + String input; + String regex; + boolean shouldMatch; + String description; + /** {@inheritDoc}*/ + @Override + public String toString() { + return description != null ? description : regex; + } + } + + private String input; + private Pattern pattern; + private boolean shouldMatch; + + + public AbstractPatternTest(PatternTestTuple tuple) { + this.input = tuple.input; + this.pattern = Pattern.compile(tuple.regex); + this.shouldMatch = tuple.shouldMatch; + } + + @Test + public void checkPatternMatches() { + Assert.assertEquals(shouldMatch, pattern.matcher(input).matches()); + } + +} diff --git a/src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java b/src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java new file mode 100644 index 000000000..4a1bd5ffb --- /dev/null +++ b/src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java @@ -0,0 +1,91 @@ +package org.owasp.esapi.reference; + +import java.util.ArrayList; +import java.util.Collection; + +import org.junit.Assume; +import org.junit.runners.Parameterized.Parameters; +import org.owasp.esapi.reference.DefaultSecurityConfiguration; + +/** + * Extension of the AbstractPatternTest which focuses on asserting that the default whitelist regex values applied in + * the validation process are performing the intended function in the environment. + * + *
    + * + * If the regex values in this test are found to not match the running environment configurations, then the tests will + * be skipped. + * + * @author Jeremiah + * @since Jan 20, 2018 + */ +public class EsapiWhitelistValidationPatternTester extends AbstractPatternTest { + //See ESAPI.properties + private static final String HTTP_QUERY_STRING_PROP_NAME="HTTPQueryString"; + private static final String HTTP_QUERY_STRING_REGEX="^([a-zA-Z0-9_\\-]{1,32}=[\\p{L}\\p{N}.\\-/+=_ !$*?@%]*&?)*$"; + + @Parameters(name = "{0}-{1}") + public static Collection createDefaultPatternTests() { + Collection parameters = new ArrayList<>(); + + for(PatternTestTuple tuple : buildHttpQueryStringTests()) { + parameters.add(new Object[] { HTTP_QUERY_STRING_PROP_NAME, tuple }); + } + + + return parameters; + } + + private static Collection buildHttpQueryStringTests() { + Collection httpQueryStringTests = new ArrayList<>(); + + //MATCHING CASES + PatternTestTuple tuple = newHttpQueryStringTuple("Default Case", "b", true); + httpQueryStringTests.add(tuple); + tuple = newHttpQueryStringTuple("Percent Encoded Value", "%62", true); + httpQueryStringTests.add(tuple); + tuple = newHttpQueryStringTuple("Percent Encoded Null Character", "%00", true); + httpQueryStringTests.add(tuple); + tuple = newHttpQueryStringTuple("Double Equals", "=", true); + httpQueryStringTests.add(tuple); + + //NON-MATCHING CASES + tuple = newHttpQueryStringTuple("Ampersand In Value", "&b", false); + httpQueryStringTests.add(tuple); + tuple = newHttpQueryStringTuple("Null Character", ""+Character.MIN_VALUE, false); + httpQueryStringTests.add(tuple); + tuple = newHttpQueryStringTuple("Encoded Null Character", "\u0000", false); + httpQueryStringTests.add(tuple); + + return httpQueryStringTests; + } + + private static PatternTestTuple newHttpQueryStringTuple(String description, String value, boolean shouldPass) { + PatternTestTuple tuple = new PatternTestTuple(); + tuple.input = "a="+value; + tuple.shouldMatch = shouldPass; + tuple.regex = HTTP_QUERY_STRING_REGEX; + tuple.description = description; + return tuple; + } + + public EsapiWhitelistValidationPatternTester(String property, PatternTestTuple tuple) { + super(tuple); + /* + * This next block causes the case to be skipped programatically if the regex being tested + * is different than the one being loaded at runtime. + * This is being done to prevent a false sense of security. + * If the configurations are changed to meet additional environmental concerns, the intent of this test should + * be copied into that environment and tested there to assert the additional expectations or changes in desired + * behavior. + */ + DefaultSecurityConfiguration configuration = new DefaultSecurityConfiguration(); + Assume.assumeTrue( + "The regular expression specified does not match the configuration settings.\n" + + "If the value was changed from the ESAPI default, it is recommended to copy " + + "this class into your project, update the regex being tested, and update all " + + "associated input expectations for your unique environment.", + configuration.getValidationPattern(property).toString().equals(tuple.regex)); + } + +} From f5a190ff8a709bf712783cac12280c4f786c2624 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Sat, 20 Jan 2018 09:34:08 -0600 Subject: [PATCH 067/709] PercentCodec String/character encode/decode Set of tests for PercentCodec which tests the encode and decode features using both String and Character api calls. Initial test data sets are derived from 'AbstractCodecTest', 'SafeRequestTest', as well as the interal immune character set from PercentCodec implementation. --- .../codecs/AbstractCodecCharacterTest.java | 72 ++++++++++++ .../esapi/codecs/AbstractCodecStringTest.java | 61 ++++++++++ .../codecs/PercentCodecCharacterTest.java | 108 ++++++++++++++++++ .../esapi/codecs/PercentCodecStringTest.java | 85 ++++++++++++++ 4 files changed, 326 insertions(+) create mode 100644 src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java create mode 100644 src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java create mode 100644 src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java create mode 100644 src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java new file mode 100644 index 000000000..f6b708a67 --- /dev/null +++ b/src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java @@ -0,0 +1,72 @@ +package org.owasp.esapi.codecs; + +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.owasp.esapi.codecs.Codec; +import org.owasp.esapi.codecs.PushbackString; + + +/** + * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. + * Why do people care this exists? + * @author Jeremiah + * @since Jan 20, 2018 + * + */ +@RunWith(Parameterized.class) +public abstract class AbstractCodecCharacterTest { + + protected static class CodecCharacterTestTuple { + Codec codec; + char[] encodeImmune; + String input; + Character decodedValue; + String description; + /** {@inheritDoc}*/ + + @Override + public String toString() { + return description != null ? description : codec.getClass().getSimpleName() + " "+input; + } + } + + + protected final Codec codec; + protected final String input; + protected final char[] encodeImmune; + protected final Character decodedValue; + + public AbstractCodecCharacterTest(CodecCharacterTestTuple tuple) { + this.codec = tuple.codec; + this.input = tuple.input; + this.decodedValue = tuple.decodedValue; + this.encodeImmune = tuple.encodeImmune; + } + + @Test + public void testEncodeCharacter() { + Assert.assertEquals(input, codec.encodeCharacter(encodeImmune, decodedValue)); + } + + @Test + public void testEncode() { + String expected = Arrays.asList(encodeImmune).contains(decodedValue) ? decodedValue.toString() : input; + Assert.assertEquals(expected, codec.encode(encodeImmune, decodedValue.toString())); + } + + @Test + public void testDecode() { + Assert.assertEquals(decodedValue.toString(), codec.decode(input)); + } + + + @Test + public void testDecodePushbackSequence() { + Assert.assertEquals(decodedValue, codec.decodeCharacter(new PushbackString(input))); + } + +} diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java new file mode 100644 index 000000000..27d0ea12e --- /dev/null +++ b/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java @@ -0,0 +1,61 @@ +package org.owasp.esapi.codecs; + +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.owasp.esapi.codecs.Codec; + + +/** + * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. + * Why do people care this exists? + * @author Jeremiah + * @since Jan 20, 2018 + * + */ +@RunWith(Parameterized.class) +public abstract class AbstractCodecStringTest { + + protected static class CodecStringTestTuple { + Codec codec; + char[] encodeImmune; + String input; + String decodedValue; + String description; + /** {@inheritDoc}*/ + + @Override + public String toString() { + return description != null ? description : codec.getClass().getSimpleName() + " "+input; + } + } + + + private final Codec codec; + private final String input; + private final char[] encodeImmune; + private final String decodedValue; + + public AbstractCodecStringTest(CodecStringTestTuple tuple) { + this.codec = tuple.codec; + this.input = tuple.input; + this.decodedValue = tuple.decodedValue; + this.encodeImmune = tuple.encodeImmune; + } + + @Test + public void testDecode() { + Assert.assertEquals(decodedValue, codec.decode(input)); + } + + + @Test + public void testEncode() { + String expected = Arrays.asList(encodeImmune).contains(decodedValue) ? decodedValue : input; + Assert.assertEquals(expected, codec.encode(encodeImmune, decodedValue)); + } + +} diff --git a/src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java new file mode 100644 index 000000000..9617ccb9c --- /dev/null +++ b/src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java @@ -0,0 +1,108 @@ +package org.owasp.esapi.codecs; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameters; +import org.owasp.esapi.codecs.PercentCodec; +import org.owasp.esapi.codecs.PushbackString; + +/** + * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. + * Why do people care this exists? + * @author Jeremiah + * @since Jan 20, 2018 + * + */ +public class PercentCodecCharacterTest extends AbstractCodecCharacterTest { + private static final char[] PERCENT_CODEC_IMMUNE; + + static { + /* + * The percent codec contains a unique immune character set which include letters and numbers that will not be transformed. + * + * It is being replicated here to allow the test to reasonably expect the correct state back. + */ + List immune = new ArrayList<>(); + // 65 - 90 (capital letters) 97 - 122 lower case 48 - 57 digits + //numbers + for (int index = 48 ; index < 58; index ++) { + immune.add((char)index); + } + //letters + for (int index = 65 ; index < 91; index ++) { + Character capsChar = (char)index; + immune.add(capsChar); + immune.add(Character.toLowerCase(capsChar)); + } + + PERCENT_CODEC_IMMUNE = new char[immune.size()]; + for (int index = 0; index < immune.size(); index++) { + PERCENT_CODEC_IMMUNE[index] = immune.get(index).charValue(); + } + } + + @Parameters(name="{0}") + public static Collection buildTests() { + Collection tests = new ArrayList<>(); + + Collection tuples = new ArrayList<>(); + tuples.add(newTuple("%3C",Character.valueOf('<'))); + + tuples.add(newTuple("%C4%80",Character.valueOf((char)0x100))); + tuples.add(newTuple("%00",Character.MIN_VALUE)); + tuples.add(newTuple("%3D",'=')); + tuples.add(newTuple("%26",'&')); + + for (char c : PERCENT_CODEC_IMMUNE) { + tuples.add(newTuple(Character.toString(c), c)); + } + + for (CodecCharacterTestTuple tuple : tuples) { + tests.add(new Object[]{tuple}); + } + + return tests; + } + + + + private static CodecCharacterTestTuple newTuple(String encodedInput, Character decoded) { + CodecCharacterTestTuple tuple = new CodecCharacterTestTuple(); + tuple.codec = new PercentCodec(); + tuple.encodeImmune = PERCENT_CODEC_IMMUNE; + tuple.decodedValue = decoded; + tuple.input = encodedInput; + + return tuple; + } + /** + * @param tuple + */ + public PercentCodecCharacterTest(CodecCharacterTestTuple tuple) { + super(tuple); + } + + + @Override + @Test + public void testDecodePushbackSequence() { + //If the input is not decoded, then null should be returned and the pushback string index should be unchanged. + //If the input is decode then the decoded value should be returned, and the pushback string index should have progressed forward. + + PushbackString pbs = new PushbackString(input); + int startIndex = pbs.index(); + boolean shouldDecode = input.startsWith("%"); + if (shouldDecode) { + Assert.assertEquals(decodedValue, codec.decodeCharacter(pbs)); + Assert.assertTrue(startIndex < pbs.index()); + } else { + Assert.assertEquals(null, codec.decodeCharacter(pbs)); + Assert.assertEquals(startIndex, pbs.index()); + } + } + +} diff --git a/src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java new file mode 100644 index 000000000..91ca18b87 --- /dev/null +++ b/src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java @@ -0,0 +1,85 @@ +package org.owasp.esapi.codecs; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.runners.Parameterized.Parameters; +import org.owasp.esapi.codecs.PercentCodec; + +/** + * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. + * Why do people care this exists? + * + * @author Jeremiah + * @since Jan 20, 2018 + */ +public class PercentCodecStringTest extends AbstractCodecStringTest { + private static final char[] PERCENT_CODEC_IMMUNE; + + static { + /* + * The percent codec contains a unique immune character set which include letters and numbers that will not be transformed. + * + * It is being replicated here to allow the test to reasonably expect the correct state back. + */ + List immune = new ArrayList(); + // 65 - 90 (capital letters) 97 - 122 lower case 48 - 57 digits + //numbers + for (int index = 48 ; index < 58; index ++) { + immune.add((char)index); + } + //letters + for (int index = 65 ; index < 91; index ++) { + Character capsChar = (char)index; + immune.add(capsChar); + immune.add(Character.toLowerCase(capsChar)); + } + + PERCENT_CODEC_IMMUNE = new char[immune.size()]; + for (int index = 0; index < immune.size(); index++) { + PERCENT_CODEC_IMMUNE[index] = immune.get(index).charValue(); + } + } + + @Parameters(name = "{0}") + public static Collection buildTests() { + Collection tests = new ArrayList<>(); + + List tuples = new ArrayList<>(); + tuples.add(newTuple("%3C", "<")); + + tuples.add(newTuple("%C4%80", (char) 0x100)); + tuples.add(newTuple("%00", Character.MIN_VALUE)); + tuples.add(newTuple("%3D", '=')); + tuples.add(newTuple("%26", '&')); + + for (char c : PERCENT_CODEC_IMMUNE) { + tuples.add(newTuple(Character.toString(c), c)); + } + + for (CodecStringTestTuple tuple : tuples) { + tests.add(new Object[] { tuple }); + } + + return tests; + } + + private static CodecStringTestTuple newTuple(String input, Object decoded) { + CodecStringTestTuple tuple = new CodecStringTestTuple(); + tuple.codec = new PercentCodec(); + tuple.encodeImmune = PERCENT_CODEC_IMMUNE; + tuple.decodedValue = decoded.toString(); + tuple.input = input; + + return tuple; + } + + /** + * @param tuple + */ + public PercentCodecStringTest(CodecStringTestTuple tuple) { + super(tuple); + } + +} From 6f60045cdd20e7b32cb2814734e58eb2c23e8ffe Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Sat, 20 Jan 2018 09:38:11 -0600 Subject: [PATCH 068/709] Removing unstable tests The tests removed from SafeRequestTest were attempting to validate environment state as well as the unit functionality. The intent of these tests has been distributed to a workflow test check (SecurityWrapperRequestTest), a whitelist validation test (EsapiWhitelistValidationPatternTester), and tests for the PercentCodec's impact on input data (PercentCodecStringTest, PercentCodecCharacterTest). The combination of these tests assert that the pieces that were composing the removed content will function under more explict and controlled conditions. Changes to configuration will nChanges to configuration will now be revealed closer to the component that is directly impacted. --- .../owasp/esapi/filters/SafeRequestTest.java | 61 ------------------- 1 file changed, 61 deletions(-) diff --git a/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java b/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java index e404f8c46..db2f0d770 100644 --- a/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java +++ b/src/test/java/org/owasp/esapi/filters/SafeRequestTest.java @@ -94,67 +94,6 @@ public void testGetQueryStringNull() assertNull(wrappedReq.getQueryString()); } - public void testGetQueryStringNonNull() - { - MockHttpServletRequest req = new MockHttpServletRequest(); - SecurityWrapperRequest wrappedReq; - - req.setQueryString("a=b"); - wrappedReq = new SecurityWrapperRequest(req); - assertEquals("a=b",wrappedReq.getQueryString()); - } - - public void testGetQueryStringNUL() - { - MockHttpServletRequest req = new MockHttpServletRequest(); - SecurityWrapperRequest wrappedReq; - - req.setQueryString("a=\u0000"); - wrappedReq = new SecurityWrapperRequest(req); - assertEquals("",wrappedReq.getQueryString()); - } - - public void testGetQueryStringPercent() - { - MockHttpServletRequest req = new MockHttpServletRequest(); - SecurityWrapperRequest wrappedReq; - - req.setQueryString("a=%62"); - wrappedReq = new SecurityWrapperRequest(req); - assertEquals("a=b",wrappedReq.getQueryString()); - } - - public void testGetQueryStringPercentNUL() - { - MockHttpServletRequest req = new MockHttpServletRequest(); - SecurityWrapperRequest wrappedReq; - - req.setQueryString("a=%00"); - wrappedReq = new SecurityWrapperRequest(req); - assertEquals("a="+Character.MIN_VALUE, wrappedReq.getQueryString()); - } - - public void testGetQueryStringPercentEquals() - { - MockHttpServletRequest req = new MockHttpServletRequest(); - SecurityWrapperRequest wrappedReq; - - req.setQueryString("a=%3d"); - wrappedReq = new SecurityWrapperRequest(req); - assertEquals("a==",wrappedReq.getQueryString()); - } - - public void testGetQueryStringPercentAmpersand() - { - MockHttpServletRequest req = new MockHttpServletRequest(); - SecurityWrapperRequest wrappedReq; - - req.setQueryString("a=%26b"); - wrappedReq = new SecurityWrapperRequest(req); - assertEquals("a=&b",wrappedReq.getQueryString()); - } - - // Test to ensure null-value contract defined by ServletRequest.getParameterNames(String) is met. public void testGetParameterValuesReturnsNullWhenParameterDoesNotExistInRequest() { MockHttpServletRequest request = new MockHttpServletRequest(); From 9d3d93a39d5192bac7835317f76a1cd1a26c921c Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Mon, 22 Jan 2018 08:33:48 -0600 Subject: [PATCH 069/709] Updating SecurityWrapperRequestTest Test cleanup. Extracting constants & static imports. Adding documentation. Fixing spelling errors. --- .../filters/SecurityWrapperRequestTest.java | 137 ++++++++++++------ 1 file changed, 92 insertions(+), 45 deletions(-) diff --git a/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java b/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java index 3200700c1..bf1a2d627 100644 --- a/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java +++ b/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java @@ -1,35 +1,48 @@ package org.owasp.esapi.filters; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import javax.servlet.http.HttpServletRequest; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Matchers; import org.mockito.Mock; -import org.mockito.Mockito; import org.owasp.esapi.ESAPI; import org.owasp.esapi.SecurityConfiguration; import org.owasp.esapi.Validator; import org.owasp.esapi.errors.IntrusionException; import org.owasp.esapi.errors.ValidationException; -import org.owasp.esapi.filters.SecurityWrapperRequest; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; /** - * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. - * Why do people care this exists? - * - * @author Jeremiah - * @since Jan 3, 2018 + * Unit tests for {@link SecurityWrapperRequest}. + *
    + * This test uses static context mocking! This can affect certain behaviors if it is executed in a JVM container with + * other tests depending on the same static reference - Which is going to be everything. + *
    + * This may affect some test environments, mostly IDE's. It is not expected that this impacts the Maven build, as + * surefire plugin isolates JVM's for tests during that phase. */ @RunWith(PowerMockRunner.class) @PrepareForTest(ESAPI.class) public class SecurityWrapperRequestTest { + private static final String ESAPI_VALIDATOR_GETTER_METHOD_NAME = "validator"; + private static final String ESAPY_SECURITY_CONFIGURATION_GETTER_METHOD_NAME = "securityConfiguration"; + private static final String SECURITY_CONFIGURATION_LENGTH_KEY_NAME = "HttpUtilities.URILENGTH"; + + private static final int SECURITY_CONFIGURATION_MOCK_LENGTH = 255; + + private static final String QUERY_STRING_CANONCALIZE_TYPE_KEY = "HTTPQueryString"; @Mock private HttpServletRequest mockRequest; @@ -41,68 +54,102 @@ public class SecurityWrapperRequestTest { @Before public void setup() throws Exception { PowerMockito.mockStatic(ESAPI.class); - PowerMockito.when(ESAPI.class, "validator").thenReturn(mockValidator); - PowerMockito.when(ESAPI.class, "securityConfiguration").thenReturn(mockSecConfig); + PowerMockito.when(ESAPI.class, ESAPI_VALIDATOR_GETTER_METHOD_NAME).thenReturn(mockValidator); + PowerMockito.when(ESAPI.class, ESAPY_SECURITY_CONFIGURATION_GETTER_METHOD_NAME).thenReturn(mockSecConfig); } - + + /** + * Workflow test for happy-path getQueryString. Asserts delegation calls and parameters to delegate + * behaviors. + */ @Test - public void testGetQueryString() throws IntrusionException, ValidationException { - String queryString = "queryString"; - int maxLength = 255; + public void testGetQueryString() throws Exception { + String originalQuery = "queryString"; + String canonicalizedResponse = "canonicalized_query"; ArgumentCaptor inputCapture = ArgumentCaptor.forClass(String.class); ArgumentCaptor typeCapture = ArgumentCaptor.forClass(String.class); - ArgumentCaptor lenghtCapture = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor lengthCapture = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor allowNullCapture = ArgumentCaptor.forClass(Boolean.class); - PowerMockito.when(mockValidator.getValidInput(Matchers.anyString(), inputCapture.capture(), typeCapture - .capture(), lenghtCapture.capture(), allowNullCapture.capture())).thenReturn("canonicalized"); - PowerMockito.when(mockSecConfig.getIntProp("HttpUtilities.URILENGTH")).thenReturn(maxLength); - PowerMockito.when(mockRequest.getQueryString()).thenReturn(queryString); + String context = anyString(); + String input = inputCapture.capture(); + String type = typeCapture.capture(); + Integer length = lengthCapture.capture(); + Boolean allowNull = allowNullCapture.capture(); + + PowerMockito.when(mockValidator.getValidInput(context, input, type, length, allowNull)).thenReturn( + canonicalizedResponse); + PowerMockito.when(mockSecConfig.getIntProp(SECURITY_CONFIGURATION_LENGTH_KEY_NAME)).thenReturn( + SECURITY_CONFIGURATION_MOCK_LENGTH); + + PowerMockito.when(mockRequest.getQueryString()).thenReturn(originalQuery); SecurityWrapperRequest request = new SecurityWrapperRequest(mockRequest); String rval = request.getQueryString(); - Assert.assertEquals("canonicalized", rval); + assertEquals(canonicalizedResponse, rval); - Assert.assertEquals(queryString, inputCapture.getValue()); - Assert.assertEquals("HTTPQueryString", typeCapture.getValue()); - Assert.assertTrue(maxLength == lenghtCapture.getValue().intValue()); - Assert.assertEquals(true, allowNullCapture.getValue()); + String actualInput = inputCapture.getValue(); + String actualType = typeCapture.getValue(); + int actualLength = lengthCapture.getValue().intValue(); + boolean actualAllowNull = allowNullCapture.getValue().booleanValue(); - Mockito.verify(mockValidator, Mockito.times(1)).getValidInput(Matchers.anyString(), Matchers.anyString(), - Matchers.anyString(), Matchers.anyInt(), Matchers.anyBoolean()); - Mockito.verify(mockSecConfig, Mockito.times(1)).getIntProp("HttpUtilities.URILENGTH"); - Mockito.verify(mockRequest, Mockito.times(1)).getQueryString(); + assertEquals(originalQuery, actualInput); + assertEquals(QUERY_STRING_CANONCALIZE_TYPE_KEY, actualType); + assertTrue(SECURITY_CONFIGURATION_MOCK_LENGTH == actualLength); + assertTrue(actualAllowNull); + + verify(mockValidator, times(1)).getValidInput(anyString(), anyString(), anyString(), anyInt(), anyBoolean()); + verify(mockSecConfig, times(1)).getIntProp(SECURITY_CONFIGURATION_LENGTH_KEY_NAME); + verify(mockRequest, times(1)).getQueryString(); } + /** + * Test for getQueryString when validation throws an Exception. + *
    + * Asserts delegation calls and parameters to delegate behaviors. + */ @SuppressWarnings("unchecked") @Test public void testGetQueryStringCanonicalizeException() throws IntrusionException, ValidationException { - String queryString = "queryString"; - int maxLength = 255; + String originalQuery = "queryString"; ArgumentCaptor inputCapture = ArgumentCaptor.forClass(String.class); ArgumentCaptor typeCapture = ArgumentCaptor.forClass(String.class); - ArgumentCaptor lenghtCapture = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor lengthCapture = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor allowNullCapture = ArgumentCaptor.forClass(Boolean.class); - PowerMockito.when(mockValidator.getValidInput(Matchers.anyString(), inputCapture.capture(), typeCapture - .capture(), lenghtCapture.capture(), allowNullCapture.capture())).thenThrow(ValidationException.class); - PowerMockito.when(mockSecConfig.getIntProp("HttpUtilities.URILENGTH")).thenReturn(maxLength); - PowerMockito.when(mockRequest.getQueryString()).thenReturn(queryString); + String context = anyString(); + String input = inputCapture.capture(); + String type = typeCapture.capture(); + Integer length = lengthCapture.capture(); + Boolean allowNull = allowNullCapture.capture(); + + PowerMockito.when(mockValidator.getValidInput(context, input, type, length, allowNull)).thenThrow( + ValidationException.class); + PowerMockito.when(mockSecConfig.getIntProp(SECURITY_CONFIGURATION_LENGTH_KEY_NAME)).thenReturn( + SECURITY_CONFIGURATION_MOCK_LENGTH); + + PowerMockito.when(mockRequest.getQueryString()).thenReturn(originalQuery); SecurityWrapperRequest request = new SecurityWrapperRequest(mockRequest); String rval = request.getQueryString(); - Assert.assertEquals("", rval); - Assert.assertEquals(queryString, inputCapture.getValue()); - Assert.assertEquals("HTTPQueryString", typeCapture.getValue()); - Assert.assertTrue(maxLength == lenghtCapture.getValue().intValue()); - Assert.assertEquals(true, allowNullCapture.getValue()); + assertTrue("SecurityWrapperRequest should return an empty String when an exception occurs in validation", rval + .isEmpty()); + + String actualInput = inputCapture.getValue(); + String actualType = typeCapture.getValue(); + int actualLength = lengthCapture.getValue().intValue(); + boolean actualAllowNull = allowNullCapture.getValue().booleanValue(); + + assertEquals(originalQuery, actualInput); + assertEquals(QUERY_STRING_CANONCALIZE_TYPE_KEY, actualType); + assertTrue(SECURITY_CONFIGURATION_MOCK_LENGTH == actualLength); + assertTrue(actualAllowNull); - Mockito.verify(mockValidator, Mockito.times(1)).getValidInput(Matchers.anyString(), Matchers.anyString(), - Matchers.anyString(), Matchers.anyInt(), Matchers.anyBoolean()); - Mockito.verify(mockSecConfig, Mockito.times(1)).getIntProp("HttpUtilities.URILENGTH"); - Mockito.verify(mockRequest, Mockito.times(1)).getQueryString(); + verify(mockValidator, times(1)).getValidInput(anyString(), anyString(), anyString(), anyInt(), anyBoolean()); + verify(mockSecConfig, times(1)).getIntProp(SECURITY_CONFIGURATION_LENGTH_KEY_NAME); + verify(mockRequest, times(1)).getQueryString(); } } From c3870f660439e39a22a14ca013c8da17aa59eb66 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Mon, 22 Jan 2018 08:47:34 -0600 Subject: [PATCH 070/709] Pattern Validation Test Cleanup Adding documentation to the AbstractPatternTest to help with usability. Extracting message constant to a class var in EsapiWhitelistValidaitonPatternTests for improved readability. --- .../esapi/reference/AbstractPatternTest.java | 35 ++++++++------ ...EsapiWhitelistValidationPatternTester.java | 46 +++++++++---------- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java b/src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java index 3fd72ebaf..9cf1e0e20 100644 --- a/src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java +++ b/src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java @@ -7,43 +7,50 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; - /** - * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. - * Why do people care this exists? - * @author Jeremiah - * @since Jan 20, 2018 - * + * Abstract parameterized test case meant to assist with verifying regular expressions in test scope. + *
    + * Sub-classes are expected to provide instances of {@link PatternTestTuple} to this instance. + *
    + * For better test naming output specify {@link PatternTestTuple#description} and use {@code} @Parameters (name="{0}")}, + * where '0' is the index that the PatternTestTuple reference appears in the constructor. */ -@RunWith (Parameterized.class) +@RunWith(Parameterized.class) public abstract class AbstractPatternTest { - + + /** + * Test tuple for Pattern validation. + */ protected static class PatternTestTuple { + /** String value to be tested against the compiled regex reference. */ String input; + /** Regular expression string that will be compiled and be passed the input. */ String regex; + /** Test Expectation whether input should match the compiled regex. */ boolean shouldMatch; + /** Optional field to override the toString value of this tuple. */ String description; - /** {@inheritDoc}*/ + + /** {@inheritDoc} */ @Override public String toString() { - return description != null ? description : regex; + return description != null ? description : regex; } } private String input; private Pattern pattern; private boolean shouldMatch; - - + public AbstractPatternTest(PatternTestTuple tuple) { this.input = tuple.input; this.pattern = Pattern.compile(tuple.regex); this.shouldMatch = tuple.shouldMatch; } - + @Test public void checkPatternMatches() { Assert.assertEquals(shouldMatch, pattern.matcher(input).matches()); } - + } diff --git a/src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java b/src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java index 4a1bd5ffb..d00d4c19a 100644 --- a/src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java +++ b/src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java @@ -10,9 +10,7 @@ /** * Extension of the AbstractPatternTest which focuses on asserting that the default whitelist regex values applied in * the validation process are performing the intended function in the environment. - * *
    - * * If the regex values in this test are found to not match the running environment configurations, then the tests will * be skipped. * @@ -20,26 +18,30 @@ * @since Jan 20, 2018 */ public class EsapiWhitelistValidationPatternTester extends AbstractPatternTest { - //See ESAPI.properties - private static final String HTTP_QUERY_STRING_PROP_NAME="HTTPQueryString"; - private static final String HTTP_QUERY_STRING_REGEX="^([a-zA-Z0-9_\\-]{1,32}=[\\p{L}\\p{N}.\\-/+=_ !$*?@%]*&?)*$"; + // See ESAPI.properties + private static final String HTTP_QUERY_STRING_PROP_NAME = "HTTPQueryString"; + private static final String HTTP_QUERY_STRING_REGEX = "^([a-zA-Z0-9_\\-]{1,32}=[\\p{L}\\p{N}.\\-/+=_ !$*?@%]*&?)*$"; + + private static final String CONFIGURATION_PATTERN_MISMATCH_MESSAGE = "The regular expression specified does not match the configuration settings.\n" + + "If the value was changed from the ESAPI default, it is recommended to copy " + + "this class into your project, update the regex being tested, and update all " + + "associated input expectations for your unique environment."; @Parameters(name = "{0}-{1}") public static Collection createDefaultPatternTests() { Collection parameters = new ArrayList<>(); - - for(PatternTestTuple tuple : buildHttpQueryStringTests()) { + + for (PatternTestTuple tuple : buildHttpQueryStringTests()) { parameters.add(new Object[] { HTTP_QUERY_STRING_PROP_NAME, tuple }); } - return parameters; } - + private static Collection buildHttpQueryStringTests() { - Collection httpQueryStringTests = new ArrayList<>(); - - //MATCHING CASES + Collection httpQueryStringTests = new ArrayList<>(); + + // MATCHING CASES PatternTestTuple tuple = newHttpQueryStringTuple("Default Case", "b", true); httpQueryStringTests.add(tuple); tuple = newHttpQueryStringTuple("Percent Encoded Value", "%62", true); @@ -48,27 +50,27 @@ private static Collection buildHttpQueryStringTests() { httpQueryStringTests.add(tuple); tuple = newHttpQueryStringTuple("Double Equals", "=", true); httpQueryStringTests.add(tuple); - - //NON-MATCHING CASES + + // NON-MATCHING CASES tuple = newHttpQueryStringTuple("Ampersand In Value", "&b", false); httpQueryStringTests.add(tuple); - tuple = newHttpQueryStringTuple("Null Character", ""+Character.MIN_VALUE, false); + tuple = newHttpQueryStringTuple("Null Character", "" + Character.MIN_VALUE, false); httpQueryStringTests.add(tuple); tuple = newHttpQueryStringTuple("Encoded Null Character", "\u0000", false); httpQueryStringTests.add(tuple); - + return httpQueryStringTests; } private static PatternTestTuple newHttpQueryStringTuple(String description, String value, boolean shouldPass) { PatternTestTuple tuple = new PatternTestTuple(); - tuple.input = "a="+value; + tuple.input = "a=" + value; tuple.shouldMatch = shouldPass; tuple.regex = HTTP_QUERY_STRING_REGEX; tuple.description = description; return tuple; } - + public EsapiWhitelistValidationPatternTester(String property, PatternTestTuple tuple) { super(tuple); /* @@ -80,12 +82,8 @@ public EsapiWhitelistValidationPatternTester(String property, PatternTestTuple t * behavior. */ DefaultSecurityConfiguration configuration = new DefaultSecurityConfiguration(); - Assume.assumeTrue( - "The regular expression specified does not match the configuration settings.\n" - + "If the value was changed from the ESAPI default, it is recommended to copy " - + "this class into your project, update the regex being tested, and update all " - + "associated input expectations for your unique environment.", - configuration.getValidationPattern(property).toString().equals(tuple.regex)); + Assume.assumeTrue(CONFIGURATION_PATTERN_MISMATCH_MESSAGE, configuration.getValidationPattern(property) + .toString().equals(tuple.regex)); } } From c4d77b142edc14d22fcba6b6d7f8ffc7f0221394 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Mon, 22 Jan 2018 09:30:24 -0600 Subject: [PATCH 071/709] PercentCodecCharacterTest cleanup Splitting up the PushbackSequence test for better readability. Adding documentation. Whitespace cleanup. --- .../codecs/PercentCodecCharacterTest.java | 105 ++++++++++-------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java index 9617ccb9c..ca7ddf1b6 100644 --- a/src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java +++ b/src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java @@ -11,98 +11,109 @@ import org.owasp.esapi.codecs.PushbackString; /** - * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. - * Why do people care this exists? - * @author Jeremiah - * @since Jan 20, 2018 - * + * Codec validation focused on the PercentCodec Character-based api. + * */ public class PercentCodecCharacterTest extends AbstractCodecCharacterTest { private static final char[] PERCENT_CODEC_IMMUNE; static { /* - * The percent codec contains a unique immune character set which include letters and numbers that will not be transformed. - * + * The percent codec contains a unique immune character set which include letters and numbers that will not be + * transformed. * It is being replicated here to allow the test to reasonably expect the correct state back. */ List immune = new ArrayList<>(); // 65 - 90 (capital letters) 97 - 122 lower case 48 - 57 digits - //numbers - for (int index = 48 ; index < 58; index ++) { - immune.add((char)index); + // numbers + for (int index = 48; index < 58; index++) { + immune.add((char) index); } - //letters - for (int index = 65 ; index < 91; index ++) { - Character capsChar = (char)index; + // letters + for (int index = 65; index < 91; index++) { + Character capsChar = (char) index; immune.add(capsChar); - immune.add(Character.toLowerCase(capsChar)); + immune.add(Character.toLowerCase(capsChar)); } - + PERCENT_CODEC_IMMUNE = new char[immune.size()]; for (int index = 0; index < immune.size(); index++) { PERCENT_CODEC_IMMUNE[index] = immune.get(index).charValue(); } } - @Parameters(name="{0}") + @Parameters(name = "{0}") public static Collection buildTests() { Collection tests = new ArrayList<>(); - + Collection tuples = new ArrayList<>(); - tuples.add(newTuple("%3C",Character.valueOf('<'))); - - tuples.add(newTuple("%C4%80",Character.valueOf((char)0x100))); - tuples.add(newTuple("%00",Character.MIN_VALUE)); - tuples.add(newTuple("%3D",'=')); - tuples.add(newTuple("%26",'&')); - + tuples.add(newTuple("%3C", Character.valueOf('<'))); + + tuples.add(newTuple("%C4%80", Character.valueOf((char) 0x100))); + tuples.add(newTuple("%00", Character.MIN_VALUE)); + tuples.add(newTuple("%3D", '=')); + tuples.add(newTuple("%26", '&')); + for (char c : PERCENT_CODEC_IMMUNE) { tuples.add(newTuple(Character.toString(c), c)); } - + for (CodecCharacterTestTuple tuple : tuples) { - tests.add(new Object[]{tuple}); + tests.add(new Object[] { tuple }); } - + return tests; } - - - + private static CodecCharacterTestTuple newTuple(String encodedInput, Character decoded) { CodecCharacterTestTuple tuple = new CodecCharacterTestTuple(); tuple.codec = new PercentCodec(); tuple.encodeImmune = PERCENT_CODEC_IMMUNE; tuple.decodedValue = decoded; tuple.input = encodedInput; - + return tuple; } - /** - * @param tuple - */ + public PercentCodecCharacterTest(CodecCharacterTestTuple tuple) { super(tuple); } - - + @Override @Test public void testDecodePushbackSequence() { - //If the input is not decoded, then null should be returned and the pushback string index should be unchanged. - //If the input is decode then the decoded value should be returned, and the pushback string index should have progressed forward. - - PushbackString pbs = new PushbackString(input); - int startIndex = pbs.index(); - boolean shouldDecode = input.startsWith("%"); - if (shouldDecode) { - Assert.assertEquals(decodedValue, codec.decodeCharacter(pbs)); - Assert.assertTrue(startIndex < pbs.index()); + // check duplicated from PushbackSequence handling in PercentCodec. + boolean inputIsEncoded = input.startsWith("%"); + + if (inputIsEncoded) { + assertInputIsDecodedToValue(); } else { - Assert.assertEquals(null, codec.decodeCharacter(pbs)); - Assert.assertEquals(startIndex, pbs.index()); + assertInputIsDecodedToNull(); } } + /** + * tests that when Input is decoded through a PushbackString that the decodedValue reference is returned and that + * the PushbackString index has incremented. + */ + @SuppressWarnings("unchecked") + private void assertInputIsDecodedToValue() { + PushbackString pbs = new PushbackString(input); + int startIndex = pbs.index(); + Assert.assertEquals(decodedValue, codec.decodeCharacter(pbs)); + Assert.assertTrue(startIndex < pbs.index()); + } + + /** + * tests that when Input is decoded through a PushbackString that null is returned and that the PushbackString index + * remains unchanged. + */ + @SuppressWarnings("unchecked") + private void assertInputIsDecodedToNull() { + PushbackString pbs = new PushbackString(input); + int startIndex = pbs.index(); + Assert.assertEquals(null, codec.decodeCharacter(pbs)); + Assert.assertEquals(startIndex, pbs.index()); + } + } From d44275761b28169fcff1ed9a61450a68855b7a9f Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Mon, 22 Jan 2018 09:50:51 -0600 Subject: [PATCH 072/709] AbstractCodecCharacterTest Cleanup Adding documentation. Updating static imports. --- .../codecs/AbstractCodecCharacterTest.java | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java index f6b708a67..1101be47f 100644 --- a/src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java +++ b/src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java @@ -1,30 +1,36 @@ package org.owasp.esapi.codecs; +import static org.junit.Assert.assertEquals; + import java.util.Arrays; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import org.owasp.esapi.codecs.Codec; -import org.owasp.esapi.codecs.PushbackString; /** - * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. - * Why do people care this exists? - * @author Jeremiah - * @since Jan 20, 2018 - * + * Abstract parameterized test case meant to assist with verifying Character api of a Codec implementation. + *
    + * Sub-classes are expected to provide instances of {@link CodecCharacterTestTuple} to this instance. + *
    + * For better test naming output specify {@link CodecCharacterTestTuple#description} and use {@code} @Parameters (name="{0}")}, + * where '0' is the index that the CodecCharacterTestTuple reference appears in the constructor. */ @RunWith(Parameterized.class) public abstract class AbstractCodecCharacterTest { + /** Test Data Tuple.*/ protected static class CodecCharacterTestTuple { + /** Codec reference to be tested.*/ Codec codec; + /** Set of characters that should be considered 'immune' from decoding processes.*/ char[] encodeImmune; + /** A String representing a single encoded character.*/ String input; + /** The single character that input represents.*/ Character decodedValue; + /** Optional field to override the toString value of this tuple. */ String description; /** {@inheritDoc}*/ @@ -46,27 +52,34 @@ public AbstractCodecCharacterTest(CodecCharacterTestTuple tuple) { this.decodedValue = tuple.decodedValue; this.encodeImmune = tuple.encodeImmune; } - + + /** Checks that the input value matches the result of the codec encoding the decoded value. */ @Test public void testEncodeCharacter() { - Assert.assertEquals(input, codec.encodeCharacter(encodeImmune, decodedValue)); + assertEquals(input, codec.encodeCharacter(encodeImmune, decodedValue)); } + /** Checks encoding the character as a String. + *
    + * If the decoded value is in the immunity list, the the decoded value should be returned from the encode call. + * Otherwise, input is expected as the return. + */ @Test public void testEncode() { String expected = Arrays.asList(encodeImmune).contains(decodedValue) ? decodedValue.toString() : input; - Assert.assertEquals(expected, codec.encode(encodeImmune, decodedValue.toString())); + assertEquals(expected, codec.encode(encodeImmune, decodedValue.toString())); } + /** Checks that decoding the input value yeilds the decodedValue.*/ @Test public void testDecode() { - Assert.assertEquals(decodedValue.toString(), codec.decode(input)); + assertEquals(decodedValue.toString(), codec.decode(input)); } - + /** Checks that the encoded input String is correctly decoded to the single decodedValue character reference.*/ @Test public void testDecodePushbackSequence() { - Assert.assertEquals(decodedValue, codec.decodeCharacter(new PushbackString(input))); + assertEquals(decodedValue, codec.decodeCharacter(new PushbackString(input))); } } From dc20f53efbf026b9470ecc0092c25cbc7243b9f8 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Mon, 22 Jan 2018 09:58:25 -0600 Subject: [PATCH 073/709] AbstractCodecStringTest Cleanup Adding documentation. Removed a copy/pasta check from testEncode (originally from AbstractCodecCharacterTest). --- .../esapi/codecs/AbstractCodecStringTest.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java index 27d0ea12e..eb31da763 100644 --- a/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java @@ -7,23 +7,30 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.owasp.esapi.codecs.Codec; +import org.owasp.esapi.codecs.AbstractCodecCharacterTest.CodecCharacterTestTuple; /** - * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. - * Why do people care this exists? - * @author Jeremiah - * @since Jan 20, 2018 - * + * Abstract parameterized test case meant to assist with verifying String api of a Codec implementation. + *
    + * Sub-classes are expected to provide instances of {@link CodecStringTestTuple} to this instance. + *
    + * For better test naming output specify {@link CodecStringTestTuple#description} and use {@code} @Parameters (name="{0}")}, + * where '0' is the index that the CodecStringTestTuple reference appears in the constructor. */ @RunWith(Parameterized.class) public abstract class AbstractCodecStringTest { protected static class CodecStringTestTuple { + /** Codec reference to be tested.*/ Codec codec; + /** Set of characters that should be considered 'immune' from decoding processes.*/ char[] encodeImmune; + /** A String representing a contextually encoded String.*/ String input; + /** The decoded representation of the input value.*/ String decodedValue; + /** Optional field to override the toString value of this tuple. */ String description; /** {@inheritDoc}*/ @@ -32,8 +39,6 @@ public String toString() { return description != null ? description : codec.getClass().getSimpleName() + " "+input; } } - - private final Codec codec; private final String input; private final char[] encodeImmune; @@ -46,16 +51,17 @@ public AbstractCodecStringTest(CodecStringTestTuple tuple) { this.encodeImmune = tuple.encodeImmune; } + + /** Checks that when the input is decoded using the specified codec, that the return matches the expected decoded value.*/ @Test public void testDecode() { Assert.assertEquals(decodedValue, codec.decode(input)); } - + /** Checks that when the decoded value is encoded (using immunity), that the return matches the provided input.*/ @Test public void testEncode() { - String expected = Arrays.asList(encodeImmune).contains(decodedValue) ? decodedValue : input; - Assert.assertEquals(expected, codec.encode(encodeImmune, decodedValue)); + Assert.assertEquals(input, codec.encode(encodeImmune, decodedValue)); } } From eb42bc0598099ae19b0896add592740aa718a38b Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Mon, 22 Jan 2018 10:01:39 -0600 Subject: [PATCH 074/709] Round2: AbstractCodecStringTest cleanup Correcting imports that snuck in while I was working on documentation updates. --- .../java/org/owasp/esapi/codecs/AbstractCodecStringTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java index eb31da763..b3fe53156 100644 --- a/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java @@ -1,13 +1,9 @@ package org.owasp.esapi.codecs; -import java.util.Arrays; - import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import org.owasp.esapi.codecs.Codec; -import org.owasp.esapi.codecs.AbstractCodecCharacterTest.CodecCharacterTestTuple; /** From 674dee017337b89aa1a28a9c67b5004f538ba4ab Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Mon, 22 Jan 2018 10:02:52 -0600 Subject: [PATCH 075/709] PercentCodecStringTest cleanup Adding documentation. Fixing a java generics declaration warning. --- .../org/owasp/esapi/codecs/PercentCodecStringTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java index 91ca18b87..a851ebcae 100644 --- a/src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java @@ -8,11 +8,8 @@ import org.owasp.esapi.codecs.PercentCodec; /** - * FIXME: Document intent of class. General Function, purpose of creation, intended feature, etc. - * Why do people care this exists? - * - * @author Jeremiah - * @since Jan 20, 2018 + * Codec validation focused on the PercentCodec String-based api. + * */ public class PercentCodecStringTest extends AbstractCodecStringTest { private static final char[] PERCENT_CODEC_IMMUNE; @@ -23,7 +20,7 @@ public class PercentCodecStringTest extends AbstractCodecStringTest { * * It is being replicated here to allow the test to reasonably expect the correct state back. */ - List immune = new ArrayList(); + List immune = new ArrayList<>(); // 65 - 90 (capital letters) 97 - 122 lower case 48 - 57 digits //numbers for (int index = 48 ; index < 58; index ++) { From 93bf6928bb36ac4ac947d2b42c727c5513371b19 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Mon, 22 Jan 2018 14:06:55 -0600 Subject: [PATCH 076/709] Introducing Codec CodePoint abstraction Adding ignored test structure to share implementation thought. Has failing tests presently, probably due to incorrect test structure and/or expectations. Working to resolve. --- .../codecs/AbstractCodecCodePointTest.java | 78 +++++++++++ .../codecs/PercentCodecCodePointTest.java | 121 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 src/test/java/org/owasp/esapi/codecs/AbstractCodecCodePointTest.java create mode 100644 src/test/java/org/owasp/esapi/codecs/PercentCodecCodePointTest.java diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecCodePointTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecCodePointTest.java new file mode 100644 index 000000000..7d79d55bc --- /dev/null +++ b/src/test/java/org/owasp/esapi/codecs/AbstractCodecCodePointTest.java @@ -0,0 +1,78 @@ +package org.owasp.esapi.codecs; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + + +/** + * Abstract parameterized test case meant to assist with verifying Character api of a Codec implementation. + *
    + * Sub-classes are expected to provide instances of {@link CodecCodePointTestTuple} to this instance. + *
    + * For better test naming output specify {@link CodecCodePointTestTuple#description} and use {@code} @Parameters (name="{0}")}, + * where '0' is the index that the CodecCodePointTestTuple reference appears in the constructor. + */ +@RunWith(Parameterized.class) +public abstract class AbstractCodecCodePointTest { + + /** Test Data Tuple.*/ + protected static class CodecCodePointTestTuple { + /** Codec reference to be tested.*/ + Codec codec; + /** Set of characters that should be considered 'immune' from decoding processes.*/ + char[] encodeImmune; + /** A String representing a single encoded character.*/ + String input; + /** The int code point that input represents.*/ + int codePoint; + /** Optional field to override the toString value of this tuple. */ + String description; + /** {@inheritDoc}*/ + + @Override + public String toString() { + return description != null ? description : codec.getClass().getSimpleName() + " "+input; + } + } + + + protected final Codec codec; + protected final String input; + protected final char[] encodeImmune; + protected final int decodedValue; + protected final char codePointChar; + + public AbstractCodecCodePointTest(CodecCodePointTestTuple tuple) { + this.codec = tuple.codec; + this.input = tuple.input; + this.decodedValue = tuple.codePoint; + this.encodeImmune = tuple.encodeImmune; + this.codePointChar = (char) tuple.codePoint; + } + + /** Checks that the input value matches the result of the codec encoding the decoded value. */ + @Test + public void testEncodeCharacter() { + assertEquals(input, codec.encodeCharacter(encodeImmune, decodedValue)); + } + + /** Checks that decoding the input value yeilds the same code point decodedValue.*/ + @Test + public void testDecode() { + int expectedLength = Character.toString(codePointChar).length(); + String actualDecode = codec.decode(input); + assertTrue("CodePoint test input should decode to a String consisting of a single character: " + actualDecode + " " + actualDecode.length(),actualDecode.length() == expectedLength); + assertEquals(decodedValue, (int)actualDecode.charAt(0)); + } + + /** Checks that the encoded input String is correctly decoded to the single decodedValue character reference.*/ + @Test + public void testDecodePushbackSequence() { + assertEquals(decodedValue, codec.decodeCharacter(new PushbackString(input))); + } + +} diff --git a/src/test/java/org/owasp/esapi/codecs/PercentCodecCodePointTest.java b/src/test/java/org/owasp/esapi/codecs/PercentCodecCodePointTest.java new file mode 100644 index 000000000..d44d16097 --- /dev/null +++ b/src/test/java/org/owasp/esapi/codecs/PercentCodecCodePointTest.java @@ -0,0 +1,121 @@ +package org.owasp.esapi.codecs; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameters; + +/** + * Codec validation focused on the PercentCodec codepoint-based api. + * + */ +@Ignore(value="Implementation pending") +public class PercentCodecCodePointTest extends AbstractCodecCodePointTest { + private static final char[] PERCENT_CODEC_IMMUNE; + + static { + /* + * The percent codec contains a unique immune character set which include letters and numbers that will not be + * transformed. + * It is being replicated here to allow the test to reasonably expect the correct state back. + */ + List immune = new ArrayList<>(); + // 65 - 90 (capital letters) 97 - 122 lower case 48 - 57 digits + // numbers + for (int index = 48; index < 58; index++) { + immune.add((char) index); + } + // letters + for (int index = 65; index < 91; index++) { + Character capsChar = (char) index; + immune.add(capsChar); + immune.add(Character.toLowerCase(capsChar)); + } + + PERCENT_CODEC_IMMUNE = new char[immune.size()]; + for (int index = 0; index < immune.size(); index++) { + PERCENT_CODEC_IMMUNE[index] = immune.get(index).charValue(); + } + } + + @Parameters(name = "{0}") + public static Collection buildTests() { + Collection tests = new ArrayList<>(); + + Collection tuples = new ArrayList<>(); + tuples.add(newTuple("%3C", Character.valueOf('<'))); + + tuples.add(newTuple("%C4%80", Character.valueOf((char) 0x100))); + tuples.add(newTuple("%00", Character.MIN_VALUE)); + tuples.add(newTuple("%3D", '=')); + tuples.add(newTuple("%26", '&')); + + for (char c : PERCENT_CODEC_IMMUNE) { + tuples.add(newTuple(Character.toString(c), c)); + } + + for (CodecCodePointTestTuple tuple : tuples) { + tests.add(new Object[] { tuple }); + } + + return tests; + } + + private static CodecCodePointTestTuple newTuple(String encodedInput, Character decoded) { + CodecCodePointTestTuple tuple = new CodecCodePointTestTuple(); + tuple.codec = new PercentCodec(); + tuple.encodeImmune = PERCENT_CODEC_IMMUNE; + tuple.codePoint = decoded.charValue(); + tuple.input = encodedInput; + + return tuple; + } + + public PercentCodecCodePointTest(CodecCodePointTestTuple tuple) { + super(tuple); + } + + @Override + @Test + public void testDecodePushbackSequence() { + // check duplicated from PushbackSequence handling in PercentCodec. + boolean inputIsEncoded = input.startsWith("%"); + + if (inputIsEncoded) { + assertInputIsDecodedToValue(); + } else { + assertInputIsDecodedToNull(); + } + } + + /** + * tests that when Input is decoded through a PushbackString that the decodedValue reference is returned and that + * the PushbackString index has incremented. + */ + @SuppressWarnings("unchecked") + private void assertInputIsDecodedToValue() { + PushbackString pbs = new PushbackString(input); + int startIndex = pbs.index(); + Character decChar = (Character) codec.decodeCharacter(pbs); + char actual = decChar.charValue(); + Assert.assertEquals(String.format("%s(%s) != %s(%s)", (char)decodedValue, decodedValue, actual, (int)actual), decodedValue, (int)actual); + Assert.assertTrue(startIndex < pbs.index()); + } + + /** + * tests that when Input is decoded through a PushbackString that null is returned and that the PushbackString index + * remains unchanged. + */ + @SuppressWarnings("unchecked") + private void assertInputIsDecodedToNull() { + PushbackString pbs = new PushbackString(input); + int startIndex = pbs.index(); + Assert.assertEquals(null, codec.decodeCharacter(pbs)); + Assert.assertEquals(startIndex, pbs.index()); + } + +} From 46d2484972a4b5e67817c3c63c8c8ddf9b177c5f Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Sat, 3 Feb 2018 06:03:08 -0600 Subject: [PATCH 077/709] Removing CodePoint test & abstraction The management and testing of code point handling is out of scope of the current effort. Future work on code point handling will allow the PercentCodec to be tested in this way. --- .../codecs/AbstractCodecCodePointTest.java | 78 ----------- .../codecs/PercentCodecCodePointTest.java | 121 ------------------ 2 files changed, 199 deletions(-) delete mode 100644 src/test/java/org/owasp/esapi/codecs/AbstractCodecCodePointTest.java delete mode 100644 src/test/java/org/owasp/esapi/codecs/PercentCodecCodePointTest.java diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecCodePointTest.java b/src/test/java/org/owasp/esapi/codecs/AbstractCodecCodePointTest.java deleted file mode 100644 index 7d79d55bc..000000000 --- a/src/test/java/org/owasp/esapi/codecs/AbstractCodecCodePointTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.owasp.esapi.codecs; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - - -/** - * Abstract parameterized test case meant to assist with verifying Character api of a Codec implementation. - *
    - * Sub-classes are expected to provide instances of {@link CodecCodePointTestTuple} to this instance. - *
    - * For better test naming output specify {@link CodecCodePointTestTuple#description} and use {@code} @Parameters (name="{0}")}, - * where '0' is the index that the CodecCodePointTestTuple reference appears in the constructor. - */ -@RunWith(Parameterized.class) -public abstract class AbstractCodecCodePointTest { - - /** Test Data Tuple.*/ - protected static class CodecCodePointTestTuple { - /** Codec reference to be tested.*/ - Codec codec; - /** Set of characters that should be considered 'immune' from decoding processes.*/ - char[] encodeImmune; - /** A String representing a single encoded character.*/ - String input; - /** The int code point that input represents.*/ - int codePoint; - /** Optional field to override the toString value of this tuple. */ - String description; - /** {@inheritDoc}*/ - - @Override - public String toString() { - return description != null ? description : codec.getClass().getSimpleName() + " "+input; - } - } - - - protected final Codec codec; - protected final String input; - protected final char[] encodeImmune; - protected final int decodedValue; - protected final char codePointChar; - - public AbstractCodecCodePointTest(CodecCodePointTestTuple tuple) { - this.codec = tuple.codec; - this.input = tuple.input; - this.decodedValue = tuple.codePoint; - this.encodeImmune = tuple.encodeImmune; - this.codePointChar = (char) tuple.codePoint; - } - - /** Checks that the input value matches the result of the codec encoding the decoded value. */ - @Test - public void testEncodeCharacter() { - assertEquals(input, codec.encodeCharacter(encodeImmune, decodedValue)); - } - - /** Checks that decoding the input value yeilds the same code point decodedValue.*/ - @Test - public void testDecode() { - int expectedLength = Character.toString(codePointChar).length(); - String actualDecode = codec.decode(input); - assertTrue("CodePoint test input should decode to a String consisting of a single character: " + actualDecode + " " + actualDecode.length(),actualDecode.length() == expectedLength); - assertEquals(decodedValue, (int)actualDecode.charAt(0)); - } - - /** Checks that the encoded input String is correctly decoded to the single decodedValue character reference.*/ - @Test - public void testDecodePushbackSequence() { - assertEquals(decodedValue, codec.decodeCharacter(new PushbackString(input))); - } - -} diff --git a/src/test/java/org/owasp/esapi/codecs/PercentCodecCodePointTest.java b/src/test/java/org/owasp/esapi/codecs/PercentCodecCodePointTest.java deleted file mode 100644 index d44d16097..000000000 --- a/src/test/java/org/owasp/esapi/codecs/PercentCodecCodePointTest.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.owasp.esapi.codecs; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runners.Parameterized.Parameters; - -/** - * Codec validation focused on the PercentCodec codepoint-based api. - * - */ -@Ignore(value="Implementation pending") -public class PercentCodecCodePointTest extends AbstractCodecCodePointTest { - private static final char[] PERCENT_CODEC_IMMUNE; - - static { - /* - * The percent codec contains a unique immune character set which include letters and numbers that will not be - * transformed. - * It is being replicated here to allow the test to reasonably expect the correct state back. - */ - List immune = new ArrayList<>(); - // 65 - 90 (capital letters) 97 - 122 lower case 48 - 57 digits - // numbers - for (int index = 48; index < 58; index++) { - immune.add((char) index); - } - // letters - for (int index = 65; index < 91; index++) { - Character capsChar = (char) index; - immune.add(capsChar); - immune.add(Character.toLowerCase(capsChar)); - } - - PERCENT_CODEC_IMMUNE = new char[immune.size()]; - for (int index = 0; index < immune.size(); index++) { - PERCENT_CODEC_IMMUNE[index] = immune.get(index).charValue(); - } - } - - @Parameters(name = "{0}") - public static Collection buildTests() { - Collection tests = new ArrayList<>(); - - Collection tuples = new ArrayList<>(); - tuples.add(newTuple("%3C", Character.valueOf('<'))); - - tuples.add(newTuple("%C4%80", Character.valueOf((char) 0x100))); - tuples.add(newTuple("%00", Character.MIN_VALUE)); - tuples.add(newTuple("%3D", '=')); - tuples.add(newTuple("%26", '&')); - - for (char c : PERCENT_CODEC_IMMUNE) { - tuples.add(newTuple(Character.toString(c), c)); - } - - for (CodecCodePointTestTuple tuple : tuples) { - tests.add(new Object[] { tuple }); - } - - return tests; - } - - private static CodecCodePointTestTuple newTuple(String encodedInput, Character decoded) { - CodecCodePointTestTuple tuple = new CodecCodePointTestTuple(); - tuple.codec = new PercentCodec(); - tuple.encodeImmune = PERCENT_CODEC_IMMUNE; - tuple.codePoint = decoded.charValue(); - tuple.input = encodedInput; - - return tuple; - } - - public PercentCodecCodePointTest(CodecCodePointTestTuple tuple) { - super(tuple); - } - - @Override - @Test - public void testDecodePushbackSequence() { - // check duplicated from PushbackSequence handling in PercentCodec. - boolean inputIsEncoded = input.startsWith("%"); - - if (inputIsEncoded) { - assertInputIsDecodedToValue(); - } else { - assertInputIsDecodedToNull(); - } - } - - /** - * tests that when Input is decoded through a PushbackString that the decodedValue reference is returned and that - * the PushbackString index has incremented. - */ - @SuppressWarnings("unchecked") - private void assertInputIsDecodedToValue() { - PushbackString pbs = new PushbackString(input); - int startIndex = pbs.index(); - Character decChar = (Character) codec.decodeCharacter(pbs); - char actual = decChar.charValue(); - Assert.assertEquals(String.format("%s(%s) != %s(%s)", (char)decodedValue, decodedValue, actual, (int)actual), decodedValue, (int)actual); - Assert.assertTrue(startIndex < pbs.index()); - } - - /** - * tests that when Input is decoded through a PushbackString that null is returned and that the PushbackString index - * remains unchanged. - */ - @SuppressWarnings("unchecked") - private void assertInputIsDecodedToNull() { - PushbackString pbs = new PushbackString(input); - int startIndex = pbs.index(); - Assert.assertEquals(null, codec.decodeCharacter(pbs)); - Assert.assertEquals(startIndex, pbs.index()); - } - -} From 172828eeaab482dee229b88e0c380893f12e0c51 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Sat, 3 Feb 2018 06:12:43 -0600 Subject: [PATCH 078/709] Reorganizing new test classes Breaking the new abstract parameterizations and percent codec test implementations into sub-packages within test scope. Updating dependencies and references to resolve compilation issues. --- .../AbstractCodecCharacterTest.java | 18 +++++++++++------- .../AbstractCodecStringTest.java | 18 +++++++++++------- .../PercentCodecCharacterTest.java | 3 ++- .../{ => percent}/PercentCodecStringTest.java | 3 ++- 4 files changed, 26 insertions(+), 16 deletions(-) rename src/test/java/org/owasp/esapi/codecs/{ => abstraction}/AbstractCodecCharacterTest.java (88%) rename src/test/java/org/owasp/esapi/codecs/{ => abstraction}/AbstractCodecStringTest.java (85%) rename src/test/java/org/owasp/esapi/codecs/{ => percent}/PercentCodecCharacterTest.java (97%) rename src/test/java/org/owasp/esapi/codecs/{ => percent}/PercentCodecStringTest.java (95%) diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java similarity index 88% rename from src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java rename to src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java index 1101be47f..438d86e26 100644 --- a/src/test/java/org/owasp/esapi/codecs/AbstractCodecCharacterTest.java +++ b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java @@ -1,4 +1,4 @@ -package org.owasp.esapi.codecs; +package org.owasp.esapi.codecs.abstraction; import static org.junit.Assert.assertEquals; @@ -7,6 +7,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.owasp.esapi.codecs.Codec; +import org.owasp.esapi.codecs.PushbackString; /** @@ -23,17 +25,19 @@ public abstract class AbstractCodecCharacterTest { /** Test Data Tuple.*/ protected static class CodecCharacterTestTuple { /** Codec reference to be tested.*/ - Codec codec; + public Codec codec; /** Set of characters that should be considered 'immune' from decoding processes.*/ - char[] encodeImmune; + public char[] encodeImmune; /** A String representing a single encoded character.*/ - String input; + public String input; /** The single character that input represents.*/ - Character decodedValue; + public Character decodedValue; /** Optional field to override the toString value of this tuple. */ - String description; - /** {@inheritDoc}*/ + public String description; + /**Default public constructor.*/ + public CodecCharacterTestTuple() { /* No Op*/ } + /** {@inheritDoc}*/ @Override public String toString() { return description != null ? description : codec.getClass().getSimpleName() + " "+input; diff --git a/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java similarity index 85% rename from src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java rename to src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java index b3fe53156..e54d1172d 100644 --- a/src/test/java/org/owasp/esapi/codecs/AbstractCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java @@ -1,9 +1,10 @@ -package org.owasp.esapi.codecs; +package org.owasp.esapi.codecs.abstraction; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.owasp.esapi.codecs.Codec; /** @@ -19,17 +20,20 @@ public abstract class AbstractCodecStringTest { protected static class CodecStringTestTuple { /** Codec reference to be tested.*/ - Codec codec; + public Codec codec; /** Set of characters that should be considered 'immune' from decoding processes.*/ - char[] encodeImmune; + public char[] encodeImmune; /** A String representing a contextually encoded String.*/ - String input; + public String input; /** The decoded representation of the input value.*/ - String decodedValue; + public String decodedValue; /** Optional field to override the toString value of this tuple. */ - String description; - /** {@inheritDoc}*/ + public String description; + + /**Default public constructor.*/ + public CodecStringTestTuple() { /* No Op*/ } + /** {@inheritDoc}*/ @Override public String toString() { return description != null ? description : codec.getClass().getSimpleName() + " "+input; diff --git a/src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java similarity index 97% rename from src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java rename to src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java index ca7ddf1b6..76c5c1c52 100644 --- a/src/test/java/org/owasp/esapi/codecs/PercentCodecCharacterTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java @@ -1,4 +1,4 @@ -package org.owasp.esapi.codecs; +package org.owasp.esapi.codecs.percent; import java.util.ArrayList; import java.util.Collection; @@ -9,6 +9,7 @@ import org.junit.runners.Parameterized.Parameters; import org.owasp.esapi.codecs.PercentCodec; import org.owasp.esapi.codecs.PushbackString; +import org.owasp.esapi.codecs.abstraction.AbstractCodecCharacterTest; /** * Codec validation focused on the PercentCodec Character-based api. diff --git a/src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java similarity index 95% rename from src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java rename to src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java index a851ebcae..c769a2e01 100644 --- a/src/test/java/org/owasp/esapi/codecs/PercentCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java @@ -1,4 +1,4 @@ -package org.owasp.esapi.codecs; +package org.owasp.esapi.codecs.percent; import java.util.ArrayList; import java.util.Collection; @@ -6,6 +6,7 @@ import org.junit.runners.Parameterized.Parameters; import org.owasp.esapi.codecs.PercentCodec; +import org.owasp.esapi.codecs.abstraction.AbstractCodecStringTest; /** * Codec validation focused on the PercentCodec String-based api. From 2c960807e2060956848158fed146be375de92804 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Thu, 8 Feb 2018 15:38:41 -0600 Subject: [PATCH 079/709] Removing invalid test content Removing entries that would otherwise test the codepoint handling of the PercentCodec. The feature of code point handling has not been fully implemented on the PercentCodec at this time. --- .../owasp/esapi/codecs/percent/PercentCodecCharacterTest.java | 2 +- .../org/owasp/esapi/codecs/percent/PercentCodecStringTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java index 76c5c1c52..f9e1d616d 100644 --- a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java @@ -50,7 +50,7 @@ public static Collection buildTests() { Collection tuples = new ArrayList<>(); tuples.add(newTuple("%3C", Character.valueOf('<'))); - tuples.add(newTuple("%C4%80", Character.valueOf((char) 0x100))); + //CODEPOINT tuples.add(newTuple("%C4%80", Character.valueOf((char) 0x100))); tuples.add(newTuple("%00", Character.MIN_VALUE)); tuples.add(newTuple("%3D", '=')); tuples.add(newTuple("%26", '&')); diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java index c769a2e01..786444b27 100644 --- a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java @@ -47,7 +47,7 @@ public static Collection buildTests() { List tuples = new ArrayList<>(); tuples.add(newTuple("%3C", "<")); - tuples.add(newTuple("%C4%80", (char) 0x100)); + //CODEPOINT tuples.add(newTuple("%C4%80", (char) 0x100)); tuples.add(newTuple("%00", Character.MIN_VALUE)); tuples.add(newTuple("%3D", '=')); tuples.add(newTuple("%26", '&')); From 804350f0f25605da3cf69df3885c133636f3860b Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Thu, 8 Feb 2018 15:44:02 -0600 Subject: [PATCH 080/709] Relocating Tests & Updating references Moving the pattern validation test constructs into a sub directory intended to hold regex-type testing elements. --- .../owasp/esapi/reference/{ => regex}/AbstractPatternTest.java | 2 +- .../{ => regex}/EsapiWhitelistValidationPatternTester.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/test/java/org/owasp/esapi/reference/{ => regex}/AbstractPatternTest.java (97%) rename src/test/java/org/owasp/esapi/reference/{ => regex}/EsapiWhitelistValidationPatternTester.java (98%) diff --git a/src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java b/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java similarity index 97% rename from src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java rename to src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java index 9cf1e0e20..3e3847719 100644 --- a/src/test/java/org/owasp/esapi/reference/AbstractPatternTest.java +++ b/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java @@ -1,4 +1,4 @@ -package org.owasp.esapi.reference; +package org.owasp.esapi.reference.regex; import java.util.regex.Pattern; diff --git a/src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java b/src/test/java/org/owasp/esapi/reference/regex/EsapiWhitelistValidationPatternTester.java similarity index 98% rename from src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java rename to src/test/java/org/owasp/esapi/reference/regex/EsapiWhitelistValidationPatternTester.java index d00d4c19a..2042eee33 100644 --- a/src/test/java/org/owasp/esapi/reference/EsapiWhitelistValidationPatternTester.java +++ b/src/test/java/org/owasp/esapi/reference/regex/EsapiWhitelistValidationPatternTester.java @@ -1,4 +1,4 @@ -package org.owasp.esapi.reference; +package org.owasp.esapi.reference.regex; import java.util.ArrayList; import java.util.Collection; From 4f903d37feb4b32c6d9d1f2339d3f5c3b45162c5 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Mon, 19 Feb 2018 08:06:06 -0600 Subject: [PATCH 081/709] Adding known exception testing for PercentCodec Making a test that will fail when the UTF16 handling is corrected for the implementation. Also centralizing the test-scope of the immune character set to just the StringTest impl for easier maintenance later when it's replaced. --- .../percent/PercentCodecCharacterTest.java | 31 ++----------------- .../percent/PercentCodecStringTest.java | 2 +- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java index f9e1d616d..419762dc1 100644 --- a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java @@ -1,8 +1,9 @@ package org.owasp.esapi.codecs.percent; +import static org.owasp.esapi.codecs.percent.PercentCodecStringTest.PERCENT_CODEC_IMMUNE; + import java.util.ArrayList; import java.util.Collection; -import java.util.List; import org.junit.Assert; import org.junit.Test; @@ -16,33 +17,6 @@ * */ public class PercentCodecCharacterTest extends AbstractCodecCharacterTest { - private static final char[] PERCENT_CODEC_IMMUNE; - - static { - /* - * The percent codec contains a unique immune character set which include letters and numbers that will not be - * transformed. - * It is being replicated here to allow the test to reasonably expect the correct state back. - */ - List immune = new ArrayList<>(); - // 65 - 90 (capital letters) 97 - 122 lower case 48 - 57 digits - // numbers - for (int index = 48; index < 58; index++) { - immune.add((char) index); - } - // letters - for (int index = 65; index < 91; index++) { - Character capsChar = (char) index; - immune.add(capsChar); - immune.add(Character.toLowerCase(capsChar)); - } - - PERCENT_CODEC_IMMUNE = new char[immune.size()]; - for (int index = 0; index < immune.size(); index++) { - PERCENT_CODEC_IMMUNE[index] = immune.get(index).charValue(); - } - } - @Parameters(name = "{0}") public static Collection buildTests() { Collection tests = new ArrayList<>(); @@ -50,7 +24,6 @@ public static Collection buildTests() { Collection tuples = new ArrayList<>(); tuples.add(newTuple("%3C", Character.valueOf('<'))); - //CODEPOINT tuples.add(newTuple("%C4%80", Character.valueOf((char) 0x100))); tuples.add(newTuple("%00", Character.MIN_VALUE)); tuples.add(newTuple("%3D", '=')); tuples.add(newTuple("%26", '&')); diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java index 786444b27..665a8ab71 100644 --- a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java @@ -13,7 +13,7 @@ * */ public class PercentCodecStringTest extends AbstractCodecStringTest { - private static final char[] PERCENT_CODEC_IMMUNE; + public static final char[] PERCENT_CODEC_IMMUNE; static { /* From d9e12d6be4c2b556fafdaab14e4c62b68b30a679 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Wed, 21 Feb 2018 17:40:10 -0600 Subject: [PATCH 082/709] Really committing known exceptions test Lost a workflow argument with git, so I'm making another commit to actually add the file that I intended to add last commit. --- .../percent/PercentCodecKnownIssuesTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java new file mode 100644 index 000000000..9b163b4bf --- /dev/null +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java @@ -0,0 +1,41 @@ +package org.owasp.esapi.codecs.percent; +import static org.owasp.esapi.codecs.percent.PercentCodecStringTest.PERCENT_CODEC_IMMUNE; + +import org.junit.Assert; +import org.junit.Test; +import org.owasp.esapi.codecs.PercentCodec; +/** + * This test class holds the proof of known deficiencies, inconsistencies, or bugs with the PercentCodec implementation. + *
    + * The intent is that when that functionality is corrected, these tests should break. That should hopefully encourage + * the author to move the test to an appropriate Test file and update the functionality to a working expectation. + */ +public class PercentCodecKnownIssuesTest { + + private PercentCodec codec = new PercentCodec(); + + /** + * PercentCodec has not been fully implemented for codepoint support, which handles UTF16 characters (based on my current understanding). + * As such, the encoding/decoding of UTF16 will not function as desired through the codec implementation. + *
    + * When the functionality is corrected this test will break. At that point UTF16 tests should be added to {@link PercentCodecStringTest} and {@link PercentCodecCharacterTest}. + */ + @Test + public void failsUTF16Conversions() { + //This should be 195 + int incorrectDecodeExpect = 196; + + char[] encodeImmune = PERCENT_CODEC_IMMUNE; + String decodedValue = ""+(char) 0x100; + String input = "%C4%80"; + + String actualDecodeChar = codec.decode(input); + int actualChar = (int)actualDecodeChar.charAt(0); + + Assert.assertEquals(incorrectDecodeExpect, actualChar); + + //This works as expected. + Assert.assertEquals(input, codec.encode(encodeImmune, decodedValue)); + } + +} From bf91374d3acb5cf79943ac9127a7fd39473af881 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Wed, 21 Feb 2018 17:47:00 -0600 Subject: [PATCH 083/709] Adding License Information Adding license content to files created in this effort. --- .../abstraction/AbstractCodecCharacterTest.java | 14 ++++++++++++++ .../abstraction/AbstractCodecStringTest.java | 14 ++++++++++++++ .../codecs/percent/PercentCodecCharacterTest.java | 14 ++++++++++++++ .../percent/PercentCodecKnownIssuesTest.java | 15 +++++++++++++++ .../codecs/percent/PercentCodecStringTest.java | 14 ++++++++++++++ .../esapi/filters/SecurityWrapperRequestTest.java | 14 ++++++++++++++ .../reference/regex/AbstractPatternTest.java | 13 +++++++++++++ .../EsapiWhitelistValidationPatternTester.java | 14 ++++++++++++++ 8 files changed, 112 insertions(+) diff --git a/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java index 438d86e26..66ff3cb82 100644 --- a/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java +++ b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java @@ -1,3 +1,17 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2008-2018 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + */ + package org.owasp.esapi.codecs.abstraction; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java index e54d1172d..c00cbf699 100644 --- a/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java @@ -1,3 +1,17 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2008-2018 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + */ + package org.owasp.esapi.codecs.abstraction; import org.junit.Assert; diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java index 419762dc1..2c7199c23 100644 --- a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java @@ -1,3 +1,17 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2008-2018 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + */ + package org.owasp.esapi.codecs.percent; import static org.owasp.esapi.codecs.percent.PercentCodecStringTest.PERCENT_CODEC_IMMUNE; diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java index 9b163b4bf..2f3f1049c 100644 --- a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java @@ -1,4 +1,19 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2008-2018 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + */ + package org.owasp.esapi.codecs.percent; + import static org.owasp.esapi.codecs.percent.PercentCodecStringTest.PERCENT_CODEC_IMMUNE; import org.junit.Assert; diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java index 665a8ab71..38bb6fb27 100644 --- a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java @@ -1,3 +1,17 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2008-2018 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + */ + package org.owasp.esapi.codecs.percent; import java.util.ArrayList; diff --git a/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java b/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java index bf1a2d627..c5c1c3c5e 100644 --- a/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java +++ b/src/test/java/org/owasp/esapi/filters/SecurityWrapperRequestTest.java @@ -1,3 +1,17 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2008-2018 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + */ + package org.owasp.esapi.filters; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java b/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java index 3e3847719..f05397dfb 100644 --- a/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java +++ b/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java @@ -1,3 +1,16 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2008-2018 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + */ package org.owasp.esapi.reference.regex; import java.util.regex.Pattern; diff --git a/src/test/java/org/owasp/esapi/reference/regex/EsapiWhitelistValidationPatternTester.java b/src/test/java/org/owasp/esapi/reference/regex/EsapiWhitelistValidationPatternTester.java index 2042eee33..b6080344d 100644 --- a/src/test/java/org/owasp/esapi/reference/regex/EsapiWhitelistValidationPatternTester.java +++ b/src/test/java/org/owasp/esapi/reference/regex/EsapiWhitelistValidationPatternTester.java @@ -1,3 +1,17 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2008-2018 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + */ + package org.owasp.esapi.reference.regex; import java.util.ArrayList; From 113add5fa9e280eb2bae9263c8c9868b963ec938 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Thu, 22 Feb 2018 04:47:54 -0600 Subject: [PATCH 084/709] Documentation updates Feedback from pull request. --- .../esapi/codecs/abstraction/AbstractCodecCharacterTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java index 66ff3cb82..aa7208399 100644 --- a/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java +++ b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecCharacterTest.java @@ -30,7 +30,7 @@ *
    * Sub-classes are expected to provide instances of {@link CodecCharacterTestTuple} to this instance. *
    - * For better test naming output specify {@link CodecCharacterTestTuple#description} and use {@code} @Parameters (name="{0}")}, + * For better test naming output specify {@link CodecCharacterTestTuple#description} and use @Parameters (name="{0}"), * where '0' is the index that the CodecCharacterTestTuple reference appears in the constructor. */ @RunWith(Parameterized.class) @@ -88,7 +88,7 @@ public void testEncode() { assertEquals(expected, codec.encode(encodeImmune, decodedValue.toString())); } - /** Checks that decoding the input value yeilds the decodedValue.*/ + /** Checks that decoding the input value yields the decodedValue.*/ @Test public void testDecode() { assertEquals(decodedValue.toString(), codec.decode(input)); From e5b7c0d8d9f93e1d8617db2362e2cf59b1d093bd Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Thu, 22 Feb 2018 04:50:16 -0600 Subject: [PATCH 085/709] Updating Imports Pull request feedback. Setting Assert.assertEquals to a static import to match other implementations within this effort. --- .../esapi/codecs/abstraction/AbstractCodecStringTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java index c00cbf699..789c8a153 100644 --- a/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java @@ -14,7 +14,8 @@ package org.owasp.esapi.codecs.abstraction; -import org.junit.Assert; +import static org.junit.Assert.assertEquals; + import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -69,13 +70,13 @@ public AbstractCodecStringTest(CodecStringTestTuple tuple) { /** Checks that when the input is decoded using the specified codec, that the return matches the expected decoded value.*/ @Test public void testDecode() { - Assert.assertEquals(decodedValue, codec.decode(input)); + assertEquals(decodedValue, codec.decode(input)); } /** Checks that when the decoded value is encoded (using immunity), that the return matches the provided input.*/ @Test public void testEncode() { - Assert.assertEquals(input, codec.encode(encodeImmune, decodedValue)); + assertEquals(input, codec.encode(encodeImmune, decodedValue)); } } From ce025aa4b5e2e8952d0cc87239cc916d88b84c8e Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Thu, 22 Feb 2018 04:54:45 -0600 Subject: [PATCH 086/709] Updating Imports, readability improvements. Pull request feedback. Using static Assert imports for consistency within this effort. Altering whitespace for better readability in parameter construction. --- .../codecs/percent/PercentCodecCharacterTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java index 2c7199c23..7d4425ddc 100644 --- a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecCharacterTest.java @@ -14,12 +14,13 @@ package org.owasp.esapi.codecs.percent; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.owasp.esapi.codecs.percent.PercentCodecStringTest.PERCENT_CODEC_IMMUNE; import java.util.ArrayList; import java.util.Collection; -import org.junit.Assert; import org.junit.Test; import org.junit.runners.Parameterized.Parameters; import org.owasp.esapi.codecs.PercentCodec; @@ -34,8 +35,8 @@ public class PercentCodecCharacterTest extends AbstractCodecCharacterTest { @Parameters(name = "{0}") public static Collection buildTests() { Collection tests = new ArrayList<>(); - Collection tuples = new ArrayList<>(); + tuples.add(newTuple("%3C", Character.valueOf('<'))); tuples.add(newTuple("%00", Character.MIN_VALUE)); @@ -88,8 +89,8 @@ public void testDecodePushbackSequence() { private void assertInputIsDecodedToValue() { PushbackString pbs = new PushbackString(input); int startIndex = pbs.index(); - Assert.assertEquals(decodedValue, codec.decodeCharacter(pbs)); - Assert.assertTrue(startIndex < pbs.index()); + assertEquals(decodedValue, codec.decodeCharacter(pbs)); + assertTrue(startIndex < pbs.index()); } /** @@ -100,8 +101,8 @@ private void assertInputIsDecodedToValue() { private void assertInputIsDecodedToNull() { PushbackString pbs = new PushbackString(input); int startIndex = pbs.index(); - Assert.assertEquals(null, codec.decodeCharacter(pbs)); - Assert.assertEquals(startIndex, pbs.index()); + assertEquals(null, codec.decodeCharacter(pbs)); + assertEquals(startIndex, pbs.index()); } } From b468673f93155fcfbf1c0f7ed1f1c15c31731758 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Thu, 22 Feb 2018 05:12:09 -0600 Subject: [PATCH 087/709] Updating imports Applying static Assert imports for implementation consistency. --- .../esapi/codecs/percent/PercentCodecKnownIssuesTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java index 2f3f1049c..ac8c6d6e2 100644 --- a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecKnownIssuesTest.java @@ -14,9 +14,9 @@ package org.owasp.esapi.codecs.percent; +import static org.junit.Assert.assertEquals; import static org.owasp.esapi.codecs.percent.PercentCodecStringTest.PERCENT_CODEC_IMMUNE; -import org.junit.Assert; import org.junit.Test; import org.owasp.esapi.codecs.PercentCodec; /** @@ -47,10 +47,10 @@ public void failsUTF16Conversions() { String actualDecodeChar = codec.decode(input); int actualChar = (int)actualDecodeChar.charAt(0); - Assert.assertEquals(incorrectDecodeExpect, actualChar); + assertEquals(incorrectDecodeExpect, actualChar); //This works as expected. - Assert.assertEquals(input, codec.encode(encodeImmune, decodedValue)); + assertEquals(input, codec.encode(encodeImmune, decodedValue)); } } From 5a65083bfd4d2c1966e00e04701190db6d397a96 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Thu, 22 Feb 2018 05:14:32 -0600 Subject: [PATCH 088/709] Readability Improvements Pull Request Cleanup. Clarifying the capital letters are ASCII specific. Applying better whitespace formatting for readability. --- .../owasp/esapi/codecs/percent/PercentCodecStringTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java index 38bb6fb27..edec02321 100644 --- a/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/percent/PercentCodecStringTest.java @@ -36,7 +36,7 @@ public class PercentCodecStringTest extends AbstractCodecStringTest { * It is being replicated here to allow the test to reasonably expect the correct state back. */ List immune = new ArrayList<>(); - // 65 - 90 (capital letters) 97 - 122 lower case 48 - 57 digits + // 65 - 90 (capital letters in ASCII) 97 - 122 lower case 48 - 57 digits //numbers for (int index = 48 ; index < 58; index ++) { immune.add((char)index); @@ -57,11 +57,9 @@ public class PercentCodecStringTest extends AbstractCodecStringTest { @Parameters(name = "{0}") public static Collection buildTests() { Collection tests = new ArrayList<>(); - List tuples = new ArrayList<>(); + tuples.add(newTuple("%3C", "<")); - - //CODEPOINT tuples.add(newTuple("%C4%80", (char) 0x100)); tuples.add(newTuple("%00", Character.MIN_VALUE)); tuples.add(newTuple("%3D", '=')); tuples.add(newTuple("%26", '&')); From 5d42bfe5544da508aafaa3cef78a0dde2b47d4a2 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Thu, 22 Feb 2018 05:18:09 -0600 Subject: [PATCH 089/709] Import cleanup Using static Assert imports for implementation consistency. --- .../org/owasp/esapi/reference/regex/AbstractPatternTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java b/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java index f05397dfb..89dd1f126 100644 --- a/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java +++ b/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java @@ -13,9 +13,10 @@ */ package org.owasp.esapi.reference.regex; +import static org.junit.Assert.assertEquals; + import java.util.regex.Pattern; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -63,7 +64,7 @@ public AbstractPatternTest(PatternTestTuple tuple) { @Test public void checkPatternMatches() { - Assert.assertEquals(shouldMatch, pattern.matcher(input).matches()); + assertEquals(shouldMatch, pattern.matcher(input).matches()); } } From 7ad97030300b3c0f4e6dc2533370445e0cfa7d59 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Thu, 22 Feb 2018 05:19:42 -0600 Subject: [PATCH 090/709] Documentation Cleanup Correcting use of javadoc {@code} to for better readability of parameterization usage and suggestions. --- .../owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java | 2 +- .../org/owasp/esapi/reference/regex/AbstractPatternTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java index 789c8a153..cf48dcfff 100644 --- a/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java +++ b/src/test/java/org/owasp/esapi/codecs/abstraction/AbstractCodecStringTest.java @@ -27,7 +27,7 @@ *
    * Sub-classes are expected to provide instances of {@link CodecStringTestTuple} to this instance. *
    - * For better test naming output specify {@link CodecStringTestTuple#description} and use {@code} @Parameters (name="{0}")}, + * For better test naming output specify {@link CodecStringTestTuple#description} and use @Parameters (name="{0}"), * where '0' is the index that the CodecStringTestTuple reference appears in the constructor. */ @RunWith(Parameterized.class) diff --git a/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java b/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java index 89dd1f126..f887db57b 100644 --- a/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java +++ b/src/test/java/org/owasp/esapi/reference/regex/AbstractPatternTest.java @@ -26,7 +26,7 @@ *
    * Sub-classes are expected to provide instances of {@link PatternTestTuple} to this instance. *
    - * For better test naming output specify {@link PatternTestTuple#description} and use {@code} @Parameters (name="{0}")}, + * For better test naming output specify {@link PatternTestTuple#description} and use @Parameters (name="{0}"), * where '0' is the index that the PatternTestTuple reference appears in the constructor. */ @RunWith(Parameterized.class) From 3bf59c6c02d929412ad551ef566f08938e4696af Mon Sep 17 00:00:00 2001 From: Matt Seil Date: Sun, 13 May 2018 15:41:29 -0700 Subject: [PATCH 091/709] Moved esapi.tld into the correct resources location. Fixes issues #213, #244, #253. --- {configuration => src/main/resources}/META-INF/esapi.tld | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {configuration => src/main/resources}/META-INF/esapi.tld (100%) diff --git a/configuration/META-INF/esapi.tld b/src/main/resources/META-INF/esapi.tld similarity index 100% rename from configuration/META-INF/esapi.tld rename to src/main/resources/META-INF/esapi.tld From b54019eab92f58cdf017a26568f079b028cc5f35 Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Mon, 9 Jul 2018 17:08:38 -0500 Subject: [PATCH 092/709] Updating Asserts for Additional Output Converting AssertTrue to other Assert API (mostly assertEquals) to see the values being compared in the test output. --- .../owasp/esapi/reference/crypto/EncryptorTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/owasp/esapi/reference/crypto/EncryptorTest.java b/src/test/java/org/owasp/esapi/reference/crypto/EncryptorTest.java index 787464cb8..180a590ed 100644 --- a/src/test/java/org/owasp/esapi/reference/crypto/EncryptorTest.java +++ b/src/test/java/org/owasp/esapi/reference/crypto/EncryptorTest.java @@ -251,7 +251,7 @@ private String runNewEncryptDecryptTestCase(String cipherXform, int keySize, byt } else if ( cipherAlg.equals( "DES" ) ) { keySize = 64; } // Else... use specified keySize. - assertTrue( (keySize / 8) == skey.getEncoded().length ); + assertEquals(cipherXform + " encoded key size does not match provided key size", (keySize / 8), skey.getEncoded().length ); // System.out.println("testNewEncryptDecrypt(): Skey length (bits) = " + 8 * skey.getEncoded().length); // Change to a possibly different cipher. This is kludgey at best. Am thinking about an @@ -271,7 +271,7 @@ private String runNewEncryptDecryptTestCase(String cipherXform, int keySize, byt // Do the encryption with the new encrypt() method and get back the CipherText. CipherText ciphertext = instance.encrypt(skey, plaintext); // The new encrypt() method. System.out.println("DEBUG: Encrypt(): CipherText object is -- " + ciphertext); - assertTrue( ciphertext != null ); + assertNotNull( ciphertext ); // System.out.println("DEBUG: After encryption: base64-encoded IV+ciphertext: " + ciphertext.getEncodedIVCipherText()); // System.out.println("\t\tOr... " + ESAPI.encoder().decodeFromBase64(ciphertext.getEncodedIVCipherText()) ); // System.out.println("DEBUG: After encryption: base64-encoded raw ciphertext: " + ciphertext.getBase64EncodedRawCipherText()); @@ -290,14 +290,14 @@ private String runNewEncryptDecryptTestCase(String cipherXform, int keySize, byt // Make sure we got back the same thing we started with. System.out.println("\tOriginal plaintext: " + origPlainText); System.out.println("\tResult after decryption: " + decryptedPlaintext); - assertTrue( "Failed to decrypt properly.", origPlainText.toString().equals( decryptedPlaintext.toString() ) ); + assertEquals( "Failed to decrypt properly.", origPlainText.toString(), decryptedPlaintext.toString() ); // Restore the previous cipher transformation. For now, this is only way to do this. @SuppressWarnings("deprecation") String previousCipherXform = ESAPI.securityConfiguration().setCipherTransformation(null); - assertTrue( previousCipherXform.equals( cipherXform ) ); + assertEquals( previousCipherXform, cipherXform ); String defaultCipherXform = ESAPI.securityConfiguration().getCipherTransformation(); - assertTrue( defaultCipherXform.equals( oldCipherXform ) ); + assertEquals( defaultCipherXform, oldCipherXform ); return ciphertext.getEncodedIVCipherText(); } catch (Exception e) { From 5232e21036f2959875c7ec7996406f9692d8e5fe Mon Sep 17 00:00:00 2001 From: Jeremiah Stacey Date: Sun, 22 Jul 2018 08:04:57 -0500 Subject: [PATCH 093/709] Attempt at SLF4J Implementation Experimenting with one method that may be functional for slf4j hooks. This implementation has not been tested at either the unit or integration level. I do not know if it works yet. --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 086f70917..6112a4e7f 100644 --- a/pom.xml +++ b/pom.xml @@ -243,6 +243,11 @@ 1.7.0 test + + org.slf4j + slf4j-api + 1.7.25 + org.powermock - powermock-api-mockito - 1.7.0 + powermock-api-mockito2 + 2.0.0-beta.5 test org.powermock powermock-module-junit4 - 1.7.0 + 2.0.0-beta.5 test org.slf4j slf4j-api - 1.7.25 + 1.8.0-beta2 - - joda-time - joda-time - 2.10 - + + + joda-time + joda-time + 2.10 + commons-configuration commons-configuration @@ -142,18 +145,11 @@ commons-beanutils 1.9.3 - - - junit - junit - 4.12 - test - - - org.bouncycastle - bcprov-jdk15on - 1.60 - test + javax.servlet @@ -163,8 +159,8 @@ javax.servlet.jsp - jsp-api - 2.2.1-b03 + javax.servlet.jsp-api + 2.3.3 provided @@ -173,12 +169,6 @@ 1.3.3 compile - - commons-io - commons-io - 2.6 - test - org.apache.commons commons-collections4 @@ -191,121 +181,104 @@ compile jar + + org.slf4j + slf4j-api + 1.7.25 + com.io7m.xom xom 1.2.10 - - xerces - xercesImpl - - - xml-apis - xml-apis - - - - - xml-apis - xml-apis - 1.4.01 - - - xerces - xercesImpl - 2.12.0 + + xerces + xercesImpl + + + xml-apis + xml-apis + + org.apache-extras.beanshell - bsh - 2.0b6 + bsh + 2.0b6 org.owasp.antisamy antisamy 1.5.7 - - xml-apis - xml-apis - - - xerces - xercesImpl - - + + xml-apis + xml-apis + + + xerces + xercesImpl + + - xalan xalan 2.7.2 - - - xml-apis - xml-apis - - + + + xml-apis + xml-apis + 1.4.01 org.apache.xmlgraphics batik-css 1.10 - - - - org.powermock - powermock-api-mockito2 - 2.0.0-beta.5 - test - - - org.powermock - powermock-module-junit4 - 2.0.0-beta.5 - test - - - org.slf4j - slf4j-api - 1.7.25 - - - - - + + xerces + xercesImpl + 2.12.0 + + + + junit + junit + 4.12 + test + + + org.bouncycastle + bcprov-jdk15on + 1.60 + test + + + commons-io + commons-io + 2.6 + test + + + + org.powermock + powermock-api-mockito2 + 2.0.0-beta.5 + test + + + org.powermock + powermock-module-junit4 + 2.0.0-beta.5 + test + @@ -404,7 +377,7 @@ dependency-check-maven 2.1.0 - 5.9 + ./suppressions.xml @@ -609,37 +582,37 @@
    - - org.apache.maven.plugins - maven-jar-plugin - 2.6 + + org.apache.maven.plugins + maven-jar-plugin + 2.6 - + - - - true - true - - - true - - - - + + + true + true + + + true + + + + diff --git a/src/main/java/org/owasp/esapi/reference/accesscontrol/DelegatingACR.java b/src/main/java/org/owasp/esapi/reference/accesscontrol/DelegatingACR.java index fa532ddaf..8c7698908 100644 --- a/src/main/java/org/owasp/esapi/reference/accesscontrol/DelegatingACR.java +++ b/src/main/java/org/owasp/esapi/reference/accesscontrol/DelegatingACR.java @@ -5,7 +5,7 @@ import java.util.Iterator; import java.util.Vector; -import org.apache.commons.collections.iterators.ArrayListIterator; +import org.apache.commons.collections4.iterators.ArrayListIterator; public class DelegatingACR extends BaseACR { protected Method delegateMethod; From fe2cfff3c415d43f5c0a5f53dbba32f636e57dae Mon Sep 17 00:00:00 2001 From: kwwall Date: Fri, 5 Oct 2018 15:43:47 -0400 Subject: [PATCH 112/709] Close issue #444. Delete deprecated decodeToObject() method and 2 related encodeObject() methods. General whitespace clean-up. Delete EncoderTest.testBase64decodToObject() method which is no longer relevant. --- .../java/org/owasp/esapi/codecs/Base64.java | 839 ++++++------------ .../owasp/esapi/reference/EncoderTest.java | 54 -- 2 files changed, 293 insertions(+), 600 deletions(-) diff --git a/src/main/java/org/owasp/esapi/codecs/Base64.java b/src/main/java/org/owasp/esapi/codecs/Base64.java index b92f35590..150bd29ef 100644 --- a/src/main/java/org/owasp/esapi/codecs/Base64.java +++ b/src/main/java/org/owasp/esapi/codecs/Base64.java @@ -15,14 +15,14 @@ *

    Homepage: http://iharder.net/base64 * (based on version 2.2.2).

    * - *

    The options parameter, which appears in a few places, is used to pass - * several pieces of information to the encoder. In the "higher level" methods such as - * encodeBytes( bytes, options ) the options parameter can be used to indicate such - * things as first gzipping the bytes before encoding them, not inserting linefeeds - * (though that breaks strict Base64 compatibility), and encoding using the URL-safe + *

    The options parameter, which appears in a few places, is used to pass + * several pieces of information to the encoder. In the "higher level" methods such as + * encodeBytes( bytes, options ) the options parameter can be used to indicate such + * things as first gzipping the bytes before encoding them, not inserting linefeeds + * (though that breaks strict Base64 compatibility), and encoding using the URL-safe * and Ordered dialects.

    * - *

    The constants defined in Base64 can be OR-ed together to combine options, so you + *

    The constants defined in Base64 can be OR-ed together to combine options, so you * might make a call like this:

    * * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DONT_BREAK_LINES ); @@ -54,24 +54,24 @@ * Special thanks to Jim Kellerman at http://www.powerset.com/ * for contributing the new Base64 dialects. * - * + * *
  • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added * some convenience methods for reading and writing to and from files.
  • *
  • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems * with other encodings (like EBCDIC).
  • *
  • v2.0.1 - Fixed an error when decoding a single byte, that is, when the * encoded data was a single byte.
  • - *
  • v2.0 - I got rid of methods that used booleans to set options. + *
  • v2.0 - I got rid of methods that used booleans to set options. * Now everything is more consolidated and cleaner. The code now detects * when data that's being decoded is gzip-compressed and will decompress it * automatically. Generally things are cleaner. You'll probably have to * change some method calls that you were making to support the new * options format (ints that you "OR" together).
  • - *
  • v1.5.1 - Fixed bug when decompressing and decoding to a - * byte[] using decode( String s, boolean gzipCompressed ). - * Added the ability to "suspend" encoding in the Output Stream so - * you can turn on and off the encoding if you need to embed base64 - * data in an otherwise "normal" stream (like an XML file).
  • + *
  • v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using decode( String s, boolean gzipCompressed ). + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).
  • *
  • v1.5 - Output stream pases on flush() command but doesn't do anything itself. * This helps when using GZIP streams. * Added the ability to GZip-compress objects before encoding them.
  • @@ -97,108 +97,98 @@ */ public class Base64 { - -/* ******** P U B L I C F I E L D S ******** */ - - + +/* ******** P U B L I C F I E L D S ******** */ + + /** No options specified. Value is zero. */ public final static int NO_OPTIONS = 0; - + /** Specify encoding. */ public final static int ENCODE = 1; - - + /** Specify decoding. */ public final static int DECODE = 0; - - + /** Specify that data should be gzip-compressed. */ public final static int GZIP = 2; - - + /** Don't break lines when encoding (violates strict Base64 specification) */ public final static int DONT_BREAK_LINES = 8; - - /** - * Encode using Base64-like encoding that is URL- and Filename-safe as described - * in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * It is important to note that data encoded this way is not officially valid Base64, - * or at the very least should not be called Base64 without also specifying that is - * was encoded using the URL- and Filename-safe dialect. - */ - public final static int URL_SAFE = 16; - - - /** - * Encode using the special "ordered" dialect of Base64 described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - public final static int ORDERED = 32; - + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described + * in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * It is important to note that data encoded this way is not officially valid Base64, + * or at the very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect. + */ + public final static int URL_SAFE = 16; + + /** + * Encode using the special "ordered" dialect of Base64 described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + public final static int ORDERED = 32; + /** * System property name that must be set to true in order to invoke {@code Base64.decodeToObject()}. * @see https://github.com/ESAPI/esapi-java-legacy/issues/354 * @see http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/ */ public final static String ENABLE_UNSAFE_SERIALIZATION = "org.owasp.esapi.enableUnsafeSerialization"; // Do NOT change! - -/* ******** P R I V A T E F I E L D S ******** */ - - + +/* ******** P R I V A T E F I E L D S ******** */ + /** Maximum line length (76) of Base64 output. */ private final static int MAX_LINE_LENGTH = 76; - - + /** The equals sign (=) as a byte. */ private final static byte EQUALS_SIGN = (byte)'='; - - + /** The new line character (\n) as a byte. */ private final static byte NEW_LINE = (byte)'\n'; - - + /** Preferred encoding. */ private final static String PREFERRED_ENCODING = "UTF-8"; - + /** End of line character. */ private final static String EOL = System.getProperty("line.separator", "\n"); - - + + // I think I end up not using the BAD_ENCODING indicator. //private final static byte BAD_ENCODING = -9; // Indicates error in encoding private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - + private static final Logger logger = ESAPI.getLogger("Base64"); - - -/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ - + +/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + /** The 64 valid Base64 values. */ //private final static byte[] ALPHABET; - /* Host platform me be something funny like EBCDIC, so we hard code these values. */ - private final static byte[] _STANDARD_ALPHABET = + /* Host platform me be something funny like EBCDIC, so we hard code these values. */ + private final static byte[] _STANDARD_ALPHABET = { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' }; - - - /** + + /** * Translates a Base64 value to either its 6-bit reconstruction value * or a negative number indicating some other meaning. **/ private final static byte[] _STANDARD_DECODABET = - { + { -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 -5,-5, // Whitespace: Tab and Linefeed -9,-9, // Decimal 11 - 12 @@ -231,34 +221,34 @@ public class Base64 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ }; - - + + /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ - - /** - * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." - */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." + */ private final static byte[] _URL_SAFE_ALPHABET = { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' }; - - /** - * Used in decoding URL- and Filename-safe dialects of Base64. - */ + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ private final static byte[] _URL_SAFE_DECODABET = - { + { -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 -5,-5, // Whitespace: Tab and Linefeed -9,-9, // Decimal 11 - 12 @@ -300,10 +290,10 @@ public class Base64 /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ - /** - * I don't get the point of this technique, but it is described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ + /** + * I don't get the point of this technique, but it is described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ private final static byte[] _ORDERED_ALPHABET = { (byte)'-', @@ -319,12 +309,12 @@ public class Base64 (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' }; - - /** - * Used in decoding the "ordered" dialect of Base64. - */ + + /** + * Used in decoding the "ordered" dialect of Base64. + */ private final static byte[] _ORDERED_DECODABET = - { + { -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 -5,-5, // Whitespace: Tab and Linefeed -9,-9, // Decimal 11 - 12 @@ -362,46 +352,43 @@ public class Base64 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ }; - + /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ - /** - * Returns one of the _SOMETHING_ALPHABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URLSAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getAlphabet( int options ) - { - if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_ALPHABET; - else if( (options & ORDERED) == ORDERED ) return _ORDERED_ALPHABET; - else return _STANDARD_ALPHABET; - - } // end getAlphabet - - - /** - * Returns one of the _SOMETHING_DECODABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URL_SAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getDecodabet( int options ) - { - if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_DECODABET; - else if( (options & ORDERED) == ORDERED ) return _ORDERED_DECODABET; - else return _STANDARD_DECODABET; - - } // end getAlphabet - - - + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URLSAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getAlphabet( int options ) + { + if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_ALPHABET; + else if( (options & ORDERED) == ORDERED ) return _ORDERED_ALPHABET; + else return _STANDARD_ALPHABET; + + } // end getAlphabet + + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URL_SAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getDecodabet( int options ) + { + if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_DECODABET; + else if( (options & ORDERED) == ORDERED ) return _ORDERED_DECODABET; + else return _STANDARD_DECODABET; + + } // end getDecodaabet + /** Defeats instantiation. */ private Base64(){} - /** * Encodes or decodes two files from the command line; @@ -423,10 +410,10 @@ public final static void main( String[] args ) } // end if: encode else if( flag.equals( "-d" ) ) { Base64.decodeFileToFile( infile, outfile ); - } // end else if: decode + } // end else if: decode else { usage( "Unknown flag: " + flag ); - } // end else + } // end else } // end else } // end main @@ -440,11 +427,9 @@ private final static void usage( String msg ) System.err.println( msg ); System.err.println( "Usage: java Base64 -e|-d inputfile outputfile" ); } // end usage - - -/* ******** E N C O D I N G M E T H O D S ******** */ - - + +/* ******** E N C O D I N G M E T H O D S ******** */ + /** * Encodes up to the first three bytes of array threeBytes * and returns a four-byte array in Base64 notation. @@ -466,12 +451,11 @@ private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, return b4; } // end encode3to4 - /** *

    Encodes up to three bytes of the array source * and writes the resulting four Base64 bytes to destination. * The source and destination arrays can be manipulated - * anywhere along their length by specifying + * anywhere along their length by specifying * srcOffset and destOffset. * This method does not check to make sure your arrays * are large enough to accomodate srcOffset + 3 for @@ -479,8 +463,8 @@ private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, * the destination array. * The actual number of significant bytes in your array is * given by numSigBytes.

    - *

    This is the lowest level of the encoding methods with - * all possible parameters.

    + *

    This is the lowest level of the encoding methods with + * all possible parameters.

    * * @param source the array to convert * @param srcOffset the index where conversion begins @@ -490,19 +474,19 @@ private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, * @return the destination array * @since 1.3 */ - private static byte[] encode3to4( - byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset, int options ) + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset, int options ) { - byte[] ALPHABET = getAlphabet( options ); - - // 1 2 3 + byte[] ALPHABET = getAlphabet( options ); + + // 1 2 3 // 01234567890123456789012345678901 Bit position // --------000000001111111122222222 Array position from threeBytes // --------| || || || | Six bit groups to index ALPHABET // >>18 >>12 >> 6 >> 0 Right shift necessary // 0x3f 0x3f 0x3f Additional AND - + // Create buffer with zero-padding if there are only one or two // significant bytes passed in the array. // We have to shift left 24 in order to flush out the 1's that appear @@ -519,124 +503,25 @@ private static byte[] encode3to4( destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; return destination; - + case 2: destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; destination[ destOffset + 3 ] = EQUALS_SIGN; return destination; - + case 1: destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; destination[ destOffset + 2 ] = EQUALS_SIGN; destination[ destOffset + 3 ] = EQUALS_SIGN; return destination; - + default: return destination; } // end switch } // end encode3to4 - - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. If the object - * cannot be serialized or there is another error, - * the method will return null. - * The object is not GZip-compressed before being encoded. - * - * @param serializableObject The object to encode - * @return The Base64-encoded object - * @since 1.4 - */ - public static String encodeObject( java.io.Serializable serializableObject ) - { - return encodeObject( serializableObject, NO_OPTIONS ); - } // end encodeObject - - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. If the object - * cannot be serialized or there is another error, - * the method will return null. - *

    - * Valid options:

    -     *   GZIP: gzip-compresses object before encoding it.
    -     *   DONT_BREAK_LINES: don't break lines at 76 characters
    -     *     Note: Technically, this makes your encoding non-compliant.
    -     * 
    - *

    - * Example: encodeObject( myObj, Base64.GZIP ) or - *

    - * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) - * - * @param serializableObject The object to encode - * @param options Specified options - * @return The Base64-encoded object - * @see Base64#GZIP - * @see Base64#DONT_BREAK_LINES - * @since 2.0 - */ - public static String encodeObject( java.io.Serializable serializableObject, int options ) - { - // Streams - java.io.ByteArrayOutputStream baos = null; - java.io.OutputStream b64os = null; - java.io.ObjectOutputStream oos = null; - java.util.zip.GZIPOutputStream gzos = null; - - // Isolate options - int gzip = (options & GZIP); - //int dontBreakLines = (options & DONT_BREAK_LINES); - - try - { - // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | options ); - - // GZip? - if( gzip == GZIP ) - { - gzos = new java.util.zip.GZIPOutputStream( b64os ); - oos = new java.io.ObjectOutputStream( gzos ); - } // end if: gzip - else - oos = new java.io.ObjectOutputStream( b64os ); - - oos.writeObject( serializableObject ); - } // end try - catch( java.io.IOException e ) - { - logger.error( Logger.SECURITY_FAILURE, "Problem writing object", e ); - return null; - } // end catch - finally - { - try{ oos.close(); } catch( Exception e ){} - try{ gzos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} - } // end finally - - // Return value according to relevant encoding. - try - { - return new String( baos.toByteArray(), PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException uue) - { - return new String( baos.toByteArray() ); - } // end catch - - } // end encode - - /** * Encodes a byte array into Base64 notation. @@ -650,7 +535,7 @@ public static String encodeBytes( byte[] source ) { return encodeBytes( source, 0, source.length, NO_OPTIONS ); } // end encodeBytes - + /** @@ -675,11 +560,10 @@ public static String encodeBytes( byte[] source ) * @since 2.0 */ public static String encodeBytes( byte[] source, int options ) - { + { return encodeBytes( source, 0, source.length, options ); } // end encodeBytes - - + /** * Encodes a byte array into Base64 notation. * Does not GZip-compress data. @@ -694,8 +578,6 @@ public static String encodeBytes( byte[] source, int off, int len ) { return encodeBytes( source, off, len, NO_OPTIONS ); } // end encodeBytes - - /** * Encodes a byte array into Base64 notation. @@ -725,22 +607,21 @@ public static String encodeBytes( byte[] source, int off, int len, int options ) // Isolate options int dontBreakLines = ( options & DONT_BREAK_LINES ); int gzip = ( options & GZIP ); - + // Compress? if( gzip == GZIP ) { java.io.ByteArrayOutputStream baos = null; java.util.zip.GZIPOutputStream gzos = null; Base64.OutputStream b64os = null; - - + try { // GZip -> Base64 -> ByteArray baos = new java.io.ByteArrayOutputStream(); b64os = new Base64.OutputStream( baos, ENCODE | options ); - gzos = new java.util.zip.GZIPOutputStream( b64os ); - + gzos = new java.util.zip.GZIPOutputStream( b64os ); + gzos.write( source, off, len ); gzos.close(); } // end try @@ -766,17 +647,17 @@ public static String encodeBytes( byte[] source, int off, int len, int options ) return new String( baos.toByteArray() ); } // end catch } // end if: compress - + // Else, don't compress. Better not to use streams at all then. else { // Convert option to boolean in way that code likes it. boolean breakLines = dontBreakLines == 0; - + int len43 = len * 4 / 3; byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding - + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines int d = 0; int e = 0; int len2 = len - 2; @@ -787,7 +668,7 @@ public static String encodeBytes( byte[] source, int off, int len, int options ) lineLength += 4; if( breakLines && lineLength == MAX_LINE_LENGTH ) - { + { outBuff[e+4] = NEW_LINE; e++; lineLength = 0; @@ -800,7 +681,7 @@ public static String encodeBytes( byte[] source, int off, int len, int options ) e += 4; } // end if: some padding needed - + // Return value according to relevant encoding. try { @@ -810,47 +691,42 @@ public static String encodeBytes( byte[] source, int off, int len, int options ) { return new String( outBuff, 0, e ); } // end catch - + } // end else: don't compress - + } // end encodeBytes - - - - /* ******** D E C O D I N G M E T H O D S ******** */ - - + /** * Decodes four bytes from array source * and writes the resulting bytes (up to three of them) * to destination. * The source and destination arrays can be manipulated - * anywhere along their length by specifying + * anywhere along their length by specifying * srcOffset and destOffset. * This method does not check to make sure your arrays * are large enough to accomodate srcOffset + 4 for * the source array or destOffset + 3 for * the destination array. - * This method returns the actual number of bytes that + * This method returns the actual number of bytes that * were converted from the Base64 encoding. - *

    This is the lowest level of the decoding methods with - * all possible parameters.

    - * + *

    This is the lowest level of the decoding methods with + * all possible parameters.

    + * * * @param source the array to convert * @param srcOffset the index where conversion begins * @param destination the array to hold the conversion * @param destOffset the index where output will be put - * @param options alphabet type is pulled from this (standard, url-safe, ordered) + * @param options alphabet type is pulled from this (standard, url-safe, ordered) * @return the number of decoded bytes converted * @since 1.3 */ private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset, int options ) { - byte[] DECODABET = getDecodabet( options ); - + byte[] DECODABET = getDecodabet( options ); + // Example: Dk== if( source[ srcOffset + 2] == EQUALS_SIGN ) { @@ -859,11 +735,11 @@ private static int decode4to3( byte[] source, int srcOffset, byte[] destination, // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); - + destination[ destOffset ] = (byte)( outBuff >>> 16 ); return 1; } - + // Example: DkL= else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { @@ -874,12 +750,12 @@ else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); - + destination[ destOffset ] = (byte)( outBuff >>> 16 ); destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); return 2; } - + // Example: DkLE else { @@ -894,44 +770,41 @@ else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); - + destination[ destOffset ] = (byte)( outBuff >> 16 ); destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); destination[ destOffset + 2 ] = (byte)( outBuff ); return 3; }catch( Exception e){ - + // Remove these after checking -- for context only. // logger.error( Logger.SECURITY_FAILURE, "Problem writing object", e ); // logger.error( Logger.SECURITY_FAILURE, ""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) ); // logger.error( Logger.SECURITY_FAILURE, ""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) ); // logger.error( Logger.SECURITY_FAILURE, ""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) ); // logger.error( Logger.SECURITY_FAILURE, ""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) ); - - // CHECKME: I replaced the 5 separate logger.error() calls above with a single logger.error() call so they can't - // become interleaved with other log entries from other threads. Normally this would have placed log entries - // on separate lines, so I also added line terminators here as well. (Probably don't want it all on one single - // really long log entry, do we?) Anyhow, somebody should check the formatting to ensure that it's - // esthetically pleasing, etc. But this works for me. I'm also OK if you want to remove all the line terminators - // in which case the declaration for EOL should be removed as well. - Kevin Wall - + + // CHECKME: I replaced the 5 separate logger.error() calls above with a single logger.error() call so they can't + // become interleaved with other log entries from other threads. Normally this would have placed log entries + // on separate lines, so I also added line terminators here as well. (Probably don't want it all on one single + // really long log entry, do we?) Anyhow, somebody should check the formatting to ensure that it's + // esthetically pleasing, etc. But this works for me. I'm also OK if you want to remove all the line terminators + // in which case the declaration for EOL should be removed as well. - Kevin Wall + StringBuilder sb = new StringBuilder("Problem writing object:"); sb.append(EOL); sb.append( source[srcOffset] ).append(": ").append( ( DECODABET[ source[ srcOffset ] ] ) ).append(EOL); sb.append( source[srcOffset+1] ).append(": ").append( ( DECODABET[ source[ srcOffset + 1 ] ] ) ).append(EOL); sb.append( source[srcOffset+2] ).append(": ").append( ( DECODABET[ source[ srcOffset + 2 ] ] ) ).append(EOL); sb.append( source[srcOffset+3] ).append(": ").append( ( DECODABET[ source[ srcOffset + 3 ] ] ) ).append(EOL); - + logger.error( Logger.SECURITY_FAILURE, sb.toString(), e ); return -1; } // end catch } } // end decodeToBytes - - - - + /** * Very low-level access to decoding ASCII characters in * the form of a byte array. Does not support automatically @@ -946,12 +819,12 @@ else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) */ public static byte[] decode( byte[] source, int off, int len, int options ) { - byte[] DECODABET = getDecodabet( options ); - + byte[] DECODABET = getDecodabet( options ); + int len34 = len * 3 / 4; byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output int outBuffPosn = 0; - + byte[] b4 = new byte[4]; int b4Posn = 0; int i = 0; @@ -961,7 +834,7 @@ public static byte[] decode( byte[] source, int off, int len, int options ) { sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits sbiDecode = DECODABET[ sbiCrop ]; - + if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better { if( sbiDecode >= EQUALS_SIGN_ENC ) @@ -971,30 +844,27 @@ public static byte[] decode( byte[] source, int off, int len, int options ) { outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); b4Posn = 0; - + // If that was the equals sign, break out of 'for' loop if( sbiCrop == EQUALS_SIGN ) break; } // end if: quartet built - + } // end if: equals sign or better - + } // end if: white space, equals sign or better else { - logger.error( Logger.SECURITY_FAILURE, "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" ); + logger.error( Logger.SECURITY_FAILURE, "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" ); return null; - } // end else: + } // end else: } // each input character - + byte[] out = new byte[ outBuffPosn ]; - System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); + System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); return out; } // end decode - - - - + /** * Decodes data from Base64 notation, automatically * detecting gzip-compressed data and decompressing it. @@ -1004,22 +874,21 @@ public static byte[] decode( byte[] source, int off, int len, int options ) * @since 1.4 */ public static byte[] decode( String s ) - { - return decode( s, NO_OPTIONS ); - } - - + { + return decode( s, NO_OPTIONS ); + } + /** * Decodes data from Base64 notation, automatically * detecting gzip-compressed data and decompressing it. * * @param s the string to decode - * @param options encode options such as URL_SAFE + * @param options encode options such as URL_SAFE * @return the decoded data * @since 1.4 */ public static byte[] decode( String s, int options ) - { + { byte[] bytes; try { @@ -1027,25 +896,24 @@ public static byte[] decode( String s, int options ) } catch( java.io.UnsupportedEncodingException uee ) { - bytes = s.getBytes(); // Uses native encoding + bytes = s.getBytes(); // Uses native encoding // CHECKME: Is this correct? I think it should be a warning instead of an error since nothing // is re-thrown. I do think that *some* sort of logging is in order here especially since UTF-8 should // always be available on all platforms. If it's not, then all bets are off on your runtime env. - Kevin Wall logger.warning( Logger.SECURITY_FAILURE, "Problem decoding string using " + - PREFERRED_ENCODING + "; substituting native platform encoding instead", uee ); + PREFERRED_ENCODING + "; substituting native platform encoding instead", uee ); } - + // Decode bytes = decode( bytes, 0, bytes.length, options ); - - + // Check to see if it's gzip-compressed // GZIP Magic Two-Byte Number: 0x8b1f (35615) if( bytes != null && bytes.length >= 4 ) { - - int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); - if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) + + int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if ( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { java.io.ByteArrayInputStream bais = null; java.util.zip.GZIPInputStream gzis = null; @@ -1081,95 +949,10 @@ public static byte[] decode( String s, int options ) } // end if: gzipped } // end if: bytes.length >= 2 - + return bytes; } // end decode - - - - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * - *

    - * WARNING: Using this method to decode non-validated / - * untrusted data from a string and deserialize it into - * an object can potentially result in remote command - * injection vulnerabilities. Use at your own risk! - *

    IMPORTANT BACKWARD COMPATIBILITY NOTICE
    - * Because this static method can easily be used as an attack vector - * for those passing in deserialized objects, in a manner similar to the - * Apache Commons Collections InvokerTransformer - * issue, we are requiring that the system property - * {@code org.owasp.esapi.enableUnsafeSerialization} - * be set to "true" in order for this method to be successfully invoked. - * We apologize for the inconvenience this may cause in breaking anyone's - * application, but we feel that it is for the greater good. - *

    - * - * @param encodedObject The Base64 data to decode - * @return The decoded and deserialized object - * @since 1.5 - * - * @deprecated Because of security issues, this method will be - * removed from ESAPI in a future release and no substitute - * is planned. Because as of JDK 8 (in 1Q2016) there is - * currently no way to restrict which objects - * ObjectInputStream.readObject() - * may safely deserialize in the general case. Oracle - * may decide to address this deficiency in a future Java - * release, but until they do, there is no simple way for - * a general class library like ESAPI to address this. - */ - @Deprecated - public static Object decodeToObject( String encodedObject ) - { - // We will do better when we attempt this again, allowing for a second argument - // to specify some sort of a collection of white-listed classes. Until then... - // See: http://www.ibm.com/developerworks/library/se-lookahead/ for how-to. - if ( ! "true".equalsIgnoreCase( System.getProperty( ENABLE_UNSAFE_SERIALIZATION ) ) ) { - throw new UnsupportedOperationException( - "Deserialization by Base64.decodeToObject(String) is disabled for security reasons. " + - "To re-enable it, set the system property '" + ENABLE_UNSAFE_SERIALIZATION + "' to 'true'." + - "For details, see: https://github.com/ESAPI/esapi-java-legacy/issues/354"); - } - - // Decode and gunzip if necessary - byte[] objBytes = decode( encodedObject ); - - java.io.ByteArrayInputStream bais = null; - java.io.ObjectInputStream ois = null; - Object obj = null; - - try - { - bais = new java.io.ByteArrayInputStream( objBytes ); - ois = new java.io.ObjectInputStream( bais ); - - obj = ois.readObject(); - } // end try - catch( java.io.IOException e ) - { - logger.error( Logger.SECURITY_FAILURE, "Problem reading object", e ); - obj = null; - } // end catch - catch( java.lang.ClassNotFoundException e ) - { - logger.error( Logger.SECURITY_FAILURE, "Problem reading object", e ); - obj = null; - } // end catch - finally - { - try{ bais.close(); } catch( Exception e ){} - try{ ois.close(); } catch( Exception e ){} - } // end finally - - return obj; - } // end decodeObject - - - /** * Convenience method for encoding data to a file. * @@ -1185,25 +968,24 @@ public static boolean encodeToFile( byte[] dataToEncode, String filename ) Base64.OutputStream bos = null; try { - bos = new Base64.OutputStream( + bos = new Base64.OutputStream( new java.io.FileOutputStream( filename ), Base64.ENCODE ); bos.write( dataToEncode ); success = true; } // end try catch( java.io.IOException e ) { - + success = false; } // end catch: IOException finally { try{ bos.close(); } catch( Exception e ){} } // end finally - + return success; } // end encodeToFile - - + /** * Convenience method for decoding data to a file. * @@ -1219,7 +1001,7 @@ public static boolean decodeToFile( String dataToDecode, String filename ) Base64.OutputStream bos = null; try { - bos = new Base64.OutputStream( + bos = new Base64.OutputStream( new java.io.FileOutputStream( filename ), Base64.DECODE ); bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); success = true; @@ -1232,13 +1014,10 @@ public static boolean decodeToFile( String dataToDecode, String filename ) { try{ bos.close(); } catch( Exception e ){} } // end finally - + return success; } // end decodeToFile - - - - + /** * Convenience method for reading a base64-encoded * file and decoding it. @@ -1259,7 +1038,7 @@ public static byte[] decodeFromFile( String filename ) byte[] buffer = null; int length = 0; int numBytes = 0; - + // Check for size of file if( file.length() > Integer.MAX_VALUE ) { @@ -1267,20 +1046,20 @@ public static byte[] decodeFromFile( String filename ) return null; } // end if: file too big for int index buffer = new byte[ (int)file.length() ]; - + // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( + bis = new Base64.InputStream( + new java.io.BufferedInputStream( new java.io.FileInputStream( file ) ), Base64.DECODE ); - + // Read until done while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) length += numBytes; - + // Save in a variable to return decodedData = new byte[ length ]; System.arraycopy( buffer, 0, decodedData, 0, length ); - + } // end try catch( java.io.IOException e ) { @@ -1290,12 +1069,10 @@ public static byte[] decodeFromFile( String filename ) { try{ if (bis != null ) bis.close(); } catch( Exception e) {} } // end finally - + return decodedData; } // end decodeFromFile - - - + /** * Convenience method for reading a binary file * and base64-encoding it. @@ -1316,19 +1093,19 @@ public static String encodeFromFile( String filename ) byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4),40) ]; // Need max() for math on small files (v2.2.1) int length = 0; int numBytes = 0; - + // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( + bis = new Base64.InputStream( + new java.io.BufferedInputStream( new java.io.FileInputStream( file ) ), Base64.ENCODE ); - + // Read until done while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) length += numBytes; - + // Save in a variable to return encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); - + } // end try catch( java.io.IOException e ) { @@ -1338,13 +1115,10 @@ public static String encodeFromFile( String filename ) { try{ bis.close(); } catch( Exception e) {} } // end finally - + return encodedData; } // end encodeFromFile - - - - + /** * Reads infile and encodes it to outfile. * @@ -1359,9 +1133,9 @@ public static boolean encodeFileToFile( String infile, String outfile ) java.io.InputStream in = null; java.io.OutputStream out = null; try{ - in = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( infile ) ), + in = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( infile ) ), Base64.ENCODE ); out = new java.io.BufferedOutputStream( new java.io.FileOutputStream( outfile ) ); byte[] buffer = new byte[65536]; // 64K @@ -1376,12 +1150,10 @@ public static boolean encodeFileToFile( String infile, String outfile ) try{ in.close(); } catch( Exception exc ){} try{ out.close(); } catch( Exception exc ){} } // end finally - + return success; } // end encodeFileToFile - - - + /** * Reads infile and decodes it to outfile. * @@ -1396,9 +1168,9 @@ public static boolean decodeFileToFile( String infile, String outfile ) java.io.InputStream in = null; java.io.OutputStream out = null; try{ - in = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( infile ) ), + in = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( infile ) ), Base64.DECODE ); out = new java.io.BufferedOutputStream( new java.io.FileOutputStream( outfile ) ); byte[] buffer = new byte[65536]; // 64K @@ -1413,15 +1185,12 @@ public static boolean decodeFileToFile( String infile, String outfile ) try{ in.close(); } catch( Exception exc ){} try{ out.close(); } catch( Exception exc ){} } // end finally - + return success; } // end decodeFileToFile - - + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ - - - + /** * A {@link Base64.InputStream} will read data from another * java.io.InputStream, given in the constructor, @@ -1439,10 +1208,9 @@ public static class InputStream extends java.io.FilterInputStream private int numSigBytes; // Number of meaningful bytes in the buffer private int lineLength; private boolean breakLines; // Break lines at less than 80 characters - private int options; // Record options used to create the stream. - private byte[] decodabet; // Local copies to avoid extra method calls - - + private int options; // Record options used to create the stream. + private byte[] decodabet; // Local copies to avoid extra method calls + /** * Constructs a {@link Base64.InputStream} in DECODE mode. * @@ -1450,11 +1218,10 @@ public static class InputStream extends java.io.FilterInputStream * @since 1.3 */ public InputStream( java.io.InputStream in ) - { + { this( in, DECODE ); } // end constructor - - + /** * Constructs a {@link Base64.InputStream} in * either ENCODE or DECODE mode. @@ -1477,7 +1244,7 @@ public InputStream( java.io.InputStream in ) * @since 2.0 */ public InputStream( java.io.InputStream in, int options ) - { + { super( in ); this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; this.encode = (options & ENCODE) == ENCODE; @@ -1485,10 +1252,10 @@ public InputStream( java.io.InputStream in, int options ) this.buffer = new byte[ bufferLength ]; this.position = -1; this.lineLength = 0; - this.options = options; // Record for later, mostly to determine which alphabet to use - this.decodabet = getDecodabet(options); + this.options = options; // Record for later, mostly to determine which alphabet to use + this.decodabet = getDecodabet(options); } // end constructor - + /** * Reads enough of the input stream to convert * to/from Base64 and returns the next byte. @@ -1497,8 +1264,8 @@ public InputStream( java.io.InputStream in, int options ) * @throws java.io.IOException * @since 1.3 */ - public int read() throws java.io.IOException - { + public int read() throws java.io.IOException + { // Do we need to get data? if( position < 0 ) { @@ -1509,26 +1276,26 @@ public int read() throws java.io.IOException for( int i = 0; i < 3; i++ ) { try - { + { int b = in.read(); - + // If end of stream, b is -1. if( b >= 0 ) { b3[i] = (byte)b; numBinaryBytes++; } // end if: not end of stream - + } // end try: read catch( java.io.IOException e ) - { + { // Only a problem if we got no data at all. if( i == 0 ) throw e; - + } // end catch } // end for: each needed input byte - + if( numBinaryBytes > 0 ) { encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); @@ -1540,7 +1307,7 @@ public int read() throws java.io.IOException return -1; } // end else } // end if: encoding - + // Else decoding else { @@ -1552,13 +1319,13 @@ public int read() throws java.io.IOException int b = 0; do{ b = in.read(); } while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); - + if( b < 0 ) break; // Reads a -1 if end of stream - + b4[i] = (byte)b; } // end for: each needed input byte - + if( i == 4 ) { numSigBytes = decode4to3( b4, 0, buffer, 0, options ); @@ -1571,18 +1338,18 @@ else if( i == 0 ){ { // Must have broken out from above. throw new java.io.IOException( "Improperly padded Base64 input." ); - } // end - + } // end + } // end else: decode } // end else: get data - + // Got data? if( position >= 0 ) { // End of relevant data? if( /*!encode &&*/ position >= numSigBytes ) return -1; - + if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { lineLength = 0; @@ -1593,7 +1360,7 @@ else if( i == 0 ){ lineLength++; // This isn't important when decoding // but throwing an extra "if" seems // just as wasteful. - + int b = buffer[ position++ ]; if( position >= bufferLength ) @@ -1603,16 +1370,15 @@ else if( i == 0 ){ // intended to be unsigned. } // end else } // end if: position >= 0 - + // Else error else - { + { // When JDK1.4 is more accepted, use an assertion here. throw new java.io.IOException( "Error in Base64 code reading stream." ); } // end else } // end read - - + /** * Calls {@link #read()} repeatedly until the end of stream * is reached or len bytes are read. @@ -1633,10 +1399,10 @@ public int read( byte[] dest, int off, int len ) throws java.io.IOException for( i = 0; i < len; i++ ) { b = read(); - + //if( b < 0 && i == 0 ) // return -1; - + if( b >= 0 ) dest[off + i] = (byte)b; else if( i == 0 ) @@ -1646,18 +1412,12 @@ else if( i == 0 ) } // end for: each byte read return i; } // end read - + } // end inner class InputStream - - - - - - + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ - - - + /** * A {@link Base64.OutputStream} will write data to another * java.io.OutputStream, given in the constructor, @@ -1676,9 +1436,9 @@ public static class OutputStream extends java.io.FilterOutputStream private boolean breakLines; private byte[] b4; // Scratch used in a few places private boolean suspendEncoding; - private int options; // Record for later - private byte[] decodabet; // Local copies to avoid extra method calls - + private int options; // Record for later + private byte[] decodabet; // Local copies to avoid extra method calls + /** * Constructs a {@link Base64.OutputStream} in ENCODE mode. * @@ -1686,11 +1446,10 @@ public static class OutputStream extends java.io.FilterOutputStream * @since 1.3 */ public OutputStream( java.io.OutputStream out ) - { + { this( out, ENCODE ); } // end constructor - - + /** * Constructs a {@link Base64.OutputStream} in * either ENCODE or DECODE mode. @@ -1712,7 +1471,7 @@ public OutputStream( java.io.OutputStream out ) * @since 1.3 */ public OutputStream( java.io.OutputStream out, int options ) - { + { super( out ); this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; this.encode = (options & ENCODE) == ENCODE; @@ -1722,11 +1481,10 @@ public OutputStream( java.io.OutputStream out, int options ) this.lineLength = 0; this.suspendEncoding = false; this.b4 = new byte[4]; - this.options = options; - this.decodabet = getDecodabet(options); + this.options = options; + this.decodabet = getDecodabet(options); } // end constructor - - + /** * Writes the byte to the output stream after * converting to/from Base64 notation. @@ -1748,7 +1506,7 @@ public void write(int theByte) throws java.io.IOException super.out.write( theByte ); return; } // end if: supsended - + // Encode? if( encode ) { @@ -1789,11 +1547,9 @@ else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) } // end else: not white space either } // end else: decoding } // end write - - - + /** - * Calls {@link #write(int)} repeatedly until len + * Calls {@link #write(int)} repeatedly until len * bytes are written. * * @param theBytes array from which to read bytes @@ -1810,22 +1566,20 @@ public void write( byte[] theBytes, int off, int len ) throws java.io.IOExceptio super.out.write( theBytes, off, len ); return; } // end if: supsended - + for( int i = 0; i < len; i++ ) { write( theBytes[ off + i ] ); } // end for: each byte written - + } // end write - - - + /** * Method added by PHIL. [Thanks, PHIL. -Rob] * This pads the buffer without closing the stream. * @throws java.io.IOException */ - public void flushBase64() throws java.io.IOException + public void flushBase64() throws java.io.IOException { if( position > 0 ) { @@ -1842,9 +1596,8 @@ public void flushBase64() throws java.io.IOException } // end flush - - /** - * Flushes and closes (I think, in the superclass) the stream. + /** + * Flushes and closes (I think, in the superclass) the stream. * * @throws java.io.IOException * @since 1.3 @@ -1857,13 +1610,11 @@ public void close() throws java.io.IOException // 2. Actually close the stream // Base class both flushes and closes. super.close(); - + buffer = null; out = null; } // end close - - - + /** * Suspends encoding of the stream. * May be helpful if you need to embed a piece of @@ -1872,13 +1623,12 @@ public void close() throws java.io.IOException * @throws java.io.IOException * @since 1.5.1 */ - public void suspendEncoding() throws java.io.IOException + public void suspendEncoding() throws java.io.IOException { flushBase64(); this.suspendEncoding = true; } // end suspendEncoding - - + /** * Resumes encoding of the stream. * May be helpful if you need to embed a piece of @@ -1890,10 +1640,7 @@ public void resumeEncoding() { this.suspendEncoding = false; } // end resumeEncoding - - - + } // end inner class OutputStream - - + } // end class Base64 diff --git a/src/test/java/org/owasp/esapi/reference/EncoderTest.java b/src/test/java/org/owasp/esapi/reference/EncoderTest.java index 631dd96b2..255519752 100644 --- a/src/test/java/org/owasp/esapi/reference/EncoderTest.java +++ b/src/test/java/org/owasp/esapi/reference/EncoderTest.java @@ -661,60 +661,6 @@ public void testDecodeFromBase64() { } } - /** - * Test of Base64.decodeToObject() method. Should really be put into a - * separate Base64Test.java class, but this method has been deprecated - * so hopefully, we can kill it off soon. - */ - public void testBase64decodToObject() { - try { - System.out.println("testBase64decodeToObject"); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream dout = new ObjectOutputStream(baos); - - // If you don't get the joke, google John Draper. (And, BTW, you should - // be ashamed of yourself if you call yourself a hacker!) - String cerealizeThis = new String("Cap'n Crunch - every hacker's favorite cereal"); - dout.writeObject( cerealizeThis ); - byte[] serializedData = baos.toByteArray(); - dout.close(); - - String b64serialized = Base64.encodeBytes( serializedData ); - final String propName = Base64.ENABLE_UNSAFE_SERIALIZATION; - - // Make sure the property is not set. - try { System.clearProperty( propName ); } catch(Throwable t) { ; } - String capnCrunch = null; - - try { - capnCrunch = (String)Base64.decodeToObject( b64serialized ); - fail("Case 1: Did not throw UnsupportedOperationException"); - } catch(UnsupportedOperationException uoex) { - ; // Expected case - } - - try { - System.setProperty( propName, "false" ); - capnCrunch = (String)Base64.decodeToObject( b64serialized ); - fail("Case 2: Did not throw UnsupportedOperationException"); - } catch(UnsupportedOperationException uoex) { - ; // Expected case - } - - try { - // This case should work. - System.setProperty( propName, "true" ); - capnCrunch = (String)Base64.decodeToObject( b64serialized ); - assertTrue( capnCrunch.equals( cerealizeThis ) ); - } catch(Throwable t) { - fail("Case 3: Caught unexpected exception: " + t); - } - } catch(Throwable t) { - fail("Caught unexpected exception: " + t); - } - } - /** * Test of WindowsCodec */ From 32dd026374de4db9527ff0b50778c74078a7cd87 Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Mon, 8 Oct 2018 20:37:36 -0400 Subject: [PATCH 113/709] Update README.md Add short paragraph of how this GitHub issues is not appropriate forum for asking questions about ESAPI. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c36670d59..09f0df50d 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ When reporting an issue, please be clear and try to ensure that the ESAPI develo ### Find an Issue? If you have found a bug, then create an issue on the esapi-legacy-java repo: https://github.com/ESAPI/esapi-java-legacy/issues +NOTE: Please do NOT use GitHub issues to ask questions about ESAPI. If you wish to do this, post to either of the 2 mailing lists found at the bottom of this page. If we find questions as GitHub issues, we simply will close them and direct you to do this anyhow. + ### Find a Vulnerability? If you have found a vulnerability in ESAPI legacy, first search the issues list (see above) to see if it has already been reported. If it has not, then please contact both Kevin W. Wall (kevin.w.wall at gmail.com) and Matt Seil (matt.seil at owasp.org) directly. Please do not report vulnerabilities via GitHub issues or via the ESAPI mailing lists as we wish to keep our users secure while a patch is implemented and deployed. If you wish to be acknowledged for finding the vulnerability, then please follow this process. (Eventually, we would like to have BugCrowd handle this, but that's still a ways off.) Also, when you post the email describing the vulnerability, please do so from an email address that you usually monitor. From bbf431afab90b5b7f0012b13bcbc4f96cbeeb927 Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Mon, 8 Oct 2018 23:59:52 -0400 Subject: [PATCH 114/709] Log special (#451) * Close issue #448 * Close issue #444. Delete deprecated decodeToObject() method and 2 related encodeObject() methods. General whitespace clean-up. Delete EncoderTest.testBase64decodToObject() method which is no longer relevant. * Close issue #385. Close issue #386. NOTE: Was unwilling to comply with request in these 2 issues to make logSpecial() 'protected'. Could not do so without introducing potential security vulnerabilities. However, since the intent of the user submitting these 2 GitHub issues was only to override logSpecial() in order to completely suppress the output from them to stdout or stderr, I have arranged it so that setting the System property 'org.owasp.esapi.logSpecial.discard' to 'true' will do exactly this...it will suppress all output from logSpecial. (Also, the calls to System.err.println() and System.out.println() have been replaced by calls to logSpecial(), which will log to System.out if not suppressed. So the end result is all or nothing. I suspect that most will keep the default behavior, which is to print to System.out rather than suppressing all output. --- .../AbstractPrioritizedPropertyLoader.java | 33 +++++++++- .../StandardEsapiPropertyLoader.java | 4 +- .../DefaultSecurityConfiguration.java | 62 ++++++++++++++++--- 3 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/owasp/esapi/configuration/AbstractPrioritizedPropertyLoader.java b/src/main/java/org/owasp/esapi/configuration/AbstractPrioritizedPropertyLoader.java index 75e6497c7..61c5c7d26 100644 --- a/src/main/java/org/owasp/esapi/configuration/AbstractPrioritizedPropertyLoader.java +++ b/src/main/java/org/owasp/esapi/configuration/AbstractPrioritizedPropertyLoader.java @@ -44,13 +44,13 @@ public String name() { /** * Initializes properties object and fills it with data from configuration file. */ - protected void initProperties() { + private void initProperties() { properties = new Properties(); File file = new File(filename); if (file.exists() && file.isFile()) { loadPropertiesFromFile(file); } else { - System.err.println("Configuration file " + filename + " does not exist"); + logSpecial("Configuration file " + filename + " does not exist"); } } @@ -59,4 +59,33 @@ protected void initProperties() { * @param file */ protected abstract void loadPropertiesFromFile(File file); + + /** + * Used to log errors to the console during the loading of the properties file itself. Can't use + * standard logging in this case, since the Logger may not be initialized yet. Output is sent to + * {@code PrintStream} {@code System.out}. Output is discarded if the {@code System} property + * "org.owasp.esapi.logSpecial.discard" is set to {@code true}. + * + * @param msg The message to log to the console. + * @param t Associated exception that was caught. + */ + protected final void logSpecial(String msg, Throwable t) { + // Note: It is really distasteful to tie this class to DefaultSecurityConfiguration + // like this, but the alternative is to move the logSpecial() and + // logToStdout() some utilities class and that is even more + // distasteful because it may encourage people to use these. -kwwall + org.owasp.esapi.reference.DefaultSecurityConfiguration.logToStdout(msg, t); + } + + /** + * Used to log errors to the console during the loading of the properties file itself. Can't use + * standard logging in this case, since the Logger may not be initialized yet. Output is sent to + * {@code PrintStream} {@code System.out}. Output is discarded if the {@code System} property + * "org.owasp.esapi.logSpecial.discard" is set to {@code true}. + * + * @param msg The message to log to the console. + */ + protected final void logSpecial(String msg) { + logSpecial(msg, null); + } } diff --git a/src/main/java/org/owasp/esapi/configuration/StandardEsapiPropertyLoader.java b/src/main/java/org/owasp/esapi/configuration/StandardEsapiPropertyLoader.java index 6a896a2cd..850b16905 100644 --- a/src/main/java/org/owasp/esapi/configuration/StandardEsapiPropertyLoader.java +++ b/src/main/java/org/owasp/esapi/configuration/StandardEsapiPropertyLoader.java @@ -90,13 +90,13 @@ protected void loadPropertiesFromFile(File file) { input = new FileInputStream(file); properties.load(input); } catch (IOException ex) { - System.err.println("Loading " + file.getName() + " via file I/O failed. Exception was: " + ex); + logSpecial("Loading " + file.getName() + " via file I/O failed.", ex); } finally { if (input != null) { try { input.close(); } catch (IOException e) { - System.err.println("Could not close stream"); + logSpecial("Could not close stream"); } } } diff --git a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java index 403fd4922..504372037 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java @@ -153,6 +153,18 @@ public static SecurityConfiguration getInstance() { public static final String VALIDATION_PROPERTIES_MULTIVALUED = "Validator.ConfigurationFile.MultiValued"; public static final String ACCEPT_LENIENT_DATES = "Validator.AcceptLenientDates"; + /** + * Special {@code System} property that, if set to {@code true}, will + * disable logging from {@code DefaultSecurityConfiguration.logToStdout()} + * methods, which is called from various {@code logSpecial()} methods. + * @see org.owasp.esapi.reference.DefaultSecurityConfiguration#logToStdout(String msg, Throwable t) + * @see org.owasp.esapi.reference.DefaultSecurityConfiguration#logToStdout(String msg) + */ + public static final String DISCARD_LOGSPECIAL = "org.owasp.esapi.logSpecial.discard"; + + // We assume that this does not change in the middle of processing the + // ESAPI.properties files and thus only fetch its value once. + private static final String logSpecialValue = System.getProperty(DISCARD_LOGSPECIAL, "false"); /** @@ -702,34 +714,64 @@ private Properties loadConfigurationFromClasspath(String fileName) throws Illega return result; } + /** + * Log to standard output (i.e., {@code System.out}. This method is + * synchronized to reduce the possibility of interleaving the message + * output (since the {@code System.out} {@code PrintStream} is buffered) + * it invoked from multiple threads. Output is discarded if the + * {@code System} property "org.owasp.esapi.logSpecial.discard" is set to + * {@code true}. + * + * @param msg Message to be logged. + * @param t Associated exception that was caught. The class name and + * exception message is also logged. + * @see #logToStdout(String msg) + */ + public final synchronized static void logToStdout(String msg, Throwable t) { + // Note that this class was made final because it is called from this class' + // CTOR and we want to prohibit someone from easily doing sneaky + // things like subclassing this class and inserting a malicious code as a + // shim. Of course, really in hindsight, this entire class should have been + // declared 'final', but doing so at this point would likely break someone's + // code, including possibly some of our own test code. But since this is a + // new method, we can get away with it here. + boolean discard = logSpecialValue.trim().equalsIgnoreCase("true"); + if ( discard ) { + return; // Output is discarded! + } + if ( t == null ) { + System.out.println("ESAPI: " + msg); + } else { + System.out.println("ESAPI: " + msg + + ". Caught " + t.getClass().getName() + + "; exception message was: " + t); + } + } + /** * Used to log errors to the console during the loading of the properties file itself. Can't use * standard logging in this case, since the Logger may not be initialized yet. Output is sent to - * {@code PrintStream} {@code System.out}. + * {@code PrintStream} {@code System.out}. Output is discarded if the {@code System} property + * "org.owasp.esapi.logSpecial.discard" is set to {@code true}. * * @param message The message to send to the console. * @param e The error that occurred. (This value printed via {@code e.toString()}.) */ private void logSpecial(String message, Throwable e) { - StringBuffer msg = new StringBuffer(message); - if (e != null) { - msg.append(" Exception was: ").append( e.toString() ); - } - System.out.println( msg.toString() ); - // if ( e != null) e.printStackTrace(); // TODO ??? Do we want this? + logToStdout(message, e); } /** * Used to log errors to the console during the loading of the properties file itself. Can't use * standard logging in this case, since the Logger may not be initialized yet. Output is sent to - * {@code PrintStream} {@code System.out}. + * {@code PrintStream} {@code System.out}. Output is discarded if the {@code System} property + * "org.owasp.esapi.logSpecial.discard" is set to {@code true}. * * @param message The message to send to the console. */ private void logSpecial(String message) { - System.out.println(message); + logToStdout(message, null); } - /** * {@inheritDoc} */ From 74a38b0096f68a2dcd17427f054d5fe31c5a1335 Mon Sep 17 00:00:00 2001 From: Jacky Date: Sat, 3 Nov 2018 10:16:58 +0800 Subject: [PATCH 115/709] #304 encodeForCSS breaks color values (#453) --- .../java/org/owasp/esapi/reference/DefaultEncoder.java | 2 +- src/test/java/org/owasp/esapi/reference/EncoderTest.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java b/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java index 8100632a7..9a0eefa1a 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java @@ -88,7 +88,7 @@ public static Encoder getInstance() { */ private final static char[] IMMUNE_HTML = { ',', '.', '-', '_', ' ' }; private final static char[] IMMUNE_HTMLATTR = { ',', '.', '-', '_' }; - private final static char[] IMMUNE_CSS = {}; + private final static char[] IMMUNE_CSS = { '#' }; private final static char[] IMMUNE_JAVASCRIPT = { ',', '.', '_' }; private final static char[] IMMUNE_VBSCRIPT = { ',', '.', '_' }; private final static char[] IMMUNE_XML = { ',', '.', '-', '_', ' ' }; diff --git a/src/test/java/org/owasp/esapi/reference/EncoderTest.java b/src/test/java/org/owasp/esapi/reference/EncoderTest.java index 255519752..cdef5e06a 100644 --- a/src/test/java/org/owasp/esapi/reference/EncoderTest.java +++ b/src/test/java/org/owasp/esapi/reference/EncoderTest.java @@ -382,10 +382,14 @@ public void testEncodeForCSS() { assertEquals(null, instance.encodeForCSS(null)); assertEquals("\\3c script\\3e ", instance.encodeForCSS(""); verify(servResp, times(0)).setHeader("foo", ""); } - + @Test public void testInvalidDateHeader(){ HttpServletResponse servResp = mock(HttpServletResponse.class); @@ -91,7 +662,7 @@ public void testInvalidDateHeader(){ resp.addDateHeader("Foo\\r\\n", currentTime); verify(servResp, times(0)).addDateHeader("Foo", currentTime); } - + @Test public void testAddHeaderInvalidValueLength(){ //refactor this to use a spy. @@ -102,7 +673,7 @@ public void testAddHeaderInvalidValueLength(){ resp.addHeader("Foo", TestUtils.generateStringOfLength(4097)); verify(servResp, times(0)).addHeader("Foo", "bar"); } - + @Test public void testAddHeaderInvalidKeyLength(){ HttpServletResponse servResp = mock(HttpServletResponse.class); @@ -110,7 +681,7 @@ public void testAddHeaderInvalidKeyLength(){ resp.addHeader(TestUtils.generateStringOfLength(257), "bar"); verify(servResp, times(0)).addHeader("Foo", "bar"); } - + @Test public void testAddIntHeader(){ HttpServletResponse servResp = mock(HttpServletResponse.class); @@ -118,7 +689,7 @@ public void testAddIntHeader(){ resp.addIntHeader("aaaa", 4); verify(servResp, times(1)).addIntHeader("aaaa", 4); } - + @Test public void testAddInvalidIntHeader(){ HttpServletResponse servResp = mock(HttpServletResponse.class); @@ -126,7 +697,7 @@ public void testAddInvalidIntHeader(){ resp.addIntHeader(TestUtils.generateStringOfLength(257), Integer.MIN_VALUE); verify(servResp, times(0)).addIntHeader(TestUtils.generateStringOfLength(257), Integer.MIN_VALUE); } - + @Test public void testContainsHeader(){ HttpServletResponse servResp = new MockHttpServletResponse(); @@ -137,7 +708,7 @@ public void testContainsHeader(){ verify(servResp, times(1)).addIntHeader("aaaa", Integer.MIN_VALUE); assertEquals(true, servResp.containsHeader("aaaa")); } - + @Test public void testAddValidCookie(){ HttpServletResponse servResp = new MockHttpServletResponse(); @@ -148,7 +719,7 @@ public void testAddValidCookie(){ cookie.setMaxAge(5000); Mockito.doCallRealMethod().when(spyResp).addCookie(cookie); spyResp.addCookie(cookie); - + /* * We're indirectly testing our class. Since it ultimately * delegates to HttpServletResponse.addHeader, we're actually @@ -158,7 +729,7 @@ public void testAddValidCookie(){ */ verify(servResp, times(1)).addHeader("Set-Cookie", "Foo=aaaaaaaaaa; Max-Age=5000; Secure; HttpOnly"); } - + @Test public void testAddValidCookieWithDomain(){ HttpServletResponse servResp = new MockHttpServletResponse(); @@ -172,7 +743,7 @@ public void testAddValidCookieWithDomain(){ spyResp.addCookie(cookie); verify(servResp, times(1)).addHeader("Set-Cookie", "Foo=aaaaaaaaaa; Domain=evil.com; Secure; HttpOnly"); } - + @Test public void testAddValidCookieWithPath(){ HttpServletResponse servResp = new MockHttpServletResponse(); @@ -186,7 +757,7 @@ public void testAddValidCookieWithPath(){ spyResp.addCookie(cookie); verify(servResp, times(1)).addHeader("Set-Cookie", "Foo=aaaaaaaaaa; Domain=evil.com; Path=/foo/bar; Secure; HttpOnly"); } - + @Test public void testAddInValidCookie(){ HttpServletResponse servResp = new MockHttpServletResponse(); @@ -195,11 +766,11 @@ public void testAddInValidCookie(){ SecurityWrapperResponse spyResp = spy(resp); Cookie cookie = new Cookie("Foo", TestUtils.generateStringOfLength(5000)); Mockito.doCallRealMethod().when(spyResp).addCookie(cookie); - + spyResp.addCookie(cookie); verify(servResp, times(0)).addHeader("Set-Cookie", "Foo=" + TestUtils.generateStringOfLength(5000) + "; Secure; HttpOnly"); } - + @Test public void testSendError() throws Exception{ HttpServletResponse servResp = new MockHttpServletResponse(); @@ -208,10 +779,10 @@ public void testSendError() throws Exception{ SecurityWrapperResponse spyResp = spy(resp); Mockito.doCallRealMethod().when(spyResp).sendError(200); spyResp.sendError(200); - + verify(servResp, times(1)).sendError(200, "HTTP error code: 200");; } - + @Test public void testSendStatus() throws Exception{ HttpServletResponse servResp = new MockHttpServletResponse(); @@ -220,10 +791,10 @@ public void testSendStatus() throws Exception{ SecurityWrapperResponse spyResp = spy(resp); Mockito.doCallRealMethod().when(spyResp).setStatus(200);; spyResp.setStatus(200); - + verify(servResp, times(1)).setStatus(200);; } - + @Test public void testSendStatusWithString() throws Exception{ HttpServletResponse servResp = new MockHttpServletResponse(); @@ -232,7 +803,7 @@ public void testSendStatusWithString() throws Exception{ SecurityWrapperResponse spyResp = spy(resp); Mockito.doCallRealMethod().when(spyResp).setStatus(200, "foo");; spyResp.setStatus(200, "foo"); - + verify(servResp, times(1)).sendError(200, "foo");; } } From 422ba9f27ad1956682a7b0903c9f8119e6cd598d Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Sat, 8 Feb 2020 22:34:19 -0500 Subject: [PATCH 250/709] Issue 521 (#535) * Add formal deprecation policy. * Add property Validator.ValidationRule.getValid.ignore509Fix. Truly a kludge if there every was one. * Add static field VALIDATOR_IGNORE509 for kludge. * Address issue #521 by splitting out failing JUnit test cases, testGetValidSafeHTML() and testIsValidSafeHTML() into separate test files. New files will be src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleLogsTest.java and src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleThrowsTest.java * Address issue #521 by kludge to add backward-compatibility flag to restore the old behavior accidentally broken by the changes to address issue #509. * Javadoc clarifications to address issue #521 for behavior broken by issue #509 commits. * New test files for GitHub issue #521; initial commit. * Add additional sentence about ESAPI deprecation policy. * Since we've deprecated Log4J 1 logger, let's go all in and remove it from the default ESAPI.Logger in ESAPI.properties as well. * Changed new property name from the horribly named Validator.ValidationRule.getValid.ignore509Fix to the more appropriately named Validator.HtmlValidationAction whose possible values are "clean" (for legacy behavior) and "throw" for the new behavior as fixed by GitHub issue #509. If the property is not encountered, it is treated as if "clean" had been specified, i.e., the legacy behavior. * Added string constant for new property, Validator.HtmlValidationAction. * Changes in keeping with new prop name, Validator.HtmlValidationAction * Rename JUnit test file. * Rename JUnit test class to sync w/ new file name. * Convert from JUnit 3 to JUnit 4. --- README.md | 4 + configuration/esapi/ESAPI.properties | 40 ++++- .../java/org/owasp/esapi/ValidationRule.java | 20 ++- .../DefaultSecurityConfiguration.java | 3 +- .../validation/HTMLValidationRule.java | 60 ++++++- .../owasp/esapi/reference/ValidatorTest.java | 63 +------ .../HTMLValidationRuleCleanTest.java | 158 +++++++++++++++++ .../HTMLValidationRuleThrowsTest.java | 167 ++++++++++++++++++ src/test/resources/esapi/ESAPI.properties | 41 ++++- 9 files changed, 484 insertions(+), 72 deletions(-) create mode 100644 src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleCleanTest.java create mode 100644 src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleThrowsTest.java diff --git a/README.md b/README.md index e6701c280..f7c38d99d 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ The default branch for ESAPI legacy is now the 'develop' branch (rather than the # Where can I find ESAPI 3.x? https://github.com/ESAPI/esapi-java +# ESAPI Deprecation Policy +Unless we unintentionally screw-up, our intent is to keep classes, methods, and/or fields whihc have been annotated as "@deprecated" for a minimum of two (2) years or until the next major release number (e.g., 3.x as of now), which ever comes first, before we remove them. +Note that this policy does not apply to classes under the **org.owasp.esapi.reference** package. You are not expected to be using such classes directly in your code. + # Contributing to ESAPI legacy ## How can I contribute or help with fix bugs? Fork and submit a pull request! Simple as pi! We generally only accept bug fixes, not new features because as a legacy project, we don't intend on adding new features, although we may make exceptions. If you wish to propose a new feature, the best place to discuss it is via the ESAPI-DEV mailing list mentioned below. Note that we vet all pull requests, including coding style of any contributions; use the same coding style found in the files you are already editing. diff --git a/configuration/esapi/ESAPI.properties b/configuration/esapi/ESAPI.properties index 20421a0bd..7ac0fb9d3 100644 --- a/configuration/esapi/ESAPI.properties +++ b/configuration/esapi/ESAPI.properties @@ -67,8 +67,9 @@ ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector # Log4JFactory Requires log4j.xml or log4j.properties in classpath - http://www.laliluna.de/log4j-tutorial.html -ESAPI.Logger=org.owasp.esapi.logging.log4j.Log4JLogFactory -#ESAPI.Logger=org.owasp.esapi.reference.JavaLogFactory +# Note that this is now considered deprecated! +#ESAPI.Logger=org.owasp.esapi.logging.log4j.Log4JLogFactory +ESAPI.Logger=org.owasp.esapi.logging.java.JavaLogFactory # To use the new SLF4J logger in ESAPI (see GitHub issue #129), set # ESAPI.Logger=org.owasp.esapi.logging.slf4j.Slf4JLogFactory # and do whatever other normal SLF4J configuration that you normally would do for your application. @@ -499,3 +500,38 @@ Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$ # Validation of dates. Controls whether or not 'lenient' dates are accepted. # See DataFormat.setLenient(boolean flag) for further details. Validator.AcceptLenientDates=false + +# ~~~~~ Important Note ~~~~~ +# This is a workaround to make sure that a commit to address GitHub issue #509 +# doesn't accidentally break someone's production code. So essentially what we +# are doing is to reverting back to the previous possibly buggy (by +# documentation intent at least), but, by now, expected legacy behavior. +# Prior to the code changes for issue #509, if invalid / malicious HTML input was +# observed, AntiSamy would simply attempt to sanitize (cleanse) it and it would +# only be logged. However, the code change made ESAPI comply with its +# documentation, which stated that a ValidationException should be thrown in +# such cases. Unfortunately, changing this behavior--especially when no one is +# 100% certain that the documentation was correct--could break existing code +# using ESAPI so after a lot of debate, issue #521 was created to restore the +# previous behavior, but still allow the documented behavior. (We did this +# because it wasn't really causing an security issues since AntiSamy would clean +# it up anyway and we value backward compatibility as long as it doesn't clearly +# present security vulnerabilities.) +# More defaults about this are written up under GitHub issue #521 and +# the pull request it references. Future major releases of ESAPI (e.g., ESAPI 3.x) +# will not support this previous behavior, but it will remain for ESAPI 2.x. +# Set this to 'throw' if you want the originally intended behavior of throwing +# that was fixed via issue #509. Set to 'clean' if you want want the HTML input +# sanitized instead. +# +# Possible values: +# clean -- Use the legacy behavior where unsafe HTML input is logged and the +# sanitized (i.e., clean) input as determined by AntiSamy and your +# AntiSamy rules is returned. This is the default behavior if this +# new property is not found. +# throw -- The new, presumably correct and originally intended behavior where +# a ValidationException is thrown when unsafe HTML input is +# encountered. +# +#Validator.HtmlValidationAction=clean +Validator.HtmlValidationAction=throw diff --git a/src/main/java/org/owasp/esapi/ValidationRule.java b/src/main/java/org/owasp/esapi/ValidationRule.java index 25cc95ac1..8f186a54b 100644 --- a/src/main/java/org/owasp/esapi/ValidationRule.java +++ b/src/main/java/org/owasp/esapi/ValidationRule.java @@ -15,14 +15,23 @@ public interface ValidationRule { * the value to be parsed * @return a validated value * @throws ValidationException - * if any validation rules fail + * if any validation rules fail, except if the + * {@code ESAPI.properties}> property + * "Validator.ValidationRule.getValid.ignore509Fix" is set to + * {@code true}, which is the default behavior for ESAPI 2.x + * releases. See + * {@link https://github.com/ESAPI/esapi-java-legacy/issues/509} + * and {@link https://github.com/ESAPI/esapi-java-legacy/issues/521} + * for futher details. + * + * @see #getValid(String context, String input, ValidationErrorList errorList) */ Object getValid(String context, String input) throws ValidationException; /** - * Whether or not a valid valid can be null. getValid will throw an - * Exception and getSafe will return the default value if flag is set to + * Whether or not a valid valid can be null. {@code getValid} will throw an + * Exception and {#code getSafe} will return the default value if flag is set to * true * * @param flag @@ -59,7 +68,8 @@ Object getValid(String context, String input, ValidationErrorList errorList) throws ValidationException; /** - * Try to call get valid, then call sanitize, finally return a default value + * Try to call {@code getvalid}, then call a 'sanitize' method for sanitization (if one exists), + * finally return a default value. */ Object getSafe(String context, String input); @@ -78,4 +88,4 @@ Object getValid(String context, String input, */ String whitelist(String input, Set list); -} \ No newline at end of file +} diff --git a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java index a91e0e9b7..a9fd89cc9 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java @@ -114,7 +114,7 @@ public static SecurityConfiguration getInstance() { public static final String DIGITAL_SIGNATURE_ALGORITHM = "Encryptor.DigitalSignatureAlgorithm"; public static final String DIGITAL_SIGNATURE_KEY_LENGTH = "Encryptor.DigitalSignatureKeyLength"; // ==================================// - // New in ESAPI Java 2.0 // + // New in ESAPI Java 2.x // // ================================= // public static final String PREFERRED_JCE_PROVIDER = "Encryptor.PreferredJCEProvider"; public static final String CIPHER_TRANSFORMATION_IMPLEMENTATION = "Encryptor.CipherTransformation"; @@ -157,6 +157,7 @@ public static SecurityConfiguration getInstance() { public static final String VALIDATION_PROPERTIES = "Validator.ConfigurationFile"; public static final String VALIDATION_PROPERTIES_MULTIVALUED = "Validator.ConfigurationFile.MultiValued"; public static final String ACCEPT_LENIENT_DATES = "Validator.AcceptLenientDates"; + public static final String VALIDATOR_HTML_VALIDATION_ACTION = "Validator.HtmlValidationAction"; /** * Special {@code System} property that, if set to {@code true}, will diff --git a/src/main/java/org/owasp/esapi/reference/validation/HTMLValidationRule.java b/src/main/java/org/owasp/esapi/reference/validation/HTMLValidationRule.java index f0196fc04..0670860d9 100644 --- a/src/main/java/org/owasp/esapi/reference/validation/HTMLValidationRule.java +++ b/src/main/java/org/owasp/esapi/reference/validation/HTMLValidationRule.java @@ -97,8 +97,60 @@ public String sanitize( String context, String input ) { return safe; } + /** + * Check whether we want the legacy behavior ("clean") or the presumably intended + * behavior of "throw" for how to treat unsafe HTML input when AntiSamy is invoked. + * This admittedly is an UGLY hack to ensure that issue 509 and its corresponding + * fix in PR #510 does not break existing developer's existing code. Full + * details are described in GitHub issue 521. + * + * Checks new ESAPI property "Validator.HtmlValidationAction". A value of "clean" + * means to revert to legacy behavior. A value of "throw" means to use the new + * behavior as implemented in GitHub issue 509. + * + * @return false - If "Validator.HtmlValidationAction" is set to "throw". Otherwise {@code true}. + * @since 2.2.1.0 + */ + private boolean legacyHtmlValidation() { + boolean legacy = true; // Make legacy support the default behavior for backward compatibility. + String propValue = "clean"; // For legacy support. + try { + // DISCUSS: + // Hindsight: maybe we should have getBooleanProp(), getStringProp(), + // getIntProp() methods that take a default arg as well? + // At least for ESAPI 3.x. + propValue = ESAPI.securityConfiguration().getStringProp( + // Future: This will be moved to a new PropNames class + org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_ACTION ); + switch ( propValue.toLowerCase() ) { + case "throw": + legacy = false; // New, presumably correct behavior, as addressed by GitHub issue 509 + break; + case "clean": + legacy = true; // Give the caller that legacy behavior of sanitizing. + break; + default: + LOGGER.warning(Logger.EVENT_FAILURE, "ESAPI property " + + org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_ACTION + + " was set to \"" + propValue + "\". Must be set to either \"clean\"" + + " (the default for legacy support) or \"throw\"; assuming \"clean\" for legacy behavior."); + legacy = true; + break; + } + } catch( ConfigurationException cex ) { + // OPEN ISSUE: Should we log this? I think so. Convince me otherwise. But maybe + // we should only log it once or every Nth time?? + LOGGER.warning(Logger.EVENT_FAILURE, "ESAPI property " + + org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_ACTION + + " must be set to either \"clean\" (the default for legacy support) or \"throw\"; assuming \"clean\"", + cex); + } + + return legacy; + } + private String invokeAntiSamy( String context, String input ) throws ValidationException { - // CHECKME should this allow empty Strings? " " us IsBlank instead? + // CHECKME should this allow empty Strings? " " use IsBlank instead? if ( StringUtilities.isEmpty(input) ) { if (allowNull) { return null; @@ -114,7 +166,11 @@ private String invokeAntiSamy( String context, String input ) throws ValidationE List errors = test.getErrorMessages(); if ( !errors.isEmpty() ) { - throw new ValidationException( context + ": Invalid HTML input", "Invalid HTML input does not follow rules in antisamy-esapi.xml: context=" + context + " errors=" + errors.toString()); + if ( legacyHtmlValidation() ) { // See GitHub issues 509 and 521 + LOGGER.info(Logger.SECURITY_FAILURE, "Cleaned up invalid HTML input: " + errors ); + } else { + throw new ValidationException( context + ": Invalid HTML input", "Invalid HTML input does not follow rules in antisamy-esapi.xml: context=" + context + " errors=" + errors.toString()); + } } return test.getCleanHTML().trim(); diff --git a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java index ad48ef53b..b0fd24789 100644 --- a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java +++ b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java @@ -238,39 +238,8 @@ public void testGetValidRedirectLocation() { // instance.getValidRedirectLocation(String, String, boolean, ValidationErrorList) } - public void testGetValidSafeHTML() throws Exception { - System.out.println("getValidSafeHTML"); - Validator instance = ESAPI.validator(); - ValidationErrorList errors = new ValidationErrorList(); - - // new school test case setup - HTMLValidationRule rule = new HTMLValidationRule("test"); - ESAPI.validator().addRule(rule); - - assertEquals("Test.", ESAPI.validator().getRule("test").getValid("test", "Test. ")); - - String test1 = "Jeff"; - String result1 = instance.getValidSafeHTML("test", test1, 100, false, errors); - assertEquals(test1, result1); - - String test2 = "Aspect Security"; - String result2 = instance.getValidSafeHTML("test", test2, 100, false, errors); - assertEquals(test2, result2); - - String test3 = "Test. "; - assertEquals("Test.", rule.getSafe("test", test3)); - - assertEquals("Test. <
    load=alert()
    ", rule.getSafe("test", "Test. <
    load=alert()")); - assertEquals("Test.
    b
    ", rule.getSafe("test", "Test.
    b
    ")); - assertEquals("Test. alert(document.cookie)", rule.getSafe("test", "Test. alert(document.cookie)")); - assertEquals("Test. alert(document.cookie)", rule.getSafe("test", "Test. alert(document.cookie)")); - assertEquals("Test. alert(document.cookie)", rule.getSafe("test", "Test. alert(document.cookie)")); - // TODO: ENHANCE waiting for a way to validate text headed for an attribute for scripts - // This would be nice to catch, but just looks like text to AntiSamy - // assertFalse(instance.isValidSafeHTML("test", "\" onload=\"alert(document.cookie)\" ")); - // String result4 = instance.getValidSafeHTML("test", test4); - // assertEquals("", result4); - } + // Test split out and moved to HTMLValidationRuleLogsTest.java & HTMLValidationRuleThrowsTest.java + // public void testGetValidSafeHTML() throws Exception { public void testIsInvalidFilename() { System.out.println("testIsInvalidFilename"); @@ -881,32 +850,8 @@ public void testIsValidRedirectLocation() { // isValidRedirectLocation(String, String, boolean) } - public void testIsValidSafeHTML() { - System.out.println("isValidSafeHTML"); - Validator instance = ESAPI.validator(); - - assertTrue(instance.isValidSafeHTML("test", "Jeff", 100, false)); - assertTrue(instance.isValidSafeHTML("test", "Aspect Security", 100, false)); - assertTrue(instance.isValidSafeHTML("test", "Test. ", 100, false)); - assertTrue(instance.isValidSafeHTML("test", "Test.
    ", 100, false)); - assertTrue(instance.isValidSafeHTML("test", "Test. alert(document.cookie)", 100, false)); - assertTrue(instance.isValidSafeHTML("test", "Test. alert(document.cookie)", 100, false)); - assertTrue(instance.isValidSafeHTML("test", "Test. alert(document.cookie)", 100, false)); - - // TODO: waiting for a way to validate text headed for an attribute for scripts - // This would be nice to catch, but just looks like text to AntiSamy - // assertFalse(instance.isValidSafeHTML("test", "\" onload=\"alert(document.cookie)\" ")); - ValidationErrorList errors = new ValidationErrorList(); - assertTrue(instance.isValidSafeHTML("test1", "Jeff", 100, false, errors)); - assertTrue(instance.isValidSafeHTML("test2", "Aspect Security", 100, false, errors)); - assertTrue(instance.isValidSafeHTML("test3", "Test. ", 100, false, errors)); - assertTrue(instance.isValidSafeHTML("test4", "Test.
    ", 100, false, errors)); - assertTrue(instance.isValidSafeHTML("test5", "Test. alert(document.cookie)", 100, false, errors)); - assertTrue(instance.isValidSafeHTML("test6", "Test. alert(document.cookie)", 100, false, errors)); - assertTrue(instance.isValidSafeHTML("test7", "Test. alert(document.cookie)", 100, false, errors)); - assertTrue(errors.size() == 0); - - } + // Test split out and moved to HTMLValidationRuleLogsTest.java & HTMLValidationRuleThrowsTest.java + // public void testIsValidSafeHTML() { public void testSafeReadLine() { System.out.println("safeReadLine"); diff --git a/src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleCleanTest.java b/src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleCleanTest.java new file mode 100644 index 000000000..dfed45607 --- /dev/null +++ b/src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleCleanTest.java @@ -0,0 +1,158 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2019 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + * @author kevin.w.wall@gmail.com + * @since 2019 + */ +package org.owasp.esapi.reference; + +import org.owasp.esapi.ESAPI; +import org.owasp.esapi.EncoderConstants; +import org.owasp.esapi.SecurityConfiguration; +import org.owasp.esapi.SecurityConfigurationWrapper; +import org.owasp.esapi.ValidationErrorList; +import org.owasp.esapi.ValidationRule; +import org.owasp.esapi.Validator; +import org.owasp.esapi.errors.ValidationException; +import org.owasp.esapi.filters.SecurityWrapperRequest; +import org.owasp.esapi.reference.validation.HTMLValidationRule; + +import org.junit.Test; +import org.junit.Before; +import org.junit.After; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import static org.junit.Assert.*; + +/** + * The Class HTMLValidationRuleCleanTest. + * + * Based on original test cases, testGetValidSafeHTML() and + * testIsValidSafeHTML() from ValidatorTest by + * Mike Fauzy (mike.fauzy@aspectsecurity.com) and + * Jeff Williams (jeff.williams@aspectsecurity.com) + * that were originally part of src/test/java/org/owasp/esapi/reference/ValidatorTest.java. + * + * This class tests the cases where the new ESAPI.property + * Validator.HtmlValidationAction + * is set to "clean", which causes certain calls to + * ESAPI.validator().getValidSafeHTML() or ESAPI.validator().isValidSafeHTML() + * to simply log a warning and return the cleansed (sanitizied) output rather + * than throwing a ValidationException when certain unsafe input is + * encountered. + */ +public class HTMLValidationRuleCleanTest { + + private static class ConfOverride extends SecurityConfigurationWrapper { + private String desiredReturn = "clean"; + + ConfOverride(SecurityConfiguration orig, String desiredReturn) { + super(orig); + this.desiredReturn = desiredReturn; + } + + @Override + public String getStringProp(String propName) { + // Would it be better making this file a static import? + if ( propName.equals( org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_ACTION ) ) { + return desiredReturn; + } else { + return super.getStringProp( propName ); + } + } + } + + + /** + * Instantiates a new HTTP utilities test. + * + * @param testName the test name + */ + public HTMLValidationRuleCleanTest() { + } + + @After + public void tearDown() throws Exception { + ESAPI.override(null); + } + + @Before + public void setUp() throws Exception { + ESAPI.override( + new ConfOverride( ESAPI.securityConfiguration(), "clean" ) + ); + + } + + @Test + public void testGetValidSafeHTML() throws Exception { + System.out.println("getValidSafeHTML"); + Validator instance = ESAPI.validator(); + ValidationErrorList errors = new ValidationErrorList(); + + HTMLValidationRule rule = new HTMLValidationRule("test"); + ESAPI.validator().addRule(rule); + + assertEquals("Test.", ESAPI.validator().getRule("test").getValid("test", "Test. ")); + + String test1 = "Jeff"; + String result1 = instance.getValidSafeHTML("test", test1, 100, false, errors); + assertEquals(test1, result1); + + String test2 = "Aspect Security"; + String result2 = instance.getValidSafeHTML("test", test2, 100, false, errors); + assertEquals(test2, result2); + + String test3 = "Test. Cookie :-)"; + assertEquals("Test. Cookie :-)", rule.getSafe("test", test3)); + + assertEquals("Test. <
    load=alert()
    ", rule.getSafe("test", "Test. <
    load=alert()")); + assertEquals("Test.
    b
    ", rule.getSafe("test", "Test.
    b
    ")); + assertEquals("Test. alert(document.cookie)", rule.getSafe("test", "Test. alert(document.cookie)")); + assertEquals("Test. alert(document.cookie)", rule.getSafe("test", "Test. alert(document.cookie)")); + assertEquals("Test. alert(document.cookie)", rule.getSafe("test", "Test. alert(document.cookie)")); + // TODO: ENHANCE waiting for a way to validate text headed for an attribute for scripts + // This would be nice to catch, but just looks like text to AntiSamy + // assertFalse(instance.isValidSafeHTML("test", "\" onload=\"alert(document.cookie)\" ")); + // String result4 = instance.getValidSafeHTML("test", test4); + // assertEquals("", result4); + } + + + @Test + public void testIsValidSafeHTML() { + System.out.println("isValidSafeHTML"); + Validator instance = ESAPI.validator(); + + assertTrue(instance.isValidSafeHTML("test", "Jeff", 100, false)); + assertTrue(instance.isValidSafeHTML("test", "Aspect Security", 100, false)); + assertTrue(instance.isValidSafeHTML("test", "Test. ", 100, false)); + assertTrue(instance.isValidSafeHTML("test", "Test.
    ", 100, false)); + assertTrue(instance.isValidSafeHTML("test", "Test. alert(document.cookie)", 100, false)); + assertTrue(instance.isValidSafeHTML("test", "Test. alert(document.cookie)", 100, false)); + assertTrue(instance.isValidSafeHTML("test", "Test. alert(document.cookie)", 100, false)); + + // TODO: waiting for a way to validate text headed for an attribute for scripts + // This would be nice to catch, but just looks like text to AntiSamy + // assertFalse(instance.isValidSafeHTML("test", "\" onload=\"alert(document.cookie)\" ")); + ValidationErrorList errors = new ValidationErrorList(); + assertTrue(instance.isValidSafeHTML("test1", "Jeff", 100, false, errors)); + assertTrue(instance.isValidSafeHTML("test2", "Aspect Security", 100, false, errors)); + assertTrue(instance.isValidSafeHTML("test3", "Test. ", 100, false, errors)); + assertTrue(instance.isValidSafeHTML("test4", "Test.
    ", 100, false, errors)); + assertTrue(instance.isValidSafeHTML("test5", "Test. alert(document.cookie)", 100, false, errors)); + assertTrue(instance.isValidSafeHTML("test6", "Test. alert(document.cookie)", 100, false, errors)); + assertTrue(instance.isValidSafeHTML("test7", "Test. alert(document.cookie)", 100, false, errors)); + assertTrue(errors.size() == 0); + + } +} diff --git a/src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleThrowsTest.java b/src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleThrowsTest.java new file mode 100644 index 000000000..6726ef56f --- /dev/null +++ b/src/test/java/org/owasp/esapi/reference/validation/HTMLValidationRuleThrowsTest.java @@ -0,0 +1,167 @@ +/** + * 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 + * http://www.owasp.org/index.php/ESAPI. + * + * Copyright (c) 2019 - The OWASP Foundation + * + * The ESAPI is published by OWASP under the BSD license. You should read and accept the + * LICENSE before you use, modify, and/or redistribute this software. + * + * @author kevin.w.wall@gmail.com + * @since 2019 + */ +package org.owasp.esapi.reference; + +import org.owasp.esapi.ESAPI; +import org.owasp.esapi.SecurityConfiguration; +import org.owasp.esapi.SecurityConfigurationWrapper; +import org.owasp.esapi.ValidationErrorList; +import org.owasp.esapi.ValidationRule; +import org.owasp.esapi.Validator; +import org.owasp.esapi.errors.ValidationException; +import org.owasp.esapi.reference.validation.HTMLValidationRule; + +import org.junit.Test; +import org.junit.Before; +import org.junit.After; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import static org.junit.Assert.*; + +/** + * The Class HTMLValidationRuleThrowsTest. + * + * Based on original test cases, testGetValidSafeHTML() and + * testIsValidSafeHTML() from ValidatorTest by + * Mike Fauzy (mike.fauzy@aspectsecurity.com) and + * Jeff Williams (jeff.williams@aspectsecurity.com) + * that were originally part of src/test/java/org/owasp/esapi/reference/ValidatorTest.java. + * + * This class tests the cases where the new ESAPI.property + * Validator.HtmlValidationAction + * is set to "throw", which causes certain calls to + * ESAPI.validator().getValidSafeHTML() or ESAPI.validator().isValidSafeHTML() + * to throw a ValidationException rather than simply logging a warning and returning + * the cleansed (sanitizied) output when certain unsafe input is encountered. + */ +public class HTMLValidationRuleThrowsTest { + private static class ConfOverride extends SecurityConfigurationWrapper { + private String desiredReturn = "clean"; + + ConfOverride(SecurityConfiguration orig, String desiredReturn) { + super(orig); + this.desiredReturn = desiredReturn; + } + + @Override + public String getStringProp(String propName) { + // Would it be better making this file a static import? + if ( propName.equals( org.owasp.esapi.reference.DefaultSecurityConfiguration.VALIDATOR_HTML_VALIDATION_ACTION ) ) { + return desiredReturn; + } else { + return super.getStringProp( propName ); + } + } + } + + // Must be public! + @Rule + public ExpectedException thrownEx = ExpectedException.none(); + + @After + public void tearDown() throws Exception { + ESAPI.override(null); + thrownEx = ExpectedException.none(); + } + + @Before + public void setUp() throws Exception { + ESAPI.override( + new ConfOverride( ESAPI.securityConfiguration(), "throw" ) + ); + + } + + @Test + public void testGetValid() throws Exception { + System.out.println("getValid"); + Validator instance = ESAPI.validator(); + HTMLValidationRule rule = new HTMLValidationRule("test"); + ESAPI.validator().addRule(rule); + + thrownEx.expect(ValidationException.class); + thrownEx.expectMessage("test: Invalid HTML input"); + + instance.getRule("test").getValid("test", "Test. "); + } + + @Test + public void testGetValidSafeHTML() throws Exception { + System.out.println("getValidSafeHTML"); + Validator instance = ESAPI.validator(); + + HTMLValidationRule rule = new HTMLValidationRule("test"); + ESAPI.validator().addRule(rule); + + String[] testInput = { + // These first two don't cause AntiSamy to throw. + // "Test. Aspect Security", + // "Test. <
    load=alert()", + "Test. ", + "Test. ", + "Test.
    b
    ", + "Test. alert(document.cookie)", + "Test. alert(document.cookie)", + "Test. alert(document.cookie)" + }; + + int errors = 0; + for( int i = 0; i < testInput.length; i++ ) { + try { + String result = instance.getValidSafeHTML("test", testInput[i], 100, false); + errors++; + System.out.println("testGetValidSafeHTML(): testInput '" + testInput[i] + "' failed to throw."); + } + catch( ValidationException vex ) { + System.out.println("testGetValidSafeHTML(): testInput '" + testInput[i] + "' returned:"); + System.out.println("\t" + i + ": logMsg =" + vex.getLogMessage()); + assertEquals( vex.getUserMessage(), "test: Invalid HTML input"); + } + catch( Exception ex ) { + errors++; + System.out.println("testGetValidSafeHTML(): testInput '" + testInput[i] + + "' threw wrong exception type: " + ex.getClass().getName() ); + } + } + + if ( errors > 0 ) { + fail("testGetValidSafeHTML() encountered " + errors + " failures."); + } + } + + @Test + public void testIsValidSafeHTML() { + System.out.println("isValidSafeHTML"); + Validator instance = ESAPI.validator(); + thrownEx = ExpectedException.none(); // Not expecting any exceptions here. + + assertTrue(instance.isValidSafeHTML("test", "Jeff", 100, false)); + assertTrue(instance.isValidSafeHTML("test", "Aspect Security", 100, false)); + assertFalse(instance.isValidSafeHTML("test", "Test. ", 100, false)); + assertFalse(instance.isValidSafeHTML("test", "Test.
    ", 100, false)); + assertFalse(instance.isValidSafeHTML("test", "Test. alert(document.cookie)", 100, false)); + assertFalse(instance.isValidSafeHTML("test", "Test. alert(document.cookie)", 100, false)); + assertFalse(instance.isValidSafeHTML("test", "Test. alert(document.cookie)", 100, false)); + + ValidationErrorList errors = new ValidationErrorList(); + assertFalse(instance.isValidSafeHTML("test1", "Test. ", 100, false, errors)); + assertFalse(instance.isValidSafeHTML("test2", "Test.
    ", 100, false, errors)); + assertFalse(instance.isValidSafeHTML("test3", "Test. alert(document.cookie)", 100, false, errors)); + assertFalse(instance.isValidSafeHTML("test4", "Test. alert(document.cookie)", 100, false, errors)); + assertFalse(instance.isValidSafeHTML("test5", "Test. alert(document.cookie)", 100, false, errors)); + assertTrue( errors.size() == 5 ); + } +} diff --git a/src/test/resources/esapi/ESAPI.properties b/src/test/resources/esapi/ESAPI.properties index 2537757c1..14b47d32f 100644 --- a/src/test/resources/esapi/ESAPI.properties +++ b/src/test/resources/esapi/ESAPI.properties @@ -97,9 +97,9 @@ ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector # Log4JFactory Requires log4j.xml or log4j.properties in classpath - http://www.laliluna.de/log4j-tutorial.html -ESAPI.Logger=org.owasp.esapi.logging.log4j.Log4JLogFactory -#ESAPI.Logger=org.owasp.esapi.logging.java.JavaLogFactory -#ESAPI.Logger=org.owasp.esapi.reference.ExampleExtendedLog4JLogFactory +# Note that this is now considered deprecated! +#ESAPI.Logger=org.owasp.esapi.logging.log4j.Log4JLogFactory +ESAPI.Logger=org.owasp.esapi.logging.java.JavaLogFactory # To use the new SLF4J logger in ESAPI (see GitHub issue #129), set # ESAPI.Logger=org.owasp.esapi.logging.slf4j.Slf4JLogFactory # and do whatever other normal SLF4J configuration that you normally would do for your application. @@ -529,3 +529,38 @@ Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$ # Validation of dates. Controls whether or not 'lenient' dates are accepted. # See DataFormat.setLenient(boolean flag) for further details. Validator.AcceptLenientDates=false + +# ~~~~~ Important Note ~~~~~ +# This is a workaround to make sure that a commit to address GitHub issue #509 +# doesn't accidentally break someone's production code. So essentially what we +# are doing is to reverting back to the previous possibly buggy (by +# documentation intent at least), but, by now, expected legacy behavior. +# Prior to the code changes for issue #509, if invalid / malicious HTML input was +# observed, AntiSamy would simply attempt to sanitize (cleanse) it and it would +# only be logged. However, the code change made ESAPI comply with its +# documentation, which stated that a ValidationException should be thrown in +# such cases. Unfortunately, changing this behavior--especially when no one is +# 100% certain that the documentation was correct--could break existing code +# using ESAPI so after a lot of debate, issue #521 was created to restore the +# previous behavior, but still allow the documented behavior. (We did this +# because it wasn't really causing an security issues since AntiSamy would clean +# it up anyway and we value backward compatibility as long as it doesn't clearly +# present security vulnerabilities.) +# More defaults about this are written up under GitHub issue #521 and +# the pull request it references. Future major releases of ESAPI (e.g., ESAPI 3.x) +# will not support this previous behavior, but it will remain for ESAPI 2.x. +# Set this to 'throw' if you want the originally intended behavior of throwing +# that was fixed via issue #509. Set to 'clean' if you want want the HTML input +# sanitized instead. +# +# Possible values: +# clean -- Use the legacy behavior where unsafe HTML input is logged and the +# sanitized (i.e., clean) input as determined by AntiSamy and your +# AntiSamy rules is returned. This is the default behavior if this +# new property is not found. +# throw -- The new, presumably correct and originally intended behavior where +# a ValidationException is thrown when unsafe HTML input is +# encountered. +# +#Validator.HtmlValidationAction=clean +Validator.HtmlValidationAction=throw From 74492fee25b9640c985da28a91db478f21e6de92 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 9 Feb 2020 00:37:18 -0500 Subject: [PATCH 251/709] Close issue #538 addressing CVE-2019-17571 with security bulletin. --- SECURITY.md | 8 ++++++++ documentation/ESAPI-security-bulletin2.odt | Bin 0 -> 31990 bytes documentation/ESAPI-security-bulletin2.pdf | Bin 0 -> 103931 bytes 3 files changed, 8 insertions(+) create mode 100644 documentation/ESAPI-security-bulletin2.odt create mode 100644 documentation/ESAPI-security-bulletin2.pdf diff --git a/SECURITY.md b/SECURITY.md index f4d5ecb55..bee2b246a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -40,3 +40,11 @@ can understand what needs to be done to fix it. Unfortunately at this time, we are not in a position to pay out bug bounties for vulnerabilities. Eventually, we would like to have BugCrowd handle this, but that's still a ways off. + +## Security Bulletins + +There are some ESAPI security bulletins published in the "documentation" directory on GitHub. +For details see: + +* (Security Bulletin #1 - MAC Bypass in ESAPI Symmetric Encryption)[documentation/ESAPI-security-bulletin1.pdf], which covers CVE-2013-5679 and CVE-2013-5960 +* (Security Bulletin #2 - How Does CVE-2019-17571 Impact ESAPI?)[documentation/ESAPI-security-bulletin2.pdf], which covers the Log4J 1 deserialization CVE. diff --git a/documentation/ESAPI-security-bulletin2.odt b/documentation/ESAPI-security-bulletin2.odt new file mode 100644 index 0000000000000000000000000000000000000000..c65623126fc7cab343fded85c86ac6b1b636ca78 GIT binary patch literal 31990 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 hu8LAtC6J)dh{>{Mu-IV&pn?RhbK0;$T~CAi{{R Date: Sun, 9 Feb 2020 00:53:42 -0500 Subject: [PATCH 252/709] Update SECURITY.md to fix security bulletin links. It must be past my bedtime. Sigh. ;-) --- SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index bee2b246a..18d510ad9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -46,5 +46,5 @@ Eventually, we would like to have BugCrowd handle this, but that's still a ways There are some ESAPI security bulletins published in the "documentation" directory on GitHub. For details see: -* (Security Bulletin #1 - MAC Bypass in ESAPI Symmetric Encryption)[documentation/ESAPI-security-bulletin1.pdf], which covers CVE-2013-5679 and CVE-2013-5960 -* (Security Bulletin #2 - How Does CVE-2019-17571 Impact ESAPI?)[documentation/ESAPI-security-bulletin2.pdf], which covers the Log4J 1 deserialization CVE. +* [Security Bulletin #1 - MAC Bypass in ESAPI Symmetric Encryption](documentation/ESAPI-security-bulletin1.pdf), which covers CVE-2013-5679 and CVE-2013-5960 +* [Security Bulletin #2 - How Does CVE-2019-17571 Impact ESAPI?](documentation/ESAPI-security-bulletin2.pdf), which covers the Log4J 1 deserialization CVE. From 5ed5f253e3e9a5135a627942e17f9c5791b09f07 Mon Sep 17 00:00:00 2001 From: Wiiitek Date: Sun, 9 Feb 2020 21:53:35 +0100 Subject: [PATCH 253/709] upgrade for convergence --- pom.xml | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index 6a34d2cc0..1997e5a00 100644 --- a/pom.xml +++ b/pom.xml @@ -364,7 +364,7 @@ org.powermock powermock-reflect - 2.0.0 + 2.0.2 test @@ -526,36 +526,25 @@ - enforce-bytecode-version enforce + + + 1.7 + + ESAPI 2.x now uses the JDK1.7 for it's baseline. Please make sure that your + JAVA_HOME environment variable is pointed to a JDK1.7 distribution. + + 1.7 - true - - enforce-jdk-version - - enforce - - - - - 1.7 - - ESAPI 2.x now uses the JDK1.7 for it's baseline. Please make sure that your - JAVA_HOME environment variable is pointed to a JDK1.7 distribution. - - - - - From e179580e133c03044fd6c0adfc93d335afb98549 Mon Sep 17 00:00:00 2001 From: Wiiitek Date: Sun, 9 Feb 2020 22:25:30 +0100 Subject: [PATCH 254/709] new way for getting file content from filesystem --- .../reference/ExtensiveEncoderURITest.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java b/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java index 37e9ea213..36bd7c116 100644 --- a/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java +++ b/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java @@ -2,10 +2,12 @@ import static org.junit.Assert.assertEquals; -import java.io.File; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -34,14 +36,24 @@ public ExtensiveEncoderURITest(String uri){ @Parameters public static Collection getMyUris() throws Exception{ URL url = ExtensiveEncoderURITest.class.getResource("/urisForTest.txt"); - String fileName = url.getFile(); - File urisForText = new File(fileName); - - inputs = Files.readAllLines(urisForText.toPath(), StandardCharsets.UTF_8); + try( InputStream is = url.openStream() ) { + InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); + BufferedReader br = new BufferedReader(isr); + inputs = readAllLines(br); + } return inputs; } - + + private static List readAllLines(BufferedReader br) throws IOException { + List lines = new ArrayList<>(); + String line; + while ((line = br.readLine()) != null) { + lines.add(line); + } + return lines; + } + @Test public void testUrlsFromFile() throws Exception{ assertEquals(this.expected, v.isValidURI("URL", uri, false)); From c951b1cd05112bf9760d3cd3d08c842c418c443a Mon Sep 17 00:00:00 2001 From: Wiiitek Date: Sun, 9 Feb 2020 23:32:58 +0100 Subject: [PATCH 255/709] update for reading configuration from file path with spaces --- .../DefaultSecurityConfiguration.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java index a9fd89cc9..d578850ba 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultSecurityConfiguration.java @@ -20,6 +20,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; @@ -581,13 +582,17 @@ public File getResourceFile(String filename) { } if (fileUrl != null) { - String fileLocation = fileUrl.getFile(); - f = new File(fileLocation); - if (f.exists()) { - logSpecial("Found in SystemResource Directory/resourceDirectory: " + f.getAbsolutePath()); - return f; - } else { - logSpecial("Not found in SystemResource Directory/resourceDirectory (this should never happen): " + f.getAbsolutePath()); + try { + String fileLocation = fileUrl.toURI().getPath(); + f = new File(fileLocation); + if (f.exists()) { + logSpecial("Found in SystemResource Directory/resourceDirectory: " + f.getAbsolutePath()); + return f; + } else { + logSpecial("Not found in SystemResource Directory/resourceDirectory (this should never happen): " + f.getAbsolutePath()); + } + } catch (URISyntaxException e) { + logSpecial("Error while converting URL " + fileUrl + " to file path: " + e.getMessage()); } } else { logSpecial("Not found in SystemResource Directory/resourceDirectory: " + resourceDirectory + File.separator + filename); From fd1650d4fd8da129843a1346811f26dc43466de7 Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Mon, 17 Feb 2020 14:14:54 -0500 Subject: [PATCH 256/709] Update pom.xml SNAPSHOT version Changed from 2.3.0.0-SNAPSHOT to 2.2.1.0-SNAPSHOT. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1997e5a00..881b59c5e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.owasp.esapi esapi - 2.3.0.0-SNAPSHOT + 2.2.1.0-SNAPSHOT jar From f8180f59c636e90a9677deb9c29afae2f1f7c665 Mon Sep 17 00:00:00 2001 From: kwwall Date: Mon, 17 Feb 2020 20:42:13 -0500 Subject: [PATCH 257/709] Drop 'failBuildOnCVSS' from 5.9 to 5.0. Corrected spelling of 'suppressionFiles'. (Old value still worked.) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 881b59c5e..132075ffd 100644 --- a/pom.xml +++ b/pom.xml @@ -689,8 +689,8 @@ dependency-check-maven 5.0.0 - 5.9 - ./suppressions.xml + 5.0 + ./suppressions.xml From 448c8f3a1feb517ba07880e709ea4ed37ab447ee Mon Sep 17 00:00:00 2001 From: kwwall Date: Mon, 17 Feb 2020 20:45:04 -0500 Subject: [PATCH 258/709] Added suppression of CVE-2019-17571; deleted suppression of CVE-2016-1000031. --- suppressions.xml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/suppressions.xml b/suppressions.xml index 181b75909..ebedb9648 100644 --- a/suppressions.xml +++ b/suppressions.xml @@ -1,10 +1,22 @@ - + + - .*\bcommons-fileupload-1.3.2.jar - CVE-2016-1000031 + ^log4j:log4j:1\.2\.17$ + cpe:/a:apache:log4j + CVE-2019-17571 - \ No newline at end of file + From 7aafaeeb3476c86886756699501f09963b8657f8 Mon Sep 17 00:00:00 2001 From: Wiiitek Date: Sun, 23 Feb 2020 18:12:58 +0100 Subject: [PATCH 259/709] additional time for windows to always sleep more than given seconds --- src/test/java/org/owasp/esapi/crypto/CryptoTokenTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/owasp/esapi/crypto/CryptoTokenTest.java b/src/test/java/org/owasp/esapi/crypto/CryptoTokenTest.java index 8f43d3b1b..0d2b8a519 100644 --- a/src/test/java/org/owasp/esapi/crypto/CryptoTokenTest.java +++ b/src/test/java/org/owasp/esapi/crypto/CryptoTokenTest.java @@ -391,7 +391,9 @@ public final void testAddandGetAttributes() { private static void nap(int n) { try { System.out.println("Sleeping " + n + " seconds..."); - Thread.sleep( n * 1000 ); + // adds additional time to make sure we sleep more than n seconds + int additionalTimeToSleep = 100; + Thread.sleep( n * 1000 + additionalTimeToSleep ); } catch (InterruptedException e) { ; // Ignore } From b479d5908d10100a32355c9b9cde05964d4075f0 Mon Sep 17 00:00:00 2001 From: Wiiitek Date: Sun, 23 Feb 2020 19:16:36 +0100 Subject: [PATCH 260/709] inline reader for try-with-resource --- .../org/owasp/esapi/reference/ExtensiveEncoderURITest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java b/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java index 36bd7c116..0283492c7 100644 --- a/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java +++ b/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java @@ -36,10 +36,8 @@ public ExtensiveEncoderURITest(String uri){ @Parameters public static Collection getMyUris() throws Exception{ URL url = ExtensiveEncoderURITest.class.getResource("/urisForTest.txt"); - - try( InputStream is = url.openStream() ) { - InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); - BufferedReader br = new BufferedReader(isr); + + try( BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)) ) { inputs = readAllLines(br); } return inputs; From 048fc0c40549cd86b28d0ac5ef89cbfdc4cbb289 Mon Sep 17 00:00:00 2001 From: Wiiitek Date: Sun, 23 Feb 2020 19:17:42 +0100 Subject: [PATCH 261/709] removing not needed code --- .../org/owasp/esapi/reference/ExtensiveEncoderURITest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java b/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java index 0283492c7..18d88c79e 100644 --- a/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java +++ b/src/test/java/org/owasp/esapi/reference/ExtensiveEncoderURITest.java @@ -4,7 +4,6 @@ import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -22,7 +21,7 @@ @RunWith(Parameterized.class) public class ExtensiveEncoderURITest { - static List inputs = new ArrayList(); + static List inputs = new ArrayList<>(); Validator v = ESAPI.validator(); String uri; boolean expected; @@ -53,7 +52,7 @@ private static List readAllLines(BufferedReader br) throws IOException { } @Test - public void testUrlsFromFile() throws Exception{ + public void testUrlsFromFile() { assertEquals(this.expected, v.isValidURI("URL", uri, false)); } From 7003acc1da17eb68bc92c97a2b7d47a093021385 Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Sun, 23 Feb 2020 17:17:02 -0500 Subject: [PATCH 262/709] Updated README.md to refer to config jar Updated 2.2.0.0 release on GitHub and added jar and signature for ESAPI default config files. This README.md update just references that. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f7c38d99d..fd3e2d5bf 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,12 @@ The default branch for ESAPI legacy is now the 'develop' branch (rather than the # Where can I find ESAPI 3.x? https://github.com/ESAPI/esapi-java +# Locating ESAPI Jar files +The latest ESAPI 2.2.0.0 default configuration jar and its GPG signature can be found at [esapi-2.2.0.0-configuration.jar](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.0.0/esapi-2.2.0.0-configuration.jar) and [esapi-2.2.0.0-configuration.jar.asc](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.0.0/esapi-2.2.0.0-configuration.jar.asc) respectively. + +The latest regular ESAPI jars can are available from Maven Central. + + # ESAPI Deprecation Policy Unless we unintentionally screw-up, our intent is to keep classes, methods, and/or fields whihc have been annotated as "@deprecated" for a minimum of two (2) years or until the next major release number (e.g., 3.x as of now), which ever comes first, before we remove them. Note that this policy does not apply to classes under the **org.owasp.esapi.reference** package. You are not expected to be using such classes directly in your code. From e007ec83f5169a7f75ba63354a63c697af685fe2 Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Tue, 25 Feb 2020 22:22:57 -0500 Subject: [PATCH 263/709] Update to include link to the latest release. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd3e2d5bf..68037a5d2 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ OWASP ESAPI (The OWASP Enterprise Security API) is a free, open source, web appl # What does Legacy mean? -

    This is the legacy branch of ESAPI which means it is an actively maintained branch of the project, however feature development for this branch will not be done. Features that have already been scheduled for the 2.x branch will move forward, but the main focus will be working on the ESAPI 3.x branch. +

    This is the legacy branch of ESAPI which means it is an actively maintained branch of the project, however feature development for this branch will not be done. Features that have already been scheduled for the 2.x branch will move forward, but the main focus will be working on the ESAPI 3.x branch. IMPORTANT NOTE: The default branch for ESAPI legacy is now the 'develop' branch (rather than the 'master' branch), where future development, bug fixes, etc. will now be done. The 'master' branch is now marked as "protected"; it reflects the latest stable ESAPI release (2.1.0.1 as of this date). Note that this change of making the 'develop' branch the default may affect any pull requests that you were intending to make. @@ -24,7 +24,7 @@ The default branch for ESAPI legacy is now the 'develop' branch (rather than the https://github.com/ESAPI/esapi-java # Locating ESAPI Jar files -The latest ESAPI 2.2.0.0 default configuration jar and its GPG signature can be found at [esapi-2.2.0.0-configuration.jar](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.0.0/esapi-2.2.0.0-configuration.jar) and [esapi-2.2.0.0-configuration.jar.asc](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.0.0/esapi-2.2.0.0-configuration.jar.asc) respectively. +The [latest ESAPI release](https://github.com/ESAPI/esapi-java-legacy/releases/latest) is 2.2.0.0. The default configuration jar and its GPG signature can be found at [esapi-2.2.0.0-configuration.jar](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.0.0/esapi-2.2.0.0-configuration.jar) and [esapi-2.2.0.0-configuration.jar.asc](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.0.0/esapi-2.2.0.0-configuration.jar.asc) respectively. The latest regular ESAPI jars can are available from Maven Central. From d26b98f11d0d1bacc66df36d7c659c822bb3fba7 Mon Sep 17 00:00:00 2001 From: Bill Sempf Date: Tue, 19 May 2020 00:01:59 -0400 Subject: [PATCH 264/709] Release notes for 2.2.1.0 (#543) I have no idea how to associate this with Issue 542 but if I figure it out I will do it. --- .../esapi4java-core-2.2.1.0-release-notes.txt | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 documentation/esapi4java-core-2.2.1.0-release-notes.txt diff --git a/documentation/esapi4java-core-2.2.1.0-release-notes.txt b/documentation/esapi4java-core-2.2.1.0-release-notes.txt new file mode 100644 index 000000000..7f91e07a5 --- /dev/null +++ b/documentation/esapi4java-core-2.2.1.0-release-notes.txt @@ -0,0 +1,115 @@ +Release notes for ESAPI 2.2.1.0 + Release date: 2020-May-12 + Project leaders: + -Kevin W. Wall + -Matt Seil + +Previous release: ESAPI 2.2.0.0, 2019-June-24 + + +Executive Summary: Important Things to Note for this Release +------------------------------------------------------------ + + TBD + +================================================================================================================= + +Basic ESAPI facts + +ESAPI 2.2.0.0 release: + 194 Java source files + 4150 JUnit tests in 118 Java source files + +ESAPI 2.2.1.0 release: + TBD + +GitHub Issues fixed in this release + +Issue # GitHub Issue Title +---------------------------------------------------------------------------------------------- + +143 Enchance encodeForOS to auto-detect the underling OS +226 Javadoc Inaccuracy in getRandomInteger() and getRandomReal() +245 KeyDerivationFunction::computeDerivedKey - possible security level mismatch +256 White space clean up +382 Build Fails on path with space +494 Encoder's encodeForCSS doesn't handle RGB Triplets +503 Bug on on referrer header when value contains `§ion` like `www.asdf.com?a=1§ion=2` +509 HTMLValidationRule.getValid(String,String) does not follow documented specifications +511 Add missing documentation to Validator.addRule() and Validator.getRule() +512 Update Apache Commons Bean Utils to 1.9.4 +515 Adding tests for getCookies (also 516) +519 Issue 494 CSSCodec RGB Triplets +530 Log Bridge Tests +536 Various fixes +538 Addressing log4j 1.x CVE-2019-17571 + +----------------------------------------------------------------------------- + + Changes requiring special attention + +----------------------------------------------------------------------------- + +TBD + +----------------------------------------------------------------------------- + + Other changes in this release, some of which not tracked via GitHub issues + +----------------------------------------------------------------------------- + +Documentation updates for locating Jar files +Unneeded code removed from ExtensiveEncoder +Inline reader added to ExtensiveEncoder +Additional time for windows to always sleep more than given seconds in CryptoTokenTest +Change required by tweak to CipherText.toString() method +Removed call to deprecated CryptoHelper.computeDerivedKey() method +New JUnit tests for org.owasp.esapi.crypto.KeyDerivationFunction class +Use existing toString method rather than a StringBuilder +Documentation and tests +JavaLogger move +Splitting user infor from Client Supplier + +----------------------------------------------------------------------------- + +Developer Activity Report (Changes between release 2.2.0.0 and 2.2.1.0, i.e., between 2019-06-25 and 2020-05-12) +Generated manually (this time) + +Developer Total commits Total Number + of Files Changed +===================================================== +jeremiahjstacey 11 68 +kwwall 15 26 +wiitek 3 6 +xeno6696 8 9 +Michael-Ziluck 2 3 +===================================================== + +----------------------------------------------------------------------------- + +53 Closed PRs since 2.2.0.0 release +=================================== +504 New scripts to suppress noise for 'mvn test' +510 Resolve #509 - Properly throw exception when HTML fails +513 Close issue #512 by updating to 1.9.4 of Commons Beans Util.\ +519 Issue 494 CSSCodec RGB Triplets +520 OS Name DefaultExecutorTests #143 +540 Issue 382: Build Fails on path with space +596 Closes Issue 245 + +----------------------------------------------------------------------------- + +Notice: + + Release notes written by Bill Sempf (bill.sempf@owasp.org) please direct any communication to me. + +Project co-leaders + Kevin W. Wall (kwwall) + Matt Seil (xeno6696) + +Special shout-outs to: + Jeremiah Stacey (jeremiahjstacey) -- All around ESAPI support and JUnit test case developer extraordinaire + Dave Wichers (davewichers) - for Maven Central / Sonatype help + +Thanks you all for your time and effort to ESAPI and making it a better project. And if I've missed any, my apologies; let me know and I will correct it. + From 482194884f5db00b8d22bfde6e5a8823374dd128 Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Tue, 19 May 2020 00:03:59 -0400 Subject: [PATCH 265/709] Update release date to 2020-TBD Need to wait until other PR is merged. --- documentation/esapi4java-core-2.2.1.0-release-notes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/esapi4java-core-2.2.1.0-release-notes.txt b/documentation/esapi4java-core-2.2.1.0-release-notes.txt index 7f91e07a5..bb46556b8 100644 --- a/documentation/esapi4java-core-2.2.1.0-release-notes.txt +++ b/documentation/esapi4java-core-2.2.1.0-release-notes.txt @@ -1,5 +1,5 @@ Release notes for ESAPI 2.2.1.0 - Release date: 2020-May-12 + Release date: 2020-TBD Project leaders: -Kevin W. Wall -Matt Seil From 90d3d0d9ba48c6b372980cdded869faf31fd59e7 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sat, 6 Jun 2020 17:25:09 -0400 Subject: [PATCH 266/709] Fix Javadoc --- src/main/java/org/owasp/esapi/codecs/HashTrie.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/owasp/esapi/codecs/HashTrie.java b/src/main/java/org/owasp/esapi/codecs/HashTrie.java index 4c8d41910..fef14aee7 100644 --- a/src/main/java/org/owasp/esapi/codecs/HashTrie.java +++ b/src/main/java/org/owasp/esapi/codecs/HashTrie.java @@ -252,8 +252,7 @@ Entry getLongestMatch(CharSequence key, int pos) /** * Recursively lookup the longest key match. * @param keyIn Where to read the key from - * @param pos The position in the key that is being - * looked up at this level. + * @param key The key that is being looked up at this level. * @return The Entry associated with the longest key * match or null if none exists. */ From 9be40d4055c0bc186845e8679c72cf4b476ad710 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sat, 6 Jun 2020 17:27:51 -0400 Subject: [PATCH 267/709] Fix Javadoc --- src/main/java/org/owasp/esapi/crypto/CipherText.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/owasp/esapi/crypto/CipherText.java b/src/main/java/org/owasp/esapi/crypto/CipherText.java index 8e57f03e7..1047116fa 100644 --- a/src/main/java/org/owasp/esapi/crypto/CipherText.java +++ b/src/main/java/org/owasp/esapi/crypto/CipherText.java @@ -804,7 +804,8 @@ protected boolean canEqual(Object other) { * proved in 1996 [see http://pssic.free.fr/Extra%20Reading/SEC+/SEC+/hmac-cb.pdf] that * HMAC security doesn’t require that the underlying hash function be collision resistant, * but only that it acts as a pseudo-random function, which SHA1 satisfies. - * @param ciphertext The ciphertext value for which the MAC is computed. + * @param authKey The {@Code SecretKey} used with the computed HMAC-SHA1 + * to ensure authenticity. * @return The value for the MAC. */ private byte[] computeMAC(SecretKey authKey) { From 23608fc130683b1ec1b3efea9331be92a815272b Mon Sep 17 00:00:00 2001 From: kwwall Date: Sat, 6 Jun 2020 17:59:19 -0400 Subject: [PATCH 268/709] Converted String array explicity to string, which is probably what was intended in exception messages. Otherwise end up with something that looks like '[Ljava.lang.String;@7825ed4c' which is not very helpful. --- .../esapi/reference/accesscontrol/DelegatingACR.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/owasp/esapi/reference/accesscontrol/DelegatingACR.java b/src/main/java/org/owasp/esapi/reference/accesscontrol/DelegatingACR.java index 07af135d7..65cb0ebb9 100644 --- a/src/main/java/org/owasp/esapi/reference/accesscontrol/DelegatingACR.java +++ b/src/main/java/org/owasp/esapi/reference/accesscontrol/DelegatingACR.java @@ -4,6 +4,7 @@ import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.Vector; +import java.util.Arrays; import org.apache.commons.collections4.iterators.ArrayListIterator; @@ -25,12 +26,12 @@ public void setPolicyParameters(DynaBeanACRParameter policyParameter) { } catch (SecurityException e) { throw new IllegalArgumentException(e.getMessage() + " delegateClass.delegateMethod(parameterClasses): \"" + - delegateClassName + "." + methodName + "(" + parameterClassNames + + delegateClassName + "." + methodName + "(" + Arrays.toString(parameterClassNames) + ")\" must be public.", e); } catch (NoSuchMethodException e) { throw new IllegalArgumentException(e.getMessage() + " delegateClass.delegateMethod(parameterClasses): \"" + - delegateClassName + "." + methodName + "(" + parameterClassNames + + delegateClassName + "." + methodName + "(" + Arrays.toString(parameterClassNames) + ")\" does not exist.", e); } @@ -42,14 +43,14 @@ public void setPolicyParameters(DynaBeanACRParameter policyParameter) { throw new IllegalArgumentException( " Delegate class \"" + delegateClassName + "\" must be concrete, because method " + - delegateClassName + "." + methodName + "(" + parameterClassNames + + delegateClassName + "." + methodName + "(" + Arrays.toString(parameterClassNames) + ") is not static.", ex); } catch (IllegalAccessException ex) { new IllegalArgumentException( " Delegate class \"" + delegateClassName + "\" must must have a zero-argument constructor, because " + "method delegateClass.delegateMethod(parameterClasses): \"" + - delegateClassName + "." + methodName + "(" + parameterClassNames + + delegateClassName + "." + methodName + "(" + Arrays.toString(parameterClassNames) + ")\" is not static.", ex); } } else { From 1531303c2d5f0904740124031043f552d77c351e Mon Sep 17 00:00:00 2001 From: kwwall Date: Sat, 6 Jun 2020 18:09:03 -0400 Subject: [PATCH 269/709] Add missing space in exception message. --- src/main/java/org/owasp/esapi/util/ObjFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/owasp/esapi/util/ObjFactory.java b/src/main/java/org/owasp/esapi/util/ObjFactory.java index 2c23e5f00..7853ef593 100644 --- a/src/main/java/org/owasp/esapi/util/ObjFactory.java +++ b/src/main/java/org/owasp/esapi/util/ObjFactory.java @@ -92,7 +92,7 @@ public static T make(String className, String typeName) throws Configuration // The class is meant to be singleton, however, the SecurityManager restricts us from calling the // getInstance method on the class, thus this is a configuration issue and a ConfigurationException // is thrown - throw new ConfigurationException( "The SecurityManager has restricted the object factory from getting a reference to the singleton implementation" + + throw new ConfigurationException( "The SecurityManager has restricted the object factory from getting a reference to the singleton implementation " + "of the class [" + className + "]", e ); } From b8c48a9c22317eff3ac52003de9b6eaecce0f7de Mon Sep 17 00:00:00 2001 From: kwwall Date: Sat, 6 Jun 2020 18:10:40 -0400 Subject: [PATCH 270/709] Make conversion from String array to String explicit for exception message. --- src/main/java/org/owasp/esapi/reference/DefaultValidator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/owasp/esapi/reference/DefaultValidator.java b/src/main/java/org/owasp/esapi/reference/DefaultValidator.java index abe6c93a5..530e2efa8 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultValidator.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultValidator.java @@ -23,6 +23,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.text.DateFormat; +import java.util.Arrays; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -764,7 +765,7 @@ public boolean isValidFileContent(String context, byte[] input, int maxBytes, bo public byte[] getValidFileContent(String context, byte[] input, int maxBytes, boolean allowNull) throws ValidationException, IntrusionException { if (isEmpty(input)) { if (allowNull) return null; - throw new ValidationException( context + ": Input required", "Input required: context=" + context + ", input=" + input, context ); + throw new ValidationException( context + ": Input required", "Input required: context=" + context + ", input=" + Arrays.toString(input), context ); } long esapiMaxBytes = ESAPI.securityConfiguration().getAllowedFileUploadSize(); From 120ff02fe530ac2933d36253316cf1e208d13023 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 7 Jun 2020 13:27:52 -0400 Subject: [PATCH 271/709] Remove references to WAF aliases. Feature not fullyed developed. Issue identifed by LGTM. Checked with Arshan and he confirmed it should be removed. --- configuration/esapi/waf-policy.xsd | Bin 20582 -> 19482 bytes .../AppGuardianConfiguration.java | 10 -------- .../configuration/ConfigurationParser.java | 22 +----------------- 3 files changed, 1 insertion(+), 31 deletions(-) diff --git a/configuration/esapi/waf-policy.xsd b/configuration/esapi/waf-policy.xsd index 4287e792b508ce0b8010b83473965465fca10b2d..1ddc99039e59d5d626245497c0e53a33e74f38f1 100644 GIT binary patch delta 28 kcmaF1fN|Cg#toHBn-!U*94FVyv2A|oUdzG4z{|h|0GodZ(EtDd delta 128 zcmbO=gYnq{#toHBoQVuM44Djx48@Z_u#57-c?_vQkpzd}&HhX-j!26B$<1bmDw_O( yMU)+;Sx?M)vOaGTOm33A_~iA@Rg+mw%_iR#u>jH)lPk^kP5v*=v3a(0H3tBCASYA+ diff --git a/src/main/java/org/owasp/esapi/waf/configuration/AppGuardianConfiguration.java b/src/main/java/org/owasp/esapi/waf/configuration/AppGuardianConfiguration.java index 36f5239fe..53e6c3404 100644 --- a/src/main/java/org/owasp/esapi/waf/configuration/AppGuardianConfiguration.java +++ b/src/main/java/org/owasp/esapi/waf/configuration/AppGuardianConfiguration.java @@ -77,11 +77,6 @@ public class AppGuardianConfiguration { public static final String JAVASCRIPT_TARGET_TOKEN = "##1##"; public static final String JAVASCRIPT_REDIRECT = ""; - /* - * The aliases declared in the beginning of the config file. - */ - private HashMap aliases; - /* * Fail response settings. */ @@ -114,8 +109,6 @@ public AppGuardianConfiguration() { afterBodyRules = new ArrayList(); beforeResponseRules = new ArrayList(); cookieRules = new ArrayList(); - - aliases = new HashMap(); } /* @@ -160,9 +153,6 @@ public void setDefaultResponseCode(int defaultResponseCode) { this.defaultResponseCode = defaultResponseCode; } - public void addAlias(String key, Object obj) { - aliases.put(key, obj); - } public List getBeforeBodyRules() { return beforeBodyRules; diff --git a/src/main/java/org/owasp/esapi/waf/configuration/ConfigurationParser.java b/src/main/java/org/owasp/esapi/waf/configuration/ConfigurationParser.java index 4531ed058..cc429d182 100644 --- a/src/main/java/org/owasp/esapi/waf/configuration/ConfigurationParser.java +++ b/src/main/java/org/owasp/esapi/waf/configuration/ConfigurationParser.java @@ -94,7 +94,6 @@ public static AppGuardianConfiguration readConfigurationFile(InputStream stream, doc = parser.build(stream); root = doc.getRootElement(); - Element aliasesRoot = root.getFirstChildElement("aliases"); Element settingsRoot = root.getFirstChildElement("settings"); Element authNRoot = root.getFirstChildElement("authentication-rules"); Element authZRoot = root.getFirstChildElement("authorization-rules"); @@ -106,30 +105,11 @@ public static AppGuardianConfiguration readConfigurationFile(InputStream stream, Element beanShellRoot = root.getFirstChildElement("bean-shell-rules"); - /** - * Parse the 'aliases' section. - */ - if ( aliasesRoot != null ) { - Elements aliases = aliasesRoot.getChildElements("alias"); - - for(int i=0;i section is required"); + throw new ConfigurationException("", "The section is required"); } else if ( settingsRoot != null ) { From 9fb1ff81b9ced9108e64ec79661d958d28dd293a Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 7 Jun 2020 16:53:10 -0400 Subject: [PATCH 272/709] Address LGTM alerts about failure to use 'secure' cookies. --- .../esapi/filters/SecurityWrapperRequest.java | 92 +++++++++---------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java b/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java index 910733d99..939f572b6 100644 --- a/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java +++ b/src/main/java/org/owasp/esapi/filters/SecurityWrapperRequest.java @@ -64,11 +64,11 @@ public class SecurityWrapperRequest extends HttpServletRequestWrapper implements * @param request The {@code HttpServletRequest} we are wrapping. */ public SecurityWrapperRequest(HttpServletRequest request) { - super( request ); + super( request ); } private HttpServletRequest getHttpServletRequest() { - return (HttpServletRequest)super.getRequest(); + return (HttpServletRequest)super.getRequest(); } /** @@ -128,8 +128,8 @@ public String getContentType() { public String getContextPath() { String path = getHttpServletRequest().getContextPath(); SecurityConfiguration sc = ESAPI.securityConfiguration(); - //Return empty String for the ROOT context - if (path == null || "".equals(path.trim())) return ""; + //Return empty String for the ROOT context + if (path == null || "".equals(path.trim())) return ""; String clean = ""; try { @@ -159,7 +159,7 @@ public Cookie[] getCookies() { int maxAge = c.getMaxAge(); String domain = c.getDomain(); String path = c.getPath(); - + Cookie n = new Cookie(name, value); n.setMaxAge(maxAge); @@ -347,7 +347,7 @@ public String getParameter(String name) { * @return The "scrubbed" parameter value. */ public String getParameter(String name, boolean allowNull) { - SecurityConfiguration sc = ESAPI.securityConfiguration(); + SecurityConfiguration sc = ESAPI.securityConfiguration(); return getParameter(name, allowNull, sc.getIntProp("HttpUtilities.httpQueryParamValueLength"), "HTTPParameterValue"); } @@ -423,7 +423,7 @@ public Enumeration getParameterNames() { Enumeration en = getHttpServletRequest().getParameterNames(); while (en.hasMoreElements()) { try { - SecurityConfiguration sc = ESAPI.securityConfiguration(); + SecurityConfiguration sc = ESAPI.securityConfiguration(); String name = (String) en.nextElement(); String clean = ESAPI.validator().getValidInput("HTTP parameter name: " + name, name, "HTTPParameterName", sc.getIntProp("HttpUtilities.httpQueryParamNameLength"), true); v.add(clean); @@ -446,8 +446,8 @@ public String[] getParameterValues(String name) { String[] values = getHttpServletRequest().getParameterValues(name); List newValues; - if(values == null) - return null; + if(values == null) + return null; newValues = new ArrayList(); SecurityConfiguration sc = ESAPI.securityConfiguration(); for (String value : values) { @@ -469,7 +469,7 @@ public String[] getParameterValues(String name) { */ public String getPathInfo() { String path = getHttpServletRequest().getPathInfo(); - if (path == null) return null; + if (path == null) return null; String clean = ""; SecurityConfiguration sc = ESAPI.securityConfiguration(); try { @@ -681,15 +681,15 @@ public String getServerName() { * HttpServletRequest after parsing and checking the range 0-65536. * @return The local server port */ - public int getServerPort() { - int port = getHttpServletRequest().getServerPort(); - if ( port < 0 || port > 0xFFFF ) { - logger.warning( Logger.SECURITY_FAILURE, "HTTP server port out of range: " + port ); - port = 0; - } - return port; - } - + public int getServerPort() { + int port = getHttpServletRequest().getServerPort(); + if ( port < 0 || port > 0xFFFF ) { + logger.warning( Logger.SECURITY_FAILURE, "HTTP server port out of range: " + port ); + port = 0; + } + return port; + } + /** * Returns the server path from the HttpServletRequest after canonicalizing @@ -710,52 +710,44 @@ public String getServletPath() { /** * Returns a session, creating it if necessary, and sets the HttpOnly flag - * on the Session ID cookie. + * on the Session ID cookie. The 'secure' flag is also set if the property + * {@Code HttpUtilities.ForceSecureCookies} is set to {@Code true} in the ESAPI.properties file. * @return The current session */ public HttpSession getSession() { - HttpSession session = getHttpServletRequest().getSession(); - - // send a new cookie header with HttpOnly on first and second responses - if (ESAPI.securityConfiguration().getBooleanProp("HttpUtilities.ForceHttpOnlySession")) { - if (session.getAttribute("HTTP_ONLY") == null) { - session.setAttribute("HTTP_ONLY", "set"); - Cookie cookie = new Cookie(ESAPI.securityConfiguration().getStringProp("HttpUtilities.HttpSessionIdName"), session.getId()); - cookie.setPath( getHttpServletRequest().getContextPath() ); - cookie.setMaxAge(-1); // session cookie - HttpServletResponse response = ESAPI.currentResponse(); - if (response != null) { - ESAPI.currentResponse().addCookie(cookie); - } - } - } - return session; + return getSession(true); } /** - * Returns a session, creating it if necessary, and sets the HttpOnly flag - * on the Session ID cookie. - * @param create Create a new session if one doesn't exist + * Returns the current session associated with this request or, if there is no current session and + * {@Code create} is {@Code true}, returns a new session and sets the HttpOnly flag on the session ID cookie. + * The 'secure' flag is also set if the property {@Code HttpUtilities.ForceSecureCookies} is set to + * {@Code true} in the ESAPI.properties file. + * @param create If set to {@Code true}, create a new session if one doesn't exist, otherwise return {@Code null} * @return The current session */ public HttpSession getSession(boolean create) { HttpSession session = getHttpServletRequest().getSession(create); + if (session == null) { return null; } + SecurityConfiguration sc = ESAPI.securityConfiguration(); + // send a new cookie header with HttpOnly on first and second responses - if (ESAPI.securityConfiguration().getBooleanProp("HttpUtilities.ForceHttpOnlySession")) { - if (session.getAttribute("HTTP_ONLY") == null) { - session.setAttribute("HTTP_ONLY", "set"); - Cookie cookie = new Cookie(ESAPI.securityConfiguration().getStringProp("HttpUtilities.HttpSessionIdName"), session.getId()); - cookie.setMaxAge(-1); // session cookie - cookie.setPath( getHttpServletRequest().getContextPath() ); - HttpServletResponse response = ESAPI.currentResponse(); - if (response != null) { - ESAPI.currentResponse().addCookie(cookie); - } - } + if ( sc.getBooleanProp("HttpUtilities.ForceHttpOnlySession") ) { + if (session.getAttribute("HTTP_ONLY") == null) { + session.setAttribute("HTTP_ONLY", "set"); + Cookie cookie = new Cookie( sc.getStringProp("HttpUtilities.HttpSessionIdName"), session.getId() ); + cookie.setMaxAge(-1); // session cookie + cookie.setPath( getHttpServletRequest().getContextPath() ); + cookie.setSecure( sc.getBooleanProp("HttpUtilities.ForceSecureCookies") ); + HttpServletResponse response = ESAPI.currentResponse(); + if (response != null) { + ESAPI.currentResponse().addCookie(cookie); + } + } } return session; } From e700d20f5d3b3bdb7f4323b3174ca64468385c23 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 7 Jun 2020 20:31:38 -0400 Subject: [PATCH 273/709] Reorganized one of the tests and added some new test cases. --- .../owasp/esapi/crypto/CryptoTokenTest.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/owasp/esapi/crypto/CryptoTokenTest.java b/src/test/java/org/owasp/esapi/crypto/CryptoTokenTest.java index 0d2b8a519..52c60c6ee 100644 --- a/src/test/java/org/owasp/esapi/crypto/CryptoTokenTest.java +++ b/src/test/java/org/owasp/esapi/crypto/CryptoTokenTest.java @@ -288,34 +288,42 @@ public final void testSetAndGetAttribute() { // where attribute values contains each of the values that will // be quoted, namely: '\', '=', and ';' try { + String[] attrValues = { "\\", ";", "=", "foo=", "bar\\=", "foobar=\"" }; String complexValue = "kwwall;1291183520293;abc=x=yx;xyz=;efg=a;a;;bbb=quotes\\tuff"; - - ctok.setAttribute("..--__", ""); // Ugly, but legal attr name; empty is legal value. - ctok.setAttribute("attr1", "\\"); - ctok.setAttribute("attr2", ";"); - ctok.setAttribute("attr3", "="); + ctok.setAttribute("complexAttr", complexValue); + ctok.setAttribute("..--__", ""); // Ugly weird but legal attr name; empty is legal value. + + for ( int i = 0; i < attrValues.length; i++ ) { + String attrName = "attr" + i; + String attrVal = attrValues[i]; + + ctok.setAttribute( attrName, attrVal ); + } + String tokenVal = ctok.getToken(); assertNotNull("tokenVal should not be null", tokenVal); CryptoToken ctok2 = new CryptoToken(tokenVal); + String weirdAttr = ctok2.getAttribute("..--__"); assertTrue("Expecting empty string for value of weird attr, but got: " + weirdAttr, weirdAttr.equals("")); - String attr1 = ctok2.getAttribute("attr1"); - assertTrue("attr1 has unexpected value of " + attr1, attr1.equals("\\") ); - - String attr2 = ctok2.getAttribute("attr2"); - assertTrue("attr2 has unexpected value of " + attr2, attr2.equals(";") ); - - String attr3 = ctok2.getAttribute("attr3"); - assertTrue("attr3 has unexpected value of " + attr3, attr3.equals("=") ); - String complexAttr = ctok2.getAttribute("complexAttr"); assertNotNull(complexAttr); assertTrue("complexAttr has unexpected value of " + complexAttr, complexAttr.equals(complexValue) ); + for ( int i = 0; i < attrValues.length; i++ ) { + String attrName = "attr" + i; + String attrVal = attrValues[i]; + String retrieved = ctok2.getAttribute( attrName ); + String assertMsg = "Exptected attribute '" + attrName + "' to have value of '" + attrVal + "', but had value of '" + retrieved; + + assertTrue( assertMsg, attrVal.equals( attrVal ) ); + ctok.setAttribute( attrName, attrVal ); + } + } catch (ValidationException e) { fail("Caught unexpected ValidationException: " + e); } catch (Exception e) { From e1b634e81606b1a2c7cd60b1251af13ee1831511 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 21 Jun 2020 17:08:52 -0400 Subject: [PATCH 274/709] Add registered trademark to first 'OWASP' referene. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 68037a5d2..155838fae 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Enterprise Security API for Java (Legacy)
    -OWASP ESAPI (The OWASP Enterprise Security API) is a free, open source, web application security control library that makes it easier for programmers to write lower-risk applications. The ESAPI for Java library is designed to make it easier for programmers to retrofit security into existing applications. ESAPI for Java also serves as a solid foundation for new development. +OWASP® ESAPI (The OWASP Enterprise Security API) is a free, open source, web application security control library that makes it easier for programmers to write lower-risk applications. The ESAPI for Java library is designed to make it easier for programmers to retrofit security into existing applications. ESAPI for Java also serves as a solid foundation for new development.
    @@ -85,3 +85,6 @@ Old archives for the old Mailman mailing lists for ESAPI-Users and ESAPI-Dev are For a general overview of Google Groups and its web interface, see https://groups.google.com/forum/#!overview For assistance subscribing and unsubscribing to Google Groups, see https://webapps.stackexchange.com/questions/13508/how-can-i-subscribe-to-a-google-mailing-list-with-a-non-google-e-mail-address/15593#15593 + +---------- +OWASP is a registered trademark of The OWASP Foundation. From 8eb9d0fa5528aadbdf474908f18cb4b27ee70df4 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 21 Jun 2020 17:10:55 -0400 Subject: [PATCH 275/709] Update Jaavdoc. --- src/main/java/org/owasp/esapi/ESAPI.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/owasp/esapi/ESAPI.java b/src/main/java/org/owasp/esapi/ESAPI.java index 33eca3fcc..e11239c75 100644 --- a/src/main/java/org/owasp/esapi/ESAPI.java +++ b/src/main/java/org/owasp/esapi/ESAPI.java @@ -93,6 +93,8 @@ public static Authenticator authenticator() { } /** + * The ESAPI Encoder is primarilly used to provide output encoding to + * prevent Cross-Site Scripting (XSS). * @return the current ESAPI Encoder object being used to encode and decode data for this application. */ public static Encoder encoder() { From 56b4aa68c6dafa6b85f6d187756bad1f4da75074 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 21 Jun 2020 17:11:18 -0400 Subject: [PATCH 276/709] Update Javadoc. --- src/main/java/org/owasp/esapi/Encoder.java | 106 +++++++++++++++++---- 1 file changed, 90 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/owasp/esapi/Encoder.java b/src/main/java/org/owasp/esapi/Encoder.java index ca82238be..b89c85e82 100644 --- a/src/main/java/org/owasp/esapi/Encoder.java +++ b/src/main/java/org/owasp/esapi/Encoder.java @@ -23,28 +23,102 @@ /** - * The Encoder interface contains a number of methods for decoding input and encoding output - * so that it will be safe for a variety of interpreters. To prevent - * double-encoding, callers should make sure input does not already contain encoded characters - * by calling canonicalize. Validator implementations should call canonicalize on user input - * before validating to prevent encoded attacks. + * The {@code Encoder} interface contains a number of methods for decoding input and encoding output + * so that it will be safe for a variety of interpreters. Its primary use is to + * provide output encoding to prevent XSS. *

    - * All of the methods must use a "whitelist" or "positive" security model. - * For the encoding methods, this means that all characters should be encoded, except for a specific list of - * "immune" characters that are known to be safe. - *

    - * The Encoder performs two key functions, encoding and decoding. These functions rely + * To prevent double-encoding, callers should make sure input does not already contain encoded characters + * by calling one of the {@code canonicalize()} methods. Validator implementations should call + * {@code canonicalize()} on user input before validating to prevent encoded attacks. + *

    + * All of the methods must use an "allow list" or "positive" security model rather + * than a "deny list" or "negative" security model. For the encoding methods, this means that + * all characters should be encoded, except for a specific list of "immune" characters that are + * known to be safe. + *

    + * The {@code Encoder} performs two key functions, encoding and decoding. These functions rely * on a set of codecs that can be found in the org.owasp.esapi.codecs package. These include: - *

    • CSS Escaping
    • + *
        + *
      • CSS Escaping
      • *
      • HTMLEntity Encoding
      • *
      • JavaScript Escaping
      • - *
      • MySQL Escaping
      • - *
      • Oracle Escaping
      • + *
      • MySQL Database Escaping
      • + *
      • Oracle Database Escaping
      • *
      • Percent Encoding (aka URL Encoding)
      • - *
      • Unix Escaping
      • + *
      • Unix Shell Escaping
      • *
      • VBScript Escaping
      • - *
      • Windows Encoding
      - *

      + *

    • Windows Cmd Escaping
    • + *
    • LDAP Escaping
    • + *
    • XML and XML Attribute Encoding
    • + *
    • XPath Escaping
    • + *
    • Base64 Encoding
    • + *
    + *

    + * Note that in addition to these encoder methods, ESAPI also provides a JSP Tag + * Library ({@code META-INF/esapi.tld}) in the ESAPI jar. This allows one to use + * the more convenient JSP tags in JSPs. These * tags are simply wrappers for the + * various "encodeForXXYZ()" methods. + *

    + * Some important final words: + *

      + *
    • Where to output encode: + * Knowing where to place the output encoding in your code + * is just as important as knowing which context (HTML, HTML attribute, CSS, + * JavaScript, or URL) to use for the output encoding and surprisingly the two + * are often related. In general, output encoding should be done just prior to the + * output being rendered because that is what determines what the appropriate + * context is for the output encoding. In fact, doing output encoding on + * untrusted data that is stored and to be used later--whether stored in an HTTP + * session or in a database--is almost always considered an anti-pattern. An + * example of this is one gathers and stores some untrusted data item such as an + * email address from a user. A developer thinks "let's output encode this and + * store the encoded data in the database, thus making the untrusted data safe + * to use, thus saving us all the encoding troubles later on". On the surface, + * that sounds like a reasonable approach. The problem is how to know what + * output encoding to use, not only for now, but for all possible future + * uses? It might be that the current application code base is only using it in + * an HTML contexxt that is displayed in an HTML report or shown in an HTML + * context in the user's profile. But what it it is later used in a mailto: URL? + * Then instead of HTML encoding, it would need to have URL encoding. Similarly, + * what if there is a later switch made to use AJAX and the untrusted email + * address gets used in a JavaScript context? The complication is that even if + * you know with certainty today all the ways that an untrusted data item is + * used in your application, it is genrally impossible to predict all the + * contexts that it may be used in the future, not only in your application, but + * in other applications that could access that data in the database. + *
    • + *
    • Avoiding multiple nested contexts: + * A really tricky situation to get correct is hen there are multiple nested + * encoding contexts. But far, the most common place this seems to come up is + * untrusted URLs used in JavaScript. How should you handle that? Well, to be + * honest, the best way is to rewrite your code to avoid it. An example of + * this that is well worth reading may be found at + * ESAPI-DEV mailing list archives: + * URL encoding within JavaScript. Be sure to read the entire thread. + * The question itself is too nuanced to be answered in Javadoc, but now, + * hopefully you are at least aware of the potential pitfalls. + *
    • + *
    * * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security From eae125386565b5b89ed4c11565eac041a4b50f57 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 21 Jun 2020 17:12:08 -0400 Subject: [PATCH 277/709] Change cookie path from "//" to "/". --- .../java/org/owasp/esapi/reference/DefaultHTTPUtilities.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java b/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java index 149937579..f690e26bd 100644 --- a/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java +++ b/src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java @@ -715,7 +715,7 @@ public void killAllCookies(HttpServletRequest request, HttpServletResponse respo * @param name */ public void killCookie(HttpServletRequest request, HttpServletResponse response, String name) { - String path = "//"; + String path = "/"; String domain=""; Cookie cookie = getFirstCookie(request, name); if ( cookie != null ) { From 195f07c6553e9c62ea520468c3c34faf2a4aaccd Mon Sep 17 00:00:00 2001 From: kwwall Date: Fri, 26 Jun 2020 20:40:09 -0400 Subject: [PATCH 278/709] Minor typo fix; add Inc. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 155838fae..01095ed7d 100644 --- a/README.md +++ b/README.md @@ -87,4 +87,4 @@ For a general overview of Google Groups and its web interface, see https://group For assistance subscribing and unsubscribing to Google Groups, see https://webapps.stackexchange.com/questions/13508/how-can-i-subscribe-to-a-google-mailing-list-with-a-non-google-e-mail-address/15593#15593 ---------- -OWASP is a registered trademark of The OWASP Foundation. +OWASP is a registered trademark of the OWASP Foundation, Inc. From 980cce5b2e20770088c7a68641a95706864e32d5 Mon Sep 17 00:00:00 2001 From: kwwall Date: Fri, 26 Jun 2020 20:47:26 -0400 Subject: [PATCH 279/709] Update pom.xml to use latest versions of AntiSamy, slf4j, & batik-css. Antisamy: 1.5.8 -> 1.5.10 slf4j: 1.7.26 -> 1.7.30 batik: 1.11 -> 1.13 (addresses CVE-2019-17566) --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 132075ffd..a57922307 100644 --- a/pom.xml +++ b/pom.xml @@ -228,7 +228,7 @@ org.owasp.antisamy antisamy - 1.5.8 + 1.5.10 xml-apis @@ -243,7 +243,7 @@ org.slf4j slf4j-api - 1.7.26 + 1.7.30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ecf4a202c46c910b3b98559fd635b44b683dfa7a Mon Sep 17 00:00:00 2001 From: kwwall Date: Thu, 2 Jul 2020 16:30:30 -0400 Subject: [PATCH 288/709] Update wiki link to new OWASP URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01095ed7d..baddc52bb 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ More detail is available in the file '[SECURITY.md](https://raw.githubuserconten ## Where to Find More Information on ESAPI -*Wiki:* https://www.owasp.org/index.php/Category:OWASP_Enterprise_Security_API +*Wiki:* https://owasp.org/www-project-enterprise-security-api/ *Nightly Build:* Travis CI - https://travis-ci.org/bkimminich/esapi-java-legacy From 7abf4a5cfe296a04003ca5222cfb5a53f3bca935 Mon Sep 17 00:00:00 2001 From: kwwall Date: Fri, 3 Jul 2020 00:20:56 -0400 Subject: [PATCH 289/709] Fix typo. s/parse/parser/ --- src/main/java/org/owasp/esapi/Encoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/owasp/esapi/Encoder.java b/src/main/java/org/owasp/esapi/Encoder.java index 48b1e9cf4..b9d42dea5 100644 --- a/src/main/java/org/owasp/esapi/Encoder.java +++ b/src/main/java/org/owasp/esapi/Encoder.java @@ -468,7 +468,7 @@ public interface Encoder { *

    * The use of a real XML parser is strongly encouraged. However, in the * hopefully rare case that you need to make sure that data is safe for - * inclusion in an XML document and cannot use a parse, this method provides + * inclusion in an XML document and cannot use a parser, this method provides * a safe mechanism to do so. * * @see Character Encoding in Entities From 807adf71fa3c1efd7a3b09a7a190b87c4c18f228 Mon Sep 17 00:00:00 2001 From: davewichers Date: Fri, 3 Jul 2020 16:35:08 -0400 Subject: [PATCH 290/709] Upgrade some dependencies and most plugins. Add FindSecBugs plugin to SpotBugs. Add animal sniffer plugin to try to detect use of Java APIs not in Java 7. Plugin runs but doesn't detect use of APIs currently causing compilation errors with Java 7. --- pom.xml | 204 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 118 insertions(+), 86 deletions(-) diff --git a/pom.xml b/pom.xml index a9dcd8a5e..3235520cd 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ The Open Web Application Security Project (OWASP) - http://www.owasp.org/index.php + https://owasp.org @@ -90,7 +90,7 @@ Jeff Williams - Aspect Security + Contrast Security Project Inventor @@ -121,7 +121,6 @@ Dave Wichers - Aspect Security Jim Manico @@ -133,12 +132,17 @@ UTF-8 + 1.23 + 2.0.7 + 4.0.4 + 3.0.0-M5 javax.servlet javax.servlet-api + 3.0.1 provided @@ -253,6 +257,7 @@ commons-io commons-io + 2.6 @@ -292,24 +297,38 @@ 1.4.01 + + + com.github.spotbugs + spotbugs-annotations + ${version.spotbugs} + true + + + net.jcip + jcip-annotations + 1.0 + true + + junit junit - 4.12 + 4.13 test org.bouncycastle bcprov-jdk15on - 1.61 + 1.65.01 test org.powermock powermock-api-mockito2 - 2.0.2 + ${version.powermock} test @@ -319,31 +338,13 @@ org.javassist javassist - - - org.mockito - mockito-core - - - org.powermock - powermock-reflect - - - net.bytebuddy - byte-buddy - - - net.bytebuddy - byte-buddy-agent - - + org.javassist javassist 3.25.0-GA @@ -352,19 +353,26 @@ org.mockito mockito-core - 2.27.0 + + 2.28.2 test org.powermock powermock-module-junit4 - 2.0.2 + ${version.powermock} test + + + junit + junit + + org.powermock powermock-reflect - 2.0.2 + ${version.powermock} test @@ -384,13 +392,13 @@ org.openjdk.jmh jmh-core - 1.21 + ${version.jmh} test org.openjdk.jmh jmh-generator-annprocess - 1.21 + ${version.jmh} test @@ -401,51 +409,22 @@ org.apache.maven.plugins maven-assembly-plugin - 3.1.1 + 3.3.0 org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.1.2 org.apache.maven.plugins maven-release-plugin - 2.5.3 + 3.0.0-M1 - - - com.github.spotbugs - spotbugs-maven-plugin - 3.1.11 - - - - com.github.spotbugs - spotbugs - 3.1.12 - - - - - - ${PhaseIfJava8plus} - - - - true - true - true - Low - Max - false - - - + net.sourceforge.maven-taglib maven-taglib-plugin @@ -467,7 +446,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.8.1 1.7 1.7 @@ -501,7 +480,7 @@ org.apache.maven.plugins maven-deploy-plugin - 2.8.2 + 3.0.0-M1 @@ -516,33 +495,60 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.0.0-M2 + 3.0.0-M3 org.codehaus.mojo extra-enforcer-rules 1.2 + + org.codehaus.mojo + animal-sniffer-enforcer-rule + + 1.17 + - - enforce - + check-java-versions + compile + enforce 1.7 - + ESAPI 2.x now uses the JDK1.7 for its baseline. Please make sure that your JAVA_HOME environment variable is pointed to a JDK1.7 or later distribution. - + 1.7 + true + + Dependencies shouldn't require Java 8+ + true + + + + check-java7API-signatures + compile + enforce + + + + + org.codehaus.mojo.signature + + java17 + 1.0 + + + @@ -564,13 +570,13 @@ org.apache.maven.plugins maven-install-plugin - 2.5.2 + 3.0.0-M1 org.apache.maven.plugins maven-jar-plugin - 3.1.0 + 3.2.0 @@ -584,7 +590,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.0 + 3.2.0 7 none @@ -608,13 +614,13 @@ org.apache.maven.plugins maven-pmd-plugin - 3.11.0 + 3.13.0 org.apache.maven.plugins maven-project-info-reports-plugin - 3.0.0 + 3.1.0 @@ -626,13 +632,13 @@ org.apache.maven.plugins maven-site-plugin - 3.7.1 + 3.9.1 org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.2.1 attach-sources @@ -644,14 +650,14 @@ org.apache.maven.plugins - maven-surefire-report-plugin - 2.22.1 + maven-surefire-plugin + ${version.surefire} org.apache.maven.plugins - maven-surefire-plugin - 2.22.1 + maven-surefire-report-plugin + ${version.surefire} @@ -687,7 +693,7 @@ org.owasp dependency-check-maven - 5.0.0 + 5.3.2 5.0 ./suppressions.xml @@ -767,11 +773,11 @@ org.apache.maven.plugins - maven-surefire-report-plugin + maven-surefire-plugin org.apache.maven.plugins - maven-surefire-plugin + maven-surefire-report-plugin + org.codehaus.mojo versions-maven-plugin @@ -799,12 +805,38 @@ + Java8plus [1.8,) site + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + ${version.spotbugs} + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.10.1 + + + Max + false + + + + + From cceffa6aab0887e379be1c7117d2e620b36cfdee Mon Sep 17 00:00:00 2001 From: davewichers Date: Fri, 3 Jul 2020 17:56:33 -0400 Subject: [PATCH 291/709] Fix 1 link to the OWASP web site in pom. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3235520cd..8623ac2e6 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ ESAPI - https://www.owasp.org/index.php/Category:OWASP_Enterprise_Security_API + https://owasp.org/www-project-enterprise-security-api/ The Enterprise Security API (ESAPI) project is an OWASP project to create simple strong security controls for every web platform. Security controls are not simple to build. You can read about the From f94ca94e15237c0f0887913d5d33c697fb97594a Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Fri, 3 Jul 2020 18:48:59 -0400 Subject: [PATCH 292/709] Fix for GitHub Issue 552 (#553) * Fix #552 * Undesired commit, but 'git checkout -- documentation/esapi4java-core-2.2.1.0-release-notes.txt' has no effect. Sigh. * Updated from 'misc-cleanup' branch. --- .../esapi4java-core-2.2.1.0-release-notes.txt | 248 ++++++++++-------- .../logging/appender/ClientInfoSupplier.java | 8 +- .../appender/EventTypeLogSupplier.java | 8 +- .../logging/appender/ServerInfoSupplier.java | 8 +- .../logging/appender/UserInfoSupplier.java | 8 +- 5 files changed, 153 insertions(+), 127 deletions(-) diff --git a/documentation/esapi4java-core-2.2.1.0-release-notes.txt b/documentation/esapi4java-core-2.2.1.0-release-notes.txt index bb46556b8..46f8bab9d 100644 --- a/documentation/esapi4java-core-2.2.1.0-release-notes.txt +++ b/documentation/esapi4java-core-2.2.1.0-release-notes.txt @@ -1,115 +1,133 @@ -Release notes for ESAPI 2.2.1.0 - Release date: 2020-TBD - Project leaders: - -Kevin W. Wall - -Matt Seil - -Previous release: ESAPI 2.2.0.0, 2019-June-24 - - -Executive Summary: Important Things to Note for this Release ------------------------------------------------------------- - - TBD - -================================================================================================================= - -Basic ESAPI facts - -ESAPI 2.2.0.0 release: - 194 Java source files - 4150 JUnit tests in 118 Java source files - -ESAPI 2.2.1.0 release: - TBD - -GitHub Issues fixed in this release - -Issue # GitHub Issue Title ----------------------------------------------------------------------------------------------- - -143 Enchance encodeForOS to auto-detect the underling OS -226 Javadoc Inaccuracy in getRandomInteger() and getRandomReal() -245 KeyDerivationFunction::computeDerivedKey - possible security level mismatch -256 White space clean up -382 Build Fails on path with space -494 Encoder's encodeForCSS doesn't handle RGB Triplets -503 Bug on on referrer header when value contains `§ion` like `www.asdf.com?a=1§ion=2` -509 HTMLValidationRule.getValid(String,String) does not follow documented specifications -511 Add missing documentation to Validator.addRule() and Validator.getRule() -512 Update Apache Commons Bean Utils to 1.9.4 -515 Adding tests for getCookies (also 516) -519 Issue 494 CSSCodec RGB Triplets -530 Log Bridge Tests -536 Various fixes -538 Addressing log4j 1.x CVE-2019-17571 - ------------------------------------------------------------------------------ - - Changes requiring special attention - ------------------------------------------------------------------------------ - -TBD - ------------------------------------------------------------------------------ - - Other changes in this release, some of which not tracked via GitHub issues - ------------------------------------------------------------------------------ - -Documentation updates for locating Jar files -Unneeded code removed from ExtensiveEncoder -Inline reader added to ExtensiveEncoder -Additional time for windows to always sleep more than given seconds in CryptoTokenTest -Change required by tweak to CipherText.toString() method -Removed call to deprecated CryptoHelper.computeDerivedKey() method -New JUnit tests for org.owasp.esapi.crypto.KeyDerivationFunction class -Use existing toString method rather than a StringBuilder -Documentation and tests -JavaLogger move -Splitting user infor from Client Supplier - ------------------------------------------------------------------------------ - -Developer Activity Report (Changes between release 2.2.0.0 and 2.2.1.0, i.e., between 2019-06-25 and 2020-05-12) -Generated manually (this time) - -Developer Total commits Total Number - of Files Changed -===================================================== -jeremiahjstacey 11 68 -kwwall 15 26 -wiitek 3 6 -xeno6696 8 9 -Michael-Ziluck 2 3 -===================================================== - ------------------------------------------------------------------------------ - -53 Closed PRs since 2.2.0.0 release -=================================== -504 New scripts to suppress noise for 'mvn test' -510 Resolve #509 - Properly throw exception when HTML fails -513 Close issue #512 by updating to 1.9.4 of Commons Beans Util.\ -519 Issue 494 CSSCodec RGB Triplets -520 OS Name DefaultExecutorTests #143 -540 Issue 382: Build Fails on path with space -596 Closes Issue 245 - ------------------------------------------------------------------------------ - -Notice: - - Release notes written by Bill Sempf (bill.sempf@owasp.org) please direct any communication to me. - -Project co-leaders - Kevin W. Wall (kwwall) - Matt Seil (xeno6696) - -Special shout-outs to: - Jeremiah Stacey (jeremiahjstacey) -- All around ESAPI support and JUnit test case developer extraordinaire - Dave Wichers (davewichers) - for Maven Central / Sonatype help - -Thanks you all for your time and effort to ESAPI and making it a better project. And if I've missed any, my apologies; let me know and I will correct it. - +Release notes for ESAPI 2.2.1.0 + Release date: 2020-July-?? + Project leaders: + -Kevin W. Wall + -Matt Seil + +Previous release: ESAPI 2.2.0.0, 2019-June-24 + + +Executive Summary: Important Things to Note for this Release +------------------------------------------------------------ + +This is a minor release. It's main purpose was to update dependencies to eliminate potential vulnerabilities arising from dependencies with known CVEs. See the section "Changes requiring special attention" below for additional details. + +Also special props to Bill Sempf for stepping up and volunteering to prepare the initial cut of these release notes. Had he not done so, this release either would not have release notes or it would have been delayed another 6 months while I procrastinated further with various distractions. (Squirrel!) + +================================================================================================================= + +Basic ESAPI facts +----------------- + +ESAPI 2.2.0.0 release: + 194 Java source files + 4150 JUnit tests in 118 Java source files + +ESAPI 2.2.1.0 release: + 211 Java source files + 4309 JUnit tests in 134 Java source files + +GitHub Issues fixed in this release + +Issue # GitHub Issue Title +---------------------------------------------------------------------------------------------- + +143 Enchance encodeForOS to auto-detect the underling OS +226 Javadoc Inaccuracy in getRandomInteger() and getRandomReal() +245 KeyDerivationFunction::computeDerivedKey - possible security level mismatch +256 White space clean up +382 Build Fails on path with space +494 Encoder's encodeForCSS doesn't handle RGB Triplets +503 Bug on on referrer header when value contains `§ion` like `www.asdf.com?a=1§ion=2` +509 HTMLValidationRule.getValid(String,String) does not follow documented specifications +511 Add missing documentation to Validator.addRule() and Validator.getRule() +512 Update Apache Commons Bean Utils to 1.9.4 +515 Adding tests for getCookies (also 516) +519 Issue 494 CSSCodec RGB Triplets +522 javadoc corrections for Encoder.canonicalize() +530 Log Bridge Tests +536 Various fixes +538 Addressing log4j 1.x CVE-2019-17571 +552 Rewrite implementation of some ESAPI classes to remove Java 8 dependencies + +----------------------------------------------------------------------------- + + Changes requiring special attention + +----------------------------------------------------------------------------- +The new default ESAPI logger is JUL (java.util.logging packages) and we have deprecated the use of Log4j 1.x as it is way past the end-of-life and we now support SLF4J. We did not want to make SLF4J the default logger (at least not yet) as we did not want to have the default ESAPI use require additional dependencies. However, SLF4J is likely to be the future choice, at least once we start on EsAPI 3.0. A special shout-out to Jeremiah Stacey for making this possible by re-factoring much of the ESAPI logger code. Note, the straw that broke the proverbial camel's back was the announcement of CVE-2019-17571 (rated Critical), for which there is no fix available and likely will never be. + +Related to that CVE and how it affects ESAPI, be sure to read + https://github.com/ESAPI/esapi-java-legacy/blob/develop/documentation/ESAPI-security-bulletin2.pdf +which describes CVE-2019-17571, a deserialization vulnerability in Log4j 1.2.17. ESAPI is not affected by this (even if you chose to use Log4j 1 as you default ESAPI logger). This security bulletin describes why this CVE is not exploitable as used by ESAPI. + +Notable dependency updates (excludes those only used with JUnit tests): + antiSamy 1.5.8 -> 1.5.10 + batik-css 1.11 -> 1.13 + commons-beansutil 1.9.3 -> 1.9.4 + slf4j-api 1.7.26 -> 1.7.30 + +Finally, while ESAPI still supports JDK 7 (even though that too is way past end-of-life), the next ESAPI release will move to JDK 8 as the minimal baseline. (We already use Java 8 for development but still to Java 7 source and runtime compatiblity.) + +----------------------------------------------------------------------------- + + Other changes in this release, some of which not tracked via GitHub issues + +----------------------------------------------------------------------------- + +Documentation updates for locating Jar files +Unneeded code removed from ExtensiveEncoder +Inline reader added to ExtensiveEncoder +Additional time for windows to always sleep more than given seconds in CryptoTokenTest +Change required by tweak to CipherText.toString() method +Removed call to deprecated CryptoHelper.computeDerivedKey() method +New JUnit tests for org.owasp.esapi.crypto.KeyDerivationFunction class +Use existing toString method rather than a StringBuilder +Documentation and tests +JavaLogger moved +Splitting user info from Client Supplier + +----------------------------------------------------------------------------- + +Developer Activity Report (Changes between release 2.2.0.0 and 2.2.1.0, i.e., between 2019-06-25 and 2020-05-12) +Generated manually (this time) + +Developer Total Total Number +(GitHub ID) commits of Files Changed +===================================================== +jeremiahjstacey 11 68 +kwwall 16 26 +wiitek 3 6 +xeno6696 8 9 +Michael-Ziluck 2 3 +sempf 1 1 +===================================================== + +----------------------------------------------------------------------------- + +53 Closed PRs since 2.2.0.0 release (those rejected not listed) +=============================================================== +504 New scripts to suppress noise for 'mvn test' +510 Resolve #509 - Properly throw exception when HTML fails +513 Close issue #512 by updating to 1.9.4 of Commons Beans Util.\ +519 Issue 494 CSSCodec RGB Triplets +520 OS Name DefaultExecutorTests #143 +540 Issue 382: Build Fails on path with space +596 Closes Issue 245 + +----------------------------------------------------------------------------- + +Notice: + + Release notes written by Bill Sempf (bill.sempf@owasp.org), but please direct any communication to the project leaders. + +Project co-leaders + Kevin W. Wall (kwwall) + Matt Seil (xeno6696) + +Special shout-outs to: + Jeremiah Stacey (jeremiahjstacey) -- All around ESAPI support and JUnit test case developer extraordinaire + Dave Wichers (davewichers) - for pom.xml improvements + Bill Sempf -- for these release notes. Awesome job, Bill. I owe you a brew. + +Thanks you all for your time and effort to ESAPI and making it a better project. And if I've missed any, my apologies; let me know and I will correct it. diff --git a/src/main/java/org/owasp/esapi/logging/appender/ClientInfoSupplier.java b/src/main/java/org/owasp/esapi/logging/appender/ClientInfoSupplier.java index 2ff07a19a..5a8524340 100644 --- a/src/main/java/org/owasp/esapi/logging/appender/ClientInfoSupplier.java +++ b/src/main/java/org/owasp/esapi/logging/appender/ClientInfoSupplier.java @@ -15,7 +15,8 @@ package org.owasp.esapi.logging.appender; -import java.util.function.Supplier; +// Uncomment and use once ESAPI supports Java 8 as the minimal baseline. +// import java.util.function.Supplier; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -27,7 +28,8 @@ * Supplier which can provide a String representing the client-side connection * information. */ -public class ClientInfoSupplier implements Supplier { +public class ClientInfoSupplier // implements Supplier +{ /** Default Last Host string if the Authenticated user is null.*/ private static final String DEFAULT_LAST_HOST = "#UNKNOWN_HOST#"; /** Session Attribute containing the ESAPI Session id. */ @@ -47,7 +49,7 @@ public class ClientInfoSupplier implements Supplier { /** Whether to log the user info from this instance. */ private boolean logClientInfo = true; - @Override + // @Override -- Uncomment when we switch to Java 8 as minimal baseline. public String get() { String clientInfo = ""; diff --git a/src/main/java/org/owasp/esapi/logging/appender/EventTypeLogSupplier.java b/src/main/java/org/owasp/esapi/logging/appender/EventTypeLogSupplier.java index 2f857f128..4af1bd50b 100644 --- a/src/main/java/org/owasp/esapi/logging/appender/EventTypeLogSupplier.java +++ b/src/main/java/org/owasp/esapi/logging/appender/EventTypeLogSupplier.java @@ -15,7 +15,8 @@ package org.owasp.esapi.logging.appender; -import java.util.function.Supplier; +// Uncomment and use once ESAPI supports Java 8 as the minimal baseline. +// import java.util.function.Supplier; import org.owasp.esapi.Logger; import org.owasp.esapi.Logger.EventType; @@ -25,7 +26,8 @@ * an EventType for logging * */ -public class EventTypeLogSupplier implements Supplier { +public class EventTypeLogSupplier // implements Supplier +{ /** EventType reference to supply log representation of. */ private final EventType eventType; @@ -38,7 +40,7 @@ public EventTypeLogSupplier(EventType evtyp) { this.eventType = evtyp == null ? Logger.EVENT_UNSPECIFIED : evtyp; } - @Override + // @Override -- Uncomment when we switch to Java 8 as minimal baseline. public String get() { return eventType.toString(); } diff --git a/src/main/java/org/owasp/esapi/logging/appender/ServerInfoSupplier.java b/src/main/java/org/owasp/esapi/logging/appender/ServerInfoSupplier.java index ca9b4bbd8..c9e0d8e02 100644 --- a/src/main/java/org/owasp/esapi/logging/appender/ServerInfoSupplier.java +++ b/src/main/java/org/owasp/esapi/logging/appender/ServerInfoSupplier.java @@ -15,7 +15,8 @@ package org.owasp.esapi.logging.appender; -import java.util.function.Supplier; +// Uncomment and use once ESAPI supports Java 8 as the minimal baseline. +// import java.util.function.Supplier; import javax.servlet.http.HttpServletRequest; @@ -25,7 +26,8 @@ * Supplier which can provide a String representing the server-side connection * information. */ -public class ServerInfoSupplier implements Supplier { +public class ServerInfoSupplier // implements Supplier +{ /** Whether to log the server connection info. */ private boolean logServerIP = true; /** Whether to log the application name. */ @@ -45,7 +47,7 @@ public ServerInfoSupplier(String logName) { this.logName = logName; } - @Override + // @Override -- Uncomment when we switch to Java 8 as minimal baseline. public String get() { // log server, port, app name, module name -- server:80/app/module StringBuilder appInfo = new StringBuilder(); diff --git a/src/main/java/org/owasp/esapi/logging/appender/UserInfoSupplier.java b/src/main/java/org/owasp/esapi/logging/appender/UserInfoSupplier.java index e9c34e31a..6065ed366 100644 --- a/src/main/java/org/owasp/esapi/logging/appender/UserInfoSupplier.java +++ b/src/main/java/org/owasp/esapi/logging/appender/UserInfoSupplier.java @@ -15,7 +15,8 @@ package org.owasp.esapi.logging.appender; -import java.util.function.Supplier; +// Uncomment and use once ESAPI supports Java 8 as the minimal baseline. +// import java.util.function.Supplier; import org.owasp.esapi.ESAPI; import org.owasp.esapi.User; @@ -24,14 +25,15 @@ * Supplier which can provide a String representing the client-side connection * information. */ -public class UserInfoSupplier implements Supplier { +public class UserInfoSupplier // implements Supplier +{ /** Default UserName string if the Authenticated user is null.*/ private static final String DEFAULT_USERNAME = "#ANONYMOUS#"; /** Whether to log the user info from this instance. */ private boolean logUserInfo = true; - @Override + // @Override -- Uncomment when we switch to Java 8 as minimal baseline. public String get() { // log user information - username:session@ipaddr User user = ESAPI.authenticator().getCurrentUser(); From e8e613fefb24e263a1676c042261e388f16c4569 Mon Sep 17 00:00:00 2001 From: "Kevin W. Wall" Date: Sun, 12 Jul 2020 21:59:43 -0400 Subject: [PATCH 293/709] Prep 2.2.1.0 (#557) * Delete the release notes that prevents me from doing a 'git pull origin' from my fork. * Add note referring to minimal Java 7 baseline. * Close #542 - final release notes for 2.2.1.0. * Close #556 - Add some final additional 'see also' refs. * Close #554 * Close #555 * Added issue 521 which should have been closed per PR 535. Issue now closed. * Close #558 * Update to reflect fix to issue #558 * Find/fix dependency causing java.lang.OutOfMemoryError: PermGen space that only occurs on Mac with Java 7. Apparently it was the surefire plugin. Co-authored-by: davewichers --- README.md | 4 +- .../esapi4java-core-2.2.1.0-release-notes.txt | 253 ++++++++++++++---- pom.xml | 5 +- src/main/java/org/owasp/esapi/Encoder.java | 3 + .../org/owasp/esapi/crypto/CryptoHelper.java | 10 +- .../owasp/esapi/crypto/CryptoHelperTest.java | 10 +- .../esapi/reference/AccessControllerTest.java | 2 +- .../owasp/esapi/reference/ValidatorTest.java | 8 +- .../esapi/fbac-policies/DataAccessRules.txt | 4 +- 9 files changed, 234 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index baddc52bb..dc59e7a2a 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,11 @@ OWASP® ESAPI (The OWASP Enterprise Security API) is a free, open source, web ap # What does Legacy mean?

    This is the legacy branch of ESAPI which means it is an actively maintained branch of the project, however feature development for this branch will not be done. Features that have already been scheduled for the 2.x branch will move forward, but the main focus will be working on the ESAPI 3.x branch. -IMPORTANT NOTE: +IMPORTANT NOTES: The default branch for ESAPI legacy is now the 'develop' branch (rather than the 'master' branch), where future development, bug fixes, etc. will now be done. The 'master' branch is now marked as "protected"; it reflects the latest stable ESAPI release (2.1.0.1 as of this date). Note that this change of making the 'develop' branch the default may affect any pull requests that you were intending to make. +Also, the minimal baseline Java version to use ESAPI is Java 7. (This was changed from Java 6 during the 2.2.0.0 release.) + # Where can I find ESAPI 3.x? https://github.com/ESAPI/esapi-java diff --git a/documentation/esapi4java-core-2.2.1.0-release-notes.txt b/documentation/esapi4java-core-2.2.1.0-release-notes.txt index 94da6b95d..ee3caf5ea 100644 --- a/documentation/esapi4java-core-2.2.1.0-release-notes.txt +++ b/documentation/esapi4java-core-2.2.1.0-release-notes.txt @@ -27,40 +27,61 @@ ESAPI 2.2.1.0 release: 211 Java source files 4309 JUnit tests in 134 Java source files -GitHub Issues fixed in this release +39 GitHub Issues closed in this release Issue # GitHub Issue Title ---------------------------------------------------------------------------------------------- -143 Enchance encodeForOS to auto-detect the underling OS -226 Javadoc Inaccuracy in getRandomInteger() and getRandomReal() -245 KeyDerivationFunction::computeDerivedKey - possible security level mismatch -256 White space clean up -382 Build Fails on path with space -494 Encoder's encodeForCSS doesn't handle RGB Triplets -503 Bug on on referrer header when value contains `§ion` like `www.asdf.com?a=1§ion=2` -509 HTMLValidationRule.getValid(String,String) does not follow documented specifications -511 Add missing documentation to Validator.addRule() and Validator.getRule() -512 Update Apache Commons Bean Utils to 1.9.4 -515 Adding tests for getCookies (also 516) -519 Issue 494 CSSCodec RGB Triplets -522 javadoc corrections for Encoder.canonicalize() -530 Log Bridge Tests -536 Various fixes -538 Addressing log4j 1.x CVE-2019-17571 -552 Rewrite implementation of some ESAPI classes to remove Java 8 dependencies - +143 - Enchance encodeForOS to auto-detect the underling OS +173 - DOMConfigurator is being used inappropriately in the ESAPIWebApplicationFirewallFilter +226 - Javadoc Inaccuracy in getRandomInteger() and getRandomReal() +232 - SecurityWrapperResponse.createCookieHeader modification request (closed; marked 'wontfix') +235 - exception is java.lang.NoClassDefFoundError: org.owasp.esapi.codecs.Codec +245 - KeyDerivationFunction::computeDerivedKey - possible security level mismatch +256 - Whitespace in JavaEncryptor +263 - I am getting validation exception while validating a paramter coming from http request +268 - SecurityWrapperResponse setStatus should not always set SC_OK +269 - org.owasp.esapi.reference.DefaultValidator reports ValidationException with IE 9 +271 - Add Constructor to DefaultSecurityConfiguration to accept a properties file (1.4) +276 - Patch for /branches/2.1/src/main/java/org/owasp/esapi/reference/DefaultExecutor.java +310 - Make HTMLValidationRule to look for antisamy-esapi.xml in classpaths enhancement +382 - Build Fails on path with space +465 - Update both ESAPI.properties files to show comment for ESAPI logger support for SLF4J +488 - Missed a legal input case in DefaultSecurityConfiguration.java +494 - Encoder's encodeForCSS doesn't handle RGB Triplets +495 - Maven Install Requires GPG Key +499 - ValidatorTest.isValidDirectoryPath() has tests that fail under Windows if ESAPI tests run from different drive where Windows installed +500 - Suppress noise from ESAPI searching for properties and stop ignoring important IOExceptions +503 - Bug on on referrer header when value contains `§ion` like `www.asdf.com?a=1§ion=2` +509 - HTMLValidationRule.getValid(String,String) does not follow documented specifications +511 - Add missing documentation to Validator.addRule() and Validator.getRule() +512 - Update Apache Commons Bean Utils to 1.9.4 +515 - NullPointerException can result from line 163 of SecurityWrapperRequest.java: +521 - JUnit test ValidatorTest.testIsValidSafeHTML() now failing +522 - javadoc corrections for Encoder.canonicalize() +523 - Links in README to users list and dev list are reversed +527 - Configuration flag for disabling Logger User and App Information +530 - Apply default logging content to SLF4J messages +532 - Update JUL and Log4J code structure and workflow to match SLF4J +536 - SecurityWrapperResponse setHeader error message is unclear +538 - Addressing log4j 1.x CVE-2019-17571 +542 - Write up ESAPI release notes for planned 2.2.1.0 release +552 - Rewrite implementation of some ESAPI classes to remove Java 8 dependencies +554 - CryptoHelper.arrayCompare() fails with NPE under Java 7 when one of the arguments is null +555 - JUnit test org.owasp.esapi.reference.AccessControllerTest.testIsAuthorizedForData fails when run against Java 7 on Linux +556 - Major overhaul to ESAPI Encoder Javadoc based on ESAPI Encoder Usability Study +558 - ValidatorTest.testIsValidDirectoryPath() JUnit test fails under MacOS ----------------------------------------------------------------------------- - Changes requiring special attention + Changes Requiring Special Attention ----------------------------------------------------------------------------- -The new default ESAPI logger is JUL (java.util.logging packages) and we have deprecated the use of Log4j 1.x as it is way past the end-of-life and we now support SLF4J. We did not want to make SLF4J the default logger (at least not yet) as we did not want to have the default ESAPI use require additional dependencies. However, SLF4J is likely to be the future choice, at least once we start on EsAPI 3.0. A special shout-out to Jeremiah Stacey for making this possible by re-factoring much of the ESAPI logger code. Note, the straw that broke the proverbial camel's back was the announcement of CVE-2019-17571 (rated Critical), for which there is no fix available and likely will never be. +The new default ESAPI logger is JUL (java.util.logging packages) and we have deprecated the use of Log4J 1.x because we now support SLF4J and Log4J 1.x is way past its end-of-life. We did not want to make SLF4J the default logger (at least not yet) as we did not want to have the default ESAPI use require additional dependencies. However, SLF4J is likely to be the future choice, at least once we start on EsAPI 3.0. A special shout-out to Jeremiah Stacey for making this possible by re-factoring much of the ESAPI logger code. Note, the straw that broke the proverbial camel's back was the announcement of CVE-2019-17571 (rated Critical), for which there is no fix available and likely will never be. Related to that CVE and how it affects ESAPI, be sure to read https://github.com/ESAPI/esapi-java-legacy/blob/develop/documentation/ESAPI-security-bulletin2.pdf -which describes CVE-2019-17571, a deserialization vulnerability in Log4j 1.2.17. ESAPI is not affected by this (even if you chose to use Log4j 1 as you default ESAPI logger). This security bulletin describes why this CVE is not exploitable as used by ESAPI. +which describes CVE-2019-17571, a deserialization vulnerability in Log4J 1.2.17. ESAPI is not affected by this (even if you chose to use Log4J 1 as you default ESAPI logger). This security bulletin describes why this CVE is not exploitable as used by ESAPI. Notable dependency updates (excludes those only used with JUnit tests): antiSamy 1.5.8 -> 1.5.10 @@ -70,6 +91,32 @@ Notable dependency updates (excludes those only used with JUnit tests): Finally, while ESAPI still supports JDK 7 (even though that too is way past end-of-life), the next ESAPI release will move to JDK 8 as the minimal baseline. (We already use Java 8 for development but still to Java 7 source and runtime compatiblity.) +----------------------------------------------------------------------------- + + Known Issues / Problems + +----------------------------------------------------------------------------- +If you use Java 7 (the minimal Java baseline supported by ESAPI) and try to run 'mvn test' there is one test that fails. This test passes with Java 8. The failing test is: + + [ERROR] Tests run: 5, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.203 s + <<< FAILURE! - in org.owasp.esapi.crypto.SecurityProviderLoaderTest + [ERROR] org.owasp.esapi.crypto.SecurityProviderLoaderTest.testWithBouncyCastle + Time elapsed: 0.116 s <<< FAILURE! + java.lang.AssertionError: Encryption w/ Bouncy Castle failed with + EncryptionException for preferred cipher transformation; exception was: + org.owasp.esapi.errors.EncryptionException: Encryption failure (unavailable + cipher requested) + at + org.owasp.esapi.crypto.SecurityProviderLoaderTest.testWithBouncyCastle(Security + ProviderLoaderTest.java:133) + +I will spare you all the details and tell you that this has to do with Java 7 not being able to correctly parse the signed Bouncy Castle JCE provider jar. More details are available at: + https://www.bouncycastle.org/latest_releases.html +and + https://github.com/bcgit/bc-java/issues/477 +I am sure that there are ways of making Bouncy Castle work with Java 7, but since ESAPI does not rely on Bouncy Castle (it can use any compliant JCE provider), this should not be a problem. (It works fine with the default SunJCE provider.) If it is important to get the BC provider working with the ESAPI Encryptor and Java 7, then open a GitHub issue and we will take a deeper look at it and see if we can suggest something. + + ----------------------------------------------------------------------------- Other changes in this release, some of which not tracked via GitHub issues @@ -77,60 +124,154 @@ Finally, while ESAPI still supports JDK 7 (even though that too is way past end- ----------------------------------------------------------------------------- Documentation updates for locating Jar files -Unneeded code removed from ExtensiveEncoder +Unneeded code removed from ExtensiveEncoderURI test Inline reader added to ExtensiveEncoder Additional time for windows to always sleep more than given seconds in CryptoTokenTest Change required by tweak to CipherText.toString() method Removed call to deprecated CryptoHelper.computeDerivedKey() method New JUnit tests for org.owasp.esapi.crypto.KeyDerivationFunction class -Use existing toString method rather than a StringBuilder -Documentation and tests -JavaLogger moved -Splitting user info from Client Supplier +Miscellaneous documentation and tests +JavaLogger moved to new package +Log4J 1.x no longer ESAPI's default logger ----------------------------------------------------------------------------- -Developer Activity Report (Changes between release 2.2.0.0 and 2.2.1.0, i.e., between 2019-06-25 and 2020-05-12) -Generated manually (this time) +Developer Activity Report (Changes between release 2.2.0.0 and 2.2.1.0, i.e., between 2019-06-25 and 2020-07-11) +Generated manually (this time) -- all errors are the fault of kwwall and his inability to do simple arithmetic. + +Developer Total Total Number # Merged +(GitHub ID) commits of Files Changed PRs +======================================================== +HJW8472 11 8 1 +jeremiahjstacey 78 70 6 +kwwall 65 64 8 +Michael-Ziluck 3 2 2 +sempf 1 1 1 +wiitek 6 4 2 +xeno6696 3 5 1 +======================================================== + Total: 21 -Developer Total Total Number -(GitHub ID) commits of Files Changed -===================================================== -jeremiahjstacey 11 68 -kwwall 16 26 -wiitek 3 6 -xeno6696 8 9 -Michael-Ziluck 2 3 -sempf 1 1 -===================================================== ----------------------------------------------------------------------------- -53 Closed PRs since 2.2.0.0 release (those rejected not listed) -=============================================================== +21 Closed PRs merged since 2.2.0.0 release (those rejected not listed) +====================================================================== +PR# GitHub ID Description +---------------------------------------------------------------------- +504 -- kwwall -- New scripts to suppress noise for 'mvn test' +505 -- kwwall -- Close issue #256. White-space clean up. +506 -- kwwall -- Closes Issue 245 +508 -- Michael-Ziluck -- Resolves #226 - Corrected docs for the bounded, numeric, random methods +510 -- Michael-Ziluck -- Resolve #509 - Properly throw exception when HTML fails +513 -- kwwall -- Close issue #512 by updating to 1.9.4 of Commons Beans Util. +514 -- xeno6696 -- Fixed issues #503 by writing a new addReferer method, also temporaril… +516 -- jeremiahjstacey -- Issue 515 +518 -- jeremiahjstacey -- Issue #511 Copying Docs from DefaultValidator +519 -- jeremiahjstacey -- Issue 494 CSSCodec RGB Triplets +520 -- jeremiahjstacey -- OS Name DefaultExecutorTests #143 +533 -- jeremiahjstacey -- #532 JUL and Log4J match SLF4J class structure and Workflow +535 -- kwwall -- Issue 521 +537 -- jeremiahjstacey -- Issue 536 +539 -- wiiitek -- upgrade for convergence +540 -- wiiitek -- Issue 382: Build Fails on path with space +541 -- HJW8472 -- Fixed issue #310 +543 -- sempf -- Release notes for 2.2.1.0 +551 -- kwwall -- Misc cleanup +553 -- kwwall -- Fix for GitHub Issue 552 +557 -- kwwall -- Final prep for 2.2.1.0 release + + +CHANGELOG: Create your own. May I suggest: + + git log --since=2019-06-25 --reverse --pretty=medium + + which will show all the commits since just after the last (2.2.0.0) release. + +----------------------------------------------------------------------------- -504 New scripts to suppress noise for 'mvn test' -510 Resolve #509 - Properly throw exception when HTML fails -513 Close issue #512 by updating to 1.9.4 of Commons Beans Util.\ -519 Issue 494 CSSCodec RGB Triplets -520 OS Name DefaultExecutorTests #143 -540 Issue 382: Build Fails on path with space -596 Closes Issue 245 +Direct and Transitive Runtime and Test Dependencies: + + $ mvn dependency:tree + [INFO] Scanning for projects... + [INFO] + [INFO] -----------------------< org.owasp.esapi:esapi >------------------------ + [INFO] Building ESAPI 2.2.1.0 + [INFO] --------------------------------[ jar ]--------------------------------- + [INFO] + [INFO] --- maven-dependency-plugin:3.1.2:tree (default-cli) @ esapi --- + [INFO] org.owasp.esapi:esapi:jar:2.2.1.0-RC1 + [INFO] +- javax.servlet:javax.servlet-api:jar:3.0.1:provided + [INFO] +- javax.servlet.jsp:javax.servlet.jsp-api:jar:2.3.3:provided + [INFO] +- com.io7m.xom:xom:jar:1.2.10:compile + [INFO] +- commons-beanutils:commons-beanutils:jar:1.9.4:compile + [INFO] | +- commons-logging:commons-logging:jar:1.2:compile + [INFO] | \- commons-collections:commons-collections:jar:3.2.2:compile + [INFO] +- commons-configuration:commons-configuration:jar:1.10:compile + [INFO] +- commons-lang:commons-lang:jar:2.6:compile + [INFO] +- commons-fileupload:commons-fileupload:jar:1.3.3:compile + [INFO] +- log4j:log4j:jar:1.2.17:compile + [INFO] +- org.apache.commons:commons-collections4:jar:4.2:compile + [INFO] +- org.apache-extras.beanshell:bsh:jar:2.0b6:compile + [INFO] +- org.owasp.antisamy:antisamy:jar:1.5.10:compile + [INFO] | +- net.sourceforge.nekohtml:nekohtml:jar:1.9.22:compile + [INFO] | +- org.apache.httpcomponents:httpclient:jar:4.5.12:compile + [INFO] | | \- org.apache.httpcomponents:httpcore:jar:4.4.13:compile + [INFO] | \- commons-codec:commons-codec:jar:1.14:compile + [INFO] +- org.slf4j:slf4j-api:jar:1.7.30:compile + [INFO] +- commons-io:commons-io:jar:2.6:compile + [INFO] +- org.apache.xmlgraphics:batik-css:jar:1.13:compile + [INFO] | +- org.apache.xmlgraphics:batik-shared-resources:jar:1.13:compile + [INFO] | +- org.apache.xmlgraphics:batik-util:jar:1.13:compile + [INFO] | | +- org.apache.xmlgraphics:batik-constants:jar:1.13:compile + [INFO] | | \- org.apache.xmlgraphics:batik-i18n:jar:1.13:compile + [INFO] | +- org.apache.xmlgraphics:xmlgraphics-commons:jar:2.4:compile + [INFO] | \- xml-apis:xml-apis-ext:jar:1.3.04:compile + [INFO] +- xalan:xalan:jar:2.7.2:compile + [INFO] | \- xalan:serializer:jar:2.7.2:compile + [INFO] +- xerces:xercesImpl:jar:2.12.0:compile + [INFO] +- xml-apis:xml-apis:jar:1.4.01:compile + [INFO] +- com.github.spotbugs:spotbugs-annotations:jar:4.0.4:compile (optional) + [INFO] | \- com.google.code.findbugs:jsr305:jar:3.0.2:compile (optional) + [INFO] +- net.jcip:jcip-annotations:jar:1.0:compile (optional) + [INFO] +- junit:junit:jar:4.13:test + [INFO] | \- org.hamcrest:hamcrest-core:jar:1.3:test + [INFO] +- org.bouncycastle:bcprov-jdk15on:jar:1.65.01:test + [INFO] +- org.powermock:powermock-api-mockito2:jar:2.0.7:test + [INFO] | \- org.powermock:powermock-api-support:jar:2.0.7:test + [INFO] | \- org.powermock:powermock-core:jar:2.0.7:test + [INFO] +- org.javassist:javassist:jar:3.25.0-GA:test + [INFO] +- org.mockito:mockito-core:jar:2.28.2:test + [INFO] | +- net.bytebuddy:byte-buddy:jar:1.9.10:test + [INFO] | +- net.bytebuddy:byte-buddy-agent:jar:1.9.10:test + [INFO] | \- org.objenesis:objenesis:jar:2.6:test + [INFO] +- org.powermock:powermock-module-junit4:jar:2.0.7:test + [INFO] | \- org.powermock:powermock-module-junit4-common:jar:2.0.7:test + [INFO] +- org.powermock:powermock-reflect:jar:2.0.7:test + [INFO] +- org.openjdk.jmh:jmh-core:jar:1.23:test + [INFO] | +- net.sf.jopt-simple:jopt-simple:jar:4.6:test + [INFO] | \- org.apache.commons:commons-math3:jar:3.2:test + [INFO] \- org.openjdk.jmh:jmh-generator-annprocess:jar:1.23:test + [INFO] ------------------------------------------------------------------------ + [INFO] BUILD SUCCESS + [INFO] ------------------------------------------------------------------------ ----------------------------------------------------------------------------- -Notice: +Ackknowledgements: Release notes written by Bill Sempf (bill.sempf@owasp.org), but please direct any communication to the project leaders. -Project co-leaders - Kevin W. Wall (kwwall) - Matt Seil (xeno6696) - Special shout-outs to: - Jeremiah Stacey (jeremiahjstacey) -- All around ESAPI support and JUnit test case developer extraordinaire - Dave Wichers (davewichers) - for pom.xml improvements - Bill Sempf -- for these release notes. Awesome job, Bill. I owe you a brew. + Jeremiah Stacey (jeremiahjstacey) -- All around ESAPI support and JUnit test case developer extraordinaire and for refactoring ESAPI loggers. + Dave Wichers (davewichers) - for several extremely useful pom.xml improvements. + Bill Sempf (sempf) -- for these release notes. Awesome job, Bill. I owe you a brew. + Chamila Wijayarathna and Nalin A. G. Arachchilage for their authorship and subsequent extensive discussion of their paper "Fighting Against XSS Attacks: A Usability Evaluation of OWASP ESAPI Output Encoding" (https://scholarspace.manoa.hawaii.edu/bitstream/10125/60167/0727.pdf). Their paper and their willingness to engage with me to discuss it was what led to the (hopefully) improved Javadoc for the ESAPI Encoder interface. + And lastly a special thanks to first-time contributors Michael-Ziluck, wiiitek, and HJW8472. Thanks you all for your time and effort to ESAPI and making it a better project. And if I've missed any, my apologies; let me know and I will correct it. + +A special thanks to the ESAPI community from the ESAPI project co-leaders: + Kevin W. Wall (kwwall) <== The irresponsible party for these release notes! + Matt Seil (xeno6696) diff --git a/pom.xml b/pom.xml index 8623ac2e6..8a383d6f2 100644 --- a/pom.xml +++ b/pom.xml @@ -135,7 +135,10 @@ 1.23 2.0.7 4.0.4 - 3.0.0-M5 + + 3.0.0-M2 diff --git a/src/main/java/org/owasp/esapi/Encoder.java b/src/main/java/org/owasp/esapi/Encoder.java index b9d42dea5..a8b949d71 100644 --- a/src/main/java/org/owasp/esapi/Encoder.java +++ b/src/main/java/org/owasp/esapi/Encoder.java @@ -149,6 +149,9 @@ * * * + * @see OWASP Cross-Site Scripting Prevention Cheat Sheet. + * @see OWASP Proactive Controls: C4: Encode and Escape Data + * @see Properly encoding and escaping for the web. * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security * @since June 1, 2007 diff --git a/src/main/java/org/owasp/esapi/crypto/CryptoHelper.java b/src/main/java/org/owasp/esapi/crypto/CryptoHelper.java index 11a6bcb9f..31f276ac4 100644 --- a/src/main/java/org/owasp/esapi/crypto/CryptoHelper.java +++ b/src/main/java/org/owasp/esapi/crypto/CryptoHelper.java @@ -352,7 +352,15 @@ public static void copyByteArray(final byte[] src, byte[] dest) */ @Deprecated public static boolean arrayCompare(byte[] b1, byte[] b2) { - // Note: See GitHub issue #246 + // Note: See GitHub issue #246 and #554. + // If we make Java 8 the minimal ESAPI baseline before we remove this + // method, we can at least remove these next 6 lines. (Issue 554.) + if ( b1 == null && b2 == null ) { // Must test this first! + return true; // Prevent NPE; compatibility with Java 8 and later. + } + if ( b1 == null || b2 == null ) { + return false; // Prevent NPE; compatibility with Java 8 and later. + } return java.security.MessageDigest.isEqual(b1, b2); } diff --git a/src/test/java/org/owasp/esapi/crypto/CryptoHelperTest.java b/src/test/java/org/owasp/esapi/crypto/CryptoHelperTest.java index 678b0acf8..aa7ae0cf3 100644 --- a/src/test/java/org/owasp/esapi/crypto/CryptoHelperTest.java +++ b/src/test/java/org/owasp/esapi/crypto/CryptoHelperTest.java @@ -131,7 +131,13 @@ public final void testArrayCompare() { // stop = System.nanoTime(); // diff = stop - start; // System.out.println("diff: " + diff + " nanosec"); - + +// start = System.nanoTime(); + assertFalse(CryptoHelper.arrayCompare(null, ba1)); +// stop = System.nanoTime(); +// diff = stop - start; +// System.out.println("diff: " + diff + " nanosec"); + ba2 = ba1; // start = System.nanoTime(); assertTrue(CryptoHelper.arrayCompare(ba1, ba2)); @@ -186,4 +192,4 @@ private boolean checkByteArray(byte[] ba, byte b) { public static junit.framework.Test suite() { return new JUnit4TestAdapter(CryptoHelperTest.class); } -} \ No newline at end of file +} diff --git a/src/test/java/org/owasp/esapi/reference/AccessControllerTest.java b/src/test/java/org/owasp/esapi/reference/AccessControllerTest.java index ad53d9ea3..107c1da71 100644 --- a/src/test/java/org/owasp/esapi/reference/AccessControllerTest.java +++ b/src/test/java/org/owasp/esapi/reference/AccessControllerTest.java @@ -226,7 +226,7 @@ public void testIsAuthorizedForData() { userRW = Class.forName("java.lang.String"); anyR = Class.forName("java.io.BufferedReader"); userAdminR = Class.forName("java.util.Random"); - userAdminRW = Class.forName("java.awt.event.MouseWheelEvent"); + userAdminRW = Class.forName("javax.crypto.Cipher"); undefined = Class.forName("java.io.FileWriter"); }catch(ClassNotFoundException cnf){ diff --git a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java index b0fd24789..fc0dc7e06 100644 --- a/src/test/java/org/owasp/esapi/reference/ValidatorTest.java +++ b/src/test/java/org/owasp/esapi/reference/ValidatorTest.java @@ -350,7 +350,13 @@ public void testIsValidDirectoryPath() throws IOException { // Unix specific paths should pass assertTrue(instance.isValidDirectoryPath("test", "/", parent, false)); // Root directory - assertTrue(instance.isValidDirectoryPath("test", "/etc", parent, false)); // Always exist directory + // Unfortunately, on MacOS both "/etc" and "/var" are symlinks + // to "/private/etc" and "/private/var" respectively, and "/sbin" + // and "/bin" sometimes are symlinks on certain *nix OSs, so we need + // to special case MacOS here. + boolean isMac = System.getProperty("os.name").toLowerCase().contains("mac"); + String testDirNotSymLink = isMac ? "/private" : "/etc"; + assertTrue(instance.isValidDirectoryPath("test", testDirNotSymLink, parent, false)); // Always exist directory // Unix specific paths that should not exist or work assertFalse(instance.isValidDirectoryPath("test", "/bin/sh", parent, false)); // Standard shell, not dir diff --git a/src/test/resources/esapi/fbac-policies/DataAccessRules.txt b/src/test/resources/esapi/fbac-policies/DataAccessRules.txt index f21e8c869..0341aa56f 100644 --- a/src/test/resources/esapi/fbac-policies/DataAccessRules.txt +++ b/src/test/resources/esapi/fbac-policies/DataAccessRules.txt @@ -4,6 +4,6 @@ java.io.BufferedReader | any | read | default deny java.lang.String | User | read, write | java.lang.Math | Admin | read, write | java.util.ArrayList | Admin | read | -java.awt.event.MouseWheelEvent | Admin, User | write, read | +javax.crypto.Cipher | Admin, User | write, read | java.util.Date | User | write | -java.util.Random | User, Admin | read | \ No newline at end of file +java.util.Random | User, Admin | read | From 6bc2889e370acfdee6d8ffd711fc9a354506893d Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 12 Jul 2020 22:40:52 -0400 Subject: [PATCH 294/709] Change release from 2.2.1.0-RC1 to 2.2.1.0. Also add additional comment about surefire plug-in. --- pom.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 8a383d6f2..33af4721a 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.owasp.esapi esapi - 2.2.1.0-RC1 + 2.2.1.0 jar @@ -136,8 +136,11 @@ 2.0.7 4.0.4 + org.owasp.esapi.reference.DefaultValidatorInputStringAPITest.getValidInputNullAllowedPassthrough Time elapsed: 2.057 s <<< ERROR! + java.lang.OutOfMemoryError: PermGen space + + when running tests with Java 7 on Mac OS X. No problems observed on Linux. + --> 3.0.0-M2 From fefe6033d8e21963ab5f84b1649b8708366cd524 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 12 Jul 2020 22:42:38 -0400 Subject: [PATCH 295/709] Correct # of commits. --- documentation/esapi4java-core-2.2.1.0-release-notes.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/esapi4java-core-2.2.1.0-release-notes.txt b/documentation/esapi4java-core-2.2.1.0-release-notes.txt index ee3caf5ea..f91f82332 100644 --- a/documentation/esapi4java-core-2.2.1.0-release-notes.txt +++ b/documentation/esapi4java-core-2.2.1.0-release-notes.txt @@ -142,9 +142,10 @@ Generated manually (this time) -- all errors are the fault of kwwall and his ina Developer Total Total Number # Merged (GitHub ID) commits of Files Changed PRs ======================================================== +davewichers 2 1 0 HJW8472 11 8 1 jeremiahjstacey 78 70 6 -kwwall 65 64 8 +kwwall 67 64 8 Michael-Ziluck 3 2 2 sempf 1 1 1 wiitek 6 4 2 From fd009ec4cb166f8ecd72e4cb0fa303109558372e Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 12 Jul 2020 22:57:35 -0400 Subject: [PATCH 296/709] Prep for next development release and change version from 2.2.1.0 to 2.3.0.0-SNAPSHOT. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 33af4721a..6356f1973 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.owasp.esapi esapi - 2.2.1.0 + 2.3.0.0-SNAPSHOT jar From 5ddb8399e60d947dd454007976910e3f51c37f87 Mon Sep 17 00:00:00 2001 From: kwwall Date: Mon, 13 Jul 2020 23:23:44 -0400 Subject: [PATCH 297/709] Miscellaneous minor clean-up of release notes. 1) Add release date. 2) Fix multiple spelling errors 3) Document new 'Known Issue' about running 'mvn test' from Windows 10 'cmd' prompt. --- .../esapi4java-core-2.2.1.0-release-notes.txt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/documentation/esapi4java-core-2.2.1.0-release-notes.txt b/documentation/esapi4java-core-2.2.1.0-release-notes.txt index f91f82332..7d6c54ef7 100644 --- a/documentation/esapi4java-core-2.2.1.0-release-notes.txt +++ b/documentation/esapi4java-core-2.2.1.0-release-notes.txt @@ -1,5 +1,5 @@ Release notes for ESAPI 2.2.1.0 - Release date: 2020-July-?? + Release date: 2020-July-12 Project leaders: -Kevin W. Wall -Matt Seil @@ -32,14 +32,14 @@ ESAPI 2.2.1.0 release: Issue # GitHub Issue Title ---------------------------------------------------------------------------------------------- -143 - Enchance encodeForOS to auto-detect the underling OS +143 - Enhance encodeForOS to auto-detect the underling OS 173 - DOMConfigurator is being used inappropriately in the ESAPIWebApplicationFirewallFilter 226 - Javadoc Inaccuracy in getRandomInteger() and getRandomReal() 232 - SecurityWrapperResponse.createCookieHeader modification request (closed; marked 'wontfix') 235 - exception is java.lang.NoClassDefFoundError: org.owasp.esapi.codecs.Codec 245 - KeyDerivationFunction::computeDerivedKey - possible security level mismatch 256 - Whitespace in JavaEncryptor -263 - I am getting validation exception while validating a paramter coming from http request +263 - I am getting validation exception while validating a parameter coming from http request 268 - SecurityWrapperResponse setStatus should not always set SC_OK 269 - org.owasp.esapi.reference.DefaultValidator reports ValidationException with IE 9 271 - Add Constructor to DefaultSecurityConfiguration to accept a properties file (1.4) @@ -77,7 +77,7 @@ Issue # GitHub Issue Title Changes Requiring Special Attention ----------------------------------------------------------------------------- -The new default ESAPI logger is JUL (java.util.logging packages) and we have deprecated the use of Log4J 1.x because we now support SLF4J and Log4J 1.x is way past its end-of-life. We did not want to make SLF4J the default logger (at least not yet) as we did not want to have the default ESAPI use require additional dependencies. However, SLF4J is likely to be the future choice, at least once we start on EsAPI 3.0. A special shout-out to Jeremiah Stacey for making this possible by re-factoring much of the ESAPI logger code. Note, the straw that broke the proverbial camel's back was the announcement of CVE-2019-17571 (rated Critical), for which there is no fix available and likely will never be. +The new default ESAPI logger is JUL (java.util.logging packages) and we have deprecated the use of Log4J 1.x because we now support SLF4J and Log4J 1.x is way past its end-of-life. We did not want to make SLF4J the default logger (at least not yet) as we did not want to have the default ESAPI use require additional dependencies. However, SLF4J is likely to be the future choice, at least once we start on ESAPI 3.0. A special shout-out to Jeremiah Stacey for making this possible by re-factoring much of the ESAPI logger code. Note, the straw that broke the proverbial camel's back was the announcement of CVE-2019-17571 (rated Critical), for which there is no fix available and likely will never be. Related to that CVE and how it affects ESAPI, be sure to read https://github.com/ESAPI/esapi-java-legacy/blob/develop/documentation/ESAPI-security-bulletin2.pdf @@ -89,7 +89,7 @@ Notable dependency updates (excludes those only used with JUnit tests): commons-beansutil 1.9.3 -> 1.9.4 slf4j-api 1.7.26 -> 1.7.30 -Finally, while ESAPI still supports JDK 7 (even though that too is way past end-of-life), the next ESAPI release will move to JDK 8 as the minimal baseline. (We already use Java 8 for development but still to Java 7 source and runtime compatiblity.) +Finally, while ESAPI still supports JDK 7 (even though that too is way past end-of-life), the next ESAPI release will move to JDK 8 as the minimal baseline. (We already use Java 8 for development but still to Java 7 source and runtime compatibility.) ----------------------------------------------------------------------------- @@ -117,6 +117,13 @@ and I am sure that there are ways of making Bouncy Castle work with Java 7, but since ESAPI does not rely on Bouncy Castle (it can use any compliant JCE provider), this should not be a problem. (It works fine with the default SunJCE provider.) If it is important to get the BC provider working with the ESAPI Encryptor and Java 7, then open a GitHub issue and we will take a deeper look at it and see if we can suggest something. + +Another problem is if you run 'mvn test' from the 'cmd' prompt (and possibly PowerShell as well), you will get intermittent failures (generally between 10-25% of the time) at arbitrary spots. If you run it again without any changes it will work fine without any failures. We have discovered that it doesn't seem to fail if you run the tests from an IDE like Eclipse or if you redirect both stdout and stderr to a file; e.g., + + C:\code\esapi-java-legacy> mvn test >testoutput.txt 2>&1 + +We do not know the reason for these failures, but only that we have observed them on Windows 10. If you see this error, please do NOT report it as a GitHub issue unless you know a fix for it. + ----------------------------------------------------------------------------- Other changes in this release, some of which not tracked via GitHub issues @@ -167,7 +174,7 @@ PR# GitHub ID Description 508 -- Michael-Ziluck -- Resolves #226 - Corrected docs for the bounded, numeric, random methods 510 -- Michael-Ziluck -- Resolve #509 - Properly throw exception when HTML fails 513 -- kwwall -- Close issue #512 by updating to 1.9.4 of Commons Beans Util. -514 -- xeno6696 -- Fixed issues #503 by writing a new addReferer method, also temporaril… +514 -- xeno6696 -- Fixed issues #503 by writing a new addReferer method, also temporarily… 516 -- jeremiahjstacey -- Issue 515 518 -- jeremiahjstacey -- Issue #511 Copying Docs from DefaultValidator 519 -- jeremiahjstacey -- Issue 494 CSSCodec RGB Triplets @@ -260,7 +267,7 @@ Direct and Transitive Runtime and Test Dependencies: ----------------------------------------------------------------------------- -Ackknowledgements: +Acknowledgments: Release notes written by Bill Sempf (bill.sempf@owasp.org), but please direct any communication to the project leaders. From 31f0f988d45de4ed08e515dc3a82842fad4f5bd4 Mon Sep 17 00:00:00 2001 From: kwwall Date: Fri, 17 Jul 2020 21:28:21 -0400 Subject: [PATCH 298/709] Rephrase potentially confusing or misleading statements about ESAPI 3. Thanks to Timo Pagel for bringing this to our awareness. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc59e7a2a..85874bed9 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ OWASP® ESAPI (The OWASP Enterprise Security API) is a free, open source, web ap # What does Legacy mean? -

    This is the legacy branch of ESAPI which means it is an actively maintained branch of the project, however feature development for this branch will not be done. Features that have already been scheduled for the 2.x branch will move forward, but the main focus will be working on the ESAPI 3.x branch. +

    This is the legacy branch of ESAPI which means it is an actively maintained branch of the project, however significan _new_ feature development for this branch will _not_ be done. Features that have already been scheduled for the 2.x branch will move forward. IMPORTANT NOTES: The default branch for ESAPI legacy is now the 'develop' branch (rather than the 'master' branch), where future development, bug fixes, etc. will now be done. The 'master' branch is now marked as "protected"; it reflects the latest stable ESAPI release (2.1.0.1 as of this date). Note that this change of making the 'develop' branch the default may affect any pull requests that you were intending to make. @@ -25,6 +25,8 @@ Also, the minimal baseline Java version to use ESAPI is Java 7. (This was # Where can I find ESAPI 3.x? https://github.com/ESAPI/esapi-java +Note however that work on ESAPI 3 has not yet become in earnest and is only in its earliest planning stages. Even the code that is presently there will likely change. + # Locating ESAPI Jar files The [latest ESAPI release](https://github.com/ESAPI/esapi-java-legacy/releases/latest) is 2.2.0.0. The default configuration jar and its GPG signature can be found at [esapi-2.2.0.0-configuration.jar](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.0.0/esapi-2.2.0.0-configuration.jar) and [esapi-2.2.0.0-configuration.jar.asc](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.0.0/esapi-2.2.0.0-configuration.jar.asc) respectively. From 92ae8e11b2d16e47e85c61db0e47f503da6162d4 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 19 Jul 2020 22:56:06 -0400 Subject: [PATCH 299/709] Fix latest release #s. Fix some MarkDown. Add link to 'Should I use ESAPI?'. --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 85874bed9..3f7970602 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ OWASP® ESAPI (The OWASP Enterprise Security API) is a free, open source, web ap # What does Legacy mean? -

    This is the legacy branch of ESAPI which means it is an actively maintained branch of the project, however significan _new_ feature development for this branch will _not_ be done. Features that have already been scheduled for the 2.x branch will move forward. +

    This is the legacy branch of ESAPI which means it is an actively maintained branch of the project, however significan *new* feature development for this branch will *not* be done. Features that have already been scheduled for the 2.x branch will move forward. IMPORTANT NOTES: The default branch for ESAPI legacy is now the 'develop' branch (rather than the 'master' branch), where future development, bug fixes, etc. will now be done. The 'master' branch is now marked as "protected"; it reflects the latest stable ESAPI release (2.1.0.1 as of this date). Note that this change of making the 'develop' branch the default may affect any pull requests that you were intending to make. @@ -28,10 +28,11 @@ https://github.com/ESAPI/esapi-java Note however that work on ESAPI 3 has not yet become in earnest and is only in its earliest planning stages. Even the code that is presently there will likely change. # Locating ESAPI Jar files -The [latest ESAPI release](https://github.com/ESAPI/esapi-java-legacy/releases/latest) is 2.2.0.0. The default configuration jar and its GPG signature can be found at [esapi-2.2.0.0-configuration.jar](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.0.0/esapi-2.2.0.0-configuration.jar) and [esapi-2.2.0.0-configuration.jar.asc](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.0.0/esapi-2.2.0.0-configuration.jar.asc) respectively. +The [latest ESAPI release](https://github.com/ESAPI/esapi-java-legacy/releases/latest) is 2.2.1.0. The default configuration jar and its GPG signature can be found at [esapi-2.2.1.0-configuration.jar](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.1.0/esapi-2.2.1.0-configuration.jar) and [esapi-2.2.1.0-configuration.jar.asc](https://github.com/ESAPI/esapi-java-legacy/releases/download/esapi-2.2.1.0/esapi-2.2.1.0-configuration.jar.asc) respectively. The latest regular ESAPI jars can are available from Maven Central. +However, before you start a *new* project using ESAPI, but sure to read "[Should I use ESAPI?](https://owasp.org/www-project-enterprise-security-api/#div-shouldiuseesapi)". # ESAPI Deprecation Policy Unless we unintentionally screw-up, our intent is to keep classes, methods, and/or fields whihc have been annotated as "@deprecated" for a minimum of two (2) years or until the next major release number (e.g., 3.x as of now), which ever comes first, before we remove them. @@ -82,7 +83,7 @@ Webchat http://webchat.freenode.net/ *Mailing lists:* As of 2019-03-25, ESAPI's 2 mailing lists were officially moved OFF of their Mailman mailing lists to a new home on Google Groups. -The names of the 2 Google Groups are "[esapi-project-users](mailto:esapi-project-users@owasp.org)" and "[esapi-project-dev](mailto:esapi-project-dev@owasp.org)", which you may POST to _after_ you subscribe to them via "[Subscribe to ESAPI Users list](https://groups.google.com/a/owasp.org/forum/#!forum/esapi-project-users/join)" and "[Subscribe to ESAPI Developers list](https://groups.google.com/a/owasp.org/forum/#!forum/esapi-project-dev/join)" respectively. +The names of the 2 Google Groups are "[esapi-project-users](mailto:esapi-project-users@owasp.org)" and "[esapi-project-dev](mailto:esapi-project-dev@owasp.org)", which you may POST to *after* you subscribe to them via "[Subscribe to ESAPI Users list](https://groups.google.com/a/owasp.org/forum/#!forum/esapi-project-users/join)" and "[Subscribe to ESAPI Developers list](https://groups.google.com/a/owasp.org/forum/#!forum/esapi-project-dev/join)" respectively. Old archives for the old Mailman mailing lists for ESAPI-Users and ESAPI-Dev are still available at https://lists.owasp.org/pipermail/esapi-users/ and https://lists.owasp.org/pipermail/esapi-dev/ respectively. From 073193e4498c83a325c1caec6bd42f3912438ee2 Mon Sep 17 00:00:00 2001 From: jeremiahjstacey Date: Fri, 24 Jul 2020 18:46:39 -0500 Subject: [PATCH 300/709] Issue #560 JUL fixes (#562) * JUL Property Resource Logic fix Adjusting the loading behavior of the esapi-java-logging.properties file to account for a possible null stream resolution. Updating the error handling from system error output to throwing ConfigurationExceptions. Tests updated accordingly * JUL Default Logging configuration Adding a default version of esapi-java-logging.properties to the configuration directory. * JUL Documentation updates Noting the property file requirement and resource location in the class java doc. * Property Comment Updates Supplying a more accurate description of the effect of Logger.ClientInfo * Property Comment Updates Supplying a more accurate description of the effect of Logger.ClientInfo --- configuration/esapi/ESAPI.properties | 2 +- .../esapi/esapi-java-logging.properties | 6 +++ .../esapi/logging/java/JavaLogFactory.java | 10 ++++- .../logging/java/JavaLogFactoryTest.java | 44 +++++++------------ src/test/resources/esapi/ESAPI.properties | 2 +- 5 files changed, 32 insertions(+), 32 deletions(-) create mode 100644 configuration/esapi/esapi-java-logging.properties diff --git a/configuration/esapi/ESAPI.properties b/configuration/esapi/ESAPI.properties index bafe1e38c..ccf146114 100644 --- a/configuration/esapi/ESAPI.properties +++ b/configuration/esapi/ESAPI.properties @@ -394,7 +394,7 @@ Logger.LogFileName=ESAPI_logging_file Logger.MaxLogFileSize=10000000 # Determines whether ESAPI should log the user info. Logger.UserInfo=true -# Determines whether ESAPI should log the app info. +# Determines whether ESAPI should log the session id and client IP. Logger.ClientInfo=true #=========================================================================== diff --git a/configuration/esapi/esapi-java-logging.properties b/configuration/esapi/esapi-java-logging.properties new file mode 100644 index 000000000..71011acc5 --- /dev/null +++ b/configuration/esapi/esapi-java-logging.properties @@ -0,0 +1,6 @@ +handlers= java.util.logging.ConsoleHandler +.level= INFO +java.util.logging.ConsoleHandler.level = INFO +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%3$-7s] %5$s %n +#https://www.logicbig.com/tutorials/core-java-tutorial/logging/customizing-default-format.html \ No newline at end of file diff --git a/src/main/java/org/owasp/esapi/logging/java/JavaLogFactory.java b/src/main/java/org/owasp/esapi/logging/java/JavaLogFactory.java index 93230c8e5..601d0da2a 100644 --- a/src/main/java/org/owasp/esapi/logging/java/JavaLogFactory.java +++ b/src/main/java/org/owasp/esapi/logging/java/JavaLogFactory.java @@ -26,6 +26,7 @@ import org.owasp.esapi.LogFactory; import org.owasp.esapi.Logger; import org.owasp.esapi.codecs.HTMLEntityCodec; +import org.owasp.esapi.errors.ConfigurationException; import org.owasp.esapi.logging.appender.LogAppender; import org.owasp.esapi.logging.appender.LogPrefixAppender; import org.owasp.esapi.logging.cleaning.CodecLogScrubber; @@ -35,6 +36,10 @@ import org.owasp.esapi.reference.DefaultSecurityConfiguration; /** * LogFactory implementation which creates JAVA supporting Loggers. + * + * This implementation requires that a file named 'esapi-java-logging.properties' exists on the classpath. + *
    + * A default file implementation is available in the configuration jar on GitHub under the 'Releases' * */ public class JavaLogFactory implements LogFactory { @@ -86,9 +91,12 @@ public class JavaLogFactory implements LogFactory { */ try (InputStream stream = JavaLogFactory.class.getClassLoader(). getResourceAsStream("esapi-java-logging.properties")) { + if (stream == null) { + throw new ConfigurationException("Unable to locate resource: esapi-java-logging.properties"); + } logManager.readConfiguration(stream); } catch (IOException ioe) { - System.err.print(new IOException("Failed to load esapi-java-logging.properties.", ioe)); + throw new ConfigurationException("Failed to load esapi-java-logging.properties.", ioe); } } diff --git a/src/test/java/org/owasp/esapi/logging/java/JavaLogFactoryTest.java b/src/test/java/org/owasp/esapi/logging/java/JavaLogFactoryTest.java index c0713f239..465083da3 100644 --- a/src/test/java/org/owasp/esapi/logging/java/JavaLogFactoryTest.java +++ b/src/test/java/org/owasp/esapi/logging/java/JavaLogFactoryTest.java @@ -14,24 +14,21 @@ */ package org.owasp.esapi.logging.java; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; import java.util.List; import java.util.logging.LogManager; +import org.hamcrest.CustomMatcher; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; import org.owasp.esapi.Logger; +import org.owasp.esapi.errors.ConfigurationException; import org.owasp.esapi.logging.appender.LogAppender; import org.owasp.esapi.logging.appender.LogPrefixAppender; import org.owasp.esapi.logging.cleaning.CodecLogScrubber; @@ -47,9 +44,12 @@ public class JavaLogFactoryTest { @Rule public TestName testName = new TestName(); + + @Rule + public ExpectedException exEx = ExpectedException.none(); @Test - public void testIOExceptionOnMissingConfiguration() throws Exception { + public void testConfigurationExceptionOnMissingConfiguration() throws Exception { final IOException originException = new IOException(testName.getMethodName()); LogManager testLogManager = new LogManager() { @@ -59,31 +59,17 @@ public void readConfiguration(InputStream ins) throws IOException, SecurityExcep } }; - OutputStream nullOutputStream = new OutputStream() { + exEx.expectMessage("Failed to load esapi-java-logging.properties"); + exEx.expect(ConfigurationException.class); + + exEx.expectCause(new CustomMatcher("Check for IOException") { @Override - public void write(int b) throws IOException { - //No Op + public boolean matches(Object item) { + return item instanceof IOException; } - }; - - ArgumentCaptor stdErrOut = ArgumentCaptor.forClass(Object.class); - PrintStream orig = System.err; - try (PrintStream errPrinter = new PrintStream(nullOutputStream)) { - PrintStream spyPrinter = PowerMockito.spy(errPrinter); - Mockito.doCallRealMethod().when(spyPrinter).print(stdErrOut.capture()); - System.setErr(spyPrinter); - - JavaLogFactory.readLoggerConfiguration(testLogManager); - - Object writeData = stdErrOut.getValue(); - assertTrue(writeData instanceof IOException); - IOException actual = (IOException) writeData; - assertEquals(originException, actual.getCause()); - assertEquals("Failed to load esapi-java-logging.properties.", actual.getMessage()); - } finally { - System.setErr(orig); - } + }); + JavaLogFactory.readLoggerConfiguration(testLogManager); } @Test diff --git a/src/test/resources/esapi/ESAPI.properties b/src/test/resources/esapi/ESAPI.properties index 14b47d32f..6df0acc93 100644 --- a/src/test/resources/esapi/ESAPI.properties +++ b/src/test/resources/esapi/ESAPI.properties @@ -426,7 +426,7 @@ Logger.LogFileName=ESAPI_logging_file Logger.MaxLogFileSize=10000000 # Determines whether ESAPI should log the user info. Logger.UserInfo=true -# Determines whether ESAPI should log the app info. +# Determines whether ESAPI should log the session id and client IP. Logger.ClientInfo=true #=========================================================================== From 4b97073075a26facadf2931de792eddf0f8141b4 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Mon, 27 Jul 2020 03:29:35 +0100 Subject: [PATCH 301/709] fix: upgrade com.github.spotbugs:spotbugs-annotations from 4.0.4 to 4.0.5 (#559) Snyk has created this PR to upgrade com.github.spotbugs:spotbugs-annotations from 4.0.4 to 4.0.5. See this package in NPM: https://www.npmjs.com/package/com.github.spotbugs:spotbugs-annotations See this project in Snyk: https://app.snyk.io/org/planetlevel/project/f53b118f-f068-49f2-98e2-0b5681787cd7?utm_source=github&utm_medium=upgrade-pr --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 6356f1973..5c574aca3 100644 --- a/pom.xml +++ b/pom.xml @@ -134,7 +134,7 @@ UTF-8 1.23 2.0.7 - 4.0.4 + 4.0.5 + Dependencies shouldn't require Java 8+ From b7a7043963b09acf2c2401a4b565298b90c32308 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 26 Jul 2020 22:59:44 -0400 Subject: [PATCH 302/709] Revert "fix: upgrade com.github.spotbugs:spotbugs-annotations from 4.0.4 to 4.0.5 (#559)" This reverts commit 4b97073075a26facadf2931de792eddf0f8141b4. Synk-bot PR #559 results in errors when running 'mvn site' about the '4.0.5' version of com.github.spotbugs:spotbugs-annotations not being found (in Maven Central). I should have tested it first. :-( Lesson learned. --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 5c574aca3..6356f1973 100644 --- a/pom.xml +++ b/pom.xml @@ -134,7 +134,7 @@ UTF-8 1.23 2.0.7 - 4.0.5 + 4.0.4 + Dependencies shouldn't require Java 8+ From ba79395ecbddcfb0896f565cf54ba26936268668 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 26 Jul 2020 23:20:58 -0400 Subject: [PATCH 303/709] Update pom to reflect new 2.2.1.1 patch release. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6356f1973..4a60e515e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.owasp.esapi esapi - 2.3.0.0-SNAPSHOT + 2.2.1.1 jar From 3200f669ce3ae1fe7685a592d1e2f32eb987b892 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 26 Jul 2020 23:28:22 -0400 Subject: [PATCH 304/709] Javadoc fix-ups. --- src/main/java/org/owasp/esapi/Encoder.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/owasp/esapi/Encoder.java b/src/main/java/org/owasp/esapi/Encoder.java index a8b949d71..4c831d281 100644 --- a/src/main/java/org/owasp/esapi/Encoder.java +++ b/src/main/java/org/owasp/esapi/Encoder.java @@ -149,11 +149,10 @@ * * * - * @see OWASP Cross-Site Scripting Prevention Cheat Sheet. + * @see OWASP Cross-Site Scripting Prevention Cheat Sheet * @see OWASP Proactive Controls: C4: Encode and Escape Data - * @see Properly encoding and escaping for the web. - * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security + * @see Properly encoding and escaping for the web + * @author Jeff Williams (jeff.williams .at. owasp.org) * @since June 1, 2007 */ public interface Encoder { @@ -167,7 +166,7 @@ public interface Encoder { * Encoder.AllowMixedEncoding=false * * - * @see Encoder#canonicalize(String, boolean, boolean) canonicalize + * @see #canonicalize(String, boolean, boolean) * @see W3C specifications * * @param input the text to canonicalize @@ -178,7 +177,7 @@ public interface Encoder { /** * This method is the equivalent to calling {@code Encoder.canonicalize(input, strict, strict);}. * - * @see Encoder#canonicalize(String, boolean, boolean) canonicalize + * @see #canonicalize(String, boolean, boolean) * @see W3C specifications * * @param input From 5b95e700c8ef812b01a4d788daad9cf30cf35e29 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 26 Jul 2020 23:36:37 -0400 Subject: [PATCH 305/709] Added 'IMPORTANT WORKAROUND for 2.2.1.0 ESAPI Logging' section. --- .../esapi4java-core-2.2.1.0-release-notes.txt | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/documentation/esapi4java-core-2.2.1.0-release-notes.txt b/documentation/esapi4java-core-2.2.1.0-release-notes.txt index 7d6c54ef7..6119a5c4c 100644 --- a/documentation/esapi4java-core-2.2.1.0-release-notes.txt +++ b/documentation/esapi4java-core-2.2.1.0-release-notes.txt @@ -124,6 +124,37 @@ Another problem is if you run 'mvn test' from the 'cmd' prompt (and possibly Pow We do not know the reason for these failures, but only that we have observed them on Windows 10. If you see this error, please do NOT report it as a GitHub issue unless you know a fix for it. + + *** IMPORTANT WORKAROUND for 2.2.1.0 ESAPI Logging *** + +Lastly, if you try to use the new ESAPI 2.2.1.0 logging, you will notice that you need to change ESAPI.Logger and also possibly provide some other logging properties as well. This is because the logger packages were reorganized to improve maintainability, but we failed to mention it. To use ESAPI logging in ESAPI 2.2.1.0 (and later), you MUST set the ESAPI.Logger property to one of: + + org.owasp.esapi.logging.java.JavaLogFactory - To use the new default, java.util.logging (JUL) + org.owasp.esapi.logging.log4j.Log4JLogFactory - To use the end-of-life Log4J 1.x logger + org.owasp.esapi.logging.slf4j.Slf4JLogFactory - To use the new (to release 2.2.0.0) SLF4J logger + +In addition, if you wish to use JUL for logging, you *must* supply an "esapi-java-logging.properties" file in your classpath. Unfortunately, we failed to drop add that to the ESAPI configuration jar under the GitHub 'Releases', so this file has been added explicitly to the 2.2.1.0 release 'Assets' for this release (for details, see https://github.com/ESAPI/esapi-java-legacy/releases/esapi-2.2.1.0). Even worse, there was a logic error in the static initializer of JavaLogFactory (now fixed in the 2.2.1.1 patch release) that causes a NullPointerException to be thrown so that the message about the missing "esapi-java-logging.properties" file was never seen. + +If you are using JavaLogFactory or Slf4JLogFactory, you will also want to ensure that you have the following ESAPI logging properties set to get the logs to appear what you are used to with Log4J 1.x logging: + # Set the application name if these logs are combined with other applications + Logger.ApplicationName=ExampleApplication + # If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true + Logger.LogEncodingRequired=false + # Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments. + Logger.LogApplicationName=true + # Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments. + Logger.LogServerIP=true + # LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you + # want to place it in a specific directory. + Logger.LogFileName=ESAPI_logging_file + # MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000) + Logger.MaxLogFileSize=10000000 + # Determines whether ESAPI should log the user info. + Logger.UserInfo=true + # Determines whether ESAPI should log the session id and client IP. + Logger.ClientInfo=true + +See GitHub issue #560 for additional details. ----------------------------------------------------------------------------- Other changes in this release, some of which not tracked via GitHub issues @@ -209,7 +240,7 @@ Direct and Transitive Runtime and Test Dependencies: [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-dependency-plugin:3.1.2:tree (default-cli) @ esapi --- - [INFO] org.owasp.esapi:esapi:jar:2.2.1.0-RC1 + [INFO] org.owasp.esapi:esapi:jar:2.2.1.0 [INFO] +- javax.servlet:javax.servlet-api:jar:3.0.1:provided [INFO] +- javax.servlet.jsp:javax.servlet.jsp-api:jar:2.3.3:provided [INFO] +- com.io7m.xom:xom:jar:1.2.10:compile From 156b98cd8aaa7a21d483bded315f3509663576b2 Mon Sep 17 00:00:00 2001 From: kwwall Date: Sun, 26 Jul 2020 23:51:12 -0400 Subject: [PATCH 306/709] New release notes for 2.2.1.1 patch release. --- .../esapi4java-core-2.2.1.1-release-notes.txt | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 documentation/esapi4java-core-2.2.1.1-release-notes.txt diff --git a/documentation/esapi4java-core-2.2.1.1-release-notes.txt b/documentation/esapi4java-core-2.2.1.1-release-notes.txt new file mode 100644 index 000000000..7fa3ffc35 --- /dev/null +++ b/documentation/esapi4java-core-2.2.1.1-release-notes.txt @@ -0,0 +1,237 @@ +Release notes for ESAPI 2.2.1.1 + Release date: 2020-July-26 + Project leaders: + -Kevin W. Wall + -Matt Seil + +Previous release: ESAPI 2.2.1.0, 2020-July-12 + + +Executive Summary: Important Things to Note for this Release +------------------------------------------------------------ + +This is a patch release to address GitHub issue #560. See that GitHub issue and + +Also special props to Bill Sempf for stepping up and volunteering to prepare the initial cut of these release notes. Had he not done so, this release either would not have release notes or it would have been delayed another 6 months while I procrastinated further with various distractions. (Squirrel!) + +================================================================================================================= + +Basic ESAPI facts +----------------- + +ESAPI 2.2.1.0 release: + 211 Java source files + 4309 JUnit tests in 134 Java source files + +ESAPI 2.2.1.1 release: + 211 Java source files + 4312 JUnit tests in 134 Java source files + +39 GitHub Issues closed in this release + +Issue # GitHub Issue Title +---------------------------------------------------------------------------------------------- + +560 - Could not initialize class org.owasp.esapi.logging.java.JavaLogFactory (ESAPI 2.2.1.0) +561 - Update ESAPI-release-steps.odt to note how to do 'Release' on GitHub +564 - Create release notes for 2.2.1.1 patch release + +----------------------------------------------------------------------------- + + Changes Requiring Special Attention + +----------------------------------------------------------------------------- +As of ESAPI 2.2.1.0 (the previous release), the new default ESAPI logger is JUL (java.util.logging packages) and we have deprecated the use of Log4J 1.x because we now support SLF4J and Log4J 1.x is way past its end-of-life. We did not want to make SLF4J the default logger (at least not yet) as we did not want to have the default ESAPI use require additional dependencies. However, SLF4J is likely to be the future choice, at least once we start on ESAPI 3.0. A special shout-out to Jeremiah Stacey for making this possible by re-factoring much of the ESAPI logger code. Note, the straw that broke the proverbial camel's back was the announcement of CVE-2019-17571 (rated Critical), for which there is no fix available and likely will never be. + +However, if you try to juse the new ESAPI 2.2.1.0 logging you will notice that you need to change ESAPI.Logger and also possibly provide some other properties as well to get the logging behavior that you desire. + +To use ESAPI logging in ESAPI 2.2.1.0 (and later), you will need to set the ESAPI.Logger property to + + org.owasp.esapi.logging.java.JavaLogFactory - To use the new default, java.util.logging (JUL) + org.owasp.esapi.logging.log4j.Log4JLogFactory - To use the end-of-life Log4J 1.x logger + org.owasp.esapi.logging.slf4j.Slf4JLogFactory - To use the new (to release 2.2.0.0) SLF4J logger + +In addition, if you wish to use JUL for logging, you *MUST* supply an "esapi-java-logging.properties" file in your classpath. This file is included in the 'esapi-2.2.1.1-configuration.jar' file provided under the 'Assets' section of the GitHub Release at + https://github.com/ESAPI/esapi-java-legacy/releases/esapi-2.2.1.1 + +Unfortunately, there was a logic error in the static initializer of JavaLogFactory (now fixed in this release) that caused a NullPointerException to be thrown so that the message about the missing "esapi-java-logging.properties" file was never seen. + +If you are using JavaLogFactory, you will also want to ensure that you have the following ESAPI logging properties set: + # Set the application name if these logs are combined with other applications + Logger.ApplicationName=ExampleApplication + # If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true + Logger.LogEncodingRequired=false + # Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments. + Logger.LogApplicationName=true + # Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments. + Logger.LogServerIP=true + # LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you + # want to place it in a specific directory. + Logger.LogFileName=ESAPI_logging_file + # MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000) + Logger.MaxLogFileSize=10000000 + # Determines whether ESAPI should log the user info. + Logger.UserInfo=true + # Determines whether ESAPI should log the session id and client IP. + Logger.ClientInfo=true + +See GitHub issue #560 for additional details. + + +Related to that aforemented Log4J 1.x CVE and how it affects ESAPI, be sure to read + https://github.com/ESAPI/esapi-java-legacy/blob/develop/documentation/ESAPI-security-bulletin2.pdf +which describes CVE-2019-17571, a deserialization vulnerability in Log4J 1.2.17. ESAPI is *NOT* affected by this (even if you chose to use Log4J 1 as you default ESAPI logger). This security bulletin describes why this CVE is not exploitable as used by ESAPI. + + +Finally, while ESAPI still supports JDK 7 (even though that too is way past end-of-life), the next ESAPI release will move to JDK 8 as the minimal baseline. (We already use Java 8 for development but still to Java 7 source and runtime compatibility.) + +----------------------------------------------------------------------------- + + Known Issues / Problems + +----------------------------------------------------------------------------- +If you use Java 7 (the minimal Java baseline supported by ESAPI) and try to run 'mvn test' there is one test that fails. This test passes with Java 8. The failing test is: + + [ERROR] Tests run: 5, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.203 s + <<< FAILURE! - in org.owasp.esapi.crypto.SecurityProviderLoaderTest + [ERROR] org.owasp.esapi.crypto.SecurityProviderLoaderTest.testWithBouncyCastle + Time elapsed: 0.116 s <<< FAILURE! + java.lang.AssertionError: Encryption w/ Bouncy Castle failed with + EncryptionException for preferred cipher transformation; exception was: + org.owasp.esapi.errors.EncryptionException: Encryption failure (unavailable + cipher requested) + at + org.owasp.esapi.crypto.SecurityProviderLoaderTest.testWithBouncyCastle(Security + ProviderLoaderTest.java:133) + +I will spare you all the details and tell you that this has to do with Java 7 not being able to correctly parse the signed Bouncy Castle JCE provider jar. More details are available at: + https://www.bouncycastle.org/latest_releases.html +and + https://github.com/bcgit/bc-java/issues/477 +I am sure that there are ways of making Bouncy Castle work with Java 7, but since ESAPI does not rely on Bouncy Castle (it can use any compliant JCE provider), this should not be a problem. (It works fine with the default SunJCE provider.) If it is important to get the BC provider working with the ESAPI Encryptor and Java 7, then open a GitHub issue and we will take a deeper look at it and see if we can suggest something. + + + +Another problem is if you run 'mvn test' from the 'cmd' prompt (and possibly PowerShell as well), you will get intermittent failures (generally between 10-25% of the time) at arbitrary spots. If you run it again without any changes it will work fine without any failures. We have discovered that it doesn't seem to fail if you run the tests from an IDE like Eclipse or if you redirect both stdout and stderr to a file; e.g., + + C:\code\esapi-java-legacy> mvn test >testoutput.txt 2>&1 + +We do not know the reason for these failures, but only that we have observed them on Windows 10. If you see this error, please do NOT report it as a GitHub issue unless you know a fix for it. + +----------------------------------------------------------------------------- + + Other changes in this release, some of which not tracked via GitHub issues + +----------------------------------------------------------------------------- + +* Updates to README.md fileg +* Minor Javadoc fixes to org.owasp.esapi.Encoder +* Fixes / cleanup to 2.2.1.0 release notes (documentation/esapi4java-core-2.2.1.0-release-notes.txt) + +----------------------------------------------------------------------------- + +Developer Activity Report (Changes between release 2.2.1.0 and 2.2.1.1, i.e., between 2020-07-12 and 2020-07-26) +Generated manually (this time) -- all errors are the fault of kwwall and his inability to do simple arithmetic. + +Developer Total Total Number # Merged +(GitHub ID) commits of Files Changed PRs +======================================================== +jeremiahjstacey 5 5 1 +kwwall 67 64 8 +======================================================== + Total: 21 + + +----------------------------------------------------------------------------- + + +2 Closed PRs merged since 2.2.1.0 release (those rejected not listed) +====================================================================== +PR# GitHub ID Description +---------------------------------------------------------------------- +559 -- synk-bot -- Upgrade com.github.spotbugs:spotbugs-annotations from 4.0.4 to 4.0.5 +562 -- jeremiahjstacey -- Issue #560 JUL fixes + +CHANGELOG: Create your own. May I suggest: + + git log --since=2020-07-13 --reverse --pretty=medium + + which will show all the commits since just after the last (2.2.1.0) release. + +----------------------------------------------------------------------------- + +Direct and Transitive Runtime and Test Dependencies: + + $ mvn dependency:tree + [INFO] Scanning for projects... + [INFO] + [INFO] -----------------------< org.owasp.esapi:esapi >------------------------ + [INFO] Building ESAPI 2.2.1.1 + [INFO] --------------------------------[ jar ]--------------------------------- + [INFO] + [INFO] --- maven-dependency-plugin:3.1.2:tree (default-cli) @ esapi --- + [INFO] org.owasp.esapi:esapi:jar:2.2.1.1 + [INFO] +- javax.servlet:javax.servlet-api:jar:3.0.1:provided + [INFO] +- javax.servlet.jsp:javax.servlet.jsp-api:jar:2.3.3:provided + [INFO] +- com.io7m.xom:xom:jar:1.2.10:compile + [INFO] +- commons-beanutils:commons-beanutils:jar:1.9.4:compile + [INFO] | +- commons-logging:commons-logging:jar:1.2:compile + [INFO] | \- commons-collections:commons-collections:jar:3.2.2:compile + [INFO] +- commons-configuration:commons-configuration:jar:1.10:compile + [INFO] +- commons-lang:commons-lang:jar:2.6:compile + [INFO] +- commons-fileupload:commons-fileupload:jar:1.3.3:compile + [INFO] +- log4j:log4j:jar:1.2.17:compile + [INFO] +- org.apache.commons:commons-collections4:jar:4.2:compile + [INFO] +- org.apache-extras.beanshell:bsh:jar:2.0b6:compile + [INFO] +- org.owasp.antisamy:antisamy:jar:1.5.10:compile + [INFO] | +- net.sourceforge.nekohtml:nekohtml:jar:1.9.22:compile + [INFO] | +- org.apache.httpcomponents:httpclient:jar:4.5.12:compile + [INFO] | | \- org.apache.httpcomponents:httpcore:jar:4.4.13:compile + [INFO] | \- commons-codec:commons-codec:jar:1.14:compile + [INFO] +- org.slf4j:slf4j-api:jar:1.7.30:compile + [INFO] +- commons-io:commons-io:jar:2.6:compile + [INFO] +- org.apache.xmlgraphics:batik-css:jar:1.13:compile + [INFO] | +- org.apache.xmlgraphics:batik-shared-resources:jar:1.13:compile + [INFO] | +- org.apache.xmlgraphics:batik-util:jar:1.13:compile + [INFO] | | +- org.apache.xmlgraphics:batik-constants:jar:1.13:compile + [INFO] | | \- org.apache.xmlgraphics:batik-i18n:jar:1.13:compile + [INFO] | +- org.apache.xmlgraphics:xmlgraphics-commons:jar:2.4:compile + [INFO] | \- xml-apis:xml-apis-ext:jar:1.3.04:compile + [INFO] +- xalan:xalan:jar:2.7.2:compile + [INFO] | \- xalan:serializer:jar:2.7.2:compile + [INFO] +- xerces:xercesImpl:jar:2.12.0:compile + [INFO] +- xml-apis:xml-apis:jar:1.4.01:compile + [INFO] +- com.github.spotbugs:spotbugs-annotations:jar:4.0.5:compile (optional) + [INFO] | \- com.google.code.findbugs:jsr305:jar:3.0.2:compile (optional) + [INFO] +- net.jcip:jcip-annotations:jar:1.0:compile (optional) + [INFO] +- junit:junit:jar:4.13:test + [INFO] | \- org.hamcrest:hamcrest-core:jar:1.3:test + [INFO] +- org.bouncycastle:bcprov-jdk15on:jar:1.65.01:test + [INFO] +- org.powermock:powermock-api-mockito2:jar:2.0.7:test + [INFO] | \- org.powermock:powermock-api-support:jar:2.0.7:test + [INFO] | \- org.powermock:powermock-core:jar:2.0.7:test + [INFO] +- org.javassist:javassist:jar:3.25.0-GA:test + [INFO] +- org.mockito:mockito-core:jar:2.28.2:test + [INFO] | +- net.bytebuddy:byte-buddy:jar:1.9.10:test + [INFO] | +- net.bytebuddy:byte-buddy-agent:jar:1.9.10:test + [INFO] | \- org.objenesis:objenesis:jar:2.6:test + [INFO] +- org.powermock:powermock-module-junit4:jar:2.0.7:test + [INFO] | \- org.powermock:powermock-module-junit4-common:jar:2.0.7:test + [INFO] +- org.powermock:powermock-reflect:jar:2.0.7:test + [INFO] +- org.openjdk.jmh:jmh-core:jar:1.23:test + [INFO] | +- net.sf.jopt-simple:jopt-simple:jar:4.6:test + [INFO] | \- org.apache.commons:commons-math3:jar:3.2:test + [INFO] \- org.openjdk.jmh:jmh-generator-annprocess:jar:1.23:test + [INFO] ------------------------------------------------------------------------ + [INFO] BUILD SUCCESS + [INFO] ------------------------------------------------------------------------ + +----------------------------------------------------------------------------- + +Acknowledgments: + +elangoravi for bringing GitHub issue #560 to our attention. This is one where we thought the workaround instructions was harder than just trying to fix it and thus we were encouraged to release a patch. + +A special thanks to the ESAPI community from the ESAPI project co-leaders: + Kevin W. Wall (kwwall) <== The irresponsible party for these release notes! + Matt Seil (xeno6696) From 74fc4ba1fa9d356efa5e7052286482c689aafa09 Mon Sep 17 00:00:00 2001 From: kwwall Date: Mon, 27 Jul 2020 00:01:15 -0400 Subject: [PATCH 307/709] Close #561 plus other major changes and cleanup. --- documentation/ESAPI-release-steps.odt | Bin 273380 -> 165319 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/documentation/ESAPI-release-steps.odt b/documentation/ESAPI-release-steps.odt index bf188f05f965c9d5051d1f4fc60a4ff4df2e1b23..33e7a367d25f75d5535c1da7d9ec4f8ee7888f6c 100644 GIT binary patch delta 58060 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

    + * The primary use of ESAPI {@code Encoder} is to prevent XSS vulnerabilities by + * providing output encoding using the various "encodeForXYZ()" methods, + * where XYZ is one of CSS, HTML, HTMLAttribute, JavaScript, or URL. When + * using the ESAPI output encoders, it is important that you use the one for the + * appropriate context where the output will be rendered. For example, it + * the output appears in an JavaScript context, you should use {@code encodeForJavaScript} + * (note this includes all of the DOM JavaScript event handler attributes such as + * 'onfocus', 'onclick', 'onload', etc.). If the output would be rendered in an HTML + * attribute context (with the exception of the aforementioned 'onevent' type event + * handler attributes), you would use {@code encodeForHTMLAttribute}. If you are + * encoding anywhere a URL is expected (e.g., a 'href' attribute for for <a> or + * a 'src' attribute on a <img> tag, etc.), then you should use use {@code encodeForURL}. + * If encoding CSS, then use {@code encodeForCSS}. Etc. This is because there are + * different escaping requirements for these different contexts. Developers who are + * new to ESAPI or to defending against XSS vulnerabilities are highly encouraged to + * first read the + * + * OWASP Cross-Site Scripting Prevention Cheat Sheet. + *