honkit
Version:
HonKit is building beautiful books using Markdown.
94 lines (93 loc) • 3.06 kB
JavaScript
;
/**
* Minimal dot-path get/set with prototype-pollution guards.
* Replaces object-path for config key access (e.g. "plugins", "structure.readme").
*
* Behavior is kept compatible with the previous object-path wrapper:
* - get / set accept `string | string[]`
* - lookups only descend through own properties (matches object-path)
* - set throws when an intermediate value is a non-object scalar (string,
* number, boolean, null) — null follows from native JS, the others match
* object-path's check
* - set returns undefined
* - paths containing __proto__ / constructor / prototype are rejected as a
* whole, so e.g. "__proto__.polluted" does not collapse to "polluted"
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAtPath = getAtPath;
exports.setAtPath = setAtPath;
const UNSAFE = new Set(["__proto__", "constructor", "prototype"]);
function toSegments(path) {
let parts;
if (Array.isArray(path)) {
// Array paths are caller-supplied keys — empty string is a valid property name
parts = path.map((s) => String(s));
}
else {
// String paths are dot-delimited; trim whitespace and drop empty split artifacts
parts = String(path)
.split(".")
.map((s) => s.trim())
.filter((s) => s.length > 0);
}
if (parts.length === 0) {
return null;
}
for (const part of parts) {
if (UNSAFE.has(part)) {
return null;
}
}
return parts;
}
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
function getAtPath(obj, path, defaultValue) {
if (obj == null || path == null) {
return defaultValue;
}
const parts = toSegments(path);
if (parts === null) {
return defaultValue;
}
let cur = obj;
for (const key of parts) {
if (cur == null || typeof cur !== "object") {
return defaultValue;
}
if (!hasOwn(cur, key)) {
return defaultValue;
}
cur = cur[key];
}
return cur === undefined ? defaultValue : cur;
}
function setAtPath(obj, path, value) {
if (obj == null || path == null) {
return;
}
const parts = toSegments(path);
if (parts === null) {
return;
}
let cur = obj;
for (let i = 0; i < parts.length - 1; i++) {
const key = parts[i];
if (!hasOwn(cur, key) || cur[key] === undefined) {
const nextKey = parts[i + 1];
const created = /^\d+$/.test(nextKey) ? [] : {};
cur[key] = created;
cur = created;
continue;
}
const ownNext = cur[key];
if (ownNext === null || typeof ownNext !== "object") {
throw new Error(`safeObjectPath.setAtPath: cannot set "${parts.join(".")}" because intermediate "${parts
.slice(0, i + 1)
.join(".")}" is not an object`);
}
cur = ownNext;
}
cur[parts[parts.length - 1]] = value;
}