@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,98 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { FolderType } from "@rapidmx/restapi";
6
+ import { BufferWriter } from "../codec/BufferCursor.js";
7
+ import { PropertyType, writeTaggedPropertyValue } from "../codec/PropertyValue.js";
8
+ import { resolveFolderCalendarEvents } from "./CalendarEventTarget.js";
9
+ import { resolveFolderMessages } from "./MessageTarget.js";
10
+ import { resolvePropertyValues } from "./PropertyResolvers.js";
11
+ /**
12
+ * Builds the `FastTransfer` binary stream (`[MS-OXCFXICS]` §2.2.4) `RopFastTransferSourceCopyTo`/
13
+ * `CopyProperties` hand off to `RopFastTransferSourceGetBuffer` for paging out, per this pragmatic subset's own
14
+ * "ICS" scope (see the architecture plan's "Incremental sync" row): a **full, non-differential** dump of the
15
+ * target's current live state, built eagerly and stored whole on the output handle - not the real, much larger
16
+ * `contentsSync`/`hierarchySync` grammar (`IncrSyncChg`/`IncrSyncDel`/`IncrSyncStateBegin` markers), which
17
+ * requires a persisted per-device ICS state object (an `IDSET` baseline) this pragmatic subset doesn't
18
+ * implement. A real client that asks for an incremental sync here simply gets a full re-list every time and
19
+ * reconciles by diffing against its own local cache - spec-legitimate client behavior, just more state
20
+ * re-fetched per sync than a byte-perfect ICS server would send, exactly the tradeoff the plan accepts.
21
+ *
22
+ * Only the plain `PidTag*` properties this pragmatic subset already resolves via `PropertyResolvers.ts` are
23
+ * ever included - a Calendar item's own named properties (start/end/location/recurrence/...) are **not**
24
+ * included in the stream (only `PidTagSubject`), a deliberate scope cut: a client wanting those already has to
25
+ * `RopOpenMessage`+`RopGetPropertiesSpecific` the item directly (the same "just re-fetches more state" tradeoff
26
+ * applied one level deeper). No recipients/attachments/subfolders are emitted either - `messageChildren` is
27
+ * always empty and `Level` (whether to recurse into subfolders) is not honored, matching this pragmatic
28
+ * subset's existing "no `RopModifyRecipients`" and folder-hierarchy-is-flat-per-call precedents elsewhere.
29
+ *
30
+ * @author Jean-Philippe Steinmetz
31
+ */
32
+ /** FastTransfer markers (`[MS-OXCFXICS]` §2.2.4.1.4, confirmed this session) - only the four this pragmatic
33
+ * subset's flat, non-differential dump ever needs. */
34
+ const MARKER_START_TOP_FLD = 0x40090003;
35
+ const MARKER_END_FOLDER = 0x400b0003;
36
+ const MARKER_START_MESSAGE = 0x400c0003;
37
+ const MARKER_END_MESSAGE = 0x400d0003;
38
+ /** The well-known columns this pragmatic subset includes by default when the caller (`RopFastTransferSourceCopyTo`)
39
+ * didn't supply an explicit include list - the same small "what a real client needs" sets `PropertyResolvers.ts`
40
+ * already defines its own defaults around. */
41
+ const DEFAULT_FOLDER_COLUMNS = [
42
+ { propertyId: 0x3001, propertyType: PropertyType.PtypString }, // PidTagDisplayName
43
+ { propertyId: 0x3602, propertyType: PropertyType.PtypInteger32 }, // PidTagContentCount
44
+ { propertyId: 0x3603, propertyType: PropertyType.PtypInteger32 }, // PidTagContentUnreadCount
45
+ ];
46
+ const DEFAULT_MESSAGE_COLUMNS = [
47
+ { propertyId: 0x0037, propertyType: PropertyType.PtypString }, // PidTagSubject
48
+ { propertyId: 0x0e07, propertyType: PropertyType.PtypInteger32 }, // PidTagMessageFlags
49
+ { propertyId: 0x0e1b, propertyType: PropertyType.PtypBoolean }, // PidTagHasAttachments
50
+ { propertyId: 0x0e06, propertyType: PropertyType.PtypTime }, // PidTagMessageDeliveryTime
51
+ ];
52
+ /** A calendar item's named properties are deliberately excluded from the stream (see class doc comment) -
53
+ * `PidTagSubject` is the only column emitted for one. */
54
+ const DEFAULT_CALENDAR_COLUMNS = [{ propertyId: 0x0037, propertyType: PropertyType.PtypString }];
55
+ async function writePropList(writer, target, columns, context) {
56
+ const values = await resolvePropertyValues(target, columns, context);
57
+ columns.forEach((column, index) => writeTaggedPropertyValue(writer, { propertyId: column.propertyId, propertyType: column.propertyType, value: values[index] }));
58
+ }
59
+ function filterExcluded(columns, excludePropertyIds) {
60
+ return columns.filter((column) => !excludePropertyIds.has(column.propertyId));
61
+ }
62
+ /**
63
+ * Builds the complete FastTransfer stream for `handle` (a `"folder"` or `"message"` Server object). `columns`,
64
+ * when given (a `RopFastTransferSourceCopyProperties` explicit include list), replaces every default column set
65
+ * uniformly at both folder- and message-level; `excludePropertyIds` (a `RopFastTransferSourceCopyTo` exclude
66
+ * list) is only ever applied to this pragmatic subset's own default columns, per the same reasoning.
67
+ */
68
+ export async function buildFastTransferStream(handle, context, options = {}) {
69
+ const writer = new BufferWriter();
70
+ const exclude = options.excludePropertyIds ?? new Set();
71
+ if (handle.type === "folder") {
72
+ const folderColumns = options.columns ?? filterExcluded(DEFAULT_FOLDER_COLUMNS, exclude);
73
+ writer.writeUInt32LE(MARKER_START_TOP_FLD);
74
+ await writePropList(writer, handle.entityUid, folderColumns, context);
75
+ if (handle.entityUid.startsWith("folder:")) {
76
+ const folderUid = handle.entityUid.slice("folder:".length);
77
+ const folder = await context.folderRepo.findOne(folderUid, { ignoreACL: true });
78
+ const isCalendar = folder?.type === FolderType.CALENDAR;
79
+ const rows = isCalendar
80
+ ? await resolveFolderCalendarEvents(folderUid, context.calendarEventRepo)
81
+ : await resolveFolderMessages(folderUid, context.messageRepo);
82
+ const messageColumns = options.columns ?? filterExcluded(isCalendar ? DEFAULT_CALENDAR_COLUMNS : DEFAULT_MESSAGE_COLUMNS, exclude);
83
+ for (const row of rows) {
84
+ writer.writeUInt32LE(MARKER_START_MESSAGE);
85
+ await writePropList(writer, row, messageColumns, context);
86
+ writer.writeUInt32LE(MARKER_END_MESSAGE);
87
+ }
88
+ }
89
+ writer.writeUInt32LE(MARKER_END_FOLDER);
90
+ }
91
+ else {
92
+ const isCalendar = handle.entityUid.startsWith("calendarEvent:");
93
+ const columns = options.columns ?? filterExcluded(isCalendar ? DEFAULT_CALENDAR_COLUMNS : DEFAULT_MESSAGE_COLUMNS, exclude);
94
+ await writePropList(writer, handle.entityUid, columns, context);
95
+ }
96
+ return writer.toBuffer();
97
+ }
98
+ //# sourceMappingURL=FastTransferStream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FastTransferStream.js","sourceRoot":"","sources":["../../../src/rop/FastTransferStream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Shared helpers for resolving the `"virtual:<name>"`/`"folder:<uid>"` target strings `RopLogonHandler`
3
+ * assigns to FIDs (see its own doc comment) into real folder data - used by `RopOpenFolderHandler` and
4
+ * `RopGetHierarchyTableHandler`.
5
+ */
6
+ /** Display names for the virtual (no real backing `Folder`) special folders - see `RopLogonHandler`'s own
7
+ * doc comment for why these don't have real rows in this data model. */
8
+ const VIRTUAL_DISPLAY_NAMES = {
9
+ root: "Root",
10
+ deferredAction: "Deferred Action",
11
+ spoolerQueue: "Spooler Queue",
12
+ ipmSubtree: "Top of Information Store",
13
+ commonViews: "Common Views",
14
+ schedule: "Schedule",
15
+ search: "Finder",
16
+ views: "Views",
17
+ shortcuts: "Shortcuts",
18
+ };
19
+ /** Resolves a target string into the display data a folder-table row needs. A `"folder:<uid>"` target whose
20
+ * `Folder` has since been deleted (soft-deleted or otherwise vanished) degrades to empty-looking values rather
21
+ * than throwing - the row simply won't be interesting to a client, not a reason to fail the whole ROP. */
22
+ export async function resolveFolderInfo(mailboxUid, target, folderRepo, cache) {
23
+ if (target.startsWith("folder:")) {
24
+ const uid = target.slice("folder:".length);
25
+ const folder = await folderRepo.findOne(uid, { ignoreACL: true });
26
+ const hasChildren = (await resolveFolderChildren(mailboxUid, target, folderRepo, cache)).length > 0;
27
+ return {
28
+ displayName: folder?.name ?? "",
29
+ unreadCount: folder?.unreadCount ?? 0,
30
+ totalCount: folder?.totalCount ?? 0,
31
+ hasChildren,
32
+ };
33
+ }
34
+ const name = target.slice("virtual:".length);
35
+ const hasChildren = (await resolveFolderChildren(mailboxUid, target, folderRepo, cache)).length > 0;
36
+ return { displayName: VIRTUAL_DISPLAY_NAMES[name] ?? name, unreadCount: 0, totalCount: 0, hasChildren };
37
+ }
38
+ /**
39
+ * Resolves the direct children of `target`, as an array of the same target-string format, for
40
+ * `RopGetHierarchyTable`. Only `"virtual:root"`/`"virtual:ipmSubtree"` (this mailbox's top-level real
41
+ * folders - both collapse to the same "top of the visible tree" concept in this pragmatic subset, see
42
+ * `RopLogonHandler`'s own doc comment) and a real folder (its own real children) have any children at all;
43
+ * every other virtual folder (Deferred Action, Spooler Queue, ...) is permanently empty, since this data
44
+ * model has no concept of nesting anything under them.
45
+ *
46
+ * Filters an already-fetched full folder list in application code rather than querying by
47
+ * `parentFolderUid: undefined` directly - query-DSL semantics for "field is unset" aren't reliably consistent
48
+ * across backends (the exact kind of gap this project's own testing philosophy has caught before, e.g. `$or`
49
+ * being Mongo-only), so filtering a fetched array sidesteps the question entirely rather than risking it.
50
+ * The comparison itself uses `== null` (matching both `null` and `undefined`), not `=== undefined` - a
51
+ * top-level folder's unset `parentFolderUid` round-trips as genuine `undefined` from Mongo but as `null` from
52
+ * a SQL `nullable` column, a real, previously-confirmed cross-backend discrepancy in this codebase (caught by
53
+ * this exact test against a real SQLite-backed server, not assumed).
54
+ *
55
+ * **`cache`**: without it, this fetches every folder in the mailbox on every call - fine for the single call
56
+ * `RopGetHierarchyTableHandler` makes to build a table, but `resolveFolderInfo` also calls this once per row
57
+ * just to compute `hasChildren`, and a table page can hold many rows. A caller resolving a whole page (
58
+ * `RopQueryRowsHandler`/`RopGetPropertiesSpecificHandler`) passes one shared cache object through every call in
59
+ * that batch so the full list is fetched at most once per batch instead of once per row - see
60
+ * `FolderResolutionCache`'s own doc comment for why this isn't session state.
61
+ */
62
+ export async function resolveFolderChildren(mailboxUid, target, folderRepo, cache) {
63
+ const isTopLevel = target === "virtual:root" || target === "virtual:ipmSubtree";
64
+ let parentFolderUid;
65
+ if (isTopLevel) {
66
+ parentFolderUid = undefined;
67
+ }
68
+ else if (target.startsWith("folder:")) {
69
+ parentFolderUid = target.slice("folder:".length);
70
+ }
71
+ else {
72
+ return [];
73
+ }
74
+ let allFolders;
75
+ if (cache) {
76
+ cache.allFolders ?? (cache.allFolders = await folderRepo.find({ mailboxUid }, { ignoreACL: true }));
77
+ allFolders = cache.allFolders;
78
+ }
79
+ else {
80
+ allFolders = await folderRepo.find({ mailboxUid }, { ignoreACL: true });
81
+ }
82
+ return allFolders
83
+ .filter((f) => (isTopLevel ? f.parentFolderUid == null : f.parentFolderUid === parentFolderUid))
84
+ .map((f) => `folder:${f.uid}`);
85
+ }
86
+ /**
87
+ * Returns `target`'s existing FID if `RopLogon` or an earlier `RopGetHierarchyTable` row already assigned
88
+ * one, otherwise assigns and remembers the next free small integer FID. This is what lets a client
89
+ * `RopOpenFolder` a child folder it only ever learned about via a `RopQueryRows` row's `PidTagFolderId`
90
+ * column - without this, only the 13 `RopLogon`-time special folders could ever be opened.
91
+ *
92
+ * Backed by `session.folderTargetIds` (target -> FID) and `session.nextFolderId`, an O(1) reverse index/counter
93
+ * pair rather than a linear scan of `session.folderIds` plus a `Math.max(...spread)` over its keys - both real
94
+ * costs, not just theoretical ones: `folderTargetIds` only ever grows for a session's lifetime as a client
95
+ * browses more of a mailbox's folder tree, so the old scan-per-lookup approach was quadratic across a session
96
+ * that opens many folders, and `Math.max` spreading an unbounded array as call arguments risked a stack
97
+ * overflow past V8's argument-count limit on a mailbox with enough folders. `RopLogonHandler` keeps
98
+ * `folderTargetIds`/`nextFolderId` in sync with `folderIds` for the 13 special folders it assigns directly,
99
+ * rather than going through this function, since it assigns all 13 as one batch up front.
100
+ */
101
+ export function assignOrGetFid(session, target) {
102
+ const existing = session.folderTargetIds[target];
103
+ if (existing !== undefined) {
104
+ return existing;
105
+ }
106
+ const fid = session.nextFolderId++;
107
+ session.folderIds[String(fid)] = target;
108
+ session.folderTargetIds[target] = fid;
109
+ return fid;
110
+ }
111
+ //# sourceMappingURL=FolderTarget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FolderTarget.js","sourceRoot":"","sources":["../../../src/rop/FolderTarget.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { AttendeeResponseStatus } from "@rapidmx/restapi";
6
+ import { BufferReader } from "../codec/BufferCursor.js";
7
+ import { decodeGlobalObjectId } from "../codec/GlobalObjectId.js";
8
+ import { LID_GLOBAL_OBJECT_ID, PSETID_MEETING } from "./CalendarNamedProperties.js";
9
+ import { resolveNamedProperty } from "./NamedPropertyRegistry.js";
10
+ /** `PidTagMessageClass` suffix -> the caller's own new `AttendeeResponseStatus`, per `[MS-OXOCAL]`'s meeting
11
+ * response object naming convention. An unrecognized suffix (a message class this pragmatic subset doesn't
12
+ * know how to interpret as a response) is simply not handled - see `submitMeetingResponse`'s own doc comment. */
13
+ const RESPONSE_STATUS_BY_MESSAGE_CLASS = {
14
+ "IPM.Schedule.Meeting.Resp.Pos": AttendeeResponseStatus.ACCEPTED,
15
+ "IPM.Schedule.Meeting.Resp.Tent": AttendeeResponseStatus.TENTATIVE,
16
+ "IPM.Schedule.Meeting.Resp.Neg": AttendeeResponseStatus.DECLINED,
17
+ };
18
+ /** Scans `properties` (a draft handle's accumulated `RopSetProperties` values) for the one already-assigned
19
+ * named property matching `(guid, lid)`, returning its raw stored string value - the single-property analog of
20
+ * `RopSaveChangesMessageHandler.ts`'s own full-table decode loop, needed here only for `PidLidGlobalObjectId`. */
21
+ function findNamedPropertyValue(session, properties, guid, lid) {
22
+ for (const [key, value] of Object.entries(properties)) {
23
+ const propertyId = Number(key);
24
+ if (propertyId < 0x8000) {
25
+ continue;
26
+ }
27
+ const named = resolveNamedProperty(session, propertyId);
28
+ if (named && named.kind === "lid" && named.lid === lid && named.guid.toLowerCase() === guid) {
29
+ return value;
30
+ }
31
+ }
32
+ return undefined;
33
+ }
34
+ /**
35
+ * Handles a submitted `"IPM.Schedule.Meeting.Resp.{Pos,Neg,Tent}"` message - an attendee's own response to a
36
+ * meeting invite this server previously sent (`RopSubmitMessageHandler.submitAppointment`). Delegated to from
37
+ * `RopSubmitMessageHandler` for that message-class prefix, instead of the ordinary mail or Appointment paths.
38
+ *
39
+ * Correlates the response back to the original `CalendarEvent` via `PidLidGlobalObjectId` (echoed by a real
40
+ * client from the invite it's responding to - see `GlobalObjectId.ts`'s own doc comment for why this requires
41
+ * no separate correlation table) and updates the caller's own `Attendee.responseStatus` in place, reusing the
42
+ * exact same matching-by-mailbox-address logic `MeetingResponseCommand` (EAS's own `MeetingResponse` handler)
43
+ * already implements - this is a from-scratch reimplementation of that small algorithm rather than a
44
+ * cross-import, since EAS and MAPI are independent protocol verticals in this codebase with no dependency
45
+ * between them.
46
+ *
47
+ * Every failure mode here (no `PidLidGlobalObjectId` set, no matching `CalendarEvent`, no resolvable mailbox,
48
+ * caller isn't actually an attendee, unrecognized message-class suffix) is a silent no-op rather than an error
49
+ * response - the same "don't fail the whole ROP over a stale/unresolvable reference" principle this pragmatic
50
+ * subset applies throughout (e.g. `MessageTarget.resolveMessageInfo`'s own doc comment). A response that can't
51
+ * be correlated or applied is simply dropped, not a reason to reject the client's `RopSubmitMessage` call.
52
+ *
53
+ * **The one query in this package that isn't `mailboxUid`-scoped, and why that's unavoidable here**: unlike
54
+ * every other `ignoreACL: true` repo call in this codebase (all transitively scoped to the caller's own
55
+ * mailbox via a session FID/MID registry that itself only ever gets populated from mailbox-scoped queries),
56
+ * `calendarEventRepo.find({ icalUid })` below deliberately searches *every* mailbox on this server - the
57
+ * `CalendarEvent` being responded to belongs to the meeting's *organizer*, who is very often a different
58
+ * mailbox than the attendee submitting this response, and the caller's own session has no way to know which
59
+ * mailbox that is. Safety instead comes from two independent properties: `icalUid` is a server-generated
60
+ * `crypto.randomUUID()` (see `RopSaveChangesMessageHandler.saveAppointment`), for all practical purposes
61
+ * globally unique, so this can't be used to enumerate or collide with another organizer's events; and the
62
+ * actual authorization gate is the attendee-membership check a few lines down (`callerAddresses.has(...)`),
63
+ * derived entirely from *this* caller's own resolved mailbox record, not from anything the client supplied -
64
+ * a crafted `PidLidGlobalObjectId` can at best name a real `icalUid` it doesn't already know the value of
65
+ * (astronomically unlikely against a random UUID) and still can't mutate an event unless the caller's own
66
+ * mailbox is genuinely a listed attendee on it.
67
+ */
68
+ export async function submitMeetingResponse(messageClass, draftProperties, context) {
69
+ const responseStatus = RESPONSE_STATUS_BY_MESSAGE_CLASS[messageClass];
70
+ if (!responseStatus) {
71
+ return;
72
+ }
73
+ const globalObjectIdBase64 = findNamedPropertyValue(context.session, draftProperties, PSETID_MEETING, LID_GLOBAL_OBJECT_ID);
74
+ if (!globalObjectIdBase64) {
75
+ return;
76
+ }
77
+ const icalUid = decodeGlobalObjectId(new BufferReader(Buffer.from(globalObjectIdBase64, "base64")));
78
+ const events = await context.calendarEventRepo.find({ icalUid }, { ignoreACL: true, limit: 1 });
79
+ const event = events[0];
80
+ if (!event) {
81
+ return;
82
+ }
83
+ const mailbox = await context.mailboxRepo.findOne(context.mailboxUid, { ignoreACL: true });
84
+ if (!mailbox) {
85
+ return;
86
+ }
87
+ const callerAddresses = new Set([mailbox.primarySmtpAddress.toLowerCase(), ...mailbox.aliasAddresses.map((a) => a.toLowerCase())]);
88
+ const attendeeIndex = event.attendees.findIndex((attendee) => callerAddresses.has(attendee.address.toLowerCase()));
89
+ if (attendeeIndex === -1) {
90
+ return;
91
+ }
92
+ const attendees = event.attendees.map((attendee, i) => (i === attendeeIndex ? { ...attendee, responseStatus } : attendee));
93
+ await context.calendarEventRepo.update({ uid: event.uid, version: event.version, attendees }, event, { ignoreACL: true });
94
+ }
95
+ //# sourceMappingURL=MeetingMessageClassHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeetingMessageClassHandler.js","sourceRoot":"","sources":["../../../src/rop/MeetingMessageClassHandler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { simpleParser } from "mailparser";
6
+ import { BufferWriter } from "../codec/BufferCursor.js";
7
+ /** `PidTagBody` (`[MS-OXPROPS]`): property ID `0x1000`, `PtypString`. The only streamable property this
8
+ * pragmatic subset supports via `RopOpenStream`/`RopReadStream` - `PidTagHtml`/`PidTagRtfCompressed` are a
9
+ * documented gap (see `RopOpenStreamHandler`'s own doc comment). */
10
+ export const PID_TAG_BODY = 0x1000;
11
+ /**
12
+ * Resolves a `"message:<uid>"` target's plain-text body as the exact byte sequence `RopReadStream` must serve
13
+ * for a streamed `PtypString` property - confirmed via `[MS-OXCPRPT]`'s `RopOpenStream` page ("a string of
14
+ * Unicode characters in UTF-16LE format encoding with terminating null character"), the identical encoding
15
+ * `writePropertyValue()` already uses for an inline `PtypString` value, so this reuses
16
+ * `BufferWriter.writeNullTerminatedUtf16LE` rather than reimplementing it.
17
+ *
18
+ * Always derived from the message's raw MIME source (`bodyBlobKey`, via `mailparser` - the same fallback path
19
+ * `ItemOperationsCommand.fetchMessage()` already uses for EAS's own plain-text body delivery) rather than
20
+ * `sanitizedHtmlBlobKey` - `PidTagBody` is specifically the plain-text body per `[MS-OXPROPS]`, unlike EAS's
21
+ * `Body` element, which can carry either format tagged by its own `Type` field.
22
+ *
23
+ * Recomputed on every call rather than cached anywhere (including on the `"stream"` handle itself) -
24
+ * `MapiSessionContext` is serialized through `RedisCache`'s JSON round trip for multi-instance deployments, so
25
+ * a raw `Buffer` stored there wouldn't survive it (the same class of gap this codebase already documented for
26
+ * `Date` fields, see `MapiSessionContext`'s own doc comment). A documented, pragmatic trade-off: a large body
27
+ * read across several `RopReadStream` calls re-parses the MIME source each time rather than once - correct,
28
+ * not byte-perfect-efficient.
29
+ */
30
+ export async function resolveMessageBodyBytes(target, messageRepo, blobStore) {
31
+ const uid = target.slice("message:".length);
32
+ const message = await messageRepo.findOne(uid, { ignoreACL: true });
33
+ if (!message) {
34
+ return Buffer.alloc(0);
35
+ }
36
+ const raw = await blobStore.get(message.bodyBlobKey);
37
+ const parsed = await simpleParser(raw);
38
+ const writer = new BufferWriter();
39
+ writer.writeNullTerminatedUtf16LE(parsed.text ?? "");
40
+ return writer.toBuffer();
41
+ }
42
+ //# sourceMappingURL=MessageBodyStream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageBodyStream.js","sourceRoot":"","sources":["../../../src/rop/MessageBodyStream.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD;;oEAEoE;AACpE,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AAEnC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,MAAc,EAAE,WAA2B,EAAE,SAAoB;IAC3G,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAwB,MAAM,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzF,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;IAClC,MAAM,CAAC,0BAA0B,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,44 @@
1
+ /** Degrades to empty-looking values for a `"message:<uid>"` target whose real `Message` has since vanished
2
+ * (soft-deleted or otherwise), the same "don't fail the whole ROP over one stale row" principle
3
+ * `FolderTarget.resolveFolderInfo` already applies. */
4
+ export async function resolveMessageInfo(target, messageRepo) {
5
+ const uid = target.slice("message:".length);
6
+ const message = await messageRepo.findOne(uid, { ignoreACL: true });
7
+ return {
8
+ subject: message?.subject ?? "",
9
+ read: message?.flags?.read ?? false,
10
+ hasAttachments: message?.hasAttachments ?? false,
11
+ receivedDate: message?.receivedDate ?? new Date(0),
12
+ labelUids: message?.labelUids ?? [],
13
+ };
14
+ }
15
+ /** Resolves the messages directly in `folderUid`, as `"message:<uid>"` target strings, for
16
+ * `RopGetContentsTable`. */
17
+ export async function resolveFolderMessages(folderUid, messageRepo) {
18
+ const messages = await messageRepo.find({ folderUid }, { ignoreACL: true });
19
+ return messages.map((m) => `message:${m.uid}`);
20
+ }
21
+ /**
22
+ * Returns `target`'s existing MID if an earlier `RopQueryRows` row already assigned one, otherwise assigns and
23
+ * remembers the next free small integer MID. The exact `FolderTarget.assignOrGetFid` pattern, adapted for
24
+ * messages: this is what lets a client `RopOpenMessage` a message it only ever learned about via a
25
+ * `RopQueryRows` row's `PidTagMid` column.
26
+ *
27
+ * Backed by `session.messageTargetIds` (target -> MID) and `session.nextMessageId`, an O(1) reverse index/
28
+ * counter pair rather than a linear scan of `session.messageIds` plus a `Math.max(...spread)` over its keys -
29
+ * see `FolderTarget.assignOrGetFid`'s own doc comment for why both of those were real costs (not just
30
+ * theoretical ones) at real mailbox/session scale: `messageIds` only ever grows for a session's lifetime as a
31
+ * client pages through a mailbox, so a linear-scan lookup repeated once per row is quadratic over a session
32
+ * that pages through many messages.
33
+ */
34
+ export function assignOrGetMid(session, target) {
35
+ const existing = session.messageTargetIds[target];
36
+ if (existing !== undefined) {
37
+ return existing;
38
+ }
39
+ const mid = session.nextMessageId++;
40
+ session.messageIds[String(mid)] = target;
41
+ session.messageTargetIds[target] = mid;
42
+ return mid;
43
+ }
44
+ //# sourceMappingURL=MessageTarget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageTarget.js","sourceRoot":"","sources":["../../../src/rop/MessageTarget.ts"],"names":[],"mappings":"AAwBA;;uDAEuD;AACvD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc,EAAE,WAA2B;IAChF,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAwB,MAAM,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzF,OAAO;QACH,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE;QAC/B,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,IAAI,KAAK;QACnC,cAAc,EAAE,OAAO,EAAE,cAAc,IAAI,KAAK;QAChD,YAAY,EAAE,OAAO,EAAE,YAAY,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC;QAClD,SAAS,EAAE,OAAO,EAAE,SAAS,IAAI,EAAE;KACtC,CAAC;AACN,CAAC;AAED;4BAC4B;AAC5B,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,SAAiB,EAAE,WAA2B;IACtF,MAAM,QAAQ,GAAc,MAAM,WAAW,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvF,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAC,OAA2B,EAAE,MAAc;IACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,QAAQ,CAAC;IACpB,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IACpC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC;IACzC,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC;IACvC,OAAO,GAAG,CAAC;AACf,CAAC"}
@@ -0,0 +1,66 @@
1
+ /** The first numeric property ID this pragmatic subset ever assigns to a named property - real Exchange also
2
+ * reserves the `0x0000`-`0x7FFF` range for well-known/`PidTag*` properties, only ever assigning named
3
+ * properties IDs at `0x8000` and above (`[MS-OXCPRPT]`'s own `RopGetPropertyIdsFromNames` processing rules). */
4
+ const FIRST_NAMED_PROPERTY_ID = 0x8000;
5
+ /** A `PropertyName`'s registry key - a JSON string rather than a delimiter-joined one (e.g. `"<guid>:<lid>"`)
6
+ * because a string-`Kind` named property's own `name` is an arbitrary client-supplied string that could
7
+ * legitimately contain any delimiter this codec might otherwise pick (some real named properties do use
8
+ * URN-shaped names). `JSON.stringify` on a small, fixed-shape object has no such ambiguity. */
9
+ function keyFor(propertyName) {
10
+ return propertyName.kind === "lid"
11
+ ? JSON.stringify({ guid: propertyName.guid.toLowerCase(), kind: "lid", lid: propertyName.lid })
12
+ : JSON.stringify({ guid: propertyName.guid.toLowerCase(), kind: "name", name: propertyName.name });
13
+ }
14
+ /** The highest numeric property ID a `PropertyTag`'s 16-bit `PropertyId` field can carry at all - not a
15
+ * pragmatic-subset choice, an absolute wire-format ceiling (`writeUInt16LE` cannot encode anything past this). */
16
+ const LAST_NAMED_PROPERTY_ID = 0xffff;
17
+ /**
18
+ * Returns `propertyName`'s existing numeric property ID if an earlier `RopGetPropertyIdsFromNames` call in
19
+ * this session already assigned one, otherwise assigns and remembers the next free ID starting at
20
+ * `FIRST_NAMED_PROPERTY_ID`. This pragmatic subset always behaves as if the request's own `Flags` field
21
+ * requested "assign a new ID if unmapped" (`0x02`) - real Exchange's alternative (`0x00`, "only return
22
+ * already-mapped IDs") exists to let a client probe without committing a mailbox-wide registration; since this
23
+ * registry is already only ever session-scoped (not a real persisted per-mailbox mapping table), there is no
24
+ * meaningful difference between "probe" and "assign" here.
25
+ *
26
+ * Backed by `session.namedProperties`/`namedPropertyIds` (a forward and reverse map, kept in sync) and
27
+ * `session.nextNamedPropertyId`, an O(1) lookup/assignment pair rather than a linear scan plus a
28
+ * `Math.max(...spread)` over `Object.values(session.namedProperties)` - both real costs at scale: the spread
29
+ * form risks a `RangeError: Maximum call stack size exceeded` once a session has registered enough distinct
30
+ * names to exceed V8's function-argument-count limit, and the registry only ever grows for a session's
31
+ * lifetime, so a linear scan repeated once per property per row is quadratic over a session that resolves many
32
+ * named properties.
33
+ *
34
+ * Once `nextNamedPropertyId` would exceed `LAST_NAMED_PROPERTY_ID` (32,768 distinct names already registered
35
+ * this session - the entire `0x8000`-`0xFFFF` numeric ID space this pragmatic subset has to hand out), a *new*
36
+ * name can no longer be assigned a real ID; this returns `0x0000` for it instead of throwing, the exact value
37
+ * `[MS-OXCPRPT]`'s own `RopGetPropertyIdsFromNames` processing rules already use for "this `PropertyName`
38
+ * could not be resolved" (the same value this pragmatic subset already produces for a `Kind = 0xFF` entry) -
39
+ * not a new failure mode, just the same one applied to a different unmappable case. A name already registered
40
+ * before the registry filled up keeps returning its real, previously-assigned ID.
41
+ */
42
+ export function assignOrGetNamedPropertyId(session, propertyName) {
43
+ const key = keyFor(propertyName);
44
+ const existing = session.namedProperties[key];
45
+ if (existing !== undefined) {
46
+ return existing;
47
+ }
48
+ if (session.nextNamedPropertyId > LAST_NAMED_PROPERTY_ID) {
49
+ return 0x0000;
50
+ }
51
+ const id = session.nextNamedPropertyId++;
52
+ session.namedProperties[key] = id;
53
+ session.namedPropertyIds[id] = key;
54
+ return id;
55
+ }
56
+ /** The reverse lookup `RopSetProperties`/`RopGetPropertiesSpecific`/`RopQueryRows` use to recognize an incoming
57
+ * property ID `>= 0x8000` as one of this session's own mapped named properties - an O(1) lookup against
58
+ * `session.namedPropertyIds` (kept in sync by `assignOrGetNamedPropertyId`) rather than a linear scan of
59
+ * `session.namedProperties` with a `JSON.parse` per candidate - see that function's own doc comment for why
60
+ * this matters at scale, particularly since this is called once per named-property column per row from
61
+ * `RopQueryRows`. */
62
+ export function resolveNamedProperty(session, propertyId) {
63
+ const key = session.namedPropertyIds[propertyId];
64
+ return key !== undefined ? JSON.parse(key) : undefined;
65
+ }
66
+ //# sourceMappingURL=NamedPropertyRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NamedPropertyRegistry.js","sourceRoot":"","sources":["../../../src/rop/NamedPropertyRegistry.ts"],"names":[],"mappings":"AAmBA;;gHAEgH;AAChH,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAEvC;;;+FAG+F;AAC/F,SAAS,MAAM,CAAC,YAA0B;IACtC,OAAO,YAAY,CAAC,IAAI,KAAK,KAAK;QAC9B,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,CAAC,GAAG,EAAE,CAAC;QAC/F,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC;AAC3G,CAAC;AAED;kHACkH;AAClH,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAA2B,EAAE,YAA0B;IAC9F,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC9C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,QAAQ,CAAC;IACpB,CAAC;IACD,IAAI,OAAO,CAAC,mBAAmB,GAAG,sBAAsB,EAAE,CAAC;QACvD,OAAO,MAAM,CAAC;IAClB,CAAC;IACD,MAAM,EAAE,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IACzC,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IAClC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;IACnC,OAAO,EAAE,CAAC;AACd,CAAC;AAED;;;;;qBAKqB;AACrB,MAAM,UAAU,oBAAoB,CAAC,OAA2B,EAAE,UAAkB;IAChF,MAAM,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACjD,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7E,CAAC"}