assign-gingerly 0.0.75 → 0.0.76
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/DX/installForwarding.js +1 -1
- package/DX/installForwarding.ts +1 -1
- package/README.md +2 -0
- package/assignFrom.js +18 -17
- package/assignFrom.ts +23 -23
- package/assignFromAsync.js +14 -13
- package/assignFromAsync.ts +15 -15
- package/assignGingerly.js +46 -42
- package/assignGingerly.ts +131 -124
- package/assignPermissions/PermissionProcessor.js +166 -0
- package/assignPermissions/PermissionProcessor.ts +210 -0
- package/assignPermissions/isAllowedImportPath.js +2 -6
- package/assignPermissions/isAllowedImportPath.ts +3 -5
- package/assignPermissions/isAllowedUrl.js +18 -0
- package/assignPermissions/isAllowedUrl.ts +15 -0
- package/assignTentatively.js +9 -11
- package/assignTentatively.ts +11 -13
- package/beVigilant.js +1 -1
- package/beVigilant.ts +1 -1
- package/buildCSSQuery.js +1 -1
- package/buildCSSQuery.ts +1 -1
- package/eachTime.js +5 -6
- package/eachTime.ts +12 -15
- package/enhanceAll.js +3 -3
- package/enhanceAll.ts +4 -4
- package/evaluatePathWithAsyncMethods.js +12 -8
- package/evaluatePathWithAsyncMethods.ts +129 -117
- package/handlers/addEventListener.js +11 -11
- package/handlers/addEventListener.ts +12 -13
- package/handlers/lazyLoad.ts +7 -7
- package/handlers/lazyLoadSwitch.ts +3 -3
- package/handlers/manageTemplateList.js +10 -10
- package/handlers/manageTemplateList.ts +12 -12
- package/handlers/rangeSelector.ts +3 -3
- package/index.js +2 -3
- package/index.ts +2 -3
- package/inferencer/types/assign-gingerly/types.d.ts +37 -8
- package/inferencer/types/el-maker/types.d.ts +33 -0
- package/inferencer/types/scratch-box/types.d.ts +3 -0
- package/inferencer/types/truth-sourcer/types.d.ts +4 -0
- package/package.json +24 -23
- package/parseWithAttrs.js +1 -1
- package/parseWithAttrs.ts +1 -1
- package/processHandlerCommands.js +5 -7
- package/processHandlerCommands.ts +12 -15
- package/{getValues.js → resolve/getValues.js} +19 -8
- package/{getValues.ts → resolve/getValues.ts} +331 -314
- package/{resolveIdRef.js → resolve/resolveIdRef.js} +1 -1
- package/{resolveIdRef.ts → resolve/resolveIdRef.ts} +1 -1
- package/{resolveValues.ts → resolve/resolveValues.ts} +1 -1
- package/types/assign-gingerly/types.d.ts +37 -8
- package/assignPermissions/restrictedProps.js +0 -43
- package/assignPermissions/restrictedProps.ts +0 -53
- package/resolveAndAssignFeatures.js +0 -36
- package/resolveAndAssignFeatures.ts +0 -43
- /package/{resolveTemplate.js → resolve/resolveTemplate.js} +0 -0
- /package/{resolveTemplate.ts → resolve/resolveTemplate.ts} +0 -0
- /package/{resolveValues.js → resolve/resolveValues.js} +0 -0
package/assignGingerly.ts
CHANGED
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { EnhancementConfig } from "./types/assign-gingerly/types";
|
|
4
4
|
import type { AssignFromOptions, FeatureConfigsMap, IAssignGingerlyOptions } from "./types/assign-gingerly/types";
|
|
5
|
-
import type {
|
|
6
|
-
import {
|
|
7
|
-
import type { RestrictedPropSettingsMap } from './assignPermissions/restrictedProps.js';
|
|
8
|
-
import { normalizeAliasOptions } from './getValues.js';
|
|
5
|
+
import type { PermissionProcessor } from './types/assign-gingerly/types.js';
|
|
6
|
+
import { normalizeAliasOptions } from './resolve/getValues.js';
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* Constructor signature for ItemScope Manager classes
|
|
@@ -250,30 +248,30 @@ function isIncCommand(key: string): boolean {
|
|
|
250
248
|
/**
|
|
251
249
|
* Helper function to parse an += command and extract the path
|
|
252
250
|
*/
|
|
253
|
-
function parseIncCommand(key: string): string | null {
|
|
254
|
-
if (!isIncCommand(key)) {
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
return key.substring(0, key.length - 3); // Remove ' +=' suffix
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Apply the scalar and array semantics of the += command.
|
|
262
|
-
*/
|
|
263
|
-
function addValue(lhs: any, rhs: any): any {
|
|
264
|
-
if (Array.isArray(lhs)) {
|
|
265
|
-
return Array.isArray(rhs) ? [...lhs, ...rhs] : [...lhs, rhs];
|
|
266
|
-
}
|
|
267
|
-
if (typeof lhs === 'number' && typeof rhs === 'string') {
|
|
268
|
-
const parsed = Number(rhs);
|
|
269
|
-
return Number.isNaN(parsed) ? lhs + rhs : lhs + parsed;
|
|
270
|
-
}
|
|
271
|
-
if (typeof lhs === 'string' && typeof rhs === 'number') {
|
|
272
|
-
const parsed = Number(lhs);
|
|
273
|
-
return Number.isNaN(parsed) ? lhs + rhs : (parsed + rhs).toString();
|
|
274
|
-
}
|
|
275
|
-
return lhs + rhs;
|
|
276
|
-
}
|
|
251
|
+
function parseIncCommand(key: string): string | null {
|
|
252
|
+
if (!isIncCommand(key)) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
return key.substring(0, key.length - 3); // Remove ' +=' suffix
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Apply the scalar and array semantics of the += command.
|
|
260
|
+
*/
|
|
261
|
+
function addValue(lhs: any, rhs: any): any {
|
|
262
|
+
if (Array.isArray(lhs)) {
|
|
263
|
+
return Array.isArray(rhs) ? [...lhs, ...rhs] : [...lhs, rhs];
|
|
264
|
+
}
|
|
265
|
+
if (typeof lhs === 'number' && typeof rhs === 'string') {
|
|
266
|
+
const parsed = Number(rhs);
|
|
267
|
+
return Number.isNaN(parsed) ? lhs + rhs : lhs + parsed;
|
|
268
|
+
}
|
|
269
|
+
if (typeof lhs === 'string' && typeof rhs === 'number') {
|
|
270
|
+
const parsed = Number(lhs);
|
|
271
|
+
return Number.isNaN(parsed) ? lhs + rhs : (parsed + rhs).toString();
|
|
272
|
+
}
|
|
273
|
+
return lhs + rhs;
|
|
274
|
+
}
|
|
277
275
|
|
|
278
276
|
/**
|
|
279
277
|
* Helper function to check if a key represents a =! command
|
|
@@ -444,6 +442,19 @@ export function isClassInstance(value: any): boolean {
|
|
|
444
442
|
return proto !== Object.prototype && proto !== null;
|
|
445
443
|
}
|
|
446
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Check whether a method name is listed in withMethods and is not restricted
|
|
447
|
+
* by the permission processor. Restricted methods are treated as non-method
|
|
448
|
+
* property names so they fall through to normal access.
|
|
449
|
+
*/
|
|
450
|
+
export function isAllowedMethod(
|
|
451
|
+
methodName: string,
|
|
452
|
+
withMethods: Set<string>,
|
|
453
|
+
permissionProcessor?: PermissionProcessor
|
|
454
|
+
): boolean {
|
|
455
|
+
return withMethods.has(methodName) && !permissionProcessor?.checkRestrictedMethod(methodName);
|
|
456
|
+
}
|
|
457
|
+
|
|
447
458
|
/**
|
|
448
459
|
* Helper function to evaluate a nested path with method calls
|
|
449
460
|
* Handles chained method calls where path segments can be methods
|
|
@@ -454,7 +465,8 @@ export function evaluatePathWithMethods(
|
|
|
454
465
|
target: any,
|
|
455
466
|
pathParts: string[],
|
|
456
467
|
value: any,
|
|
457
|
-
withMethods: Set<string
|
|
468
|
+
withMethods: Set<string>,
|
|
469
|
+
permissionProcessor?: PermissionProcessor
|
|
458
470
|
): { target: any; lastKey: string; isMethod: boolean; isZeroArg: boolean } {
|
|
459
471
|
let current = target;
|
|
460
472
|
let i = 0;
|
|
@@ -466,11 +478,11 @@ export function evaluatePathWithMethods(
|
|
|
466
478
|
|
|
467
479
|
// A trailing | marks a zero-argument method call: 'deref|' calls deref()
|
|
468
480
|
// without consuming the next segment. Only applies to names in withMethods.
|
|
469
|
-
const isZeroArgMethod = part.endsWith('|') &&
|
|
481
|
+
const isZeroArgMethod = part.endsWith('|') && isAllowedMethod(part.slice(0, -1), withMethods, permissionProcessor);
|
|
470
482
|
const nextIsMethod = withMethods.has(nextPart)
|
|
471
483
|
|| (nextPart.endsWith('|') && withMethods.has(nextPart.slice(0, -1)));
|
|
472
484
|
|
|
473
|
-
if (
|
|
485
|
+
if (isAllowedMethod(part, withMethods, permissionProcessor) || isZeroArgMethod) {
|
|
474
486
|
const methodName = isZeroArgMethod ? part.slice(0, -1) : part;
|
|
475
487
|
const method = current[methodName];
|
|
476
488
|
if (typeof method === 'function') {
|
|
@@ -503,12 +515,12 @@ export function evaluatePathWithMethods(
|
|
|
503
515
|
// Strip a trailing | from the last segment only when it names a listed method;
|
|
504
516
|
// otherwise it is a literal property name (e.g. an exotic key ending in |).
|
|
505
517
|
const rawLastKey = pathParts[pathParts.length - 1];
|
|
506
|
-
const isZeroArg = rawLastKey.endsWith('|') &&
|
|
518
|
+
const isZeroArg = rawLastKey.endsWith('|') && isAllowedMethod(rawLastKey.slice(0, -1), withMethods, permissionProcessor);
|
|
507
519
|
const lastKey = isZeroArg ? rawLastKey.slice(0, -1) : rawLastKey;
|
|
508
520
|
return {
|
|
509
521
|
target: current,
|
|
510
522
|
lastKey,
|
|
511
|
-
isMethod:
|
|
523
|
+
isMethod: isAllowedMethod(lastKey, withMethods, permissionProcessor),
|
|
512
524
|
isZeroArg
|
|
513
525
|
};
|
|
514
526
|
}
|
|
@@ -568,8 +580,7 @@ function applyToEach(
|
|
|
568
580
|
withMethods: Set<string>,
|
|
569
581
|
aliasMap: Map<string, string>,
|
|
570
582
|
options?: IAssignGingerlyOptions,
|
|
571
|
-
|
|
572
|
-
restrictedPropSet?: RestrictedPropSettingsMap
|
|
583
|
+
permissionProcessor?: PermissionProcessor
|
|
573
584
|
): void {
|
|
574
585
|
// Convert to array for iteration
|
|
575
586
|
const items = Array.isArray(iterable) ? iterable : Array.from(iterable);
|
|
@@ -594,8 +605,8 @@ function applyToEach(
|
|
|
594
605
|
let current = item;
|
|
595
606
|
for (const part of pathToForEach) {
|
|
596
607
|
// A trailing | marks a zero-argument method call (only for names in withMethods)
|
|
597
|
-
const isZeroArgMethod = part.endsWith('|') &&
|
|
598
|
-
if (
|
|
608
|
+
const isZeroArgMethod = part.endsWith('|') && isAllowedMethod(part.slice(0, -1), withMethods, permissionProcessor);
|
|
609
|
+
if (isAllowedMethod(part, withMethods, permissionProcessor) || isZeroArgMethod) {
|
|
599
610
|
const methodName = isZeroArgMethod ? part.slice(0, -1) : part;
|
|
600
611
|
const method = current[methodName];
|
|
601
612
|
if (typeof method === 'function') {
|
|
@@ -627,11 +638,11 @@ function applyToEach(
|
|
|
627
638
|
|
|
628
639
|
// Recursively apply to the nested iterable
|
|
629
640
|
if (isIterable(current)) {
|
|
630
|
-
applyToEach(current, pathAfterForEach, value, withMethods, aliasMap, options,
|
|
641
|
+
applyToEach(current, pathAfterForEach, value, withMethods, aliasMap, options, permissionProcessor);
|
|
631
642
|
}
|
|
632
643
|
} else {
|
|
633
644
|
// No nested @each, evaluate the remaining path normally
|
|
634
|
-
const result = evaluatePathWithMethods(item, remainingPath, value, withMethods);
|
|
645
|
+
const result = evaluatePathWithMethods(item, remainingPath, value, withMethods, permissionProcessor);
|
|
635
646
|
|
|
636
647
|
if (result.isMethod) {
|
|
637
648
|
// Last segment is a method - call it
|
|
@@ -651,7 +662,7 @@ function applyToEach(
|
|
|
651
662
|
const lastKey = result.lastKey;
|
|
652
663
|
const parent = result.target;
|
|
653
664
|
|
|
654
|
-
if (redirectRestrictedProp(
|
|
665
|
+
if (permissionProcessor?.redirectRestrictedProp(parent, lastKey, value)) {
|
|
655
666
|
// skip
|
|
656
667
|
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
657
668
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
@@ -659,7 +670,7 @@ function applyToEach(
|
|
|
659
670
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
660
671
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
661
672
|
}
|
|
662
|
-
assignGingerly(currentValue, value, options,
|
|
673
|
+
assignGingerly(currentValue, value, options, permissionProcessor);
|
|
663
674
|
} else {
|
|
664
675
|
parent[lastKey] = value;
|
|
665
676
|
}
|
|
@@ -702,7 +713,7 @@ export function assignGingerly(
|
|
|
702
713
|
target: any,
|
|
703
714
|
source: Record<string | symbol, any>,
|
|
704
715
|
options?: IAssignGingerlyOptions,
|
|
705
|
-
|
|
716
|
+
permissionProcessor?: PermissionProcessor
|
|
706
717
|
): any {
|
|
707
718
|
if (!target || typeof target !== 'object') {
|
|
708
719
|
return target;
|
|
@@ -710,9 +721,6 @@ export function assignGingerly(
|
|
|
710
721
|
|
|
711
722
|
const { aliasMap, withMethods: withMethodsSet } = normalizeAliasOptions(options);
|
|
712
723
|
|
|
713
|
-
// Normalize restrictedPropSettings once per top-level call
|
|
714
|
-
const restrictedPropSet = buildRestrictedPropSet(permissions);
|
|
715
|
-
|
|
716
724
|
// Convert withAsyncMethods array to Set for O(1) lookup
|
|
717
725
|
const withAsyncMethodsSet = options?.withAsyncMethods
|
|
718
726
|
? options.withAsyncMethods instanceof Set
|
|
@@ -800,7 +808,7 @@ export function assignGingerly(
|
|
|
800
808
|
let lhsParent: any;
|
|
801
809
|
let lhsKey: string;
|
|
802
810
|
if (withMethodsSet) {
|
|
803
|
-
const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet);
|
|
811
|
+
const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet, permissionProcessor);
|
|
804
812
|
lhsParent = result.target;
|
|
805
813
|
lhsKey = result.lastKey;
|
|
806
814
|
lhsValue = lhsParent[lhsKey];
|
|
@@ -811,49 +819,49 @@ export function assignGingerly(
|
|
|
811
819
|
}
|
|
812
820
|
|
|
813
821
|
//TODO: this logic seems to occur twice at least. Maybe make it a method?
|
|
814
|
-
if (checkRestrictedProp(
|
|
815
|
-
continue;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// Event handler: Element LHS + object RHS with 'on' property
|
|
822
|
+
if (permissionProcessor?.checkRestrictedProp(lhsKey)) {
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Event handler: Element LHS + object RHS with 'on' property
|
|
819
827
|
if (lhsValue instanceof Element && value && typeof value === 'object' && !Array.isArray(value) && 'on' in value) {
|
|
820
828
|
const capturedLhs = lhsValue;
|
|
821
829
|
const capturedValue = value;
|
|
822
830
|
const capturedTarget = target;
|
|
823
|
-
const capturedOptions = options as AssignFromOptions | undefined;
|
|
824
|
-
import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
|
|
825
|
-
attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {},
|
|
831
|
+
const capturedOptions = options as AssignFromOptions | undefined;
|
|
832
|
+
import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
|
|
833
|
+
attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {}, permissionProcessor);
|
|
826
834
|
});
|
|
827
835
|
continue;
|
|
828
836
|
}
|
|
829
837
|
|
|
830
|
-
if (!(lhsKey in lhsParent)) {
|
|
831
|
-
lhsParent[lhsKey] = value;
|
|
832
|
-
} else {
|
|
833
|
-
lhsParent[lhsKey] = addValue(lhsValue, value);
|
|
838
|
+
if (!(lhsKey in lhsParent)) {
|
|
839
|
+
lhsParent[lhsKey] = value;
|
|
840
|
+
} else {
|
|
841
|
+
lhsParent[lhsKey] = addValue(lhsValue, value);
|
|
834
842
|
}
|
|
835
843
|
} else {
|
|
836
|
-
// Plain key - direct operation on target
|
|
837
|
-
if (checkRestrictedProp(
|
|
838
|
-
continue;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// Event handler: Element LHS + object RHS with 'on' property
|
|
844
|
+
// Plain key - direct operation on target
|
|
845
|
+
if (permissionProcessor?.checkRestrictedProp(path)) {
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Event handler: Element LHS + object RHS with 'on' property
|
|
842
850
|
if (target[path] instanceof Element && value && typeof value === 'object' && !Array.isArray(value) && 'on' in value) {
|
|
843
851
|
const capturedLhs = target[path];
|
|
844
852
|
const capturedValue = value;
|
|
845
853
|
const capturedTarget = target;
|
|
846
|
-
const capturedOptions = options as AssignFromOptions | undefined;
|
|
847
|
-
import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
|
|
848
|
-
attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {},
|
|
854
|
+
const capturedOptions = options as AssignFromOptions | undefined;
|
|
855
|
+
import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
|
|
856
|
+
attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {}, permissionProcessor);
|
|
849
857
|
});
|
|
850
858
|
continue;
|
|
851
859
|
}
|
|
852
860
|
|
|
853
|
-
if (!(path in target)) {
|
|
854
|
-
target[path] = value;
|
|
855
|
-
} else {
|
|
856
|
-
target[path] = addValue(target[path], value);
|
|
861
|
+
if (!(path in target)) {
|
|
862
|
+
target[path] = value;
|
|
863
|
+
} else {
|
|
864
|
+
target[path] = addValue(target[path], value);
|
|
857
865
|
}
|
|
858
866
|
}
|
|
859
867
|
}
|
|
@@ -909,7 +917,7 @@ export function assignGingerly(
|
|
|
909
917
|
}
|
|
910
918
|
|
|
911
919
|
// Apply negation to LHS — check restriction first
|
|
912
|
-
if (!checkRestrictedProp(
|
|
920
|
+
if (!permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
|
|
913
921
|
lhsParent[lhsLastKey] = !valueToNegate;
|
|
914
922
|
}
|
|
915
923
|
}
|
|
@@ -961,19 +969,19 @@ export function assignGingerly(
|
|
|
961
969
|
}
|
|
962
970
|
|
|
963
971
|
// Handle Y= merge commands (recursive assignGingerly into sub-object)
|
|
964
|
-
if (isMergeCommand(key)) {
|
|
965
|
-
const path = parseMergeCommand(key);
|
|
966
|
-
if (path) {
|
|
967
|
-
const pathParts = isNestedPath(path) ? parsePath(path) : [path];
|
|
968
|
-
const lastKey = pathParts[pathParts.length - 1];
|
|
969
|
-
if (checkRestrictedProp(
|
|
970
|
-
continue;
|
|
971
|
-
}
|
|
972
|
-
// Navigate to the target sub-object
|
|
973
|
-
let mergeTarget: any;
|
|
972
|
+
if (isMergeCommand(key)) {
|
|
973
|
+
const path = parseMergeCommand(key);
|
|
974
|
+
if (path) {
|
|
975
|
+
const pathParts = isNestedPath(path) ? parsePath(path) : [path];
|
|
976
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
977
|
+
if (permissionProcessor?.checkRestrictedProp(lastKey)) {
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
// Navigate to the target sub-object
|
|
981
|
+
let mergeTarget: any;
|
|
974
982
|
if (isNestedPath(path)) {
|
|
975
983
|
if (withMethodsSet) {
|
|
976
|
-
const result = evaluatePathWithMethods(target, parsePath(path), value, withMethodsSet);
|
|
984
|
+
const result = evaluatePathWithMethods(target, parsePath(path), value, withMethodsSet, permissionProcessor);
|
|
977
985
|
mergeTarget = result.target[result.lastKey];
|
|
978
986
|
} else {
|
|
979
987
|
const pathParts = parsePath(path);
|
|
@@ -993,7 +1001,7 @@ export function assignGingerly(
|
|
|
993
1001
|
|
|
994
1002
|
// Recursively merge if target is a valid object
|
|
995
1003
|
if (mergeTarget && typeof mergeTarget === 'object') {
|
|
996
|
-
assignGingerly(mergeTarget, value, options,
|
|
1004
|
+
assignGingerly(mergeTarget, value, options, permissionProcessor);
|
|
997
1005
|
}
|
|
998
1006
|
}
|
|
999
1007
|
continue;
|
|
@@ -1021,11 +1029,10 @@ export function assignGingerly(
|
|
|
1021
1029
|
pathParts,
|
|
1022
1030
|
forEachIndex,
|
|
1023
1031
|
value,
|
|
1024
|
-
withMethodsSet,
|
|
1025
|
-
aliasMap,
|
|
1026
|
-
options,
|
|
1027
|
-
|
|
1028
|
-
restrictedPropSet
|
|
1032
|
+
withMethodsSet,
|
|
1033
|
+
aliasMap,
|
|
1034
|
+
options,
|
|
1035
|
+
permissionProcessor
|
|
1029
1036
|
);
|
|
1030
1037
|
} catch (error) {
|
|
1031
1038
|
console.error('Error in @eachTime:', error);
|
|
@@ -1042,7 +1049,7 @@ export function assignGingerly(
|
|
|
1042
1049
|
let current = target;
|
|
1043
1050
|
if (pathToForEach.length > 0) {
|
|
1044
1051
|
if (withMethodsSet) {
|
|
1045
|
-
const result = evaluatePathWithMethods(target, pathToForEach, value, withMethodsSet);
|
|
1052
|
+
const result = evaluatePathWithMethods(target, pathToForEach, value, withMethodsSet, permissionProcessor);
|
|
1046
1053
|
// The result.target is the current position after evaluating the path
|
|
1047
1054
|
// This is already the iterable we want
|
|
1048
1055
|
current = result.target;
|
|
@@ -1055,7 +1062,7 @@ export function assignGingerly(
|
|
|
1055
1062
|
|
|
1056
1063
|
// Apply to each item in the iterable
|
|
1057
1064
|
if (isIterable(current)) {
|
|
1058
|
-
applyToEach(current, pathAfterForEach, value, withMethodsSet || new Set(), aliasMap, options,
|
|
1065
|
+
applyToEach(current, pathAfterForEach, value, withMethodsSet || new Set(), aliasMap, options, permissionProcessor);
|
|
1059
1066
|
}
|
|
1060
1067
|
// If not iterable, let JavaScript throw error naturally when trying to iterate
|
|
1061
1068
|
|
|
@@ -1069,15 +1076,15 @@ export function assignGingerly(
|
|
|
1069
1076
|
const capturedTarget = target;
|
|
1070
1077
|
const capturedPathParts = pathParts;
|
|
1071
1078
|
const capturedValue = value;
|
|
1072
|
-
const capturedWithMethodsSet = withMethodsSet || new Set<string>();
|
|
1073
|
-
const capturedOptions = options;
|
|
1074
|
-
const
|
|
1075
|
-
const capturedRestrictedPropSet = restrictedPropSet;
|
|
1079
|
+
const capturedWithMethodsSet = withMethodsSet || new Set<string>();
|
|
1080
|
+
const capturedOptions = options;
|
|
1081
|
+
const capturedPermissionProcessor = permissionProcessor;
|
|
1076
1082
|
(async () => {
|
|
1077
1083
|
const { evaluatePathWithAsyncMethods } = await import('./evaluatePathWithAsyncMethods.js');
|
|
1078
1084
|
const result = await evaluatePathWithAsyncMethods(
|
|
1079
1085
|
capturedTarget, capturedPathParts, capturedValue,
|
|
1080
|
-
capturedWithMethodsSet, withAsyncMethodsSet
|
|
1086
|
+
capturedWithMethodsSet, withAsyncMethodsSet,
|
|
1087
|
+
capturedPermissionProcessor
|
|
1081
1088
|
);
|
|
1082
1089
|
|
|
1083
1090
|
if (result.isMethod || result.isAsyncMethod) {
|
|
@@ -1096,16 +1103,16 @@ export function assignGingerly(
|
|
|
1096
1103
|
} else {
|
|
1097
1104
|
// Not a method — assign the value
|
|
1098
1105
|
const lastKey = result.lastKey;
|
|
1099
|
-
const parent = result.target;
|
|
1100
|
-
if (redirectRestrictedProp(
|
|
1101
|
-
// skip
|
|
1102
|
-
} else if (typeof capturedValue === 'object' && capturedValue !== null && !Array.isArray(capturedValue)) {
|
|
1106
|
+
const parent = result.target;
|
|
1107
|
+
if (capturedPermissionProcessor?.redirectRestrictedProp(parent, lastKey, capturedValue)) {
|
|
1108
|
+
// skip
|
|
1109
|
+
} else if (typeof capturedValue === 'object' && capturedValue !== null && !Array.isArray(capturedValue)) {
|
|
1103
1110
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
1104
1111
|
const currentValue = parent[lastKey];
|
|
1105
1112
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
1106
1113
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
1107
1114
|
}
|
|
1108
|
-
assignGingerly(currentValue, capturedValue, capturedOptions,
|
|
1115
|
+
assignGingerly(currentValue, capturedValue, capturedOptions, capturedPermissionProcessor);
|
|
1109
1116
|
} else {
|
|
1110
1117
|
parent[lastKey] = capturedValue;
|
|
1111
1118
|
}
|
|
@@ -1119,7 +1126,7 @@ export function assignGingerly(
|
|
|
1119
1126
|
|
|
1120
1127
|
// Check if we need to handle methods
|
|
1121
1128
|
if (withMethodsSet) {
|
|
1122
|
-
const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet);
|
|
1129
|
+
const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet, permissionProcessor);
|
|
1123
1130
|
|
|
1124
1131
|
if (result.isMethod) {
|
|
1125
1132
|
// Last segment is a method - call it
|
|
@@ -1142,19 +1149,19 @@ export function assignGingerly(
|
|
|
1142
1149
|
const lastKey = result.lastKey;
|
|
1143
1150
|
const parent = result.target;
|
|
1144
1151
|
|
|
1145
|
-
if (redirectRestrictedProp(
|
|
1146
|
-
// skip
|
|
1147
|
-
// Check for static assignTo protocol
|
|
1148
|
-
} else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
|
|
1149
|
-
continue;
|
|
1150
|
-
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1152
|
+
if (permissionProcessor?.redirectRestrictedProp(parent, lastKey, value)) {
|
|
1153
|
+
// skip
|
|
1154
|
+
// Check for static assignTo protocol
|
|
1155
|
+
} else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
|
|
1156
|
+
continue;
|
|
1157
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1151
1158
|
// Check if property exists and is readonly
|
|
1152
1159
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
1153
1160
|
const currentValue = parent[lastKey];
|
|
1154
1161
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
1155
1162
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
1156
1163
|
}
|
|
1157
|
-
assignGingerly(currentValue, value, options,
|
|
1164
|
+
assignGingerly(currentValue, value, options, permissionProcessor);
|
|
1158
1165
|
} else {
|
|
1159
1166
|
// Property is writable - replace it
|
|
1160
1167
|
parent[lastKey] = value;
|
|
@@ -1167,12 +1174,12 @@ export function assignGingerly(
|
|
|
1167
1174
|
const lastKey = pathParts[pathParts.length - 1];
|
|
1168
1175
|
const parent = ensureNestedPath(target, pathParts);
|
|
1169
1176
|
|
|
1170
|
-
if (redirectRestrictedProp(
|
|
1171
|
-
// skip
|
|
1172
|
-
// Check for static assignTo protocol
|
|
1173
|
-
} else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
|
|
1174
|
-
continue;
|
|
1175
|
-
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1177
|
+
if (permissionProcessor?.redirectRestrictedProp(parent, lastKey, value)) {
|
|
1178
|
+
// skip
|
|
1179
|
+
// Check for static assignTo protocol
|
|
1180
|
+
} else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
|
|
1181
|
+
continue;
|
|
1182
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1176
1183
|
// Check if property exists and is readonly
|
|
1177
1184
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
1178
1185
|
// Property is readonly - check if current value is an object
|
|
@@ -1181,7 +1188,7 @@ export function assignGingerly(
|
|
|
1181
1188
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
1182
1189
|
}
|
|
1183
1190
|
// Recursively apply assignGingerly to the readonly object
|
|
1184
|
-
assignGingerly(currentValue, value, options,
|
|
1191
|
+
assignGingerly(currentValue, value, options, permissionProcessor);
|
|
1185
1192
|
} else {
|
|
1186
1193
|
// Property is writable - replace it
|
|
1187
1194
|
parent[lastKey] = value;
|
|
@@ -1212,12 +1219,12 @@ export function assignGingerly(
|
|
|
1212
1219
|
continue;
|
|
1213
1220
|
}
|
|
1214
1221
|
|
|
1215
|
-
if (redirectRestrictedProp(
|
|
1216
|
-
// skip
|
|
1217
|
-
// Check for static assignTo protocol
|
|
1218
|
-
} else if (key in target && tryAssignTo(target[key], value, target, key)) {
|
|
1219
|
-
continue;
|
|
1220
|
-
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1222
|
+
if (permissionProcessor?.redirectRestrictedProp(target, key, value)) {
|
|
1223
|
+
// skip
|
|
1224
|
+
// Check for static assignTo protocol
|
|
1225
|
+
} else if (key in target && tryAssignTo(target[key], value, target, key)) {
|
|
1226
|
+
continue;
|
|
1227
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1221
1228
|
// Check if property exists and is readonly
|
|
1222
1229
|
if (key in target && isReadonlyProperty(target, key)) {
|
|
1223
1230
|
// Property is readonly - check if current value is an object
|
|
@@ -1226,7 +1233,7 @@ export function assignGingerly(
|
|
|
1226
1233
|
throw new Error(`Cannot merge object into readonly primitive property '${String(key)}'`);
|
|
1227
1234
|
}
|
|
1228
1235
|
// Recursively apply assignGingerly to the readonly object
|
|
1229
|
-
assignGingerly(currentValue, value, options,
|
|
1236
|
+
assignGingerly(currentValue, value, options, permissionProcessor);
|
|
1230
1237
|
} else {
|
|
1231
1238
|
// Property is writable - replace it
|
|
1232
1239
|
target[key] = value;
|
|
@@ -1370,7 +1377,7 @@ export function assignGingerly(
|
|
|
1370
1377
|
// Fire-and-forget bulk enhancements (async, non-blocking)
|
|
1371
1378
|
if (options?.enhance && options.enhance.length > 0 && typeof target === 'object' && target instanceof Element) {
|
|
1372
1379
|
import('./enhanceAll.js').then(({ enhanceAll }) => {
|
|
1373
|
-
enhanceAll(target, options!.enhance!,
|
|
1380
|
+
enhanceAll(target, options!.enhance!, permissionProcessor);
|
|
1374
1381
|
});
|
|
1375
1382
|
}
|
|
1376
1383
|
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { isAllowedUrl } from './isAllowedUrl.js';
|
|
2
|
+
export class PermissionProcessor {
|
|
3
|
+
constructor(permissions) {
|
|
4
|
+
this.permissions = permissions;
|
|
5
|
+
const { props, attrs } = buildMaps(permissions);
|
|
6
|
+
this.props = props;
|
|
7
|
+
this.attrs = attrs;
|
|
8
|
+
this.methods = buildMethodSet(permissions);
|
|
9
|
+
this.warned = new Set();
|
|
10
|
+
this.warnedMethods = new Set();
|
|
11
|
+
}
|
|
12
|
+
get crossDomainImports() {
|
|
13
|
+
return !!this.permissions?.crossDomainImports;
|
|
14
|
+
}
|
|
15
|
+
get hasProps() {
|
|
16
|
+
return this.props.size > 0;
|
|
17
|
+
}
|
|
18
|
+
get hasAttrs() {
|
|
19
|
+
return this.attrs.size > 0;
|
|
20
|
+
}
|
|
21
|
+
checkRestrictedProp(key) {
|
|
22
|
+
if (!this.props.has(key))
|
|
23
|
+
return false;
|
|
24
|
+
this.warnRestricted(key);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
checkRestrictedMethod(methodName) {
|
|
28
|
+
if (!this.methods.has(methodName))
|
|
29
|
+
return false;
|
|
30
|
+
this.warnRestrictedMethod(methodName);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
redirectRestrictedProp(target, key, value) {
|
|
34
|
+
if (!this.props.has(key))
|
|
35
|
+
return false;
|
|
36
|
+
const setting = this.props.get(key);
|
|
37
|
+
const { allowCrossDomain, allowFromSameDomain } = setting || {};
|
|
38
|
+
if (allowCrossDomain) {
|
|
39
|
+
return this.applyAllowedSetting(setting, target, key, value);
|
|
40
|
+
}
|
|
41
|
+
if (allowFromSameDomain) {
|
|
42
|
+
if (isAllowedUrl(value)) {
|
|
43
|
+
return this.applyAllowedSetting(setting, target, key, value);
|
|
44
|
+
}
|
|
45
|
+
this.warnRestricted(key);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (setting?.useMethod) {
|
|
49
|
+
const method = target?.[setting.useMethod];
|
|
50
|
+
if (typeof method === 'function') {
|
|
51
|
+
method.call(target, value);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
this.warnRestricted(key);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
checkRestrictedAttributeCall(methodName, args) {
|
|
59
|
+
if (methodName !== 'setAttribute' || args.length === 0 || this.attrs.size === 0) {
|
|
60
|
+
return { blocked: false };
|
|
61
|
+
}
|
|
62
|
+
const attrName = String(args[0]);
|
|
63
|
+
if (!this.attrs.has(attrName)) {
|
|
64
|
+
return { blocked: false };
|
|
65
|
+
}
|
|
66
|
+
const setting = this.attrs.get(attrName);
|
|
67
|
+
const value = args[1];
|
|
68
|
+
const { allowCrossDomain, allowFromSameDomain } = setting || {};
|
|
69
|
+
if (allowCrossDomain) {
|
|
70
|
+
return { blocked: false, attrName };
|
|
71
|
+
}
|
|
72
|
+
if (allowFromSameDomain) {
|
|
73
|
+
if (isAllowedUrl(value)) {
|
|
74
|
+
return { blocked: false, attrName };
|
|
75
|
+
}
|
|
76
|
+
this.warnRestricted(attrName);
|
|
77
|
+
return { blocked: true, attrName };
|
|
78
|
+
}
|
|
79
|
+
this.warnRestricted(attrName);
|
|
80
|
+
return { blocked: true, attrName };
|
|
81
|
+
}
|
|
82
|
+
applyAllowedSetting(setting, target, key, value) {
|
|
83
|
+
if (setting?.useMethod) {
|
|
84
|
+
const method = target?.[setting.useMethod];
|
|
85
|
+
if (typeof method === 'function') {
|
|
86
|
+
method.call(target, value);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
this.warnRestricted(key);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
warnRestricted(key) {
|
|
95
|
+
if (!this.warned.has(key)) {
|
|
96
|
+
this.warned.add(key);
|
|
97
|
+
console.warn(`assignGingerly: property '${key}' is in restrictedPropSettings — assignment skipped.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
warnRestrictedMethod(methodName) {
|
|
101
|
+
if (!this.warnedMethods.has(methodName)) {
|
|
102
|
+
this.warnedMethods.add(methodName);
|
|
103
|
+
console.warn(`assignGingerly: method '${methodName}' is in restrictedMethodSettings — method call skipped.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function buildMaps(permissions) {
|
|
108
|
+
const settings = permissions?.restrictedPropSettings;
|
|
109
|
+
const props = new Map();
|
|
110
|
+
const attrs = new Map();
|
|
111
|
+
if (!settings || settings.length === 0) {
|
|
112
|
+
return { props, attrs };
|
|
113
|
+
}
|
|
114
|
+
for (const setting of settings) {
|
|
115
|
+
if (typeof setting === 'string') {
|
|
116
|
+
if (props.has(setting)) {
|
|
117
|
+
throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${setting}'.`);
|
|
118
|
+
}
|
|
119
|
+
props.set(setting, undefined);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const propNames = normalizeStrings(setting.props);
|
|
123
|
+
if (propNames.length === 0) {
|
|
124
|
+
throw new Error('assignGingerly: restrictedPropSettings object entry must have a props value.');
|
|
125
|
+
}
|
|
126
|
+
for (const prop of propNames) {
|
|
127
|
+
if (props.has(prop)) {
|
|
128
|
+
throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${prop}'.`);
|
|
129
|
+
}
|
|
130
|
+
props.set(prop, setting);
|
|
131
|
+
}
|
|
132
|
+
const attrNames = normalizeAttrNames(setting.attr, propNames);
|
|
133
|
+
for (const attr of attrNames) {
|
|
134
|
+
if (attrs.has(attr)) {
|
|
135
|
+
throw new Error(`assignGingerly: duplicate restrictedPropSettings attr entry for '${attr}'.`);
|
|
136
|
+
}
|
|
137
|
+
attrs.set(attr, setting);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { props, attrs };
|
|
141
|
+
}
|
|
142
|
+
function normalizeStrings(value) {
|
|
143
|
+
if (value === undefined)
|
|
144
|
+
return [];
|
|
145
|
+
return Array.isArray(value) ? value : [value];
|
|
146
|
+
}
|
|
147
|
+
function normalizeAttrNames(attr, propNames) {
|
|
148
|
+
if (attr === undefined || attr === false)
|
|
149
|
+
return [];
|
|
150
|
+
if (attr === true)
|
|
151
|
+
return propNames;
|
|
152
|
+
return normalizeStrings(attr);
|
|
153
|
+
}
|
|
154
|
+
function buildMethodSet(permissions) {
|
|
155
|
+
const methodSettings = permissions?.restrictedMethodSettings;
|
|
156
|
+
const methods = new Set();
|
|
157
|
+
if (!methodSettings || methodSettings.length === 0) {
|
|
158
|
+
return methods;
|
|
159
|
+
}
|
|
160
|
+
for (const setting of methodSettings) {
|
|
161
|
+
if (typeof setting === 'string') {
|
|
162
|
+
methods.add(setting);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return methods;
|
|
166
|
+
}
|