Skip to content
This repository has been archived by the owner on Jan 22, 2024. It is now read-only.

Commit

Permalink
Automated g4 rollback
Browse files Browse the repository at this point in the history
*** Reason for rollback ***

Moved SanitizedContent itself (but not its subclasses) to Closure too
so that typechecking works.

*** Original change description ***

Automated g4 rollback

*** Reason for rollback ***

Broke Closure. 

*** Original change description ***

goog.soy to check that templates are either returning string or
SanitizedHtml and not other types.

Add an @define for projects to force Strict mode.

Move soydata.SanitizedContentKind definitions into Closure's Soy
directory goog.soy can depend on it.  Have soydata.SanitizedContentKind

... description truncated by g4 rollback ...

R=kai
DELTA=687  (482 added, 140 deleted, 65 changed)


Revision created by MOE tool push_codebase.
MOE_MIGRATION=5780


git-svn-id: http://closure-library.googlecode.com/svn/trunk@2280 0b95b8e8-c90f-11de-9d4f-f947ee5921c8
  • Loading branch information
gboyer@google.com committed Nov 8, 2012
1 parent 33354d3 commit f49c45e
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 8 deletions.
5 changes: 3 additions & 2 deletions closure/goog/deps.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

118 changes: 118 additions & 0 deletions closure/goog/soy/data.js
@@ -0,0 +1,118 @@
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// 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.

/**
* @fileoverview Soy data primitives.
*
* The goal is to encompass data types used by Soy, especially to mark content
* as known to be "safe".
*
* @author gboyer@google.com (Garrett Boyer)
*/

goog.provide('goog.soy.data');
goog.provide('goog.soy.data.SanitizedContent');
goog.provide('goog.soy.data.SanitizedContentKind');


/**
* A type of textual content.
*
* This is an enum of type Object so that these values are unforgeable.
*
* @enum {!Object}
*/
goog.soy.data.SanitizedContentKind = {

/**
* A snippet of HTML that does not start or end inside a tag, comment, entity,
* or DOCTYPE; and that does not contain any executable code
* (JS, {@code <object>}s, etc.) from a different trust domain.
*/
HTML: goog.DEBUG ? {sanitizedContentKindHtml: true} : {},

/**
* A sequence of code units that can appear between quotes (either kind) in a
* JS program without causing a parse error, and without causing any side
* effects.
* <p>
* The content should not contain unescaped quotes, newlines, or anything else
* that would cause parsing to fail or to cause a JS parser to finish the
* string its parsing inside the content.
* <p>
* The content must also not end inside an escape sequence ; no partial octal
* escape sequences or odd number of '{@code \}'s at the end.
*/
JS_STR_CHARS: goog.DEBUG ? {sanitizedContentJsStrChars: true} : {},

/** A properly encoded portion of a URI. */
URI: goog.DEBUG ? {sanitizedContentUri: true} : {},

/** An attribute name and value such as {@code dir="ltr"}. */
HTML_ATTRIBUTE: goog.DEBUG ? {sanitizedContentHtmlAttribute: true} : {},

// TODO: Consider separating rules, declarations, and values into
// separate types, but for simplicity, we'll treat explicitly blessed
// SanitizedContent as allowed in all of these contexts.
/**
* A CSS3 declaration, property, value or group of semicolon separated
* declarations.
*/
CSS: goog.DEBUG ? {sanitizedContentCss: true} : {},

/**
* Unsanitized plain-text content.
*
* This is effectively the "null" entry of this enum, and is sometimes used
* to explicitly mark content that should never be used unescaped. Since any
* string is safe to use as text, being of ContentKind.TEXT makes no
* guarantees about its safety in any other context such as HTML.
*/
TEXT: goog.DEBUG ? {sanitizedContentKindText: true} : {}
};



/**
* A string-like object that carries a content-type.
*
* IMPORTANT! Do not create these directly, nor instantiate the subclasses.
* Instead, use a trusted, centrally reviewed library as endorsed by your team
* to generate these objects. Otherwise, you risk accidentally creating
* SanitizedContent that is attacker-controlled and gets evaluated unescaped in
* templates.
*
* @param {string} content The assumed-sanitized string. Be careful!
* @constructor
*/
goog.soy.data.SanitizedContent = function(content) {
/**
* The textual content.
* @type {string}
*/
this.content = content;
};


/**
* The context in which this content is safe from XSS attacks.
* @type {goog.soy.data.SanitizedContentKind}
*/
goog.soy.data.SanitizedContent.prototype.contentKind;


