Commit Graph
282 Commits
Author SHA1 Message Date
Linus Rath 1807b2df6b feat: allow setting an address book as default (#924) 2026-08-29 17:15:50 +02:00
Linus Rath 39dcc6b219 fix: invalidate persisted update status after an upgrade 2026-08-29 12:46:39 +02:00
Linus Rath 1e2409f8b9 fix: keep basePath when normalizing Chinese Accept-Language 2026-08-29 12:35:38 +02:00
Linus Rath 42f736b96d Merge pull request #932 from kchuang1015/feat/zh-tw-locale
feat(i18n): add Traditional Chinese (Taiwan) locale
2026-08-29 12:32:02 +02:00
Linus Rath 4fe5701b3b feat: default sidebar apps for all users #931 2026-08-29 12:31:17 +02:00
Linus Rath d800689924 feat: use Stalwart synthetic ids for recurring occurrences #140 2026-08-29 11:03:48 +02:00
Kuan-Chieh Huang f1736b63c9 fix(i18n): preserve locale cookie precedence 2026-08-29 13:04:25 +08:00
Kuan-Chieh Huang 9fab1d1f4a fix(i18n): normalize Chinese proxy locale detection 2026-08-29 11:44:40 +08:00
Linus Rath 19756e4337 fix: pin resolved IP for stored custom JMAP endpoints and branding URLs 2026-08-26 23:02:19 +02:00
Linus Rath 3288589e52 fix: pin resolved IP at connect time to block DNS-rebinding SSRF (GHSA-24w9-8r42-8jwm) 2026-08-26 22:11:06 +02:00
Linus Rath 4bf3a3f8bb feat: re-sync existing push registrations on app start 2026-08-26 21:56:58 +02:00
Linus Rath a1ed482a6e test: type expected emailPush fixture so urgency matches EmailPushConfig 2026-08-26 21:47:39 +02:00
Linus Rath 63fa2d2d93 fix: stop notifying for spam by using the JMAP emailPush delivery filter 2026-08-26 21:45:12 +02:00
Linus Rath ccf199f7ed Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-26 19:29:48 +02:00
Linus Rath 67be0446e3 fix: forward every request header from the proxy so Next 16.3's RSC hash check stops 307-looping navigations #919 2026-08-26 19:28:31 +02:00
Stefan Hildebrandt ced81835ef fix(calendar): drop default SKIP=OMIT from recurrence rules (DAVx5 sync)
Recurring events defaulted skip:"omit"+rscale:"gregorian" on every rule,
which Stalwart serialises into the CalDAV RRULE as RSCALE=GREGORIAN;SKIP=OMIT.
DAVx5 rejects that ("Invalid recurrence rule"), breaking Android sync. Those
RFC 7529 fields only matter for non-Gregorian scales / invalid-date handling,
so stop emitting them for plain Gregorian rules.

- lib/recurrence-rule.ts: createRecurrenceRule() helper with no skip/rscale.
- CalendarRecurrenceRule.rscale/skip are now optional.
- event-modal + recurrence-editor build rules via the helper.
- demo fixture cleaned; unit test asserts no skip/rscale.

Closes #805.
2026-08-25 21:13:34 +02:00
Linus Rath 67f1790299 fix: close IPv6 transition-address and redirect bypasses in endpoint guard 2026-08-25 20:50:35 +02:00
Linus Rath 99daa53c8c feat: search suggestions with recent searches and contact autocomplete #845 2026-08-25 20:09:18 +02:00
Linus Rath 55ef780342 fix: cap live SSE streams per tab so many logins can't starve JMAP requests #702 2026-08-25 20:03:29 +02:00
Linus Rath 0b20f60579 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-25 19:52:02 +02:00
Linus Rath af5fac6baa fix: flip $junk/$notjunk keywords on spam and not-spam #850 2026-08-25 19:36:52 +02:00
Linus Rath 3472bc0419 fix: parse calendar invitations in directly viewed shared folders against the folder owner #867 2026-08-25 19:36:04 +02:00
Linus Rath 987ed75aaf Merge pull request #861 from dealerweb/fix/fresh-compose-tab-title
fix: keep the fresh-compose tab title clear of the selected email subject
2026-08-25 19:26:54 +02:00
Linus Rath 1faa6f6900 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-25 19:22:50 +02:00
Linus Rath 3ef2b45fb4 feat: configurable message-list ordering #718 2026-08-25 19:20:48 +02:00
Linus Rath 9f32fc879b feat: add custom time zone setting overriding browser detection #755 2026-08-25 19:18:16 +02:00
Linus Rath 46ccd2267d fix: pin supported-calendar-component-set on created calendars #760 2026-08-25 19:16:20 +02:00
Linus Rath 8cd5c00b06 fix: keep the folder tree when a refresh burst hits maxConcurrentRequests #780 2026-08-25 19:09:21 +02:00
Linus Rath 8a7f8b0493 fix: gate first-touch calendar/contacts requests to stop duplicate default calendars #907
Stalwart lazily creates an account's default calendar on the first request
that touches it. Bulwark's login fires Calendar/get and CalendarEvent/query
concurrently, and on multi-node servers each node can create its own default,
leaving users with two identical "Personal" calendars. Hold later calendar and
address-book requests per account until the first one settles.
2026-08-25 19:07:01 +02:00
Linus Rath e838794133 Merge pull request #895 from romanvru/feat/upload-progress
Real byte progress for attachment uploads, stock and plugin-offloaded
2026-08-25 18:55:07 +02:00
Linus Rath a29cf27ff8 fix: route multi-account email lookups and invitation parsing by source account #847 2026-08-25 18:50:50 +02:00
Linus Rath 5109aa059a fix: decode percent-encoded FileNode names from WebDAV-created nodes #869 2026-08-25 18:47:22 +02:00
Linus Rath f189e0bab6 fix: refresh account display name from the Stalwart principal on login/restore/switch #900
Stalwart seeds the default identity name from the principal "Full name" only
once, and addAccount never rewrote displayName after first login, so an admin
rename never showed up in Bulwark. Read the live name via x:Account/get
through the passthrough (falling back to the identity name elsewhere) and
prefer the synced registry name in account settings.
2026-08-25 18:45:44 +02:00
Linus Rath ed340cc2c1 feat: make Stalwart admin access to the dashboard configurable #870 2026-08-25 18:39:07 +02:00
Roman Varlakov 7928f72ee2 feat: real byte progress for attachment uploads, stock and plugin-offloaded
The composer chip only knew a boolean: `uploading` flipped on, an
indeterminate bar animated at 40% width, and nothing anywhere carried a
percentage, so a large attachment on a slow link looked frozen for the
whole transfer. The JMAP client has been able to report real progress
since #333 gave uploadBlob an XHR path with onProgress; the composer
just never passed it.

Stock path: pass onProgress and the existing AbortController's signal to
client.uploadBlob. The chip's bar becomes determinate as soon as bytes
are reported (falling back to the indeterminate animation otherwise),
and cancel now aborts the transfer itself instead of being checked only
once the upload has finished.

Plugin offload path (#751): a plugin that offloads an attachment moves
the bytes itself via api.http.post, which the composer cannot observe.
It also cannot be handed a callback: the sandbox bridge marshals
functions host-to-plugin only, and the File a plugin holds is a
structured clone, so object identity cannot correlate its request back
to an attachment. The staged file id can: the composer generated it and
handed it to onBeforeBlobUpload. A plugin echoes it back as the new
optional `progressFileId` on the post options; the host sends the Blob
via XMLHttpRequest (fetch exposes no upload progress events) and reports
bytes into a host-side registry keyed by that id, which the composer
subscribes to before running the hook. Progress never crosses into the
sandbox, and reports for an id the composer is not tracking are dropped,
so the plugin API grows exactly one optional field. Calls without
progressFileId keep the fetch path untouched.

Closes #754
2026-08-23 19:44:56 +06:00
Linus Rath 6a3bb3f459 feat: redesign the unread favicon badge as a compact keyline badge 2026-08-20 16:49:31 +02:00
Linus Rath fc88fc6d3b feat: drag a mail to a new browser tab to open it fullscreen 2026-08-20 15:55:44 +02:00
Linus Rath 65e70a8088 feat: address bar reflects the focused Pro tab + live deep-link delivery to mounted surfaces 2026-08-20 15:35:41 +02:00
Linus Rath b9fe961ae8 fix: session cookie not stored for relative JMAP server URLs (dev mock logouts) 2026-08-20 14:42:29 +02:00
Linus Rath 50741d1131 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-20 14:00:42 +02:00
Linus Rath 9a6cc23876 fix: refresh TOTP-minted tokens with the default client id when no OAuth client is configured #873 2026-08-20 13:59:57 +02:00
Linus Rath a95267ba59 Merge pull request #853 from dev-hive-kazniisa/fix/jmap-keyword-patch-pointer
fix: keyword patches use an unescaped JSON Pointer, and a failed migration reports as a clean one
2026-08-20 13:53:39 +02:00
Linus Rath 3c299baa3e Merge pull request #831 from zisisvakras/fix/email-keyword-removal-uses-null
fix(jmap): remove keywords with null rather than false in Email/set
2026-08-20 13:37:42 +02:00
Linus Rath 5d2b937ae3 fix: keep attachments when re-opening a draft and destroy old draft versions only after a successful create/send #849 2026-08-20 13:25:25 +02:00
dealerweb d6ae84f5e8 fix: keep the fresh-compose tab title clear of the selected email subject
The Pro-mode bridge that turns an inline compose intent into a compose
tab derived its working title from the draft subject or, failing that,
the selected email's subject - regardless of mode. For reply/forward the
selected email is the message being acted on; for a fresh compose it is
whatever happens to be highlighted in the list, so "New Email" opened
with an unrelated subject in the tab strip (#856). Extract the rule into
buildComposeTabTitle: a fresh compose only ever uses its own draft
subject (mailto, resumed draft) and otherwise starts on the "New
message" fallback. Typing a subject already renames the tab live via
handleSaveState, so nothing else changes.
2026-08-18 15:48:05 +02:00
KazNIISA IT 5c484af1f7 fix(jmap): escape the JSON Pointer in keyword patches, and migrate a keyword honestly
A PatchObject key is a JSON Pointer (RFC 8620 section 5.3), so a slash
inside the member name it points at has to be escaped as `~1`. Keyword
patches were built as `keywords/${keyword}` verbatim, which is fine for
every flat keyword but wrong for a nested tag: `$label:work/clients`
became the pointer `keywords/$label:work/clients`, asking for the
`clients` member of the `$label:work` keyword rather than for the keyword
itself. The patch is not a boolean at that path, so the server rejects or
ignores it and the tag never lands.

Route the keyword patch sites - setKeyword, removeKeyword, migrateKeyword
and the category-tab batch patch - through `keywordPointer`, which escapes
per RFC 6901 the way the calendar store already does for participant ids.
Flat keywords are untouched, so `$seen`/`$flagged` and friends keep the
exact pointers they had. The dev JMAP mock reads the escaping back, or
nested tags would behave differently against the mock than against a real
server.

migrateKeyword also took any answer as success: a method-level error on
the query read as "no message carries this keyword", one on the write was
ignored outright, and a per-message refusal was invisible - so a rename
that moved nothing still reported a clean migration. It now answers with
{ migrated, refused }, because the work is batched and a failure partway
through cannot undo the batches already applied. A whole batch the server
refuses stops the rest, which it would refuse too; a single message it
refuses does not, the mail behind it being unaffected. A query error
throws, nothing having been written yet, and so does a run that migrated
nothing at all - the case where the caller must not rename a tag over
messages that all kept the old keyword.

The walk over tagged mail was not paginating safely either. It stopped at
the first page shorter than the limit it asked for, but RFC 8620 section
5.5 lets a server clamp that limit, so a clamping server left most of the
mail unmigrated. Positions are also only comparable within one queryState,
which the walk ignored: mail arriving mid-walk shifts every later page. It
now restarts when the state changes under it, and gives up - before any
write, so the caller can simply repeat - once a mailbox has moved under it
three times, rather than mixing ids from states that no longer agree. A
page carrying no id it has not already seen ends the walk, which keeps it
bounded against a server that answers every position with the same page.
2026-08-18 08:15:22 +05:00
zisisvakras 816cd1bf56 test(jmap): cover the Email/set keyword removal patch shape
Extend the existing keyword write coverage to pin the three call sites
that clear a keyword. markAsRead, batchMarkAsRead and toggleStar must
patch with null and never with false, and must still send true when
setting the keyword.

The three removal cases fail against the previous boolean form, one per
call site. The three set cases pass either way, since that path was
already correct.
2026-08-17 15:26:41 +03:00
Linus Rath 58ddd01cf7 Merge pull request #792 from dev-hive-kazniisa/fix/shared-mailbox-folder-management
fix(email-store): route shared-folder management to the owner account
2026-08-17 12:18:24 +02:00
Linus Rath 344542337a fix: don't export OG_WIDTH/OG_HEIGHT from the og-image route 2026-08-17 00:18:59 +02:00
Linus Rath 117bf4a3f2 fix: import vCard dates as RFC 9553 PartialDate and map common X- extensions #224 2026-08-17 00:09:30 +02:00