@jsreport/jsreport-core 3.4.1 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/lib/main/blobStorage/blobStorage.js +8 -0
- package/lib/main/logger.js +3 -0
- package/lib/main/optionsLoad.js +10 -2
- package/lib/main/optionsSchema.js +21 -3
- package/lib/main/profiler.js +166 -75
- package/lib/main/reporter.js +113 -78
- package/lib/main/schemaValidator.js +30 -0
- package/lib/main/settings.js +1 -2
- package/lib/main/store/collection.js +10 -8
- package/lib/main/store/documentStore.js +19 -0
- package/lib/main/store/setupValidateId.js +7 -1
- package/lib/main/store/setupValidateShortid.js +4 -0
- package/lib/shared/normalizeMetaFromLogs.js +1 -1
- package/lib/shared/reporter.js +16 -0
- package/lib/worker/render/executeEngine.js +108 -27
- package/lib/worker/render/moduleHelper.js +4 -4
- package/lib/worker/render/profiler.js +20 -13
- package/lib/worker/render/render.js +0 -4
- package/lib/worker/reporter.js +6 -4
- package/lib/worker/sandbox/{safeSandbox.js → createSandbox.js} +85 -52
- package/lib/worker/sandbox/runInSandbox.js +38 -16
- package/package.json +9 -9
- package/lib/main/monitoring.js +0 -92
|
@@ -9,11 +9,13 @@ const LRU = require('lru-cache')
|
|
|
9
9
|
const { nanoid } = require('nanoid')
|
|
10
10
|
|
|
11
11
|
module.exports = (reporter) => {
|
|
12
|
-
const
|
|
12
|
+
const templatesCache = LRU(reporter.options.sandbox.cache)
|
|
13
|
+
let systemHelpersCache
|
|
13
14
|
|
|
14
|
-
reporter.templatingEngines = { cache }
|
|
15
|
+
reporter.templatingEngines = { cache: templatesCache }
|
|
15
16
|
|
|
16
17
|
const executionFnParsedParamsMap = new Map()
|
|
18
|
+
const executionAsyncResultsMap = new Map()
|
|
17
19
|
|
|
18
20
|
const templatingEnginesEvaluate = async (mainCall, { engine, content, helpers, data }, { entity, entitySet }, req) => {
|
|
19
21
|
const engineImpl = reporter.extensionsManager.engines.find((e) => e.name === engine)
|
|
@@ -28,19 +30,23 @@ module.exports = (reporter) => {
|
|
|
28
30
|
executionFnParsedParamsMap.set(req.context.id, new Map())
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
const executionId = nanoid(7)
|
|
34
|
+
|
|
31
35
|
try {
|
|
32
36
|
const res = await executeEngine({
|
|
33
37
|
engine: engineImpl,
|
|
34
38
|
content,
|
|
35
39
|
helpers,
|
|
36
40
|
data
|
|
37
|
-
}, { handleErrors: false, entity, entitySet }, req)
|
|
41
|
+
}, { executionId, handleErrors: false, entity, entitySet }, req)
|
|
38
42
|
|
|
39
43
|
return res.content
|
|
40
44
|
} finally {
|
|
41
45
|
if (mainCall) {
|
|
42
46
|
executionFnParsedParamsMap.delete(req.context.id)
|
|
43
47
|
}
|
|
48
|
+
|
|
49
|
+
executionAsyncResultsMap.delete(executionId)
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
|
|
@@ -54,6 +60,39 @@ module.exports = (reporter) => {
|
|
|
54
60
|
proxy.templatingEngines = {
|
|
55
61
|
evaluate: async (executionInfo, entityInfo) => {
|
|
56
62
|
return templatingEnginesEvaluate(false, executionInfo, entityInfo, req)
|
|
63
|
+
},
|
|
64
|
+
waitForAsyncHelper: async (maybeAsyncContent) => {
|
|
65
|
+
if (
|
|
66
|
+
context.__executionId == null ||
|
|
67
|
+
!executionAsyncResultsMap.has(context.__executionId) ||
|
|
68
|
+
typeof maybeAsyncContent !== 'string'
|
|
69
|
+
) {
|
|
70
|
+
return maybeAsyncContent
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
|
|
74
|
+
const asyncHelperResultRegExp = /{#asyncHelperResult ([^{}]+)}/
|
|
75
|
+
let content = maybeAsyncContent
|
|
76
|
+
let matchResult
|
|
77
|
+
|
|
78
|
+
do {
|
|
79
|
+
if (matchResult != null) {
|
|
80
|
+
const matchedPart = matchResult[0]
|
|
81
|
+
const asyncResultId = matchResult[1]
|
|
82
|
+
const result = await asyncResultMap.get(asyncResultId)
|
|
83
|
+
content = `${content.slice(0, matchResult.index)}${result}${content.slice(matchResult.index + matchedPart.length)}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
matchResult = content.match(asyncHelperResultRegExp)
|
|
87
|
+
} while (matchResult != null)
|
|
88
|
+
|
|
89
|
+
return content
|
|
90
|
+
},
|
|
91
|
+
waitForAsyncHelpers: async () => {
|
|
92
|
+
if (context.__executionId != null && executionAsyncResultsMap.has(context.__executionId)) {
|
|
93
|
+
const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
|
|
94
|
+
return Promise.all([...asyncResultMap.keys()].map((k) => asyncResultMap.get(k)))
|
|
95
|
+
}
|
|
57
96
|
}
|
|
58
97
|
}
|
|
59
98
|
})
|
|
@@ -64,6 +103,8 @@ module.exports = (reporter) => {
|
|
|
64
103
|
req.data.__rootDirectory = reporter.options.rootDirectory
|
|
65
104
|
req.data.__parentModuleDirectory = reporter.options.parentModuleDirectory
|
|
66
105
|
|
|
106
|
+
const executionId = nanoid(7)
|
|
107
|
+
|
|
67
108
|
try {
|
|
68
109
|
return await executeEngine({
|
|
69
110
|
engine,
|
|
@@ -71,55 +112,88 @@ module.exports = (reporter) => {
|
|
|
71
112
|
helpers: req.template.helpers,
|
|
72
113
|
data: req.data
|
|
73
114
|
}, {
|
|
115
|
+
executionId,
|
|
74
116
|
handleErrors: true,
|
|
75
117
|
entity: req.template,
|
|
76
118
|
entitySet: 'templates'
|
|
77
119
|
}, req)
|
|
78
120
|
} finally {
|
|
79
121
|
executionFnParsedParamsMap.delete(req.context.id)
|
|
122
|
+
executionAsyncResultsMap.delete(executionId)
|
|
80
123
|
}
|
|
81
124
|
}
|
|
82
125
|
|
|
83
|
-
async function executeEngine ({ engine, content, helpers, data }, { handleErrors, entity, entitySet }, req) {
|
|
126
|
+
async function executeEngine ({ engine, content, helpers, data }, { executionId, handleErrors, entity, entitySet }, req) {
|
|
84
127
|
let entityPath
|
|
85
128
|
|
|
86
129
|
if (entity._id) {
|
|
87
130
|
entityPath = await reporter.folders.resolveEntityPath(entity, entitySet, req)
|
|
88
131
|
}
|
|
89
132
|
|
|
90
|
-
const
|
|
91
|
-
const
|
|
133
|
+
const normalizedHelpers = `${helpers || ''}`
|
|
134
|
+
const executionFnParsedParamsKey = `entity:${entity.shortid || 'anonymous'}:helpers:${normalizedHelpers}`
|
|
92
135
|
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
136
|
+
const initFn = async (getTopLevelFunctions, compileScript) => {
|
|
137
|
+
if (systemHelpersCache != null) {
|
|
138
|
+
return systemHelpersCache
|
|
96
139
|
}
|
|
97
140
|
|
|
98
|
-
|
|
99
|
-
|
|
141
|
+
const registerResults = await reporter.registerHelpersListeners.fire()
|
|
142
|
+
const systemHelpers = []
|
|
143
|
+
|
|
144
|
+
for (const result of registerResults) {
|
|
145
|
+
if (result == null) {
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof result === 'string') {
|
|
150
|
+
systemHelpers.push(result)
|
|
151
|
+
}
|
|
100
152
|
}
|
|
101
|
-
}
|
|
102
153
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
154
|
+
const systemHelpersStr = systemHelpers.join('\n')
|
|
155
|
+
|
|
156
|
+
const functionNames = getTopLevelFunctions(systemHelpersStr)
|
|
106
157
|
|
|
107
|
-
|
|
158
|
+
const exposeSystemHelpersCode = `for (const fName of ${JSON.stringify(functionNames)}) { this[fName] = __topLevelFunctions[fName] }`
|
|
159
|
+
|
|
160
|
+
// we sync the __topLevelFunctions with system helpers and expose it immediately to the global context
|
|
161
|
+
const userCode = `(async () => { ${systemHelpersStr};
|
|
162
|
+
__topLevelFunctions = {...__topLevelFunctions, ${functionNames.map(h => `"${h}": ${h}`).join(',')}}; ${exposeSystemHelpersCode}
|
|
163
|
+
})()`
|
|
164
|
+
|
|
165
|
+
const filename = 'system-helpers.js'
|
|
166
|
+
const script = compileScript(userCode, filename)
|
|
167
|
+
|
|
168
|
+
systemHelpersCache = {
|
|
169
|
+
filename,
|
|
170
|
+
source: systemHelpersStr,
|
|
171
|
+
script
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return systemHelpersCache
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const executionFn = async ({ require, console, topLevelFunctions, context }) => {
|
|
108
178
|
const asyncResultMap = new Map()
|
|
109
|
-
|
|
179
|
+
|
|
180
|
+
context.__executionId = executionId
|
|
181
|
+
|
|
182
|
+
executionAsyncResultsMap.set(executionId, asyncResultMap)
|
|
183
|
+
executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).resolve({ require, console, topLevelFunctions, context })
|
|
184
|
+
|
|
110
185
|
const key = `template:${content}:${engine.name}`
|
|
111
186
|
|
|
112
|
-
if (!
|
|
187
|
+
if (!templatesCache.has(key)) {
|
|
113
188
|
try {
|
|
114
|
-
|
|
189
|
+
templatesCache.set(key, engine.compile(content, { require }))
|
|
115
190
|
} catch (e) {
|
|
116
191
|
e.property = 'content'
|
|
117
192
|
throw e
|
|
118
193
|
}
|
|
119
194
|
}
|
|
120
195
|
|
|
121
|
-
const compiledTemplate =
|
|
122
|
-
|
|
196
|
+
const compiledTemplate = templatesCache.get(key)
|
|
123
197
|
const wrappedTopLevelFunctions = {}
|
|
124
198
|
|
|
125
199
|
for (const h of Object.keys(topLevelFunctions)) {
|
|
@@ -127,7 +201,9 @@ module.exports = (reporter) => {
|
|
|
127
201
|
}
|
|
128
202
|
|
|
129
203
|
let contentResult = await engine.execute(compiledTemplate, wrappedTopLevelFunctions, data, { require })
|
|
204
|
+
|
|
130
205
|
const resolvedResultsMap = new Map()
|
|
206
|
+
|
|
131
207
|
while (asyncResultMap.size > 0) {
|
|
132
208
|
await Promise.all([...asyncResultMap.keys()].map(async (k) => {
|
|
133
209
|
resolvedResultsMap.set(k, `${await asyncResultMap.get(k)}`)
|
|
@@ -143,26 +219,31 @@ module.exports = (reporter) => {
|
|
|
143
219
|
}
|
|
144
220
|
|
|
145
221
|
return {
|
|
146
|
-
|
|
222
|
+
// handlebars escapes single brackets before execution to prevent errors on {#asset}
|
|
223
|
+
// we need to unescape them later here, because at the moment the engine.execute finishes
|
|
224
|
+
// the async helpers aren't executed yet
|
|
225
|
+
content: engine.unescape ? engine.unescape(contentResult) : contentResult
|
|
147
226
|
}
|
|
148
227
|
}
|
|
149
228
|
|
|
150
229
|
// executionFnParsedParamsMap is there to cache parsed components helpers to speed up longer loops
|
|
151
230
|
// we store there for the particular request and component a promise and only the first component gets compiled
|
|
152
231
|
if (executionFnParsedParamsMap.get(req.context.id).has(executionFnParsedParamsKey)) {
|
|
153
|
-
const { require, console, topLevelFunctions } = await (executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).promise)
|
|
232
|
+
const { require, console, topLevelFunctions, context } = await (executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).promise)
|
|
154
233
|
|
|
155
|
-
return executionFn({ require, console, topLevelFunctions })
|
|
234
|
+
return executionFn({ require, console, topLevelFunctions, context })
|
|
156
235
|
} else {
|
|
157
236
|
const awaiter = {}
|
|
237
|
+
|
|
158
238
|
awaiter.promise = new Promise((resolve) => {
|
|
159
239
|
awaiter.resolve = resolve
|
|
160
240
|
})
|
|
241
|
+
|
|
161
242
|
executionFnParsedParamsMap.get(req.context.id).set(executionFnParsedParamsKey, awaiter)
|
|
162
243
|
}
|
|
163
244
|
|
|
164
245
|
if (reporter.options.sandbox.cache && reporter.options.sandbox.cache.enabled === false) {
|
|
165
|
-
|
|
246
|
+
templatesCache.reset()
|
|
166
247
|
}
|
|
167
248
|
|
|
168
249
|
try {
|
|
@@ -170,10 +251,10 @@ module.exports = (reporter) => {
|
|
|
170
251
|
context: {
|
|
171
252
|
...(engine.createContext ? engine.createContext() : {})
|
|
172
253
|
},
|
|
173
|
-
userCode:
|
|
254
|
+
userCode: normalizedHelpers,
|
|
255
|
+
initFn,
|
|
174
256
|
executionFn,
|
|
175
257
|
currentPath: entityPath,
|
|
176
|
-
errorLineNumberOffset: systemHelpersStr.split('\n').length,
|
|
177
258
|
onRequire: (moduleName, { context }) => {
|
|
178
259
|
if (engine.onRequire) {
|
|
179
260
|
return engine.onRequire(moduleName, { context })
|
|
@@ -4,7 +4,7 @@ const path = require('path')
|
|
|
4
4
|
module.exports = (reporter) => {
|
|
5
5
|
let helpersScript
|
|
6
6
|
|
|
7
|
-
reporter.registerHelpersListeners.add('core-helpers', (
|
|
7
|
+
reporter.registerHelpersListeners.add('core-helpers', () => {
|
|
8
8
|
return helpersScript
|
|
9
9
|
})
|
|
10
10
|
|
|
@@ -12,11 +12,11 @@ module.exports = (reporter) => {
|
|
|
12
12
|
helpersScript = await fs.readFile(path.join(__dirname, '../../static/helpers.js'), 'utf8')
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
-
reporter.extendProxy((proxy, req, {
|
|
15
|
+
reporter.extendProxy((proxy, req, { sandboxRequire }) => {
|
|
16
16
|
proxy.module = async (module) => {
|
|
17
|
-
if (!reporter.options.
|
|
17
|
+
if (!reporter.options.trustUserCode && reporter.options.sandbox.allowedModules !== '*') {
|
|
18
18
|
if (reporter.options.sandbox.allowedModules.indexOf(module) === -1) {
|
|
19
|
-
throw reporter.createError(`require of module ${module} was rejected. Either set
|
|
19
|
+
throw reporter.createError(`require of module ${module} was rejected. Either set trustUserCode=true or sandbox.allowLocalModules='*' or sandbox.allowLocalModules=['${module}'] `, { status: 400 })
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -57,9 +57,9 @@ class Profiler {
|
|
|
57
57
|
|
|
58
58
|
if (m.type !== 'log') {
|
|
59
59
|
req.context.profiling.lastEventId = m.id
|
|
60
|
+
m.operationId = m.operationId || generateRequestId()
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
m.operationId = m.operationId || generateRequestId()
|
|
63
63
|
if (m.previousOperationId == null && req.context.profiling.lastOperationId) {
|
|
64
64
|
m.previousOperationId = req.context.profiling.lastOperationId
|
|
65
65
|
}
|
|
@@ -72,15 +72,21 @@ class Profiler {
|
|
|
72
72
|
let content = res.content
|
|
73
73
|
|
|
74
74
|
if (content != null) {
|
|
75
|
-
if (
|
|
75
|
+
if (content.length > this.reporter.options.profiler.maxDiffSize) {
|
|
76
76
|
content = {
|
|
77
|
-
|
|
78
|
-
encoding: 'base64'
|
|
77
|
+
tooLarge: true
|
|
79
78
|
}
|
|
80
79
|
} else {
|
|
81
|
-
content
|
|
82
|
-
content
|
|
83
|
-
|
|
80
|
+
if (isbinaryfile(content)) {
|
|
81
|
+
content = {
|
|
82
|
+
content: res.content.toString('base64'),
|
|
83
|
+
encoding: 'base64'
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
content = {
|
|
87
|
+
content: createPatch('res', req.context.profiling.resLastVal ? req.context.profiling.resLastVal.toString() : '', res.content.toString(), 0),
|
|
88
|
+
encoding: 'diff'
|
|
89
|
+
}
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
92
|
}
|
|
@@ -91,9 +97,14 @@ class Profiler {
|
|
|
91
97
|
|
|
92
98
|
const stringifiedReq = JSON.stringify({ template: req.template, data: req.data }, null, 2)
|
|
93
99
|
|
|
94
|
-
m.req = {
|
|
100
|
+
m.req = { }
|
|
101
|
+
if (stringifiedReq.length * 4 > this.reporter.options.profiler.maxDiffSize) {
|
|
102
|
+
m.req.tooLarge = true
|
|
103
|
+
} else {
|
|
104
|
+
m.req.diff = createPatch('req', req.context.profiling.reqLastVal || '', stringifiedReq, 0)
|
|
105
|
+
}
|
|
95
106
|
|
|
96
|
-
req.context.profiling.resLastVal = (res.content == null || isbinaryfile(res.content)) ? null : res.content.toString()
|
|
107
|
+
req.context.profiling.resLastVal = (res.content == null || isbinaryfile(res.content) || content.tooLarge) ? null : res.content.toString()
|
|
97
108
|
req.context.profiling.resMetaLastVal = stringifiedResMeta
|
|
98
109
|
req.context.profiling.reqLastVal = stringifiedReq
|
|
99
110
|
}
|
|
@@ -129,10 +140,6 @@ class Profiler {
|
|
|
129
140
|
previousOperationId: parentReq ? parentReq.context.profiling.lastOperationId : null
|
|
130
141
|
}
|
|
131
142
|
|
|
132
|
-
if (!req.context.isChildRequest) {
|
|
133
|
-
profilerEvent.profileId = req.context.profiling.entity._id
|
|
134
|
-
}
|
|
135
|
-
|
|
136
143
|
return this.emit(profilerEvent, req, res)
|
|
137
144
|
}
|
|
138
145
|
|
|
@@ -10,7 +10,6 @@ const Request = require('../../shared/request')
|
|
|
10
10
|
const generateRequestId = require('../../shared/generateRequestId')
|
|
11
11
|
const resolveReferences = require('./resolveReferences.js')
|
|
12
12
|
const moduleHelper = require('./moduleHelper')
|
|
13
|
-
let reportCounter = 0
|
|
14
13
|
|
|
15
14
|
module.exports = (reporter) => {
|
|
16
15
|
moduleHelper(reporter)
|
|
@@ -127,9 +126,6 @@ module.exports = (reporter) => {
|
|
|
127
126
|
response.meta.reportName = 'report'
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
request.context.reportCounter = ++reportCounter
|
|
131
|
-
request.context.startTimestamp = new Date().getTime()
|
|
132
|
-
|
|
133
129
|
if (parentReq == null) {
|
|
134
130
|
reporter.requestModulesCache.set(request.context.rootId, Object.create(null))
|
|
135
131
|
}
|
package/lib/worker/reporter.js
CHANGED
|
@@ -111,14 +111,14 @@ class WorkerReporter extends Reporter {
|
|
|
111
111
|
this._proxyRegistrationFns.push(registrationFn)
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
createProxy ({ req, runInSandbox, context, getTopLevelFunctions,
|
|
114
|
+
createProxy ({ req, runInSandbox, context, getTopLevelFunctions, sandboxRequire }) {
|
|
115
115
|
const proxyInstance = {}
|
|
116
116
|
for (const fn of this._proxyRegistrationFns) {
|
|
117
117
|
fn(proxyInstance, req, {
|
|
118
118
|
runInSandbox,
|
|
119
119
|
context,
|
|
120
120
|
getTopLevelFunctions,
|
|
121
|
-
|
|
121
|
+
sandboxRequire
|
|
122
122
|
})
|
|
123
123
|
}
|
|
124
124
|
return proxyInstance
|
|
@@ -137,10 +137,11 @@ class WorkerReporter extends Reporter {
|
|
|
137
137
|
manager,
|
|
138
138
|
context,
|
|
139
139
|
userCode,
|
|
140
|
+
initFn,
|
|
140
141
|
executionFn,
|
|
142
|
+
currentPath,
|
|
141
143
|
onRequire,
|
|
142
144
|
propertiesConfig,
|
|
143
|
-
currentPath,
|
|
144
145
|
errorLineNumberOffset
|
|
145
146
|
}, req) {
|
|
146
147
|
// we flush before running code in sandbox because it can potentially
|
|
@@ -152,10 +153,11 @@ class WorkerReporter extends Reporter {
|
|
|
152
153
|
manager,
|
|
153
154
|
context,
|
|
154
155
|
userCode,
|
|
156
|
+
initFn,
|
|
155
157
|
executionFn,
|
|
158
|
+
currentPath,
|
|
156
159
|
onRequire,
|
|
157
160
|
propertiesConfig,
|
|
158
|
-
currentPath,
|
|
159
161
|
errorLineNumberOffset
|
|
160
162
|
}, req)
|
|
161
163
|
}
|
|
@@ -19,6 +19,7 @@ module.exports = (_sandbox, options = {}) => {
|
|
|
19
19
|
propertiesConfig = {},
|
|
20
20
|
globalModules = [],
|
|
21
21
|
allowedModules = [],
|
|
22
|
+
safeExecution,
|
|
22
23
|
requireMap
|
|
23
24
|
} = options
|
|
24
25
|
|
|
@@ -65,7 +66,7 @@ module.exports = (_sandbox, options = {}) => {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
if (allowAllModules || allowedModules === '*') {
|
|
69
|
+
if (!safeExecution || allowAllModules || allowedModules === '*') {
|
|
69
70
|
return doRequire(moduleName, requirePaths, modulesCache)
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -111,28 +112,28 @@ module.exports = (_sandbox, options = {}) => {
|
|
|
111
112
|
require: (m) => _require(m, { context: _sandbox })
|
|
112
113
|
})
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
let safeVM
|
|
116
|
+
let vmSandbox
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// to later use them
|
|
119
|
-
const wrapAndSaveProxyResult = (originalFn, thisArg) => {
|
|
120
|
-
return (value, ...args) => {
|
|
121
|
-
const result = originalFn.call(thisArg, value, ...args)
|
|
118
|
+
if (safeExecution) {
|
|
119
|
+
safeVM = new VM()
|
|
122
120
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
// delete the vm.sandbox.global because it introduces json stringify issues
|
|
122
|
+
// and we don't need such global in context
|
|
123
|
+
delete safeVM.sandbox.global
|
|
126
124
|
|
|
127
|
-
|
|
125
|
+
for (const name in sandbox) {
|
|
126
|
+
safeVM.setGlobal(name, sandbox[name])
|
|
128
127
|
}
|
|
129
|
-
}
|
|
130
128
|
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
vmSandbox = safeVM.sandbox
|
|
130
|
+
} else {
|
|
131
|
+
vmSandbox = originalVM.createContext(undefined)
|
|
132
|
+
vmSandbox.Buffer = Buffer
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
for (const name in sandbox) {
|
|
135
|
+
vmSandbox[name] = sandbox[name]
|
|
136
|
+
}
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
// processing top level props because getter/setter descriptors
|
|
@@ -141,57 +142,46 @@ module.exports = (_sandbox, options = {}) => {
|
|
|
141
142
|
const currentConfig = propsConfig[key]
|
|
142
143
|
|
|
143
144
|
if (currentConfig.root && currentConfig.root.sandboxReadOnly) {
|
|
144
|
-
readOnlyProp(
|
|
145
|
+
readOnlyProp(vmSandbox, key, [], customProxies, { onlyTopLevel: true })
|
|
145
146
|
}
|
|
146
147
|
})
|
|
147
148
|
|
|
148
149
|
const sourceFilesInfo = new Map()
|
|
149
150
|
|
|
150
151
|
return {
|
|
151
|
-
sandbox:
|
|
152
|
+
sandbox: vmSandbox,
|
|
152
153
|
console: _console,
|
|
153
154
|
sourceFilesInfo,
|
|
154
|
-
|
|
155
|
-
return
|
|
156
|
-
},
|
|
157
|
-
decontextifyValue: (value) => {
|
|
158
|
-
return vm._internal.Decontextify.value(value)
|
|
155
|
+
compileScript: (code, filename) => {
|
|
156
|
+
return doCompileScript(code, filename, safeExecution)
|
|
159
157
|
},
|
|
160
158
|
restore: () => {
|
|
161
|
-
return restoreProperties(
|
|
162
|
-
},
|
|
163
|
-
unproxyValue: (value) => {
|
|
164
|
-
return getOriginalFromProxy(proxiesInVM, customProxies, value)
|
|
159
|
+
return restoreProperties(vmSandbox, originalValues, proxiesInVM, customProxies)
|
|
165
160
|
},
|
|
166
|
-
|
|
167
|
-
run: async (
|
|
168
|
-
|
|
161
|
+
sandboxRequire: (modulePath) => _require(modulePath, { context: _sandbox, allowAllModules: true }),
|
|
162
|
+
run: async (codeOrScript, { filename, errorLineNumberOffset = 0, source, entity, entitySet } = {}) => {
|
|
163
|
+
let run
|
|
169
164
|
|
|
170
165
|
if (filename != null && source != null) {
|
|
171
166
|
sourceFilesInfo.set(filename, { filename, source, entity, entitySet, errorLineNumberOffset })
|
|
172
167
|
}
|
|
173
168
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// We can't throw an error object here because since vm.Script doesn't store a context, we can't properly contextify that error object.
|
|
187
|
-
// eslint-disable-next-line no-throw-literal
|
|
188
|
-
throw 'Dynamic imports are not allowed.'
|
|
189
|
-
}
|
|
190
|
-
})
|
|
169
|
+
const script = typeof codeOrScript !== 'string' ? codeOrScript : doCompileScript(codeOrScript, filename, safeExecution)
|
|
170
|
+
|
|
171
|
+
if (safeExecution) {
|
|
172
|
+
run = async () => {
|
|
173
|
+
return safeVM.run(script)
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
run = async () => {
|
|
177
|
+
return script.runInContext(vmSandbox, {
|
|
178
|
+
displayErrors: true
|
|
179
|
+
})
|
|
180
|
+
}
|
|
191
181
|
}
|
|
192
182
|
|
|
193
183
|
try {
|
|
194
|
-
const result = await
|
|
184
|
+
const result = await run()
|
|
195
185
|
return result
|
|
196
186
|
} catch (e) {
|
|
197
187
|
decorateErrorMessage(e, sourceFilesInfo)
|
|
@@ -202,10 +192,53 @@ module.exports = (_sandbox, options = {}) => {
|
|
|
202
192
|
}
|
|
203
193
|
}
|
|
204
194
|
|
|
195
|
+
function doCompileScript (code, filename, safeExecution) {
|
|
196
|
+
let script
|
|
197
|
+
|
|
198
|
+
if (safeExecution) {
|
|
199
|
+
script = new VMScript(code, filename)
|
|
200
|
+
|
|
201
|
+
// NOTE: if we need to upgrade vm2 we will need to check the source of this function
|
|
202
|
+
// in vm2 repo and see if we need to change this,
|
|
203
|
+
// we needed to override this method because we want "displayErrors" to be true in order
|
|
204
|
+
// to show nice error when the compile of a script fails
|
|
205
|
+
script._compile = function (prefix, suffix) {
|
|
206
|
+
return new originalVM.Script(prefix + this.getCompiledCode() + suffix, {
|
|
207
|
+
__proto__: null,
|
|
208
|
+
filename: this.filename,
|
|
209
|
+
displayErrors: true,
|
|
210
|
+
lineOffset: this.lineOffset,
|
|
211
|
+
columnOffset: this.columnOffset,
|
|
212
|
+
// THIS FN WAS TAKEN FROM vm2 source, nothing special here
|
|
213
|
+
importModuleDynamically: () => {
|
|
214
|
+
// We can't throw an error object here because since vm.Script doesn't store a context, we can't properly contextify that error object.
|
|
215
|
+
// eslint-disable-next-line no-throw-literal
|
|
216
|
+
throw 'Dynamic imports are not allowed.'
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// do the compilation
|
|
222
|
+
script._compileVM()
|
|
223
|
+
} else {
|
|
224
|
+
script = new originalVM.Script(code, {
|
|
225
|
+
filename,
|
|
226
|
+
displayErrors: true,
|
|
227
|
+
importModuleDynamically: () => {
|
|
228
|
+
// We can't throw an error object here because since vm.Script doesn't store a context, we can't properly contextify that error object.
|
|
229
|
+
// eslint-disable-next-line no-throw-literal
|
|
230
|
+
throw 'Dynamic imports are not allowed.'
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return script
|
|
236
|
+
}
|
|
237
|
+
|
|
205
238
|
function doRequire (moduleName, requirePaths = [], modulesCache) {
|
|
206
239
|
const searchedPaths = []
|
|
207
240
|
|
|
208
|
-
function
|
|
241
|
+
function optimizedRequire (require, modulePath) {
|
|
209
242
|
// save the current module cache, we will use this to restore the cache to the
|
|
210
243
|
// original values after the require finish
|
|
211
244
|
const originalModuleCache = Object.assign(Object.create(null), require.cache)
|
|
@@ -260,13 +293,13 @@ function doRequire (moduleName, requirePaths = [], modulesCache) {
|
|
|
260
293
|
}
|
|
261
294
|
}
|
|
262
295
|
|
|
263
|
-
let result =
|
|
296
|
+
let result = optimizedRequire(require, moduleName)
|
|
264
297
|
|
|
265
298
|
if (!result) {
|
|
266
299
|
let pathsSearched = 0
|
|
267
300
|
|
|
268
301
|
while (!result && pathsSearched < requirePaths.length) {
|
|
269
|
-
result =
|
|
302
|
+
result = optimizedRequire(require, path.join(requirePaths[pathsSearched], moduleName))
|
|
270
303
|
pathsSearched++
|
|
271
304
|
}
|
|
272
305
|
}
|