@rapidmx/mapi-plugin 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/LICENSE +382 -0
  2. package/README.md +60 -0
  3. package/dist/lib/BaseMapiEmsmdbRoute.js +297 -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 +119 -0
  8. package/dist/lib/MapiSessionManager.js.map +1 -0
  9. package/dist/lib/RopDispatcher.js +34 -0
  10. package/dist/lib/RopDispatcher.js.map +1 -0
  11. package/dist/lib/codec/AppointmentRecurrence.js +314 -0
  12. package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
  13. package/dist/lib/codec/BufferCursor.js +213 -0
  14. package/dist/lib/codec/BufferCursor.js.map +1 -0
  15. package/dist/lib/codec/GlobalObjectId.js +92 -0
  16. package/dist/lib/codec/GlobalObjectId.js.map +1 -0
  17. package/dist/lib/codec/MapiGuid.js +41 -0
  18. package/dist/lib/codec/MapiGuid.js.map +1 -0
  19. package/dist/lib/codec/MapiTimeZone.js +93 -0
  20. package/dist/lib/codec/MapiTimeZone.js.map +1 -0
  21. package/dist/lib/codec/PropertyValue.js +214 -0
  22. package/dist/lib/codec/PropertyValue.js.map +1 -0
  23. package/dist/lib/codec/RopBuffer.js +26 -0
  24. package/dist/lib/codec/RopBuffer.js.map +1 -0
  25. package/dist/lib/codec/TypedString.js +54 -0
  26. package/dist/lib/codec/TypedString.js.map +1 -0
  27. package/dist/lib/index.js +99 -0
  28. package/dist/lib/index.js.map +1 -0
  29. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +84 -0
  30. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
  31. package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
  32. package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
  33. package/dist/lib/mongo/index.js +7 -0
  34. package/dist/lib/mongo/index.js.map +1 -0
  35. package/dist/lib/mongo.js +6 -0
  36. package/dist/lib/mongo.js.map +1 -0
  37. package/dist/lib/nspi/NspiBindHandler.js +54 -0
  38. package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
  39. package/dist/lib/nspi/NspiCodec.js +106 -0
  40. package/dist/lib/nspi/NspiCodec.js.map +1 -0
  41. package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
  42. package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
  43. package/dist/lib/rop/CalendarEventTarget.js +27 -0
  44. package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
  45. package/dist/lib/rop/CalendarNamedProperties.js +57 -0
  46. package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
  47. package/dist/lib/rop/ContactTarget.js +23 -0
  48. package/dist/lib/rop/ContactTarget.js.map +1 -0
  49. package/dist/lib/rop/FastTransferStream.js +98 -0
  50. package/dist/lib/rop/FastTransferStream.js.map +1 -0
  51. package/dist/lib/rop/FolderTarget.js +111 -0
  52. package/dist/lib/rop/FolderTarget.js.map +1 -0
  53. package/dist/lib/rop/MeetingMessageClassHandler.js +95 -0
  54. package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
  55. package/dist/lib/rop/MessageBodyStream.js +42 -0
  56. package/dist/lib/rop/MessageBodyStream.js.map +1 -0
  57. package/dist/lib/rop/MessageTarget.js +44 -0
  58. package/dist/lib/rop/MessageTarget.js.map +1 -0
  59. package/dist/lib/rop/NamedPropertyRegistry.js +66 -0
  60. package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
  61. package/dist/lib/rop/PropertyResolvers.js +313 -0
  62. package/dist/lib/rop/PropertyResolvers.js.map +1 -0
  63. package/dist/lib/rop/RopCreateMessageHandler.js +53 -0
  64. package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
  65. package/dist/lib/rop/RopDeleteFolderHandler.js +133 -0
  66. package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
  67. package/dist/lib/rop/RopDeleteMessagesHandler.js +70 -0
  68. package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
  69. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
  70. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
  71. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +61 -0
  72. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
  73. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +75 -0
  74. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
  75. package/dist/lib/rop/RopGetContentsTableHandler.js +76 -0
  76. package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
  77. package/dist/lib/rop/RopGetHierarchyTableHandler.js +40 -0
  78. package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
  79. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +55 -0
  80. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
  81. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
  82. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
  83. package/dist/lib/rop/RopHandler.js +2 -0
  84. package/dist/lib/rop/RopHandler.js.map +1 -0
  85. package/dist/lib/rop/RopLogonHandler.js +132 -0
  86. package/dist/lib/rop/RopLogonHandler.js.map +1 -0
  87. package/dist/lib/rop/RopOpenFolderHandler.js +46 -0
  88. package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
  89. package/dist/lib/rop/RopOpenMessageHandler.js +81 -0
  90. package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
  91. package/dist/lib/rop/RopOpenStreamHandler.js +81 -0
  92. package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
  93. package/dist/lib/rop/RopQueryRowsHandler.js +91 -0
  94. package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
  95. package/dist/lib/rop/RopReadStreamHandler.js +55 -0
  96. package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
  97. package/dist/lib/rop/RopReleaseHandler.js +20 -0
  98. package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
  99. package/dist/lib/rop/RopSaveChangesMessageHandler.js +210 -0
  100. package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
  101. package/dist/lib/rop/RopSetColumnsHandler.js +52 -0
  102. package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
  103. package/dist/lib/rop/RopSetPropertiesHandler.js +162 -0
  104. package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
  105. package/dist/lib/rop/RopSubmitMessageHandler.js +314 -0
  106. package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
  107. package/dist/lib/rop/RopWriteStreamHandler.js +45 -0
  108. package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
  109. package/dist/lib/rop/TaskNamedProperties.js +29 -0
  110. package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
  111. package/dist/lib/rop/TaskTarget.js +19 -0
  112. package/dist/lib/rop/TaskTarget.js.map +1 -0
  113. package/dist/lib/sql/MapiEmsmdbRouteSQL.js +84 -0
  114. package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
  115. package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
  116. package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
  117. package/dist/lib/sql/index.js +7 -0
  118. package/dist/lib/sql/index.js.map +1 -0
  119. package/dist/lib/sql.js +6 -0
  120. package/dist/lib/sql.js.map +1 -0
  121. package/dist/types/BaseMapiEmsmdbRoute.d.ts +82 -0
  122. package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
  123. package/dist/types/MapiSessionManager.d.ts +130 -0
  124. package/dist/types/RopDispatcher.d.ts +17 -0
  125. package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
  126. package/dist/types/codec/BufferCursor.d.ts +78 -0
  127. package/dist/types/codec/GlobalObjectId.d.ts +11 -0
  128. package/dist/types/codec/MapiGuid.d.ts +6 -0
  129. package/dist/types/codec/MapiTimeZone.d.ts +7 -0
  130. package/dist/types/codec/PropertyValue.d.ts +77 -0
  131. package/dist/types/codec/RopBuffer.d.ts +25 -0
  132. package/dist/types/codec/TypedString.d.ts +17 -0
  133. package/dist/types/index.d.ts +94 -0
  134. package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +19 -0
  135. package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
  136. package/dist/types/mongo/index.d.ts +2 -0
  137. package/dist/types/mongo.d.ts +1 -0
  138. package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
  139. package/dist/types/nspi/NspiCodec.d.ts +59 -0
  140. package/dist/types/nspi/NspiGetMatchesHandler.d.ts +24 -0
  141. package/dist/types/rop/CalendarEventTarget.d.ts +29 -0
  142. package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
  143. package/dist/types/rop/ContactTarget.d.ts +29 -0
  144. package/dist/types/rop/FastTransferStream.d.ts +18 -0
  145. package/dist/types/rop/FolderTarget.d.ts +63 -0
  146. package/dist/types/rop/MeetingMessageClassHandler.d.ts +36 -0
  147. package/dist/types/rop/MessageBodyStream.d.ts +26 -0
  148. package/dist/types/rop/MessageTarget.d.ts +38 -0
  149. package/dist/types/rop/NamedPropertyRegistry.d.ts +46 -0
  150. package/dist/types/rop/PropertyResolvers.d.ts +60 -0
  151. package/dist/types/rop/RopCreateMessageHandler.d.ts +23 -0
  152. package/dist/types/rop/RopDeleteFolderHandler.d.ts +38 -0
  153. package/dist/types/rop/RopDeleteMessagesHandler.d.ts +31 -0
  154. package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +24 -0
  155. package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +24 -0
  156. package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +23 -0
  157. package/dist/types/rop/RopGetContentsTableHandler.d.ts +31 -0
  158. package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +17 -0
  159. package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +23 -0
  160. package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
  161. package/dist/types/rop/RopHandler.d.ts +67 -0
  162. package/dist/types/rop/RopLogonHandler.d.ts +31 -0
  163. package/dist/types/rop/RopOpenFolderHandler.d.ts +19 -0
  164. package/dist/types/rop/RopOpenMessageHandler.d.ts +27 -0
  165. package/dist/types/rop/RopOpenStreamHandler.d.ts +27 -0
  166. package/dist/types/rop/RopQueryRowsHandler.d.ts +36 -0
  167. package/dist/types/rop/RopReadStreamHandler.d.ts +18 -0
  168. package/dist/types/rop/RopReleaseHandler.d.ts +14 -0
  169. package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +44 -0
  170. package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
  171. package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
  172. package/dist/types/rop/RopSubmitMessageHandler.d.ts +70 -0
  173. package/dist/types/rop/RopWriteStreamHandler.d.ts +21 -0
  174. package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
  175. package/dist/types/rop/TaskTarget.d.ts +24 -0
  176. package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +19 -0
  177. package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
  178. package/dist/types/sql/index.d.ts +2 -0
  179. package/dist/types/sql.d.ts +1 -0
  180. package/package.json +104 -0
