cloudflare-esi
Advanced tools
Comparing version 0.1.2 to 0.2.0
@@ -18,2 +18,3 @@ import { tagParser } from "./tagParser"; | ||
value = prev_chunk + value; | ||
prev_chunk = ""; | ||
const parser = new tagParser(value); | ||
@@ -27,3 +28,2 @@ do { | ||
value = after; | ||
prev_chunk = ""; | ||
} | ||
@@ -30,0 +30,0 @@ else if (tag && !tag.whole) { |
@@ -37,3 +37,3 @@ /// <reference types="@cloudflare/workers-types" /> | ||
* @property {ESIConfig} config - ESIConfig when class was created | ||
* @property {Object} headers - All headers of the request uppercased | ||
* @property {object} headers - All headers of the request uppercased | ||
* @property {string} method - Method of the request | ||
@@ -94,3 +94,3 @@ * @property {URLSearchParams} esiArgs - Any ESI Arguments extracted from the URL Search params of the original request | ||
export declare type customESIVarsFunction = (request: Request) => Promise<customESIVars>; | ||
export declare type fetchFunction = (request: string | Request, requestInitr?: Request | RequestInit | undefined) => Promise<Response>; | ||
export declare type fetchFunction = (request: string | Request) => Promise<Response>; | ||
export declare class esi { | ||
@@ -97,0 +97,0 @@ options: ESIConfig; |
@@ -27,2 +27,5 @@ import { create as createHandleChunk } from "./handleChunk"; | ||
if (recursion >= limit) { | ||
// We dont have to set the URL value here | ||
// As we're going to get here in a ESI loop | ||
// And the parent value will have a URL set | ||
return new Response(""); | ||
@@ -64,3 +67,6 @@ } | ||
(await canDelegateToSurrogate(origRequest, this.options))) { | ||
return new Response(response.body, response); | ||
const resp = new Response(response.body, response); | ||
// We set the URL manually here as it doesn't come across from the copy˛ | ||
Object.defineProperty(resp, "url", { value: response.url }); | ||
return resp; | ||
} | ||
@@ -72,2 +78,4 @@ const { readable, writable } = new TransformStream(); | ||
const mutResponse = new Response(readable, response); | ||
// We set the URL manually here as it doesn't come across from the copy˛ | ||
Object.defineProperty(mutResponse, "url", { value: response.url }); | ||
// Zero downstream lifetime | ||
@@ -243,3 +251,3 @@ mutResponse.headers.set("Cache-Control", "private, max-age=0"); | ||
// return a brand new | ||
return [new Request(current.toString(), request), vars]; | ||
return [new Request(current.toString(), request.clone()), vars]; | ||
} | ||
@@ -246,0 +254,0 @@ /** |
@@ -87,3 +87,7 @@ import { tagParser } from "./tagParser"; | ||
} | ||
// All of our ESI regexs | ||
const regexExtractor = /\/(.*?)(?<!\\)\/([a-z]*)/; | ||
const reg_esi_seperator = /(?:'.*?(?<!\\)')|(\|{1,2}|&{1,2}|!(?!=))/g; | ||
const reg_esi_brackets = /(?:'.*?(?<!\\)')|(\(|\))/g; | ||
const reg_esi_condition = /(?:(\d+(?:\.\d+)?)|(?:'(.*?)(?<!\\)'))\s*(!=|==|=~|<=|>=|>|<)\s*(?:(\d+(?:\.\d+)?)|(?:'(.*?)(?<!\\)'))/; | ||
/** | ||
@@ -108,95 +112,182 @@ * Evaluates esi Vars within when tag conditional statements | ||
/** | ||
* Takes a condition string and turns it into javascript to be ran | ||
* if the condition is invalid returns null otherwise the compiled string | ||
* Takes a condition string and splits it into its two sides and operator | ||
* passes that to the tester and returns the result | ||
* | ||
* @param {string} condition conditional string to build out | ||
* @returns {null[] | [boolean, string]} valid condition compiled or null | ||
* @param {string} condition conditional string to split | ||
* @returns {boolean} condition result | ||
*/ | ||
async function _esi_condition_lexer(condition) { | ||
const reg_esi_condition = /(\d+(?:\.\d+)?)|(?:'(.*?)(?<!\\)')|(!=|!|\|{1,2}|&{1,2}|={2}|=~|\(|\)|<=|>=|>|<)/g; | ||
const op_replacements = { | ||
"!=": "!==", | ||
"|": " || ", | ||
"&": " && ", | ||
"||": " || ", | ||
"&&": " && ", | ||
"!": " ! ", | ||
"|": "||", | ||
"&": "&&", | ||
"||": "||", | ||
"&&": "&&", | ||
"!": "!", | ||
}; | ||
const lexer_rules = { | ||
number: { | ||
nil: true, | ||
operator: true, | ||
}, | ||
string: { | ||
nil: true, | ||
operator: true, | ||
}, | ||
operator: { | ||
nil: true, | ||
number: true, | ||
string: true, | ||
operator: true, | ||
}, | ||
const tokensSplit = condition.match(reg_esi_condition); | ||
if (tokensSplit == null) { | ||
return false; | ||
} | ||
const left = tokensSplit[1] || tokensSplit[2]; | ||
const op = op_replacements[tokensSplit[3]] || tokensSplit[3]; | ||
const right = tokensSplit[4] || tokensSplit[5]; | ||
return esiConditionTester(left, right, op); | ||
} | ||
/** | ||
* Takes a condition broken down into an a, b & operator and tests the data | ||
* returns the result | ||
* | ||
* @param {string | number} left conditional left | ||
* @param {string | number} right conditional right | ||
* @param {string} operator operator to compare | ||
* @returns {boolean} condition result | ||
*/ | ||
function esiConditionTester(left, right, operator) { | ||
switch (operator) { | ||
case "==": | ||
case "===": | ||
return left === right; | ||
case "!==": | ||
return left !== right; | ||
case ">=": | ||
return left >= right; | ||
case "<=": | ||
return left <= right; | ||
case "<": | ||
return left < right; | ||
case ">": | ||
return left > right; | ||
case "=~": { | ||
const regex = right.match(regexExtractor); | ||
if (!regex) | ||
return false; | ||
// Bloody javascript! | ||
// Gotta cleanup some escaping here | ||
// Only have to do it for regex'd strings | ||
// As normal comparison strings should be escaped the same (need to be to be equal) | ||
left = left.replace(/\\"/g, '"'); | ||
left = left.replace(/\\'/g, "'"); | ||
left = left.replace(/\\\\/g, "\\"); | ||
const reg = new RegExp(regex[1], regex[2]); | ||
return reg.test(left); | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Takes a condition string and splits it into parts splitting along a seperator | ||
* seperators are logical seperators ie `|` or `&` | ||
* passes the splits along to ${_esi_condition_lexer} and then | ||
* returns the result of the condition after comparing the splits | ||
* against their logical seperators | ||
* | ||
* @param {string} condition conditional string to split | ||
* @returns {boolean} condition result | ||
*/ | ||
async function esi_seperator_splitter(condition) { | ||
let startingIndex = 0; | ||
let negatorySeperator = false; | ||
let prevSeperator = ""; | ||
let valid = null; | ||
const handleString = async function (str) { | ||
if (str == "false" || str == "true") { | ||
return str === "true"; | ||
} | ||
else { | ||
return await _esi_condition_lexer(str); | ||
} | ||
}; | ||
const tokens = []; | ||
let prev_type = "nil"; | ||
let expectingPattern = false; | ||
const tokensSplit = condition.matchAll(reg_esi_condition); | ||
const validityCheck = function (res, seperator) { | ||
if (negatorySeperator) { | ||
res = !res; | ||
negatorySeperator = !negatorySeperator; | ||
} | ||
if (valid == null) { | ||
return res; | ||
} | ||
switch (seperator) { | ||
case "&": | ||
case "&&": | ||
return valid && res; | ||
case "||": | ||
case "|": | ||
return valid || res; | ||
} | ||
return valid; | ||
}; | ||
const tokensSplit = condition.matchAll(reg_esi_seperator); | ||
for (const token of tokensSplit) { | ||
const number = token[1]; | ||
const string = token[2]; | ||
const operator = token[3]; | ||
let token_type = "nil"; | ||
if (number) { | ||
token_type = "number"; | ||
tokens.push(number); | ||
if (!token[1]) | ||
continue; | ||
const seperator = token[1]; | ||
// Negatory seperator! | ||
if (seperator == "!") { | ||
negatorySeperator = !negatorySeperator; | ||
continue; | ||
} | ||
else if (string) { | ||
token_type = "string"; | ||
if (expectingPattern) { | ||
const regex = string.match(regexExtractor); | ||
if (!regex) { | ||
return null; | ||
} | ||
else { | ||
const pattern = regex[1]; | ||
const options = regex[2]; | ||
const cmpString = tokens.pop(); | ||
// tokens.push(`(${cmpString}.search(/${pattern}/${options}) !== -1)`) | ||
tokens.push(`/${pattern}/${options}.test(${cmpString})`); | ||
} | ||
expectingPattern = false; | ||
} | ||
else { | ||
tokens.push(`'${string}'`); | ||
} | ||
const conditionBefore = condition | ||
.substring(startingIndex, token.index) | ||
.trim(); | ||
const res = await handleString(conditionBefore); | ||
valid = validityCheck(res, prevSeperator); | ||
prevSeperator = seperator; | ||
// Move onto the next one | ||
startingIndex = token.index + seperator.length; | ||
} | ||
const finalRes = await handleString(condition.substring(startingIndex).trim()); | ||
valid = validityCheck(finalRes, prevSeperator); | ||
return valid; | ||
} | ||
/** | ||
* Takes a condition string and splits it into seperate parts based off | ||
* brackets amd passes the splits along to ${esi_seperator_splitter} and then | ||
* returns the result of the condition | ||
* | ||
* @param {string} condition conditional string to split | ||
* @returns {boolean} condition result | ||
*/ | ||
async function esi_bracket_splitter(condition) { | ||
let parsedPoint = 0; | ||
let startingIndex = 0; | ||
let endIndex = -1; | ||
let depth = 0; | ||
const fullExpression = []; | ||
const tokensSplit = condition.matchAll(reg_esi_brackets); | ||
for (const token of tokensSplit) { | ||
if (!token[1]) | ||
continue; | ||
const bracket = token[1]; | ||
if (bracket == "(") { | ||
if (depth == 0) | ||
startingIndex = token.index; | ||
depth = depth + 1; | ||
} | ||
else if (operator) { | ||
token_type = "operator"; | ||
if (operator == "=~") { | ||
if (prev_type == "operator") { | ||
return null; | ||
} | ||
else { | ||
expectingPattern = true; | ||
} | ||
if (bracket == ")") { | ||
// bail out if its invalid depth | ||
if (depth == 0) | ||
return false; | ||
depth = depth - 1; | ||
// Right we have a full bracketed set | ||
if (depth == 0) { | ||
endIndex = token.index; | ||
fullExpression.push(condition.substring(parsedPoint, startingIndex)); | ||
const conditionBracketed = condition.substring(startingIndex + 1, endIndex); | ||
// Loop it back to see if there is another bracket inside | ||
const bracketResult = await esi_bracket_splitter(conditionBracketed); | ||
fullExpression.push(bracketResult.toString()); | ||
// Know were we are up too | ||
parsedPoint = endIndex + 1; | ||
} | ||
else { | ||
tokens.push(op_replacements[operator] || operator); | ||
} | ||
} | ||
if (prev_type !== "nil") { | ||
if (!lexer_rules[prev_type][token_type]) { | ||
return null; | ||
} | ||
} | ||
// Derefence it | ||
prev_type = `${token_type}`; | ||
} | ||
// If we havent got a regex yet but we're expecting one | ||
// fail. | ||
if (expectingPattern) { | ||
return null; | ||
// If we didnt have any then push it all along | ||
// Otherwise push the leftovers and return | ||
if (endIndex == -1) { | ||
return await esi_seperator_splitter(condition); | ||
} | ||
return tokens.join(" "); | ||
else { | ||
fullExpression.push(condition.substring(endIndex + 1)); | ||
return await esi_seperator_splitter(fullExpression.join("")); | ||
} | ||
} | ||
@@ -213,14 +304,4 @@ /** | ||
condition = replace_vars(esiData, condition, esi_eval_var_in_when_tag); | ||
const compiledCondition = await _esi_condition_lexer(condition); | ||
if (!compiledCondition) { | ||
return false; | ||
} | ||
try { | ||
const ret = Function(`"use strict"; return( ${compiledCondition} )`)(); | ||
return ret; | ||
} | ||
catch (err) { | ||
return false; | ||
} | ||
return await esi_bracket_splitter(condition); | ||
} | ||
//# sourceMappingURL=processConditionals.js.map |
@@ -31,3 +31,3 @@ import { replace_vars } from "./processESIVars"; | ||
// Push anything from before the tag into the response | ||
const before = chunk.substring(retFrom, includeMatch.index); | ||
const before = chunk.substring(0, includeMatch.index); | ||
res.push(evalVars ? replace_vars(eventData, before) : before); | ||
@@ -34,0 +34,0 @@ // Keep the remainder for next chunk |
{ | ||
"name": "cloudflare-esi", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"repository": "cdloh/cloudflare-esi", | ||
@@ -17,4 +17,27 @@ "description": "ESI Parser built to run in Cloudflare workers", | ||
"prepack": "npm run-script test && npm run-script lint && npm run-script build", | ||
"pretty": "prettier --check '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'" | ||
"pretty": "prettier --check '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'", | ||
"semantic-release": "semantic-release" | ||
}, | ||
"release": { | ||
"branches": [ | ||
"main" | ||
], | ||
"plugins": [ | ||
"@semantic-release/release-notes-generator", | ||
"@semantic-release/changelog", | ||
"@semantic-release/github", | ||
"@semantic-release/npm", | ||
[ | ||
"@semantic-release/git", | ||
{ | ||
"assets": [ | ||
"package.json", | ||
"package-lock.json", | ||
"CHANGELOG.md" | ||
], | ||
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" | ||
} | ||
] | ||
] | ||
}, | ||
"keywords": [ | ||
@@ -46,3 +69,9 @@ "serviceworker", | ||
"rules": { | ||
"jsdoc/no-undefined-types": 0 | ||
"jsdoc/no-undefined-types": 0, | ||
"no-new-func": [ | ||
"error" | ||
], | ||
"no-eval": [ | ||
"error" | ||
] | ||
} | ||
@@ -52,10 +81,16 @@ }, | ||
"@cloudflare/workers-types": "^3.1.1", | ||
"@semantic-release/changelog": "^6.0.1", | ||
"@semantic-release/commit-analyzer": "^9.0.2", | ||
"@semantic-release/git": "^10.0.1", | ||
"@semantic-release/github": "^8.0.5", | ||
"@semantic-release/npm": "^9.0.1", | ||
"@semantic-release/release-notes-generator": "^10.0.3", | ||
"@types/jest": "^27.0.2", | ||
"@typescript-eslint/eslint-plugin": "^5.10.0", | ||
"@typescript-eslint/parser": "^5.10.0", | ||
"esbuild": "^0.14.11", | ||
"esbuild": "^0.15.5", | ||
"eslint": "^8.7.0", | ||
"eslint-config-prettier": "^8.1.0", | ||
"eslint-config-typescript": "^3.0.0", | ||
"eslint-plugin-jsdoc": "^37.6.1", | ||
"eslint-plugin-jsdoc": "^39.3.6", | ||
"jest": "^27.3.1", | ||
@@ -65,2 +100,3 @@ "jest-environment-miniflare": "^2.0.0-rc.4", | ||
"prettier": "^2.4.1", | ||
"semantic-release": "^19.0.4", | ||
"ts-jest": "^27.0.7", | ||
@@ -67,0 +103,0 @@ "typescript": "^4.4.4" |
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 not supported yet
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
103219
1485
1
22