292 lines
9.2 KiB
JavaScript
292 lines
9.2 KiB
JavaScript
'use strict'
|
|
|
|
module.exports = {
|
|
groupRedact,
|
|
groupRestore,
|
|
nestedRedact,
|
|
nestedRestore
|
|
}
|
|
|
|
function groupRestore ({ keys, values, target }) {
|
|
if (target == null || typeof target === 'string') return
|
|
const length = keys.length
|
|
for (var i = 0; i < length; i++) {
|
|
const k = keys[i]
|
|
target[k] = values[i]
|
|
}
|
|
}
|
|
|
|
function groupRedact (o, path, censor, isCensorFct, censorFctTakesPath) {
|
|
const target = get(o, path)
|
|
if (target == null || typeof target === 'string') return { keys: null, values: null, target, flat: true }
|
|
const keys = Object.keys(target)
|
|
const keysLength = keys.length
|
|
const pathLength = path.length
|
|
const pathWithKey = censorFctTakesPath ? [...path] : undefined
|
|
const values = new Array(keysLength)
|
|
|
|
for (var i = 0; i < keysLength; i++) {
|
|
const key = keys[i]
|
|
values[i] = target[key]
|
|
|
|
if (censorFctTakesPath) {
|
|
pathWithKey[pathLength] = key
|
|
target[key] = censor(target[key], pathWithKey)
|
|
} else if (isCensorFct) {
|
|
target[key] = censor(target[key])
|
|
} else {
|
|
target[key] = censor
|
|
}
|
|
}
|
|
return { keys, values, target, flat: true }
|
|
}
|
|
|
|
/**
|
|
* @param {RestoreInstruction[]} instructions a set of instructions for restoring values to objects
|
|
*/
|
|
function nestedRestore (instructions) {
|
|
for (let i = 0; i < instructions.length; i++) {
|
|
const { target, path, value } = instructions[i]
|
|
let current = target
|
|
for (let i = path.length - 1; i > 0; i--) {
|
|
current = current[path[i]]
|
|
}
|
|
current[path[0]] = value
|
|
}
|
|
}
|
|
|
|
function nestedRedact (store, o, path, ns, censor, isCensorFct, censorFctTakesPath) {
|
|
const target = get(o, path)
|
|
if (target == null) return
|
|
const keys = Object.keys(target)
|
|
const keysLength = keys.length
|
|
for (var i = 0; i < keysLength; i++) {
|
|
const key = keys[i]
|
|
specialSet(store, target, key, path, ns, censor, isCensorFct, censorFctTakesPath)
|
|
}
|
|
return store
|
|
}
|
|
|
|
function has (obj, prop) {
|
|
return obj !== undefined && obj !== null
|
|
? ('hasOwn' in Object ? Object.hasOwn(obj, prop) : Object.prototype.hasOwnProperty.call(obj, prop))
|
|
: false
|
|
}
|
|
|
|
function specialSet (store, o, k, path, afterPath, censor, isCensorFct, censorFctTakesPath) {
|
|
const afterPathLen = afterPath.length
|
|
const lastPathIndex = afterPathLen - 1
|
|
const originalKey = k
|
|
var i = -1
|
|
var n
|
|
var nv
|
|
var ov
|
|
var oov = null
|
|
var wc = null
|
|
var kIsWc
|
|
var wcov
|
|
var consecutive = false
|
|
var level = 0
|
|
// need to track depth of the `redactPath` tree
|
|
var depth = 0
|
|
var redactPathCurrent = tree()
|
|
ov = n = o[k]
|
|
if (typeof n !== 'object') return
|
|
while (n != null && ++i < afterPathLen) {
|
|
depth += 1
|
|
k = afterPath[i]
|
|
oov = ov
|
|
if (k !== '*' && !wc && !(typeof n === 'object' && k in n)) {
|
|
break
|
|
}
|
|
if (k === '*') {
|
|
if (wc === '*') {
|
|
consecutive = true
|
|
}
|
|
wc = k
|
|
if (i !== lastPathIndex) {
|
|
continue
|
|
}
|
|
}
|
|
if (wc) {
|
|
const wcKeys = Object.keys(n)
|
|
for (var j = 0; j < wcKeys.length; j++) {
|
|
const wck = wcKeys[j]
|
|
wcov = n[wck]
|
|
kIsWc = k === '*'
|
|
if (consecutive) {
|
|
redactPathCurrent = node(redactPathCurrent, wck, depth)
|
|
level = i
|
|
ov = iterateNthLevel(wcov, level - 1, k, path, afterPath, censor, isCensorFct, censorFctTakesPath, originalKey, n, nv, ov, kIsWc, wck, i, lastPathIndex, redactPathCurrent, store, o[originalKey], depth + 1)
|
|
} else {
|
|
if (kIsWc || (typeof wcov === 'object' && wcov !== null && k in wcov)) {
|
|
if (kIsWc) {
|
|
ov = wcov
|
|
} else {
|
|
ov = wcov[k]
|
|
}
|
|
nv = (i !== lastPathIndex)
|
|
? ov
|
|
: (isCensorFct
|
|
? (censorFctTakesPath ? censor(ov, [...path, originalKey, ...afterPath]) : censor(ov))
|
|
: censor)
|
|
if (kIsWc) {
|
|
const rv = restoreInstr(node(redactPathCurrent, wck, depth), ov, o[originalKey])
|
|
store.push(rv)
|
|
n[wck] = nv
|
|
} else {
|
|
if (wcov[k] === nv) {
|
|
// pass
|
|
} else if ((nv === undefined && censor !== undefined) || (has(wcov, k) && nv === ov)) {
|
|
redactPathCurrent = node(redactPathCurrent, wck, depth)
|
|
} else {
|
|
redactPathCurrent = node(redactPathCurrent, wck, depth)
|
|
const rv = restoreInstr(node(redactPathCurrent, k, depth + 1), ov, o[originalKey])
|
|
store.push(rv)
|
|
wcov[k] = nv
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
wc = null
|
|
} else {
|
|
ov = n[k]
|
|
redactPathCurrent = node(redactPathCurrent, k, depth)
|
|
nv = (i !== lastPathIndex)
|
|
? ov
|
|
: (isCensorFct
|
|
? (censorFctTakesPath ? censor(ov, [...path, originalKey, ...afterPath]) : censor(ov))
|
|
: censor)
|
|
if ((has(n, k) && nv === ov) || (nv === undefined && censor !== undefined)) {
|
|
// pass
|
|
} else {
|
|
const rv = restoreInstr(redactPathCurrent, ov, o[originalKey])
|
|
store.push(rv)
|
|
n[k] = nv
|
|
}
|
|
n = n[k]
|
|
}
|
|
if (typeof n !== 'object') break
|
|
// prevent circular structure, see https://github.com/pinojs/pino/issues/1513
|
|
if (ov === oov || typeof ov === 'undefined') {
|
|
// pass
|
|
}
|
|
}
|
|
}
|
|
|
|
function get (o, p) {
|
|
var i = -1
|
|
var l = p.length
|
|
var n = o
|
|
while (n != null && ++i < l) {
|
|
n = n[p[i]]
|
|
}
|
|
return n
|
|
}
|
|
|
|
function iterateNthLevel (wcov, level, k, path, afterPath, censor, isCensorFct, censorFctTakesPath, originalKey, n, nv, ov, kIsWc, wck, i, lastPathIndex, redactPathCurrent, store, parent, depth) {
|
|
if (level === 0) {
|
|
if (kIsWc || (typeof wcov === 'object' && wcov !== null && k in wcov)) {
|
|
if (kIsWc) {
|
|
ov = wcov
|
|
} else {
|
|
ov = wcov[k]
|
|
}
|
|
nv = (i !== lastPathIndex)
|
|
? ov
|
|
: (isCensorFct
|
|
? (censorFctTakesPath ? censor(ov, [...path, originalKey, ...afterPath]) : censor(ov))
|
|
: censor)
|
|
if (kIsWc) {
|
|
const rv = restoreInstr(redactPathCurrent, ov, parent)
|
|
store.push(rv)
|
|
n[wck] = nv
|
|
} else {
|
|
if (wcov[k] === nv) {
|
|
// pass
|
|
} else if ((nv === undefined && censor !== undefined) || (has(wcov, k) && nv === ov)) {
|
|
// pass
|
|
} else {
|
|
const rv = restoreInstr(node(redactPathCurrent, k, depth + 1), ov, parent)
|
|
store.push(rv)
|
|
wcov[k] = nv
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const key in wcov) {
|
|
if (typeof wcov[key] === 'object') {
|
|
redactPathCurrent = node(redactPathCurrent, key, depth)
|
|
iterateNthLevel(wcov[key], level - 1, k, path, afterPath, censor, isCensorFct, censorFctTakesPath, originalKey, n, nv, ov, kIsWc, wck, i, lastPathIndex, redactPathCurrent, store, parent, depth + 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @typedef {object} TreeNode
|
|
* @prop {TreeNode} [parent] reference to the parent of this node in the tree, or `null` if there is no parent
|
|
* @prop {string} key the key that this node represents (key here being part of the path being redacted
|
|
* @prop {TreeNode[]} children the child nodes of this node
|
|
* @prop {number} depth the depth of this node in the tree
|
|
*/
|
|
|
|
/**
|
|
* instantiate a new, empty tree
|
|
* @returns {TreeNode}
|
|
*/
|
|
function tree () {
|
|
return { parent: null, key: null, children: [], depth: 0 }
|
|
}
|
|
|
|
/**
|
|
* creates a new node in the tree, attaching it as a child of the provided parent node
|
|
* if the specified depth matches the parent depth, adds the new node as a _sibling_ of the parent instead
|
|
* @param {TreeNode} parent the parent node to add a new node to (if the parent depth matches the provided `depth` value, will instead add as a sibling of this
|
|
* @param {string} key the key that the new node represents (key here being part of the path being redacted)
|
|
* @param {number} depth the depth of the new node in the tree - used to determing whether to add the new node as a child or sibling of the provided `parent` node
|
|
* @returns {TreeNode} a reference to the newly created node in the tree
|
|
*/
|
|
function node (parent, key, depth) {
|
|
if (parent.depth === depth) {
|
|
return node(parent.parent, key, depth)
|
|
}
|
|
|
|
var child = {
|
|
parent,
|
|
key,
|
|
depth,
|
|
children: []
|
|
}
|
|
|
|
parent.children.push(child)
|
|
|
|
return child
|
|
}
|
|
|
|
/**
|
|
* @typedef {object} RestoreInstruction
|
|
* @prop {string[]} path a reverse-order path that can be used to find the correct insertion point to restore a `value` for the given `parent` object
|
|
* @prop {*} value the value to restore
|
|
* @prop {object} target the object to restore the `value` in
|
|
*/
|
|
|
|
/**
|
|
* create a restore instruction for the given redactPath node
|
|
* generates a path in reverse order by walking up the redactPath tree
|
|
* @param {TreeNode} node a tree node that should be at the bottom of the redact path (i.e. have no children) - this will be used to walk up the redact path tree to construct the path needed to restore
|
|
* @param {*} value the value to restore
|
|
* @param {object} target a reference to the parent object to apply the restore instruction to
|
|
* @returns {RestoreInstruction} an instruction used to restore a nested value for a specific object
|
|
*/
|
|
function restoreInstr (node, value, target) {
|
|
let current = node
|
|
const path = []
|
|
do {
|
|
path.push(current.key)
|
|
current = current.parent
|
|
} while (current.parent != null)
|
|
|
|
return { path, value, target }
|
|
}
|