Files
webmail/lib/jmap/server-keywords.ts
Linus Rath d7004b3851 fix: report refused tags and keep Stalwart 1.0's attachment keywords intact
Stalwart 1.0 puts `$hasattachment` or `$hasnoattachment` on every
message and derives `hasAttachment` from them alone. It also refuses
keywords over 128 bytes (0.16 cuts them to 128 characters) and more than
100 keywords on one message. The keyword writers ignored Email/set
`notUpdated`, so a refused tag showed until the next reload and then
vanished.

- `markAsRead`, `batchMarkAsRead`, `toggleStar`, `updateEmailKeywords`,
  `setKeyword`, `removeKeyword` and `batchUpdateKeywords` check the
  Email/set outcome, and the errors carry the JMAP type. Tagging from
  the list or the viewer shows "Tagging failed"; a refused category-tab
  move rejects the plugin's promise and skips the categorize hooks.
- Tag ids are capped at 121 characters (128 bytes with `$label:`).
- The attachment keywords are hidden in view source, the plugin
  projection and the `getKeywords` fallback labels, reserved for
  category tabs, and refused by the plugin keyword API. Email/copy and
  the cross-server import still carry them over.
- A tag comes off by leaving its key out of the full keyword map instead
  of being sent as false, which RFC 8621 does not allow;
  `updateEmailKeywords` also drops the false entries local updates leave
  behind (`$seen: false` after marking unread).
- The automatic mark-as-read no longer leaves an unhandled rejection
  when the server refuses it.
2026-09-25 19:24:30 +02:00

19 lines
924 B
TypeScript

/**
* Keywords the server sets on its own.
*
* Stalwart 1.0 puts `$hasattachment` or `$hasnoattachment` on every message it
* stores and derives Email `hasAttachment` (the property and the filter) from
* them alone. A patch can still add or remove them, which flips
* `hasAttachment`. So they are not tags: they are hidden wherever keywords are
* listed, and no plugin or category tab may add or remove them. A copy must
* carry them over, though: Email/copy takes a message's keywords only from the
* request, and a copy sent without them loses its attachment flag. Stalwart
* 0.16 never sets them.
*/
export const SERVER_SET_KEYWORDS: ReadonlySet<string> = new Set(['$hasattachment', '$hasnoattachment']);
/** True for a keyword the server sets itself (keywords are case-insensitive). */
export function isServerSetKeyword(keyword: string): boolean {
return SERVER_SET_KEYWORDS.has(keyword.toLowerCase());
}