@rapidmx/mapi-plugin 1.0.0-beta.10

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 (204) hide show
  1. package/LICENSE +382 -0
  2. package/README.md +60 -0
  3. package/dist/lib/BaseMapiEmsmdbRoute.js +489 -0
  4. package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -0
  5. package/dist/lib/BaseMapiNspiRoute.js +114 -0
  6. package/dist/lib/BaseMapiNspiRoute.js.map +1 -0
  7. package/dist/lib/MapiSessionManager.js +475 -0
  8. package/dist/lib/MapiSessionManager.js.map +1 -0
  9. package/dist/lib/RegexPatternUtils.js +30 -0
  10. package/dist/lib/RegexPatternUtils.js.map +1 -0
  11. package/dist/lib/RopDispatcher.js +145 -0
  12. package/dist/lib/RopDispatcher.js.map +1 -0
  13. package/dist/lib/codec/AppointmentRecurrence.js +315 -0
  14. package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
  15. package/dist/lib/codec/BufferCursor.js +242 -0
  16. package/dist/lib/codec/BufferCursor.js.map +1 -0
  17. package/dist/lib/codec/GlobalObjectId.js +98 -0
  18. package/dist/lib/codec/GlobalObjectId.js.map +1 -0
  19. package/dist/lib/codec/MapiGuid.js +41 -0
  20. package/dist/lib/codec/MapiGuid.js.map +1 -0
  21. package/dist/lib/codec/MapiTimeZone.js +126 -0
  22. package/dist/lib/codec/MapiTimeZone.js.map +1 -0
  23. package/dist/lib/codec/PropertyValue.js +240 -0
  24. package/dist/lib/codec/PropertyValue.js.map +1 -0
  25. package/dist/lib/codec/RopBuffer.js +38 -0
  26. package/dist/lib/codec/RopBuffer.js.map +1 -0
  27. package/dist/lib/codec/TypedString.js +54 -0
  28. package/dist/lib/codec/TypedString.js.map +1 -0
  29. package/dist/lib/index.js +99 -0
  30. package/dist/lib/index.js.map +1 -0
  31. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +85 -0
  32. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
  33. package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
  34. package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
  35. package/dist/lib/mongo/index.js +7 -0
  36. package/dist/lib/mongo/index.js.map +1 -0
  37. package/dist/lib/mongo.js +6 -0
  38. package/dist/lib/mongo.js.map +1 -0
  39. package/dist/lib/nspi/NspiBindHandler.js +54 -0
  40. package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
  41. package/dist/lib/nspi/NspiCodec.js +106 -0
  42. package/dist/lib/nspi/NspiCodec.js.map +1 -0
  43. package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
  44. package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
  45. package/dist/lib/rop/AddressList.js +114 -0
  46. package/dist/lib/rop/AddressList.js.map +1 -0
  47. package/dist/lib/rop/CalendarEventTarget.js +30 -0
  48. package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
  49. package/dist/lib/rop/CalendarNamedProperties.js +57 -0
  50. package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
  51. package/dist/lib/rop/ContactTarget.js +19 -0
  52. package/dist/lib/rop/ContactTarget.js.map +1 -0
  53. package/dist/lib/rop/ContentsTable.js +60 -0
  54. package/dist/lib/rop/ContentsTable.js.map +1 -0
  55. package/dist/lib/rop/ExecuteBudget.js +82 -0
  56. package/dist/lib/rop/ExecuteBudget.js.map +1 -0
  57. package/dist/lib/rop/FastTransferStream.js +195 -0
  58. package/dist/lib/rop/FastTransferStream.js.map +1 -0
  59. package/dist/lib/rop/FolderTarget.js +118 -0
  60. package/dist/lib/rop/FolderTarget.js.map +1 -0
  61. package/dist/lib/rop/HandleDataCache.js +412 -0
  62. package/dist/lib/rop/HandleDataCache.js.map +1 -0
  63. package/dist/lib/rop/MeetingMessageClassHandler.js +241 -0
  64. package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
  65. package/dist/lib/rop/MessageBodyStream.js +68 -0
  66. package/dist/lib/rop/MessageBodyStream.js.map +1 -0
  67. package/dist/lib/rop/MessageTarget.js +57 -0
  68. package/dist/lib/rop/MessageTarget.js.map +1 -0
  69. package/dist/lib/rop/NamedPropertyRegistry.js +69 -0
  70. package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
  71. package/dist/lib/rop/PropertyResolvers.js +353 -0
  72. package/dist/lib/rop/PropertyResolvers.js.map +1 -0
  73. package/dist/lib/rop/RepoPaging.js +54 -0
  74. package/dist/lib/rop/RepoPaging.js.map +1 -0
  75. package/dist/lib/rop/RestapiRules.js +52 -0
  76. package/dist/lib/rop/RestapiRules.js.map +1 -0
  77. package/dist/lib/rop/RopCreateMessageHandler.js +55 -0
  78. package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
  79. package/dist/lib/rop/RopDeleteFolderHandler.js +174 -0
  80. package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
  81. package/dist/lib/rop/RopDeleteMessagesHandler.js +110 -0
  82. package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
  83. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
  84. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
  85. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +60 -0
  86. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
  87. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +76 -0
  88. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
  89. package/dist/lib/rop/RopGetContentsTableHandler.js +55 -0
  90. package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
  91. package/dist/lib/rop/RopGetHierarchyTableHandler.js +42 -0
  92. package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
  93. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +56 -0
  94. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
  95. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
  96. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
  97. package/dist/lib/rop/RopHandler.js +6 -0
  98. package/dist/lib/rop/RopHandler.js.map +1 -0
  99. package/dist/lib/rop/RopLogonHandler.js +135 -0
  100. package/dist/lib/rop/RopLogonHandler.js.map +1 -0
  101. package/dist/lib/rop/RopOpenFolderHandler.js +48 -0
  102. package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
  103. package/dist/lib/rop/RopOpenMessageHandler.js +83 -0
  104. package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
  105. package/dist/lib/rop/RopOpenStreamHandler.js +84 -0
  106. package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
  107. package/dist/lib/rop/RopQueryRowsHandler.js +120 -0
  108. package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
  109. package/dist/lib/rop/RopReadStreamHandler.js +73 -0
  110. package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
  111. package/dist/lib/rop/RopReleaseHandler.js +23 -0
  112. package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
  113. package/dist/lib/rop/RopSaveChangesMessageHandler.js +269 -0
  114. package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
  115. package/dist/lib/rop/RopSetColumnsHandler.js +50 -0
  116. package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
  117. package/dist/lib/rop/RopSetPropertiesHandler.js +173 -0
  118. package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
  119. package/dist/lib/rop/RopSubmitMessageHandler.js +342 -0
  120. package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
  121. package/dist/lib/rop/RopWriteStreamHandler.js +72 -0
  122. package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
  123. package/dist/lib/rop/TaskNamedProperties.js +29 -0
  124. package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
  125. package/dist/lib/rop/TaskTarget.js +15 -0
  126. package/dist/lib/rop/TaskTarget.js.map +1 -0
  127. package/dist/lib/rop/TransportSend.js +16 -0
  128. package/dist/lib/rop/TransportSend.js.map +1 -0
  129. package/dist/lib/sql/MapiEmsmdbRouteSQL.js +85 -0
  130. package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
  131. package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
  132. package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
  133. package/dist/lib/sql/index.js +7 -0
  134. package/dist/lib/sql/index.js.map +1 -0
  135. package/dist/lib/sql.js +6 -0
  136. package/dist/lib/sql.js.map +1 -0
  137. package/dist/types/BaseMapiEmsmdbRoute.d.ts +139 -0
  138. package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
  139. package/dist/types/MapiSessionManager.d.ts +285 -0
  140. package/dist/types/RegexPatternUtils.d.ts +14 -0
  141. package/dist/types/RopDispatcher.d.ts +53 -0
  142. package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
  143. package/dist/types/codec/BufferCursor.d.ts +92 -0
  144. package/dist/types/codec/GlobalObjectId.d.ts +15 -0
  145. package/dist/types/codec/MapiGuid.d.ts +6 -0
  146. package/dist/types/codec/MapiTimeZone.d.ts +7 -0
  147. package/dist/types/codec/PropertyValue.d.ts +87 -0
  148. package/dist/types/codec/RopBuffer.d.ts +32 -0
  149. package/dist/types/codec/TypedString.d.ts +17 -0
  150. package/dist/types/index.d.ts +94 -0
  151. package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +20 -0
  152. package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
  153. package/dist/types/mongo/index.d.ts +2 -0
  154. package/dist/types/mongo.d.ts +1 -0
  155. package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
  156. package/dist/types/nspi/NspiCodec.d.ts +59 -0
  157. package/dist/types/nspi/NspiGetMatchesHandler.d.ts +26 -0
  158. package/dist/types/rop/AddressList.d.ts +48 -0
  159. package/dist/types/rop/CalendarEventTarget.d.ts +33 -0
  160. package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
  161. package/dist/types/rop/ContactTarget.d.ts +29 -0
  162. package/dist/types/rop/ContentsTable.d.ts +16 -0
  163. package/dist/types/rop/ExecuteBudget.d.ts +48 -0
  164. package/dist/types/rop/FastTransferStream.d.ts +57 -0
  165. package/dist/types/rop/FolderTarget.d.ts +64 -0
  166. package/dist/types/rop/HandleDataCache.d.ts +145 -0
  167. package/dist/types/rop/MeetingMessageClassHandler.d.ts +30 -0
  168. package/dist/types/rop/MessageBodyStream.d.ts +35 -0
  169. package/dist/types/rop/MessageTarget.d.ts +45 -0
  170. package/dist/types/rop/NamedPropertyRegistry.d.ts +50 -0
  171. package/dist/types/rop/PropertyResolvers.d.ts +86 -0
  172. package/dist/types/rop/RepoPaging.d.ts +30 -0
  173. package/dist/types/rop/RestapiRules.d.ts +32 -0
  174. package/dist/types/rop/RopCreateMessageHandler.d.ts +24 -0
  175. package/dist/types/rop/RopDeleteFolderHandler.d.ts +53 -0
  176. package/dist/types/rop/RopDeleteMessagesHandler.d.ts +43 -0
  177. package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +25 -0
  178. package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +25 -0
  179. package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +22 -0
  180. package/dist/types/rop/RopGetContentsTableHandler.d.ts +25 -0
  181. package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +18 -0
  182. package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +26 -0
  183. package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
  184. package/dist/types/rop/RopHandler.d.ts +106 -0
  185. package/dist/types/rop/RopLogonHandler.d.ts +31 -0
  186. package/dist/types/rop/RopOpenFolderHandler.d.ts +20 -0
  187. package/dist/types/rop/RopOpenMessageHandler.d.ts +28 -0
  188. package/dist/types/rop/RopOpenStreamHandler.d.ts +28 -0
  189. package/dist/types/rop/RopQueryRowsHandler.d.ts +43 -0
  190. package/dist/types/rop/RopReadStreamHandler.d.ts +20 -0
  191. package/dist/types/rop/RopReleaseHandler.d.ts +15 -0
  192. package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +59 -0
  193. package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
  194. package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
  195. package/dist/types/rop/RopSubmitMessageHandler.d.ts +80 -0
  196. package/dist/types/rop/RopWriteStreamHandler.d.ts +31 -0
  197. package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
  198. package/dist/types/rop/TaskTarget.d.ts +24 -0
  199. package/dist/types/rop/TransportSend.d.ts +10 -0
  200. package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +20 -0
  201. package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
  202. package/dist/types/sql/index.d.ts +2 -0
  203. package/dist/types/sql.d.ts +1 -0
  204. package/package.json +104 -0
