Revision: 8823
Author: x...@google.com
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 &lt; 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

Reply via email to