@jsreport/jsreport-core 3.2.0 → 3.4.2
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 +17 -1
- package/lib/main/blobStorage/blobStorage.js +1 -0
- package/lib/main/logger.js +21 -11
- package/lib/main/monitoring.js +2 -1
- package/lib/main/optionsLoad.js +3 -9
- package/lib/main/profiler.js +13 -1
- package/lib/main/reporter.js +42 -27
- package/lib/main/request.js +21 -0
- package/lib/worker/render/executeEngine.js +0 -7
- package/lib/worker/render/profiler.js +16 -12
- package/lib/worker/reporter.js +5 -0
- package/lib/worker/sandbox/runInSandbox.js +48 -6
- package/lib/worker/sandbox/safeSandbox.js +11 -32
- package/lib/worker/workerHandler.js +1 -0
- package/package.json +4 -5
- package/test/blobStorage/common.js +4 -0
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ const result = await jsreport.render({
|
|
|
36
36
|
foo: "world"
|
|
37
37
|
}
|
|
38
38
|
})
|
|
39
|
-
await fs.writeFile('out.pdf',
|
|
39
|
+
await fs.writeFile('out.pdf', result.content)
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
## Render
|
|
@@ -282,6 +282,22 @@ jsreport.documentStore.collection('templates')
|
|
|
282
282
|
|
|
283
283
|
## Changelog
|
|
284
284
|
|
|
285
|
+
### 3.4.2
|
|
286
|
+
|
|
287
|
+
- update dep `vm2` to fix security vulnerability in sandbox
|
|
288
|
+
|
|
289
|
+
### 3.4.1
|
|
290
|
+
|
|
291
|
+
- fix passing data to async report
|
|
292
|
+
- fix blob appends
|
|
293
|
+
|
|
294
|
+
### 3.4.0
|
|
295
|
+
|
|
296
|
+
- fix for reports execution
|
|
297
|
+
- fix for render profiling
|
|
298
|
+
- fix for blob storage remove
|
|
299
|
+
- update deps to fix npm audit
|
|
300
|
+
|
|
285
301
|
### 3.1.0
|
|
286
302
|
|
|
287
303
|
- fix blob storage append to not existing blob (mongo)
|
package/lib/main/logger.js
CHANGED
|
@@ -37,14 +37,25 @@ function createLogger () {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function configureLogger (logger, _transports) {
|
|
40
|
+
const transports = _transports || {}
|
|
41
|
+
const transportFormatMap = new WeakMap()
|
|
42
|
+
|
|
43
|
+
// we ensure we do .format cleanup on options first before checking if the logger
|
|
44
|
+
// is configured or not, this ensure that options are properly cleaned up when
|
|
45
|
+
// configureLogger is called more than once (like when execution cli commands from extensions)
|
|
46
|
+
for (const [, transpOptions] of Object.entries(transports)) {
|
|
47
|
+
if (transpOptions.format != null) {
|
|
48
|
+
transportFormatMap.set(transpOptions, transpOptions.format)
|
|
49
|
+
delete transpOptions.format
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
const configuredPreviously = logger.__configured__ === true
|
|
41
54
|
|
|
42
55
|
if (configuredPreviously) {
|
|
43
56
|
return
|
|
44
57
|
}
|
|
45
58
|
|
|
46
|
-
const transports = _transports || {}
|
|
47
|
-
|
|
48
59
|
const knownTransports = {
|
|
49
60
|
debug: DebugTransport,
|
|
50
61
|
console: winston.transports.Console,
|
|
@@ -78,22 +89,21 @@ function configureLogger (logger, _transports) {
|
|
|
78
89
|
continue
|
|
79
90
|
}
|
|
80
91
|
|
|
92
|
+
let originalFormat
|
|
93
|
+
|
|
94
|
+
if (transportFormatMap.has(transpOptions)) {
|
|
95
|
+
originalFormat = transportFormatMap.get(transpOptions)
|
|
96
|
+
}
|
|
97
|
+
|
|
81
98
|
if (
|
|
82
|
-
|
|
83
|
-
typeof
|
|
99
|
+
originalFormat != null &&
|
|
100
|
+
typeof originalFormat.constructor !== 'function'
|
|
84
101
|
) {
|
|
85
102
|
throw new Error(`Invalid option for transport object "${
|
|
86
103
|
transpName
|
|
87
104
|
}", option "format" has an incorrect value, must be an instance of loggerFormat. check your "logger" config`)
|
|
88
105
|
}
|
|
89
106
|
|
|
90
|
-
let originalFormat
|
|
91
|
-
|
|
92
|
-
if (transpOptions.format != null) {
|
|
93
|
-
originalFormat = transpOptions.format
|
|
94
|
-
delete transpOptions.format
|
|
95
|
-
}
|
|
96
|
-
|
|
97
107
|
const options = Object.assign(omit(transpOptions, knownOptions), {
|
|
98
108
|
name: transpName
|
|
99
109
|
})
|
package/lib/main/monitoring.js
CHANGED
package/lib/main/optionsLoad.js
CHANGED
|
@@ -133,15 +133,9 @@ async function loadConfig (defaults, options, loadExternal = true) {
|
|
|
133
133
|
const currentValue = obj.value[extensionKey]
|
|
134
134
|
delete obj.value[extensionKey]
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
obj.value[realExtensionName],
|
|
140
|
-
currentValue
|
|
141
|
-
)
|
|
142
|
-
} else {
|
|
143
|
-
obj.value[realExtensionName] = currentValue
|
|
144
|
-
}
|
|
136
|
+
// the camelCase key version should already contain all merged values
|
|
137
|
+
// (from both the real extension name with "-" and camel case)
|
|
138
|
+
obj.value[realExtensionName] = currentValue
|
|
145
139
|
})
|
|
146
140
|
} else if (!normalize && obj.key.startsWith('extensions')) {
|
|
147
141
|
// the transform ensures that camelCase alias keys of extensions
|
package/lib/main/profiler.js
CHANGED
|
@@ -26,9 +26,13 @@ module.exports = (reporter) => {
|
|
|
26
26
|
return
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
let lastOperation
|
|
30
|
+
|
|
29
31
|
for (const m of events) {
|
|
30
32
|
if (m.type === 'log') {
|
|
31
33
|
reporter.logger[m.level](m.message, { ...req, ...m.meta, timestamp: m.timestamp })
|
|
34
|
+
} else {
|
|
35
|
+
lastOperation = m
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
if (profilersMap.has(req.context.rootId)) {
|
|
@@ -36,8 +40,15 @@ module.exports = (reporter) => {
|
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
if (lastOperation != null) {
|
|
44
|
+
req.context.profiling.lastOperation = lastOperation
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
profilerAppendChain.set(req.context.rootId, profilerAppendChain.get(req.context.rootId).then(() => {
|
|
40
|
-
return reporter.blobStorage.append(
|
|
48
|
+
return reporter.blobStorage.append(
|
|
49
|
+
req.context.profiling.entity.blobName,
|
|
50
|
+
Buffer.from(events.map(m => JSON.stringify(m)).join('\n') + '\n'), req
|
|
51
|
+
).catch(e => {
|
|
41
52
|
reporter.logger.error('Failed to append to profile blob', e)
|
|
42
53
|
})
|
|
43
54
|
}))
|
|
@@ -64,6 +75,7 @@ module.exports = (reporter) => {
|
|
|
64
75
|
profilerAppendChain.set(req.context.rootId, Promise.resolve())
|
|
65
76
|
|
|
66
77
|
req.context.profiling = req.context.profiling || {}
|
|
78
|
+
req.context.profiling.lastOperation = null
|
|
67
79
|
|
|
68
80
|
let blobName = `profiles/${req.context.rootId}.log`
|
|
69
81
|
|
package/lib/main/reporter.js
CHANGED
|
@@ -25,7 +25,7 @@ const setupValidateShortid = require('./store/setupValidateShortid')
|
|
|
25
25
|
const documentStoreActions = require('./store/mainActions')
|
|
26
26
|
const blobStorageActions = require('./blobStorage/mainActions')
|
|
27
27
|
const Reporter = require('../shared/reporter')
|
|
28
|
-
const Request = require('
|
|
28
|
+
const Request = require('./request')
|
|
29
29
|
const generateRequestId = require('../shared/generateRequestId')
|
|
30
30
|
const Profiler = require('./profiler')
|
|
31
31
|
const Monitoring = require('./monitoring')
|
|
@@ -315,7 +315,7 @@ class MainReporter extends Reporter {
|
|
|
315
315
|
*
|
|
316
316
|
* @public
|
|
317
317
|
*/
|
|
318
|
-
async render (req,
|
|
318
|
+
async render (req, options = {}) {
|
|
319
319
|
if (!this._initialized) {
|
|
320
320
|
throw new Error('Not initialized, you need to call jsreport.init().then before rendering')
|
|
321
321
|
}
|
|
@@ -325,21 +325,18 @@ class MainReporter extends Reporter {
|
|
|
325
325
|
req.context.rootId = req.context.rootId || generateRequestId()
|
|
326
326
|
req.context.id = req.context.rootId
|
|
327
327
|
|
|
328
|
-
const worker = await this._workersManager.allocate(req, {
|
|
328
|
+
const worker = options.worker || await this._workersManager.allocate(req, {
|
|
329
329
|
timeout: this.options.reportTimeout
|
|
330
330
|
})
|
|
331
331
|
|
|
332
|
+
let keepWorker
|
|
332
333
|
let workerAborted
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
workerAborted = true
|
|
340
|
-
worker.release(req).catch((e) => this.logger.error('Failed to release worker ' + e))
|
|
341
|
-
})
|
|
342
|
-
}
|
|
334
|
+
|
|
335
|
+
if (options.abortEmitter) {
|
|
336
|
+
options.abortEmitter.once('abort', () => {
|
|
337
|
+
workerAborted = true
|
|
338
|
+
worker.release(req).catch((e) => this.logger.error('Failed to release worker ' + e))
|
|
339
|
+
})
|
|
343
340
|
}
|
|
344
341
|
|
|
345
342
|
const res = { meta: {} }
|
|
@@ -348,10 +345,7 @@ class MainReporter extends Reporter {
|
|
|
348
345
|
throw this.createError('Request aborted by client')
|
|
349
346
|
}
|
|
350
347
|
|
|
351
|
-
let isDataStoredInWorker = false
|
|
352
|
-
|
|
353
348
|
if (req.rawContent) {
|
|
354
|
-
isDataStoredInWorker = true
|
|
355
349
|
const result = await worker.execute({
|
|
356
350
|
actionName: 'parse',
|
|
357
351
|
req,
|
|
@@ -362,13 +356,7 @@ class MainReporter extends Reporter {
|
|
|
362
356
|
req = result
|
|
363
357
|
}
|
|
364
358
|
|
|
365
|
-
req = Request(req
|
|
366
|
-
|
|
367
|
-
if (isDataStoredInWorker) {
|
|
368
|
-
// we unset this because we want the Request() call in worker to evaluate the data
|
|
369
|
-
// and determine if the original was empty or not
|
|
370
|
-
delete req.context.originalInputDataIsEmpty
|
|
371
|
-
}
|
|
359
|
+
req = Request(req)
|
|
372
360
|
|
|
373
361
|
// TODO: we will probably validate in the thread
|
|
374
362
|
if (this.entityTypeValidator.getSchema('TemplateType') != null) {
|
|
@@ -388,13 +376,31 @@ class MainReporter extends Reporter {
|
|
|
388
376
|
req.options &&
|
|
389
377
|
req.options.timeout != null
|
|
390
378
|
) {
|
|
391
|
-
reportTimeout = req
|
|
379
|
+
reportTimeout = req.options.timeout
|
|
392
380
|
}
|
|
393
381
|
|
|
394
|
-
await this.beforeRenderListeners.fire(req, res)
|
|
382
|
+
await this.beforeRenderListeners.fire(req, res, { worker })
|
|
395
383
|
|
|
396
|
-
|
|
384
|
+
// this is used so far just in the reports extension
|
|
385
|
+
// it wants to send to the client immediate response with link to the report status
|
|
386
|
+
// but the previous steps already allocated worker which has the parsed input request
|
|
387
|
+
// so we need to keep the worker active and let the subsequent real render call use it
|
|
388
|
+
// we cant move the main beforeRenderListener before the worker allocation, because at that point
|
|
389
|
+
// the request isn't parsed and we don't know the template and options
|
|
390
|
+
if (req.context.returnResponseAndKeepWorker) {
|
|
391
|
+
keepWorker = true
|
|
397
392
|
res.stream = Readable.from(res.content)
|
|
393
|
+
|
|
394
|
+
// just temporary workaround until we change how report render works
|
|
395
|
+
await this.documentStore.collection('profiles').update({
|
|
396
|
+
_id: req.context.profiling.entity._id
|
|
397
|
+
}, {
|
|
398
|
+
$set: {
|
|
399
|
+
state: 'success',
|
|
400
|
+
finishedOn: new Date(),
|
|
401
|
+
blobPersisted: true
|
|
402
|
+
}
|
|
403
|
+
}, req)
|
|
398
404
|
return res
|
|
399
405
|
}
|
|
400
406
|
|
|
@@ -414,6 +420,15 @@ class MainReporter extends Reporter {
|
|
|
414
420
|
} catch (err) {
|
|
415
421
|
if (err.code === 'WORKER_TIMEOUT') {
|
|
416
422
|
err.message = 'Report timeout'
|
|
423
|
+
if (req.context.profiling?.lastOperation != null && req.context.profiling?.entity != null) {
|
|
424
|
+
err.message += `. Last profiler operation: (${req.context.profiling.lastOperation.subtype}) ${req.context.profiling.lastOperation.name}`
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (req.context.http != null) {
|
|
428
|
+
const profileUrl = `${req.context.http.baseUrl}/studio/profiles/${req.context.profiling.entity._id}`
|
|
429
|
+
err.message += `. You can inspect and find more details here: ${profileUrl}`
|
|
430
|
+
}
|
|
431
|
+
|
|
417
432
|
err.weak = true
|
|
418
433
|
}
|
|
419
434
|
|
|
@@ -429,7 +444,7 @@ class MainReporter extends Reporter {
|
|
|
429
444
|
await this.renderErrorListeners.fire(req, res, err)
|
|
430
445
|
throw err
|
|
431
446
|
} finally {
|
|
432
|
-
if (!workerAborted) {
|
|
447
|
+
if (!workerAborted && !keepWorker) {
|
|
433
448
|
await worker.release(req)
|
|
434
449
|
}
|
|
435
450
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const extend = require('node.extend.without.arrays')
|
|
2
|
+
|
|
3
|
+
module.exports = (obj) => {
|
|
4
|
+
const request = Object.create({}, {
|
|
5
|
+
__isJsreportRequest__: {
|
|
6
|
+
value: true,
|
|
7
|
+
writable: false,
|
|
8
|
+
configurable: false,
|
|
9
|
+
enumerable: false
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
request.template = extend(true, {}, obj.template)
|
|
14
|
+
|
|
15
|
+
request.options = extend(true, {}, request.options, obj.options)
|
|
16
|
+
request.context = extend(true, {}, request.context, obj.context)
|
|
17
|
+
request.context.shared = extend(true, {}, request.context.shared)
|
|
18
|
+
request.data = obj.data
|
|
19
|
+
|
|
20
|
+
return request
|
|
21
|
+
}
|
|
@@ -85,7 +85,6 @@ module.exports = (reporter) => {
|
|
|
85
85
|
|
|
86
86
|
if (entity._id) {
|
|
87
87
|
entityPath = await reporter.folders.resolveEntityPath(entity, entitySet, req)
|
|
88
|
-
entityPath = entityPath.substring(0, entityPath.lastIndexOf('/'))
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
const registerResults = await reporter.registerHelpersListeners.fire(req)
|
|
@@ -106,12 +105,6 @@ module.exports = (reporter) => {
|
|
|
106
105
|
const executionFnParsedParamsKey = `entity:${entity.shortid || 'anonymous'}:helpers:${joinedHelpers}`
|
|
107
106
|
|
|
108
107
|
const executionFn = async ({ require, console, topLevelFunctions }) => {
|
|
109
|
-
// cached components dont call runInSandbox but share the proxy, we get to it here
|
|
110
|
-
if (entitySet !== 'templates') {
|
|
111
|
-
const jsreport = require('jsreport-proxy')
|
|
112
|
-
jsreport.currentPath = entityPath
|
|
113
|
-
}
|
|
114
|
-
|
|
115
108
|
const asyncResultMap = new Map()
|
|
116
109
|
executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).resolve({ require, console, topLevelFunctions })
|
|
117
110
|
const key = `template:${content}:${engine.name}`
|
|
@@ -8,7 +8,7 @@ class Profiler {
|
|
|
8
8
|
constructor (reporter) {
|
|
9
9
|
this.reporter = reporter
|
|
10
10
|
|
|
11
|
-
this.reporter.addRequestContextMetaConfig('profiling', {
|
|
11
|
+
this.reporter.addRequestContextMetaConfig('profiling', { sandboxHidden: true })
|
|
12
12
|
this.reporter.addRequestContextMetaConfig('resolvedTemplate', { sandboxHidden: true })
|
|
13
13
|
|
|
14
14
|
this.reporter.beforeMainActionListeners.add('profiler', (actionName, data, req) => {
|
|
@@ -18,16 +18,7 @@ class Profiler {
|
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
this.profiledRequestsMap = new Map()
|
|
21
|
-
const profileEventsFlushInterval = setInterval(
|
|
22
|
-
for (const id of [...this.profiledRequestsMap.keys()]) {
|
|
23
|
-
const profilingInfo = this.profiledRequestsMap.get(id)
|
|
24
|
-
if (profilingInfo) {
|
|
25
|
-
const batch = profilingInfo.batch
|
|
26
|
-
profilingInfo.batch = []
|
|
27
|
-
await this.reporter.executeMainAction('profile', batch, profilingInfo.req).catch((e) => this.reporter.logger.error(e, profilingInfo.req))
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}, 100)
|
|
21
|
+
const profileEventsFlushInterval = setInterval(() => this.flush(), 100)
|
|
31
22
|
profileEventsFlushInterval.unref()
|
|
32
23
|
|
|
33
24
|
this.reporter.closeListeners.add('profiler', this, () => {
|
|
@@ -37,6 +28,19 @@ class Profiler {
|
|
|
37
28
|
})
|
|
38
29
|
}
|
|
39
30
|
|
|
31
|
+
async flush (id) {
|
|
32
|
+
const toProcess = id == null ? [...this.profiledRequestsMap.keys()] : [id]
|
|
33
|
+
|
|
34
|
+
for (const id of toProcess) {
|
|
35
|
+
const profilingInfo = this.profiledRequestsMap.get(id)
|
|
36
|
+
if (profilingInfo) {
|
|
37
|
+
const batch = profilingInfo.batch
|
|
38
|
+
profilingInfo.batch = []
|
|
39
|
+
await this.reporter.executeMainAction('profile', batch, profilingInfo.req).catch((e) => this.reporter.logger.error(e, profilingInfo.req))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
emit (m, req, res) {
|
|
41
45
|
m.timestamp = m.timestamp || new Date().getTime()
|
|
42
46
|
|
|
@@ -89,7 +93,7 @@ class Profiler {
|
|
|
89
93
|
|
|
90
94
|
m.req = { diff: createPatch('req', req.context.profiling.reqLastVal || '', stringifiedReq, 0) }
|
|
91
95
|
|
|
92
|
-
req.context.profiling.resLastVal = res.content
|
|
96
|
+
req.context.profiling.resLastVal = (res.content == null || isbinaryfile(res.content)) ? null : res.content.toString()
|
|
93
97
|
req.context.profiling.resMetaLastVal = stringifiedResMeta
|
|
94
98
|
req.context.profiling.reqLastVal = stringifiedReq
|
|
95
99
|
}
|
package/lib/worker/reporter.js
CHANGED
|
@@ -143,6 +143,11 @@ class WorkerReporter extends Reporter {
|
|
|
143
143
|
currentPath,
|
|
144
144
|
errorLineNumberOffset
|
|
145
145
|
}, req) {
|
|
146
|
+
// we flush before running code in sandbox because it can potentially
|
|
147
|
+
// include code that blocks the whole process (like `while (true) {}`) and we
|
|
148
|
+
// want to ensure that the batched messages are flushed before trying to execute the code
|
|
149
|
+
await this.profiler.flush(req.context.rootId)
|
|
150
|
+
|
|
146
151
|
return this._runInSandbox({
|
|
147
152
|
manager,
|
|
148
153
|
context,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const LRU = require('lru-cache')
|
|
2
|
-
const
|
|
2
|
+
const stackTrace = require('stack-trace')
|
|
3
3
|
const { customAlphabet } = require('nanoid')
|
|
4
|
+
const safeSandbox = require('./safeSandbox')
|
|
4
5
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
|
|
5
6
|
|
|
6
7
|
module.exports = (reporter) => {
|
|
@@ -27,7 +28,7 @@ module.exports = (reporter) => {
|
|
|
27
28
|
context.__topLevelFunctions = {}
|
|
28
29
|
context.__handleError = (err) => handleError(reporter, err)
|
|
29
30
|
|
|
30
|
-
const { run, restore,
|
|
31
|
+
const { sourceFilesInfo, run, restore, sandbox, safeRequire } = safeSandbox(context, {
|
|
31
32
|
onLog: (log) => {
|
|
32
33
|
reporter.logger[log.level](log.message, { ...req, timestamp: log.timestamp })
|
|
33
34
|
},
|
|
@@ -61,16 +62,57 @@ module.exports = (reporter) => {
|
|
|
61
62
|
})
|
|
62
63
|
|
|
63
64
|
jsreportProxy = reporter.createProxy({ req, runInSandbox: run, context: sandbox, getTopLevelFunctions, safeRequire })
|
|
64
|
-
|
|
65
|
+
|
|
66
|
+
jsreportProxy.currentPath = async () => {
|
|
67
|
+
// we get the current path by throwing an error, which give us a stack trace
|
|
68
|
+
// which we analyze and see if some source file is associated to an entity
|
|
69
|
+
// if it is then we can properly get the path associated to it, if not we
|
|
70
|
+
// fallback to the current path passed as options
|
|
71
|
+
const filesCount = sourceFilesInfo.size
|
|
72
|
+
let resolvedPath = currentPath
|
|
73
|
+
|
|
74
|
+
if (filesCount > 0) {
|
|
75
|
+
const err = new Error('get me stack trace please')
|
|
76
|
+
const trace = stackTrace.parse(err)
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < trace.length; i++) {
|
|
79
|
+
const current = trace[i]
|
|
80
|
+
|
|
81
|
+
if (sourceFilesInfo.has(current.getFileName())) {
|
|
82
|
+
const { entity, entitySet } = sourceFilesInfo.get(current.getFileName())
|
|
83
|
+
|
|
84
|
+
if (entity != null && entitySet != null) {
|
|
85
|
+
resolvedPath = await reporter.folders.resolveEntityPath(entity, entitySet, req)
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return resolvedPath
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
jsreportProxy.currentDirectoryPath = async () => {
|
|
96
|
+
const currentPath = await jsreportProxy.currentPath()
|
|
97
|
+
|
|
98
|
+
if (currentPath != null) {
|
|
99
|
+
const localPath = currentPath.substring(0, currentPath.lastIndexOf('/'))
|
|
100
|
+
|
|
101
|
+
if (localPath === '') {
|
|
102
|
+
return '/'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return localPath
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return currentPath
|
|
109
|
+
}
|
|
65
110
|
|
|
66
111
|
// NOTE: it is important that cleanup, restore methods are not called from a function attached to the
|
|
67
112
|
// sandbox, because the arguments and return value of such function call will be sandboxed again, to solve this
|
|
68
113
|
// we don't attach these methods to the sandbox, and instead share them through a "manager" object that should
|
|
69
114
|
// be passed in options
|
|
70
115
|
manager.restore = restore
|
|
71
|
-
manager.contextifyValue = contextifyValue
|
|
72
|
-
manager.decontextifyValue = decontextifyValue
|
|
73
|
-
manager.unproxyValue = unproxyValue
|
|
74
116
|
|
|
75
117
|
const functionNames = getTopLevelFunctions(userCode)
|
|
76
118
|
const functionsCode = `return {${functionNames.map(h => `"${h}": ${h}`).join(',')}}`
|
|
@@ -113,26 +113,12 @@ module.exports = (_sandbox, options = {}) => {
|
|
|
113
113
|
|
|
114
114
|
const vm = new VM()
|
|
115
115
|
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
const wrapAndSaveProxyResult = (originalFn, thisArg) => {
|
|
120
|
-
return (value, ...args) => {
|
|
121
|
-
const result = originalFn.call(thisArg, value, ...args)
|
|
122
|
-
|
|
123
|
-
if (result != null && result.isVMProxy === true) {
|
|
124
|
-
proxiesInVM.set(result, value)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return result
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
vm._internal.Contextify.object = wrapAndSaveProxyResult(vm._internal.Contextify.object, vm._internal.Contextify)
|
|
132
|
-
vm._internal.Decontextify.object = wrapAndSaveProxyResult(vm._internal.Decontextify.object, vm._internal.Decontextify)
|
|
116
|
+
// delete the vm.sandbox.global because it introduces json stringify issues
|
|
117
|
+
// and we don't need such global in context
|
|
118
|
+
delete vm.sandbox.global
|
|
133
119
|
|
|
134
120
|
for (const name in sandbox) {
|
|
135
|
-
vm.
|
|
121
|
+
vm.setGlobal(name, sandbox[name])
|
|
136
122
|
}
|
|
137
123
|
|
|
138
124
|
// processing top level props because getter/setter descriptors
|
|
@@ -141,33 +127,25 @@ module.exports = (_sandbox, options = {}) => {
|
|
|
141
127
|
const currentConfig = propsConfig[key]
|
|
142
128
|
|
|
143
129
|
if (currentConfig.root && currentConfig.root.sandboxReadOnly) {
|
|
144
|
-
readOnlyProp(vm.
|
|
130
|
+
readOnlyProp(vm.sandbox, key, [], customProxies, { onlyTopLevel: true })
|
|
145
131
|
}
|
|
146
132
|
})
|
|
147
133
|
|
|
148
134
|
const sourceFilesInfo = new Map()
|
|
149
135
|
|
|
150
136
|
return {
|
|
151
|
-
sandbox: vm.
|
|
137
|
+
sandbox: vm.sandbox,
|
|
152
138
|
console: _console,
|
|
153
|
-
|
|
154
|
-
return vm._internal.Contextify.value(value)
|
|
155
|
-
},
|
|
156
|
-
decontextifyValue: (value) => {
|
|
157
|
-
return vm._internal.Decontextify.value(value)
|
|
158
|
-
},
|
|
139
|
+
sourceFilesInfo,
|
|
159
140
|
restore: () => {
|
|
160
|
-
return restoreProperties(vm.
|
|
161
|
-
},
|
|
162
|
-
unproxyValue: (value) => {
|
|
163
|
-
return getOriginalFromProxy(proxiesInVM, customProxies, value)
|
|
141
|
+
return restoreProperties(vm.sandbox, originalValues, proxiesInVM, customProxies)
|
|
164
142
|
},
|
|
165
143
|
safeRequire: (modulePath) => _require(modulePath, { context: _sandbox, allowAllModules: true }),
|
|
166
|
-
run: async (code, { filename, errorLineNumberOffset = 0, source, entity } = {}) => {
|
|
144
|
+
run: async (code, { filename, errorLineNumberOffset = 0, source, entity, entitySet } = {}) => {
|
|
167
145
|
const script = new VMScript(code, filename)
|
|
168
146
|
|
|
169
147
|
if (filename != null && source != null) {
|
|
170
|
-
sourceFilesInfo.set(filename, { filename, source, entity, errorLineNumberOffset })
|
|
148
|
+
sourceFilesInfo.set(filename, { filename, source, entity, entitySet, errorLineNumberOffset })
|
|
171
149
|
}
|
|
172
150
|
|
|
173
151
|
// NOTE: if we need to upgrade vm2 we will need to check the source of this function
|
|
@@ -176,6 +154,7 @@ module.exports = (_sandbox, options = {}) => {
|
|
|
176
154
|
// to show nice error when the compile of a script fails
|
|
177
155
|
script._compile = function (prefix, suffix) {
|
|
178
156
|
return new originalVM.Script(prefix + this.getCompiledCode() + suffix, {
|
|
157
|
+
__proto__: null,
|
|
179
158
|
filename: this.filename,
|
|
180
159
|
displayErrors: true,
|
|
181
160
|
lineOffset: this.lineOffset,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsreport/jsreport-core",
|
|
3
|
-
"version": "3.2
|
|
3
|
+
"version": "3.4.2",
|
|
4
4
|
"description": "javascript based business reporting",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"report",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"lodash.set": "4.3.2",
|
|
54
54
|
"lru-cache": "4.1.1",
|
|
55
55
|
"ms": "2.1.3",
|
|
56
|
-
"nanoid": "3.
|
|
57
|
-
"nconf": "0.
|
|
56
|
+
"nanoid": "3.2.0",
|
|
57
|
+
"nconf": "0.11.3",
|
|
58
58
|
"node.extend.without.arrays": "1.1.6",
|
|
59
59
|
"reap2": "1.0.1",
|
|
60
60
|
"semver": "7.3.5",
|
|
@@ -63,8 +63,7 @@
|
|
|
63
63
|
"triple-beam": "1.3.0",
|
|
64
64
|
"unset-value": "1.0.0",
|
|
65
65
|
"uuid": "8.3.2",
|
|
66
|
-
"
|
|
67
|
-
"vm2": "3.9.5",
|
|
66
|
+
"vm2": "3.9.7",
|
|
68
67
|
"winston": "3.3.3",
|
|
69
68
|
"winston-transport": "4.4.0"
|
|
70
69
|
},
|