/** @override */
goog.soy.data.SanitizedContent.prototype.toString = function() {
return this.content;
};
65 changes: 59 additions & 6 deletions closure/goog/soy/soy.js
Expand Up @@ -18,11 +18,24 @@


goog.provide('goog.soy'); goog.provide('goog.soy');


goog.require('goog.asserts');
goog.require('goog.dom'); goog.require('goog.dom');
goog.require('goog.dom.NodeType'); goog.require('goog.dom.NodeType');
goog.require('goog.dom.TagName'); goog.require('goog.dom.TagName');
goog.require('goog.soy.data');




/**
* @define {boolean} Whether to require all Soy templates to be "strict html".
* Soy templates that use strict autoescaping forbid noAutoescape along with
* many dangerous directives, and return a runtime type SanitizedContent that
* marks them as safe.
*
* If this flag is enabled, Soy templates will fail to render if a template
* returns plain text -- indicating it is a non-strict template.
*/
goog.soy.REQUIRE_STRICT_AUTOESCAPE = false;



/** /**
* Renders a Soy template and then set the output string as * Renders a Soy template and then set the output string as
Expand All @@ -37,9 +50,9 @@ goog.require('goog.dom.TagName');
*/ */
goog.soy.renderElement = function(element, template, opt_templateData, goog.soy.renderElement = function(element, template, opt_templateData,
opt_injectedData) { opt_injectedData) {
element.innerHTML = template( element.innerHTML = goog.soy.verifyTemplateOutputSafe_(template(
opt_templateData || goog.soy.defaultTemplateData_, undefined, opt_templateData || goog.soy.defaultTemplateData_, undefined,
opt_injectedData); opt_injectedData));
}; };




