mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-27 05:30:39 +00:00
fix(i18n): survive a malformed locale cookie (#7553)
* fix(i18n): survive a malformed locale cookie getLocaleFromCookie decoded the raw cookie value without a guard, so a malformed percent-encoding threw URIError during locale bootstrap and in formatTimeAgo. It also split the cookie on every "=", truncating values that contain one. Decode defensively and split on the first "=" only. * fix(i18n): normalize the cookie locale in formatTimeAgo The second caller of getLocaleFromCookie cast the raw value to Locale, so a malformed-but-known-prefixed cookie skipped normalization. Normalize it like useI18n does, and cover the malformed and valid cookie paths. --------- Co-authored-by: 一片一片 <192845873+gdut4140@users.noreply.github.com>
This commit is contained in:
parent
19768d73cc
commit
a2fd094b40
@ -15,9 +15,21 @@ export function getLocaleFromCookie(): string | null {
|
||||
|
||||
const cookies = document.cookie.split(";");
|
||||
for (const cookie of cookies) {
|
||||
const [name, value] = cookie.trim().split("=");
|
||||
if (name === LOCALE_COOKIE_NAME) {
|
||||
return decodeURIComponent(value ?? "");
|
||||
// Split on the first "=" only: a cookie value may contain "=" itself.
|
||||
const separator = cookie.indexOf("=");
|
||||
if (separator < 0) {
|
||||
continue;
|
||||
}
|
||||
if (cookie.slice(0, separator).trim() !== LOCALE_COOKIE_NAME) {
|
||||
continue;
|
||||
}
|
||||
const value = cookie.slice(separator + 1).trim();
|
||||
try {
|
||||
return decodeURIComponent(value);
|
||||
} catch {
|
||||
// A malformed value must not crash locale bootstrap; hand the raw
|
||||
// value to normalizeLocale, which falls back to the default locale.
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { enUS as dateFnsEnUS, zhCN as dateFnsZhCN } from "date-fns/locale";
|
||||
|
||||
import { detectLocale, type Locale } from "@/core/i18n";
|
||||
import { detectLocale, normalizeLocale, type Locale } from "@/core/i18n";
|
||||
import { getLocaleFromCookie } from "@/core/i18n/cookies";
|
||||
|
||||
function getDateFnsLocale(locale: Locale) {
|
||||
@ -15,9 +15,9 @@ function getDateFnsLocale(locale: Locale) {
|
||||
}
|
||||
|
||||
export function formatTimeAgo(date: Date | string | number, locale?: Locale) {
|
||||
const savedLocale = locale ?? getLocaleFromCookie();
|
||||
const effectiveLocale =
|
||||
locale ??
|
||||
(getLocaleFromCookie() as Locale | null) ??
|
||||
(savedLocale ? normalizeLocale(savedLocale) : null) ??
|
||||
// Fallback when cookie is missing (or on first render)
|
||||
detectLocale();
|
||||
// Guard against invalid/empty timestamps (e.g. a backend returning "" for
|
||||
|
||||
26
frontend/tests/unit/core/i18n/cookies.dom.test.tsx
Normal file
26
frontend/tests/unit/core/i18n/cookies.dom.test.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { afterEach, expect, test } from "@rstest/core";
|
||||
|
||||
import { getLocaleFromCookie } from "@/core/i18n/cookies";
|
||||
|
||||
function setLocaleCookie(value: string) {
|
||||
document.cookie = `locale=${value}; path=/`;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
document.cookie = "locale=; max-age=0; path=/";
|
||||
});
|
||||
|
||||
test("decodes an encoded locale", () => {
|
||||
setLocaleCookie("zh-CN");
|
||||
expect(getLocaleFromCookie()).toBe("zh-CN");
|
||||
});
|
||||
|
||||
test("does not throw on a malformed percent-encoding", () => {
|
||||
setLocaleCookie("%E0%A4%A");
|
||||
expect(getLocaleFromCookie()).toBe("%E0%A4%A");
|
||||
});
|
||||
|
||||
test("keeps a cookie value that contains '='", () => {
|
||||
setLocaleCookie("en=US");
|
||||
expect(getLocaleFromCookie()).toBe("en=US");
|
||||
});
|
||||
17
frontend/tests/unit/core/utils/datetime.dom.test.ts
Normal file
17
frontend/tests/unit/core/utils/datetime.dom.test.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { afterEach, expect, test } from "@rstest/core";
|
||||
|
||||
import { formatTimeAgo } from "@/core/utils/datetime";
|
||||
|
||||
afterEach(() => {
|
||||
document.cookie = "locale=; max-age=0; path=/";
|
||||
});
|
||||
|
||||
test("falls back to a known locale for a malformed locale cookie", () => {
|
||||
document.cookie = "locale=zh%FF; path=/";
|
||||
expect(() => formatTimeAgo(Date.now() - 5_000)).not.toThrow();
|
||||
});
|
||||
|
||||
test("uses the locale from the cookie", () => {
|
||||
document.cookie = "locale=zh-CN; path=/";
|
||||
expect(formatTimeAgo(Date.now() - 5_000)).toContain("前");
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user