@jsreport/jsreport-core 3.5.0 → 3.7.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 +23 -0
- package/index.js +1 -1
- package/lib/main/createDefaultLoggerFormat.js +6 -1
- package/lib/main/createNormalizeMetaLoggerFormat.js +19 -0
- package/lib/main/folders/moveBetweenFolders.js +244 -215
- package/lib/main/logger.js +28 -17
- package/lib/main/optionsLoad.js +10 -2
- package/lib/main/optionsSchema.js +6 -3
- package/lib/main/profiler.js +230 -86
- package/lib/main/reporter.js +34 -5
- package/lib/worker/render/executeEngine.js +90 -22
- package/lib/worker/render/moduleHelper.js +4 -4
- package/lib/worker/render/profiler.js +15 -4
- package/lib/worker/render/render.js +1 -1
- package/lib/worker/reporter.js +6 -4
- package/lib/worker/sandbox/{safeSandbox.js → createSandbox.js} +90 -35
- package/lib/worker/sandbox/runInSandbox.js +40 -13
- package/package.json +16 -14
- package/test/extensions/validExtensions/listeners/jsreport.dontdiscover.config.js +9 -0
- package/test/extensions/validExtensions/listeners/main.js +51 -0
- package/test/extensions/validExtensions/listeners/worker.js +68 -0
- package/test/store/common.js +1 -1
package/lib/main/reporter.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
const path = require('path')
|
|
7
7
|
const { Readable } = require('stream')
|
|
8
|
-
const Reaper = require('
|
|
8
|
+
const Reaper = require('@jsreport/reap')
|
|
9
9
|
const optionsLoad = require('./optionsLoad')
|
|
10
10
|
const { createLogger, configureLogger, silentLogs } = require('./logger')
|
|
11
11
|
const checkEntityName = require('./validateEntityName')
|
|
@@ -90,8 +90,8 @@ class MainReporter extends Reporter {
|
|
|
90
90
|
return this
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
async extensionsLoad (
|
|
94
|
-
const appliedConfigFile = await optionsLoad({
|
|
93
|
+
async extensionsLoad (_opts = {}) {
|
|
94
|
+
const [explicitOptions, appliedConfigFile] = await optionsLoad({
|
|
95
95
|
defaults: this.defaults,
|
|
96
96
|
options: this.options,
|
|
97
97
|
validator: this.optionsValidator,
|
|
@@ -106,6 +106,8 @@ class MainReporter extends Reporter {
|
|
|
106
106
|
silentLogs(this.logger)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
const { onConfigDetails, ...opts } = _opts
|
|
110
|
+
|
|
109
111
|
this.logger.info(`Initializing jsreport (version: ${this.version}, configuration file: ${appliedConfigFile || 'none'}, nodejs: ${process.versions.node})`)
|
|
110
112
|
|
|
111
113
|
await this.extensionsManager.load(opts)
|
|
@@ -130,6 +132,10 @@ class MainReporter extends Reporter {
|
|
|
130
132
|
throw new Error(`options contain values that does not match the defined full root schema. ${rootOptionsValidation.fullErrorMessage}`)
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
if (typeof onConfigDetails === 'function') {
|
|
136
|
+
onConfigDetails(explicitOptions)
|
|
137
|
+
}
|
|
138
|
+
|
|
133
139
|
return this
|
|
134
140
|
}
|
|
135
141
|
|
|
@@ -157,6 +163,7 @@ class MainReporter extends Reporter {
|
|
|
157
163
|
*/
|
|
158
164
|
async init () {
|
|
159
165
|
this.closing = this.closed = false
|
|
166
|
+
|
|
160
167
|
if (this._initialized || this._initializing) {
|
|
161
168
|
throw new Error('jsreport already initialized or just initializing. Make sure init is called only once')
|
|
162
169
|
}
|
|
@@ -175,14 +182,22 @@ class MainReporter extends Reporter {
|
|
|
175
182
|
|
|
176
183
|
try {
|
|
177
184
|
this._registerLogMainAction()
|
|
178
|
-
|
|
185
|
+
|
|
186
|
+
let explicitOptions
|
|
187
|
+
|
|
188
|
+
await this.extensionsLoad({
|
|
189
|
+
onConfigDetails: (_explicitOptions) => {
|
|
190
|
+
explicitOptions = _explicitOptions
|
|
191
|
+
}
|
|
192
|
+
})
|
|
179
193
|
|
|
180
194
|
this.documentStore = DocumentStore(Object.assign({}, this.options, { logger: this.logger }), this.entityTypeValidator, this.encryption)
|
|
181
195
|
documentStoreActions(this)
|
|
182
196
|
this.blobStorage = BlobStorage(this, this.options)
|
|
183
197
|
blobStorageActions(this)
|
|
184
198
|
Templates(this)
|
|
185
|
-
|
|
199
|
+
|
|
200
|
+
this._cleanProfileInRequest = Profiler(this)
|
|
186
201
|
|
|
187
202
|
this.folders = Object.assign(this.folders, Folders(this))
|
|
188
203
|
|
|
@@ -200,6 +215,14 @@ class MainReporter extends Reporter {
|
|
|
200
215
|
|
|
201
216
|
await this.extensionsManager.init()
|
|
202
217
|
|
|
218
|
+
if (this.options.trustUserCode) {
|
|
219
|
+
this.logger.info('Code sandboxing is disabled, users can potentially penetrate the local system if you allow code from external users to be part of your reports')
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (explicitOptions.trustUserCode == null && explicitOptions.allowLocalFilesAccess != null) {
|
|
223
|
+
this.logger.warn('options.allowLocalFilesAccess is deprecated, use options.trustUserCode instead')
|
|
224
|
+
}
|
|
225
|
+
|
|
203
226
|
this.logger.info(`Using general timeout for rendering (reportTimeout: ${this.options.reportTimeout})`)
|
|
204
227
|
|
|
205
228
|
if (this.options.store.provider === 'memory') {
|
|
@@ -450,11 +473,17 @@ class MainReporter extends Reporter {
|
|
|
450
473
|
}, req)
|
|
451
474
|
|
|
452
475
|
Object.assign(res, responseResult)
|
|
476
|
+
|
|
453
477
|
await this.afterRenderListeners.fire(req, res)
|
|
478
|
+
|
|
454
479
|
res.stream = Readable.from(res.content)
|
|
480
|
+
|
|
481
|
+
this._cleanProfileInRequest(req)
|
|
482
|
+
|
|
455
483
|
return res
|
|
456
484
|
} catch (err) {
|
|
457
485
|
await this._handleRenderError(req, res, err)
|
|
486
|
+
this._cleanProfileInRequest(req)
|
|
458
487
|
throw err
|
|
459
488
|
} finally {
|
|
460
489
|
if (worker && !workerAborted && !dontCloseProcessing) {
|
|
@@ -9,9 +9,10 @@ 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()
|
|
17
18
|
const executionAsyncResultsMap = new Map()
|
|
@@ -60,11 +61,44 @@ module.exports = (reporter) => {
|
|
|
60
61
|
evaluate: async (executionInfo, entityInfo) => {
|
|
61
62
|
return templatingEnginesEvaluate(false, executionInfo, entityInfo, req)
|
|
62
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
|
+
},
|
|
63
91
|
waitForAsyncHelpers: async () => {
|
|
64
92
|
if (context.__executionId != null && executionAsyncResultsMap.has(context.__executionId)) {
|
|
65
93
|
const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
|
|
66
94
|
return Promise.all([...asyncResultMap.keys()].map((k) => asyncResultMap.get(k)))
|
|
67
95
|
}
|
|
96
|
+
},
|
|
97
|
+
createAsyncHelperResult: (v) => {
|
|
98
|
+
const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
|
|
99
|
+
const asyncResultId = nanoid(7)
|
|
100
|
+
asyncResultMap.set(asyncResultId, v)
|
|
101
|
+
return `{#asyncHelperResult ${asyncResultId}}`
|
|
68
102
|
}
|
|
69
103
|
}
|
|
70
104
|
})
|
|
@@ -102,22 +136,49 @@ module.exports = (reporter) => {
|
|
|
102
136
|
entityPath = await reporter.folders.resolveEntityPath(entity, entitySet, req)
|
|
103
137
|
}
|
|
104
138
|
|
|
105
|
-
const
|
|
106
|
-
const
|
|
139
|
+
const normalizedHelpers = `${helpers || ''}`
|
|
140
|
+
const executionFnParsedParamsKey = `entity:${entity.shortid || 'anonymous'}:helpers:${normalizedHelpers}`
|
|
107
141
|
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
|
|
142
|
+
const initFn = async (getTopLevelFunctions, compileScript) => {
|
|
143
|
+
if (systemHelpersCache != null) {
|
|
144
|
+
return systemHelpersCache
|
|
111
145
|
}
|
|
112
146
|
|
|
113
|
-
|
|
114
|
-
|
|
147
|
+
const registerResults = await reporter.registerHelpersListeners.fire()
|
|
148
|
+
const systemHelpers = []
|
|
149
|
+
|
|
150
|
+
for (const result of registerResults) {
|
|
151
|
+
if (result == null) {
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (typeof result === 'string') {
|
|
156
|
+
systemHelpers.push(result)
|
|
157
|
+
}
|
|
115
158
|
}
|
|
116
|
-
}
|
|
117
159
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
160
|
+
const systemHelpersStr = systemHelpers.join('\n')
|
|
161
|
+
|
|
162
|
+
const functionNames = getTopLevelFunctions(systemHelpersStr)
|
|
163
|
+
|
|
164
|
+
const exposeSystemHelpersCode = `for (const fName of ${JSON.stringify(functionNames)}) { this[fName] = __topLevelFunctions[fName] }`
|
|
165
|
+
|
|
166
|
+
// we sync the __topLevelFunctions with system helpers and expose it immediately to the global context
|
|
167
|
+
const userCode = `(async () => { ${systemHelpersStr};
|
|
168
|
+
__topLevelFunctions = {...__topLevelFunctions, ${functionNames.map(h => `"${h}": ${h}`).join(',')}}; ${exposeSystemHelpersCode}
|
|
169
|
+
})()`
|
|
170
|
+
|
|
171
|
+
const filename = 'system-helpers.js'
|
|
172
|
+
const script = compileScript(userCode, filename)
|
|
173
|
+
|
|
174
|
+
systemHelpersCache = {
|
|
175
|
+
filename,
|
|
176
|
+
source: systemHelpersStr,
|
|
177
|
+
script
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return systemHelpersCache
|
|
181
|
+
}
|
|
121
182
|
|
|
122
183
|
const executionFn = async ({ require, console, topLevelFunctions, context }) => {
|
|
123
184
|
const asyncResultMap = new Map()
|
|
@@ -129,25 +190,30 @@ module.exports = (reporter) => {
|
|
|
129
190
|
|
|
130
191
|
const key = `template:${content}:${engine.name}`
|
|
131
192
|
|
|
132
|
-
if (!
|
|
193
|
+
if (!templatesCache.has(key)) {
|
|
133
194
|
try {
|
|
134
|
-
|
|
195
|
+
templatesCache.set(key, engine.compile(content, { require }))
|
|
135
196
|
} catch (e) {
|
|
136
197
|
e.property = 'content'
|
|
137
198
|
throw e
|
|
138
199
|
}
|
|
139
200
|
}
|
|
140
201
|
|
|
141
|
-
const compiledTemplate =
|
|
142
|
-
|
|
202
|
+
const compiledTemplate = templatesCache.get(key)
|
|
143
203
|
const wrappedTopLevelFunctions = {}
|
|
144
204
|
|
|
145
205
|
for (const h of Object.keys(topLevelFunctions)) {
|
|
146
|
-
|
|
206
|
+
if (engine.getWrappingHelpersEnabled && engine.getWrappingHelpersEnabled(req) === false) {
|
|
207
|
+
wrappedTopLevelFunctions[h] = engine.wrapHelper(topLevelFunctions[h], { context })
|
|
208
|
+
} else {
|
|
209
|
+
wrappedTopLevelFunctions[h] = wrapHelperForAsyncSupport(topLevelFunctions[h], asyncResultMap)
|
|
210
|
+
}
|
|
147
211
|
}
|
|
148
212
|
|
|
149
213
|
let contentResult = await engine.execute(compiledTemplate, wrappedTopLevelFunctions, data, { require })
|
|
214
|
+
|
|
150
215
|
const resolvedResultsMap = new Map()
|
|
216
|
+
|
|
151
217
|
while (asyncResultMap.size > 0) {
|
|
152
218
|
await Promise.all([...asyncResultMap.keys()].map(async (k) => {
|
|
153
219
|
resolvedResultsMap.set(k, `${await asyncResultMap.get(k)}`)
|
|
@@ -178,25 +244,27 @@ module.exports = (reporter) => {
|
|
|
178
244
|
return executionFn({ require, console, topLevelFunctions, context })
|
|
179
245
|
} else {
|
|
180
246
|
const awaiter = {}
|
|
247
|
+
|
|
181
248
|
awaiter.promise = new Promise((resolve) => {
|
|
182
249
|
awaiter.resolve = resolve
|
|
183
250
|
})
|
|
251
|
+
|
|
184
252
|
executionFnParsedParamsMap.get(req.context.id).set(executionFnParsedParamsKey, awaiter)
|
|
185
253
|
}
|
|
186
254
|
|
|
187
255
|
if (reporter.options.sandbox.cache && reporter.options.sandbox.cache.enabled === false) {
|
|
188
|
-
|
|
256
|
+
templatesCache.reset()
|
|
189
257
|
}
|
|
190
258
|
|
|
191
259
|
try {
|
|
192
260
|
return await reporter.runInSandbox({
|
|
193
261
|
context: {
|
|
194
|
-
...(engine.createContext ? engine.createContext() : {})
|
|
262
|
+
...(engine.createContext ? engine.createContext(req) : {})
|
|
195
263
|
},
|
|
196
|
-
userCode:
|
|
264
|
+
userCode: normalizedHelpers,
|
|
265
|
+
initFn,
|
|
197
266
|
executionFn,
|
|
198
267
|
currentPath: entityPath,
|
|
199
|
-
errorLineNumberOffset: systemHelpersStr.split('\n').length,
|
|
200
268
|
onRequire: (moduleName, { context }) => {
|
|
201
269
|
if (engine.onRequire) {
|
|
202
270
|
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
|
|
|
@@ -36,7 +36,10 @@ class Profiler {
|
|
|
36
36
|
if (profilingInfo) {
|
|
37
37
|
const batch = profilingInfo.batch
|
|
38
38
|
profilingInfo.batch = []
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
if (batch.length > 0) {
|
|
41
|
+
await this.reporter.executeMainAction('profile', batch, profilingInfo.req).catch((e) => this.reporter.logger.error(e, profilingInfo.req))
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
}
|
|
@@ -72,7 +75,7 @@ class Profiler {
|
|
|
72
75
|
let content = res.content
|
|
73
76
|
|
|
74
77
|
if (content != null) {
|
|
75
|
-
if (content.length > this.reporter.options.profiler.
|
|
78
|
+
if (content.length > this.reporter.options.profiler.maxDiffSize) {
|
|
76
79
|
content = {
|
|
77
80
|
tooLarge: true
|
|
78
81
|
}
|
|
@@ -97,7 +100,12 @@ class Profiler {
|
|
|
97
100
|
|
|
98
101
|
const stringifiedReq = JSON.stringify({ template: req.template, data: req.data }, null, 2)
|
|
99
102
|
|
|
100
|
-
m.req = {
|
|
103
|
+
m.req = { }
|
|
104
|
+
if (stringifiedReq.length * 4 > this.reporter.options.profiler.maxDiffSize) {
|
|
105
|
+
m.req.tooLarge = true
|
|
106
|
+
} else {
|
|
107
|
+
m.req.diff = createPatch('req', req.context.profiling.reqLastVal || '', stringifiedReq, 0)
|
|
108
|
+
}
|
|
101
109
|
|
|
102
110
|
req.context.profiling.resLastVal = (res.content == null || isbinaryfile(res.content) || content.tooLarge) ? null : res.content.toString()
|
|
103
111
|
req.context.profiling.resMetaLastVal = stringifiedResMeta
|
|
@@ -153,7 +161,10 @@ class Profiler {
|
|
|
153
161
|
const profilingInfo = this.profiledRequestsMap.get(req.context.rootId)
|
|
154
162
|
if (profilingInfo) {
|
|
155
163
|
this.profiledRequestsMap.delete(req.context.rootId)
|
|
156
|
-
|
|
164
|
+
|
|
165
|
+
if (profilingInfo.batch.length > 0) {
|
|
166
|
+
await this.reporter.executeMainAction('profile', profilingInfo.batch, req)
|
|
167
|
+
}
|
|
157
168
|
}
|
|
158
169
|
}
|
|
159
170
|
}
|
|
@@ -130,7 +130,7 @@ module.exports = (reporter) => {
|
|
|
130
130
|
reporter.requestModulesCache.set(request.context.rootId, Object.create(null))
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
reporter.logger.info(`Starting rendering request ${request.context.reportCounter} (user: ${(request.context.user ? request.context.user.
|
|
133
|
+
reporter.logger.info(`Starting rendering request ${request.context.reportCounter} (user: ${(request.context.user ? request.context.user.name : 'null')})`, request)
|
|
134
134
|
|
|
135
135
|
// TODO
|
|
136
136
|
/* if (reporter.entityTypeValidator.getSchema('TemplateType') != null) {
|
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,14 +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
|
-
delete vm.sandbox.global
|
|
118
|
+
if (safeExecution) {
|
|
119
|
+
safeVM = new VM()
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
124
|
+
|
|
125
|
+
for (const name in sandbox) {
|
|
126
|
+
safeVM.setGlobal(name, sandbox[name])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
vmSandbox = safeVM.sandbox
|
|
130
|
+
} else {
|
|
131
|
+
vmSandbox = originalVM.createContext(undefined)
|
|
132
|
+
vmSandbox.Buffer = Buffer
|
|
133
|
+
|
|
134
|
+
for (const name in sandbox) {
|
|
135
|
+
vmSandbox[name] = sandbox[name]
|
|
136
|
+
}
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
// processing top level props because getter/setter descriptors
|
|
@@ -127,49 +142,46 @@ module.exports = (_sandbox, options = {}) => {
|
|
|
127
142
|
const currentConfig = propsConfig[key]
|
|
128
143
|
|
|
129
144
|
if (currentConfig.root && currentConfig.root.sandboxReadOnly) {
|
|
130
|
-
readOnlyProp(
|
|
145
|
+
readOnlyProp(vmSandbox, key, [], customProxies, { onlyTopLevel: true })
|
|
131
146
|
}
|
|
132
147
|
})
|
|
133
148
|
|
|
134
149
|
const sourceFilesInfo = new Map()
|
|
135
150
|
|
|
136
151
|
return {
|
|
137
|
-
sandbox:
|
|
152
|
+
sandbox: vmSandbox,
|
|
138
153
|
console: _console,
|
|
139
154
|
sourceFilesInfo,
|
|
155
|
+
compileScript: (code, filename) => {
|
|
156
|
+
return doCompileScript(code, filename, safeExecution)
|
|
157
|
+
},
|
|
140
158
|
restore: () => {
|
|
141
|
-
return restoreProperties(
|
|
159
|
+
return restoreProperties(vmSandbox, originalValues, proxiesInVM, customProxies)
|
|
142
160
|
},
|
|
143
|
-
|
|
144
|
-
run: async (
|
|
145
|
-
|
|
161
|
+
sandboxRequire: (modulePath) => _require(modulePath, { context: _sandbox, allowAllModules: true }),
|
|
162
|
+
run: async (codeOrScript, { filename, errorLineNumberOffset = 0, source, entity, entitySet } = {}) => {
|
|
163
|
+
let run
|
|
146
164
|
|
|
147
165
|
if (filename != null && source != null) {
|
|
148
166
|
sourceFilesInfo.set(filename, { filename, source, entity, entitySet, errorLineNumberOffset })
|
|
149
167
|
}
|
|
150
168
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
importModuleDynamically: () => {
|
|
164
|
-
// 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.
|
|
165
|
-
// eslint-disable-next-line no-throw-literal
|
|
166
|
-
throw 'Dynamic imports are not allowed.'
|
|
167
|
-
}
|
|
168
|
-
})
|
|
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
|
+
}
|
|
169
181
|
}
|
|
170
182
|
|
|
171
183
|
try {
|
|
172
|
-
const result = await
|
|
184
|
+
const result = await run()
|
|
173
185
|
return result
|
|
174
186
|
} catch (e) {
|
|
175
187
|
decorateErrorMessage(e, sourceFilesInfo)
|
|
@@ -180,10 +192,53 @@ module.exports = (_sandbox, options = {}) => {
|
|
|
180
192
|
}
|
|
181
193
|
}
|
|
182
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
|
+
|
|
183
238
|
function doRequire (moduleName, requirePaths = [], modulesCache) {
|
|
184
239
|
const searchedPaths = []
|
|
185
240
|
|
|
186
|
-
function
|
|
241
|
+
function optimizedRequire (require, modulePath) {
|
|
187
242
|
// save the current module cache, we will use this to restore the cache to the
|
|
188
243
|
// original values after the require finish
|
|
189
244
|
const originalModuleCache = Object.assign(Object.create(null), require.cache)
|
|
@@ -238,13 +293,13 @@ function doRequire (moduleName, requirePaths = [], modulesCache) {
|
|
|
238
293
|
}
|
|
239
294
|
}
|
|
240
295
|
|
|
241
|
-
let result =
|
|
296
|
+
let result = optimizedRequire(require, moduleName)
|
|
242
297
|
|
|
243
298
|
if (!result) {
|
|
244
299
|
let pathsSearched = 0
|
|
245
300
|
|
|
246
301
|
while (!result && pathsSearched < requirePaths.length) {
|
|
247
|
-
result =
|
|
302
|
+
result = optimizedRequire(require, path.join(requirePaths[pathsSearched], moduleName))
|
|
248
303
|
pathsSearched++
|
|
249
304
|
}
|
|
250
305
|
}
|