@rapidmx/activesync 1.0.0-beta.0

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.
Files changed (195) hide show
  1. package/LICENSE +382 -0
  2. package/README.md +57 -0
  3. package/dist/lib/BaseDeviceSyncStateRoute.js +90 -0
  4. package/dist/lib/BaseDeviceSyncStateRoute.js.map +1 -0
  5. package/dist/lib/BaseEasRoute.js +213 -0
  6. package/dist/lib/BaseEasRoute.js.map +1 -0
  7. package/dist/lib/CompactDateTime.js +43 -0
  8. package/dist/lib/CompactDateTime.js.map +1 -0
  9. package/dist/lib/EasCommandHandler.js +2 -0
  10. package/dist/lib/EasCommandHandler.js.map +1 -0
  11. package/dist/lib/EasSyncKeyUtils.js +119 -0
  12. package/dist/lib/EasSyncKeyUtils.js.map +1 -0
  13. package/dist/lib/adapters/CalendarSyncAdapter.js +255 -0
  14. package/dist/lib/adapters/CalendarSyncAdapter.js.map +1 -0
  15. package/dist/lib/adapters/ContactsSyncAdapter.js +164 -0
  16. package/dist/lib/adapters/ContactsSyncAdapter.js.map +1 -0
  17. package/dist/lib/adapters/EasCollectionSyncAdapter.js +2 -0
  18. package/dist/lib/adapters/EasCollectionSyncAdapter.js.map +1 -0
  19. package/dist/lib/adapters/EmailSyncAdapter.js +230 -0
  20. package/dist/lib/adapters/EmailSyncAdapter.js.map +1 -0
  21. package/dist/lib/adapters/TasksSyncAdapter.js +100 -0
  22. package/dist/lib/adapters/TasksSyncAdapter.js.map +1 -0
  23. package/dist/lib/codec/WbxmlCodePages.js +706 -0
  24. package/dist/lib/codec/WbxmlCodePages.js.map +1 -0
  25. package/dist/lib/codec/WbxmlDecoder.js +154 -0
  26. package/dist/lib/codec/WbxmlDecoder.js.map +1 -0
  27. package/dist/lib/codec/WbxmlElement.js +31 -0
  28. package/dist/lib/codec/WbxmlElement.js.map +1 -0
  29. package/dist/lib/codec/WbxmlEncoder.js +88 -0
  30. package/dist/lib/codec/WbxmlEncoder.js.map +1 -0
  31. package/dist/lib/commands/ComposeMailCommand.js +187 -0
  32. package/dist/lib/commands/ComposeMailCommand.js.map +1 -0
  33. package/dist/lib/commands/FolderSyncCommand.js +150 -0
  34. package/dist/lib/commands/FolderSyncCommand.js.map +1 -0
  35. package/dist/lib/commands/GetItemEstimateCommand.js +149 -0
  36. package/dist/lib/commands/GetItemEstimateCommand.js.map +1 -0
  37. package/dist/lib/commands/ItemOperationsCommand.js +337 -0
  38. package/dist/lib/commands/ItemOperationsCommand.js.map +1 -0
  39. package/dist/lib/commands/MeetingResponseCommand.js +120 -0
  40. package/dist/lib/commands/MeetingResponseCommand.js.map +1 -0
  41. package/dist/lib/commands/MoveItemsCommand.js +103 -0
  42. package/dist/lib/commands/MoveItemsCommand.js.map +1 -0
  43. package/dist/lib/commands/PingCommand.js +145 -0
  44. package/dist/lib/commands/PingCommand.js.map +1 -0
  45. package/dist/lib/commands/ProvisionCommand.js +164 -0
  46. package/dist/lib/commands/ProvisionCommand.js.map +1 -0
  47. package/dist/lib/commands/ResolveRecipientsCommand.js +124 -0
  48. package/dist/lib/commands/ResolveRecipientsCommand.js.map +1 -0
  49. package/dist/lib/commands/SearchCommand.js +137 -0
  50. package/dist/lib/commands/SearchCommand.js.map +1 -0
  51. package/dist/lib/commands/SendMailCommand.js +18 -0
  52. package/dist/lib/commands/SendMailCommand.js.map +1 -0
  53. package/dist/lib/commands/SettingsCommand.js +138 -0
  54. package/dist/lib/commands/SettingsCommand.js.map +1 -0
  55. package/dist/lib/commands/SmartForwardCommand.js +22 -0
  56. package/dist/lib/commands/SmartForwardCommand.js.map +1 -0
  57. package/dist/lib/commands/SmartReplyCommand.js +22 -0
  58. package/dist/lib/commands/SmartReplyCommand.js.map +1 -0
  59. package/dist/lib/commands/SyncCommand.js +406 -0
  60. package/dist/lib/commands/SyncCommand.js.map +1 -0
  61. package/dist/lib/commands/mongo/FolderSyncCommandMongo.js +16 -0
  62. package/dist/lib/commands/mongo/FolderSyncCommandMongo.js.map +1 -0
  63. package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js +21 -0
  64. package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js.map +1 -0
  65. package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js +18 -0
  66. package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js.map +1 -0
  67. package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js +17 -0
  68. package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js.map +1 -0
  69. package/dist/lib/commands/mongo/MoveItemsCommandMongo.js +17 -0
  70. package/dist/lib/commands/mongo/MoveItemsCommandMongo.js.map +1 -0
  71. package/dist/lib/commands/mongo/ResolveRecipientsCommandMongo.js +20 -0
  72. package/dist/lib/commands/mongo/ResolveRecipientsCommandMongo.js.map +1 -0
  73. package/dist/lib/commands/mongo/SearchCommandMongo.js +20 -0
  74. package/dist/lib/commands/mongo/SearchCommandMongo.js.map +1 -0
  75. package/dist/lib/commands/mongo/SendMailCommandMongo.js +17 -0
  76. package/dist/lib/commands/mongo/SendMailCommandMongo.js.map +1 -0
  77. package/dist/lib/commands/mongo/SettingsCommandMongo.js +16 -0
  78. package/dist/lib/commands/mongo/SettingsCommandMongo.js.map +1 -0
  79. package/dist/lib/commands/mongo/SmartForwardCommandMongo.js +17 -0
  80. package/dist/lib/commands/mongo/SmartForwardCommandMongo.js.map +1 -0
  81. package/dist/lib/commands/mongo/SmartReplyCommandMongo.js +17 -0
  82. package/dist/lib/commands/mongo/SmartReplyCommandMongo.js.map +1 -0
  83. package/dist/lib/commands/mongo/SyncCommandMongo.js +26 -0
  84. package/dist/lib/commands/mongo/SyncCommandMongo.js.map +1 -0
  85. package/dist/lib/commands/mongo/index.js +14 -0
  86. package/dist/lib/commands/mongo/index.js.map +1 -0
  87. package/dist/lib/commands/sql/FolderSyncCommandSQL.js +16 -0
  88. package/dist/lib/commands/sql/FolderSyncCommandSQL.js.map +1 -0
  89. package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js +21 -0
  90. package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js.map +1 -0
  91. package/dist/lib/commands/sql/ItemOperationsCommandSQL.js +18 -0
  92. package/dist/lib/commands/sql/ItemOperationsCommandSQL.js.map +1 -0
  93. package/dist/lib/commands/sql/MeetingResponseCommandSQL.js +17 -0
  94. package/dist/lib/commands/sql/MeetingResponseCommandSQL.js.map +1 -0
  95. package/dist/lib/commands/sql/MoveItemsCommandSQL.js +17 -0
  96. package/dist/lib/commands/sql/MoveItemsCommandSQL.js.map +1 -0
  97. package/dist/lib/commands/sql/ResolveRecipientsCommandSQL.js +20 -0
  98. package/dist/lib/commands/sql/ResolveRecipientsCommandSQL.js.map +1 -0
  99. package/dist/lib/commands/sql/SearchCommandSQL.js +20 -0
  100. package/dist/lib/commands/sql/SearchCommandSQL.js.map +1 -0
  101. package/dist/lib/commands/sql/SendMailCommandSQL.js +17 -0
  102. package/dist/lib/commands/sql/SendMailCommandSQL.js.map +1 -0
  103. package/dist/lib/commands/sql/SettingsCommandSQL.js +16 -0
  104. package/dist/lib/commands/sql/SettingsCommandSQL.js.map +1 -0
  105. package/dist/lib/commands/sql/SmartForwardCommandSQL.js +17 -0
  106. package/dist/lib/commands/sql/SmartForwardCommandSQL.js.map +1 -0
  107. package/dist/lib/commands/sql/SmartReplyCommandSQL.js +17 -0
  108. package/dist/lib/commands/sql/SmartReplyCommandSQL.js.map +1 -0
  109. package/dist/lib/commands/sql/SyncCommandSQL.js +26 -0
  110. package/dist/lib/commands/sql/SyncCommandSQL.js.map +1 -0
  111. package/dist/lib/commands/sql/index.js +14 -0
  112. package/dist/lib/commands/sql/index.js.map +1 -0
  113. package/dist/lib/index.js +56 -0
  114. package/dist/lib/index.js.map +1 -0
  115. package/dist/lib/mongo/DeviceSyncStateRouteMongo.js +16 -0
  116. package/dist/lib/mongo/DeviceSyncStateRouteMongo.js.map +1 -0
  117. package/dist/lib/mongo/EasRouteMongo.js +51 -0
  118. package/dist/lib/mongo/EasRouteMongo.js.map +1 -0
  119. package/dist/lib/mongo/index.js +8 -0
  120. package/dist/lib/mongo/index.js.map +1 -0
  121. package/dist/lib/mongo.js +6 -0
  122. package/dist/lib/mongo.js.map +1 -0
  123. package/dist/lib/sql/DeviceSyncStateRouteSQL.js +16 -0
  124. package/dist/lib/sql/DeviceSyncStateRouteSQL.js.map +1 -0
  125. package/dist/lib/sql/EasRouteSQL.js +50 -0
  126. package/dist/lib/sql/EasRouteSQL.js.map +1 -0
  127. package/dist/lib/sql/index.js +8 -0
  128. package/dist/lib/sql/index.js.map +1 -0
  129. package/dist/lib/sql.js +6 -0
  130. package/dist/lib/sql.js.map +1 -0
  131. package/dist/types/BaseDeviceSyncStateRoute.d.ts +34 -0
  132. package/dist/types/BaseEasRoute.d.ts +64 -0
  133. package/dist/types/CompactDateTime.d.ts +22 -0
  134. package/dist/types/EasCommandHandler.d.ts +59 -0
  135. package/dist/types/EasSyncKeyUtils.d.ts +89 -0
  136. package/dist/types/adapters/CalendarSyncAdapter.d.ts +52 -0
  137. package/dist/types/adapters/ContactsSyncAdapter.d.ts +33 -0
  138. package/dist/types/adapters/EasCollectionSyncAdapter.d.ts +52 -0
  139. package/dist/types/adapters/EmailSyncAdapter.d.ts +53 -0
  140. package/dist/types/adapters/TasksSyncAdapter.d.ts +32 -0
  141. package/dist/types/codec/WbxmlCodePages.d.ts +45 -0
  142. package/dist/types/codec/WbxmlDecoder.d.ts +33 -0
  143. package/dist/types/codec/WbxmlElement.d.ts +43 -0
  144. package/dist/types/codec/WbxmlEncoder.d.ts +20 -0
  145. package/dist/types/commands/ComposeMailCommand.d.ts +50 -0
  146. package/dist/types/commands/FolderSyncCommand.d.ts +25 -0
  147. package/dist/types/commands/GetItemEstimateCommand.d.ts +43 -0
  148. package/dist/types/commands/ItemOperationsCommand.d.ts +72 -0
  149. package/dist/types/commands/MeetingResponseCommand.d.ts +32 -0
  150. package/dist/types/commands/MoveItemsCommand.d.ts +29 -0
  151. package/dist/types/commands/PingCommand.d.ts +33 -0
  152. package/dist/types/commands/ProvisionCommand.d.ts +54 -0
  153. package/dist/types/commands/ResolveRecipientsCommand.d.ts +34 -0
  154. package/dist/types/commands/SearchCommand.d.ts +39 -0
  155. package/dist/types/commands/SendMailCommand.d.ts +10 -0
  156. package/dist/types/commands/SettingsCommand.d.ts +37 -0
  157. package/dist/types/commands/SmartForwardCommand.d.ts +17 -0
  158. package/dist/types/commands/SmartReplyCommand.d.ts +17 -0
  159. package/dist/types/commands/SyncCommand.d.ts +100 -0
  160. package/dist/types/commands/mongo/FolderSyncCommandMongo.d.ts +8 -0
  161. package/dist/types/commands/mongo/GetItemEstimateCommandMongo.d.ts +7 -0
  162. package/dist/types/commands/mongo/ItemOperationsCommandMongo.d.ts +9 -0
  163. package/dist/types/commands/mongo/MeetingResponseCommandMongo.d.ts +8 -0
  164. package/dist/types/commands/mongo/MoveItemsCommandMongo.d.ts +8 -0
  165. package/dist/types/commands/mongo/ResolveRecipientsCommandMongo.d.ts +8 -0
  166. package/dist/types/commands/mongo/SearchCommandMongo.d.ts +8 -0
  167. package/dist/types/commands/mongo/SendMailCommandMongo.d.ts +8 -0
  168. package/dist/types/commands/mongo/SettingsCommandMongo.d.ts +7 -0
  169. package/dist/types/commands/mongo/SmartForwardCommandMongo.d.ts +8 -0
  170. package/dist/types/commands/mongo/SmartReplyCommandMongo.d.ts +8 -0
  171. package/dist/types/commands/mongo/SyncCommandMongo.d.ts +8 -0
  172. package/dist/types/commands/mongo/index.d.ts +9 -0
  173. package/dist/types/commands/sql/FolderSyncCommandSQL.d.ts +8 -0
  174. package/dist/types/commands/sql/GetItemEstimateCommandSQL.d.ts +7 -0
  175. package/dist/types/commands/sql/ItemOperationsCommandSQL.d.ts +9 -0
  176. package/dist/types/commands/sql/MeetingResponseCommandSQL.d.ts +8 -0
  177. package/dist/types/commands/sql/MoveItemsCommandSQL.d.ts +8 -0
  178. package/dist/types/commands/sql/ResolveRecipientsCommandSQL.d.ts +8 -0
  179. package/dist/types/commands/sql/SearchCommandSQL.d.ts +8 -0
  180. package/dist/types/commands/sql/SendMailCommandSQL.d.ts +8 -0
  181. package/dist/types/commands/sql/SettingsCommandSQL.d.ts +7 -0
  182. package/dist/types/commands/sql/SmartForwardCommandSQL.d.ts +8 -0
  183. package/dist/types/commands/sql/SmartReplyCommandSQL.d.ts +8 -0
  184. package/dist/types/commands/sql/SyncCommandSQL.d.ts +8 -0
  185. package/dist/types/commands/sql/index.d.ts +9 -0
  186. package/dist/types/index.d.ts +51 -0
  187. package/dist/types/mongo/DeviceSyncStateRouteMongo.d.ts +8 -0
  188. package/dist/types/mongo/EasRouteMongo.d.ts +14 -0
  189. package/dist/types/mongo/index.d.ts +3 -0
  190. package/dist/types/mongo.d.ts +1 -0
  191. package/dist/types/sql/DeviceSyncStateRouteSQL.d.ts +8 -0
  192. package/dist/types/sql/EasRouteSQL.d.ts +13 -0
  193. package/dist/types/sql/index.d.ts +3 -0
  194. package/dist/types/sql.d.ts +1 -0
  195. package/package.json +94 -0
