assign-gingerly 0.0.72 → 0.0.73
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/assignFrom.ts +1 -1
- package/assignFromAsync.ts +1 -1
- package/assignGingerly.js +1 -1
- package/assignGingerly.ts +3 -2
- package/assignPermissions/isAllowedImportPath.js +41 -0
- package/assignPermissions/isAllowedImportPath.ts +38 -0
- package/assignPermissions/restrictedProps.js +43 -0
- package/assignPermissions/restrictedProps.ts +53 -0
- package/assignTentatively.js +1 -1
- package/assignTentatively.ts +2 -2
- package/eachTime.js +1 -1
- package/eachTime.ts +3 -2
- package/enhanceAll.js +2 -2
- package/enhanceAll.ts +3 -3
- package/handlers/addEventListener.ts +1 -1
- package/handlers/lazyLoad.ts +1 -1
- package/handlers/lazyLoadSwitch.ts +1 -1
- package/handlers/manageTemplateList.ts +1 -1
- package/handlers/rangeSelector.ts +1 -1
- package/inferencer/types/assign-gingerly/types.d.ts +3 -1
- package/inferencer/types/nested-regex-groups/types.d.ts +12 -0
- package/package.json +5 -4
- package/processHandlerCommands.js +3 -2
- package/processHandlerCommands.ts +4 -3
- package/types/assign-gingerly/types.d.ts +11 -9
- package/isAllowedImportPath.js +0 -104
- package/isAllowedImportPath.ts +0 -118
package/assignFrom.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { getValues, getValue } from './getValues.js';
|
|
|
13
13
|
import assignGingerly from './assignGingerly.js';
|
|
14
14
|
import { resolveIdVariable, parseIdRef } from './resolveIdRef.js';
|
|
15
15
|
import { processInferredAssignments } from './inferredAssignments.js';
|
|
16
|
-
import type { AssignPermissions } from './
|
|
16
|
+
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
17
17
|
import type { AssignFromOptions, AssignFromHandler, AssignFromHandlerConstructor } from './types/assign-gingerly/types.js';
|
|
18
18
|
|
|
19
19
|
// Re-export types for consumers
|
package/assignFromAsync.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import { resolveValues } from './resolveValues.js';
|
|
23
23
|
import {IAssignGingerlyOptions} from './types/assign-gingerly/types.js';
|
|
24
24
|
import assignGingerly from './assignGingerly.js';
|
|
25
|
-
import type { AssignPermissions } from './
|
|
25
|
+
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
26
26
|
import type { AssignFromHandler, AssignFromHandlerConstructor } from './types/assign-gingerly/types.js';
|
|
27
27
|
import {
|
|
28
28
|
expandSubstitutions, categorizeKeys, handleSpreads, isHandlerCommand
|
package/assignGingerly.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildRestrictedPropSet, checkRestrictedProp, redirectRestrictedProp } from './
|
|
1
|
+
import { buildRestrictedPropSet, checkRestrictedProp, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
2
2
|
import { normalizeAliasOptions } from './getValues.js';
|
|
3
3
|
/**
|
|
4
4
|
* GUID for global instance map storage to ensure uniqueness across package versions
|
package/assignGingerly.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
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 { AssignPermissions
|
|
6
|
-
import { buildRestrictedPropSet, checkRestrictedProp, redirectRestrictedProp } from './
|
|
5
|
+
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
6
|
+
import { buildRestrictedPropSet, checkRestrictedProp, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
7
|
+
import type { RestrictedPropSettingsMap } from './assignPermissions/restrictedProps.js';
|
|
7
8
|
import { normalizeAliasOptions } from './getValues.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check whether an import specifier resolves to the current origin or is
|
|
3
|
+
* covered by an import-map entry.
|
|
4
|
+
*/
|
|
5
|
+
export function isAllowedImportPath(value) {
|
|
6
|
+
if (typeof document === 'undefined' || typeof location === 'undefined')
|
|
7
|
+
return false;
|
|
8
|
+
if (!value || !(value.startsWith('./') || value.startsWith('../') || value.startsWith('/'))) {
|
|
9
|
+
return isImportMapSpecifier(value);
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
return new URL(value, document.baseURI).origin === location.origin;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function isImportMapSpecifier(value) {
|
|
19
|
+
const importMaps = document.querySelectorAll('script[type="importmap"]');
|
|
20
|
+
for (const importMap of importMaps) {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(importMap.textContent ?? '');
|
|
23
|
+
if (!isImportMap(parsed))
|
|
24
|
+
continue;
|
|
25
|
+
for (const key of Object.keys(parsed.imports)) {
|
|
26
|
+
if (key.endsWith('/') ? value.startsWith(key) : value === key)
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Ignore malformed import maps and continue searching.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function isImportMap(value) {
|
|
37
|
+
if (!value || typeof value !== 'object' || !('imports' in value))
|
|
38
|
+
return false;
|
|
39
|
+
const { imports } = value;
|
|
40
|
+
return !!imports && typeof imports === 'object' && !Array.isArray(imports);
|
|
41
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check whether an import specifier resolves to the current origin or is
|
|
3
|
+
* covered by an import-map entry.
|
|
4
|
+
*/
|
|
5
|
+
export function isAllowedImportPath(value: string): boolean {
|
|
6
|
+
if (typeof document === 'undefined' || typeof location === 'undefined') return false;
|
|
7
|
+
if (!value || !(value.startsWith('./') || value.startsWith('../') || value.startsWith('/'))) {
|
|
8
|
+
return isImportMapSpecifier(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
return new URL(value, document.baseURI).origin === location.origin;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isImportMapSpecifier(value: string): boolean {
|
|
19
|
+
const importMaps = document.querySelectorAll('script[type="importmap"]');
|
|
20
|
+
for (const importMap of importMaps) {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(importMap.textContent ?? '');
|
|
23
|
+
if (!isImportMap(parsed)) continue;
|
|
24
|
+
for (const key of Object.keys(parsed.imports)) {
|
|
25
|
+
if (key.endsWith('/') ? value.startsWith(key) : value === key) return true;
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// Ignore malformed import maps and continue searching.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isImportMap(value: unknown): value is { imports: Record<string, unknown> } {
|
|
35
|
+
if (!value || typeof value !== 'object' || !('imports' in value)) return false;
|
|
36
|
+
const { imports } = value;
|
|
37
|
+
return !!imports && typeof imports === 'object' && !Array.isArray(imports);
|
|
38
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const warnedOnce = new Set();
|
|
2
|
+
function warnRestricted(key) {
|
|
3
|
+
if (!warnedOnce.has(key)) {
|
|
4
|
+
warnedOnce.add(key);
|
|
5
|
+
console.warn(`assignGingerly: property '${key}' is in restrictedPropSettings — assignment skipped.`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function buildRestrictedPropSet(permissions) {
|
|
9
|
+
const settings = permissions?.restrictedPropSettings;
|
|
10
|
+
if (!settings || settings.length === 0)
|
|
11
|
+
return undefined;
|
|
12
|
+
const restrictedPropSet = new Map();
|
|
13
|
+
for (const setting of settings) {
|
|
14
|
+
const prop = typeof setting === 'string' ? setting : setting.prop;
|
|
15
|
+
if (restrictedPropSet.has(prop)) {
|
|
16
|
+
throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${prop}'.`);
|
|
17
|
+
}
|
|
18
|
+
restrictedPropSet.set(prop, typeof setting === 'string' ? undefined : setting);
|
|
19
|
+
}
|
|
20
|
+
return restrictedPropSet;
|
|
21
|
+
}
|
|
22
|
+
export function checkRestrictedProp(restrictedPropSet, key) {
|
|
23
|
+
if (!restrictedPropSet || !restrictedPropSet.has(key))
|
|
24
|
+
return false;
|
|
25
|
+
warnRestricted(key);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
export function redirectRestrictedProp(restrictedPropSet, target, key, value) {
|
|
29
|
+
if (!restrictedPropSet || !restrictedPropSet.has(key))
|
|
30
|
+
return false;
|
|
31
|
+
const setting = restrictedPropSet.get(key);
|
|
32
|
+
if (!setting?.useMethod) {
|
|
33
|
+
warnRestricted(key);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const method = target?.[setting.useMethod];
|
|
37
|
+
if (typeof method !== 'function') {
|
|
38
|
+
warnRestricted(key);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
method.call(target, value);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AssignPermissions, RestrictedPropSetting } from '../types/assign-gingerly/types.js';
|
|
2
|
+
|
|
3
|
+
export type RestrictedPropSettingsMap = Map<string, RestrictedPropSetting | undefined>;
|
|
4
|
+
|
|
5
|
+
const warnedOnce = new Set<string>();
|
|
6
|
+
|
|
7
|
+
function warnRestricted(key: string): void {
|
|
8
|
+
if (!warnedOnce.has(key)) {
|
|
9
|
+
warnedOnce.add(key);
|
|
10
|
+
console.warn(`assignGingerly: property '${key}' is in restrictedPropSettings — assignment skipped.`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildRestrictedPropSet(permissions: AssignPermissions | undefined): RestrictedPropSettingsMap | undefined {
|
|
15
|
+
const settings = permissions?.restrictedPropSettings;
|
|
16
|
+
if (!settings || settings.length === 0) return undefined;
|
|
17
|
+
const restrictedPropSet: RestrictedPropSettingsMap = new Map();
|
|
18
|
+
for (const setting of settings) {
|
|
19
|
+
const prop = typeof setting === 'string' ? setting : setting.prop;
|
|
20
|
+
if (restrictedPropSet.has(prop)) {
|
|
21
|
+
throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${prop}'.`);
|
|
22
|
+
}
|
|
23
|
+
restrictedPropSet.set(prop, typeof setting === 'string' ? undefined : setting);
|
|
24
|
+
}
|
|
25
|
+
return restrictedPropSet;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function checkRestrictedProp(restrictedPropSet: RestrictedPropSettingsMap | undefined, key: string): boolean {
|
|
29
|
+
if (!restrictedPropSet || !restrictedPropSet.has(key)) return false;
|
|
30
|
+
warnRestricted(key);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function redirectRestrictedProp(
|
|
35
|
+
restrictedPropSet: RestrictedPropSettingsMap | undefined,
|
|
36
|
+
target: any,
|
|
37
|
+
key: string,
|
|
38
|
+
value: any
|
|
39
|
+
): boolean {
|
|
40
|
+
if (!restrictedPropSet || !restrictedPropSet.has(key)) return false;
|
|
41
|
+
const setting = restrictedPropSet.get(key);
|
|
42
|
+
if (!setting?.useMethod) {
|
|
43
|
+
warnRestricted(key);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const method = target?.[setting.useMethod];
|
|
47
|
+
if (typeof method !== 'function') {
|
|
48
|
+
warnRestricted(key);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
method.call(target, value);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
package/assignTentatively.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildRestrictedPropSet, checkRestrictedProp } from './
|
|
1
|
+
import { buildRestrictedPropSet, checkRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
2
2
|
/**
|
|
3
3
|
* Helper function to check if a string key represents an += command
|
|
4
4
|
*/
|
package/assignTentatively.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* assignTentatively — reversible assignment with change tracking.
|
|
3
3
|
*/
|
|
4
4
|
import type { IAssignTentativelyOptions } from './types/assign-gingerly/types.js';
|
|
5
|
-
import type { AssignPermissions } from './
|
|
6
|
-
import { buildRestrictedPropSet, checkRestrictedProp } from './
|
|
5
|
+
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
6
|
+
import { buildRestrictedPropSet, checkRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
7
7
|
export type { IAssignTentativelyOptions };
|
|
8
8
|
|
|
9
9
|
/**
|
package/eachTime.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* This module is dynamically loaded only when @eachTime is encountered
|
|
4
4
|
* Provides event-driven iteration over elements as they mount
|
|
5
5
|
*/
|
|
6
|
-
import { redirectRestrictedProp } from './
|
|
6
|
+
import { redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
7
7
|
/**
|
|
8
8
|
* Check if a value is an EventTarget
|
|
9
9
|
*/
|
package/eachTime.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { IAssignGingerlyOptions } from './types/assign-gingerly/types.js';
|
|
8
|
-
import type { AssignPermissions
|
|
9
|
-
import { redirectRestrictedProp } from './
|
|
8
|
+
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
9
|
+
import { redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
10
|
+
import type { RestrictedPropSettingsMap } from './assignPermissions/restrictedProps.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Check if a value is an EventTarget
|
package/enhanceAll.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* { emc: 'be-observant/emc.json', matching: '[itemprop]' },
|
|
16
16
|
* ]);
|
|
17
17
|
*/
|
|
18
|
-
import { isAllowedImportPath } from './isAllowedImportPath.js';
|
|
18
|
+
import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
|
|
19
19
|
/**
|
|
20
20
|
* Apply enhancements in bulk to matching elements within a target.
|
|
21
21
|
*
|
|
@@ -37,7 +37,7 @@ export async function enhanceAll(target, configs, permissions) {
|
|
|
37
37
|
// Validate EMC path unless cross-domain imports are explicitly permitted
|
|
38
38
|
if (!permissions?.crossDomainImports && !isAllowedImportPath(config.emc)) {
|
|
39
39
|
throw new Error(`enhanceAll: EMC path "${config.emc}" is a cross-domain URL. ` +
|
|
40
|
-
`Only
|
|
40
|
+
`Only same-origin paths or import-map-covered specifiers are allowed by default. ` +
|
|
41
41
|
`Pass { crossDomainImports: true } in permissions to override.`);
|
|
42
42
|
}
|
|
43
43
|
// 1. Import the EMC JSON
|
package/enhanceAll.ts
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* ]);
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { isAllowedImportPath } from './isAllowedImportPath.js';
|
|
20
|
-
import type { AssignPermissions } from './
|
|
19
|
+
import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
|
|
20
|
+
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Configuration for a single enhancement to apply in bulk.
|
|
@@ -57,7 +57,7 @@ export async function enhanceAll(
|
|
|
57
57
|
if (!permissions?.crossDomainImports && !isAllowedImportPath(config.emc)) {
|
|
58
58
|
throw new Error(
|
|
59
59
|
`enhanceAll: EMC path "${config.emc}" is a cross-domain URL. ` +
|
|
60
|
-
`Only
|
|
60
|
+
`Only same-origin paths or import-map-covered specifiers are allowed by default. ` +
|
|
61
61
|
`Pass { crossDomainImports: true } in permissions to override.`
|
|
62
62
|
);
|
|
63
63
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import assignGingerly from '../assignGingerly.js';
|
|
11
11
|
import type { AddEventListenerConfig, AssignDispatchVector } from '../types/assign-gingerly/types.js';
|
|
12
|
-
import type { AssignPermissions } from '../
|
|
12
|
+
import type { AssignPermissions } from '../types/assign-gingerly/types.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* WeakMap for dedup: Element → Map<key, AbortController>
|
package/handlers/lazyLoad.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { LazyLoadResolvedParams, LazyLoadInstantiatedContext } from '../typ
|
|
|
24
24
|
import { withTransition, ensureHideStyle, DEFAULT_HIDE_CLASS } from '../transitionHelper.js';
|
|
25
25
|
import { findMarkers, createMarkers, getNodesBetweenMarkers, findMarkersSibling, createMarkersSibling, MARKER_START_PREFIX, MARKER_END } from '../markerUtils.js';
|
|
26
26
|
import { assignFrom } from '../assignFrom.js';
|
|
27
|
-
import type { AssignPermissions } from '../
|
|
27
|
+
import type { AssignPermissions } from '../types/assign-gingerly/types.js';
|
|
28
28
|
|
|
29
29
|
export type { LazyLoadResolvedParams, LazyLoadInstantiatedContext };
|
|
30
30
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { LazyLoadHandler } from './lazyLoad.js';
|
|
24
|
-
import type { AssignPermissions } from '../
|
|
24
|
+
import type { AssignPermissions } from '../types/assign-gingerly/types.js';
|
|
25
25
|
import type { AssignFromHandler } from '../assignFromAsync.js';
|
|
26
26
|
import type { LazyLoadSwitchResolvedParams } from '../types/assign-gingerly/types.js';
|
|
27
27
|
|
|
@@ -31,7 +31,7 @@ import { findMarkers, createMarkers, getNodesBetweenMarkers, MARKER_START_PREFIX
|
|
|
31
31
|
import { resolveValue } from '../resolveValues.js';
|
|
32
32
|
import { assignFrom } from '../assignFrom.js';
|
|
33
33
|
import { processInferredAssignments } from '../inferredAssignments.js';
|
|
34
|
-
import type { AssignPermissions } from '../
|
|
34
|
+
import type { AssignPermissions } from '../types/assign-gingerly/types.js';
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Reserved keys in fromEachItem config — not treated as shorthand patterns.
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import type { AssignFromHandler } from '../assignFromAsync.js';
|
|
25
25
|
import assignGingerly from '../assignGingerly.js';
|
|
26
|
-
import type { AssignPermissions } from '../
|
|
26
|
+
import type { AssignPermissions } from '../types/assign-gingerly/types.js';
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Operator keys recognized in case objects.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {ParserOptions} from '../nested-regex-groups/types.js';
|
|
2
|
+
|
|
1
3
|
export type EnhKey = string | symbol;
|
|
2
4
|
|
|
3
5
|
// type NoUnderscore<T extends string> = T extends `_${string}` ? never : T;
|
|
@@ -196,7 +198,7 @@ export interface AttrConfig<T = unknown, TParserConfig = unknown> {
|
|
|
196
198
|
* For named parsers like 'parse-pattern-statements', this is forwarded
|
|
197
199
|
* as the options argument to the underlying parse function.
|
|
198
200
|
*/
|
|
199
|
-
parserOptions?:
|
|
201
|
+
parserOptions?: ParserOptions;
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
export type AttrPatterns<T = any> = {
|
|
@@ -91,6 +91,18 @@ export interface ParserOptions {
|
|
|
91
91
|
* as statement delimiters during splitting.
|
|
92
92
|
*/
|
|
93
93
|
ignorePeriodInsideBraces?: boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* When true, normalizes whitespace in the input string before parsing.
|
|
97
|
+
* This replaces multiple whitespace characters with a single space and trims the string.
|
|
98
|
+
* Default is false.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* // Input: " First. Second. "
|
|
102
|
+
* // With normalizeWhitespace: true -> "First. Second."
|
|
103
|
+
* // With normalizeWhitespace: false -> " First. Second. "
|
|
104
|
+
*/
|
|
105
|
+
normalizeWhitespace?: boolean;
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.73",
|
|
4
4
|
"description": "This package provides a utility function for carefully merging one object into another.",
|
|
5
5
|
"homepage": "https://github.com/bahrus/assign-gingerly#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"*.js",
|
|
19
19
|
"*.ts",
|
|
20
20
|
"DX/**",
|
|
21
|
+
"assignPermissions/**",
|
|
21
22
|
"handlers/**",
|
|
22
23
|
"inferencer/**",
|
|
23
24
|
"README.md",
|
|
@@ -143,9 +144,9 @@
|
|
|
143
144
|
"default": "./enhanceAll.js",
|
|
144
145
|
"types": "./enhanceAll.ts"
|
|
145
146
|
},
|
|
146
|
-
"./isAllowedImportPath.js": {
|
|
147
|
-
"default": "./isAllowedImportPath.js",
|
|
148
|
-
"types": "./isAllowedImportPath.ts"
|
|
147
|
+
"./assignPermissions/isAllowedImportPath.js": {
|
|
148
|
+
"default": "./assignPermissions/isAllowedImportPath.js",
|
|
149
|
+
"types": "./assignPermissions/isAllowedImportPath.ts"
|
|
149
150
|
},
|
|
150
151
|
"./markerUtils.js": {
|
|
151
152
|
"default": "./markerUtils.js",
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import { resolveValues } from './resolveValues.js';
|
|
7
7
|
import { getValues } from './getValues.js';
|
|
8
8
|
import { evaluatePathWithMethods } from './assignGingerly.js';
|
|
9
|
-
import {
|
|
9
|
+
import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
|
|
10
|
+
import { buildRestrictedPropSet, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
10
11
|
/**
|
|
11
12
|
* Map of built-in handler names to their module paths.
|
|
12
13
|
* These are auto-loaded on demand — no explicit import required.
|
|
@@ -80,7 +81,7 @@ async function resolveFromHandlers(name, handlers, permissions) {
|
|
|
80
81
|
// Import path string — validate and dynamically import
|
|
81
82
|
if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
|
|
82
83
|
throw new Error(`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
|
|
83
|
-
`Only
|
|
84
|
+
`Only same-origin paths or import-map-covered specifiers are allowed. ` +
|
|
84
85
|
`Pass { crossDomainImports: true } in permissions to override.`);
|
|
85
86
|
}
|
|
86
87
|
const module = await import(entry);
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
import { resolveValues } from './resolveValues.js';
|
|
8
8
|
import { getValues } from './getValues.js';
|
|
9
9
|
import { evaluatePathWithMethods } from './assignGingerly.js';
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
10
|
+
import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
|
|
11
|
+
import { buildRestrictedPropSet, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
12
|
+
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
12
13
|
import type { AssignFromOptions, AssignFromHandlerConstructor } from './assignFromAsync.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -93,7 +94,7 @@ async function resolveFromHandlers(
|
|
|
93
94
|
if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
|
|
94
95
|
throw new Error(
|
|
95
96
|
`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
|
|
96
|
-
`Only
|
|
97
|
+
`Only same-origin paths or import-map-covered specifiers are allowed. ` +
|
|
97
98
|
`Pass { crossDomainImports: true } in permissions to override.`
|
|
98
99
|
);
|
|
99
100
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {ParserOptions} from '../nested-regex-groups/types.js';
|
|
2
|
+
|
|
1
3
|
export type EnhKey = string | symbol;
|
|
2
4
|
|
|
3
5
|
// type NoUnderscore<T extends string> = T extends `_${string}` ? never : T;
|
|
@@ -196,7 +198,7 @@ export interface AttrConfig<T = unknown, TParserConfig = unknown> {
|
|
|
196
198
|
* For named parsers like 'parse-pattern-statements', this is forwarded
|
|
197
199
|
* as the options argument to the underlying parse function.
|
|
198
200
|
*/
|
|
199
|
-
parserOptions?:
|
|
201
|
+
parserOptions?: ParserOptions;
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
export type AttrPatterns<T = any> = {
|
|
@@ -706,8 +708,8 @@ export interface HandlerConfig {
|
|
|
706
708
|
* Interface for assignFrom handler classes.
|
|
707
709
|
* Handlers are invoked when a LHS key ends with ' =>'.
|
|
708
710
|
*/
|
|
709
|
-
export interface AssignFromHandler {
|
|
710
|
-
assign(lhsTarget: any, resolvedParams: Record<string, any>, options: any, permissions?: AssignPermissions): Promise<void> | void;
|
|
711
|
+
export interface AssignFromHandler {
|
|
712
|
+
assign(lhsTarget: any, resolvedParams: Record<string, any>, options: any, permissions?: AssignPermissions): Promise<void> | void;
|
|
711
713
|
}
|
|
712
714
|
|
|
713
715
|
/**
|
|
@@ -885,7 +887,7 @@ export interface LazyLoadInstantiatedContext {
|
|
|
885
887
|
export declare class LazyLoadHandler implements AssignFromHandler {
|
|
886
888
|
config: any;
|
|
887
889
|
constructor(config: any);
|
|
888
|
-
assign(lhsTarget: any, resolvedParams: Record<string, any>, options?: any, permissions?: AssignPermissions): Promise<void>;
|
|
890
|
+
assign(lhsTarget: any, resolvedParams: Record<string, any>, options?: any, permissions?: AssignPermissions): Promise<void>;
|
|
889
891
|
protected onCloneInserted(nodes: Node[], lhsTarget: Element, resolvedParams: Record<string, any>): Promise<void>;
|
|
890
892
|
}
|
|
891
893
|
|
|
@@ -969,12 +971,12 @@ export interface AssignPermissions {
|
|
|
969
971
|
/**
|
|
970
972
|
* Restricted property settings.
|
|
971
973
|
* Phase I: string entries are property names that cannot be assigned.
|
|
972
|
-
* Phase II: an object with useMethod redirects ordinary assignment to that
|
|
973
|
-
* method; command operations remain blocked. Phase III+ adds attr support.
|
|
974
|
+
* Phase II: an object with useMethod redirects ordinary assignment to that
|
|
975
|
+
* method; command operations remain blocked. Phase III+ adds attr support.
|
|
974
976
|
*
|
|
975
|
-
* NOTE: This is a property-assignment guard only. Method calls (setAttribute, etc.)
|
|
976
|
-
* are not blocked — see Phase III+. Event listeners can still be registered, but
|
|
977
|
-
* assignments performed by their vectors inherit these permissions.
|
|
977
|
+
* NOTE: This is a property-assignment guard only. Method calls (setAttribute, etc.)
|
|
978
|
+
* are not blocked — see Phase III+. Event listeners can still be registered, but
|
|
979
|
+
* assignments performed by their vectors inherit these permissions.
|
|
978
980
|
*/
|
|
979
981
|
restrictedPropSettings?: Array<string | RestrictedPropSetting>;
|
|
980
982
|
|
package/isAllowedImportPath.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* isAllowedImportPath.ts — Security utility for validating import paths.
|
|
3
|
-
*
|
|
4
|
-
* Checks that a path is local (relative, absolute, or bare specifier) and
|
|
5
|
-
* not a cross-domain URL. Used to prevent untrusted HTML attributes from
|
|
6
|
-
* triggering imports to arbitrary external domains.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* import { isAllowedImportPath } from 'assign-gingerly/isAllowedImportPath.js';
|
|
10
|
-
*
|
|
11
|
-
* isAllowedImportPath('./local.js'); // true
|
|
12
|
-
* isAllowedImportPath('../parent/file.js'); // true
|
|
13
|
-
* isAllowedImportPath('/absolute/path.js'); // true
|
|
14
|
-
* isAllowedImportPath('bare-specifier/mod.js'); // true
|
|
15
|
-
* isAllowedImportPath('https://evil.com/x.js'); // false
|
|
16
|
-
* isAllowedImportPath('//cdn.example.com/x.js'); // false
|
|
17
|
-
*/
|
|
18
|
-
/**
|
|
19
|
-
* Module-level warn dedup — one warning per property key per process lifetime.
|
|
20
|
-
*/
|
|
21
|
-
const warnedOnce = new Set();
|
|
22
|
-
/**
|
|
23
|
-
* Warn once per key that a restricted property assignment was skipped.
|
|
24
|
-
*/
|
|
25
|
-
function warnRestricted(key) {
|
|
26
|
-
if (!warnedOnce.has(key)) {
|
|
27
|
-
warnedOnce.add(key);
|
|
28
|
-
console.warn(`assignGingerly: property '${key}' is in restrictedPropSettings — assignment skipped.`);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Normalize the restrictedPropSettings from permissions into a fast-lookup Set.
|
|
33
|
-
* Phase I: extracts string entries only (object entries are Phase II+).
|
|
34
|
-
* Returns undefined when nothing is restricted (fast-bail in callers).
|
|
35
|
-
*/
|
|
36
|
-
export function buildRestrictedPropSet(permissions) {
|
|
37
|
-
const settings = permissions?.restrictedPropSettings;
|
|
38
|
-
if (!settings || settings.length === 0)
|
|
39
|
-
return undefined;
|
|
40
|
-
const restrictedPropSet = new Map();
|
|
41
|
-
for (const setting of settings) {
|
|
42
|
-
const prop = typeof setting === 'string' ? setting : setting.prop;
|
|
43
|
-
if (restrictedPropSet.has(prop)) {
|
|
44
|
-
throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${prop}'.`);
|
|
45
|
-
}
|
|
46
|
-
restrictedPropSet.set(prop, typeof setting === 'string' ? undefined : setting);
|
|
47
|
-
}
|
|
48
|
-
return restrictedPropSet;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Check if a property key is restricted. If so, warn (once) and return true.
|
|
52
|
-
* Call sites should `continue` or skip the assignment when this returns true.
|
|
53
|
-
*/
|
|
54
|
-
export function checkRestrictedProp(restrictedPropSet, key) {
|
|
55
|
-
if (!restrictedPropSet || !restrictedPropSet.has(key))
|
|
56
|
-
return false;
|
|
57
|
-
warnRestricted(key);
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Redirect an ordinary assignment through its configured safe method.
|
|
62
|
-
* Returns true when the property is restricted, whether redirected or skipped.
|
|
63
|
-
*/
|
|
64
|
-
export function redirectRestrictedProp(restrictedPropSet, target, key, value) {
|
|
65
|
-
if (!restrictedPropSet || !restrictedPropSet.has(key))
|
|
66
|
-
return false;
|
|
67
|
-
const setting = restrictedPropSet.get(key);
|
|
68
|
-
if (!setting?.useMethod) {
|
|
69
|
-
warnRestricted(key);
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
const method = target?.[setting.useMethod];
|
|
73
|
-
if (typeof method !== 'function') {
|
|
74
|
-
warnRestricted(key);
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
method.call(target, value);
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Check if an import path is allowed (non-cross-domain).
|
|
82
|
-
*
|
|
83
|
-
* Allowed:
|
|
84
|
-
* - Relative paths: ./foo.js, ../bar.js
|
|
85
|
-
* - Absolute paths: /path/to/file.js
|
|
86
|
-
* - Bare specifiers: package-name/file.js, @scope/package/file.js
|
|
87
|
-
*
|
|
88
|
-
* Blocked:
|
|
89
|
-
* - Protocol URLs: https://..., http://..., data:..., etc.
|
|
90
|
-
* - Protocol-relative: //cdn.example.com/...
|
|
91
|
-
*
|
|
92
|
-
* @param path - The import path to validate
|
|
93
|
-
* @returns true if the path is local/safe, false if cross-domain
|
|
94
|
-
*/
|
|
95
|
-
export function isAllowedImportPath(path) {
|
|
96
|
-
if (path.startsWith('./') || path.startsWith('../') || path.startsWith('/')) {
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
if (path.includes('://') || path.startsWith('//')) {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
// Bare specifier (no protocol, no //) — allowed
|
|
103
|
-
return true;
|
|
104
|
-
}
|
package/isAllowedImportPath.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* isAllowedImportPath.ts — Security utility for validating import paths.
|
|
3
|
-
*
|
|
4
|
-
* Checks that a path is local (relative, absolute, or bare specifier) and
|
|
5
|
-
* not a cross-domain URL. Used to prevent untrusted HTML attributes from
|
|
6
|
-
* triggering imports to arbitrary external domains.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* import { isAllowedImportPath } from 'assign-gingerly/isAllowedImportPath.js';
|
|
10
|
-
*
|
|
11
|
-
* isAllowedImportPath('./local.js'); // true
|
|
12
|
-
* isAllowedImportPath('../parent/file.js'); // true
|
|
13
|
-
* isAllowedImportPath('/absolute/path.js'); // true
|
|
14
|
-
* isAllowedImportPath('bare-specifier/mod.js'); // true
|
|
15
|
-
* isAllowedImportPath('https://evil.com/x.js'); // false
|
|
16
|
-
* isAllowedImportPath('//cdn.example.com/x.js'); // false
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
// Re-export AssignPermissions from canonical location for backwards compatibility
|
|
20
|
-
export type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
21
|
-
import type { AssignPermissions, RestrictedPropSetting } from './types/assign-gingerly/types.js';
|
|
22
|
-
|
|
23
|
-
export type RestrictedPropSettingsMap = Map<string, RestrictedPropSetting | undefined>;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Module-level warn dedup — one warning per property key per process lifetime.
|
|
27
|
-
*/
|
|
28
|
-
const warnedOnce = new Set<string>();
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Warn once per key that a restricted property assignment was skipped.
|
|
32
|
-
*/
|
|
33
|
-
function warnRestricted(key: string): void {
|
|
34
|
-
if (!warnedOnce.has(key)) {
|
|
35
|
-
warnedOnce.add(key);
|
|
36
|
-
console.warn(`assignGingerly: property '${key}' is in restrictedPropSettings — assignment skipped.`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Normalize the restrictedPropSettings from permissions into a fast-lookup Set.
|
|
42
|
-
* Phase I: extracts string entries only (object entries are Phase II+).
|
|
43
|
-
* Returns undefined when nothing is restricted (fast-bail in callers).
|
|
44
|
-
*/
|
|
45
|
-
export function buildRestrictedPropSet(permissions: AssignPermissions | undefined): RestrictedPropSettingsMap | undefined {
|
|
46
|
-
const settings = permissions?.restrictedPropSettings;
|
|
47
|
-
if (!settings || settings.length === 0) return undefined;
|
|
48
|
-
const restrictedPropSet: RestrictedPropSettingsMap = new Map();
|
|
49
|
-
for (const setting of settings) {
|
|
50
|
-
const prop = typeof setting === 'string' ? setting : setting.prop;
|
|
51
|
-
if (restrictedPropSet.has(prop)) {
|
|
52
|
-
throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${prop}'.`);
|
|
53
|
-
}
|
|
54
|
-
restrictedPropSet.set(prop, typeof setting === 'string' ? undefined : setting);
|
|
55
|
-
}
|
|
56
|
-
return restrictedPropSet;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Check if a property key is restricted. If so, warn (once) and return true.
|
|
61
|
-
* Call sites should `continue` or skip the assignment when this returns true.
|
|
62
|
-
*/
|
|
63
|
-
export function checkRestrictedProp(restrictedPropSet: RestrictedPropSettingsMap | undefined, key: string): boolean {
|
|
64
|
-
if (!restrictedPropSet || !restrictedPropSet.has(key)) return false;
|
|
65
|
-
warnRestricted(key);
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Redirect an ordinary assignment through its configured safe method.
|
|
71
|
-
* Returns true when the property is restricted, whether redirected or skipped.
|
|
72
|
-
*/
|
|
73
|
-
export function redirectRestrictedProp(
|
|
74
|
-
restrictedPropSet: RestrictedPropSettingsMap | undefined,
|
|
75
|
-
target: any,
|
|
76
|
-
key: string,
|
|
77
|
-
value: any
|
|
78
|
-
): boolean {
|
|
79
|
-
if (!restrictedPropSet || !restrictedPropSet.has(key)) return false;
|
|
80
|
-
const setting = restrictedPropSet.get(key);
|
|
81
|
-
if (!setting?.useMethod) {
|
|
82
|
-
warnRestricted(key);
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
const method = target?.[setting.useMethod];
|
|
86
|
-
if (typeof method !== 'function') {
|
|
87
|
-
warnRestricted(key);
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
method.call(target, value);
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Check if an import path is allowed (non-cross-domain).
|
|
96
|
-
*
|
|
97
|
-
* Allowed:
|
|
98
|
-
* - Relative paths: ./foo.js, ../bar.js
|
|
99
|
-
* - Absolute paths: /path/to/file.js
|
|
100
|
-
* - Bare specifiers: package-name/file.js, @scope/package/file.js
|
|
101
|
-
*
|
|
102
|
-
* Blocked:
|
|
103
|
-
* - Protocol URLs: https://..., http://..., data:..., etc.
|
|
104
|
-
* - Protocol-relative: //cdn.example.com/...
|
|
105
|
-
*
|
|
106
|
-
* @param path - The import path to validate
|
|
107
|
-
* @returns true if the path is local/safe, false if cross-domain
|
|
108
|
-
*/
|
|
109
|
-
export function isAllowedImportPath(path: string): boolean {
|
|
110
|
-
if (path.startsWith('./') || path.startsWith('../') || path.startsWith('/')) {
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
if (path.includes('://') || path.startsWith('//')) {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
// Bare specifier (no protocol, no //) — allowed
|
|
117
|
-
return true;
|
|
118
|
-
}
|