summaryrefslogtreecommitdiffstats
authorStuart McCulloch2012-04-12 11:54:47 (EDT)
committer Stuart McCulloch2012-04-12 11:54:47 (EDT)
commit5cca490c5b9fc44f60a877f767508bc81bff5945 (patch) (side-by-side diff)
treee0e35789a2d3b818df80aa1f346375d57ab2932a
parent927dae24ffde533911d9a7b4dd445c629c6158f0 (diff)
downloadorg.eclipse.hudson.stapler-5cca490c5b9fc44f60a877f767508bc81bff5945.zip
org.eclipse.hudson.stapler-5cca490c5b9fc44f60a877f767508bc81bff5945.tar.gz
org.eclipse.hudson.stapler-5cca490c5b9fc44f60a877f767508bc81bff5945.tar.bz2
Support escape-by-default and raw HTML arguments in InternationalizedStringExpressions
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--stapler-jelly/src/main/java/org/kohsuke/stapler/jelly/CustomJellyContext.java30
-rw-r--r--stapler-jelly/src/main/java/org/kohsuke/stapler/jelly/InternationalizedStringExpression.java76
2 files changed, 104 insertions, 2 deletions
diff --git a/stapler-jelly/src/main/java/org/kohsuke/stapler/jelly/CustomJellyContext.java b/stapler-jelly/src/main/java/org/kohsuke/stapler/jelly/CustomJellyContext.java
index 4f1826f..6154280 100644
--- a/stapler-jelly/src/main/java/org/kohsuke/stapler/jelly/CustomJellyContext.java
+++ b/stapler-jelly/src/main/java/org/kohsuke/stapler/jelly/CustomJellyContext.java
@@ -19,11 +19,14 @@ import org.apache.commons.jelly.parser.XMLParser;
import org.apache.commons.jelly.expression.ExpressionFactory;
import org.apache.commons.jelly.expression.Expression;
import org.apache.commons.jelly.expression.ExpressionSupport;
+import org.apache.commons.jelly.impl.ExpressionScript;
+import org.apache.commons.jelly.impl.ScriptBlock;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyException;
import org.apache.commons.jelly.TagLibrary;
import org.kohsuke.stapler.MetaClassLoader;
+import java.lang.reflect.Field;
import java.net.URL;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
@@ -101,6 +104,18 @@ class CustomJellyContext extends JellyContext {
private static class CustomXMLParser extends XMLParser implements ExpressionFactory {
private ResourceBundle resourceBundle;
+
+ private static Field escapeByDefaultField;
+ static
+ {
+ try {
+ escapeByDefaultField = XMLParser.class.getDeclaredField("escapeByDefault");
+ escapeByDefaultField.setAccessible(true);
+ } catch (Exception e) {
+ // inconsistent base class - ignore
+ }
+ }
+
@Override
protected ExpressionFactory createExpressionFactory() {
return this;
@@ -144,6 +159,19 @@ class CustomJellyContext extends JellyContext {
return new InternationalizedStringExpression(getResourceBundle(),text);
}
+ @Override
+ protected void addExpressionScript(ScriptBlock script, Expression exp) {
+ try {
+ if (exp instanceof InternationalizedStringExpression && escapeByDefaultField.getBoolean(this)) {
+ script.addScript(new ExpressionScript(((InternationalizedStringExpression) exp).escape()));
+ return; // stick with our escaped+internationalized script
+ }
+ } catch (Exception e) {
+ // fall back to original behaviour...
+ }
+ super.addExpressionScript(script, exp);
+ }
+
private String unquote(String s) {
return s.substring(1,s.length()-1);
}
@@ -182,4 +210,4 @@ class CustomJellyContext extends JellyContext {
// "%...." string literal that starts with '%'
private static final Pattern RESOURCE_LITERAL_STRING = Pattern.compile("(\"%[^\"]+\")|('%[^']+')");
-} \ No newline at end of file
+}
diff --git a/stapler-jelly/src/main/java/org/kohsuke/stapler/jelly/InternationalizedStringExpression.java b/stapler-jelly/src/main/java/org/kohsuke/stapler/jelly/InternationalizedStringExpression.java
index 8ac3153..53c375f 100644
--- a/stapler-jelly/src/main/java/org/kohsuke/stapler/jelly/InternationalizedStringExpression.java
+++ b/stapler-jelly/src/main/java/org/kohsuke/stapler/jelly/InternationalizedStringExpression.java
@@ -22,9 +22,10 @@ import org.apache.commons.jelly.JellyContext;
import org.jvnet.localizer.LocaleProvider;
import org.kohsuke.stapler.Stapler;
+import java.util.Calendar;
+import java.util.Date;
import java.util.List;
import java.util.ArrayList;
-import java.util.Locale;
import java.util.Collections;
import java.util.Arrays;
@@ -125,10 +126,17 @@ public class InternationalizedStringExpression extends ExpressionSupport {
}
public Object evaluate(JellyContext jellyContext) {
+ return format(evaluateArguments(jellyContext));
+ }
+
+ Object[] evaluateArguments(JellyContext jellyContext) {
Object[] args = new Object[arguments.length];
for (int i = 0; i < args.length; i++)
args[i] = arguments[i].evaluate(jellyContext);
+ return args;
+ }
+ String format(Object[] args) {
// notify the listener if set
InternationalizedStringExpressionListener listener = (InternationalizedStringExpressionListener)Stapler.getCurrentRequest().getAttribute(LISTENER_NAME);
if(listener!=null)
@@ -137,6 +145,72 @@ public class InternationalizedStringExpression extends ExpressionSupport {
return resourceBundle.format(LocaleProvider.getLocale(),key,args);
}
+ /**
+ * Wraps value to indicate it contains raw HTML that should not be escaped.
+ */
+ public Object rawHtml(Object value) {
+ return value != null ? new RawHtml(value) : null;
+ }
+
+ static final class RawHtml {
+ final Object value;
+
+ RawHtml(Object value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+ }
+
+ Expression escape() {
+ return new ExpressionSupport() {
+ public String getExpressionText() {
+ return expressionText;
+ }
+
+ public Object evaluate(JellyContext context) {
+ Object[] args = evaluateArguments(context);
+ for (int i = 0; i < args.length; i++) {
+ args[i] = escapeArgument(args[i]);
+ }
+ return format(args);
+ }
+ };
+ }
+
+ static Object escapeArgument(Object arg) {
+ if (arg instanceof RawHtml) {
+ return ((RawHtml)arg).value; // no escaping wanted
+ }
+ if ( arg == null || arg instanceof Number || arg instanceof Date || arg instanceof Calendar ) {
+ return arg; // no escaping required
+ }
+ final String text = arg.toString();
+ StringBuilder buf = null; // create on-demand
+ for (int i = 0, len = text.length(); i < len; i++) {
+ final char c = text.charAt(i);
+ String replacement = null;
+ if (c == '&') {
+ replacement = "&amp;";
+ } else if (c == '<') {
+ replacement = "&lt;";
+ } else if (buf != null) {
+ buf.append(c); // maintain buffer
+ }
+ if (replacement != null) {
+ if (buf == null) {
+ // only create translation buffer when we actually need it
+ buf = new StringBuilder(len+8).append(text.substring(0, i));
+ }
+ buf.append(replacement);
+ }
+ }
+ return buf != null ? buf : text;
+ }
+
private static final Expression[] EMPTY_ARGUMENTS = new Expression[0];
private static final String LISTENER_NAME = InternationalizedStringExpressionListener.class.getName();
}