Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions packages/pug-code-gen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ Compiler.prototype = {
}

if (debug && node.debug !== false && node.type !== 'Block') {
if (node.line) {
if (Object.prototype.hasOwnProperty.call(node, 'line') && node.line) {
var js = ';pug_debug_line = ' + node.line;
if (node.filename)
js += ';pug_debug_filename = ' + stringify(node.filename);
Expand Down Expand Up @@ -414,7 +414,7 @@ Compiler.prototype = {
} else {
this.buf.push('case ' + node.expr + ':');
}
if (node.block) {
if (Object.prototype.hasOwnProperty.call(node, 'block') && node.block) {
this.visit(node.block, node);
this.buf.push(' break;');
}
Expand Down Expand Up @@ -450,7 +450,9 @@ Compiler.prototype = {
pp &&
block.nodes.length > 1 &&
!escapePrettyMode &&
Object.prototype.hasOwnProperty.call(block.nodes[0], 'type') &&
block.nodes[0].type === 'Text' &&
Object.prototype.hasOwnProperty.call(block.nodes[1], 'type') &&
block.nodes[1].type === 'Text'
) {
this.prettyIndent(1, true);
Expand All @@ -461,8 +463,11 @@ Compiler.prototype = {
pp &&
i > 0 &&
!escapePrettyMode &&
Object.prototype.hasOwnProperty.call(block.nodes[i], 'type') &&
block.nodes[i].type === 'Text' &&
Object.prototype.hasOwnProperty.call(block.nodes[i - 1], 'type') &&
block.nodes[i - 1].type === 'Text' &&
Object.prototype.hasOwnProperty.call(block.nodes[i - 1], 'val') &&
/\n$/.test(block.nodes[i - 1].val)
) {
this.prettyIndent(1, false);
Expand Down Expand Up @@ -671,11 +676,17 @@ Compiler.prototype = {
}
// if it is non-empty throw an error
if (
tag.code ||
(tag.block &&
(Object.prototype.hasOwnProperty.call(tag, 'code') && tag.code) ||
(Object.prototype.hasOwnProperty.call(tag, 'block') &&
tag.block &&
!(tag.block.type === 'Block' && tag.block.nodes.length === 0) &&
tag.block.nodes.some(function(tag) {
return tag.type !== 'Text' || !/^\s*$/.test(tag.val);
return (
!Object.prototype.hasOwnProperty.call(tag, 'type') ||
tag.type !== 'Text' ||
!Object.prototype.hasOwnProperty.call(tag, 'val') ||
!/^\s*$/.test(tag.val)
);
}))
) {
this.error(
Expand All @@ -696,7 +707,7 @@ Compiler.prototype = {
this.attributeBlocks(tag.attributeBlocks)
);
this.buffer('>');
if (tag.code) this.visitCode(tag.code);
if (Object.prototype.hasOwnProperty.call(tag, 'code') && tag.code) this.visitCode(tag.code);
this.visit(tag.block, tag);

// pretty print
Expand Down Expand Up @@ -807,7 +818,7 @@ Compiler.prototype = {
}

// Block support
if (code.block) {
if (Object.prototype.hasOwnProperty.call(code, 'block') && code.block) {
if (!code.buffer) this.buf.push('{');
this.visit(code.block, code);
if (!code.buffer) this.buf.push('}');
Expand All @@ -826,7 +837,7 @@ Compiler.prototype = {
this.buf.push('if (' + test + ') {');
this.visit(cond.consequent, cond);
this.buf.push('}');
if (cond.alternate) {
if (Object.prototype.hasOwnProperty.call(cond, 'alternate') && cond.alternate) {
if (cond.alternate.type === 'Conditional') {
this.buf.push('else');
this.visitConditional(cond.alternate);
Expand Down Expand Up @@ -1021,12 +1032,16 @@ Compiler.prototype = {
function tagCanInline(tag) {
function isInline(node) {
// Recurse if the node is a block
if (node.type === 'Block') return node.nodes.every(isInline);
if (Object.prototype.hasOwnProperty.call(node, 'type') && node.type === 'Block') return node.nodes.every(isInline);
// When there is a YieldBlock here, it is an indication that the file is
// expected to be included but is not. If this is the case, the block
// must be empty.
if (node.type === 'YieldBlock') return true;
return (node.type === 'Text' && !/\n/.test(node.val)) || node.isInline;
if (Object.prototype.hasOwnProperty.call(node, 'type') && node.type === 'YieldBlock') return true;
return (
(Object.prototype.hasOwnProperty.call(node, 'type') && node.type === 'Text' &&
Object.prototype.hasOwnProperty.call(node, 'val') && !/\n/.test(node.val)) ||
(Object.prototype.hasOwnProperty.call(node, 'isInline') && node.isInline)
);
}

return tag.block.nodes.every(isInline);
Expand Down
18 changes: 15 additions & 3 deletions packages/pug-filters/lib/handle-filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,28 @@ var walk = require('pug-walk');
var error = require('pug-error');
var runFilter = require('./run-filter');

var ownProp = Object.prototype.hasOwnProperty;

module.exports = handleFilters;
function handleFilters(ast, filters, options, filterAliases) {
options = options || {};
walk(
ast,
function(node) {
var dir = node.filename ? dirname(node.filename) : null;
if (node.type === 'Filter') {
if (ownProp.call(node, 'type') && node.type === 'Filter') {
handleNestedFilters(node, filters, options, filterAliases);
var text = getBodyAsText(node);
var attrs = getAttributes(node, options);
attrs.filename = node.filename;
node.type = 'Text';
node.val = filterWithFallback(node, text, attrs);
} else if (node.type === 'RawInclude' && node.filters.length) {
} else if (
ownProp.call(node, 'type') &&
node.type === 'RawInclude' &&
ownProp.call(node, 'filters') &&
node.filters.length
) {
var firstFilter = node.filters.pop();
var attrs = getAttributes(firstFilter, options);
var filename = (attrs.filename = node.file.fullPath);
Expand Down Expand Up @@ -98,7 +105,12 @@ function handleFilters(ast, filters, options, filterAliases) {
}

function handleNestedFilters(node, filters, options, filterAliases) {
if (node.block.nodes[0] && node.block.nodes[0].type === 'Filter') {
if (
ownProp.call(node, 'block') &&
node.block.nodes[0] &&
ownProp.call(node.block.nodes[0], 'type') &&
node.block.nodes[0].type === 'Filter'
) {
node.block.nodes[0] = handleFilters(
node.block,
filters,
Expand Down
61 changes: 41 additions & 20 deletions packages/pug-linker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@
var assert = require('assert');
var walk = require('pug-walk');

var ownProp = Object.prototype.hasOwnProperty;

function error() {
throw require('pug-error').apply(null, arguments);
}

module.exports = link;
function link(ast) {
assert(
ast.type === 'Block',
ownProp.call(ast, 'type') && ast.type === 'Block',
'The top level element should always be a block'
);
var extendsNode = null;
if (ast.nodes.length) {
var hasExtends = ast.nodes[0].type === 'Extends';
var hasExtends =
ownProp.call(ast.nodes[0], 'type') && ast.nodes[0].type === 'Extends';
checkExtendPosition(ast, hasExtends);
if (hasExtends) {
extendsNode = ast.nodes.shift();
Expand All @@ -27,11 +30,16 @@ function link(ast) {
var mixins = [];
var expectedBlocks = [];
ast.nodes.forEach(function addNode(node) {
if (node.type === 'NamedBlock') {
if (ownProp.call(node, 'type') && node.type === 'NamedBlock') {
expectedBlocks.push(node);
} else if (node.type === 'Block') {
} else if (ownProp.call(node, 'type') && node.type === 'Block') {
node.nodes.forEach(addNode);
} else if (node.type === 'Mixin' && node.call === false) {
} else if (
ownProp.call(node, 'type') &&
node.type === 'Mixin' &&
ownProp.call(node, 'call') &&
node.call === false
) {
mixins.push(node);
} else {
error(
Expand All @@ -45,7 +53,7 @@ function link(ast) {
extend(parent.declaredBlocks, ast);
var foundBlockNames = [];
walk(parent, function(node) {
if (node.type === 'NamedBlock') {
if (ownProp.call(node, 'type') && node.type === 'NamedBlock') {
foundBlockNames.push(node.name);
}
});
Expand All @@ -71,7 +79,12 @@ function link(ast) {
function findDeclaredBlocks(ast) /*: {[name: string]: Array<BlockNode>}*/ {
var definitions = {};
walk(ast, function before(node) {
if (node.type === 'NamedBlock' && node.mode === 'replace') {
if (
ownProp.call(node, 'type') &&
node.type === 'NamedBlock' &&
ownProp.call(node, 'mode') &&
node.mode === 'replace'
) {
definitions[node.name] = definitions[node.name] || [];
definitions[node.name].push(node);
}
Expand All @@ -82,7 +95,7 @@ function findDeclaredBlocks(ast) /*: {[name: string]: Array<BlockNode>}*/ {
function flattenParentBlocks(parentBlocks, accumulator) {
accumulator = accumulator || [];
parentBlocks.forEach(function(parentBlock) {
if (parentBlock.parents) {
if (ownProp.call(parentBlock, 'parents') && parentBlock.parents) {
flattenParentBlocks(parentBlock.parents, accumulator);
}
accumulator.push(parentBlock);
Expand All @@ -95,7 +108,7 @@ function extend(parentBlocks, ast) {
walk(
ast,
function before(node) {
if (node.type === 'NamedBlock') {
if (ownProp.call(node, 'type') && node.type === 'NamedBlock') {
if (stack[node.name] === node.name) {
return (node.ignore = true);
}
Expand All @@ -106,7 +119,7 @@ function extend(parentBlocks, ast) {
if (parentBlockList.length) {
node.parents = parentBlockList;
parentBlockList.forEach(function(parentBlock) {
switch (node.mode) {
switch (ownProp.call(node, 'mode') ? node.mode : undefined) {
case 'append':
parentBlock.nodes = parentBlock.nodes.concat(node.nodes);
break;
Expand All @@ -122,7 +135,11 @@ function extend(parentBlocks, ast) {
}
},
function after(node) {
if (node.type === 'NamedBlock' && !node.ignore) {
if (
ownProp.call(node, 'type') &&
node.type === 'NamedBlock' &&
!(ownProp.call(node, 'ignore') && node.ignore)
) {
delete stack[node.name];
}
}
Expand All @@ -133,24 +150,24 @@ function applyIncludes(ast, child) {
return walk(
ast,
function before(node, replace) {
if (node.type === 'RawInclude') {
if (ownProp.call(node, 'type') && node.type === 'RawInclude') {
replace({type: 'Text', val: node.file.str.replace(/\r/g, '')});
}
},
function after(node, replace) {
if (node.type === 'Include') {
if (ownProp.call(node, 'type') && node.type === 'Include') {
var childAST = link(node.file.ast);
if (childAST.hasExtends) {
childAST = removeBlocks(childAST);
}
replace(applyYield(childAST, node.block));
replace(applyYield(childAST, ownProp.call(node, 'block') ? node.block : null));
}
}
);
}
function removeBlocks(ast) {
return walk(ast, function(node, replace) {
if (node.type === 'NamedBlock') {
if (ownProp.call(node, 'type') && node.type === 'NamedBlock') {
replace({
type: 'Block',
nodes: node.nodes,
Expand All @@ -163,7 +180,7 @@ function applyYield(ast, block) {
if (!block || !block.nodes.length) return ast;
var replaced = false;
ast = walk(ast, null, function(node, replace) {
if (node.type === 'YieldBlock') {
if (ownProp.call(node, 'type') && node.type === 'YieldBlock') {
replaced = true;
node.type = 'Block';
node.nodes = [block];
Expand All @@ -172,10 +189,14 @@ function applyYield(ast, block) {
function defaultYieldLocation(node) {
var res = node;
for (var i = 0; i < node.nodes.length; i++) {
if (node.nodes[i].textOnly) continue;
if (node.nodes[i].type === 'Block') {
if (ownProp.call(node.nodes[i], 'textOnly') && node.nodes[i].textOnly) continue;
if (ownProp.call(node.nodes[i], 'type') && node.nodes[i].type === 'Block') {
res = defaultYieldLocation(node.nodes[i]);
} else if (node.nodes[i].block && node.nodes[i].block.nodes.length) {
} else if (
ownProp.call(node.nodes[i], 'block') &&
node.nodes[i].block &&
node.nodes[i].block.nodes.length
) {
res = defaultYieldLocation(node.nodes[i].block);
}
}
Expand All @@ -191,7 +212,7 @@ function applyYield(ast, block) {
function checkExtendPosition(ast, hasExtends) {
var legitExtendsReached = false;
walk(ast, function(node) {
if (node.type === 'Extends') {
if (ownProp.call(node, 'type') && node.type === 'Extends') {
if (hasExtends && !legitExtendsReached) {
legitExtendsReached = true;
} else {
Expand Down
23 changes: 15 additions & 8 deletions packages/pug-walk/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

var ownProp = Object.prototype.hasOwnProperty;

module.exports = walkAST;
function walkAST(ast, before, after, options) {
if (after && typeof after === 'object' && typeof options === 'undefined') {
Expand Down Expand Up @@ -34,7 +36,12 @@ function walkAST(ast, before, after, options) {

parents.unshift(ast);

switch (ast.type) {
// Use own-property check to prevent prototype pollution gadgets:
// an attacker could set Object.prototype.type to a node type string,
// causing arbitrary AST branches to be walked on plain objects.
var astType = ownProp.call(ast, 'type') ? ast.type : undefined;

switch (astType) {
case 'NamedBlock':
case 'Block':
ast.nodes = walkAndMergeNodes(ast.nodes);
Expand All @@ -47,28 +54,28 @@ function walkAST(ast, before, after, options) {
case 'When':
case 'Code':
case 'While':
if (ast.block) {
if (ownProp.call(ast, 'block') && ast.block) {
ast.block = walkAST(ast.block, before, after, options);
}
break;
case 'Each':
if (ast.block) {
if (ownProp.call(ast, 'block') && ast.block) {
ast.block = walkAST(ast.block, before, after, options);
}
if (ast.alternate) {
if (ownProp.call(ast, 'alternate') && ast.alternate) {
ast.alternate = walkAST(ast.alternate, before, after, options);
}
break;
case 'EachOf':
if (ast.block) {
if (ownProp.call(ast, 'block') && ast.block) {
ast.block = walkAST(ast.block, before, after, options);
}
break;
case 'Conditional':
if (ast.consequent) {
if (ownProp.call(ast, 'consequent') && ast.consequent) {
ast.consequent = walkAST(ast.consequent, before, after, options);
}
if (ast.alternate) {
if (ownProp.call(ast, 'alternate') && ast.alternate) {
ast.alternate = walkAST(ast.alternate, before, after, options);
}
break;
Expand All @@ -93,7 +100,7 @@ function walkAST(ast, before, after, options) {
case 'Text':
break;
case 'FileReference':
if (options.includeDependencies && ast.ast) {
if (options.includeDependencies && ownProp.call(ast, 'ast') && ast.ast) {
walkAST(ast.ast, before, after, options);
}
break;
Expand Down