fix(ncm): 封面矩阵实锤修正 —— 200解码失败=死对象返JSON错误体(非webp)/UA改桌面浏览器(CNMPlayer UA被拦403)/非图k不定性不重试

真机(设备恢复)CDN 行为矩阵结论并落地:
- 裸请求(无Referer)→403: 防盗链坐实, 封面请求头加 Referer https://music.163.com/。
- +Referer 仍不返真图; 全部变体只见 403/404/200+JSON错误体(43B {"code, application/json)。
  ③「200解码失败」真因=源站死对象返 200+JSON错误体, 非webp; stbi解不了→旧逻辑盲指数退避8次。
  → download_cover 回填 *out_kind(0可解码/1 webp/2非图), worker 对 kind=1/2 与确定性 4xx 一律
  立即清目标走兜底(保留歌单封面)不重试; kind=2 打 'not an image (origin error body/200)'。
- UA 分表: CNMPlayer口径 UA(Mozilla/5.0 CNMPlayer/0.1)同Referer也返403(CDN拦bot串);
  桌面 Chrome UA(旧cover.c)返200。→ NCM_COVER_UA 改回桌面浏览器 UA。
- ?param=300y300 在死对象上→404(对死链无效); 存活URL未获样本, 保留 USE_PARAM=1(标准缩略)。
- cover_chain_sim 增 T10(200+JSON→非图不重试) + stub 内容加 JPEG 魔数(过 body_is_decodable_img);
  T1-T10 ALL PASS。mipsel 0 warning(text 1020306/bss 1362500 持平)。

新 ncm md5 06896b54f31ddf55980cbf921a00d67a (未部署, 留主会话上卡)
This commit is contained in:
AskaEth committed 2026-09-02 10:50:07 +08:00
1 parent aeb9176101
commit 9d93f1b1ef
5 files changed
+138 -56

No files matched your search

+21 -1
View File
@@ -118,6 +118,7 @@ static int g_http_calls;
static int g_fail_next; /* 剩余注入失败次数 */
static int g_delay_ms; /* 模拟慢网络 */
static int g_webp_next; /* 下一次下载返回 WebP 魔数体(200,测「200 解码失败」三分型③) */
static int g_json_next; /* 下一次下载返回 JSON 错误体(200,实测「200 解码失败」真凶=死对象回 JSON) */
static int g_status_next; /* 下一次下载强制返回该 HTTP status(测 403 防盗链;0=200) */
static unsigned long djb2(const char *s)
@@ -135,6 +136,9 @@ int ncm_http_request(const ncm_http_req_t *req, char *buf, size_t buf_sz, ncm_ht
size_t n = 4096 < buf_sz ? 4096 : buf_sz;
unsigned long h = djb2(req->url);
for (size_t i = 0; i < n; i++) buf[i] = (char)((h >> ((i % 8) * 8)) ^ (unsigned char)i);
/* 真实 cover.c 会做可解码图魔数判定(REPORT §34:200 但非 JPEG/PNG/GIF 判非图不重试),
* 故 mock body 加 JPEG 魔数 FFD8FF,与 url_hash() 期望同规则。 */
if (n >= 3) { buf[0]=(char)0xFF; buf[1]=(char)0xD8; buf[2]=(char)0xFF; }
memset(resp, 0, sizeof *resp);
resp->status = 200;
resp->body = buf;
@@ -157,7 +161,12 @@ int ncm_http_download(const ncm_http_req_t *req, ncm_http_write_cb cb, void *ctx
size_t n = sizeof buf;
unsigned long h = djb2(req->url);
for (size_t i = 0; i < n; i++) buf[i] = (unsigned char)((h >> ((i % 8) * 8)) ^ (unsigned char)i);
/* webp 注入:前 12 字节置 RIFF....WEBP 魔数(stb 不支持 → cover.c 判 webp=1 不解码) */
/* JPEG 魔数(cover.c 可解码图判定通过);webp/json 注入优先覆盖各自魔数 */
buf[0]=0xFF; buf[1]=0xD8; buf[2]=0xFF;
if (g_json_next) {
g_json_next = 0;
buf[0]='{'; buf[1]='"'; buf[2]='c'; buf[3]='o'; buf[4]='d'; buf[5]='e';
}
if (g_webp_next) {
g_webp_next = 0;
buf[0]='R';buf[1]='I';buf[2]='F';buf[3]='F';
@@ -193,6 +202,7 @@ static unsigned long url_hash(const char *url)
unsigned char tmp[4096];
unsigned long h = djb2(n);
for (size_t i = 0; i < sizeof tmp; i++) tmp[i] = (unsigned char)((h >> ((i % 8) * 8)) ^ (unsigned char)i);
tmp[0]=0xFF; tmp[1]=0xD8; tmp[2]=0xFF; /* 与 stub JPEG 魔数一致 */
return fnv1a(tmp, sizeof tmp);
}
@@ -242,6 +252,7 @@ static unsigned long curjpg_hash(void)
#define URL_D "http://p1.music.126.net/albumD/www.jpg"
#define URL_E "http://p1.music.126.net/albumE/vvv.jpg" /* webp 注入用(未缓存) */
#define URL_F "http://p1.music.126.net/albumF/uuu.jpg" /* 403 注入用(未缓存) */
#define URL_G "http://p1.music.126.net/albumG/ttt.jpg" /* JSON 错误体注入用(未缓存) */
int main(void)
{
@@ -317,6 +328,15 @@ int main(void)
CHECK(strstr(g_last_log, "403") != NULL, "log: '403' seen");
g_status_next = 0;
printf("== T10: 200+JSON error body (dead origin) -> not an image, no retry ==\n");
int b10 = g_http_calls;
g_json_next = 1;
cover_request(URL_G);
usleep(800 * 1000);
CHECK(g_http_calls == b10 + 1, "json: single download=%d (no retry)", g_http_calls - b10);
CHECK(cur_cover_hash() == url_hash(URL_C), "json: 旧封面 C 保留(兜底)");
CHECK(strstr(g_last_log, "not an image") != NULL, "log: 'not an image' seen");
printf("== T7: null/empty + reset ==\n");
int b7 = g_http_calls;
cover_request(NULL);