@@ -0,0 +1,174 @@
1
+ import { auditMessageDelete } from "./RopDeleteMessagesHandler.js";
2
+ import { findAllCapped, findPage, MAX_COLLECTION_ROWS } from "./RepoPaging.js";
3
+ const ROP_ID_DELETE_FOLDER = 0x1d;
4
+ /** The well-known MAPI HRESULT `MAPI_E_NOT_FOUND`, reused for an unrecognized `FolderId` - the same constant
5
+ * `RopOpenFolderHandler` uses for its own analogous lookup miss. */
6
+ const ERROR_NOT_FOUND = 0x8004010f;
7
+ /** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for both "the referenced handle isn't a folder"
8
+ * and "the folder has content/subfolders but the matching cascade flag wasn't set" - the same constant reused
9
+ * throughout this pragmatic subset as a generic "can't do this" signal, not a claim of exact per-condition
10
+ * `[MS-OXCFOLD]` return-value-table parity (the real spec's own `ecFolderNotEmpty` isn't separately modeled). */
11
+ const ERROR_INVALID_OBJECT = 0x80070005;
12
+ /** `MAPI_E_TOO_COMPLEX`, for a subfolder tree deeper than `MAX_FOLDER_DEPTH` or larger than `MAX_FOLDERS_PER_DELETE`. */
13
+ const ERROR_TOO_COMPLEX = 0x80040117;
14
+ /** `DeleteFolderFlags` bits (`[MS-OXCFOLD]` §2.2.1.3.1). `DELETE_HARD_DELETE` (`0x10`) is deliberately not
15
+ * honored - see the class doc comment. */
16
+ const DEL_MESSAGES = 0x01;
17
+ const DEL_FOLDERS = 0x04;
18
+ /** The deepest subfolder nesting a recursive delete walks. */
19
+ export const MAX_FOLDER_DEPTH = 32;
20
+ /** The most folders one recursive delete removes. */
21
+ export const MAX_FOLDERS_PER_DELETE = 1000;
22
+ /** The most items (messages, events, contacts, tasks) one `RopDeleteFolder` deletes. Past this the ROP stops,
23
+ * reports `PartialCompletion`, and leaves the folders that still hold items in place; the client can retry. */
24
+ export const MAX_ITEMS_PER_DELETE = MAX_COLLECTION_ROWS;
25
+ const UID_SORT = { uid: "ASC" };
26
+ /**
27
+ * `RopDeleteFolder` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`, RopId `0x1D`): deletes a folder by `FolderId`, via
28
+ * `RecoverableRepoUtils.delete()` - see `RopDeleteMessagesHandler.ts`'s own doc comment for why that matters (EAS's
29
+ * watermark-based incremental delete detection depends on it).
30
+ *
31
+ * **Real spec semantics honored**: `RopDeleteFolder` only operates on an empty folder by default - `DEL_MESSAGES`
32
+ * must be set to also delete the folder's own messages/calendar events/contacts/tasks, and `DEL_FOLDERS` to also
33
+ * delete (recursively) its subfolders; a non-empty folder deleted without the matching flag fails rather than
34
+ * silently cascading.
35
+ *
36
+ * **Always a soft delete.** `DELETE_HARD_DELETE` is ignored. A permanent delete has to go through restapi's legal
37
+ * hold check first (the REST routes call `LegalHoldUtils.assertNotOnLegalHold()` for `purge=true`), and that helper
38
+ * isn't part of `@rapidmx/restapi`'s public exports. A soft-deleted item stays recoverable and discoverable, which is
39
+ * exactly what a hold requires, so MAPI never purges. Each deleted message is audited as `MESSAGE_DELETE`, like the
40
+ * REST message route.
41
+ *
42
+ * **Bounded.** The subfolder tree is walked iteratively with a visited set (a corrupt `parentFolderUid` cycle
43
+ * can't loop), at most `MAX_FOLDER_DEPTH` deep and `MAX_FOLDERS_PER_DELETE` wide, and only through folders in the
44
+ * caller's own mailbox. Items are fetched in explicit, paged queries (a bare `find()` stops at 100 rows, which used
45
+ * to leave items behind in a folder that was then deleted), up to `MAX_ITEMS_PER_DELETE`. Every query and delete is
46
+ * charged to the request's `ExecuteBudget` before it runs, so repeating this ROP can't multiply the walk unbounded.
47
+ *
48
+ * The request's own `InputHandleIndex` (nominally the *parent* folder of the one being deleted) is validated to
49
+ * be a real, open folder handle but not cross-checked against the target folder's actual `parentFolderUid` -
50
+ * the same pragmatic simplification `RopOpenMessageHandler`'s own doc comment documents for its analogous
51
+ * `FolderId` field.
52
+ *
53
+ * @author Jean-Philippe Steinmetz
54
+ */
55
+ export class RopDeleteFolderHandler {
56
+ constructor() {
57
+ this.ropId = ROP_ID_DELETE_FOLDER;
58
+ }
59
+ async handle(reader, writer, context) {
60
+ reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
61
+ const inputHandleIndex = reader.readUInt8();
62
+ const deleteFolderFlags = reader.readUInt8();
63
+ const folderId = reader.readBigUInt64LE();
64
+ const handle = context.session.handles[inputHandleIndex];
65
+ if (!handle || handle.type !== "folder") {
66
+ this.writeFailure(writer, inputHandleIndex, ERROR_INVALID_OBJECT);
67
+ return;
68
+ }
69
+ const target = context.session.folderIds[folderId.toString()];
70
+ if (!target?.startsWith("folder:")) {
71
+ this.writeFailure(writer, inputHandleIndex, ERROR_NOT_FOUND);
72
+ return;
73
+ }
74
+ const uid = target.slice("folder:".length);
75
+ if ((deleteFolderFlags & DEL_MESSAGES) === 0 && (await this.hasItems(uid, context))) {
76
+ this.writeFailure(writer, inputHandleIndex, ERROR_INVALID_OBJECT);
77
+ return;
78
+ }
79
+ const childQuery = { parentFolderUid: uid, mailboxUid: context.mailboxUid };
80
+ if ((deleteFolderFlags & DEL_FOLDERS) === 0 && (await findPage(context.folderRepo, childQuery, UID_SORT, 0, 1, context.budget)).length > 0) {
81
+ this.writeFailure(writer, inputHandleIndex, ERROR_INVALID_OBJECT);
82
+ return;
83
+ }
84
+ const folders = await this.collectSubtree(uid, context);
85
+ if (!folders) {
86
+ this.writeFailure(writer, inputHandleIndex, ERROR_TOO_COMPLEX);
87
+ return;
88
+ }
89
+ const budget = { remaining: MAX_ITEMS_PER_DELETE };
90
+ let partialCompletion = false;
91
+ for (const folderUid of folders) {
92
+ if (!(await this.deleteItems(folderUid, context, budget))) {
93
+ partialCompletion = true;
94
+ // Every folder deleted below (this loop's earlier iterations) is gone outright - nothing to refresh.
95
+ // This one is different: it survives (the budget ran out before `deleteItems` could empty it, so it's
96
+ // never reached below), but `itemRepos()` always drains `context.messageRepo` for a folder before any
97
+ // other repo, so some or all of its messages may already be gone - its stored counts must be
98
+ // refreshed rather than left counting messages that no longer exist.
99
+ await context.notifyFolderCounts?.([folderUid]);
100
+ break;
101
+ }
102
+ context.budget?.chargeQueries();
103
+ await context.folderRepo.delete(folderUid, { ignoreACL: true });
104
+ }
105
+ writer.writeUInt8(ROP_ID_DELETE_FOLDER);
106
+ writer.writeUInt8(inputHandleIndex);
107
+ writer.writeUInt32LE(0); // ReturnValue - success
108
+ writer.writeUInt8(partialCompletion ? 1 : 0); // PartialCompletion
109
+ }
110
+ writeFailure(writer, inputHandleIndex, returnValue) {
111
+ writer.writeUInt8(ROP_ID_DELETE_FOLDER);
112
+ writer.writeUInt8(inputHandleIndex);
113
+ writer.writeUInt32LE(returnValue);
114
+ }
115
+ /** The repos a folder's items can live in, skipping the optional ones the context doesn't have. */
116
+ itemRepos(context) {
117
+ return [context.messageRepo, context.calendarEventRepo, context.contactRepo, context.taskRepo].filter((repo) => repo !== undefined);
118
+ }
119
+ async hasItems(folderUid, context) {
120
+ for (const repo of this.itemRepos(context)) {
121
+ if ((await findPage(repo, { folderUid, mailboxUid: context.mailboxUid }, UID_SORT, 0, 1, context.budget)).length > 0) {
122
+ return true;
123
+ }
124
+ }
125
+ return false;
126
+ }
127
+ /**
128
+ * `rootUid` and every folder below it in the caller's mailbox, children before their parents, or `undefined` when
129
+ * the tree is deeper than `MAX_FOLDER_DEPTH` or has more than `MAX_FOLDERS_PER_DELETE` folders.
130
+ */
131
+ async collectSubtree(rootUid, context) {
132
+ const visited = new Set([rootUid]);
133
+ const preOrder = [];
134
+ const stack = [{ uid: rootUid, depth: 0 }];
135
+ while (stack.length > 0) {
136
+ const { uid, depth } = stack.pop();
137
+ preOrder.push(uid);
138
+ const { items: children, truncated } = await findAllCapped(context.folderRepo, { parentFolderUid: uid, mailboxUid: context.mailboxUid }, UID_SORT, MAX_FOLDERS_PER_DELETE, context.budget);
139
+ if (truncated) {
140
+ return undefined;
141
+ }
142
+ for (const child of children) {
143
+ if (visited.has(child.uid)) {
144
+ continue;
145
+ }
146
+ if (depth + 1 > MAX_FOLDER_DEPTH || visited.size >= MAX_FOLDERS_PER_DELETE) {
147
+ return undefined;
148
+ }
149
+ visited.add(child.uid);
150
+ stack.push({ uid: child.uid, depth: depth + 1 });
151
+ }
152
+ }
153
+ return preOrder.reverse();
154
+ }
155
+ /** Soft-deletes `folderUid`'s items within `budget`. Returns `false` if items remain once the budget is spent. */
156
+ async deleteItems(folderUid, context, budget) {
157
+ for (const repo of this.itemRepos(context)) {
158
+ const { items, truncated } = await findAllCapped(repo, { folderUid, mailboxUid: context.mailboxUid }, UID_SORT, budget.remaining, context.budget);
159
+ for (const item of items) {
160
+ context.budget?.chargeQueries();
161
+ await repo.delete(item.uid, { ignoreACL: true });
162
+ if (repo === context.messageRepo) {
163
+ await auditMessageDelete(context, item);
164
+ }
165
+ }
166
+ budget.remaining -= items.length;
167
+ if (truncated) {
168
+ return false;
169
+ }
170
+ }
171
+ return true;
172
+ }
173
+ }
174
+ //# sourceMappingURL=RopDeleteFolderHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RopDeleteFolderHandler.js","sourceRoot":"","sources":["../../../src/rop/RopDeleteFolderHandler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ import { AuditAction } from "@rapidmx/restapi";
2
+ const ROP_ID_DELETE_MESSAGES = 0x1e;
3
+ /** Audits a message deleted through MAPI with the same `MESSAGE_DELETE` entry the REST message route records.
4
+ * Charged to `context.budget` like the `findOne`/`delete` calls around every call site of this function - a real
5
+ * DB `create()` whenever `context.audit` is configured (the case in every real deployment), so leaving it
6
+ * uncharged undercounted this loop's real DB work by a third. No-op (and no charge - there's no DB work to
7
+ * account for) when `context.audit` is absent, the same as an optional-repo item kind this pragmatic subset
8
+ * already skips charging for. */
9
+ export async function auditMessageDelete(context, message) {
10
+ if (!context.audit) {
11
+ return;
12
+ }
13
+ context.budget?.chargeQueries();
14
+ await context.audit({
15
+ action: AuditAction.MESSAGE_DELETE,
16
+ targetType: "Message",
17
+ targetUid: message.uid,
18
+ mailboxUid: message.mailboxUid,
19
+ details: { subject: message.subject, folderUid: message.folderUid },
20
+ });
21
+ }
22
+ /** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder (or
23
+ * doesn't exist)" - the same constant `RopGetContentsTableHandler` uses for its own analogous check. */
24
+ const ERROR_INVALID_OBJECT = 0x80070005;
25
+ /**
26
+ * `RopDeleteMessages` (`[MS-OXCMSG]`/`[MS-OXCROPS]`, RopId `0x1E`): deletes one or more messages by `MessageId`
27
+ * from an already-open folder handle. Reuses `RecoverableRepoUtils.delete()` exactly as-built in Phase 2 - the
28
+ * same soft-delete-with-watermark-bump machinery EAS's own `Sync`/`FolderSync` incremental-delete detection
29
+ * already relies on (see `BaseMapiEmsmdbRoute.ts`'s own doc comment for why `folderRepo`/`messageRepo`/
30
+ * `calendarEventRepo` are built as `RecoverableRepoUtils`, not plain `RepoUtils`) - no new deletion-tracking
31
+ * code, zero duplication.
32
+ *
33
+ * A `MessageId` resolves via `session.messageIds` (`MessageTarget.assignOrGetMid`'s own registry, already
34
+ * generic over both `"message:<uid>"` and `"calendarEvent:<uid>"` target strings, since a Calendar item is
35
+ * itself a Message object on the wire - see `CalendarEventTarget.ts`'s own doc comment) directly to the entity
36
+ * to delete, the same "the session already knows what this small integer refers to" design every other MID-
37
+ * consuming ROP in this pragmatic subset already uses (`RopOpenMessage`, `RopSaveChangesMessage`) - the
38
+ * request's own `InputHandleIndex` (nominally the messages' shared parent folder) is validated to be a real,
39
+ * open folder handle but not cross-checked against each message's actual `folderUid`, the same pragmatic
40
+ * simplification `RopOpenMessageHandler`'s own doc comment documents for its analogous `FolderId` field.
41
+ *
42
+ * `WantAsynchronous` (`RopProgress`-reported async completion) and `NotifyNonRead` (non-read-receipt generation
43
+ * to the original sender) are decoded to advance the reader correctly but not honored - this pragmatic subset
44
+ * has no async-ROP or non-read-receipt machinery. `PartialCompletion` is reported `true` if any requested
45
+ * `MessageId` didn't resolve to a real message/calendar-event target - the rest are still deleted, matching
46
+ * this pragmatic subset's "don't fail the whole ROP over one stale reference" principle used throughout.
47
+ *
48
+ * @author Jean-Philippe Steinmetz
49
+ */
50
+ export class RopDeleteMessagesHandler {
51
+ constructor() {
52
+ this.ropId = ROP_ID_DELETE_MESSAGES;
53
+ }
54
+ async handle(reader, writer, context) {
55
+ reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
56
+ const inputHandleIndex = reader.readUInt8();
57
+ reader.readUInt8(); // WantAsynchronous - no RopProgress/async-ROP support in this pragmatic subset
58
+ reader.readUInt8(); // NotifyNonRead - no non-read-receipt generation support
59
+ const messageIdCount = reader.readUInt16LE();
60
+ const messageIds = [];
61
+ for (let i = 0; i < messageIdCount; i++) {
62
+ messageIds.push(reader.readBigUInt64LE());
63
+ }
64
+ const handle = context.session.handles[inputHandleIndex];
65
+ if (!handle || handle.type !== "folder") {
66
+ writer.writeUInt8(ROP_ID_DELETE_MESSAGES);
67
+ writer.writeUInt8(inputHandleIndex);
68
+ writer.writeUInt32LE(ERROR_INVALID_OBJECT);
69
+ return;
70
+ }
71
+ let partialCompletion = false;
72
+ // The folders that lost a message - refreshed once below, after the loop, rather than once per message (a
73
+ // client can name several `MessageId`s in one call, several of which are often the same folder).
74
+ const affectedFolderUids = new Set();
75
+ for (const messageId of messageIds) {
76
+ const target = context.session.messageIds[messageId.toString()];
77
+ if (target?.startsWith("message:")) {
78
+ const uid = target.slice("message:".length);
79
+ // Always fetched now (not just when `context.audit` is set): `notifyFolderCounts` below needs to know
80
+ // which folder this message was in, the same as `auditMessageDelete` already needed its subject.
81
+ // Charged like `resolveContentsKind`'s own single-row lookup and `RopDeleteFolderHandler.deleteItems()`'s
82
+ // own per-item delete - each is a real DB round trip, so a client can't repeat this ROP with a large
83
+ // `messageIdCount` to run unbounded queries against its own mailbox within one `Execute`.
84
+ context.budget?.chargeQueries();
85
+ const message = await context.messageRepo.findOne(uid, { ignoreACL: true });
86
+ context.budget?.chargeQueries();
87
+ await context.messageRepo.delete(uid, { ignoreACL: true });
88
+ if (message) {
89
+ affectedFolderUids.add(message.folderUid);
90
+ await auditMessageDelete(context, message);
91
+ }
92
+ }
93
+ else if (target?.startsWith("calendarEvent:")) {
94
+ context.budget?.chargeQueries();
95
+ await context.calendarEventRepo.delete(target.slice("calendarEvent:".length), { ignoreACL: true });
96
+ }
97
+ else {
98
+ partialCompletion = true;
99
+ }
100
+ }
101
+ // Not `bumpSyncKey` - only adding a message to a folder does that (see `refreshFolderCounts()`'s own doc
102
+ // comment); a deletion doesn't, matching restapi's own `ScanQueueJob.processRecall()` deletion call site.
103
+ await context.notifyFolderCounts?.(affectedFolderUids);
104
+ writer.writeUInt8(ROP_ID_DELETE_MESSAGES);
105
+ writer.writeUInt8(inputHandleIndex);
106
+ writer.writeUInt32LE(0); // ReturnValue - success
107
+ writer.writeUInt8(partialCompletion ? 1 : 0); // PartialCompletion
108
+ }
109
+ }
110
+ //# sourceMappingURL=RopDeleteMessagesHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RopDeleteMessagesHandler.js","sourceRoot":"","sources":["../../../src/rop/RopDeleteMessagesHandler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import { readPropertyTagArray } from "../codec/PropertyValue.js";
2
+ import { openFastTransferHandle } from "./FastTransferStream.js";
3
+ const ROP_ID_COPY_PROPERTIES = 0x69;
4
+ /** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder or
5
+ * message (or doesn't exist)" - the same constant `RopGetPropertiesSpecificHandler` uses for its own analogous
6
+ * check. */
7
+ const ERROR_INVALID_OBJECT = 0x80070005;
8
+ /** `MAPI_E_TOO_BIG`: more than `MAX_PROPERTY_TAG_COUNT` tags, or a stream past `MAX_FAST_TRANSFER_BYTES`. */
9
+ const ERROR_TOO_BIG = 0x80040305;
10
+ /**
11
+ * `RopFastTransferSourceCopyProperties` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x69`): the explicit-column
12
+ * sibling of `RopFastTransferSourceCopyTo` - `PropertyTags` here is the properties to **copy** (an include
13
+ * list, per `[MS-OXCROPS]`'s own wording, the opposite of `CopyTo`'s exclude list), applied uniformly at both
14
+ * folder- and message-level wherever `FastTransferStream.ts` builds a `propList` - see that file's own doc
15
+ * comment for this pragmatic subset's overall ICS scope. An empty `PropertyTags` list is honored literally
16
+ * (copies zero properties, i.e. empty `propList`s) rather than falling back to `CopyTo`'s own default column
17
+ * set - that fallback belongs to `CopyTo` alone, since an explicit "copy nothing" request here is spec-valid
18
+ * and distinguishable from "use the default set" (`CopyTo` simply has no `PropertyTags`-supplied alternative to
19
+ * fall back from).
20
+ *
21
+ * `Level`/`CopyFlags`/`SendOptions` are decoded to advance the reader correctly but not honored - see
22
+ * `RopFastTransferSourceCopyToHandler`'s own doc comment for the identical reasoning. Note `CopyFlags` here is
23
+ * **1 byte**, not 4 like `CopyTo`'s own field of the same name - confirmed against each ROP's own request-buffer
24
+ * page separately, not assumed identical.
25
+ *
26
+ * @author Jean-Philippe Steinmetz
27
+ */
28
+ export class RopFastTransferSourceCopyPropertiesHandler {
29
+ constructor() {
30
+ this.ropId = ROP_ID_COPY_PROPERTIES;
31
+ this.responseHandleIndexOffset = 3; // OutputHandleIndex
32
+ }
33
+ async handle(reader, writer, context) {
34
+ reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
35
+ const inputHandleIndex = reader.readUInt8();
36
+ const outputHandleIndex = reader.readUInt8();
37
+ reader.readUInt8(); // Level - subfolder recursion not honored, see class doc comment
38
+ reader.readUInt8(); // CopyFlags (1 byte here, unlike CopyTo's 4) - not honored, see class doc comment
39
+ reader.readUInt8(); // SendOptions - not honored, see class doc comment
40
+ const tags = readPropertyTagArray(reader, reader.readUInt16LE());
41
+ // One column per property: a repeated tag would only repeat the same value in every row of the stream.
42
+ const columns = tags?.filter((tag, index) => tags.findIndex((other) => other.propertyId === tag.propertyId) === index);
43
+ const handle = context.session.handles[inputHandleIndex];
44
+ if (!handle || (handle.type !== "folder" && handle.type !== "message")) {
45
+ writer.writeUInt8(ROP_ID_COPY_PROPERTIES);
46
+ writer.writeUInt8(outputHandleIndex);
47
+ writer.writeUInt32LE(ERROR_INVALID_OBJECT);
48
+ return;
49
+ }
50
+ if (!columns || !(await openFastTransferHandle(context, outputHandleIndex, handle, { columns }))) {
51
+ writer.writeUInt8(ROP_ID_COPY_PROPERTIES);
52
+ writer.writeUInt8(outputHandleIndex);
53
+ writer.writeUInt32LE(ERROR_TOO_BIG);
54
+ return;
55
+ }
56
+ writer.writeUInt8(ROP_ID_COPY_PROPERTIES);
57
+ writer.writeUInt8(outputHandleIndex);
58
+ writer.writeUInt32LE(0); // ReturnValue - success
59
+ }
60
+ }
61
+ //# sourceMappingURL=RopFastTransferSourceCopyPropertiesHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RopFastTransferSourceCopyPropertiesHandler.js","sourceRoot":"","sources":["../../../src/rop/RopFastTransferSourceCopyPropertiesHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGjE,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAEpC;;YAEY;AACZ,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAExC,6GAA6G;AAC7G,MAAM,aAAa,GAAG,UAAU,CAAC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,0CAA0C;IAAvD;QACoB,UAAK,GAAG,sBAAsB,CAAC;QAC/B,8BAAyB,GAAG,CAAC,CAAC,CAAC,oBAAoB;IA+BvE,CAAC;IA7BU,KAAK,CAAC,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QAC/E,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,gBAAgB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,MAAM,iBAAiB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,iEAAiE;QACrF,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,kFAAkF;QACtG,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,mDAAmD;QACvE,MAAM,IAAI,GAA8B,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;QAC5F,uGAAuG;QACvG,MAAM,OAAO,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,KAAK,GAAG,CAAC,UAAU,CAAC,KAAK,KAAK,CAAC,CAAC;QAEvH,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE,CAAC;YACrE,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;YAC1C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;YAC3C,OAAO;QACX,CAAC;QACD,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,sBAAsB,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC;YAC/F,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;YAC1C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;YACpC,OAAO;QACX,CAAC;QAED,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;QAC1C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;IACrD,CAAC;CACJ"}
@@ -0,0 +1,60 @@
1
+ import { readPropertyTagArray } from "../codec/PropertyValue.js";
2
+ import { openFastTransferHandle } from "./FastTransferStream.js";
3
+ const ROP_ID_COPY_TO = 0x4d;
4
+ /** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder or
5
+ * message (or doesn't exist)" - the same constant `RopGetPropertiesSpecificHandler` uses for its own analogous
6
+ * check. */
7
+ const ERROR_INVALID_OBJECT = 0x80070005;
8
+ /** `MAPI_E_TOO_BIG`: more than `MAX_PROPERTY_TAG_COUNT` tags, or a stream past `MAX_FAST_TRANSFER_BYTES`. */
9
+ const ERROR_TOO_BIG = 0x80040305;
10
+ /**
11
+ * `RopFastTransferSourceCopyTo` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x4D`): begins a FastTransfer download
12
+ * of an already-open Folder or Message (or Calendar item, itself a Message object on the wire) object,
13
+ * producing a new `"fastTransfer"` Server object handle `RopFastTransferSourceGetBuffer` pages the built stream
14
+ * out of. See `FastTransferStream.ts`'s own doc comment for this pragmatic subset's "full, non-differential
15
+ * dump" ICS scope and its default property columns.
16
+ *
17
+ * `Level` (whether to recurse into subfolders) is decoded to advance the reader correctly but not honored -
18
+ * this pragmatic subset never recurses into subfolders regardless. `CopyFlags`/`SendOptions` (Move mode, best-
19
+ * body preference, Unicode preference, recoverable-mode, ...) are likewise decoded but not honored - this
20
+ * pragmatic subset always copies (never moves) and always encodes the same way regardless of client
21
+ * preference, matching `RopGetPropertiesSpecificHandler`'s own treatment of its analogous `WantUnicode` field.
22
+ * `PropertyTags` is the properties to *exclude* from the default column set (`[MS-OXCROPS]`'s own wording) -
23
+ * see `FastTransferStream.ts` for why an explicit include list instead is `RopFastTransferSourceCopyProperties`'s
24
+ * own job, not this ROP's.
25
+ *
26
+ * @author Jean-Philippe Steinmetz
27
+ */
28
+ export class RopFastTransferSourceCopyToHandler {
29
+ constructor() {
30
+ this.ropId = ROP_ID_COPY_TO;
31
+ this.responseHandleIndexOffset = 3; // OutputHandleIndex
32
+ }
33
+ async handle(reader, writer, context) {
34
+ reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
35
+ const inputHandleIndex = reader.readUInt8();
36
+ const outputHandleIndex = reader.readUInt8();
37
+ reader.readUInt8(); // Level - subfolder recursion not honored, see class doc comment
38
+ reader.readUInt32LE(); // CopyFlags - not honored, see class doc comment
39
+ reader.readUInt8(); // SendOptions - not honored, see class doc comment
40
+ const excludeTags = readPropertyTagArray(reader, reader.readUInt16LE());
41
+ const handle = context.session.handles[inputHandleIndex];
42
+ if (!handle || (handle.type !== "folder" && handle.type !== "message")) {
43
+ writer.writeUInt8(ROP_ID_COPY_TO);
44
+ writer.writeUInt8(outputHandleIndex);
45
+ writer.writeUInt32LE(ERROR_INVALID_OBJECT);
46
+ return;
47
+ }
48
+ const excludePropertyIds = excludeTags ? [...new Set(excludeTags.map((tag) => tag.propertyId))] : undefined;
49
+ if (!excludePropertyIds || !(await openFastTransferHandle(context, outputHandleIndex, handle, { excludePropertyIds }))) {
50
+ writer.writeUInt8(ROP_ID_COPY_TO);
51
+ writer.writeUInt8(outputHandleIndex);
52
+ writer.writeUInt32LE(ERROR_TOO_BIG);
53
+ return;
54
+ }
55
+ writer.writeUInt8(ROP_ID_COPY_TO);
56
+ writer.writeUInt8(outputHandleIndex);
57
+ writer.writeUInt32LE(0); // ReturnValue - success
58
+ }
59
+ }
60
+ //# sourceMappingURL=RopFastTransferSourceCopyToHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RopFastTransferSourceCopyToHandler.js","sourceRoot":"","sources":["../../../src/rop/RopFastTransferSourceCopyToHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGjE,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B;;YAEY;AACZ,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAExC,6GAA6G;AAC7G,MAAM,aAAa,GAAG,UAAU,CAAC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,kCAAkC;IAA/C;QACoB,UAAK,GAAG,cAAc,CAAC;QACvB,8BAAyB,GAAG,CAAC,CAAC,CAAC,oBAAoB;IA8BvE,CAAC;IA5BU,KAAK,CAAC,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QAC/E,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,gBAAgB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,MAAM,iBAAiB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,iEAAiE;QACrF,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,iDAAiD;QACxE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,mDAAmD;QACvE,MAAM,WAAW,GAAG,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;QAExE,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE,CAAC;YACrE,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;YAClC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;YAC3C,OAAO;QACX,CAAC;QACD,MAAM,kBAAkB,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5G,IAAI,CAAC,kBAAkB,IAAI,CAAC,CAAC,MAAM,sBAAsB,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,EAAE,kBAAkB,EAAE,CAAC,CAAC,EAAE,CAAC;YACrH,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;YAClC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;YACpC,OAAO;QACX,CAAC;QAED,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;QAClC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;IACrD,CAAC;CACJ"}
@@ -0,0 +1,76 @@
1
+ import { loadFastTransferBuffer } from "./FastTransferStream.js";
2
+ const ROP_ID_GET_BUFFER = 0x4e;
3
+ /** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a
4
+ * `fastTransfer` handle (or doesn't exist)" - the same constant `RopGetPropertiesSpecificHandler` uses for its
5
+ * own analogous check. */
6
+ const ERROR_INVALID_OBJECT = 0x80070005;
7
+ /** The `BufferSize` sentinel (`[MS-OXCROPS]`'s own documented value) meaning "let the server pick the chunk
8
+ * size". */
9
+ const BUFFER_SIZE_SERVER_DETERMINED = 0xbabe;
10
+ /** The response's own `TransferBufferSize` field is a 16-bit count, so a single call never returns more than this. */
11
+ const MAX_TRANSFER_BUFFER_SIZE = 0xffff;
12
+ /** `MAPI_E_TOO_BIG`: rebuilding the stream passed `MAX_FAST_TRANSFER_BYTES`. */
13
+ const ERROR_TOO_BIG = 0x80040305;
14
+ /** `MAPI_E_CALL_FAILED`: the stream was lost part-way through paging and can't be continued - see
15
+ * `loadFastTransferBuffer`. The client starts the transfer again. */
16
+ const ERROR_TRANSFER_LOST = 0x80004005;
17
+ /** The response bytes before `TransferBuffer`: RopId, InputHandleIndex, ReturnValue, TransferStatus,
18
+ * InProgressCount, TotalStepCount, Reserved, TransferBufferSize. */
19
+ const RESPONSE_HEADER_BYTES = 15;
20
+ /** `TransferStatus` values (`[MS-OXCFXICS]` §2.2.3.1.1.5.2). */
21
+ const TRANSFER_STATUS_PARTIAL = 0x0001;
22
+ const TRANSFER_STATUS_NO_ROOM = 0x0002;
23
+ const TRANSFER_STATUS_DONE = 0x0003;
24
+ /**
25
+ * `RopFastTransferSourceGetBuffer` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x4E`): pages the FastTransfer
26
+ * stream a prior `RopFastTransferSourceCopyTo`/`CopyProperties` built (`FastTransferStream.ts`) out of its
27
+ * `"fastTransfer"` handle, `BufferSize` bytes at a time. For the `0xBABE` "server-determined" sentinel the server
28
+ * picks the size, up to the client's `MaximumBufferSize`.
29
+ *
30
+ * Every chunk is also held to the room left in this request's ROP output buffer (`context.ropOutputRemaining`) and
31
+ * the 16-bit `TransferBufferSize`. Reports `Done` once the whole stream has been returned, `Partial` otherwise, and
32
+ * `NoRoom` when the output buffer has no space for any bytes this time (the client asks again in a new request).
33
+ *
34
+ * Fails with `MAPI_E_TOO_BIG` when a stream that had to be rebuilt is too large, and with `MAPI_E_CALL_FAILED` when
35
+ * the stream was lost part-way through (see `loadFastTransferBuffer`) - never with bytes from a different stream.
36
+ * Failure responses carry only `ReturnValue`, like every other failing handler here. `BackoffTime` is never emitted.
37
+ *
38
+ * @author Jean-Philippe Steinmetz
39
+ */
40
+ export class RopFastTransferSourceGetBufferHandler {
41
+ constructor() {
42
+ this.ropId = ROP_ID_GET_BUFFER;
43
+ }
44
+ async handle(reader, writer, context) {
45
+ reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
46
+ const inputHandleIndex = reader.readUInt8();
47
+ const bufferSize = reader.readUInt16LE();
48
+ const requestedSize = bufferSize === BUFFER_SIZE_SERVER_DETERMINED ? reader.readUInt16LE() : bufferSize;
49
+ const handle = context.session.handles[inputHandleIndex];
50
+ const loaded = handle?.type === "fastTransfer" ? await loadFastTransferBuffer(context, inputHandleIndex, handle) : undefined;
51
+ if (!loaded || typeof loaded === "string") {
52
+ writer.writeUInt8(ROP_ID_GET_BUFFER);
53
+ writer.writeUInt8(inputHandleIndex);
54
+ writer.writeUInt32LE(!loaded ? ERROR_INVALID_OBJECT : loaded === "tooBig" ? ERROR_TOO_BIG : ERROR_TRANSFER_LOST);
55
+ return;
56
+ }
57
+ const position = handle.transferPosition ?? 0;
58
+ const remaining = loaded.length - position;
59
+ const room = Math.max(0, (context.ropOutputRemaining ?? Infinity) - RESPONSE_HEADER_BYTES);
60
+ const chunkSize = Math.min(requestedSize, remaining, MAX_TRANSFER_BUFFER_SIZE, room);
61
+ const chunk = loaded.subarray(position, position + chunkSize);
62
+ handle.transferPosition = position + chunk.length;
63
+ const done = position + chunk.length >= loaded.length;
64
+ const status = done ? TRANSFER_STATUS_DONE : room === 0 ? TRANSFER_STATUS_NO_ROOM : TRANSFER_STATUS_PARTIAL;
65
+ writer.writeUInt8(ROP_ID_GET_BUFFER);
66
+ writer.writeUInt8(inputHandleIndex);
67
+ writer.writeUInt32LE(0); // ReturnValue - success
68
+ writer.writeUInt16LE(status);
69
+ writer.writeUInt16LE(0); // InProgressCount - no real progress tracking in this pragmatic subset
70
+ writer.writeUInt16LE(1); // TotalStepCount - pragmatic constant, only ever used for progress-bar display
71
+ writer.writeUInt8(0); // Reserved
72
+ writer.writeUInt16LE(chunk.length); // TransferBufferSize
73
+ writer.writeBytes(chunk); // TransferBuffer
74
+ }
75
+ }
76
+ //# sourceMappingURL=RopFastTransferSourceGetBufferHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RopFastTransferSourceGetBufferHandler.js","sourceRoot":"","sources":["../../../src/rop/RopFastTransferSourceGetBufferHandler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { assignHandle } from "../MapiSessionManager.js";
2
+ import { resolveContentsKind } from "./ContentsTable.js";
3
+ const ROP_ID_GET_CONTENTS_TABLE = 0x05;
4
+ /** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder (or
5
+ * doesn't exist)" - the same constant `RopGetHierarchyTableHandler` uses for its own analogous check. */
6
+ const ERROR_INVALID_OBJECT = 0x80070005;
7
+ /**
8
+ * `RopGetContentsTable` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`): opens a table listing an already-opened folder's
9
+ * messages, producing a new `"table"` Server object handle `RopSetColumns`/`RopQueryRows` reference by
10
+ * `InputHandleIndex` - the exact same request/response shape as `RopGetHierarchyTable` (confirmed identical
11
+ * field-by-field against `[MS-OXCROPS]`'s own request-buffer page, differing only in `RopId`), just listing
12
+ * messages instead of child folders.
13
+ *
14
+ * The table holds no rows. It records which kind of item the folder contains (`contentsKind`, decided once here
15
+ * from the folder's own `type`: Calendar folders list `"calendarEvent:<uid>"` rows, Contacts `"contact:<uid>"`,
16
+ * Tasks `"task:<uid>"`, everything else `"message:<uid>"`), and `RopQueryRows` reads each requested window from
17
+ * the database - see `ContentsTable.ts`.
18
+ *
19
+ * A folder's contents table is only ever non-empty for a real folder (`"folder:<uid>"`) - the virtual special
20
+ * folders (`RopLogonHandler`'s own doc comment) have no real backing row to hold messages under, so opening a
21
+ * contents table on one always yields an empty table rather than an error.
22
+ *
23
+ * @author Jean-Philippe Steinmetz
24
+ */
25
+ export class RopGetContentsTableHandler {
26
+ constructor() {
27
+ this.ropId = ROP_ID_GET_CONTENTS_TABLE;
28
+ this.responseHandleIndexOffset = 3; // OutputHandleIndex
29
+ }
30
+ async handle(reader, writer, context) {
31
+ reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
32
+ const inputHandleIndex = reader.readUInt8();
33
+ const outputHandleIndex = reader.readUInt8();
34
+ reader.readUInt8(); // TableFlags - only the Standard (0x00) case is supported; see RopGetHierarchyTableHandler's own doc comment
35
+ const folderHandle = context.session.handles[inputHandleIndex];
36
+ if (!folderHandle || folderHandle.type !== "folder") {
37
+ writer.writeUInt8(ROP_ID_GET_CONTENTS_TABLE);
38
+ writer.writeUInt8(outputHandleIndex);
39
+ writer.writeUInt32LE(ERROR_INVALID_OBJECT);
40
+ return;
41
+ }
42
+ const entityUid = folderHandle.entityUid;
43
+ if (entityUid.startsWith("folder:")) {
44
+ const contentsKind = await resolveContentsKind(entityUid.slice("folder:".length), context);
45
+ assignHandle(context.session, outputHandleIndex, { type: "table", entityUid, contentsKind, cursor: 0 });
46
+ }
47
+ else {
48
+ assignHandle(context.session, outputHandleIndex, { type: "table", entityUid, rows: [], cursor: 0 });
49
+ }
50
+ writer.writeUInt8(ROP_ID_GET_CONTENTS_TABLE);
51
+ writer.writeUInt8(outputHandleIndex);
52
+ writer.writeUInt32LE(0); // ReturnValue - success
53
+ }
54
+ }
55
+ //# sourceMappingURL=RopGetContentsTableHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RopGetContentsTableHandler.js","sourceRoot":"","sources":["../../../src/rop/RopGetContentsTableHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAEvC;yGACyG;AACzG,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAExC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,0BAA0B;IAAvC;QACoB,UAAK,GAAG,yBAAyB,CAAC;QAClC,8BAAyB,GAAG,CAAC,CAAC,CAAC,oBAAoB;IA4BvE,CAAC;IA1BU,KAAK,CAAC,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QAC/E,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,gBAAgB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,MAAM,iBAAiB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,6GAA6G;QAEjI,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC/D,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClD,MAAM,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAC;YAC7C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;YAC3C,OAAO;QACX,CAAC;QAED,MAAM,SAAS,GAAG,YAAY,CAAC,SAAS,CAAC;QACzC,IAAI,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAClC,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;YAC3F,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5G,CAAC;aAAM,CAAC;YACJ,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACxG,CAAC;QAED,MAAM,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAC;QAC7C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;IACrD,CAAC;CACJ"}
@@ -0,0 +1,42 @@
1
+ import { assignHandle } from "../MapiSessionManager.js";
2
+ import { resolveFolderChildren } from "./FolderTarget.js";
3
+ const ROP_ID_GET_HIERARCHY_TABLE = 0x04;
4
+ /** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder
5
+ * (or doesn't exist)". */
6
+ const ERROR_INVALID_OBJECT = 0x80070005;
7
+ /**
8
+ * `RopGetHierarchyTable` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`): opens a table listing an already-opened folder's
9
+ * direct child folders, producing a new `"table"` Server object handle `RopSetColumns`/`RopQueryRows`
10
+ * reference by `InputHandleIndex`. Only `TableFlags = 0x00` ("Standard") is supported - this pragmatic subset
11
+ * has no deferred/async table population, so the immediate-row-count-returning variants some `TableFlags`
12
+ * values request are moot, and the response omits the row-count field those variants add (confirmed absent
13
+ * for the Standard case against `[MS-OXCROPS]`'s own request-buffer page, which explicitly lists `TableFlags`
14
+ * as this ROP's only request payload beyond the common handle-index header).
15
+ *
16
+ * @author Jean-Philippe Steinmetz
17
+ */
18
+ export class RopGetHierarchyTableHandler {
19
+ constructor() {
20
+ this.ropId = ROP_ID_GET_HIERARCHY_TABLE;
21
+ this.responseHandleIndexOffset = 3; // OutputHandleIndex
22
+ }
23
+ async handle(reader, writer, context) {
24
+ reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
25
+ const inputHandleIndex = reader.readUInt8();
26
+ const outputHandleIndex = reader.readUInt8();
27
+ reader.readUInt8(); // TableFlags - only the Standard (0x00) case is supported; see class doc comment
28
+ const folderHandle = context.session.handles[inputHandleIndex];
29
+ if (!folderHandle || folderHandle.type !== "folder") {
30
+ writer.writeUInt8(ROP_ID_GET_HIERARCHY_TABLE);
31
+ writer.writeUInt8(outputHandleIndex);
32
+ writer.writeUInt32LE(ERROR_INVALID_OBJECT);
33
+ return;
34
+ }
35
+ const rows = await resolveFolderChildren(context.mailboxUid, folderHandle.entityUid, context.folderRepo, undefined, context.budget);
36
+ assignHandle(context.session, outputHandleIndex, { type: "table", entityUid: folderHandle.entityUid, rows, cursor: 0 });
37
+ writer.writeUInt8(ROP_ID_GET_HIERARCHY_TABLE);
38
+ writer.writeUInt8(outputHandleIndex);
39
+ writer.writeUInt32LE(0); // ReturnValue - success
40
+ }
41
+ }
42
+ //# sourceMappingURL=RopGetHierarchyTableHandler.js.map