@rapidmx/mapi-plugin 1.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +382 -0
- package/README.md +60 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js +489 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -0
- package/dist/lib/BaseMapiNspiRoute.js +114 -0
- package/dist/lib/BaseMapiNspiRoute.js.map +1 -0
- package/dist/lib/MapiSessionManager.js +475 -0
- package/dist/lib/MapiSessionManager.js.map +1 -0
- package/dist/lib/RegexPatternUtils.js +30 -0
- package/dist/lib/RegexPatternUtils.js.map +1 -0
- package/dist/lib/RopDispatcher.js +145 -0
- package/dist/lib/RopDispatcher.js.map +1 -0
- package/dist/lib/codec/AppointmentRecurrence.js +315 -0
- package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
- package/dist/lib/codec/BufferCursor.js +242 -0
- package/dist/lib/codec/BufferCursor.js.map +1 -0
- package/dist/lib/codec/GlobalObjectId.js +98 -0
- package/dist/lib/codec/GlobalObjectId.js.map +1 -0
- package/dist/lib/codec/MapiGuid.js +41 -0
- package/dist/lib/codec/MapiGuid.js.map +1 -0
- package/dist/lib/codec/MapiTimeZone.js +126 -0
- package/dist/lib/codec/MapiTimeZone.js.map +1 -0
- package/dist/lib/codec/PropertyValue.js +240 -0
- package/dist/lib/codec/PropertyValue.js.map +1 -0
- package/dist/lib/codec/RopBuffer.js +38 -0
- package/dist/lib/codec/RopBuffer.js.map +1 -0
- package/dist/lib/codec/TypedString.js +54 -0
- package/dist/lib/codec/TypedString.js.map +1 -0
- package/dist/lib/index.js +99 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +85 -0
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
- package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
- package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
- package/dist/lib/mongo/index.js +7 -0
- package/dist/lib/mongo/index.js.map +1 -0
- package/dist/lib/mongo.js +6 -0
- package/dist/lib/mongo.js.map +1 -0
- package/dist/lib/nspi/NspiBindHandler.js +54 -0
- package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
- package/dist/lib/nspi/NspiCodec.js +106 -0
- package/dist/lib/nspi/NspiCodec.js.map +1 -0
- package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
- package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
- package/dist/lib/rop/AddressList.js +114 -0
- package/dist/lib/rop/AddressList.js.map +1 -0
- package/dist/lib/rop/CalendarEventTarget.js +30 -0
- package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
- package/dist/lib/rop/CalendarNamedProperties.js +57 -0
- package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
- package/dist/lib/rop/ContactTarget.js +19 -0
- package/dist/lib/rop/ContactTarget.js.map +1 -0
- package/dist/lib/rop/ContentsTable.js +60 -0
- package/dist/lib/rop/ContentsTable.js.map +1 -0
- package/dist/lib/rop/ExecuteBudget.js +82 -0
- package/dist/lib/rop/ExecuteBudget.js.map +1 -0
- package/dist/lib/rop/FastTransferStream.js +195 -0
- package/dist/lib/rop/FastTransferStream.js.map +1 -0
- package/dist/lib/rop/FolderTarget.js +118 -0
- package/dist/lib/rop/FolderTarget.js.map +1 -0
- package/dist/lib/rop/HandleDataCache.js +412 -0
- package/dist/lib/rop/HandleDataCache.js.map +1 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js +241 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
- package/dist/lib/rop/MessageBodyStream.js +68 -0
- package/dist/lib/rop/MessageBodyStream.js.map +1 -0
- package/dist/lib/rop/MessageTarget.js +57 -0
- package/dist/lib/rop/MessageTarget.js.map +1 -0
- package/dist/lib/rop/NamedPropertyRegistry.js +69 -0
- package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
- package/dist/lib/rop/PropertyResolvers.js +353 -0
- package/dist/lib/rop/PropertyResolvers.js.map +1 -0
- package/dist/lib/rop/RepoPaging.js +54 -0
- package/dist/lib/rop/RepoPaging.js.map +1 -0
- package/dist/lib/rop/RestapiRules.js +52 -0
- package/dist/lib/rop/RestapiRules.js.map +1 -0
- package/dist/lib/rop/RopCreateMessageHandler.js +55 -0
- package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js +174 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js +110 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +60 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +76 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js +55 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js +42 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +56 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
- package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
- package/dist/lib/rop/RopHandler.js +6 -0
- package/dist/lib/rop/RopHandler.js.map +1 -0
- package/dist/lib/rop/RopLogonHandler.js +135 -0
- package/dist/lib/rop/RopLogonHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenFolderHandler.js +48 -0
- package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenMessageHandler.js +83 -0
- package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenStreamHandler.js +84 -0
- package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopQueryRowsHandler.js +120 -0
- package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
- package/dist/lib/rop/RopReadStreamHandler.js +73 -0
- package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopReleaseHandler.js +23 -0
- package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js +269 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopSetColumnsHandler.js +50 -0
- package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js +173 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js +342 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopWriteStreamHandler.js +72 -0
- package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
- package/dist/lib/rop/TaskNamedProperties.js +29 -0
- package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
- package/dist/lib/rop/TaskTarget.js +15 -0
- package/dist/lib/rop/TaskTarget.js.map +1 -0
- package/dist/lib/rop/TransportSend.js +16 -0
- package/dist/lib/rop/TransportSend.js.map +1 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js +85 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
- package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
- package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
- package/dist/lib/sql/index.js +7 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql.js +6 -0
- package/dist/lib/sql.js.map +1 -0
- package/dist/types/BaseMapiEmsmdbRoute.d.ts +139 -0
- package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
- package/dist/types/MapiSessionManager.d.ts +285 -0
- package/dist/types/RegexPatternUtils.d.ts +14 -0
- package/dist/types/RopDispatcher.d.ts +53 -0
- package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
- package/dist/types/codec/BufferCursor.d.ts +92 -0
- package/dist/types/codec/GlobalObjectId.d.ts +15 -0
- package/dist/types/codec/MapiGuid.d.ts +6 -0
- package/dist/types/codec/MapiTimeZone.d.ts +7 -0
- package/dist/types/codec/PropertyValue.d.ts +87 -0
- package/dist/types/codec/RopBuffer.d.ts +32 -0
- package/dist/types/codec/TypedString.d.ts +17 -0
- package/dist/types/index.d.ts +94 -0
- package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +20 -0
- package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
- package/dist/types/mongo/index.d.ts +2 -0
- package/dist/types/mongo.d.ts +1 -0
- package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
- package/dist/types/nspi/NspiCodec.d.ts +59 -0
- package/dist/types/nspi/NspiGetMatchesHandler.d.ts +26 -0
- package/dist/types/rop/AddressList.d.ts +48 -0
- package/dist/types/rop/CalendarEventTarget.d.ts +33 -0
- package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
- package/dist/types/rop/ContactTarget.d.ts +29 -0
- package/dist/types/rop/ContentsTable.d.ts +16 -0
- package/dist/types/rop/ExecuteBudget.d.ts +48 -0
- package/dist/types/rop/FastTransferStream.d.ts +57 -0
- package/dist/types/rop/FolderTarget.d.ts +64 -0
- package/dist/types/rop/HandleDataCache.d.ts +145 -0
- package/dist/types/rop/MeetingMessageClassHandler.d.ts +30 -0
- package/dist/types/rop/MessageBodyStream.d.ts +35 -0
- package/dist/types/rop/MessageTarget.d.ts +45 -0
- package/dist/types/rop/NamedPropertyRegistry.d.ts +50 -0
- package/dist/types/rop/PropertyResolvers.d.ts +86 -0
- package/dist/types/rop/RepoPaging.d.ts +30 -0
- package/dist/types/rop/RestapiRules.d.ts +32 -0
- package/dist/types/rop/RopCreateMessageHandler.d.ts +24 -0
- package/dist/types/rop/RopDeleteFolderHandler.d.ts +53 -0
- package/dist/types/rop/RopDeleteMessagesHandler.d.ts +43 -0
- package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +25 -0
- package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +25 -0
- package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +22 -0
- package/dist/types/rop/RopGetContentsTableHandler.d.ts +25 -0
- package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +18 -0
- package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +26 -0
- package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
- package/dist/types/rop/RopHandler.d.ts +106 -0
- package/dist/types/rop/RopLogonHandler.d.ts +31 -0
- package/dist/types/rop/RopOpenFolderHandler.d.ts +20 -0
- package/dist/types/rop/RopOpenMessageHandler.d.ts +28 -0
- package/dist/types/rop/RopOpenStreamHandler.d.ts +28 -0
- package/dist/types/rop/RopQueryRowsHandler.d.ts +43 -0
- package/dist/types/rop/RopReadStreamHandler.d.ts +20 -0
- package/dist/types/rop/RopReleaseHandler.d.ts +15 -0
- package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +59 -0
- package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
- package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
- package/dist/types/rop/RopSubmitMessageHandler.d.ts +80 -0
- package/dist/types/rop/RopWriteStreamHandler.d.ts +31 -0
- package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
- package/dist/types/rop/TaskTarget.d.ts +24 -0
- package/dist/types/rop/TransportSend.d.ts +10 -0
- package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +20 -0
- package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
- package/dist/types/sql/index.d.ts +2 -0
- package/dist/types/sql.d.ts +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { auditMessageDelete } from "./RopDeleteMessagesHandler.js";
|
|
2
|
+
import { findAllCapped, findPage, MAX_COLLECTION_ROWS } from "./RepoPaging.js";
|
|
3
|
+
const ROP_ID_DELETE_FOLDER = 0x1d;
|
|
4
|
+
/** The well-known MAPI HRESULT `MAPI_E_NOT_FOUND`, reused for an unrecognized `FolderId` - the same constant
|
|
5
|
+
* `RopOpenFolderHandler` uses for its own analogous lookup miss. */
|
|
6
|
+
const ERROR_NOT_FOUND = 0x8004010f;
|
|
7
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for both "the referenced handle isn't a folder"
|
|
8
|
+
* and "the folder has content/subfolders but the matching cascade flag wasn't set" - the same constant reused
|
|
9
|
+
* throughout this pragmatic subset as a generic "can't do this" signal, not a claim of exact per-condition
|
|
10
|
+
* `[MS-OXCFOLD]` return-value-table parity (the real spec's own `ecFolderNotEmpty` isn't separately modeled). */
|
|
11
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
12
|
+
/** `MAPI_E_TOO_COMPLEX`, for a subfolder tree deeper than `MAX_FOLDER_DEPTH` or larger than `MAX_FOLDERS_PER_DELETE`. */
|
|
13
|
+
const ERROR_TOO_COMPLEX = 0x80040117;
|
|
14
|
+
/** `DeleteFolderFlags` bits (`[MS-OXCFOLD]` §2.2.1.3.1). `DELETE_HARD_DELETE` (`0x10`) is deliberately not
|
|
15
|
+
* honored - see the class doc comment. */
|
|
16
|
+
const DEL_MESSAGES = 0x01;
|
|
17
|
+
const DEL_FOLDERS = 0x04;
|
|
18
|
+
/** The deepest subfolder nesting a recursive delete walks. */
|
|
19
|
+
export const MAX_FOLDER_DEPTH = 32;
|
|
20
|
+
/** The most folders one recursive delete removes. */
|
|
21
|
+
export const MAX_FOLDERS_PER_DELETE = 1000;
|
|
22
|
+
/** The most items (messages, events, contacts, tasks) one `RopDeleteFolder` deletes. Past this the ROP stops,
|
|
23
|
+
* reports `PartialCompletion`, and leaves the folders that still hold items in place; the client can retry. */
|
|
24
|
+
export const MAX_ITEMS_PER_DELETE = MAX_COLLECTION_ROWS;
|
|
25
|
+
const UID_SORT = { uid: "ASC" };
|
|
26
|
+
/**
|
|
27
|
+
* `RopDeleteFolder` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`, RopId `0x1D`): deletes a folder by `FolderId`, via
|
|
28
|
+
* `RecoverableRepoUtils.delete()` - see `RopDeleteMessagesHandler.ts`'s own doc comment for why that matters (EAS's
|
|
29
|
+
* watermark-based incremental delete detection depends on it).
|
|
30
|
+
*
|
|
31
|
+
* **Real spec semantics honored**: `RopDeleteFolder` only operates on an empty folder by default - `DEL_MESSAGES`
|
|
32
|
+
* must be set to also delete the folder's own messages/calendar events/contacts/tasks, and `DEL_FOLDERS` to also
|
|
33
|
+
* delete (recursively) its subfolders; a non-empty folder deleted without the matching flag fails rather than
|
|
34
|
+
* silently cascading.
|
|
35
|
+
*
|
|
36
|
+
* **Always a soft delete.** `DELETE_HARD_DELETE` is ignored. A permanent delete has to go through restapi's legal
|
|
37
|
+
* hold check first (the REST routes call `LegalHoldUtils.assertNotOnLegalHold()` for `purge=true`), and that helper
|
|
38
|
+
* isn't part of `@rapidmx/restapi`'s public exports. A soft-deleted item stays recoverable and discoverable, which is
|
|
39
|
+
* exactly what a hold requires, so MAPI never purges. Each deleted message is audited as `MESSAGE_DELETE`, like the
|
|
40
|
+
* REST message route.
|
|
41
|
+
*
|
|
42
|
+
* **Bounded.** The subfolder tree is walked iteratively with a visited set (a corrupt `parentFolderUid` cycle
|
|
43
|
+
* can't loop), at most `MAX_FOLDER_DEPTH` deep and `MAX_FOLDERS_PER_DELETE` wide, and only through folders in the
|
|
44
|
+
* caller's own mailbox. Items are fetched in explicit, paged queries (a bare `find()` stops at 100 rows, which used
|
|
45
|
+
* to leave items behind in a folder that was then deleted), up to `MAX_ITEMS_PER_DELETE`. Every query and delete is
|
|
46
|
+
* charged to the request's `ExecuteBudget` before it runs, so repeating this ROP can't multiply the walk unbounded.
|
|
47
|
+
*
|
|
48
|
+
* The request's own `InputHandleIndex` (nominally the *parent* folder of the one being deleted) is validated to
|
|
49
|
+
* be a real, open folder handle but not cross-checked against the target folder's actual `parentFolderUid` -
|
|
50
|
+
* the same pragmatic simplification `RopOpenMessageHandler`'s own doc comment documents for its analogous
|
|
51
|
+
* `FolderId` field.
|
|
52
|
+
*
|
|
53
|
+
* @author Jean-Philippe Steinmetz
|
|
54
|
+
*/
|
|
55
|
+
export class RopDeleteFolderHandler {
|
|
56
|
+
constructor() {
|
|
57
|
+
this.ropId = ROP_ID_DELETE_FOLDER;
|
|
58
|
+
}
|
|
59
|
+
async handle(reader, writer, context) {
|
|
60
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
61
|
+
const inputHandleIndex = reader.readUInt8();
|
|
62
|
+
const deleteFolderFlags = reader.readUInt8();
|
|
63
|
+
const folderId = reader.readBigUInt64LE();
|
|
64
|
+
const handle = context.session.handles[inputHandleIndex];
|
|
65
|
+
if (!handle || handle.type !== "folder") {
|
|
66
|
+
this.writeFailure(writer, inputHandleIndex, ERROR_INVALID_OBJECT);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const target = context.session.folderIds[folderId.toString()];
|
|
70
|
+
if (!target?.startsWith("folder:")) {
|
|
71
|
+
this.writeFailure(writer, inputHandleIndex, ERROR_NOT_FOUND);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const uid = target.slice("folder:".length);
|
|
75
|
+
if ((deleteFolderFlags & DEL_MESSAGES) === 0 && (await this.hasItems(uid, context))) {
|
|
76
|
+
this.writeFailure(writer, inputHandleIndex, ERROR_INVALID_OBJECT);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const childQuery = { parentFolderUid: uid, mailboxUid: context.mailboxUid };
|
|
80
|
+
if ((deleteFolderFlags & DEL_FOLDERS) === 0 && (await findPage(context.folderRepo, childQuery, UID_SORT, 0, 1, context.budget)).length > 0) {
|
|
81
|
+
this.writeFailure(writer, inputHandleIndex, ERROR_INVALID_OBJECT);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const folders = await this.collectSubtree(uid, context);
|
|
85
|
+
if (!folders) {
|
|
86
|
+
this.writeFailure(writer, inputHandleIndex, ERROR_TOO_COMPLEX);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const budget = { remaining: MAX_ITEMS_PER_DELETE };
|
|
90
|
+
let partialCompletion = false;
|
|
91
|
+
for (const folderUid of folders) {
|
|
92
|
+
if (!(await this.deleteItems(folderUid, context, budget))) {
|
|
93
|
+
partialCompletion = true;
|
|
94
|
+
// Every folder deleted below (this loop's earlier iterations) is gone outright - nothing to refresh.
|
|
95
|
+
// This one is different: it survives (the budget ran out before `deleteItems` could empty it, so it's
|
|
96
|
+
// never reached below), but `itemRepos()` always drains `context.messageRepo` for a folder before any
|
|
97
|
+
// other repo, so some or all of its messages may already be gone - its stored counts must be
|
|
98
|
+
// refreshed rather than left counting messages that no longer exist.
|
|
99
|
+
await context.notifyFolderCounts?.([folderUid]);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
context.budget?.chargeQueries();
|
|
103
|
+
await context.folderRepo.delete(folderUid, { ignoreACL: true });
|
|
104
|
+
}
|
|
105
|
+
writer.writeUInt8(ROP_ID_DELETE_FOLDER);
|
|
106
|
+
writer.writeUInt8(inputHandleIndex);
|
|
107
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
108
|
+
writer.writeUInt8(partialCompletion ? 1 : 0); // PartialCompletion
|
|
109
|
+
}
|
|
110
|
+
writeFailure(writer, inputHandleIndex, returnValue) {
|
|
111
|
+
writer.writeUInt8(ROP_ID_DELETE_FOLDER);
|
|
112
|
+
writer.writeUInt8(inputHandleIndex);
|
|
113
|
+
writer.writeUInt32LE(returnValue);
|
|
114
|
+
}
|
|
115
|
+
/** The repos a folder's items can live in, skipping the optional ones the context doesn't have. */
|
|
116
|
+
itemRepos(context) {
|
|
117
|
+
return [context.messageRepo, context.calendarEventRepo, context.contactRepo, context.taskRepo].filter((repo) => repo !== undefined);
|
|
118
|
+
}
|
|
119
|
+
async hasItems(folderUid, context) {
|
|
120
|
+
for (const repo of this.itemRepos(context)) {
|
|
121
|
+
if ((await findPage(repo, { folderUid, mailboxUid: context.mailboxUid }, UID_SORT, 0, 1, context.budget)).length > 0) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* `rootUid` and every folder below it in the caller's mailbox, children before their parents, or `undefined` when
|
|
129
|
+
* the tree is deeper than `MAX_FOLDER_DEPTH` or has more than `MAX_FOLDERS_PER_DELETE` folders.
|
|
130
|
+
*/
|
|
131
|
+
async collectSubtree(rootUid, context) {
|
|
132
|
+
const visited = new Set([rootUid]);
|
|
133
|
+
const preOrder = [];
|
|
134
|
+
const stack = [{ uid: rootUid, depth: 0 }];
|
|
135
|
+
while (stack.length > 0) {
|
|
136
|
+
const { uid, depth } = stack.pop();
|
|
137
|
+
preOrder.push(uid);
|
|
138
|
+
const { items: children, truncated } = await findAllCapped(context.folderRepo, { parentFolderUid: uid, mailboxUid: context.mailboxUid }, UID_SORT, MAX_FOLDERS_PER_DELETE, context.budget);
|
|
139
|
+
if (truncated) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
for (const child of children) {
|
|
143
|
+
if (visited.has(child.uid)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (depth + 1 > MAX_FOLDER_DEPTH || visited.size >= MAX_FOLDERS_PER_DELETE) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
visited.add(child.uid);
|
|
150
|
+
stack.push({ uid: child.uid, depth: depth + 1 });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return preOrder.reverse();
|
|
154
|
+
}
|
|
155
|
+
/** Soft-deletes `folderUid`'s items within `budget`. Returns `false` if items remain once the budget is spent. */
|
|
156
|
+
async deleteItems(folderUid, context, budget) {
|
|
157
|
+
for (const repo of this.itemRepos(context)) {
|
|
158
|
+
const { items, truncated } = await findAllCapped(repo, { folderUid, mailboxUid: context.mailboxUid }, UID_SORT, budget.remaining, context.budget);
|
|
159
|
+
for (const item of items) {
|
|
160
|
+
context.budget?.chargeQueries();
|
|
161
|
+
await repo.delete(item.uid, { ignoreACL: true });
|
|
162
|
+
if (repo === context.messageRepo) {
|
|
163
|
+
await auditMessageDelete(context, item);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
budget.remaining -= items.length;
|
|
167
|
+
if (truncated) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=RopDeleteFolderHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopDeleteFolderHandler.js","sourceRoot":"","sources":["../../../src/rop/RopDeleteFolderHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { AuditAction } from "@rapidmx/restapi";
|
|
2
|
+
const ROP_ID_DELETE_MESSAGES = 0x1e;
|
|
3
|
+
/** Audits a message deleted through MAPI with the same `MESSAGE_DELETE` entry the REST message route records.
|
|
4
|
+
* Charged to `context.budget` like the `findOne`/`delete` calls around every call site of this function - a real
|
|
5
|
+
* DB `create()` whenever `context.audit` is configured (the case in every real deployment), so leaving it
|
|
6
|
+
* uncharged undercounted this loop's real DB work by a third. No-op (and no charge - there's no DB work to
|
|
7
|
+
* account for) when `context.audit` is absent, the same as an optional-repo item kind this pragmatic subset
|
|
8
|
+
* already skips charging for. */
|
|
9
|
+
export async function auditMessageDelete(context, message) {
|
|
10
|
+
if (!context.audit) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
context.budget?.chargeQueries();
|
|
14
|
+
await context.audit({
|
|
15
|
+
action: AuditAction.MESSAGE_DELETE,
|
|
16
|
+
targetType: "Message",
|
|
17
|
+
targetUid: message.uid,
|
|
18
|
+
mailboxUid: message.mailboxUid,
|
|
19
|
+
details: { subject: message.subject, folderUid: message.folderUid },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder (or
|
|
23
|
+
* doesn't exist)" - the same constant `RopGetContentsTableHandler` uses for its own analogous check. */
|
|
24
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
25
|
+
/**
|
|
26
|
+
* `RopDeleteMessages` (`[MS-OXCMSG]`/`[MS-OXCROPS]`, RopId `0x1E`): deletes one or more messages by `MessageId`
|
|
27
|
+
* from an already-open folder handle. Reuses `RecoverableRepoUtils.delete()` exactly as-built in Phase 2 - the
|
|
28
|
+
* same soft-delete-with-watermark-bump machinery EAS's own `Sync`/`FolderSync` incremental-delete detection
|
|
29
|
+
* already relies on (see `BaseMapiEmsmdbRoute.ts`'s own doc comment for why `folderRepo`/`messageRepo`/
|
|
30
|
+
* `calendarEventRepo` are built as `RecoverableRepoUtils`, not plain `RepoUtils`) - no new deletion-tracking
|
|
31
|
+
* code, zero duplication.
|
|
32
|
+
*
|
|
33
|
+
* A `MessageId` resolves via `session.messageIds` (`MessageTarget.assignOrGetMid`'s own registry, already
|
|
34
|
+
* generic over both `"message:<uid>"` and `"calendarEvent:<uid>"` target strings, since a Calendar item is
|
|
35
|
+
* itself a Message object on the wire - see `CalendarEventTarget.ts`'s own doc comment) directly to the entity
|
|
36
|
+
* to delete, the same "the session already knows what this small integer refers to" design every other MID-
|
|
37
|
+
* consuming ROP in this pragmatic subset already uses (`RopOpenMessage`, `RopSaveChangesMessage`) - the
|
|
38
|
+
* request's own `InputHandleIndex` (nominally the messages' shared parent folder) is validated to be a real,
|
|
39
|
+
* open folder handle but not cross-checked against each message's actual `folderUid`, the same pragmatic
|
|
40
|
+
* simplification `RopOpenMessageHandler`'s own doc comment documents for its analogous `FolderId` field.
|
|
41
|
+
*
|
|
42
|
+
* `WantAsynchronous` (`RopProgress`-reported async completion) and `NotifyNonRead` (non-read-receipt generation
|
|
43
|
+
* to the original sender) are decoded to advance the reader correctly but not honored - this pragmatic subset
|
|
44
|
+
* has no async-ROP or non-read-receipt machinery. `PartialCompletion` is reported `true` if any requested
|
|
45
|
+
* `MessageId` didn't resolve to a real message/calendar-event target - the rest are still deleted, matching
|
|
46
|
+
* this pragmatic subset's "don't fail the whole ROP over one stale reference" principle used throughout.
|
|
47
|
+
*
|
|
48
|
+
* @author Jean-Philippe Steinmetz
|
|
49
|
+
*/
|
|
50
|
+
export class RopDeleteMessagesHandler {
|
|
51
|
+
constructor() {
|
|
52
|
+
this.ropId = ROP_ID_DELETE_MESSAGES;
|
|
53
|
+
}
|
|
54
|
+
async handle(reader, writer, context) {
|
|
55
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
56
|
+
const inputHandleIndex = reader.readUInt8();
|
|
57
|
+
reader.readUInt8(); // WantAsynchronous - no RopProgress/async-ROP support in this pragmatic subset
|
|
58
|
+
reader.readUInt8(); // NotifyNonRead - no non-read-receipt generation support
|
|
59
|
+
const messageIdCount = reader.readUInt16LE();
|
|
60
|
+
const messageIds = [];
|
|
61
|
+
for (let i = 0; i < messageIdCount; i++) {
|
|
62
|
+
messageIds.push(reader.readBigUInt64LE());
|
|
63
|
+
}
|
|
64
|
+
const handle = context.session.handles[inputHandleIndex];
|
|
65
|
+
if (!handle || handle.type !== "folder") {
|
|
66
|
+
writer.writeUInt8(ROP_ID_DELETE_MESSAGES);
|
|
67
|
+
writer.writeUInt8(inputHandleIndex);
|
|
68
|
+
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
let partialCompletion = false;
|
|
72
|
+
// The folders that lost a message - refreshed once below, after the loop, rather than once per message (a
|
|
73
|
+
// client can name several `MessageId`s in one call, several of which are often the same folder).
|
|
74
|
+
const affectedFolderUids = new Set();
|
|
75
|
+
for (const messageId of messageIds) {
|
|
76
|
+
const target = context.session.messageIds[messageId.toString()];
|
|
77
|
+
if (target?.startsWith("message:")) {
|
|
78
|
+
const uid = target.slice("message:".length);
|
|
79
|
+
// Always fetched now (not just when `context.audit` is set): `notifyFolderCounts` below needs to know
|
|
80
|
+
// which folder this message was in, the same as `auditMessageDelete` already needed its subject.
|
|
81
|
+
// Charged like `resolveContentsKind`'s own single-row lookup and `RopDeleteFolderHandler.deleteItems()`'s
|
|
82
|
+
// own per-item delete - each is a real DB round trip, so a client can't repeat this ROP with a large
|
|
83
|
+
// `messageIdCount` to run unbounded queries against its own mailbox within one `Execute`.
|
|
84
|
+
context.budget?.chargeQueries();
|
|
85
|
+
const message = await context.messageRepo.findOne(uid, { ignoreACL: true });
|
|
86
|
+
context.budget?.chargeQueries();
|
|
87
|
+
await context.messageRepo.delete(uid, { ignoreACL: true });
|
|
88
|
+
if (message) {
|
|
89
|
+
affectedFolderUids.add(message.folderUid);
|
|
90
|
+
await auditMessageDelete(context, message);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (target?.startsWith("calendarEvent:")) {
|
|
94
|
+
context.budget?.chargeQueries();
|
|
95
|
+
await context.calendarEventRepo.delete(target.slice("calendarEvent:".length), { ignoreACL: true });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
partialCompletion = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Not `bumpSyncKey` - only adding a message to a folder does that (see `refreshFolderCounts()`'s own doc
|
|
102
|
+
// comment); a deletion doesn't, matching restapi's own `ScanQueueJob.processRecall()` deletion call site.
|
|
103
|
+
await context.notifyFolderCounts?.(affectedFolderUids);
|
|
104
|
+
writer.writeUInt8(ROP_ID_DELETE_MESSAGES);
|
|
105
|
+
writer.writeUInt8(inputHandleIndex);
|
|
106
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
107
|
+
writer.writeUInt8(partialCompletion ? 1 : 0); // PartialCompletion
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=RopDeleteMessagesHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopDeleteMessagesHandler.js","sourceRoot":"","sources":["../../../src/rop/RopDeleteMessagesHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readPropertyTagArray } from "../codec/PropertyValue.js";
|
|
2
|
+
import { openFastTransferHandle } from "./FastTransferStream.js";
|
|
3
|
+
const ROP_ID_COPY_PROPERTIES = 0x69;
|
|
4
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder or
|
|
5
|
+
* message (or doesn't exist)" - the same constant `RopGetPropertiesSpecificHandler` uses for its own analogous
|
|
6
|
+
* check. */
|
|
7
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
8
|
+
/** `MAPI_E_TOO_BIG`: more than `MAX_PROPERTY_TAG_COUNT` tags, or a stream past `MAX_FAST_TRANSFER_BYTES`. */
|
|
9
|
+
const ERROR_TOO_BIG = 0x80040305;
|
|
10
|
+
/**
|
|
11
|
+
* `RopFastTransferSourceCopyProperties` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x69`): the explicit-column
|
|
12
|
+
* sibling of `RopFastTransferSourceCopyTo` - `PropertyTags` here is the properties to **copy** (an include
|
|
13
|
+
* list, per `[MS-OXCROPS]`'s own wording, the opposite of `CopyTo`'s exclude list), applied uniformly at both
|
|
14
|
+
* folder- and message-level wherever `FastTransferStream.ts` builds a `propList` - see that file's own doc
|
|
15
|
+
* comment for this pragmatic subset's overall ICS scope. An empty `PropertyTags` list is honored literally
|
|
16
|
+
* (copies zero properties, i.e. empty `propList`s) rather than falling back to `CopyTo`'s own default column
|
|
17
|
+
* set - that fallback belongs to `CopyTo` alone, since an explicit "copy nothing" request here is spec-valid
|
|
18
|
+
* and distinguishable from "use the default set" (`CopyTo` simply has no `PropertyTags`-supplied alternative to
|
|
19
|
+
* fall back from).
|
|
20
|
+
*
|
|
21
|
+
* `Level`/`CopyFlags`/`SendOptions` are decoded to advance the reader correctly but not honored - see
|
|
22
|
+
* `RopFastTransferSourceCopyToHandler`'s own doc comment for the identical reasoning. Note `CopyFlags` here is
|
|
23
|
+
* **1 byte**, not 4 like `CopyTo`'s own field of the same name - confirmed against each ROP's own request-buffer
|
|
24
|
+
* page separately, not assumed identical.
|
|
25
|
+
*
|
|
26
|
+
* @author Jean-Philippe Steinmetz
|
|
27
|
+
*/
|
|
28
|
+
export class RopFastTransferSourceCopyPropertiesHandler {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.ropId = ROP_ID_COPY_PROPERTIES;
|
|
31
|
+
this.responseHandleIndexOffset = 3; // OutputHandleIndex
|
|
32
|
+
}
|
|
33
|
+
async handle(reader, writer, context) {
|
|
34
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
35
|
+
const inputHandleIndex = reader.readUInt8();
|
|
36
|
+
const outputHandleIndex = reader.readUInt8();
|
|
37
|
+
reader.readUInt8(); // Level - subfolder recursion not honored, see class doc comment
|
|
38
|
+
reader.readUInt8(); // CopyFlags (1 byte here, unlike CopyTo's 4) - not honored, see class doc comment
|
|
39
|
+
reader.readUInt8(); // SendOptions - not honored, see class doc comment
|
|
40
|
+
const tags = readPropertyTagArray(reader, reader.readUInt16LE());
|
|
41
|
+
// One column per property: a repeated tag would only repeat the same value in every row of the stream.
|
|
42
|
+
const columns = tags?.filter((tag, index) => tags.findIndex((other) => other.propertyId === tag.propertyId) === index);
|
|
43
|
+
const handle = context.session.handles[inputHandleIndex];
|
|
44
|
+
if (!handle || (handle.type !== "folder" && handle.type !== "message")) {
|
|
45
|
+
writer.writeUInt8(ROP_ID_COPY_PROPERTIES);
|
|
46
|
+
writer.writeUInt8(outputHandleIndex);
|
|
47
|
+
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!columns || !(await openFastTransferHandle(context, outputHandleIndex, handle, { columns }))) {
|
|
51
|
+
writer.writeUInt8(ROP_ID_COPY_PROPERTIES);
|
|
52
|
+
writer.writeUInt8(outputHandleIndex);
|
|
53
|
+
writer.writeUInt32LE(ERROR_TOO_BIG);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
writer.writeUInt8(ROP_ID_COPY_PROPERTIES);
|
|
57
|
+
writer.writeUInt8(outputHandleIndex);
|
|
58
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=RopFastTransferSourceCopyPropertiesHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopFastTransferSourceCopyPropertiesHandler.js","sourceRoot":"","sources":["../../../src/rop/RopFastTransferSourceCopyPropertiesHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGjE,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAEpC;;YAEY;AACZ,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAExC,6GAA6G;AAC7G,MAAM,aAAa,GAAG,UAAU,CAAC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,0CAA0C;IAAvD;QACoB,UAAK,GAAG,sBAAsB,CAAC;QAC/B,8BAAyB,GAAG,CAAC,CAAC,CAAC,oBAAoB;IA+BvE,CAAC;IA7BU,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,CAAC,SAAS,EAAE,CAAC,CAAC,iEAAiE;QACrF,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,kFAAkF;QACtG,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,mDAAmD;QACvE,MAAM,IAAI,GAA8B,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;QAC5F,uGAAuG;QACvG,MAAM,OAAO,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,KAAK,GAAG,CAAC,UAAU,CAAC,KAAK,KAAK,CAAC,CAAC;QAEvH,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE,CAAC;YACrE,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;YAC1C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;YAC3C,OAAO;QACX,CAAC;QACD,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,sBAAsB,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC;YAC/F,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;YAC1C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;YACpC,OAAO;QACX,CAAC;QAED,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;QAC1C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;IACrD,CAAC;CACJ"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readPropertyTagArray } from "../codec/PropertyValue.js";
|
|
2
|
+
import { openFastTransferHandle } from "./FastTransferStream.js";
|
|
3
|
+
const ROP_ID_COPY_TO = 0x4d;
|
|
4
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder or
|
|
5
|
+
* message (or doesn't exist)" - the same constant `RopGetPropertiesSpecificHandler` uses for its own analogous
|
|
6
|
+
* check. */
|
|
7
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
8
|
+
/** `MAPI_E_TOO_BIG`: more than `MAX_PROPERTY_TAG_COUNT` tags, or a stream past `MAX_FAST_TRANSFER_BYTES`. */
|
|
9
|
+
const ERROR_TOO_BIG = 0x80040305;
|
|
10
|
+
/**
|
|
11
|
+
* `RopFastTransferSourceCopyTo` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x4D`): begins a FastTransfer download
|
|
12
|
+
* of an already-open Folder or Message (or Calendar item, itself a Message object on the wire) object,
|
|
13
|
+
* producing a new `"fastTransfer"` Server object handle `RopFastTransferSourceGetBuffer` pages the built stream
|
|
14
|
+
* out of. See `FastTransferStream.ts`'s own doc comment for this pragmatic subset's "full, non-differential
|
|
15
|
+
* dump" ICS scope and its default property columns.
|
|
16
|
+
*
|
|
17
|
+
* `Level` (whether to recurse into subfolders) is decoded to advance the reader correctly but not honored -
|
|
18
|
+
* this pragmatic subset never recurses into subfolders regardless. `CopyFlags`/`SendOptions` (Move mode, best-
|
|
19
|
+
* body preference, Unicode preference, recoverable-mode, ...) are likewise decoded but not honored - this
|
|
20
|
+
* pragmatic subset always copies (never moves) and always encodes the same way regardless of client
|
|
21
|
+
* preference, matching `RopGetPropertiesSpecificHandler`'s own treatment of its analogous `WantUnicode` field.
|
|
22
|
+
* `PropertyTags` is the properties to *exclude* from the default column set (`[MS-OXCROPS]`'s own wording) -
|
|
23
|
+
* see `FastTransferStream.ts` for why an explicit include list instead is `RopFastTransferSourceCopyProperties`'s
|
|
24
|
+
* own job, not this ROP's.
|
|
25
|
+
*
|
|
26
|
+
* @author Jean-Philippe Steinmetz
|
|
27
|
+
*/
|
|
28
|
+
export class RopFastTransferSourceCopyToHandler {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.ropId = ROP_ID_COPY_TO;
|
|
31
|
+
this.responseHandleIndexOffset = 3; // OutputHandleIndex
|
|
32
|
+
}
|
|
33
|
+
async handle(reader, writer, context) {
|
|
34
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
35
|
+
const inputHandleIndex = reader.readUInt8();
|
|
36
|
+
const outputHandleIndex = reader.readUInt8();
|
|
37
|
+
reader.readUInt8(); // Level - subfolder recursion not honored, see class doc comment
|
|
38
|
+
reader.readUInt32LE(); // CopyFlags - not honored, see class doc comment
|
|
39
|
+
reader.readUInt8(); // SendOptions - not honored, see class doc comment
|
|
40
|
+
const excludeTags = readPropertyTagArray(reader, reader.readUInt16LE());
|
|
41
|
+
const handle = context.session.handles[inputHandleIndex];
|
|
42
|
+
if (!handle || (handle.type !== "folder" && handle.type !== "message")) {
|
|
43
|
+
writer.writeUInt8(ROP_ID_COPY_TO);
|
|
44
|
+
writer.writeUInt8(outputHandleIndex);
|
|
45
|
+
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const excludePropertyIds = excludeTags ? [...new Set(excludeTags.map((tag) => tag.propertyId))] : undefined;
|
|
49
|
+
if (!excludePropertyIds || !(await openFastTransferHandle(context, outputHandleIndex, handle, { excludePropertyIds }))) {
|
|
50
|
+
writer.writeUInt8(ROP_ID_COPY_TO);
|
|
51
|
+
writer.writeUInt8(outputHandleIndex);
|
|
52
|
+
writer.writeUInt32LE(ERROR_TOO_BIG);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
writer.writeUInt8(ROP_ID_COPY_TO);
|
|
56
|
+
writer.writeUInt8(outputHandleIndex);
|
|
57
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=RopFastTransferSourceCopyToHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopFastTransferSourceCopyToHandler.js","sourceRoot":"","sources":["../../../src/rop/RopFastTransferSourceCopyToHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGjE,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B;;YAEY;AACZ,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAExC,6GAA6G;AAC7G,MAAM,aAAa,GAAG,UAAU,CAAC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,kCAAkC;IAA/C;QACoB,UAAK,GAAG,cAAc,CAAC;QACvB,8BAAyB,GAAG,CAAC,CAAC,CAAC,oBAAoB;IA8BvE,CAAC;IA5BU,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,CAAC,SAAS,EAAE,CAAC,CAAC,iEAAiE;QACrF,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,iDAAiD;QACxE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,mDAAmD;QACvE,MAAM,WAAW,GAAG,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;QAExE,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE,CAAC;YACrE,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;YAClC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;YAC3C,OAAO;QACX,CAAC;QACD,MAAM,kBAAkB,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5G,IAAI,CAAC,kBAAkB,IAAI,CAAC,CAAC,MAAM,sBAAsB,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,EAAE,kBAAkB,EAAE,CAAC,CAAC,EAAE,CAAC;YACrH,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;YAClC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;YACpC,OAAO;QACX,CAAC;QAED,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;QAClC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;IACrD,CAAC;CACJ"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { loadFastTransferBuffer } from "./FastTransferStream.js";
|
|
2
|
+
const ROP_ID_GET_BUFFER = 0x4e;
|
|
3
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a
|
|
4
|
+
* `fastTransfer` handle (or doesn't exist)" - the same constant `RopGetPropertiesSpecificHandler` uses for its
|
|
5
|
+
* own analogous check. */
|
|
6
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
7
|
+
/** The `BufferSize` sentinel (`[MS-OXCROPS]`'s own documented value) meaning "let the server pick the chunk
|
|
8
|
+
* size". */
|
|
9
|
+
const BUFFER_SIZE_SERVER_DETERMINED = 0xbabe;
|
|
10
|
+
/** The response's own `TransferBufferSize` field is a 16-bit count, so a single call never returns more than this. */
|
|
11
|
+
const MAX_TRANSFER_BUFFER_SIZE = 0xffff;
|
|
12
|
+
/** `MAPI_E_TOO_BIG`: rebuilding the stream passed `MAX_FAST_TRANSFER_BYTES`. */
|
|
13
|
+
const ERROR_TOO_BIG = 0x80040305;
|
|
14
|
+
/** `MAPI_E_CALL_FAILED`: the stream was lost part-way through paging and can't be continued - see
|
|
15
|
+
* `loadFastTransferBuffer`. The client starts the transfer again. */
|
|
16
|
+
const ERROR_TRANSFER_LOST = 0x80004005;
|
|
17
|
+
/** The response bytes before `TransferBuffer`: RopId, InputHandleIndex, ReturnValue, TransferStatus,
|
|
18
|
+
* InProgressCount, TotalStepCount, Reserved, TransferBufferSize. */
|
|
19
|
+
const RESPONSE_HEADER_BYTES = 15;
|
|
20
|
+
/** `TransferStatus` values (`[MS-OXCFXICS]` §2.2.3.1.1.5.2). */
|
|
21
|
+
const TRANSFER_STATUS_PARTIAL = 0x0001;
|
|
22
|
+
const TRANSFER_STATUS_NO_ROOM = 0x0002;
|
|
23
|
+
const TRANSFER_STATUS_DONE = 0x0003;
|
|
24
|
+
/**
|
|
25
|
+
* `RopFastTransferSourceGetBuffer` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x4E`): pages the FastTransfer
|
|
26
|
+
* stream a prior `RopFastTransferSourceCopyTo`/`CopyProperties` built (`FastTransferStream.ts`) out of its
|
|
27
|
+
* `"fastTransfer"` handle, `BufferSize` bytes at a time. For the `0xBABE` "server-determined" sentinel the server
|
|
28
|
+
* picks the size, up to the client's `MaximumBufferSize`.
|
|
29
|
+
*
|
|
30
|
+
* Every chunk is also held to the room left in this request's ROP output buffer (`context.ropOutputRemaining`) and
|
|
31
|
+
* the 16-bit `TransferBufferSize`. Reports `Done` once the whole stream has been returned, `Partial` otherwise, and
|
|
32
|
+
* `NoRoom` when the output buffer has no space for any bytes this time (the client asks again in a new request).
|
|
33
|
+
*
|
|
34
|
+
* Fails with `MAPI_E_TOO_BIG` when a stream that had to be rebuilt is too large, and with `MAPI_E_CALL_FAILED` when
|
|
35
|
+
* the stream was lost part-way through (see `loadFastTransferBuffer`) - never with bytes from a different stream.
|
|
36
|
+
* Failure responses carry only `ReturnValue`, like every other failing handler here. `BackoffTime` is never emitted.
|
|
37
|
+
*
|
|
38
|
+
* @author Jean-Philippe Steinmetz
|
|
39
|
+
*/
|
|
40
|
+
export class RopFastTransferSourceGetBufferHandler {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.ropId = ROP_ID_GET_BUFFER;
|
|
43
|
+
}
|
|
44
|
+
async handle(reader, writer, context) {
|
|
45
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
46
|
+
const inputHandleIndex = reader.readUInt8();
|
|
47
|
+
const bufferSize = reader.readUInt16LE();
|
|
48
|
+
const requestedSize = bufferSize === BUFFER_SIZE_SERVER_DETERMINED ? reader.readUInt16LE() : bufferSize;
|
|
49
|
+
const handle = context.session.handles[inputHandleIndex];
|
|
50
|
+
const loaded = handle?.type === "fastTransfer" ? await loadFastTransferBuffer(context, inputHandleIndex, handle) : undefined;
|
|
51
|
+
if (!loaded || typeof loaded === "string") {
|
|
52
|
+
writer.writeUInt8(ROP_ID_GET_BUFFER);
|
|
53
|
+
writer.writeUInt8(inputHandleIndex);
|
|
54
|
+
writer.writeUInt32LE(!loaded ? ERROR_INVALID_OBJECT : loaded === "tooBig" ? ERROR_TOO_BIG : ERROR_TRANSFER_LOST);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const position = handle.transferPosition ?? 0;
|
|
58
|
+
const remaining = loaded.length - position;
|
|
59
|
+
const room = Math.max(0, (context.ropOutputRemaining ?? Infinity) - RESPONSE_HEADER_BYTES);
|
|
60
|
+
const chunkSize = Math.min(requestedSize, remaining, MAX_TRANSFER_BUFFER_SIZE, room);
|
|
61
|
+
const chunk = loaded.subarray(position, position + chunkSize);
|
|
62
|
+
handle.transferPosition = position + chunk.length;
|
|
63
|
+
const done = position + chunk.length >= loaded.length;
|
|
64
|
+
const status = done ? TRANSFER_STATUS_DONE : room === 0 ? TRANSFER_STATUS_NO_ROOM : TRANSFER_STATUS_PARTIAL;
|
|
65
|
+
writer.writeUInt8(ROP_ID_GET_BUFFER);
|
|
66
|
+
writer.writeUInt8(inputHandleIndex);
|
|
67
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
68
|
+
writer.writeUInt16LE(status);
|
|
69
|
+
writer.writeUInt16LE(0); // InProgressCount - no real progress tracking in this pragmatic subset
|
|
70
|
+
writer.writeUInt16LE(1); // TotalStepCount - pragmatic constant, only ever used for progress-bar display
|
|
71
|
+
writer.writeUInt8(0); // Reserved
|
|
72
|
+
writer.writeUInt16LE(chunk.length); // TransferBufferSize
|
|
73
|
+
writer.writeBytes(chunk); // TransferBuffer
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=RopFastTransferSourceGetBufferHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopFastTransferSourceGetBufferHandler.js","sourceRoot":"","sources":["../../../src/rop/RopFastTransferSourceGetBufferHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { assignHandle } from "../MapiSessionManager.js";
|
|
2
|
+
import { resolveContentsKind } from "./ContentsTable.js";
|
|
3
|
+
const ROP_ID_GET_CONTENTS_TABLE = 0x05;
|
|
4
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder (or
|
|
5
|
+
* doesn't exist)" - the same constant `RopGetHierarchyTableHandler` uses for its own analogous check. */
|
|
6
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
7
|
+
/**
|
|
8
|
+
* `RopGetContentsTable` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`): opens a table listing an already-opened folder's
|
|
9
|
+
* messages, producing a new `"table"` Server object handle `RopSetColumns`/`RopQueryRows` reference by
|
|
10
|
+
* `InputHandleIndex` - the exact same request/response shape as `RopGetHierarchyTable` (confirmed identical
|
|
11
|
+
* field-by-field against `[MS-OXCROPS]`'s own request-buffer page, differing only in `RopId`), just listing
|
|
12
|
+
* messages instead of child folders.
|
|
13
|
+
*
|
|
14
|
+
* The table holds no rows. It records which kind of item the folder contains (`contentsKind`, decided once here
|
|
15
|
+
* from the folder's own `type`: Calendar folders list `"calendarEvent:<uid>"` rows, Contacts `"contact:<uid>"`,
|
|
16
|
+
* Tasks `"task:<uid>"`, everything else `"message:<uid>"`), and `RopQueryRows` reads each requested window from
|
|
17
|
+
* the database - see `ContentsTable.ts`.
|
|
18
|
+
*
|
|
19
|
+
* A folder's contents table is only ever non-empty for a real folder (`"folder:<uid>"`) - the virtual special
|
|
20
|
+
* folders (`RopLogonHandler`'s own doc comment) have no real backing row to hold messages under, so opening a
|
|
21
|
+
* contents table on one always yields an empty table rather than an error.
|
|
22
|
+
*
|
|
23
|
+
* @author Jean-Philippe Steinmetz
|
|
24
|
+
*/
|
|
25
|
+
export class RopGetContentsTableHandler {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.ropId = ROP_ID_GET_CONTENTS_TABLE;
|
|
28
|
+
this.responseHandleIndexOffset = 3; // OutputHandleIndex
|
|
29
|
+
}
|
|
30
|
+
async handle(reader, writer, context) {
|
|
31
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
32
|
+
const inputHandleIndex = reader.readUInt8();
|
|
33
|
+
const outputHandleIndex = reader.readUInt8();
|
|
34
|
+
reader.readUInt8(); // TableFlags - only the Standard (0x00) case is supported; see RopGetHierarchyTableHandler's own doc comment
|
|
35
|
+
const folderHandle = context.session.handles[inputHandleIndex];
|
|
36
|
+
if (!folderHandle || folderHandle.type !== "folder") {
|
|
37
|
+
writer.writeUInt8(ROP_ID_GET_CONTENTS_TABLE);
|
|
38
|
+
writer.writeUInt8(outputHandleIndex);
|
|
39
|
+
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const entityUid = folderHandle.entityUid;
|
|
43
|
+
if (entityUid.startsWith("folder:")) {
|
|
44
|
+
const contentsKind = await resolveContentsKind(entityUid.slice("folder:".length), context);
|
|
45
|
+
assignHandle(context.session, outputHandleIndex, { type: "table", entityUid, contentsKind, cursor: 0 });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
assignHandle(context.session, outputHandleIndex, { type: "table", entityUid, rows: [], cursor: 0 });
|
|
49
|
+
}
|
|
50
|
+
writer.writeUInt8(ROP_ID_GET_CONTENTS_TABLE);
|
|
51
|
+
writer.writeUInt8(outputHandleIndex);
|
|
52
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=RopGetContentsTableHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopGetContentsTableHandler.js","sourceRoot":"","sources":["../../../src/rop/RopGetContentsTableHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAEvC;yGACyG;AACzG,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAExC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,0BAA0B;IAAvC;QACoB,UAAK,GAAG,yBAAyB,CAAC;QAClC,8BAAyB,GAAG,CAAC,CAAC,CAAC,oBAAoB;IA4BvE,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,iBAAiB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,6GAA6G;QAEjI,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC/D,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClD,MAAM,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAC;YAC7C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;YAC3C,OAAO;QACX,CAAC;QAED,MAAM,SAAS,GAAG,YAAY,CAAC,SAAS,CAAC;QACzC,IAAI,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAClC,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;YAC3F,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5G,CAAC;aAAM,CAAC;YACJ,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACxG,CAAC;QAED,MAAM,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAC;QAC7C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;IACrD,CAAC;CACJ"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { assignHandle } from "../MapiSessionManager.js";
|
|
2
|
+
import { resolveFolderChildren } from "./FolderTarget.js";
|
|
3
|
+
const ROP_ID_GET_HIERARCHY_TABLE = 0x04;
|
|
4
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder
|
|
5
|
+
* (or doesn't exist)". */
|
|
6
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
7
|
+
/**
|
|
8
|
+
* `RopGetHierarchyTable` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`): opens a table listing an already-opened folder's
|
|
9
|
+
* direct child folders, producing a new `"table"` Server object handle `RopSetColumns`/`RopQueryRows`
|
|
10
|
+
* reference by `InputHandleIndex`. Only `TableFlags = 0x00` ("Standard") is supported - this pragmatic subset
|
|
11
|
+
* has no deferred/async table population, so the immediate-row-count-returning variants some `TableFlags`
|
|
12
|
+
* values request are moot, and the response omits the row-count field those variants add (confirmed absent
|
|
13
|
+
* for the Standard case against `[MS-OXCROPS]`'s own request-buffer page, which explicitly lists `TableFlags`
|
|
14
|
+
* as this ROP's only request payload beyond the common handle-index header).
|
|
15
|
+
*
|
|
16
|
+
* @author Jean-Philippe Steinmetz
|
|
17
|
+
*/
|
|
18
|
+
export class RopGetHierarchyTableHandler {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.ropId = ROP_ID_GET_HIERARCHY_TABLE;
|
|
21
|
+
this.responseHandleIndexOffset = 3; // OutputHandleIndex
|
|
22
|
+
}
|
|
23
|
+
async handle(reader, writer, context) {
|
|
24
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
25
|
+
const inputHandleIndex = reader.readUInt8();
|
|
26
|
+
const outputHandleIndex = reader.readUInt8();
|
|
27
|
+
reader.readUInt8(); // TableFlags - only the Standard (0x00) case is supported; see class doc comment
|
|
28
|
+
const folderHandle = context.session.handles[inputHandleIndex];
|
|
29
|
+
if (!folderHandle || folderHandle.type !== "folder") {
|
|
30
|
+
writer.writeUInt8(ROP_ID_GET_HIERARCHY_TABLE);
|
|
31
|
+
writer.writeUInt8(outputHandleIndex);
|
|
32
|
+
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const rows = await resolveFolderChildren(context.mailboxUid, folderHandle.entityUid, context.folderRepo, undefined, context.budget);
|
|
36
|
+
assignHandle(context.session, outputHandleIndex, { type: "table", entityUid: folderHandle.entityUid, rows, cursor: 0 });
|
|
37
|
+
writer.writeUInt8(ROP_ID_GET_HIERARCHY_TABLE);
|
|
38
|
+
writer.writeUInt8(outputHandleIndex);
|
|
39
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=RopGetHierarchyTableHandler.js.map
|