From 1a6f3607f0c59bdeaca02f45a41ecbf236f6a981 Mon Sep 17 00:00:00 2001 From: Henri Sara Date: Tue, 27 Sep 2011 10:08:40 +0000 Subject: [PATCH] Merged changes from 6.6 svn changeset:21335/svn branch:6.7 --- src/com/vaadin/data/Validator.java | 19 +++- .../data/validator/AbstractValidator.java | 6 ++ src/com/vaadin/terminal/SystemError.java | 33 +++++-- src/com/vaadin/terminal/UserError.java | 29 ++++-- .../gwt/client/ApplicationConnection.java | 54 ++++++++-- src/com/vaadin/terminal/gwt/client/Util.java | 22 ++++- .../vaadin/terminal/gwt/client/ui/Action.java | 4 +- .../terminal/gwt/client/ui/VEmbedded.java | 78 ++++++--------- .../terminal/gwt/client/ui/VFilterSelect.java | 7 +- .../terminal/gwt/client/ui/VMenuBar.java | 14 +-- .../terminal/gwt/client/ui/VNotification.java | 2 +- .../terminal/gwt/client/ui/VScrollTable.java | 3 +- .../terminal/gwt/client/ui/VWindow.java | 3 +- .../server/AbstractApplicationServlet.java | 60 +++++++++++- .../server/AbstractCommunicationManager.java | 98 +++++++++++++++---- src/com/vaadin/ui/AbstractComponent.java | 11 ++- src/com/vaadin/ui/Panel.java | 18 +++- 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. * + *

+ * 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. + *

+ * * @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. *

+ *

+ * 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)}. + *

* * @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; + /** * SystemError is a runtime exception caused by error in system. * The system error can be shown to the user as it implements * ErrorMessage 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("

"); - sb.append(message); + sb.append(AbstractApplicationServlet.safeEscapeForHtml(message)); sb.append("

"); } @@ -89,15 +110,11 @@ public class SystemError extends RuntimeException implements ErrorMessage { final StringWriter buffer = new StringWriter(); cause.printStackTrace(new PrintWriter(buffer)); sb.append("
");
-            sb.append(buffer.toString());
+            sb.append(AbstractApplicationServlet.safeEscapeForHtml(buffer
+                    .toString()));
             sb.append("
"); } - - 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; + /** * UserError is a controlled error occurred in application. User * errors are occur in normal usage of the application and guide the user. @@ -32,6 +34,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. */ @@ -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("
"
+                    + AbstractApplicationServlet.safeEscapeForHtml(msg)
+                    + "
"); + 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))); } } } @@ -1650,6 +1653,39 @@ public class ApplicationConnection { immediate, 'a'); } + /** + * 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. * 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 @@ -258,6 +258,22 @@ public class Util { return escapedText; } + /** + * Escapes the string so it is safe to write inside an HTML attribute. + * + * @param attribute + * The string to escape + * @return An escaped version of attribute. + */ + public static String escapeAttribute(String attribute) { + attribute = attribute.replace("\"", """); + attribute = attribute.replace("'", "'"); + attribute = attribute.replace(">", ">"); + attribute = attribute.replace("<", "<"); + attribute = attribute.replace("&", "&"); + return attribute; + } + /** * Adds transparent PNG fix to image element; only use for IE6. * @@ -1075,8 +1091,10 @@ public class Util { ArrayList vars = new ArrayList(); 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("
"); if (getIconUrl() != null) { - sb.append("\"icon\""); + sb.append("\"icon\""); } sb.append(getCaption()); sb.append("
"); 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(""); + setHTML(""); 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(""); } // Build inner EMBED tag html.append("attribute. - */ - private String escapeAttribute(String attribute) { - attribute = attribute.replace("\"", """); - attribute = attribute.replace("'", "'"); - attribute = attribute.replace(">", ">"); - attribute = attribute.replace("<", "<"); - attribute = attribute.replace("&", "&"); - return attribute; - } - /** * Returns a map (name -> value) of all parameters in the 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("\"\""); } sb.append("" + Util.escapeHTML(caption) + ""); @@ -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("\"\""); + + 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(""); if (item.hasAttribute("icon")) { itemHTML.append("\"\""); } 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 += ""; + html += ""; } 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 = "\"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 = "" + html; + html = "" + 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 @@ -1328,6 +1338,47 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements is.close(); } + /** + * 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" @@ -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 currentlyOpenWindowsInClient = new HashMap(); @@ -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(); 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 map = new HashMap(); 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 tokens = new ArrayList(); - 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]; } @@ -1620,6 +1634,54 @@ public abstract class AbstractCommunicationManager implements return values; } + /** + * 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 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 /** *

- * 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: *

* *

@@ -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. */ @@ -124,6 +124,20 @@ public class Panel extends AbstractComponentContainer implements Scrollable, setCaption(caption); } + /** + * 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. * -- 2.39.5