@rapidmx/activesync-plugin 1.0.0-beta.2 → 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/lib/BaseDeviceSyncStateRoute.js +32 -0
- package/dist/lib/BaseDeviceSyncStateRoute.js.map +1 -1
- package/dist/lib/BaseEasRoute.js +91 -15
- package/dist/lib/BaseEasRoute.js.map +1 -1
- package/dist/lib/EasAuditLog.js +56 -0
- package/dist/lib/EasAuditLog.js.map +1 -0
- package/dist/lib/EasCollectionLease.js +199 -0
- package/dist/lib/EasCollectionLease.js.map +1 -0
- package/dist/lib/EasCollectionStore.js +102 -0
- package/dist/lib/EasCollectionStore.js.map +1 -0
- package/dist/lib/EasCollectionSync.js +294 -0
- package/dist/lib/EasCollectionSync.js.map +1 -0
- package/dist/lib/EasSyncKeyUtils.js +125 -62
- package/dist/lib/EasSyncKeyUtils.js.map +1 -1
- package/dist/lib/MessageMoveRules.js +100 -0
- package/dist/lib/MessageMoveRules.js.map +1 -0
- package/dist/lib/MimeHeaderUtils.js +395 -0
- package/dist/lib/MimeHeaderUtils.js.map +1 -0
- package/dist/lib/RegexPatternUtils.js +30 -0
- package/dist/lib/RegexPatternUtils.js.map +1 -0
- package/dist/lib/RestapiCompat.js +41 -0
- package/dist/lib/RestapiCompat.js.map +1 -0
- package/dist/lib/adapters/CalendarSyncAdapter.js +142 -33
- package/dist/lib/adapters/CalendarSyncAdapter.js.map +1 -1
- package/dist/lib/adapters/EmailSyncAdapter.js +114 -27
- package/dist/lib/adapters/EmailSyncAdapter.js.map +1 -1
- package/dist/lib/adapters/mongo/EmailSyncAdapterMongo.js +2 -1
- package/dist/lib/adapters/mongo/EmailSyncAdapterMongo.js.map +1 -1
- package/dist/lib/adapters/sql/EmailSyncAdapterSQL.js +2 -1
- package/dist/lib/adapters/sql/EmailSyncAdapterSQL.js.map +1 -1
- package/dist/lib/codec/WbxmlDecoder.js +86 -34
- package/dist/lib/codec/WbxmlDecoder.js.map +1 -1
- package/dist/lib/codec/WbxmlEncoder.js +103 -22
- package/dist/lib/codec/WbxmlEncoder.js.map +1 -1
- package/dist/lib/commands/ComposeMailCommand.js +105 -31
- package/dist/lib/commands/ComposeMailCommand.js.map +1 -1
- package/dist/lib/commands/FolderSyncCommand.js +67 -27
- package/dist/lib/commands/FolderSyncCommand.js.map +1 -1
- package/dist/lib/commands/GetItemEstimateCommand.js +80 -59
- package/dist/lib/commands/GetItemEstimateCommand.js.map +1 -1
- package/dist/lib/commands/ItemOperationsCommand.js +219 -56
- package/dist/lib/commands/ItemOperationsCommand.js.map +1 -1
- package/dist/lib/commands/MeetingResponseCommand.js +212 -53
- package/dist/lib/commands/MeetingResponseCommand.js.map +1 -1
- package/dist/lib/commands/MoveItemsCommand.js +54 -21
- package/dist/lib/commands/MoveItemsCommand.js.map +1 -1
- package/dist/lib/commands/PingCommand.js +288 -56
- package/dist/lib/commands/PingCommand.js.map +1 -1
- package/dist/lib/commands/ProvisionCommand.js +34 -17
- package/dist/lib/commands/ProvisionCommand.js.map +1 -1
- package/dist/lib/commands/ResolveRecipientsCommand.js +43 -5
- package/dist/lib/commands/ResolveRecipientsCommand.js.map +1 -1
- package/dist/lib/commands/SearchCommand.js +92 -17
- package/dist/lib/commands/SearchCommand.js.map +1 -1
- package/dist/lib/commands/SettingsCommand.js +2 -1
- package/dist/lib/commands/SettingsCommand.js.map +1 -1
- package/dist/lib/commands/SmartForwardCommand.js +2 -1
- package/dist/lib/commands/SmartForwardCommand.js.map +1 -1
- package/dist/lib/commands/SmartReplyCommand.js +2 -1
- package/dist/lib/commands/SmartReplyCommand.js.map +1 -1
- package/dist/lib/commands/SyncCommand.js +481 -244
- package/dist/lib/commands/SyncCommand.js.map +1 -1
- package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js +6 -1
- package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js +3 -1
- package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js +2 -1
- package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/PingCommandMongo.js +23 -0
- package/dist/lib/commands/mongo/PingCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/SearchCommandMongo.js +3 -1
- package/dist/lib/commands/mongo/SearchCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/SendMailCommandMongo.js +2 -1
- package/dist/lib/commands/mongo/SendMailCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/SmartForwardCommandMongo.js +2 -1
- package/dist/lib/commands/mongo/SmartForwardCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/SmartReplyCommandMongo.js +2 -1
- package/dist/lib/commands/mongo/SmartReplyCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/SyncCommandMongo.js +7 -1
- package/dist/lib/commands/mongo/SyncCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/index.js +1 -0
- package/dist/lib/commands/mongo/index.js.map +1 -1
- package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js +6 -1
- package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/ItemOperationsCommandSQL.js +3 -1
- package/dist/lib/commands/sql/ItemOperationsCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/MeetingResponseCommandSQL.js +2 -1
- package/dist/lib/commands/sql/MeetingResponseCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/PingCommandSQL.js +23 -0
- package/dist/lib/commands/sql/PingCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/SearchCommandSQL.js +3 -1
- package/dist/lib/commands/sql/SearchCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/SendMailCommandSQL.js +2 -1
- package/dist/lib/commands/sql/SendMailCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/SmartForwardCommandSQL.js +2 -1
- package/dist/lib/commands/sql/SmartForwardCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/SmartReplyCommandSQL.js +2 -1
- package/dist/lib/commands/sql/SmartReplyCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/SyncCommandSQL.js +7 -1
- package/dist/lib/commands/sql/SyncCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/index.js +1 -0
- package/dist/lib/commands/sql/index.js.map +1 -1
- package/dist/lib/index.js +5 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/jobs/EasDeviceStateCleanupJob.js +44 -2
- package/dist/lib/jobs/EasDeviceStateCleanupJob.js.map +1 -1
- package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js +4 -0
- package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js.map +1 -1
- package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js +9 -0
- package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js.map +1 -1
- package/dist/lib/models/EasCollectionChunk.js +2 -0
- package/dist/lib/models/EasCollectionChunk.js.map +1 -0
- package/dist/lib/models/EasCollectionState.js +2 -0
- package/dist/lib/models/EasCollectionState.js.map +1 -0
- package/dist/lib/models/mongo/DeviceSyncStateMongo.js +7 -0
- package/dist/lib/models/mongo/DeviceSyncStateMongo.js.map +1 -1
- package/dist/lib/models/mongo/EasCollectionChunkMongo.js +83 -0
- package/dist/lib/models/mongo/EasCollectionChunkMongo.js.map +1 -0
- package/dist/lib/models/mongo/EasCollectionStateMongo.js +162 -0
- package/dist/lib/models/mongo/EasCollectionStateMongo.js.map +1 -0
- package/dist/lib/models/sql/DeviceSyncStateSQL.js +7 -0
- package/dist/lib/models/sql/DeviceSyncStateSQL.js.map +1 -1
- package/dist/lib/models/sql/EasCollectionChunkSQL.js +83 -0
- package/dist/lib/models/sql/EasCollectionChunkSQL.js.map +1 -0
- package/dist/lib/models/sql/EasCollectionStateSQL.js +162 -0
- package/dist/lib/models/sql/EasCollectionStateSQL.js.map +1 -0
- package/dist/lib/mongo/EasRouteMongo.js +2 -2
- package/dist/lib/mongo/EasRouteMongo.js.map +1 -1
- package/dist/lib/mongo.js +2 -0
- package/dist/lib/mongo.js.map +1 -1
- package/dist/lib/sql/EasRouteSQL.js +2 -2
- package/dist/lib/sql/EasRouteSQL.js.map +1 -1
- package/dist/lib/sql.js +2 -0
- package/dist/lib/sql.js.map +1 -1
- package/dist/types/BaseDeviceSyncStateRoute.d.ts +10 -0
- package/dist/types/BaseEasRoute.d.ts +15 -4
- package/dist/types/EasAuditLog.d.ts +47 -0
- package/dist/types/EasCollectionLease.d.ts +60 -0
- package/dist/types/EasCollectionStore.d.ts +51 -0
- package/dist/types/EasCollectionSync.d.ts +96 -0
- package/dist/types/EasCommandHandler.d.ts +7 -4
- package/dist/types/EasSyncKeyUtils.d.ts +73 -42
- package/dist/types/MessageMoveRules.d.ts +45 -0
- package/dist/types/MimeHeaderUtils.d.ts +73 -0
- package/dist/types/RegexPatternUtils.d.ts +14 -0
- package/dist/types/RestapiCompat.d.ts +24 -0
- package/dist/types/adapters/CalendarSyncAdapter.d.ts +47 -18
- package/dist/types/adapters/EasCollectionSyncAdapter.d.ts +18 -2
- package/dist/types/adapters/EmailSyncAdapter.d.ts +24 -7
- package/dist/types/adapters/mongo/EmailSyncAdapterMongo.d.ts +1 -0
- package/dist/types/adapters/sql/EmailSyncAdapterSQL.d.ts +1 -0
- package/dist/types/codec/WbxmlDecoder.d.ts +39 -0
- package/dist/types/codec/WbxmlEncoder.d.ts +36 -1
- package/dist/types/commands/ComposeMailCommand.d.ts +33 -15
- package/dist/types/commands/FolderSyncCommand.d.ts +4 -1
- package/dist/types/commands/GetItemEstimateCommand.d.ts +20 -15
- package/dist/types/commands/ItemOperationsCommand.d.ts +29 -4
- package/dist/types/commands/MeetingResponseCommand.d.ts +52 -13
- package/dist/types/commands/MoveItemsCommand.d.ts +13 -3
- package/dist/types/commands/PingCommand.d.ts +72 -14
- package/dist/types/commands/ProvisionCommand.d.ts +16 -11
- package/dist/types/commands/ResolveRecipientsCommand.d.ts +4 -0
- package/dist/types/commands/SearchCommand.d.ts +13 -1
- package/dist/types/commands/SyncCommand.d.ts +102 -62
- package/dist/types/commands/mongo/GetItemEstimateCommandMongo.d.ts +3 -0
- package/dist/types/commands/mongo/ItemOperationsCommandMongo.d.ts +2 -0
- package/dist/types/commands/mongo/MeetingResponseCommandMongo.d.ts +1 -0
- package/dist/types/commands/mongo/PingCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/SearchCommandMongo.d.ts +2 -0
- package/dist/types/commands/mongo/SendMailCommandMongo.d.ts +1 -0
- package/dist/types/commands/mongo/SmartForwardCommandMongo.d.ts +1 -0
- package/dist/types/commands/mongo/SmartReplyCommandMongo.d.ts +1 -0
- package/dist/types/commands/mongo/SyncCommandMongo.d.ts +4 -0
- package/dist/types/commands/mongo/index.d.ts +1 -0
- package/dist/types/commands/sql/GetItemEstimateCommandSQL.d.ts +3 -0
- package/dist/types/commands/sql/ItemOperationsCommandSQL.d.ts +2 -0
- package/dist/types/commands/sql/MeetingResponseCommandSQL.d.ts +1 -0
- package/dist/types/commands/sql/PingCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/SearchCommandSQL.d.ts +2 -0
- package/dist/types/commands/sql/SendMailCommandSQL.d.ts +1 -0
- package/dist/types/commands/sql/SmartForwardCommandSQL.d.ts +1 -0
- package/dist/types/commands/sql/SmartReplyCommandSQL.d.ts +1 -0
- package/dist/types/commands/sql/SyncCommandSQL.d.ts +4 -0
- package/dist/types/commands/sql/index.d.ts +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/jobs/EasDeviceStateCleanupJob.d.ts +15 -0
- package/dist/types/jobs/mongo/EasDeviceStateCleanupJobMongo.d.ts +2 -0
- package/dist/types/jobs/sql/EasDeviceStateCleanupJobSQL.d.ts +4 -0
- package/dist/types/models/DeviceSyncState.d.ts +13 -2
- package/dist/types/models/EasCollectionChunk.d.ts +20 -0
- package/dist/types/models/EasCollectionState.d.ts +79 -0
- package/dist/types/models/mongo/DeviceSyncStateMongo.d.ts +1 -0
- package/dist/types/models/mongo/EasCollectionChunkMongo.d.ts +16 -0
- package/dist/types/models/mongo/EasCollectionStateMongo.d.ts +27 -0
- package/dist/types/models/sql/DeviceSyncStateSQL.d.ts +1 -0
- package/dist/types/models/sql/EasCollectionChunkSQL.d.ts +16 -0
- package/dist/types/models/sql/EasCollectionStateSQL.d.ts +27 -0
- package/dist/types/mongo.d.ts +2 -0
- package/dist/types/sql.d.ts +2 -0
- package/package.json +267 -238
|
@@ -13,7 +13,12 @@ import { type Mailbox, type Message } from "@rapidmx/restapi";
|
|
|
13
13
|
* the only `Email` write EAS itself allows) - a plain-text-only pragmatic subset: no HTML body, no attachments
|
|
14
14
|
* (mirrors `ComposeMailCommand`'s own already-documented attachment gap). `To`/`Cc`/`Bcc` (the latter MS-ASEMAIL2's
|
|
15
15
|
* own `Bcc` tag) are ghosted independently per recipient type, not as one combined group - a `Change` touching
|
|
16
|
-
* only one of them leaves the others untouched, carried over from `existing.recipients`.
|
|
16
|
+
* only one of them leaves the others untouched, carried over from `existing.recipients`. A new Draft always gets a body
|
|
17
|
+
* blob (empty when no `Body` was sent), so `ItemOperations` can fetch it like any other message.
|
|
18
|
+
*
|
|
19
|
+
* `Flag` is the MS-ASEMAIL container form in both directions: `<Flag><Status>2</Status></Flag>` (tokenized as
|
|
20
|
+
* `FlagStatus`) for a flagged message, an empty `<Flag/>` otherwise; `Status` 0/1 (cleared/complete) or an empty
|
|
21
|
+
* `Flag` from the device clears `flags.flagged`.
|
|
17
22
|
*
|
|
18
23
|
* Emits MS-ASEMAIL2's `Email2:ConversationId` (read-only - no `fromApplicationData` handling, since EAS itself
|
|
19
24
|
* never lets a client set it) whenever `Message.conversationId` is populated, so a device's threaded-view UI can
|
|
@@ -27,23 +32,31 @@ import { type Mailbox, type Message } from "@rapidmx/restapi";
|
|
|
27
32
|
* already takes for a stale search-index entry. Deliberately not writable: unlike `Contact.categories` (a
|
|
28
33
|
* plain free-form string array with no separate entity behind it), a `Label` is a real mailbox-scoped entity
|
|
29
34
|
* referenced by uid - a write path would need to resolve category name strings back to `Label`s and create new
|
|
30
|
-
* ones on the fly for names that don't exist yet, real added scope this pragmatic subset defers.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* further to let a caller pre-resolve names for a whole batch.
|
|
35
|
+
* ones on the fly for names that don't exist yet, real added scope this pragmatic subset defers. Callers
|
|
36
|
+
* rendering a whole Sync page/search result set use `toApplicationDataBatch()`, which resolves every referenced
|
|
37
|
+
* label with one `in(...)` query per mailbox rather than one per labelled message.
|
|
34
38
|
*
|
|
35
|
-
* `labelClass`
|
|
39
|
+
* `labelClass`/`folderClass` are supplied by the Mongo/SQL concrete subclasses.
|
|
36
40
|
*
|
|
37
41
|
* @author Jean-Philippe Steinmetz
|
|
38
42
|
*/
|
|
39
43
|
export declare abstract class EmailSyncAdapter implements EasCollectionSyncAdapter<Message> {
|
|
40
44
|
readonly collectionClass = "Email";
|
|
41
45
|
protected abstract labelClass: any;
|
|
46
|
+
protected abstract folderClass: any;
|
|
42
47
|
private _objectFactory?;
|
|
43
48
|
private labelRepo?;
|
|
49
|
+
private folderRepo?;
|
|
44
50
|
private blobStore?;
|
|
45
51
|
init(): Promise<void>;
|
|
46
52
|
toApplicationData(message: Message): Promise<WbxmlElement>;
|
|
53
|
+
/** Renders a whole page of messages, resolving every referenced `Label` with one `find()` per distinct
|
|
54
|
+
* mailbox (in practice one per page) instead of one per labelled message. */
|
|
55
|
+
toApplicationDataBatch(messages: Message[]): Promise<WbxmlElement[]>;
|
|
56
|
+
private render;
|
|
57
|
+
/** Resolves every distinct `labelUids` entry across `messages` to its `Label.name`, keyed by `labelKey()`.
|
|
58
|
+
* Labels are mailbox-scoped, so uids are grouped per `mailboxUid` and each group is fetched with a single
|
|
59
|
+
* `in(...)` query (chunked to stay within `RepoUtils`' page size). A stale uid simply has no entry. */
|
|
47
60
|
private resolveLabelNames;
|
|
48
61
|
/**
|
|
49
62
|
* `Message.bodyBlobKey` is documented (see the `Message` interface itself) as holding raw MIME "unmodified
|
|
@@ -53,7 +66,11 @@ export declare abstract class EmailSyncAdapter implements EasCollectionSyncAdapt
|
|
|
53
66
|
* for every consumer, not just this write path - a Draft created/edited via `Sync` must `Fetch` correctly
|
|
54
67
|
* the same way any other message does.
|
|
55
68
|
*/
|
|
56
|
-
fromApplicationData(el: WbxmlElement, existing?: Message): Promise<Partial<Message>>;
|
|
69
|
+
fromApplicationData(el: WbxmlElement, existing?: Message, mailbox?: Mailbox): Promise<Partial<Message>>;
|
|
70
|
+
/** Whether `message` is a genuine draft (`MessageMoveRules.isGenuineDraft`): in its mailbox's Drafts folder, and
|
|
71
|
+
* never delivered. Moving any other message into Drafts is refused over ActiveSync, so its body can't be rewritten
|
|
72
|
+
* here by first moving it there. */
|
|
73
|
+
private isDraft;
|
|
57
74
|
/** Defaults for a brand-new Draft created via a client-originated `Add` - `from` is the caller's own
|
|
58
75
|
* mailbox address, per `ComposeMailCommand`'s identical `{ address, type: RecipientType.TO }` shape
|
|
59
76
|
* convention for a `from` field (the `Recipient` struct's `type` is only meaningful for real recipients;
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
import type { WbxmlElement } from "./WbxmlElement.js";
|
|
2
|
+
/** Default cap on the total number of elements decoded from one document. An element costs as little as one
|
|
3
|
+
* wire byte (an empty tag token), so without this a small body could allocate millions of element objects. */
|
|
4
|
+
export declare const WBXML_DEFAULT_MAX_ELEMENTS = 50000;
|
|
5
|
+
/** Default cap on the number of direct children of any single element. */
|
|
6
|
+
export declare const WBXML_DEFAULT_MAX_CHILDREN_PER_ELEMENT = 10000;
|
|
7
|
+
/** Default cap on element nesting depth (the root is depth 1). Nesting costs ~2 wire bytes per level and drives
|
|
8
|
+
* the decoder's recursion, so it needs its own bound; real EAS documents stay well under 20 levels. */
|
|
9
|
+
export declare const WBXML_DEFAULT_MAX_DEPTH = 64;
|
|
10
|
+
/** Limits applied by `WbxmlDecoder`. Every value must be a positive integer; omitted values use the defaults. */
|
|
11
|
+
export interface WbxmlDecoderOptions {
|
|
12
|
+
/** Maximum total elements in the document. Default `WBXML_DEFAULT_MAX_ELEMENTS`. */
|
|
13
|
+
maxElements?: number;
|
|
14
|
+
/** Maximum direct children of any one element. Default `WBXML_DEFAULT_MAX_CHILDREN_PER_ELEMENT`. */
|
|
15
|
+
maxChildrenPerElement?: number;
|
|
16
|
+
/** Maximum nesting depth (root = 1). Default `WBXML_DEFAULT_MAX_DEPTH`. */
|
|
17
|
+
maxDepth?: number;
|
|
18
|
+
}
|
|
19
|
+
/** Thrown by `WbxmlDecoder.decode()` for malformed or unsupported input (truncated buffer, unterminated string,
|
|
20
|
+
* attribute-flagged tag, a length field running past the end of the buffer, ...). */
|
|
21
|
+
export declare class WbxmlDecodeError extends Error {
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
/** Thrown by `WbxmlDecoder.decode()` when a document exceeds a configured resource limit (element count,
|
|
25
|
+
* children per element, or nesting depth). A subclass of `WbxmlDecodeError`, so callers that treat every
|
|
26
|
+
* decoder failure as a bad request can catch just the base class. */
|
|
27
|
+
export declare class WbxmlLimitError extends WbxmlDecodeError {
|
|
28
|
+
constructor(message: string);
|
|
29
|
+
}
|
|
2
30
|
/**
|
|
3
31
|
* Decodes a WBXML byte stream (an EAS request/response body) back into a `WbxmlElement` tree — the exact
|
|
4
32
|
* inverse of `WbxmlEncoder`. Reads the fixed EAS document header, skips its (always-empty, in real
|
|
5
33
|
* ActiveSync traffic) string table, then parses the single root element.
|
|
6
34
|
*
|
|
35
|
+
* Input is untrusted, so decoding is bounded: total elements, children per element and nesting depth are
|
|
36
|
+
* capped (see `WbxmlDecoderOptions`), throwing `WbxmlLimitError` when exceeded; all other malformed input throws
|
|
37
|
+
* `WbxmlDecodeError`. Text and opaque payloads are bounded by the input buffer itself.
|
|
38
|
+
*
|
|
7
39
|
* The `publicid` header field is read via the same generic `mb_u_int32` reader used everywhere else, which is
|
|
8
40
|
* only a partial implementation of the full WBXML spec for that field (a raw leading `0x00` byte would
|
|
9
41
|
* signal "public identifier is a string-table reference" under the full spec, a form real ActiveSync traffic
|
|
@@ -13,12 +45,19 @@ import type { WbxmlElement } from "./WbxmlElement.js";
|
|
|
13
45
|
* @author Jean-Philippe Steinmetz
|
|
14
46
|
*/
|
|
15
47
|
export declare class WbxmlDecoder {
|
|
48
|
+
private readonly maxElements;
|
|
49
|
+
private readonly maxChildrenPerElement;
|
|
50
|
+
private readonly maxDepth;
|
|
16
51
|
private buf;
|
|
17
52
|
private pos;
|
|
18
53
|
private currentPage;
|
|
19
54
|
private depth;
|
|
55
|
+
private elementCount;
|
|
56
|
+
constructor(options?: WbxmlDecoderOptions);
|
|
20
57
|
decode(data: Buffer): WbxmlElement;
|
|
21
58
|
private readByte;
|
|
59
|
+
/** Throws unless at least `length` bytes remain from the current position. */
|
|
60
|
+
private requireAvailable;
|
|
22
61
|
/** Decodes a WBXML `mb_u_int32`: base-128 digits, most significant group first, every byte but the last
|
|
23
62
|
* carrying the 0x80 continuation bit. Mirrors `WbxmlEncoder.writeMbUint()`. */
|
|
24
63
|
private readMbUint;
|
|
@@ -1,17 +1,52 @@
|
|
|
1
1
|
import type { WbxmlElement } from "./WbxmlElement.js";
|
|
2
|
+
/** Token bytes are buffered in a small scratch array and flushed into a `Buffer` chunk once it reaches this
|
|
3
|
+
* many bytes (or before a text/opaque payload is appended as its own chunk). */
|
|
4
|
+
export declare const WBXML_ENCODER_SCRATCH_SIZE = 4096;
|
|
5
|
+
/** Options for `WbxmlEncoder`. */
|
|
6
|
+
export interface WbxmlEncoderOptions {
|
|
7
|
+
/** Maximum size in bytes of an encoded document (header included). Must be a positive integer or
|
|
8
|
+
* `Infinity`; default `Infinity` (no limit). Exceeding it throws `WbxmlSizeLimitError`. */
|
|
9
|
+
maxBytes?: number;
|
|
10
|
+
}
|
|
11
|
+
/** Thrown by `WbxmlEncoder.encode()` when the encoded document would exceed the configured `maxBytes`. */
|
|
12
|
+
export declare class WbxmlSizeLimitError extends Error {
|
|
13
|
+
readonly maxBytes: number;
|
|
14
|
+
constructor(maxBytes: number);
|
|
15
|
+
}
|
|
16
|
+
/** Returns the exact wire size of an `OPAQUE` token carrying `payloadLength` bytes (token + length + payload),
|
|
17
|
+
* so callers can check a large payload against a size budget before building the element. */
|
|
18
|
+
export declare function wbxmlOpaqueSize(payloadLength: number): number;
|
|
2
19
|
/**
|
|
3
20
|
* Encodes a `WbxmlElement` tree into a WBXML byte stream, per MS-ASWBXML's encoding algorithm. Emits the
|
|
4
21
|
* fixed EAS document header (`version=1.3, publicid=unknown, charset=UTF-8, empty string table`) followed by
|
|
5
22
|
* the token stream for the given root element, switching code pages only when the page actually changes from
|
|
6
23
|
* whatever was last active (starting from `AirSync`, page 0, the implicit default per spec).
|
|
7
24
|
*
|
|
25
|
+
* Output is accumulated as a list of `Buffer` chunks (token bytes batched in a small scratch array, text/opaque
|
|
26
|
+
* payloads appended whole) and joined once at the end, so large payloads are never copied byte-by-byte. An
|
|
27
|
+
* optional `maxBytes` bounds the output size, throwing `WbxmlSizeLimitError` as soon as it would be exceeded.
|
|
28
|
+
*
|
|
8
29
|
* @author Jean-Philippe Steinmetz
|
|
9
30
|
*/
|
|
10
31
|
export declare class WbxmlEncoder {
|
|
11
|
-
private
|
|
32
|
+
private readonly maxBytes;
|
|
33
|
+
private chunks;
|
|
34
|
+
private scratch;
|
|
35
|
+
private size;
|
|
12
36
|
private currentPage;
|
|
37
|
+
constructor(options?: WbxmlEncoderOptions);
|
|
13
38
|
encode(root: WbxmlElement): Buffer;
|
|
39
|
+
/** Accounts for `length` more output bytes, throwing before anything is appended if that would exceed
|
|
40
|
+
* `maxBytes`. */
|
|
41
|
+
private reserve;
|
|
42
|
+
private writeByte;
|
|
43
|
+
/** Appends a payload as its own chunk, after flushing any pending token bytes to preserve ordering. */
|
|
44
|
+
private writeBuffer;
|
|
45
|
+
private flush;
|
|
14
46
|
private writeElement;
|
|
47
|
+
/** A NUL byte terminates a WBXML inline string, so any U+0000 in `text` (e.g. a label name or subject
|
|
48
|
+
* controlled by another user) would end the string early and let the remaining bytes be parsed as tokens.
|
|
49
|
+
* UTF-8 never produces a 0x00 byte for any other code point, so stripping U+0000 is sufficient. */
|
|
15
50
|
private writeStrI;
|
|
16
51
|
private writeOpaque;
|
|
17
52
|
/** Encodes `value` as a WBXML multi-byte unsigned integer (`mb_u_int32`): base-128 digits, most
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { RepoUtils } from "@rapidrest/service-core";
|
|
1
2
|
import { type Message, RecoverableRepoUtils } from "@rapidmx/restapi";
|
|
2
3
|
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
4
|
+
import { stripHeader } from "../MimeHeaderUtils.js";
|
|
3
5
|
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
6
|
+
/** Most envelope recipients (To + Cc + Bcc) one composed message may carry. */
|
|
7
|
+
export declare const MAX_COMPOSE_RECIPIENTS = 500;
|
|
8
|
+
export { stripHeader };
|
|
4
9
|
/**
|
|
5
10
|
* Shared implementation for EAS `SendMail`, `SmartForward`, and `SmartReply` (MS-ASCMD `ComposeMail` namespace)
|
|
6
11
|
* — all three submit a client-composed raw MIME body directly (`<Mime>`, opaque WBXML content) rather than
|
|
@@ -8,23 +13,33 @@ import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.
|
|
|
8
13
|
* `BaseMessageRoute.send()`, which this class's `scanAndRelay()` call shares its scan-then-relay core with via
|
|
9
14
|
* `MailSendUtils.ts`).
|
|
10
15
|
*
|
|
16
|
+
* **Sender and envelope checks** (the MIME is entirely device-controlled): the raw bytes pass restapi's own originator
|
|
17
|
+
* rules before anything parses them (`MimeHeaderUtils.checkComposedOriginators`: an inline copy of restapi's
|
|
18
|
+
* `checkOriginatorHeaders` with `rejectAddressLikeDisplayNames`): exactly one `From`, at most one `Sender` (found by a tolerant lexer - `From :`, folded lines and bare-CR
|
|
19
|
+
* line breaks included), every address in them - group members too - the caller's own mailbox's primary or alias
|
|
20
|
+
* address, no address-like text outside an address (`<me@x> <victim@y>`), and no address in a display name or comment
|
|
21
|
+
* (`"ceo@y" <me@x>`, `me@x (victim@y)`), and - stricter than restapi - no empty group (`victims:;, me@x`). Anything
|
|
22
|
+
* else is HTTP 403 (a MIME with no `From` at all is HTTP 400), so a device can't send as, or appear to be, anyone else;
|
|
23
|
+
* the envelope sender is the validated `From`. A message with no To/Cc/Bcc address is answered with the command's own
|
|
24
|
+
* `Status` 119 (MessageHasNoRecipient), nothing relayed. The envelope is capped at `MAX_COMPOSE_RECIPIENTS`
|
|
25
|
+
* recipients (HTTP 400). `Bcc` recipients are delivered via the envelope, but every `Bcc` header field (found by the
|
|
26
|
+
* same lexer, so `Bcc :` too) is stripped from the relayed copy so other recipients never see it (the Sent Items copy
|
|
27
|
+
* keeps it). The Sent Items copy records the relay's own `Message-ID`/`conversationId` (bounded like restapi's), so
|
|
28
|
+
* recall and threading match what recipients received.
|
|
29
|
+
*
|
|
11
30
|
* **Pragmatic subset, deliberately not the full MS-ASCMD semantics**:
|
|
12
31
|
* - `SmartForward`/`SmartReply`'s `<Source>` (the message being forwarded/replied to) is used only to thread
|
|
13
32
|
* the outgoing message (`inReplyTo`/`references`) and to flip the original's `Answered`/`Forwarded` flag - the
|
|
14
|
-
* real spec has the *server* splice the original message's full content into the outgoing MIME
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* fidelity" precedent elsewhere (e.g. `FolderSyncCommand`'s SyncKey replay-protection gap).
|
|
33
|
+
* real spec has the *server* splice the original message's full content into the outgoing MIME; this subset
|
|
34
|
+
* expects the client's own `<Mime>` to already be the complete outgoing message. The flag flip needs `UPDATE` on
|
|
35
|
+
* the original's folder and is best-effort: the message has already been sent, so a denied or conflicting flag
|
|
36
|
+
* update is logged, never turned into a failed request.
|
|
19
37
|
* - `ReplaceMime`/`AccountId`/`InstanceId` are not read - single-account, non-recurring-meeting compose only.
|
|
20
|
-
* - Attachments present in the composed MIME are relayed correctly
|
|
21
|
-
*
|
|
22
|
-
* copy - `Message.hasAttachments` is still set correctly from the parsed MIME, just not each attachment's own
|
|
23
|
-
* row (deferred, matching `ItemOperationsCommand`'s own future `Fetch`-of-Sent-Items scope).
|
|
38
|
+
* - Attachments present in the composed MIME are relayed correctly but are not additionally persisted as
|
|
39
|
+
* `Attachment` records on the saved Sent Items copy (`Message.hasAttachments` is still set).
|
|
24
40
|
*
|
|
25
|
-
* `folderClass`/`messageClass` are supplied by the Mongo/SQL concrete subclasses, and
|
|
26
|
-
* `
|
|
27
|
-
* `<Source>` to act on).
|
|
41
|
+
* `folderClass`/`messageClass`/`mailboxClass` are supplied by the Mongo/SQL concrete subclasses, and
|
|
42
|
+
* `markOriginal()` by the `SmartForwardCommand`/`SmartReplyCommand` subclasses.
|
|
28
43
|
*
|
|
29
44
|
* @author Jean-Philippe Steinmetz
|
|
30
45
|
*/
|
|
@@ -32,17 +47,20 @@ export declare abstract class ComposeMailCommand implements EasCommandHandler {
|
|
|
32
47
|
abstract readonly command: string;
|
|
33
48
|
protected abstract folderClass: any;
|
|
34
49
|
protected abstract messageClass: any;
|
|
50
|
+
protected abstract mailboxClass: any;
|
|
35
51
|
private _objectFactory?;
|
|
36
52
|
protected folderRepo?: RecoverableRepoUtils<any>;
|
|
37
53
|
protected messageRepo?: RecoverableRepoUtils<any>;
|
|
54
|
+
protected mailboxRepo?: RepoUtils<any>;
|
|
38
55
|
private blobStore?;
|
|
39
56
|
private mailTransport?;
|
|
40
57
|
private scanPipeline?;
|
|
41
58
|
private aclUtils?;
|
|
59
|
+
private logger;
|
|
42
60
|
init(): Promise<void>;
|
|
43
|
-
/** Called once the outgoing message has been sent, only when the request carried a `<Source>`
|
|
44
|
-
*
|
|
45
|
-
*
|
|
61
|
+
/** Called once the outgoing message has been sent, only when the request carried a `<Source>` the caller may
|
|
62
|
+
* update - flips the referenced original message's own `Answered`/`Forwarded` flag. A no-op here; overridden by
|
|
63
|
+
* the two subclasses that need it. */
|
|
46
64
|
protected markOriginal(_ctx: EasCommandContext, _original: Message & {
|
|
47
65
|
uid: string;
|
|
48
66
|
}): Promise<void>;
|
|
@@ -7,6 +7,10 @@ import { Folder } from "@rapidmx/restapi";
|
|
|
7
7
|
* `EasSyncKeyUtils.ts` (scoped by `mailboxUid` over the `Folder` collection, rather than `folderUid` over a
|
|
8
8
|
* per-folder item collection the way `SyncCommand` will be).
|
|
9
9
|
*
|
|
10
|
+
* **Keys**: an unknown `SyncKey` is answered with Status 9 ([MS-ASCMD] FolderSync: "synchronization key mismatch or
|
|
11
|
+
* invalid synchronization key"; the client restarts from `0`). The key issued before the current one is still
|
|
12
|
+
* accepted: the key itself encodes its cursor, so a retry simply recomputes that round's changes.
|
|
13
|
+
*
|
|
10
14
|
* `folderClass` is supplied by the Mongo/SQL concrete subclasses, following the exact one-line-per-backend
|
|
11
15
|
* pattern used throughout this library's other routes/jobs.
|
|
12
16
|
*
|
|
@@ -21,5 +25,4 @@ export declare abstract class FolderSyncCommand<F extends Folder> implements Eas
|
|
|
21
25
|
init(): Promise<void>;
|
|
22
26
|
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
23
27
|
private folderToChangeElement;
|
|
24
|
-
private persistSyncKey;
|
|
25
28
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
2
|
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
-
/** Binds one MS-ASCMD `Class` value to the concrete entity class this command counts against
|
|
4
|
-
*
|
|
5
|
-
* never serializes an item, only counts them). Supplied by the Mongo/SQL concrete subclasses. */
|
|
3
|
+
/** Binds one MS-ASCMD `Class` value to the concrete entity class this command counts against. Supplied by the
|
|
4
|
+
* Mongo/SQL concrete subclasses. */
|
|
6
5
|
export interface EstimateCollectionBinding {
|
|
7
6
|
entityClass: any;
|
|
8
7
|
}
|
|
@@ -12,32 +11,38 @@ export interface EstimateCollectionBinding {
|
|
|
12
11
|
* consumes a `SyncKey` itself.
|
|
13
12
|
*
|
|
14
13
|
* The modern (14.0+) request/response reuses `WbxmlCodePage.AirSync`'s own `Collections`/`Collection`/`Class`/
|
|
15
|
-
* `CollectionId`/`SyncKey` via `SWITCH_PAGE` rather than this page's own legacy (`Folders`/`Folder`/`FolderId`)
|
|
16
|
-
* shape - see `WbxmlCodePages.ts`'s own doc comment on `WbxmlCodePage.ItemEstimate` for why, and
|
|
17
|
-
* `ItemOperationsCommand.fetchMessage`'s identical cross-page-reuse precedent.
|
|
14
|
+
* `CollectionId`/`SyncKey` via `SWITCH_PAGE` rather than this page's own legacy (`Folders`/`Folder`/`FolderId`) shape.
|
|
18
15
|
*
|
|
19
|
-
*
|
|
20
|
-
* total live item count
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
16
|
+
* The collection's `EasCollectionState` (the same per-device row `SyncCommand` keeps) decides the estimate: a
|
|
17
|
+
* `SyncKey` of `"0"` reports the folder's total live item count (what the first `Sync` would `Add`); the
|
|
18
|
+
* collection's current `SyncKey` runs `SyncCommand`'s own enumeration as a dry run (nothing persisted), capped at
|
|
19
|
+
* `mail:eas:item_estimate_max_count`; any other key is Status 2. A request without `Class` falls back to the class
|
|
20
|
+
* remembered for the collection, then to the folder's type.
|
|
24
21
|
*
|
|
25
|
-
* **
|
|
26
|
-
* `CollectionId`
|
|
27
|
-
*
|
|
28
|
-
*
|
|
22
|
+
* **Bounded like `Sync`**: at most `MAX_SYNC_COLLECTIONS` collections per request (more is a single Status 2), a
|
|
23
|
+
* `CollectionId` repeated within one request is estimated once, and a `SyncKey 0` count is capped at the same maximum.
|
|
24
|
+
*
|
|
25
|
+
* **ACL-checked like `Sync`**: `READ` on the client-supplied `CollectionId` is required before counting anything;
|
|
26
|
+
* a denied folder is reported identically to an unrecognized collection (`Status 2`).
|
|
29
27
|
*
|
|
30
28
|
* @author Jean-Philippe Steinmetz
|
|
31
29
|
*/
|
|
32
30
|
export declare abstract class GetItemEstimateCommand implements EasCommandHandler {
|
|
33
31
|
readonly command = "GetItemEstimate";
|
|
34
32
|
protected abstract collectionBindings: Record<string, EstimateCollectionBinding>;
|
|
33
|
+
protected abstract folderClass: any;
|
|
34
|
+
protected abstract collectionStateClass: any;
|
|
35
|
+
protected abstract collectionChunkClass: any;
|
|
35
36
|
private maxCount;
|
|
36
37
|
private _objectFactory?;
|
|
37
38
|
private aclUtils?;
|
|
38
39
|
private repos;
|
|
40
|
+
private folderRepo?;
|
|
41
|
+
private collectionStateRepo?;
|
|
42
|
+
private collectionChunkRepo?;
|
|
39
43
|
init(): Promise<void>;
|
|
40
44
|
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
45
|
+
private statusResponse;
|
|
41
46
|
private estimateCollection;
|
|
42
47
|
private collectionElement;
|
|
43
48
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
2
|
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/** Most batches (of `mail:eas:itemoperations_batch_size` messages) one `EmptyFolderContents` deletes per request. */
|
|
4
|
+
export declare const MAX_EMPTY_FOLDER_BATCHES = 20;
|
|
3
5
|
/**
|
|
4
6
|
* Handles EAS `ItemOperations`: `Fetch` (a `Message`'s full body or an `Attachment`'s binary content, by the
|
|
5
7
|
* same `ServerId`/uid this library already exposes elsewhere) and `EmptyFolderContents`.
|
|
@@ -16,7 +18,10 @@ import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.
|
|
|
16
18
|
* `MoveItems` command's per-message `SrcFldId`/`SrcMsgId`/`DstFldId` shape. Every `Message` sharing the
|
|
17
19
|
* decoded `conversationId` across the whole mailbox (not just one folder) that the caller has `UPDATE` on is
|
|
18
20
|
* relocated to `DstFldId`; one lacking permission is silently skipped rather than failing the whole move (a
|
|
19
|
-
* conversation can legitimately span folders the caller doesn't control, e.g. a shared mailbox's Inbox).
|
|
21
|
+
* conversation can legitimately span folders the caller doesn't control, e.g. a shared mailbox's Inbox). The
|
|
22
|
+
* `ConversationId` is looked up bounded and exact-matched in memory (it derives from sender-controlled headers), and
|
|
23
|
+
* each message's move follows `MessageMoveRules.planMessageMove` (never into Outbox, into Drafts only for drafts, out of
|
|
24
|
+
* Outbox cancels the scheduled send) - a refused message counts as failed. An
|
|
20
25
|
* optional `MoveAlways` (a hint to keep auto-moving future messages in this conversation) is accepted but not
|
|
21
26
|
* acted on - this library's `MailFilterRule` has no conversation-scoped condition to key an ongoing rule off
|
|
22
27
|
* of, a documented simplification, not silent data loss (the move itself still happens).
|
|
@@ -28,15 +33,29 @@ import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.
|
|
|
28
33
|
* command's original single-`Fetch` design, not a new gap introduced by adding multi-`Fetch` support.
|
|
29
34
|
* - Only the "inline" delivery method is used (content embedded directly in the WBXML response) - the real
|
|
30
35
|
* spec's "multipart" alternative (WBXML as one part, binary content as a separate part) is not implemented;
|
|
31
|
-
*
|
|
32
|
-
*
|
|
36
|
+
* the combined size of the bodies/attachments embedded in one response is capped at
|
|
37
|
+
* `mail:eas:itemoperations_max_response_bytes` (default 64 MB) - a Fetch that would exceed it gets Status 11
|
|
38
|
+
* ("data too large") instead of content. Attachment access is checked against the owning message's *current*
|
|
39
|
+
* folder (`Attachment.folderUid` is not updated when a message moves).
|
|
33
40
|
* - `Options/BodyPreference`'s `Type`/`TruncationSize` are honored for a `Message` body fetch (plain text,
|
|
34
41
|
* HTML, or - `Type 4` - the raw MIME source verbatim); byte-range fetching (`Range`) is not implemented.
|
|
35
42
|
* - `EmptyFolderContents`'s `DeleteSubFolders` option is rejected outright rather than silently ignored -
|
|
36
43
|
* recursive subfolder deletion is out of scope for this pragmatic subset; emptying a single folder's own
|
|
37
44
|
* `Message`s is the common case this implements.
|
|
38
45
|
*
|
|
39
|
-
*
|
|
46
|
+
* **Bulk operations are bounded and per item**: `EmptyFolderContents` deletes at most `MAX_EMPTY_FOLDER_BATCHES`
|
|
47
|
+
* batches per request, and `Move` at most one batch; a message that fails to delete or move (e.g. a concurrent
|
|
48
|
+
* edit's version conflict) is skipped rather than failing the request, and the operation then reports Status 17
|
|
49
|
+
* (partial success) - or Status 3 when nothing at all succeeded - so the client can retry for the rest.
|
|
50
|
+
* `EmptyFolderContents` skips (as failed) a message whose send is in flight (`MessageMoveRules.hasLiveSendLease`).
|
|
51
|
+
*
|
|
52
|
+
* **Audit**: in a mailbox the caller doesn't own (restapi's `isNonOwnerAccess()` - an administrator or a delegate), a
|
|
53
|
+
* `Fetch` that returns a body or an attachment records one `MESSAGE_CONTENT_ACCESSED` entry per item, and
|
|
54
|
+
* `EmptyFolderContents` records one `MESSAGE_DELETE` entry per delete batch, listing the deleted message uids
|
|
55
|
+
* (`EasAuditLog`).
|
|
56
|
+
*
|
|
57
|
+
* `folderClass`/`messageClass`/`attachmentClass`/`mailboxClass`/`auditLogClass` are supplied by the Mongo/SQL concrete
|
|
58
|
+
* subclasses.
|
|
40
59
|
*
|
|
41
60
|
* @author Jean-Philippe Steinmetz
|
|
42
61
|
*/
|
|
@@ -45,13 +64,19 @@ export declare abstract class ItemOperationsCommand implements EasCommandHandler
|
|
|
45
64
|
protected abstract folderClass: any;
|
|
46
65
|
protected abstract messageClass: any;
|
|
47
66
|
protected abstract attachmentClass: any;
|
|
67
|
+
protected abstract mailboxClass: any;
|
|
68
|
+
protected abstract auditLogClass: any;
|
|
48
69
|
private _objectFactory?;
|
|
70
|
+
private config?;
|
|
71
|
+
private logger;
|
|
49
72
|
private folderRepo?;
|
|
73
|
+
private mailboxRepo?;
|
|
50
74
|
private messageRepo?;
|
|
51
75
|
private attachmentRepo?;
|
|
52
76
|
private blobStore?;
|
|
53
77
|
private aclUtils?;
|
|
54
78
|
private maxFetchesPerRequest;
|
|
79
|
+
private maxResponseBytes;
|
|
55
80
|
private batchSize;
|
|
56
81
|
init(): Promise<void>;
|
|
57
82
|
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
2
|
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/** Most `Request` elements one `MeetingResponse` may carry. */
|
|
4
|
+
export declare const MAX_MEETING_RESPONSES = 100;
|
|
3
5
|
/**
|
|
4
|
-
* Handles EAS `MeetingResponse`: records the caller's own accept/tentative/decline response to
|
|
5
|
-
*
|
|
6
|
-
* exposes as `ServerId` elsewhere (Sync/FolderSync) - no separate lookup table is needed.
|
|
6
|
+
* Handles EAS `MeetingResponse`: records the caller's own accept/tentative/decline response to each meeting the
|
|
7
|
+
* request's `<Request>` elements name, each answered with its own `<Result>`.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* meeting from the declining attendee's own calendar, leaving the organizer's and every other attendee's own
|
|
14
|
-
* copy untouched. Accept/Tentative still update `Attendee.responseStatus` in place. The response omits
|
|
15
|
-
* `CalendarId` for a decline either way, matching the spec's own convention, so a client relying on that
|
|
16
|
-
* signal isn't misled into thinking a new item was created.
|
|
9
|
+
* **`RequestId`** is either the `CalendarEvent.uid` (a response from the calendar) or the `Message.uid` of the
|
|
10
|
+
* meeting request in the Inbox (a response from the mail view - what most clients send). For a message, the
|
|
11
|
+
* caller needs `READ` on its folder; its `text/calendar` part's `UID` is then resolved to the caller's own copy of
|
|
12
|
+
* the event (`icalUid` within the caller's mailbox) - looked up bounded (`boundIndexedValue`, as restapi stores it) and
|
|
13
|
+
* exact-matched in memory, since the UID is sender-controlled text a query parser could read as an operator.
|
|
17
14
|
*
|
|
18
|
-
*
|
|
15
|
+
* **Someone else's calendar**: whether a copy is the organizer's is judged against the event's own mailbox, not the
|
|
16
|
+
* caller's. A response to an event in another mailbox (a delegate with `UPDATE` on it) is refused with Status 2 when
|
|
17
|
+
* that event is its owner's organizer copy - it can only ever be an attendee copy. Updates are version-checked on both
|
|
18
|
+
* backends (`asEntity`).
|
|
19
|
+
*
|
|
20
|
+
* **Effect**: the caller must be an attendee and have `UPDATE` on the event's folder. Accept/Tentative update
|
|
21
|
+
* the caller's `Attendee.responseStatus`. A decline removes the caller's own copy of the event (matching Exchange)
|
|
22
|
+
* when the caller also has `DELETE` on the folder, and otherwise just records the declined status - a delegate
|
|
23
|
+
* with edit-only rights can't delete. Each mailbox has its own event row, so neither touches the organizer's or
|
|
24
|
+
* any other attendee's copy. `CalendarId` is omitted for a removed event. Since the caller's copy is an attendee's copy
|
|
25
|
+
* of someone else's meeting, neither write may look like the organizer acting to restapi's `MeetingSchedulingJob`:
|
|
26
|
+
* a removed copy is stamped `cancelNoticeSentAt` first (no CANCEL mailed as the organizer), and an updated copy keeps
|
|
27
|
+
* `inviteSequenceSent` equal to its `sequence` (no REQUEST).
|
|
28
|
+
*
|
|
29
|
+
* **Reply to the organizer**: in protocol 16.x the client asks the server to notify the organizer with
|
|
30
|
+
* `SendResponse`; when it's present an iTIP `REPLY` (built with restapi's `buildEventIcs`, the same payload
|
|
31
|
+
* `BaseCalendarEventRoute.respond` sends) is mailed from the caller's attendee address to the organizer,
|
|
32
|
+
* and a transport that accepts none of it (or rejects the organizer) is reported as Status 4 - the response itself is
|
|
33
|
+
* already recorded, so a retry only re-sends the reply. Without it (14.x clients send their own reply via `SendMail`) nothing is mailed, so the organizer
|
|
34
|
+
* never receives two replies.
|
|
35
|
+
*
|
|
36
|
+
* Failures are per request: an unknown/unresolvable meeting, a meeting the caller may not respond to, or a
|
|
37
|
+
* malformed `Request` gets Status 2 (indistinguishable, so nothing about other mailboxes leaks); a failed write
|
|
38
|
+
* Status 3.
|
|
39
|
+
*
|
|
40
|
+
* `calendarEventClass`/`mailboxClass`/`messageClass` are supplied by the Mongo/SQL concrete subclasses.
|
|
19
41
|
*
|
|
20
42
|
* @author Jean-Philippe Steinmetz
|
|
21
43
|
*/
|
|
@@ -23,10 +45,27 @@ export declare abstract class MeetingResponseCommand implements EasCommandHandle
|
|
|
23
45
|
readonly command = "MeetingResponse";
|
|
24
46
|
protected abstract calendarEventClass: any;
|
|
25
47
|
protected abstract mailboxClass: any;
|
|
48
|
+
protected abstract messageClass: any;
|
|
26
49
|
private _objectFactory?;
|
|
27
50
|
private calendarEventRepo?;
|
|
28
51
|
private mailboxRepo?;
|
|
52
|
+
private messageRepo?;
|
|
29
53
|
private aclUtils?;
|
|
54
|
+
private blobStore?;
|
|
55
|
+
private mailTransport?;
|
|
56
|
+
private logger;
|
|
30
57
|
init(): Promise<void>;
|
|
31
58
|
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
59
|
+
private result;
|
|
60
|
+
private respond;
|
|
61
|
+
/** Resolves `requestId` to the caller's event: an event uid directly, or a meeting request message's
|
|
62
|
+
* `text/calendar` `UID` looked up among the caller's own mailbox's events. */
|
|
63
|
+
private resolveEvent;
|
|
64
|
+
/** The iCalendar `UID` of a meeting request message's `text/calendar` part, if it has one. */
|
|
65
|
+
private meetingUidOf;
|
|
66
|
+
/** Mails an iTIP `REPLY` for `attendee`'s response to the event's organizer. Returns `false` (after logging) when
|
|
67
|
+
* the transport threw or didn't accept the message for the organizer - checked the way restapi's `sendOrThrow`
|
|
68
|
+
* does, since every bundled transport reports a relay failure through `TransportResult.rejected` rather than
|
|
69
|
+
* throwing. */
|
|
70
|
+
private sendReply;
|
|
32
71
|
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
2
|
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/** Most `Move` elements one `MoveItems` request may carry - more is rejected with HTTP 400. */
|
|
4
|
+
export declare const MAX_MOVES_PER_REQUEST = 500;
|
|
3
5
|
/**
|
|
4
6
|
* Handles the standalone EAS `MoveItems` command: moves one or more `Message`s between folders in the caller's
|
|
5
|
-
* own mailbox by `ServerId`/`uid`.
|
|
7
|
+
* own mailbox by `ServerId`/`uid`. A move never crosses mailboxes (the destination folder must belong to the
|
|
8
|
+
* message's own mailbox) and a request may carry at most `MAX_MOVES_PER_REQUEST` moves.
|
|
9
|
+
*
|
|
10
|
+
* Each move gets its own [MS-ASCMD] status: `3` success; `1` an unknown/unreadable message, or a `SrcFldId` that
|
|
11
|
+
* isn't where it lives (or can't be updated); `2` an unknown destination, one in another mailbox, one the caller
|
|
12
|
+
* can't create in, Outbox, or Drafts for a message that isn't already a draft (`MessageMoveRules.planMessageMove`);
|
|
13
|
+
* `4` source and destination are the same folder; `7` the update itself failed (e.g. a concurrent edit's version
|
|
14
|
+
* conflict - the update is version-checked on both backends), a send in flight, or the message already left Outbox as sent - one failing
|
|
15
|
+
* move never aborts the others. Moving a message out of Outbox cancels its scheduled send.
|
|
6
16
|
*
|
|
7
17
|
* **Pragmatic subset**: `Message` only - `Contacts`/`Calendar`/`Tasks` moves are rare in practice (unlike
|
|
8
18
|
* `Message`, whose Inbox-to-subfolder filing is a real, common client action) and would each need their own
|
|
9
|
-
* ACL/ownership verification path for comparatively little value; a client attempting one gets
|
|
10
|
-
* rather than being silently ignored. `DstMsgId` in the response is always the same `uid` as `SrcMsgId` - this
|
|
19
|
+
* ACL/ownership verification path for comparatively little value; a client attempting one gets Status 1 (its
|
|
20
|
+
* `SrcMsgId` isn't a message) rather than being silently ignored. `DstMsgId` in the response is always the same `uid` as `SrcMsgId` - this
|
|
11
21
|
* library never mints a new identifier on move, unlike a real Exchange server, which sometimes does.
|
|
12
22
|
*
|
|
13
23
|
* `messageClass`/`folderClass` are supplied by the Mongo/SQL concrete subclasses.
|