@rapidmx/mapi-plugin 1.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/LICENSE +382 -0
  2. package/README.md +60 -0
  3. package/dist/lib/BaseMapiEmsmdbRoute.js +489 -0
  4. package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -0
  5. package/dist/lib/BaseMapiNspiRoute.js +114 -0
  6. package/dist/lib/BaseMapiNspiRoute.js.map +1 -0
  7. package/dist/lib/MapiSessionManager.js +475 -0
  8. package/dist/lib/MapiSessionManager.js.map +1 -0
  9. package/dist/lib/RegexPatternUtils.js +30 -0
  10. package/dist/lib/RegexPatternUtils.js.map +1 -0
  11. package/dist/lib/RopDispatcher.js +145 -0
  12. package/dist/lib/RopDispatcher.js.map +1 -0
  13. package/dist/lib/codec/AppointmentRecurrence.js +315 -0
  14. package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
  15. package/dist/lib/codec/BufferCursor.js +242 -0
  16. package/dist/lib/codec/BufferCursor.js.map +1 -0
  17. package/dist/lib/codec/GlobalObjectId.js +98 -0
  18. package/dist/lib/codec/GlobalObjectId.js.map +1 -0
  19. package/dist/lib/codec/MapiGuid.js +41 -0
  20. package/dist/lib/codec/MapiGuid.js.map +1 -0
  21. package/dist/lib/codec/MapiTimeZone.js +126 -0
  22. package/dist/lib/codec/MapiTimeZone.js.map +1 -0
  23. package/dist/lib/codec/PropertyValue.js +240 -0
  24. package/dist/lib/codec/PropertyValue.js.map +1 -0
  25. package/dist/lib/codec/RopBuffer.js +38 -0
  26. package/dist/lib/codec/RopBuffer.js.map +1 -0
  27. package/dist/lib/codec/TypedString.js +54 -0
  28. package/dist/lib/codec/TypedString.js.map +1 -0
  29. package/dist/lib/index.js +99 -0
  30. package/dist/lib/index.js.map +1 -0
  31. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +85 -0
  32. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
  33. package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
  34. package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
  35. package/dist/lib/mongo/index.js +7 -0
  36. package/dist/lib/mongo/index.js.map +1 -0
  37. package/dist/lib/mongo.js +6 -0
  38. package/dist/lib/mongo.js.map +1 -0
  39. package/dist/lib/nspi/NspiBindHandler.js +54 -0
  40. package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
  41. package/dist/lib/nspi/NspiCodec.js +106 -0
  42. package/dist/lib/nspi/NspiCodec.js.map +1 -0
  43. package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
  44. package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
  45. package/dist/lib/rop/AddressList.js +114 -0
  46. package/dist/lib/rop/AddressList.js.map +1 -0
  47. package/dist/lib/rop/CalendarEventTarget.js +30 -0
  48. package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
  49. package/dist/lib/rop/CalendarNamedProperties.js +57 -0
  50. package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
  51. package/dist/lib/rop/ContactTarget.js +19 -0
  52. package/dist/lib/rop/ContactTarget.js.map +1 -0
  53. package/dist/lib/rop/ContentsTable.js +60 -0
  54. package/dist/lib/rop/ContentsTable.js.map +1 -0
  55. package/dist/lib/rop/ExecuteBudget.js +82 -0
  56. package/dist/lib/rop/ExecuteBudget.js.map +1 -0
  57. package/dist/lib/rop/FastTransferStream.js +195 -0
  58. package/dist/lib/rop/FastTransferStream.js.map +1 -0
  59. package/dist/lib/rop/FolderTarget.js +118 -0
  60. package/dist/lib/rop/FolderTarget.js.map +1 -0
  61. package/dist/lib/rop/HandleDataCache.js +412 -0
  62. package/dist/lib/rop/HandleDataCache.js.map +1 -0
  63. package/dist/lib/rop/MeetingMessageClassHandler.js +241 -0
  64. package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
  65. package/dist/lib/rop/MessageBodyStream.js +68 -0
  66. package/dist/lib/rop/MessageBodyStream.js.map +1 -0
  67. package/dist/lib/rop/MessageTarget.js +57 -0
  68. package/dist/lib/rop/MessageTarget.js.map +1 -0
  69. package/dist/lib/rop/NamedPropertyRegistry.js +69 -0
  70. package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
  71. package/dist/lib/rop/PropertyResolvers.js +353 -0
  72. package/dist/lib/rop/PropertyResolvers.js.map +1 -0
  73. package/dist/lib/rop/RepoPaging.js +54 -0
  74. package/dist/lib/rop/RepoPaging.js.map +1 -0
  75. package/dist/lib/rop/RestapiRules.js +52 -0
  76. package/dist/lib/rop/RestapiRules.js.map +1 -0
  77. package/dist/lib/rop/RopCreateMessageHandler.js +55 -0
  78. package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
  79. package/dist/lib/rop/RopDeleteFolderHandler.js +174 -0
  80. package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
  81. package/dist/lib/rop/RopDeleteMessagesHandler.js +110 -0
  82. package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
  83. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
  84. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
  85. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +60 -0
  86. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
  87. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +76 -0
  88. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
  89. package/dist/lib/rop/RopGetContentsTableHandler.js +55 -0
  90. package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
  91. package/dist/lib/rop/RopGetHierarchyTableHandler.js +42 -0
  92. package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
  93. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +56 -0
  94. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
  95. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
  96. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
  97. package/dist/lib/rop/RopHandler.js +6 -0
  98. package/dist/lib/rop/RopHandler.js.map +1 -0
  99. package/dist/lib/rop/RopLogonHandler.js +135 -0
  100. package/dist/lib/rop/RopLogonHandler.js.map +1 -0
  101. package/dist/lib/rop/RopOpenFolderHandler.js +48 -0
  102. package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
  103. package/dist/lib/rop/RopOpenMessageHandler.js +83 -0
  104. package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
  105. package/dist/lib/rop/RopOpenStreamHandler.js +84 -0
  106. package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
  107. package/dist/lib/rop/RopQueryRowsHandler.js +120 -0
  108. package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
  109. package/dist/lib/rop/RopReadStreamHandler.js +73 -0
  110. package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
  111. package/dist/lib/rop/RopReleaseHandler.js +23 -0
  112. package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
  113. package/dist/lib/rop/RopSaveChangesMessageHandler.js +269 -0
  114. package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
  115. package/dist/lib/rop/RopSetColumnsHandler.js +50 -0
  116. package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
  117. package/dist/lib/rop/RopSetPropertiesHandler.js +173 -0
  118. package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
  119. package/dist/lib/rop/RopSubmitMessageHandler.js +342 -0
  120. package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
  121. package/dist/lib/rop/RopWriteStreamHandler.js +72 -0
  122. package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
  123. package/dist/lib/rop/TaskNamedProperties.js +29 -0
  124. package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
  125. package/dist/lib/rop/TaskTarget.js +15 -0
  126. package/dist/lib/rop/TaskTarget.js.map +1 -0
  127. package/dist/lib/rop/TransportSend.js +16 -0
  128. package/dist/lib/rop/TransportSend.js.map +1 -0
  129. package/dist/lib/sql/MapiEmsmdbRouteSQL.js +85 -0
  130. package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
  131. package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
  132. package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
  133. package/dist/lib/sql/index.js +7 -0
  134. package/dist/lib/sql/index.js.map +1 -0
  135. package/dist/lib/sql.js +6 -0
  136. package/dist/lib/sql.js.map +1 -0
  137. package/dist/types/BaseMapiEmsmdbRoute.d.ts +139 -0
  138. package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
  139. package/dist/types/MapiSessionManager.d.ts +285 -0
  140. package/dist/types/RegexPatternUtils.d.ts +14 -0
  141. package/dist/types/RopDispatcher.d.ts +53 -0
  142. package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
  143. package/dist/types/codec/BufferCursor.d.ts +92 -0
  144. package/dist/types/codec/GlobalObjectId.d.ts +15 -0
  145. package/dist/types/codec/MapiGuid.d.ts +6 -0
  146. package/dist/types/codec/MapiTimeZone.d.ts +7 -0
  147. package/dist/types/codec/PropertyValue.d.ts +87 -0
  148. package/dist/types/codec/RopBuffer.d.ts +32 -0
  149. package/dist/types/codec/TypedString.d.ts +17 -0
  150. package/dist/types/index.d.ts +94 -0
  151. package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +20 -0
  152. package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
  153. package/dist/types/mongo/index.d.ts +2 -0
  154. package/dist/types/mongo.d.ts +1 -0
  155. package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
  156. package/dist/types/nspi/NspiCodec.d.ts +59 -0
  157. package/dist/types/nspi/NspiGetMatchesHandler.d.ts +26 -0
  158. package/dist/types/rop/AddressList.d.ts +48 -0
  159. package/dist/types/rop/CalendarEventTarget.d.ts +33 -0
  160. package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
  161. package/dist/types/rop/ContactTarget.d.ts +29 -0
  162. package/dist/types/rop/ContentsTable.d.ts +16 -0
  163. package/dist/types/rop/ExecuteBudget.d.ts +48 -0
  164. package/dist/types/rop/FastTransferStream.d.ts +57 -0
  165. package/dist/types/rop/FolderTarget.d.ts +64 -0
  166. package/dist/types/rop/HandleDataCache.d.ts +145 -0
  167. package/dist/types/rop/MeetingMessageClassHandler.d.ts +30 -0
  168. package/dist/types/rop/MessageBodyStream.d.ts +35 -0
  169. package/dist/types/rop/MessageTarget.d.ts +45 -0
  170. package/dist/types/rop/NamedPropertyRegistry.d.ts +50 -0
  171. package/dist/types/rop/PropertyResolvers.d.ts +86 -0
  172. package/dist/types/rop/RepoPaging.d.ts +30 -0
  173. package/dist/types/rop/RestapiRules.d.ts +32 -0
  174. package/dist/types/rop/RopCreateMessageHandler.d.ts +24 -0
  175. package/dist/types/rop/RopDeleteFolderHandler.d.ts +53 -0
  176. package/dist/types/rop/RopDeleteMessagesHandler.d.ts +43 -0
  177. package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +25 -0
  178. package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +25 -0
  179. package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +22 -0
  180. package/dist/types/rop/RopGetContentsTableHandler.d.ts +25 -0
  181. package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +18 -0
  182. package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +26 -0
  183. package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
  184. package/dist/types/rop/RopHandler.d.ts +106 -0
  185. package/dist/types/rop/RopLogonHandler.d.ts +31 -0
  186. package/dist/types/rop/RopOpenFolderHandler.d.ts +20 -0
  187. package/dist/types/rop/RopOpenMessageHandler.d.ts +28 -0
  188. package/dist/types/rop/RopOpenStreamHandler.d.ts +28 -0
  189. package/dist/types/rop/RopQueryRowsHandler.d.ts +43 -0
  190. package/dist/types/rop/RopReadStreamHandler.d.ts +20 -0
  191. package/dist/types/rop/RopReleaseHandler.d.ts +15 -0
  192. package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +59 -0
  193. package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
  194. package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
  195. package/dist/types/rop/RopSubmitMessageHandler.d.ts +80 -0
  196. package/dist/types/rop/RopWriteStreamHandler.d.ts +31 -0
  197. package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
  198. package/dist/types/rop/TaskTarget.d.ts +24 -0
  199. package/dist/types/rop/TransportSend.d.ts +10 -0
  200. package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +20 -0
  201. package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
  202. package/dist/types/sql/index.d.ts +2 -0
  203. package/dist/types/sql.d.ts +1 -0
  204. package/package.json +104 -0
