From 5e680814e196e315f597c3240d3db35cbcc9462a Mon Sep 17 00:00:00 2001 From: carsonxu <459452372@qq.com> Date: Wed, 24 Sep 2025 00:27:10 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9BFix(tcyun):=20compatible=20tencent?= =?UTF-8?q?=20cos=20bucket=20with=20strict=20signature=20mode=20enabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/uploader/tcyun.ts | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/plugins/uploader/tcyun.ts b/src/plugins/uploader/tcyun.ts index ecb6d1e..917983e 100644 --- a/src/plugins/uploader/tcyun.ts +++ b/src/plugins/uploader/tcyun.ts @@ -13,7 +13,20 @@ export interface ISignature { signTime: string } -const generateSignature = (options: ITcyunConfig, fileName: string): ISignature => { +const cosSafeUrlEncode = (str: string): string => { + return encodeURIComponent(str) + .replace(/!/g, '%21') + .replace(/'/g, '%27') + .replace(/\(/g, '%28') + .replace(/\)/g, '%29') + .replace(/\*/g, '%2A') +} + +const generateContentType = (fileName: string): string => { + return mime.lookup(fileName) || 'application/octet-stream' +} + +const generateSignature = (options: ITcyunConfig, fileName: string, contentType: string, contentLength: number): ISignature => { const secretId = options.secretId const secretKey = options.secretKey const appId = options.appId @@ -37,7 +50,7 @@ const generateSignature = (options: ITcyunConfig, fileName: string): ISignature signTime = `${today};${tomorrow}` const signKey = crypto.createHmac('sha1', secretKey).update(signTime).digest('hex') const endpoint = options.endpoint ? options.endpoint : `cos.${options.area}.myqcloud.com` - const httpString = `put\n/${options.path}${fileName}\n\nhost=${options.bucket}.${endpoint}\n` + const httpString = `put\n/${options.path}${fileName}\n\ncontent-length=${contentLength}&content-type=${cosSafeUrlEncode(contentType)}&host=${options.bucket}.${endpoint}\n` const sha1edHttpString = crypto.createHash('sha1').update(httpString).digest('hex') const stringToSign = `sha1\n${signTime}\n${sha1edHttpString}\n` signature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex') @@ -78,8 +91,9 @@ const postOptions = (options: ITcyunConfig, fileName: string, signature: ISignat url: `http://${options.bucket}.${endpoint}/${encodeURI(path)}${encodeURIComponent(fileName)}`, headers: { Host: `${options.bucket}.${endpoint}`, - Authorization: `q-sign-algorithm=sha1&q-ak=${options.secretId}&q-sign-time=${signature.signTime}&q-key-time=${signature.signTime}&q-header-list=host&q-url-param-list=&q-signature=${signature.signature}`, - contentType: mime.lookup(fileName), + Authorization: `q-sign-algorithm=sha1&q-ak=${options.secretId}&q-sign-time=${signature.signTime}&q-key-time=${signature.signTime}&q-header-list=content-length;content-type;host&q-url-param-list=&q-signature=${signature.signature}`, + contentType: generateContentType(fileName), + contentLength: image.byteLength, 'User-Agent': `PicGo;${version};null;null` }, body: image, @@ -100,14 +114,14 @@ const handle = async (ctx: IPicGo): Promise => { const useV4 = !tcYunOptions.version || tcYunOptions.version === 'v4' for (const img of imgList) { if (img.fileName && img.buffer) { - const signature = generateSignature(tcYunOptions, img.fileName) - if (!signature) { - return false - } let image = img.buffer if (!image && img.base64Image) { image = Buffer.from(img.base64Image, 'base64') } + const signature = generateSignature(tcYunOptions, img.fileName, generateContentType(img.fileName), image.byteLength) + if (!signature) { + return false + } const options = postOptions(tcYunOptions, img.fileName, signature, image, ctx.GUI_VERSION || ctx.VERSION) const res = await ctx.request(options) .then((res: any) => res)