diff --git a/src/renderer/src/utils/__tests__/naming.test.ts b/src/renderer/src/utils/__tests__/naming.test.ts index 43af9d432b..ff93db25f9 100644 --- a/src/renderer/src/utils/__tests__/naming.test.ts +++ b/src/renderer/src/utils/__tests__/naming.test.ts @@ -330,17 +330,53 @@ describe('naming', () => { expect(sanitizeProviderName('My Provider')).toBe('My-Provider') }) - it('should replace dangerous characters with underscores', () => { - expect(sanitizeProviderName('Provider/Name')).toBe('Provider_Name') + it('should strip characters outside env-var-safe whitelist', () => { + expect(sanitizeProviderName('Provider/Name')).toBe('ProviderName') }) it('should handle mixed special characters', () => { - expect(sanitizeProviderName('My Provider :name')).toBe('My-Provider-_test__name') + expect(sanitizeProviderName('My Provider :name')).toBe('My-Provider-testname') }) it('should return empty string for empty input', () => { expect(sanitizeProviderName('')).toBe('') }) + + it('should fall back to hash for pure non-ASCII names', () => { + expect(sanitizeProviderName('测试')).toMatch(/^p_[a-z0-9]+$/) + // deterministic: same input produces same hash + expect(sanitizeProviderName('测试')).toBe(sanitizeProviderName('测试')) + }) + + it('should handle various non-ASCII characters', () => { + // Chinese + expect(sanitizeProviderName('测试')).toMatch(/^p_[a-z0-9]+$/) + // Japanese + expect(sanitizeProviderName('プロバイダー')).toMatch(/^p_[a-z0-9]+$/) + // Korean + expect(sanitizeProviderName('공급자')).toMatch(/^p_[a-z0-9]+$/) + // Emoji + expect(sanitizeProviderName('🎉provider')).toBe('provider') + }) + + it('should produce a valid env var identifier for mixed ASCII and non-ASCII', () => { + expect(sanitizeProviderName('日本語Provider')).toBe('Provider') + expect(sanitizeProviderName('My 测试 Provider')).toBe('My-Provider') + }) + + it('should strip ASCII symbols not allowed in env var names', () => { + expect(sanitizeProviderName('foo@bar')).toBe('foobar') + expect(sanitizeProviderName('foo@bar+baz(test)')).toBe('foobarbaztest') + expect(sanitizeProviderName('my$provider!name')).toBe('myprovidername') + expect(sanitizeProviderName('a#b%c&d')).toBe('abcd') + }) + + it('should keep allowed env-var-safe characters', () => { + expect(sanitizeProviderName('my-provider')).toBe('my-provider') + expect(sanitizeProviderName('my_provider')).toBe('my_provider') + expect(sanitizeProviderName('my.provider')).toBe('my.provider') + expect(sanitizeProviderName('Provider123')).toBe('Provider123') + }) }) describe('truncateText', () => { diff --git a/src/renderer/src/utils/naming.ts b/src/renderer/src/utils/naming.ts index a1b2717061..d910a63135 100644 --- a/src/renderer/src/utils/naming.ts +++ b/src/renderer/src/utils/naming.ts @@ -207,16 +207,28 @@ export function getBriefInfo(text: string, maxLength: number = 50): string { } /** - * 清理 provider 名称,用于环境变量值: - * - 替换空格为短横线 - * - 替换其他危险字符为下划线 + * 清理 provider 名称,用于环境变量名: + * - 只保留 [a-zA-Z0-9_\s.-](白名单) + * - 空格转短横线(下游会把 - 和 . 再转 _) + * - 清理后为空时用 hash 兜底 * @param {string} name 输入字符串 * @returns {string} 清理后的字符串 */ export function sanitizeProviderName(name: string): string { - return name + if (!name) return name + + const sanitized = name + .replace(/[^a-zA-Z0-9_\s.-]/g, '') // whitelist: only keep env-var-safe chars .replace(/\s+/g, '-') // spaces -> dashes - .replace(/[<>:"|?*\\/_]/g, '_') // dangerous chars -> underscores + + if (!sanitized) { + let hash = 0 + for (let i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0 + } + return 'p_' + Math.abs(hash).toString(36) + } + return sanitized } /**