@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.
- package/LICENSE +382 -0
- package/README.md +60 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js +489 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -0
- package/dist/lib/BaseMapiNspiRoute.js +114 -0
- package/dist/lib/BaseMapiNspiRoute.js.map +1 -0
- package/dist/lib/MapiSessionManager.js +475 -0
- package/dist/lib/MapiSessionManager.js.map +1 -0
- package/dist/lib/RegexPatternUtils.js +30 -0
- package/dist/lib/RegexPatternUtils.js.map +1 -0
- package/dist/lib/RopDispatcher.js +145 -0
- package/dist/lib/RopDispatcher.js.map +1 -0
- package/dist/lib/codec/AppointmentRecurrence.js +315 -0
- package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
- package/dist/lib/codec/BufferCursor.js +242 -0
- package/dist/lib/codec/BufferCursor.js.map +1 -0
- package/dist/lib/codec/GlobalObjectId.js +98 -0
- package/dist/lib/codec/GlobalObjectId.js.map +1 -0
- package/dist/lib/codec/MapiGuid.js +41 -0
- package/dist/lib/codec/MapiGuid.js.map +1 -0
- package/dist/lib/codec/MapiTimeZone.js +126 -0
- package/dist/lib/codec/MapiTimeZone.js.map +1 -0
- package/dist/lib/codec/PropertyValue.js +240 -0
- package/dist/lib/codec/PropertyValue.js.map +1 -0
- package/dist/lib/codec/RopBuffer.js +38 -0
- package/dist/lib/codec/RopBuffer.js.map +1 -0
- package/dist/lib/codec/TypedString.js +54 -0
- package/dist/lib/codec/TypedString.js.map +1 -0
- package/dist/lib/index.js +99 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +85 -0
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
- package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
- package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
- package/dist/lib/mongo/index.js +7 -0
- package/dist/lib/mongo/index.js.map +1 -0
- package/dist/lib/mongo.js +6 -0
- package/dist/lib/mongo.js.map +1 -0
- package/dist/lib/nspi/NspiBindHandler.js +54 -0
- package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
- package/dist/lib/nspi/NspiCodec.js +106 -0
- package/dist/lib/nspi/NspiCodec.js.map +1 -0
- package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
- package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
- package/dist/lib/rop/AddressList.js +114 -0
- package/dist/lib/rop/AddressList.js.map +1 -0
- package/dist/lib/rop/CalendarEventTarget.js +30 -0
- package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
- package/dist/lib/rop/CalendarNamedProperties.js +57 -0
- package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
- package/dist/lib/rop/ContactTarget.js +19 -0
- package/dist/lib/rop/ContactTarget.js.map +1 -0
- package/dist/lib/rop/ContentsTable.js +60 -0
- package/dist/lib/rop/ContentsTable.js.map +1 -0
- package/dist/lib/rop/ExecuteBudget.js +82 -0
- package/dist/lib/rop/ExecuteBudget.js.map +1 -0
- package/dist/lib/rop/FastTransferStream.js +195 -0
- package/dist/lib/rop/FastTransferStream.js.map +1 -0
- package/dist/lib/rop/FolderTarget.js +118 -0
- package/dist/lib/rop/FolderTarget.js.map +1 -0
- package/dist/lib/rop/HandleDataCache.js +412 -0
- package/dist/lib/rop/HandleDataCache.js.map +1 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js +241 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
- package/dist/lib/rop/MessageBodyStream.js +68 -0
- package/dist/lib/rop/MessageBodyStream.js.map +1 -0
- package/dist/lib/rop/MessageTarget.js +57 -0
- package/dist/lib/rop/MessageTarget.js.map +1 -0
- package/dist/lib/rop/NamedPropertyRegistry.js +69 -0
- package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
- package/dist/lib/rop/PropertyResolvers.js +353 -0
- package/dist/lib/rop/PropertyResolvers.js.map +1 -0
- package/dist/lib/rop/RepoPaging.js +54 -0
- package/dist/lib/rop/RepoPaging.js.map +1 -0
- package/dist/lib/rop/RestapiRules.js +52 -0
- package/dist/lib/rop/RestapiRules.js.map +1 -0
- package/dist/lib/rop/RopCreateMessageHandler.js +55 -0
- package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js +174 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js +110 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +60 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +76 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js +55 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js +42 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +56 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
- package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
- package/dist/lib/rop/RopHandler.js +6 -0
- package/dist/lib/rop/RopHandler.js.map +1 -0
- package/dist/lib/rop/RopLogonHandler.js +135 -0
- package/dist/lib/rop/RopLogonHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenFolderHandler.js +48 -0
- package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenMessageHandler.js +83 -0
- package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenStreamHandler.js +84 -0
- package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopQueryRowsHandler.js +120 -0
- package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
- package/dist/lib/rop/RopReadStreamHandler.js +73 -0
- package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopReleaseHandler.js +23 -0
- package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js +269 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopSetColumnsHandler.js +50 -0
- package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js +173 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js +342 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopWriteStreamHandler.js +72 -0
- package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
- package/dist/lib/rop/TaskNamedProperties.js +29 -0
- package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
- package/dist/lib/rop/TaskTarget.js +15 -0
- package/dist/lib/rop/TaskTarget.js.map +1 -0
- package/dist/lib/rop/TransportSend.js +16 -0
- package/dist/lib/rop/TransportSend.js.map +1 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js +85 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
- package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
- package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
- package/dist/lib/sql/index.js +7 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql.js +6 -0
- package/dist/lib/sql.js.map +1 -0
- package/dist/types/BaseMapiEmsmdbRoute.d.ts +139 -0
- package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
- package/dist/types/MapiSessionManager.d.ts +285 -0
- package/dist/types/RegexPatternUtils.d.ts +14 -0
- package/dist/types/RopDispatcher.d.ts +53 -0
- package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
- package/dist/types/codec/BufferCursor.d.ts +92 -0
- package/dist/types/codec/GlobalObjectId.d.ts +15 -0
- package/dist/types/codec/MapiGuid.d.ts +6 -0
- package/dist/types/codec/MapiTimeZone.d.ts +7 -0
- package/dist/types/codec/PropertyValue.d.ts +87 -0
- package/dist/types/codec/RopBuffer.d.ts +32 -0
- package/dist/types/codec/TypedString.d.ts +17 -0
- package/dist/types/index.d.ts +94 -0
- package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +20 -0
- package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
- package/dist/types/mongo/index.d.ts +2 -0
- package/dist/types/mongo.d.ts +1 -0
- package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
- package/dist/types/nspi/NspiCodec.d.ts +59 -0
- package/dist/types/nspi/NspiGetMatchesHandler.d.ts +26 -0
- package/dist/types/rop/AddressList.d.ts +48 -0
- package/dist/types/rop/CalendarEventTarget.d.ts +33 -0
- package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
- package/dist/types/rop/ContactTarget.d.ts +29 -0
- package/dist/types/rop/ContentsTable.d.ts +16 -0
- package/dist/types/rop/ExecuteBudget.d.ts +48 -0
- package/dist/types/rop/FastTransferStream.d.ts +57 -0
- package/dist/types/rop/FolderTarget.d.ts +64 -0
- package/dist/types/rop/HandleDataCache.d.ts +145 -0
- package/dist/types/rop/MeetingMessageClassHandler.d.ts +30 -0
- package/dist/types/rop/MessageBodyStream.d.ts +35 -0
- package/dist/types/rop/MessageTarget.d.ts +45 -0
- package/dist/types/rop/NamedPropertyRegistry.d.ts +50 -0
- package/dist/types/rop/PropertyResolvers.d.ts +86 -0
- package/dist/types/rop/RepoPaging.d.ts +30 -0
- package/dist/types/rop/RestapiRules.d.ts +32 -0
- package/dist/types/rop/RopCreateMessageHandler.d.ts +24 -0
- package/dist/types/rop/RopDeleteFolderHandler.d.ts +53 -0
- package/dist/types/rop/RopDeleteMessagesHandler.d.ts +43 -0
- package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +25 -0
- package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +25 -0
- package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +22 -0
- package/dist/types/rop/RopGetContentsTableHandler.d.ts +25 -0
- package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +18 -0
- package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +26 -0
- package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
- package/dist/types/rop/RopHandler.d.ts +106 -0
- package/dist/types/rop/RopLogonHandler.d.ts +31 -0
- package/dist/types/rop/RopOpenFolderHandler.d.ts +20 -0
- package/dist/types/rop/RopOpenMessageHandler.d.ts +28 -0
- package/dist/types/rop/RopOpenStreamHandler.d.ts +28 -0
- package/dist/types/rop/RopQueryRowsHandler.d.ts +43 -0
- package/dist/types/rop/RopReadStreamHandler.d.ts +20 -0
- package/dist/types/rop/RopReleaseHandler.d.ts +15 -0
- package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +59 -0
- package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
- package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
- package/dist/types/rop/RopSubmitMessageHandler.d.ts +80 -0
- package/dist/types/rop/RopWriteStreamHandler.d.ts +31 -0
- package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
- package/dist/types/rop/TaskTarget.d.ts +24 -0
- package/dist/types/rop/TransportSend.d.ts +10 -0
- package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +20 -0
- package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
- package/dist/types/sql/index.d.ts +2 -0
- package/dist/types/sql.d.ts +1 -0
- 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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"}
|