-
-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathindex.js
More file actions
executable file
·161 lines (141 loc) · 5.55 KB
/
Copy pathindex.js
File metadata and controls
executable file
·161 lines (141 loc) · 5.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
'use strict'
const hasBuffer = typeof Buffer !== 'undefined'
const suspectProtoRx = /"(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])"\s*:/
const suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/
/**
* @description Internal parse function that parses JSON text with security checks.
* @private
* @param {string|Buffer} text - The JSON text string or Buffer to parse.
* @param {Function} [reviver] - The JSON.parse() optional reviver argument.
* @param {import('./types').ParseOptions} [options] - Optional configuration object.
* @returns {*} The parsed object.
* @throws {SyntaxError} If a forbidden prototype property is found and `options.protoAction` or
* `options.constructorAction` is `'error'`.
*/
function _parse (text, reviver, options) {
// Normalize arguments
if (options == null) {
if (reviver !== null && typeof reviver === 'object') {
options = reviver
reviver = undefined
}
}
if (hasBuffer && Buffer.isBuffer(text)) {
text = text.toString()
}
// BOM checker
if (text && text.charCodeAt(0) === 0xFEFF) {
text = text.slice(1)
}
// Parse normally, allowing exceptions
const obj = JSON.parse(text, reviver)
// Ignore null and non-objects
if (obj === null || typeof obj !== 'object') {
return obj
}
const protoAction = (options && options.protoAction) || 'error'
const constructorAction = (options && options.constructorAction) || 'error'
// options: 'error' (default) / 'remove' / 'ignore'
if (protoAction === 'ignore' && constructorAction === 'ignore') {
return obj
}
if (protoAction !== 'ignore' && constructorAction !== 'ignore') {
if (suspectProtoRx.test(text) === false && suspectConstructorRx.test(text) === false) {
return obj
}
} else if (protoAction !== 'ignore' && constructorAction === 'ignore') {
if (suspectProtoRx.test(text) === false) {
return obj
}
} else {
if (suspectConstructorRx.test(text) === false) {
return obj
}
}
// Scan result for proto keys
return filter(obj, { protoAction, constructorAction, safe: options && options.safe })
}
/**
* @description Scans and filters an object for forbidden prototype properties.
* @param {Object} obj - The object being scanned.
* @param {import('./types').ParseOptions} [options] - Optional configuration object.
* @returns {Object|null} The filtered object, or `null` if safe mode is enabled and issues are found.
* @throws {SyntaxError} If a forbidden prototype property is found and `options.protoAction` or
* `options.constructorAction` is `'error'`.
*/
function filter (obj, { protoAction = 'error', constructorAction = 'error', safe } = {}) {
let next = [obj]
while (next.length) {
const nodes = next
next = []
for (const node of nodes) {
if (protoAction !== 'ignore' && Object.prototype.hasOwnProperty.call(node, '__proto__')) { // Avoid calling node.hasOwnProperty directly
if (safe === true) {
return null
} else if (protoAction === 'error') {
throw new SyntaxError('Object contains forbidden prototype property')
}
delete node.__proto__ // eslint-disable-line no-proto
}
if (constructorAction !== 'ignore' &&
Object.prototype.hasOwnProperty.call(node, 'constructor') &&
node.constructor !== null &&
typeof node.constructor === 'object' &&
Object.prototype.hasOwnProperty.call(node.constructor, 'prototype')) { // Avoid calling node.hasOwnProperty directly
if (safe === true) {
return null
} else if (constructorAction === 'error') {
throw new SyntaxError('Object contains forbidden prototype property')
}
delete node.constructor
}
for (const key in node) {
const value = node[key]
if (value && typeof value === 'object') {
next.push(value)
}
}
}
}
return obj
}
/**
* @description Parses a given JSON-formatted text into an object.
* @param {string|Buffer} text - The JSON text string or Buffer to parse.
* @param {Function} [reviver] - The `JSON.parse()` optional reviver argument, or options object.
* @param {import('./types').ParseOptions} [options] - Optional configuration object.
* @returns {*} The parsed object.
* @throws {SyntaxError} If the JSON text is malformed or contains forbidden prototype properties
* when `options.protoAction` or `options.constructorAction` is `'error'`.
*/
function parse (text, reviver, options) {
const { stackTraceLimit } = Error
Error.stackTraceLimit = 0
try {
return _parse(text, reviver, options)
} finally {
Error.stackTraceLimit = stackTraceLimit
}
}
/**
* @description Safely parses a given JSON-formatted text into an object.
* @param {string|Buffer} text - The JSON text string or Buffer to parse.
* @param {Function} [reviver] - The `JSON.parse()` optional reviver argument.
* @returns {*|null|undefined} The parsed object, `null` if security issues found, or `undefined` on parse error.
*/
function safeParse (text, reviver) {
const { stackTraceLimit } = Error
Error.stackTraceLimit = 0
try {
return _parse(text, reviver, { safe: true })
} catch {
return undefined
} finally {
Error.stackTraceLimit = stackTraceLimit
}
}
module.exports = parse
module.exports.default = parse
module.exports.parse = parse
module.exports.safeParse = safeParse
module.exports.scan = filter