mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
fix(feishu): recover mojibake filenames from Content-Disposition (#72388)
This commit is contained in:
@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Memory/Dreaming: retry Dream Diary once with the session default when a configured dreaming model is unavailable, while leaving subagent trust and allowlist errors visible instead of silently masking configuration problems. Refs #67409 and #69209. Thanks @Ghiggins18 and @everySympathy.
|
||||
- Feishu/inbound files: recover CJK filenames from plain `Content-Disposition: filename=` download headers when Feishu exposes UTF-8 bytes through Latin-1 header decoding, while leaving valid Latin-1 and JSON-derived names unchanged. (#48578, #50435, #59431) Thanks @alex-xuweilong, @lishuaigit, and @DoChaoing.
|
||||
|
||||
## 2026.4.27
|
||||
|
||||
|
||||
@@ -716,4 +716,60 @@ describe("downloadMessageResourceFeishu", () => {
|
||||
fileName: "clip.mp4",
|
||||
});
|
||||
});
|
||||
|
||||
it("recovers CJK filenames from plain Content-Disposition headers decoded as Latin-1", async () => {
|
||||
const fileName = "武汉15座山登山信息汇总.csv";
|
||||
const latin1HeaderFileName = Buffer.from(fileName, "utf8").toString("latin1");
|
||||
messageResourceGetMock.mockResolvedValueOnce({
|
||||
data: Buffer.from("fake-file-data"),
|
||||
headers: {
|
||||
"content-disposition": `attachment; filename="${latin1HeaderFileName}"`,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await downloadMessageResourceFeishu({
|
||||
cfg: emptyConfig,
|
||||
messageId: "om_file_msg",
|
||||
fileKey: "file_key_csv",
|
||||
type: "file",
|
||||
});
|
||||
|
||||
expect(result.fileName).toBe(fileName);
|
||||
});
|
||||
|
||||
it("keeps valid Latin-1 filenames from plain Content-Disposition headers unchanged", async () => {
|
||||
messageResourceGetMock.mockResolvedValueOnce({
|
||||
data: Buffer.from("fake-file-data"),
|
||||
headers: {
|
||||
"content-disposition": `attachment; filename="café-©.txt"`,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await downloadMessageResourceFeishu({
|
||||
cfg: emptyConfig,
|
||||
messageId: "om_latin1_msg",
|
||||
fileKey: "file_key_latin1",
|
||||
type: "file",
|
||||
});
|
||||
|
||||
expect(result.fileName).toBe("café-©.txt");
|
||||
});
|
||||
|
||||
it("keeps JSON-derived file_name metadata unchanged", async () => {
|
||||
const fileName = "武汉15座山登山信息汇总.csv";
|
||||
const latin1LookingFileName = Buffer.from(fileName, "utf8").toString("latin1");
|
||||
messageResourceGetMock.mockResolvedValueOnce({
|
||||
data: Buffer.from("fake-file-data"),
|
||||
file_name: latin1LookingFileName,
|
||||
});
|
||||
|
||||
const result = await downloadMessageResourceFeishu({
|
||||
cfg: emptyConfig,
|
||||
messageId: "om_json_file_msg",
|
||||
fileKey: "file_key_json",
|
||||
type: "file",
|
||||
});
|
||||
|
||||
expect(result.fileName).toBe(latin1LookingFileName);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -146,6 +146,18 @@ function readHeaderValue(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function containsEastAsianScript(value: string): boolean {
|
||||
return /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u.test(value);
|
||||
}
|
||||
|
||||
function recoverUtf8FileNameFromLatin1Header(value: string): string {
|
||||
const recovered = Buffer.from(value, "latin1").toString("utf8");
|
||||
if (recovered !== value && !recovered.includes("\uFFFD") && containsEastAsianScript(recovered)) {
|
||||
return recovered;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function decodeDispositionFileName(value: string): string | undefined {
|
||||
const utf8Match = value.match(/filename\*=UTF-8''([^;]+)/i);
|
||||
if (utf8Match?.[1]) {
|
||||
@@ -157,7 +169,8 @@ function decodeDispositionFileName(value: string): string | undefined {
|
||||
}
|
||||
|
||||
const plainMatch = value.match(/filename="?([^";]+)"?/i);
|
||||
return plainMatch?.[1]?.trim();
|
||||
const plainFileName = plainMatch?.[1]?.trim();
|
||||
return plainFileName ? recoverUtf8FileNameFromLatin1Header(plainFileName) : undefined;
|
||||
}
|
||||
|
||||
function extractFeishuDownloadMetadata(response: FeishuDownloadResponse): {
|
||||
|
||||
Reference in New Issue
Block a user