secure-handlebars
Advanced tools
Comparing version 0.3.0 to 1.0.0
@@ -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 < 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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
95
0
97
5
8
659154
5
14
8396
+ Addedfs@0.0.2
+ Addedhandlebars@^3.0.3
+ Addedpath@^0.11.14
+ Addedalign-text@0.1.4(transitive)
+ Addedamdefine@1.0.1(transitive)
+ Addedcamelcase@1.2.1(transitive)
+ Addedcenter-align@0.1.3(transitive)
+ Addedcliui@2.1.0(transitive)
+ Addeddecamelize@1.2.0(transitive)
+ Addedfs@0.0.2(transitive)
+ Addedhandlebars@3.0.8(transitive)
+ Addedis-buffer@1.1.6(transitive)
+ Addedkind-of@3.2.2(transitive)
+ Addedlazy-cache@1.0.4(transitive)
+ Addedlongest@1.0.1(transitive)
+ Addedminimist@0.0.10(transitive)
+ Addedoptimist@0.6.1(transitive)
+ Addedpath@0.11.14(transitive)
+ Addedrepeat-string@1.6.1(transitive)
+ Addedright-align@0.1.3(transitive)
+ Addedsource-map@0.1.430.5.7(transitive)
+ Addeduglify-js@2.8.29(transitive)
+ Addeduglify-to-browserify@1.0.2(transitive)
+ Addedwindow-size@0.1.0(transitive)
+ Addedwordwrap@0.0.20.0.3(transitive)
+ Addedyargs@3.10.0(transitive)
- Removeddebug@^2.1.2
- Removeddebug@2.6.9(transitive)
- Removedms@2.0.0(transitive)
Updatedcontext-parser@^1.1.0
Updatedxss-filters@^1.2.0