@@ -0,0 +1,173 @@
1
+ import { PropertyType, readTaggedPropertyValue } from "../codec/PropertyValue.js";
2
+ import { LID_APPOINTMENT_END_WHOLE, LID_APPOINTMENT_RECUR, LID_APPOINTMENT_START_WHOLE, LID_BUSY_STATUS, LID_GLOBAL_OBJECT_ID, LID_LOCATION, LID_REMINDER_DELTA, LID_TIME_ZONE_STRUCT, PSETID_APPOINTMENT, PSETID_COMMON, PSETID_MEETING, } from "./CalendarNamedProperties.js";
3
+ import { resolveNamedProperty } from "./NamedPropertyRegistry.js";
4
+ const ROP_ID_SET_PROPERTIES = 0x0a;
5
+ /** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a message (or
6
+ * doesn't exist)" - the same constant `RopGetPropertiesSpecificHandler` uses for its own analogous check.
7
+ * `RopSetProperties` is also spec-valid on Folder/Attachment/Logon objects, none of which this pragmatic
8
+ * subset's compose/send path ever needs to set properties on. */
9
+ const ERROR_INVALID_OBJECT = 0x80070005;
10
+ /** `MAPI_E_INVALID_PARAMETER`: `PropertyValues` couldn't be decoded, or didn't match `PropertyValueSize`. */
11
+ const ERROR_INVALID_PARAMETER = 0x80070057;
12
+ // The small, well-known set of plain (non-named) properties this pragmatic subset's compose/send path tracks -
13
+ // exactly what RopSubmitMessageHandler/RopSaveChangesMessageHandler need to build a MIME message or persist a
14
+ // CalendarEvent (Subject, an inline small body, the PidTagDisplayTo/Cc/Bcc "cached recipient display string"
15
+ // properties real Outlook also always sets alongside RopModifyRecipients - see RopSubmitMessageHandler's own
16
+ // doc comment for why this pragmatic subset reads addresses from these instead of implementing
17
+ // RopModifyRecipients - and PidTagMessageClass, which RopSaveChangesMessageHandler/RopSubmitMessageHandler
18
+ // branch on to route Calendar items down a different path than ordinary mail). Every other plain property a
19
+ // client sets is accepted (this ROP always reports success, no PropertyProblems) but simply not tracked -
20
+ // harmless for this pragmatic subset's narrow compose/send scope. A *named* property (ID `>= 0x8000`, resolved
21
+ // via `resolveNamedProperty`) is tracked separately below, against the Calendar LID table `PropertyResolvers.ts`
22
+ // also reads from - see `CalendarNamedProperties.ts`.
23
+ const PID_TAG_SUBJECT = 0x0037;
24
+ const PID_TAG_MESSAGE_CLASS = 0x001a;
25
+ const PID_TAG_DISPLAY_BCC = 0x0e02;
26
+ const PID_TAG_DISPLAY_CC = 0x0e03;
27
+ const PID_TAG_DISPLAY_TO = 0x0e04;
28
+ const PID_TAG_BODY = 0x1000;
29
+ /** `PidTagReadReceiptRequested` (`PT_BOOLEAN`) - a client checking "Request a read receipt" before Submit sets
30
+ * this; `RopSubmitMessageHandler` reads it back to attach a real `Disposition-Notification-To` header, mirroring
31
+ * `BaseMessageRoute.send()`'s own explicit-`Message.requestReceipt` handling (see `util/ReceiptUtils.ts`). */
32
+ export const PID_TAG_READ_RECEIPT_REQUESTED = 0x0029;
33
+ /** `PidTagDeferredSendTime` (`PT_SYSTIME`) - real Outlook's "Do not deliver before" compose option;
34
+ * `RopSubmitMessageHandler` reads it back to defer relay exactly like `BaseMessageRoute.send()`'s own
35
+ * `Message.scheduledSendTime` branch does (moving the message to Outbox for `ScheduledSendJob` to relay later)
36
+ * instead of sending immediately. */
37
+ export const PID_TAG_DEFERRED_SEND_TIME = 0x3fef;
38
+ const TRACKED_PROPERTY_IDS = new Set([
39
+ PID_TAG_SUBJECT,
40
+ PID_TAG_MESSAGE_CLASS,
41
+ PID_TAG_DISPLAY_BCC,
42
+ PID_TAG_DISPLAY_CC,
43
+ PID_TAG_DISPLAY_TO,
44
+ PID_TAG_BODY,
45
+ PID_TAG_READ_RECEIPT_REQUESTED,
46
+ PID_TAG_DEFERRED_SEND_TIME,
47
+ ]);
48
+ /** The Calendar named-property LIDs this handler tracks when set under the correct property-set GUID (per
49
+ * `CalendarNamedProperties.ts`) - the write-side mirror of `PropertyResolvers.calendarEventValueFor`'s read-side
50
+ * switch. `PidLidRecurring`/`PidLidReminderSet` are deliberately not tracked here: both are purely-derived
51
+ * boolean flags on the read side (`recurrenceRule !== undefined` / `reminderMinutesBeforeStart != null`), so a
52
+ * client setting them carries no additional information `RopSaveChangesMessageHandler` needs to persist. */
53
+ const TRACKED_APPOINTMENT_LIDS = new Set([
54
+ LID_LOCATION,
55
+ LID_APPOINTMENT_START_WHOLE,
56
+ LID_APPOINTMENT_END_WHOLE,
57
+ LID_BUSY_STATUS,
58
+ LID_APPOINTMENT_RECUR,
59
+ LID_TIME_ZONE_STRUCT,
60
+ ]);
61
+ const TRACKED_COMMON_LIDS = new Set([LID_REMINDER_DELTA]);
62
+ /** `PidLidGlobalObjectId` - the meeting-response side needs this tracked so `RopSubmitMessageHandler` can
63
+ * correlate an incoming `"IPM.Schedule.Meeting.Resp.*"` response back to the `CalendarEvent` it answers, via
64
+ * `MeetingMessageClassHandler.ts`. */
65
+ const TRACKED_MEETING_LIDS = new Set([LID_GLOBAL_OBJECT_ID]);
66
+ /**
67
+ * `RopSetProperties` (`[MS-OXCPRPT]`/`[MS-OXCROPS]`): sets an explicit, client-chosen list of properties
68
+ * (`TaggedPropertyValue`s, each carrying its own `PropertyTag`) on an already-open object. This pragmatic
69
+ * subset supports only `"message"` handles (a `RopCreateMessage` draft, or an already-open `RopOpenMessage`
70
+ * handle) and only tracks the small property set `TRACKED_PROPERTY_IDS`/`TRACKED_APPOINTMENT_LIDS`/
71
+ * `TRACKED_COMMON_LIDS` name - see those constants' own comments for why. A named property (ID `>= 0x8000`) is
72
+ * resolved back to its `(PropertySet GUID, LID)` identity via `resolveNamedProperty` before it can be tracked,
73
+ * since MAPI has no fixed numeric ID for any Calendar property the way `PidTagSubject` has one.
74
+ *
75
+ * Every value is coerced to a plain string via `stringifyValue` before being stored in `handle.draftProperties`
76
+ * (itself a `Record<string, string>` for `MapiSessionContext`'s own JSON-safety reasons, see
77
+ * `MapiSessionManager.ts`) - a lossless, type-aware encoding (ISO-8601 for `PtypTime`, base64 for `PtypBinary`,
78
+ * plain `String()` otherwise) so `RopSaveChangesMessageHandler` can decode a Calendar item's properties back
79
+ * into a real `CalendarEvent`'s typed fields, not just display strings.
80
+ *
81
+ * Always reports success with zero `PropertyProblems`, even for a property this pragmatic subset doesn't
82
+ * track - the same "never fail over an unsupported property" pragmatic stance `RopQueryRows`/
83
+ * `RopGetPropertiesSpecific` already take (falling back to a default value there; simply not tracking the
84
+ * property here, since there is no row to fill in for a *set* operation).
85
+ *
86
+ * @author Jean-Philippe Steinmetz
87
+ */
88
+ export class RopSetPropertiesHandler {
89
+ constructor() {
90
+ this.ropId = ROP_ID_SET_PROPERTIES;
91
+ }
92
+ handle(reader, writer, context) {
93
+ reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
94
+ const inputHandleIndex = reader.readUInt8();
95
+ const propertyValueSize = reader.readUInt16LE();
96
+ // PropertyValueSize covers the PropertyValueCount field itself plus PropertyValues, counted from right
97
+ // here (immediately after the PropertyValueSize field, before PropertyValueCount is read).
98
+ const propertyValuesEnd = reader.position + propertyValueSize;
99
+ let values = [];
100
+ try {
101
+ const propertyValueCount = reader.readUInt16LE();
102
+ for (let i = 0; i < propertyValueCount; i++) {
103
+ values.push(readTaggedPropertyValue(reader));
104
+ }
105
+ }
106
+ catch {
107
+ values = undefined; // e.g. an unsupported property type, whose size can't be known
108
+ }
109
+ // PropertyValueSize frames this ROP, so a list that couldn't be decoded, or decoded to a different length, is
110
+ // skipped by it and only this ROP fails; the ROPs after it are still where PropertyValueSize says. When
111
+ // PropertyValueSize itself points past the request, seek throws and the whole request is rejected.
112
+ const decodedEnd = reader.position;
113
+ reader.seek(propertyValuesEnd);
114
+ if (!values || decodedEnd !== propertyValuesEnd) {
115
+ writer.writeUInt8(ROP_ID_SET_PROPERTIES);
116
+ writer.writeUInt8(inputHandleIndex);
117
+ writer.writeUInt32LE(ERROR_INVALID_PARAMETER);
118
+ return;
119
+ }
120
+ const handle = context.session.handles[inputHandleIndex];
121
+ // A draft already submitted is finished: changing it can't change what was sent, and must not set up a resend.
122
+ if (!handle || handle.type !== "message" || handle.submitted) {
123
+ writer.writeUInt8(ROP_ID_SET_PROPERTIES);
124
+ writer.writeUInt8(inputHandleIndex);
125
+ writer.writeUInt32LE(ERROR_INVALID_OBJECT);
126
+ return;
127
+ }
128
+ handle.draftProperties ?? (handle.draftProperties = {});
129
+ for (const tagged of values) {
130
+ if (TRACKED_PROPERTY_IDS.has(tagged.propertyId)) {
131
+ handle.draftProperties[String(tagged.propertyId)] = stringifyValue(tagged);
132
+ }
133
+ else if (tagged.propertyId >= 0x8000 && this.isTrackedNamedProperty(context, tagged.propertyId)) {
134
+ handle.draftProperties[String(tagged.propertyId)] = stringifyValue(tagged);
135
+ }
136
+ }
137
+ writer.writeUInt8(ROP_ID_SET_PROPERTIES);
138
+ writer.writeUInt8(inputHandleIndex);
139
+ writer.writeUInt32LE(0); // ReturnValue - success
140
+ writer.writeUInt16LE(0); // PropertyProblemCount - never reported, see class doc comment
141
+ }
142
+ isTrackedNamedProperty(context, propertyId) {
143
+ const named = resolveNamedProperty(context.session, propertyId);
144
+ if (!named || named.kind !== "lid" || named.lid === undefined) {
145
+ return false;
146
+ }
147
+ const guid = named.guid.toLowerCase();
148
+ if (guid === PSETID_APPOINTMENT) {
149
+ return TRACKED_APPOINTMENT_LIDS.has(named.lid);
150
+ }
151
+ if (guid === PSETID_COMMON) {
152
+ return TRACKED_COMMON_LIDS.has(named.lid);
153
+ }
154
+ if (guid === PSETID_MEETING) {
155
+ return TRACKED_MEETING_LIDS.has(named.lid);
156
+ }
157
+ return false;
158
+ }
159
+ }
160
+ function stringifyValue(tagged) {
161
+ switch (tagged.propertyType) {
162
+ case PropertyType.PtypString:
163
+ case PropertyType.PtypString8:
164
+ return tagged.value;
165
+ case PropertyType.PtypTime:
166
+ return tagged.value.toISOString();
167
+ case PropertyType.PtypBinary:
168
+ return tagged.value.toString("base64");
169
+ default:
170
+ return String(tagged.value);
171
+ }
172
+ }
173
+ //# sourceMappingURL=RopSetPropertiesHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RopSetPropertiesHandler.js","sourceRoot":"","sources":["../../../src/rop/RopSetPropertiesHandler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,342 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import * as crypto from "crypto";
6
+ import MailComposer from "nodemailer/lib/mail-composer/index.js";
7
+ import { ApiErrors } from "@rapidrest/service-core";
8
+ import { findOrCreateWellKnownFolder, FolderType, MessageImportance, RecipientType, scanAndRelay, } from "@rapidmx/restapi";
9
+ import { MAX_RECIPIENTS_PER_MESSAGE, parseRecipientList, resolveRecipientList } from "./AddressList.js";
10
+ import { handleDataOwners, writeStreamKey } from "./HandleDataCache.js";
11
+ import { submitMeetingResponse } from "./MeetingMessageClassHandler.js";
12
+ import { handleDataStoreOf } from "./RopHandler.js";
13
+ import { readWriteStream } from "./RopWriteStreamHandler.js";
14
+ import { asEntity, isInvitePending } from "./RestapiRules.js";
15
+ const ROP_ID_SUBMIT_MESSAGE = 0x32;
16
+ /** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for both "the referenced handle isn't a draft
17
+ * message" and "the draft has no resolvable recipients/sender" - the same constant reused throughout this
18
+ * pragmatic subset as a generic "can't do this" signal, not a claim of exact per-condition `[MS-OXCRPC]`
19
+ * return-value-table parity. */
20
+ const ERROR_INVALID_OBJECT = 0x80070005;
21
+ /** `MAPI_E_INVALID_PARAMETER`: the draft has no recipients, a recipient that isn't a usable address, or no sender. */
22
+ const ERROR_INVALID_PARAMETER = 0x80070057;
23
+ /** `MAPI_E_NOT_FOUND`: a recipient given only by display name matches no single contact of the caller's. */
24
+ const ERROR_NOT_FOUND = 0x8004010f;
25
+ /** `MAPI_E_TOO_BIG`: more than `MAX_RECIPIENTS_PER_MESSAGE` recipients. */
26
+ const ERROR_TOO_BIG = 0x80040305;
27
+ /** `MAPI_E_CALL_FAILED`: the body written through `RopWriteStream` can no longer be reassembled (its chunks expired). */
28
+ const ERROR_CALL_FAILED = 0x80004005;
29
+ // The same well-known property IDs RopSetPropertiesHandler tracks - duplicated here (rather than imported)
30
+ // since importing them would only save a handful of literals; see that file for the real documentation of what
31
+ // each means.
32
+ const PID_TAG_SUBJECT = 0x0037;
33
+ const PID_TAG_MESSAGE_CLASS = 0x001a;
34
+ const PID_TAG_DISPLAY_BCC = 0x0e02;
35
+ const PID_TAG_DISPLAY_CC = 0x0e03;
36
+ const PID_TAG_DISPLAY_TO = 0x0e04;
37
+ const PID_TAG_BODY = 0x1000;
38
+ const PID_TAG_READ_RECEIPT_REQUESTED = 0x0029;
39
+ const PID_TAG_DEFERRED_SEND_TIME = 0x3fef;
40
+ /** `PidTagMessageClass` values starting with this prefix (`IPM.Appointment`, `IPM.Appointment.*`) route through
41
+ * `submitAppointment()` instead of the ordinary mail compose/send path below - see that method's own doc
42
+ * comment. */
43
+ const MESSAGE_CLASS_APPOINTMENT_PREFIX = "IPM.Appointment";
44
+ /** `PidTagMessageClass` values starting with this prefix (`IPM.Schedule.Meeting.Resp.{Pos,Neg,Tent}`) route
45
+ * through `MeetingMessageClassHandler.submitMeetingResponse()` instead of every other path here - see that
46
+ * function's own doc comment. */
47
+ const MESSAGE_CLASS_MEETING_RESPONSE_PREFIX = "IPM.Schedule.Meeting.Resp.";
48
+ // Re-exported for existing importers; the implementation lives in AddressList.ts.
49
+ export { isPlainEmailAddress, parseAddressList } from "./AddressList.js";
50
+ function writeResult(writer, inputHandleIndex, returnValue) {
51
+ writer.writeUInt8(ROP_ID_SUBMIT_MESSAGE);
52
+ writer.writeUInt8(inputHandleIndex);
53
+ writer.writeUInt32LE(returnValue);
54
+ }
55
+ /**
56
+ * `RopSubmitMessage` (`[MS-OXOMSG]`/`[MS-OXCROPS]`): sends a composed message - the actual trigger point for
57
+ * this pragmatic subset's compose/send pipeline, since `RopSaveChangesMessage` itself does no real persistence
58
+ * (see its own doc comment). Builds a raw MIME buffer from whatever `RopSetProperties`/`RopWriteStream` calls
59
+ * accumulated on the draft handle (`Subject`, the `PidTagBody` stream or an inline `PidTagBody` property, and
60
+ * addressing - see below), via `nodemailer`'s `MailComposer` (already a dependency, used elsewhere in this
61
+ * library only to *send*, not build, MIME - this is its first use purely as a RFC 5322 serializer), then calls
62
+ * the exact same `scanAndRelay()` scan-then-relay pipeline `BaseMessageRoute.send()`/EAS's `ComposeMailCommand`
63
+ * already share, and persists a Sent Items copy (mirroring `ComposeMailCommand`'s own always-committed Sent
64
+ * Items behavior for MAPI submission specifically, since - unlike EAS's optional `SaveInSentItems` - a MAPI
65
+ * `RopSubmitMessage` call has no "don't keep a copy" option to honor).
66
+ *
67
+ * **Known, deliberate limitation - no `RopModifyRecipients` support**: real recipient rows (`[MS-OXCMSG]`
68
+ * §2.2.3.2, each with its own `PidTagEmailAddress`/`PidTagRecipientType`) are a substantial structure this
69
+ * pragmatic subset's ROP coverage doesn't include. Instead, addressing is read from `PidTagDisplayTo`/
70
+ * `DisplayCc`/`DisplayBcc` - the same semicolon-separated "cached recipient display string" properties real
71
+ * Outlook *also* always sets via `RopSetProperties` alongside `RopModifyRecipients` (as a display-only
72
+ * convenience cache). Entries are separated by `;` and parsed with nodemailer's `addressparser`, so both bare
73
+ * addresses and resolved `Name <address>` forms work. A client that resolved a name against an address book may
74
+ * write the display name alone; that is looked up among the caller's own contacts (see `resolveRecipientList`), and
75
+ * a name matching no single contact fails the submit with `MAPI_E_NOT_FOUND`. It is still **not** a substitute for
76
+ * real recipient rows (there is no recipient table to fall back on), so the whole send is refused rather than sent to
77
+ * fewer people than the user addressed. A malformed recipient fails with `MAPI_E_INVALID_PARAMETER`.
78
+ *
79
+ * `SubmitFlags` (`PreprocessOnly`, ...) is decoded to advance past it correctly but not honored - this
80
+ * pragmatic subset has no transport-agent preprocessing distinction to vary by flag.
81
+ *
82
+ * **`PidTagDeferredSendTime`** ("Do not deliver before"), if set to a future time via `RopSetProperties`,
83
+ * parks the message in Outbox with `Message.scheduledSendTime` set instead of relaying immediately - mirroring
84
+ * `BaseMessageRoute.send()`'s own identical branch - for `ScheduledSendJob` to relay later via this same
85
+ * `scanAndRelay()` pipeline. **`PidTagReadReceiptRequested`**, if `true`, attaches a real
86
+ * `Disposition-Notification-To` header (see `util/ReceiptUtils.ts`) and is recorded on the persisted message's
87
+ * own `requestReceipt` field - honored only when the client explicitly sets it, not this mailbox's
88
+ * `alwaysRequestReceiptInternal`/`External` defaults (those apply to the REST/webmail compose path only).
89
+ *
90
+ * **Once per draft.** A mail or meeting-response draft is marked `submitted` as soon as a submit gets past the handle
91
+ * check, whatever the outcome, and a submitted draft can't be submitted again (`MAPI_E_INVALID_OBJECT`) or changed by
92
+ * `RopSetProperties`/`RopWriteStream`; its write-stream chunks are deleted once read. Each `Execute` runs at most
93
+ * `MAX_SUBMITS_PER_EXECUTE` submits, and a message may have at most `MAX_RECIPIENTS_PER_MESSAGE` recipients
94
+ * (`MAPI_E_TOO_BIG`), counted before any display name is looked up.
95
+ *
96
+ * **Calendar branches**: a draft whose `PidTagMessageClass` starts with `"IPM.Appointment"` is routed to
97
+ * `submitAppointment()` instead of the mail path below - see that method's own doc comment. One starting with
98
+ * `"IPM.Schedule.Meeting.Resp."` (an attendee's own accept/decline/tentative response to a meeting this server
99
+ * previously invited them to) is routed to `MeetingMessageClassHandler.submitMeetingResponse()` instead - see
100
+ * that function's own doc comment.
101
+ *
102
+ * @author Jean-Philippe Steinmetz
103
+ */
104
+ export class RopSubmitMessageHandler {
105
+ constructor() {
106
+ this.ropId = ROP_ID_SUBMIT_MESSAGE;
107
+ }
108
+ async handle(reader, writer, context) {
109
+ reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
110
+ const inputHandleIndex = reader.readUInt8();
111
+ reader.readUInt8(); // SubmitFlags - see class doc comment
112
+ const handle = context.session.handles[inputHandleIndex];
113
+ if (!handle || handle.type !== "message" || handle.submitted) {
114
+ writer.writeUInt8(ROP_ID_SUBMIT_MESSAGE);
115
+ writer.writeUInt8(inputHandleIndex);
116
+ writer.writeUInt32LE(ERROR_INVALID_OBJECT);
117
+ return;
118
+ }
119
+ context.budget?.chargeSubmit();
120
+ const properties = handle.draftProperties ?? {};
121
+ const messageClass = properties[String(PID_TAG_MESSAGE_CLASS)] ?? "IPM.Note";
122
+ if (messageClass.startsWith(MESSAGE_CLASS_APPOINTMENT_PREFIX)) {
123
+ await this.submitAppointment(handle, context, writer, inputHandleIndex);
124
+ return;
125
+ }
126
+ // From here on the draft is used up by this attempt, whatever its outcome, so it can't be sent again.
127
+ handle.submitted = true;
128
+ if (messageClass.startsWith(MESSAGE_CLASS_MEETING_RESPONSE_PREFIX)) {
129
+ writeResult(writer, inputHandleIndex, await submitMeetingResponse(messageClass, properties, context));
130
+ return;
131
+ }
132
+ const recipientCount = [PID_TAG_DISPLAY_TO, PID_TAG_DISPLAY_CC, PID_TAG_DISPLAY_BCC].reduce((count, propertyId) => count + parseRecipientList(properties[String(propertyId)]).length, 0);
133
+ if (recipientCount > MAX_RECIPIENTS_PER_MESSAGE) {
134
+ writeResult(writer, inputHandleIndex, ERROR_TOO_BIG);
135
+ return;
136
+ }
137
+ const resolutions = [
138
+ await resolveRecipientList(properties[String(PID_TAG_DISPLAY_TO)], context),
139
+ await resolveRecipientList(properties[String(PID_TAG_DISPLAY_CC)], context),
140
+ await resolveRecipientList(properties[String(PID_TAG_DISPLAY_BCC)], context),
141
+ ];
142
+ const [to, cc, bcc] = resolutions.map((resolution) => resolution.recipients);
143
+ const mailbox = await context.mailboxRepo.findOne(context.mailboxUid, { ignoreACL: true });
144
+ const envelopeFrom = mailbox?.primarySmtpAddress;
145
+ // Refuse the whole send over one malformed or unresolvable recipient rather than silently dropping it.
146
+ if (!envelopeFrom || to.length + cc.length + bcc.length === 0 || resolutions.some((resolution) => resolution.invalid.length > 0)) {
147
+ writeResult(writer, inputHandleIndex, ERROR_INVALID_PARAMETER);
148
+ return;
149
+ }
150
+ if (resolutions.some((resolution) => resolution.unresolved.length > 0)) {
151
+ writeResult(writer, inputHandleIndex, ERROR_NOT_FOUND);
152
+ return;
153
+ }
154
+ const subject = properties[String(PID_TAG_SUBJECT)] ?? "";
155
+ const bodyText = await resolveDraftBody(context, inputHandleIndex, properties);
156
+ if (bodyText === undefined) {
157
+ writeResult(writer, inputHandleIndex, ERROR_CALL_FAILED);
158
+ return;
159
+ }
160
+ const requestReceipt = properties[String(PID_TAG_READ_RECEIPT_REQUESTED)] === "true";
161
+ // "Do not deliver before" - a real Outlook compose option, tracked as a plain ISO-8601 string by
162
+ // RopSetPropertiesHandler.stringifyValue()'s PtypTime encoding.
163
+ const deferredSendTimeRaw = properties[String(PID_TAG_DEFERRED_SEND_TIME)];
164
+ const deferredSendTime = deferredSendTimeRaw ? new Date(deferredSendTimeRaw) : undefined;
165
+ const isDeferred = deferredSendTime !== undefined && deferredSendTime.getTime() > Date.now();
166
+ const raw = await new MailComposer({
167
+ from: envelopeFrom,
168
+ to: to.map(mailAddress),
169
+ cc: cc.map(mailAddress),
170
+ bcc: bcc.map(mailAddress),
171
+ subject,
172
+ text: bodyText,
173
+ // A real Disposition-Notification-To request, mirroring BaseMessageRoute.send()'s own explicit
174
+ // Message.requestReceipt handling (see util/ReceiptUtils.ts) - honored here only when the client
175
+ // itself asked for one via PidTagReadReceiptRequested, not this mailbox's own
176
+ // alwaysRequestReceiptInternal/External defaults (those apply to the REST/webmail compose path,
177
+ // which already goes through BaseMessageRoute.send() directly).
178
+ ...(requestReceipt ? { headers: [{ key: "Disposition-Notification-To", value: envelopeFrom }] } : {}),
179
+ })
180
+ .compile()
181
+ .build();
182
+ const envelopeTo = [...to, ...cc, ...bcc].map((recipient) => recipient.address);
183
+ const bodyBlobKey = `bodies/${crypto.randomUUID()}`;
184
+ const recipients = [
185
+ ...to.map((recipient) => storedRecipient(recipient, RecipientType.TO)),
186
+ ...cc.map((recipient) => storedRecipient(recipient, RecipientType.CC)),
187
+ ...bcc.map((recipient) => storedRecipient(recipient, RecipientType.BCC)),
188
+ ];
189
+ if (isDeferred) {
190
+ // Mirrors BaseMessageRoute.send()'s own scheduledSendTime branch: park the message in Outbox,
191
+ // unscanned/unrelayed, for the same ScheduledSendJob to pick up and relay (via this exact
192
+ // scanAndRelay() pipeline) once due - see that job's own doc comment.
193
+ await context.blobStore.put(bodyBlobKey, raw, { contentType: "message/rfc822" });
194
+ const outbox = await findOrCreateWellKnownFolder(context.folderRepo, context.folderClass, context.mailboxUid, FolderType.OUTBOX);
195
+ await context.messageRepo.create(new context.messageClass({
196
+ folderUid: outbox.uid,
197
+ mailboxUid: context.mailboxUid,
198
+ messageId: `${crypto.randomUUID()}@mapi`,
199
+ subject,
200
+ from: { address: envelopeFrom, type: RecipientType.TO },
201
+ recipients,
202
+ sentDate: new Date(),
203
+ receivedDate: new Date(),
204
+ bodyBlobKey,
205
+ bodyPreview: bodyText.slice(0, 200),
206
+ flags: { read: true, flagged: false, answered: false, forwarded: false },
207
+ importance: MessageImportance.NORMAL,
208
+ references: [],
209
+ hasAttachments: false,
210
+ scheduledSendTime: deferredSendTime,
211
+ requestReceipt,
212
+ }), { ignoreACL: true });
213
+ // Adding a message to a folder always bumps its sync key too - see `refreshFolderCounts()`'s own doc comment.
214
+ await context.notifyFolderCounts?.([outbox.uid], { bumpSyncKey: true });
215
+ writer.writeUInt8(ROP_ID_SUBMIT_MESSAGE);
216
+ writer.writeUInt8(inputHandleIndex);
217
+ writer.writeUInt32LE(0); // ReturnValue - success
218
+ return;
219
+ }
220
+ const relayed = await scanAndRelay(raw, envelopeFrom, envelopeTo, context.scanPipeline, context.mailTransport, context.blobStore);
221
+ // The Sent Items copy stores exactly what was relayed: scanAndRelay may have added a Message-ID header, and
222
+ // that header's value (plus the thread it derived) is what recipients' copies carry and what recall targets.
223
+ await context.blobStore.put(bodyBlobKey, relayed.raw, { contentType: "message/rfc822" });
224
+ const sentFolder = await findOrCreateWellKnownFolder(context.folderRepo, context.folderClass, context.mailboxUid, FolderType.SENT_ITEMS);
225
+ await context.messageRepo.create(new context.messageClass({
226
+ folderUid: sentFolder.uid,
227
+ mailboxUid: context.mailboxUid,
228
+ messageId: relayed.messageId,
229
+ conversationId: relayed.conversationId,
230
+ encrypted: relayed.encrypted,
231
+ subject,
232
+ from: { address: envelopeFrom, type: RecipientType.TO },
233
+ recipients,
234
+ sentDate: new Date(),
235
+ receivedDate: new Date(),
236
+ bodyBlobKey,
237
+ sanitizedHtmlBlobKey: relayed.sanitizedHtmlBlobKey,
238
+ bodyPreview: bodyText.slice(0, 200),
239
+ flags: { read: true, flagged: false, answered: false, forwarded: false },
240
+ importance: MessageImportance.NORMAL,
241
+ references: [],
242
+ hasAttachments: false,
243
+ requestReceipt,
244
+ }), { ignoreACL: true });
245
+ // Adding a message to a folder always bumps its sync key too - see `refreshFolderCounts()`'s own doc comment.
246
+ await context.notifyFolderCounts?.([sentFolder.uid], { bumpSyncKey: true });
247
+ writer.writeUInt8(ROP_ID_SUBMIT_MESSAGE);
248
+ writer.writeUInt8(inputHandleIndex);
249
+ writer.writeUInt32LE(0); // ReturnValue - success
250
+ }
251
+ /**
252
+ * Submits a Calendar item. The appointment itself is already persisted (`RopSaveChangesMessageHandler` writes the
253
+ * real `CalendarEvent` row at Save time, unlike a mail draft, which stays purely in-session until Submit), so there
254
+ * is nothing left to store and nothing is sent from here.
255
+ *
256
+ * **Invites are restapi's `MeetingSchedulingJob`'s job.** That job sends an iTIP `REQUEST` for every
257
+ * organizer-owned event whose `inviteSequenceSent` differs from its `sequence`, claiming each revision with a
258
+ * versioned update before sending and building the invite with restapi's own `buildEventIcs` (so a recurring
259
+ * meeting's invite carries its `RRULE` and exceptions). Saving stamps `inviteSequenceSent = sequence`, so a meeting
260
+ * that is only saved ("Save & Close", or "save changes but don't send") invites no one. Submitting clears the stamp
261
+ * (`inviteSequenceSent: null`) with a versioned update, which makes the job send the current revision exactly once.
262
+ * - A revision that is already waiting for the job (submitted and not yet sent, or never stamped) is left alone, so
263
+ * submitting it again before the job runs doesn't cause a second invite.
264
+ * - The update is versioned (`asEntity`). The job only ever claims a waiting revision, so a conflict here comes from
265
+ * some other edit (an attendee's reply updating the row, a REST edit); the row is read again and the check repeated,
266
+ * up to `MAX_INVITE_REQUEST_ATTEMPTS` times, after which the ROP fails (`MAPI_E_CALL_FAILED`).
267
+ * - Submitting a revision the job has already sent sends it again: the client asked to send.
268
+ *
269
+ * Only the organizer's own copy may be submitted (an attendee's copy of someone else's meeting answers
270
+ * `MAPI_E_INVALID_OBJECT`, as before, and nothing is changed).
271
+ */
272
+ async submitAppointment(handle, context, writer, inputHandleIndex) {
273
+ const uid = handle.entityUid.startsWith("calendarEvent:") ? handle.entityUid.slice("calendarEvent:".length) : undefined;
274
+ const event = uid ? await context.calendarEventRepo.findOne(uid, { ignoreACL: true }) : undefined;
275
+ const mailbox = event?.mailboxUid === context.mailboxUid ? await context.mailboxRepo.findOne(context.mailboxUid, { ignoreACL: true }) : undefined;
276
+ const callerAddresses = mailbox ? [mailbox.primarySmtpAddress, ...(mailbox.aliasAddresses ?? [])].map((a) => a.toLowerCase()) : [];
277
+ const isOrganizer = !!event && callerAddresses.includes((event.organizer?.address ?? "").toLowerCase());
278
+ if (!isOrganizer) {
279
+ writeResult(writer, inputHandleIndex, ERROR_INVALID_OBJECT);
280
+ return;
281
+ }
282
+ writeResult(writer, inputHandleIndex, (await requestInvites(context, uid, event)) ? 0 : ERROR_NOT_FOUND);
283
+ }
284
+ }
285
+ /** How many times `requestInvites` re-reads a row whose versioned update lost to another edit before giving up. */
286
+ export const MAX_INVITE_REQUEST_ATTEMPTS = 3;
287
+ /** Makes restapi's `MeetingSchedulingJob` send invites for the organizer copy `event`'s current revision (see
288
+ * `submitAppointment`). `false` when the row disappeared meanwhile. */
289
+ async function requestInvites(context, uid, event) {
290
+ let row = event;
291
+ for (let attempt = 0; attempt < MAX_INVITE_REQUEST_ATTEMPTS; attempt++) {
292
+ if (!row) {
293
+ return false;
294
+ }
295
+ if (isInvitePending(row)) {
296
+ return true;
297
+ }
298
+ try {
299
+ await context.calendarEventRepo.update({ uid: row.uid, version: row.version, inviteSequenceSent: null }, asEntity(context.calendarEventRepo, row), { ignoreACL: true });
300
+ return true;
301
+ }
302
+ catch (err) {
303
+ if (err?.code !== ApiErrors.INVALID_OBJECT_VERSION) {
304
+ throw err;
305
+ }
306
+ row = await context.calendarEventRepo.findOne(uid, { ignoreACL: true });
307
+ }
308
+ }
309
+ throw new Error("RopSubmitMessage: the meeting kept changing while its invites were requested.");
310
+ }
311
+ /** A recipient as nodemailer's `MailComposer` takes it: a display name only when there is one. */
312
+ function mailAddress(recipient) {
313
+ return recipient.name ? { name: recipient.name, address: recipient.address } : recipient.address;
314
+ }
315
+ /** A recipient as the Sent Items copy stores it. */
316
+ function storedRecipient(recipient, type) {
317
+ return { address: recipient.address, ...(recipient.name ? { displayName: recipient.name } : {}), type };
318
+ }
319
+ /** Prefers a `RopWriteStream`-accumulated body (the real path a large body takes) over an inline `PidTagBody`
320
+ * set directly via `RopSetProperties` (only realistic for a short body small enough to set inline) - see
321
+ * `RopOpenStreamHandler`'s write-mode branch for how `writeTargetHandleIndex` links a stream handle back to the
322
+ * draft message handle it was opened against. The generation must match too, so a stream left over from an
323
+ * earlier message at the same handle index never supplies this message's body. `undefined` when the stream's
324
+ * chunks can no longer be reassembled - sending a truncated body would be worse than failing. */
325
+ async function resolveDraftBody(context, messageHandleIndex, properties) {
326
+ const generation = context.session.handles[messageHandleIndex].generation;
327
+ for (const [index, candidate] of Object.entries(context.session.handles)) {
328
+ if (candidate.type === "stream" &&
329
+ candidate.writeTargetHandleIndex === messageHandleIndex &&
330
+ candidate.writeTargetGeneration === generation &&
331
+ candidate.writeSize) {
332
+ const raw = await readWriteStream(context, Number(index), candidate);
333
+ // The chunks are only needed for this one submit; best effort, they expire anyway.
334
+ await handleDataStoreOf(context)
335
+ .delete(writeStreamKey(context.session.uid, Number(index), candidate.generation), handleDataOwners(context.session.uid, context.session.userUid))
336
+ .catch(() => undefined);
337
+ return raw?.toString("utf16le").replace(/\0+$/, "");
338
+ }
339
+ }
340
+ return properties[String(PID_TAG_BODY)] ?? "";
341
+ }
342
+ //# sourceMappingURL=RopSubmitMessageHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RopSubmitMessageHandler.js","sourceRoot":"","sources":["../../../src/rop/RopSubmitMessageHandler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import { handleDataOwners, writeStreamKey } from "./HandleDataCache.js";
2
+ import { handleDataStoreOf } from "./RopHandler.js";
3
+ const ROP_ID_WRITE_STREAM = 0x2d;
4
+ /** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a write-mode
5
+ * stream (or doesn't exist)" - the same constant `RopReadStreamHandler` uses for its own analogous check. */
6
+ const ERROR_INVALID_OBJECT = 0x80070005;
7
+ /** `MAPI_E_TOO_BIG`, for a write that would take the stream past `MAX_WRITE_STREAM_BYTES`. */
8
+ const ERROR_TOO_BIG = 0x80040305;
9
+ /** The largest body a write stream accumulates. */
10
+ export const MAX_WRITE_STREAM_BYTES = 4 * 1024 * 1024;
11
+ /** How long a write stream's chunks are kept after its last write - the session's own idle lifetime. */
12
+ export const WRITE_STREAM_TTL_SECONDS = 15 * 60;
13
+ // Re-exported for existing importers; the key lives next to the other handle data keys.
14
+ export { writeStreamKey } from "./HandleDataCache.js";
15
+ /**
16
+ * `RopWriteStream` (`[MS-OXCPRPT]`/`[MS-OXCROPS]`): writes bytes to a stream opened in `ReadWrite`/`Create`
17
+ * mode (`RopOpenStreamHandler`'s write-mode branch - the only kind of writable stream this pragmatic subset
18
+ * ever produces, always a `RopCreateMessage` draft's `PidTagBody`). Each write is stored as a chunk keyed by its
19
+ * offset in the `HandleDataStore` (Redis when configured), not in the session: only `writeSize` is session state.
20
+ * Keying by offset makes a retried write (after a request whose session save lost a conflict) replace its chunk
21
+ * instead of appending it twice. Up to `MAX_WRITE_STREAM_BYTES`; a write past that fails with `MAPI_E_TOO_BIG` and
22
+ * nothing is stored, as it is when the session's or user's handle data quota (`handleDataOwners`) is used up. A stream
23
+ * whose draft has already been submitted takes no more writes. `RopSubmitMessageHandler` reassembles the body (`readWriteStream`) once composing is complete.
24
+ *
25
+ * Like `RopReadStream`, `[MS-OXCROPS]` documents only one combined response-buffer shape for this ROP (no
26
+ * separate Success/Failure pages) - `WrittenSize` is always present, `0` standing in for the failure case.
27
+ *
28
+ * @author Jean-Philippe Steinmetz
29
+ */
30
+ export class RopWriteStreamHandler {
31
+ constructor() {
32
+ this.ropId = ROP_ID_WRITE_STREAM;
33
+ this.failureTailBytes = 2; // DataSize/WrittenSize
34
+ }
35
+ async handle(reader, writer, context) {
36
+ reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
37
+ const inputHandleIndex = reader.readUInt8();
38
+ const dataSize = reader.readUInt16LE();
39
+ const data = reader.readBytes(dataSize);
40
+ const handle = context.session.handles[inputHandleIndex];
41
+ const target = handle?.writeTargetHandleIndex !== undefined ? context.session.handles[handle.writeTargetHandleIndex] : undefined;
42
+ if (!handle || handle.type !== "stream" || handle.writeTargetHandleIndex === undefined || target?.submitted) {
43
+ writer.writeUInt8(ROP_ID_WRITE_STREAM);
44
+ writer.writeUInt8(inputHandleIndex);
45
+ writer.writeUInt32LE(ERROR_INVALID_OBJECT);
46
+ writer.writeUInt16LE(0); // WrittenSize - see class doc comment on why this is still written on failure
47
+ return;
48
+ }
49
+ const existingSize = handle.writeSize ?? 0;
50
+ const stored = existingSize + data.length <= MAX_WRITE_STREAM_BYTES &&
51
+ (data.length === 0 ||
52
+ (await handleDataStoreOf(context).putChunk(writeStreamKey(context.session.uid, inputHandleIndex, handle.generation), existingSize, data, WRITE_STREAM_TTL_SECONDS, handleDataOwners(context.session.uid, context.session.userUid))));
53
+ if (!stored) {
54
+ writer.writeUInt8(ROP_ID_WRITE_STREAM);
55
+ writer.writeUInt8(inputHandleIndex);
56
+ writer.writeUInt32LE(ERROR_TOO_BIG);
57
+ writer.writeUInt16LE(0); // WrittenSize
58
+ return;
59
+ }
60
+ handle.writeSize = existingSize + data.length;
61
+ writer.writeUInt8(ROP_ID_WRITE_STREAM);
62
+ writer.writeUInt8(inputHandleIndex);
63
+ writer.writeUInt32LE(0); // ReturnValue - success
64
+ writer.writeUInt16LE(data.length); // WrittenSize
65
+ }
66
+ }
67
+ /** The bytes written so far to the write stream `stream` at `handleIndex`, or `undefined` when they can no longer be
68
+ * reassembled (a chunk expired or was evicted). */
69
+ export async function readWriteStream(context, handleIndex, stream) {
70
+ return handleDataStoreOf(context).readChunks(writeStreamKey(context.session.uid, handleIndex, stream.generation), stream.writeSize ?? 0);
71
+ }
72
+ //# sourceMappingURL=RopWriteStreamHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RopWriteStreamHandler.js","sourceRoot":"","sources":["../../../src/rop/RopWriteStreamHandler.ts"],"names":[],"mappings":"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"}