@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/lib/undici.js
CHANGED
|
@@ -453,7 +453,17 @@ export async function request(u, data, opt = {}) {
|
|
|
453
453
|
return res;
|
|
454
454
|
}
|
|
455
455
|
// --- 哦,要追踪 ---
|
|
456
|
-
headers['
|
|
456
|
+
const nextUrl = lText.urlResolve(u, req.headers['location']);
|
|
457
|
+
if (lText.isSameOrigin(u, nextUrl)) {
|
|
458
|
+
headers['referer'] = u;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
// --- 跨域跳转不得携带来源站点凭据 ---
|
|
462
|
+
delete headers['authorization'];
|
|
463
|
+
delete headers['proxy-authorization'];
|
|
464
|
+
delete headers['cookie'];
|
|
465
|
+
delete headers['referer'];
|
|
466
|
+
}
|
|
457
467
|
let nextMethod = method;
|
|
458
468
|
let nextData = data;
|
|
459
469
|
const status = res.headers['http-code'];
|
|
@@ -461,7 +471,12 @@ export async function request(u, data, opt = {}) {
|
|
|
461
471
|
nextMethod = 'GET';
|
|
462
472
|
nextData = undefined;
|
|
463
473
|
}
|
|
464
|
-
|
|
474
|
+
if (nextData instanceof stream.Readable) {
|
|
475
|
+
return res;
|
|
476
|
+
}
|
|
477
|
+
// --- 释放原响应体,避免跟随多次跳转时占用连接 ---
|
|
478
|
+
await req.body.dump();
|
|
479
|
+
return request(nextUrl, nextData, {
|
|
465
480
|
...opt,
|
|
466
481
|
...{
|
|
467
482
|
'method': nextMethod,
|
package/package.json
CHANGED
package/sys/child.js
CHANGED
|
@@ -243,14 +243,6 @@ async function requestHandler(req, res, https) {
|
|
|
243
243
|
// --- 不能用 req.socket.destroy() 可能会导致底层复用(如 CDN) 的连接被直接断开 ---
|
|
244
244
|
res.end('403 Forbidden');
|
|
245
245
|
return;
|
|
246
|
-
/*
|
|
247
|
-
const text = '<h1>Kebab: No permissions</h1>host: ' + (req.headers[':authority'] as string | undefined ?? req.headers['host'] ?? '') + '<br>url: ' + (lText.htmlescape(req.url ?? ''));
|
|
248
|
-
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
249
|
-
res.setHeader('content-length', Buffer.byteLength(text));
|
|
250
|
-
res.writeHead(403);
|
|
251
|
-
res.end(text);
|
|
252
|
-
return;
|
|
253
|
-
*/
|
|
254
246
|
}
|
|
255
247
|
/** --- 请求的路径部分,前导带 / 末尾不一定,用户怎么请求就是什么 --- */
|
|
256
248
|
let path = uri.pathname ?? '/';
|
package/sys/ctr.d.ts
CHANGED
|
@@ -344,6 +344,12 @@ export declare class Ctr {
|
|
|
344
344
|
'allowedExts'?: string[];
|
|
345
345
|
/** --- 单个字段(非文件)最大字节数,默认 1 MB --- */
|
|
346
346
|
'maxFieldSize'?: number;
|
|
347
|
+
/** --- 整体请求最大字节数,不设置或设为 0 则不限制 --- */
|
|
348
|
+
'maxTotalSize'?: number;
|
|
349
|
+
/** --- 单个 multipart 段头部最大字节数,默认 16 KB --- */
|
|
350
|
+
'maxHeaderSize'?: number;
|
|
351
|
+
/** --- multipart 字段与文件总数量,默认 1000 --- */
|
|
352
|
+
'maxParts'?: number;
|
|
347
353
|
/** --- 整体请求超时时间(毫秒),默认 5 分钟,设为 0 禁用超时 --- */
|
|
348
354
|
'timeout'?: number;
|
|
349
355
|
}): Promise<boolean>;
|
package/sys/master.js
CHANGED
|
@@ -7,6 +7,7 @@ import cluster from 'cluster';
|
|
|
7
7
|
import * as os from 'os';
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as http from 'http';
|
|
10
|
+
import * as path from 'path';
|
|
10
11
|
// --- 库和定义 ---
|
|
11
12
|
import * as kebab from '#kebab/index.js';
|
|
12
13
|
import * as sRoute from '#kebab/sys/route.js';
|
|
@@ -18,6 +19,70 @@ import * as lTime from '#kebab/lib/time.js';
|
|
|
18
19
|
import * as lZip from '#kebab/lib/zip.js';
|
|
19
20
|
/** --- 当前运行中的子进程列表 --- */
|
|
20
21
|
const workerList = {};
|
|
22
|
+
/**
|
|
23
|
+
* --- 判断候选路径是否位于指定根目录内 ---
|
|
24
|
+
* @param rootPath 已解析的真实根目录
|
|
25
|
+
* @param candidate 候选绝对路径
|
|
26
|
+
*/
|
|
27
|
+
function isPathInside(rootPath, candidate) {
|
|
28
|
+
const relative = path.relative(rootPath, candidate);
|
|
29
|
+
return (relative === '') || (!relative.startsWith(`..${path.sep}`) && (relative !== '..') && !path.isAbsolute(relative));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* --- 解析根目录内的路径,并阻止通过绝对路径、.. 或符号链接逃逸 ---
|
|
33
|
+
* @param rootPath 限定的根目录
|
|
34
|
+
* @param inputPath 用户输入的相对路径
|
|
35
|
+
*/
|
|
36
|
+
async function resolvePathInside(rootPath, inputPath) {
|
|
37
|
+
if ((typeof inputPath !== 'string') || inputPath.includes('\0')) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const rootReal = await fs.promises.realpath(rootPath);
|
|
41
|
+
const normalizedInput = inputPath.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
42
|
+
if (/^[a-zA-Z]:/.test(normalizedInput)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const candidate = path.resolve(rootReal, normalizedInput);
|
|
46
|
+
if (!isPathInside(rootReal, candidate)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/** --- 向上找到已存在的最近父路径,用 realpath 校验其中的符号链接 --- */
|
|
50
|
+
let existingPath = candidate;
|
|
51
|
+
const missingParts = [];
|
|
52
|
+
while (true) {
|
|
53
|
+
try {
|
|
54
|
+
const existingReal = await fs.promises.realpath(existingPath);
|
|
55
|
+
const resolved = path.resolve(existingReal, ...missingParts);
|
|
56
|
+
return isPathInside(rootReal, resolved) ? resolved : null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
const parent = path.dirname(existingPath);
|
|
60
|
+
if (parent === existingPath) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
missingParts.unshift(path.basename(existingPath));
|
|
64
|
+
existingPath = parent;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* --- 规范化 ZIP 条目路径,返回带前导斜杠的安全相对路径 ---
|
|
70
|
+
* @param archivePath ZIP 内的原始路径
|
|
71
|
+
*/
|
|
72
|
+
function normalizeArchivePath(archivePath) {
|
|
73
|
+
const normalizedSlashes = archivePath.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
74
|
+
if (!normalizedSlashes ||
|
|
75
|
+
normalizedSlashes.includes('\0') ||
|
|
76
|
+
/^[a-zA-Z]:/.test(normalizedSlashes) ||
|
|
77
|
+
normalizedSlashes.split('/').some(part => !part || (part === '.') || (part === '..'))) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const normalized = path.posix.normalize(normalizedSlashes);
|
|
81
|
+
if (normalized.startsWith('../') || (normalized === '..') || path.posix.isAbsolute(normalized)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return `/${normalized}`;
|
|
85
|
+
}
|
|
21
86
|
/**
|
|
22
87
|
* --- 等待指定 worker 开始监听端口就绪 ---
|
|
23
88
|
* @param worker 要等待的 worker 对象
|
|
@@ -128,7 +193,7 @@ function createRpcListener() {
|
|
|
128
193
|
res.end('Failed');
|
|
129
194
|
return;
|
|
130
195
|
}
|
|
131
|
-
if (msg.time
|
|
196
|
+
if ((typeof msg.time !== 'number') || (Math.abs(msg.time - time) > 5)) {
|
|
132
197
|
res.end('Timeout');
|
|
133
198
|
return;
|
|
134
199
|
}
|
|
@@ -226,28 +291,16 @@ function createRpcListener() {
|
|
|
226
291
|
res.end('Invalid staticVer');
|
|
227
292
|
return;
|
|
228
293
|
}
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
path = path.slice(1);
|
|
232
|
-
}
|
|
233
|
-
if (path.endsWith('/')) {
|
|
234
|
-
path = path.slice(0, -1);
|
|
235
|
-
}
|
|
236
|
-
// --- 拒绝路径穿越,防止跳出 ROOT_CWD ---
|
|
237
|
-
if (path.includes('..')) {
|
|
294
|
+
const to = await resolvePathInside(kebab.ROOT_CWD, msg.path);
|
|
295
|
+
if (!to) {
|
|
238
296
|
res.end('Invalid path');
|
|
239
297
|
return;
|
|
240
298
|
}
|
|
241
|
-
/** --- 最终的项目根目录,以 / 结尾,但用户传入的无所谓 --- */
|
|
242
|
-
let to = kebab.ROOT_CWD + path;
|
|
243
|
-
if (!to.endsWith('/')) {
|
|
244
|
-
to += '/';
|
|
245
|
-
}
|
|
246
299
|
if (!await lFs.isDir(to)) {
|
|
247
300
|
res.end('[project] Path not found: ' + to);
|
|
248
301
|
return;
|
|
249
302
|
}
|
|
250
|
-
const projectFile = to
|
|
303
|
+
const projectFile = path.join(to, 'kebab.json');
|
|
251
304
|
if (!await lFs.isFile(projectFile)) {
|
|
252
305
|
res.end('kebab.json not found in project path');
|
|
253
306
|
return;
|
|
@@ -303,18 +356,20 @@ function createRpcListener() {
|
|
|
303
356
|
await sRoute.unlinkUploadFiles(rtn.files);
|
|
304
357
|
return;
|
|
305
358
|
}
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
path = path.slice(0, -1);
|
|
359
|
+
const inputPath = rtn.post['path'];
|
|
360
|
+
if (typeof inputPath !== 'string') {
|
|
361
|
+
res.end('Invalid path');
|
|
362
|
+
await sRoute.unlinkUploadFiles(rtn.files);
|
|
363
|
+
return;
|
|
312
364
|
}
|
|
313
|
-
/** ---
|
|
314
|
-
|
|
315
|
-
if (!
|
|
316
|
-
|
|
365
|
+
/** --- 最终更新的根目录 --- */
|
|
366
|
+
const targetPath = await resolvePathInside(kebab.ROOT_CWD, inputPath);
|
|
367
|
+
if (!targetPath) {
|
|
368
|
+
res.end('Invalid path');
|
|
369
|
+
await sRoute.unlinkUploadFiles(rtn.files);
|
|
370
|
+
return;
|
|
317
371
|
}
|
|
372
|
+
const to = targetPath + path.sep;
|
|
318
373
|
if (!await lFs.isDir(to)) {
|
|
319
374
|
if (rtn.post['strict'] === '1') {
|
|
320
375
|
res.end(`[code][0] [${rtn.post['strict']}] Path not found: ${to}`);
|
|
@@ -335,7 +390,28 @@ function createRpcListener() {
|
|
|
335
390
|
await sRoute.unlinkUploadFiles(rtn.files);
|
|
336
391
|
return;
|
|
337
392
|
}
|
|
338
|
-
const
|
|
393
|
+
const archiveEntries = zip.readDir('/', {
|
|
394
|
+
'hasChildren': true,
|
|
395
|
+
'hasDir': false,
|
|
396
|
+
});
|
|
397
|
+
const archiveSize = archiveEntries.reduce((total, item) => total + item.uncompressedSize, 0);
|
|
398
|
+
if ((archiveEntries.length > 10_000) || (archiveSize > 512 * 1024 * 1024)) {
|
|
399
|
+
res.end('Archive limit exceeded');
|
|
400
|
+
await sRoute.unlinkUploadFiles(rtn.files);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const rawList = await zip.getList();
|
|
404
|
+
/** --- 经过路径校验并统一为带前导斜杠的 ZIP 文件列表 --- */
|
|
405
|
+
const ls = {};
|
|
406
|
+
for (const archivePath in rawList) {
|
|
407
|
+
const normalizedPath = normalizeArchivePath(archivePath);
|
|
408
|
+
if (!normalizedPath || (ls[normalizedPath] !== undefined)) {
|
|
409
|
+
res.end('Invalid archive path');
|
|
410
|
+
await sRoute.unlinkUploadFiles(rtn.files);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
ls[normalizedPath] = rawList[archivePath];
|
|
414
|
+
}
|
|
339
415
|
// --- 预扫描:收集 .cga 锁定目录和 kebab 子项目目录 ---
|
|
340
416
|
/** --- .cga 锁定的目录集合,key 格式为"父路径/目录名/"(不含开头/,含尾部/),例如 "www/pika/" --- */
|
|
341
417
|
const cgaLockedDirs = new Set();
|
|
@@ -401,9 +477,9 @@ function createRpcListener() {
|
|
|
401
477
|
'tmp',
|
|
402
478
|
'temp',
|
|
403
479
|
]);
|
|
404
|
-
for (const
|
|
480
|
+
for (const archivePath in ls) {
|
|
405
481
|
/** --- 带 / 开头的 zip 中文件完整路径,例如 "/www/pika/ctr/api.js" --- */
|
|
406
|
-
const fpath =
|
|
482
|
+
const fpath = archivePath.startsWith('/') ? archivePath : '/' + archivePath;
|
|
407
483
|
/** --- 纯路径中最后一个 / 的位置索引 --- */
|
|
408
484
|
const lio = fpath.lastIndexOf('/');
|
|
409
485
|
/** --- 纯路径,不以 / 开头,以 / 结尾,若是根路径就是空字符串,例如 "www/pika/ctr/" --- */
|
|
@@ -473,21 +549,33 @@ function createRpcListener() {
|
|
|
473
549
|
}
|
|
474
550
|
}
|
|
475
551
|
// --- 看文件夹是否存在 ---
|
|
476
|
-
|
|
552
|
+
const targetDir = await resolvePathInside(to, pat);
|
|
553
|
+
if (!targetDir) {
|
|
554
|
+
res.end('Invalid archive directory');
|
|
555
|
+
await sRoute.unlinkUploadFiles(rtn.files);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
if (pat && !await lFs.isDir(targetDir)) {
|
|
477
559
|
if (rtn.post['strict'] === '1') {
|
|
478
|
-
res.end(`[code][1] [${rtn.post['strict']}] Path not found: ${
|
|
560
|
+
res.end(`[code][1] [${rtn.post['strict']}] Path not found: ${targetDir}`);
|
|
479
561
|
await sRoute.unlinkUploadFiles(rtn.files);
|
|
480
562
|
return;
|
|
481
563
|
}
|
|
482
|
-
await lFs.mkdir(
|
|
564
|
+
await lFs.mkdir(targetDir);
|
|
565
|
+
}
|
|
566
|
+
const targetFile = await resolvePathInside(to, pat + fname);
|
|
567
|
+
if (!targetFile) {
|
|
568
|
+
res.end('Invalid archive file');
|
|
569
|
+
await sRoute.unlinkUploadFiles(rtn.files);
|
|
570
|
+
return;
|
|
483
571
|
}
|
|
484
572
|
// --- 覆盖或创建文件 ---
|
|
485
|
-
if ((rtn.post['strict'] === '1') && !await lFs.isFile(
|
|
486
|
-
res.end(`[code][2] [${rtn.post['strict']}] Path not found: ${
|
|
573
|
+
if ((rtn.post['strict'] === '1') && !await lFs.isFile(targetFile)) {
|
|
574
|
+
res.end(`[code][2] [${rtn.post['strict']}] Path not found: ${targetFile}`);
|
|
487
575
|
await sRoute.unlinkUploadFiles(rtn.files);
|
|
488
576
|
return;
|
|
489
577
|
}
|
|
490
|
-
await lFs.putContent(
|
|
578
|
+
await lFs.putContent(targetFile, ls[archivePath]);
|
|
491
579
|
}
|
|
492
580
|
await sRoute.unlinkUploadFiles(rtn.files);
|
|
493
581
|
// --- 检查是否更新 config ---
|
|
@@ -529,25 +617,47 @@ function createRpcListener() {
|
|
|
529
617
|
// --- 获取日志信息 ---
|
|
530
618
|
const format = lCore.globalConfig.logFormat ?? 'jsonl';
|
|
531
619
|
const ext = format === 'jsonl' ? '.jsonl' : '.csv';
|
|
532
|
-
const
|
|
533
|
-
if (
|
|
620
|
+
const fend = msg.fend ?? '';
|
|
621
|
+
if ((typeof msg.hostname !== 'string') ||
|
|
622
|
+
(typeof msg.path !== 'string') ||
|
|
623
|
+
!/^[\w.-]+$/.test(msg.hostname) ||
|
|
624
|
+
!/^(?:|-[\w-]+)$/.test(fend) ||
|
|
625
|
+
!/^[\w./-]+$/.test(msg.path)) {
|
|
626
|
+
res.end('Invalid log path');
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const logPath = await resolvePathInside(kebab.LOG_CWD, `${msg.hostname}${fend}/${msg.path}${ext}`);
|
|
630
|
+
if (!logPath) {
|
|
631
|
+
res.end('Invalid log path');
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (!await lFs.isFile(logPath)) {
|
|
534
635
|
res.end(lText.stringifyJson({
|
|
535
636
|
'result': 1,
|
|
536
637
|
'data': null,
|
|
537
638
|
}));
|
|
538
639
|
return;
|
|
539
640
|
}
|
|
540
|
-
let limit = msg.limit
|
|
541
|
-
const offset = msg.offset
|
|
641
|
+
let limit = Number.isSafeInteger(msg.limit) ? msg.limit : 100;
|
|
642
|
+
const offset = Number.isSafeInteger(msg.offset) ? msg.offset : 0;
|
|
643
|
+
limit = Math.min(Math.max(limit, 1), 1000);
|
|
644
|
+
if (offset < 0) {
|
|
645
|
+
res.end('Invalid offset');
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
542
648
|
let total = 0;
|
|
543
649
|
if (msg.search) {
|
|
650
|
+
if ((typeof msg.search !== 'string') || (msg.search.length > 500) || msg.search.includes('\0')) {
|
|
651
|
+
res.end('Invalid search');
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
544
654
|
// === 搜索模式:total 为匹配行数,offset/limit 均基于匹配行 ===
|
|
545
655
|
// --- shell 单引号安全转义(防止特殊字符破坏命令)---
|
|
546
656
|
const escaped = msg.search.replace(/'/g, "'\\''");
|
|
547
657
|
// --- 获取匹配行总数(grep -c 无匹配时退出码 1,|| echo 0 兜底)---
|
|
548
658
|
const countCmd = format === 'csv'
|
|
549
|
-
? `tail -n +2 "${
|
|
550
|
-
: `grep -F -c '${escaped}' "${
|
|
659
|
+
? `tail -n +2 "${logPath}" | grep -F -c '${escaped}' || echo 0`
|
|
660
|
+
: `grep -F -c '${escaped}' "${logPath}" || echo 0`;
|
|
551
661
|
const countRtn = await lCore.exec(countCmd);
|
|
552
662
|
if (countRtn !== false) {
|
|
553
663
|
total = parseInt(countRtn.trim()) || 0;
|
|
@@ -556,8 +666,8 @@ function createRpcListener() {
|
|
|
556
666
|
const from = offset + 1;
|
|
557
667
|
const to = offset + limit;
|
|
558
668
|
const dataCmd = format === 'csv'
|
|
559
|
-
? `tail -n +2 "${
|
|
560
|
-
: `grep -F '${escaped}' "${
|
|
669
|
+
? `tail -n +2 "${logPath}" | grep -F '${escaped}' | sed -n '${from},${to}p'`
|
|
670
|
+
: `grep -F '${escaped}' "${logPath}" | sed -n '${from},${to}p'`;
|
|
561
671
|
const dataRtn = await lCore.exec(dataCmd);
|
|
562
672
|
if (dataRtn === false) {
|
|
563
673
|
res.end(lText.stringifyJson({
|
|
@@ -619,7 +729,7 @@ function createRpcListener() {
|
|
|
619
729
|
return;
|
|
620
730
|
}
|
|
621
731
|
// === 无搜索模式:wc -l 获取总行数,grep -b '^' 定位字节偏移直接跳至 offset ===
|
|
622
|
-
const wclRtn = await lCore.exec(`wc -l "${
|
|
732
|
+
const wclRtn = await lCore.exec(`wc -l "${logPath}"`);
|
|
623
733
|
if (wclRtn !== false) {
|
|
624
734
|
const wclMatch = /^\s*(\d+)/.exec(wclRtn);
|
|
625
735
|
if (wclMatch) {
|
|
@@ -641,7 +751,7 @@ function createRpcListener() {
|
|
|
641
751
|
if (offset > 0) {
|
|
642
752
|
const skipLines = format === 'jsonl' ? offset : offset + 1;
|
|
643
753
|
// --- sed -n '${N}{p;q}' 找到第 N 行后立即退出,使 grep 收到 SIGPIPE 提前终止 ---
|
|
644
|
-
const grepRtn = await lCore.exec(`grep -b '^' "${
|
|
754
|
+
const grepRtn = await lCore.exec(`grep -b '^' "${logPath}" | sed -n '${skipLines + 1}{p;q}'`);
|
|
645
755
|
if (grepRtn !== false) {
|
|
646
756
|
const grepMatch = /^(\d+):/.exec(grepRtn.trim());
|
|
647
757
|
if (grepMatch) {
|
|
@@ -671,7 +781,7 @@ function createRpcListener() {
|
|
|
671
781
|
let line = 0;
|
|
672
782
|
/** --- 当前行数据 --- */
|
|
673
783
|
let packet = '';
|
|
674
|
-
lFs.createReadStream(
|
|
784
|
+
lFs.createReadStream(logPath, {
|
|
675
785
|
'encoding': 'utf8',
|
|
676
786
|
'start': startByte,
|
|
677
787
|
}).on('data', buf => {
|
|
@@ -759,10 +869,10 @@ function createRpcListener() {
|
|
|
759
869
|
res.end('Invalid path');
|
|
760
870
|
return;
|
|
761
871
|
}
|
|
762
|
-
const
|
|
872
|
+
const targetPath = lText.urlResolve(kebab.ROOT_CWD, msg.path, true);
|
|
763
873
|
res.end(lText.stringifyJson({
|
|
764
874
|
'result': 1,
|
|
765
|
-
'data': (await lFs.readDir(
|
|
875
|
+
'data': (await lFs.readDir(targetPath, msg.encoding)).map(item => ({
|
|
766
876
|
'isFile': item.isFile(),
|
|
767
877
|
'isDirectory': item.isDirectory(),
|
|
768
878
|
'isSymbolicLink': item.isSymbolicLink(),
|
package/sys/route.d.ts
CHANGED
|
@@ -79,6 +79,12 @@ export declare function getFormData(req: http2.Http2ServerRequest | http.Incomin
|
|
|
79
79
|
'allowedExts'?: string[];
|
|
80
80
|
/** --- 单个字段(非文件)最大字节数,默认 1 MB --- */
|
|
81
81
|
'maxFieldSize'?: number;
|
|
82
|
+
/** --- 整体请求最大字节数,不设置或设为 0 则不限制 --- */
|
|
83
|
+
'maxTotalSize'?: number;
|
|
84
|
+
/** --- 单个 multipart 段头部最大字节数,默认 16 KB --- */
|
|
85
|
+
'maxHeaderSize'?: number;
|
|
86
|
+
/** --- multipart 字段与文件总数量,默认 1000 --- */
|
|
87
|
+
'maxParts'?: number;
|
|
82
88
|
/** --- 整体请求超时时间(毫秒),默认 5 分钟,设为 0 禁用超时 --- */
|
|
83
89
|
'timeout'?: number;
|
|
84
90
|
}): Promise<{
|