Skip to content

Commit

Permalink
1. Remove Htmls.encodeJavaScript(), Strings.encodeJavaScript(), Strin…
Browse files Browse the repository at this point in the history
…gs.escape() with Strings.ESCAPE_JAVASCRIPT, and replace them with OWASP Java Encoder APIs instead.

2. Refine test cases.
3. Fix ZK-676
  • Loading branch information
jumperchen committed Aug 27, 2024
1 parent 4b9f8a3 commit 58e4ccb
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 535 deletions.
113 changes: 0 additions & 113 deletions zcommon/src/main/java/org/zkoss/html/HTMLs.java
Original file line number Diff line number Diff line change
Expand Up @@ -211,117 +211,4 @@ public static final boolean isOrphanTag(String tagname) {
for (int j = orphans.length; --j >= 0;)
_orphans.add(orphans[j]);
}

// Since 7.0.2
// The following implementation for encoding JavaScript is referred from
// https://code.google.com/p/owasp-esapi-java/
// which is licensed under New BSD License - http://opensource.org/licenses/BSD-3-Clause
private static char[] IMMUNE_JAVASCRIPT = { ',', '.', '_' };
private static final String[] hex = new String[256];

private static boolean containsCharacter(char c, char[] array) {
for (char ch : array) {
if (c == ch)
return true;
}
return false;
}

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();
}
}
}

private static String toHex(char c) {
return Integer.toHexString(c);
}

private static String getHexForNonAlphanumeric(char c) {
if (c < 0xFF)
return hex[c];
return toHex(c);
}

/**
* Encodes the JavaScript content for <a href=
* "https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet#RULE_.233_-_JavaScript_Escape_Before_Inserting_Untrusted_Data_into_JavaScript_Data_Values"
* > XSS vulnerabilities</a>, the implementation is referred from <a
* href="https://code.google.com/p/owasp-esapi-java/">owasp-esapi-java</a>
* <p>
* Returns backslash encoded numeric format. Does not use backslash
* character escapes such as, \" or \' as these may cause parsing problems.
* For example, if a javascript attribute, such as onmouseover, contains a
* \" that will close the entire attribute and allow an attacker to inject
* another script attribute.
*
* @since 7.0.2
* @deprecated as of release 10.1.0, replaced by {@link org.owasp.encoder.Encode#forJavaScript(String)}
*/
public static String encodeJavaScript(String input) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
sb.append(encodeCharacter(IMMUNE_JAVASCRIPT, c));
}
return sb.toString();
}

/**
* Encodes the JavaScript content for <a href=
* "https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet#RULE_.233_-_JavaScript_Escape_Before_Inserting_Untrusted_Data_into_JavaScript_Data_Values"
* > XSS vulnerabilities</a>, the implementation is referred from <a
* href="https://code.google.com/p/owasp-esapi-java/">owasp-esapi-java</a>
* <p>
* Returns backslash encoded numeric format. Does not use backslash
* character escapes such as, \" or \' as these may cause parsing problems.
* For example, if a javascript attribute, such as onmouseover, contains a
* \" that will close the entire attribute and allow an attacker to inject
* another script attribute.
*
* @since 7.0.2
* @deprecated as of release 10.1.0, replaced by {@link org.owasp.encoder.Encode#forJavaScript(String)}
*/
public static String encodeCharacter(char[] immune, Character c) {

// check for immune characters
if (containsCharacter(c, immune)) {
return "" + c;
}

// check for alphanumeric characters
String hex = getHexForNonAlphanumeric(c);
if (hex == null) {
return "" + c;
}

// Do not use these shortcuts as they can be used to break out of a
// context
// if ( ch == 0x00 ) return "\\0";
// if ( ch == 0x08 ) return "\\b";
// if ( ch == 0x09 ) return "\\t";
// if ( ch == 0x0a ) return "\\n";
// if ( ch == 0x0b ) return "\\v";
// if ( ch == 0x0c ) return "\\f";
// if ( ch == 0x0d ) return "\\r";
// if ( ch == 0x22 ) return "\\\"";
// if ( ch == 0x27 ) return "\\'";
// if ( ch == 0x5c ) return "\\\\";

// encode up to 256 with \\xHH
String temp = Integer.toHexString(c);
if (c < 256) {
String pad = "00".substring(temp.length());
return "\\x" + pad + temp.toUpperCase();
}

// otherwise encode with \\uHHHH
String pad = "0000".substring(temp.length());
return "\\u" + pad + temp.toUpperCase();
}
}
149 changes: 4 additions & 145 deletions zcommon/src/main/java/org/zkoss/lang/Strings.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
package org.zkoss.lang;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

import org.owasp.encoder.Encode;

