Files
powerpoint-toolbox/node_modules/@exodus/schemasafe/src/safe-format.js
T
2025-03-07 19:22:02 +01:00

99 lines
4.2 KiB
JavaScript

'use strict'
class SafeString extends String {} // used for instanceof checks
const compares = new Set(['<', '>', '<=', '>='])
const escapeCode = (code) => `\\u${code.toString(16).padStart(4, '0')}`
// Supports simple js variables only, i.e. constants and JSON-stringifiable
// Converts a variable to be safe for inclusion in JS context
// This works on top of JSON.stringify with minor fixes to negate the JS/JSON parsing differences
const jsval = (val) => {
if ([Infinity, -Infinity, NaN, undefined, null].includes(val)) return `${val}`
const primitive = ['string', 'boolean', 'number'].includes(typeof val)
if (!primitive) {
if (typeof val !== 'object') throw new Error('Unexpected value type')
const proto = Object.getPrototypeOf(val)
const ok = (proto === Array.prototype && Array.isArray(val)) || proto === Object.prototype
if (!ok) throw new Error('Unexpected object given as value')
}
return (
JSON.stringify(val)
// JSON context and JS eval context have different handling of __proto__ property name
// Refs: https://www.ecma-international.org/ecma-262/#sec-json.parse
// Refs: https://www.ecma-international.org/ecma-262/#sec-__proto__-property-names-in-object-initializers
// Replacement is safe because it's the only way that encodes __proto__ property in JSON and
// it can't occur inside strings or other properties, due to the leading `"` and traling `":`
.replace(/([{,])"__proto__":/g, '$1["__proto__"]:')
// The above line should cover all `"__proto__":` occurances except for `"...\"__proto__":`
.replace(/[^\\]"__proto__":/g, () => {
/* c8 ignore next */
throw new Error('Unreachable')
})
// https://v8.dev/features/subsume-json#security, e.g. {'\u2028':0} on Node.js 8
.replace(/[\u2028\u2029]/g, (char) => escapeCode(char.charCodeAt(0)))
)
}
const format = (fmt, ...args) => {
const res = fmt.replace(/%[%drscjw]/g, (match) => {
if (match === '%%') return '%'
if (args.length === 0) throw new Error('Unexpected arguments count')
const val = args.shift()
switch (match) {
case '%d':
if (typeof val === 'number') return val
throw new Error('Expected a number')
case '%r':
// String(regex) is not ok on Node.js 10 and below: console.log(String(new RegExp('\n')))
if (val instanceof RegExp) return format('new RegExp(%j, %j)', val.source, val.flags)
throw new Error('Expected a RegExp instance')
case '%s':
if (val instanceof SafeString) return val
throw new Error('Expected a safe string')
case '%c':
if (compares.has(val)) return val
throw new Error('Expected a compare op')
case '%j':
return jsval(val)
case '%w':
if (Number.isInteger(val) && val >= 0) return ' '.repeat(val)
throw new Error('Expected a non-negative integer for indentation')
}
/* c8 ignore next */
throw new Error('Unreachable')
})
if (args.length !== 0) throw new Error('Unexpected arguments count')
return new SafeString(res)
}
const safe = (string) => {
if (!/^[a-z][a-z0-9_]*$/i.test(string)) throw new Error('Does not look like a safe id')
return new SafeString(string)
}
// too dangereous to export, use with care
const safewrap = (fun) => (...args) => {
if (!args.every((arg) => arg instanceof SafeString)) throw new Error('Unsafe arguments')
return new SafeString(fun(...args))
}
const safepriority = (arg) =>
// simple expression and single brackets can not break priority
/^[a-z][a-z0-9_().]*$/i.test(arg) || /^\([^()]+\)$/i.test(arg) ? arg : format('(%s)', arg)
const safeor = safewrap(
(...args) => (args.some((arg) => `${arg}` === 'true') ? 'true' : args.join(' || ') || 'false')
)
const safeand = safewrap(
(...args) => (args.some((arg) => `${arg}` === 'false') ? 'false' : args.join(' && ') || 'true')
)
const safenot = (arg) => {
if (`${arg}` === 'true') return safe('false')
if (`${arg}` === 'false') return safe('true')
return format('!%s', safepriority(arg))
}
// this function is priority-safe, unlike safeor, hence it's exported and safeor is not atm
const safenotor = (...args) => safenot(safeor(...args))
module.exports = { format, safe, safeand, safenot, safenotor }