@rapidmx/mapi-plugin 1.0.0-beta.3 → 1.0.0-beta.4
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/README.md +4 -4
- package/dist/lib/BaseMapiEmsmdbRoute.js +211 -36
- package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -1
- package/dist/lib/MapiSessionManager.js +404 -48
- package/dist/lib/MapiSessionManager.js.map +1 -1
- package/dist/lib/RegexPatternUtils.js +30 -0
- package/dist/lib/RegexPatternUtils.js.map +1 -0
- package/dist/lib/RopDispatcher.js +130 -19
- package/dist/lib/RopDispatcher.js.map +1 -1
- package/dist/lib/codec/AppointmentRecurrence.js +22 -21
- package/dist/lib/codec/AppointmentRecurrence.js.map +1 -1
- package/dist/lib/codec/BufferCursor.js +41 -12
- package/dist/lib/codec/BufferCursor.js.map +1 -1
- package/dist/lib/codec/GlobalObjectId.js +26 -20
- package/dist/lib/codec/GlobalObjectId.js.map +1 -1
- package/dist/lib/codec/MapiTimeZone.js +43 -10
- package/dist/lib/codec/MapiTimeZone.js.map +1 -1
- package/dist/lib/codec/PropertyValue.js +28 -2
- package/dist/lib/codec/PropertyValue.js.map +1 -1
- package/dist/lib/codec/RopBuffer.js +15 -3
- package/dist/lib/codec/RopBuffer.js.map +1 -1
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +2 -1
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -1
- package/dist/lib/nspi/NspiCodec.js +5 -5
- package/dist/lib/nspi/NspiCodec.js.map +1 -1
- package/dist/lib/nspi/NspiGetMatchesHandler.js +14 -14
- package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -1
- package/dist/lib/rop/AddressList.js +109 -0
- package/dist/lib/rop/AddressList.js.map +1 -0
- package/dist/lib/rop/CalendarEventTarget.js +7 -4
- package/dist/lib/rop/CalendarEventTarget.js.map +1 -1
- package/dist/lib/rop/ContactTarget.js +2 -6
- package/dist/lib/rop/ContactTarget.js.map +1 -1
- 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 +99 -2
- package/dist/lib/rop/FastTransferStream.js.map +1 -1
- package/dist/lib/rop/FolderTarget.js +13 -6
- package/dist/lib/rop/FolderTarget.js.map +1 -1
- package/dist/lib/rop/HandleDataCache.js +412 -0
- package/dist/lib/rop/HandleDataCache.js.map +1 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js +194 -48
- package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -1
- package/dist/lib/rop/MessageBodyStream.js +33 -7
- package/dist/lib/rop/MessageBodyStream.js.map +1 -1
- package/dist/lib/rop/MessageTarget.js +17 -4
- package/dist/lib/rop/MessageTarget.js.map +1 -1
- package/dist/lib/rop/NamedPropertyRegistry.js +8 -5
- package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -1
- package/dist/lib/rop/PropertyResolvers.js +3 -1
- package/dist/lib/rop/PropertyResolvers.js.map +1 -1
- 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 +4 -2
- package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -1
- package/dist/lib/rop/RopDeleteFolderHandler.js +112 -77
- package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -1
- package/dist/lib/rop/RopDeleteMessagesHandler.js +17 -1
- package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -1
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +14 -14
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -1
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +13 -14
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -1
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +33 -32
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -1
- package/dist/lib/rop/RopGetContentsTableHandler.js +17 -38
- package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -1
- package/dist/lib/rop/RopGetHierarchyTableHandler.js +4 -2
- package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -1
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +6 -8
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -1
- package/dist/lib/rop/RopHandler.js +5 -1
- package/dist/lib/rop/RopHandler.js.map +1 -1
- package/dist/lib/rop/RopLogonHandler.js +5 -2
- package/dist/lib/rop/RopLogonHandler.js.map +1 -1
- package/dist/lib/rop/RopOpenFolderHandler.js +3 -1
- package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -1
- package/dist/lib/rop/RopOpenMessageHandler.js +3 -1
- package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -1
- package/dist/lib/rop/RopOpenStreamHandler.js +11 -8
- package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -1
- package/dist/lib/rop/RopQueryRowsHandler.js +31 -5
- package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -1
- package/dist/lib/rop/RopReadStreamHandler.js +22 -4
- package/dist/lib/rop/RopReadStreamHandler.js.map +1 -1
- package/dist/lib/rop/RopReleaseHandler.js +4 -1
- package/dist/lib/rop/RopReleaseHandler.js.map +1 -1
- package/dist/lib/rop/RopSaveChangesMessageHandler.js +66 -7
- package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -1
- package/dist/lib/rop/RopSetColumnsHandler.js +6 -8
- package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -1
- package/dist/lib/rop/RopSetPropertiesHandler.js +23 -12
- package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -1
- package/dist/lib/rop/RopSubmitMessageHandler.js +144 -120
- package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -1
- package/dist/lib/rop/RopWriteStreamHandler.js +37 -10
- package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -1
- package/dist/lib/rop/TaskTarget.js +2 -6
- package/dist/lib/rop/TaskTarget.js.map +1 -1
- package/dist/lib/rop/TransportSend.js +16 -0
- package/dist/lib/rop/TransportSend.js.map +1 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js +2 -1
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -1
- package/dist/types/BaseMapiEmsmdbRoute.d.ts +52 -2
- package/dist/types/MapiSessionManager.d.ts +218 -63
- package/dist/types/RegexPatternUtils.d.ts +14 -0
- package/dist/types/RopDispatcher.d.ts +47 -11
- package/dist/types/codec/BufferCursor.d.ts +14 -0
- package/dist/types/codec/GlobalObjectId.d.ts +9 -5
- package/dist/types/codec/PropertyValue.d.ts +10 -0
- package/dist/types/codec/RopBuffer.d.ts +7 -0
- package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +1 -0
- package/dist/types/nspi/NspiGetMatchesHandler.d.ts +2 -0
- package/dist/types/rop/AddressList.d.ts +48 -0
- package/dist/types/rop/CalendarEventTarget.d.ts +7 -3
- package/dist/types/rop/ContactTarget.d.ts +3 -3
- 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 +41 -2
- package/dist/types/rop/FolderTarget.d.ts +3 -2
- package/dist/types/rop/HandleDataCache.d.ts +145 -0
- package/dist/types/rop/MeetingMessageClassHandler.d.ts +23 -29
- package/dist/types/rop/MessageBodyStream.d.ts +16 -7
- package/dist/types/rop/MessageTarget.d.ts +10 -3
- package/dist/types/rop/NamedPropertyRegistry.d.ts +6 -2
- package/dist/types/rop/PropertyResolvers.d.ts +1 -1
- 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 +1 -0
- package/dist/types/rop/RopDeleteFolderHandler.d.ts +36 -21
- package/dist/types/rop/RopDeleteMessagesHandler.d.ts +7 -0
- package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +1 -0
- package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +1 -0
- package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +10 -11
- package/dist/types/rop/RopGetContentsTableHandler.d.ts +7 -13
- package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +1 -0
- package/dist/types/rop/RopHandler.d.ts +25 -1
- package/dist/types/rop/RopOpenFolderHandler.d.ts +1 -0
- package/dist/types/rop/RopOpenMessageHandler.d.ts +1 -0
- package/dist/types/rop/RopOpenStreamHandler.d.ts +1 -0
- package/dist/types/rop/RopQueryRowsHandler.d.ts +3 -0
- package/dist/types/rop/RopReadStreamHandler.d.ts +3 -1
- package/dist/types/rop/RopReleaseHandler.d.ts +1 -0
- package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +16 -1
- package/dist/types/rop/RopSubmitMessageHandler.d.ts +33 -23
- package/dist/types/rop/RopWriteStreamHandler.d.ts +18 -8
- package/dist/types/rop/TaskTarget.d.ts +3 -3
- package/dist/types/rop/TransportSend.d.ts +10 -0
- package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RepoPaging.js","sourceRoot":"","sources":["../../../src/rop/RepoPaging.ts"],"names":[],"mappings":"AAOA,6CAA6C;AAC7C,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;AAEnC,iDAAiD;AACjD,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAMzC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC1B,IAAoB,EACpB,KAA0B,EAC1B,IAAc,EACd,IAAY,EACZ,WAAmB,cAAc,EACjC,MAAsB;IAEtB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,MAAM,IAAI,GAAQ,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAChI,MAAM,EAAE,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvC,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAC/B,IAAoB,EACpB,KAA0B,EAC1B,IAAc,EACd,MAAc,mBAAmB,EACjC,MAAsB;IAEtB,MAAM,KAAK,GAAQ,EAAE,CAAC;IACtB,KAAK,IAAI,IAAI,GAAG,CAAC,GAAI,IAAI,EAAE,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAQ,MAAM,QAAQ,CAAI,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QACrF,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACpB,IAAI,IAAI,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;YAC/B,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACzE,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YACtB,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC3D,CAAC;IACL,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC5B,IAAoB,EACpB,KAA0B,EAC1B,IAAc,EACd,KAAa,EACb,KAAa,EACb,MAAsB;IAEtB,MAAM,MAAM,GAAQ,EAAE,CAAC;IACvB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,OAAO,MAAM,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,cAAc,CAAC,CAAC;QACnD,MAAM,IAAI,GAAQ,MAAM,QAAQ,CAAI,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QACrF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,GAAG,cAAc,EAAE,QAAQ,GAAG,IAAI,GAAG,cAAc,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACvH,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QACtB,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;YAC/B,MAAM;QACV,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
|
+
import { BaseEntity, ModelUtils } from "@rapidrest/service-core";
|
|
7
|
+
// Copies of restapi rules that @rapidmx/restapi 0.9.0 doesn't export from its package root. Keep them in sync with
|
|
8
|
+
// restapi's `util/ConversationUtils.ts` (`boundIndexedValue`) and `util/EntityUtils.ts` (`asEntity`).
|
|
9
|
+
/** The longest value `boundIndexedValue()` stores verbatim - restapi's `MAX_INDEXED_VALUE_LENGTH`. */
|
|
10
|
+
export const MAX_INDEXED_VALUE_LENGTH = 255;
|
|
11
|
+
/**
|
|
12
|
+
* restapi's `boundIndexedValue`: `value` unchanged when it's at most `MAX_INDEXED_VALUE_LENGTH` characters, otherwise
|
|
13
|
+
* `sha256:<64 hex digits>` of its UTF-8 bytes. restapi stores `CalendarEvent.icalUid` (and `Message.messageId`/
|
|
14
|
+
* `conversationId`) this way, so a lookup by one of those identifiers has to bound its value the same way to match.
|
|
15
|
+
*/
|
|
16
|
+
export function boundIndexedValue(value) {
|
|
17
|
+
if (typeof value !== "string" || value.length <= MAX_INDEXED_VALUE_LENGTH) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
return `sha256:${createHash("sha256").update(value, "utf8").digest("hex")}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* restapi's `asEntity`: `row` as an instance of `repo`'s model class. Before service-core 2.1.0, `RepoUtils.update()`
|
|
24
|
+
* only enforced its optimistic lock when `existing instanceof BaseEntity`, and the Mongo backend's `find()`/`findOne()`
|
|
25
|
+
* return plain documents, so passing one straight through turned a versioned update into an unconditional overwrite.
|
|
26
|
+
* 2.1.0 also locks plain documents carrying a numeric `version`, so this is now defence in depth, kept while restapi
|
|
27
|
+
* keeps its own copy. SQL rows are already instances and pass through as they are.
|
|
28
|
+
*/
|
|
29
|
+
export function asEntity(repo, row) {
|
|
30
|
+
if (row instanceof BaseEntity) {
|
|
31
|
+
return row;
|
|
32
|
+
}
|
|
33
|
+
const modelClass = repo.modelClass;
|
|
34
|
+
return modelClass ? new modelClass(row) : row;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A `RepoUtils` query value that always matches `value` literally: service-core's `ModelUtils.literal()`, so a
|
|
38
|
+
* client- or sender-supplied value like `ne(x)` is never read as an operator and `me`/`null`/numeric strings are not
|
|
39
|
+
* substituted or coerced. Callers still compare the returned rows' field against `value` themselves.
|
|
40
|
+
*/
|
|
41
|
+
export function literalQueryValue(value) {
|
|
42
|
+
return ModelUtils.literal(value);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* restapi `MeetingSchedulingJob`'s eligibility rule (`jobs/MeetingSchedulingJob.ts`, `processInviteCandidates`): `true`
|
|
46
|
+
* while the job will still send invites for `event`'s current revision, i.e. its `inviteSequenceSent` is unset or
|
|
47
|
+
* differs from its `sequence`. Keep in sync with that job.
|
|
48
|
+
*/
|
|
49
|
+
export function isInvitePending(event) {
|
|
50
|
+
return event.inviteSequenceSent == null || event.inviteSequenceSent !== event.sequence;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=RestapiRules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RestapiRules.js","sourceRoot":"","sources":["../../../src/rop/RestapiRules.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAqC,MAAM,yBAAyB,CAAC;AAEpG,mHAAmH;AACnH,sGAAsG;AAEtG,sGAAsG;AACtG,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAE5C;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAsC,KAAQ;IAC3E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,IAAI,wBAAwB,EAAE,CAAC;QACxE,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAO,CAAC;AACrF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAI,IAAoB,EAAE,GAAM;IACpD,IAAI,GAAG,YAAY,UAAU,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC;IACf,CAAC;IACD,MAAM,UAAU,GAAS,IAAY,CAAC,UAAU,CAAC;IACjD,OAAO,UAAU,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC3C,OAAO,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAAgE;IAC5F,OAAO,KAAK,CAAC,kBAAkB,IAAI,IAAI,IAAI,KAAK,CAAC,kBAAkB,KAAK,KAAK,CAAC,QAAQ,CAAC;AAC3F,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assignHandle } from "../MapiSessionManager.js";
|
|
1
2
|
const ROP_ID_CREATE_MESSAGE = 0x06;
|
|
2
3
|
/** The well-known MAPI HRESULT `MAPI_E_NOT_FOUND`, reused as this handler's only failure `ReturnValue` - an
|
|
3
4
|
* unrecognized `FolderId`, the same constant `RopOpenFolderHandler` uses for its own analogous lookup miss. */
|
|
@@ -22,6 +23,7 @@ const ERROR_NOT_FOUND = 0x8004010f;
|
|
|
22
23
|
export class RopCreateMessageHandler {
|
|
23
24
|
constructor() {
|
|
24
25
|
this.ropId = ROP_ID_CREATE_MESSAGE;
|
|
26
|
+
this.responseHandleIndexOffset = 3; // OutputHandleIndex
|
|
25
27
|
}
|
|
26
28
|
handle(reader, writer, context) {
|
|
27
29
|
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
@@ -38,12 +40,12 @@ export class RopCreateMessageHandler {
|
|
|
38
40
|
writer.writeUInt32LE(ERROR_NOT_FOUND);
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
41
|
-
context.session
|
|
43
|
+
assignHandle(context.session, outputHandleIndex, {
|
|
42
44
|
type: "message",
|
|
43
45
|
entityUid: "",
|
|
44
46
|
draftFolderUid: target,
|
|
45
47
|
draftProperties: {},
|
|
46
|
-
};
|
|
48
|
+
});
|
|
47
49
|
writer.writeUInt8(ROP_ID_CREATE_MESSAGE);
|
|
48
50
|
writer.writeUInt8(outputHandleIndex);
|
|
49
51
|
writer.writeUInt32LE(0); // ReturnValue - success
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RopCreateMessageHandler.js","sourceRoot":"","sources":["../../../src/rop/RopCreateMessageHandler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RopCreateMessageHandler.js","sourceRoot":"","sources":["../../../src/rop/RopCreateMessageHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAEnC;+GAC+G;AAC/G,MAAM,eAAe,GAAG,UAAU,CAAC;AAEnC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,uBAAuB;IAApC;QACoB,UAAK,GAAG,qBAAqB,CAAC;QAC9B,8BAAyB,GAAG,CAAC,CAAC,CAAC,oBAAoB;IA+BvE,CAAC;IA7BU,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QACzE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,4EAA4E;QAChG,2FAA2F;QAC3F,MAAM,iBAAiB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,qCAAqC;QAC5D,MAAM,QAAQ,GAAW,MAAM,CAAC,eAAe,EAAE,CAAC;QAClD,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,yCAAyC;QAE7D,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,qBAAqB,CAAC,CAAC;YACzC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;YACtC,OAAO;QACX,CAAC;QAED,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE;YAC7C,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,EAAE;YACb,cAAc,EAAE,MAAM;YACtB,eAAe,EAAE,EAAE;SACtB,CAAC,CAAC;QAEH,MAAM,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC;QACzC,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,gEAAgE;IAC1F,CAAC;CACJ"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { auditMessageDelete } from "./RopDeleteMessagesHandler.js";
|
|
2
|
+
import { findAllCapped, findPage, MAX_COLLECTION_ROWS } from "./RepoPaging.js";
|
|
1
3
|
const ROP_ID_DELETE_FOLDER = 0x1d;
|
|
2
4
|
/** The well-known MAPI HRESULT `MAPI_E_NOT_FOUND`, reused for an unrecognized `FolderId` - the same constant
|
|
3
5
|
* `RopOpenFolderHandler` uses for its own analogous lookup miss. */
|
|
@@ -7,29 +9,41 @@ const ERROR_NOT_FOUND = 0x8004010f;
|
|
|
7
9
|
* throughout this pragmatic subset as a generic "can't do this" signal, not a claim of exact per-condition
|
|
8
10
|
* `[MS-OXCFOLD]` return-value-table parity (the real spec's own `ecFolderNotEmpty` isn't separately modeled). */
|
|
9
11
|
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
10
|
-
/** `
|
|
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. */
|
|
11
16
|
const DEL_MESSAGES = 0x01;
|
|
12
17
|
const DEL_FOLDERS = 0x04;
|
|
13
|
-
|
|
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" };
|
|
14
26
|
/**
|
|
15
|
-
* `RopDeleteFolder` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`, RopId `0x1D`): deletes a folder by `FolderId
|
|
16
|
-
* `RecoverableRepoUtils.delete()`
|
|
17
|
-
*
|
|
18
|
-
* delete detection depends on it).
|
|
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).
|
|
19
30
|
*
|
|
20
|
-
* **Real spec semantics honored
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* onto `RepoDeleteOptions.purge` - real, exact semantic overlap with this library's own soft-delete model, not
|
|
25
|
-
* a coincidence this codec papers over.
|
|
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.
|
|
26
35
|
*
|
|
27
|
-
* **
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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.
|
|
33
47
|
*
|
|
34
48
|
* The request's own `InputHandleIndex` (nominally the *parent* folder of the one being deleted) is validated to
|
|
35
49
|
* be a real, open folder handle but not cross-checked against the target folder's actual `parentFolderUid` -
|
|
@@ -49,85 +63,106 @@ export class RopDeleteFolderHandler {
|
|
|
49
63
|
const folderId = reader.readBigUInt64LE();
|
|
50
64
|
const handle = context.session.handles[inputHandleIndex];
|
|
51
65
|
if (!handle || handle.type !== "folder") {
|
|
52
|
-
|
|
53
|
-
writer.writeUInt8(inputHandleIndex);
|
|
54
|
-
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
66
|
+
this.writeFailure(writer, inputHandleIndex, ERROR_INVALID_OBJECT);
|
|
55
67
|
return;
|
|
56
68
|
}
|
|
57
69
|
const target = context.session.folderIds[folderId.toString()];
|
|
58
70
|
if (!target?.startsWith("folder:")) {
|
|
59
|
-
|
|
60
|
-
writer.writeUInt8(inputHandleIndex);
|
|
61
|
-
writer.writeUInt32LE(ERROR_NOT_FOUND);
|
|
71
|
+
this.writeFailure(writer, inputHandleIndex, ERROR_NOT_FOUND);
|
|
62
72
|
return;
|
|
63
73
|
}
|
|
64
74
|
const uid = target.slice("folder:".length);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const events = await context.calendarEventRepo.find({ folderUid: uid }, { ignoreACL: true });
|
|
68
|
-
const contacts = context.contactRepo ? await context.contactRepo.find({ folderUid: uid }, { ignoreACL: true }) : [];
|
|
69
|
-
const tasks = context.taskRepo ? await context.taskRepo.find({ folderUid: uid }, { ignoreACL: true }) : [];
|
|
70
|
-
const childFolders = await context.folderRepo.find({ parentFolderUid: uid }, { ignoreACL: true });
|
|
71
|
-
if ((messages.length > 0 || events.length > 0 || contacts.length > 0 || tasks.length > 0) &&
|
|
72
|
-
(deleteFolderFlags & DEL_MESSAGES) === 0) {
|
|
73
|
-
writer.writeUInt8(ROP_ID_DELETE_FOLDER);
|
|
74
|
-
writer.writeUInt8(inputHandleIndex);
|
|
75
|
-
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
75
|
+
if ((deleteFolderFlags & DEL_MESSAGES) === 0 && (await this.hasItems(uid, context))) {
|
|
76
|
+
this.writeFailure(writer, inputHandleIndex, ERROR_INVALID_OBJECT);
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
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
82
|
return;
|
|
83
83
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
await context.calendarEventRepo.delete(event.uid, { ignoreACL: true, purge });
|
|
89
|
-
}
|
|
90
|
-
for (const contact of contacts) {
|
|
91
|
-
await context.contactRepo.delete(contact.uid, { ignoreACL: true, purge });
|
|
92
|
-
}
|
|
93
|
-
for (const task of tasks) {
|
|
94
|
-
await context.taskRepo.delete(task.uid, { ignoreACL: true, purge });
|
|
84
|
+
const folders = await this.collectSubtree(uid, context);
|
|
85
|
+
if (!folders) {
|
|
86
|
+
this.writeFailure(writer, inputHandleIndex, ERROR_TOO_COMPLEX);
|
|
87
|
+
return;
|
|
95
88
|
}
|
|
96
|
-
|
|
97
|
-
|
|
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
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
context.budget?.chargeQueries();
|
|
97
|
+
await context.folderRepo.delete(folderUid, { ignoreACL: true });
|
|
98
98
|
}
|
|
99
|
-
await context.folderRepo.delete(uid, { ignoreACL: true, purge });
|
|
100
99
|
writer.writeUInt8(ROP_ID_DELETE_FOLDER);
|
|
101
100
|
writer.writeUInt8(inputHandleIndex);
|
|
102
101
|
writer.writeUInt32LE(0); // ReturnValue - success
|
|
103
|
-
writer.writeUInt8(0); // PartialCompletion
|
|
102
|
+
writer.writeUInt8(partialCompletion ? 1 : 0); // PartialCompletion
|
|
104
103
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
for (const
|
|
116
|
-
await
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
await context.calendarEventRepo.delete(event.uid, { ignoreACL: true, purge });
|
|
120
|
-
}
|
|
121
|
-
for (const contact of contacts) {
|
|
122
|
-
await context.contactRepo.delete(contact.uid, { ignoreACL: true, purge });
|
|
104
|
+
writeFailure(writer, inputHandleIndex, returnValue) {
|
|
105
|
+
writer.writeUInt8(ROP_ID_DELETE_FOLDER);
|
|
106
|
+
writer.writeUInt8(inputHandleIndex);
|
|
107
|
+
writer.writeUInt32LE(returnValue);
|
|
108
|
+
}
|
|
109
|
+
/** The repos a folder's items can live in, skipping the optional ones the context doesn't have. */
|
|
110
|
+
itemRepos(context) {
|
|
111
|
+
return [context.messageRepo, context.calendarEventRepo, context.contactRepo, context.taskRepo].filter((repo) => repo !== undefined);
|
|
112
|
+
}
|
|
113
|
+
async hasItems(folderUid, context) {
|
|
114
|
+
for (const repo of this.itemRepos(context)) {
|
|
115
|
+
if ((await findPage(repo, { folderUid, mailboxUid: context.mailboxUid }, UID_SORT, 0, 1, context.budget)).length > 0) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
123
118
|
}
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* `rootUid` and every folder below it in the caller's mailbox, children before their parents, or `undefined` when
|
|
123
|
+
* the tree is deeper than `MAX_FOLDER_DEPTH` or has more than `MAX_FOLDERS_PER_DELETE` folders.
|
|
124
|
+
*/
|
|
125
|
+
async collectSubtree(rootUid, context) {
|
|
126
|
+
const visited = new Set([rootUid]);
|
|
127
|
+
const preOrder = [];
|
|
128
|
+
const stack = [{ uid: rootUid, depth: 0 }];
|
|
129
|
+
while (stack.length > 0) {
|
|
130
|
+
const { uid, depth } = stack.pop();
|
|
131
|
+
preOrder.push(uid);
|
|
132
|
+
const { items: children, truncated } = await findAllCapped(context.folderRepo, { parentFolderUid: uid, mailboxUid: context.mailboxUid }, UID_SORT, MAX_FOLDERS_PER_DELETE, context.budget);
|
|
133
|
+
if (truncated) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
for (const child of children) {
|
|
137
|
+
if (visited.has(child.uid)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (depth + 1 > MAX_FOLDER_DEPTH || visited.size >= MAX_FOLDERS_PER_DELETE) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
visited.add(child.uid);
|
|
144
|
+
stack.push({ uid: child.uid, depth: depth + 1 });
|
|
145
|
+
}
|
|
126
146
|
}
|
|
127
|
-
|
|
128
|
-
|
|
147
|
+
return preOrder.reverse();
|
|
148
|
+
}
|
|
149
|
+
/** Soft-deletes `folderUid`'s items within `budget`. Returns `false` if items remain once the budget is spent. */
|
|
150
|
+
async deleteItems(folderUid, context, budget) {
|
|
151
|
+
for (const repo of this.itemRepos(context)) {
|
|
152
|
+
const { items, truncated } = await findAllCapped(repo, { folderUid, mailboxUid: context.mailboxUid }, UID_SORT, budget.remaining, context.budget);
|
|
153
|
+
for (const item of items) {
|
|
154
|
+
context.budget?.chargeQueries();
|
|
155
|
+
await repo.delete(item.uid, { ignoreACL: true });
|
|
156
|
+
if (repo === context.messageRepo) {
|
|
157
|
+
await auditMessageDelete(context, item);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
budget.remaining -= items.length;
|
|
161
|
+
if (truncated) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
129
164
|
}
|
|
130
|
-
|
|
165
|
+
return true;
|
|
131
166
|
}
|
|
132
167
|
}
|
|
133
168
|
//# sourceMappingURL=RopDeleteFolderHandler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RopDeleteFolderHandler.js","sourceRoot":"","sources":["../../../src/rop/RopDeleteFolderHandler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RopDeleteFolderHandler.js","sourceRoot":"","sources":["../../../src/rop/RopDeleteFolderHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import { AuditAction } from "@rapidmx/restapi";
|
|
1
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
|
+
export async function auditMessageDelete(context, message) {
|
|
5
|
+
await context.audit?.({
|
|
6
|
+
action: AuditAction.MESSAGE_DELETE,
|
|
7
|
+
targetType: "Message",
|
|
8
|
+
targetUid: message.uid,
|
|
9
|
+
mailboxUid: message.mailboxUid,
|
|
10
|
+
details: { subject: message.subject, folderUid: message.folderUid },
|
|
11
|
+
});
|
|
12
|
+
}
|
|
2
13
|
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder (or
|
|
3
14
|
* doesn't exist)" - the same constant `RopGetContentsTableHandler` uses for its own analogous check. */
|
|
4
15
|
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
@@ -52,7 +63,12 @@ export class RopDeleteMessagesHandler {
|
|
|
52
63
|
for (const messageId of messageIds) {
|
|
53
64
|
const target = context.session.messageIds[messageId.toString()];
|
|
54
65
|
if (target?.startsWith("message:")) {
|
|
55
|
-
|
|
66
|
+
const uid = target.slice("message:".length);
|
|
67
|
+
const message = context.audit ? await context.messageRepo.findOne(uid, { ignoreACL: true }) : undefined;
|
|
68
|
+
await context.messageRepo.delete(uid, { ignoreACL: true });
|
|
69
|
+
if (message) {
|
|
70
|
+
await auditMessageDelete(context, message);
|
|
71
|
+
}
|
|
56
72
|
}
|
|
57
73
|
else if (target?.startsWith("calendarEvent:")) {
|
|
58
74
|
await context.calendarEventRepo.delete(target.slice("calendarEvent:".length), { ignoreACL: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RopDeleteMessagesHandler.js","sourceRoot":"","sources":["../../../src/rop/RopDeleteMessagesHandler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RopDeleteMessagesHandler.js","sourceRoot":"","sources":["../../../src/rop/RopDeleteMessagesHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readPropertyTagArray } from "../codec/PropertyValue.js";
|
|
2
|
+
import { openFastTransferHandle } from "./FastTransferStream.js";
|
|
3
3
|
const ROP_ID_COPY_PROPERTIES = 0x69;
|
|
4
4
|
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder or
|
|
5
5
|
* message (or doesn't exist)" - the same constant `RopGetPropertiesSpecificHandler` uses for its own analogous
|
|
6
6
|
* check. */
|
|
7
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;
|
|
8
10
|
/**
|
|
9
11
|
* `RopFastTransferSourceCopyProperties` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x69`): the explicit-column
|
|
10
12
|
* sibling of `RopFastTransferSourceCopyTo` - `PropertyTags` here is the properties to **copy** (an include
|
|
@@ -26,6 +28,7 @@ const ERROR_INVALID_OBJECT = 0x80070005;
|
|
|
26
28
|
export class RopFastTransferSourceCopyPropertiesHandler {
|
|
27
29
|
constructor() {
|
|
28
30
|
this.ropId = ROP_ID_COPY_PROPERTIES;
|
|
31
|
+
this.responseHandleIndexOffset = 3; // OutputHandleIndex
|
|
29
32
|
}
|
|
30
33
|
async handle(reader, writer, context) {
|
|
31
34
|
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
@@ -34,11 +37,9 @@ export class RopFastTransferSourceCopyPropertiesHandler {
|
|
|
34
37
|
reader.readUInt8(); // Level - subfolder recursion not honored, see class doc comment
|
|
35
38
|
reader.readUInt8(); // CopyFlags (1 byte here, unlike CopyTo's 4) - not honored, see class doc comment
|
|
36
39
|
reader.readUInt8(); // SendOptions - not honored, see class doc comment
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
columns.push(readPropertyTag(reader));
|
|
41
|
-
}
|
|
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);
|
|
42
43
|
const handle = context.session.handles[inputHandleIndex];
|
|
43
44
|
if (!handle || (handle.type !== "folder" && handle.type !== "message")) {
|
|
44
45
|
writer.writeUInt8(ROP_ID_COPY_PROPERTIES);
|
|
@@ -46,13 +47,12 @@ export class RopFastTransferSourceCopyPropertiesHandler {
|
|
|
46
47
|
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
};
|
|
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
56
|
writer.writeUInt8(ROP_ID_COPY_PROPERTIES);
|
|
57
57
|
writer.writeUInt8(outputHandleIndex);
|
|
58
58
|
writer.writeUInt32LE(0); // ReturnValue - success
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RopFastTransferSourceCopyPropertiesHandler.js","sourceRoot":"","sources":["../../../src/rop/RopFastTransferSourceCopyPropertiesHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,
|
|
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"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readPropertyTagArray } from "../codec/PropertyValue.js";
|
|
2
|
+
import { openFastTransferHandle } from "./FastTransferStream.js";
|
|
3
3
|
const ROP_ID_COPY_TO = 0x4d;
|
|
4
4
|
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a folder or
|
|
5
5
|
* message (or doesn't exist)" - the same constant `RopGetPropertiesSpecificHandler` uses for its own analogous
|
|
6
6
|
* check. */
|
|
7
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;
|
|
8
10
|
/**
|
|
9
11
|
* `RopFastTransferSourceCopyTo` (`[MS-OXCFXICS]`/`[MS-OXCROPS]`, RopId `0x4D`): begins a FastTransfer download
|
|
10
12
|
* of an already-open Folder or Message (or Calendar item, itself a Message object on the wire) object,
|
|
@@ -26,6 +28,7 @@ const ERROR_INVALID_OBJECT = 0x80070005;
|
|
|
26
28
|
export class RopFastTransferSourceCopyToHandler {
|
|
27
29
|
constructor() {
|
|
28
30
|
this.ropId = ROP_ID_COPY_TO;
|
|
31
|
+
this.responseHandleIndexOffset = 3; // OutputHandleIndex
|
|
29
32
|
}
|
|
30
33
|
async handle(reader, writer, context) {
|
|
31
34
|
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
@@ -34,11 +37,7 @@ export class RopFastTransferSourceCopyToHandler {
|
|
|
34
37
|
reader.readUInt8(); // Level - subfolder recursion not honored, see class doc comment
|
|
35
38
|
reader.readUInt32LE(); // CopyFlags - not honored, see class doc comment
|
|
36
39
|
reader.readUInt8(); // SendOptions - not honored, see class doc comment
|
|
37
|
-
const
|
|
38
|
-
const excludePropertyIds = new Set();
|
|
39
|
-
for (let i = 0; i < propertyTagCount; i++) {
|
|
40
|
-
excludePropertyIds.add(readPropertyTag(reader).propertyId);
|
|
41
|
-
}
|
|
40
|
+
const excludeTags = readPropertyTagArray(reader, reader.readUInt16LE());
|
|
42
41
|
const handle = context.session.handles[inputHandleIndex];
|
|
43
42
|
if (!handle || (handle.type !== "folder" && handle.type !== "message")) {
|
|
44
43
|
writer.writeUInt8(ROP_ID_COPY_TO);
|
|
@@ -46,13 +45,13 @@ export class RopFastTransferSourceCopyToHandler {
|
|
|
46
45
|
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
47
46
|
return;
|
|
48
47
|
}
|
|
49
|
-
const
|
|
50
|
-
context
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
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
|
+
}
|
|
56
55
|
writer.writeUInt8(ROP_ID_COPY_TO);
|
|
57
56
|
writer.writeUInt8(outputHandleIndex);
|
|
58
57
|
writer.writeUInt32LE(0); // ReturnValue - success
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RopFastTransferSourceCopyToHandler.js","sourceRoot":"","sources":["../../../src/rop/RopFastTransferSourceCopyToHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,
|
|
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"}
|