@bonniernews/local-esi
Advanced tools
Comparing version 1.0.3 to 1.1.0
@@ -6,2 +6,3 @@ "use strict"; | ||
const {asStream, transform, createESIParser} = require("./lib/transformHtml"); | ||
const redirectCodes = [301, 302, 303, 307, 308]; | ||
@@ -8,0 +9,0 @@ |
"use strict"; | ||
const esiExpressionParser = require("./esiExpressionParser"); | ||
const evaluateExpression = require("./evaluateExpression"); | ||
const HtmlParser = require("atlas-html-stream"); | ||
const ListenerContext = require("./ListenerContext"); | ||
const request = require("request"); | ||
const url = require("url"); | ||
const {assign, test, replace} = require("./evaluateExpression"); | ||
const {convert, createESIParser} = require("./transformHtml"); | ||
const {opentag: toOpenTag, closetag: toCloseTag, voidElements, selfClosingElements} = require("./markup"); | ||
const {chunkToMarkup} = require("./markup"); | ||
const {Readable} = require("stream"); | ||
@@ -44,7 +43,9 @@ module.exports = function ESIEvaluator(context) { | ||
} | ||
const value = data.value; | ||
if (value.startsWith("'''") && value.endsWith("'''")) { | ||
context.assigns[data.name] = value.replace(/'''/ig, ""); | ||
} else { | ||
context.assigns[data.name] = removeReservedCharacters(evaluateExpression(value, context)); | ||
try { | ||
context.assigns[data.name] = assign(value, context); | ||
} catch (err) { | ||
if (/unknown keyword/i.test(err.message)) context.assigns[data.name] = value; | ||
else return next(err); | ||
} | ||
@@ -58,7 +59,5 @@ | ||
open(data, next) { | ||
context.inEsiStatementProcessingContext = true; | ||
next(); | ||
}, | ||
close(next) { | ||
context.inEsiStatementProcessingContext = false; | ||
next(); | ||
@@ -76,3 +75,3 @@ } | ||
} | ||
const listener = ESIEvaluator(ListenerContext(context.req, context.res, context.emitter)); | ||
const listener = ESIEvaluator(context.clone()); | ||
@@ -100,5 +99,4 @@ const chunks = []; | ||
} | ||
const wasInProcessingContext = context.inEsiStatementProcessingContext; | ||
context.inEsiStatementProcessingContext = false; | ||
const listener = ESIEvaluator(context); | ||
const listener = ESIEvaluator(context.clone(true)); | ||
const chunks = []; | ||
@@ -110,3 +108,2 @@ convert(fetchResult) | ||
.on("finish", () => { | ||
context.inEsiStatementProcessingContext = wasInProcessingContext; | ||
writeToResult(chunks, next); | ||
@@ -145,3 +142,3 @@ }) | ||
const lastChoose = context.chooses[context.chooses.length - 1]; | ||
const result = evaluateExpression(data.test, context); | ||
const result = test(data.test, context); | ||
if (data.matchname) { | ||
@@ -153,3 +150,2 @@ context.assigns[data.matchname] = result; | ||
lastChoose.hasEvaluatedToTrue = lastChoose.hasEvaluatedToTrue || result; | ||
context.inEsiStatementProcessingContext = true; | ||
@@ -159,3 +155,2 @@ return next(); | ||
close(next) { | ||
context.inEsiStatementProcessingContext = false; | ||
next(); | ||
@@ -169,8 +164,5 @@ } | ||
lastChoose.isCurrentlyEvaluatedTo = !lastChoose.hasEvaluatedToTrue; | ||
context.inEsiStatementProcessingContext = true; | ||
return next(); | ||
}, | ||
close(next) { | ||
context.inEsiStatementProcessingContext = false; | ||
next(); | ||
@@ -180,2 +172,55 @@ } | ||
esiTags["esi:foreach"] = { | ||
open(data, next) { | ||
context.items = assign(data.collection, context); | ||
if (!Array.isArray(context.items)) { | ||
context.items = Object.entries(context.items); | ||
} | ||
context.foreachChunks = []; | ||
return next(); | ||
}, | ||
close(next) { | ||
const foreachChunks = context.foreachChunks; | ||
delete context.foreachChunks; | ||
let buffered = []; | ||
context.items.forEach((value) => { | ||
if (Array.isArray(value)) value = `[${value.map((v) => typeof v === "string" ? `'${v}'` : v).join(",")}]`; | ||
buffered = buffered.concat([{ | ||
name: "esi:assign", data: {name: "item", value: value.toString()} | ||
}, {name: "esi:assign"}], foreachChunks); | ||
}); | ||
const localContext = context.subContext(); | ||
localContext.inForeach = true; | ||
const listener = ESIEvaluator(localContext); | ||
const chunks = []; | ||
createChunkStream(buffered) | ||
.pipe(createESIParser(listener)) | ||
.on("data", function onData(chunk) { | ||
if (chunk.name === "esi:break") { | ||
this.pause(); | ||
return process.nextTick(() => this.destroy()); | ||
} | ||
chunks.push(chunk); | ||
}) | ||
.on("finish", complete) | ||
.on("error", next); | ||
function complete() { | ||
writeToResult(chunks, next); | ||
} | ||
} | ||
}; | ||
esiTags["esi:break"] = { | ||
open(data, next) { | ||
if (!context.inForeach) return next(new Error("esi:break outside esi:foreach")); | ||
return shouldWrite() ? next(null, {name: "esi:break"}) : next(); | ||
} | ||
}; | ||
function fetchIncluded(data, fetchCallback) { | ||
@@ -189,22 +234,7 @@ const options = { | ||
let source = data.src; | ||
if (source.indexOf("$(") > -1 && source.indexOf(")") > -1) { | ||
source = handleProcessingInstructions(source); | ||
let includeUrl = replace(data.src, context); | ||
if (!includeUrl.startsWith("http")) { | ||
includeUrl = url.resolve(`http://localhost:${context.req.socket.server.address().port}`, includeUrl); | ||
} | ||
for (const key in context.assigns) { | ||
if (typeof context.assigns[key] === "string") { | ||
source = source.replace(`$(${key})`, context.assigns[key]); | ||
} | ||
} | ||
source = source.replace(/\$url_encode\((.*?)\)/, (_, b) => { | ||
return encodeURIComponent(b); | ||
}); | ||
let includeUrl = source; | ||
if (!includeUrl.startsWith("http")) { | ||
includeUrl = url.resolve(`http://localhost:${context.req.socket.server.address().port}`, source); | ||
} | ||
request.get(includeUrl, options, (err, res, body) => { | ||
@@ -231,19 +261,5 @@ if (!err && res.statusCode > 399) { | ||
function shouldWrite() { | ||
if (context.inExcept && !context.lastAttemptWasError) { | ||
return false; | ||
} | ||
if (context.chooses.length) { | ||
return context.chooses.every((choose) => choose.isCurrentlyEvaluatedTo); | ||
} | ||
return true; | ||
} | ||
function onopentag(name, data, next) { | ||
const [current = {}] = context.tags.slice(-1); | ||
if (current.plainText && current.callExpression) { | ||
current.text += toOpenTag(name, data); | ||
if (context.foreachChunks) { | ||
context.foreachChunks.push({name, data}); | ||
return next(); | ||
@@ -253,93 +269,18 @@ } | ||
if (name.startsWith("esi:")) { | ||
if (!current.plainText) { | ||
const esiFunc = esiTags[name]; | ||
context.tags.push(esiFunc); | ||
if (!esiFunc) { | ||
throw new Error(`ESI tag ${name} not implemented.`); | ||
} | ||
const res = esiFunc.open(data, next); | ||
return res; | ||
const esiFunc = esiTags[name]; | ||
const wasInPlainText = context.isInPlainText(); | ||
if (!esiFunc && !wasInPlainText) { | ||
throw new Error(`ESI tag ${name} not implemented.`); | ||
} | ||
if (selfClosingElements.includes(name)) { | ||
return writeToResult({name, data: makeAttributes(data)}, next); | ||
} | ||
context.tags.push(esiFunc); | ||
if (!wasInPlainText) return esiFunc.open(data, next); | ||
} | ||
if (name === "!--") { | ||
return writeToResult({name, data: makeAttributes(data)}, next); | ||
} | ||
writeToResult({name, data: makeAttributes(data)}, next); | ||
} | ||
function ontext(text, next) { | ||
const [current = {}] = context.tags.slice(-1); | ||
if (!context.inEsiStatementProcessingContext) { | ||
return writeToResult({text}, next); | ||
} | ||
if (/^\$\w+\(/.test(text)) { | ||
const expression = esiExpressionParser(text); | ||
if (expression.type !== "CallExpression" && !current.plainText) { | ||
context.tags.push({ | ||
text: text, | ||
plainText: true, | ||
callExpression: true, | ||
}); | ||
return next(); | ||
} | ||
try { | ||
return writeToResult(() => { | ||
return {text: handleProcessingInstructions(ensureNoIllegalCharacters(text))}; | ||
}, next); //handleProcessingInstructions may cause an (expected) error and we're not sure writeToResult will actually write so we pass a function that it can call if it should write | ||
} catch (err) { | ||
return next(err); | ||
} | ||
} | ||
if (current.callExpression) { | ||
const testText = current.text + text; | ||
const expression = esiExpressionParser(testText); | ||
if (expression.type !== "CallExpression") { | ||
current.text = testText; | ||
return next(); | ||
} | ||
context.tags.pop(); | ||
try { | ||
return writeToResult(() => { | ||
return {text: handleProcessingInstructions(ensureNoIllegalCharacters(testText))}; | ||
}, next); //handleProcessingInstructions may cause an (expected) error and we're not sure writeToResult will actually write so we pass a function that it can call if it should write | ||
} catch (err) { | ||
return next(err); | ||
} | ||
} | ||
if (!current.plainText) { | ||
try { | ||
return writeToResult(() => { | ||
return {text: handleProcessingInstructions(ensureNoIllegalCharacters(text))}; | ||
}, next); //handleProcessingInstructions may cause an (expected) error and we're not sure writeToResult will actually write so we pass a function that it can call if it should write | ||
} catch (err) { | ||
return next(err); | ||
} | ||
} | ||
try { | ||
return writeToResult({text}, next); | ||
} catch (err) { | ||
return next(err); | ||
} | ||
} | ||
function onclosetag(name, next) { | ||
const [current = {}] = context.tags.slice(-1); | ||
if (current.plainText && current.callExpression) { | ||
current.text += toCloseTag(name); | ||
if (name !== "esi:foreach" && context.foreachChunks) { | ||
context.foreachChunks.push({name}); | ||
return next(); | ||
@@ -349,106 +290,45 @@ } | ||
if (name.startsWith("esi:")) { | ||
if (!current.plainText) { | ||
const esiFunc = esiTags[name]; | ||
if (!esiFunc) { | ||
throw new Error(`ESI tag ${name} not implemented.`); | ||
} | ||
const popped = context.tags.pop(); | ||
if (esiFunc.close) { | ||
return esiFunc.close(next); | ||
} | ||
if (!context.isInPlainText()) { | ||
if (popped && popped.close) return popped.close(next); | ||
return next(); | ||
} else if (current.plainText && esiTags[name] === current) { | ||
context.tags.pop(); | ||
if (current.close) { | ||
return current.close(next); | ||
} | ||
return next(); | ||
} | ||
if (selfClosingElements.includes(name)) { | ||
return next(); | ||
} | ||
} | ||
if (voidElements.includes(name)) return next(); | ||
writeToResult({name}, next); | ||
} | ||
function ensureNoIllegalCharacters(text) { | ||
// matches | ||
// - dollar signs not part of an esi expression | ||
const pattern = /(\$(?!\w*?\())/g; | ||
let match; | ||
function ontext(text, next) { | ||
if (context.foreachChunks) { | ||
context.foreachChunks.push({text}); | ||
return next(); | ||
} | ||
while ((match = pattern.exec(text))) { | ||
const {"1": character, index} = match; | ||
if (text.charAt(index - 1) === "\\") continue; | ||
if (!context.isProcessing()) { | ||
return writeToResult({text}, next); | ||
} | ||
const excerptStart = Math.max(index - 30, 0); | ||
const excerpt = text.substr(excerptStart, 60); | ||
throw new Error(`Illegal character "${character}" in "${excerpt}"`); | ||
const current = context.tags[context.tags.length - 1]; | ||
if (context.bufferingString && current.text) { | ||
text = current.text + text; | ||
} | ||
return text; | ||
} | ||
function handleProcessingInstructions(text) { | ||
text = removeReservedCharacters(text); | ||
let newText = ""; | ||
let inExpression = false; | ||
let expressionStart; | ||
let openParentheses = 0; | ||
for (let i = 0; i < text.length; i++) { | ||
const char = text[i]; | ||
if (char === "$") { | ||
if (!inExpression) { | ||
expressionStart = i; | ||
} | ||
inExpression = true; | ||
try { | ||
return writeToResult((currentContext) => { | ||
const result = {text: replace(text, currentContext || context)}; | ||
context.bufferingString = false; | ||
return result; | ||
}, next); //handleProcessingInstructions may cause an (expected) error and we're not sure writeToResult will actually write so we pass a function that it can call if it should write | ||
} catch (err) { | ||
if (err.message.includes("Found end of file before end")) { | ||
context.bufferingString = true; | ||
current.text = text; | ||
return next(); | ||
} | ||
if (!inExpression) { | ||
newText += char; | ||
} | ||
if (inExpression && char === "(") { | ||
openParentheses++; | ||
} | ||
if (inExpression && char === ")") { | ||
openParentheses--; | ||
if (openParentheses === 0) { | ||
inExpression = false; | ||
const expressionResult = evaluateExpression(text.substring(expressionStart, i + 1), context); | ||
if (expressionResult !== undefined) { | ||
newText += expressionResult; | ||
} | ||
} | ||
} | ||
return next(err); | ||
} | ||
return newText; | ||
} | ||
function removeReservedCharacters(original) { | ||
if (!original || typeof original !== "string") { | ||
return original; | ||
} | ||
let text = original.replace(/\\["]/g, "\""); | ||
text = text.replace(/(^|[^\\])(\\)($|[^\\])/ig, (_, group1, _2, group3) => { //Remove backslashes, but not escaped ones | ||
return `${group1}${group3}`; | ||
}); | ||
text = text.replace(/\\\\/g, "\\"); //Escaped backslashes, remove the escaping backslash | ||
return text; | ||
} | ||
function makeAttributes(data) { | ||
@@ -459,5 +339,4 @@ if (!data) return {}; | ||
let value = data[key]; | ||
const [current = {}] = context.tags.slice(-1); | ||
if (context.inEsiStatementProcessingContext && !current.plainText) { | ||
value = handleProcessingInstructions(value); | ||
if (context.isProcessing()) { | ||
value = replace(value, context); | ||
} | ||
@@ -470,15 +349,50 @@ attributes[key] = value; | ||
function writeToResult(chunk, next) { | ||
if (typeof chunk === "function" && (context.inReplacement || shouldWrite())) { | ||
chunk = chunk(); | ||
} | ||
if (context.bufferingString) { | ||
const [current = {}] = context.tags.slice(-1); | ||
if (typeof chunk === "function") { | ||
chunk = chunk(); | ||
} | ||
if (context.inReplacement) { | ||
context.replacement += chunk; | ||
current.text += chunkToMarkup(chunk); | ||
return next(); | ||
} | ||
if (shouldWrite()) { | ||
if (typeof chunk === "function") { | ||
chunk = chunk(); | ||
} | ||
return next(null, chunk); | ||
} | ||
next(); | ||
} | ||
function shouldWrite() { | ||
if (context.inExcept && !context.lastAttemptWasError) { | ||
return false; | ||
} | ||
if (context.chooses.length) { | ||
return context.chooses.every((choose) => choose.isCurrentlyEvaluatedTo); | ||
} | ||
return true; | ||
} | ||
}; | ||
function createChunkStream(chunks) { | ||
const reader = new Readable({objectMode: true}); | ||
reader._read = function () { | ||
if (!chunks.length) return reader.push(null); | ||
while (reader.push(chunks.shift())) { | ||
if (!chunks.length) { | ||
reader.push(null); | ||
break; | ||
} | ||
} | ||
}; | ||
return reader; | ||
} |
@@ -1,157 +0,59 @@ | ||
/* eslint-disable camelcase */ | ||
"use strict"; | ||
const esiExpressionParser = require("./esiExpressionParser"); | ||
module.exports = function evaluateExpression(test, context) { | ||
const funcs = { | ||
Identifier(node, nodeContext = context.assigns) { | ||
return nodeContext[node.name]; | ||
}, | ||
exists([arg]) { | ||
return !!getFunc(arg.type)(arg); | ||
}, | ||
int([arg]) { | ||
return parseInt(getFunc(arg.type)(arg)) || 0; | ||
}, | ||
index([arg1, arg2]) { | ||
return getFunc(arg1.type)(arg1).indexOf(getFunc(arg2.type)(arg2)); | ||
}, | ||
base64_decode([arg]) { | ||
const string = getFunc(arg.type)(arg); | ||
if (!string) { | ||
return ""; | ||
} | ||
return Buffer.from(string, "base64").toString("utf8"); | ||
}, | ||
base64_encode([arg]) { | ||
const string = getFunc(arg.type)(arg); | ||
if (!string) { | ||
return ""; | ||
} | ||
return Buffer.from(string, "utf8").toString("base64"); | ||
}, | ||
url_encode([arg]) { | ||
const string = getFunc(arg.type)(arg); | ||
if (!string) { | ||
return ""; | ||
} | ||
return encodeURIComponent(string); | ||
}, | ||
add_header([name, value]) { | ||
context.emit("add_header", getFunc(name.type)(name), getFunc(value.type)(value)); | ||
}, | ||
set_redirect([location]) { | ||
context.emit("set_redirect", 302, getFunc(location.type)(location)); | ||
context.redirected = true; | ||
}, | ||
set_response_code([code, body]) { | ||
if (body) { | ||
return context.emit("set_response_code", getFunc(code.type)(code), getFunc(body.type)(body)); | ||
} | ||
const evaluate = require("./expression/evaluate"); | ||
const {parse, split} = require("./expression/parser"); | ||
context.emit("set_response_code", getFunc(code.type)(code)); | ||
}, | ||
substr([arg1, arg2, arg3]) { | ||
const string = getFunc(arg1.type)(arg1); | ||
if (typeof string !== "string") { | ||
throw new Error("substr invoked on non-string"); | ||
} | ||
let startIndex; | ||
let length; | ||
module.exports = { | ||
assign, | ||
test, | ||
replace, | ||
}; | ||
if (arg2) { | ||
startIndex = getFunc(arg2.type)(arg2); | ||
} | ||
function assign(value, context) { | ||
return evaluate(parse(value), context); | ||
} | ||
if (typeof startIndex !== "number") { | ||
throw new Error("substr invoked with non-number as start index"); | ||
} | ||
function test(expression, context) { | ||
return evaluate(parse(expression), context); | ||
} | ||
if (arg3) { | ||
length = getFunc(arg3.type)(arg3); | ||
} | ||
function replace(text, context) { | ||
if (!text) return; | ||
if (length < 0) { | ||
length = string.length - startIndex + length; | ||
} | ||
return string.substr(startIndex, length); | ||
}, | ||
time() { | ||
return Math.round(Date.now() / 1000); | ||
}, | ||
http_time([seconds]) { | ||
const secondsInt = parseInt(getFunc(seconds.type)(seconds)); | ||
const now = new Date(secondsInt * 1000); | ||
return now.toUTCString(); | ||
}, | ||
CallExpression(node) { | ||
return getFunc(node.callee.name)(node.arguments); | ||
}, | ||
LogicalExpression(node) { | ||
const left = getFunc(node.left.type); | ||
const right = getFunc(node.right.type); | ||
const expressions = split(text); | ||
if (!expressions.length) return removeReservedCharacters(text); | ||
if (node.operator === "&" || node.operator === "&&") return left(node.left) && right(node.right); | ||
if (node.operator === "|" || node.operator === "||") return left(node.left) || right(node.right); | ||
let newText = ""; | ||
throw new Error(`Uknown BinaryExpression operator ${node.operator}`); | ||
}, | ||
BinaryExpression(node) { | ||
const left = getFunc(node.left.type)(node.left); | ||
const right = getFunc(node.right.type)(node.right); | ||
for (const expr of expressions) { | ||
if (expr.type === "TEXT") { | ||
newText += removeReservedCharacters(expr.text); | ||
continue; | ||
} | ||
if (node.operator === "==") return left === right; | ||
if (node.operator === "!=") return left !== right; | ||
if (node.operator === ">=") return left >= right; | ||
if (node.operator === "<=") return left <= right; | ||
if (node.operator === "<") return left < right; | ||
if (node.operator === ">") return left > right; | ||
if (node.operator === "+") return left + right; | ||
if (node.operator === "-") return left - right; | ||
if (node.operator === "*") return left * right; | ||
if (node.operator === "/") return left / right; | ||
if (node.operator === "%") return left % right; | ||
if (node.operator === "has") return left.indexOf(right) > -1; | ||
if (node.operator === "has_i") return left.toLowerCase().indexOf(right.toLowerCase()) > -1; | ||
if (node.operator === "matches") { | ||
if (!left) { | ||
return; | ||
} | ||
return left.match(right); | ||
} | ||
if (node.operator === "matches_i") { | ||
if (!left) { | ||
return; | ||
} | ||
return left.match(new RegExp(right, "i")); | ||
} | ||
let result = evaluate(expr.expression, context); | ||
if (Array.isArray(result)) result = `[${result.join(", ")}]`; | ||
throw new Error(`Uknown BinaryExpression operator ${node.operator}`); | ||
}, | ||
MemberExpression(node) { | ||
const object = getFunc(node.object.type)(node.object); | ||
if (result === undefined) continue; | ||
if (!object) return; | ||
newText += result; | ||
} | ||
return getFunc(node.property.type)(node.property, object); | ||
}, | ||
Literal(node) { | ||
return node.value; | ||
}, | ||
UnaryExpression(node) { | ||
if (node.operator !== "!") { | ||
throw new Error(`Unary operator ${node.operator} not implemented`); | ||
} | ||
return newText; | ||
} | ||
return !getFunc(node.argument.type)(node.argument); | ||
} | ||
}; | ||
function removeReservedCharacters(original) { | ||
if (!original || typeof original !== "string") { | ||
return original; | ||
} | ||
const parsedTree = esiExpressionParser(test); | ||
return getFunc(parsedTree.type)(parsedTree); | ||
let text = original.replace(/\\["]/g, "\""); | ||
function getFunc(name) { | ||
if (!funcs[name]) throw new Error(`${name} is not implemented`); | ||
return funcs[name]; | ||
} | ||
}; | ||
text = text.replace(/(^|[^\\])(\\)($|[^\\])/ig, (_, group1, _2, group3) => { //Remove backslashes, but not escaped ones | ||
return `${group1}${group3}`; | ||
}); | ||
text = text.replace(/\\\\/g, "\\"); //Escaped backslashes, remove the escaping backslash | ||
return text; | ||
} |
@@ -6,14 +6,5 @@ "use strict"; | ||
module.exports = function ListenerContext(req, res, emitter) { | ||
const buildHeaderVariables = (headers) => { | ||
if (!headers) return {}; | ||
return Object.entries(headers).reduce((acc, pair) => { | ||
const httpKey = pair[0].replace(/-/g, "_").toUpperCase(); | ||
acc[`HTTP_${httpKey}`] = pair[1]; | ||
return acc; | ||
}, {}); | ||
}; | ||
emitter = emitter || new EventEmitter(); | ||
const context = { | ||
return { | ||
assigns: Object.assign(buildHeaderVariables(req && req.headers), { | ||
@@ -26,3 +17,8 @@ "HTTP_COOKIE": req.cookies || {}, | ||
res, | ||
inEsiStatementProcessingContext: false, | ||
isProcessing() { | ||
return Boolean((this.tags.length || this.isSubContext) && !this.isInPlainText()); | ||
}, | ||
isInPlainText() { | ||
return this.tags.some((tag) => tag.plainText); | ||
}, | ||
inAttempt: false, | ||
@@ -53,5 +49,29 @@ lastAttemptWasError: false, | ||
}, | ||
clone(linkAssigns) { | ||
const c = ListenerContext(req, res, emitter); | ||
if (linkAssigns) { | ||
c.assigns = this.assigns; | ||
} | ||
return c; | ||
}, | ||
subContext() { | ||
const clone = this.clone(true); | ||
clone.isSubContext = true; | ||
return clone; | ||
} | ||
}; | ||
return context; | ||
function buildHeaderVariables(headers) { | ||
if (!headers) return {}; | ||
return Object.entries(headers).reduce((acc, pair) => { | ||
const header = pair[0]; | ||
if (header === "x-forwarded-for") { | ||
acc.REMOTE_ADDR = pair[1]; | ||
} | ||
const httpKey = header.replace(/-/g, "_").toUpperCase(); | ||
acc[`HTTP_${httpKey}`] = pair[1]; | ||
return acc; | ||
}, {}); | ||
} | ||
}; |
"use strict"; | ||
const voidElements = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]; | ||
const selfClosingElements = ["esi:include", "esi:eval", "esi:assign", "esi:debug"]; | ||
const selfClosingElements = ["esi:include", "esi:eval", "esi:assign", "esi:debug", "esi:break"]; | ||
@@ -6,0 +6,0 @@ module.exports = { |
{ | ||
"name": "@bonniernews/local-esi", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "Local Edge Side Includes parser", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha && eslint . --cache" | ||
"test": "mocha", | ||
"posttest": "eslint . --cache" | ||
}, | ||
@@ -9,0 +10,0 @@ "keywords": [ |
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
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
45151
15
1453
1