Skip to content

Commit 50b3e4e

Browse files
longsizhuogithub-actions[bot]
authored andcommitted
fix(editor): 上传前 normalize file.type,避免 R2 SignatureDoesNotMatch
服务端现在用 primaryMime(split(';')[0].trim().toLowerCase())绑进 PutObjectCommand.ContentType, 客户端 PUT 时的 Content-Type header 必须 byte-exact 对得上,否则 R2 返 403 SignatureDoesNotMatch。 之前客户端直接 file.type 透传 —— 浏览器在少见情况下会给 'Image/JPEG'(大小写混合)或 'image/jpeg; foo=bar'(带参数),都和服务端签名不一致,真实上传失败。 改法:uploadImage() 开头算一次 primaryMime = file.type.split(';')[0].trim().toLowerCase(), POST /api/upload 的 body.contentType 和后续 PUT 的 Content-Type header 都用同一个值。 空串(浏览器识别不出 MIME)走本地 throw,走 handlePublish 里的 alert,比让服务端 400 更直观。
1 parent 352e83c commit 50b3e4e

1 file changed

Lines changed: 18 additions & 3 deletions

File tree

app/editor/EditorPageClient.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
8282
file: File,
8383
articleSlug: string,
8484
): Promise<{ blobUrl: string; publicUrl: string }> => {
85+
// 规范化 Content-Type:只取主 MIME(分号前)+ trim + 小写。
86+
// 服务端预签名 URL 绑的是这个规范化后的 ContentType,客户端 PUT 时的
87+
// Content-Type header 必须 byte-exact 对得上,否则 R2 返 403 SignatureDoesNotMatch。
88+
// 浏览器 file.type 在极少见情况下可能是 "Image/JPEG" 或 "image/jpeg; foo=bar",
89+
// 不能直接原样透传。
90+
const primaryMime = file.type.split(";")[0]!.trim().toLowerCase();
91+
if (!primaryMime) {
92+
// 浏览器识别不出 MIME(某些冷门类型会给空串)。此时继续走会被服务端 MIME_PATTERN
93+
// 正则直接 400,给个本地报错更清晰,和 editor 里其它 throw -> handlePublish alert 的
94+
// 链路一致。
95+
throw new Error(
96+
`无法识别图片类型:${file.name}(浏览器未给出 MIME),请另存为 PNG/JPG/WebP 后重试`,
97+
);
98+
}
99+
85100
// 1. 获取预签名 URL(带 x-satoken 请求头,供服务端验证身份)
86101
const token = localStorage.getItem("satoken") ?? "";
87102
const response = await fetch("/api/upload", {
@@ -92,7 +107,7 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
92107
},
93108
body: JSON.stringify({
94109
filename: file.name,
95-
contentType: file.type,
110+
contentType: primaryMime,
96111
articleSlug,
97112
fileSize: file.size,
98113
}),
@@ -105,11 +120,11 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
105120

106121
const { uploadUrl, publicUrl } = await response.json();
107122

108-
// 2. 上传文件到 R2
123+
// 2. 上传文件到 R2 —— Content-Type 必须和签名时服务端绑的 primaryMime byte-exact 一致
109124
const uploadResponse = await fetch(uploadUrl, {
110125
method: "PUT",
111126
headers: {
112-
"Content-Type": file.type,
127+
"Content-Type": primaryMime,
113128
},
114129
body: file,
115130
});

0 commit comments

Comments
 (0)