@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,25 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopFastTransferSourceCopyTo` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x4D`): begins a FastTransfer download
5
+ * of an already-open Folder or Message (or Calendar item, itself a Message object on the wire) object,
6
+ * producing a new `"fastTransfer"` Server object handle `RopFastTransferSourceGetBuffer` pages the built stream
7
+ * out of. See `FastTransferStream.ts`'s own doc comment for this pragmatic subset's "full, non-differential
8
+ * dump" ICS scope and its default property columns.
9
+ *
10
+ * `Level` (whether to recurse into subfolders) is decoded to advance the reader correctly but not honored -
11
+ * this pragmatic subset never recurses into subfolders regardless. `CopyFlags`/`SendOptions` (Move mode, best-
12
+ * body preference, Unicode preference, recoverable-mode, ...) are likewise decoded but not honored - this
13
+ * pragmatic subset always copies (never moves) and always encodes the same way regardless of client
14
+ * preference, matching `RopGetPropertiesSpecificHandler`'s own treatment of its analogous `WantUnicode` field.
15
+ * `PropertyTags` is the properties to *exclude* from the default column set (`[MS-OXCROPS]`'s own wording) -
16
+ * see `FastTransferStream.ts` for why an explicit include list instead is `RopFastTransferSourceCopyProperties`'s
17
+ * own job, not this ROP's.
18
+ *
19
+ * @author Jean-Philippe Steinmetz
20
+ */
21
+ export declare class RopFastTransferSourceCopyToHandler implements RopHandler {
22
+ readonly ropId = 77;
23
+ readonly responseHandleIndexOffset = 3;
24
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
25
+ }
@@ -0,0 +1,22 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopFastTransferSourceGetBuffer` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x4E`): pages the FastTransfer
5
+ * stream a prior `RopFastTransferSourceCopyTo`/`CopyProperties` built (`FastTransferStream.ts`) out of its
6
+ * `"fastTransfer"` handle, `BufferSize` bytes at a time. For the `0xBABE` "server-determined" sentinel the server
7
+ * picks the size, up to the client's `MaximumBufferSize`.
8
+ *
9
+ * Every chunk is also held to the room left in this request's ROP output buffer (`context.ropOutputRemaining`) and
10
+ * the 16-bit `TransferBufferSize`. Reports `Done` once the whole stream has been returned, `Partial` otherwise, and
11
+ * `NoRoom` when the output buffer has no space for any bytes this time (the client asks again in a new request).
12
+ *
13
+ * Fails with `MAPI_E_TOO_BIG` when a stream that had to be rebuilt is too large, and with `MAPI_E_CALL_FAILED` when
14
+ * the stream was lost part-way through (see `loadFastTransferBuffer`) - never with bytes from a different stream.
15
+ * Failure responses carry only `ReturnValue`, like every other failing handler here. `BackoffTime` is never emitted.
16
+ *
17
+ * @author Jean-Philippe Steinmetz
18
+ */
19
+ export declare class RopFastTransferSourceGetBufferHandler implements RopHandler {
20
+ readonly ropId = 78;
21
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
22
+ }
@@ -0,0 +1,25 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopGetContentsTable` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`): opens a table listing an already-opened folder's
5
+ * messages, producing a new `"table"` Server object handle `RopSetColumns`/`RopQueryRows` reference by
6
+ * `InputHandleIndex` - the exact same request/response shape as `RopGetHierarchyTable` (confirmed identical
7
+ * field-by-field against `[MS-OXCROPS]`'s own request-buffer page, differing only in `RopId`), just listing
8
+ * messages instead of child folders.
9
+ *
10
+ * The table holds no rows. It records which kind of item the folder contains (`contentsKind`, decided once here
11
+ * from the folder's own `type`: Calendar folders list `"calendarEvent:<uid>"` rows, Contacts `"contact:<uid>"`,
12
+ * Tasks `"task:<uid>"`, everything else `"message:<uid>"`), and `RopQueryRows` reads each requested window from
13
+ * the database - see `ContentsTable.ts`.
14
+ *
15
+ * A folder's contents table is only ever non-empty for a real folder (`"folder:<uid>"`) - the virtual special
16
+ * folders (`RopLogonHandler`'s own doc comment) have no real backing row to hold messages under, so opening a
17
+ * contents table on one always yields an empty table rather than an error.
18
+ *
19
+ * @author Jean-Philippe Steinmetz
20
+ */
21
+ export declare class RopGetContentsTableHandler implements RopHandler {
22
+ readonly ropId = 5;
23
+ readonly responseHandleIndexOffset = 3;
24
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
25
+ }
@@ -0,0 +1,18 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopGetHierarchyTable` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`): opens a table listing an already-opened folder's
5
+ * direct child folders, producing a new `"table"` Server object handle `RopSetColumns`/`RopQueryRows`
6
+ * reference by `InputHandleIndex`. Only `TableFlags = 0x00` ("Standard") is supported - this pragmatic subset
7
+ * has no deferred/async table population, so the immediate-row-count-returning variants some `TableFlags`
8
+ * values request are moot, and the response omits the row-count field those variants add (confirmed absent
9
+ * for the Standard case against `[MS-OXCROPS]`'s own request-buffer page, which explicitly lists `TableFlags`
10
+ * as this ROP's only request payload beyond the common handle-index header).
11
+ *
12
+ * @author Jean-Philippe Steinmetz
13
+ */
14
+ export declare class RopGetHierarchyTableHandler implements RopHandler {
15
+ readonly ropId = 4;
16
+ readonly responseHandleIndexOffset = 3;
17
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
18
+ }
@@ -0,0 +1,26 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopGetPropertiesSpecific` (`[MS-OXCPRPT]`/`[MS-OXCROPS]`): fetches named property values for a single
5
+ * already-opened Folder or Message object (`RopOpenFolder`/`RopOpenMessage`), by an explicit `PropertyTags`
6
+ * list - the single-object analog of `RopQueryRows`' per-row column fetch, reusing the exact same
7
+ * `PropertyResolvers` value-resolution logic (a folder/message handle's `entityUid` is already the same
8
+ * `"folder:"`/`"virtual:"`/`"message:"` target-string format a table row uses). Like `RopQueryRows`, this
9
+ * pragmatic subset always emits a `StandardPropertyRow` (`Flags = 0x00`, confirmed as the response's `RowData`
10
+ * format via `[MS-OXCROPS]`'s own "Success Response Buffer" page, which names `[MS-OXCDATA]` §2.8's
11
+ * `PropertyRow` structure directly) - an unsupported property falls back to a type-appropriate default rather
12
+ * than a `FlaggedPropertyRow`'s per-column error signaling, exactly as `RopQueryRows` already does. That includes
13
+ * a client-requested `propertyType` that doesn't match what a tag's own `propertyId` actually resolves to
14
+ * (`PropertyResolvers.writePropertyValueSafely`, shared with `RopQueryRowsHandler`) - not just a `propertyId`
15
+ * this codebase has no data for.
16
+ *
17
+ * `PropertySizeLimit`/`WantUnicode` are decoded (to advance the reader correctly) but not honored - this
18
+ * pragmatic subset never truncates a property value and always encodes strings the same way regardless of the
19
+ * client's Unicode preference (see `PropertyValue.ts`'s own string-encoding doc comments).
20
+ *
21
+ * @author Jean-Philippe Steinmetz
22
+ */
23
+ export declare class RopGetPropertiesSpecificHandler implements RopHandler {
24
+ readonly ropId = 7;
25
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
26
+ }
@@ -0,0 +1,23 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopGetPropertyIdsFromNames` (`[MS-OXCPRPT]`/`[MS-OXCROPS]` §2.2.8.1): resolves a list of named properties
5
+ * (`PropertyName` structures - a property-set `GUID` plus a numeric `LID` or string `Name`) into numeric
6
+ * property IDs a subsequent `RopSetProperties`/`RopGetPropertiesSpecific`/`RopQueryRows` call can use directly.
7
+ * This is the mechanism almost every Calendar-specific property (`PidLidAppointmentStartWhole`,
8
+ * `PidLidBusyStatus`, `PidLidAppointmentRecur`, ...) requires, since none of them have a fixed numeric
9
+ * `PidTag*`-style ID the way `PidTagSubject` does - see `NamedPropertyRegistry.ts`'s own doc comment for the
10
+ * session-scoped registry this delegates to.
11
+ *
12
+ * An unresolvable `PropertyName` (`Kind = 0xFF`) maps to `0x0000` in the response, per spec - this pragmatic
13
+ * subset has no other unmappable case (no permission/quota/hard-limit checks), so every `Kind = 0x00`/`0x01`
14
+ * entry always succeeds. `Flags` is decoded to advance past it correctly but not honored - see
15
+ * `assignOrGetNamedPropertyId`'s own doc comment for why "probe" and "assign" collapse to the same behavior
16
+ * for a purely session-scoped registry.
17
+ *
18
+ * @author Jean-Philippe Steinmetz
19
+ */
20
+ export declare class RopGetPropertyIdsFromNamesHandler implements RopHandler {
21
+ readonly ropId = 86;
22
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): void;
23
+ }
@@ -0,0 +1,106 @@
1
+ import type { RepoUtils } from "@rapidrest/service-core";
2
+ import type { AuditLogParams, BlobStore } from "@rapidmx/restapi";
3
+ import type { ScanPipeline } from "@rapidmx/restapi/scan";
4
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
5
+ import type { MapiSessionContext } from "../MapiSessionManager.js";
6
+ import type { ExecuteBudget } from "./ExecuteBudget.js";
7
+ import { type HandleDataStore } from "./HandleDataCache.js";
8
+ /**
9
+ * Everything a `RopHandler` needs beyond the raw ROP bytes it decodes itself. `folderRepo`/`mailboxRepo` are
10
+ * built once by `BaseMapiEmsmdbRoute` (not by each handler, unlike EAS's per-command repo pattern - MAPI's ROPs
11
+ * are far more tightly interdependent around the same few entity types than EAS's per-command-collection-type
12
+ * structure was, so centralizing the common repos here avoids every handler rebuilding its own). Untyped as
13
+ * `RepoUtils<any>` rather than a generic `F extends Folder` parameter threaded through every handler file -
14
+ * the same pragmatic `any` this codebase already uses throughout for `xxxClass`/`xxxRepo` DI fields, since
15
+ * `RepoUtils<F>`'s method contravariance makes a generic version genuinely awkward for no real benefit here
16
+ * (every handler only ever reads `Folder`-shaped rows, never constructs backend-specific entities itself).
17
+ *
18
+ * `folderClass`/`messageClass` (the concrete Mongo/SQL entity constructors) are threaded through here rather
19
+ * than held as a field on each handler - unlike EAS's `EasCommandHandler`s, a single shared `RopHandler`
20
+ * instance (e.g. `RopSubmitMessageHandler`) is reused for *both* the Mongo and SQL route (`ropHandlerClasses`
21
+ * lists the exact same classes in `MapiEmsmdbRouteMongo`/`SQL`), so the concrete class can only ever be known
22
+ * per-request, via this context - needed by `RopSubmitMessageHandler`'s `findOrCreateWellKnownFolder()`/
23
+ * `messageRepo.create()` calls when persisting a submitted message's Sent Items copy. `scanPipeline`/
24
+ * `mailTransport` back the same `scanAndRelay()` compose/send pipeline `BaseMessageRoute.send()`/EAS's
25
+ * `ComposeMailCommand` already share via `MailSendUtils.ts`.
26
+ *
27
+ * `contactRepo`/`taskRepo`/`contactClass`/`taskClass`/`labelRepo`/`labelClass` are **optional**, unlike every
28
+ * other repo/class pair here - `BaseMapiEmsmdbRoute` always populates them in real use (see its own `@Init`),
29
+ * but making them required would force every one of this codebase's many existing `RopContext`-literal test
30
+ * fixtures (most predating Contacts/Tasks folder support, and all predating Label support) to grow new fields
31
+ * each for no behavioral reason. Every consumer (`PropertyResolvers.ts`, `RopGetContentsTableHandler.ts`,
32
+ * `RopOpenMessageHandler.ts`) already degrades a `"contact:"`/`"task:"` target to empty/no-rows when the
33
+ * corresponding repo is absent, the same "missing data, not a crash" stance `resolveContactInfo`/
34
+ * `resolveTaskInfo` take for a vanished row; `labelRepo` absent degrades `PidNameKeywords` (Outlook Categories)
35
+ * to an empty list the same way.
36
+ */
37
+ export interface RopContext {
38
+ mailboxUid: string;
39
+ userUid: string;
40
+ session: MapiSessionContext;
41
+ mailboxRepo: RepoUtils<any>;
42
+ folderRepo: RepoUtils<any>;
43
+ messageRepo: RepoUtils<any>;
44
+ calendarEventRepo: RepoUtils<any>;
45
+ contactRepo?: RepoUtils<any>;
46
+ taskRepo?: RepoUtils<any>;
47
+ labelRepo?: RepoUtils<any>;
48
+ folderClass: any;
49
+ messageClass: any;
50
+ calendarEventClass: any;
51
+ contactClass?: any;
52
+ taskClass?: any;
53
+ labelClass?: any;
54
+ blobStore: BlobStore;
55
+ scanPipeline: ScanPipeline;
56
+ mailTransport: any;
57
+ /** Records an audit log entry as the calling user, the same way the REST routes do. Optional like the repos
58
+ * above: absent when the route has no audit log class configured, in which case nothing is recorded. */
59
+ audit?: (params: AuditLogParams) => Promise<void>;
60
+ /** Recomputes, re-caches and publishes the given folders' `unreadCount`/`totalCount` - restapi's
61
+ * `refreshFolderCounts()` (`@rapidmx/restapi`), bound to this route's own `messageRepo`/`folderRepo`/`folderClass`/
62
+ * `notificationUtils`. `Folder.unreadCount`/`totalCount` are a cache restapi's own writes keep fresh (see
63
+ * `FolderCountUtils.ts`'s module doc comment); a handler that adds, deletes or moves a `Message` row through
64
+ * `context.messageRepo` itself - never through a `BaseFolderRoute`/`BaseMessageRoute` this library already refreshes
65
+ * after - must call this for every folder it touched, or Outlook's own folder pane (which reads
66
+ * `PidTagContentCount`/`PidTagContentUnreadCount` straight off the stored fields, not derived on read - see
67
+ * `PropertyResolvers.ts`/`FolderTarget.ts`) goes stale. `bumpSyncKey` should be `true` when a message was added to a
68
+ * folder (restapi's own convention - see `refreshFolderCounts()`'s doc comment), and left unset for a plain
69
+ * deletion or status change. Always present in real use (`BaseMapiEmsmdbRoute.dispatch()` populates it
70
+ * unconditionally); optional here only so existing `RopContext`-literal test fixtures that predate this need no
71
+ * new field, the same reasoning `contactRepo`/`taskRepo`/`labelRepo` above already documents. */
72
+ notifyFolderCounts?: (folderUids: Iterable<string | undefined | null>, options?: {
73
+ bumpSyncKey?: boolean;
74
+ }) => Promise<void>;
75
+ /** Shared storage for FastTransfer streams and write-stream chunks. Absent in unit tests, where
76
+ * `defaultHandleDataStore` (this process only) is used - see `handleDataStoreOf`. */
77
+ handleData?: HandleDataStore;
78
+ /** This request's work budget, set by `dispatchRops`. Absent when a handler runs on its own. */
79
+ budget?: ExecuteBudget;
80
+ /** The bytes still free in this request's ROP output buffer for the current ROP's response, set by
81
+ * `dispatchRops` before each ROP. Handlers returning variable-size data (`RopReadStream`,
82
+ * `RopFastTransferSourceGetBuffer`, `RopQueryRows`) shrink their response to fit. Absent means unlimited. */
83
+ ropOutputRemaining?: number;
84
+ }
85
+ /** The `HandleDataStore` a handler should use for `context`. */
86
+ export declare function handleDataStoreOf(context: Pick<RopContext, "handleData">): HandleDataStore;
87
+ /**
88
+ * One class per ROP, mirroring `EasCommandHandler`'s exact one-class-per-command convention. A handler owns
89
+ * its own request/response wire format entirely - decoding whatever fields follow its own `RopId` byte from
90
+ * `reader` (already consumed by the dispatcher) and appending its response bytes (if any - `RopRelease`
91
+ * intentionally appends none) to the shared `writer`. See `RopBuffer.ts`'s doc comment for why a generic
92
+ * dispatcher can't do this decoding itself.
93
+ */
94
+ export interface RopHandler {
95
+ /** The `RopId` byte this handler processes (e.g. `0xFE` for `RopLogon`). */
96
+ readonly ropId: number;
97
+ /** Where the handle index a failure response echoes sits in the request, counting the `RopId` byte as 0. Most
98
+ * ROPs echo `InputHandleIndex` (2, the default); ROPs that create an object echo `OutputHandleIndex` (3). Used
99
+ * by `RopDispatcher` to write a failure response when the handler throws. */
100
+ readonly responseHandleIndexOffset?: number;
101
+ /** Zero bytes a failure response carries after `ReturnValue` (`RopReadStream`'s `DataSize`, ...). */
102
+ readonly failureTailBytes?: number;
103
+ /** `true` for a ROP that never has a response (`RopRelease`), so a failure writes nothing either. */
104
+ readonly hasNoResponse?: boolean;
105
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void> | void;
106
+ }
@@ -0,0 +1,31 @@
1
+ import { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopLogon` (`[MS-OXCSTOR]`/`[MS-OXCROPS]`): the first ROP of any session, a prerequisite for every other
5
+ * ROP. This pragmatic subset always performs a private-mailbox logon against the mailbox `BaseMapiEmsmdbRoute`
6
+ * already resolved and authorized at `Connect` time - the request's own `Essdn` field is decoded (to advance
7
+ * past it correctly) but never trusted for mailbox identity, the same principle `BaseMapiEmsmdbRoute.Connect`
8
+ * already applies to that request's `UserDn` field. Because mailbox resolution already happened upstream,
9
+ * this handler has no real failure path to model (`ecUnknownUser`/`ecLoginFailure`/... are all deferred).
10
+ *
11
+ * @author Jean-Philippe Steinmetz
12
+ */
13
+ export declare class RopLogonHandler implements RopHandler {
14
+ readonly ropId = 254;
15
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
16
+ /**
17
+ * Assigns this session's FIDs for the 13 special folders, remembering each one's real-`Folder`-or-virtual-
18
+ * placeholder target in `session.folderIds` so a later `RopOpenFolder` can resolve it back to something
19
+ * real. Delegates to `FolderTarget.assignOrGetFid` - the same "reuse an existing FID for this target if one
20
+ * was already assigned, otherwise mint the next free one" logic `RopGetHierarchyTable`/`RopQueryRows` use
21
+ * for child folders - rather than wholesale-replacing `session.folderIds` with a freshly-numbered map
22
+ * starting back at FID 1. A second `RopLogon` on a live session (spec-legal - a real client can reconnect
23
+ * or re-logon within one session context) previously invalidated every FID a client had already learned
24
+ * for a child folder via `RopGetHierarchyTable`/`RopQueryRows` (all assigned starting at FID 14, since the
25
+ * old code always reset the counter to 1-13 for just these specials) and could even cause FIDs 14+ to be
26
+ * silently re-issued to a *different* folder than the one the client last associated with that number.
27
+ * Reusing `assignOrGetFid` means a re-`RopLogon` is now purely additive: these 13 targets get their
28
+ * existing FIDs back if this is a second logon, and any child-folder FIDs already assigned are left alone.
29
+ */
30
+ private assignFolderIds;
31
+ }
@@ -0,0 +1,20 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopOpenFolder` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`): opens a folder by FID, producing a new `"folder"` Server
5
+ * object handle later ROPs (`RopGetHierarchyTable`, a future `RopGetContentsTable`) reference by
6
+ * `InputHandleIndex`. The request's own `OpenModeFlags` (`OpenSoftDeleted`, ...) is decoded to advance past
7
+ * it correctly but not acted on - this pragmatic subset has no soft-deleted-folder recovery flow.
8
+ *
9
+ * Private-mailbox-only response shape: `IsGhosted` is always `0` and its conditional
10
+ * `ServerCount`/`CheapServerCount`/`Servers` fields are never present, since `IsGhosted` and its trailing
11
+ * fields are documented as public-folder-only (`[MS-OXCFOLD]`'s own "RopOpenFolder ROP Response Buffer"
12
+ * page). `HasRules` is always `0` - this library has no `[MS-OXORULE]` rules-engine support.
13
+ *
14
+ * @author Jean-Philippe Steinmetz
15
+ */
16
+ export declare class RopOpenFolderHandler implements RopHandler {
17
+ readonly ropId = 2;
18
+ readonly responseHandleIndexOffset = 3;
19
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): void;
20
+ }
@@ -0,0 +1,28 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopOpenMessage` (`[MS-OXCMSG]`/`[MS-OXCROPS]`): opens a message by MID, producing a new `"message"` Server
5
+ * object handle later ROPs (`RopGetPropertiesSpecific`, `RopOpenStream`) reference by `InputHandleIndex`. The
6
+ * request's own `FolderId` (the message's expected parent folder) and `CodePageId` are decoded to advance past
7
+ * them correctly but not otherwise acted on - a MID is resolved directly via `session.messageIds`
8
+ * (`MessageTarget.assignOrGetMid`), the same "the session already knows what this small integer refers to"
9
+ * design `RopOpenFolderHandler` uses for FIDs, so cross-checking the caller-supplied `FolderId` would be pure
10
+ * redundancy. `OpenModeFlags` is likewise decoded but not honored - this pragmatic subset only ever opens a
11
+ * message for reading (no `RopSetProperties`/`RopSaveChangesMessage` write-back support yet).
12
+ *
13
+ * **Pragmatic success response**: `HasNamedProperties` is always `false` - a conservative hint value only, not
14
+ * a claim that named properties are unsupported (`RopGetPropertyIdsFromNames`/`PropertyResolvers.ts` do resolve
15
+ * them for a subsequently-opened Calendar item; a real client calls `RopGetPropertyIdsFromNames` unconditionally
16
+ * for a known Calendar message class regardless of this flag, so leaving it `false` doesn't block that path).
17
+ * The `SubjectPrefix`/`NormalizedSubject` `TypedString`s are "no prefix" + the item's plain subject/title (this
18
+ * data model has no separate prefix/normalized-subject split the way real Exchange does), and the recipient
19
+ * table (`RecipientCount`/`ColumnCount`/`RecipientColumns`/`RowCount`/`RecipientRows`) is always empty - reading
20
+ * a message's `To`/`Cc` list is a documented gap in this pass, not silently wrong data.
21
+ *
22
+ * @author Jean-Philippe Steinmetz
23
+ */
24
+ export declare class RopOpenMessageHandler implements RopHandler {
25
+ readonly ropId = 3;
26
+ readonly responseHandleIndexOffset = 3;
27
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
28
+ }
@@ -0,0 +1,28 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopOpenStream` (`[MS-OXCPRPT]`/`[MS-OXCROPS]`): opens a property for streaming access, producing a new
5
+ * `"stream"` Server object handle `RopReadStream`/`RopWriteStream` reads/writes by `InputHandleIndex`.
6
+ * **Pragmatic subset**: only `PidTagBody` (`PtypString`, the plain-text message body) on a `"message"` handle
7
+ * is supported - a real client also streams `PidTagHtml`/`PidTagRtfCompressed` (rich body formats) and
8
+ * folder/attachment binary properties, none of which this pass implements; requesting any other `PropertyTag`,
9
+ * or opening a stream against a non-message handle, fails with `MAPI_E_NOT_FOUND` rather than succeeding with
10
+ * wrong data.
11
+ *
12
+ * `OpenModeFlags` (`[MS-OXCPRPT]` §2.2.14.1) selects which of two independent code paths runs:
13
+ * - `ReadOnly` (`0x00`): the step-7 behavior - resolves the real, already-saved message's body via
14
+ * `MessageBodyStream.resolveMessageBodyBytes()` (`BlobStore` + `mailparser`) and reports its length as
15
+ * `StreamSize`, for `RopReadStream` to read back.
16
+ * - `ReadWrite`/`Create` (`0x01`/`0x02`): a **write-mode** stream, the step-8 addition compose/send needs to
17
+ * accept a message body via `RopWriteStream` - `StreamSize` is always `0` (matching `Create`'s "discards the
18
+ * existing value" semantics; this pragmatic subset never opens `ReadWrite` against a body that already has
19
+ * content) and the new handle's `writeTargetHandleIndex` remembers which message handle to write back into,
20
+ * the link `RopWriteStream`/`RopSaveChangesMessageHandler`/`RopSubmitMessageHandler` use to find it again.
21
+ *
22
+ * @author Jean-Philippe Steinmetz
23
+ */
24
+ export declare class RopOpenStreamHandler implements RopHandler {
25
+ readonly ropId = 43;
26
+ readonly responseHandleIndexOffset = 3;
27
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
28
+ }
@@ -0,0 +1,43 @@
1
+ import { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /** The most rows one `RopQueryRows` returns. Fewer rows than requested is normal table paging: the client asks
4
+ * again from the new cursor position. */
5
+ export declare const MAX_ROWS_PER_QUERY = 500;
6
+ /**
7
+ * `RopQueryRows` (`[MS-OXCTABL]`/`[MS-OXCROPS]`): fetches up to `RowCount` rows from an already-configured
8
+ * table (`RopGetHierarchyTable` + `RopSetColumns`), advancing the table's cursor. Confirmed field-by-field
9
+ * against `[MS-OXCTABL]`'s own real captured request/response byte example - including the response's
10
+ * `RowData` encoding (`PropertyRow`: a leading `Flags` byte, `0x00` for a `StandardPropertyRow` where every
11
+ * column is a plain `PropertyValue`, `0x01` for a `FlaggedPropertyRow` where each column gets its own
12
+ * `FlaggedPropertyValue` flag+optional-value). This pragmatic subset always emits `StandardPropertyRow`
13
+ * (`Flags = 0x00`) - every configured column always resolves to *some* value here (falling back to a
14
+ * type-appropriate zero/empty default for an unsupported property, never an error), so `FlaggedPropertyRow`'s
15
+ * per-column error signaling is never needed. That "never an error" promise covers a client-requested
16
+ * `propertyType` that doesn't match what a column's own `propertyId` resolves to, not just an entirely
17
+ * unmodeled `propertyId` - `buildRow` writes each column through `PropertyResolvers.writePropertyValueSafely`,
18
+ * which falls back to the same type-appropriate default there too, so one row's mismatched column degrades
19
+ * instead of failing this whole call (and discarding every row already built in it).
20
+ *
21
+ * Backward reads (`ForwardRead = FALSE`) and the `NoAdvance` flag are decoded (to advance the reader
22
+ * correctly) but not honored - this pragmatic subset's tables are simple forward-only cursors. Works generically
23
+ * against either a `RopGetHierarchyTable` (folder rows, `"folder:"`/`"virtual:"` targets) or a
24
+ * `RopGetContentsTable` (message rows, `"message:"` targets) handle - dispatching to `FolderTarget`'s or
25
+ * `MessageTarget`'s resolver and property-value mapping by row-target prefix, since a single table handle only
26
+ * ever holds one kind of row.
27
+ *
28
+ * **Known, deliberate performance limitation**: `resolvePropertyValues` still does one `repo.findOne()` per
29
+ * row (message/calendarEvent/contact/task) rather than a single batched fetch for the whole page - a genuine
30
+ * N+1 for a large `RowCount`. A real fix needs a batched "fetch all these uids" query, which this codebase's
31
+ * `RepoUtils<T>` abstraction doesn't expose a backend-agnostic way to express (Mongo's `$in` and TypeORM's
32
+ * `In()` aren't interchangeable at this call site, which has no idea which backend it's running against) - see
33
+ * `ResolutionCache` for the narrower, safe win this pass *did* make (the redundant *identical* whole-mailbox-
34
+ * folder-list and calendar-mailbox fetches that were happening once per row regardless of which distinct
35
+ * entity each row was, now happen at most once per call).
36
+ *
37
+ * @author Jean-Philippe Steinmetz
38
+ */
39
+ export declare class RopQueryRowsHandler implements RopHandler {
40
+ readonly ropId = 21;
41
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
42
+ private buildRow;
43
+ }
@@ -0,0 +1,20 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopReadStream` (`[MS-OXCPRPT]`/`[MS-OXCROPS]`): reads up to `ByteCount` (or `MaximumByteCount`, if
5
+ * `ByteCount` is the `0xBABE` sentinel) bytes from an already-opened stream (`RopOpenStream`), advancing the
6
+ * stream's read position - clamped to `MAX_DATA_SIZE` (`0xFFFF`) regardless of what the client asked for, since
7
+ * the response's own `DataSize` field can't carry more than that; a client wanting more of a large body simply
8
+ * calls `RopReadStream` again from the advanced `streamPosition`, the same paging behavior real Exchange uses. It is
9
+ * likewise held to the room left in the request's ROP output buffer (`context.ropOutputRemaining`).
10
+ * Unlike most other ROPs in this pragmatic subset, `[MS-OXCROPS]` documents only one combined response-buffer
11
+ * shape for this ROP (no separate Success/Failure pages) - `DataSize`/`Data` are always present, `DataSize = 0`
12
+ * (empty `Data`) standing in for the failure case rather than the fields being omitted entirely.
13
+ *
14
+ * @author Jean-Philippe Steinmetz
15
+ */
16
+ export declare class RopReadStreamHandler implements RopHandler {
17
+ readonly ropId = 44;
18
+ readonly failureTailBytes = 2;
19
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
20
+ }
@@ -0,0 +1,15 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopRelease` (`[MS-OXCROPS]`): releases a Server object handle. Confirmed against the spec's own real
5
+ * captured example (`08 00 01 00 00 01 00 01 6F 00 00 00 6E 00 00 00`) that the request is exactly 3 bytes
6
+ * (`RopId`/`LogonId`/`InputHandleIndex`) and, uniquely among ROPs, **produces no response entry at all** - the
7
+ * server MUST NOT write anything to the output buffer for it, so `handle()` never touches `writer`.
8
+ *
9
+ * @author Jean-Philippe Steinmetz
10
+ */
11
+ export declare class RopReleaseHandler implements RopHandler {
12
+ readonly ropId = 1;
13
+ readonly hasNoResponse = true;
14
+ handle(reader: BufferReader, _writer: BufferWriter, context: RopContext): void;
15
+ }
@@ -0,0 +1,59 @@
1
+ import { type CalendarEvent } from "@rapidmx/restapi";
2
+ import { BufferReader, type BufferWriter } from "../codec/BufferCursor.js";
3
+ import type { RopContext, RopHandler } from "./RopHandler.js";
4
+ /**
5
+ * `RopSaveChangesMessage` (`[MS-OXCMSG]`/`[MS-OXCROPS]`): persists a message's pending changes, returning the
6
+ * `MID` a client can reference it by afterwards. **Pragmatic subset**: this library has no separate Drafts-
7
+ * folder persistence step (matching EAS's own `ComposeMailCommand`, which never creates a standalone draft
8
+ * `Message` row either) - saving a `RopCreateMessage` draft does not yet write anything to a database. It only
9
+ * assigns a session-scoped MID (`MessageTarget.assignOrGetMid`, the exact mechanism a `RopQueryRows` row's
10
+ * `PidTagMid` column already uses) so the wire contract's `MessageId` field is well-formed, keyed by this
11
+ * handle's own index (`` `draft:${inputHandleIndex}` ``) since an unsaved draft has no real `"message:<uid>"`
12
+ * target yet. The real work - actually building and sending a MIME message from the properties/body
13
+ * accumulated so far - happens at `RopSubmitMessage` time, not here; calling Save without ever calling Submit
14
+ * silently discards the draft when the session expires, a real, deliberate limitation (this pragmatic subset
15
+ * has no autosave-recovery story) rather than a silently-wrong one.
16
+ *
17
+ * Re-saving an already-real message (a `RopOpenMessage` handle, not a fresh `RopCreateMessage` draft) is
18
+ * accepted but is a no-op beyond echoing that message's own already-assigned MID - editing an existing
19
+ * message's properties is out of scope for this pragmatic subset's compose/send-only ROP coverage.
20
+ *
21
+ * `SaveFlags` is decoded to advance past it correctly but not honored - this pragmatic subset has no
22
+ * conflict-resolution/force-save semantics to vary by flag.
23
+ *
24
+ * **Calendar branch**: unlike mail, a draft whose `PidTagMessageClass` starts with `"IPM.Appointment"` *is*
25
+ * durably persisted here (`saveAppointment()`) - creating (or updating, if this handle already refers to a real
26
+ * `"calendarEvent:<uid>"`) a real `CalendarEvent` row, so a subsequent `RopGetPropertiesSpecific` can read it
27
+ * back before `RopSubmitMessage` is ever called (a calendar item must exist as soon as it's saved, the same way
28
+ * a real Exchange server behaves - unlike a mail draft, which this pragmatic subset never persists at all until
29
+ * Submit). Saving never invites anyone: a saved revision is stamped as already handled (`inviteSequenceSent =
30
+ * sequence`), so restapi's `MeetingSchedulingJob` skips it until `RopSubmitMessageHandler.submitAppointment` asks for
31
+ * invites. Attendees of a submitted meeting are then mailed by that job, which is why a meeting is capped at
32
+ * `MAX_RECIPIENTS_PER_MESSAGE` attendees here. Client-supplied times and reminder minutes that don't parse are ignored (the field keeps its
33
+ * existing or default value) rather than stored as `Invalid Date`/`NaN`, and an update is versioned (`asEntity`).
34
+ *
35
+ * @author Jean-Philippe Steinmetz
36
+ */
37
+ export declare class RopSaveChangesMessageHandler implements RopHandler {
38
+ readonly ropId = 12;
39
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
40
+ /**
41
+ * Persists (creating, or updating an already-real one) a `CalendarEvent` from the draft's accumulated
42
+ * `RopSetProperties` values, and returns the MID a client can reference it by. Every field this pragmatic
43
+ * subset understands (per `CalendarNamedProperties.ts`) is decoded via `decodeCalendarFieldsFromDraft`
44
+ * below - the write-side mirror of `PropertyResolvers.calendarEventValueFor`'s read-side switch. A field the
45
+ * client never set (e.g. no `RopSetProperties` call touched `PidLidBusyStatus`) falls back to the existing
46
+ * row's own value on an update, or the entity class's own default on a fresh create.
47
+ *
48
+ * Returns `undefined`, saving nothing, for more than `MAX_RECIPIENTS_PER_MESSAGE` attendees: restapi's
49
+ * `MeetingSchedulingJob` mails every attendee of a saved meeting.
50
+ */
51
+ private saveAppointment;
52
+ }
53
+ /**
54
+ * `true` when an update changes what attendees were invited to: the start or end time, location, recurrence, or
55
+ * who is invited. The same fields restapi's `BaseCalendarEventRoute.update()` bumps `sequence` for (a MAPI save
56
+ * never sets `status`), so an edit made in Outlook is re-sent to attendees and supersedes their copy (RFC 5546
57
+ * `SEQUENCE`) exactly like one made through REST.
58
+ */
59
+ export declare function isSchedulingRelevantChange(existing: Pick<CalendarEvent, "startDate" | "endDate" | "location" | "recurrenceRule" | "attendees">, updated: Pick<CalendarEvent, "startDate" | "endDate" | "location" | "recurrenceRule" | "attendees">): boolean;
@@ -0,0 +1,21 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopSetColumns` (`[MS-OXCTABL]`/`[MS-OXCROPS]`): configures which properties a table's subsequent
5
+ * `RopQueryRows` calls return, and in what order. Unlike `RopOpenFolder`/`RopGetHierarchyTable`, this ROP
6
+ * doesn't create a new handle - it mutates the existing table handle referenced by `InputHandleIndex` in
7
+ * place, so its response echoes `InputHandleIndex` rather than an `OutputHandleIndex`, the same convention
8
+ * `RopQueryRows`' own response uses (confirmed against `[MS-OXCTABL]`'s real captured response example).
9
+ *
10
+ * Validates that `InputHandleIndex` actually refers to a `"table"` handle before mutating it, the same check
11
+ * every other handle-consuming ROP in this pragmatic subset already makes - a missing or wrong-type handle
12
+ * previously fell through silently (no `else` branch) to a bare success response, meaning a client that set
13
+ * columns against a stale/released/wrong-kind handle index only discovered the real problem one ROP later,
14
+ * when `RopQueryRows` on that same index returned `MAPI_E_INVALID_OBJECT` instead.
15
+ *
16
+ * @author Jean-Philippe Steinmetz
17
+ */
18
+ export declare class RopSetColumnsHandler implements RopHandler {
19
+ readonly ropId = 18;
20
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): void;
21
+ }
@@ -0,0 +1,38 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /** `PidTagReadReceiptRequested` (`PT_BOOLEAN`) - a client checking "Request a read receipt" before Submit sets
4
+ * this; `RopSubmitMessageHandler` reads it back to attach a real `Disposition-Notification-To` header, mirroring
5
+ * `BaseMessageRoute.send()`'s own explicit-`Message.requestReceipt` handling (see `util/ReceiptUtils.ts`). */
6
+ export declare const PID_TAG_READ_RECEIPT_REQUESTED = 41;
7
+ /** `PidTagDeferredSendTime` (`PT_SYSTIME`) - real Outlook's "Do not deliver before" compose option;
8
+ * `RopSubmitMessageHandler` reads it back to defer relay exactly like `BaseMessageRoute.send()`'s own
9
+ * `Message.scheduledSendTime` branch does (moving the message to Outbox for `ScheduledSendJob` to relay later)
10
+ * instead of sending immediately. */
11
+ export declare const PID_TAG_DEFERRED_SEND_TIME = 16367;
12
+ /**
13
+ * `RopSetProperties` (`[MS-OXCPRPT]`/`[MS-OXCROPS]`): sets an explicit, client-chosen list of properties
14
+ * (`TaggedPropertyValue`s, each carrying its own `PropertyTag`) on an already-open object. This pragmatic
15
+ * subset supports only `"message"` handles (a `RopCreateMessage` draft, or an already-open `RopOpenMessage`
16
+ * handle) and only tracks the small property set `TRACKED_PROPERTY_IDS`/`TRACKED_APPOINTMENT_LIDS`/
17
+ * `TRACKED_COMMON_LIDS` name - see those constants' own comments for why. A named property (ID `>= 0x8000`) is
18
+ * resolved back to its `(PropertySet GUID, LID)` identity via `resolveNamedProperty` before it can be tracked,
19
+ * since MAPI has no fixed numeric ID for any Calendar property the way `PidTagSubject` has one.
20
+ *
21
+ * Every value is coerced to a plain string via `stringifyValue` before being stored in `handle.draftProperties`
22
+ * (itself a `Record<string, string>` for `MapiSessionContext`'s own JSON-safety reasons, see
23
+ * `MapiSessionManager.ts`) - a lossless, type-aware encoding (ISO-8601 for `PtypTime`, base64 for `PtypBinary`,
24
+ * plain `String()` otherwise) so `RopSaveChangesMessageHandler` can decode a Calendar item's properties back
25
+ * into a real `CalendarEvent`'s typed fields, not just display strings.
26
+ *
27
+ * Always reports success with zero `PropertyProblems`, even for a property this pragmatic subset doesn't
28
+ * track - the same "never fail over an unsupported property" pragmatic stance `RopQueryRows`/
29
+ * `RopGetPropertiesSpecific` already take (falling back to a default value there; simply not tracking the
30
+ * property here, since there is no row to fill in for a *set* operation).
31
+ *
32
+ * @author Jean-Philippe Steinmetz
33
+ */
34
+ export declare class RopSetPropertiesHandler implements RopHandler {
35
+ readonly ropId = 10;
36
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): void;
37
+ private isTrackedNamedProperty;
38
+ }