二七王:补齐客户端牌id入参校验(重复/越界/非数组)

check_cards_inhand 此前只逐个校验牌id是否在手上,不查重复、不查
类型与范围,且在牌已出/已埋时 return 空值而非 false,造成两个可
被利用的缺陷:

1. 重复牌id 可伪造牌型:do_playcard([方块K,方块K]) 实测返回
   result=true、cardtype=201(同一张牌被判成一对),可进而伪造
   拖拉机与甩牌分量,本轮结算时该分牌的 score 还会被重复累加。
2. 埋牌传 8 个相同 id 实测只埋下 1 张,庄家带 35 张进入出牌阶段,
   整局牌数错乱。
3. 越界 id 或非数组入参会在解引用处抛异常,中断 DoPack。

新增 check_cards_valid 作为唯一入参校验入口(非空数组、元素为本
局牌表内的整数牌id、互不重复),由 check_cards_inhand 先行调用;
mod.maipai/mod.chupai 在读取 cards.length 之前先过该校验。按
engineering §03「显式失败优于隐式兜底」直接拒绝,不做类型兜底。

新增 test_input.js 28 项正/反/边界用例回归上述三点。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 04:10:15 +08:00
co-authored by Claude Opus 5
parent 391dffc04a
commit a4a8eb8408
3 changed files with 139 additions and 5 deletions
+38 -3
View File
@@ -201,10 +201,15 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
cls_youle_erqiwang_paiju.do_choiceflower(paiju, flower);
}
//校验客户端提交的牌id列表本身是否合法(数组/整数/范围/无重复)
paiju.method.check_cards_valid = function(cards){
return cls_youle_erqiwang_paiju.check_cards_valid(paiju, cards);
}
//判断牌是否在玩家手上
paiju.method.check_cards_inhand = function(cards, seat){
return cls_youle_erqiwang_paiju.check_cards_inhand(paiju, cards, seat);
}
}
//埋牌
paiju.method.do_burycard = function(cards){
@@ -449,12 +454,42 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
o_paiju.step = 3;
},
//判断牌是否在玩家手上
//校验客户端提交的牌id列表本身是否合法:必须是非空数组、元素为本局牌表内的整数牌id、且互不重复
//(客户端入参不可信:重复id 会被 can_playcard 当成对子/拖拉机,也会让埋牌少埋;越界id 会取到
// undefined 而在后续解引用时抛异常中断 DoPack。此处显式失败,不做隐式兜底)
check_cards_valid: function(o_paiju, cards){
if (Object.prototype.toString.call(cards) != "[object Array]"){
return false;
}
if (cards.length <= 0){
return false;
}
var _seen = {};
for (var i = 0; i < cards.length; i++){
var _id = cards[i];
//必须是落在本局牌表范围内的整数牌id
if (typeof _id != "number" || _id != parseInt(_id) || _id < 0 || _id >= o_paiju.cards.length){
return false;
}
//同一张牌不得重复提交
if (_seen[_id]){
return false;
}
_seen[_id] = 1;
}
return true;
},
//判断牌是否在玩家手上(入参合法性由 check_cards_valid 一并把关,本函数是唯一的持牌校验入口)
check_cards_inhand: function(o_paiju, cards, seat){
if (!cls_youle_erqiwang_paiju.check_cards_valid(o_paiju, cards)){
return false;
}
for (var i = 0; i < cards.length; i++) {
var o_card = o_paiju.cards[cards[i]];
if (o_card.playround != -1){
return;
//已埋下或已打出的牌
return false;
}
if (seat == o_paiju.banker){
//是庄家