Socket
Socket
Sign inDemoInstall

secure-handlebars

Package Overview
Dependencies
Maintainers
4
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

secure-handlebars - npm Package Compare versions

Comparing version 0.3.0 to 1.0.0

data/entities.json

31

Gruntfile.js

@@ -10,4 +10,12 @@ /*

pkg: grunt.file.readJSON('package.json'),
execute: {
cssparser: {
options: {
args: ['src/css-parser/css-parser.strict.attr.partial.y', 'src/css-parser/css.strict.l', '--outfile', 'src/css-parser/css-parser.js']
},
src: ['node_modules/jison/lib/cli.js']
},
},
jshint: {
files: ['src/*.js'],
files: [ 'src/*.js' ],
options: {

@@ -45,3 +53,3 @@ scripturl: true,

buildMin: {
src: ['src/polyfill.js', 'dist/<%= pkg.name %>.js'],
src: ['src/polyfills/*.js', 'dist/<%= pkg.name %>.js'],
dest: 'dist/<%= pkg.name %>.min.js'

@@ -76,6 +84,14 @@ }

options: {
coverage:true,
excludes: [
'src/css-parser/css-parser.js',
'src/html-decoder/*.js',
'src/html-decoder/gen/*.js',
'src/html-decoder/polyfills/*.js',
'src/polyfills/browser.js',
'src/polyfills/minimal.js'
],
coverage: true,
check: {
lines: 80,
statements: 80
statements: 80
}

@@ -86,4 +102,4 @@ }

clean: {
all: ['xunit.xml', 'artifacts', 'coverage', 'tests/samples/files/*.precompiled', 'tests/samples/files/*.js', 'node_modules'],
buildResidues: ['xunit.xml', 'artifacts', 'coverage', 'tests/samples/files/*.precompiled', 'tests/samples/files/*.js']
all: ['xunit.xml', 'artifacts', 'coverage', 'node_modules'],
buildResidues: ['xunit.xml', 'artifacts', 'coverage']
}

@@ -98,4 +114,5 @@ });

grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-execute');
grunt.registerTask('test', ['clean:buildResidues', 'jshint', 'dist', 'karma', 'mocha_istanbul']);
grunt.registerTask('test', ['clean:buildResidues', 'jshint', 'execute', 'dist', 'karma', 'mocha_istanbul']);
grunt.registerTask('dist', ['browserify', 'uglify']);

@@ -102,0 +119,0 @@ grunt.registerTask('default', ['test']);

{
"name": "secure-handlebars",
"version": "0.3.0",
"version": "1.0.0",
"licenses": [

@@ -44,5 +44,7 @@ {

"dependencies": {
"context-parser": "^1.0.0",
"debug": "^2.1.2",
"xss-filters": "^1.1.0"
"context-parser": "^1.1.0",
"fs": "0.0.2",
"handlebars": "^3.0.3",
"path": "^0.11.14",
"xss-filters": "^1.2.0"
},

@@ -57,8 +59,9 @@ "devDependencies": {

"grunt-contrib-jshint": "^0.11.0",
"grunt-contrib-uglify": "^0.8.0",
"grunt-karma": "^0.10.1",
"grunt-contrib-uglify": "^0.9.1",
"grunt-execute": "^0.2.2",
"grunt-karma": "^0.11.0",
"grunt-mocha-istanbul": "^2.3.0",
"handlebars": "^3.0.1",
"karma": "^0.12.36",
"karma-mocha": "~0.1.10",
"karma-phantomjs-launcher": "^0.1.4"
"karma-phantomjs-launcher": "^0.2.0"
},

@@ -65,0 +68,0 @@ "main": "./src/secure-handlebars.js",

@@ -5,2 +5,10 @@ SecureHandlebars

[![npm version][npm-badge]][npm]
[![dependency status][dep-badge]][dep-status]
[npm]: https://www.npmjs.org/package/secure-handlebars
[npm-badge]: https://img.shields.io/npm/v/secure-handlebars.svg?style=flat-square
[dep-status]: https://david-dm.org/yahoo/secure-handlebars
[dep-badge]: https://img.shields.io/david/yahoo/secure-handlebars.svg?style=flat-square
## Introduction

@@ -7,0 +15,0 @@ The original [Handlebars](http://handlebarsjs.com/) is overriden to perform the following major steps:

@@ -14,10 +14,18 @@ /*

/* debug facility */
var debug = require('debug')('cph');
/* import the required package */
var ContextParser = require('./strict-context-parser.js'),
configContextParser = {
enableInputPreProcessing: true,
enableCanonicalization: true,
enableIEConditionalComments: true,
enableStateTracking: true
},
handlebarsUtils = require('./handlebars-utils.js'),
stateMachine = require('context-parser').StateMachine;
var cssParserUtils = require('./css-utils.js');
var HtmlEntitiesDecoder = require("./html-decoder/html-decoder.js"),
htmlDecoder = new HtmlEntitiesDecoder();
/////////////////////////////////////////////////////

@@ -37,2 +45,8 @@ //

FILTER_ATTRIBUTE_VALUE_UNQUOTED: 'yavu',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_DOUBLE_QUOTED: 'yced',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_SINGLE_QUOTED: 'yces',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_UNQUOTED: 'yceu',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_UNQUOTED: 'yceuu',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_DOUBLE_QUOTED: 'yceud',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_SINGLE_QUOTED: 'yceus',
FILTER_ENCODE_URI: 'yu',

@@ -102,3 +116,3 @@ FILTER_ENCODE_URI_COMPONENT: 'yuc',

/* context parser for HTML5 parsing */
this.contextParser = new ContextParser(config);
this.contextParser = new ContextParser(configContextParser);
}

@@ -189,2 +203,3 @@

