XuqmGroup-RNSDK/packages/common/security/canonical-json.js

47 行
1.8 KiB
JavaScript

'use strict'
/**
* 安全协议唯一使用的 canonical JSON。对象键按字典序排列,null/undefined 字段省略;
* 非 JSON 协议值、数组空位和不确定的对象形态直接拒绝。
*/
function canonicalJson(value) {
if (value === null) return 'null'
if (typeof value === 'string' || typeof value === 'boolean') return JSON.stringify(value)
if (typeof value === 'number') {
if (!Number.isFinite(value)) throw new TypeError('Canonical JSON requires finite numbers')
return JSON.stringify(value)
}
if (Array.isArray(value)) {
for (let index = 0; index < value.length; index += 1) {
if (!Object.prototype.hasOwnProperty.call(value, index) || value[index] === undefined) {
throw new TypeError('Canonical JSON arrays cannot contain holes or undefined')
}
}
return `[${value.map(canonicalJson).join(',')}]`
}
if (value && typeof value === 'object') {
const prototype = Object.getPrototypeOf(value)
if (prototype !== Object.prototype && prototype !== null) {
throw new TypeError('Canonical JSON only supports plain objects')
}
const keys = Reflect.ownKeys(value)
if (keys.some(key => typeof key === 'symbol')) {
throw new TypeError('Canonical JSON does not support symbol keys')
}
for (const key of keys) {
const descriptor = Object.getOwnPropertyDescriptor(value, key)
if (!descriptor?.enumerable || !('value' in descriptor)) {
throw new TypeError('Canonical JSON only supports enumerable data properties')
}
}
return `{${keys
.filter(key => value[key] !== null && value[key] !== undefined)
.sort()
.map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
.join(',')}}`
}
throw new TypeError(`Canonical JSON does not support ${typeof value}`)
}
module.exports = { canonicalJson }