@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,132 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { BufferWriter } from "../codec/BufferCursor.js";
|
|
6
|
+
import { encodeGuid } from "../codec/MapiGuid.js";
|
|
7
|
+
import { FolderType } from "@rapidmx/restapi";
|
|
8
|
+
import { assignOrGetFid } from "./FolderTarget.js";
|
|
9
|
+
const ROP_ID_LOGON = 0xfe;
|
|
10
|
+
/** A fixed replica identity shared by every logon in this deployment - legitimate per `[MS-OXCSTOR]`'s own
|
|
11
|
+
* "RopLogon ROP Success Response Buffer for Private Mailbox" section ("If the client did not set
|
|
12
|
+
* USE_PER_MDB_REPLID_MAPPING... this value MUST be identical for all private mailbox logons on the same RPC
|
|
13
|
+
* session"); this pragmatic subset never varies replica identity, so one fixed constant satisfies that. */
|
|
14
|
+
const REPL_ID = 1;
|
|
15
|
+
const REPL_GUID = "00000000-0000-0000-0000-000000000001";
|
|
16
|
+
/**
|
|
17
|
+
* Order matches `[MS-OXCSTOR]` §2.2.1.1.3 ("RopLogon ROP Success Response Buffer for Private Mailbox")'s
|
|
18
|
+
* `FolderIds` list exactly: Root, Deferred Action, Spooler Queue, IPM Subtree, Inbox, Outbox, Sent Items,
|
|
19
|
+
* Deleted Items, Common Views, Schedule, Search, Views, Shortcuts (13 folders total, confirmed against the
|
|
20
|
+
* spec page directly). Folders with a matching `FolderType` resolve to a real `Folder` when one exists for
|
|
21
|
+
* this mailbox; the rest are virtual placeholders. Root/IPM Subtree have no real backing row at all in this
|
|
22
|
+
* data model, matching how EAS's own `FolderSync` already represents "the root" as `ParentId "0"` rather than
|
|
23
|
+
* a real `Folder` - not a new gap this ROP introduces. Deferred Action/Spooler Queue/Common Views/Schedule/
|
|
24
|
+
* Search/Views/Shortcuts are MAPI-internal administrative folders this library's `FolderType` enum has no
|
|
25
|
+
* concept of at all (it models the REST/EAS-relevant folder kinds - Inbox/Sent/Drafts/Deleted/Outbox/Junk/
|
|
26
|
+
* Calendar/Contacts/Tasks/Notes/User - not Exchange's own internal bookkeeping folders). A real client rarely
|
|
27
|
+
* if ever tries to open most of these directly; documented gap, not silently dropped.
|
|
28
|
+
*/
|
|
29
|
+
const SPECIAL_FOLDERS = [
|
|
30
|
+
{ name: "root" },
|
|
31
|
+
{ name: "deferredAction" },
|
|
32
|
+
{ name: "spoolerQueue" },
|
|
33
|
+
{ name: "ipmSubtree" },
|
|
34
|
+
{ name: "inbox", folderType: FolderType.INBOX },
|
|
35
|
+
{ name: "outbox", folderType: FolderType.OUTBOX },
|
|
36
|
+
{ name: "sentItems", folderType: FolderType.SENT_ITEMS },
|
|
37
|
+
{ name: "deletedItems", folderType: FolderType.DELETED_ITEMS },
|
|
38
|
+
{ name: "commonViews" },
|
|
39
|
+
{ name: "schedule" },
|
|
40
|
+
{ name: "search" },
|
|
41
|
+
{ name: "views" },
|
|
42
|
+
{ name: "shortcuts" },
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* `RopLogon` (`[MS-OXCSTOR]`/`[MS-OXCROPS]`): the first ROP of any session, a prerequisite for every other
|
|
46
|
+
* ROP. This pragmatic subset always performs a private-mailbox logon against the mailbox `BaseMapiEmsmdbRoute`
|
|
47
|
+
* already resolved and authorized at `Connect` time - the request's own `Essdn` field is decoded (to advance
|
|
48
|
+
* past it correctly) but never trusted for mailbox identity, the same principle `BaseMapiEmsmdbRoute.Connect`
|
|
49
|
+
* already applies to that request's `UserDn` field. Because mailbox resolution already happened upstream,
|
|
50
|
+
* this handler has no real failure path to model (`ecUnknownUser`/`ecLoginFailure`/... are all deferred).
|
|
51
|
+
*
|
|
52
|
+
* @author Jean-Philippe Steinmetz
|
|
53
|
+
*/
|
|
54
|
+
export class RopLogonHandler {
|
|
55
|
+
constructor() {
|
|
56
|
+
this.ropId = ROP_ID_LOGON;
|
|
57
|
+
}
|
|
58
|
+
async handle(reader, writer, context) {
|
|
59
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
60
|
+
const outputHandleIndex = reader.readUInt8();
|
|
61
|
+
const logonFlags = reader.readUInt8();
|
|
62
|
+
reader.readUInt32LE(); // OpenFlags - unused; every logon behaves as a plain private-mailbox logon
|
|
63
|
+
reader.readUInt32LE(); // StoreState - per spec, unused and always 0
|
|
64
|
+
const essdnSize = reader.readUInt16LE();
|
|
65
|
+
if (essdnSize > 0) {
|
|
66
|
+
reader.readBytes(essdnSize); // Essdn - never trusted for mailbox identity, see class doc comment
|
|
67
|
+
}
|
|
68
|
+
const fids = await this.assignFolderIds(context);
|
|
69
|
+
context.session.handles[outputHandleIndex] = { type: "logon", entityUid: context.mailboxUid };
|
|
70
|
+
writer.writeUInt8(ROP_ID_LOGON);
|
|
71
|
+
writer.writeUInt8(outputHandleIndex);
|
|
72
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
73
|
+
writer.writeUInt8(logonFlags); // echoed back unchanged, per spec
|
|
74
|
+
for (const folder of SPECIAL_FOLDERS) {
|
|
75
|
+
writer.writeBigUInt64LE(BigInt(fids[folder.name]));
|
|
76
|
+
}
|
|
77
|
+
writer.writeUInt8(0x01); // ResponseFlags - Reserved bit only; no OwnerRight/SendAsRight/OOF claims in this subset
|
|
78
|
+
writer.writeBytes(encodeGuid(context.mailboxUid));
|
|
79
|
+
writer.writeUInt16LE(REPL_ID);
|
|
80
|
+
writer.writeBytes(encodeGuid(REPL_GUID));
|
|
81
|
+
writer.writeBytes(encodeLogonTime(new Date()));
|
|
82
|
+
writer.writeBigUInt64LE(1n); // GwartTime - fixed constant; no real GWART/AD concept in this deployment
|
|
83
|
+
writer.writeUInt32LE(0); // StoreState - unused
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Assigns this session's FIDs for the 13 special folders, remembering each one's real-`Folder`-or-virtual-
|
|
87
|
+
* placeholder target in `session.folderIds` so a later `RopOpenFolder` can resolve it back to something
|
|
88
|
+
* real. Delegates to `FolderTarget.assignOrGetFid` - the same "reuse an existing FID for this target if one
|
|
89
|
+
* was already assigned, otherwise mint the next free one" logic `RopGetHierarchyTable`/`RopQueryRows` use
|
|
90
|
+
* for child folders - rather than wholesale-replacing `session.folderIds` with a freshly-numbered map
|
|
91
|
+
* starting back at FID 1. A second `RopLogon` on a live session (spec-legal - a real client can reconnect
|
|
92
|
+
* or re-logon within one session context) previously invalidated every FID a client had already learned
|
|
93
|
+
* for a child folder via `RopGetHierarchyTable`/`RopQueryRows` (all assigned starting at FID 14, since the
|
|
94
|
+
* old code always reset the counter to 1-13 for just these specials) and could even cause FIDs 14+ to be
|
|
95
|
+
* silently re-issued to a *different* folder than the one the client last associated with that number.
|
|
96
|
+
* Reusing `assignOrGetFid` means a re-`RopLogon` is now purely additive: these 13 targets get their
|
|
97
|
+
* existing FIDs back if this is a second logon, and any child-folder FIDs already assigned are left alone.
|
|
98
|
+
*/
|
|
99
|
+
async assignFolderIds(context) {
|
|
100
|
+
// The four real-folder-type lookups (Inbox/Outbox/Sent Items/Deleted Items) are independent of each
|
|
101
|
+
// other, so resolve them concurrently rather than one sequential round trip per special folder -
|
|
102
|
+
// FID *assignment* itself still happens afterward, in SPECIAL_FOLDERS' own fixed order, so which FID
|
|
103
|
+
// ends up bound to which target is unaffected by fetch order.
|
|
104
|
+
const targets = await Promise.all(SPECIAL_FOLDERS.map(async (folder) => {
|
|
105
|
+
if (!folder.folderType) {
|
|
106
|
+
return `virtual:${folder.name}`;
|
|
107
|
+
}
|
|
108
|
+
const existing = await context.folderRepo.find({ mailboxUid: context.mailboxUid, type: folder.folderType }, { ignoreACL: true, limit: 1 });
|
|
109
|
+
return existing[0] ? `folder:${existing[0].uid}` : `virtual:${folder.name}`;
|
|
110
|
+
}));
|
|
111
|
+
const fids = {};
|
|
112
|
+
SPECIAL_FOLDERS.forEach((folder, index) => {
|
|
113
|
+
fids[folder.name] = assignOrGetFid(context.session, targets[index]);
|
|
114
|
+
});
|
|
115
|
+
return fids;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** `LogonTime` structure (`[MS-OXCROPS]` "LogonTime Structure"): `Seconds`/`Minutes`/`Hour`/`DayOfWeek`
|
|
119
|
+
* (1 byte each, `DayOfWeek` Sunday=0)/`Day`/`Month` (1 byte each, `Month` January=1)/`Year` (2 bytes) - all
|
|
120
|
+
* UTC, confirmed against the spec's own byte-layout table. */
|
|
121
|
+
function encodeLogonTime(date) {
|
|
122
|
+
const writer = new BufferWriter();
|
|
123
|
+
writer.writeUInt8(date.getUTCSeconds());
|
|
124
|
+
writer.writeUInt8(date.getUTCMinutes());
|
|
125
|
+
writer.writeUInt8(date.getUTCHours());
|
|
126
|
+
writer.writeUInt8(date.getUTCDay());
|
|
127
|
+
writer.writeUInt8(date.getUTCDate());
|
|
128
|
+
writer.writeUInt8(date.getUTCMonth() + 1);
|
|
129
|
+
writer.writeUInt16LE(date.getUTCFullYear());
|
|
130
|
+
return writer.toBuffer();
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=RopLogonHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopLogonHandler.js","sourceRoot":"","sources":["../../../src/rop/RopLogonHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const ROP_ID_OPEN_FOLDER = 0x02;
|
|
2
|
+
/** The well-known MAPI HRESULT `MAPI_E_NOT_FOUND`, reused as this handler's only failure `ReturnValue` (an
|
|
3
|
+
* unrecognized `FolderId` - one not present in `session.folderIds`, i.e. not something `RopLogon` handed out
|
|
4
|
+
* this session). */
|
|
5
|
+
const ERROR_NOT_FOUND = 0x8004010f;
|
|
6
|
+
/**
|
|
7
|
+
* `RopOpenFolder` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`): opens a folder by FID, producing a new `"folder"` Server
|
|
8
|
+
* object handle later ROPs (`RopGetHierarchyTable`, a future `RopGetContentsTable`) reference by
|
|
9
|
+
* `InputHandleIndex`. The request's own `OpenModeFlags` (`OpenSoftDeleted`, ...) is decoded to advance past
|
|
10
|
+
* it correctly but not acted on - this pragmatic subset has no soft-deleted-folder recovery flow.
|
|
11
|
+
*
|
|
12
|
+
* Private-mailbox-only response shape: `IsGhosted` is always `0` and its conditional
|
|
13
|
+
* `ServerCount`/`CheapServerCount`/`Servers` fields are never present, since `IsGhosted` and its trailing
|
|
14
|
+
* fields are documented as public-folder-only (`[MS-OXCFOLD]`'s own "RopOpenFolder ROP Response Buffer"
|
|
15
|
+
* page). `HasRules` is always `0` - this library has no `[MS-OXORULE]` rules-engine support.
|
|
16
|
+
*
|
|
17
|
+
* @author Jean-Philippe Steinmetz
|
|
18
|
+
*/
|
|
19
|
+
export class RopOpenFolderHandler {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.ropId = ROP_ID_OPEN_FOLDER;
|
|
22
|
+
}
|
|
23
|
+
handle(reader, writer, context) {
|
|
24
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
25
|
+
reader.readUInt8(); // InputHandleIndex - the logon handle this open is performed against; only one
|
|
26
|
+
// logon exists per session in this pragmatic subset, so it's consumed to advance the reader correctly
|
|
27
|
+
// but never separately validated.
|
|
28
|
+
const outputHandleIndex = reader.readUInt8();
|
|
29
|
+
reader.readUInt8(); // OpenModeFlags - OpenSoftDeleted not supported in this pragmatic subset
|
|
30
|
+
const folderId = reader.readBigUInt64LE();
|
|
31
|
+
const target = context.session.folderIds[folderId.toString()];
|
|
32
|
+
if (!target) {
|
|
33
|
+
writer.writeUInt8(ROP_ID_OPEN_FOLDER);
|
|
34
|
+
writer.writeUInt8(outputHandleIndex);
|
|
35
|
+
writer.writeUInt32LE(ERROR_NOT_FOUND);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
context.session.handles[outputHandleIndex] = { type: "folder", entityUid: target };
|
|
39
|
+
writer.writeUInt8(ROP_ID_OPEN_FOLDER);
|
|
40
|
+
writer.writeUInt8(outputHandleIndex);
|
|
41
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
42
|
+
writer.writeUInt8(0); // HasRules - no rules-engine support
|
|
43
|
+
writer.writeUInt8(0); // IsGhosted - always false for a private mailbox
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=RopOpenFolderHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopOpenFolderHandler.js","sourceRoot":"","sources":["../../../src/rop/RopOpenFolderHandler.ts"],"names":[],"mappings":"AAOA,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC;;oBAEoB;AACpB,MAAM,eAAe,GAAG,UAAU,CAAC;AAEnC;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,oBAAoB;IAAjC;QACoB,UAAK,GAAG,kBAAkB,CAAC;IA2B/C,CAAC;IAzBU,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QACzE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,+EAA+E;QACnG,sGAAsG;QACtG,kCAAkC;QAClC,MAAM,iBAAiB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,yEAAyE;QAC7F,MAAM,QAAQ,GAAW,MAAM,CAAC,eAAe,EAAE,CAAC;QAElD,MAAM,MAAM,GAAuB,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClF,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;YACtC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;YACtC,OAAO;QACX,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QAEnF,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;QACtC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;QACjD,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,qCAAqC;QAC3D,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,iDAAiD;IAC3E,CAAC;CACJ"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { writeTypedString } from "../codec/TypedString.js";
|
|
2
|
+
import { resolveCalendarEventInfo } from "./CalendarEventTarget.js";
|
|
3
|
+
import { resolveContactInfo } from "./ContactTarget.js";
|
|
4
|
+
import { resolveMessageInfo } from "./MessageTarget.js";
|
|
5
|
+
import { resolveTaskInfo } from "./TaskTarget.js";
|
|
6
|
+
const ROP_ID_OPEN_MESSAGE = 0x03;
|
|
7
|
+
/** The well-known MAPI HRESULT `MAPI_E_NOT_FOUND`, reused as this handler's only failure `ReturnValue` - an
|
|
8
|
+
* unrecognized `MessageId`, i.e. not something a `RopQueryRows` `PidTagMid` column has handed out this session
|
|
9
|
+
* (see `MessageTarget.assignOrGetMid`'s own doc comment). Same constant `RopOpenFolderHandler` uses for its
|
|
10
|
+
* own analogous `FolderId` lookup miss. */
|
|
11
|
+
const ERROR_NOT_FOUND = 0x8004010f;
|
|
12
|
+
/**
|
|
13
|
+
* `RopOpenMessage` (`[MS-OXCMSG]`/`[MS-OXCROPS]`): opens a message by MID, producing a new `"message"` Server
|
|
14
|
+
* object handle later ROPs (`RopGetPropertiesSpecific`, `RopOpenStream`) reference by `InputHandleIndex`. The
|
|
15
|
+
* request's own `FolderId` (the message's expected parent folder) and `CodePageId` are decoded to advance past
|
|
16
|
+
* them correctly but not otherwise acted on - a MID is resolved directly via `session.messageIds`
|
|
17
|
+
* (`MessageTarget.assignOrGetMid`), the same "the session already knows what this small integer refers to"
|
|
18
|
+
* design `RopOpenFolderHandler` uses for FIDs, so cross-checking the caller-supplied `FolderId` would be pure
|
|
19
|
+
* redundancy. `OpenModeFlags` is likewise decoded but not honored - this pragmatic subset only ever opens a
|
|
20
|
+
* message for reading (no `RopSetProperties`/`RopSaveChangesMessage` write-back support yet).
|
|
21
|
+
*
|
|
22
|
+
* **Pragmatic success response**: `HasNamedProperties` is always `false` - a conservative hint value only, not
|
|
23
|
+
* a claim that named properties are unsupported (`RopGetPropertyIdsFromNames`/`PropertyResolvers.ts` do resolve
|
|
24
|
+
* them for a subsequently-opened Calendar item; a real client calls `RopGetPropertyIdsFromNames` unconditionally
|
|
25
|
+
* for a known Calendar message class regardless of this flag, so leaving it `false` doesn't block that path).
|
|
26
|
+
* The `SubjectPrefix`/`NormalizedSubject` `TypedString`s are "no prefix" + the item's plain subject/title (this
|
|
27
|
+
* data model has no separate prefix/normalized-subject split the way real Exchange does), and the recipient
|
|
28
|
+
* table (`RecipientCount`/`ColumnCount`/`RecipientColumns`/`RowCount`/`RecipientRows`) is always empty - reading
|
|
29
|
+
* a message's `To`/`Cc` list is a documented gap in this pass, not silently wrong data.
|
|
30
|
+
*
|
|
31
|
+
* @author Jean-Philippe Steinmetz
|
|
32
|
+
*/
|
|
33
|
+
export class RopOpenMessageHandler {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.ropId = ROP_ID_OPEN_MESSAGE;
|
|
36
|
+
}
|
|
37
|
+
async handle(reader, writer, context) {
|
|
38
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
39
|
+
reader.readUInt8(); // InputHandleIndex - the folder/logon handle this open is performed against; not
|
|
40
|
+
// separately validated, matching RopOpenFolderHandler's own treatment of its input handle.
|
|
41
|
+
const outputHandleIndex = reader.readUInt8();
|
|
42
|
+
reader.readUInt16LE(); // CodePageId - this pragmatic subset always encodes strings as UTF-16LE/UTF-8 directly
|
|
43
|
+
reader.readBigUInt64LE(); // FolderId - the message's expected parent folder; not cross-checked, see class doc comment
|
|
44
|
+
reader.readUInt8(); // OpenModeFlags - read-only opens only in this pragmatic subset
|
|
45
|
+
const messageId = reader.readBigUInt64LE();
|
|
46
|
+
const target = context.session.messageIds[messageId.toString()];
|
|
47
|
+
if (!target) {
|
|
48
|
+
writer.writeUInt8(ROP_ID_OPEN_MESSAGE);
|
|
49
|
+
writer.writeUInt8(outputHandleIndex);
|
|
50
|
+
writer.writeUInt32LE(ERROR_NOT_FOUND);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// A calendar/contact/task item's "subject" is its title/display name (PidTagSubject, the same tag a
|
|
54
|
+
// Message uses) - see CalendarEventTarget.ts's own doc comment for why no separate MID-registry
|
|
55
|
+
// mechanism is needed for any of these target kinds.
|
|
56
|
+
let subject;
|
|
57
|
+
if (target.startsWith("calendarEvent:")) {
|
|
58
|
+
subject = (await resolveCalendarEventInfo(target, context.calendarEventRepo)).title;
|
|
59
|
+
}
|
|
60
|
+
else if (target.startsWith("contact:")) {
|
|
61
|
+
subject = context.contactRepo ? (await resolveContactInfo(target, context.contactRepo)).displayName : "";
|
|
62
|
+
}
|
|
63
|
+
else if (target.startsWith("task:")) {
|
|
64
|
+
subject = context.taskRepo ? (await resolveTaskInfo(target, context.taskRepo)).title : "";
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
subject = (await resolveMessageInfo(target, context.messageRepo)).subject;
|
|
68
|
+
}
|
|
69
|
+
context.session.handles[outputHandleIndex] = { type: "message", entityUid: target };
|
|
70
|
+
writer.writeUInt8(ROP_ID_OPEN_MESSAGE);
|
|
71
|
+
writer.writeUInt8(outputHandleIndex);
|
|
72
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
73
|
+
writer.writeUInt8(0); // HasNamedProperties - conservative fixed value, see class doc comment
|
|
74
|
+
writeTypedString(writer, undefined); // SubjectPrefix - no prefix/normalized-subject split in this data model
|
|
75
|
+
writeTypedString(writer, subject); // NormalizedSubject
|
|
76
|
+
writer.writeUInt16LE(0); // RecipientCount - reading recipients is a documented gap, see class doc comment
|
|
77
|
+
writer.writeUInt16LE(0); // ColumnCount
|
|
78
|
+
writer.writeUInt8(0); // RowCount
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=RopOpenMessageHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopOpenMessageHandler.js","sourceRoot":"","sources":["../../../src/rop/RopOpenMessageHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { PropertyType, readPropertyTag } from "../codec/PropertyValue.js";
|
|
2
|
+
import { PID_TAG_BODY, resolveMessageBodyBytes } from "./MessageBodyStream.js";
|
|
3
|
+
const ROP_ID_OPEN_STREAM = 0x2b;
|
|
4
|
+
/** `[MS-OXCPRPT]` §2.2.14.1 `OpenModeFlags` values - `ReadOnly` opens the existing value for reading;
|
|
5
|
+
* `ReadWrite`/`Create` both open for writing (`Create` additionally discards any existing value first, "the
|
|
6
|
+
* mode required for a property that has not been set" per the spec - always true for a fresh
|
|
7
|
+
* `RopCreateMessage` draft's body, which is the only write-mode case this pragmatic subset ever produces). */
|
|
8
|
+
const OPEN_MODE_READ_ONLY = 0x00;
|
|
9
|
+
/** The well-known MAPI HRESULT `MAPI_E_NOT_FOUND`, reused for "the referenced handle isn't a message, or the
|
|
10
|
+
* requested property isn't one this pragmatic subset can stream" - the same constant `RopOpenFolderHandler`
|
|
11
|
+
* uses for its own analogous lookup-miss case. */
|
|
12
|
+
const ERROR_NOT_FOUND = 0x8004010f;
|
|
13
|
+
/**
|
|
14
|
+
* `RopOpenStream` (`[MS-OXCPRPT]`/`[MS-OXCROPS]`): opens a property for streaming access, producing a new
|
|
15
|
+
* `"stream"` Server object handle `RopReadStream`/`RopWriteStream` reads/writes by `InputHandleIndex`.
|
|
16
|
+
* **Pragmatic subset**: only `PidTagBody` (`PtypString`, the plain-text message body) on a `"message"` handle
|
|
17
|
+
* is supported - a real client also streams `PidTagHtml`/`PidTagRtfCompressed` (rich body formats) and
|
|
18
|
+
* folder/attachment binary properties, none of which this pass implements; requesting any other `PropertyTag`,
|
|
19
|
+
* or opening a stream against a non-message handle, fails with `MAPI_E_NOT_FOUND` rather than succeeding with
|
|
20
|
+
* wrong data.
|
|
21
|
+
*
|
|
22
|
+
* `OpenModeFlags` (`[MS-OXCPRPT]` §2.2.14.1) selects which of two independent code paths runs:
|
|
23
|
+
* - `ReadOnly` (`0x00`): the step-7 behavior - resolves the real, already-saved message's body via
|
|
24
|
+
* `MessageBodyStream.resolveMessageBodyBytes()` (`BlobStore` + `mailparser`) and reports its length as
|
|
25
|
+
* `StreamSize`, for `RopReadStream` to read back.
|
|
26
|
+
* - `ReadWrite`/`Create` (`0x01`/`0x02`): a **write-mode** stream, the step-8 addition compose/send needs to
|
|
27
|
+
* accept a message body via `RopWriteStream` - `StreamSize` is always `0` (matching `Create`'s "discards the
|
|
28
|
+
* existing value" semantics; this pragmatic subset never opens `ReadWrite` against a body that already has
|
|
29
|
+
* content) and the new handle's `writeTargetHandleIndex` remembers which message handle to write back into,
|
|
30
|
+
* the link `RopWriteStream`/`RopSaveChangesMessageHandler`/`RopSubmitMessageHandler` use to find it again.
|
|
31
|
+
*
|
|
32
|
+
* @author Jean-Philippe Steinmetz
|
|
33
|
+
*/
|
|
34
|
+
export class RopOpenStreamHandler {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.ropId = ROP_ID_OPEN_STREAM;
|
|
37
|
+
}
|
|
38
|
+
async handle(reader, writer, context) {
|
|
39
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
40
|
+
const inputHandleIndex = reader.readUInt8();
|
|
41
|
+
const outputHandleIndex = reader.readUInt8();
|
|
42
|
+
const propertyTag = readPropertyTag(reader);
|
|
43
|
+
const openModeFlags = reader.readUInt8();
|
|
44
|
+
const handle = context.session.handles[inputHandleIndex];
|
|
45
|
+
const isSupportedProperty = propertyTag.propertyId === PID_TAG_BODY && propertyTag.propertyType === PropertyType.PtypString;
|
|
46
|
+
if (!handle || handle.type !== "message" || !isSupportedProperty) {
|
|
47
|
+
writer.writeUInt8(ROP_ID_OPEN_STREAM);
|
|
48
|
+
writer.writeUInt8(outputHandleIndex);
|
|
49
|
+
writer.writeUInt32LE(ERROR_NOT_FOUND);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let streamSize;
|
|
53
|
+
if (openModeFlags === OPEN_MODE_READ_ONLY) {
|
|
54
|
+
const bytes = await resolveMessageBodyBytes(handle.entityUid, context.messageRepo, context.blobStore);
|
|
55
|
+
streamSize = bytes.length;
|
|
56
|
+
context.session.handles[outputHandleIndex] = {
|
|
57
|
+
type: "stream",
|
|
58
|
+
entityUid: handle.entityUid,
|
|
59
|
+
propertyId: propertyTag.propertyId,
|
|
60
|
+
propertyType: propertyTag.propertyType,
|
|
61
|
+
streamPosition: 0,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
streamSize = 0;
|
|
66
|
+
context.session.handles[outputHandleIndex] = {
|
|
67
|
+
type: "stream",
|
|
68
|
+
entityUid: handle.entityUid,
|
|
69
|
+
propertyId: propertyTag.propertyId,
|
|
70
|
+
propertyType: propertyTag.propertyType,
|
|
71
|
+
writeTargetHandleIndex: inputHandleIndex,
|
|
72
|
+
writeBufferBase64: "",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
writer.writeUInt8(ROP_ID_OPEN_STREAM);
|
|
76
|
+
writer.writeUInt8(outputHandleIndex);
|
|
77
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
78
|
+
writer.writeUInt32LE(streamSize); // StreamSize
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=RopOpenStreamHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopOpenStreamHandler.js","sourceRoot":"","sources":["../../../src/rop/RopOpenStreamHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAG/E,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC;;;8GAG8G;AAC9G,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC;;kDAEkD;AAClD,MAAM,eAAe,GAAG,UAAU,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,oBAAoB;IAAjC;QACoB,UAAK,GAAG,kBAAkB,CAAC;IA8C/C,CAAC;IA5CU,KAAK,CAAC,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QAC/E,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,gBAAgB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,MAAM,iBAAiB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,WAAW,GAAgB,eAAe,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,aAAa,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QAEjD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACzD,MAAM,mBAAmB,GAAG,WAAW,CAAC,UAAU,KAAK,YAAY,IAAI,WAAW,CAAC,YAAY,KAAK,YAAY,CAAC,UAAU,CAAC;QAC5H,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC/D,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;YACtC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;YACtC,OAAO;QACX,CAAC;QAED,IAAI,UAAkB,CAAC;QACvB,IAAI,aAAa,KAAK,mBAAmB,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,MAAM,uBAAuB,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YACtG,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC;YAC1B,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG;gBACzC,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,UAAU,EAAE,WAAW,CAAC,UAAU;gBAClC,YAAY,EAAE,WAAW,CAAC,YAAY;gBACtC,cAAc,EAAE,CAAC;aACpB,CAAC;QACN,CAAC;aAAM,CAAC;YACJ,UAAU,GAAG,CAAC,CAAC;YACf,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG;gBACzC,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,UAAU,EAAE,WAAW,CAAC,UAAU;gBAClC,YAAY,EAAE,WAAW,CAAC,YAAY;gBACtC,sBAAsB,EAAE,gBAAgB;gBACxC,iBAAiB,EAAE,EAAE;aACxB,CAAC;QACN,CAAC;QAED,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;QACtC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;QACjD,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa;IACnD,CAAC;CACJ"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { BufferWriter } from "../codec/BufferCursor.js";
|
|
6
|
+
import { writePropertyValue } from "../codec/PropertyValue.js";
|
|
7
|
+
import { resolvePropertyValues } from "./PropertyResolvers.js";
|
|
8
|
+
const ROP_ID_QUERY_ROWS = 0x15;
|
|
9
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a table (or
|
|
10
|
+
* doesn't exist)" - the same constant `RopGetHierarchyTableHandler` uses for its own analogous check. */
|
|
11
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
12
|
+
/** `BOOKMARK_END` (`[MS-OXCTABL]`'s `Origin` field) - this pragmatic subset has no separate seek/bookmark ROP
|
|
13
|
+
* support, so every response reports the cursor as having landed at the table's current end. */
|
|
14
|
+
const ORIGIN_BOOKMARK_END = 0x02;
|
|
15
|
+
/**
|
|
16
|
+
* `RopQueryRows` (`[MS-OXCTABL]`/`[MS-OXCROPS]`): fetches up to `RowCount` rows from an already-configured
|
|
17
|
+
* table (`RopGetHierarchyTable` + `RopSetColumns`), advancing the table's cursor. Confirmed field-by-field
|
|
18
|
+
* against `[MS-OXCTABL]`'s own real captured request/response byte example - including the response's
|
|
19
|
+
* `RowData` encoding (`PropertyRow`: a leading `Flags` byte, `0x00` for a `StandardPropertyRow` where every
|
|
20
|
+
* column is a plain `PropertyValue`, `0x01` for a `FlaggedPropertyRow` where each column gets its own
|
|
21
|
+
* `FlaggedPropertyValue` flag+optional-value). This pragmatic subset always emits `StandardPropertyRow`
|
|
22
|
+
* (`Flags = 0x00`) - every configured column always resolves to *some* value here (falling back to a
|
|
23
|
+
* type-appropriate zero/empty default for an unsupported property, never an error), so `FlaggedPropertyRow`'s
|
|
24
|
+
* per-column error signaling is never needed.
|
|
25
|
+
*
|
|
26
|
+
* Backward reads (`ForwardRead = FALSE`) and the `NoAdvance` flag are decoded (to advance the reader
|
|
27
|
+
* correctly) but not honored - this pragmatic subset's tables are simple forward-only cursors. Works generically
|
|
28
|
+
* against either a `RopGetHierarchyTable` (folder rows, `"folder:"`/`"virtual:"` targets) or a
|
|
29
|
+
* `RopGetContentsTable` (message rows, `"message:"` targets) handle - dispatching to `FolderTarget`'s or
|
|
30
|
+
* `MessageTarget`'s resolver and property-value mapping by row-target prefix, since a single table handle only
|
|
31
|
+
* ever holds one kind of row.
|
|
32
|
+
*
|
|
33
|
+
* **Known, deliberate performance limitation**: `resolvePropertyValues` still does one `repo.findOne()` per
|
|
34
|
+
* row (message/calendarEvent/contact/task) rather than a single batched fetch for the whole page - a genuine
|
|
35
|
+
* N+1 for a large `RowCount`. A real fix needs a batched "fetch all these uids" query, which this codebase's
|
|
36
|
+
* `RepoUtils<T>` abstraction doesn't expose a backend-agnostic way to express (Mongo's `$in` and TypeORM's
|
|
37
|
+
* `In()` aren't interchangeable at this call site, which has no idea which backend it's running against) - see
|
|
38
|
+
* `ResolutionCache` for the narrower, safe win this pass *did* make (the redundant *identical* whole-mailbox-
|
|
39
|
+
* folder-list and calendar-mailbox fetches that were happening once per row regardless of which distinct
|
|
40
|
+
* entity each row was, now happen at most once per call).
|
|
41
|
+
*
|
|
42
|
+
* @author Jean-Philippe Steinmetz
|
|
43
|
+
*/
|
|
44
|
+
export class RopQueryRowsHandler {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.ropId = ROP_ID_QUERY_ROWS;
|
|
47
|
+
}
|
|
48
|
+
async handle(reader, writer, context) {
|
|
49
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
50
|
+
const inputHandleIndex = reader.readUInt8();
|
|
51
|
+
reader.readUInt8(); // QueryRowsFlags - NoAdvance/EnablePackedBuffers not supported; always advances
|
|
52
|
+
reader.readUInt8(); // ForwardRead - backward reads not supported; always reads forward
|
|
53
|
+
const requestedCount = reader.readUInt16LE();
|
|
54
|
+
const table = context.session.handles[inputHandleIndex];
|
|
55
|
+
if (!table || table.type !== "table") {
|
|
56
|
+
writer.writeUInt8(ROP_ID_QUERY_ROWS);
|
|
57
|
+
writer.writeUInt8(inputHandleIndex);
|
|
58
|
+
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const rows = table.rows ?? [];
|
|
62
|
+
const cursor = table.cursor ?? 0;
|
|
63
|
+
const slice = rows.slice(cursor, cursor + requestedCount);
|
|
64
|
+
table.cursor = cursor + slice.length;
|
|
65
|
+
// Shared across every row this call resolves - see ResolutionCache's own doc comment for why: without
|
|
66
|
+
// it, a hierarchy table's `hasChildren` column (or a calendar table's organizer/attendee resolution)
|
|
67
|
+
// would re-fetch the same whole-mailbox folder list/mailbox record once per row instead of once per
|
|
68
|
+
// call.
|
|
69
|
+
const cache = {};
|
|
70
|
+
const rowBuffers = [];
|
|
71
|
+
for (const target of slice) {
|
|
72
|
+
rowBuffers.push(await this.buildRow(context, target, table.columns ?? [], cache));
|
|
73
|
+
}
|
|
74
|
+
writer.writeUInt8(ROP_ID_QUERY_ROWS);
|
|
75
|
+
writer.writeUInt8(inputHandleIndex);
|
|
76
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
77
|
+
writer.writeUInt8(ORIGIN_BOOKMARK_END);
|
|
78
|
+
writer.writeUInt16LE(slice.length);
|
|
79
|
+
for (const rowBuffer of rowBuffers) {
|
|
80
|
+
writer.writeBytes(rowBuffer);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async buildRow(context, target, columns, cache) {
|
|
84
|
+
const writer = new BufferWriter();
|
|
85
|
+
writer.writeUInt8(0x00); // Flags - StandardPropertyRow, see class doc comment
|
|
86
|
+
const values = await resolvePropertyValues(target, columns, context, cache);
|
|
87
|
+
columns.forEach((column, index) => writePropertyValue(writer, column.propertyType, values[index]));
|
|
88
|
+
return writer.toBuffer();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=RopQueryRowsHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopQueryRowsHandler.js","sourceRoot":"","sources":["../../../src/rop/RopQueryRowsHandler.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAgB,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAmB,MAAM,wBAAwB,CAAC;AAGhF,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAE/B;yGACyG;AACzG,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAExC;gGACgG;AAChG,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,OAAO,mBAAmB;IAAhC;QACoB,UAAK,GAAG,iBAAiB,CAAC;IAsD9C,CAAC;IApDU,KAAK,CAAC,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QAC/E,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,gBAAgB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,gFAAgF;QACpG,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,mEAAmE;QACvF,MAAM,cAAc,GAAW,MAAM,CAAC,YAAY,EAAE,CAAC;QAErD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACxD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACnC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;YACpC,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;YAC3C,OAAO;QACX,CAAC;QAED,MAAM,IAAI,GAAa,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;QACxC,MAAM,MAAM,GAAW,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;QACzC,MAAM,KAAK,GAAa,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC;QACpE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAErC,sGAAsG;QACtG,qGAAqG;QACrG,oGAAoG;QACpG,QAAQ;QACR,MAAM,KAAK,GAAoB,EAAE,CAAC;QAClC,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;YACzB,UAAU,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;QACtF,CAAC;QAED,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;QACpC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;QACjD,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;QACvC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACnC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACjC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QACjC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,QAAQ,CAClB,OAAmB,EACnB,MAAc,EACd,OAAuD,EACvD,KAAsB;QAEtB,MAAM,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAClC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,qDAAqD;QAC9E,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC5E,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACnG,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;CACJ"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { resolveMessageBodyBytes } from "./MessageBodyStream.js";
|
|
2
|
+
const ROP_ID_READ_STREAM = 0x2c;
|
|
3
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a stream (or
|
|
4
|
+
* doesn't exist)" - the same constant `RopGetHierarchyTableHandler` uses for its own analogous check. */
|
|
5
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
6
|
+
/** `0xBABE`: `ByteCount`'s sentinel value signaling "the real limit is the following 4-byte `MaximumByteCount`
|
|
7
|
+
* field instead" (`[MS-OXCPRPT]`'s own `RopReadStream` request-buffer page). */
|
|
8
|
+
const BYTE_COUNT_USE_MAXIMUM = 0xbabe;
|
|
9
|
+
/** The response's own `DataSize` field is a 16-bit count (`[MS-OXCPRPT]`'s own `RopReadStream` response-buffer
|
|
10
|
+
* page), so a single call can never actually return more than this many bytes - regardless of what `ByteCount`/
|
|
11
|
+
* `MaximumByteCount` (a full 32-bit value in the latter case) the client asked for. Returning fewer bytes than
|
|
12
|
+
* requested is spec-legal (the client just calls `RopReadStream` again for the rest, exactly how paging a large
|
|
13
|
+
* stream is meant to work) - `writeUInt16LE` throwing `RangeError` for an out-of-range value is not. */
|
|
14
|
+
const MAX_DATA_SIZE = 0xffff;
|
|
15
|
+
/**
|
|
16
|
+
* `RopReadStream` (`[MS-OXCPRPT]`/`[MS-OXCROPS]`): reads up to `ByteCount` (or `MaximumByteCount`, if
|
|
17
|
+
* `ByteCount` is the `0xBABE` sentinel) bytes from an already-opened stream (`RopOpenStream`), advancing the
|
|
18
|
+
* stream's read position - clamped to `MAX_DATA_SIZE` (`0xFFFF`) regardless of what the client asked for, since
|
|
19
|
+
* the response's own `DataSize` field can't carry more than that; a client wanting more of a large body simply
|
|
20
|
+
* calls `RopReadStream` again from the advanced `streamPosition`, the same paging behavior real Exchange uses.
|
|
21
|
+
* Unlike most other ROPs in this pragmatic subset, `[MS-OXCROPS]` documents only one combined response-buffer
|
|
22
|
+
* shape for this ROP (no separate Success/Failure pages) - `DataSize`/`Data` are always present, `DataSize = 0`
|
|
23
|
+
* (empty `Data`) standing in for the failure case rather than the fields being omitted entirely.
|
|
24
|
+
*
|
|
25
|
+
* @author Jean-Philippe Steinmetz
|
|
26
|
+
*/
|
|
27
|
+
export class RopReadStreamHandler {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.ropId = ROP_ID_READ_STREAM;
|
|
30
|
+
}
|
|
31
|
+
async handle(reader, writer, context) {
|
|
32
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
33
|
+
const inputHandleIndex = reader.readUInt8();
|
|
34
|
+
const byteCount = reader.readUInt16LE();
|
|
35
|
+
const requestedCount = byteCount === BYTE_COUNT_USE_MAXIMUM ? reader.readUInt32LE() : byteCount;
|
|
36
|
+
const handle = context.session.handles[inputHandleIndex];
|
|
37
|
+
if (!handle || handle.type !== "stream") {
|
|
38
|
+
writer.writeUInt8(ROP_ID_READ_STREAM);
|
|
39
|
+
writer.writeUInt8(inputHandleIndex);
|
|
40
|
+
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
41
|
+
writer.writeUInt16LE(0); // DataSize - see class doc comment on why this is still written on failure
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const bytes = await resolveMessageBodyBytes(handle.entityUid, context.messageRepo, context.blobStore);
|
|
45
|
+
const position = handle.streamPosition ?? 0;
|
|
46
|
+
const slice = bytes.subarray(position, position + Math.min(requestedCount, MAX_DATA_SIZE));
|
|
47
|
+
handle.streamPosition = position + slice.length;
|
|
48
|
+
writer.writeUInt8(ROP_ID_READ_STREAM);
|
|
49
|
+
writer.writeUInt8(inputHandleIndex);
|
|
50
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
51
|
+
writer.writeUInt16LE(slice.length); // DataSize
|
|
52
|
+
writer.writeBytes(slice);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=RopReadStreamHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopReadStreamHandler.js","sourceRoot":"","sources":["../../../src/rop/RopReadStreamHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC;yGACyG;AACzG,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAExC;gFACgF;AAChF,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAEtC;;;;wGAIwG;AACxG,MAAM,aAAa,GAAG,MAAM,CAAC;AAE7B;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,oBAAoB;IAAjC;QACoB,UAAK,GAAG,kBAAkB,CAAC;IA4B/C,CAAC;IA1BU,KAAK,CAAC,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QAC/E,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,gBAAgB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,MAAM,SAAS,GAAW,MAAM,CAAC,YAAY,EAAE,CAAC;QAChD,MAAM,cAAc,GAAW,SAAS,KAAK,sBAAsB,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAExG,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtC,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;YACtC,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;YACpC,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;YAC3C,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,2EAA2E;YACpG,OAAO;QACX,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,uBAAuB,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QACtG,MAAM,QAAQ,GAAG,MAAM,CAAC,cAAc,IAAI,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC,CAAC;QAC3F,MAAM,CAAC,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC;QAEhD,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;QACtC,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;QACpC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;QACjD,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW;QAC/C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;CACJ"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const ROP_ID_RELEASE = 0x01;
|
|
2
|
+
/**
|
|
3
|
+
* `RopRelease` (`[MS-OXCROPS]`): releases a Server object handle. Confirmed against the spec's own real
|
|
4
|
+
* 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
|
|
5
|
+
* (`RopId`/`LogonId`/`InputHandleIndex`) and, uniquely among ROPs, **produces no response entry at all** - the
|
|
6
|
+
* server MUST NOT write anything to the output buffer for it, so `handle()` never touches `writer`.
|
|
7
|
+
*
|
|
8
|
+
* @author Jean-Philippe Steinmetz
|
|
9
|
+
*/
|
|
10
|
+
export class RopReleaseHandler {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.ropId = ROP_ID_RELEASE;
|
|
13
|
+
}
|
|
14
|
+
handle(reader, _writer, context) {
|
|
15
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track per-logon-id state separately
|
|
16
|
+
const inputHandleIndex = reader.readUInt8();
|
|
17
|
+
delete context.session.handles[inputHandleIndex];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=RopReleaseHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopReleaseHandler.js","sourceRoot":"","sources":["../../../src/rop/RopReleaseHandler.ts"],"names":[],"mappings":"AAOA,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAiB;IAA9B;QACoB,UAAK,GAAG,cAAc,CAAC;IAO3C,CAAC;IALU,MAAM,CAAC,MAAoB,EAAE,OAAqB,EAAE,OAAmB;QAC1E,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,8EAA8E;QAClG,MAAM,gBAAgB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACrD,CAAC;CACJ"}
|