diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/utils/fetch/__tests__/console-fetch.spec.ts b/frontend/packages/console-dynamic-plugin-sdk/src/utils/fetch/__tests__/console-fetch.spec.ts index 12d298c40f6..0cea5359eb5 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/utils/fetch/__tests__/console-fetch.spec.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/utils/fetch/__tests__/console-fetch.spec.ts @@ -1,6 +1,30 @@ import { RetryError } from '../../error/http-error'; import { consoleFetch } from '../console-fetch'; -import { shouldLogout, validateStatus } from '../console-fetch-utils'; +import { shouldLogout, unescapeGoUnicode, validateStatus } from '../console-fetch-utils'; + +describe('unescapeGoUnicode', () => { + it('should unescape 4-digit Go unicode escapes', () => { + expect(unescapeGoUnicode('\\ue00f')).toBe('\ue00f'); + expect(unescapeGoUnicode('\\ue4c8')).toBe('\ue4c8'); + }); + + it('should unescape 8-digit Go unicode escapes for supplementary plane characters', () => { + expect(unescapeGoUnicode('\\U0002ebf0')).toBe(String.fromCodePoint(0x2ebf0)); + expect(unescapeGoUnicode('\\U0002ebf1')).toBe(String.fromCodePoint(0x2ebf1)); + }); + + it('should unescape mixed content with normal text and escapes', () => { + const input = 'a啊阿沸犯跃kg\\ue00f\\ue010\\ue011\\ue4c8丙乩h妖哪匸与f去\\U0002ebf0\\U0002ebf1'; + const expected = `a啊阿沸犯跃kg\ue00f\ue010\ue011\ue4c8丙乩h妖哪匸与f去${String.fromCodePoint( + 0x2ebf0, + )}${String.fromCodePoint(0x2ebf1)}`; + expect(unescapeGoUnicode(input)).toBe(expected); + }); + + it('should not throw on out-of-range 8-digit escape sequences', () => { + expect(unescapeGoUnicode('\\UFFFFFFFF')).toBe('\\UFFFFFFFF'); + }); +}); describe('consoleFetch', () => { const json = async () => ({ diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/utils/fetch/console-fetch-utils.ts b/frontend/packages/console-dynamic-plugin-sdk/src/utils/fetch/console-fetch-utils.ts index fc3c5cfe5d7..52b9f3823c0 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/utils/fetch/console-fetch-utils.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/utils/fetch/console-fetch-utils.ts @@ -146,6 +146,18 @@ export const shouldLogout = (url: string): boolean => { return false; }; +/** + * Converts Go-style Unicode escape sequences (\uXXXX, \UXXXXXXXX) in K8s API error + * messages back to actual Unicode characters for proper display in the browser. + */ +export const unescapeGoUnicode = (str: string): string => + str + .replace(/\\U([0-9a-fA-F]{8})/g, (match, hex) => { + const codePoint = parseInt(hex, 16); + return codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : match; + }) + .replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16))); + export const validateStatus = async ( response: Response, url: string, @@ -183,7 +195,7 @@ export const validateStatus = async ( if (response.status === 403) { return response.json().then((json) => { throw new HttpError( - json.message || 'Access denied due to cluster policy.', + unescapeGoUnicode(json.message || 'Access denied due to cluster policy.'), response.status, response, json, @@ -217,6 +229,6 @@ export const validateStatus = async ( reason = response.statusText; } - throw new HttpError(reason, response.status, response, json); + throw new HttpError(unescapeGoUnicode(reason), response.status, response, json); }); };