前一阵子写了一个cf worker用于b2的下载鉴权(参考了kun的这篇文章 使用 Cloudflare Workers 实现 B2 私有存储桶文件下载 | KUN's Blog),实现的效果就是
传入:文件路径
输出:签名完成后的下载链接
鉴权部分参考了b2的文档
Download Files with the Native API
Download Your Desired File From the Backblaze Cloud by Name
参考代码如下
async function handleAuthRequest(request) {
// 处理预检
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, X-Gal-Id, X-User-Id, X-Timestamp, X-Signature",
"Access-Control-Max-Age": "86400"
}
})
}
// 验证请求
const validation = await validateRequest(request)
if (!validation.valid) {
return new Response(JSON.stringify({
success: false,
error: validation.error,
message: '未授权的请求'
}), {
status: 403,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
}
})
}
try {
const url = new URL(request.url)
const filePath = url.searchParams.get('path')
if (!filePath) {
return new Response(JSON.stringify({
success: false,
error: "missing required query",
message: '缺少文件路径参数'
}), {
status: 400,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
}
})
}
// 获取 B2 令牌
const urlData = await createShortLivedUrl(filePath)
const b2Token = urlData.authorizationToken
// 最终的下载URL
const downloadUrl = `https://file-download-cfcdn-02.yurari.moe/${filePath}?Authorization=${b2Token}`
return new Response(JSON.stringify({
success: true,
downloadUrl: downloadUrl
}), {
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
}
})
} catch (error) {
console.error("生成下载链接失败:", error)
return new Response(JSON.stringify({
success: false,
error: "生成下载链接失败: " + error.message
}), {
status: 500,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
}
})
}
}
这个worker一直运行良好,直到我今天碰到了这个文件
[140425][スミレ] 夏恋ハイプレッシャー 予約特典同梱 ソフマップ特典CD (iso+mds+wav+cue+rr3).rar
请求下载链接的步骤依旧没有出现任何问题(也就是说整个签名步骤是正常的,划重点),但是使用返回的下载链接进行下载时就出现问题了,显示
{
"code": "invalid_request",
"message": "",
"status": 400
}
我第一时间想到的就是各种字符的编码问题,因为最初的代码中,filePath 是没有进行任何编码的字符串,前端传过来什么就是什么,所以我就使用了encodeURI(filePath)对filePath 进行了一次编码,修改后是这个样子
const url = new URL(request.url)
const filePath = url.searchParams.get('path')
const encodedFilePath = encodeURI(filePath)
const urlData = await createShortLivedUrl(encodedFilePath)
const b2Token = urlData.authorizationToken
const downloadUrl = `https://file-download-cfcdn-02.yurari.moe/${encodedFilePath}?Authorization=${b2Token}`
但是遗憾的是,这样修改后,不仅这个文件会出现问题,正常的文件也出现问题了,全部变成了签名验证失败(403)
经过我的一番折腾,发现问题在于:B2授权用的路径和最终下载URL的路径编码格式不一致
于是我尝试分离这两个步骤:
const url = new URL(request.url)
const filePath = url.searchParams.get('path')
const encodedFilePath = encodeURI(filePath)
// 授权用原始路径
const urlData = await createShortLivedUrl(filePath)
const b2Token = urlData.authorizationToken
// 下载URL用编码路径
const downloadUrl = `https://file-download-cfcdn-02.yurari.moe/${encodedFilePath}?Authorization=${b2Token}`
这样修改后,不带+号的文件恢复正常了,但带+号的文件还是404:
{
"code": "not_found",
"message": "File with such name does not exist.",
"status": 404
}
经过一波debug,我发现了一个非常隐蔽的问题:URLSearchParams.get()会自动将查询参数中的+号解释为空格!!!
这是Web标准中的行为,在application/x-www-form-urlencoded格式中,+号就是用来表示空格的。所以:
原始文件名: file+(iso+mds).rar
经过 URLSearchParams.get() 后变成: file (iso mds).rar