import org.zkoss.mesg.MCommon;
import org.zkoss.util.IllegalSyntaxException;
Expand All @@ -28,10 +29,6 @@
* @author tomyeh
*/
public class Strings {
/** Used with {@link #escape} to escape a string in
* JavaScript. It assumes the string will be enclosed with a single quote.
*/
public static final String ESCAPE_JAVASCRIPT = "'\n\r\t\f\\/!";
public static final String EMPTY = "";
/**
* Returns true if the string is null or empty.
Expand Down Expand Up @@ -274,7 +271,7 @@ public static final int nextWhitespace(CharSequence src, int from) {
*
* @param src the string to process. If null, null is returned.
* @param specials a string of characters that shall be escaped/quoted
* To escape a string in JavaScript code snippet, you can use {@link #ESCAPE_JAVASCRIPT}.
* To escape a string in JavaScript code snippet, you can use {@link Encode#forJavaScript(String)}.
* @see #unescape
*/
public static final String escape(String src, String specials) {
Expand All @@ -284,10 +281,7 @@ public static final String escape(String src, String specials) {

int k = 0;
for (char c : chars) {
if (shallEncodeUnicode(c, specials)) { // Check if it should be Unicode encoded
String encoded = encodeUnicode(c);
sb.append('\\').append(encoded); // Append encoded form with a backslash
} else if (specials.indexOf(c) >= 0) { // Check if char is a special character to escape
if (specials.indexOf(c) >= 0) { // Check if char is a special character to escape
char escaped = escapeSpecial(src, c, k, specials);
if (escaped != (char) 0) {
sb.append('\\').append(escaped); // Append escaped character with a backslash
Expand All @@ -310,144 +304,9 @@ private static char escapeSpecial(CharSequence src,
case '\t': return 't';
case '\r': return 'r';
case '\f': return 'f';
case '/':
String key;
//escape </script>
if (ESCAPE_JAVASCRIPT.equals(specials) // handle it specially
&& (k <= 0 || src.charAt(k - 1) != '<'
|| k + 8 > src.length() || !("script>"
.equalsIgnoreCase((key = src.subSequence(k + 1,
k + 8).toString())) || "script "
.equalsIgnoreCase(key)))) {
return (char) 0; // don't escape
}
break;
case '!':
//escape <!-- (ZK-676: it causes problem if used with <script>)
if (ESCAPE_JAVASCRIPT.equals(specials) //handle it specially
&& (k <= 0 || src.charAt(k - 1) != '<' || k + 3 > src.length()
|| !"--".equals(src.subSequence(k+1, k+3)))) {
return (char)0; //don't escape
}
break;
}
return cc;
}
/** Escapes (a.k.a. quote) the special characters with backslash
* and appends it the specified string buffer.
*
* @param dst the destination buffer to append to.
* @param src the source to escape from.
* @param specials a string of characters that shall be escaped/quoted
* To escape a string in JavaScript code snippet, you can use {@link #ESCAPE_JAVASCRIPT}.
* @since 5.0.0
*/
public static final
StringBuffer escape(StringBuffer dst, CharSequence src, String specials) {
if (src == null)
return dst;

for (int j = 0, j2 = 0, len = src.length();;) {
String enc = null;
char cc;
int k = j2;
for (;; ++k) {
if (k >= len)
return dst.append((Object)src.subSequence(j, src.length()));

cc = src.charAt(k);
if (shallEncodeUnicode(cc, specials)) {
enc = encodeUnicode(cc);
break;
}
if (specials.indexOf(cc) >= 0)
break;
}

if (enc == null
&& (cc = escapeSpecial(src, cc, k, specials)) == (char)0) {
j2 = k + 1;
continue;
}

dst.append((Object)src.subSequence(j, k)).append('\\');
if (enc != null) dst.append(enc);
else dst.append(cc);
j2 = j = k + 1;
}
}

/** Escapes (a.k.a. quote) the special characters with backslash
* and appends it the specified string buffer.
*
* @param dst the destination buffer to append to.
* @param src the source to escape from.
* @param specials a string of characters that shall be escaped/quoted
* To escape a string in JavaScript code snippet, you can use {@link #ESCAPE_JAVASCRIPT}.
* @since 8.0.0
*/
public static final
StringBuilder escape(StringBuilder dst, CharSequence src, String specials) {
if (src == null)
return dst;
String str = src.toString();

char[] chars = str.toCharArray();
for (int j = 0, j2 = 0, len = chars.length;;) {
String enc = null;
char cc;
int k = j2;
for (;; ++k) {
if (k >= len) {
return dst.append(Arrays.copyOfRange(chars, j, len));
}

cc = chars[k];
if (shallEncodeUnicode(cc, specials)) {
enc = encodeUnicode(cc);
break;
}
if (specials.indexOf(cc) >= 0)
break;
}

if (enc == null
&& (cc = escapeSpecial(src, cc, k, specials)) == (char)0) {
j2 = k + 1;
continue;
}

dst.append(Arrays.copyOfRange(chars, j, k)).append('\\');
if (enc != null) dst.append(enc);
else dst.append(cc);
j2 = j = k + 1;
}
}
/** Escapes (a.k.a. quote) the special characters with backslash.
* <p>Note: this implementation is referred from <a href="https://github.com/unbescape/unbescape">unbescape</a></p>
* @since 8.0.0
*/
public static final String escapeJavaScript(String text) {
// We utilize the unbescape project's implementation to do the escape for Javascript value
// which license is under Apache License 2.0 - https://github.com/unbescape/unbescape
return JavaScriptEscape.escapeJavaScript(text);
}

private static final boolean shallEncodeUnicode(char cc, String specials) {
return ESCAPE_JAVASCRIPT.equals(specials) && cc > (char)255
&& !Character.isLetterOrDigit(cc);
//don't check isSpaceChar since \u2028 will return true and it
//is not recognized by Firefox
}
/** Return "u????". */
private static final String encodeUnicode(int cc) {
final StringBuilder sb = new StringBuilder(6)
.append('u').append(Integer.toHexString(cc));
while (sb.length() < 5)
sb.insert(1, '0');
return sb.toString();
}


/** Un-escape the quoted string.
* @see #escape
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* TagRenderContext.java
Purpose:
Description:
History:
Mon Jan 5 11:48:18 2009, Created by tomyeh
Expand All @@ -19,6 +19,8 @@
import java.util.List;
import java.util.Map;

import org.owasp.encoder.Encode;

import org.zkoss.lang.Strings;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
Expand Down Expand Up @@ -97,7 +99,7 @@ else if (_2ndChild.get(0) == Boolean.TRUE)
final String id = comp.getId();
if (id.length() > 0) {
first = false;
_jsout.append("id:'").append(Strings.escape(id, Strings.ESCAPE_JAVASCRIPT))
_jsout.append("id:'").append(Encode.forJavaScript(id))
.append('\'');
}
if (!comp.isVisible()) {
Expand Down
3 changes: 2 additions & 1 deletion zk/src/main/java/org/zkoss/zk/ui/AbstractComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import org.owasp.encoder.Encode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -2232,7 +2233,7 @@ public void redraw(final Writer out) throws IOException {
if (aupg) {
if (extra.length() > 0) {
out.write(",0,null,'");
out.write(Strings.escape(extra, Strings.ESCAPE_JAVASCRIPT));
out.write(Encode.forJavaScript(extra));
out.write('\'');
}
out.write(']');
Expand Down
11 changes: 6 additions & 5 deletions zk/src/main/java/org/zkoss/zk/ui/http/Wpds.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* Wpds.java
Purpose:
Description:
History:
Fri Jul 17 22:10:49 2009, Created by tomyeh
Expand Down Expand Up @@ -31,6 +31,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.owasp.encoder.Encode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -309,7 +310,7 @@ private static final String getDateJavaScript(Locale locale, int firstDayOfWeek)
//since ZK 8.0.0 default is false
if ("true".equals(Library.getProperty("org.zkoss.zk.ui.invokeFirstRootForAfterKeyDown.enabled", "false")))
sb.append("if (zk.invokeFirstRootForAfterKeyDown == undefined)zk.invokeFirstRootForAfterKeyDown=true;\n");

// since ZK 8.6.2 ZK-4235 for 2DigYearStart
sb.append("zk.TDYS=").append(twoDigitYearStart).append(";\n");

Expand All @@ -319,7 +320,7 @@ private static final String getDateJavaScript(Locale locale, int firstDayOfWeek)
private static final void appendJavaScriptArray(StringBuffer sb, String varnm, String[] vals) {
sb.append("zk.").append(varnm).append("=[");
for (int j = 0;;) {
sb.append('\'').append(Strings.escape(vals[j], Strings.ESCAPE_JAVASCRIPT)).append('\'');
sb.append('\'').append(Encode.forJavaScript(vals[j])).append('\'');
if (++j >= vals.length)
break;
else
Expand Down Expand Up @@ -348,7 +349,7 @@ public void onActivate() {
public void onDeactivate() {
}

//use PhantomExecution
//use PhantomExecution
public Evaluator getEvaluator(Page page, Class<? extends ExpressionFactory> expfcls) {
return null;
}
Expand Down
Loading

0 comments on commit 58e4ccb

Please sign in to comment.