*/
// TODO: using flex syntax to build the AST.
ContextParserHandlebars.prototype.buildAst = function(input, i, sp) {

@@ -394,3 +409,3 @@

var output = '', leftParser, rightParser,
t, msg, exceptionObj, debugString = [];
t, msg, exceptionObj;

@@ -407,3 +422,3 @@ this._charNo = charNo;

output += parser.parsePartial(node.content);
output += parser.contextualize(node.content);

@@ -422,6 +437,3 @@ } else if (node.type === 'escapeexpression' ||

t = this.analyzeAst(node.content, parser, node.startPos);
// cloning states from the branches
parser.state = t.parser.state;
parser.attributeName = t.parser.attributeName;
parser.attributeValue = t.parser.attributeValue;
parser.cloneStates(t.parser);

@@ -464,3 +476,2 @@ output += t.output;

) {
// debug("analyzeAst:["+r.parsers[0].state+"/"+r.parsers[1].state+"]");
msg = "[ERROR] SecureHandlebars: Inconsistent HTML5 state after conditional branches. Please fix your template! ";

@@ -492,6 +503,2 @@ msg += "state:("+leftParser.state+"/"+rightParser.state+"),";

* Handle the Handlebars template. (Handlebars Template Context)
*
* TODO: the function handleTemplate does not need to handle other expressions
* except RAW_EXPRESSION and ESCAPE_EXPRESSION anymore, we can safely remove it
* when the code is stable.
*/

@@ -513,3 +520,2 @@ ContextParserHandlebars.prototype.handleTemplate = function(input, i, stateObj) {

/* _handleRawExpression and no validation need, it is safe guard in buildAst function */
debug("handleTemplate:handlebarsExpressionType:"+handlebarsExpressionType,",i:"+i+",state:"+stateObj.state);
obj = this.consumeExpression(input, i, handlebarsExpressionType, true);

@@ -523,3 +529,2 @@ return;

/* handleEscapeExpression and no validation need, it is safe guard in buildAst function */
debug("handleTemplate:handlebarsExpressionType:"+handlebarsExpressionType,",i:"+i+",state:"+stateObj.state);
obj = this.handleEscapeExpression(input, i, len, stateObj, true);

@@ -558,4 +563,4 @@ return;

tagName = parser.getStartTagName(),
attributeName = parser.attributeName,
attributeValue = parser.attributeValue;
attributeName = parser.getAttributeName(),
attributeValue = parser.getAttributeValue();

@@ -585,3 +590,3 @@ try {

/* we don't support javascript parsing yet */
// TODO: this filtering rule cannot cover all cases.
// TODO: should use yup() instead
if (handlebarsUtils.blacklistProtocol(attributeValue)) {

@@ -601,7 +606,56 @@ throw 'scriptable URI attribute (e.g., after <a href="javascript: )';

} else if (parser.getAttributeNameType() === ContextParser.ATTRTYPE_CSS) { // CSS
/* we don't support css parser yet
* we use filter.FILTER_NOT_HANDLE to warn the developers for unsafe output expression,
* and we fall back to default Handlebars escaping filter. IT IS UNSAFE.
*/
throw 'CSS style attribute';
var r;
try {
attributeValue = htmlDecoder.decode(attributeValue);
r = cssParserUtils.parseStyleAttributeValue(attributeValue);
} catch (e) {
throw 'Unsafe output expression @ attribute style CSS context (Parsing error OR expression position not supported!)';
}
switch(r.code) {
case cssParserUtils.STYLE_ATTRIBUTE_URL_UNQUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_UNQUOTED);
isFullUri = true;
break;
case cssParserUtils.STYLE_ATTRIBUTE_URL_SINGLE_QUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_SINGLE_QUOTED);
isFullUri = true;
break;
case cssParserUtils.STYLE_ATTRIBUTE_URL_DOUBLE_QUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_DOUBLE_QUOTED);
isFullUri = true;
break;
case cssParserUtils.STYLE_ATTRIBUTE_UNQUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_UNQUOTED);
break;
case cssParserUtils.STYLE_ATTRIBUTE_SINGLE_QUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_SINGLE_QUOTED);
break;
case cssParserUtils.STYLE_ATTRIBUTE_DOUBLE_QUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_DOUBLE_QUOTED);
break;
case cssParserUtils.STYLE_ATTRIBUTE_ERROR:
throw 'Unsafe output expression @ attribute style CSS context (Parsing error OR expression position not supported!)';
}
/* add the attribute value filter */
switch(state) {
case stateMachine.State.STATE_ATTRIBUTE_VALUE_DOUBLE_QUOTED:
f = filter.FILTER_ATTRIBUTE_VALUE_DOUBLE_QUOTED;
break;
case stateMachine.State.STATE_ATTRIBUTE_VALUE_SINGLE_QUOTED:
f = filter.FILTER_ATTRIBUTE_VALUE_SINGLE_QUOTED;
break;
default: // stateMachine.State.STATE_ATTRIBUTE_VALUE_UNQUOTED:
f = filter.FILTER_ATTRIBUTE_VALUE_UNQUOTED;
break;
}
filters.push(f);
/* add blacklist filters at the end of filtering chain */
if (isFullUri) {
/* blacklist the URI scheme for full uri */
filters.push(filter.FILTER_URI_SCHEME_BLACKLIST);
}
return filters;
} else if (parser.getAttributeNameType() === ContextParser.ATTRTYPE_SCRIPTABLE) { // JS

@@ -654,7 +708,9 @@ /* we don't support js parser yet

// the following will be caught by parser.isScriptableTag() anyway
// case stateMachine.State.STATE_SCRIPT_DATA: // 6
// throw 'inside <script> tag (i.e., SCRIPT_DATA state)';
// TODO: need tagname tracing in Context Parser such that we can have
// ability to capture the case of putting output expression within dangerous tag.
// like svg etc.
// the following will be caught by handlebarsUtils.isScriptableTag(tagName) anyway
case stateMachine.State.STATE_SCRIPT_DATA: // 6
throw 'inside <script> tag (i.e., SCRIPT_DATA state)';
// should not fall into the following states

@@ -676,3 +732,3 @@ case stateMachine.State.STATE_BEFORE_ATTRIBUTE_VALUE: // 37

// or an application-specific whitelisted url check (e.g., <script src=""> with yubl-yavu-yufull is not enough)
errorMessage += parser.isScriptableTag() ? 'scriptable <' + tagName + '> tag' : exception;
errorMessage += handlebarsUtils.isScriptableTag(tagName) ? 'scriptable <' + tagName + '> tag' : exception;

@@ -679,0 +735,0 @@ exceptionObj = new ContextParserHandlebarsException(errorMessage, this._lineNo, this._charNo);

@@ -220,4 +220,26 @@ /*

// <iframe srcdoc=""> is a scriptable attribute too
// Reference: https://html.spec.whatwg.org/multipage/embedded-content.html#attr-iframe-srcdoc
HandlebarsUtils.scriptableTags = {
script:1,style:1,
svg:1,xml:1,math:1,
applet:1,object:1,embed:1,link:1,
scriptlet:1 // IE-specific
};
/**
* @function HandlebarsUtils#isScriptableTag
*
* @returns {boolean} true if the current tag can possibly incur script either through configuring its attribute name or inner HTML
*
* @description
* Check if the current tag can possibly incur script either through configuring its attribute name or inner HTML
*
*/
HandlebarsUtils.isScriptableTag = function(tag) {
return HandlebarsUtils.scriptableTags[tag] === 1;
};
module.exports = HandlebarsUtils;
})();

