Revision: 8823
Author: [email protected]
Date: Mon Sep 20 07:10:58 2010
Log: Add streaming HTML parser library to tools/lib, as well as the guava
library it depends on.
Use this parser to verify (in non-prod mode) that arguments to
SafeHtmlBuilder#appendHtmlConstant satisfy the SafeHtml composability
constraint.
Clarify SafeHtml type contract.
Review at http://gwt-code-reviews.appspot.com/850801
http://code.google.com/p/google-web-toolkit/source/detail?r=8823
Added:
/trunk/user/src/com/google/gwt/safehtml/shared/SafeHtmlHostedModeUtils.java
/trunk/user/super/com/google/gwt/safehtml
/trunk/user/super/com/google/gwt/safehtml/super
/trunk/user/super/com/google/gwt/safehtml/super/com
/trunk/user/super/com/google/gwt/safehtml/super/com/google
/trunk/user/super/com/google/gwt/safehtml/super/com/google/gwt
/trunk/user/super/com/google/gwt/safehtml/super/com/google/gwt/safehtml
/trunk/user/super/com/google/gwt/safehtml/super/com/google/gwt/safehtml/shared
/trunk/user/super/com/google/gwt/safehtml/super/com/google/gwt/safehtml/shared/SafeHtmlHostedModeUtils.java
/trunk/user/test/com/google/gwt/safehtml/server/SafeHtmlHostedModeUtilsTest.java
/trunk/user/test/com/google/gwt/safehtml/shared/GwtSafeHtmlHostedModeUtilsTest.java
Modified:
/trunk/dev/build.xml
/trunk/eclipse/user/.classpath
/trunk/tools/api-checker/config/gwt20_21userApi.conf
/trunk/user/src/com/google/gwt/safehtml/SafeHtml.gwt.xml
/trunk/user/src/com/google/gwt/safehtml/shared/SafeHtml.java
/trunk/user/src/com/google/gwt/safehtml/shared/SafeHtmlBuilder.java
/trunk/user/src/com/google/gwt/safehtml/shared/SafeHtmlUtils.java
/trunk/user/test/com/google/gwt/safehtml/SafeHtmlGwtSuite.java
/trunk/user/test/com/google/gwt/safehtml/SafeHtmlJreSuite.java
/trunk/user/test/com/google/gwt/safehtml/shared/GwtSafeHtmlBuilderTest.java
/trunk/user/test/com/google/gwt/safehtml/shared/GwtSafeHtmlUtilsTest.java
/trunk/user/test/com/google/gwt/safehtml/shared/SafeHtmlBuilderTest.java
/trunk/user/test/com/google/gwt/safehtml/shared/SafeHtmlUtilsTest.java
=======================================
--- /dev/null
+++
/trunk/user/src/com/google/gwt/safehtml/shared/SafeHtmlHostedModeUtils.java
Mon Sep 20 07:10:58 2010
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.safehtml.shared;
+
+import com.google.gwt.core.client.GWT;
+import
com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.base.Preconditions;
+import com.google.gwt.thirdparty.streamhtmlparser.HtmlParser;
+import com.google.gwt.thirdparty.streamhtmlparser.HtmlParserFactory;
+import com.google.gwt.thirdparty.streamhtmlparser.ParseException;
+
+/**
+ * SafeHtml utilities whose implementation differs between hosted and web
mode.
+ *
+ * <p>
+ * This class has a super-source peer that provides the web-mode
implementation.
+ */
+public class SafeHtmlHostedModeUtils {
+
+ public static final String FORCE_CHECK_COMPLETE_HTML =
+ "com.google.gwt.safehtml.ForceCheckCompleteHtml";
+
+ private static boolean forceCheckCompleteHtml;
+
+ static {
+ setForceCheckCompleteHtmlFromProperty();
+ }
+
+ /**
+ * Checks if the provided HTML string is complete (ends in "inner HTML"
+ * context).
+ *
+ * <p>
+ * This method parses the provided string as HTML and determines the HTML
+ * context at the end of the string. If the context is not "inner HTML
text",
+ * a {...@link IllegalArgumentException} or {...@link AssertionError} is
thrown.
+ *
+ * <p>
+ * For example, this check will pass for the following strings:
+ *
+ * <pre>{...@code
+ * <foo>blah
+ * baz<em>foo</em> <x a="b">hello
+ * }</pre>
+ *
+ * <p>
+ * The check will fail for the following strings:
+ *
+ * <pre>{...@code
+ * baz<em>foo</em> <x
+ * baz<em>foo</em> <x a="b
+ * baz<em>foo</em> <x a="b"
+ * }</pre>
+ *
+ * <p>
+ * Note that the parser is lenient and this check will pass for HTML
that is
+ * not well-formed, or contains invalid tags, as long as the parser can
+ * determine the HTML context at the end of the string.
+ *
+ * <p>
+ * This check is intended to assert a convention-of-use constraint of
{...@link
+ *
com.google.gwt.safehtml.shared.SafeHtmlBuilder#appendHtmlConstant(String)}.
+ * Since the check is somewhat expensive, it is intended to run only in
the
+ * context of unit-tests or test environments, and not in production
+ * environments. Hence this check will only execute under the following
+ * conditions, and will be short-circuited otherwise:
+ *
+ * <ul>
+ * <li>In client-side code in hosted mode,</li>
+ * <li>In server-side code if assertions are enabled,</li>
+ * <li>In server-side code if the property {...@code
+ * com.google.gwt.safehtml.ForceCheckCompleteHtml} is set.</li>
+ * <li>In server-side code if {...@link
#setForceCheckCompleteHtml(boolean)} has
+ * been called with a {...@code true} argument.</li>
+ * </ul>
+ *
+ * @param html the HTML to check
+ */
+ public static void maybeCheckCompleteHtml(String html) {
+ if (GWT.isClient() || forceCheckCompleteHtml) {
+ Preconditions.checkArgument(isCompleteHtml(html),
+ "String is not complete HTML (ends in non-inner-HTML
context): %s",
+ html);
+ } else {
+ assert isCompleteHtml(html) :
+ "String is not complete HTML (ends in non-inner-HTML context): "
+ + html;
+ }
+ }
+
+ /**
+ * Sets a global flag that controls whether or not
+ * {...@link #maybeCheckCompleteHtml(String)} should perform its check in a
+ * server-side environment.
+ */
+ public static void setForceCheckCompleteHtml(boolean check) {
+ forceCheckCompleteHtml = check;
+ }
+
+ @VisibleForTesting
+ public static void setForceCheckCompleteHtmlFromProperty() {
+ forceCheckCompleteHtml =
+ System.getProperty(FORCE_CHECK_COMPLETE_HTML) != null;
+ }
+
+ private static boolean isCompleteHtml(String html) {
+ HtmlParser htmlParser = HtmlParserFactory.createParser();
+ try {
+ htmlParser.parse(html);
+ } catch (ParseException e) {
+ return false;
+ }
+ return htmlParser.getState() == HtmlParser.STATE_TEXT;
+ }
+}
=======================================
--- /dev/null
+++
/trunk/user/super/com/google/gwt/safehtml/super/com/google/gwt/safehtml/shared/SafeHtmlHostedModeUtils.java
Mon Sep 20 07:10:58 2010
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.safehtml.shared;
+
+import com.google.gwt.core.client.GwtScriptOnly;
+
+// This is the super-source peer of this class.
+...@gwtscriptonly
+public class SafeHtmlHostedModeUtils {
+
+ // Unused in super-source; only defined to avoid compiler warnings
+ public static final String FORCE_CHECK_COMPLETE_HTML = null;
+
+ public static void maybeCheckCompleteHtml(String html) {
+ // This check is a noop in web mode.
+ }
+
+ // Unused in super-source; only defined to avoid compiler warnings
+ public static void setForceCheckCompleteHtml(boolean check) { }
+ static void setForceCheckCompleteHtmlFromProperty() { }
+}
=======================================
--- /dev/null
+++
/trunk/user/test/com/google/gwt/safehtml/server/SafeHtmlHostedModeUtilsTest.java
Mon Sep 20 07:10:58 2010
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.safehtml.server;
+
+import com.google.gwt.safehtml.shared.GwtSafeHtmlHostedModeUtilsTest;
+import com.google.gwt.safehtml.shared.SafeHtmlHostedModeUtils;
+
+/**
+ * JUnit tests for {...@link SafeHtmlHostedModeUtils}.
+ */
+public class SafeHtmlHostedModeUtilsTest
+ extends GwtSafeHtmlHostedModeUtilsTest {
+
+ // This forces a GWTTestCase to run as a vanilla JUnit TestCase.
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ super.gwtSetUp();
+ // Since we can't assume assertions are enabled, force
+ // SafeHtmlHostedModeUtils#maybeCheckCompleteHtml to perform its check
+ // when running in JRE.
+ System.setProperty(
+ SafeHtmlHostedModeUtils.FORCE_CHECK_COMPLETE_HTML, "true");
+ SafeHtmlHostedModeUtils.setForceCheckCompleteHtmlFromProperty();
+ }
+}
=======================================
--- /dev/null
+++
/trunk/user/test/com/google/gwt/safehtml/shared/GwtSafeHtmlHostedModeUtilsTest.java
Mon Sep 20 07:10:58 2010
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.safehtml.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * GWT Unit tests for {...@link SafeHtmlHostedModeUtils}.
+ */
+public class GwtSafeHtmlHostedModeUtilsTest extends GWTTestCase {
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.safehtml.SafeHtmlTestsModule";
+ }
+
+ public void testMaybeCheckCompleteHtml() {
+ if (GWT.isProdMode()) {
+ // SafeHtmlHostedModeUtils#isCompleteHtml always returns true in
+ // prod mode
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml("<foo>blah");
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml("baz<em>foo</em> <x");
+ } else {
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml("");
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml("blah");
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml("<foo>blah");
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml("<>blah");
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml("baz");
+
+ assertCheckCompleteHtmlFails("baz<");
+ assertCheckCompleteHtmlFails("baz<em>foo</em> <x");
+ assertCheckCompleteHtmlFails("baz<em>foo</em> <x a=b");
+ assertCheckCompleteHtmlFails("baz<em>foo</em> <x a=\"b");
+ assertCheckCompleteHtmlFails("baz<em>foo</em> <x a=\"b\"");
+ assertCheckCompleteHtmlFails("baz<em>foo</em> <x a=\"b\" ");
+
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml(
+ "baz<em>foo</em> <x a=\"b\"> ");
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml(
+ "baz<em>foo</em> <x a=\"b\">sadf");
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml(
+ "baz<em>foo</em> <x a=\"b\">");
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml(
+ "baz<em>foo</em> <x a=\"b\"/>");
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml(
+ "baz<em>foo</em> <x a=\"b\"/>bbb");
+ }
+ }
+
+ private void assertCheckCompleteHtmlFails(String html) {
+ try {
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml(html);
+ fail("maybeCheckCompleteHtml failed to throw exception for: " +
html);
+ } catch (IllegalArgumentException e) {
+ // expected
+ } catch (AssertionError e) {
+ // expected
+ }
+ }
+}
=======================================
--- /trunk/dev/build.xml Wed Sep 8 15:25:08 2010
+++ /trunk/dev/build.xml Mon Sep 20 07:10:58 2010
@@ -57,6 +57,7 @@
<include name="apache/tapestry-util-text-4.0.2.jar" />
<include name="apache/ant-1.6.5.jar" />
<include name="eclipse/jdt-3.4.2.jar" />
+ <include name="guava/guava-r06/guava-r06-rebased.jar" />
<include name="jetty/jetty-6.1.11.jar" />
<include name="icu4j/icu4j-4_4_1.jar" />
<include
name="protobuf/protobuf-2.2.0/protobuf-java-rebased-2.2.0.jar" />
@@ -99,6 +100,7 @@
<include name="htmlunit/htmlunit-r5940/htmlunit-r5940.jar" />
<include
name="htmlunit/htmlunit-r5940/htmlunit-core-js-r5940.jar" />
<include name="nekohtml/nekohtml-1.9.13.jar" />
+ <include
name="streamhtmlparser/streamhtmlparser-jsilver-r10/streamhtmlparser-jsilver-r10-rebased.jar"
/>
<include name="xalan/xalan-2.7.1.jar" />
<include name="xerces/xerces-2_9_1/serializer.jar" />
<include name="xerces/xerces-2_9_1/xercesImpl-NoMetaInf.jar" />
@@ -117,9 +119,11 @@
<zipfileset
src="${gwt.tools.lib}/apache/tapestry-util-text-4.0.2.jar" />
<zipfileset src="${gwt.tools.lib}/apache/ant-1.6.5.jar" />
<zipfileset src="${gwt.tools.lib}/eclipse/jdt-3.4.2.jar" />
+ <zipfileset
src="${gwt.tools.lib}/guava/guava-r06/guava-r06-rebased.jar" />
<zipfileset src="${gwt.tools.lib}/jetty/jetty-6.1.11.jar" />
<zipfileset src="${gwt.tools.lib}/icu4j/icu4j-4_4_1.jar" />
<zipfileset
src="${gwt.tools.lib}/protobuf/protobuf-2.2.0/protobuf-java-rebased-2.2.0.jar"
/>
+ <zipfileset
src="${gwt.tools.lib}/streamhtmlparser/streamhtmlparser-jsilver-r10/streamhtmlparser-jsilver-r10-rebased.jar"
/>
<zipfileset src="${gwt.tools.lib}/tomcat/ant-launcher-1.6.5.jar"
/>
<zipfileset src="${gwt.tools.lib}/tomcat/catalina-1.0.jar" />
<zipfileset
src="${gwt.tools.lib}/tomcat/catalina-optional-1.0.jar" />
=======================================
--- /trunk/eclipse/user/.classpath Wed Sep 8 07:45:57 2010
+++ /trunk/eclipse/user/.classpath Mon Sep 20 07:10:58 2010
@@ -39,5 +39,7 @@
<classpathentry exported="true" kind="var"
path="GWT_TOOLS/lib/javax/validation/validation-api-1.0.0.GA.jar"
sourcepath="/GWT_TOOLS/lib/javax/validation/validation-api-1.0.0.GA-sources.jar"/>
<classpathentry exported="true" kind="var"
path="GWT_TOOLS/lib/javax/validation/validation-api-1.0.0.GA-sources.jar"/>
<classpathentry kind="var" path="GWT_TOOLS/lib/jetty/jetty-6.1.11.jar"
sourcepath="/GWT_TOOLS/lib/jetty/jetty-6.1.11-src.zip"/>
+ <classpathentry kind="var"
path="GWT_TOOLS/lib/guava/guava-r06/guava-r06-rebased.jar"/>
+ <classpathentry kind="var"
path="GWT_TOOLS/lib/streamhtmlparser/streamhtmlparser-jsilver-r10/streamhtmlparser-jsilver-r10-rebased.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
=======================================
--- /trunk/tools/api-checker/config/gwt20_21userApi.conf Thu Sep 16
12:42:20 2010
+++ /trunk/tools/api-checker/config/gwt20_21userApi.conf Mon Sep 20
07:10:58 2010
@@ -78,6 +78,7 @@
:user/src/com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
:user/src/com/google/gwt/rpc/client/impl/EscapeUtil.java\
:user/src/com/google/gwt/rpc/linker\
+:user/src/com/google/gwt/safehtml/shared/SafeHtmlHostedModeUtils.java\
:user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java\
:user/src/com/google/gwt/user/linker\
:user/src/com/google/gwt/uibinder/attributeparsers\
=======================================
--- /trunk/user/src/com/google/gwt/safehtml/SafeHtml.gwt.xml Fri Aug 27
03:13:26 2010
+++ /trunk/user/src/com/google/gwt/safehtml/SafeHtml.gwt.xml Mon Sep 20
07:10:58 2010
@@ -17,9 +17,10 @@
<!-- SafeHtml - facilities for avoiding XSS attacks
-->
<!--
-->
<module>
- <inherits name='com.google.gwt.user.User'/>
+ <inherits name="com.google.gwt.regexp.RegExp"/>
<source path="client"/>
<source path="shared"/>
+ <super-source path="super"/>
<generate-with
class="com.google.gwt.safehtml.rebind.SafeHtmlTemplatesGenerator">
<when-type-assignable
class="com.google.gwt.safehtml.client.SafeHtmlTemplates"/>
</generate-with>
=======================================
--- /trunk/user/src/com/google/gwt/safehtml/shared/SafeHtml.java Mon Aug 23
04:03:59 2010
+++ /trunk/user/src/com/google/gwt/safehtml/shared/SafeHtml.java Mon Sep 20
07:10:58 2010
@@ -21,10 +21,14 @@
* An object that implements this interface encapsulates HTML that is
guaranteed
* to be safe to use (with respect to potential Cross-Site-Scripting
* vulnerabilities) in an HTML context.
- *
- * Note on usage: SafeHtml should be used to ensure user input is not
executed
- * in the browser. SafeHtml should not be used to sanitize input before
sending
- * it to the server.
+ *
+ * <p>
+ * Note on usage: SafeHtml should be used to ensure user input is not
executed
+ * in the browser. SafeHtml should not be used to sanitize input before
sending
+ * it to the server: The server cannot rely on the type contract of
SafeHtml
+ * values received from clients, because a malicious client could provide
+ * maliciously crafted serialized forms of implementations of this type
that
+ * violate the type contract.
*
* <p>
* All implementing classes must maintain the class invariant (by design
and
@@ -34,15 +38,25 @@
* context), in the sense that doing so must not cause execution of script
in
* the browser.
*
+ * <p>
+ * Furthermore, values of this type must be composable, i.e. for any two
values
+ * {...@code A} and {...@code B} of this type, {...@code A.asString() +
B.asString()}
+ * must itself be a value that satisfies the SafeHtml type constraint. This
+ * requirement implies that for any value {...@code A} of this type, if
{...@code
+ * A.asString()} includes HTML markup, the string must end in an "inner
HTML"
+ * context and not inside a tag or attribute. For example, a value of
{...@code
+ * <div style="} or {...@code <img src="} would not satisfy the SafeHtml
contract.
+ * This is because concatenating such strings with a second value that
itself
+ * does not contain script-executing HTML markup can result in an overall
string
+ * that does. For example, if {...@code javascript:malicious()">} is appended
to
+ * {...@code <img src="}, the resulting string may result in script execution.
+ *
+ * <p>
* All implementations must implement equals() and hashCode() to behave
* consistently with the result of asString().equals() and
asString.hashCode().
- *
- * The internal string must not be null.
*
* <p>
- * Implementations of this interface must not implement
- * {...@link com.google.gwt.user.client.rpc.IsSerializable}, since
deserialization
- * can result in violation of the class invariant.
+ * Implementations must not return {...@code null} from {...@link #asString()}.
*/
public interface SafeHtml extends Serializable {
/*
@@ -63,20 +77,23 @@
*/
/**
- * Returns this object's contained HTML as a string. Based on this class'
- * contract, the returned string will be safe to use in an HTML context.
+ * Returns this object's contained HTML as a string.
+ *
+ * <p>
+ * Based on this class' contract, the returned value will be non-null
and a
+ * string that is safe to use in an HTML context.
*/
String asString();
/**
* Compares this string to the specified object.
- * Must be equal to asString().equals()
+ * Must be equal to asString().equals().
*/
boolean equals(Object anObject);
/**
* Returns a hash code for this string.
- * Must be equal to asString().hashCode()
+ * Must be equal to asString().hashCode().
*/
int hashCode();
}
=======================================
--- /trunk/user/src/com/google/gwt/safehtml/shared/SafeHtmlBuilder.java Wed
Sep 1 10:49:54 2010
+++ /trunk/user/src/com/google/gwt/safehtml/shared/SafeHtmlBuilder.java Mon
Sep 20 07:10:58 2010
@@ -24,11 +24,11 @@
* <p>
* In addition, it supports methods that allow strings with HTML markup to
be
* appended without escaping: One can append other {...@link SafeHtml}
objects, and
- * one can append constant strings. The method that appends constant
strings (
- * {...@link #appendHtmlConstant(String)}) requires a convention of use to be
- * adhered to in order for this class to adhere to the contract required by
+ * one can append constant strings. The method that appends constant
strings
+ * ({...@link #appendHtmlConstant(String)}) requires a convention of use to be
+ * adhered to in order for this class to adhere to the contract required by
* {...@link SafeHtml}: The argument expression must be fully determined and
known
- * to be safe at compile time, and the value of the argument must not
contain
+ * to be safe at compile time, and the value of the argument must not
contain
* incomplete HTML tags. See {...@link #appendHtmlConstant(String)} for
details.
*
* <p>
@@ -40,7 +40,7 @@
*/
public final class SafeHtmlBuilder {
- private StringBuilder sb = new StringBuilder();
+ private final StringBuilder sb = new StringBuilder();
/**
* Constructs an empty SafeHtmlBuilder.
@@ -52,7 +52,7 @@
* Boolean and numeric types converted to String are always HTML safe --
no
* escaping necessary.
*/
-
+
/**
* Appends the string representation of a boolean.
*
@@ -155,7 +155,7 @@
/**
* Appends a string consisting of several newline-separated lines after
- * HTML-escaping it. Newlines in the original string are converted to
{...@code
+ * HTML-escaping it. Newlines in the original string are converted to
{...@code
* <br>}.
*
* @param text the string to append
@@ -172,28 +172,42 @@
* <p>
* <b>Important</b>: For this class to be able to honor its contract as
* required by {...@link SafeHtml}, all uses of this method must satisfy the
- * following requirements:
+ * following constraints:
*
- * <ul>
+ * <ol>
*
- * <li>The argument expression must be fully determined and known to be
safe
- * at compile time.
+ * <li>The argument expression must be fully determined at compile time.
*
- * <li>The value of the argument must not contain incomplete HTML tags.
I.e.,
- * the following is not a correct use of this method, because the {...@code
<a>}
- * tag is incomplete:
+ * <li>The value of the argument must end in "inner HTML" context and not
+ * contain incomplete HTML tags. I.e., the following is not a correct
use of
+ * this method, because the {...@code <a>} tag is incomplete:
*
* <pre class="code">
* {...@code shb.appendConstantHtml("<a href='").append(url)}</pre>
*
- * </ul>
+ * </ol>
+ *
+ * <p>
+ * The first constraint provides a sufficient condition that the appended
+ * string (and any HTML markup contained in it) originates from a trusted
+ * source. The second constraint ensures the composability of {...@link
SafeHtml}
+ * values.
+ *
+ * <p>
+ * When executing client-side in hosted mode, or server side with
assertions
+ * enabled, the argument is HTML-parsed and validated to satisfy the
second
+ * constraint (the server-side check can also be enabled
programmatically, see
+ * {...@link SafeHtmlHostedModeUtils#maybeCheckCompleteHtml(String)} for
+ * details). For performance reasons, this check is not performed in
prod mode
+ * on the client, and with assertions disabled on the server.
*
* @param html the HTML snippet to be appended
* @return a reference to this object
+ * @throws IllegalArgumentException if not running in prod mode and
{...@code
+ * html} violates the second constraint
*/
public SafeHtmlBuilder appendHtmlConstant(String html) {
- // TODO(xtof): (hosted-mode only) assert that html satisfies the second
- // constraint.
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml(html);
sb.append(html);
return this;
}
=======================================
--- /trunk/user/src/com/google/gwt/safehtml/shared/SafeHtmlUtils.java Thu
Sep 2 08:33:16 2010
+++ /trunk/user/src/com/google/gwt/safehtml/shared/SafeHtmlUtils.java Mon
Sep 20 07:10:58 2010
@@ -38,22 +38,42 @@
* the string.
*
* <p>
- * <b>Important</b>: For this class to be able to honor its contract as
- * required by {...@link SafeHtml}, all uses of this method must satisfy the
- * following requirements:
+ * <b>Important</b>: For this method to be able to honor the {...@link
SafeHtml}
+ * contract, all uses of this method must satisfy the following
constraints:
*
- * <ul>
+ * <ol>
*
- * <li>The argument expression must be fully determined and known to be
safe
- * at compile time.
+ * <li>The argument expression must be fully determined at compile time.
*
- * <li>The value of the argument must not contain incomplete HTML tags.
+ * <li>The value of the argument must end in "inner HTML" context and not
+ * contain incomplete HTML tags. I.e., the following is not a correct
use of
+ * this method, because the {...@code <a>} tag is incomplete:
*
- * </ul>
+ * <pre class="code">
+ * {...@code shb.appendConstantHtml("<a href='").append(url)}</pre>
+ *
+ * </ol>
+ *
+ * <p>
+ * The first constraint provides a sufficient condition that the
argument (and
+ * any HTML markup contained in it) originates from a trusted source. The
+ * second constraint ensures the composability of {...@link SafeHtml}
values.
+ *
+ * <p>
+ * When executing client-side in hosted mode, or server side with
assertions
+ * enabled, the argument is HTML-parsed and validated to satisfy the
second
+ * constraint (the server-side check can also be enabled
programmatically, see
+ * {...@link SafeHtmlHostedModeUtils#maybeCheckCompleteHtml(String)} for
+ * details). For performance reasons, this check is not performed in
prod mode
+ * on the client, and with assertions disabled on the server.
+ *
+ * @param s the string to be wrapped as a {...@link SafeHtml}
+ * @return {...@code s}, wrapped as a {...@link SafeHtml}
+ * @throws IllegalArgumentException if not running in prod mode and
{...@code
+ * html} violates the second constraint
*/
public static SafeHtml fromSafeConstant(String s) {
- // TODO(pdr): (hosted-mode only) assert that html satisfies the second
- // constraint.
+ SafeHtmlHostedModeUtils.maybeCheckCompleteHtml(s);
return new SafeHtmlString(s);
}
=======================================
--- /trunk/user/test/com/google/gwt/safehtml/SafeHtmlGwtSuite.java Wed Sep
1 10:49:54 2010
+++ /trunk/user/test/com/google/gwt/safehtml/SafeHtmlGwtSuite.java Mon Sep
20 07:10:58 2010
@@ -18,6 +18,7 @@
import com.google.gwt.junit.tools.GWTTestSuite;
import com.google.gwt.safehtml.client.SafeHtmlTemplatesTest;
import com.google.gwt.safehtml.shared.GwtSafeHtmlBuilderTest;
+import com.google.gwt.safehtml.shared.GwtSafeHtmlHostedModeUtilsTest;
import com.google.gwt.safehtml.shared.GwtSafeHtmlStringTest;
import com.google.gwt.safehtml.shared.GwtSafeHtmlUtilsTest;
@@ -35,6 +36,7 @@
suite.addTestSuite(GwtSafeHtmlStringTest.class);
suite.addTestSuite(GwtSafeHtmlBuilderTest.class);
suite.addTestSuite(SafeHtmlTemplatesTest.class);
+ suite.addTestSuite(GwtSafeHtmlHostedModeUtilsTest.class);
return suite;
}
=======================================
--- /trunk/user/test/com/google/gwt/safehtml/SafeHtmlJreSuite.java Wed Sep
1 10:49:54 2010
+++ /trunk/user/test/com/google/gwt/safehtml/SafeHtmlJreSuite.java Mon Sep
20 07:10:58 2010
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may
not
* use this file except in compliance with the License. You may obtain a
copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -17,6 +17,7 @@
import com.google.gwt.safehtml.rebind.HtmlTemplateParserTest;
import com.google.gwt.safehtml.rebind.ParsedHtmlTemplateTest;
+import com.google.gwt.safehtml.server.SafeHtmlHostedModeUtilsTest;
import com.google.gwt.safehtml.server.UriUtilsTest;
import com.google.gwt.safehtml.shared.SafeHtmlBuilderTest;
import com.google.gwt.safehtml.shared.SafeHtmlStringTest;
@@ -33,7 +34,7 @@
public static Test suite() {
TestSuite suite = new TestSuite(
"Test suite for SafeHtml tests that require the JRE");
-
+
suite.addTestSuite(SafeHtmlUtilsTest.class);
suite.addTestSuite(SafeHtmlBuilderTest.class);
suite.addTestSuite(SimpleHtmlSanitizerTest.class);
@@ -41,10 +42,11 @@
suite.addTestSuite(UriUtilsTest.class);
suite.addTestSuite(HtmlTemplateParserTest.class);
suite.addTestSuite(ParsedHtmlTemplateTest.class);
-
+ suite.addTestSuite(SafeHtmlHostedModeUtilsTest.class);
+
return suite;
}
-
+
private SafeHtmlJreSuite() {
}
}
=======================================
---
/trunk/user/test/com/google/gwt/safehtml/shared/GwtSafeHtmlBuilderTest.java
Wed Sep 1 10:49:54 2010
+++
/trunk/user/test/com/google/gwt/safehtml/shared/GwtSafeHtmlBuilderTest.java
Mon Sep 20 07:10:58 2010
@@ -15,6 +15,7 @@
*/
package com.google.gwt.safehtml.shared;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.junit.client.GWTTestCase;
/**
@@ -45,6 +46,29 @@
+ FOOBARBAZ_HTML;
assertEquals(expected, b.toSafeHtml().asString());
}
+
+ public void testAppendHtmlConstant_innerHtml() {
+ SafeHtml html = new SafeHtmlBuilder()
+ .appendHtmlConstant("<div id=\"div_0\">")
+ .appendEscaped("0 < 1")
+ .appendHtmlConstant("</div>").toSafeHtml();
+ assertEquals("<div id=\"div_0\">0 < 1</div>", html.asString());
+ }
+
+ public void testAppendHtmlConstant_withIncompleteHtml() {
+ if (GWT.isProdMode()) {
+ // appendHtmlConstant does not parse/validate its argument in prod
mode.
+ // Hence we short-circuit this test in prod mode.
+ return;
+ }
+ SafeHtmlBuilder b = new SafeHtmlBuilder();
+ try {
+ b.appendHtmlConstant("<a href=\"");
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
@Override
public String getModuleName() {
=======================================
---
/trunk/user/test/com/google/gwt/safehtml/shared/GwtSafeHtmlUtilsTest.java
Wed Sep 1 10:49:54 2010
+++
/trunk/user/test/com/google/gwt/safehtml/shared/GwtSafeHtmlUtilsTest.java
Mon Sep 20 07:10:58 2010
@@ -15,6 +15,7 @@
*/
package com.google.gwt.safehtml.shared;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.junit.client.GWTTestCase;
/**
@@ -80,6 +81,20 @@
SafeHtml h = SafeHtmlUtils.fromSafeConstant(CONSTANT_HTML);
assertEquals(CONSTANT_HTML, h.asString());
}
+
+ public void testFromSafeConstant_withIncompleteHtml() {
+ if (GWT.isProdMode()) {
+ // fromSafeConstant does not parse/validate its argument in prod
mode.
+ // Hence we short-circuit this test in prod mode.
+ return;
+ }
+ try {
+ SafeHtml h = SafeHtmlUtils.fromSafeConstant("<a href=\"");
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
public void testFromString() {
SafeHtml h = SafeHtmlUtils.fromString(CONSTANT_HTML);
=======================================
---
/trunk/user/test/com/google/gwt/safehtml/shared/SafeHtmlBuilderTest.java
Wed Sep 1 10:49:54 2010
+++
/trunk/user/test/com/google/gwt/safehtml/shared/SafeHtmlBuilderTest.java
Mon Sep 20 07:10:58 2010
@@ -25,4 +25,13 @@
public String getModuleName() {
return null;
}
-}
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ super.gwtSetUp();
+ // Since we can't assume assertions are enabled, force
+ // SafeHtmlHostedModeUtils#maybeCheckCompleteHtml to perform its check
+ // when running in JRE.
+ SafeHtmlHostedModeUtils.setForceCheckCompleteHtml(true);
+ }
+}
=======================================
--- /trunk/user/test/com/google/gwt/safehtml/shared/SafeHtmlUtilsTest.java
Mon Aug 23 04:03:59 2010
+++ /trunk/user/test/com/google/gwt/safehtml/shared/SafeHtmlUtilsTest.java
Mon Sep 20 07:10:58 2010
@@ -19,10 +19,19 @@
* Unit tests for SafeHtmlUtils.
*/
public class SafeHtmlUtilsTest extends GwtSafeHtmlUtilsTest {
-
+
// This forces a GWTTestCase to run as a vanilla JUnit TestCase.
@Override
public String getModuleName() {
return null;
}
-}
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ super.gwtSetUp();
+ // Since we can't assume assertions are enabled, force
+ // SafeHtmlHostedModeUtils#maybeCheckCompleteHtml to perform its check
+ // when running in JRE.
+ SafeHtmlHostedModeUtils.setForceCheckCompleteHtml(true);
+ }
+}
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors