@pdfme/common
Version:
TypeScript base PDF generator and React base UI. Open source, developed by the community, and completely free to use under the MIT license!
463 lines • 17.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.replacePlaceholders = void 0;
const acorn = __importStar(require("acorn"));
const expressionCache = new Map();
const parseDataCache = new Map();
const parseData = (data) => {
const key = JSON.stringify(data);
if (parseDataCache.has(key)) {
return parseDataCache.get(key);
}
const parsed = Object.fromEntries(Object.entries(data).map(([key, value]) => {
if (typeof value === 'string') {
try {
const parsedValue = JSON.parse(value);
return [key, parsedValue];
}
catch {
return [key, value];
}
}
return [key, value];
}));
parseDataCache.set(key, parsed);
return parsed;
};
const padZero = (num) => String(num).padStart(2, '0');
const formatDate = (date) => `${date.getFullYear()}/${padZero(date.getMonth() + 1)}/${padZero(date.getDate())}`;
const formatDateTime = (date) => `${formatDate(date)} ${padZero(date.getHours())}:${padZero(date.getMinutes())}`;
// Safe assign function that prevents prototype pollution
const safeAssign = (target, ...sources) => {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
const to = { ...target };
for (const source of sources) {
if (source != null) {
for (const key in source) {
// Skip prototype pollution keys
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}
// Only copy own properties
if (Object.prototype.hasOwnProperty.call(source, key)) {
to[key] = source[key];
}
}
}
}
return to;
};
// Create a safe copy of Object with dangerous methods excluded
const safeObject = {
keys: Object.keys,
values: Object.values,
entries: Object.entries,
fromEntries: Object.fromEntries,
is: Object.is,
hasOwnProperty: Object.hasOwnProperty,
assign: safeAssign, // Safe version of Object.assign
// The following methods are excluded due to security concerns:
// - Side effects: create, freeze, seal (can still be used for attacks)
// - Prototype access: getOwnPropertyDescriptor, getPrototypeOf, setPrototypeOf,
// defineProperty, defineProperties, getOwnPropertyNames, getOwnPropertySymbols
};
const allowedGlobals = {
Math,
String,
Number,
Boolean,
Array,
Object: safeObject,
Date,
JSON,
isNaN,
parseFloat,
parseInt,
decodeURI,
decodeURIComponent,
encodeURI,
encodeURIComponent,
};
const validateAST = (node) => {
switch (node.type) {
case 'Literal':
case 'Identifier':
break;
case 'BinaryExpression':
case 'LogicalExpression': {
const binaryNode = node;
validateAST(binaryNode.left);
validateAST(binaryNode.right);
break;
}
case 'UnaryExpression': {
const unaryNode = node;
validateAST(unaryNode.argument);
break;
}
case 'ConditionalExpression': {
const condNode = node;
validateAST(condNode.test);
validateAST(condNode.consequent);
validateAST(condNode.alternate);
break;
}
case 'MemberExpression': {
const memberNode = node;
validateAST(memberNode.object);
if (memberNode.computed) {
validateAST(memberNode.property);
}
else {
const propName = memberNode.property.name;
if (['constructor', '__proto__', 'prototype'].includes(propName)) {
throw new Error('Access to prohibited property');
}
// Block prototype pollution methods
if (['__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__'].includes(propName)) {
throw new Error(`Access to prohibited method: ${propName}`);
}
const prohibitedMethods = ['toLocaleString', 'valueOf'];
if (typeof propName === 'string' && prohibitedMethods.includes(propName)) {
throw new Error(`Access to prohibited method: ${propName}`);
}
}
break;
}
case 'CallExpression': {
const callNode = node;
validateAST(callNode.callee);
callNode.arguments.forEach(validateAST);
break;
}
case 'ArrayExpression': {
const arrayNode = node;
arrayNode.elements.forEach((elem) => {
if (elem)
validateAST(elem);
});
break;
}
case 'ObjectExpression': {
const objectNode = node;
objectNode.properties.forEach((prop) => {
const propNode = prop;
validateAST(propNode.key);
validateAST(propNode.value);
});
break;
}
case 'ArrowFunctionExpression': {
const arrowFuncNode = node;
arrowFuncNode.params.forEach((param) => {
if (param.type !== 'Identifier') {
throw new Error('Only identifier parameters are supported in arrow functions');
}
validateAST(param);
});
validateAST(arrowFuncNode.body);
break;
}
default:
throw new Error(`Unsupported syntax in placeholder: ${node.type}`);
}
};
const evaluateAST = (node, context) => {
switch (node.type) {
case 'Literal': {
const literalNode = node;
return literalNode.value;
}
case 'Identifier': {
const idNode = node;
if (Object.prototype.hasOwnProperty.call(context, idNode.name)) {
return context[idNode.name];
}
else if (Object.prototype.hasOwnProperty.call(allowedGlobals, idNode.name)) {
return allowedGlobals[idNode.name];
}
else {
throw new Error(`Undefined variable: ${idNode.name}`);
}
}
case 'BinaryExpression': {
const binaryNode = node;
const left = evaluateAST(binaryNode.left, context);
const right = evaluateAST(binaryNode.right, context);
switch (binaryNode.operator) {
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 '==':
return left == right;
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 '>=':
return left >= right;
default:
throw new Error(`Unsupported operator: ${binaryNode.operator}`);
}
}
case 'LogicalExpression': {
const logicalNode = node;
const leftLogical = evaluateAST(logicalNode.left, context);
const rightLogical = evaluateAST(logicalNode.right, context);
switch (logicalNode.operator) {
case '&&':
return leftLogical && rightLogical;
case '||':
return leftLogical || rightLogical;
default:
throw new Error(`Unsupported operator: ${logicalNode.operator}`);
}
}
case 'UnaryExpression': {
const unaryNode = node;
const arg = evaluateAST(unaryNode.argument, context);
switch (unaryNode.operator) {
case '+':
return +arg;
case '-':
return -arg;
case '!':
return !arg;
default:
throw new Error(`Unsupported operator: ${unaryNode.operator}`);
}
}
case 'ConditionalExpression': {
const condNode = node;
const test = evaluateAST(condNode.test, context);
return test
? evaluateAST(condNode.consequent, context)
: evaluateAST(condNode.alternate, context);
}
case 'MemberExpression': {
const memberNode = node;
const obj = evaluateAST(memberNode.object, context);
let prop;
if (memberNode.computed) {
prop = evaluateAST(memberNode.property, context);
}
else {
prop = memberNode.property.name;
}
if (typeof prop === 'string' || typeof prop === 'number') {
if (typeof prop === 'string' && ['constructor', '__proto__', 'prototype'].includes(prop)) {
throw new Error('Access to prohibited property');
}
// Block prototype pollution methods
if (typeof prop === 'string' && ['__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__'].includes(prop)) {
throw new Error(`Access to prohibited method: ${prop}`);
}
return obj[prop];
}
else {
throw new Error('Invalid property access');
}
}
case 'CallExpression': {
const callNode = node;
const callee = evaluateAST(callNode.callee, context);
const args = callNode.arguments.map((argNode) => evaluateAST(argNode, context));
if (typeof callee === 'function') {
if (callNode.callee.type === 'MemberExpression') {
const memberExpr = callNode.callee;
const obj = evaluateAST(memberExpr.object, context);
if (obj !== null &&
(typeof obj === 'object' ||
typeof obj === 'number' ||
typeof obj === 'string' ||
typeof obj === 'boolean')) {
return callee.call(obj, ...args);
}
else {
throw new Error('Invalid object in member function call');
}
}
else {
// Use a type assertion to tell TypeScript this is a safe function call
return callee(...args);
}
}
else {
throw new Error('Attempted to call a non-function');
}
}
case 'ArrowFunctionExpression': {
const arrowFuncNode = node;
const params = arrowFuncNode.params.map((param) => param.name);
const body = arrowFuncNode.body;
return (...args) => {
const newContext = { ...context };
params.forEach((param, index) => {
newContext[param] = args[index];
});
return evaluateAST(body, newContext);
};
}
case 'ArrayExpression': {
const arrayNode = node;
return arrayNode.elements.map((elem) => (elem ? evaluateAST(elem, context) : null));
}
case 'ObjectExpression': {
const objectNode = node;
const objResult = {};
objectNode.properties.forEach((prop) => {
const propNode = prop;
let key;
if (propNode.key.type === 'Identifier') {
key = propNode.key.name;
}
else {
const evaluatedKey = evaluateAST(propNode.key, context);
if (typeof evaluatedKey !== 'string' && typeof evaluatedKey !== 'number') {
throw new Error('Object property keys must be strings or numbers');
}
key = String(evaluatedKey);
}
const value = evaluateAST(propNode.value, context);
objResult[key] = value;
});
return objResult;
}
default:
throw new Error(`Unsupported syntax in placeholder: ${node.type}`);
}
};
const evaluatePlaceholders = (arg) => {
const { content, context } = arg;
let resultContent = '';
let index = 0;
while (index < content.length) {
const startIndex = content.indexOf('{', index);
if (startIndex === -1) {
resultContent += content.slice(index);
break;
}
resultContent += content.slice(index, startIndex);
let braceCount = 1;
let endIndex = startIndex + 1;
while (endIndex < content.length && braceCount > 0) {
if (content[endIndex] === '{') {
braceCount++;
}
else if (content[endIndex] === '}') {
braceCount--;
}
endIndex++;
}
if (braceCount === 0) {
const code = content.slice(startIndex + 1, endIndex - 1).trim();
if (expressionCache.has(code)) {
const evalFunc = expressionCache.get(code);
try {
const value = evalFunc(context);
resultContent += String(value);
}
catch {
resultContent += content.slice(startIndex, endIndex);
}
}
else {
try {
const ast = acorn.parseExpressionAt(code, 0, { ecmaVersion: 'latest' });
validateAST(ast);
const evalFunc = (ctx) => evaluateAST(ast, ctx);
expressionCache.set(code, evalFunc);
const value = evalFunc(context);
resultContent += String(value);
}
catch {
resultContent += content.slice(startIndex, endIndex);
}
}
index = endIndex;
}
else {
throw new Error('Invalid placeholder');
}
}
return resultContent;
};
const replacePlaceholders = (arg) => {
const { content, variables, schemas } = arg;
if (!content || typeof content !== 'string' || !content.includes('{') || !content.includes('}')) {
return content;
}
const date = new Date();
const formattedDate = formatDate(date);
const formattedDateTime = formatDateTime(date);
const data = {
...Object.fromEntries(schemas.flat().map((schema) => [schema.name, schema.readOnly ? schema.content || '' : ''])),
...variables,
};
const parsedInput = parseData(data);
const context = {
date: formattedDate,
dateTime: formattedDateTime,
...parsedInput,
};
Object.entries(context).forEach(([key, value]) => {
if (typeof value === 'string' && value.includes('{') && value.includes('}')) {
context[key] = evaluatePlaceholders({ content: value, context });
}
});
return evaluatePlaceholders({ content, context });
};
exports.replacePlaceholders = replacePlaceholders;
//# sourceMappingURL=expression.js.map