@rapidmx/mapi-plugin 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +382 -0
- package/README.md +60 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js +297 -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 +119 -0
- package/dist/lib/MapiSessionManager.js.map +1 -0
- package/dist/lib/RopDispatcher.js +34 -0
- package/dist/lib/RopDispatcher.js.map +1 -0
- package/dist/lib/codec/AppointmentRecurrence.js +314 -0
- package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
- package/dist/lib/codec/BufferCursor.js +213 -0
- package/dist/lib/codec/BufferCursor.js.map +1 -0
- package/dist/lib/codec/GlobalObjectId.js +92 -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 +93 -0
- package/dist/lib/codec/MapiTimeZone.js.map +1 -0
- package/dist/lib/codec/PropertyValue.js +214 -0
- package/dist/lib/codec/PropertyValue.js.map +1 -0
- package/dist/lib/codec/RopBuffer.js +26 -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 +84 -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/CalendarEventTarget.js +27 -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 +23 -0
- package/dist/lib/rop/ContactTarget.js.map +1 -0
- package/dist/lib/rop/FastTransferStream.js +98 -0
- package/dist/lib/rop/FastTransferStream.js.map +1 -0
- package/dist/lib/rop/FolderTarget.js +111 -0
- package/dist/lib/rop/FolderTarget.js.map +1 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js +95 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
- package/dist/lib/rop/MessageBodyStream.js +42 -0
- package/dist/lib/rop/MessageBodyStream.js.map +1 -0
- package/dist/lib/rop/MessageTarget.js +44 -0
- package/dist/lib/rop/MessageTarget.js.map +1 -0
- package/dist/lib/rop/NamedPropertyRegistry.js +66 -0
- package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
- package/dist/lib/rop/PropertyResolvers.js +313 -0
- package/dist/lib/rop/PropertyResolvers.js.map +1 -0
- package/dist/lib/rop/RopCreateMessageHandler.js +53 -0
- package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js +133 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js +70 -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 +61 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +75 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js +76 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js +40 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +55 -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 +2 -0
- package/dist/lib/rop/RopHandler.js.map +1 -0
- package/dist/lib/rop/RopLogonHandler.js +132 -0
- package/dist/lib/rop/RopLogonHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenFolderHandler.js +46 -0
- package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenMessageHandler.js +81 -0
- package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenStreamHandler.js +81 -0
- package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopQueryRowsHandler.js +91 -0
- package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
- package/dist/lib/rop/RopReadStreamHandler.js +55 -0
- package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopReleaseHandler.js +20 -0
- package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js +210 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopSetColumnsHandler.js +52 -0
- package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js +162 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js +314 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopWriteStreamHandler.js +45 -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 +19 -0
- package/dist/lib/rop/TaskTarget.js.map +1 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js +84 -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 +82 -0
- package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
- package/dist/types/MapiSessionManager.d.ts +130 -0
- package/dist/types/RopDispatcher.d.ts +17 -0
- package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
- package/dist/types/codec/BufferCursor.d.ts +78 -0
- package/dist/types/codec/GlobalObjectId.d.ts +11 -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 +77 -0
- package/dist/types/codec/RopBuffer.d.ts +25 -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 +19 -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 +24 -0
- package/dist/types/rop/CalendarEventTarget.d.ts +29 -0
- package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
- package/dist/types/rop/ContactTarget.d.ts +29 -0
- package/dist/types/rop/FastTransferStream.d.ts +18 -0
- package/dist/types/rop/FolderTarget.d.ts +63 -0
- package/dist/types/rop/MeetingMessageClassHandler.d.ts +36 -0
- package/dist/types/rop/MessageBodyStream.d.ts +26 -0
- package/dist/types/rop/MessageTarget.d.ts +38 -0
- package/dist/types/rop/NamedPropertyRegistry.d.ts +46 -0
- package/dist/types/rop/PropertyResolvers.d.ts +60 -0
- package/dist/types/rop/RopCreateMessageHandler.d.ts +23 -0
- package/dist/types/rop/RopDeleteFolderHandler.d.ts +38 -0
- package/dist/types/rop/RopDeleteMessagesHandler.d.ts +31 -0
- package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +24 -0
- package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +24 -0
- package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +23 -0
- package/dist/types/rop/RopGetContentsTableHandler.d.ts +31 -0
- package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +17 -0
- package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +23 -0
- package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
- package/dist/types/rop/RopHandler.d.ts +67 -0
- package/dist/types/rop/RopLogonHandler.d.ts +31 -0
- package/dist/types/rop/RopOpenFolderHandler.d.ts +19 -0
- package/dist/types/rop/RopOpenMessageHandler.d.ts +27 -0
- package/dist/types/rop/RopOpenStreamHandler.d.ts +27 -0
- package/dist/types/rop/RopQueryRowsHandler.d.ts +36 -0
- package/dist/types/rop/RopReadStreamHandler.d.ts +18 -0
- package/dist/types/rop/RopReleaseHandler.d.ts +14 -0
- package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +44 -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 +70 -0
- package/dist/types/rop/RopWriteStreamHandler.d.ts +21 -0
- package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
- package/dist/types/rop/TaskTarget.d.ts +24 -0
- package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +19 -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,31 @@
|
|
|
1
|
+
import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
|
|
2
|
+
import type { RopContext, RopHandler } from "./RopHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* `RopDeleteMessages` (`[MS-OXCMSG]`/`[MS-OXCROPS]`, RopId `0x1E`): deletes one or more messages by `MessageId`
|
|
5
|
+
* from an already-open folder handle. Reuses `RecoverableRepoUtils.delete()` exactly as-built in Phase 2 - the
|
|
6
|
+
* same soft-delete-with-watermark-bump machinery EAS's own `Sync`/`FolderSync` incremental-delete detection
|
|
7
|
+
* already relies on (see `BaseMapiEmsmdbRoute.ts`'s own doc comment for why `folderRepo`/`messageRepo`/
|
|
8
|
+
* `calendarEventRepo` are built as `RecoverableRepoUtils`, not plain `RepoUtils`) - no new deletion-tracking
|
|
9
|
+
* code, zero duplication.
|
|
10
|
+
*
|
|
11
|
+
* A `MessageId` resolves via `session.messageIds` (`MessageTarget.assignOrGetMid`'s own registry, already
|
|
12
|
+
* generic over both `"message:<uid>"` and `"calendarEvent:<uid>"` target strings, since a Calendar item is
|
|
13
|
+
* itself a Message object on the wire - see `CalendarEventTarget.ts`'s own doc comment) directly to the entity
|
|
14
|
+
* to delete, the same "the session already knows what this small integer refers to" design every other MID-
|
|
15
|
+
* consuming ROP in this pragmatic subset already uses (`RopOpenMessage`, `RopSaveChangesMessage`) - the
|
|
16
|
+
* request's own `InputHandleIndex` (nominally the messages' shared parent folder) is validated to be a real,
|
|
17
|
+
* open folder handle but not cross-checked against each message's actual `folderUid`, the same pragmatic
|
|
18
|
+
* simplification `RopOpenMessageHandler`'s own doc comment documents for its analogous `FolderId` field.
|
|
19
|
+
*
|
|
20
|
+
* `WantAsynchronous` (`RopProgress`-reported async completion) and `NotifyNonRead` (non-read-receipt generation
|
|
21
|
+
* to the original sender) are decoded to advance the reader correctly but not honored - this pragmatic subset
|
|
22
|
+
* has no async-ROP or non-read-receipt machinery. `PartialCompletion` is reported `true` if any requested
|
|
23
|
+
* `MessageId` didn't resolve to a real message/calendar-event target - the rest are still deleted, matching
|
|
24
|
+
* this pragmatic subset's "don't fail the whole ROP over one stale reference" principle used throughout.
|
|
25
|
+
*
|
|
26
|
+
* @author Jean-Philippe Steinmetz
|
|
27
|
+
*/
|
|
28
|
+
export declare class RopDeleteMessagesHandler implements RopHandler {
|
|
29
|
+
readonly ropId = 30;
|
|
30
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
|
|
2
|
+
import type { RopContext, RopHandler } from "./RopHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* `RopFastTransferSourceCopyProperties` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x69`): the explicit-column
|
|
5
|
+
* sibling of `RopFastTransferSourceCopyTo` - `PropertyTags` here is the properties to **copy** (an include
|
|
6
|
+
* list, per `[MS-OXCROPS]`'s own wording, the opposite of `CopyTo`'s exclude list), applied uniformly at both
|
|
7
|
+
* folder- and message-level wherever `FastTransferStream.ts` builds a `propList` - see that file's own doc
|
|
8
|
+
* comment for this pragmatic subset's overall ICS scope. An empty `PropertyTags` list is honored literally
|
|
9
|
+
* (copies zero properties, i.e. empty `propList`s) rather than falling back to `CopyTo`'s own default column
|
|
10
|
+
* set - that fallback belongs to `CopyTo` alone, since an explicit "copy nothing" request here is spec-valid
|
|
11
|
+
* and distinguishable from "use the default set" (`CopyTo` simply has no `PropertyTags`-supplied alternative to
|
|
12
|
+
* fall back from).
|
|
13
|
+
*
|
|
14
|
+
* `Level`/`CopyFlags`/`SendOptions` are decoded to advance the reader correctly but not honored - see
|
|
15
|
+
* `RopFastTransferSourceCopyToHandler`'s own doc comment for the identical reasoning. Note `CopyFlags` here is
|
|
16
|
+
* **1 byte**, not 4 like `CopyTo`'s own field of the same name - confirmed against each ROP's own request-buffer
|
|
17
|
+
* page separately, not assumed identical.
|
|
18
|
+
*
|
|
19
|
+
* @author Jean-Philippe Steinmetz
|
|
20
|
+
*/
|
|
21
|
+
export declare class RopFastTransferSourceCopyPropertiesHandler implements RopHandler {
|
|
22
|
+
readonly ropId = 105;
|
|
23
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 (or the entire remaining buffer, capped to
|
|
7
|
+
* `MAX_TRANSFER_BUFFER_SIZE`, for the `0xBABE` "server-determined" sentinel - `MaximumBufferSize`, present only
|
|
8
|
+
* in that case, is decoded to advance the reader correctly but not honored, since honoring it would mean
|
|
9
|
+
* returning *more* than one chunk can carry, not less). Reports `Done` once the whole buffer has been returned
|
|
10
|
+
* across one or more calls, `Partial` otherwise - `NoRoom` and `Error` are never produced (a `"fastTransfer"`
|
|
11
|
+
* handle, once created, always has a complete, already-valid buffer to page from).
|
|
12
|
+
*
|
|
13
|
+
* `BackoffTime` is never emitted (this pragmatic subset never returns the one `ReturnValue` that field is
|
|
14
|
+
* conditional on), and the failure path (`ERROR_INVALID_OBJECT`) omits every field after `ReturnValue` entirely
|
|
15
|
+
* - the same "just the fixed header, no success-only tail" shape every other failing handler in this pragmatic
|
|
16
|
+
* subset already uses, not a specific claim about the real spec's own error-path byte layout.
|
|
17
|
+
*
|
|
18
|
+
* @author Jean-Philippe Steinmetz
|
|
19
|
+
*/
|
|
20
|
+
export declare class RopFastTransferSourceGetBufferHandler implements RopHandler {
|
|
21
|
+
readonly ropId = 78;
|
|
22
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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. `RopSetColumns`/`RopQueryRows` themselves need no changes at all to
|
|
9
|
+
* support this - they already operate generically on whatever `rows`/`columns` a table handle carries,
|
|
10
|
+
* dispatching to `FolderTarget`'s or `MessageTarget`'s resolvers by row-target prefix (`"folder:"`/`"virtual:"`
|
|
11
|
+
* vs `"message:"`).
|
|
12
|
+
*
|
|
13
|
+
* A folder's contents table is only ever non-empty for a real folder (`"folder:<uid>"`) - the virtual special
|
|
14
|
+
* folders (`RopLogonHandler`'s own doc comment) have no real backing row to hold messages under, so opening a
|
|
15
|
+
* contents table on one always yields an empty table rather than an error.
|
|
16
|
+
*
|
|
17
|
+
* **Calendar folders** (`Folder.type === FolderType.CALENDAR`) list `"calendarEvent:<uid>"` rows resolved via
|
|
18
|
+
* `calendarEventRepo` instead of `"message:<uid>"` rows - a single table handle only ever holds one kind of
|
|
19
|
+
* row, decided once here by checking the folder's own `type`, exactly the same "resolve by target-string
|
|
20
|
+
* prefix downstream" design `MessageTarget`/`FolderTarget` already use for their own rows. **Contacts and
|
|
21
|
+
* Tasks** folders (`FolderType.CONTACTS`/`TASKS`) list `"contact:<uid>"`/`"task:<uid>"` rows the same way, via
|
|
22
|
+
* `contactRepo`/`taskRepo` - empty (rather than an error) when the context has neither wired up, the same
|
|
23
|
+
* optional-repo degradation `RopContext`'s own doc comment describes.
|
|
24
|
+
*
|
|
25
|
+
* @author Jean-Philippe Steinmetz
|
|
26
|
+
*/
|
|
27
|
+
export declare class RopGetContentsTableHandler implements RopHandler {
|
|
28
|
+
readonly ropId = 5;
|
|
29
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
30
|
+
private resolveRows;
|
|
31
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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.
|
|
13
|
+
*
|
|
14
|
+
* `PropertySizeLimit`/`WantUnicode` are decoded (to advance the reader correctly) but not honored - this
|
|
15
|
+
* pragmatic subset never truncates a property value and always encodes strings the same way regardless of the
|
|
16
|
+
* client's Unicode preference (see `PropertyValue.ts`'s own string-encoding doc comments).
|
|
17
|
+
*
|
|
18
|
+
* @author Jean-Philippe Steinmetz
|
|
19
|
+
*/
|
|
20
|
+
export declare class RopGetPropertiesSpecificHandler implements RopHandler {
|
|
21
|
+
readonly ropId = 7;
|
|
22
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
23
|
+
}
|
|
@@ -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,67 @@
|
|
|
1
|
+
import type { RepoUtils } from "@rapidrest/service-core";
|
|
2
|
+
import type { 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
|
+
/**
|
|
7
|
+
* Everything a `RopHandler` needs beyond the raw ROP bytes it decodes itself. `folderRepo`/`mailboxRepo` are
|
|
8
|
+
* built once by `BaseMapiEmsmdbRoute` (not by each handler, unlike EAS's per-command repo pattern - MAPI's ROPs
|
|
9
|
+
* are far more tightly interdependent around the same few entity types than EAS's per-command-collection-type
|
|
10
|
+
* structure was, so centralizing the common repos here avoids every handler rebuilding its own). Untyped as
|
|
11
|
+
* `RepoUtils<any>` rather than a generic `F extends Folder` parameter threaded through every handler file -
|
|
12
|
+
* the same pragmatic `any` this codebase already uses throughout for `xxxClass`/`xxxRepo` DI fields, since
|
|
13
|
+
* `RepoUtils<F>`'s method contravariance makes a generic version genuinely awkward for no real benefit here
|
|
14
|
+
* (every handler only ever reads `Folder`-shaped rows, never constructs backend-specific entities itself).
|
|
15
|
+
*
|
|
16
|
+
* `folderClass`/`messageClass` (the concrete Mongo/SQL entity constructors) are threaded through here rather
|
|
17
|
+
* than held as a field on each handler - unlike EAS's `EasCommandHandler`s, a single shared `RopHandler`
|
|
18
|
+
* instance (e.g. `RopSubmitMessageHandler`) is reused for *both* the Mongo and SQL route (`ropHandlerClasses`
|
|
19
|
+
* lists the exact same classes in `MapiEmsmdbRouteMongo`/`SQL`), so the concrete class can only ever be known
|
|
20
|
+
* per-request, via this context - needed by `RopSubmitMessageHandler`'s `findOrCreateWellKnownFolder()`/
|
|
21
|
+
* `messageRepo.create()` calls when persisting a submitted message's Sent Items copy. `scanPipeline`/
|
|
22
|
+
* `mailTransport` back the same `scanAndRelay()` compose/send pipeline `BaseMessageRoute.send()`/EAS's
|
|
23
|
+
* `ComposeMailCommand` already share via `MailSendUtils.ts`.
|
|
24
|
+
*
|
|
25
|
+
* `contactRepo`/`taskRepo`/`contactClass`/`taskClass`/`labelRepo`/`labelClass` are **optional**, unlike every
|
|
26
|
+
* other repo/class pair here - `BaseMapiEmsmdbRoute` always populates them in real use (see its own `@Init`),
|
|
27
|
+
* but making them required would force every one of this codebase's many existing `RopContext`-literal test
|
|
28
|
+
* fixtures (most predating Contacts/Tasks folder support, and all predating Label support) to grow new fields
|
|
29
|
+
* each for no behavioral reason. Every consumer (`PropertyResolvers.ts`, `RopGetContentsTableHandler.ts`,
|
|
30
|
+
* `RopOpenMessageHandler.ts`) already degrades a `"contact:"`/`"task:"` target to empty/no-rows when the
|
|
31
|
+
* corresponding repo is absent, the same "missing data, not a crash" stance `resolveContactInfo`/
|
|
32
|
+
* `resolveTaskInfo` take for a vanished row; `labelRepo` absent degrades `PidNameKeywords` (Outlook Categories)
|
|
33
|
+
* to an empty list the same way.
|
|
34
|
+
*/
|
|
35
|
+
export interface RopContext {
|
|
36
|
+
mailboxUid: string;
|
|
37
|
+
userUid: string;
|
|
38
|
+
session: MapiSessionContext;
|
|
39
|
+
mailboxRepo: RepoUtils<any>;
|
|
40
|
+
folderRepo: RepoUtils<any>;
|
|
41
|
+
messageRepo: RepoUtils<any>;
|
|
42
|
+
calendarEventRepo: RepoUtils<any>;
|
|
43
|
+
contactRepo?: RepoUtils<any>;
|
|
44
|
+
taskRepo?: RepoUtils<any>;
|
|
45
|
+
labelRepo?: RepoUtils<any>;
|
|
46
|
+
folderClass: any;
|
|
47
|
+
messageClass: any;
|
|
48
|
+
calendarEventClass: any;
|
|
49
|
+
contactClass?: any;
|
|
50
|
+
taskClass?: any;
|
|
51
|
+
labelClass?: any;
|
|
52
|
+
blobStore: BlobStore;
|
|
53
|
+
scanPipeline: ScanPipeline;
|
|
54
|
+
mailTransport: any;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* One class per ROP, mirroring `EasCommandHandler`'s exact one-class-per-command convention. A handler owns
|
|
58
|
+
* its own request/response wire format entirely - decoding whatever fields follow its own `RopId` byte from
|
|
59
|
+
* `reader` (already consumed by the dispatcher) and appending its response bytes (if any - `RopRelease`
|
|
60
|
+
* intentionally appends none) to the shared `writer`. See `RopBuffer.ts`'s doc comment for why a generic
|
|
61
|
+
* dispatcher can't do this decoding itself.
|
|
62
|
+
*/
|
|
63
|
+
export interface RopHandler {
|
|
64
|
+
/** The `RopId` byte this handler processes (e.g. `0xFE` for `RopLogon`). */
|
|
65
|
+
readonly ropId: number;
|
|
66
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void> | void;
|
|
67
|
+
}
|
|
@@ -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,19 @@
|
|
|
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
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
|
|
2
|
+
import type { RopContext, RopHandler } from "./RopHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* `RopQueryRows` (`[MS-OXCTABL]`/`[MS-OXCROPS]`): fetches up to `RowCount` rows from an already-configured
|
|
5
|
+
* table (`RopGetHierarchyTable` + `RopSetColumns`), advancing the table's cursor. Confirmed field-by-field
|
|
6
|
+
* against `[MS-OXCTABL]`'s own real captured request/response byte example - including the response's
|
|
7
|
+
* `RowData` encoding (`PropertyRow`: a leading `Flags` byte, `0x00` for a `StandardPropertyRow` where every
|
|
8
|
+
* column is a plain `PropertyValue`, `0x01` for a `FlaggedPropertyRow` where each column gets its own
|
|
9
|
+
* `FlaggedPropertyValue` flag+optional-value). This pragmatic subset always emits `StandardPropertyRow`
|
|
10
|
+
* (`Flags = 0x00`) - every configured column always resolves to *some* value here (falling back to a
|
|
11
|
+
* type-appropriate zero/empty default for an unsupported property, never an error), so `FlaggedPropertyRow`'s
|
|
12
|
+
* per-column error signaling is never needed.
|
|
13
|
+
*
|
|
14
|
+
* Backward reads (`ForwardRead = FALSE`) and the `NoAdvance` flag are decoded (to advance the reader
|
|
15
|
+
* correctly) but not honored - this pragmatic subset's tables are simple forward-only cursors. Works generically
|
|
16
|
+
* against either a `RopGetHierarchyTable` (folder rows, `"folder:"`/`"virtual:"` targets) or a
|
|
17
|
+
* `RopGetContentsTable` (message rows, `"message:"` targets) handle - dispatching to `FolderTarget`'s or
|
|
18
|
+
* `MessageTarget`'s resolver and property-value mapping by row-target prefix, since a single table handle only
|
|
19
|
+
* ever holds one kind of row.
|
|
20
|
+
*
|
|
21
|
+
* **Known, deliberate performance limitation**: `resolvePropertyValues` still does one `repo.findOne()` per
|
|
22
|
+
* row (message/calendarEvent/contact/task) rather than a single batched fetch for the whole page - a genuine
|
|
23
|
+
* N+1 for a large `RowCount`. A real fix needs a batched "fetch all these uids" query, which this codebase's
|
|
24
|
+
* `RepoUtils<T>` abstraction doesn't expose a backend-agnostic way to express (Mongo's `$in` and TypeORM's
|
|
25
|
+
* `In()` aren't interchangeable at this call site, which has no idea which backend it's running against) - see
|
|
26
|
+
* `ResolutionCache` for the narrower, safe win this pass *did* make (the redundant *identical* whole-mailbox-
|
|
27
|
+
* folder-list and calendar-mailbox fetches that were happening once per row regardless of which distinct
|
|
28
|
+
* entity each row was, now happen at most once per call).
|
|
29
|
+
*
|
|
30
|
+
* @author Jean-Philippe Steinmetz
|
|
31
|
+
*/
|
|
32
|
+
export declare class RopQueryRowsHandler implements RopHandler {
|
|
33
|
+
readonly ropId = 21;
|
|
34
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
35
|
+
private buildRow;
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
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.
|
|
9
|
+
* Unlike most other ROPs in this pragmatic subset, `[MS-OXCROPS]` documents only one combined response-buffer
|
|
10
|
+
* shape for this ROP (no separate Success/Failure pages) - `DataSize`/`Data` are always present, `DataSize = 0`
|
|
11
|
+
* (empty `Data`) standing in for the failure case rather than the fields being omitted entirely.
|
|
12
|
+
*
|
|
13
|
+
* @author Jean-Philippe Steinmetz
|
|
14
|
+
*/
|
|
15
|
+
export declare class RopReadStreamHandler implements RopHandler {
|
|
16
|
+
readonly ropId = 44;
|
|
17
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
handle(reader: BufferReader, _writer: BufferWriter, context: RopContext): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { BufferReader, type BufferWriter } from "../codec/BufferCursor.js";
|
|
2
|
+
import type { RopContext, RopHandler } from "./RopHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* `RopSaveChangesMessage` (`[MS-OXCMSG]`/`[MS-OXCROPS]`): persists a message's pending changes, returning the
|
|
5
|
+
* `MID` a client can reference it by afterwards. **Pragmatic subset**: this library has no separate Drafts-
|
|
6
|
+
* folder persistence step (matching EAS's own `ComposeMailCommand`, which never creates a standalone draft
|
|
7
|
+
* `Message` row either) - saving a `RopCreateMessage` draft does not yet write anything to a database. It only
|
|
8
|
+
* assigns a session-scoped MID (`MessageTarget.assignOrGetMid`, the exact mechanism a `RopQueryRows` row's
|
|
9
|
+
* `PidTagMid` column already uses) so the wire contract's `MessageId` field is well-formed, keyed by this
|
|
10
|
+
* handle's own index (`` `draft:${inputHandleIndex}` ``) since an unsaved draft has no real `"message:<uid>"`
|
|
11
|
+
* target yet. The real work - actually building and sending a MIME message from the properties/body
|
|
12
|
+
* accumulated so far - happens at `RopSubmitMessage` time, not here; calling Save without ever calling Submit
|
|
13
|
+
* silently discards the draft when the session expires, a real, deliberate limitation (this pragmatic subset
|
|
14
|
+
* has no autosave-recovery story) rather than a silently-wrong one.
|
|
15
|
+
*
|
|
16
|
+
* Re-saving an already-real message (a `RopOpenMessage` handle, not a fresh `RopCreateMessage` draft) is
|
|
17
|
+
* accepted but is a no-op beyond echoing that message's own already-assigned MID - editing an existing
|
|
18
|
+
* message's properties is out of scope for this pragmatic subset's compose/send-only ROP coverage.
|
|
19
|
+
*
|
|
20
|
+
* `SaveFlags` is decoded to advance past it correctly but not honored - this pragmatic subset has no
|
|
21
|
+
* conflict-resolution/force-save semantics to vary by flag.
|
|
22
|
+
*
|
|
23
|
+
* **Calendar branch**: unlike mail, a draft whose `PidTagMessageClass` starts with `"IPM.Appointment"` *is*
|
|
24
|
+
* durably persisted here (`saveAppointment()`) - creating (or updating, if this handle already refers to a real
|
|
25
|
+
* `"calendarEvent:<uid>"`) a real `CalendarEvent` row, so a subsequent `RopGetPropertiesSpecific` can read it
|
|
26
|
+
* back before `RopSubmitMessage` is ever called (a calendar item must exist as soon as it's saved, the same way
|
|
27
|
+
* a real Exchange server behaves - unlike a mail draft, which this pragmatic subset never persists at all until
|
|
28
|
+
* Submit).
|
|
29
|
+
*
|
|
30
|
+
* @author Jean-Philippe Steinmetz
|
|
31
|
+
*/
|
|
32
|
+
export declare class RopSaveChangesMessageHandler implements RopHandler {
|
|
33
|
+
readonly ropId = 12;
|
|
34
|
+
handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Persists (creating, or updating an already-real one) a `CalendarEvent` from the draft's accumulated
|
|
37
|
+
* `RopSetProperties` values, and returns the MID a client can reference it by. Every field this pragmatic
|
|
38
|
+
* subset understands (per `CalendarNamedProperties.ts`) is decoded via `decodeCalendarFieldsFromDraft`
|
|
39
|
+
* below - the write-side mirror of `PropertyResolvers.calendarEventValueFor`'s read-side switch. A field the
|
|
40
|
+
* client never set (e.g. no `RopSetProperties` call touched `PidLidBusyStatus`) falls back to the existing
|
|
41
|
+
* row's own value on an update, or the entity class's own default on a fresh create.
|
|
42
|
+
*/
|
|
43
|
+
private saveAppointment;
|
|
44
|
+
}
|
|
@@ -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
|
+
}
|