@maiyunnet/kebab 2.0.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.
Files changed (114) hide show
  1. package/.VSCodeCounter/2025-02-14_14-46-44/details.md +82 -0
  2. package/.VSCodeCounter/2025-02-14_14-46-44/diff-details.md +15 -0
  3. package/.VSCodeCounter/2025-02-14_14-46-44/diff.csv +2 -0
  4. package/.VSCodeCounter/2025-02-14_14-46-44/diff.md +19 -0
  5. package/.VSCodeCounter/2025-02-14_14-46-44/diff.txt +22 -0
  6. package/.VSCodeCounter/2025-02-14_14-46-44/results.csv +69 -0
  7. package/.VSCodeCounter/2025-02-14_14-46-44/results.json +1 -0
  8. package/.VSCodeCounter/2025-02-14_14-46-44/results.md +48 -0
  9. package/.VSCodeCounter/2025-02-14_14-46-44/results.txt +118 -0
  10. package/.vscode/tasks.json +15 -0
  11. package/LICENSE +201 -0
  12. package/README.md +201 -0
  13. package/bin/kebab.js +2 -0
  14. package/eslint.config.js +22 -0
  15. package/index.js +19 -0
  16. package/index.ts +33 -0
  17. package/lib/buffer.js +108 -0
  18. package/lib/buffer.ts +152 -0
  19. package/lib/captcha/zcool-addict-italic.ttf +0 -0
  20. package/lib/captcha.js +71 -0
  21. package/lib/captcha.ts +63 -0
  22. package/lib/consistent.js +171 -0
  23. package/lib/consistent.ts +219 -0
  24. package/lib/core.js +663 -0
  25. package/lib/core.ts +880 -0
  26. package/lib/crypto.js +256 -0
  27. package/lib/crypto.ts +384 -0
  28. package/lib/db.js +521 -0
  29. package/lib/db.ts +719 -0
  30. package/lib/dns.js +321 -0
  31. package/lib/dns.ts +405 -0
  32. package/lib/fs.js +405 -0
  33. package/lib/fs.ts +527 -0
  34. package/lib/jwt.js +223 -0
  35. package/lib/jwt.ts +276 -0
  36. package/lib/kv.js +1004 -0
  37. package/lib/kv.ts +1489 -0
  38. package/lib/lan.js +99 -0
  39. package/lib/lan.ts +87 -0
  40. package/lib/net/cacert.pem +3480 -0
  41. package/lib/net/formdata.js +137 -0
  42. package/lib/net/formdata.ts +166 -0
  43. package/lib/net/request.js +102 -0
  44. package/lib/net/request.ts +150 -0
  45. package/lib/net/response.js +28 -0
  46. package/lib/net/response.ts +59 -0
  47. package/lib/net.js +462 -0
  48. package/lib/net.ts +662 -0
  49. package/lib/s3.js +180 -0
  50. package/lib/s3.ts +235 -0
  51. package/lib/scan.js +276 -0
  52. package/lib/scan.ts +364 -0
  53. package/lib/session.js +177 -0
  54. package/lib/session.ts +230 -0
  55. package/lib/sql.js +818 -0
  56. package/lib/sql.ts +1151 -0
  57. package/lib/ssh/sftp.js +373 -0
  58. package/lib/ssh/sftp.ts +508 -0
  59. package/lib/ssh/shell.js +109 -0
  60. package/lib/ssh/shell.ts +123 -0
  61. package/lib/ssh.js +171 -0
  62. package/lib/ssh.ts +191 -0
  63. package/lib/text/tld.json +1 -0
  64. package/lib/text.js +452 -0
  65. package/lib/text.ts +607 -0
  66. package/lib/time.js +216 -0
  67. package/lib/time.ts +254 -0
  68. package/lib/ws.js +373 -0
  69. package/lib/ws.ts +523 -0
  70. package/lib/zip.js +381 -0
  71. package/lib/zip.ts +447 -0
  72. package/lib/zlib.js +289 -0
  73. package/lib/zlib.ts +350 -0
  74. package/main.js +51 -0
  75. package/main.ts +27 -0
  76. package/package.json +37 -0
  77. package/sys/child.js +585 -0
  78. package/sys/child.ts +678 -0
  79. package/sys/cmd.js +226 -0
  80. package/sys/cmd.ts +225 -0
  81. package/sys/ctr.js +608 -0
  82. package/sys/ctr.ts +904 -0
  83. package/sys/master.js +314 -0
  84. package/sys/master.ts +355 -0
  85. package/sys/mod.js +1273 -0
  86. package/sys/mod.ts +1871 -0
  87. package/sys/route.js +922 -0
  88. package/sys/route.ts +1113 -0
  89. package/types/index.d.ts +283 -0
  90. package/www/example/ctr/main.js +42 -0
  91. package/www/example/ctr/main.ts +9 -0
  92. package/www/example/ctr/middle.js +57 -0
  93. package/www/example/ctr/middle.ts +26 -0
  94. package/www/example/ctr/test.js +2818 -0
  95. package/www/example/ctr/test.ts +3218 -0
  96. package/www/example/data/locale/en.test.json +8 -0
  97. package/www/example/data/locale/index.html +1 -0
  98. package/www/example/data/locale/ja.test.json +8 -0
  99. package/www/example/data/locale/sc.test.json +8 -0
  100. package/www/example/data/locale/tc.test.json +8 -0
  101. package/www/example/data/test.zip +0 -0
  102. package/www/example/kebab.json +24 -0
  103. package/www/example/mod/test.js +49 -0
  104. package/www/example/mod/test.ts +47 -0
  105. package/www/example/mod/testdata.js +11 -0
  106. package/www/example/mod/testdata.ts +30 -0
  107. package/www/example/route.json +6 -0
  108. package/www/example/view/test.ejs +11 -0
  109. package/www/example/ws/mproxy.js +49 -0
  110. package/www/example/ws/mproxy.ts +16 -0
  111. package/www/example/ws/rproxy.js +47 -0
  112. package/www/example/ws/rproxy.ts +14 -0
  113. package/www/example/ws/test.js +68 -0
  114. package/www/example/ws/test.ts +36 -0