@@ -0,0 +1,82 @@
1
+ import { type JWTUser } from "@rapidrest/core";
2
+ import { HttpRequest, HttpResponse } from "@rapidrest/service-core";
3
+ import { Mailbox } from "@rapidmx/restapi";
4
+ /**
5
+ * Abstract base for the single fixed EMSMDB endpoint (`POST /mapi/emsmdb` by `[MS-OXCMAPIHTTP]` convention,
6
+ * though the concrete path is left to the consuming application to mount via `@Route(...)` - see
7
+ * `BaseEasRoute.ts` for the identical undecorated-base-class pattern this follows). Like EAS, MAPI/HTTP
8
+ * multiplexes several request types against one URL - here via the `X-RequestType` **header**
9
+ * (`Connect`/`Execute`/`Disconnect`/`NotificationWait`) rather than a query parameter - so there is exactly
10
+ * one `@Post()` method, not one per request type.
11
+ *
12
+ * **Auth**: `@Auth(["jwt"])` - unchanged from every other route in this app. Real, modern Exchange Server's
13
+ * own MAPI virtual directory genuinely supports `OAuth` as a configured `IISAuthenticationMethods` value
14
+ * alongside `NTLM`/`Negotiate`, so Bearer-token auth on this exact endpoint is real current Exchange
15
+ * behavior, not a deviation this library invents - see the architecture plan's "Auth" section.
16
+ *
17
+ * **Connect/Disconnect are complete**: `Connect` establishes a real `MapiSessionContext` (via
18
+ * `MapiSessionManager`) and sets the two spec-fixed session cookies (`MapiContext`/`MapiSequence`);
19
+ * `Disconnect` releases the session. **`Execute` dispatches real ROPs** (starting with `RopLogon`/`RopRelease`
20
+ * - build-order step 4) via `RopDispatcher`, using whichever `RopHandler`s `ropHandlerClasses` registers;
21
+ * an unrecognized `RopId` simply stops processing (see `RopDispatcher`'s own doc comment for why), the same
22
+ * "real, functional, no commands implemented yet" stance `BaseEasRoute`'s own skeleton step took for its
23
+ * empty `commandHandlerClasses` before `ProvisionCommand` landed - here scoped per-ROP instead of per-request.
24
+ *
25
+ * **Always non-chunked**: every response here uses `Content-Length` (via `res.status(200).send(buffer)`),
26
+ * never `Transfer-Encoding: chunked` - both are equally spec-valid per `[MS-OXCMAPIHTTP]`'s own "Common
27
+ * Response Format", and skipping the `PROCESSING`/`PENDING`/`DONE` keep-alive meta-tag streaming matches
28
+ * every other route in this library's own response idiom. See the architecture plan's "Protocol facts"
29
+ * section for the full reasoning and its documented limitation.
30
+ *
31
+ * `mailboxClass`/`folderClass` are supplied by the Mongo/SQL concrete subclasses, following the exact
32
+ * one-line-per-backend pattern used throughout this library's other routes/jobs. `folderRepo` is built once
33
+ * here (not per-handler, unlike EAS's per-command repo pattern) and threaded through `RopContext` to every
34
+ * `RopHandler` - see `RopHandler.ts`'s own doc comment for why.
35
+ *
36
+ * @author Jean-Philippe Steinmetz
37
+ */
38
+ export declare abstract class BaseMapiEmsmdbRoute<M extends Mailbox> {
39
+ protected abstract mailboxClass: any;
40
+ protected abstract folderClass: any;
41
+ protected abstract messageClass: any;
42
+ protected abstract calendarEventClass: any;
43
+ protected abstract contactClass: any;
44
+ protected abstract taskClass: any;
45
+ protected abstract labelClass: any;
46
+ /** ROP handler classes to instantiate (one each) in `@Init`, keyed by their own `ropId`. Empty until a
47
+ * concrete `RopHandler` lands - every ROP is then simply left unprocessed (see `RopDispatcher`'s own doc
48
+ * comment), the correct, honest behavior for a transport skeleton with no ROPs implemented yet. */
49
+ protected ropHandlerClasses: any[];
50
+ private _objectFactory?;
51
+ private mailboxRepo?;
52
+ private folderRepo?;
53
+ private messageRepo?;
54
+ private calendarEventRepo?;
55
+ private contactRepo?;
56
+ private taskRepo?;
57
+ private labelRepo?;
58
+ private sessionManager?;
59
+ private readonly ropHandlers;
60
+ private blobStore?;
61
+ private scanPipeline?;
62
+ private mailTransport?;
63
+ private logger;
64
+ init(): Promise<void>;
65
+ dispatch(req: HttpRequest, res: HttpResponse, user?: JWTUser): Promise<void>;
66
+ /**
67
+ * `Connect` establishes a new Session Context - never trusts the request body's own `UserDn` field for
68
+ * mailbox identity (the same "never a client-supplied mailbox, always resolved from the authenticated
69
+ * JWT" principle `BaseEasRoute`/`BaseSearchRoute` already apply via `resolveCallerMailboxUid`) - so the
70
+ * request body is never decoded at all here.
71
+ */
72
+ private handleConnect;
73
+ /**
74
+ * `Execute` decodes the outer envelope (`Flags`/`RopBufferSize`/`RopBuffer`/...) and the inner ROP buffer
75
+ * framing (`RopBuffer.ts`), dispatches every contained ROP via `RopDispatcher`, and re-encodes the
76
+ * collected responses - preserving the incoming `handleTable` unchanged (this pragmatic subset never
77
+ * allocates/frees table-wide handle slots at the framing level; individual `RopHandler`s manage their own
78
+ * entries within `session.handles` instead).
79
+ */
80
+ private handleExecute;
81
+ private handleDisconnect;
82
+ }
@@ -0,0 +1,38 @@
1
+ import { type JWTUser } from "@rapidrest/core";
2
+ import { HttpRequest, HttpResponse } from "@rapidrest/service-core";
3
+ import { Mailbox } from "@rapidmx/restapi";
4
+ /**
5
+ * Abstract base for the single fixed NSPI endpoint (`POST /mapi/nspi` by `[MS-OXCMAPIHTTP]` convention, though
6
+ * the concrete path is left to the consuming application to mount via `@Route(...)`, the identical
7
+ * undecorated-base-class pattern `BaseMapiEmsmdbRoute`/`BaseEasRoute` already establish). Dispatches on the
8
+ * `X-RequestType` header, the same multiplexing mechanism EMSMDB uses - but unlike EMSMDB, there is no ROP
9
+ * buffer framing here: each `X-RequestType` value (`Bind`/`Unbind`/`GetMatches`) is its own flat, self-
10
+ * contained request/response body (`[MS-OXCMAPIHTTP]` §2.2.5), so this route dispatches directly to a small
11
+ * handler function per operation instead of decoding a multiplexed stream.
12
+ *
13
+ * **Pragmatic subset scope**: only `Bind`/`Unbind`/`GetMatches` are implemented - real NSPI's other dozen-plus
14
+ * request types (`QueryRows`, `ResolveNames`, `GetProps`, `ModProps`, ...) are genuine full address-book/
15
+ * directory-replication operations this deployment's own minimal GAL-lookup use case doesn't need; an
16
+ * unrecognized `X-RequestType` gets a `501`, the same "recognized-but-deferred vs malformed" distinction
17
+ * `BaseMapiEmsmdbRoute`'s own `NotificationWait` case already draws. See `NspiBindHandler.ts`'s own doc comment
18
+ * for why no real NSPI session state is created or tracked between calls.
19
+ *
20
+ * **Auth**: `@Auth(["jwt"])`, unchanged from every other route in this app - the same reasoning
21
+ * `BaseMapiEmsmdbRoute`'s own doc comment already gives for why Bearer-token auth on an address-book endpoint
22
+ * is real, current Exchange behavior, not a deviation this library invents.
23
+ *
24
+ * `mailboxClass`/`contactClass` are supplied by the Mongo/SQL concrete subclasses. GAL search itself (see
25
+ * `NspiGetMatchesHandler.ts`) uses `RepoUtils`'s `regex(...)` operator, which compiles identically on both
26
+ * backends - no per-backend hook needed here.
27
+ *
28
+ * @author Jean-Philippe Steinmetz
29
+ */
30
+ export declare abstract class BaseMapiNspiRoute<M extends Mailbox> {
31
+ protected abstract mailboxClass: any;
32
+ protected abstract contactClass: any;
33
+ private _objectFactory?;
34
+ private mailboxRepo?;
35
+ private contactRepo?;
36
+ init(): Promise<void>;
37
+ dispatch(req: HttpRequest, res: HttpResponse, user?: JWTUser): Promise<void>;
38
+ }
@@ -0,0 +1,130 @@
1
+ import { SimpleEntity } from "@rapidrest/service-core";
2
+ /** Tags what a ROP-assigned integer handle (the `ServerObjectHandleTable` index space) refers to.
3
+ * `entityUid` for a `"folder"` handle is one of `session.folderIds`' own value strings (`"virtual:<name>"` or
4
+ * `"folder:<uid>"`), not a bare UID - the same format throughout avoids a second parallel encoding. A
5
+ * `"table"` handle's `rows`/`columns`/`cursor` hold `RopGetHierarchyTable`/`RopSetColumns`/`RopQueryRows`
6
+ * state for that specific table instance: `rows` is the resolved, order-fixed list of entity targets (same
7
+ * `"virtual:<name>"`/`"folder:<uid>"` format) this table enumerates, `columns` the `RopSetColumns`-configured
8
+ * property list, `cursor` how many rows `RopQueryRows` has already returned. A `"stream"` handle's `entityUid`
9
+ * is the `"message:<uid>"` target its content was opened from, `propertyId`/`propertyType` the `PropertyTag`
10
+ * `RopOpenStream` opened (this pragmatic subset only ever supports `PidTagBody`/`PtypString`, see
11
+ * `MessageBodyStream.ts`), and `streamPosition` how many bytes `RopReadStream` has already returned (a
12
+ * read-mode stream) or `writeTargetHandleIndex`/`writeBufferBase64` the accumulated write state (a write-mode
13
+ * stream opened `ReadWrite`/`Create` against a `RopCreateMessage` draft's `PidTagBody` - see
14
+ * `RopOpenStreamHandler`'s own doc comment).
15
+ *
16
+ * A `"message"` handle from `RopCreateMessage` (a draft not yet `RopSaveChangesMessage`d) has `entityUid: ""`
17
+ * and instead carries `draftFolderUid` (the folder it will belong to) and `draftProperties` (the small,
18
+ * well-known set of properties this pragmatic subset's `RopSetProperties` tracks - Subject/DisplayTo/
19
+ * DisplayCc/DisplayBcc/an inline `PidTagBody`, each coerced to a plain string, keyed by decimal `PropertyId` -
20
+ * a string key because a JSON-object key is always a string regardless of how it's written). `writeBufferBase64`
21
+ * stores accumulated `RopWriteStream` bytes as base64 rather than a raw `Buffer` for the same reason `Date`
22
+ * fields elsewhere in this class are stored as ISO strings: `RedisCache`'s Redis-backed path round-trips
23
+ * everything through `JSON.stringify`/`JSON.parse`, which cannot represent a `Buffer` (or a `bigint`, which is
24
+ * why no property value is ever stored in its native decoded MAPI type here) losslessly.
25
+ *
26
+ * A `"fastTransfer"` handle (`RopFastTransferSourceCopyTo`/`CopyProperties`'s output handle) carries the
27
+ * entire pre-built FastTransfer stream eagerly (`transferBufferBase64`, same base64-for-`RedisCache` reasoning
28
+ * as `writeBufferBase64`) - this pragmatic subset builds the whole stream up front rather than truly
29
+ * incrementally, since a real client only ever pages it out via `RopFastTransferSourceGetBuffer`'s own
30
+ * `transferPosition` cursor into that already-complete buffer, see `FastTransferStream.ts`'s own doc comment. */
31
+ export interface MapiObjectHandle {
32
+ type: "logon" | "folder" | "message" | "table" | "stream" | "fastTransfer";
33
+ entityUid: string;
34
+ rows?: string[];
35
+ columns?: {
36
+ propertyId: number;
37
+ propertyType: number;
38
+ }[];
39
+ cursor?: number;
40
+ propertyId?: number;
41
+ propertyType?: number;
42
+ streamPosition?: number;
43
+ draftFolderUid?: string;
44
+ draftProperties?: Record<string, string>;
45
+ writeTargetHandleIndex?: number;
46
+ writeBufferBase64?: string;
47
+ transferBufferBase64?: string;
48
+ transferPosition?: number;
49
+ }
50
+ /**
51
+ * The MAPI/HTTP `Session Context` (`[MS-OXCMAPIHTTP]` §3.1.1.1): everything a `Connect`-established session
52
+ * needs across subsequent `Execute` requests. Never persisted to a real database - purely an ephemeral,
53
+ * TTL-bound cache entry (see `MapiSessionManager` below), so this deliberately extends `SimpleEntity` (just a
54
+ * `uid`) rather than this library's own `BaseEntity`, which would add ACL/soft-delete/optimistic-locking
55
+ * machinery this object has no use for.
56
+ *
57
+ * `createdAt` is a plain ISO-8601 string, not a `Date` - `RedisCache`'s Redis-backed path round-trips values
58
+ * through `JSON.stringify`/`JSON.parse`, which silently turns a `Date` into a string on the way out without
59
+ * reviving it back to a `Date` on the way in; storing it as a string from the start keeps the shape identical
60
+ * whether a session happens to be served from the in-memory or the Redis-backed path.
61
+ */
62
+ export declare class MapiSessionContext extends SimpleEntity {
63
+ mailboxUid: string;
64
+ userUid: string;
65
+ handles: Record<number, MapiObjectHandle>;
66
+ nextHandleIndex: number;
67
+ createdAt: string;
68
+ /** This session's FID assignments for the 13 `RopLogon` special folders, keyed by FID (decimal string),
69
+ * valued `"virtual:<name>"` or `"folder:<uid>"` - see `RopLogonHandler`'s own doc comment. Populated by
70
+ * `RopLogon`, read back by a later `RopOpenFolder`. */
71
+ folderIds: Record<string, string>;
72
+ /** The reverse of `folderIds` (target -> FID) plus a monotonic counter, maintained alongside it by
73
+ * `FolderTarget.assignOrGetFid`/`RopLogonHandler` so "does this target already have a FID" is an O(1)
74
+ * lookup instead of a linear scan of `folderIds` repeated once per table row - see `assignOrGetFid`'s own
75
+ * doc comment for why the scan this replaces was a genuine (not just theoretical) quadratic cost across a
76
+ * session's lifetime. Small and purely additive to session state (one more string-keyed map of numbers,
77
+ * the same order of magnitude as `folderIds` itself), unlike caching whole resolved rows would be. */
78
+ folderTargetIds: Record<string, number>;
79
+ nextFolderId: number;
80
+ /** This session's MID assignments, keyed by MID (decimal string), valued `"message:<uid>"` - the message
81
+ * analog of `folderIds` above. Unlike `folderIds` (pre-populated by `RopLogon`), a MID only ever comes into
82
+ * existence lazily, the first time a `RopQueryRows` row exposes a message's `PidTagMid` column (see
83
+ * `MessageTarget.assignOrGetMid`), read back by a later `RopOpenMessage`. */
84
+ messageIds: Record<string, string>;
85
+ /** The reverse of `messageIds` (target -> MID) plus a monotonic counter - the message analog of
86
+ * `folderTargetIds`/`nextFolderId` above, for the identical O(1)-lookup reason. */
87
+ messageTargetIds: Record<string, number>;
88
+ nextMessageId: number;
89
+ /** This session's `RopGetPropertyIdsFromNames` mapping table (`[MS-OXCPRPT]` §2.2.12), keyed by a JSON
90
+ * string encoding of the `{guid, kind, lid|name}` `PropertyName` the numeric ID was assigned to - see
91
+ * `NamedPropertyRegistry.ts`'s own doc comment for why a JSON string (rather than a delimiter-joined one)
92
+ * is the safest key shape here. A real client resolves every named property (almost every Calendar-specific
93
+ * one - `PidLidAppointmentStartWhole`, `PidLidBusyStatus`, `PidLidAppointmentRecur`, ...) through this table
94
+ * once per session before ever setting/reading it via `RopSetProperties`/`RopGetPropertiesSpecific`. */
95
+ namedProperties: Record<string, number>;
96
+ /** The reverse of `namedProperties` (numeric ID -> `PropertyName` JSON key) plus a monotonic counter -
97
+ * `NamedPropertyRegistry.ts`'s own doc comment covers why both the O(n) forward scan this replaces and the
98
+ * `Math.max(...spread)` id-allocation it used are real problems, not just theoretical ones, at real
99
+ * mailbox/session scale. */
100
+ namedPropertyIds: Record<number, string>;
101
+ nextNamedPropertyId: number;
102
+ /** `mailboxUid`/`userUid` are always known at construction time (the only call site is
103
+ * `MapiSessionManager.create()`, which resolves both up front) - required here rather than optional with
104
+ * a same-value fallback, which would just be dead code no real caller ever takes the other branch of. */
105
+ constructor(other: Partial<SimpleEntity> & {
106
+ mailboxUid: string;
107
+ userUid: string;
108
+ });
109
+ }
110
+ /**
111
+ * Stores/loads `MapiSessionContext`s, keyed by the opaque session id that also becomes the `MapiContext`
112
+ * cookie value. Reuses `@rapidrest/service-core`'s existing generic `RedisCache<T>` (Redis-backed when a
113
+ * `cache` datastore is configured, transparent in-memory fallback otherwise) rather than either a bespoke
114
+ * Redis client (unlike `PingCommand`'s own throwaway pub/sub client - fine for ephemeral fan-out, not a good
115
+ * fit for stateful session data) or the framework's generic cookie/session middleware (built for a whole-app
116
+ * `req.session` object, not this protocol's specific handle-table shape). `RedisCache` needs no real
117
+ * persisted entity - only a class with a `.name` for its Redis key prefix - so a purely ephemeral,
118
+ * never-persisted class like `MapiSessionContext` is a perfectly valid `type` for it.
119
+ *
120
+ * @author Jean-Philippe Steinmetz
121
+ */
122
+ export declare class MapiSessionManager {
123
+ private _objectFactory?;
124
+ private cache?;
125
+ init(): Promise<void>;
126
+ create(mailboxUid: string, userUid: string): Promise<MapiSessionContext>;
127
+ load(sessionId: string): Promise<MapiSessionContext | undefined>;
128
+ save(context: MapiSessionContext): Promise<void>;
129
+ destroy(sessionId: string): Promise<void>;
130
+ }
@@ -0,0 +1,17 @@
1
+ import type { RopContext, RopHandler } from "./rop/RopHandler.js";
2
+ /**
3
+ * Walks a decoded `RopBuffer`'s `ropsList` blob, dispatching each contained ROP - identified by its own
4
+ * leading `RopId` byte - to the matching registered handler in turn, collecting every response into one
5
+ * output buffer. Deliberately ROP-agnostic: it knows nothing about any specific ROP's own fields, only how to
6
+ * find the *next* one, via the handler's own advanced `reader` position once `handle()` returns - see
7
+ * `RopBuffer.ts`'s doc comment for why a fully generic split isn't possible for this wire format.
8
+ *
9
+ * An unrecognized `RopId` stops processing immediately: the bytes belonging to that ROP (and everything after
10
+ * it) can't be skipped without knowing that ROP's own layout, so there is no safe way to resync and continue.
11
+ * This is an inherent constraint of the wire format, not a design choice this library is choosing not to
12
+ * handle - a real client only ever sends ROPs the server has already told it (via its supported-command
13
+ * surface) that it understands.
14
+ *
15
+ * @author Jean-Philippe Steinmetz
16
+ */
17
+ export declare function dispatchRops(ropsList: Buffer, handlers: Map<number, RopHandler>, context: RopContext): Promise<Buffer>;
@@ -0,0 +1,14 @@
1
+ import { BufferReader } from "./BufferCursor.js";
2
+ import { type RecurrenceRule } from "@rapidmx/restapi";
3
+ /**
4
+ * Encodes `rule` (plus the occurrence's own `startDate`/`endDate`, needed for phase/time-of-day derivation)
5
+ * into an `AppointmentRecurrencePattern` blob, ready to be written as the raw content of a `PtypBinary`
6
+ * `PidLidAppointmentRecur` property value.
7
+ */
8
+ export declare function encodeAppointmentRecurrence(rule: RecurrenceRule, startDate: Date, endDate: Date): Buffer;
9
+ /**
10
+ * Decodes an `AppointmentRecurrencePattern` blob (read from `reader`'s current position) back into a
11
+ * `RecurrenceRule`. Throws a clear error for any pattern this pragmatic subset can't represent (see this
12
+ * file's own doc comment) rather than silently dropping data.
13
+ */
14
+ export declare function decodeAppointmentRecurrence(reader: BufferReader): RecurrenceRule;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * A small positional cursor over a `Buffer`, shared by every MAPI codec in this package (`MapiGuid`,
3
+ * `PropertyValue`, `RopBuffer`, and every individual ROP handler's own request/response encoding). Unlike
4
+ * WBXML's self-describing tag stream, MAPI's wire structures are fixed/variable-length binary records with no
5
+ * generic "read until end tag" mechanism, so every codec needs the same little-endian primitive reads/writes
6
+ * with automatic offset advancement - reimplementing that per file would be pure repetition across the dozens
7
+ * of ROP handlers this phase's plan calls for, unlike EAS's WBXML codec, which only needed one such reader.
8
+ *
9
+ * Underlying `Buffer` methods already bounds-check and throw `RangeError` on overflow/truncation for every
10
+ * fixed-width primitive read - a malformed or truncated ROP buffer legitimately failing loudly is the correct
11
+ * behavior, not something to swallow here. `readBytes`/`readNullTerminatedUtf16LE`/`readNullTerminatedString8`
12
+ * are the three exceptions: `Buffer.subarray` silently clamps instead of throwing, and a null-terminated scan
13
+ * has no fixed width to bounds-check against `Buffer` methods at all, so those three enforce the same
14
+ * "malformed/truncated input throws" contract explicitly below instead of relying on it happening for free.
15
+ *
16
+ * @author Jean-Philippe Steinmetz
17
+ */
18
+ export declare class BufferReader {
19
+ private readonly buffer;
20
+ private offset;
21
+ constructor(buffer: Buffer, offset?: number);
22
+ get position(): number;
23
+ get remaining(): number;
24
+ hasMore(): boolean;
25
+ readUInt8(): number;
26
+ readInt16LE(): number;
27
+ readUInt16LE(): number;
28
+ readInt32LE(): number;
29
+ readUInt32LE(): number;
30
+ readFloatLE(): number;
31
+ readDoubleLE(): number;
32
+ readBigInt64LE(): bigint;
33
+ readBigUInt64LE(): bigint;
34
+ /** Unlike every fixed-width `readXxxLE` above, `Buffer.prototype.subarray` never throws - an out-of-range
35
+ * `length` (negative, or reaching past the buffer's end) silently clamps instead, which would otherwise let
36
+ * a malformed/truncated ROP buffer produce garbage-but-not-erroring reads (a negative `length` even rewinds
37
+ * `offset` backwards) instead of failing loudly the way this class's own doc comment promises. Explicitly
38
+ * bounds-checked here for that reason. */
39
+ readBytes(length: number): Buffer;
40
+ /** Reads a UTF-16LE string up to (and consuming) its terminating `0x0000` code unit - `PtypString`'s wire
41
+ * encoding. Scans on 2-byte boundaries, since a null code unit's low/high byte pair can't be mistaken for
42
+ * one half of a non-null UTF-16 code unit at an odd offset the way a naive single-byte scan could.
43
+ *
44
+ * Throws if no terminator is found before running out of buffer, rather than silently returning whatever
45
+ * content preceded the truncation and leaving `offset` past the buffer's actual end - a truncated/malformed
46
+ * string is exactly the kind of input this class's own doc comment says should fail loudly. This also
47
+ * closes a real DoS: `PropertyValue.ts`'s `readCountedArray` loops a client-controlled element count with
48
+ * no independent cap, trusting each element's own reader to terminate the loop early against a too-small
49
+ * buffer the way every fixed-width reader already does - without this, `PtypMultipleString`'s
50
+ * `readNullTerminatedUtf16LE` calls kept silently returning `""` past the buffer's end forever instead. */
51
+ readNullTerminatedUtf16LE(): string;
52
+ /** Reads a single-byte-terminated 8-bit string (`PtypString8`'s wire encoding). Multibyte string content
53
+ * itself is treated as UTF-8, a pragmatic choice documented in `PropertyValue.ts` rather than the
54
+ * "externally specified encoding" the spec leaves open-ended.
55
+ *
56
+ * Throws if no terminator is found before running out of buffer - see `readNullTerminatedUtf16LE`'s own
57
+ * doc comment for why (identical reasoning, including the `readCountedArray` DoS this closes). */
58
+ readNullTerminatedString8(): string;
59
+ }
60
+ /** The write-side counterpart to `BufferReader`, accumulating chunks and concatenating once via `toBuffer()` -
61
+ * the same "array of pieces, `Buffer.concat` once at the end" idiom `WbxmlEncoder` already uses for its own
62
+ * byte-array accumulator, adapted for multi-byte little-endian fields instead of single bytes. */
63
+ export declare class BufferWriter {
64
+ private readonly chunks;
65
+ writeUInt8(value: number): this;
66
+ writeInt16LE(value: number): this;
67
+ writeUInt16LE(value: number): this;
68
+ writeInt32LE(value: number): this;
69
+ writeUInt32LE(value: number): this;
70
+ writeFloatLE(value: number): this;
71
+ writeDoubleLE(value: number): this;
72
+ writeBigInt64LE(value: bigint): this;
73
+ writeBigUInt64LE(value: bigint): this;
74
+ writeBytes(value: Buffer): this;
75
+ writeNullTerminatedUtf16LE(value: string): this;
76
+ writeNullTerminatedString8(value: string): this;
77
+ toBuffer(): Buffer;
78
+ }
@@ -0,0 +1,11 @@
1
+ import { BufferReader } from "./BufferCursor.js";
2
+ /** Encodes `icalUid` into a `GlobalObjectId` BLOB in the `VCALID` form a real client synthesizes from a plain
3
+ * iCalendar UID (see this file's own doc comment for why that's the form that matters here), using `at` as the
4
+ * `CreationTime`. */
5
+ export declare function encodeGlobalObjectId(icalUid: string, at: Date): Buffer;
6
+ /** Decodes a `GlobalObjectId` BLOB (read from `reader`'s current position) back into the `icalUid` it was
7
+ * built from - unwrapping the `VCALID` form (see this file's own doc comment) when `Data` carries the
8
+ * `"vCal-Uid"` marker, falling back to treating `Data` as bare `OutlookID` bytes otherwise. Throws if
9
+ * `ByteArrayID` doesn't match the spec's own fixed constant - a real, spec-mandated identity check, not an
10
+ * invented restriction. */
11
+ export declare function decodeGlobalObjectId(reader: BufferReader): string;
@@ -0,0 +1,6 @@
1
+ import { BufferReader } from "./BufferCursor.js";
2
+ /** Encodes a standard hyphenated GUID/UUID string into its 16-byte MS wire form. */
3
+ export declare function encodeGuid(guid: string): Buffer;
4
+ /** Decodes a 16-byte MS wire-form GUID (read from `reader`'s current position) into a standard hyphenated
5
+ * GUID/UUID string. */
6
+ export declare function decodeGuid(reader: BufferReader): string;
@@ -0,0 +1,7 @@
1
+ import { BufferReader } from "./BufferCursor.js";
2
+ /** Encodes `timezone` (an IANA identifier, or `"UTC"`) into a 48-byte `TimeZoneStruct` BLOB, using `at` as the
3
+ * reference instant for resolving a non-UTC zone's current fixed offset. */
4
+ export declare function encodeTimeZoneStruct(timezone: string, at: Date): Buffer;
5
+ /** Decodes a `TimeZoneStruct` BLOB (read from `reader`'s current position) into an approximate IANA zone
6
+ * identifier - see this file's own doc comment for the lossy `Etc/GMT±N` fallback's exact rounding rules. */
7
+ export declare function decodeTimeZoneStruct(reader: BufferReader): string;
@@ -0,0 +1,77 @@
1
+ import { BufferReader, BufferWriter } from "./BufferCursor.js";
2
+ /**
3
+ * The property-value system every meaningful ROP depends on (`RopGetPropertiesSpecific`/`RopSetProperties`,
4
+ * table row data in `RopQueryRows`, ...), per `[MS-OXCDATA]` §2.11. Only the property types this phase's
5
+ * planned ROP handlers actually need are implemented (`readPropertyValue`/`writePropertyValue` throw a clear
6
+ * error for anything else) - add more as a real handler needs them, not speculatively.
7
+ *
8
+ * Numeric values and member names (`Ptyp*`) are exactly as published in `[MS-OXCDATA]`'s "Property Data
9
+ * Types" table - the full `Ptyp`-prefixed spec names are used rather than shorter aliases (`String`,
10
+ * `Boolean`, ...) both for fidelity to the spec and because those shorter names collide with this project's
11
+ * lint config's `id-denylist` rule (which exists to stop primitive-wrapper-like names shadowing globals).
12
+ */
13
+ export declare enum PropertyType {
14
+ PtypNull = 1,
15
+ PtypInteger16 = 2,
16
+ PtypInteger32 = 3,
17
+ PtypFloating32 = 4,
18
+ PtypFloating64 = 5,
19
+ PtypBoolean = 11,
20
+ PtypInteger64 = 20,
21
+ PtypString8 = 30,
22
+ PtypString = 31,
23
+ PtypTime = 64,
24
+ PtypGuid = 72,
25
+ PtypBinary = 258,
26
+ PtypMultipleInteger32 = 4099,
27
+ PtypMultipleString8 = 4126,
28
+ PtypMultipleString = 4127,
29
+ PtypMultipleBinary = 4354
30
+ }
31
+ export interface PropertyTag {
32
+ propertyId: number;
33
+ propertyType: PropertyType;
34
+ }
35
+ export interface TypedPropertyValue {
36
+ propertyType: PropertyType;
37
+ value: PropertyValueData;
38
+ }
39
+ export interface TaggedPropertyValue {
40
+ propertyId: number;
41
+ propertyType: PropertyType;
42
+ value: PropertyValueData;
43
+ }
44
+ export type PropertyValueData = number | bigint | boolean | string | Buffer | Date | string[] | number[] | Buffer[];
45
+ /**
46
+ * `PropertyTag` structure (`[MS-OXCDATA]` §2.9): a 4-byte value with **`PropertyType` in the low-order 16
47
+ * bits and `PropertyId` in the high-order 16 bits** - confirmed against the spec's own bit-range description
48
+ * and cross-checked against the well-known real property-tag constant `PR_SUBJECT = 0x0037001F` (`PidTagSubject`
49
+ * = property ID `0x0037`, `PtypString` = type `0x001F`), which only decodes correctly with `PropertyType` as
50
+ * the low half. On the wire (little-endian), that means `PropertyType` is read/written first.
51
+ */
52
+ export declare function readPropertyTag(reader: BufferReader): PropertyTag;
53
+ export declare function writePropertyTag(writer: BufferWriter, tag: PropertyTag): void;
54
+ /** `TypedPropertyValue` structure (`[MS-OXCDATA]` §2.11.4): `PropertyType` (2 bytes) followed by the value
55
+ * itself, encoded per `readPropertyValue`/`writePropertyValue` below. */
56
+ export declare function readTypedPropertyValue(reader: BufferReader): TypedPropertyValue;
57
+ export declare function writeTypedPropertyValue(writer: BufferWriter, typed: TypedPropertyValue): void;
58
+ /** `TaggedPropertyValue` structure (`[MS-OXCDATA]` §2.11.4, confirmed via `RopSetProperties`'s own request-buffer
59
+ * page): a full `PropertyTag` (4 bytes - `PropertyId` **and** `PropertyType`, unlike `TypedPropertyValue`'s bare
60
+ * `PropertyType`) followed by the value itself. Used where a property's identity can't be inferred from
61
+ * context (e.g. `RopSetProperties`, which sets an arbitrary, client-chosen set of properties in one call). */
62
+ export declare function readTaggedPropertyValue(reader: BufferReader): TaggedPropertyValue;
63
+ export declare function writeTaggedPropertyValue(writer: BufferWriter, tagged: TaggedPropertyValue): void;
64
+ /**
65
+ * Reads a bare `PropertyValue` (`[MS-OXCDATA]` §2.11.2) of the given, already-known `type`. `PtypBinary`'s
66
+ * length prefix is 16 bits and every `PtypMultiple*`'s value count is 32 bits wide, per the spec's explicit
67
+ * "in the context of ROP buffers" sizing rule - the only context every planned caller of this function
68
+ * operates in (the wider 32-bit `PtypBinary` count only applies to extended rules / a specific MAPI/HTTP
69
+ * structure this library doesn't implement).
70
+ */
71
+ export declare function readPropertyValue(reader: BufferReader, type: PropertyType): PropertyValueData;
72
+ export declare function writePropertyValue(writer: BufferWriter, type: PropertyType, value: PropertyValueData): void;
73
+ /** Converts a `PtypTime` value (100-ns intervals since 1601-01-01) into a JS `Date`. Sub-millisecond
74
+ * precision is truncated - `Date` has no finer resolution, a real, bounded, documented gap. */
75
+ export declare function filetimeToDate(filetime: bigint): Date;
76
+ /** Converts a JS `Date` into a `PtypTime` value (100-ns intervals since 1601-01-01). */
77
+ export declare function dateToFiletime(date: Date): bigint;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The outer `ROP input/output buffer` framing (`[MS-OXCROPS]` §2.2.1): `RopSize` (2 bytes, the size of
3
+ * itself plus `RopsList`) + `RopsList` (variable) + `ServerObjectHandleTable` (the remaining bytes, each a
4
+ * 32-bit Server object handle referenced by index from within the ROPs).
5
+ *
6
+ * Deliberately **ROP-agnostic**: `RopsList` is a concatenated sequence of individual ROP request/response
7
+ * buffers with no per-ROP length prefix (unlike WBXML's tag-based self-description) - each ROP's own byte
8
+ * layout is bespoke and keyed by its leading `RopId` byte, so splitting `RopsList` into individual ROPs
9
+ * requires ROP-specific decode logic this generic framing codec can't provide. A `RopDispatcher` (a later
10
+ * build step, once real `RopHandler`s exist) walks `ropsList` with a `BufferReader`, reading each ROP's
11
+ * `RopId` and delegating to the matching handler, which itself knows how many bytes its own ROP consumes -
12
+ * exactly the same "handler owns its own wire format" division of responsibility `EasCommandHandler` already
13
+ * uses for EAS commands.
14
+ *
15
+ * @author Jean-Philippe Steinmetz
16
+ */
17
+ export interface RopBuffer {
18
+ /** Raw bytes of the concatenated ROP request/response entries (`RopsList`). */
19
+ ropsList: Buffer;
20
+ /** The `ServerObjectHandleTable` array - one 32-bit handle per referenced Server object, in the order
21
+ * ROPs within `ropsList` reference them by index. */
22
+ handleTable: number[];
23
+ }
24
+ export declare function encodeRopBuffer(buf: RopBuffer): Buffer;
25
+ export declare function decodeRopBuffer(buffer: Buffer): RopBuffer;
@@ -0,0 +1,17 @@
1
+ import type { BufferReader, BufferWriter } from "./BufferCursor.js";
2
+ /**
3
+ * `TypedString` (`[MS-OXCDATA]` §2.11.7): a leading `StringType` byte followed by an optional string in the
4
+ * format that byte names - used by `RopOpenMessage`'s success response (`SubjectPrefix`/`NormalizedSubject`).
5
+ * `writeTypedString` never produces the "reduced Unicode" (`0x03`) form - that's purely a wire-size
6
+ * optimization real Exchange applies when every character happens to fit in one byte, not something a peer is
7
+ * ever required to send, so this codec always writes plain UTF-16LE (`0x04`) for non-empty strings. `undefined`
8
+ * writes as "no string" (`0x00`), matching a field the spec allows to be entirely absent (e.g. no subject
9
+ * prefix at all, as opposed to an empty one).
10
+ */
11
+ export declare function writeTypedString(writer: BufferWriter, value: string | undefined): void;
12
+ /** Reads a `TypedString`, supporting every `StringType` value a peer could legally send (`0x00`-`0x04`) even
13
+ * though `writeTypedString` only ever produces `0x00`/`0x01`/`0x04` itself - this side of the codec is only
14
+ * ever exercised in this library's own round-trip tests, since a real MAPI/HTTP client never sends a
15
+ * `TypedString` value to the server. Returns `undefined` for `StringType = 0x00` ("no string"), matching
16
+ * `writeTypedString`'s own `undefined` input. */
17
+ export declare function readTypedString(reader: BufferReader): string | undefined;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * MAPI-over-HTTP protocol compatibility (Phase 3 of this library's roadmap): a hand-built ROP (Remote
3
+ * Operation) codec and buffer-framing layer (`[MS-OXCROPS]`), JWT-secured EMSMDB transport (`BaseMapiEmsmdbRoute`,
4
+ * reusing the framework's existing `@Auth(["jwt"])` strategy - no MAPI-specific auth code of its own), and a
5
+ * minimal NSPI address-book endpoint (`BaseMapiNspiRoute`) for GAL "search as you type" lookups.
6
+ *
7
+ * This module exports only the backend-agnostic surface: the wire-format codecs, the abstract route base
8
+ * classes, `MapiSessionManager`, the ROP dispatcher, and every ROP/NSPI handler class (each takes a generic
9
+ * `RopContext`/repo argument, not a concrete entity class, so none of them are backend-specific). The concrete
10
+ * Mongo/SQL classes a deployment actually instantiates (`MapiEmsmdbRouteMongo`/`SQL`,
11
+ * `MapiNspiRouteMongo`/`SQL`) are exported from this package's `./mongo`/`./sql` subpaths instead, alongside
12
+ * every other entity/route/job this library defines - see `src/eas/index.ts`'s identical doc comment for the
13
+ * same convention.
14
+ *
15
+ * A deployment mounts both MAPI endpoints with a trivial one-line subclass each, the same pattern
16
+ * `src/eas/index.ts`'s doc comment describes for `BaseEasRoute`:
17
+ * ```ts
18
+ * import { MapiEmsmdbRouteMongo, MapiNspiRouteMongo } from "@rapidrest/mail/mongo";
19
+ * import { RouteDecorators } from "@rapidrest/service-core";
20
+ * const { Route } = RouteDecorators;
21
+ *
22
+ * @Route("/mapi/emsmdb")
23
+ * export class MyMapiEmsmdbRoute extends MapiEmsmdbRouteMongo {}
24
+ *
25
+ * @Route("/mapi/nspi")
26
+ * export class MyMapiNspiRoute extends MapiNspiRouteMongo {}
27
+ * ```
28
+ * Real Outlook desktop locates both URLs via `@rapidrest/mail/autodiscover`'s Outlook/EXCH response
29
+ * (`BaseAutodiscoverRoute`'s `mapiUrl` property) - see that module's own doc comment.
30
+ *
31
+ * **Documented gaps in this pragmatic subset** (see the architecture plan's "MAPI over HTTP (Phase 3)" section
32
+ * for the full reasoning behind each):
33
+ * - Incremental sync (`RopFastTransferSourceCopyTo`/`CopyProperties`/`GetBuffer`) always builds a full,
34
+ * non-differential dump, not real IDSET-based ICS (`[MS-OXCFXICS]`) - a real client still works correctly
35
+ * against this, just less efficiently than byte-perfect ICS.
36
+ * - Calendar/meetings: no counter-proposals, meeting-forwarding, delegate scheduling, or resource-booking
37
+ * auto-accept; no DST-aware timezones (fixed-offset approximation only); no recurrence exceptions/modified
38
+ * instances; no Hijri/`MonthEnd` recurrence patterns.
39
+ * - No `RopModifyRecipients` - attendees/recipients are always added via the same `PidTagDisplayTo`/`Cc`
40
+ * pragmatic path used for ordinary mail.
41
+ * - No public-folder support, no delegate/shared-mailbox access, no rules/permissions/search-folder ROPs, no
42
+ * client-certificate enrollment.
43
+ * - Calendar named properties (location/recurrence/timezone/etc.) are excluded from FastTransfer streams -
44
+ * only the columns in `FastTransferStream.ts`'s `DEFAULT_*_COLUMNS` tables are included.
45
+ * - NSPI is limited to `Bind`/`Unbind`/`GetMatches` only - no row/name lookups beyond a single content-
46
+ * restriction search, no directory replication.
47
+ */
48
+ export * from "./codec/BufferCursor.js";
49
+ export * from "./codec/MapiGuid.js";
50
+ export * from "./codec/TypedString.js";
51
+ export * from "./codec/PropertyValue.js";
52
+ export * from "./codec/RopBuffer.js";
53
+ export * from "./codec/AppointmentRecurrence.js";
54
+ export * from "./codec/MapiTimeZone.js";
55
+ export * from "./codec/GlobalObjectId.js";
56
+ export * from "./RopDispatcher.js";
57
+ export * from "./MapiSessionManager.js";
58
+ export * from "./BaseMapiEmsmdbRoute.js";
59
+ export * from "./BaseMapiNspiRoute.js";
60
+ export * from "./rop/RopHandler.js";
61
+ export * from "./rop/FolderTarget.js";
62
+ export * from "./rop/MessageTarget.js";
63
+ export * from "./rop/CalendarEventTarget.js";
64
+ export * from "./rop/MessageBodyStream.js";
65
+ export * from "./rop/PropertyResolvers.js";
66
+ export * from "./rop/NamedPropertyRegistry.js";
67
+ export * from "./rop/CalendarNamedProperties.js";
68
+ export * from "./rop/FastTransferStream.js";
69
+ export * from "./rop/MeetingMessageClassHandler.js";
70
+ export * from "./rop/RopLogonHandler.js";
71
+ export * from "./rop/RopReleaseHandler.js";
72
+ export * from "./rop/RopOpenFolderHandler.js";
73
+ export * from "./rop/RopGetHierarchyTableHandler.js";
74
+ export * from "./rop/RopSetColumnsHandler.js";
75
+ export * from "./rop/RopQueryRowsHandler.js";
76
+ export * from "./rop/RopGetContentsTableHandler.js";
77
+ export * from "./rop/RopOpenMessageHandler.js";
78
+ export * from "./rop/RopGetPropertiesSpecificHandler.js";
79
+ export * from "./rop/RopOpenStreamHandler.js";
80
+ export * from "./rop/RopReadStreamHandler.js";
81
+ export * from "./rop/RopCreateMessageHandler.js";
82
+ export * from "./rop/RopSetPropertiesHandler.js";
83
+ export * from "./rop/RopWriteStreamHandler.js";
84
+ export * from "./rop/RopSaveChangesMessageHandler.js";
85
+ export * from "./rop/RopSubmitMessageHandler.js";
86
+ export * from "./rop/RopGetPropertyIdsFromNamesHandler.js";
87
+ export * from "./rop/RopDeleteMessagesHandler.js";
88
+ export * from "./rop/RopDeleteFolderHandler.js";
89
+ export * from "./rop/RopFastTransferSourceCopyToHandler.js";
90
+ export * from "./rop/RopFastTransferSourceCopyPropertiesHandler.js";
91
+ export * from "./rop/RopFastTransferSourceGetBufferHandler.js";
92
+ export * from "./nspi/NspiCodec.js";
93
+ export * from "./nspi/NspiBindHandler.js";
94
+ export * from "./nspi/NspiGetMatchesHandler.js";
@@ -0,0 +1,19 @@
1
+ import { MailboxMongo } from "@rapidmx/restapi/mongo";
2
+ import { BaseMapiEmsmdbRoute } from "../BaseMapiEmsmdbRoute.js";
3
+ /**
4
+ * Mongo-backed concrete `BaseMapiEmsmdbRoute`, mounted at `/mapi/emsmdb`. Exported from this plugin's
5
+ * `./mongo` entry point, so the server host mounts it without a wrapper class of its
6
+ * own.
7
+ *
8
+ * @author Jean-Philippe Steinmetz
9
+ */
10
+ export declare class MapiEmsmdbRouteMongo extends BaseMapiEmsmdbRoute<MailboxMongo> {
11
+ protected mailboxClass: any;
12
+ protected folderClass: any;
13
+ protected messageClass: any;
14
+ protected calendarEventClass: any;
15
+ protected contactClass: any;
16
+ protected taskClass: any;
17
+ protected labelClass: any;
18
+ protected ropHandlerClasses: any[];
19
+ }