@@ -0,0 +1,337 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ ///////////////////////////////////////////////////////////////////////////////
11
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
12
+ // SPDX-License-Identifier: MPL-2.0
13
+ ///////////////////////////////////////////////////////////////////////////////
14
+ import { simpleParser } from "mailparser";
15
+ import { ApiError, ObjectDecorators } from "@rapidrest/core";
16
+ import { ACLAction, ACLUtils, ApiErrorMessages, ApiErrors, RepoUtils } from "@rapidrest/service-core";
17
+ import { RecoverableRepoUtils } from "@rapidmx/restapi";
18
+ import { WbxmlCodePage } from "../codec/WbxmlCodePages.js";
19
+ import { childText, element, findChild, findChildren, opaqueElement, textElement } from "../codec/WbxmlElement.js";
20
+ import { decodeConversationId } from "../adapters/EmailSyncAdapter.js";
21
+ const { Config, Init, Inject } = ObjectDecorators;
22
+ /** Caps how many messages `emptyFolderContents`/`moveConversation` process per backing `find()`/delete-batch
23
+ * round - keeps memory bounded and, for `emptyFolderContents`, lets an arbitrarily large folder still be fully
24
+ * emptied via repeated batches rather than one unbounded query. */
25
+ const DEFAULT_BATCH_SIZE = 500;
26
+ /** Truncates UTF-8 text to at most `maxBytes` bytes without splitting a multi-byte character in half - backs
27
+ * off past any trailing UTF-8 continuation byte (`10xxxxxx`) before decoding back to a string. Only ever
28
+ * called once the caller has already confirmed the text exceeds `maxBytes` - trusts that rather than
29
+ * re-checking it here. */
30
+ function truncateUtf8(text, maxBytes) {
31
+ const buf = Buffer.from(text, "utf8");
32
+ let end = maxBytes;
33
+ while (end > 0 && (buf[end] & 0xc0) === 0x80) {
34
+ end--;
35
+ }
36
+ return buf.subarray(0, end).toString("utf8");
37
+ }
38
+ /**
39
+ * Handles EAS `ItemOperations`: `Fetch` (a `Message`'s full body or an `Attachment`'s binary content, by the
40
+ * same `ServerId`/uid this library already exposes elsewhere) and `EmptyFolderContents`.
41
+ *
42
+ * Per the published [MS-ASCMD] `ItemOperations` request schema (confirmed directly, not assumed), the command
43
+ * is a strict choice of exactly three operations - `Fetch` (unbounded), `EmptyFolderContents`, and `Move` -
44
+ * with no fourth "write a new item" capability anywhere in it; `Store` is a required *child* of `Fetch`
45
+ * (`"Mailbox"` or `"DocumentLibrary"`, the same store-selector role it plays in `SearchCommand`), not a
46
+ * separate write/upload command as its name might suggest.
47
+ *
48
+ * **Pragmatic subset, deliberately not the full MS-ASCMD `ItemOperations` semantics**:
49
+ * - `Move` moves an entire *conversation* (by `ConversationId`, opaque binary - see `EmailSyncAdapter`'s
50
+ * `encodeConversationId`/`decodeConversationId`) to a destination folder - unrelated to the standalone
51
+ * `MoveItems` command's per-message `SrcFldId`/`SrcMsgId`/`DstFldId` shape. Every `Message` sharing the
52
+ * decoded `conversationId` across the whole mailbox (not just one folder) that the caller has `UPDATE` on is
53
+ * relocated to `DstFldId`; one lacking permission is silently skipped rather than failing the whole move (a
54
+ * conversation can legitimately span folders the caller doesn't control, e.g. a shared mailbox's Inbox). An
55
+ * optional `MoveAlways` (a hint to keep auto-moving future messages in this conversation) is accepted but not
56
+ * acted on - this library's `MailFilterRule` has no conversation-scoped condition to key an ongoing rule off
57
+ * of, a documented simplification, not silent data loss (the move itself still happens).
58
+ * - `Store: "DocumentLibrary"` is rejected per-`Fetch` - matches `SearchCommand`'s own GAL-only scope decision;
59
+ * this library has no document-library model.
60
+ * - A `Fetch` failure (not found, no permission, malformed) aborts the whole request via an HTTP-level error
61
+ * rather than an embedded per-`Fetch` `Status` code the way `Sync`/`MoveItems`/`ResolveRecipients` report
62
+ * their own per-item failures - a deliberate, documented simplification carried over unchanged from this
63
+ * command's original single-`Fetch` design, not a new gap introduced by adding multi-`Fetch` support.
64
+ * - Only the "inline" delivery method is used (content embedded directly in the WBXML response) - the real
65
+ * spec's "multipart" alternative (WBXML as one part, binary content as a separate part) is not implemented;
66
+ * every attachment this library's own `ScanPipeline` already accepts is assumed to fit comfortably in memory
67
+ * for one response, the same assumption `BaseAttachmentRoute.download()` already makes.
68
+ * - `Options/BodyPreference`'s `Type`/`TruncationSize` are honored for a `Message` body fetch (plain text,
69
+ * HTML, or - `Type 4` - the raw MIME source verbatim); byte-range fetching (`Range`) is not implemented.
70
+ * - `EmptyFolderContents`'s `DeleteSubFolders` option is rejected outright rather than silently ignored -
71
+ * recursive subfolder deletion is out of scope for this pragmatic subset; emptying a single folder's own
72
+ * `Message`s is the common case this implements.
73
+ *
74
+ * `folderClass`/`messageClass`/`attachmentClass` are supplied by the Mongo/SQL concrete subclasses.
75
+ *
76
+ * @author Jean-Philippe Steinmetz
77
+ */
78
+ export class ItemOperationsCommand {
79
+ constructor() {
80
+ this.command = "ItemOperations";
81
+ this.maxFetchesPerRequest = 25;
82
+ this.batchSize = DEFAULT_BATCH_SIZE;
83
+ }
84
+ async init() {
85
+ this.folderRepo = await this._objectFactory.newInstance(RepoUtils, {
86
+ name: this.folderClass.name,
87
+ args: [this.folderClass],
88
+ });
89
+ this.messageRepo = await this._objectFactory.newInstance(RecoverableRepoUtils, {
90
+ name: this.messageClass.name,
91
+ args: [this.messageClass],
92
+ });
93
+ this.attachmentRepo = await this._objectFactory.newInstance(RepoUtils, {
94
+ name: this.attachmentClass.name,
95
+ args: [this.attachmentClass],
96
+ });
97
+ }
98
+ async handle(ctx) {
99
+ if (!this.folderRepo || !this.messageRepo || !this.attachmentRepo || !this.blobStore) {
100
+ throw new ApiError(ApiErrors.INTERNAL_ERROR, 500, ApiErrorMessages.INTERNAL_ERROR);
101
+ }
102
+ const fetchEls = ctx.request ? findChildren(ctx.request, "Fetch") : [];
103
+ const emptyEl = ctx.request ? findChild(ctx.request, "EmptyFolderContents") : undefined;
104
+ const moveEl = ctx.request ? findChild(ctx.request, "Move") : undefined;
105
+ if (fetchEls.length === 0 && !emptyEl && !moveEl) {
106
+ throw new ApiError(ApiErrors.INVALID_REQUEST, 400, ApiErrorMessages.INVALID_REQUEST);
107
+ }
108
+ // Each Fetch's content (a full message body or a whole attachment) is buffered in memory and assumed
109
+ // to individually fit comfortably (see this class's own doc comment) - with no cap on the *count* of
110
+ // Fetches, a single request could still force many such buffers to be built and held at once (even
111
+ // repeated fetches of the caller's own single large item), amplifying memory/IO cost far past what one
112
+ // real device round-trip needs. Rejected outright rather than silently truncated, matching this
113
+ // command's own precedent for `DeleteSubFolders`/`DocumentLibrary`.
114
+ if (fetchEls.length > this.maxFetchesPerRequest) {
115
+ throw new ApiError(ApiErrors.INVALID_REQUEST, 400, `ItemOperations supports at most ${this.maxFetchesPerRequest} Fetch elements per request.`);
116
+ }
117
+ const responseChildren = [];
118
+ for (const fetchEl of fetchEls) {
119
+ const store = childText(fetchEl, "Store");
120
+ if (store === "DocumentLibrary") {
121
+ throw new ApiError(ApiErrors.INVALID_REQUEST, 400, "DocumentLibrary fetches are not supported.");
122
+ }
123
+ const fileReference = childText(fetchEl, "FileReference");
124
+ const serverId = childText(fetchEl, "ServerId");
125
+ const optionsEl = findChild(fetchEl, "Options");
126
+ responseChildren.push(fileReference
127
+ ? await this.fetchAttachment(ctx, fileReference)
128
+ : await this.fetchMessage(ctx, serverId, optionsEl));
129
+ }
130
+ if (emptyEl) {
131
+ responseChildren.push(await this.emptyFolderContents(ctx, emptyEl));
132
+ }
133
+ if (moveEl) {
134
+ responseChildren.push(await this.moveConversation(ctx, moveEl));
135
+ }
136
+ return element(WbxmlCodePage.ItemOperations, "ItemOperations", [
137
+ textElement(WbxmlCodePage.ItemOperations, "Status", "1"),
138
+ element(WbxmlCodePage.ItemOperations, "Response", responseChildren),
139
+ ]);
140
+ }
141
+ async fetchMessage(ctx, serverId, optionsEl) {
142
+ if (!serverId) {
143
+ throw new ApiError(ApiErrors.INVALID_REQUEST, 400, "Fetch requires either a ServerId or a FileReference.");
144
+ }
145
+ const message = await this.messageRepo.findOne(serverId, { ignoreACL: true });
146
+ if (!message) {
147
+ throw new ApiError(ApiErrors.NOT_FOUND, 404, ApiErrorMessages.NOT_FOUND);
148
+ }
149
+ if (!(await this.aclUtils.hasPermission(ctx.user, message.folderUid, ACLAction.READ))) {
150
+ throw new ApiError(ApiErrors.AUTH_PERMISSION_FAILURE, 403, ApiErrorMessages.AUTH_PERMISSION_FAILURE);
151
+ }
152
+ const bodyPreferenceEl = optionsEl ? findChild(optionsEl, "BodyPreference") : undefined;
153
+ const requestedType = bodyPreferenceEl ? childText(bodyPreferenceEl, "Type") : undefined;
154
+ const truncationSizeText = bodyPreferenceEl ? childText(bodyPreferenceEl, "TruncationSize") : undefined;
155
+ const truncationSize = truncationSizeText !== undefined ? Number(truncationSizeText) : undefined;
156
+ // Prefer the already-sanitized HTML body (script/active-content stripped by ScanPipeline at
157
+ // ingestion/send time) over re-deriving anything from the raw MIME - the same preference
158
+ // `Message.sanitizedHtmlBlobKey`'s own doc comment describes for any renderer. `Type 4` (MIME) is an
159
+ // explicit client request for the verbatim raw source instead, honored regardless of that preference.
160
+ let bodyType = "1";
161
+ let bodyText;
162
+ if (requestedType === "4") {
163
+ bodyType = "4";
164
+ bodyText = (await this.blobStore.get(message.bodyBlobKey)).toString("utf-8");
165
+ }
166
+ else if (message.sanitizedHtmlBlobKey) {
167
+ bodyType = "2";
168
+ bodyText = (await this.blobStore.get(message.sanitizedHtmlBlobKey)).toString("utf-8");
169
+ }
170
+ else {
171
+ const raw = await this.blobStore.get(message.bodyBlobKey);
172
+ const parsed = await simpleParser(raw);
173
+ bodyText = parsed.text ?? "";
174
+ }
175
+ // Per MS-ASAIRSYNCBASE, EstimatedDataSize reports the body's size BEFORE any truncation was applied
176
+ // (so the client knows how much more content exists beyond what it received) - captured here, before
177
+ // truncateUtf8() below reassigns bodyText to the shorter value.
178
+ const estimatedDataSize = Buffer.byteLength(bodyText, "utf8");
179
+ let truncated = false;
180
+ if (truncationSize !== undefined && Number.isFinite(truncationSize) && estimatedDataSize > truncationSize) {
181
+ bodyText = truncateUtf8(bodyText, truncationSize);
182
+ truncated = true;
183
+ }
184
+ return element(WbxmlCodePage.ItemOperations, "Fetch", [
185
+ textElement(WbxmlCodePage.ItemOperations, "Status", "1"),
186
+ textElement(WbxmlCodePage.AirSync, "Class", "Email"),
187
+ textElement(WbxmlCodePage.AirSync, "CollectionId", message.folderUid),
188
+ textElement(WbxmlCodePage.AirSync, "ServerId", serverId),
189
+ element(WbxmlCodePage.ItemOperations, "Properties", [
190
+ element(WbxmlCodePage.AirSyncBase, "Body", [
191
+ textElement(WbxmlCodePage.AirSyncBase, "Type", bodyType),
192
+ textElement(WbxmlCodePage.AirSyncBase, "EstimatedDataSize", String(estimatedDataSize)),
193
+ textElement(WbxmlCodePage.AirSyncBase, "Truncated", truncated ? "1" : "0"),
194
+ textElement(WbxmlCodePage.AirSyncBase, "Data", bodyText),
195
+ ]),
196
+ ]),
197
+ ]);
198
+ }
199
+ async emptyFolderContents(ctx, emptyEl) {
200
+ // Reuses AirSync's own `CollectionId` (the same tag `Sync`/`Fetch` responses already reference a
201
+ // folder by) rather than a page-specific tag - `WbxmlCodePage.ItemOperations` has no `FolderId` token
202
+ // of its own at all, confirmed against its own tag table.
203
+ const folderUid = childText(emptyEl, "CollectionId");
204
+ if (!folderUid) {
205
+ throw new ApiError(ApiErrors.INVALID_REQUEST, 400, "EmptyFolderContents requires a CollectionId.");
206
+ }
207
+ const optionsEl = findChild(emptyEl, "Options");
208
+ if (optionsEl && findChild(optionsEl, "DeleteSubFolders")) {
209
+ // Documented gap, not silently ignored - see this class's own doc comment.
210
+ throw new ApiError(ApiErrors.INVALID_REQUEST, 400, "DeleteSubFolders is not supported.");
211
+ }
212
+ if (!(await this.aclUtils.hasPermission(ctx.user, folderUid, ACLAction.DELETE))) {
213
+ throw new ApiError(ApiErrors.AUTH_PERMISSION_FAILURE, 403, ApiErrorMessages.AUTH_PERMISSION_FAILURE);
214
+ }
215
+ // Processed in bounded batches rather than one unbounded `find()` - a folder with a very large number
216
+ // of messages would otherwise force the whole set into memory at once. Deletes within a batch still run
217
+ // sequentially, not via `Promise.all` - the SQL backend (`better-sqlite3`) shares one connection per
218
+ // request and each `delete()` opens its own transaction, so concurrent deletes fail outright with
219
+ // "cannot start a transaction within a transaction" (confirmed against real SQL test failures, not
220
+ // theoretical). Looping (rather than a single pass) still empties the folder completely, however large -
221
+ // a deleted row no longer matches this same `find()` (soft-deleted rows are excluded from an ordinary
222
+ // query by default), so the loop always terminates once truly empty.
223
+ for (;;) {
224
+ const batch = await this.messageRepo.find({ folderUid, limit: this.batchSize }, {
225
+ ignoreACL: true,
226
+ limit: this.batchSize,
227
+ });
228
+ if (batch.length === 0) {
229
+ break;
230
+ }
231
+ for (const message of batch) {
232
+ await this.messageRepo.delete(message.uid, { ignoreACL: true, user: ctx.user });
233
+ }
234
+ }
235
+ return element(WbxmlCodePage.ItemOperations, "EmptyFolderContents", [
236
+ textElement(WbxmlCodePage.ItemOperations, "Status", "1"),
237
+ ]);
238
+ }
239
+ /**
240
+ * Handles `ItemOperations`' `Move`: relocates every `Message` sharing a decoded `ConversationId` to
241
+ * `DstFldId`. Unlike `emptyFolderContents`/`fetchMessage` (which fail the whole request via a thrown
242
+ * `ApiError`), this reports failure through the embedded `Status` the same way `MoveItemsCommand` does for
243
+ * its own per-item results - a malformed/unresolvable `Move` is a normal outcome for this operation, not an
244
+ * exceptional one. Reports failure (`Status 3`) if not even one message was actually moved - including when
245
+ * every message sharing the conversation sits in a folder the caller lacks `UPDATE` on, which would
246
+ * otherwise silently fall through to a false "success" with nothing having moved.
247
+ */
248
+ async moveConversation(ctx, moveEl) {
249
+ const conversationIdEl = findChild(moveEl, "ConversationId");
250
+ const dstFldId = childText(moveEl, "DstFldId");
251
+ if (!conversationIdEl?.opaque || !dstFldId) {
252
+ return this.moveResponse("3");
253
+ }
254
+ const destFolder = await this.folderRepo.findOne(dstFldId, { ignoreACL: true });
255
+ if (!destFolder ||
256
+ destFolder.mailboxUid !== ctx.mailboxUid ||
257
+ !(await this.aclUtils.hasPermission(ctx.user, dstFldId, ACLAction.CREATE))) {
258
+ return this.moveResponse("3");
259
+ }
260
+ const conversationId = decodeConversationId(conversationIdEl.opaque);
261
+ // Capped rather than an unbounded `find()` - an unusually long-running thread could otherwise return an
262
+ // unbounded number of rows for one request.
263
+ const messages = await this.messageRepo.find({ mailboxUid: ctx.mailboxUid, conversationId, limit: this.batchSize }, { ignoreACL: true, limit: this.batchSize });
264
+ if (messages.length === 0) {
265
+ return this.moveResponse("3");
266
+ }
267
+ // The permission checks (reads) are independent and safe to run concurrently, but the updates
268
+ // themselves are not: the SQL backend (`better-sqlite3`) shares one connection per request and each
269
+ // `update()` opens its own transaction, so concurrent updates fail outright with "cannot start a
270
+ // transaction within a transaction" (confirmed against real SQL test failures, not theoretical) -
271
+ // updates run sequentially in a plain loop instead.
272
+ const permitted = await Promise.all(messages.map((message) => this.aclUtils.hasPermission(ctx.user, message.folderUid, ACLAction.UPDATE)));
273
+ let movedAny = false;
274
+ for (let i = 0; i < messages.length; i++) {
275
+ if (!permitted[i]) {
276
+ continue;
277
+ }
278
+ const message = messages[i];
279
+ await this.messageRepo.update({ uid: message.uid, version: message.version, folderUid: dstFldId }, message, { ignoreACL: true, user: ctx.user });
280
+ movedAny = true;
281
+ }
282
+ if (!movedAny) {
283
+ return this.moveResponse("3");
284
+ }
285
+ return element(WbxmlCodePage.ItemOperations, "Move", [
286
+ textElement(WbxmlCodePage.ItemOperations, "Status", "1"),
287
+ textElement(WbxmlCodePage.ItemOperations, "DstFldId", dstFldId),
288
+ opaqueElement(WbxmlCodePage.ItemOperations, "ConversationId", conversationIdEl.opaque),
289
+ ]);
290
+ }
291
+ moveResponse(status) {
292
+ return element(WbxmlCodePage.ItemOperations, "Move", [textElement(WbxmlCodePage.ItemOperations, "Status", status)]);
293
+ }
294
+ async fetchAttachment(ctx, fileReference) {
295
+ const attachment = await this.attachmentRepo.findOne(fileReference, { ignoreACL: true });
296
+ if (!attachment) {
297
+ throw new ApiError(ApiErrors.NOT_FOUND, 404, ApiErrorMessages.NOT_FOUND);
298
+ }
299
+ if (!(await this.aclUtils.hasPermission(ctx.user, attachment.folderUid, ACLAction.READ))) {
300
+ throw new ApiError(ApiErrors.AUTH_PERMISSION_FAILURE, 403, ApiErrorMessages.AUTH_PERMISSION_FAILURE);
301
+ }
302
+ const content = await this.blobStore.get(attachment.blobKey);
303
+ return element(WbxmlCodePage.ItemOperations, "Fetch", [
304
+ textElement(WbxmlCodePage.ItemOperations, "Status", "1"),
305
+ textElement(WbxmlCodePage.AirSyncBase, "FileReference", fileReference),
306
+ element(WbxmlCodePage.ItemOperations, "Properties", [
307
+ textElement(WbxmlCodePage.AirSyncBase, "ContentType", attachment.mimeType),
308
+ // "Inline" delivery per MS-ASCMD: binary content is base64-encoded and embedded directly in the
309
+ // WBXML, rather than this library's own opaque/binary element type.
310
+ textElement(WbxmlCodePage.ItemOperations, "Data", content.toString("base64")),
311
+ ]),
312
+ ]);
313
+ }
314
+ }
315
+ __decorate([
316
+ Inject("BlobStore"),
317
+ __metadata("design:type", Object)
318
+ ], ItemOperationsCommand.prototype, "blobStore", void 0);
319
+ __decorate([
320
+ Inject(ACLUtils),
321
+ __metadata("design:type", ACLUtils)
322
+ ], ItemOperationsCommand.prototype, "aclUtils", void 0);
323
+ __decorate([
324
+ Config("mail:eas:itemoperations_max_fetch", 25),
325
+ __metadata("design:type", Number)
326
+ ], ItemOperationsCommand.prototype, "maxFetchesPerRequest", void 0);
327
+ __decorate([
328
+ Config("mail:eas:itemoperations_batch_size", DEFAULT_BATCH_SIZE),
329
+ __metadata("design:type", Number)
330
+ ], ItemOperationsCommand.prototype, "batchSize", void 0);
331
+ __decorate([
332
+ Init,
333
+ __metadata("design:type", Function),
334
+ __metadata("design:paramtypes", []),
335
+ __metadata("design:returntype", Promise)
336
+ ], ItemOperationsCommand.prototype, "init", null);
337
+ //# sourceMappingURL=ItemOperationsCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ItemOperationsCommand.js","sourceRoot":"","sources":["../../../src/commands/ItemOperationsCommand.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,120 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ ///////////////////////////////////////////////////////////////////////////////
11
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
12
+ // SPDX-License-Identifier: MPL-2.0
13
+ ///////////////////////////////////////////////////////////////////////////////
14
+ import { ApiError, ObjectDecorators } from "@rapidrest/core";
15
+ import { ACLAction, ACLUtils, ApiErrorMessages, ApiErrors, RepoUtils } from "@rapidrest/service-core";
16
+ import { WbxmlCodePage } from "../codec/WbxmlCodePages.js";
17
+ import { childText, element, findChild, textElement } from "../codec/WbxmlElement.js";
18
+ import { AttendeeResponseStatus, RecoverableRepoUtils } from "@rapidmx/restapi";
19
+ const { Init, Inject } = ObjectDecorators;
20
+ /** MS-ASCMD `UserResponse`: 1=Accepted, 2=Tentatively accepted, 3=Declined. */
21
+ const USER_RESPONSE_STATUS = {
22
+ "1": AttendeeResponseStatus.ACCEPTED,
23
+ "2": AttendeeResponseStatus.TENTATIVE,
24
+ "3": AttendeeResponseStatus.DECLINED,
25
+ };
26
+ const USER_RESPONSE_DECLINED = "3";
27
+ /**
28
+ * Handles EAS `MeetingResponse`: records the caller's own accept/tentative/decline response to a meeting on
29
+ * the referenced `CalendarEvent`. `RequestId` is the same `Message`/`CalendarEvent.uid` this library already
30
+ * exposes as `ServerId` elsewhere (Sync/FolderSync) - no separate lookup table is needed.
31
+ *
32
+ * **Pragmatic subset**: only the first `<Request>` in the command is processed (the real spec allows several
33
+ * per request, matching `Sync`'s own single-`Collection`-per-request scope in this library). A **decline**
34
+ * soft-deletes the `CalendarEvent` (matching real Exchange behavior) rather than merely flipping the caller's
35
+ * own `Attendee.responseStatus` - each attendee has their own row already (`CalendarEvent.mailboxUid` scopes
36
+ * every event to a single mailbox, per this library's architecture), so deleting *this* row only removes the
37
+ * meeting from the declining attendee's own calendar, leaving the organizer's and every other attendee's own
38
+ * copy untouched. Accept/Tentative still update `Attendee.responseStatus` in place. The response omits
39
+ * `CalendarId` for a decline either way, matching the spec's own convention, so a client relying on that
40
+ * signal isn't misled into thinking a new item was created.
41
+ *
42
+ * `calendarEventClass`/`mailboxClass` are supplied by the Mongo/SQL concrete subclasses.
43
+ *
44
+ * @author Jean-Philippe Steinmetz
45
+ */
46
+ export class MeetingResponseCommand {
47
+ constructor() {
48
+ this.command = "MeetingResponse";
49
+ }
50
+ async init() {
51
+ this.calendarEventRepo = await this._objectFactory.newInstance(RecoverableRepoUtils, {
52
+ name: this.calendarEventClass.name,
53
+ args: [this.calendarEventClass],
54
+ });
55
+ this.mailboxRepo = await this._objectFactory.newInstance(RepoUtils, {
56
+ name: this.mailboxClass.name,
57
+ args: [this.mailboxClass],
58
+ });
59
+ }
60
+ async handle(ctx) {
61
+ if (!this.calendarEventRepo || !this.mailboxRepo) {
62
+ throw new ApiError(ApiErrors.INTERNAL_ERROR, 500, ApiErrorMessages.INTERNAL_ERROR);
63
+ }
64
+ const requestEl = ctx.request ? findChild(ctx.request, "Request") : undefined;
65
+ if (!requestEl) {
66
+ throw new ApiError(ApiErrors.INVALID_REQUEST, 400, ApiErrorMessages.INVALID_REQUEST);
67
+ }
68
+ const userResponse = childText(requestEl, "UserResponse");
69
+ const requestId = childText(requestEl, "RequestId");
70
+ if (!userResponse || !requestId || !USER_RESPONSE_STATUS[userResponse]) {
71
+ throw new ApiError(ApiErrors.INVALID_REQUEST, 400, ApiErrorMessages.INVALID_REQUEST);
72
+ }
73
+ const event = await this.calendarEventRepo.findOne(requestId, { ignoreACL: true });
74
+ if (!event) {
75
+ throw new ApiError(ApiErrors.NOT_FOUND, 404, ApiErrorMessages.NOT_FOUND);
76
+ }
77
+ if (!(await this.aclUtils.hasPermission(ctx.user, event.folderUid, ACLAction.UPDATE))) {
78
+ throw new ApiError(ApiErrors.AUTH_PERMISSION_FAILURE, 403, ApiErrorMessages.AUTH_PERMISSION_FAILURE);
79
+ }
80
+ const mailbox = await this.mailboxRepo.findOne(ctx.mailboxUid, { ignoreACL: true });
81
+ if (!mailbox) {
82
+ throw new ApiError(ApiErrors.NOT_FOUND, 404, ApiErrorMessages.NOT_FOUND);
83
+ }
84
+ const callerAddresses = new Set([mailbox.primarySmtpAddress.toLowerCase(), ...mailbox.aliasAddresses.map((a) => a.toLowerCase())]);
85
+ const attendeeIndex = event.attendees.findIndex((attendee) => callerAddresses.has(attendee.address.toLowerCase()));
86
+ if (attendeeIndex === -1) {
87
+ throw new ApiError(ApiErrors.NOT_FOUND, 404, "The caller is not an attendee of this calendar event.");
88
+ }
89
+ if (userResponse === USER_RESPONSE_DECLINED) {
90
+ // Soft-delete this attendee's own copy of the event - see this class's own doc comment for why
91
+ // that's correct here rather than merely flipping responseStatus (the real spec's own behavior:
92
+ // declining removes the meeting from the declining attendee's calendar).
93
+ await this.calendarEventRepo.delete(event.uid, { ignoreACL: true, user: ctx.user });
94
+ }
95
+ else {
96
+ const attendees = event.attendees.map((attendee, i) => i === attendeeIndex ? { ...attendee, responseStatus: USER_RESPONSE_STATUS[userResponse] } : attendee);
97
+ await this.calendarEventRepo.update({ uid: event.uid, version: event.version, attendees }, event, { ignoreACL: true, user: ctx.user });
98
+ }
99
+ return element(WbxmlCodePage.MeetingResponse, "MeetingResponse", [
100
+ element(WbxmlCodePage.MeetingResponse, "Result", [
101
+ textElement(WbxmlCodePage.MeetingResponse, "RequestId", requestId),
102
+ textElement(WbxmlCodePage.MeetingResponse, "Status", "1"),
103
+ ...(userResponse !== USER_RESPONSE_DECLINED
104
+ ? [textElement(WbxmlCodePage.MeetingResponse, "CalendarId", event.uid)]
105
+ : []),
106
+ ]),
107
+ ]);
108
+ }
109
+ }
110
+ __decorate([
111
+ Inject(ACLUtils),
112
+ __metadata("design:type", ACLUtils)
113
+ ], MeetingResponseCommand.prototype, "aclUtils", void 0);
114
+ __decorate([
115
+ Init,
116
+ __metadata("design:type", Function),
117
+ __metadata("design:paramtypes", []),
118
+ __metadata("design:returntype", Promise)
119
+ ], MeetingResponseCommand.prototype, "init", null);
120
+ //# sourceMappingURL=MeetingResponseCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeetingResponseCommand.js","sourceRoot":"","sources":["../../../src/commands/MeetingResponseCommand.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ ///////////////////////////////////////////////////////////////////////////////
11
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
12
+ // SPDX-License-Identifier: MPL-2.0
13
+ ///////////////////////////////////////////////////////////////////////////////
14
+ import { ObjectDecorators } from "@rapidrest/core";
15
+ import { ACLAction, ACLUtils, RepoUtils } from "@rapidrest/service-core";
16
+ import { WbxmlCodePage } from "../codec/WbxmlCodePages.js";
17
+ import { childText, element, findChildren, textElement } from "../codec/WbxmlElement.js";
18
+ const { Init, Inject } = ObjectDecorators;
19
+ /** [MS-ASCMD] `Move` `Status` codes this pragmatic subset actually distinguishes: `1` success, everything else
20
+ * collapses to `3` ("failure") - an approximation, not a byte-exact enumeration of every real status code
21
+ * MS-ASCMD's `Move` page defines, matching `ProvisionCommand`'s own identical precedent for the same reasoning
22
+ * (this library only needs to tell a client "it worked" from "it didn't, don't retry blindly"). */
23
+ const STATUS_SUCCESS = "1";
24
+ const STATUS_FAILURE = "3";
25
+ /**
26
+ * Handles the standalone EAS `MoveItems` command: moves one or more `Message`s between folders in the caller's
27
+ * own mailbox by `ServerId`/`uid`.
28
+ *
29
+ * **Pragmatic subset**: `Message` only - `Contacts`/`Calendar`/`Tasks` moves are rare in practice (unlike
30
+ * `Message`, whose Inbox-to-subfolder filing is a real, common client action) and would each need their own
31
+ * ACL/ownership verification path for comparatively little value; a client attempting one gets `Status 3`
32
+ * rather than being silently ignored. `DstMsgId` in the response is always the same `uid` as `SrcMsgId` - this
33
+ * library never mints a new identifier on move, unlike a real Exchange server, which sometimes does.
34
+ *
35
+ * `messageClass`/`folderClass` are supplied by the Mongo/SQL concrete subclasses.
36
+ *
37
+ * @author Jean-Philippe Steinmetz
38
+ */
39
+ export class MoveItemsCommand {
40
+ constructor() {
41
+ this.command = "MoveItems";
42
+ }
43
+ async init() {
44
+ this.messageRepo = await this._objectFactory.newInstance(RepoUtils, {
45
+ name: this.messageClass.name,
46
+ args: [this.messageClass],
47
+ });
48
+ this.folderRepo = await this._objectFactory.newInstance(RepoUtils, {
49
+ name: this.folderClass.name,
50
+ args: [this.folderClass],
51
+ });
52
+ }
53
+ async handle(ctx) {
54
+ const moveEls = ctx.request ? findChildren(ctx.request, "Move") : [];
55
+ const responses = [];
56
+ for (const moveEl of moveEls) {
57
+ responses.push(await this.moveOne(ctx, moveEl));
58
+ }
59
+ return element(WbxmlCodePage.Move, "MoveItems", responses);
60
+ }
61
+ async moveOne(ctx, moveEl) {
62
+ const srcMsgId = childText(moveEl, "SrcMsgId");
63
+ const srcFldId = childText(moveEl, "SrcFldId");
64
+ const dstFldId = childText(moveEl, "DstFldId");
65
+ if (!srcMsgId || !srcFldId || !dstFldId) {
66
+ return this.responseElement(srcMsgId, STATUS_FAILURE, undefined);
67
+ }
68
+ const message = await this.messageRepo.findOne(srcMsgId, { ignoreACL: true });
69
+ // The client's claimed SrcFldId must match where the message actually lives - protects against a
70
+ // stale/mismatched client cache rather than trusting the claim outright.
71
+ if (!message || message.folderUid !== srcFldId) {
72
+ return this.responseElement(srcMsgId, STATUS_FAILURE, undefined);
73
+ }
74
+ if (!(await this.aclUtils.hasPermission(ctx.user, srcFldId, ACLAction.UPDATE)) ||
75
+ !(await this.aclUtils.hasPermission(ctx.user, dstFldId, ACLAction.CREATE))) {
76
+ return this.responseElement(srcMsgId, STATUS_FAILURE, undefined);
77
+ }
78
+ const destFolder = await this.folderRepo.findOne(dstFldId, { ignoreACL: true });
79
+ if (!destFolder || destFolder.mailboxUid !== ctx.mailboxUid) {
80
+ return this.responseElement(srcMsgId, STATUS_FAILURE, undefined);
81
+ }
82
+ await this.messageRepo.update({ uid: message.uid, version: message.version, folderUid: dstFldId }, message, { ignoreACL: true, user: ctx.user });
83
+ return this.responseElement(srcMsgId, STATUS_SUCCESS, message.uid);
84
+ }
85
+ responseElement(srcMsgId, status, dstMsgId) {
86
+ return element(WbxmlCodePage.Move, "Response", [
87
+ ...(srcMsgId ? [textElement(WbxmlCodePage.Move, "SrcMsgId", srcMsgId)] : []),
88
+ textElement(WbxmlCodePage.Move, "Status", status),
89
+ ...(dstMsgId ? [textElement(WbxmlCodePage.Move, "DstMsgId", dstMsgId)] : []),
90
+ ]);
91
+ }
92
+ }
93
+ __decorate([
94
+ Inject(ACLUtils),
95
+ __metadata("design:type", ACLUtils)
96
+ ], MoveItemsCommand.prototype, "aclUtils", void 0);
97
+ __decorate([
98
+ Init,
99
+ __metadata("design:type", Function),
100
+ __metadata("design:paramtypes", []),
101
+ __metadata("design:returntype", Promise)
102
+ ], MoveItemsCommand.prototype, "init", null);
103
+ //# sourceMappingURL=MoveItemsCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MoveItemsCommand.js","sourceRoot":"","sources":["../../../src/commands/MoveItemsCommand.ts"],"names":[],"mappings":"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"}