@jsreport/jsreport-core 3.11.3 → 3.12.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.
@@ -0,0 +1,521 @@
1
+ const extend = require('node.extend.without.arrays')
2
+ const groupBy = require('lodash.groupby')
3
+ const get = require('lodash.get')
4
+ const set = require('lodash.set')
5
+ const hasOwn = require('has-own-deep')
6
+ const unsetValue = require('unset-value')
7
+
8
+ function createPropertiesManager (propertiesConfig) {
9
+ const hierarchyPropertiesConfig = normalizePropertiesConfigToHierarchy(propertiesConfig)
10
+ const originalValues = {}
11
+ const proxiesInVM = new WeakMap()
12
+ const customProxies = new WeakMap()
13
+
14
+ return {
15
+ copyPropertyValuesFrom (context) {
16
+ return copyBasedOnPropertiesConfig(context, propertiesConfig)
17
+ },
18
+ applyPropertiesConfigTo (context) {
19
+ applyPropertiesConfig(context, hierarchyPropertiesConfig, {
20
+ original: originalValues,
21
+ customProxies
22
+ })
23
+ },
24
+ applyRootPropertiesConfigTo (context) {
25
+ Object.keys(hierarchyPropertiesConfig).forEach((key) => {
26
+ const currentConfig = hierarchyPropertiesConfig[key]
27
+
28
+ if (currentConfig.root && currentConfig.root.sandboxReadOnly) {
29
+ readOnlyProp(context, key, [], customProxies, { onlyTopLevel: true })
30
+ }
31
+ })
32
+ },
33
+ restorePropertiesFrom (context) {
34
+ return restoreProperties(context, originalValues, proxiesInVM, customProxies)
35
+ }
36
+ }
37
+ }
38
+
39
+ function normalizePropertiesConfigToHierarchy (propertiesConfig) {
40
+ const configMapKeys = Object.keys(propertiesConfig)
41
+
42
+ const groupedKeys = groupBy(configMapKeys, (key) => {
43
+ const parts = key.split('.')
44
+
45
+ if (parts.length === 1) {
46
+ return ''
47
+ }
48
+
49
+ return parts.slice(0, -1).join('.')
50
+ })
51
+
52
+ const hierarchy = []
53
+ const hierarchyLevels = {}
54
+
55
+ // we sort to ensure that top level properties names are processed first
56
+ Object.keys(groupedKeys).sort(sortPropertiesByLevel).forEach((key) => {
57
+ if (key === '') {
58
+ hierarchy.push('')
59
+ return
60
+ }
61
+
62
+ const parts = key.split('.')
63
+ const lastIndexParts = parts.length - 1
64
+
65
+ if (parts.length === 1) {
66
+ hierarchy.push(parts[0])
67
+ hierarchyLevels[key] = {}
68
+ return
69
+ }
70
+
71
+ for (let i = 0; i < parts.length; i++) {
72
+ const currentKey = parts.slice(0, i + 1).join('.')
73
+ const indexInHierarchy = hierarchy.indexOf(currentKey)
74
+ let parentHierarchy = hierarchyLevels
75
+
76
+ if (indexInHierarchy === -1 && i === lastIndexParts) {
77
+ let parentExistsInTopLevel = false
78
+
79
+ for (let j = 0; j < i; j++) {
80
+ const segmentedKey = parts.slice(0, j + 1).join('.')
81
+
82
+ if (parentExistsInTopLevel !== true) {
83
+ parentExistsInTopLevel = hierarchy.indexOf(segmentedKey) !== -1
84
+ }
85
+
86
+ if (parentHierarchy[segmentedKey] != null) {
87
+ parentHierarchy = parentHierarchy[segmentedKey]
88
+ }
89
+ }
90
+
91
+ if (!parentExistsInTopLevel) {
92
+ hierarchy.push(key)
93
+ }
94
+
95
+ parentHierarchy[key] = {}
96
+ }
97
+ }
98
+ })
99
+
100
+ const toHierarchyConfigMap = (parentLevels) => {
101
+ return (acu, key) => {
102
+ if (key === '') {
103
+ groupedKeys[key].forEach((g) => {
104
+ acu[g] = {}
105
+
106
+ if (propertiesConfig[g] != null) {
107
+ acu[g].root = propertiesConfig[g]
108
+ }
109
+ })
110
+
111
+ return acu
112
+ }
113
+
114
+ const currentLevel = parentLevels[key]
115
+
116
+ if (acu[key] == null) {
117
+ acu[key] = {}
118
+
119
+ if (propertiesConfig[key] != null) {
120
+ // root is config that was defined in the same property
121
+ // that it is grouped
122
+ acu[key].root = propertiesConfig[key]
123
+ }
124
+ }
125
+
126
+ // standalone are properties that are direct, no groups
127
+ acu[key].standalone = groupedKeys[key].reduce((obj, stdProp) => {
128
+ // only add the property is not already grouped
129
+ if (groupedKeys[stdProp] == null) {
130
+ obj[stdProp] = propertiesConfig[stdProp]
131
+ }
132
+
133
+ return obj
134
+ }, {})
135
+
136
+ if (Object.keys(acu[key].standalone).length === 0) {
137
+ delete acu[key].standalone
138
+ }
139
+
140
+ const levelKeys = Object.keys(currentLevel)
141
+
142
+ if (levelKeys.length === 0) {
143
+ return acu
144
+ }
145
+
146
+ // inner are properties which contains other properties, groups
147
+ acu[key].inner = levelKeys.reduce(toHierarchyConfigMap(currentLevel), {})
148
+
149
+ if (Object.keys(acu[key].inner).length === 0) {
150
+ delete acu[key].inner
151
+ }
152
+
153
+ return acu
154
+ }
155
+ }
156
+
157
+ return hierarchy.reduce(toHierarchyConfigMap(hierarchyLevels), {})
158
+ }
159
+
160
+ function copyBasedOnPropertiesConfig (context, propertiesConfig) {
161
+ const copied = []
162
+ const newContext = Object.assign({}, context)
163
+
164
+ Object.keys(propertiesConfig).sort(sortPropertiesByLevel).forEach((prop) => {
165
+ const parts = prop.split('.')
166
+ const lastPartsIndex = parts.length - 1
167
+
168
+ for (let i = 0; i <= lastPartsIndex; i++) {
169
+ let currentContext = newContext
170
+ const propName = parts[i]
171
+ const parentPath = parts.slice(0, i).join('.')
172
+ const fullPropName = parts.slice(0, i + 1).join('.')
173
+ let value
174
+
175
+ if (copied.indexOf(fullPropName) !== -1) {
176
+ continue
177
+ }
178
+
179
+ if (parentPath !== '') {
180
+ currentContext = get(newContext, parentPath)
181
+ }
182
+
183
+ if (currentContext) {
184
+ value = currentContext[propName]
185
+
186
+ if (typeof value === 'object') {
187
+ if (value === null) {
188
+ value = null
189
+ } else if (Array.isArray(value)) {
190
+ value = Object.assign([], value)
191
+ } else {
192
+ value = Object.assign({}, value)
193
+ }
194
+
195
+ currentContext[propName] = value
196
+ copied.push(fullPropName)
197
+ }
198
+ }
199
+ }
200
+ })
201
+
202
+ return newContext
203
+ }
204
+
205
+ function applyPropertiesConfig (context, hierarchyPropertiesConfig, {
206
+ original,
207
+ customProxies,
208
+ isRoot = true,
209
+ isGrouped = true,
210
+ onlyReadOnlyTopLevel = false,
211
+ parentOpts,
212
+ prop
213
+ } = {}, readOnlyConfigured = []) {
214
+ let isHidden
215
+ let isReadOnly
216
+ let standalonePropertiesHandled = false
217
+ let innerPropertiesHandled = false
218
+
219
+ if (isRoot) {
220
+ return Object.keys(hierarchyPropertiesConfig).forEach((key) => {
221
+ applyPropertiesConfig(context, hierarchyPropertiesConfig[key], {
222
+ original,
223
+ customProxies,
224
+ prop: key,
225
+ isRoot: false,
226
+ isGrouped: true,
227
+ onlyReadOnlyTopLevel,
228
+ parentOpts
229
+ }, readOnlyConfigured)
230
+ })
231
+ }
232
+
233
+ if (parentOpts && parentOpts.sandboxHidden === true) {
234
+ return
235
+ }
236
+
237
+ if (isGrouped) {
238
+ isHidden = hierarchyPropertiesConfig.root ? hierarchyPropertiesConfig.root.sandboxHidden === true : false
239
+ isReadOnly = hierarchyPropertiesConfig.root ? hierarchyPropertiesConfig.root.sandboxReadOnly === true : false
240
+ } else {
241
+ isHidden = hierarchyPropertiesConfig ? hierarchyPropertiesConfig.sandboxHidden === true : false
242
+ isReadOnly = hierarchyPropertiesConfig ? hierarchyPropertiesConfig.sandboxReadOnly === true : false
243
+ }
244
+
245
+ let shouldStoreOriginal = isHidden || isReadOnly
246
+
247
+ // prevent storing original value if there is config some child prop
248
+ if (
249
+ shouldStoreOriginal &&
250
+ isGrouped &&
251
+ (hierarchyPropertiesConfig.inner != null || hierarchyPropertiesConfig.standalone != null)
252
+ ) {
253
+ shouldStoreOriginal = false
254
+ }
255
+
256
+ // saving original value
257
+ if (shouldStoreOriginal) {
258
+ let exists = true
259
+ let newValue
260
+
261
+ if (hasOwn(context, prop)) {
262
+ const originalPropValue = get(context, prop)
263
+
264
+ if (typeof originalPropValue === 'object' && originalPropValue != null) {
265
+ if (Array.isArray(originalPropValue)) {
266
+ newValue = extend(true, [], originalPropValue)
267
+ } else {
268
+ newValue = extend(true, {}, originalPropValue)
269
+ }
270
+ } else {
271
+ newValue = originalPropValue
272
+ }
273
+ } else {
274
+ exists = false
275
+ }
276
+
277
+ original[prop] = {
278
+ exists,
279
+ value: newValue
280
+ }
281
+ }
282
+
283
+ const processStandAloneProperties = (c) => {
284
+ Object.keys(c.standalone).forEach((standaloneKey) => {
285
+ const standaloneConfig = c.standalone[standaloneKey]
286
+
287
+ applyPropertiesConfig(context, standaloneConfig, {
288
+ original,
289
+ customProxies,
290
+ prop: standaloneKey,
291
+ isRoot: false,
292
+ isGrouped: false,
293
+ onlyReadOnlyTopLevel,
294
+ parentOpts: { sandboxHidden: isHidden, sandboxReadOnly: isReadOnly }
295
+ }, readOnlyConfigured)
296
+ })
297
+ }
298
+
299
+ const processInnerProperties = (c) => {
300
+ Object.keys(c.inner).forEach((innerKey) => {
301
+ const innerConfig = c.inner[innerKey]
302
+
303
+ applyPropertiesConfig(context, innerConfig, {
304
+ original,
305
+ customProxies,
306
+ prop: innerKey,
307
+ isRoot: false,
308
+ isGrouped: true,
309
+ parentOpts: { sandboxHidden: isHidden, sandboxReadOnly: isReadOnly }
310
+ }, readOnlyConfigured)
311
+ })
312
+ }
313
+
314
+ if (isHidden) {
315
+ omitProp(context, prop)
316
+ } else if (isReadOnly) {
317
+ readOnlyProp(context, prop, readOnlyConfigured, customProxies, {
318
+ onlyTopLevel: false,
319
+ onBeforeProxy: () => {
320
+ if (isGrouped && hierarchyPropertiesConfig.standalone != null) {
321
+ processStandAloneProperties(hierarchyPropertiesConfig)
322
+ standalonePropertiesHandled = true
323
+ }
324
+
325
+ if (isGrouped && hierarchyPropertiesConfig.inner != null) {
326
+ processInnerProperties(hierarchyPropertiesConfig)
327
+ innerPropertiesHandled = true
328
+ }
329
+ }
330
+ })
331
+ }
332
+
333
+ if (!isGrouped) {
334
+ return
335
+ }
336
+
337
+ // don't process inner config when the value in context is empty
338
+ if (get(context, prop) == null) {
339
+ return
340
+ }
341
+
342
+ if (!standalonePropertiesHandled && hierarchyPropertiesConfig.standalone != null) {
343
+ processStandAloneProperties(hierarchyPropertiesConfig)
344
+ }
345
+
346
+ if (!innerPropertiesHandled && hierarchyPropertiesConfig.inner != null) {
347
+ processInnerProperties(hierarchyPropertiesConfig)
348
+ }
349
+ }
350
+
351
+ function restoreProperties (context, originalValues, proxiesInVM, customProxies) {
352
+ const restored = []
353
+ const newContext = Object.assign({}, context)
354
+
355
+ Object.keys(originalValues).sort(sortPropertiesByLevel).forEach((prop) => {
356
+ const confValue = originalValues[prop]
357
+ const parts = prop.split('.')
358
+ const lastPartsIndex = parts.length - 1
359
+
360
+ for (let i = 0; i <= lastPartsIndex; i++) {
361
+ let currentContext = newContext
362
+ const propName = parts[i]
363
+ const parentPath = parts.slice(0, i).join('.')
364
+ const fullPropName = parts.slice(0, i + 1).join('.')
365
+ let value
366
+
367
+ if (restored.indexOf(fullPropName) !== -1) {
368
+ continue
369
+ }
370
+
371
+ if (parentPath !== '') {
372
+ currentContext = get(newContext, parentPath)
373
+ }
374
+
375
+ if (currentContext) {
376
+ value = currentContext[propName]
377
+
378
+ // unwrapping proxies
379
+ value = getOriginalFromProxy(proxiesInVM, customProxies, value)
380
+
381
+ if (typeof value === 'object') {
382
+ // we call object assign to be able to get rid of
383
+ // previous properties descriptors (hide/readOnly) configured
384
+ if (value === null) {
385
+ value = null
386
+ } else if (Array.isArray(value)) {
387
+ value = Object.assign([], value)
388
+ } else {
389
+ value = Object.assign({}, value)
390
+ }
391
+
392
+ currentContext[propName] = value
393
+ restored.push(fullPropName)
394
+ }
395
+
396
+ if (i === lastPartsIndex) {
397
+ if (confValue.exists) {
398
+ currentContext[propName] = confValue.value
399
+ } else {
400
+ delete currentContext[propName]
401
+ }
402
+ }
403
+ }
404
+ }
405
+ })
406
+
407
+ // unwrapping proxies for top level properties
408
+ Object.keys(newContext).forEach((prop) => {
409
+ newContext[prop] = getOriginalFromProxy(proxiesInVM, customProxies, newContext[prop])
410
+ })
411
+
412
+ return newContext
413
+ }
414
+
415
+ function sortPropertiesByLevel (a, b) {
416
+ const parts = a.split('.')
417
+ const parts2 = b.split('.')
418
+
419
+ return parts.length - parts2.length
420
+ }
421
+
422
+ function omitProp (context, prop) {
423
+ // if property has value, then set it to undefined first,
424
+ // unsetValue expects that property has some non empty value to remove the property
425
+ // so we set to "true" to ensure it works for all cases,
426
+ // we use unsetValue instead of lodash.omit because
427
+ // it supports object paths x.y.z and does not copy the object for each call
428
+ if (hasOwn(context, prop)) {
429
+ set(context, prop, true)
430
+ unsetValue(context, prop)
431
+ }
432
+ }
433
+
434
+ function readOnlyProp (context, prop, configured, customProxies, { onlyTopLevel = false, onBeforeProxy } = {}) {
435
+ const parts = prop.split('.')
436
+ const lastPartsIndex = parts.length - 1
437
+
438
+ const throwError = (fullPropName) => {
439
+ throw new Error(`Can't modify read only property "${fullPropName}" inside sandbox`)
440
+ }
441
+
442
+ for (let i = 0; i <= lastPartsIndex; i++) {
443
+ let currentContext = context
444
+ const isTopLevelProp = i === 0
445
+ const propName = parts[i]
446
+ const parentPath = parts.slice(0, i).join('.')
447
+ const fullPropName = parts.slice(0, i + 1).join('.')
448
+ let value
449
+
450
+ if (configured.indexOf(fullPropName) !== -1) {
451
+ continue
452
+ }
453
+
454
+ if (parentPath !== '') {
455
+ currentContext = get(context, parentPath)
456
+ }
457
+
458
+ if (currentContext) {
459
+ value = currentContext[propName]
460
+
461
+ if (
462
+ i === lastPartsIndex &&
463
+ typeof value === 'object' &&
464
+ value != null
465
+ ) {
466
+ const valueType = Array.isArray(value) ? 'array' : 'object'
467
+ const rawValue = value
468
+
469
+ if (onBeforeProxy) {
470
+ onBeforeProxy()
471
+ }
472
+
473
+ value = new Proxy(rawValue, {
474
+ set: (target, prop) => {
475
+ throw new Error(`Can't add or modify property "${prop}" to read only ${valueType} "${fullPropName}" inside sandbox`)
476
+ },
477
+ deleteProperty: (target, prop) => {
478
+ throw new Error(`Can't delete property "${prop}" in read only ${valueType} "${fullPropName}" inside sandbox`)
479
+ }
480
+ })
481
+
482
+ customProxies.set(value, rawValue)
483
+ }
484
+
485
+ // only create the getter/setter wrapper if the property is defined,
486
+ // this prevents getting errors about proxy traps and descriptors differences
487
+ // when calling `JSON.stringify(req.context)` from a script
488
+ if (Object.prototype.hasOwnProperty.call(currentContext, propName)) {
489
+ if (!configured.includes(fullPropName)) {
490
+ configured.push(fullPropName)
491
+ }
492
+
493
+ Object.defineProperty(currentContext, propName, {
494
+ get: () => value,
495
+ set: () => { throwError(fullPropName) },
496
+ enumerable: true
497
+ })
498
+ }
499
+
500
+ if (isTopLevelProp && onlyTopLevel) {
501
+ break
502
+ }
503
+ }
504
+ }
505
+ }
506
+
507
+ function getOriginalFromProxy (proxiesInVM, customProxies, value) {
508
+ let newValue
509
+
510
+ if (customProxies.has(value)) {
511
+ newValue = getOriginalFromProxy(proxiesInVM, customProxies, customProxies.get(value))
512
+ } else if (proxiesInVM.has(value)) {
513
+ newValue = getOriginalFromProxy(proxiesInVM, customProxies, proxiesInVM.get(value))
514
+ } else {
515
+ newValue = value
516
+ }
517
+
518
+ return newValue
519
+ }
520
+
521
+ module.exports = createPropertiesManager
@@ -0,0 +1,117 @@
1
+ const Module = require('module')
2
+ const os = require('os')
3
+ const path = require('path')
4
+ const isolatedRequire = require('./isolatedRequire')
5
+
6
+ module.exports = function createSandboxRequire (safeExecution, isolateModules, modulesCache, {
7
+ rootDirectory,
8
+ requirePaths,
9
+ requireMap,
10
+ allowedModules,
11
+ compileScript,
12
+ formatError
13
+ }) {
14
+ if (rootDirectory == null || !path.isAbsolute(rootDirectory)) {
15
+ throw new Error(`rootDirectory must be an absolute path, path: ${rootDirectory}`)
16
+ }
17
+
18
+ // we pass directory with trailing slash to ensure node recognize the path as directory
19
+ const requireFromRootDirectory = Module.createRequire(ensureTrailingSlash(rootDirectory))
20
+
21
+ let isolatedModulesMeta
22
+
23
+ if (isolateModules) {
24
+ const requireExtensions = Object.create(null)
25
+
26
+ isolatedRequire.setDefaultRequireExtensions(requireExtensions, modulesCache, compileScript)
27
+
28
+ isolatedModulesMeta = {
29
+ modulesCache: modulesCache,
30
+ requireExtensions
31
+ }
32
+ }
33
+
34
+ return function sandboxRequire (moduleId, { context, useMap = true, allowAllModules = false } = {}) {
35
+ if (useMap && requireMap) {
36
+ const mapResult = requireMap(moduleId, { context })
37
+
38
+ if (mapResult != null) {
39
+ return mapResult
40
+ }
41
+ }
42
+
43
+ if (!safeExecution || allowAllModules || allowedModules === '*') {
44
+ return doRequire(moduleId, requireFromRootDirectory, requirePaths, isolatedModulesMeta)
45
+ }
46
+
47
+ const m = allowedModules.find(mod => (mod.id || mod) === moduleId)
48
+
49
+ if (m) {
50
+ return doRequire(m.path || moduleId, requireFromRootDirectory, requirePaths, isolatedModulesMeta)
51
+ }
52
+
53
+ const error = new Error(
54
+ `require of "${moduleId}" module has been blocked.`
55
+ )
56
+
57
+ if (formatError) {
58
+ formatError(error, moduleId)
59
+ }
60
+
61
+ throw error
62
+ }
63
+ }
64
+
65
+ function doRequire (moduleId, requireFromRootDirectory, _requirePaths, isolatedModulesMeta) {
66
+ const isolateModules = isolatedModulesMeta != null
67
+ const searchedPaths = []
68
+ const requirePaths = _requirePaths || []
69
+ const _require = isolateModules ? isolatedRequire : requireFromRootDirectory
70
+ const extraRequireParams = []
71
+
72
+ if (isolateModules) {
73
+ extraRequireParams.push(requireFromRootDirectory, isolatedModulesMeta)
74
+ }
75
+
76
+ let result = executeRequire(_require, moduleId, searchedPaths, ...extraRequireParams)
77
+
78
+ if (!result) {
79
+ let pathsSearched = 0
80
+
81
+ while (!result && pathsSearched < requirePaths.length) {
82
+ const newModuleId = path.join(requirePaths[pathsSearched], moduleId)
83
+ result = executeRequire(_require, newModuleId, searchedPaths, ...extraRequireParams)
84
+ pathsSearched++
85
+ }
86
+ }
87
+
88
+ if (!result) {
89
+ throw new Error(`Unable to find module ${moduleId}${os.EOL}The require calls:${os.EOL}${searchedPaths.map(p => `require('${p}')`).join(os.EOL)}${os.EOL}`)
90
+ }
91
+
92
+ return result
93
+ }
94
+
95
+ function executeRequire (_require, moduleId, searchedPaths, ...restOfParams) {
96
+ try {
97
+ return _require(moduleId, ...restOfParams)
98
+ } catch (e) {
99
+ if (e.code && e.code === 'MODULE_NOT_FOUND') {
100
+ if (!searchedPaths.includes(moduleId)) {
101
+ searchedPaths.push(moduleId)
102
+ }
103
+
104
+ return false
105
+ } else {
106
+ throw new Error(`Unable to require module ${moduleId}. ${e.message}${os.EOL}${e.stack}`)
107
+ }
108
+ }
109
+ }
110
+
111
+ function ensureTrailingSlash (fullPath) {
112
+ if (fullPath.endsWith(path.sep)) {
113
+ return fullPath
114
+ }
115
+
116
+ return fullPath + path.sep
117
+ }