Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 151 additions & 1 deletion scripts/test-lanzou.mts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ await test("Lanzou Driver get() 直链与请求头验证(纯 User-Agent,无
try {
(globalThis as any).fetch = async (url: string, init?: RequestInit) => {
const urlStr = String(url)
if (urlStr.includes("ajaxm.php")) {
if (urlStr.includes("ajaxfile.php") || urlStr.includes("ajaxm.php")) {
return new Response(JSON.stringify({
zt: 1,
dom: "https://developer.lanzoui.com",
Expand Down Expand Up @@ -161,6 +161,156 @@ await test("Lanzou Driver get() 直链与请求头验证(纯 User-Agent,无
}
})

// 5. Test 2026-10 改版 /fn 内页(apifile 绝对地址 + 签名)
await test("Lanzou Driver get() 适配改版 /fn 内页(apifile 绝对地址)", async () => {
const driver = new LanzouDriver({
type: "url",
root_folder_id: "b00test",
shareUrl: "https://pan.lanzoui.com",
})

const mockFilePage = `
<html>
<title>sample.zip - 蓝奏云</title>
<div class="fileinfo">大小:10.5 M</div>
<iframe src="/fn?newtoken"></iframe>
</html>
`
// 改版后:下载接口是 apifile 绝对地址,签名放在 wp_sign / ajaxdata
const mockIframe = `
<html>
<script>
var domain1 = 'https://apifile.woozooo.com/ajaxfile.php?file=987654';
var wp_sign = 'NEWSIGN';
var ajaxdata = 'NEWDATA';
$.post(domain1, {'action':'downprocess','websignkey':ajaxdata,'signs':ajaxdata,'sign':wp_sign,'websign':'2','kd':1,'ves':1});
</script>
</html>
`

let posted: { url: string; form: URLSearchParams } | null = null
const originalFetch = globalThis.fetch
try {
(globalThis as any).fetch = async (url: string, init: RequestInit = {}) => {
const urlStr = String(url)
const method = (init.method || "GET").toUpperCase()
if (urlStr.includes("/fn?")) return new Response(mockIframe)
if (method === "POST") {
posted = {
url: urlStr,
form: new URLSearchParams(String(init.body || "")),
}
return new Response(
JSON.stringify({
zt: 1,
dom: "https://developer.lanzoui.com",
url: "token_new",
}),
)
}
if (urlStr.includes("developer.lanzoui.com/file")) {
return new Response(null, {
status: 302,
headers: { location: "https://cdn.example.com/new.zip" },
})
}
if (urlStr.includes("pan.lanzoui.com/b00test")) {
return new Response(mockFilePage)
}
return new Response("Not Found", { status: 404 })
}

const item = await driver.get("/b00test", "/sample.zip")
const got = posted as { url: string; form: URLSearchParams } | null
if (!got) throw new Error("下载接口未被调用")
if (!got.url.startsWith("https://apifile.woozooo.com/ajaxfile.php?file=987654")) {
throw new Error(`下载接口地址错误: ${got.url}`)
}
if (got.form.get("sign") !== "NEWSIGN") throw new Error("sign 未透传")
if (got.form.get("signs") !== "NEWDATA") throw new Error("signs 未透传")
if (got.form.get("kd") !== "1") throw new Error("kd 未按改版结构传 1")
if (item.raw_url !== "https://cdn.example.com/new.zip") {
throw new Error(`直链错误: ${item.raw_url}`)
}
} finally {
globalThis.fetch = originalFetch
}
})

// 6. 旧版 /fn 内页回归:确认改版适配没有破坏原有结构
await test("Lanzou Driver get() 保持旧版 /fn 内页兼容性", async () => {
const driver = new LanzouDriver({
type: "url",
root_folder_id: "b00test",
shareUrl: "https://pan.lanzoui.com",
})

const mockFilePage = `
<html>
<title>legacy.zip - 蓝奏云</title>
<div class="fileinfo">大小:1.2 M</div>
<iframe src="/fn?oldtoken"></iframe>
</html>
`
// 旧版:相对路径 + data 对象(改版前一直用的结构)
const mockIframe = `
<html>
<script>
var ajaxdata = 'OLD_AJAX';
var skdkw = 'OLD_SIGN';
var websignkey = 'OLD_KEY';
$.ajax({
type : 'post',
url : '/ajaxm.php?file=555',
data : { 'action':'downprocess', 'signs':ajaxdata, 'sign':skdkw, 'ves':1, 'websignkey':websignkey },
dataType : 'json'
});
</script>
</html>
`

let posted: { url: string; form: URLSearchParams } | null = null
const originalFetch = globalThis.fetch
try {
(globalThis as any).fetch = async (url: string, init: RequestInit = {}) => {
const urlStr = String(url)
const method = (init.method || "GET").toUpperCase()
if (urlStr.includes("/fn?")) return new Response(mockIframe)
if (method === "POST") {
posted = { url: urlStr, form: new URLSearchParams(String(init.body || "")) }
return new Response(
JSON.stringify({ zt: 1, dom: "https://developer.lanzoui.com", url: "token_old" }),
)
}
if (urlStr.includes("developer.lanzoui.com/file")) {
return new Response(null, {
status: 302,
headers: { location: "https://cdn.example.com/legacy.zip" },
})
}
if (urlStr.includes("pan.lanzoui.com/b00test")) {
return new Response(mockFilePage)
}
return new Response("Not Found", { status: 404 })
}

const item = await driver.get("/b00test", "/legacy.zip")
const got = posted as { url: string; form: URLSearchParams } | null
if (!got) throw new Error("下载接口未被调用")
// 旧版仍应 POST 到分享域相对地址,而不是 apifile
if (got.url !== "https://pan.lanzoui.com/ajaxfile.php?file=555") {
throw new Error(`旧版下载接口地址错误: ${got.url}`)
}
if (got.form.get("signs") !== "OLD_AJAX") throw new Error("signs 未透传")
if (got.form.get("sign") !== "OLD_SIGN") throw new Error("sign 未透传")
if (item.raw_url !== "https://cdn.example.com/legacy.zip") {
throw new Error(`直链错误: ${item.raw_url}`)
}
} finally {
globalThis.fetch = originalFetch
}
})

// Clean up scratch files
try {
const fs = await import("fs")
Expand Down
18 changes: 17 additions & 1 deletion src/backend/drivers/lanzou/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export function removeJSComment(data: string): string {
inComment = true
i++
continue
} else if (nextChar === "/") {
} else if (nextChar === "/" && !isUrlDoubleSlash(result)) {
inSingleLineComment = true
i++
continue
Expand All @@ -129,6 +129,22 @@ export function removeJSComment(data: string): string {
return result
}

/**
* 判断紧随其后的 '//' 是否为 URL 的一部分(而非行注释)。
* 两类已知误伤都要放过:
* 1. 'https://' —— scheme 冒号紧跟 '//'(改版后页面大量使用绝对地址);
* 2. 协议相对地址 '//'cdn...'' —— 出现在字符串字面量里,前一个已输出字符是引号。
* 宁多保留、不误删:多留一段注释文本无害,截断真实 URL 致命。
*/
function isUrlDoubleSlash(emitted: string): boolean {
return (
emitted.endsWith(":") ||
emitted.endsWith("'") ||
emitted.endsWith('"') ||
emitted.endsWith("`")
)
}

function unbox(hexStr: string): string {
const box = [
6, 28, 34, 31, 33, 18, 30, 23, 9, 8, 19, 38, 17, 24, 0, 5, 32, 21, 10, 22,
Expand Down
Loading
Loading