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