jsql-neo 6.0.0 → 6.1.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/README.md +164 -58
- package/lib/native_client.js +69 -8
- package/lib/pg_server.js +7 -1
- package/lib/redis_server.js +58 -28
- package/lib/sql.js +1018 -84
- package/lib/wasm_client.js +58 -8
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-native/Cargo.lock +2 -2
- package/package.json +4 -3
- package/test/readme-audit.test.js +353 -0
package/lib/redis_server.js
CHANGED
|
@@ -470,52 +470,81 @@ class RedisServer {
|
|
|
470
470
|
|
|
471
471
|
/** 供多协议嗅探复用:把 socket 交给 Redis 处理,可携带已缓冲的首包 */
|
|
472
472
|
_handleSocket(socket, existing = '') {
|
|
473
|
-
|
|
473
|
+
// 刻意不 setEncoding('utf8'):RESP 的 $N 是**字节**长度,
|
|
474
|
+
// 转成字符串再按字符切片会把多字节 UTF-8(中文等)截断。
|
|
475
|
+
let buf = Buffer.isBuffer(existing) ? existing : Buffer.from(String(existing || ''), 'utf8');
|
|
474
476
|
this._authedSockets.set(socket, false);
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
buf += chunk;
|
|
477
|
+
|
|
478
|
+
const drain = () => {
|
|
478
479
|
for (;;) {
|
|
479
|
-
|
|
480
|
+
let msg;
|
|
481
|
+
try {
|
|
482
|
+
msg = this._parse(buf);
|
|
483
|
+
} catch (e) {
|
|
484
|
+
// 协议错误:回复错误并丢弃缓冲,避免一直卡在同一段坏数据上
|
|
485
|
+
this._send(socket, (e instanceof Error) ? e : new Error('ERR Protocol error'));
|
|
486
|
+
buf = Buffer.alloc(0);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
480
489
|
if (!msg) break;
|
|
481
490
|
buf = buf.slice(msg.consumed);
|
|
491
|
+
if (msg.ignore) continue;
|
|
482
492
|
this._handle(socket, msg.cmd, msg.args);
|
|
483
493
|
}
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
socket.on('data', (chunk) => {
|
|
497
|
+
buf = Buffer.concat([buf, Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), 'utf8')]);
|
|
498
|
+
drain();
|
|
484
499
|
});
|
|
485
500
|
socket.on('error', () => {});
|
|
486
|
-
|
|
487
|
-
const msg = this._parse(buf);
|
|
488
|
-
if (!msg) break;
|
|
489
|
-
buf = buf.slice(msg.consumed);
|
|
490
|
-
this._handle(socket, msg.cmd, msg.args);
|
|
491
|
-
}
|
|
501
|
+
drain();
|
|
492
502
|
}
|
|
493
503
|
|
|
504
|
+
/**
|
|
505
|
+
* 解析一条 RESP2 请求,返回 { consumed, cmd, args } 或 null(数据不完整)。
|
|
506
|
+
* buf 是 Buffer —— 所有长度计算都按**字节**进行。
|
|
507
|
+
*/
|
|
494
508
|
_parse(buf) {
|
|
495
|
-
if (buf
|
|
496
|
-
|
|
509
|
+
if (!buf || buf.length === 0) return null;
|
|
510
|
+
const crlf = (from) => buf.indexOf('\r\n', from, 'utf8');
|
|
511
|
+
|
|
512
|
+
// 非 '*' 开头:inline 命令(telnet / nc 手工输入)
|
|
513
|
+
if (buf[0] !== 0x2a) {
|
|
514
|
+
const i = crlf(0);
|
|
497
515
|
if (i < 0) return null;
|
|
498
|
-
const
|
|
499
|
-
if (!
|
|
500
|
-
|
|
516
|
+
const line = buf.slice(0, i).toString('utf8').trim();
|
|
517
|
+
if (!line) return { consumed: i + 2, ignore: true };
|
|
518
|
+
const parts = line.split(/\s+/);
|
|
519
|
+
// 之前这里返回的是数组(cmd.map(...)),导致 switch(cmd) 永远匹配不上,
|
|
520
|
+
// inline 输入一律被当成 unknown command。必须是字符串。
|
|
521
|
+
const cmd = parts[0].toUpperCase();
|
|
522
|
+
return { consumed: i + 2, cmd, args: parts.slice(1) };
|
|
501
523
|
}
|
|
502
|
-
|
|
524
|
+
|
|
525
|
+
const i = crlf(0);
|
|
503
526
|
if (i < 0) return null;
|
|
504
|
-
const n = parseInt(buf.slice(1, i), 10);
|
|
505
|
-
if (isNaN(n)) throw new Error('ERR Protocol error');
|
|
527
|
+
const n = parseInt(buf.slice(1, i).toString('utf8'), 10);
|
|
528
|
+
if (isNaN(n)) throw new Error('ERR Protocol error: invalid multibulk length');
|
|
529
|
+
// *0(空命令)与 *-1(null 数组)此前会走到 parts[0].toUpperCase() 崩溃
|
|
530
|
+
if (n <= 0) return { consumed: i + 2, ignore: true };
|
|
531
|
+
|
|
506
532
|
let off = i + 2;
|
|
507
533
|
const parts = [];
|
|
508
534
|
for (let k = 0; k < n; k++) {
|
|
509
|
-
if (buf
|
|
510
|
-
|
|
535
|
+
if (buf.length <= off) return null;
|
|
536
|
+
if (buf[off] !== 0x24) throw new Error('ERR Protocol error: expected bulk string'); // '$'
|
|
537
|
+
const j = crlf(off);
|
|
511
538
|
if (j < 0) return null;
|
|
512
|
-
const len = parseInt(buf.slice(off + 1, j), 10);
|
|
513
|
-
if (isNaN(len)) throw new Error('ERR Protocol error');
|
|
514
|
-
|
|
515
|
-
parts.push(
|
|
516
|
-
|
|
539
|
+
const len = parseInt(buf.slice(off + 1, j).toString('utf8'), 10);
|
|
540
|
+
if (isNaN(len)) throw new Error('ERR Protocol error: invalid bulk length');
|
|
541
|
+
const start = j + 2;
|
|
542
|
+
if (len === -1) { parts.push(null); off = start; continue; } // $-1 null bulk
|
|
543
|
+
if (buf.length < start + len + 2) return null; // 按字节判断
|
|
544
|
+
parts.push(buf.slice(start, start + len).toString('utf8'));
|
|
545
|
+
off = start + len + 2;
|
|
517
546
|
}
|
|
518
|
-
const cmd = parts[0].toUpperCase();
|
|
547
|
+
const cmd = String(parts[0] || '').toUpperCase();
|
|
519
548
|
return { consumed: off, cmd, args: parts.slice(1) };
|
|
520
549
|
}
|
|
521
550
|
|
|
@@ -550,7 +579,8 @@ class RedisServer {
|
|
|
550
579
|
} else if (typeof val === 'number') {
|
|
551
580
|
out = ':' + val + '\r\n';
|
|
552
581
|
} else if (Array.isArray(val)) {
|
|
553
|
-
|
|
582
|
+
// 长度用字节数,不能用 String.length —— 否则中文等多字节值会让客户端解帧错位
|
|
583
|
+
out = '*' + val.length + '\r\n' + val.map(v => '$' + Buffer.byteLength(String(v), 'utf8') + '\r\n' + v + '\r\n').join('');
|
|
554
584
|
} else if (typeof val === 'string') {
|
|
555
585
|
if (val === 'OK' || val === 'PONG') {
|
|
556
586
|
out = '+' + val + '\r\n';
|