/*
* 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.template.soy.basicdirectives;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.template.soy.coredirectives.EscapeHtmlDirective;
import com.google.template.soy.data.SanitizedContent;
import com.google.template.soy.data.UnsafeSanitizedContentOrdainer;
import com.google.template.soy.pysrc.restricted.PyExpr;
import com.google.template.soy.pysrc.restricted.PyStringExpr;
import com.google.template.soy.shared.AbstractSoyPrintDirectiveTestCase;
import com.google.template.soy.shared.AbstractSoyPrintDirectiveTestCase.JsSrcPrintDirectiveTestBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** @author Mike Samuel */
@RunWith(JUnit4.class)
public class BasicEscapeDirectiveTest extends AbstractSoyPrintDirectiveTestCase {
@Test
public final void testApplyEscapeJsString() {
BasicEscapeDirective escapeJsString = new BasicEscapeDirective.EscapeJsString();
assertTofuOutput("", "", escapeJsString);
assertTofuOutput("foo", "foo", escapeJsString);
assertTofuOutput("foo\\\\bar", "foo\\bar", escapeJsString);
assertTofuOutput(
"foo\\\\bar",
// Sanitized HTML is not exempt from escaping when embedded in JS.
UnsafeSanitizedContentOrdainer.ordainAsSafe("foo\\bar", SanitizedContent.ContentKind.HTML),
escapeJsString);
assertTofuOutput("\\\\", "\\", escapeJsString);
assertTofuOutput("\\x27\\x27", "''", escapeJsString);
assertTofuOutput("\\x22foo\\x22", "\"foo\"", escapeJsString);
assertTofuOutput("42", 42, escapeJsString);
new JsSrcPrintDirectiveTestBuilder()
.addTest("", " '' ", escapeJsString)
.addTest("foo", " 'foo' ", escapeJsString)
.addTest("a\\\\b", " 'a\\\\b' ", escapeJsString)
.addTest(
"foo\\\\bar", " soydata.VERY_UNSAFE.ordainSanitizedHtml('foo\\\\bar') ", escapeJsString)
.addTest("\\x27\\x27", " '\\'\\'' ", escapeJsString)
.addTest("\\x22foo\\x22", " '\"foo\"' ", escapeJsString)
.addTest("\\r\\n \\u2028", " '\\r\\n \\u2028' ", escapeJsString)
.addTest("42", "42", escapeJsString)
.runTests();
}
@Test
public final void testApplyEscapeJsValue() {
BasicEscapeDirective escapeJsValue = new BasicEscapeDirective.EscapeJsValue();
assertTofuOutput("''", "", escapeJsValue);
assertTofuOutput("'foo'", "foo", escapeJsValue);
assertTofuOutput("'a\\\\b'", "a\\b", escapeJsValue);
assertTofuOutput("'\\x27\\x27'", "''", escapeJsValue);
assertTofuOutput("'\\x22foo\\x22'", "\"foo\"", escapeJsValue);
assertTofuOutput("'\\r\\n \\u2028'", "\r\n \u2028", escapeJsValue);
// Not quoted
assertTofuOutput(" null ", null, escapeJsValue);
assertTofuOutput(" 42.0 ", 42, escapeJsValue);
assertTofuOutput(" true ", true, escapeJsValue);
new JsSrcPrintDirectiveTestBuilder()
.addTest("''", " '' ", escapeJsValue)
.addTest("'foo'", " 'foo' ", escapeJsValue)
.addTest("'a\\\\b'", " 'a\\\\b' ", escapeJsValue)
.addTest("'\\x27\\x27'", " '\\'\\'' ", escapeJsValue)
.addTest("'\\x22foo\\x22'", " '\"foo\"' ", escapeJsValue)
.addTest("'\\r\\n \\u2028'", " '\\r\\n \\u2028' ", escapeJsValue)
.addTest(" null ", "null", escapeJsValue)
.addTest(" 42 ", "42", escapeJsValue)
.addTest(" true ", "true", escapeJsValue)
.runTests();
}
@Test
public final void testApplyEscapeHtml() {
EscapeHtmlDirective escapeHtml = new EscapeHtmlDirective();
assertTofuOutput("", "", escapeHtml);
assertTofuOutput("1 < 2 && 3 < 4", "1 < 2 && 3 < 4", escapeHtml);
assertTofuOutput("42", 42, escapeHtml);
new JsSrcPrintDirectiveTestBuilder()
.addTest("", " '' ", escapeHtml)
.addTest("1 < 2 && 3 < 4", " '1 < 2 && 3 < 4' ", escapeHtml)
.addTest("42", " 42 ", escapeHtml)
.runTests();
}
@Test
public final void testApplyFilterNormalizeUri() {
BasicEscapeDirective filterNormalizeUri = new BasicEscapeDirective.FilterNormalizeUri();
assertTofuOutput("", "", filterNormalizeUri);
assertTofuOutput(
"http://www.google.com/a%20b", "http://www.google.com/a b", filterNormalizeUri);
assertTofuOutput("about:invalid#zSoyz", "javascript:alert(1337)", filterNormalizeUri);
assertTofuOutput("42", 42, filterNormalizeUri);
new JsSrcPrintDirectiveTestBuilder()
.addTest("", " '' ", filterNormalizeUri)
.addTest("http://www.google.com/a%20b", " 'http://www.google.com/a b' ", filterNormalizeUri)
.addTest("about:invalid#zSoyz", " 'javascript:alert(1337)' ", filterNormalizeUri)
.addTest("42", " 42 ", filterNormalizeUri)
.runTests();
}
@Test
public final void testEscapeHtmlAttributeNospace() {
BasicEscapeDirective htmlNospaceDirective =
new BasicEscapeDirective.EscapeHtmlAttributeNospace();
assertTofuOutput("", "", htmlNospaceDirective);
assertTofuOutput("a&b > c", "a&b > c", htmlNospaceDirective);
assertTofuOutput(
"<script>alert('boo');</script>",
"<script>alert('boo');</script>",
htmlNospaceDirective);
assertTofuOutput(
" < ",
// Sanitized HTML has tags stripped since this is directive is only used for attribute
// values.
UnsafeSanitizedContentOrdainer.ordainAsSafe(
"<foo> < <bar>", SanitizedContent.ContentKind.HTML),
htmlNospaceDirective);
assertTofuOutput(
"<foo>",
// But JS_STR_CHARS are.
UnsafeSanitizedContentOrdainer.ordainAsSafe("<foo>", SanitizedContent.ContentKind.JS),
htmlNospaceDirective);
new JsSrcPrintDirectiveTestBuilder()
.addTest("", "''", htmlNospaceDirective)
.addTest("a&b > c", " 'a&b > c' ", htmlNospaceDirective)
.addTest(
"<script>alert('boo');</script>",
" '<script>alert(\\'boo\\');</script>' ",
htmlNospaceDirective)
.addTest(
" < ",
"soydata.VERY_UNSAFE.ordainSanitizedHtml('<foo> < <bar>')",
htmlNospaceDirective)
.addTest(
"<foo>", "soydata.VERY_UNSAFE.ordainSanitizedJs('<foo>')", htmlNospaceDirective)
.runTests();
}
@Test
public final void testEscapeUri() {
BasicEscapeDirective escapeUri = new BasicEscapeDirective.EscapeUri();
assertTofuOutput("", "", escapeUri);
assertTofuOutput("a%25b%20%3E%20c", "a%b > c", escapeUri);
assertTofuOutput(
"a%25bc%20%3E%20d",
UnsafeSanitizedContentOrdainer.ordainAsSafe("a%bc > d", SanitizedContent.ContentKind.HTML),
escapeUri);
// NOTE: URIs are not treated specially (e.g. /redirect?continue={$url} should not allow $url
// to break out and add other query params, and would be unexpected.)
assertTofuOutput(
"a%25bc%20%3E%20d",
UnsafeSanitizedContentOrdainer.ordainAsSafe("a%bc > d", SanitizedContent.ContentKind.URI),
escapeUri);
new JsSrcPrintDirectiveTestBuilder()
.addTest("", "''", escapeUri)
.addTest("a%25b%20%3E%20c", " 'a%b > c' ", escapeUri)
.addTest(
"a%25bc%20%3E%20d", "soydata.VERY_UNSAFE.ordainSanitizedHtml('a%bc > d')", escapeUri)
.addTest(
"a%25bc%20%3E%20d", "soydata.VERY_UNSAFE.ordainSanitizedUri('a%bc > d')", escapeUri)
.runTests();
}
@Test
public final void testFilterCssValue() {
BasicEscapeDirective filterCssValue = new BasicEscapeDirective.FilterCssValue();
assertTofuOutput("", "", filterCssValue);
assertTofuOutput("green", "green", filterCssValue);
assertTofuOutput("zSoyz", "color:expression('foo')", filterCssValue);
assertTofuOutput(
"zSoyz",
// NOTE: HTML content kind should not override CSS filtering :-)
UnsafeSanitizedContentOrdainer.ordainAsSafe(
"color:expression('foo')", SanitizedContent.ContentKind.HTML),
filterCssValue);
assertTofuOutput(
"color:expression('foo')",
UnsafeSanitizedContentOrdainer.ordainAsSafe(
"color:expression('foo')", SanitizedContent.ContentKind.CSS),
filterCssValue);
new JsSrcPrintDirectiveTestBuilder()
.addTest("", "''", filterCssValue)
.addTest("green", "'green'", filterCssValue)
.addTest("zSoyz", "'color:expression(\\'foo\\')'", filterCssValue)
.addTest(
"zSoyz",
"soydata.VERY_UNSAFE.ordainSanitizedHtml('color:expression(\\'foo\\')')",
filterCssValue)
.addTest(
"color:expression('foo')",
"soydata.VERY_UNSAFE.ordainSanitizedCss('color:expression(\\'foo\\')')",
filterCssValue)
.runTests();
}
@Test
public final void testFilterCspNonce() {
BasicEscapeDirective filterCspNonceValue = new BasicEscapeDirective.FilterCspNonceValue();
assertTofuOutput("zSoyz", "", filterCspNonceValue);
assertTofuOutput("green", "green", filterCspNonceValue);
assertTofuOutput("Z3JlZW4NCg==", "Z3JlZW4NCg==", filterCspNonceValue);
assertTofuOutput("zSoyz", "color:expression('foo')", filterCspNonceValue);
assertTofuOutput("zSoyz", "\"", filterCspNonceValue);
new JsSrcPrintDirectiveTestBuilder()
.addTest("zSoyz", "''", filterCspNonceValue)
.addTest("green", "'green'", filterCspNonceValue)
.addTest("Z3JlZW4NCg==", "'Z3JlZW4NCg=='", filterCspNonceValue)
.addTest("zSoyz", "'color:expression(\\'foo\\')'", filterCspNonceValue)
.addTest("zSoyz", "'\"'", filterCspNonceValue)
.runTests();
}
@Test
public final void testPySrc() {
PyExpr data = new PyStringExpr("'data'");
// TODO(dcphillips): Add support for executing the sanitization call in Jython to verify it's
// actual output. Currently the sanitization relies on integration tests for full verification.
BasicEscapeDirective escapeJsString = new BasicEscapeDirective.EscapeJsString();
assertThat(escapeJsString.applyForPySrc(data, ImmutableList.<PyExpr>of()))
.isEqualTo(new PyExpr("sanitize.escape_js_string('data')", Integer.MAX_VALUE));
BasicEscapeDirective escapeHtmlAttribute = new BasicEscapeDirective.EscapeHtmlAttribute();
assertThat(escapeHtmlAttribute.applyForPySrc(data, ImmutableList.<PyExpr>of()))
.isEqualTo(new PyExpr("sanitize.escape_html_attribute('data')", Integer.MAX_VALUE));
BasicEscapeDirective filterCssValue = new BasicEscapeDirective.FilterCssValue();
assertThat(filterCssValue.applyForPySrc(data, ImmutableList.<PyExpr>of()))
.isEqualTo(new PyExpr("sanitize.filter_css_value('data')", Integer.MAX_VALUE));
}
}