aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/vaadin
diff options
context:
space:
mode:
authorHenri Sara <henri.sara@itmill.com>2011-09-27 10:08:40 +0000
committerHenri Sara <henri.sara@itmill.com>2011-09-27 10:08:40 +0000
commit1a6f3607f0c59bdeaca02f45a41ecbf236f6a981 (patch)
tree8fff521b674870db34cef98a205850df8742dc26 /src/com/vaadin
parenta4990f201ec1da3b24414da4f7d24b44bed5f92a (diff)
downloadvaadin-framework-1a6f3607f0c59bdeaca02f45a41ecbf236f6a981.tar.gz
vaadin-framework-1a6f3607f0c59bdeaca02f45a41ecbf236f6a981.zip
Merged changes from 6.6
svn changeset:21335/svn branch:6.7
Diffstat (limited to 'src/com/vaadin')
-rw-r--r--src/com/vaadin/data/Validator.java19
-rw-r--r--src/com/vaadin/data/validator/AbstractValidator.java6
-rw-r--r--src/com/vaadin/terminal/SystemError.java33
-rw-r--r--src/com/vaadin/terminal/UserError.java29
-rwxr-xr-xsrc/com/vaadin/terminal/gwt/client/ApplicationConnection.java54
-rw-r--r--src/com/vaadin/terminal/gwt/client/Util.java22
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/Action.java4
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java78
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java7
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java14
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VNotification.java2
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java3
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VWindow.java3
-rw-r--r--src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java60
-rw-r--r--src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java98
-rw-r--r--src/com/vaadin/ui/AbstractComponent.java11
-rw-r--r--src/com/vaadin/ui/Panel.java18
17 files changed, 347 insertions, 114 deletions
diff --git a/src/com/vaadin/data/Validator.java b/src/com/vaadin/data/Validator.java
index 21a9da9f97..b1dc5ffc14 100644
--- a/src/com/vaadin/data/Validator.java
+++ b/src/com/vaadin/data/Validator.java
@@ -9,6 +9,7 @@ import java.io.Serializable;
import com.vaadin.terminal.ErrorMessage;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
/**
* Interface that implements a method for validating if an {@link Object} is
@@ -63,6 +64,12 @@ public interface Validator extends Serializable {
/**
* Exception that is thrown by a {@link Validator} when a value is invalid.
*
+ * <p>
+ * The default implementation of InvalidValueException does not support HTML
+ * in error messages. To enable HTML support, override
+ * {@link #getHtmlMessage()} and use the subclass in validators.
+ * </p>
+ *
* @author IT Mill Ltd.
* @version
* @VERSION@
@@ -154,7 +161,7 @@ public interface Validator extends Serializable {
target.addAttribute("level", "error");
// Error message
- final String message = getLocalizedMessage();
+ final String message = getHtmlMessage();
if (message != null) {
target.addText(message);
}
@@ -167,6 +174,16 @@ public interface Validator extends Serializable {
target.endTag("error");
}
+ /**
+ * Returns the message of the error in HTML.
+ *
+ * Note that this API may change in future versions.
+ */
+ protected String getHtmlMessage() {
+ return AbstractApplicationServlet
+ .safeEscapeForHtml(getLocalizedMessage());
+ }
+
/*
* (non-Javadoc)
*
diff --git a/src/com/vaadin/data/validator/AbstractValidator.java b/src/com/vaadin/data/validator/AbstractValidator.java
index 7d4f1c3a0d..ee2fee893c 100644
--- a/src/com/vaadin/data/validator/AbstractValidator.java
+++ b/src/com/vaadin/data/validator/AbstractValidator.java
@@ -15,6 +15,12 @@ import com.vaadin.data.Validator;
* (converted to string using {@link #toString()}) or "null" if the value is
* null.
* </p>
+ * <p>
+ * The default implementation of AbstractValidator does not support HTML in
+ * error messages. To enable HTML support, override
+ * {@link InvalidValueException#getHtmlMessage()} and throw such exceptions from
+ * {@link #validate(Object)}.
+ * </p>
*
* @author IT Mill Ltd.
* @version
diff --git a/src/com/vaadin/terminal/SystemError.java b/src/com/vaadin/terminal/SystemError.java
index 8b721c07f5..fb1de0c494 100644
--- a/src/com/vaadin/terminal/SystemError.java
+++ b/src/com/vaadin/terminal/SystemError.java
@@ -7,12 +7,18 @@ package com.vaadin.terminal;
import java.io.PrintWriter;
import java.io.StringWriter;
+import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
+
/**
* <code>SystemError</code> is a runtime exception caused by error in system.
* The system error can be shown to the user as it implements
* <code>ErrorMessage</code> interface, but contains technical information such
* as stack trace and exception.
*
+ * SystemError does not support HTML in error messages or stack traces. If HTML
+ * messages are required, use {@link UserError} or a custom implementation of
+ * {@link ErrorMessage}.
+ *
* @author IT Mill Ltd.
* @version
* @VERSION@
@@ -75,11 +81,26 @@ public class SystemError extends RuntimeException implements ErrorMessage {
target.startTag("error");
target.addAttribute("level", "system");
+ String message = getHtmlMessage();
+
+ target.addXMLSection("div", message,
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
+
+ target.endTag("error");
+
+ }
+
+ /**
+ * Returns the message of the error in HTML.
+ *
+ * Note that this API may change in future versions.
+ */
+ protected String getHtmlMessage() {
StringBuilder sb = new StringBuilder();
final String message = getLocalizedMessage();
if (message != null) {
sb.append("<h2>");
- sb.append(message);
+ sb.append(AbstractApplicationServlet.safeEscapeForHtml(message));
sb.append("</h2>");
}
@@ -89,15 +110,11 @@ public class SystemError extends RuntimeException implements ErrorMessage {
final StringWriter buffer = new StringWriter();
cause.printStackTrace(new PrintWriter(buffer));
sb.append("<pre>");
- sb.append(buffer.toString());
+ sb.append(AbstractApplicationServlet.safeEscapeForHtml(buffer
+ .toString()));
sb.append("</pre>");
}
-
- target.addXMLSection("div", sb.toString(),
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
-
- target.endTag("error");
-
+ return sb.toString();
}
/**
diff --git a/src/com/vaadin/terminal/UserError.java b/src/com/vaadin/terminal/UserError.java
index bcce0ed283..d33c02d144 100644
--- a/src/com/vaadin/terminal/UserError.java
+++ b/src/com/vaadin/terminal/UserError.java
@@ -4,6 +4,8 @@
package com.vaadin.terminal;
+import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
+
/**
* <code>UserError</code> is a controlled error occurred in application. User
* errors are occur in normal usage of the application and guide the user.
@@ -33,6 +35,11 @@ public class UserError implements ErrorMessage {
public static final int CONTENT_UIDL = 2;
/**
+ * Content mode, where the error contains XHTML.
+ */
+ public static final int CONTENT_XHTML = 3;
+
+ /**
* Content mode.
*/
private int mode = CONTENT_TEXT;
@@ -80,24 +87,24 @@ public class UserError implements ErrorMessage {
level = errorLevel;
}
- /* Documenten in interface */
+ /* Documented in interface */
public int getErrorLevel() {
return level;
}
- /* Documenten in interface */
+ /* Documented in interface */
public void addListener(RepaintRequestListener listener) {
}
- /* Documenten in interface */
+ /* Documented in interface */
public void removeListener(RepaintRequestListener listener) {
}
- /* Documenten in interface */
+ /* Documented in interface */
public void requestRepaint() {
}
- /* Documenten in interface */
+ /* Documented in interface */
public void paint(PaintTarget target) throws PaintException {
target.startTag("error");
@@ -118,21 +125,25 @@ public class UserError implements ErrorMessage {
// Paint the message
switch (mode) {
case CONTENT_TEXT:
- target.addText(msg);
+ target.addText(AbstractApplicationServlet.safeEscapeForHtml(msg));
break;
case CONTENT_UIDL:
target.addUIDL(msg);
break;
case CONTENT_PREFORMATTED:
- target.startTag("pre");
+ target.addText("<pre>"
+ + AbstractApplicationServlet.safeEscapeForHtml(msg)
+ + "</pre>");
+ break;
+ case CONTENT_XHTML:
target.addText(msg);
- target.endTag("pre");
+ break;
}
target.endTag("error");
}
- /* Documenten in interface */
+ /* Documented in interface */
public void requestRepaintRequests() {
}
diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
index e1ffca55e3..18ccd363a8 100755
--- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
+++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
@@ -71,13 +71,15 @@ public class ApplicationConnection {
private static final String ERROR_CLASSNAME_EXT = "-error";
- public static final String VAR_RECORD_SEPARATOR = "\u001e";
+ public static final char VAR_RECORD_SEPARATOR = '\u001e';
- public static final String VAR_FIELD_SEPARATOR = "\u001f";
+ public static final char VAR_FIELD_SEPARATOR = '\u001f';
- public static final String VAR_BURST_SEPARATOR = "\u001d";
+ public static final char VAR_BURST_SEPARATOR = '\u001d';
- public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c";
+ public static final char VAR_ARRAYITEM_SEPARATOR = '\u001c';
+
+ public static final char VAR_ESCAPE_CHARACTER = '\u001b';
public static final String UIDL_SECURITY_TOKEN_ID = "Vaadin-Security-Key";
@@ -1387,7 +1389,8 @@ public class ApplicationConnection {
public void updateVariable(String paintableId, String variableName,
String newValue, boolean immediate) {
- addVariableToQueue(paintableId, variableName, newValue, immediate, 's');
+ addVariableToQueue(paintableId, variableName,
+ escapeVariableValue(newValue), immediate, 's');
}
/**
@@ -1536,12 +1539,12 @@ public class ApplicationConnection {
Object value = map.get(key);
char transportType = getTransportType(value);
buf.append(transportType);
- buf.append(key);
+ buf.append(escapeVariableValue(key));
buf.append(VAR_ARRAYITEM_SEPARATOR);
if (transportType == 'p') {
buf.append(getPid((Paintable) value));
} else {
- buf.append(value);
+ buf.append(escapeVariableValue(String.valueOf(value)));
}
if (iterator.hasNext()) {
@@ -1597,7 +1600,7 @@ public class ApplicationConnection {
final StringBuffer buf = new StringBuffer();
if (values != null) {
for (int i = 0; i < values.length; i++) {
- buf.append(values[i]);
+ buf.append(escapeVariableValue(values[i]));
// there will be an extra separator at the end to differentiate
// between an empty array and one containing an empty string
// only
@@ -1642,7 +1645,7 @@ public class ApplicationConnection {
if (transportType == 'p') {
buf.append(getPid((Paintable) value));
} else {
- buf.append(value);
+ buf.append(escapeVariableValue(String.valueOf(value)));
}
}
}
@@ -1651,6 +1654,39 @@ public class ApplicationConnection {
}
/**
+ * Encode burst, record, field and array item separator characters in a
+ * String for transport over the network. This protects from separator
+ * injection attacks.
+ *
+ * @param value
+ * to encode
+ * @return encoded value
+ */
+ protected String escapeVariableValue(String value) {
+ final StringBuilder result = new StringBuilder();
+ for (int i = 0; i < value.length(); ++i) {
+ char character = value.charAt(i);
+ switch (character) {
+ case VAR_ESCAPE_CHARACTER:
+ // fall-through - escape character is duplicated
+ case VAR_BURST_SEPARATOR:
+ case VAR_RECORD_SEPARATOR:
+ case VAR_FIELD_SEPARATOR:
+ case VAR_ARRAYITEM_SEPARATOR:
+ result.append(VAR_ESCAPE_CHARACTER);
+ // encode as letters for easier reading
+ result.append(((char) (character + 0x30)));
+ break;
+ default:
+ // the char is not a special one - add it to the result as is
+ result.append(character);
+ break;
+ }
+ }
+ return result.toString();
+ }
+
+ /**
* Update generic component features.
*
* <h2>Selecting correct implementation</h2>
diff --git a/src/com/vaadin/terminal/gwt/client/Util.java b/src/com/vaadin/terminal/gwt/client/Util.java
index c446d90fca..34c52357cd 100644
--- a/src/com/vaadin/terminal/gwt/client/Util.java
+++ b/src/com/vaadin/terminal/gwt/client/Util.java
@@ -259,6 +259,22 @@ public class Util {
}
/**
+ * Escapes the string so it is safe to write inside an HTML attribute.
+ *
+ * @param attribute
+ * The string to escape
+ * @return An escaped version of <literal>attribute</literal>.
+ */
+ public static String escapeAttribute(String attribute) {
+ attribute = attribute.replace("\"", "&quot;");
+ attribute = attribute.replace("'", "&#39;");
+ attribute = attribute.replace(">", "&gt;");
+ attribute = attribute.replace("<", "&lt;");
+ attribute = attribute.replace("&", "&amp;");
+ return attribute;
+ }
+
+ /**
* Adds transparent PNG fix to image element; only use for IE6.
*
* @param el
@@ -1075,8 +1091,10 @@ public class Util {
ArrayList<String[]> vars = new ArrayList<String[]>();
for (int i = 0; i < loggedBurst.size(); i++) {
String value = loggedBurst.get(i++);
- String[] split = loggedBurst.get(i).split(
- ApplicationConnection.VAR_FIELD_SEPARATOR);
+ String[] split = loggedBurst
+ .get(i)
+ .split(String
+ .valueOf(ApplicationConnection.VAR_FIELD_SEPARATOR));
String id = split[0];
if (curId == null) {
diff --git a/src/com/vaadin/terminal/gwt/client/ui/Action.java b/src/com/vaadin/terminal/gwt/client/ui/Action.java
index 4d02f0a259..20d58c69f8 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/Action.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/Action.java
@@ -5,6 +5,7 @@
package com.vaadin.terminal.gwt.client.ui;
import com.google.gwt.user.client.Command;
+import com.vaadin.terminal.gwt.client.Util;
/**
*
@@ -30,7 +31,8 @@ public abstract class Action implements Command {
final StringBuffer sb = new StringBuffer();
sb.append("<div>");
if (getIconUrl() != null) {
- sb.append("<img src=\"" + getIconUrl() + "\" alt=\"icon\" />");
+ sb.append("<img src=\"" + Util.escapeAttribute(getIconUrl())
+ + "\" alt=\"icon\" />");
}
sb.append(getCaption());
sb.append("</div>");
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java b/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java
index a0f7cc649c..59f0afdd3c 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java
@@ -121,15 +121,13 @@ public class VEmbedded extends HTML implements Paintable {
} else if (type.equals("browser")) {
addStyleName(CLASSNAME + "-browser");
if (browserElement == null) {
- setHTML("<iframe width=\"100%\" height=\"100%\" frameborder=\"0\" allowTransparency=\"true\" src=\""
- + getSrc(uidl, client)
- + "\" name=\""
- + uidl.getId() + "\"></iframe>");
+ setHTML("<iframe width=\"100%\" height=\"100%\" frameborder=\"0\""
+ + " allowTransparency=\"true\" src=\"\""
+ + " name=\"" + uidl.getId() + "\"></iframe>");
browserElement = DOM.getFirstChild(getElement());
- } else {
- DOM.setElementAttribute(browserElement, "src",
- getSrc(uidl, client));
}
+ DOM.setElementAttribute(browserElement, "src",
+ getSrc(uidl, client));
clearBrowserElement = false;
} else {
VConsole.log("Unknown Embedded type '" + type + "'");
@@ -138,6 +136,7 @@ public class VEmbedded extends HTML implements Paintable {
final String mime = uidl.getStringAttribute("mimetype");
if (mime.equals("application/x-shockwave-flash")) {
// Handle embedding of Flash
+ addStyleName(CLASSNAME + "-flash");
setHTML(createFlashEmbed(uidl));
} else if (mime.equals("image/svg+xml")) {
@@ -161,23 +160,23 @@ public class VEmbedded extends HTML implements Paintable {
}
if (uidl.hasAttribute("classid")) {
obj.setAttribute("classid",
- uidl.getStringAttribute(escapeAttribute("classid")));
+ uidl.getStringAttribute("classid"));
}
if (uidl.hasAttribute("codebase")) {
- obj.setAttribute("codebase", uidl
- .getStringAttribute(escapeAttribute("codebase")));
+ obj.setAttribute("codebase",
+ uidl.getStringAttribute("codebase"));
}
if (uidl.hasAttribute("codetype")) {
- obj.setAttribute("codetype", uidl
- .getStringAttribute(escapeAttribute("codetype")));
+ obj.setAttribute("codetype",
+ uidl.getStringAttribute("codetype"));
}
if (uidl.hasAttribute("archive")) {
obj.setAttribute("archive",
- uidl.getStringAttribute(escapeAttribute("archive")));
+ uidl.getStringAttribute("archive"));
}
if (uidl.hasAttribute("standby")) {
obj.setAttribute("standby",
- uidl.getStringAttribute(escapeAttribute("standby")));
+ uidl.getStringAttribute("standby"));
}
getElement().appendChild(obj);
@@ -202,8 +201,6 @@ public class VEmbedded extends HTML implements Paintable {
* @return Tags concatenated into a string
*/
private String createFlashEmbed(UIDL uidl) {
- addStyleName(CLASSNAME + "-flash");
-
/*
* To ensure cross-browser compatibility we are using the twice-cooked
* method to embed flash i.e. we add a OBJECT tag for IE ActiveX and
@@ -224,7 +221,7 @@ public class VEmbedded extends HTML implements Paintable {
*/
if (uidl.hasAttribute("classid")) {
html.append("classid=\""
- + escapeAttribute(uidl.getStringAttribute("classid"))
+ + Util.escapeAttribute(uidl.getStringAttribute("classid"))
+ "\" ");
} else {
html.append("classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" ");
@@ -240,32 +237,35 @@ public class VEmbedded extends HTML implements Paintable {
*/
if (uidl.hasAttribute("codebase")) {
html.append("codebase=\""
- + escapeAttribute(uidl.getStringAttribute("codebase"))
+ + Util.escapeAttribute(uidl.getStringAttribute("codebase"))
+ "\" ");
} else {
html.append("codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0\" ");
}
// Add width and height
- html.append("width=\"" + width + "\" ");
- html.append("height=\"" + height + "\" ");
+ html.append("width=\"" + Util.escapeAttribute(width) + "\" ");
+ html.append("height=\"" + Util.escapeAttribute(height) + "\" ");
html.append("type=\"application/x-shockwave-flash\" ");
// Codetype
if (uidl.hasAttribute("codetype")) {
- html.append("codetype=\"" + uidl.getStringAttribute("codetype")
+ html.append("codetype=\""
+ + Util.escapeAttribute(uidl.getStringAttribute("codetype"))
+ "\" ");
}
// Standby
if (uidl.hasAttribute("standby")) {
- html.append("standby=\"" + uidl.getStringAttribute("standby")
+ html.append("standby=\""
+ + Util.escapeAttribute(uidl.getStringAttribute("standby"))
+ "\" ");
}
// Archive
if (uidl.hasAttribute("archive")) {
- html.append("archive=\"" + uidl.getStringAttribute("archive")
+ html.append("archive=\""
+ + Util.escapeAttribute(uidl.getStringAttribute("archive"))
+ "\" ");
}
@@ -281,24 +281,26 @@ public class VEmbedded extends HTML implements Paintable {
// Add parameters to OBJECT
for (String name : parameters.keySet()) {
html.append("<param ");
- html.append("name=\"" + escapeAttribute(name) + "\" ");
- html.append("value=\"" + escapeAttribute(parameters.get(name))
+ html.append("name=\"" + Util.escapeAttribute(name) + "\" ");
+ html.append("value=\"" + Util.escapeAttribute(parameters.get(name))
+ "\" ");
html.append("/>");
}
// Build inner EMBED tag
html.append("<embed ");
- html.append("src=\"" + getSrc(uidl, client) + "\" ");
- html.append("width=\"" + width + "\" ");
- html.append("height=\"" + height + "\" ");
+ html.append("src=\"" + Util.escapeAttribute(getSrc(uidl, client))
+ + "\" ");
+ html.append("width=\"" + Util.escapeAttribute(width) + "\" ");
+ html.append("height=\"" + Util.escapeAttribute(height) + "\" ");
html.append("type=\"application/x-shockwave-flash\" ");
// Add the parameters to the Embed
for (String name : parameters.keySet()) {
- html.append(escapeAttribute(name));
+ html.append(Util.escapeAttribute(name));
html.append("=");
- html.append("\"" + escapeAttribute(parameters.get(name)) + "\"");
+ html.append("\"" + Util.escapeAttribute(parameters.get(name))
+ + "\"");
}
// End embed tag
@@ -311,22 +313,6 @@ public class VEmbedded extends HTML implements Paintable {
}
/**
- * Escapes the string so it is safe to write inside an HTML attribute.
- *
- * @param attribute
- * The string to escape
- * @return An escaped version of <literal>attribute</literal>.
- */
- private String escapeAttribute(String attribute) {
- attribute = attribute.replace("\"", "&quot;");
- attribute = attribute.replace("'", "&#39;");
- attribute = attribute.replace(">", "&gt;");
- attribute = attribute.replace("<", "&lt;");
- attribute = attribute.replace("&", "&amp;");
- return attribute;
- }
-
- /**
* Returns a map (name -> value) of all parameters in the UIDL.
*
* @param uidl
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java b/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java
index 0b052ee091..7c3d3066ce 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java
@@ -93,7 +93,7 @@ public class VFilterSelect extends Composite implements Paintable, Field,
final StringBuffer sb = new StringBuffer();
if (iconUri != null) {
sb.append("<img src=\"");
- sb.append(iconUri);
+ sb.append(Util.escapeAttribute(iconUri));
sb.append("\" alt=\"\" class=\"v-icon\" />");
}
sb.append("<span>" + Util.escapeHTML(caption) + "</span>");
@@ -1061,7 +1061,8 @@ public class VFilterSelect extends Composite implements Paintable, Field,
totalMatches = uidl.getIntAttribute("totalMatches");
}
- String captions = inputPrompt;
+ // used only to calculate minimum popup width
+ String captions = Util.escapeHTML(inputPrompt);
for (final Iterator<?> i = options.getChildIterator(); i.hasNext();) {
final UIDL optionUidl = (UIDL) i.next();
@@ -1090,7 +1091,7 @@ public class VFilterSelect extends Composite implements Paintable, Field,
if (captions.length() > 0) {
captions += "|";
}
- captions += suggestion.getReplacementString();
+ captions += Util.escapeHTML(suggestion.getReplacementString());
}
if ((!filtering || popupOpenerClicked) && uidl.hasVariable("selected")
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java b/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java
index 7f9707181d..a2b49bae05 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java
@@ -228,9 +228,10 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable,
if (moreItemUIDL.hasAttribute("icon")) {
itemHTML.append("<img src=\""
- + client.translateVaadinUri(moreItemUIDL
- .getStringAttribute("icon")) + "\" class=\""
- + Icon.CLASSNAME + "\" alt=\"\" />");
+ + Util.escapeAttribute(client
+ .translateVaadinUri(moreItemUIDL
+ .getStringAttribute("icon")))
+ + "\" class=\"" + Icon.CLASSNAME + "\" alt=\"\" />");
}
String moreItemText = moreItemUIDL.getStringAttribute("text");
@@ -334,7 +335,8 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable,
// FIXME For compatibility reasons: remove in version 7
String bgStyle = "";
if (submenuIcon != null) {
- bgStyle = " style=\"background-image: url(" + submenuIcon
+ bgStyle = " style=\"background-image: url("
+ + Util.escapeAttribute(submenuIcon)
+ "); text-indent: -999px; width: 1em;\"";
}
itemHTML.append("<span class=\"" + CLASSNAME
@@ -345,8 +347,8 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable,
+ "-menuitem-caption\">");
if (item.hasAttribute("icon")) {
itemHTML.append("<img src=\""
- + client.translateVaadinUri(item
- .getStringAttribute("icon")) + "\" class=\""
+ + Util.escapeAttribute(client.translateVaadinUri(item
+ .getStringAttribute("icon"))) + "\" class=\""
+ Icon.CLASSNAME + "\" alt=\"\" />");
}
String itemText = item.getStringAttribute("text");
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNotification.java b/src/com/vaadin/terminal/gwt/client/ui/VNotification.java
index 227bf41452..34a8229d8e 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VNotification.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VNotification.java
@@ -362,7 +362,7 @@ public class VNotification extends VOverlay {
if (notification.hasAttribute("icon")) {
final String parsedUri = client.translateVaadinUri(notification
.getStringAttribute("icon"));
- html += "<img src=\"" + parsedUri + "\" />";
+ html += "<img src=\"" + Util.escapeAttribute(parsedUri) + "\" />";
}
if (notification.hasAttribute("caption")) {
String caption = notification.getStringAttribute("caption");
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
index 1d6926c501..5596a1fdfb 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
@@ -5904,7 +5904,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
.getStringAttribute("caption") : "";
if (uidl.hasAttribute("icon")) {
s = "<img src=\""
- + client.translateVaadinUri(uidl.getStringAttribute("icon"))
+ + Util.escapeAttribute(client.translateVaadinUri(uidl
+ .getStringAttribute("icon")))
+ "\" alt=\"icon\" class=\"v-icon\">" + s;
}
return s;
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VWindow.java b/src/com/vaadin/terminal/gwt/client/ui/VWindow.java
index 8d5215f528..8ffb0246a3 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VWindow.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VWindow.java
@@ -839,7 +839,8 @@ public class VWindow extends VOverlay implements Container,
String html = Util.escapeHTML(c);
if (icon != null) {
icon = client.translateVaadinUri(icon);
- html = "<img src=\"" + icon + "\" class=\"v-icon\" />" + html;
+ html = "<img src=\"" + Util.escapeAttribute(icon)
+ + "\" class=\"v-icon\" />" + html;
}
DOM.setInnerHTML(headerText, html);
}
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
index 5582c749aa..08614ce0c8 100644
--- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
+++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
@@ -1272,6 +1272,16 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
+
+ // security check: do not permit navigation out of the VAADIN
+ // directory
+ if (!isAllowedVAADINResourceUrl(request, resourceUrl)) {
+ logger.info("Requested resource ["
+ + filename
+ + "] not accessible in the VAADIN directory or access to it is forbidden.");
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
}
// Find the modification timestamp
@@ -1329,6 +1339,47 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
}
/**
+ * Check whether a URL obtained from a classloader refers to a valid static
+ * resource in the directory VAADIN.
+ *
+ * Warning: Overriding of this method is not recommended, but is possible to
+ * support non-default classloaders or servers that may produce URLs
+ * different from the normal ones. The method prototype may change in the
+ * future. Care should be taken not to expose class files or other resources
+ * outside the VAADIN directory if the method is overridden.
+ *
+ * @param request
+ * @param resourceUrl
+ * @return
+ *
+ * @since 6.6.7
+ */
+ protected boolean isAllowedVAADINResourceUrl(HttpServletRequest request,
+ URL resourceUrl) {
+ if ("jar".equals(resourceUrl.getProtocol())) {
+ // This branch is used for accessing resources directly from the
+ // Vaadin JAR in development environments and in similar cases.
+
+ // Inside a JAR, a ".." would mean a real directory named ".." so
+ // using it in paths should just result in the file not being found.
+ // However, performing a check in case some servers or class loaders
+ // try to normalize the path by collapsing ".." before the class
+ // loader sees it.
+
+ if (!resourceUrl.getPath().contains("!/VAADIN/")) {
+ logger.warning("Attempted access to a JAR entry not starting with /VAADIN/: "
+ + resourceUrl);
+ return false;
+ }
+ return true;
+ }
+
+ // when using the class loader fall-back, other protocols than jar: are
+ // not supported
+ return false;
+ }
+
+ /**
* Checks if the browser has an up to date cached version of requested
* resource. Currently the check is performed using the "If-Modified-Since"
* header. Could be expanded if needed.
@@ -1511,8 +1562,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
}
/**
- * The default method to fetch static files location. This method does not
- * check for request attribute {@value #REQUEST_VAADIN_STATIC_FILE_PATH}.
+ * The default method to fetch static files location (URL). This method does
+ * not check for request attribute {@value #REQUEST_VAADIN_STATIC_FILE_PATH}
*
* @param request
* @return
@@ -2409,7 +2460,10 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
* @param unsafe
* @return a safe string to be added inside an html tag
*/
- protected static final String safeEscapeForHtml(String unsafe) {
+ public static final String safeEscapeForHtml(String unsafe) {
+ if (null == unsafe) {
+ return null;
+ }
StringBuilder safe = new StringBuilder();
char[] charArray = unsafe.toCharArray();
for (int i = 0; i < charArray.length; i++) {
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
index b47c3ca31f..f5e5bbc428 100644
--- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
+++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
@@ -18,9 +18,11 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.GeneralSecurityException;
+import java.text.CharacterIterator;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
+import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
@@ -294,13 +296,15 @@ public abstract class AbstractCommunicationManager implements
private static final char VTYPE_STRINGARRAY = 'c';
private static final char VTYPE_MAP = 'm';
- private static final String VAR_RECORD_SEPARATOR = "\u001e";
+ private static final char VAR_RECORD_SEPARATOR = '\u001e';
- private static final String VAR_FIELD_SEPARATOR = "\u001f";
+ private static final char VAR_FIELD_SEPARATOR = '\u001f';
- public static final String VAR_BURST_SEPARATOR = "\u001d";
+ public static final char VAR_BURST_SEPARATOR = '\u001d';
- public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c";
+ public static final char VAR_ARRAYITEM_SEPARATOR = '\u001c';
+
+ public static final char VAR_ESCAPE_CHARACTER = '\u001b';
private final HashMap<String, OpenWindowCache> currentlyOpenWindowsInClient = new HashMap<String, OpenWindowCache>();
@@ -1264,7 +1268,8 @@ public abstract class AbstractCommunicationManager implements
if (changes != null) {
// Manage bursts one by one
- final String[] bursts = changes.split(VAR_BURST_SEPARATOR);
+ final String[] bursts = changes.split(String
+ .valueOf(VAR_BURST_SEPARATOR));
// Security: double cookie submission pattern unless disabled by
// property
@@ -1326,10 +1331,11 @@ public abstract class AbstractCommunicationManager implements
public boolean handleVariableBurst(Object source, Application app,
boolean success, final String burst) {
// extract variables to two dim string array
- final String[] tmp = burst.split(VAR_RECORD_SEPARATOR);
+ final String[] tmp = burst.split(String.valueOf(VAR_RECORD_SEPARATOR));
final String[][] variableRecords = new String[tmp.length][4];
for (int i = 0; i < tmp.length; i++) {
- variableRecords[i] = tmp[i].split(VAR_FIELD_SEPARATOR);
+ variableRecords[i] = tmp[i].split(String
+ .valueOf(VAR_FIELD_SEPARATOR));
}
for (int i = 0; i < variableRecords.length; i++) {
@@ -1344,7 +1350,7 @@ public abstract class AbstractCommunicationManager implements
if (nextVariable != null
&& variable[VAR_PID].equals(nextVariable[VAR_PID])) {
// we have more than one value changes in row for
- // one variable owner, collect em in HashMap
+ // one variable owner, collect them in HashMap
m = new HashMap<String, Object>();
m.put(variable[VAR_NAME],
convertVariableValue(variable[VAR_TYPE].charAt(0),
@@ -1546,7 +1552,8 @@ public abstract class AbstractCommunicationManager implements
val = convertStringArray(strValue);
break;
case VTYPE_STRING:
- val = strValue;
+ // decode encoded separators
+ val = decodeVariableValue(strValue);
break;
case VTYPE_INTEGER:
val = Integer.valueOf(strValue);
@@ -1572,14 +1579,18 @@ public abstract class AbstractCommunicationManager implements
}
private Object convertMap(String strValue) {
- String[] parts = strValue.split(VAR_ARRAYITEM_SEPARATOR);
+ String[] parts = strValue
+ .split(String.valueOf(VAR_ARRAYITEM_SEPARATOR));
HashMap<String, Object> map = new HashMap<String, Object>();
for (int i = 0; i < parts.length; i += 2) {
String key = parts[i];
if (key.length() > 0) {
char variabletype = key.charAt(0);
- Object value = convertVariableValue(variabletype, parts[i + 1]);
- map.put(key.substring(1), value);
+ // decode encoded separators
+ String decodedValue = decodeVariableValue(parts[i + 1]);
+ String decodedKey = decodeVariableValue(key.substring(1));
+ Object value = convertVariableValue(variabletype, decodedValue);
+ map.put(decodedKey, value);
}
}
return map;
@@ -1589,15 +1600,18 @@ public abstract class AbstractCommunicationManager implements
// need to return delimiters and filter them out; otherwise empty
// strings are lost
// an extra empty delimiter at the end is automatically eliminated
+ final String arrayItemSeparator = String
+ .valueOf(VAR_ARRAYITEM_SEPARATOR);
StringTokenizer tokenizer = new StringTokenizer(strValue,
- VAR_ARRAYITEM_SEPARATOR, true);
+ arrayItemSeparator, true);
List<String> tokens = new ArrayList<String>();
- String prevToken = VAR_ARRAYITEM_SEPARATOR;
+ String prevToken = arrayItemSeparator;
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
- if (!VAR_ARRAYITEM_SEPARATOR.equals(token)) {
- tokens.add(token);
- } else if (VAR_ARRAYITEM_SEPARATOR.equals(prevToken)) {
+ if (!arrayItemSeparator.equals(token)) {
+ // decode encoded separators
+ tokens.add(decodeVariableValue(token));
+ } else if (arrayItemSeparator.equals(prevToken)) {
tokens.add("");
}
prevToken = token;
@@ -1606,7 +1620,7 @@ public abstract class AbstractCommunicationManager implements
}
private Object convertArray(String strValue) {
- String[] val = strValue.split(VAR_ARRAYITEM_SEPARATOR);
+ String[] val = strValue.split(String.valueOf(VAR_ARRAYITEM_SEPARATOR));
if (val.length == 0 || (val.length == 1 && val[0].length() == 0)) {
return new Object[0];
}
@@ -1621,6 +1635,54 @@ public abstract class AbstractCommunicationManager implements
}
/**
+ * Decode encoded burst, record, field and array item separator characters
+ * in a variable value String received from the client. This protects from
+ * separator injection attacks.
+ *
+ * @param encodedValue
+ * to decode
+ * @return decoded value
+ */
+ protected String decodeVariableValue(String encodedValue) {
+ final StringBuilder result = new StringBuilder();
+ final StringCharacterIterator iterator = new StringCharacterIterator(
+ encodedValue);
+ char character = iterator.current();
+ while (character != CharacterIterator.DONE) {
+ if (VAR_ESCAPE_CHARACTER == character) {
+ character = iterator.next();
+ switch (character) {
+ case VAR_ESCAPE_CHARACTER + 0x30:
+ // escaped escape character
+ result.append(VAR_ESCAPE_CHARACTER);
+ break;
+ case VAR_BURST_SEPARATOR + 0x30:
+ case VAR_RECORD_SEPARATOR + 0x30:
+ case VAR_FIELD_SEPARATOR + 0x30:
+ case VAR_ARRAYITEM_SEPARATOR + 0x30:
+ // +0x30 makes these letters for easier reading
+ result.append((character - 0x30));
+ break;
+ case CharacterIterator.DONE:
+ // error
+ throw new RuntimeException(
+ "Communication error: Unexpected end of message");
+ default:
+ // other escaped character - probably a client-server
+ // version mismatch
+ throw new RuntimeException(
+ "Invalid escaped character from the client - check that the widgetset and server versions match");
+ }
+ } else {
+ // not a special character - add it to the result as is
+ result.append(character);
+ }
+ character = iterator.next();
+ }
+ return result.toString();
+ }
+
+ /**
* Prints the queued (pending) locale definitions to a {@link PrintWriter}
* in a (UIDL) format that can be sent to the client and used there in
* formatting dates, times etc.
diff --git a/src/com/vaadin/ui/AbstractComponent.java b/src/com/vaadin/ui/AbstractComponent.java
index eaad6f6126..eda2fd64ad 100644
--- a/src/com/vaadin/ui/AbstractComponent.java
+++ b/src/com/vaadin/ui/AbstractComponent.java
@@ -434,9 +434,10 @@ public abstract class AbstractComponent implements Component, MethodEventSource
/**
* <p>
- * Gets the component's description. The description can be used to briefly
- * describe the state of the component to the user. The description string
- * may contain certain XML tags:
+ * Gets the component's description, used in tooltips and can be displayed
+ * directly in certain other components such as forms. The description can
+ * be used to briefly describe the state of the component to the user. The
+ * description string may contain certain XML tags:
* </p>
*
* <p>
@@ -498,6 +499,10 @@ public abstract class AbstractComponent implements Component, MethodEventSource
* {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
* RepaintRequestEvent}.
*
+ * The description is displayed as HTML/XHTML in tooltips or directly in
+ * certain components so care should be taken to avoid creating the
+ * possibility for HTML injection and possibly XSS vulnerabilities.
+ *
* @param description
* the new description string for the component.
*/
diff --git a/src/com/vaadin/ui/Panel.java b/src/com/vaadin/ui/Panel.java
index 73140fd79a..877171232e 100644
--- a/src/com/vaadin/ui/Panel.java
+++ b/src/com/vaadin/ui/Panel.java
@@ -105,7 +105,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
* Creates a new empty panel with caption. Default layout is used.
*
* @param caption
- * the caption used in the panel.
+ * the caption used in the panel (HTML/XHTML).
*/
public Panel(String caption) {
this(caption, null);
@@ -115,7 +115,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
* Creates a new empty panel with the given caption and content.
*
* @param caption
- * the caption of the panel.
+ * the caption of the panel (HTML/XHTML).
* @param content
* the content used in the panel.
*/
@@ -125,6 +125,20 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
}
/**
+ * Sets the caption of the panel.
+ *
+ * Note that the caption is interpreted as HTML/XHTML and therefore care
+ * should be taken not to enable HTML injection and XSS attacks using panel
+ * captions. This behavior may change in future versions.
+ *
+ * @see AbstractComponent#setCaption(String)
+ */
+ @Override
+ public void setCaption(String caption) {
+ super.setCaption(caption);
+ }
+
+ /**
* Gets the current layout of the panel.
*
* @return the Current layout of the panel.