@@ -14,675 +14,62 @@ /*

/* import the html context parser */
var contextParser = require('context-parser'),
stateMachine = contextParser.StateMachine,
htmlState = stateMachine.State,
htmlParser = contextParser.FastParser;
/* import the required package */
var ContextParser = require('context-parser').Parser;
// Perform input stream preprocessing
// Reference: https://html.spec.whatwg.org/multipage/syntax.html#preprocessing-the-input-stream
function InputPreProcessing (state, i) {
var input = this.input,
chr = input[i],
nextChr = input[i+1];
/////////////////////////////////////////////////////
//
// @module ContextParser
//
/////////////////////////////////////////////////////
// equivalent to inputStr.replace(/\r\n?/g, '\n')
if (chr === '\r') {
if (nextChr === '\n') {
input.splice(i, 1);
this.inputLen--;
} else {
input[i] = '\n';
}
}
// the following are control characters or permanently undefined Unicode characters (noncharacters), resulting in parse errors
// \uFFFD replacement is not required by the specification, we consider \uFFFD character as an inert character
else if ((chr >= '\x01' && chr <= '\x08') ||
(chr >= '\x0E' && chr <= '\x1F') ||
(chr >= '\x7F' && chr <= '\x9F') ||
(chr >= '\uFDD0' && chr <= '\uFDEF') ||
chr === '\x0B' || chr === '\uFFFE' || chr === '\uFFFF') {
input[i] = '\uFFFD';
}
// U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF, U+3FFFE, U+3FFFF,
// U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE, U+6FFFF,
// U+7FFFE, U+7FFFF, U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF,
// U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF, U+CFFFE, U+CFFFF,
// U+DFFFE, U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF,
// U+10FFFE, and U+10FFFF
else if ((nextChr === '\uDFFE' || nextChr === '\uDFFF') &&
( chr === '\uD83F' || chr === '\uD87F' || chr === '\uD8BF' || chr === '\uD8FF' ||
chr === '\uD93F' || chr === '\uD97F' || chr === '\uD9BF' || chr === '\uD9FF' ||
chr === '\uDA3F' || chr === '\uDA7F' || chr === '\uDABF' || chr === '\uDAFF' ||
chr === '\uDB3F' || chr === '\uDB7F' || chr === '\uDBBF' || chr === '\uDBFF')) {
input[i] = input[i+1] = '\uFFFD';
}
}
function ConvertBogusCommentToComment(i) {
// convert !--. i.e., from <* to <!--*
this.input.splice(i, 0, '!', '-', '-');
this.inputLen += 3;
// convert the next > to -->
this.on('preCanonicalize', PreCanonicalizeConvertBogusCommentEndTag);
}
function PreCanonicalizeConvertBogusCommentEndTag(state, i, endsWithEOF) {
if (this.input[i] === '>') {
// remove itself from the listener list
this.off('preCanonicalize', PreCanonicalizeConvertBogusCommentEndTag);
// convert [>] to [-]->
this.input.splice(i, 0, '-', '-');
this.inputLen += 2;
this.emit('bogusCommentCoverted', state, i, endsWithEOF);
}
}
// those doctype states (52-67) are initially treated as bogus comment state, but they are further converted to comment state
// Canonicalize() will create no more bogus comment state except the fake (context-parser treats <!doctype as bogus) one hardcoded as <!doctype html> that has no NULL inside
var statesRequiringNullReplacement = [
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
/*0*/ 0, 0, 0, 1, 0, 1, 1, 1, 0, 0,
/*1*/ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*2*/ 0, 0, 1, 1, 1, 0, 0, 0, 0, 1,
/*3*/ 1, 1, 0, 0, 1, 1, 1, 1, 1, 1,
/*4*/ 1, 0, 0, 0, 1, 0, 1, 1, 1, 1,
/*5*/ 1, 1
];
// \uFFFD replacement is not required by the spec for DATA state
statesRequiringNullReplacement[htmlState.STATE_DATA] = 1;
function Canonicalize(state, i, endsWithEOF) {
this.emit('preCanonicalize', state, i, endsWithEOF);
var reCanonicalizeNeeded = true,
input = this.input,
chr = input[i], nextChr = input[i+1],
potentialState = this._getNextState(state, i, endsWithEOF),
nextPotentialState = this._getNextState(potentialState, i + 1, endsWithEOF);
// console.log(i, state, potentialState, nextPotentialState, input.slice(i).join(''));
// batch replacement of NULL with \uFFFD would violate the spec
// - for example, NULL is untouched in CDATA section state
if (chr === '\x00' && statesRequiringNullReplacement[state]) {
input[i] = '\uFFFD';
}
// encode < into &lt; for [<]* (* is non-alpha) in STATE_DATA, [<]% and [<]! in STATE_RCDATA and STATE_RAWTEXT
else if ((potentialState === htmlState.STATE_TAG_OPEN && nextPotentialState === htmlState.STATE_DATA) || // [<]*, where * is non-alpha
((state === htmlState.STATE_RCDATA || state === htmlState.STATE_RAWTEXT) && // in STATE_RCDATA and STATE_RAWTEXT
chr === '<' && (nextChr === '%' || nextChr === '!'))) { // [<]% or [<]!
// [<]*, [<]%, [<]!
input.splice(i, 1, '&', 'l', 't', ';');
this.inputLen += 3;
}
// enforce <!doctype html>
// + convert bogus comment or unknown doctype to the standard html comment
else if (potentialState === htmlState.STATE_MARKUP_DECLARATION_OPEN) { // <[!]***
reCanonicalizeNeeded = false;
// context-parser treats the doctype and [CDATA[ as resulting into STATE_BOGUS_COMMENT
// so, we need our algorithm here to extract and check the next 7 characters
var commentKey = input.slice(i + 1, i + 8).join('');
// enforce <!doctype html>
if (commentKey.toLowerCase() === 'doctype') { // <![d]octype
// extract 6 chars immediately after <![d]octype and check if it's equal to ' html>'
if (input.slice(i + 8, i + 14).join('').toLowerCase() !== ' html>') {
// replace <[!]doctype xxxx> with <[!]--!doctype xxxx--><doctype html>
ConvertBogusCommentToComment.call(this, i);
this.once('bogusCommentCoverted', function (state, i) {
[].splice.apply(this.input, [i + 3, 0].concat('<!doctype html>'.split('')));
this.inputLen += 15;
});
reCanonicalizeNeeded = true;
}
}
// do not touch <![CDATA[ and <[!]--
else if (commentKey === '[CDATA[' ||
(nextChr === '-' && input[i+2] === '-')) {
// noop
}
// ends up in bogus comment
else {
// replace <[!]*** with <[!]--***
// will replace the next > to -->
ConvertBogusCommentToComment.call(this, i);
reCanonicalizeNeeded = true;
}
}
// convert bogus comment to the standard html comment
else if ((state === htmlState.STATE_TAG_OPEN &&
potentialState === htmlState.STATE_BOGUS_COMMENT) || // <[?] only from STATE_TAG_OPEN
(potentialState === htmlState.STATE_END_TAG_OPEN && // <[/]* or <[/]> from STATE_END_TAG_OPEN
nextPotentialState !== htmlState.STATE_TAG_NAME &&
nextPotentialState !== -1)) { // TODO: double check if there're any other cases requiring -1 check
// replace <? and </* respectively with <!--? and <!--/*
// will replace the next > to -->
ConvertBogusCommentToComment.call(this, i);
}
// remove the unnecessary SOLIDUS
else if (potentialState === htmlState.STATE_SELF_CLOSING_START_TAG && // <***[/]*
nextPotentialState === htmlState.STATE_BEFORE_ATTRIBUTE_NAME) { // input[i+1] is ANYTHING_ELSE (i.e., not EOF nor >)
// if ([htmlState.STATE_TAG_NAME, // <a[/]* replaced with <a[ ]*
// /* following is unknown to CP
// htmlState.STATE_RCDATA_END_TAG_NAME,
// htmlState.STATE_RAWTEXT_END_TAG_NAME,
// htmlState.STATE_SCRIPT_DATA_END_TAG_NAME,
// htmlState.STATE_SCRIPT_DATA_ESCAPED_END_TAG_NAME,
// */
// htmlState.STATE_BEFORE_ATTRIBUTE_NAME, // <a [/]* replaced with <a [ ]*
// htmlState.STATE_AFTER_ATTRIBUTE_VALUE_QUOTED].indexOf(state) !== -1) // <a abc=""[/]* replaced with <a abc=""[ ]*
input[i] = ' ';
// given input[i] was '/', nextPotentialState was htmlState.STATE_BEFORE_ATTRIBUTE_NAME
// given input[i] is now ' ', nextPotentialState becomes STATE_BEFORE_ATTRIBUTE_NAME if current state is STATE_ATTRIBUTE_NAME or STATE_AFTER_ATTRIBUTE_NAME
// to preserve state, remove future EQUAL SIGNs (=)s to force STATE_AFTER_ATTRIBUTE_NAME behave as if it is STATE_BEFORE_ATTRIBUTE_NAME
// this is okay since EQUAL SIGNs (=)s will be stripped anyway in the STATE_BEFORE_ATTRIBUTE_NAME cleanup handling
if (state === htmlState.STATE_ATTRIBUTE_NAME || // <a abc[/]=abc replaced with <a abc[ ]*
state === htmlState.STATE_AFTER_ATTRIBUTE_NAME) { // <a abc [/]=abc replaced with <a abc [ ]*
for (var j = i + 1; j < this.inputLen && input[j] === '='; j++) {
input.splice(j, 1);
this.inputLen--;
}
}
}
// remove unnecessary equal signs, hence <input checked[=]> become <input checked[>], or <input checked [=]> become <input checked [>]
else if (potentialState === htmlState.STATE_BEFORE_ATTRIBUTE_VALUE && // only from STATE_ATTRIBUTE_NAME or STATE_AFTER_ATTRIBUTE_NAME
nextPotentialState === htmlState.STATE_DATA) { // <a abc[=]> or <a abc [=]>
input.splice(i, 1);
this.inputLen--;
}
// insert a space for <a abc="***["]* or <a abc='***[']* after quoted attribute value (i.e., <a abc="***["] * or <a abc='***['] *)
else if (potentialState === htmlState.STATE_AFTER_ATTRIBUTE_VALUE_QUOTED && // <a abc=""[*] where * is not SPACE (\t,\n,\f,' ')
nextPotentialState === htmlState.STATE_BEFORE_ATTRIBUTE_NAME &&
this._getSymbol(i + 1) !== stateMachine.Symbol.SPACE) {
input.splice(i + 1, 0, ' ');
this.inputLen++;
}
// else here means no special pattern was found requiring rewriting
else {
reCanonicalizeNeeded = false;
}
// remove " ' < = from being treated as part of attribute name (not as the spec recommends though)
switch (potentialState) {
case htmlState.STATE_BEFORE_ATTRIBUTE_NAME: // remove ambigious symbols in <a [*]href where * is ", ', <, or =
if (nextChr === "=") {
input.splice(i + 1, 1);
this.inputLen--;
reCanonicalizeNeeded = true;
break;
}
/* falls through */
case htmlState.STATE_ATTRIBUTE_NAME: // remove ambigious symbols in <a href[*] where * is ", ', or <
case htmlState.STATE_AFTER_ATTRIBUTE_NAME: // remove ambigious symbols in <a href [*] where * is ", ', or <
if (nextChr === '"' || nextChr === "'" || nextChr === '<') {
input.splice(i + 1, 1);
this.inputLen--;
reCanonicalizeNeeded = true;
}
break;
}
if (reCanonicalizeNeeded) {
return Canonicalize.call(this, state, i, endsWithEOF);
}
switch (state) {
// escape " ' < = ` to avoid raising parse errors for unquoted value
case htmlState.STATE_ATTRIBUTE_VALUE_UNQUOTED:
if (chr === '"') {
input.splice(i, 1, '&', 'q', 'u', 'o', 't', ';');
this.inputLen += 5;
break;
} else if (chr === "'") {
input.splice(i, 1, '&', '#', '3', '9', ';');
this.inputLen += 4;
break;
}
/* falls through */
case htmlState.STATE_BEFORE_ATTRIBUTE_VALUE: // treat < = ` as if they are in STATE_ATTRIBUTE_VALUE_UNQUOTED
if (chr === '<') {
input.splice(i, 1, '&', 'l', 't', ';');
this.inputLen += 3;
} else if (chr === '=') {
input.splice(i, 1, '&', '#', '6', '1', ';');
this.inputLen += 4;
} else if (chr === '`') {
input.splice(i, 1, '&', '#', '9', '6', ';');
this.inputLen += 4;
}
break;
// add hyphens to complete <!----> to avoid raising parsing errors
// replace <!--[>] with <!--[-]->
case htmlState.STATE_COMMENT_START:
if (chr === '>') { // <!--[>]
input.splice(i, 0, '-', '-');
this.inputLen += 2;
// reCanonicalizeNeeded = true; // not need due to no where to treat its potential states
}
break;
// replace <!---[>] with <!---[-]>
case htmlState.STATE_COMMENT_START_DASH:
if (chr === '>') { // <!---[>]
input.splice(i, 0, '-');
this.inputLen++;
// reCanonicalizeNeeded = true; // not need due to no where to treat its potential states
}
break;
// replace --[!]> with --[>]
case htmlState.STATE_COMMENT_END:
if (chr === '!' && nextChr === '>') {
input.splice(i, 1);
this.inputLen--;
// reCanonicalizeNeeded = true; // not need due to no where to treat its potential states
}
// if (chr === '-'), ignored this parse error. TODO: consider stripping n-2 hyphens for ------>
break;
}
if (reCanonicalizeNeeded) {
return Canonicalize.call(this, state, i, endsWithEOF);
}
}
// remove IE conditional comments
function DisableIEConditionalComments(state, i){
var input = this.input;
if (state === htmlState.STATE_COMMENT && input[i] === ']' && input[i+1] === '>') {
input.splice(i, 0, ' ');
this.inputLen++;
}
}
/**
* @module StrictContextParser
*/
function StrictContextParser(config, listeners) {
var self = this, k;
// super
htmlParser.apply(self, arguments);
// config
config || (config = {});
self.listeners = {};
// deep copy the provided listeners, if any
if (typeof listeners === 'object') {
for (k in listeners) {
self.listeners[k] = listeners[k].slice();
}
}
// initialize default listeners, of which the order of registration matters
else {
// run through the input stream with input pre-processing
!config.disableInputPreProcessing && this.on('preWalk', InputPreProcessing);
// fix parse errors before they're encountered in walk()
!config.disableCanonicalization && this.on('preWalk', Canonicalize).on('reWalk', Canonicalize);
// disable IE conditional comments
!config.disableIEConditionalComments && this.on('preWalk', DisableIEConditionalComments);
// TODO: rewrite IE <comment> tags
// TODO: When a start tag token is emitted with its self-closing flag set, if the flag is not acknowledged when it is processed by the tree construction stage, that is a parse error.
// TODO: When an end tag token is emitted with attributes, that is a parse error.
// TODO: When an end tag token is emitted with its self-closing flag set, that is a parse error.
// for bookkeeping the processed inputs and states
if (config.enableStateTracking) {
this.states = [this.state];
this.buffer = [];
this.on('postWalk', function (lastState, state, i, endsWithEOF) {
this.buffer.push(this.input[i]);
this.states.push(state);
}).on('reWalk', this.setCurrentState);
}
}
// deep copy the config to this.config
this.config = {};
for (k in config) {
this.config[k] = config[k];
}
}
/* inherit contextParser.FastParser */
StrictContextParser.prototype = Object.create(htmlParser.prototype);
StrictContextParser.prototype.constructor = StrictContextParser;
/**
* @function StrictContextParser._getSymbol
* @param {integer} i - the index of input stream
*
* @description
* Get the html symbol mapping for the character located in the given index of input stream
*/
StrictContextParser.prototype._getSymbol = function (i) {
return i < this.inputLen ? this.lookupChar(this.input[i]) : -1;
};
/**
* @function StrictContextParser._getNextState
* @param {integer} state - the current state
* @param {integer} i - the index of input stream
* @returns {integer} the potential state about to transition into, given the current state and an index of input stream
*
* @description
* Get the potential html state about to transition into
*/
StrictContextParser.prototype._getNextState = function (state, i, endsWithEOF) {
return i < this.inputLen ? stateMachine.lookupStateFromSymbol[this._getSymbol(i)][state] : -1;
};
/**
* @function StrictContextParser.fork
* @returns {object} a new parser with all internal states inherited
*
* @description
* create a new parser with all internal states inherited
*/
StrictContextParser.prototype.fork = function() {
var parser = new this.constructor(this.config, this.listeners);
parser.state = this.state;
parser.tagNames = this.tagNames.slice();
parser.tagNameIdx = this.tagNameIdx;
parser.attributeName = this.attributeName;
parser.attributeValue = this.attributeValue;
if (this.config.enableStateTracking) {
parser.buffer = this.buffer.slice();
parser.states = this.states.slice();
}
return parser;
};
/**
* @function StrictContextParser#on
*
* @param {string} eventType - the event type (e.g., preWalk, reWalk, postWalk, ...)
* @param {function} listener - the event listener
* @returns this
*
* @description
* <p>register the given event listener to the given eventType</p>
*
*/
StrictContextParser.prototype.on = function(eventType, listener) {
var self = this, listeners = self.listeners[eventType];
if (listener) {
if (listeners) {
listeners.push(listener);
} else {
self.listeners[eventType] = [listener];
}
}
return self;
};
/**
* @function StrictContextParser#once
*
* @param {string} eventType - the event type (e.g., preWalk, reWalk, postWalk, ...)
* @param {function} listener - the event listener
* @returns this
*
* @description
* <p>register the given event listener to the given eventType, for which it will be fired only once</p>
*
*/
StrictContextParser.prototype.once = function(eventType, listener) {
var self = this, onceListener;
if (listener) {
onceListener = function () {
self.off(eventType, onceListener);
listener.apply(self, arguments);
};
return this.on(eventType, onceListener);
}
return self;
};
/**
* @function StrictContextParser#off
*
* @param {string} eventType - the event type (e.g., preWalk, reWalk, postWalk, ...)
* @param {function} listener - the event listener
* @returns this
*
* @description
* <p>remove the listener from being fired when the eventType happen</p>
*
*/
StrictContextParser.prototype.off = function (eventType, listener) {
if (listener) {
var i, len, listeners = this.listeners[eventType];
if (listeners) {
for (i = 0; listeners[i]; i++) {
if (listeners[i] === listener) {
listeners.splice(i, 1);
break;
}
}
}
}
return this;
};
/**
* @function StrictContextParser#emit
*
* @param {string} eventType - the event type (e.g., preWalk, reWalk, postWalk, ...)
* @returns this
*
* @description
* <p>fire those listeners correspoding to the given eventType</p>
*
*/
StrictContextParser.prototype.emit = function (eventType) {
var self = this,
listeners = self.listeners[eventType],
i, args, listener;
if (listeners) {
args = [].slice.call(arguments, 1);
for (i = 0; (listener = listeners[i]); i++) {
listener.apply(self, args);
}
}
return self;
};
/**
* @function StrictContextParser#parsePartial
*
* @param {string} input - The HTML fragment
* @returns {string} the inputs with parse errors and browser-inconsistent characters automatically corrected
*
* @description
* <p>Perform HTML fixer before the contextual analysis</p>
*
*/
StrictContextParser.prototype.parsePartial = function(input, endsWithEOF) {
var self = this;
self.input = input.split('');
self.inputLen = self.input.length;
for (var i = 0, lastState; i < self.inputLen; i++) {
lastState = self.state;
// TODO: endsWithEOF handling
self.emit('preWalk', lastState, i, endsWithEOF);
self.walk(i, self.input, endsWithEOF);
self.emit('postWalk', lastState, self.state, i, endsWithEOF);
}
return (self.output = self.input.join(''));
};
// the only difference from the original walk is to use the this.emit('reWalk') interface
StrictContextParser.prototype.walk = function(i, input, endsWithEOF) {
var ch = input[i],
symbol = this.lookupChar(ch),
extraLogic = stateMachine.lookupAltLogicFromSymbol[symbol][this.state],
reconsume = stateMachine.lookupReconsumeFromSymbol[symbol][this.state];
/* Set state based on the current head pointer symbol */
this.state = stateMachine.lookupStateFromSymbol[symbol][this.state];
/* See if there is any extra logic required for this state transition */
switch (extraLogic) {
case 1: this.createStartTag(ch); break;
case 2: this.createEndTag(ch); break;
case 3: this.appendTagName(ch); break;
case 4: this.resetEndTag(ch); break;
case 6: /* match end tag token with start tag token's tag name */
if(this.tagNames[0] === this.tagNames[1]) {
reconsume = 0; /* see 12.2.4.13 - switch state for the following case, otherwise, reconsume. */
this.matchEndTagWithStartTag(symbol);
}
break;
case 8: this.matchEscapedScriptTag(ch); break;
case 11: this.processTagName(ch); break;
case 12: this.createAttributeNameAndValueTag(ch); break;
case 13: this.appendAttributeNameTag(ch); break;
case 14: this.appendAttributeValueTag(ch); break;
}
if (reconsume) { /* reconsume the character */
this.emit('reWalk', this.state, i, endsWithEOF);
// if( this.states) {
// // This is error prone. May need to change the way we walk the stream to avoid this.
// this.states[i] = this.state;
// }
return this.walk(i, input);
}
return this;
};
/**
* @function StrictContextParser#setCurrentState
*
* @param {integer} state - The state of HTML5 page.
*
* @description
* Set the current state of the HTML5 Context Parser.
*
*/
StrictContextParser.prototype.setCurrentState = function(state) {
this.state = state;
if (this.states) {
this.states.pop();
this.states.push(state);
}
return this;
};
/**
* @function StrictContextParser#getCurrentState
*
* @returns {integer} The last state of the HTML5 Context Parser.
*
* @description
* Get the last state of HTML5 Context Parser.
*
*/
StrictContextParser.prototype.getCurrentState = function() {
return this.state;
};
// <iframe srcdoc=""> is a scriptable attribute too
// Reference: https://html.spec.whatwg.org/multipage/embedded-content.html#attr-iframe-srcdoc
var scriptableTags = {
script:1,style:1,
svg:1,xml:1,math:1,
applet:1,object:1,embed:1,link:1,
scriptlet:1 // IE-specific
};
/**
* @function StrictContextParser#isScriptableTag
*
* @returns {boolean} true if the current tag can possibly incur script either through configuring its attribute name or inner HTML
*
* @description
* Check if the current tag can possibly incur script either through configuring its attribute name or inner HTML
*
*/
StrictContextParser.prototype.isScriptableTag = function() {
return scriptableTags[this.tagNames[0]] === 1;
};
// Reference: http://www.w3.org/TR/html-markup/elements.html
StrictContextParser.ATTRTYPE_URI = 1,
StrictContextParser.ATTRTYPE_CSS = 2,
StrictContextParser.ATTRTYPE_SCRIPTABLE = 3,
StrictContextParser.ATTRTYPE_MIME = 4,
StrictContextParser.ATTRTYPE_GENERAL = undefined;
ContextParser.ATTRTYPE_URI = 1,
ContextParser.ATTRTYPE_CSS = 2,
ContextParser.ATTRTYPE_SCRIPTABLE = 3,
ContextParser.ATTRTYPE_MIME = 4,
ContextParser.ATTRTYPE_GENERAL = undefined;
var attributeNamesType = {
ContextParser.attributeNamesType = {
// we generally do not differentiate whether these attribtues are tag specific during matching for simplicity
'href' :StrictContextParser.ATTRTYPE_URI, // for a, link, img, area, iframe, frame, video, object, embed ...
'src' :StrictContextParser.ATTRTYPE_URI,
'background' :StrictContextParser.ATTRTYPE_URI, // for body, table, tbody, tr, td, th, etc? (obsolete)
'action' :StrictContextParser.ATTRTYPE_URI, // for form, input, button
'formaction' :StrictContextParser.ATTRTYPE_URI,
'cite' :StrictContextParser.ATTRTYPE_URI, // for blockquote, del, ins, q
'poster' :StrictContextParser.ATTRTYPE_URI, // for img, object, video, source
'usemap' :StrictContextParser.ATTRTYPE_URI, // for image
'longdesc' :StrictContextParser.ATTRTYPE_URI,
'folder' :StrictContextParser.ATTRTYPE_URI, // for a
'manifest' :StrictContextParser.ATTRTYPE_URI, // for html
'classid' :StrictContextParser.ATTRTYPE_URI, // for object
'codebase' :StrictContextParser.ATTRTYPE_URI, // for object, applet
'icon' :StrictContextParser.ATTRTYPE_URI, // for command
'profile' :StrictContextParser.ATTRTYPE_URI, // for head
'href' :ContextParser.ATTRTYPE_URI, // for a, link, img, area, iframe, frame, video, object, embed ...
'src' :ContextParser.ATTRTYPE_URI,
'background' :ContextParser.ATTRTYPE_URI, // for body, table, tbody, tr, td, th, etc? (obsolete)
'action' :ContextParser.ATTRTYPE_URI, // for form, input, button
'formaction' :ContextParser.ATTRTYPE_URI,
'cite' :ContextParser.ATTRTYPE_URI, // for blockquote, del, ins, q
'poster' :ContextParser.ATTRTYPE_URI, // for img, object, video, source
'usemap' :ContextParser.ATTRTYPE_URI, // for image
'longdesc' :ContextParser.ATTRTYPE_URI,
'folder' :ContextParser.ATTRTYPE_URI, // for a
'manifest' :ContextParser.ATTRTYPE_URI, // for html
'classid' :ContextParser.ATTRTYPE_URI, // for object
'codebase' :ContextParser.ATTRTYPE_URI, // for object, applet
'icon' :ContextParser.ATTRTYPE_URI, // for command
'profile' :ContextParser.ATTRTYPE_URI, // for head
/* TODO: we allow content before we implement the stack in CP for tracking attributeName
'content' :StrictContextParser.ATTRTYPE_URI, // for meta http-equiv=refresh
'content' :ContextParser.ATTRTYPE_URI, // for meta http-equiv=refresh
*/
// http://www.w3.org/TR/xmlbase/#syntax
'xmlns' :StrictContextParser.ATTRTYPE_URI, // for svg, etc?
'xml:base' :StrictContextParser.ATTRTYPE_URI,
'xmlns:xlink':StrictContextParser.ATTRTYPE_URI,
'xlink:href' :StrictContextParser.ATTRTYPE_URI, // for xml-related
'xmlns' :ContextParser.ATTRTYPE_URI, // for svg, etc?
'xml:base' :ContextParser.ATTRTYPE_URI,
'xmlns:xlink':ContextParser.ATTRTYPE_URI,
'xlink:href' :ContextParser.ATTRTYPE_URI, // for xml-related
// srcdoc is the STRING type, not URI
'srcdoc' :StrictContextParser.ATTRTYPE_URI, // for iframe
'srcdoc' :ContextParser.ATTRTYPE_URI, // for iframe
'style' :StrictContextParser.ATTRTYPE_CSS, // for global attributes list
'style' :ContextParser.ATTRTYPE_CSS, // for global attributes list
// pattern matching, handling it within the function getAttributeNameType
// 'on*' :StrictContextParser.ATTRTYPE_SCRIPTABLE,
// 'on*' :ContextParser.ATTRTYPE_SCRIPTABLE,
'type' :StrictContextParser.ATTRTYPE_MIME, // TODO: any potential attack of the MIME type?
'type' :ContextParser.ATTRTYPE_MIME, // TODO: any potential attack of the MIME type?
'data' :{'object' :StrictContextParser.ATTRTYPE_URI},
'rel' :{'link' :StrictContextParser.ATTRTYPE_URI},
'value' :{'param' :StrictContextParser.ATTRTYPE_URI},
'data' :{'object' :ContextParser.ATTRTYPE_URI},
'rel' :{'link' :ContextParser.ATTRTYPE_URI},
'value' :{'param' :ContextParser.ATTRTYPE_URI},
};
/**
* @function StrictContextParser#getAttributeNameType
* @function ContextParser#getAttributeNameType
*

@@ -695,17 +82,17 @@ * @returns {integer} the attribute type defined for different handling

*/
StrictContextParser.prototype.getAttributeNameType = function() {
if (this.attributeName[0] === 'o' && this.attributeName[1] === 'n') { /* assuming it is from Strict Context Parser.
ContextParser.prototype.getAttributeNameType = function() {
if (this.attrName[0] === 'o' && this.attrName[1] === 'n') { /* assuming it is from Strict Context Parser.
and o{{placeholder}}n* can bypass the check.
anyway, we are good to throw error in atttribute name state.
note: CP has lowerCase the attributeName */
return StrictContextParser.ATTRTYPE_SCRIPTABLE;
return ContextParser.ATTRTYPE_SCRIPTABLE;
} else {
// TODO: support compound uri context at <meta http-equiv="refresh" content="seconds; url">, <img srcset="url 1.5x, url 2x">
// return StrictContextParser.ATTRTYPE_GENERAL for case without special handling
// return ContextParser.ATTRTYPE_GENERAL for case without special handling
// here, attrTags === [integer] is a tag agnostic matching
// while, attrTags[tagName] === [integer] matches only those attribute of the given tagName
// while, attrTags[tags] === [integer] matches only those attribute of the given tagName
var attrTags = attributeNamesType[this.attributeName];
return typeof attrTags === 'object'? attrTags[this.tagNames[0]] : attrTags;
var attrTags = ContextParser.attributeNamesType[this.attrName];
return typeof attrTags === 'object'? attrTags[this.tags[0]] : attrTags;
}

@@ -715,115 +102,19 @@ };

/**
* ==================
* the following legacy function is maintained for backward compatibility with the contextParser.Parser
* ==================
*/
/**
* @function StrictContextParser#setCurrentState
* @function ContextParser#cloneStates
*
* @param {integer} state - The state of HTML5 page.
* @params {parser} the Context Parser for copying states.
*
* @description
* Set the current state of the HTML5 Context Parser.
* Copy the required states for state comparison in the conditional branching templates.
*
*/
// StrictContextParser.prototype.setCurrentState = function(state) {
// this.state = state;
// };
/**
* @function StrictContextParser#getStates
*
* @returns {Array} An array of states.
*
* @description
* Get the states of the HTML5 page
*
*/
StrictContextParser.prototype.getStates = function() {
return this.states;
ContextParser.prototype.cloneStates = function(parser) {
this.state = parser.getLastState();
this.attrName = parser.getAttributeName();
this.attributeValue = parser.getAttributeValue();
};
/**
* @function StrictContextParser#setInitState
*
* @param {integer} state - The initial state of the HTML5 Context Parser.
*
* @description
* Set the init state of HTML5 Context Parser.
*
*/
StrictContextParser.prototype.setInitState = function(state) {
this.states && (this.states[0] = state);
};
/**
* @function StrictContextParser#getInitState
*
* @returns {integer} The initial state of the HTML5 Context Parser.
*
* @description
* Get the init state of HTML5 Context Parser.
*
*/
StrictContextParser.prototype.getInitState = function() {
return this.states && this.states[0];
};
/**
* @function StrictContextParser#getLastState
*
* @returns {integer} The last state of the HTML5 Context Parser.
*
* @description
* Get the last state of HTML5 Context Parser.
*
*/
StrictContextParser.prototype.getLastState = function() {
// * undefined if length = 0
return this.states ? this.states[ this.states.length - 1 ] : this.state;
};
/**
* @function StrictContextParser#getAttributeName
*
* @returns {string} The current handling attribute name.
*
* @description
* Get the current handling attribute name of HTML tag.
*
*/
StrictContextParser.prototype.getAttributeName = function() {
return this.attributeName;
};
/**
* @function StrictContextParser#getAttributeValue
*
* @returns {string} The current handling attribute name's value.
*
* @description
* Get the current handling attribute name's value of HTML tag.
*
*/
StrictContextParser.prototype.getAttributeValue = function() {
return this.attributeValue;
};
/**
* @function StrictContextParser#getStartTagName
*
* @returns {string} The current handling start tag name
*
*/
StrictContextParser.prototype.getStartTagName = function() {
return this.tagNames[0];
};
/* exposing it */
module.exports = StrictContextParser;
module.exports = ContextParser;
})();

