用户定案:失效前用当前 cookie 换新登录态(用户无感);仅刷新失败才退扫码重登。 - core/ncm_req.c|h:eapi Cookie 头补齐会话锚点 MUSIC_U/MUSIC_A(对齐 request.rs, 补 §36.5 遗留——登录刷新/song_url 会员档依赖);新增 ncm_req_cookie_merge (patch 覆盖 base 同名 cookie,MUSIC_A/__remember_me 等不丢)。 - core/ncm_login.c|h:ncm_login_refresh(eapi /api/login/token/refresh 空体, code200+set-cookie→merge;code!=200→NCM_ERR_AUTH 退扫码;网络/TLS→负码退避)。 - app/logic/account.c:account.json 增 last_refresh_ts + 拆分 cvip/svip; account_cookie_maybe_refresh(年龄阈值 12h 可配 ncm.cookie_refresh_h,单飞/boot, 失败冷却 30min 跨 boot 持久化,AUTH 置回登录页标志)+ mark_fresh/need_relogin。 - app/ui/home.c:home_worker 先续期再拉歌单;app/ui/login.c:803 落盘后 mark_fresh; app/app.c:UI tick 消费回登录标志 → logout+切登录页。 - 新增测试:test_ncm_core 的 cookie_merge + eapi_musicu_hdr;bench/cookie_refresh_sim (端点语义,0 网络);bench/account_cookie_sim(临期/未临期/退避/失效/冷却,0 网络)。 验证:host+qemu 核心测试 PASS;build_ncm_sims.sh 全家桶 PASS;build_ncm.sh 0 warning。 真机只读探针 2 次(设备 10.78.193.50,不写回):refresh 走 eapi 域 interface.music.163.com 在当前网络被 TLS reset(alert 80)→ rc=-5 瞬断退避, session.enc/deviceId md5 前后一致(登录态零损伤)。新 ncm md5 c960244b。
135 lines
6.2 KiB
C
135 lines
6.2 KiB
C
/* bench/cookie_refresh_sim.c — MUSIC_U 登录续期端点语义验证(§NEW,0 真实 NCM 请求)。
|
||
*
|
||
* 用真实 core(ncm_login.c / ncm_req.c / ncm_crypto.c / ncm_json.c / ncm_common.c),
|
||
* 仅把网络层换成计数桩(canned eapi set-cookie 响应 / 网络错误),验证 ncm_login_refresh:
|
||
* ① 成功(code200 + Set-Cookie MUSIC_U/__csrf)→ NCM_OK,新 MUSIC_U patch 进 cookie、
|
||
* 旧 cookie 其它字段(MUSIC_A/__remember_me)保留(= 新 cookie 替换落盘的合并源);
|
||
* ② 会话失效(code301)→ NCM_ERR_AUTH(调用方据此退扫码重登);
|
||
* ③ 网络/TLS 瞬断 → 负错误码(调用方退避,保持旧 cookie);
|
||
* ④ code200 但无 set-cookie(服务端未轮换)→ NCM_OK,cookie 原样(不损坏)。
|
||
* 风控纪律:0 次真实 NCM 请求;桩按 mode 切换。
|
||
|
||
* 编译(宿主),在 ncm_app 目录:见 build_ncm_sims.sh 新增 cookie_refresh_sim 段。
|
||
* 运行:./cookie_refresh_sim
|
||
*/
|
||
#include "ncm.h"
|
||
#include "ncm_http.h"
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
|
||
static int g_mode; /* 0=成功+set-cookie; 1=code301; 2=网络错误; 3=code200 无 set-cookie */
|
||
static int g_http_calls; /* ncm_http_request 桩计数 = 真实网络调用数 */
|
||
|
||
static const char *HDR_OK =
|
||
"Content-Type: application/json\r\n"
|
||
"Set-Cookie: MUSIC_U=NEWTOKEN123; Path=/; Max-Age=7776000; HttpOnly\r\n"
|
||
"Set-Cookie: __csrf=newcsrf99; Domain=.music.163.com\r\n";
|
||
static const char *BODY_OK = "{\"code\":200,\"message\":\"ok\"}";
|
||
static const char *BODY_AUTH = "{\"code\":301,\"message\":\"需要登录\"}";
|
||
|
||
int ncm_http_request(const ncm_http_req_t *req, char *buf, size_t buf_sz, ncm_http_resp_t *resp) {
|
||
(void)req; (void)buf_sz;
|
||
g_http_calls++;
|
||
memset(resp, 0, sizeof *resp);
|
||
if (g_mode == 2) return NCM_ERR_NET; /* 网络瞬断 */
|
||
const char *body = (g_mode == 1) ? BODY_AUTH : BODY_OK;
|
||
size_t bn = strlen(body);
|
||
memcpy(buf, body, bn);
|
||
resp->status = 200;
|
||
resp->body = buf;
|
||
resp->body_len = bn;
|
||
resp->content_length = bn;
|
||
if (g_mode == 0) { /* 带 Set-Cookie 轮换 */
|
||
size_t hn = strlen(HDR_OK);
|
||
memcpy(resp->headers, HDR_OK, hn);
|
||
resp->headers_len = hn;
|
||
}
|
||
return NCM_OK;
|
||
}
|
||
|
||
/* 复刻 ncm_http.c 的 set-cookie 提取(解析所有 Set-Cookie 名值,去属性,"; " 连接) */
|
||
int ncm_http_parse_set_cookie(const char *hdrs, size_t len, char *out, size_t out_sz) {
|
||
if (!hdrs || !out || out_sz == 0) return -1;
|
||
out[0] = 0;
|
||
size_t o = 0;
|
||
int count = 0;
|
||
const char *p = hdrs;
|
||
const char *end = hdrs + len;
|
||
while ((p = strstr(p, "Set-Cookie:")) != NULL && p < end) {
|
||
const char *val = p + strlen("Set-Cookie:");
|
||
while (*val == ' ' || *val == '\t') val++;
|
||
const char *semi = strchr(val, ';');
|
||
size_t nlen = semi ? (size_t)(semi - val) : strlen(val);
|
||
while (nlen && (val[nlen-1]==' '||val[nlen-1]=='\t')) nlen--;
|
||
if (nlen == 0) { p = val; continue; }
|
||
if (o > 0) { if (o + 2 >= out_sz) break; out[o++] = ';'; out[o++] = ' '; }
|
||
if (o + nlen >= out_sz) break;
|
||
memcpy(out + o, val, nlen);
|
||
o += nlen;
|
||
count++;
|
||
p = val;
|
||
}
|
||
out[o] = 0;
|
||
return count;
|
||
}
|
||
void ncm_http_set_verify_default(int verify, const char *ca_path) { (void)verify; (void)ca_path; }
|
||
|
||
static int g_fail = 0;
|
||
#define CHECK(cond, fmt, ...) do { \
|
||
if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \
|
||
else { printf(" FAIL " fmt "\n", ##__VA_ARGS__); g_fail = 1; } \
|
||
} while (0)
|
||
|
||
static const char *g_device = "AA11BB22CC33DD44EE55FF6600112233445566778899AABB00";
|
||
|
||
int main(void) {
|
||
/* 基础会话 cookie(MUSIC_U 在中间,__remember_me/MUSIC_A 在后——验证不丢失、不被重排破坏) */
|
||
static char cookie[NCM_COOKIE_MAX];
|
||
snprintf(cookie, sizeof cookie,
|
||
"__csrf=old_csrf; MUSIC_U=OLD_TOKEN_abc_123; MUSIC_A=anon_a; __remember_me=true");
|
||
char out[NCM_COOKIE_MAX];
|
||
char v[64];
|
||
|
||
puts("== cookie_refresh_sim: ncm_login_refresh endpoint semantics ==");
|
||
|
||
/* ① 成功:code200 + Set-Cookie 轮换 → 新 MUSIC_U 覆盖旧值,其余字段保留 */
|
||
g_mode = 0; g_http_calls = 0;
|
||
int rc = ncm_login_refresh(g_device, cookie, out, sizeof out);
|
||
CHECK(rc == NCM_OK, "success rc=0 (got %d)", rc);
|
||
CHECK(g_http_calls == 1, "exactly 1 http call (got %d)", g_http_calls);
|
||
CHECK(ncm_req_cookie_get(out, "MUSIC_U", v, sizeof v) == NCM_OK &&
|
||
strcmp(v, "NEWTOKEN123") == 0, "MUSIC_U replaced with NEWTOKEN123 (got %s)", v);
|
||
CHECK(ncm_req_cookie_get(out, "__csrf", v, sizeof v) == NCM_OK &&
|
||
strcmp(v, "newcsrf99") == 0, "__csrf replaced (got %s)", v);
|
||
CHECK(ncm_req_cookie_get(out, "MUSIC_A", v, sizeof v) == NCM_OK &&
|
||
strcmp(v, "anon_a") == 0, "MUSIC_A preserved (got %s)", v);
|
||
CHECK(ncm_req_cookie_get(out, "__remember_me", v, sizeof v) == NCM_OK &&
|
||
strcmp(v, "true") == 0, "__remember_me preserved (got %s)", v);
|
||
|
||
/* ② 会话失效:code301 → NCM_ERR_AUTH(退扫码重登) */
|
||
g_mode = 1; g_http_calls = 0;
|
||
rc = ncm_login_refresh(g_device, cookie, out, sizeof out);
|
||
CHECK(rc == NCM_ERR_AUTH, "auth-fail rc=%d (期望 %d)", rc, NCM_ERR_AUTH);
|
||
|
||
/* ③ 网络瞬断 → 负错误码(退避,保持旧 cookie 由调用方保证) */
|
||
g_mode = 2; g_http_calls = 0;
|
||
rc = ncm_login_refresh(g_device, cookie, out, sizeof out);
|
||
CHECK(rc == NCM_ERR_NET, "net-fail rc=%d (期望 %d)", rc, NCM_ERR_NET);
|
||
|
||
/* ④ code200 但无 set-cookie(未轮换)→ NCM_OK,cookie 原样(无害) */
|
||
g_mode = 3; g_http_calls = 0;
|
||
memset(out, 0, sizeof out);
|
||
rc = ncm_login_refresh(g_device, cookie, out, sizeof out);
|
||
CHECK(rc == NCM_OK, "code200-no-cookie rc=0 (got %d)", rc);
|
||
CHECK(strcmp(out, cookie) == 0, "no-cookie: cookie unchanged");
|
||
|
||
/* ⑤ 无 cookie(未登录)→ 参数允许空 body 字段但仍应优雅(eapi 头不注入 MUSIC_U) */
|
||
g_mode = 3; g_http_calls = 0;
|
||
rc = ncm_login_refresh(g_device, "", out, sizeof out);
|
||
CHECK(rc == NCM_OK, "empty-cookie still builds http ok (got %d)", rc);
|
||
|
||
printf("\n%s\n", g_fail ? "== RESULT: FAIL ==" : "== RESULT: PASS ==");
|
||
return g_fail ? 1 : 0;
|
||
}
|