/* * Copyright 2015 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.template.soy.parsepasses.contextautoesc; import static org.junit.Assert.assertEquals; import com.google.common.base.Optional; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ContextTest { private void assertUnionNoop(String dominating, String other) { assertUnion(dominating, dominating, other); } private static void assertUnion(String expected, String a, String b) { Context aContext = Context.parse(a); Context bContext = Context.parse(b); Context expectedContext = Context.parse(expected); assertEquals( "Union of " + aContext + " and " + bContext, Optional.of(expectedContext), Context.union(aContext, bContext)); assertEquals( "Reverse union of " + bContext + " and " + aContext, Optional.of(expectedContext), Context.union(bContext, aContext)); } private static void assertUnionFails(String a, String b) { Context aContext = Context.parse(a); Context bContext = Context.parse(b); assertEquals( "Union of " + aContext + " and " + bContext, Optional.absent(), Context.union(aContext, bContext)); assertEquals( "Reverse union of " + bContext + " and " + aContext, Optional.absent(), Context.union(bContext, aContext)); } @Test public void testObviousUnions() { assertUnionNoop("CSS", "CSS"); assertUnionNoop("HTML_PCDATA", "HTML_PCDATA"); assertUnionNoop("JS", "JS"); assertUnionNoop("HTML_TAG_NAME", "HTML_TAG_NAME"); } @Test public void testJsUnions() { // Identity cases. assertUnionNoop("JS UNKNOWN", "JS UNKNOWN"); assertUnionNoop("JS DIV_OP", "JS DIV_OP"); assertUnionNoop("JS REGEX", "JS REGEX"); // Unknown accepts either. assertUnionNoop("JS UNKNOWN", "JS REGEX"); assertUnionNoop("JS UNKNOWN", "JS DIV_OP"); // Unknown is the join of the two different JS types. assertUnion("JS UNKNOWN", "JS REGEX", "JS DIV_OP"); // Same, even if it's within a script attribute. assertUnion( "JS NORMAL SCRIPT DOUBLE_QUOTE UNKNOWN", "JS NORMAL SCRIPT DOUBLE_QUOTE REGEX", "JS NORMAL SCRIPT DOUBLE_QUOTE DIV_OP"); // One is in a script, one is not: assertUnionFails("JS NORMAL SCRIPT DOUBLE_QUOTE REGEX", "JS REGEX"); } @Test public void testUriPartUnions() { // The more well-formed states: String start = "URI START NORMAL"; String maybeScheme = "URI MAYBE_SCHEME NORMAL"; String authorityOrPath = "URI AUTHORITY_OR_PATH NORMAL"; String query = "URI QUERY NORMAL"; String fragment = "URI FRAGMENT NORMAL"; // The more nebulous and sticky states: String maybeVariableScheme = "URI MAYBE_VARIABLE_SCHEME NORMAL"; String unknownPreFragment = "URI UNKNOWN_PRE_FRAGMENT NORMAL"; // NOTE: "NONE" needed to disambiguate from JsFollowingSlash.UNKNOWN String unknown = "URI NONE NONE NONE NONE UNKNOWN NORMAL"; String dangerousScheme = "URI DANGEROUS_SCHEME NORMAL"; // Well-formed states, identity: assertUnionNoop(start, start); assertUnionNoop(maybeScheme, maybeScheme); assertUnionNoop(authorityOrPath, authorityOrPath); assertUnionNoop(query, query); assertUnionNoop(fragment, fragment); // Well-formed states before the fragment: assertUnion(unknownPreFragment, start, maybeScheme); assertUnion(unknownPreFragment, start, authorityOrPath); assertUnion(unknownPreFragment, start, query); assertUnion(unknownPreFragment, maybeScheme, authorityOrPath); assertUnion(unknownPreFragment, maybeScheme, query); assertUnion(unknownPreFragment, authorityOrPath, query); // Well-formed states, both before and after fragment: assertUnion(unknown, start, fragment); assertUnion(unknown, maybeScheme, fragment); assertUnion(unknown, authorityOrPath, fragment); assertUnion(unknown, query, fragment); // Variable at the beginning matched with other parts: assertUnionNoop(maybeVariableScheme, start); assertUnionNoop(maybeVariableScheme, maybeScheme); assertUnionNoop(maybeVariableScheme, authorityOrPath); assertUnionNoop(maybeVariableScheme, query); assertUnionNoop(maybeVariableScheme, maybeVariableScheme); assertUnion(unknown, maybeVariableScheme, fragment); // Unknown but before fragment: assertUnionNoop(unknownPreFragment, start); assertUnionNoop(unknownPreFragment, maybeScheme); assertUnionNoop(unknownPreFragment, authorityOrPath); assertUnionNoop(unknownPreFragment, query); assertUnionNoop(unknownPreFragment, maybeVariableScheme); assertUnionNoop(unknownPreFragment, unknownPreFragment); assertUnion(unknown, unknownPreFragment, fragment); // Either before or after fragment: assertUnionNoop(unknown, start); assertUnionNoop(unknown, maybeScheme); assertUnionNoop(unknown, maybeVariableScheme); assertUnionNoop(unknown, authorityOrPath); assertUnionNoop(unknown, query); assertUnionNoop(unknown, unknownPreFragment); assertUnionNoop(unknown, fragment); // Poison context: assertUnionNoop(dangerousScheme, start); assertUnionNoop(dangerousScheme, maybeScheme); assertUnionNoop(dangerousScheme, maybeVariableScheme); assertUnionNoop(dangerousScheme, authorityOrPath); assertUnionNoop(dangerousScheme, query); assertUnionNoop(dangerousScheme, unknownPreFragment); assertUnionNoop(dangerousScheme, fragment); assertUnionNoop(dangerousScheme, unknown); } @Test public void testUriTypeUnions() { assertUnionNoop("URI START NORMAL", "URI START NORMAL"); assertUnionNoop("URI START MEDIA", "URI START MEDIA"); // For now, we don't allow unioning these two types. assertUnionFails("URI START MEDIA", "URI START NORMAL"); } @Test public void testTagStates() { // Identity for HTML_TAG: assertUnionNoop("HTML_TAG NORMAL", "HTML_TAG NORMAL"); // Two very different types of tags: {if $x}<img{else}<script{/if} assertUnionFails("HTML_TAG NORMAL", "HTML_TAG SCRIPT"); // Something like: <a{if $x} foo="bar"{/if} or even <a{if $x} {/if} assertUnionNoop("HTML_TAG NORMAL", "HTML_TAG_NAME NORMAL"); assertUnionNoop("HTML_TAG STYLE", "HTML_TAG_NAME STYLE"); // Incompatible tag contexts: {if $x}<img src="foo"{else}<script{/if} assertUnionFails("HTML_TAG NORMAL", "HTML_TAG_NAME SCRIPT"); // Case: <input {if $x}checked{/if} assertUnionNoop("HTML_TAG SCRIPT", "HTML_ATTRIBUTE_NAME SCRIPT"); // Same but different element types. assertUnionFails("HTML_TAG NORMAL", "HTML_ATTRIBUTE_NAME SCRIPT"); // Something like: <a {if $x}b=foo{/if} assertUnionNoop("HTML_TAG NORMAL", "HTML_NORMAL_ATTR_VALUE NORMAL PLAIN_TEXT SPACE_OR_TAG_END"); // Similar, but one side is a script and the other isn't: assertUnionFails( "HTML_TAG SCRIPT", "HTML_NORMAL_ATTR_VALUE NORMAL PLAIN_TEXT SPACE_OR_TAG_END"); // Or, unclosed quote: <a {if $x}b="foo{/if} assertUnionFails("HTML_TAG NORMAL", "HTML_NORMAL_ATTR_VALUE NORMAL PLAIN_TEXT SINGLE_QUOTE"); } @Test public void testClearlyFailingUnions() { assertUnionFails("TEXT", "HTML_PCDATA"); assertUnionFails("CSS", "JS"); } }