Expand All @@ -60,9 +73,9 @@ goog.soy.renderElement = function(element, template, opt_templateData,
goog.soy.renderAsFragment = function(template, opt_templateData, goog.soy.renderAsFragment = function(template, opt_templateData,
opt_injectedData, opt_domHelper) { opt_injectedData, opt_domHelper) {
var dom = opt_domHelper || goog.dom.getDomHelper(); var dom = opt_domHelper || goog.dom.getDomHelper();
return dom.htmlToDocumentFragment( return dom.htmlToDocumentFragment(goog.soy.verifyTemplateOutputSafe_(
template(opt_templateData || goog.soy.defaultTemplateData_, template(opt_templateData || goog.soy.defaultTemplateData_,
undefined, opt_injectedData)); undefined, opt_injectedData)));
}; };




Expand All @@ -83,9 +96,9 @@ goog.soy.renderAsElement = function(template, opt_templateData,
opt_injectedData, opt_domHelper) { opt_injectedData, opt_domHelper) {
var dom = opt_domHelper || goog.dom.getDomHelper(); var dom = opt_domHelper || goog.dom.getDomHelper();
var wrapper = dom.createElement(goog.dom.TagName.DIV); var wrapper = dom.createElement(goog.dom.TagName.DIV);
wrapper.innerHTML = template( wrapper.innerHTML = goog.soy.verifyTemplateOutputSafe_(template(
opt_templateData || goog.soy.defaultTemplateData_, opt_templateData || goog.soy.defaultTemplateData_,
undefined, opt_injectedData); undefined, opt_injectedData));


// If the template renders as a single element, return it. // If the template renders as a single element, return it.
if (wrapper.childNodes.length == 1) { if (wrapper.childNodes.length == 1) {
Expand All @@ -100,6 +113,46 @@ goog.soy.renderAsElement = function(template, opt_templateData,
}; };




/**
* Verifies that a template result is "safe" to insert as HTML.
*
* Note if the template is non-strict autoescape, the guarantees here are very
* weak. It is recommended applications switch to requiring strict
* autoescaping over time.
*
* @param {*} templateResult The template result.
* @return {string} The assumed-safe HTML output string.
* @private
*/
goog.soy.verifyTemplateOutputSafe_ = function(templateResult) {
// Allow strings as long as strict autoescaping is not mandated. Note we
// allow everything that isn't an object, because some non-escaping templates
// end up returning non-strings if their only print statement is a
// non-escaped argument, plus some unit tests spoof templates.
// TODO(gboyer): Track down and fix these cases.
if (!goog.soy.REQUIRE_STRICT_AUTOESCAPE && !goog.isObject(templateResult)) {
return String(templateResult);
}

// Allow SanitizedContent of kind HTML.
if (templateResult instanceof goog.soy.data.SanitizedContent) {
templateResult = /** @type {!goog.soy.data.SanitizedContent} */ (
templateResult);
var ContentKind = goog.soy.data.SanitizedContentKind;
if (templateResult.contentKind === ContentKind.HTML ||
templateResult.contentKind === ContentKind.HTML_ATTRIBUTE) {
return goog.asserts.assertString(templateResult.content);
}
}

goog.asserts.fail('Soy template output is unsafe for use as HTML: ' +
templateResult);

// In production, return a safe string, rather than failing hard.
return 'zSoyz';
};


/** /**
* Immutable object that is passed into templates that are rendered * Immutable object that is passed into templates that are rendered
* without any data. * without any data.
Expand Down
78 changes: 78 additions & 0 deletions closure/goog/soy/soy_test.html
Expand Up @@ -15,15 +15,27 @@
<script> <script>
goog.require('goog.dom'); goog.require('goog.dom');
goog.require('goog.dom.TagName'); goog.require('goog.dom.TagName');
goog.require('goog.functions');
goog.require('goog.soy'); goog.require('goog.soy');
goog.require('goog.soy.data');
goog.require('goog.soy.testHelper'); goog.require('goog.soy.testHelper');
goog.require('goog.string'); goog.require('goog.string');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit'); goog.require('goog.testing.jsunit');
</script> </script>
</head> </head>
<body> <body>
<script> <script>


var stubs;

function setUp() {
stubs = new goog.testing.PropertyReplacer();
}

function tearDown() {
stubs.reset();
}


function testRenderElement() { function testRenderElement() {
var testDiv = goog.dom.createElement(goog.dom.TagName.DIV); var testDiv = goog.dom.createElement(goog.dom.TagName.DIV);
Expand Down Expand Up @@ -116,6 +128,72 @@
assertEquals('Hello', elementToInnerHtml(elem)); assertEquals('Hello', elementToInnerHtml(elem));
} }


/**
* Asserts that the function throws an error for unsafe templates.
* @param {Function} function Callback to test.
*/
function assertUnsafeTemplateOutputErrorThrown(func) {
stubs.set(goog.asserts, 'ENABLE_ASSERTS', true);
assertContains('Soy template output is unsafe for use as HTML',
assertThrows(func).message);
stubs.set(goog.asserts, 'ENABLE_ASSERTS', false);
assertEquals('zSoyz', func());
}

function testRejectUnsanitizedText() {
assertUnsafeTemplateOutputErrorThrown(function() {
var div = goog.dom.createElement(goog.dom.TagName.DIV);
goog.soy.renderElement(div,
example.unsanitizedTextTemplate);
return div.innerHTML;
});
assertUnsafeTemplateOutputErrorThrown(function() {
// This will render to a text node containing only zSoyz.
var fragment = goog.soy.renderAsFragment(example.unsanitizedTextTemplate);
return fragment.nodeValue;
});
assertUnsafeTemplateOutputErrorThrown(function() {
return goog.soy.renderAsElement(example.unsanitizedTextTemplate).innerHTML;
});
}

function testRejectSanitizedCss() {
assertUnsafeTemplateOutputErrorThrown(function() {
goog.soy.renderAsElement(example.sanitizedCssTemplate);
});
}

function testRejectSanitizedCss() {
assertUnsafeTemplateOutputErrorThrown(function() {
return goog.soy.renderAsElement(
example.templateSpoofingSanitizedContentString).innerHTML;
});
}

function testRejectStringTemplatesWhenModeIsSet() {
stubs.set(goog.soy, 'REQUIRE_STRICT_AUTOESCAPE', true);
assertUnsafeTemplateOutputErrorThrown(function() {
return goog.soy.renderAsElement(example.noDataTemplate).innerHTML;
});
}

function testAcceptSanitizedHtml() {
assertEquals('Hello World', goog.dom.getTextContent(
goog.soy.renderAsElement(example.sanitizedHtmlTemplate)));
}

function testAcceptSanitizedHtmlAttribute() {
assertEquals('Hello World', goog.dom.getTextContent(
goog.soy.renderAsElement(example.sanitizedHtmlAttributeTemplate)));
}

function testAcceptNonObject() {
// Some templates, or things that spoof templates in unit tests, might return
// non-strings in unusual cases.
assertEquals('null', goog.dom.getTextContent(
goog.soy.renderAsElement(goog.functions.constant(null))));
}

</script> </script>
</body> </body>
</html> </html>

0 comments on commit f49c45e

Please sign in to comment.