feat: M6.44+M6.45 收编 — 真机 0.8.45 现场保全(reader章节/key_router按键路由/WiFi连接修复/ncm bench+IME修复)
脏工作区现场收编:devdocs/local/m6_44_45_dirty_tracked.patch(46 个已跟踪文本文件,
+3090/-585, 已对 M6.43 基线干净适配) + m6_44_45_untracked_src.tar.gz 中 patch_v2/ 下
13 个新增源文件, 合成单个提交, 内容与容器工作树(R3ProII/patch_v2)逐字节一致。
- reader 章节: 新增 core/rdchapter.{h,c} — TXT 章节规范解析器, 扫描 512KB 码位对齐段文件
逐段流式识别标题行(行首缩进≤4/显示宽≤40/行内标点≤1 三重约束防误判), 输出标题+全书字符
偏移+段号/段内偏移+阿拉伯章号(中文数字归一), 首章前内容归"卷首"; book/reader/rdenc/
rdlayout/settings 接入章节跳转与进度锚点, 附 chapter_smoke/turn_repro/build_turn_repro.sh
- key_router 按键路由: 新增 meow_launcherd/key_router.{h,c} — launcher-UI 页物理键统一路由
(P1 仅音量键), 把读取边沿+防抖+长按 held+ramp 推进从 5 个会话收敛为进程内单开;
各页每圈 key_router_pump() 取防抖后边沿, 动作(调音量/光标导航)仍由调用页决定,
保留 run_menu 的 meow_mode 分支; App 前台 volume_page 武装时边沿抑制;
main_menu/now_playing/notif_center/settings_page/subpage 接入, 附 test_key_router_vol/test_np_keys
- WiFi 连接修复: meow_netmgrd connect 有界等待 wpa_state=COMPLETED(普通 10s/隐藏网络 15s,
env 可调)修设置页连接无响应; list_networks/scan_results 的 \xNN 转义还原始字节, 修中文 SSID
永不命中致新建同名 open 记录顶影; launcher netmgr_ipc 配读超时; 附 test_wifi_connect(桩
wpa_cli 驱动 static wifi_connect_ex), test_netmgrd.sh 补用例; wifi_page 同步
- ncm bench: 新增 bench/lyric_sim.c、bench/plpage_sim.c, vol_sync_sim 与 ncm core/app 同步
- SDK IME 修复: shift 大小写、K_ENTER 只提交不关、阻塞 IME 循环喂 on_tick 不被遮挡、
IME 打开 ~200ms TAP 冷却窗吞 stray-TAP; 附 sdk/test_ime_fix.c
- 同期: meow_audiod 音量异步 ACK 线程(代际防 fd/槽复用竞态)、watchdog 音量收编(首选 audiod
权威, settings.conf 覆盖路径)、panel 文件搜索(F1-④)与蓝牙只读详情(F1-③)、各类 build 脚本同步
范围红线: third_party/legado-E-main 原开发者未跟踪, 本次不入库(保留 devdocs/local tar 参考);
二进制不入库(后续重编); devdocs/ 不入库。59 文件全部在 patch_v2/ 下, 无 third_party/devdocs。
验证: git apply --check 干净; 59 文件 md5 与容器 patch_v2 逐一比对 59/59 一致(含
key_router.c/rdchapter.c/meow_ui_ime.c/test_wifi_connect.c 抽查); 可执行位保守
(check_lgpl_release.sh 100755, build_turn_repro.sh 100644 与容器一致)。
This commit is contained in:
1 parent
e87ccdf061
commit
7c7d891570
59 files changed
+5305
-590
No files matched your search
@@ -289,6 +289,8 @@ void net_policy_parse(const char *txt, sbuf_t *s);
|
||||
int api_net_policy_get(int sock);
|
||||
int api_net_policy_set(int sock, const char *body, size_t body_len);
|
||||
int net_locked_entity(const char *entity); /* 防自锁:系统关键网络/服务组件(dropbear/wpa_supplicant/udhcpc/netmgrd/svcd/sysui/init) */
|
||||
int net_bt_aggregate(const char *st, const char *paired, const char *connected, const char *a2dp, const char *devices, sbuf_t *s); /* F1-③ 0xE6 五读文本聚合(selftest 可测) */
|
||||
int api_net_bt(int sock); /* F1-③ GET /api/net/bt 蓝牙只读详情 */
|
||||
int panel_policy_write_enabled(void); /* policy 默认关 gate:settings.conf `panel.policy_enabled`,缺省 0=关(只读透传) */
|
||||
void panel_strip_ansi(char *s);
|
||||
int txt_tokens(char *buf, char **toks, int maxtoks);
|
||||
@@ -415,6 +417,8 @@ int api_notification_dismiss_all(int sock);
|
||||
int api_files_list(int sock, const req_t *r);
|
||||
int api_files_read(int sock, const req_t *r);
|
||||
int api_files_download(int sock, const req_t *r);
|
||||
int files_search_json(const char *abs_start, const char *name, const char *typef, int max, sbuf_t *out); /* F1-④ 搜索核心(selftest 可测) */
|
||||
int api_files_search(int sock, const req_t *r); /* F1-④ GET /api/files/search(只读) */
|
||||
int api_files_upload(int sock, const req_t *r);
|
||||
int rm_recursive(const char *path);
|
||||
int api_files_mkdir(int sock, const req_t *r);
|
||||
|
||||
@@ -571,6 +571,142 @@ int api_files_delete(int sock, const req_t *r)
|
||||
return send_json(sock, 200, "{\"removed\":true}");
|
||||
}
|
||||
|
||||
/* ---- F1-④ 文件搜索(FUTURE_MEOWPANEL §2.3,只读):白名单内 readdir 递归(限深 4 / 限量 200 /
|
||||
* 限遍历 8000 条),不 run_cmd("find")(防注入、可控)。name=名子串(ASCII 大小写不敏感);
|
||||
* type=file|dir 可选过滤。不跟符号链接(防循环/逃逸出白名单根)。 ---- */
|
||||
#define SEARCH_MAX_DEPTH 4
|
||||
#define SEARCH_MAX_ITEMS 200
|
||||
#define SEARCH_MAX_VISIT 8000
|
||||
|
||||
typedef struct {
|
||||
const char *needle; /* 已小写化的匹配子串 */
|
||||
int want_type; /* 0=不限 1=仅文件 2=仅目录 */
|
||||
int max;
|
||||
int count;
|
||||
long visited;
|
||||
sbuf_t *s;
|
||||
} search_ctx_t;
|
||||
|
||||
static void name_lower(const char *in, char *out, size_t cap)
|
||||
{
|
||||
size_t i = 0;
|
||||
for (; in[i] && i + 1 < cap; i++) {
|
||||
unsigned char c = (unsigned char)in[i];
|
||||
out[i] = (c >= 'A' && c <= 'Z') ? (char)(c + 32) : (char)c;
|
||||
}
|
||||
out[i] = 0;
|
||||
}
|
||||
|
||||
/* 返回 1=到达上限应全程停止(truncated)。 */
|
||||
static int search_walk(search_ctx_t *c, const char *dir, int depth)
|
||||
{
|
||||
if (c->count >= c->max || c->visited >= SEARCH_MAX_VISIT) return 1;
|
||||
DIR *d = opendir(dir);
|
||||
if (!d) return 0;
|
||||
int stop = 0;
|
||||
struct dirent *e;
|
||||
while ((e = readdir(d))) {
|
||||
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
|
||||
if (++c->visited > SEARCH_MAX_VISIT) { stop = 1; break; }
|
||||
char full[1400];
|
||||
snprintf(full, sizeof full, "%s/%s", dir, e->d_name);
|
||||
struct stat st;
|
||||
if (lstat(full, &st) < 0) continue;
|
||||
if (S_ISLNK(st.st_mode)) continue; /* 不跟符号链接 */
|
||||
int isdir = S_ISDIR(st.st_mode);
|
||||
int type_ok = !c->want_type || (c->want_type == 1 && !isdir) || (c->want_type == 2 && isdir);
|
||||
if (type_ok) {
|
||||
char low[300];
|
||||
name_lower(e->d_name, low, sizeof low);
|
||||
if (strstr(low, c->needle)) {
|
||||
if (c->count >= c->max) { stop = 1; break; }
|
||||
char rel[1200];
|
||||
make_rel(full, rel, sizeof rel);
|
||||
if (c->count) sA(c->s, ",");
|
||||
sA(c->s, "{\"name\":");
|
||||
sA_str(c->s, e->d_name);
|
||||
sA(c->s, ",\"type\":\"%s\",\"size\":%lld,\"mtime\":%lld,\"rel\":",
|
||||
isdir ? "dir" : "file", isdir ? 0LL : (long long)st.st_size, (long long)st.st_mtime);
|
||||
sA_str(c->s, rel);
|
||||
sA(c->s, "}");
|
||||
c->count++;
|
||||
}
|
||||
}
|
||||
if (isdir && depth < SEARCH_MAX_DEPTH && search_walk(c, full, depth + 1)) { stop = 1; break; }
|
||||
}
|
||||
closedir(d);
|
||||
return stop;
|
||||
}
|
||||
|
||||
/* 0=OK(JSON 入 out);-1 白名单外;-2 路径不存在/非目录;-3 name 空。 */
|
||||
int files_search_json(const char *abs_start, const char *name, const char *typef, int max, sbuf_t *out)
|
||||
{
|
||||
if (!path_in_whitelist(abs_start)) return -1;
|
||||
struct stat st;
|
||||
if (stat(abs_start, &st) < 0 || !S_ISDIR(st.st_mode)) return -2;
|
||||
char needle[300];
|
||||
name_lower(name ? name : "", needle, sizeof needle);
|
||||
if (!needle[0]) return -3;
|
||||
int want_type = 0;
|
||||
if (typef && !strcmp(typef, "file")) want_type = 1;
|
||||
else if (typef && !strcmp(typef, "dir")) want_type = 2;
|
||||
else if (typef && typef[0]) return -4; /* 非法 type 值(api 层映射 422) */
|
||||
if (max <= 0 || max > SEARCH_MAX_ITEMS) max = SEARCH_MAX_ITEMS;
|
||||
search_ctx_t c;
|
||||
memset(&c, 0, sizeof c);
|
||||
c.needle = needle; c.want_type = want_type; c.max = max; c.s = out;
|
||||
char rel[1200];
|
||||
make_rel(abs_start, rel, sizeof rel);
|
||||
sA(out, "{\"path\":");
|
||||
sA_str(out, abs_start);
|
||||
sA(out, ",\"rel\":");
|
||||
sA_str(out, rel);
|
||||
sA(out, ",\"name\":");
|
||||
sA_str(out, name);
|
||||
sA(out, ",\"items\":[");
|
||||
int stopped = search_walk(&c, abs_start, 1);
|
||||
sA(out, "],\"count\":%d,\"truncated\":%s}", c.count,
|
||||
(stopped || c.visited >= SEARCH_MAX_VISIT) ? "true" : "false");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_files_search(int sock, const req_t *r)
|
||||
{
|
||||
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
|
||||
char path[1024] = "/";
|
||||
if (r->query[0]) qparam(r->query, "path", path, sizeof path);
|
||||
char name[512] = "";
|
||||
if (!qparam(r->query, "name", name, sizeof name) || !name[0])
|
||||
return send_err(sock, 400, "BAD_REQUEST", "缺 name(按名子串搜索)");
|
||||
char typef[16] = "";
|
||||
qparam(r->query, "type", typef, sizeof typef);
|
||||
char maxs[16] = "";
|
||||
int max = SEARCH_MAX_ITEMS;
|
||||
if (qparam(r->query, "max", maxs, sizeof maxs)) { int m = atoi(maxs); if (m > 0) max = m; }
|
||||
char abs[1024];
|
||||
if (normalize_path(path, abs, sizeof abs) < 0 || !path_in_whitelist(abs))
|
||||
return send_err(sock, 403, "FORBIDDEN", "白名单外/路径逃逸");
|
||||
sbuf_t s = {0};
|
||||
int rc = files_search_json(abs, name, typef, max, &s);
|
||||
if (rc == -1) { sbuf_free(&s); return send_err(sock, 403, "FORBIDDEN", "白名单外/路径逃逸"); }
|
||||
if (rc == -2) { sbuf_free(&s); return send_err(sock, 404, "NOT_FOUND", "路径不存在或非目录"); }
|
||||
if (rc == -4) { sbuf_free(&s); return send_err(sock, 422, "UNSUPPORTED", "type 须为 file|dir"); }
|
||||
if (rc != 0) { sbuf_free(&s); return send_err(sock, 400, "BAD_REQUEST", "name 为空"); }
|
||||
/* 放行前自校验(与 overview 同口径:括号平衡/内嵌 NUL/坏字节,浏览器 JSON.parse 不容忍) */
|
||||
jv_t *chk = jv_parse(s.b ? s.b : "{}", (int)(s.b ? s.n : 2));
|
||||
int bad = !s.b || !chk || (strlen(s.b) != s.n) ||
|
||||
!utf8_valid_n(s.b, s.n) || !json_strict_balanced(s.b, s.n);
|
||||
if (chk) jv_free(chk);
|
||||
if (bad) {
|
||||
panel_log("files/search: SELF-CHECK fail (n=%zu)", s.n);
|
||||
sbuf_free(&s);
|
||||
return send_err(sock, 500, "INTERNAL", "搜索结果输出未通过 JSON 自校验");
|
||||
}
|
||||
int out = send_json(sock, 200, s.b);
|
||||
sbuf_free(&s);
|
||||
return out;
|
||||
}
|
||||
|
||||
int multipart_extract_file(const req_t *r, const char *outpath)
|
||||
{
|
||||
const char *b = strstr(r->content_type, "boundary=");
|
||||
|
||||
@@ -175,6 +175,160 @@ int api_net_dns_flush(int sock)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ---- F1-③ 蓝牙只读详情(FUTURE_MEOWPANEL §2.6):netmgrd MSG_NET_BT_CTL(0xE6) 读级五命令
|
||||
* (status/paired/connected/a2dp/devices)聚合为一份 JSON。读级任意调用者(bt_cmd_is_write=读),
|
||||
* 无阻塞(net_ipc 2s 超时兜底);子命令失败/坏字节 → 该子块空数组降级,绝不拖垮整卡、绝不发非法 JSON。 ---- */
|
||||
|
||||
/* 单发 0xE6 读命令;成功返回文本长度(>0),IPC/服务端失败 -1,坏 UTF-8 -2(该子块降级)。 */
|
||||
static int bt_ctl_read(const char *cmd, char *out, size_t cap)
|
||||
{
|
||||
m_net_cmd_t q; memset(&q, 0, sizeof q);
|
||||
snprintf(q.cmd, sizeof q.cmd, "%s", cmd);
|
||||
uint16_t t; uint32_t code;
|
||||
int n = net_ipc(MSG_NET_BT_CTL, &q, sizeof q, &t, &code, NULL, out, cap);
|
||||
if (n <= 0 || t != MSG_NET_BT_RESULT || code != MEOW_OK) return -1;
|
||||
if (!utf8_valid_n(out, (size_t)n)) return -2;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int bt_mac_ok(const char *m)
|
||||
{
|
||||
for (int i = 0; i < 17; i++) {
|
||||
if (i % 3 == 2) { if (m[i] != ':') return 0; }
|
||||
else if (!is_hex(m[i])) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* "Device <MAC> <Name>" 行集 → JSON 数组项(不合法行跳过;名经 sA_str_n 消毒)。返回条数。 */
|
||||
static int bt_devlines_json(sbuf_t *s, const char *txt, int max)
|
||||
{
|
||||
int n = 0;
|
||||
const char *ln = txt;
|
||||
while (ln && *ln && n < max) {
|
||||
const char *nl = strchr(ln, '\n');
|
||||
size_t ll = nl ? (size_t)(nl - ln) : strlen(ln);
|
||||
const char *p = ln;
|
||||
while ((size_t)(p - ln) < ll && (*p == ' ' || *p == '\t')) p++;
|
||||
if ((size_t)(ln + ll - p) >= 7 + 17 && !strncmp(p, "Device ", 7) && bt_mac_ok(p + 7)) {
|
||||
const char *mac = p + 7;
|
||||
const char *nm = mac + 17;
|
||||
while ((size_t)(nm - ln) < ll && (*nm == ' ' || *nm == '\t')) nm++;
|
||||
size_t nlen = ll - (size_t)(nm - ln);
|
||||
while (nlen && (nm[nlen - 1] == ' ' || nm[nlen - 1] == '\t' || nm[nlen - 1] == '\r')) nlen--;
|
||||
if (n) sA(s, ",");
|
||||
sA(s, "{\"mac\":");
|
||||
sA_str_n(s, mac, 17);
|
||||
sA(s, ",\"name\":");
|
||||
sA_str_n(s, nm, nlen);
|
||||
sA(s, "}");
|
||||
n++;
|
||||
}
|
||||
ln = nl ? nl + 1 : NULL;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 行内 " key=value"(空白/行尾收尾)取值。 */
|
||||
static void bt_line_kv(const char *line, const char *key, char *out, size_t cap)
|
||||
{
|
||||
out[0] = 0;
|
||||
const char *v = strstr(line, key);
|
||||
if (!v) return;
|
||||
v += strlen(key);
|
||||
size_t i = 0;
|
||||
while (v[i] && v[i] != ' ' && v[i] != '\t' && v[i] != '\n' && v[i] != '\r' && i + 1 < cap) { out[i] = v[i]; i++; }
|
||||
out[i] = 0;
|
||||
}
|
||||
|
||||
/* "A2DP <mac> codec=.. rate=.. channels=.. bits=.. acquired=<0|1> role=<..>" 行集 → JSON 数组项。 */
|
||||
static int bt_a2dp_json(sbuf_t *s, const char *txt, int max)
|
||||
{
|
||||
int n = 0;
|
||||
const char *ln = txt;
|
||||
while (ln && *ln && n < max) {
|
||||
const char *nl = strchr(ln, '\n');
|
||||
size_t ll = nl ? (size_t)(nl - ln) : strlen(ln);
|
||||
char line[320];
|
||||
if (ll >= sizeof line) ll = sizeof line - 1;
|
||||
memcpy(line, ln, ll); line[ll] = 0;
|
||||
if (!strncmp(line, "A2DP ", 5) && strncmp(line, "A2DP none", 9) && bt_mac_ok(line + 5)) {
|
||||
char codec[24], rate[16], chn[8], bits[8], acq[8], role[8];
|
||||
bt_line_kv(line, " codec=", codec, sizeof codec);
|
||||
bt_line_kv(line, " rate=", rate, sizeof rate);
|
||||
bt_line_kv(line, " channels=", chn, sizeof chn);
|
||||
bt_line_kv(line, " bits=", bits, sizeof bits);
|
||||
bt_line_kv(line, " acquired=", acq, sizeof acq);
|
||||
bt_line_kv(line, " role=", role, sizeof role);
|
||||
if (n) sA(s, ",");
|
||||
sA(s, "{\"mac\":");
|
||||
sA_str_n(s, line + 5, 17);
|
||||
sA(s, ",\"codec\":"); sA_str(s, codec);
|
||||
sA(s, ",\"rate\":"); sA_str(s, rate);
|
||||
sA(s, ",\"channels\":"); sA_str(s, chn);
|
||||
sA(s, ",\"bits\":"); sA_str(s, bits);
|
||||
sA(s, ",\"acquired\":%s", !strcmp(acq, "1") ? "true" : "false");
|
||||
sA(s, ",\"role\":"); sA_str(s, role);
|
||||
sA(s, "}");
|
||||
n++;
|
||||
}
|
||||
ln = nl ? nl + 1 : NULL;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 五读文本聚合(任一 NULL=该子块空数组;st=NULL → {"available":false})。 */
|
||||
int net_bt_aggregate(const char *st, const char *paired, const char *connected,
|
||||
const char *a2dp, const char *devices, sbuf_t *s)
|
||||
{
|
||||
if (!st) { sA(s, "{\"available\":false}"); return -1; }
|
||||
int avail = 0, powered = 0; long long paired_n = -1, connected_n = -1;
|
||||
char addr[24] = "";
|
||||
(void)sscanf(st, "bt available=%d powered=%d address=%23s paired=%lld connected=%lld",
|
||||
&avail, &powered, addr, &paired_n, &connected_n);
|
||||
if (!strcmp(addr, "-")) addr[0] = 0;
|
||||
sA(s, "{\"available\":%s", avail ? "true" : "false");
|
||||
sA(s, ",\"powered\":%s", powered ? "true" : "false");
|
||||
sA(s, ",\"address\":"); sA_str(s, addr);
|
||||
if (paired_n >= 0) sA(s, ",\"paired_n\":%lld", paired_n); else sA(s, ",\"paired_n\":null");
|
||||
if (connected_n >= 0) sA(s, ",\"connected_n\":%lld", connected_n); else sA(s, ",\"connected_n\":null");
|
||||
sA(s, ",\"paired\":["); if (paired) bt_devlines_json(s, paired, 32); sA(s, "]");
|
||||
sA(s, ",\"connected\":["); if (connected) bt_devlines_json(s, connected, 16); sA(s, "]");
|
||||
sA(s, ",\"a2dp\":["); if (a2dp) bt_a2dp_json(s, a2dp, 8); sA(s, "]");
|
||||
sA(s, ",\"nearby\":["); if (devices) bt_devlines_json(s, devices, 16); sA(s, "]");
|
||||
sA(s, "}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_net_bt(int sock)
|
||||
{
|
||||
char st[MEOW_IPC_MAX], pa[MEOW_IPC_MAX], co[MEOW_IPC_MAX], a2[MEOW_IPC_MAX], dv[MEOW_IPC_MAX];
|
||||
int ns = bt_ctl_read("status", st, sizeof st);
|
||||
if (ns <= 0) /* netmgrd 不可达/status 坏字节 → 整体降级(net 域惯例:200 available:false) */
|
||||
return send_json(sock, 200, "{\"available\":false}");
|
||||
int np = bt_ctl_read("paired", pa, sizeof pa);
|
||||
int nc = bt_ctl_read("connected", co, sizeof co);
|
||||
int na = bt_ctl_read("a2dp", a2, sizeof a2);
|
||||
int nd = bt_ctl_read("devices", dv, sizeof dv);
|
||||
sbuf_t s = {0};
|
||||
net_bt_aggregate(st, np > 0 ? pa : NULL, nc > 0 ? co : NULL, na > 0 ? a2 : NULL, nd > 0 ? dv : NULL, &s);
|
||||
/* 放行前自校验(与 overview 同口径:浏览器 JSON.parse 不容忍尾随垃圾/括号错位/内嵌 NUL/坏字节) */
|
||||
jv_t *chk = jv_parse(s.b ? s.b : "{}", (int)(s.b ? s.n : 2));
|
||||
int bad = !s.b || !chk || (strlen(s.b) != s.n) ||
|
||||
!utf8_valid_n(s.b, s.n) || !json_strict_balanced(s.b, s.n);
|
||||
if (chk) jv_free(chk);
|
||||
if (bad) {
|
||||
FILE *df = fopen("/tmp/net_bt_bad.json", "w");
|
||||
if (df && s.b) { fwrite(s.b, 1, s.n, df); fclose(df); }
|
||||
panel_log("net/bt: SELF-CHECK fail, raw dumped /tmp/net_bt_bad.json");
|
||||
sbuf_free(&s);
|
||||
return send_err(sock, 500, "INTERNAL", "蓝牙聚合输出未通过 JSON 自校验(原始数据已留 /tmp/net_bt_bad.json)");
|
||||
}
|
||||
int rc = send_json(sock, 200, s.b);
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int api_net_log(int sock)
|
||||
{
|
||||
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
|
||||
|
||||
@@ -301,6 +301,7 @@ int handle(int sock, req_t *r)
|
||||
/* ---- 网络管理(meow_netmgrd 期2/期3 透传:读级任意调用者;flush/policy 写级走 require_auth) ---- */
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/status")) return api_net_status(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/ifaces")) return api_net_ifaces(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/bt")) return api_net_bt(sock); /* F1-③ 蓝牙只读详情(0xE6 读级聚合) */
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/policy")) return api_net_policy_get(sock);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/conns")) return api_net_conns(sock, r->query);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/dns")) return api_net_dns(sock);
|
||||
@@ -381,6 +382,7 @@ int handle(int sock, req_t *r)
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/files")) return api_files_list(sock, r);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/files/read")) return api_files_read(sock, r);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/files/download")) return api_files_download(sock, r);
|
||||
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/files/search")) return api_files_search(sock, r); /* F1-④ 搜索(只读) */
|
||||
if (!strcmp(r->method, "DELETE") && !strcmp(p, "/api/files")) return api_files_delete(sock, r);
|
||||
|
||||
/* ---- 应用 ---- */
|
||||
@@ -634,7 +636,116 @@ int panel_selftest(void)
|
||||
if (tfd >= 0) { unlink(tmpf); g_settings_file = saved; }
|
||||
}
|
||||
|
||||
printf("SELFTEST PASS: 截断 UTF-8「流媒体客户\\xE7」+ net/power/ams 新增端点池 → 合法 JSON + 无非法字节;防自锁实体 + policy 门禁 默认关/开启 校验通过\n");
|
||||
/* 10) net_bt_aggregate(F1-③):0xE6 五读文本聚合 → 合法 JSON + 字段断言;
|
||||
* 设备名含截断坏字节 → sA_str_n 消毒为 '?' 且产物仍合法;子文本 NULL → 空数组;status NULL → available:false。 */
|
||||
{
|
||||
sbuf_t b = {0};
|
||||
const char *st = "bt available=1 powered=1 address=4C:BC:98:B0:98:DB paired=2 connected=1";
|
||||
const char *pa = "Device 33:DE:2E:21:AD:30 MOONDROP Pudding\nDevice 17:0A:15:AA:BB:CC 流媒体客户\xE7\n";
|
||||
const char *co = "Device 33:DE:2E:21:AD:30 MOONDROP Pudding\n";
|
||||
const char *a2 = "A2DP 33:DE:2E:21:AD:30 codec=sbc rate=44100 channels=2 bits=16 acquired=1 role=src\n";
|
||||
const char *dv = "Device 33:DE:2E:21:AD:30 MOONDROP Pudding\nDevice 17:0A:15:AA:BB:CC 17OP15\n";
|
||||
if (net_bt_aggregate(st, pa, co, a2, dv, &b) != 0) { printf("SELFTEST FAIL: net_bt_aggregate 正常文本返回非 0\n"); sbuf_free(&b); return 1; }
|
||||
if (!b.b || !utf8_valid_n(b.b, b.n) || !json_strict_balanced(b.b, b.n)) { printf("SELFTEST FAIL: net_bt_aggregate 产物非法字节/括号错位\n"); sbuf_free(&b); return 1; }
|
||||
jv_t *br = jv_parse(b.b, (int)b.n);
|
||||
if (!br) { printf("SELFTEST FAIL: net_bt_aggregate 产物 JSON 无法解析\n"); sbuf_free(&b); return 1; }
|
||||
jv_t *pw = jv_obj_get(br, "powered");
|
||||
if (!pw || pw->type != J_BOOL || !pw->bval) { printf("SELFTEST FAIL: net_bt_aggregate powered 非 true\n"); jv_free(br); sbuf_free(&b); return 1; }
|
||||
jv_t *pr = jv_obj_get(br, "paired");
|
||||
if (!pr || pr->type != J_ARR || !pr->child || !pr->child->next || pr->child->next->next) { printf("SELFTEST FAIL: net_bt_aggregate paired 应 2 条\n"); jv_free(br); sbuf_free(&b); return 1; }
|
||||
jv_t *cn = jv_obj_get(br, "connected");
|
||||
if (!cn || cn->type != J_ARR || !cn->child || cn->child->next) { printf("SELFTEST FAIL: net_bt_aggregate connected 应 1 条\n"); jv_free(br); sbuf_free(&b); return 1; }
|
||||
jv_t *a2p = jv_obj_get(br, "a2dp");
|
||||
if (!a2p || a2p->type != J_ARR || !a2p->child) { printf("SELFTEST FAIL: net_bt_aggregate 缺 a2dp 条目\n"); jv_free(br); sbuf_free(&b); return 1; }
|
||||
jv_t *cd = jv_obj_get(a2p->child, "codec");
|
||||
if (!cd || cd->type != J_STR || !cd->str || strcmp(cd->str, "sbc") != 0) { printf("SELFTEST FAIL: net_bt_aggregate a2dp codec 非 sbc\n"); jv_free(br); sbuf_free(&b); return 1; }
|
||||
jv_t *aq = jv_obj_get(a2p->child, "acquired");
|
||||
if (!aq || aq->type != J_BOOL || !aq->bval) { printf("SELFTEST FAIL: net_bt_aggregate a2dp acquired 非 true\n"); jv_free(br); sbuf_free(&b); return 1; }
|
||||
jv_free(br);
|
||||
sbuf_free(&b);
|
||||
/* status NULL → available:false;子文本 NULL → 空数组 */
|
||||
sbuf_t b2 = {0};
|
||||
net_bt_aggregate(NULL, NULL, NULL, NULL, NULL, &b2);
|
||||
if (!b2.b || strstr(b2.b, "\"available\":false") == NULL) { printf("SELFTEST FAIL: net_bt_aggregate status NULL 未降级 available:false\n"); sbuf_free(&b2); return 1; }
|
||||
sbuf_free(&b2);
|
||||
sbuf_t b3 = {0};
|
||||
net_bt_aggregate(st, NULL, NULL, NULL, NULL, &b3);
|
||||
jv_t *b3r = b3.b ? jv_parse(b3.b, (int)b3.n) : NULL;
|
||||
jv_t *b3p = b3r ? jv_obj_get(b3r, "paired") : NULL;
|
||||
if (!b3p || b3p->type != J_ARR || b3p->child) { printf("SELFTEST FAIL: net_bt_aggregate 子文本 NULL 应为空数组\n"); if (b3r) jv_free(b3r); sbuf_free(&b3); return 1; }
|
||||
jv_free(b3r);
|
||||
sbuf_free(&b3);
|
||||
}
|
||||
|
||||
/* 11) files_search_json(F1-④):沙盒卡根递归搜索(限深/限量/类型过滤/rel 回显)→ 合法 JSON + 计数断言;
|
||||
* 白名单外起点 → -1;name 空 → -3;非法 type → -4。 */
|
||||
{
|
||||
char tmpl[] = "/tmp/mp_search_XXXXXX";
|
||||
char *root = mkdtemp(tmpl);
|
||||
if (!root) { printf("SELFTEST FAIL: mkdtemp 失败\n"); return 1; }
|
||||
char saved_root[1024];
|
||||
snprintf(saved_root, sizeof saved_root, "%s", g_tf_root_buf);
|
||||
snprintf(g_tf_root_buf, sizeof g_tf_root_buf, "%s", root);
|
||||
char d1[1200], d2[1200], f1[1200], f2[1200], f3[1200];
|
||||
snprintf(d1, sizeof d1, "%s/MeowOS", root);
|
||||
snprintf(d2, sizeof d2, "%s/MeowOS/sub", root);
|
||||
snprintf(f1, sizeof f1, "%s/MeowOS/Test Song.mp3", root);
|
||||
snprintf(f2, sizeof f2, "%s/MeowOS/another.txt", root);
|
||||
snprintf(f3, sizeof f3, "%s/MeowOS/sub/x Song2.log", root);
|
||||
int setup_ok = (mkdir(d1, 0755) == 0 && mkdir(d2, 0755) == 0);
|
||||
const char *mk[3] = { f1, f2, f3 };
|
||||
for (int i = 0; i < 3 && setup_ok; i++) {
|
||||
FILE *f = fopen(mk[i], "w");
|
||||
if (!f) { setup_ok = 0; break; }
|
||||
fputs("x", f); fclose(f);
|
||||
}
|
||||
if (!setup_ok) { printf("SELFTEST FAIL: 搜索沙盒构建失败\n"); snprintf(g_tf_root_buf, sizeof g_tf_root_buf, "%s", saved_root); rm_recursive(root); rmdir(root); return 1; }
|
||||
int fails = 0;
|
||||
sbuf_t s = {0};
|
||||
if (files_search_json(d1, "song", NULL, 0, &s) != 0) { printf("SELFTEST FAIL: files_search_json 正常搜索返回非 0\n"); fails = 1; }
|
||||
jv_t *sr = (!fails && s.b) ? jv_parse(s.b, (int)s.n) : NULL;
|
||||
if (!fails && (!sr || !utf8_valid_n(s.b, s.n) || !json_strict_balanced(s.b, s.n))) { printf("SELFTEST FAIL: files_search_json 产物非法\n"); fails = 1; }
|
||||
jv_t *cnt = (!fails && sr) ? jv_obj_get(sr, "count") : NULL;
|
||||
if (!fails && (!cnt || cnt->type != J_NUM || cnt->inum != 2)) { printf("SELFTEST FAIL: files_search_json song 应中 2 条(递归命中子目录)\n"); fails = 1; }
|
||||
jv_t *its = (!fails && sr) ? jv_obj_get(sr, "items") : NULL;
|
||||
jv_t *rel0 = (its && its->child) ? jv_obj_get(its->child, "rel") : NULL;
|
||||
if (!fails && (!rel0 || rel0->type != J_STR || !rel0->str || strncmp(rel0->str, "/MeowOS/", 8) != 0)) { printf("SELFTEST FAIL: files_search_json rel 未按卡根相对回显\n"); fails = 1; }
|
||||
if (sr) jv_free(sr);
|
||||
sbuf_free(&s);
|
||||
/* type=dir 过滤:建 Songs 目录应仅中目录 */
|
||||
char d3[1200];
|
||||
snprintf(d3, sizeof d3, "%s/MeowOS/Songs", root);
|
||||
if (!fails && mkdir(d3, 0755) != 0) { printf("SELFTEST FAIL: 沙盒 Songs 目录创建失败\n"); fails = 1; }
|
||||
sbuf_t s2 = {0};
|
||||
if (!fails && files_search_json(d1, "song", "dir", 0, &s2) == 0) {
|
||||
jv_t *sr2 = jv_parse(s2.b, (int)s2.n);
|
||||
jv_t *c2 = sr2 ? jv_obj_get(sr2, "count") : NULL;
|
||||
jv_t *i2 = sr2 ? jv_obj_get(sr2, "items") : NULL;
|
||||
jv_t *t0 = (i2 && i2->child) ? jv_obj_get(i2->child, "type") : NULL;
|
||||
if (!c2 || c2->type != J_NUM || c2->inum != 1 || !t0 || t0->type != J_STR || strcmp(t0->str, "dir") != 0) {
|
||||
printf("SELFTEST FAIL: files_search_json type=dir 应仅中 1 个目录\n"); fails = 1;
|
||||
}
|
||||
if (sr2) jv_free(sr2);
|
||||
} else if (!fails) { printf("SELFTEST FAIL: files_search_json type=dir 搜索失败\n"); fails = 1; }
|
||||
sbuf_free(&s2);
|
||||
/* 无匹配 → count 0;白名单外 → -1;空 name → -3;非法 type → -4 */
|
||||
sbuf_t s3 = {0};
|
||||
if (!fails && files_search_json(d1, "zzzz", NULL, 0, &s3) == 0) {
|
||||
if (!strstr(s3.b, "\"count\":0")) { printf("SELFTEST FAIL: files_search_json 无匹配应 count 0\n"); fails = 1; }
|
||||
} else if (!fails) { printf("SELFTEST FAIL: files_search_json 无匹配搜索失败\n"); fails = 1; }
|
||||
sbuf_free(&s3);
|
||||
sbuf_t s4 = {0};
|
||||
if (!fails && files_search_json("/etc", "x", NULL, 0, &s4) != -1) { printf("SELFTEST FAIL: files_search_json 白名单外未拒\n"); fails = 1; }
|
||||
if (!fails && files_search_json(d1, "", NULL, 0, &s4) != -3) { printf("SELFTEST FAIL: files_search_json 空 name 未拒\n"); fails = 1; }
|
||||
if (!fails && files_search_json(d1, "x", "bogus", 0, &s4) != -4) { printf("SELFTEST FAIL: files_search_json 非法 type 未拒\n"); fails = 1; }
|
||||
sbuf_free(&s4);
|
||||
snprintf(g_tf_root_buf, sizeof g_tf_root_buf, "%s", saved_root);
|
||||
rm_recursive(root);
|
||||
rmdir(root);
|
||||
if (fails) return 1;
|
||||
}
|
||||
|
||||
printf("SELFTEST PASS: 截断 UTF-8「流媒体客户\\xE7」+ net/power/ams 端点池 + 蓝牙 0xE6 聚合 + 文件搜索 → 合法 JSON + 无非法字节;防自锁实体 + policy 门禁 默认关/开启 校验通过\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user