@maiyunnet/kebab 9.14.2 → 9.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/doc/kebab-rag.md +381 -559
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/core.d.ts +2 -0
- package/lib/core.js +21 -6
- package/lib/crypto.js +1 -1
- package/lib/fs.js +1 -2
- package/lib/kv.d.ts +5 -0
- package/lib/kv.js +21 -1
- package/lib/net/formdata.d.ts +2 -81
- package/lib/net/formdata.js +2 -164
- package/lib/net.js +16 -2
- package/lib/ratelimit.js +6 -6
- package/lib/scan.js +18 -5
- package/lib/session.js +2 -0
- package/lib/text.d.ts +6 -0
- package/lib/text.js +39 -6
- package/lib/undici/response.js +0 -2
- package/lib/undici.js +17 -2
- package/package.json +1 -1
- package/sys/child.js +0 -8
- package/sys/ctr.d.ts +6 -0
- package/sys/master.js +158 -48
- package/sys/route.d.ts +6 -0
- package/sys/route.js +87 -25
- package/www/example/ctr/test.js +5 -3
- package/www/example/stc/view/hello.page.js +1 -1
- package/www/example/stc/view/hello.page.tsx +2 -2
- package/www/example/stc/view/react-router.page.js +4 -4
- package/www/example/stc/view/react-router.page.tsx +15 -16
- package/www/example/stc/view/react.page.js +7 -7
- package/www/example/stc/view/react.page.tsx +27 -22
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* --- 本文件用来定义每个目录实体地址的常量 ---
|
|
7
7
|
*/
|
|
8
8
|
/** --- 当前系统版本号 --- */
|
|
9
|
-
export const VER = '9.
|
|
9
|
+
export const VER = '9.15.0';
|
|
10
10
|
// --- 服务端用的路径 ---
|
|
11
11
|
const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
|
|
12
12
|
/** --- /xxx/xxx --- */
|
package/lib/core.d.ts
CHANGED
|
@@ -127,6 +127,8 @@ export declare function ips(ctr: sCtr.Ctr | http.IncomingHttpHeaders): {
|
|
|
127
127
|
export declare const REAL_IP_X = "x-forwarded-for";
|
|
128
128
|
/** --- 使用的是 Cloudflare --- */
|
|
129
129
|
export declare const REAL_IP_CF = "cf-connecting-ip";
|
|
130
|
+
/** --- 使用的是 EO --- */
|
|
131
|
+
export declare const REAL_IP_EO = "eo-connecting-ip";
|
|
130
132
|
/**
|
|
131
133
|
* --- 获取直连 IP(安全 IP) ---
|
|
132
134
|
* @param ctr
|
package/lib/core.js
CHANGED
|
@@ -8,6 +8,7 @@ import * as http2 from 'http2';
|
|
|
8
8
|
import * as stream from 'stream';
|
|
9
9
|
import * as os from 'os';
|
|
10
10
|
import * as net from 'net';
|
|
11
|
+
import * as crypto from 'crypto';
|
|
11
12
|
import Ajv from 'ajv';
|
|
12
13
|
import addFormats from 'ajv-formats';
|
|
13
14
|
import * as kebab from '#kebab/index.js';
|
|
@@ -110,7 +111,7 @@ export function random(length = 8, source = RANDOM_LN, block = '') {
|
|
|
110
111
|
}
|
|
111
112
|
let temp = '';
|
|
112
113
|
for (let i = 0; i < length; ++i) {
|
|
113
|
-
temp += source[
|
|
114
|
+
temp += source[crypto.randomInt(len)];
|
|
114
115
|
}
|
|
115
116
|
return temp;
|
|
116
117
|
}
|
|
@@ -337,6 +338,8 @@ export function ips(ctr) {
|
|
|
337
338
|
export const REAL_IP_X = 'x-forwarded-for';
|
|
338
339
|
/** --- 使用的是 Cloudflare --- */
|
|
339
340
|
export const REAL_IP_CF = 'cf-connecting-ip';
|
|
341
|
+
/** --- 使用的是 EO --- */
|
|
342
|
+
export const REAL_IP_EO = 'eo-connecting-ip';
|
|
340
343
|
/**
|
|
341
344
|
* --- 规范化 IP 地址 ---
|
|
342
345
|
* --- 将 IPv4-mapped IPv6(如 ::ffff:127.0.0.1)转换为纯 IPv4,原生 IPv6 保持不变 ---
|
|
@@ -1153,24 +1156,36 @@ export function clone(obj) {
|
|
|
1153
1156
|
const keys = isArray ? obj.keys() : Object.keys(obj);
|
|
1154
1157
|
for (const key of keys) {
|
|
1155
1158
|
const val = obj[key];
|
|
1159
|
+
let clonedValue;
|
|
1156
1160
|
if (val instanceof Date) {
|
|
1157
|
-
|
|
1161
|
+
clonedValue = new Date(val.getTime());
|
|
1158
1162
|
}
|
|
1159
1163
|
else if (val instanceof FormData) {
|
|
1160
1164
|
const fd = new FormData();
|
|
1161
1165
|
for (const item of val) {
|
|
1162
1166
|
fd.append(item[0], item[1]);
|
|
1163
1167
|
}
|
|
1164
|
-
|
|
1168
|
+
clonedValue = fd;
|
|
1165
1169
|
}
|
|
1166
1170
|
else if (val === null) {
|
|
1167
|
-
|
|
1171
|
+
clonedValue = null;
|
|
1168
1172
|
}
|
|
1169
1173
|
else if (typeof val === 'object') {
|
|
1170
|
-
|
|
1174
|
+
clonedValue = clone(val);
|
|
1171
1175
|
}
|
|
1172
1176
|
else {
|
|
1173
|
-
|
|
1177
|
+
clonedValue = val;
|
|
1178
|
+
}
|
|
1179
|
+
if (!isArray && ((key === '__proto__') || (key === 'prototype') || (key === 'constructor'))) {
|
|
1180
|
+
Object.defineProperty(newObj, key, {
|
|
1181
|
+
'configurable': true,
|
|
1182
|
+
'enumerable': true,
|
|
1183
|
+
'value': clonedValue,
|
|
1184
|
+
'writable': true,
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
else {
|
|
1188
|
+
newObj[key] = clonedValue;
|
|
1174
1189
|
}
|
|
1175
1190
|
}
|
|
1176
1191
|
return newObj;
|
package/lib/crypto.js
CHANGED
|
@@ -14,7 +14,7 @@ import * as lCore from '#kebab/lib/core.js';
|
|
|
14
14
|
* @param options 参数
|
|
15
15
|
*/
|
|
16
16
|
export function generateKeyPair(type, options = {}) {
|
|
17
|
-
return new Promise(
|
|
17
|
+
return new Promise(resolve => {
|
|
18
18
|
options.modulusLength ??= 2048;
|
|
19
19
|
if (options.namedCurve !== undefined) {
|
|
20
20
|
options.namedCurve = options.namedCurve.toUpperCase();
|
package/lib/fs.js
CHANGED
|
@@ -26,10 +26,9 @@ export async function getContent(path, options) {
|
|
|
26
26
|
const encoding = options.encoding;
|
|
27
27
|
const start = options.start;
|
|
28
28
|
const end = options.end;
|
|
29
|
-
if (start
|
|
29
|
+
if ((start !== undefined) || (end !== undefined)) {
|
|
30
30
|
return new Promise(function (resolve) {
|
|
31
31
|
const rs = createReadStream(path, {
|
|
32
|
-
'encoding': encoding,
|
|
33
32
|
'start': start,
|
|
34
33
|
'end': end
|
|
35
34
|
});
|
package/lib/kv.d.ts
CHANGED
|
@@ -118,6 +118,11 @@ export declare class Kv {
|
|
|
118
118
|
* @param key
|
|
119
119
|
*/
|
|
120
120
|
getJson(key: string): Promise<any | false | null>;
|
|
121
|
+
/**
|
|
122
|
+
* --- 原子获取并删除 JSON 对象 ---
|
|
123
|
+
* @param key 键
|
|
124
|
+
*/
|
|
125
|
+
getDelJson(key: string): Promise<any | false | null>;
|
|
121
126
|
/**
|
|
122
127
|
* --- 删除已存在的值 ---
|
|
123
128
|
* @param keys
|
package/lib/kv.js
CHANGED
|
@@ -37,7 +37,7 @@ export class Kv {
|
|
|
37
37
|
if (!conn) {
|
|
38
38
|
return false;
|
|
39
39
|
}
|
|
40
|
-
return conn.
|
|
40
|
+
return conn.createPipelineClient();
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* --- 设定一个值 ---
|
|
@@ -272,6 +272,26 @@ end`;
|
|
|
272
272
|
const r = lText.parseJson(v);
|
|
273
273
|
return r;
|
|
274
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* --- 原子获取并删除 JSON 对象 ---
|
|
277
|
+
* @param key 键
|
|
278
|
+
*/
|
|
279
|
+
async getDelJson(key) {
|
|
280
|
+
const conn = await this._getConnection();
|
|
281
|
+
if (!conn) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
const value = await conn.getAndDel(this._etc.pre + key);
|
|
286
|
+
if (value === null) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
return lText.parseJson(value);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
275
295
|
/**
|
|
276
296
|
* --- 删除已存在的值 ---
|
|
277
297
|
* @param keys
|
package/lib/net/formdata.d.ts
CHANGED
|
@@ -1,83 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Date: 2020-04-07 23:45:03
|
|
4
|
-
* Last: 2020-04-07 23:45:07, 2022-09-10 01:35:25
|
|
2
|
+
* --- 兼容旧版 net 模块的 FormData 导出,统一复用 undici 实现 ---
|
|
5
3
|
*/
|
|
6
|
-
|
|
7
|
-
/** --- Item 对象 --- */
|
|
8
|
-
export type IItem = {
|
|
9
|
-
/** --- key 键 --- */
|
|
10
|
-
'key': string;
|
|
11
|
-
'type': 'string';
|
|
12
|
-
/** --- 字符串值 --- */
|
|
13
|
-
'value': string;
|
|
14
|
-
'path': '';
|
|
15
|
-
} | {
|
|
16
|
-
/** --- key 键 --- */
|
|
17
|
-
'key': string;
|
|
18
|
-
'type': 'file';
|
|
19
|
-
/** --- 文件名 --- */
|
|
20
|
-
'value': string;
|
|
21
|
-
/** --- 文件路径 --- */
|
|
22
|
-
'path': string;
|
|
23
|
-
} | {
|
|
24
|
-
/** --- key 键 --- */
|
|
25
|
-
'key': string;
|
|
26
|
-
'type': 'buffer';
|
|
27
|
-
/** --- 文件名 --- */
|
|
28
|
-
'value': string;
|
|
29
|
-
/** --- Buffer 数据 --- */
|
|
30
|
-
'path': Buffer;
|
|
31
|
-
};
|
|
32
|
-
export declare class FormData extends stream.Readable {
|
|
33
|
-
/** --- read 调用次数 --- */
|
|
34
|
-
private _num;
|
|
35
|
-
/** --- 要编译的数据 --- */
|
|
36
|
-
private readonly _data;
|
|
37
|
-
/** --- 分隔符 --- */
|
|
38
|
-
private readonly _boundary;
|
|
39
|
-
/** --- 正在读取文件吗 --- */
|
|
40
|
-
private _fileReading;
|
|
41
|
-
/** --- 是否已经结束 --- */
|
|
42
|
-
private _close;
|
|
43
|
-
/** --- 总字节长度 --- */
|
|
44
|
-
private _length;
|
|
45
|
-
/** --- 已发送字节长度 --- */
|
|
46
|
-
private _sent;
|
|
47
|
-
/**
|
|
48
|
-
* --- 添加字符串 ---
|
|
49
|
-
* @param key 键
|
|
50
|
-
* @param val 值
|
|
51
|
-
*/
|
|
52
|
-
putString(key: string, val: string): void;
|
|
53
|
-
/**
|
|
54
|
-
* --- 添加文件 ---
|
|
55
|
-
* @param key 键
|
|
56
|
-
* @param path 路径
|
|
57
|
-
* @param fname 可选,文件名
|
|
58
|
-
*/
|
|
59
|
-
putFile(key: string, path: string, fname?: string): Promise<boolean>;
|
|
60
|
-
/**
|
|
61
|
-
* --- 添加 Buffer 数据 ---
|
|
62
|
-
* @param key 键
|
|
63
|
-
* @param buffer Buffer 数据
|
|
64
|
-
* @param fname 文件名
|
|
65
|
-
*/
|
|
66
|
-
putBuffer(key: string, buffer: Buffer, fname: string): void;
|
|
67
|
-
/**
|
|
68
|
-
* --- 获取 boundary ---
|
|
69
|
-
*/
|
|
70
|
-
getBoundary(): string;
|
|
71
|
-
/**
|
|
72
|
-
* --- 获取总字节长度 ---
|
|
73
|
-
*/
|
|
74
|
-
getLength(): number;
|
|
75
|
-
/**
|
|
76
|
-
* --- 获取已发送的字节长度 ---
|
|
77
|
-
*/
|
|
78
|
-
getSent(): number;
|
|
79
|
-
/**
|
|
80
|
-
* --- 间隔读取(on data 或 pipe 触发)---
|
|
81
|
-
*/
|
|
82
|
-
_read(): void;
|
|
83
|
-
}
|
|
4
|
+
export * from '#kebab/lib/undici/formdata.js';
|
package/lib/net/formdata.js
CHANGED
|
@@ -1,166 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Date: 2020-04-07 23:45:03
|
|
4
|
-
* Last: 2020-04-07 23:45:07, 2022-09-10 01:35:25
|
|
2
|
+
* --- 兼容旧版 net 模块的 FormData 导出,统一复用 undici 实现 ---
|
|
5
3
|
*/
|
|
6
|
-
|
|
7
|
-
import * as mime from '@litert/mime';
|
|
8
|
-
import * as core from '#kebab/lib/core.js';
|
|
9
|
-
import * as fs from '#kebab/lib/fs.js';
|
|
10
|
-
export class FormData extends stream.Readable {
|
|
11
|
-
/** --- read 调用次数 --- */
|
|
12
|
-
_num = 0;
|
|
13
|
-
/** --- 要编译的数据 --- */
|
|
14
|
-
_data = [];
|
|
15
|
-
/** --- 分隔符 --- */
|
|
16
|
-
_boundary = '----Kebab' + core.random(29, core.RANDOM_LUN);
|
|
17
|
-
/** --- 正在读取文件吗 --- */
|
|
18
|
-
_fileReading = false;
|
|
19
|
-
/** --- 是否已经结束 --- */
|
|
20
|
-
_close = false;
|
|
21
|
-
/** --- 总字节长度 --- */
|
|
22
|
-
_length = 4 + this._boundary.length;
|
|
23
|
-
/** --- 已发送字节长度 --- */
|
|
24
|
-
_sent = 0;
|
|
25
|
-
/**
|
|
26
|
-
* --- 添加字符串 ---
|
|
27
|
-
* @param key 键
|
|
28
|
-
* @param val 值
|
|
29
|
-
*/
|
|
30
|
-
putString(key, val) {
|
|
31
|
-
this._data.push({
|
|
32
|
-
'key': key,
|
|
33
|
-
'type': 'string',
|
|
34
|
-
'value': val,
|
|
35
|
-
'path': ''
|
|
36
|
-
});
|
|
37
|
-
this._length += this._boundary.length + 49 + Buffer.byteLength(key) + Buffer.byteLength(val);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* --- 添加文件 ---
|
|
41
|
-
* @param key 键
|
|
42
|
-
* @param path 路径
|
|
43
|
-
* @param fname 可选,文件名
|
|
44
|
-
*/
|
|
45
|
-
async putFile(key, path, fname) {
|
|
46
|
-
path = path.replace(/\\/g, '/');
|
|
47
|
-
const stat = await fs.stats(path);
|
|
48
|
-
if (!stat) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
if (!fname) {
|
|
52
|
-
const lio = path.lastIndexOf('/');
|
|
53
|
-
fname = lio === -1 ? path : path.slice(lio + 1);
|
|
54
|
-
}
|
|
55
|
-
this._data.push({
|
|
56
|
-
'key': key,
|
|
57
|
-
'type': 'file',
|
|
58
|
-
'value': fname,
|
|
59
|
-
'path': path
|
|
60
|
-
});
|
|
61
|
-
this._length += this._boundary.length +
|
|
62
|
-
76 + Buffer.byteLength(key) + Buffer.byteLength(fname) +
|
|
63
|
-
mime.getMime(fname).length + stat.size + 2;
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* --- 添加 Buffer 数据 ---
|
|
68
|
-
* @param key 键
|
|
69
|
-
* @param buffer Buffer 数据
|
|
70
|
-
* @param fname 文件名
|
|
71
|
-
*/
|
|
72
|
-
putBuffer(key, buffer, fname) {
|
|
73
|
-
this._data.push({
|
|
74
|
-
'key': key,
|
|
75
|
-
'type': 'buffer',
|
|
76
|
-
'value': fname,
|
|
77
|
-
'path': buffer
|
|
78
|
-
});
|
|
79
|
-
this._length += this._boundary.length +
|
|
80
|
-
76 + Buffer.byteLength(key) + Buffer.byteLength(fname) +
|
|
81
|
-
mime.getMime(fname).length + buffer.byteLength + 2;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* --- 获取 boundary ---
|
|
85
|
-
*/
|
|
86
|
-
getBoundary() {
|
|
87
|
-
return this._boundary;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* --- 获取总字节长度 ---
|
|
91
|
-
*/
|
|
92
|
-
getLength() {
|
|
93
|
-
return this._length;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* --- 获取已发送的字节长度 ---
|
|
97
|
-
*/
|
|
98
|
-
getSent() {
|
|
99
|
-
return this._sent;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* --- 间隔读取(on data 或 pipe 触发)---
|
|
103
|
-
*/
|
|
104
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
105
|
-
_read() {
|
|
106
|
-
if (this._close) {
|
|
107
|
-
// --- 结束了 ---
|
|
108
|
-
this.push(null);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
// --- 文件读取中 ---
|
|
112
|
-
if (this._fileReading) {
|
|
113
|
-
// --- 等待下面 fileReadable 的 on data 或 end 事件 ---
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
// --- 获取当前 item ---
|
|
117
|
-
const item = this._data[this._num];
|
|
118
|
-
if (!item) {
|
|
119
|
-
this._close = true;
|
|
120
|
-
const push = `--${this._boundary}--`;
|
|
121
|
-
this._sent += Buffer.byteLength(push);
|
|
122
|
-
this.push(push);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
if (item.type === 'buffer') {
|
|
126
|
-
// --- Buffer 数据 ---
|
|
127
|
-
const push = `--${this._boundary}\r\nContent-Disposition: form-data; name="${item.key}"; filename="${item.value}"\r\nContent-Type: ${mime.getMime(item.value)}\r\n\r\n`;
|
|
128
|
-
this._sent += Buffer.byteLength(push);
|
|
129
|
-
this.push(push);
|
|
130
|
-
this._sent += item.path.byteLength;
|
|
131
|
-
this.push(item.path);
|
|
132
|
-
const pushEnd = '\r\n';
|
|
133
|
-
this._sent += Buffer.byteLength(pushEnd);
|
|
134
|
-
this.push(pushEnd);
|
|
135
|
-
}
|
|
136
|
-
else if (item.type === 'string') {
|
|
137
|
-
// --- 字段 ---
|
|
138
|
-
const push = `--${this._boundary}\r\nContent-Disposition: form-data; name="${item.key}"\r\n\r\n${item.value}\r\n`;
|
|
139
|
-
this._sent += Buffer.byteLength(push);
|
|
140
|
-
this.push(push);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
// --- 文件 ---
|
|
144
|
-
const push = `--${this._boundary}\r\nContent-Disposition: form-data; name="${item.key}"; filename="${item.value}"\r\nContent-Type: ${mime.getMime(item.value)}\r\n\r\n`;
|
|
145
|
-
this._sent += Buffer.byteLength(push);
|
|
146
|
-
this.push(push);
|
|
147
|
-
// --- 创建流 ---
|
|
148
|
-
this._fileReading = true;
|
|
149
|
-
const fileReadable = fs.createReadStream(item.path);
|
|
150
|
-
fileReadable.on('data', (chunk) => {
|
|
151
|
-
if (!(chunk instanceof Buffer)) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
this._sent += chunk.byteLength;
|
|
155
|
-
this.push(chunk);
|
|
156
|
-
});
|
|
157
|
-
fileReadable.on('end', () => {
|
|
158
|
-
this._fileReading = false;
|
|
159
|
-
const push = '\r\n';
|
|
160
|
-
this._sent += Buffer.byteLength(push);
|
|
161
|
-
this.push(push);
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
++this._num;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
4
|
+
export * from '#kebab/lib/undici/formdata.js';
|
package/lib/net.js
CHANGED
|
@@ -436,7 +436,17 @@ export async function request(u, data, opt = {}) {
|
|
|
436
436
|
return res;
|
|
437
437
|
}
|
|
438
438
|
// --- 哦,要追踪 ---
|
|
439
|
-
headers['
|
|
439
|
+
const nextUrl = lText.urlResolve(u, req.headers['location']);
|
|
440
|
+
if (lText.isSameOrigin(u, nextUrl)) {
|
|
441
|
+
headers['referer'] = u;
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
// --- 跨域跳转不得携带来源站点凭据 ---
|
|
445
|
+
delete headers['authorization'];
|
|
446
|
+
delete headers['proxy-authorization'];
|
|
447
|
+
delete headers['cookie'];
|
|
448
|
+
delete headers['referer'];
|
|
449
|
+
}
|
|
440
450
|
let nextMethod = method;
|
|
441
451
|
let nextData = data;
|
|
442
452
|
const status = res.headers['http-code'];
|
|
@@ -444,7 +454,11 @@ export async function request(u, data, opt = {}) {
|
|
|
444
454
|
nextMethod = 'GET';
|
|
445
455
|
nextData = undefined;
|
|
446
456
|
}
|
|
447
|
-
|
|
457
|
+
if (nextData instanceof stream.Readable) {
|
|
458
|
+
return res;
|
|
459
|
+
}
|
|
460
|
+
req.getStream().resume();
|
|
461
|
+
return request(nextUrl, nextData, {
|
|
448
462
|
'method': nextMethod,
|
|
449
463
|
'type': type,
|
|
450
464
|
'timeout': timeout,
|
package/lib/ratelimit.js
CHANGED
|
@@ -8,8 +8,8 @@ import * as lCore from '#kebab/lib/core.js';
|
|
|
8
8
|
* @returns 返回结果对象
|
|
9
9
|
*/
|
|
10
10
|
export async function check(kv, key, opt = {}) {
|
|
11
|
-
const window = opt.window ?? 60;
|
|
12
|
-
const max = opt.max ?? 60;
|
|
11
|
+
const window = Math.max(Math.floor(opt.window ?? 60), 1);
|
|
12
|
+
const max = Math.max(Math.floor(opt.max ?? 60), 1);
|
|
13
13
|
const pre = opt.pre ?? 'rl:';
|
|
14
14
|
const now = lTime.stamp();
|
|
15
15
|
/** --- 将窗口分为 6 个子段 --- */
|
|
@@ -20,7 +20,7 @@ export async function check(kv, key, opt = {}) {
|
|
|
20
20
|
const segCount = Math.ceil(window / segSize);
|
|
21
21
|
/** --- 统计各段请求数总和 --- */
|
|
22
22
|
let total = 0;
|
|
23
|
-
for (let i =
|
|
23
|
+
for (let i = 1; i < segCount; ++i) {
|
|
24
24
|
const segKey = `${pre}${key}:${segId - i}`;
|
|
25
25
|
const val = await kv.get(segKey);
|
|
26
26
|
if (val) {
|
|
@@ -43,7 +43,7 @@ export async function check(kv, key, opt = {}) {
|
|
|
43
43
|
if (count === 1) {
|
|
44
44
|
await kv.expire(currentSegKey, window + segSize);
|
|
45
45
|
}
|
|
46
|
-
total +=
|
|
46
|
+
total += count;
|
|
47
47
|
const allowed = total <= max;
|
|
48
48
|
return {
|
|
49
49
|
'allowed': allowed,
|
|
@@ -59,8 +59,8 @@ export async function check(kv, key, opt = {}) {
|
|
|
59
59
|
* @param opt 限速选项
|
|
60
60
|
*/
|
|
61
61
|
export async function checkFixed(kv, key, opt = {}) {
|
|
62
|
-
const window = opt.window ?? 60;
|
|
63
|
-
const max = opt.max ?? 60;
|
|
62
|
+
const window = Math.max(Math.floor(opt.window ?? 60), 1);
|
|
63
|
+
const max = Math.max(Math.floor(opt.max ?? 60), 1);
|
|
64
64
|
const pre = opt.pre ?? 'rl:';
|
|
65
65
|
const now = lTime.stamp();
|
|
66
66
|
const rkey = pre + key;
|
package/lib/scan.js
CHANGED
|
@@ -84,8 +84,15 @@ export class Scan {
|
|
|
84
84
|
this._sql.delete(this._name).where({
|
|
85
85
|
'id': data['id']
|
|
86
86
|
});
|
|
87
|
-
const
|
|
88
|
-
|
|
87
|
+
const parsed = lText.parseJson(data['data']);
|
|
88
|
+
if (parsed === false) {
|
|
89
|
+
return -3;
|
|
90
|
+
}
|
|
91
|
+
const deleted = await this._link.execute(this._sql.getSql(), this._sql.getData());
|
|
92
|
+
if (deleted.error || !deleted.packet?.affected) {
|
|
93
|
+
return -3;
|
|
94
|
+
}
|
|
95
|
+
return parsed;
|
|
89
96
|
}
|
|
90
97
|
else if (data['time_update'] > 0) {
|
|
91
98
|
// --- 已被扫描 ---
|
|
@@ -109,9 +116,15 @@ export class Scan {
|
|
|
109
116
|
}
|
|
110
117
|
this._timeLeft = ttl;
|
|
111
118
|
if (data['data'] !== null) {
|
|
112
|
-
// ---
|
|
113
|
-
await this._link.
|
|
114
|
-
|
|
119
|
+
// --- 原子获取并删除,确保并发轮询时结果只被消费一次 ---
|
|
120
|
+
const consumed = await this._link.getDelJson('scan-' + this._name + '_' + this._token);
|
|
121
|
+
if ((consumed === false) ||
|
|
122
|
+
(consumed === null) ||
|
|
123
|
+
(typeof consumed !== 'object') ||
|
|
124
|
+
(consumed['data'] === null)) {
|
|
125
|
+
return -3;
|
|
126
|
+
}
|
|
127
|
+
return consumed['data'];
|
|
115
128
|
}
|
|
116
129
|
else if (data['time_update'] > 0) {
|
|
117
130
|
// --- 已被扫描 ---
|
package/lib/session.js
CHANGED
package/lib/text.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export declare function parseHost(host: string): {
|
|
|
19
19
|
* @param url
|
|
20
20
|
*/
|
|
21
21
|
export declare function parseUrl(url: string): kebab.IUrlParse;
|
|
22
|
+
/**
|
|
23
|
+
* --- 判断两个 URL 是否属于同源地址 ---
|
|
24
|
+
* @param from 来源 URL
|
|
25
|
+
* @param to 目标 URL
|
|
26
|
+
*/
|
|
27
|
+
export declare function isSameOrigin(from: string, to: string): boolean;
|
|
22
28
|
/**
|
|
23
29
|
* --- 将相对路径根据基准路径进行转换 ---
|
|
24
30
|
* @param from 基准路径
|
package/lib/text.js
CHANGED
|
@@ -118,6 +118,24 @@ export function parseUrl(url) {
|
|
|
118
118
|
rtn['path'] = rtn['pathname'] + (rtn['query'] ? '?' + rtn['query'] : '');
|
|
119
119
|
return rtn;
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* --- 判断两个 URL 是否属于同源地址 ---
|
|
123
|
+
* @param from 来源 URL
|
|
124
|
+
* @param to 目标 URL
|
|
125
|
+
*/
|
|
126
|
+
export function isSameOrigin(from, to) {
|
|
127
|
+
const fromUri = parseUrl(from);
|
|
128
|
+
const toUri = parseUrl(to);
|
|
129
|
+
const getPort = (uri) => {
|
|
130
|
+
if (uri.port) {
|
|
131
|
+
return uri.port;
|
|
132
|
+
}
|
|
133
|
+
return uri.protocol === 'https:' ? '443' : '80';
|
|
134
|
+
};
|
|
135
|
+
return ((fromUri.protocol === toUri.protocol) &&
|
|
136
|
+
(fromUri.hostname === toUri.hostname) &&
|
|
137
|
+
(getPort(fromUri) === getPort(toUri)));
|
|
138
|
+
}
|
|
121
139
|
/** --- 限定结果不能逃逸出基准路径 --- */
|
|
122
140
|
function urlResolveLimit(from, limit, rtn) {
|
|
123
141
|
if (!limit) {
|
|
@@ -422,15 +440,25 @@ export function queryParse(query) {
|
|
|
422
440
|
catch {
|
|
423
441
|
value = pos === -1 ? '' : i.slice(pos + 1);
|
|
424
442
|
}
|
|
425
|
-
if (arrayKeys
|
|
443
|
+
if (Object.hasOwn(arrayKeys, key)) {
|
|
426
444
|
ret[key].push(value);
|
|
427
445
|
}
|
|
428
|
-
else if (
|
|
429
|
-
ret
|
|
446
|
+
else if (!Object.hasOwn(ret, key)) {
|
|
447
|
+
Object.defineProperty(ret, key, {
|
|
448
|
+
'configurable': true,
|
|
449
|
+
'enumerable': true,
|
|
450
|
+
'value': value,
|
|
451
|
+
'writable': true,
|
|
452
|
+
});
|
|
430
453
|
}
|
|
431
454
|
else {
|
|
432
455
|
ret[key] = [ret[key], value];
|
|
433
|
-
arrayKeys
|
|
456
|
+
Object.defineProperty(arrayKeys, key, {
|
|
457
|
+
'configurable': true,
|
|
458
|
+
'enumerable': true,
|
|
459
|
+
'value': true,
|
|
460
|
+
'writable': true,
|
|
461
|
+
});
|
|
434
462
|
}
|
|
435
463
|
}
|
|
436
464
|
return ret;
|
|
@@ -444,7 +472,12 @@ export function htmlescape(html) {
|
|
|
444
472
|
if (type !== 'string') {
|
|
445
473
|
return '[' + type + ']';
|
|
446
474
|
}
|
|
447
|
-
return html
|
|
475
|
+
return html
|
|
476
|
+
.replace(/&/g, '&')
|
|
477
|
+
.replace(/</g, '<')
|
|
478
|
+
.replace(/>/g, '>')
|
|
479
|
+
.replace(/"/g, '"')
|
|
480
|
+
.replace(/'/g, ''');
|
|
448
481
|
}
|
|
449
482
|
/** --- CSV 特殊字符转换为实体字符 --- */
|
|
450
483
|
export function csvescape(str) {
|
|
@@ -568,7 +601,7 @@ export function parseJson(str) {
|
|
|
568
601
|
}
|
|
569
602
|
const ints = v.slice(10);
|
|
570
603
|
const int = parseInt(ints);
|
|
571
|
-
if (int <= Number.MAX_SAFE_INTEGER) {
|
|
604
|
+
if ((int >= Number.MIN_SAFE_INTEGER) && (int <= Number.MAX_SAFE_INTEGER)) {
|
|
572
605
|
return int;
|
|
573
606
|
}
|
|
574
607
|
return BigInt(ints);
|
package/lib/undici/response.js
CHANGED
|
@@ -70,7 +70,6 @@ export class Response {
|
|
|
70
70
|
/**
|
|
71
71
|
* --- 获取响应读取流对象 ---
|
|
72
72
|
*/
|
|
73
|
-
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
74
73
|
getStream() {
|
|
75
74
|
try {
|
|
76
75
|
// --- 要解压 ---
|
|
@@ -103,7 +102,6 @@ export class Response {
|
|
|
103
102
|
/**
|
|
104
103
|
* --- 获取原生响应读取流对象 ---
|
|
105
104
|
*/
|
|
106
|
-
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
107
105
|
getRawStream() {
|
|
108
106
|
return this._req ? this._req.body : null;
|
|
109
107
|
}
|