@@ -383,4 +383,4 @@ /*

var contextParser1, contextParser2;
parser1.contextParser.parsePartial(testObj.s1);
parser2.contextParser.parsePartial(testObj.s2);
parser1.contextParser.contextualize(testObj.s1);
parser2.contextParser.contextualize(testObj.s2);
// contextParser1 = parser1._html5Parser.getInternalState();

@@ -387,0 +387,0 @@ // contextParser2 = parser2._html5Parser.getInternalState();

@@ -14,3 +14,9 @@ /*

var expect = require("chai").expect,
ContextParser = require("../../src/strict-context-parser");
configContextParser = {
enableInputPreProcessing: true,
enableCanonicalization: true,
enableIEConditionalComments: true,
enableStateTracking: true
},
ContextParser = require("context-parser").Parser;

@@ -32,4 +38,4 @@ describe('HTML5 Customized Context Parser html5 state test suite', function(){

].forEach(function(testObj) {
var p1 = new ContextParser({enableStateTracking:true});
p1.parsePartial(testObj.html);
var p1 = new ContextParser(configContextParser);
p1.contextualize(testObj.html);
expect(p1.getStates().toString()).to.equal(testObj.states);

@@ -49,4 +55,4 @@ });

].forEach(function(testObj) {
var p1 = new ContextParser({enableStateTracking:true});
p1.parsePartial(testObj.html);
var p1 = new ContextParser(configContextParser);
p1.contextualize(testObj.html);
expect(p1.getStates().toString()).to.equal(testObj.states);

@@ -65,4 +71,4 @@ });

].forEach(function(testObj) {
var p1 = new ContextParser({enableStateTracking:true});
p1.parsePartial(testObj.html);
var p1 = new ContextParser(configContextParser);
p1.contextualize(testObj.html);
expect(p1.getStates().toString()).to.equal(testObj.states);

@@ -82,4 +88,4 @@ });

].forEach(function(testObj) {
var p1 = new ContextParser({enableStateTracking:true});
p1.parsePartial(testObj.html);
var p1 = new ContextParser(configContextParser);
p1.contextualize(testObj.html);
expect(p1.getStates().toString()).to.equal(testObj.states);

@@ -100,4 +106,4 @@ });

].forEach(function(testObj) {
var p1 = new ContextParser({enableStateTracking:true});
p1.parsePartial(testObj.html);
var p1 = new ContextParser(configContextParser);
p1.contextualize(testObj.html);
expect(p1.getStates().toString()).to.equal(testObj.states);

@@ -117,4 +123,4 @@ });

].forEach(function(testObj) {
var p1 = new ContextParser({enableStateTracking:true});
p1.parsePartial(testObj.html);
var p1 = new ContextParser(configContextParser);
p1.contextualize(testObj.html);
expect(p1.getStates().toString()).to.equal(testObj.states);

@@ -121,0 +127,0 @@ });

@@ -163,3 +163,12 @@ /*

});
/* isScriptableTag test */
it("handlebars-utils#isScriptableTag test", function() {
testPatterns.scriptableTagTestPatterns.forEach(function(testObj) {
var r = handlebarsUtils.isScriptableTag(testObj.tag);
expect(r).to.equal(testObj.result);
});
});
});
}());

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc