From bd422066869b48ba4a920b04409fd398e5400f2d Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:36:31 +0200 Subject: [PATCH] fix: accept a pasted JMAP session URL as the server address #971 --- src/lib/__tests__/server-discovery.test.ts | 15 +++++++++++++++ src/lib/server-discovery.ts | 11 ++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/lib/__tests__/server-discovery.test.ts b/src/lib/__tests__/server-discovery.test.ts index 1e4e2d06..72e78853 100644 --- a/src/lib/__tests__/server-discovery.test.ts +++ b/src/lib/__tests__/server-discovery.test.ts @@ -50,6 +50,21 @@ describe('normalizeServerUrl', () => { expect(normalizeServerUrl('mail.example.com/jmap')).toBe('https://mail.example.com'); }); + it('strips a pasted session URL with its query or fragment (#971)', () => { + expect(normalizeServerUrl('https://mail.example.com/jmap/session')).toBe('https://mail.example.com'); + expect(normalizeServerUrl('mail.example.com/jmap/session/')).toBe('https://mail.example.com'); + expect(normalizeServerUrl('https://mail.example.com/.well-known/jmap?x=1#top')).toBe( + 'https://mail.example.com', + ); + expect(normalizeServerUrl('https://mail.example.com/?x=1')).toBe('https://mail.example.com'); + }); + + it('keeps a base path prefix in front of the session suffix', () => { + expect(normalizeServerUrl('https://example.com/mail/jmap/session')).toBe('https://example.com/mail'); + expect(normalizeServerUrl('https://example.com/mail/.well-known/jmap')).toBe('https://example.com/mail'); + expect(normalizeServerUrl('https://example.com/mail')).toBe('https://example.com/mail'); + }); + it('keeps an explicit http scheme and port', () => { expect(normalizeServerUrl('http://localhost:8080')).toBe('http://localhost:8080'); }); diff --git a/src/lib/server-discovery.ts b/src/lib/server-discovery.ts index 2254f67e..e21da1a9 100644 --- a/src/lib/server-discovery.ts +++ b/src/lib/server-discovery.ts @@ -34,8 +34,8 @@ const EMAIL_RE = /^[^\s@]+@([^\s@]+\.[^\s@]+)$/; /** * Accept what people actually type — `mail.example.com`, `example.com/`, - * `https://mail.example.com/.well-known/jmap` — and return the base URL the - * JMAP client expects, or null if it can't be one. + * `https://mail.example.com/.well-known/jmap`, `…/jmap/session` — and return + * the base URL the JMAP client expects, or null if it can't be one. */ export function normalizeServerUrl(input: string): string | null { let value = input.trim(); @@ -57,11 +57,12 @@ export function normalizeServerUrl(input: string): string | null { if (/^http:\/\//i.test(value) && !isLocalHost(value)) return null; // Someone pasting the endpoint they found in the webmail's settings gets the - // same result as someone typing the address they use in the browser. + // same result as someone typing the address they use in the browser. Only + // the session or API suffix goes, so a base path prefix survives (#971). value = value + .replace(/[?#].*$/, '') .replace(/\/+$/, '') - .replace(/\/\.well-known\/jmap$/i, '') - .replace(/\/jmap$/i, '') + .replace(/\/(?:jmap\/session|\.well-known\/jmap|jmap)$/i, '') .replace(/\/+$/, ''); const host = value.slice(value.indexOf('://') + 3).split('/')[0];