fix(notifications): RFC 2047-encode ntfy header umlauts (#1621)

This commit is contained in:
Konstantinos Thermos
2026-07-20 13:32:55 +02:00
committed by GitHub
parent 0b4737448d
commit 229ec6d18e
2 changed files with 14 additions and 1 deletions
+1 -1
View File
@@ -513,7 +513,7 @@ export function resolveAdminNtfyUrl(adminCfg: NtfyConfig): string | null {
function encodeHeaderValue(value: string): string {
for (let i = 0; i < value.length; i++) {
if (value.charCodeAt(i) > 0xff) {
if (value.charCodeAt(i) > 0x7f) {
return `=?UTF-8?B?${Buffer.from(value, 'utf8').toString('base64')}?=`;
}
}
@@ -488,6 +488,19 @@ describe('sendNtfy', () => {
expect(Buffer.from(b64, 'base64').toString('utf8')).toBe('Buy →€ ticket');
});
it('NTFY-009b — title with Latin-1 umlauts is RFC 2047 encoded, not sent as raw bytes', async () => {
const mockFetch = globalThis.fetch as unknown as ReturnType<typeof vi.fn>;
mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '' } as never);
await sendNtfy(ntfyUrl, null, { ...payload, title: 'Frühstück in Zürich' });
const [, calledOpts] = mockFetch.mock.calls[0];
const encoded = calledOpts.headers['Title'] as string;
expect(encoded).toMatch(/^=\?UTF-8\?B\?/);
const b64 = encoded.replace(/^=\?UTF-8\?B\?/, '').replace(/\?=$/, '');
expect(Buffer.from(b64, 'base64').toString('utf8')).toBe('Frühstück in Zürich');
});
it('NTFY-010 — ASCII-only title is passed through verbatim', async () => {
const mockFetch = globalThis.fetch as unknown as ReturnType<typeof vi.fn>;
mockFetch.mockResolvedValueOnce({ ok: true, text: async () => '' } as never);