@nocobase/utils 2.3.0-alpha.1 → 2.3.0-beta.10
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/lib/client.d.ts +2 -0
- package/lib/client.js +5 -0
- package/lib/cors.js +5 -2
- package/lib/flow-model-rd.d.ts +20 -0
- package/lib/flow-model-rd.js +136 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +4 -0
- package/lib/sanitize-rich-text-html.d.ts +9 -0
- package/lib/sanitize-rich-text-html.js +145 -0
- package/lib/server-request.d.ts +3 -1
- package/lib/server-request.js +19 -2
- package/lib/variable-usage.d.ts +8 -0
- package/lib/variable-usage.js +265 -87
- package/package.json +3 -2
package/lib/client.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from './common';
|
|
|
14
14
|
export * from './auth-cookie-name';
|
|
15
15
|
export * from './date';
|
|
16
16
|
export * from './forEach';
|
|
17
|
+
export { generateFlowModelRdFromToken } from './flow-model-rd';
|
|
17
18
|
export * from './getValuesByPath';
|
|
18
19
|
export * from './handlebars';
|
|
19
20
|
export * from './isValidFilter';
|
|
@@ -28,6 +29,7 @@ export * from './ses';
|
|
|
28
29
|
export * from './i18n';
|
|
29
30
|
export * from './isPortalInBody';
|
|
30
31
|
export * from './parseHTML';
|
|
32
|
+
export * from './sanitize-rich-text-html';
|
|
31
33
|
export * from './uid';
|
|
32
34
|
export * from './url';
|
|
33
35
|
export * from './transformMultiColumnToSingleColumn';
|
package/lib/client.js
CHANGED
|
@@ -38,6 +38,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
38
38
|
var client_exports = {};
|
|
39
39
|
__export(client_exports, {
|
|
40
40
|
dayjs: () => import_dayjs.dayjs,
|
|
41
|
+
generateFlowModelRdFromToken: () => import_flow_model_rd.generateFlowModelRdFromToken,
|
|
41
42
|
getDayRangeByParams: () => import_dateRangeUtils.getDayRangeByParams,
|
|
42
43
|
getOffsetRangeByParams: () => import_dateRangeUtils.getOffsetRangeByParams,
|
|
43
44
|
lodash: () => import_lodash.default
|
|
@@ -51,6 +52,7 @@ __reExport(client_exports, require("./common"), module.exports);
|
|
|
51
52
|
__reExport(client_exports, require("./auth-cookie-name"), module.exports);
|
|
52
53
|
__reExport(client_exports, require("./date"), module.exports);
|
|
53
54
|
__reExport(client_exports, require("./forEach"), module.exports);
|
|
55
|
+
var import_flow_model_rd = require("./flow-model-rd");
|
|
54
56
|
__reExport(client_exports, require("./getValuesByPath"), module.exports);
|
|
55
57
|
__reExport(client_exports, require("./handlebars"), module.exports);
|
|
56
58
|
__reExport(client_exports, require("./isValidFilter"), module.exports);
|
|
@@ -65,6 +67,7 @@ __reExport(client_exports, require("./ses"), module.exports);
|
|
|
65
67
|
__reExport(client_exports, require("./i18n"), module.exports);
|
|
66
68
|
__reExport(client_exports, require("./isPortalInBody"), module.exports);
|
|
67
69
|
__reExport(client_exports, require("./parseHTML"), module.exports);
|
|
70
|
+
__reExport(client_exports, require("./sanitize-rich-text-html"), module.exports);
|
|
68
71
|
__reExport(client_exports, require("./uid"), module.exports);
|
|
69
72
|
__reExport(client_exports, require("./url"), module.exports);
|
|
70
73
|
__reExport(client_exports, require("./transformMultiColumnToSingleColumn"), module.exports);
|
|
@@ -75,6 +78,7 @@ __reExport(client_exports, require("./liquidjs"), module.exports);
|
|
|
75
78
|
// Annotate the CommonJS export names for ESM import in node:
|
|
76
79
|
0 && (module.exports = {
|
|
77
80
|
dayjs,
|
|
81
|
+
generateFlowModelRdFromToken,
|
|
78
82
|
getDayRangeByParams,
|
|
79
83
|
getOffsetRangeByParams,
|
|
80
84
|
lodash,
|
|
@@ -97,6 +101,7 @@ __reExport(client_exports, require("./liquidjs"), module.exports);
|
|
|
97
101
|
...require("./i18n"),
|
|
98
102
|
...require("./isPortalInBody"),
|
|
99
103
|
...require("./parseHTML"),
|
|
104
|
+
...require("./sanitize-rich-text-html"),
|
|
100
105
|
...require("./uid"),
|
|
101
106
|
...require("./url"),
|
|
102
107
|
...require("./transformMultiColumnToSingleColumn"),
|
package/lib/cors.js
CHANGED
|
@@ -71,11 +71,14 @@ function isSameOrigin(ctx, origin) {
|
|
|
71
71
|
}
|
|
72
72
|
__name(isSameOrigin, "isSameOrigin");
|
|
73
73
|
function isTrustedOrigin(ctx, origin) {
|
|
74
|
-
var _a;
|
|
75
74
|
if (isSameOrigin(ctx, origin)) {
|
|
76
75
|
return true;
|
|
77
76
|
}
|
|
78
|
-
|
|
77
|
+
const whitelist = getCorsWhitelist();
|
|
78
|
+
if (!whitelist) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return whitelist.has("*") || whitelist.has(origin);
|
|
79
82
|
}
|
|
80
83
|
__name(isTrustedOrigin, "isTrustedOrigin");
|
|
81
84
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
type JwtSessionPayload = {
|
|
10
|
+
userId?: unknown;
|
|
11
|
+
signInTime?: unknown;
|
|
12
|
+
jti?: unknown;
|
|
13
|
+
};
|
|
14
|
+
export declare function decodeJwtSessionPayload(token?: string | null): JwtSessionPayload | null;
|
|
15
|
+
export declare function getFlowModelRdSessionId(payload?: JwtSessionPayload | null): string;
|
|
16
|
+
export declare function getFlowModelRdSessionIdFromToken(token?: string | null): string;
|
|
17
|
+
export declare function generateFlowModelRd(flowModelUid?: string | number | null, sessionId?: string | null): string;
|
|
18
|
+
export declare function generateFlowModelRdFromToken(flowModelUid?: string | number | null, token?: string | null): string;
|
|
19
|
+
export declare function resolveFlowModelUidFromRd(rd?: string | null, sessionId?: string | null): string;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var flow_model_rd_exports = {};
|
|
29
|
+
__export(flow_model_rd_exports, {
|
|
30
|
+
decodeJwtSessionPayload: () => decodeJwtSessionPayload,
|
|
31
|
+
generateFlowModelRd: () => generateFlowModelRd,
|
|
32
|
+
generateFlowModelRdFromToken: () => generateFlowModelRdFromToken,
|
|
33
|
+
getFlowModelRdSessionId: () => getFlowModelRdSessionId,
|
|
34
|
+
getFlowModelRdSessionIdFromToken: () => getFlowModelRdSessionIdFromToken,
|
|
35
|
+
resolveFlowModelUidFromRd: () => resolveFlowModelUidFromRd
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(flow_model_rd_exports);
|
|
38
|
+
const rdCache = /* @__PURE__ */ new Map();
|
|
39
|
+
function toBase64Url(input) {
|
|
40
|
+
if (typeof btoa === "function") {
|
|
41
|
+
return btoa(input).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
42
|
+
}
|
|
43
|
+
return Buffer.from(input, "binary").toString("base64url");
|
|
44
|
+
}
|
|
45
|
+
__name(toBase64Url, "toBase64Url");
|
|
46
|
+
function fromBase64Url(input) {
|
|
47
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
48
|
+
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
49
|
+
if (typeof atob === "function") {
|
|
50
|
+
return atob(padded);
|
|
51
|
+
}
|
|
52
|
+
return Buffer.from(padded, "base64").toString("binary");
|
|
53
|
+
}
|
|
54
|
+
__name(fromBase64Url, "fromBase64Url");
|
|
55
|
+
function fastHash(input) {
|
|
56
|
+
let hash = 2166136261;
|
|
57
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
58
|
+
hash ^= input.charCodeAt(index);
|
|
59
|
+
hash = Math.imul(hash, 16777619);
|
|
60
|
+
}
|
|
61
|
+
return (hash >>> 0).toString(36);
|
|
62
|
+
}
|
|
63
|
+
__name(fastHash, "fastHash");
|
|
64
|
+
function xorString(input, mask) {
|
|
65
|
+
if (!mask) return "";
|
|
66
|
+
let output = "";
|
|
67
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
68
|
+
output += String.fromCharCode(input.charCodeAt(index) ^ mask.charCodeAt(index % mask.length));
|
|
69
|
+
}
|
|
70
|
+
return output;
|
|
71
|
+
}
|
|
72
|
+
__name(xorString, "xorString");
|
|
73
|
+
function decodeJwtSessionPayload(token) {
|
|
74
|
+
if (!token || typeof token !== "string") return null;
|
|
75
|
+
const parts = token.split(".");
|
|
76
|
+
if (parts.length < 2) return null;
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(fromBase64Url(parts[1]));
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
__name(decodeJwtSessionPayload, "decodeJwtSessionPayload");
|
|
84
|
+
function getFlowModelRdSessionId(payload) {
|
|
85
|
+
const userId = payload == null ? void 0 : payload.userId;
|
|
86
|
+
const sessionKey = (payload == null ? void 0 : payload.signInTime) || (payload == null ? void 0 : payload.jti);
|
|
87
|
+
if (typeof userId !== "string" && typeof userId !== "number" || typeof sessionKey !== "string" && typeof sessionKey !== "number") {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
return `${userId}:${sessionKey}`;
|
|
91
|
+
}
|
|
92
|
+
__name(getFlowModelRdSessionId, "getFlowModelRdSessionId");
|
|
93
|
+
function getFlowModelRdSessionIdFromToken(token) {
|
|
94
|
+
return getFlowModelRdSessionId(decodeJwtSessionPayload(token));
|
|
95
|
+
}
|
|
96
|
+
__name(getFlowModelRdSessionIdFromToken, "getFlowModelRdSessionIdFromToken");
|
|
97
|
+
function generateFlowModelRd(flowModelUid, sessionId) {
|
|
98
|
+
if (typeof flowModelUid !== "string" && typeof flowModelUid !== "number" || !sessionId) return void 0;
|
|
99
|
+
const uid = String(flowModelUid);
|
|
100
|
+
const cacheKey = `${sessionId}
|
|
101
|
+
${uid}`;
|
|
102
|
+
const cached = rdCache.get(cacheKey);
|
|
103
|
+
if (cached) return cached;
|
|
104
|
+
const cipher = toBase64Url(xorString(uid, fastHash(`mask:v1:${sessionId}`)));
|
|
105
|
+
const check = fastHash(`check:v1:${sessionId}:${uid}`);
|
|
106
|
+
const rd = `v1.${cipher}.${check}`;
|
|
107
|
+
rdCache.set(cacheKey, rd);
|
|
108
|
+
return rd;
|
|
109
|
+
}
|
|
110
|
+
__name(generateFlowModelRd, "generateFlowModelRd");
|
|
111
|
+
function generateFlowModelRdFromToken(flowModelUid, token) {
|
|
112
|
+
return generateFlowModelRd(flowModelUid, getFlowModelRdSessionIdFromToken(token));
|
|
113
|
+
}
|
|
114
|
+
__name(generateFlowModelRdFromToken, "generateFlowModelRdFromToken");
|
|
115
|
+
function resolveFlowModelUidFromRd(rd, sessionId) {
|
|
116
|
+
if (!rd || !sessionId || typeof rd !== "string") return void 0;
|
|
117
|
+
const parts = rd.split(".");
|
|
118
|
+
if (parts.length !== 3 || parts[0] !== "v1" || !parts[1] || !parts[2]) return void 0;
|
|
119
|
+
try {
|
|
120
|
+
const uid = xorString(fromBase64Url(parts[1]), fastHash(`mask:v1:${sessionId}`));
|
|
121
|
+
const check = fastHash(`check:v1:${sessionId}:${uid}`);
|
|
122
|
+
return check === parts[2] ? uid : void 0;
|
|
123
|
+
} catch {
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
__name(resolveFlowModelUidFromRd, "resolveFlowModelUidFromRd");
|
|
128
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
129
|
+
0 && (module.exports = {
|
|
130
|
+
decodeJwtSessionPayload,
|
|
131
|
+
generateFlowModelRd,
|
|
132
|
+
generateFlowModelRdFromToken,
|
|
133
|
+
getFlowModelRdSessionId,
|
|
134
|
+
getFlowModelRdSessionIdFromToken,
|
|
135
|
+
resolveFlowModelUidFromRd
|
|
136
|
+
});
|
package/lib/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export * from './dateRangeUtils';
|
|
|
19
19
|
export * from './dayjs';
|
|
20
20
|
export { dayjs } from './dayjs';
|
|
21
21
|
export * from './forEach';
|
|
22
|
+
export * from './flow-model-rd';
|
|
22
23
|
export * from './fs-exists';
|
|
23
24
|
export * from './handlebars';
|
|
24
25
|
export * from './i18n';
|
|
@@ -45,6 +46,7 @@ export * from './url';
|
|
|
45
46
|
export * from './variable-usage';
|
|
46
47
|
export * from './wrap-middleware';
|
|
47
48
|
export * from './run-sql';
|
|
49
|
+
export * from './sanitize-rich-text-html';
|
|
48
50
|
export * from './liquidjs';
|
|
49
51
|
export * from './server-request';
|
|
50
52
|
export * from './storage-path';
|
package/lib/index.js
CHANGED
|
@@ -55,6 +55,7 @@ __reExport(src_exports, require("./dateRangeUtils"), module.exports);
|
|
|
55
55
|
__reExport(src_exports, require("./dayjs"), module.exports);
|
|
56
56
|
var import_dayjs = require("./dayjs");
|
|
57
57
|
__reExport(src_exports, require("./forEach"), module.exports);
|
|
58
|
+
__reExport(src_exports, require("./flow-model-rd"), module.exports);
|
|
58
59
|
__reExport(src_exports, require("./fs-exists"), module.exports);
|
|
59
60
|
__reExport(src_exports, require("./handlebars"), module.exports);
|
|
60
61
|
__reExport(src_exports, require("./i18n"), module.exports);
|
|
@@ -81,6 +82,7 @@ __reExport(src_exports, require("./url"), module.exports);
|
|
|
81
82
|
__reExport(src_exports, require("./variable-usage"), module.exports);
|
|
82
83
|
__reExport(src_exports, require("./wrap-middleware"), module.exports);
|
|
83
84
|
__reExport(src_exports, require("./run-sql"), module.exports);
|
|
85
|
+
__reExport(src_exports, require("./sanitize-rich-text-html"), module.exports);
|
|
84
86
|
__reExport(src_exports, require("./liquidjs"), module.exports);
|
|
85
87
|
__reExport(src_exports, require("./server-request"), module.exports);
|
|
86
88
|
__reExport(src_exports, require("./storage-path"), module.exports);
|
|
@@ -99,6 +101,7 @@ __reExport(src_exports, require("./storage-path"), module.exports);
|
|
|
99
101
|
...require("./dateRangeUtils"),
|
|
100
102
|
...require("./dayjs"),
|
|
101
103
|
...require("./forEach"),
|
|
104
|
+
...require("./flow-model-rd"),
|
|
102
105
|
...require("./fs-exists"),
|
|
103
106
|
...require("./handlebars"),
|
|
104
107
|
...require("./i18n"),
|
|
@@ -125,6 +128,7 @@ __reExport(src_exports, require("./storage-path"), module.exports);
|
|
|
125
128
|
...require("./variable-usage"),
|
|
126
129
|
...require("./wrap-middleware"),
|
|
127
130
|
...require("./run-sql"),
|
|
131
|
+
...require("./sanitize-rich-text-html"),
|
|
128
132
|
...require("./liquidjs"),
|
|
129
133
|
...require("./server-request"),
|
|
130
134
|
...require("./storage-path")
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sanitizeRichTextHtml(value: string): any;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
37
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
38
|
+
var sanitize_rich_text_html_exports = {};
|
|
39
|
+
__export(sanitize_rich_text_html_exports, {
|
|
40
|
+
sanitizeRichTextHtml: () => sanitizeRichTextHtml
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(sanitize_rich_text_html_exports);
|
|
43
|
+
var import_sanitize_html = __toESM(require("sanitize-html"));
|
|
44
|
+
const imageDataUrlPattern = /^data:image\/(?:gif|jpe?g|png|webp);base64,[a-z\d+/=\s]+$/i;
|
|
45
|
+
const dimensionPattern = /^(?:\d{1,4}(?:\.\d+)?(?:px)?|(?:100|[1-9]?\d)(?:\.\d+)?%)$/;
|
|
46
|
+
const imageStyleDimensionPattern = /^(?:\d{1,4}(?:\.\d+)?px|(?:100|[1-9]?\d)(?:\.\d+)?%)$/;
|
|
47
|
+
function sanitizeLinkAttributes(attribs) {
|
|
48
|
+
const nextAttributes = { ...attribs };
|
|
49
|
+
if (nextAttributes.target && !["_blank", "_self"].includes(nextAttributes.target)) {
|
|
50
|
+
delete nextAttributes.target;
|
|
51
|
+
}
|
|
52
|
+
if (nextAttributes.target === "_blank") {
|
|
53
|
+
nextAttributes.rel = "noopener noreferrer";
|
|
54
|
+
} else {
|
|
55
|
+
delete nextAttributes.rel;
|
|
56
|
+
}
|
|
57
|
+
return nextAttributes;
|
|
58
|
+
}
|
|
59
|
+
__name(sanitizeLinkAttributes, "sanitizeLinkAttributes");
|
|
60
|
+
function sanitizeImageAttributes(attribs) {
|
|
61
|
+
var _a;
|
|
62
|
+
const nextAttributes = { ...attribs };
|
|
63
|
+
if (((_a = nextAttributes.src) == null ? void 0 : _a.trim().toLowerCase().startsWith("data:")) && !imageDataUrlPattern.test(nextAttributes.src)) {
|
|
64
|
+
delete nextAttributes.src;
|
|
65
|
+
}
|
|
66
|
+
for (const dimension of ["width", "height"]) {
|
|
67
|
+
if (nextAttributes[dimension] && !dimensionPattern.test(nextAttributes[dimension])) {
|
|
68
|
+
delete nextAttributes[dimension];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return nextAttributes;
|
|
72
|
+
}
|
|
73
|
+
__name(sanitizeImageAttributes, "sanitizeImageAttributes");
|
|
74
|
+
const richTextSanitizeOptions = {
|
|
75
|
+
allowedTags: [
|
|
76
|
+
"a",
|
|
77
|
+
"b",
|
|
78
|
+
"blockquote",
|
|
79
|
+
"br",
|
|
80
|
+
"em",
|
|
81
|
+
"h1",
|
|
82
|
+
"h2",
|
|
83
|
+
"h3",
|
|
84
|
+
"h4",
|
|
85
|
+
"h5",
|
|
86
|
+
"h6",
|
|
87
|
+
"i",
|
|
88
|
+
"img",
|
|
89
|
+
"li",
|
|
90
|
+
"ol",
|
|
91
|
+
"p",
|
|
92
|
+
"s",
|
|
93
|
+
"span",
|
|
94
|
+
"strike",
|
|
95
|
+
"strong",
|
|
96
|
+
"u",
|
|
97
|
+
"ul"
|
|
98
|
+
],
|
|
99
|
+
allowedAttributes: {
|
|
100
|
+
a: ["href", "target", "rel"],
|
|
101
|
+
img: ["src", "alt", "title", "width", "height", "style"],
|
|
102
|
+
span: ["style"]
|
|
103
|
+
},
|
|
104
|
+
allowedClasses: {
|
|
105
|
+
blockquote: [/^ql-indent-[1-9]$/],
|
|
106
|
+
h1: [/^ql-indent-[1-9]$/],
|
|
107
|
+
h2: [/^ql-indent-[1-9]$/],
|
|
108
|
+
h3: [/^ql-indent-[1-9]$/],
|
|
109
|
+
h4: [/^ql-indent-[1-9]$/],
|
|
110
|
+
h5: [/^ql-indent-[1-9]$/],
|
|
111
|
+
h6: [/^ql-indent-[1-9]$/],
|
|
112
|
+
li: [/^ql-indent-[1-9]$/],
|
|
113
|
+
p: [/^ql-indent-[1-9]$/]
|
|
114
|
+
},
|
|
115
|
+
allowedStyles: {
|
|
116
|
+
img: {
|
|
117
|
+
height: [imageStyleDimensionPattern],
|
|
118
|
+
width: [imageStyleDimensionPattern]
|
|
119
|
+
},
|
|
120
|
+
span: {
|
|
121
|
+
"font-size": [/^(?:12|14|16|18|20|24|32|48)px$/]
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
allowedSchemes: ["http", "https", "mailto", "tel"],
|
|
125
|
+
allowedSchemesByTag: {
|
|
126
|
+
img: ["http", "https", "data"]
|
|
127
|
+
},
|
|
128
|
+
allowProtocolRelative: false,
|
|
129
|
+
transformTags: {
|
|
130
|
+
a(tagName, attribs) {
|
|
131
|
+
return { tagName, attribs: sanitizeLinkAttributes(attribs) };
|
|
132
|
+
},
|
|
133
|
+
img(tagName, attribs) {
|
|
134
|
+
return { tagName, attribs: sanitizeImageAttributes(attribs) };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
function sanitizeRichTextHtml(value) {
|
|
139
|
+
return (0, import_sanitize_html.default)(value, richTextSanitizeOptions);
|
|
140
|
+
}
|
|
141
|
+
__name(sanitizeRichTextHtml, "sanitizeRichTextHtml");
|
|
142
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
143
|
+
0 && (module.exports = {
|
|
144
|
+
sanitizeRichTextHtml
|
|
145
|
+
});
|
package/lib/server-request.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare function matchesDomainPattern(hostname: string, pattern: string):
|
|
|
18
18
|
*
|
|
19
19
|
* Throws an error if:
|
|
20
20
|
* - The URL scheme is not http or https.
|
|
21
|
+
* - SERVER_REQUEST_DISALLOW_IP is true and the host is a literal IP address.
|
|
21
22
|
* - SERVER_REQUEST_WHITELIST is set and the host does not match any entry.
|
|
22
23
|
*
|
|
23
24
|
* Silently returns for relative URLs (no scheme) so that internal API calls
|
|
@@ -30,7 +31,8 @@ export declare function checkUrlAgainstWhitelist(url?: string): void;
|
|
|
30
31
|
* Drop-in replacement for `axios.request()` with built-in SSRF protection.
|
|
31
32
|
*
|
|
32
33
|
* Validates `config.url` against {@link checkUrlAgainstWhitelist} before
|
|
33
|
-
* forwarding to axios.
|
|
34
|
+
* forwarding to axios. Every redirect destination is validated before the
|
|
35
|
+
* next request is sent. Use this instead of calling axios directly for all
|
|
34
36
|
* server-initiated outbound HTTP requests.
|
|
35
37
|
*/
|
|
36
38
|
export declare function serverRequest<T = any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
package/lib/server-request.js
CHANGED
|
@@ -189,13 +189,19 @@ function checkUrlAgainstWhitelist(url) {
|
|
|
189
189
|
);
|
|
190
190
|
}
|
|
191
191
|
const host = getNormalizedHost(parsed);
|
|
192
|
+
if (process.env.SERVER_REQUEST_DISALLOW_IP === "true" && import_ipaddr.default.isValid(host)) {
|
|
193
|
+
throw new Error(`Outbound request to "${host}" is blocked because SERVER_REQUEST_DISALLOW_IP is enabled.`);
|
|
194
|
+
}
|
|
192
195
|
const whitelist = process.env.SERVER_REQUEST_WHITELIST;
|
|
193
196
|
if (!whitelist || !whitelist.trim()) {
|
|
194
197
|
warnIfSsrfRiskTarget(host);
|
|
195
198
|
return;
|
|
196
199
|
}
|
|
197
200
|
const entries = whitelist.split(",").map((e) => e.trim()).filter(Boolean);
|
|
198
|
-
if (entries.length === 0)
|
|
201
|
+
if (entries.length === 0) {
|
|
202
|
+
warnIfSsrfRiskTarget(host);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
199
205
|
for (const entry of entries) {
|
|
200
206
|
if (matchesEntry(host, entry)) return;
|
|
201
207
|
}
|
|
@@ -207,7 +213,18 @@ __name(checkUrlAgainstWhitelist, "checkUrlAgainstWhitelist");
|
|
|
207
213
|
async function serverRequest(config) {
|
|
208
214
|
checkUrlAgainstWhitelist(config.url);
|
|
209
215
|
await warnIfResolvedSsrfRiskTarget(config.url);
|
|
210
|
-
return import_axios.default.request(
|
|
216
|
+
return import_axios.default.request({
|
|
217
|
+
...config,
|
|
218
|
+
// Axios request interceptors do not run again for redirects. In Node.js,
|
|
219
|
+
// beforeRedirect runs after Location is resolved and before the next hop
|
|
220
|
+
// is sent, so every redirect destination must be checked here.
|
|
221
|
+
beforeRedirect: /* @__PURE__ */ __name((options) => {
|
|
222
|
+
if (typeof options.href !== "string") {
|
|
223
|
+
throw new Error("Unable to validate redirect destination.");
|
|
224
|
+
}
|
|
225
|
+
checkUrlAgainstWhitelist(options.href);
|
|
226
|
+
}, "beforeRedirect")
|
|
227
|
+
});
|
|
211
228
|
}
|
|
212
229
|
__name(serverRequest, "serverRequest");
|
|
213
230
|
// Annotate the CommonJS export names for ESM import in node:
|
package/lib/variable-usage.d.ts
CHANGED
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
export type JSONValue = string | {
|
|
10
10
|
[key: string]: JSONValue;
|
|
11
11
|
} | JSONValue[];
|
|
12
|
+
export type VariableUsageExtractionResult = {
|
|
13
|
+
unsupportedDynamicPath: boolean;
|
|
14
|
+
usage: Record<string, string[]>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 提取模板中的 ctx 使用情况。该函数只做静态扫描,不执行表达式。
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractVariableUsage(template: JSONValue): VariableUsageExtractionResult;
|
|
12
20
|
/**
|
|
13
21
|
* 提取模板中使用到的 ctx 顶层变量名集合。
|
|
14
22
|
* - 支持点语法与顶层括号变量:ctx.user / ctx["user"]
|
package/lib/variable-usage.js
CHANGED
|
@@ -28,111 +28,289 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
28
28
|
var variable_usage_exports = {};
|
|
29
29
|
__export(variable_usage_exports, {
|
|
30
30
|
extractUsedVariableNames: () => extractUsedVariableNames,
|
|
31
|
-
extractUsedVariablePaths: () => extractUsedVariablePaths
|
|
31
|
+
extractUsedVariablePaths: () => extractUsedVariablePaths,
|
|
32
|
+
extractVariableUsage: () => extractVariableUsage
|
|
32
33
|
});
|
|
33
34
|
module.exports = __toCommonJS(variable_usage_exports);
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
35
|
+
function isObject(value) {
|
|
36
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
__name(isObject, "isObject");
|
|
39
|
+
function isIdentifierStart(char) {
|
|
40
|
+
return !!char && /[a-zA-Z_$]/.test(char);
|
|
41
|
+
}
|
|
42
|
+
__name(isIdentifierStart, "isIdentifierStart");
|
|
43
|
+
function isIdentifierPart(char) {
|
|
44
|
+
return !!char && /[a-zA-Z0-9_$]/.test(char);
|
|
45
|
+
}
|
|
46
|
+
__name(isIdentifierPart, "isIdentifierPart");
|
|
47
|
+
function skipSpaces(input, index) {
|
|
48
|
+
let next = index;
|
|
49
|
+
while (/\s/.test(input[next] || "")) next += 1;
|
|
50
|
+
return next;
|
|
51
|
+
}
|
|
52
|
+
__name(skipSpaces, "skipSpaces");
|
|
53
|
+
function skipPostfix(input, index) {
|
|
54
|
+
let next = skipSpaces(input, index);
|
|
55
|
+
while (input[next] === "!") {
|
|
56
|
+
next = skipSpaces(input, next + 1);
|
|
57
|
+
}
|
|
58
|
+
return next;
|
|
59
|
+
}
|
|
60
|
+
__name(skipPostfix, "skipPostfix");
|
|
61
|
+
function previousNonSpaceIndex(input, index) {
|
|
62
|
+
let next = index;
|
|
63
|
+
while (next >= 0 && /\s/.test(input[next] || "")) next -= 1;
|
|
64
|
+
return next;
|
|
65
|
+
}
|
|
66
|
+
__name(previousNonSpaceIndex, "previousNonSpaceIndex");
|
|
67
|
+
function isGroupedCtxReference(input, start) {
|
|
68
|
+
const openParenIndex = previousNonSpaceIndex(input, start - 1);
|
|
69
|
+
if (input[openParenIndex] !== "(") return false;
|
|
70
|
+
const beforeOpenParenIndex = previousNonSpaceIndex(input, openParenIndex - 1);
|
|
71
|
+
if (beforeOpenParenIndex < 0) return true;
|
|
72
|
+
const beforeOpenParen = input[beforeOpenParenIndex];
|
|
73
|
+
return !isIdentifierPart(beforeOpenParen) && beforeOpenParen !== ")" && beforeOpenParen !== "]";
|
|
74
|
+
}
|
|
75
|
+
__name(isGroupedCtxReference, "isGroupedCtxReference");
|
|
76
|
+
function hasGroupedContinuation(input, index) {
|
|
77
|
+
const closeParenIndex = skipSpaces(input, index);
|
|
78
|
+
if (input[closeParenIndex] !== ")") return false;
|
|
79
|
+
const continuationIndex = skipSpaces(input, closeParenIndex + 1);
|
|
80
|
+
return input[continuationIndex] === "." || input[continuationIndex] === "[" || input.startsWith("?.", continuationIndex);
|
|
81
|
+
}
|
|
82
|
+
__name(hasGroupedContinuation, "hasGroupedContinuation");
|
|
83
|
+
function readIdentifier(input, index) {
|
|
84
|
+
if (!isIdentifierStart(input[index])) return null;
|
|
85
|
+
let next = index + 1;
|
|
86
|
+
while (isIdentifierPart(input[next])) next += 1;
|
|
87
|
+
return { value: input.slice(index, next), next };
|
|
88
|
+
}
|
|
89
|
+
__name(readIdentifier, "readIdentifier");
|
|
90
|
+
function readNumber(input, index) {
|
|
91
|
+
const match = input.slice(index).match(/^\d+/);
|
|
92
|
+
return match ? { value: match[0], next: index + match[0].length } : null;
|
|
93
|
+
}
|
|
94
|
+
__name(readNumber, "readNumber");
|
|
95
|
+
function readQuotedProperty(input, index) {
|
|
96
|
+
const quote = input[index];
|
|
97
|
+
if (quote !== '"' && quote !== "'") return null;
|
|
98
|
+
let next = index + 1;
|
|
99
|
+
let value = "";
|
|
100
|
+
while (next < input.length) {
|
|
101
|
+
const char = input[next];
|
|
102
|
+
if (char === "\\") {
|
|
103
|
+
value += input.slice(next, next + 2);
|
|
104
|
+
next += 2;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (char === quote) {
|
|
108
|
+
return { value, next: next + 1 };
|
|
109
|
+
}
|
|
110
|
+
value += char;
|
|
111
|
+
next += 1;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
__name(readQuotedProperty, "readQuotedProperty");
|
|
116
|
+
function readBracketAccess(input, index) {
|
|
117
|
+
let next = skipSpaces(input, index + 1);
|
|
118
|
+
const quoted = readQuotedProperty(input, next);
|
|
119
|
+
if (quoted) {
|
|
120
|
+
next = skipSpaces(input, quoted.next);
|
|
121
|
+
if (input[next] !== "]") return { dynamic: true };
|
|
122
|
+
return { dynamic: false, kind: "property", next: next + 1, value: quoted.value };
|
|
123
|
+
}
|
|
124
|
+
const number = readNumber(input, next);
|
|
125
|
+
if (number) {
|
|
126
|
+
next = skipSpaces(input, number.next);
|
|
127
|
+
if (input[next] !== "]") return { dynamic: true };
|
|
128
|
+
return { dynamic: false, kind: "index", next: next + 1, value: number.value };
|
|
129
|
+
}
|
|
130
|
+
return { dynamic: true };
|
|
131
|
+
}
|
|
132
|
+
__name(readBracketAccess, "readBracketAccess");
|
|
133
|
+
function appendPathSegment(segments, kind, value) {
|
|
134
|
+
if (kind === "property") {
|
|
135
|
+
segments.push(value);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (segments.length) {
|
|
139
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}[${value}]`;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
segments.push(`[${value}]`);
|
|
143
|
+
}
|
|
144
|
+
__name(appendPathSegment, "appendPathSegment");
|
|
145
|
+
function readAccess(input, index) {
|
|
146
|
+
const next = skipPostfix(input, index);
|
|
147
|
+
if (input[next] === "[") {
|
|
148
|
+
return readBracketAccess(input, next);
|
|
149
|
+
}
|
|
150
|
+
if (input.startsWith("?.", next)) {
|
|
151
|
+
const propertyStart = skipSpaces(input, next + 2);
|
|
152
|
+
if (input[propertyStart] === "[") {
|
|
153
|
+
return readBracketAccess(input, propertyStart);
|
|
154
|
+
}
|
|
155
|
+
const identifier = readIdentifier(input, propertyStart) || readNumber(input, propertyStart);
|
|
156
|
+
return identifier ? { dynamic: false, kind: "property", next: identifier.next, value: identifier.value } : null;
|
|
157
|
+
}
|
|
158
|
+
if (input[next] === ".") {
|
|
159
|
+
const propertyStart = skipSpaces(input, next + 1);
|
|
160
|
+
const identifier = readIdentifier(input, propertyStart) || readNumber(input, propertyStart);
|
|
161
|
+
return identifier ? { dynamic: false, kind: "property", next: identifier.next, value: identifier.value } : null;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
__name(readAccess, "readAccess");
|
|
166
|
+
function parseDotOnlyCtxReference(expr) {
|
|
167
|
+
const trimmed = expr.trim();
|
|
168
|
+
const segment = "(?:[a-zA-Z_$][a-zA-Z0-9_$-]*|\\d+)";
|
|
169
|
+
const match = trimmed.match(new RegExp(`^ctx\\.([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\\.(${segment}(?:\\.${segment})*))?$`));
|
|
170
|
+
if (!match) return null;
|
|
171
|
+
return {
|
|
172
|
+
methodCall: false,
|
|
173
|
+
path: match[2] || void 0,
|
|
174
|
+
unsupportedDynamicPath: false,
|
|
175
|
+
varName: match[1]
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
__name(parseDotOnlyCtxReference, "parseDotOnlyCtxReference");
|
|
179
|
+
function parseCtxReference(input, start) {
|
|
180
|
+
if (input.slice(start, start + 3) !== "ctx") return null;
|
|
181
|
+
if (isIdentifierPart(input[start - 1]) || isIdentifierPart(input[start + 3])) return null;
|
|
182
|
+
if (isGroupedCtxReference(input, start)) {
|
|
183
|
+
return {
|
|
184
|
+
next: start + 3,
|
|
185
|
+
reference: {
|
|
186
|
+
methodCall: false,
|
|
187
|
+
unsupportedDynamicPath: true,
|
|
188
|
+
varName: ""
|
|
52
189
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const rootAccess = readAccess(input, start + 3);
|
|
193
|
+
if (!rootAccess) return null;
|
|
194
|
+
if (rootAccess.dynamic) {
|
|
195
|
+
return {
|
|
196
|
+
next: start + 3,
|
|
197
|
+
reference: {
|
|
198
|
+
methodCall: false,
|
|
199
|
+
unsupportedDynamicPath: true,
|
|
200
|
+
varName: ""
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (rootAccess.kind !== "property") {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const segments = [];
|
|
208
|
+
let next = rootAccess.next;
|
|
209
|
+
let methodCall = false;
|
|
210
|
+
let unsupportedDynamicPath = false;
|
|
211
|
+
while (next < input.length) {
|
|
212
|
+
const accessStart = skipPostfix(input, next);
|
|
213
|
+
if ((input[accessStart] === "(" || input.startsWith("?.(", accessStart)) && !segments.length) {
|
|
214
|
+
methodCall = true;
|
|
215
|
+
next = accessStart;
|
|
216
|
+
break;
|
|
57
217
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
218
|
+
const access = readAccess(input, next);
|
|
219
|
+
if (!access) {
|
|
220
|
+
next = accessStart;
|
|
221
|
+
if (input[accessStart] === "[" || input[accessStart] === "." || input.startsWith("?.", accessStart)) {
|
|
222
|
+
unsupportedDynamicPath = true;
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
if (access.dynamic) {
|
|
227
|
+
unsupportedDynamicPath = true;
|
|
228
|
+
next = accessStart;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
appendPathSegment(segments, access.kind, access.value);
|
|
232
|
+
next = access.next;
|
|
233
|
+
}
|
|
234
|
+
const path = segments.join(".");
|
|
235
|
+
return {
|
|
236
|
+
next: Math.max(next, rootAccess.next),
|
|
237
|
+
reference: {
|
|
238
|
+
methodCall,
|
|
239
|
+
path: path || void 0,
|
|
240
|
+
unsupportedDynamicPath: unsupportedDynamicPath || hasGroupedContinuation(input, next),
|
|
241
|
+
varName: rootAccess.value
|
|
242
|
+
}
|
|
243
|
+
};
|
|
61
244
|
}
|
|
62
|
-
__name(
|
|
63
|
-
function
|
|
245
|
+
__name(parseCtxReference, "parseCtxReference");
|
|
246
|
+
function collectCtxReferences(expr) {
|
|
247
|
+
const dotOnly = parseDotOnlyCtxReference(expr);
|
|
248
|
+
if (dotOnly) return [dotOnly];
|
|
249
|
+
const references = [];
|
|
250
|
+
for (let index = 0; index < expr.length; index += 1) {
|
|
251
|
+
if (expr.slice(index, index + 3) !== "ctx") continue;
|
|
252
|
+
const parsed = parseCtxReference(expr, index);
|
|
253
|
+
if (!parsed) continue;
|
|
254
|
+
references.push(parsed.reference);
|
|
255
|
+
index = Math.max(index, parsed.next - 1);
|
|
256
|
+
}
|
|
257
|
+
return references;
|
|
258
|
+
}
|
|
259
|
+
__name(collectCtxReferences, "collectCtxReferences");
|
|
260
|
+
function addUsagePath(usage, varName, path, methodCall) {
|
|
261
|
+
usage[varName] = usage[varName] || [];
|
|
262
|
+
if (path) {
|
|
263
|
+
usage[varName].push(path);
|
|
264
|
+
} else if (methodCall && !usage[varName].includes("")) {
|
|
265
|
+
usage[varName].push("");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
__name(addUsagePath, "addUsagePath");
|
|
269
|
+
function extractVariableUsage(template) {
|
|
64
270
|
const usage = {};
|
|
271
|
+
let unsupportedDynamicPath = false;
|
|
272
|
+
const visitExpression = /* @__PURE__ */ __name((expr) => {
|
|
273
|
+
for (const reference of collectCtxReferences(expr)) {
|
|
274
|
+
if (reference.unsupportedDynamicPath) {
|
|
275
|
+
unsupportedDynamicPath = true;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (!reference.varName) continue;
|
|
279
|
+
addUsagePath(usage, reference.varName, reference.path, reference.methodCall);
|
|
280
|
+
}
|
|
281
|
+
}, "visitExpression");
|
|
65
282
|
const visit = /* @__PURE__ */ __name((src) => {
|
|
66
283
|
if (typeof src === "string") {
|
|
67
284
|
const regex = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
68
|
-
let
|
|
69
|
-
while ((
|
|
70
|
-
|
|
71
|
-
const pathRegex = /ctx\.([a-zA-Z_$][a-zA-Z0-9_$]*)([^\s)]*)/g;
|
|
72
|
-
let pm;
|
|
73
|
-
while ((pm = pathRegex.exec(expr)) !== null) {
|
|
74
|
-
const varName = pm[1];
|
|
75
|
-
const after = pm[2] || "";
|
|
76
|
-
usage[varName] = usage[varName] || [];
|
|
77
|
-
if (after.startsWith(".")) {
|
|
78
|
-
usage[varName].push(after.slice(1));
|
|
79
|
-
} else if (after.startsWith("[")) {
|
|
80
|
-
const mm = after.match(/^\[\s*(["'])\s*([^'"\]]+)\s*\1\s*\](.*)$/);
|
|
81
|
-
if (mm) {
|
|
82
|
-
const first = mm[2];
|
|
83
|
-
const rest = mm[3] || "";
|
|
84
|
-
usage[varName].push(`${first}${rest}`);
|
|
85
|
-
} else {
|
|
86
|
-
const mn = after.match(/^\[(\d+)\](.*)$/);
|
|
87
|
-
if (mn) {
|
|
88
|
-
const idx = mn[1];
|
|
89
|
-
const rest = mn[2] || "";
|
|
90
|
-
usage[varName].push(`[${idx}]${rest}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
} else if (after.startsWith("(")) {
|
|
94
|
-
if (!usage[varName].length) usage[varName].push("");
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const bracketVarRegex = /ctx\[\s*(["'])\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\1\s*\]([^\s)]*)/g;
|
|
98
|
-
let bm;
|
|
99
|
-
while ((bm = bracketVarRegex.exec(expr)) !== null) {
|
|
100
|
-
const varName = bm[2];
|
|
101
|
-
const after = bm[3] || "";
|
|
102
|
-
usage[varName] = usage[varName] || [];
|
|
103
|
-
if (after.startsWith(".")) {
|
|
104
|
-
usage[varName].push(after.slice(1));
|
|
105
|
-
} else if (after.startsWith("[")) {
|
|
106
|
-
const mm = after.match(/^\[\s*(["'])\s*([^'"\]]+)\s*\1\s*\](.*)$/);
|
|
107
|
-
if (mm) {
|
|
108
|
-
const first = mm[2];
|
|
109
|
-
const rest = mm[3] || "";
|
|
110
|
-
usage[varName].push(`${first}${rest}`);
|
|
111
|
-
} else {
|
|
112
|
-
const mn = after.match(/^\[(\d+)\](.*)$/);
|
|
113
|
-
if (mn) {
|
|
114
|
-
const idx = mn[1];
|
|
115
|
-
const rest = mn[2] || "";
|
|
116
|
-
usage[varName].push(`[${idx}]${rest}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
} else if (after.startsWith("(")) {
|
|
120
|
-
if (!usage[varName].length) usage[varName].push("");
|
|
121
|
-
}
|
|
122
|
-
}
|
|
285
|
+
let match;
|
|
286
|
+
while ((match = regex.exec(src)) !== null) {
|
|
287
|
+
visitExpression(match[1]);
|
|
123
288
|
}
|
|
124
|
-
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (Array.isArray(src)) {
|
|
125
292
|
src.forEach(visit);
|
|
126
|
-
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (isObject(src)) {
|
|
127
296
|
Object.values(src).forEach(visit);
|
|
128
297
|
}
|
|
129
298
|
}, "visit");
|
|
130
299
|
visit(template);
|
|
131
|
-
return usage;
|
|
300
|
+
return { unsupportedDynamicPath, usage };
|
|
301
|
+
}
|
|
302
|
+
__name(extractVariableUsage, "extractVariableUsage");
|
|
303
|
+
function extractUsedVariableNames(template) {
|
|
304
|
+
return new Set(Object.keys(extractVariableUsage(template).usage));
|
|
305
|
+
}
|
|
306
|
+
__name(extractUsedVariableNames, "extractUsedVariableNames");
|
|
307
|
+
function extractUsedVariablePaths(template) {
|
|
308
|
+
return extractVariableUsage(template).usage;
|
|
132
309
|
}
|
|
133
310
|
__name(extractUsedVariablePaths, "extractUsedVariablePaths");
|
|
134
311
|
// Annotate the CommonJS export names for ESM import in node:
|
|
135
312
|
0 && (module.exports = {
|
|
136
313
|
extractUsedVariableNames,
|
|
137
|
-
extractUsedVariablePaths
|
|
314
|
+
extractUsedVariablePaths,
|
|
315
|
+
extractVariableUsage
|
|
138
316
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/utils",
|
|
3
|
-
"version": "2.3.0-
|
|
3
|
+
"version": "2.3.0-beta.10",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"types": "./lib/index.d.ts",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"liquidjs": "^10.23.0",
|
|
20
20
|
"multer": "^2.1.1",
|
|
21
21
|
"object-path": "^0.11.8",
|
|
22
|
+
"sanitize-html": "2.17.5",
|
|
22
23
|
"ses": "^1.14.0"
|
|
23
24
|
},
|
|
24
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "7aa3dce6cf3201752882f7012c72997898015d89"
|
|
25
26
|
}
|