package/lib/net.ts ADDED
@@ -0,0 +1,662 @@
1
+ /**
2
+ * Project: Kebab, User: JianSuoQiYue
3
+ * Date: 2019-5-15 22:47
4
+ * CA: https://curl.haxx.se/ca/cacert.pem
5
+ * Last: 2020-4-9 20:11:02, 2022-09-22 14:30:13, 2024-1-1 21:32:26, 2024-1-12 13:01:54, 2025-6-13 13:20:52
6
+ */
7
+ import * as stream from 'stream';
8
+ import * as http from 'http';
9
+ import * as http2 from 'http2';
10
+ // --- 第三方 ---
11
+ import * as hc from '@litert/http-client';
12
+ // --- 库和定义 ---
13
+ import * as fs from '~/lib/fs';
14
+ import * as text from '~/lib/text';
15
+ import * as time from '~/lib/time';
16
+ import * as kebab from '~/index';
17
+ import * as ctr from '~/sys/ctr';
18
+ import * as types from '~/types';
19
+ // --- 自己 ---
20
+ import * as fd from './net/formdata';
21
+ import * as lRequest from './net/request';
22
+ import * as response from './net/response';
23
+
24
+ /** --- 请求的传入参数选项 --- */
25
+ export interface IRequestOptions {
26
+ 'method'?: 'GET' | 'POST' | 'OPTIONS';
27
+ 'type'?: 'form' | 'json';
28
+ /** --- 秒数 --- */
29
+ 'timeout'?: number;
30
+ 'follow'?: number;
31
+ 'hosts'?: Record<string, string>;
32
+ 'save'?: string;
33
+ 'local'?: string;
34
+ 'headers'?: types.THttpHeaders;
35
+ /** --- 正向 mproxy 代理,url 如 https://xxx/abc --- */
36
+ 'mproxy'?: {
37
+ 'url': string;
38
+ 'auth': string;
39
+ 'data'?: any;
40
+ };
41
+ /** --- 默认为 default --- */
42
+ 'reuse'?: string;
43
+ /** --- cookie 托管对象 --- */
44
+ 'cookie'?: Record<string, types.ICookie>;
45
+ }
46
+
47
+ /** --- 正向代理请求的传入参数选项 --- */
48
+ export interface IMproxyOptions {
49
+ /** --- 秒数 --- */
50
+ 'timeout'?: number;
51
+ 'follow'?: number;
52
+ 'hosts'?: Record<string, string>;
53
+ 'local'?: string;
54
+ 'headers'?: types.THttpHeaders;
55
+ /** --- 默认为 default --- */
56
+ 'reuse'?: string;
57
+ }
58
+
59
+ /** --- 反向代理请求的传入参数选项 --- */
60
+ export interface IRproxyOptions {
61
+ /** --- 秒数 --- */
62
+ 'timeout'?: number;
63
+ 'follow'?: number;
64
+ 'hosts'?: Record<string, string>;
65
+ 'local'?: string;
66
+ 'headers'?: types.THttpHeaders;
67
+ /** --- 正向 mproxy 代理,url 如 https://xxx/abc --- */
68
+ 'mproxy'?: {
69
+ 'url': string;
70
+ 'auth': string;
71
+ 'data'?: any;
72
+ };
73
+ /** --- 默认为 default --- */
74
+ 'reuse'?: string;
75
+ }
76
+
77
+ /** --- ca 根证书内容 --- */
78
+ let ca: string = '';
79
+
80
+ /** --- 获取 CA 证书 --- */
81
+ export async function getCa(): Promise<string> {
82
+ if (ca) {
83
+ return ca;
84
+ }
85
+ ca = (await fs.getContent(kebab.LIB_PATH + 'net/cacert.pem', 'utf8')) ?? '';
86
+ return ca;
87
+ }
88
+
89
+ /** --- 复用的 hc 对象列表 --- */
90
+ const reuses: Record<string, hc.IClient> = {
91
+ 'default': hc.createHttpClient()
92
+ };
93
+
94
+ /**
95
+ * --- 创建一个请求对象 ---
96
+ * @param u
97
+ */
98
+ export function open(u: string): lRequest.Request {
99
+ return new lRequest.Request(u);
100
+ }
101
+
102
+ /**
103
+ * --- 发起一个 get 请求 ---
104
+ * @param u 请求的 URL
105
+ * @param opt 参数
106
+ */
107
+ export async function get(u: string, opt: IRequestOptions = {}): Promise<response.Response> {
108
+ return request(u, undefined, opt);
109
+ }
110
+
111
+ /**
112
+ * --- 发起一个 post 请求 ---
113
+ * @param u 请求的 URL
114
+ * @param data 要发送的数据
115
+ * @param opt 参数
116
+ */
117
+ export async function post(
118
+ u: string,
119
+ data: Record<string, types.Json> | Buffer | string | stream.Readable,
120
+ opt: IRequestOptions = {}
121
+ ): Promise<response.Response> {
122
+ opt.method = 'POST';
123
+ return request(u, data, opt);
124
+ }
125
+
126
+ /**
127
+ * --- 发起 JSON 请求 ---
128
+ * @param u
129
+ * @param data
130
+ * @param opt
131
+ */
132
+ export async function postJson(
133
+ u: string,
134
+ data: types.Json[] | Record<string, types.Json>,
135
+ opt: IRequestOptions = {}
136
+ ): Promise<response.Response> {
137
+ opt.method = 'POST';
138
+ opt.type = 'json';
139
+ return request(u, data, opt);
140
+ }
141
+
142
+ /**
143
+ * --- 发起一个请求 ---
144
+ * @param opt 配置项
145
+ */
146
+ export async function request(
147
+ u: string,
148
+ data?: Record<string, types.Json> | Buffer | string | stream.Readable,
149
+ opt: IRequestOptions = {}
150
+ ): Promise<response.Response> {
151
+ const uri = text.parseUrl(u);
152
+ /** --- 正向代理的地址 --- */
153
+ const puri = opt.mproxy ? text.parseUrl(opt.mproxy.url) : null;
154
+ const method = opt.method ?? 'GET';
155
+ const type = opt.type ?? 'form';
156
+ const timeout = opt.timeout ?? 10;
157
+ const follow = opt.follow ?? 0;
158
+ const hosts = opt.hosts ?? {};
159
+ const save = opt.save;
160
+ const local = opt.local;
161
+ const reuse = opt.reuse ?? 'default';
162
+ const headers: Record<string, types.Json> = {};
163
+ if (opt.headers) {
164
+ for (const key in opt.headers) {
165
+ headers[key.toLowerCase()] = opt.headers[key];
166
+ }
167
+ }
168
+ headers['user-agent'] ??= 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36';
169
+ // --- DATA ---
170
+ if (method === 'GET') {
171
+ if (data && !(data instanceof stream.Readable) && !Buffer.isBuffer(data)) {
172
+ u += (u.includes('?') ? '&' : '?') +
173
+ (typeof data === 'string' ? data : text.queryStringify(data));
174
+ data = undefined;
175
+ }
176
+ }
177
+ else {
178
+ // --- POST ---
179
+ if (data && !(data instanceof stream.Readable) && !Buffer.isBuffer(data) && typeof data !== 'string') {
180
+ if (type === 'form') {
181
+ data = text.queryStringify(data);
182
+ headers['content-type'] = 'application/x-www-form-urlencoded';
183
+ }
184
+ else {
185
+ data = text.stringifyJson(data);
186
+ headers['content-type'] = 'application/json; charset=utf-8';
187
+ }
188
+ }
189
+ else if (data instanceof fd.FormData) {
190
+ headers['content-type'] = 'multipart/form-data; boundary=' + data.getBoundary();
191
+ headers['content-length'] = data.getLength();
192
+ }
193
+ }
194
+ headers['accept-encoding'] = 'gzip, deflate';
195
+ // --- ssl ---
196
+ if (uri.protocol === 'https:') {
197
+ await getCa();
198
+ }
199
+ // --- cookie 托管 ---
200
+ if (opt.cookie) {
201
+ headers['cookie'] = buildCookieQuery(opt.cookie, uri);
202
+ }
203
+ // --- 发起请求 ---
204
+ let req: hc.IResponse;
205
+ try {
206
+ // --- 重定义 IP ---
207
+ const host = (puri ? puri.hostname : uri.hostname) ?? '';
208
+ if (!host) {
209
+ const res = new response.Response(null);
210
+ res.error = {
211
+ 'name': 'Possible mProxy error',
212
+ 'message': 'host not found'
213
+ };
214
+ return res;
215
+ }
216
+ if (hosts[host] !== undefined && !hosts[host]) {
217
+ const res = new response.Response(null);
218
+ res.error = {
219
+ 'name': 'hosts error',
220
+ 'message': 'hosts param error'
221
+ };
222
+ return res;
223
+ }
224
+ if (!reuses[reuse]) {
225
+ reuses[reuse] = hc.createHttpClient();
226
+ }
227
+ req = await reuses[reuse].request({
228
+ 'url': opt.mproxy ? opt.mproxy.url + (opt.mproxy.url.includes('?') ? '&' : '?') + text.queryStringify({
229
+ 'url': u,
230
+ 'auth': opt.mproxy.auth,
231
+ 'data': opt.mproxy.data ? text.stringifyJson(opt.mproxy.data) : '{}'
232
+ }) : u,
233
+ 'method': method,
234
+ 'data': data,
235
+ 'headers': headers,
236
+ 'timeout': timeout * 1000,
237
+ 'localAddress': local,
238
+ 'ca': ca,
239
+ 'connectionOptions': {
240
+ 'remoteHost': hosts[host]
241
+ },
242
+ });
243
+ }
244
+ catch (err: types.Json) {
245
+ const res = new response.Response(null);
246
+ res.error = err;
247
+ return res;
248
+ }
249
+ // --- 处理返回值 ---
250
+ // --- 是否追踪 cookie ---
251
+ if (opt.cookie) {
252
+ // --- 提取 cookie ---
253
+ await buildCookieObject(opt.cookie, (req.headers['set-cookie'] ?? []) as string[], uri);
254
+ }
255
+ // --- 直接下载到文件 ---
256
+ /** --- 已下载文件的 size --- */
257
+ let total: number = 0;
258
+ if (save && (!req.headers['location'] || follow === 0)) {
259
+ await new Promise<void>(function(resolve) {
260
+ req.getStream().on('end', () => {
261
+ fs.stats(save).then((stat) => {
262
+ total = stat?.size ?? 0;
263
+ resolve();
264
+ }).catch(() => {
265
+ resolve();
266
+ });
267
+ }).on('error', function() {
268
+ resolve();
269
+ }).pipe(fs.createWriteStream(save));
270
+ });
271
+ }
272
+ // --- 创建 Response 对象 ---
273
+ const res = new response.Response(req);
274
+ if (total) {
275
+ res.setContent(total.toString());
276
+ }
277
+ res.headers = req.headers as types.THttpHeaders;
278
+ switch (req.protocol) {
279
+ case hc.EProtocol.HTTPS_2:
280
+ case hc.EProtocol.HTTP_2: {
281
+ req.headers['http-version'] = '2.0';
282
+ break;
283
+ }
284
+ default: {
285
+ res.headers['http-version'] = '1.1';
286
+ }
287
+ }
288
+ res.headers['http-code'] = req.statusCode;
289
+ res.headers['http-url'] = u;
290
+ // --- 判断 follow 追踪 ---
291
+ if (follow === 0) {
292
+ return res;
293
+ }
294
+ if (!res.headers['location']) {
295
+ return res;
296
+ }
297
+ // --- 哦,要追踪 ---
298
+ headers['referer'] = u;
299
+ return request(text.urlResolve(u, req.headers['location'] as string), data, {
300
+ 'method': method,
301
+ 'type': type,
302
+ 'timeout': timeout,
303
+ 'follow': follow - 1,
304
+ 'hosts': hosts,
305
+ 'save': save,
306
+ 'local': local,
307
+ 'headers': headers,
308
+ 'mproxy': opt.mproxy,
309
+ 'reuse': reuse
310
+ });
311
+ }
312
+
313
+ /**
314
+ * --- 对 cookie 对象进行操作 ---
315
+ * @param cookie 要操作的对象
316
+ * @param name 名
317
+ * @param value 值
318
+ * @param domain 应用网址,如 .xxx.com
319
+ * @param opt 选项 ttl, path, ssl, httponly
320
+ */
321
+ export function setCookie(cookie: Record<string, types.ICookie>, name: string, value: string, domain: string, opt: {
322
+ 'ttl'?: number;
323
+ 'path'?: string;
324
+ 'ssl'?: boolean;
325
+ 'httponly'?: boolean;
326
+ } = {}): void {
327
+ const tim = time.stamp();
328
+ const ttl = opt.ttl ?? 0;
329
+ domain = domain.split(':')[0];
330
+ const domainN = domain.startsWith('.') ? domain.slice(1) : domain;
331
+
332
+ let exp = -1992199400;
333
+ if (ttl) {
334
+ exp = tim + ttl;
335
+ }
336
+
337
+ cookie[name + '-' + domainN] = {
338
+ 'name': name,
339
+ 'value': value,
340
+ 'exp': exp,
341
+ 'path': opt['path'] ?? '/',
342
+ 'domain': domainN,
343
+ 'secure': opt['ssl'] ? true : false,
344
+ 'httponly': opt['httponly'] ? true : false
345
+ };
346
+ }
347
+
348
+ /**
349
+ * --- 根据 Set-Cookie 头部转换到 cookie 对象 ---
350
+ * @param cookie cookie 对象
351
+ * @param setCookies 头部的 set-cookie 数组
352
+ * @param uri 请求的 URI 对象
353
+ */
354
+ async function buildCookieObject(
355
+ cookie: Record<string, types.ICookie>,
356
+ setCookies: string[],
357
+ uri: types.IUrlParse
358
+ ): Promise<void> {
359
+ const tim = time.stamp();
360
+ uri.path ??= '/';
361
+ for (const setCookie of setCookies) {
362
+ const cookieTmp: Record<string, string> = {};
363
+ const list = setCookie.split(';');
364
+ // --- 提取 set-cookie 中的定义信息 ---
365
+ for (let index = 0; index < list.length; ++index) {
366
+ const item = list[index];
367
+ const arr = item.split('=');
368
+ const key = arr[0].trim();
369
+ const val = arr[1] ?? '';
370
+ if (index === 0) {
371
+ // --- 用户定义的信息 ---
372
+ cookieTmp['name'] = key;
373
+ cookieTmp['value'] = decodeURIComponent(val);
374
+ }
375
+ else {
376
+ // --- cookie 配置信息,可转小写方便读取 ---
377
+ cookieTmp[key.toLowerCase()] = val;
378
+ }
379
+ }
380
+ // --- 获取定义的 domain ---
381
+ let domain = '', domainN = '';
382
+ if (cookieTmp['domain']) {
383
+ cookieTmp['domain'] = cookieTmp['domain'].split(':')[0];
384
+ if (!(cookieTmp['domain'].startsWith('.'))) {
385
+ domain = '.' + cookieTmp['domain'];
386
+ domainN = cookieTmp['domain'];
387
+ }
388
+ else {
389
+ domain = cookieTmp['domain'];
390
+ domainN = cookieTmp['domain'].slice(1);
391
+ }
392
+ }
393
+ else {
394
+ domain = '.' + (uri.hostname ?? '');
395
+ domainN = uri.hostname ?? '';
396
+ }
397
+ // --- 判断有没有设置 domain 的权限 ---
398
+ // --- uri.hostname vs domain(domainN) ---
399
+ // --- ok.xxx.com vs .ok.xxx.com: true ---
400
+ // --- ok.xxx.com vs .xxx.com: true ---
401
+ // --- z.ok.xxx.com vs .xxx.com: true ---
402
+ // --- ok.xxx.com vs .zz.ok.xxx.com: false ---
403
+ if (uri.hostname !== domainN) {
404
+ // --- 设置的域名和当前 host 不相等,如果是 IP、无 . 域名,则直接失败 ---
405
+ if (!text.isDomain(uri.hostname ?? '')) {
406
+ continue;
407
+ }
408
+ const parseDomain: text.IDomain = await text.parseDomain(domainN);
409
+ if (parseDomain.tld === domainN.toLowerCase()) {
410
+ // --- 不能给 tld 设置 cookie ---
411
+ continue;
412
+ }
413
+ // --- 判断访问路径 (uri['host']) 是不是设置域名 (domain) 的孩子,domain 必须是 uriHost 的同级或者父辈 ---
414
+ if (!((uri.hostname ?? '').endsWith(domain))) {
415
+ // --- false 代表进入了,代表失败 ---
416
+ // --- ok.xxx.com, .xxx.com: true ---
417
+ // --- ok.xxx.com, .ppp.com: false ---
418
+ // --- ok.xxx.com, .p.ok.xxx.com: false ---
419
+ continue;
420
+ }
421
+ }
422
+ const cookieKey = cookieTmp['name'] + '-' + domainN;
423
+ if (cookieTmp['max-age'] && (Number(cookieTmp['max-age']) <= 0)) {
424
+ if (cookie[cookieKey]) {
425
+ delete cookie[cookieKey];
426
+ continue;
427
+ }
428
+ }
429
+ let exp = -1992199400;
430
+ if (cookieTmp['max-age']) {
431
+ exp = tim + Number(cookieTmp['max-age']);
432
+ }
433
+ // --- path ---
434
+ let path = cookieTmp['path'] ?? '';
435
+ if (path === '') {
436
+ const srp = (uri.pathname ?? '').lastIndexOf('/');
437
+ path = (uri.pathname ?? '').slice(0, srp + 1);
438
+ }
439
+ else if (!path.startsWith('/')) {
440
+ path = '/' + path;
441
+ }
442
+ cookie[cookieKey] = {
443
+ 'name': cookieTmp['name'],
444
+ 'value': cookieTmp['value'],
445
+ 'exp': exp,
446
+ 'path': path,
447
+ 'domain': domainN,
448
+ 'secure': cookieTmp['secure'] !== undefined ? true : false,
449
+ 'httponly': cookieTmp['httponly'] !== undefined ? true : false
450
+ };
451
+ }
452
+ }
453
+
454
+ /**
455
+ * --- 对象转换为 Cookie 拼接字符串(会自动筛掉不能发送的 cookie) ---
456
+ * @param cookie cookie 对象
457
+ * @param uri 请求的 URI 对象
458
+ */
459
+ export function buildCookieQuery(cookie: Record<string, types.ICookie>, uri: types.IUrlParse): string {
460
+ const tim = time.stamp();
461
+ let cookieStr: string = '';
462
+ for (const key in cookie) {
463
+ const item: types.ICookie = cookie[key];
464
+ if ((item.exp < tim) && (item.exp !== -1992199400)) {
465
+ delete cookie[key];
466
+ continue;
467
+ }
468
+ uri.path ??= '/';
469
+ if (item.secure && (uri.protocol === 'https')) {
470
+ continue;
471
+ }
472
+ // --- 判断 path 是否匹配 ---
473
+ if (!uri.path.startsWith(item.path)) {
474
+ continue;
475
+ }
476
+ const domain: string = '.' + item.domain;
477
+ // --- 判断 $uri['host'] 必须是 $domain 的同级或子级 ---
478
+ // --- $uri['host'] vs $domain ---
479
+ // --- ok.xxx.com vs .ok.xxx.com: true ---
480
+ // --- ok.xxx.com vs .xxx.com: true ---
481
+ // --- z.ok.xxx.com vs .xxx.com: true ---
482
+ // --- ok.xxx.com vs .zz.ok.xxx.com: false ---
483
+ if ('.' + (uri.hostname ?? '') !== domain) {
484
+ // --- 域名不相等,那么判断当前域名 host 是不是 domain 的孩子 ---
485
+ if (!(uri.hostname!.endsWith(domain))) {
486
+ // --- false 代表进入,被排除了,因为 cookie 的 domain 和当前 host 后半部分,代表不是 domain 的孩子 ---
487
+ // --- ok.xxx.com, .zz.ok.xxx.com: false ---
488
+ // --- pp.ok.xxx.com, .zz.ok.xxx.com: false ---
489
+ // --- q.b.ok.xx.com, .zz.ok.xxx.com: false ---
490
+ // --- z.ok.xxx.com, .xxx.com: true ---
491
+ // --- xx.xxx.com, .ok.xxx.com: false ---
492
+ continue;
493
+ }
494
+ }
495
+ cookieStr += item.name + '=' + encodeURIComponent(item.value) + '; ';
496
+ }
497
+ if (cookieStr !== '') {
498
+ return cookieStr.slice(0, -2);
499
+ }
500
+ else {
501
+ return '';
502
+ }
503
+ }
504
+
505
+ /**
506
+ * --- 模拟重启浏览器后的状态 ---
507
+ * @param cookie cookie 对象
508
+ */
509
+ export function resetCookieSession(cookie: Record<string, types.ICookie>): void {
510
+ for (const key in cookie) {
511
+ const item = cookie[key];
512
+ if (item.exp === -1992199400000) {
513
+ delete cookie[key];
514
+ }
515
+ }
516
+ }
517
+
518
+ /**
519
+ * --- 创建 FormData 对象, Mutton: false, Kebab: true ---
520
+ */
521
+ export function getFormData(): fd.FormData {
522
+ return new fd.FormData();
523
+ }
524
+
525
+ /** --- proxy 要剔除的基础头部 --- */
526
+ const proxyContinueHeaders = ['host', 'connection', 'http-version', 'http-code', 'http-url'];
527
+
528
+ /**
529
+ * --- 剔除不代理的 header ---
530
+ * @param headers 剔除前的 header
531
+ * @param res 直接设置头部而不返回,可置空
532
+ */
533
+ export function filterProxyHeaders(
534
+ headers: http.IncomingHttpHeaders | http2.IncomingHttpHeaders | types.THttpHeaders,
535
+ res?: http2.Http2ServerResponse | http.ServerResponse
536
+ ): Record<string, string | string[]> {
537
+ const heads: Record<string, string | string[]> = {};
538
+ for (const h in headers) {
539
+ if (proxyContinueHeaders.includes(h)) {
540
+ continue;
541
+ }
542
+ if (h.includes(':') || h.includes('(')) {
543
+ continue;
544
+ }
545
+ const v = headers[h];
546
+ if (v === undefined) {
547
+ continue;
548
+ }
549
+ if (res) {
550
+ res.setHeader(h, v);
551
+ continue;
552
+ }
553
+ heads[h] = v;
554
+ }
555
+ return heads;
556
+ }
557
+
558
+ /**
559
+ * --- 正向 mproxy 代理,读取 get 的 url 为实际请求地址 ---
560
+ * --- get: url, auth ---
561
+ * @param ctr 当前控制器
562
+ * @param auth 校验字符串,读取 get 的 auth 和本参数做比对
563
+ * @param opt 参数
564
+ */
565
+ export async function mproxy(
566
+ ctr: ctr.Ctr,
567
+ auth: string,
568
+ opt: IMproxyOptions = {}
569
+ ): Promise<number> {
570
+ const req = ctr.getPrototype('_req');
571
+ const res = ctr.getPrototype('_res');
572
+ const input = ctr.getPrototype('_input');
573
+ /** --- 客户端请求中的 get 的数据 --- */
574
+ const get = ctr.getPrototype('_get');
575
+ if (get['auth'] !== auth) {
576
+ return 0;
577
+ }
578
+ if (!get['url']) {
579
+ return -1;
580
+ }
581
+ (opt as types.Json).method = req.method ?? 'GET';
582
+ opt.headers ??= {};
583
+ Object.assign(opt.headers, filterProxyHeaders(req.headers));
584
+ // --- 发起请求 ---
585
+ const rres = await request(get['url'], req.headers['content-type']?.includes('form-data') ? req : input, opt);
586
+ if (rres.error) {
587
+ return -2;
588
+ }
589
+ if (rres.headers) {
590
+ filterProxyHeaders(rres.headers, res);
591
+ }
592
+ res.writeHead(rres.headers?.['http-code'] ?? 200);
593
+ await new Promise<void>((resolve) => {
594
+ rres.getRawStream().pipe(res).on('finish', () => {
595
+ resolve();
596
+ });
597
+ });
598
+ return 1;
599
+ }
600
+
601
+ /**
602
+ * --- 获取 mproxy 的附加数据 ---
603
+ * @param ctr 当前控制器
604
+ */
605
+ export function mproxyData(ctr: ctr.Ctr): any {
606
+ const get = ctr.getPrototype('_get');
607
+ if (!get['data']) {
608
+ return {};
609
+ }
610
+ const data = text.parseJson(get['data']);
611
+ if (!data) {
612
+ return {};
613
+ }
614
+ return data;
615
+ }
616
+
617
+ /**
618
+ * --- 反向代理,将本服务器的某个路由反代到其他网址 ---
619
+ * @param ctr 当前控制器
620
+ * @param route 要反代的路由
621
+ * @param opt 参数
622
+ */
623
+ export async function rproxy(
624
+ ctr: ctr.Ctr,
625
+ route: Record<string, string>,
626
+ opt: IRproxyOptions = {}
627
+ ): Promise<boolean> {
628
+ const req = ctr.getPrototype('_req');
629
+ const res = ctr.getPrototype('_res');
630
+ const config = ctr.getPrototype('_config');
631
+ const input = ctr.getPrototype('_input');
632
+ const path = config.const.path + (config.const.qs ? '?' + config.const.qs : '');
633
+ for (const key in route) {
634
+ if (!path.startsWith(key)) {
635
+ continue;
636
+ }
637
+ // --- 找到了,做转发 ---
638
+ // --- key 类似:test/net-rproxy/ ---
639
+ // --- 值类似:https://cdn.jsdelivr.net/npm/deskrt@2.0.10/ ---
640
+ /** --- 要拼接的地址 --- */
641
+ const lpath = path.slice(key.length);
642
+ (opt as types.Json).method = req.method ?? 'GET';
643
+ opt.headers ??= {};
644
+ Object.assign(opt.headers, filterProxyHeaders(req.headers));
645
+ // --- 发起请求 ---
646
+ const rres = await request(route[key] + lpath, req.headers['content-type']?.includes('form-data') ? req : input, opt);
647
+ if (rres.error) {
648
+ return false;
649
+ }
650
+ if (rres.headers) {
651
+ filterProxyHeaders(rres.headers, res);
652
+ }
653
+ res.writeHead(rres.headers?.['http-code'] ?? 200);
654
+ await new Promise<void>((resolve) => {
655
+ rres.getRawStream().pipe(res).on('finish', () => {
656
+ resolve();
657
+ });
658
+ });
659
+ return true;
660
+ }
661
+ return false;
662
+ }