202 lines
6.6 KiB
JavaScript
202 lines
6.6 KiB
JavaScript
// Copyright 2013 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 Unit tests for goog.html.SafeUrl and its builders.
|
|
*/
|
|
|
|
goog.provide('goog.html.safeUrlTest');
|
|
|
|
goog.require('goog.html.SafeUrl');
|
|
goog.require('goog.i18n.bidi.Dir');
|
|
goog.require('goog.string.Const');
|
|
goog.require('goog.testing.jsunit');
|
|
|
|
goog.setTestOnly('goog.html.safeUrlTest');
|
|
|
|
|
|
|
|
function testSafeUrl() {
|
|
var safeUrl = goog.html.SafeUrl.fromConstant(
|
|
goog.string.Const.from('javascript:trusted();'));
|
|
var extracted = goog.html.SafeUrl.unwrap(safeUrl);
|
|
assertEquals('javascript:trusted();', extracted);
|
|
assertEquals('javascript:trusted();', goog.html.SafeUrl.unwrap(safeUrl));
|
|
assertEquals('SafeUrl{javascript:trusted();}', String(safeUrl));
|
|
|
|
// URLs are always LTR.
|
|
assertEquals(goog.i18n.bidi.Dir.LTR, safeUrl.getDirection());
|
|
|
|
// Interface markers are present.
|
|
assertTrue(safeUrl.implementsGoogStringTypedString);
|
|
assertTrue(safeUrl.implementsGoogI18nBidiDirectionalString);
|
|
}
|
|
|
|
|
|
/** @suppress {checkTypes} */
|
|
function testUnwrap() {
|
|
var evil = {};
|
|
evil.safeUrlValueWithSecurityContract_googHtmlSecurityPrivate_ =
|
|
'<script>evil()</script';
|
|
evil.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
|
|
|
|
var exception = assertThrows(function() {
|
|
goog.html.SafeUrl.unwrap(evil);
|
|
});
|
|
assertTrue(exception.message.indexOf('expected object of type SafeUrl') > 0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Assert that url passes through sanitization unchanged.
|
|
* @param {string|!goog.string.TypedString} url The URL to sanitize.
|
|
*/
|
|
function assertGoodUrl(url) {
|
|
var expected = url;
|
|
if (url.implementsGoogStringTypedString) {
|
|
expected = url.getTypedStringValue();
|
|
}
|
|
var safeUrl = goog.html.SafeUrl.sanitize(url);
|
|
var extracted = goog.html.SafeUrl.unwrap(safeUrl);
|
|
assertEquals(expected, extracted);
|
|
}
|
|
|
|
|
|
/**
|
|
* Assert that url fails sanitization.
|
|
* @param {string|!goog.string.TypedString} url The URL to sanitize.
|
|
*/
|
|
function assertBadUrl(url) {
|
|
assertEquals(
|
|
goog.html.SafeUrl.INNOCUOUS_STRING,
|
|
goog.html.SafeUrl.unwrap(
|
|
goog.html.SafeUrl.sanitize(url)));
|
|
}
|
|
|
|
|
|
function testSafeUrlSanitize_validatesUrl() {
|
|
// Whitelisted schemes.
|
|
assertGoodUrl('http://example.com/');
|
|
assertGoodUrl('https://example.com');
|
|
assertGoodUrl('mailto:foo@example.com');
|
|
// Scheme is case-insensitive
|
|
assertGoodUrl('HTtp://example.com/');
|
|
// Different URL components go through.
|
|
assertGoodUrl('https://example.com/path?foo=bar#baz');
|
|
// Scheme-less URL with authority.
|
|
assertGoodUrl('//example.com/path');
|
|
// Absolute path with no authority.
|
|
assertGoodUrl('/path');
|
|
assertGoodUrl('/path?foo=bar#baz');
|
|
// Relative path.
|
|
assertGoodUrl('path');
|
|
assertGoodUrl('path?foo=bar#baz');
|
|
assertGoodUrl('p//ath');
|
|
assertGoodUrl('p//ath?foo=bar#baz');
|
|
// Restricted characters ('&', ':', \') after [/?#].
|
|
assertGoodUrl('/&');
|
|
assertGoodUrl('?:');
|
|
|
|
// .sanitize() works on program constants.
|
|
assertGoodUrl(goog.string.Const.from('http://example.com/'));
|
|
|
|
// Non-whitelisted schemes.
|
|
assertBadUrl('javascript:evil();');
|
|
assertBadUrl('javascript:evil();//\nhttp://good.com/');
|
|
assertBadUrl('data:blah');
|
|
// Restricted characters before [/?#].
|
|
assertBadUrl('&');
|
|
assertBadUrl(':');
|
|
// '\' is not treated like '/': no restricted characters allowed after it.
|
|
assertBadUrl('\\:');
|
|
// Regex anchored to the left: doesn't match on '/:'.
|
|
assertBadUrl(':/:');
|
|
// Regex multiline not enabled: first line would match but second one
|
|
// wouldn't.
|
|
assertBadUrl('path\n:');
|
|
|
|
// .sanitize() does not exempt values known to be program constants.
|
|
assertBadUrl(goog.string.Const.from('data:blah'));
|
|
}
|
|
|
|
|
|
/**
|
|
* Asserts that goog.html.SafeUrl.unwrap returns the expected string when the
|
|
* SafeUrl has been constructed by passing the given url to
|
|
* goog.html.SafeUrl.sanitize.
|
|
* @param {string} url The string to pass to goog.html.SafeUrl.sanitize.
|
|
* @param {string} expected The string representation that
|
|
* goog.html.SafeUrl.unwrap should return.
|
|
*/
|
|
function assertSanitizeEncodesTo(url, expected) {
|
|
var safeUrl = goog.html.SafeUrl.sanitize(url);
|
|
var actual = goog.html.SafeUrl.unwrap(safeUrl);
|
|
assertEquals(
|
|
'SafeUrl.sanitize().unwrap() doesn\'t return expected ' +
|
|
'percent-encoded string',
|
|
expected,
|
|
actual);
|
|
}
|
|
|
|
|
|
function testSafeUrlSanitize_percentEncodesUrl() {
|
|
// '%' is preserved.
|
|
assertSanitizeEncodesTo('%', '%');
|
|
assertSanitizeEncodesTo('%2F', '%2F');
|
|
|
|
// Unreserved characters, RFC 3986.
|
|
assertSanitizeEncodesTo('aA1-._~', 'aA1-._~');
|
|
|
|
// Reserved characters, RFC 3986. Only '\'', '(' and ')' are encoded.
|
|
assertSanitizeEncodesTo('/:?#[]@!$&\'()*+,;=', '/:?#[]@!$&%27%28%29*+,;=');
|
|
|
|
|
|
// Other ASCII characters, printable and non-printable.
|
|
assertSanitizeEncodesTo('^"\\`\x00\n\r\x7f', '%5E%22%5C%60%00%0A%0D%7F');
|
|
|
|
// Codepoints which UTF-8 encode to 2 bytes.
|
|
assertSanitizeEncodesTo('\u0080\u07ff', '%C2%80%DF%BF');
|
|
|
|
// Highest codepoint which can be UTF-16 encoded using two bytes
|
|
// (one code unit). Highest codepoint in basic multilingual plane and highest
|
|
// that JavaScript can represent using \u.
|
|
assertSanitizeEncodesTo('\uffff', '%EF%BF%BF');
|
|
|
|
// Supplementary plane codepoint which UTF-16 and UTF-8 encode to 4 bytes.
|
|
// Valid surrogate sequence.
|
|
assertSanitizeEncodesTo('\ud800\udc00', '%F0%90%80%80');
|
|
|
|
// Invalid lead/high surrogate.
|
|
assertSanitizeEncodesTo('\udc00', goog.html.SafeUrl.INNOCUOUS_STRING);
|
|
|
|
// Invalid trail/low surrogate.
|
|
assertSanitizeEncodesTo('\ud800\ud800', goog.html.SafeUrl.INNOCUOUS_STRING);
|
|
}
|
|
|
|
|
|
function testSafeUrlSanitize_idempotentForSafeUrlArgument() {
|
|
// This goes through percent-encoding.
|
|
var safeUrl = goog.html.SafeUrl.sanitize('%11"');
|
|
var safeUrl2 = goog.html.SafeUrl.sanitize(safeUrl);
|
|
assertEquals(
|
|
goog.html.SafeUrl.unwrap(safeUrl), goog.html.SafeUrl.unwrap(safeUrl2));
|
|
|
|
// This doesn't match the safe prefix, getting converted into an innocuous
|
|
// string.
|
|
safeUrl = goog.html.SafeUrl.sanitize('disallowed:foo');
|
|
safeUrl2 = goog.html.SafeUrl.sanitize(safeUrl);
|
|
assertEquals(
|
|
goog.html.SafeUrl.unwrap(safeUrl), goog.html.SafeUrl.unwrap(safeUrl2));
|
|
}
|