@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 @@
|
|
|
1
|
+
{"version":3,"file":"RopGetHierarchyTableHandler.js","sourceRoot":"","sources":["../../../src/rop/RopGetHierarchyTableHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,MAAM,0BAA0B,GAAG,IAAI,CAAC;AAExC;0BAC0B;AAC1B,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAExC;;;;;;;;;;GAUG;AACH,MAAM,OAAO,2BAA2B;IAAxC;QACoB,UAAK,GAAG,0BAA0B,CAAC;QACnC,8BAAyB,GAAG,CAAC,CAAC,CAAC,oBAAoB;IAuBvE,CAAC;IArBU,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,iFAAiF;QAErG,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,0BAA0B,CAAC,CAAC;YAC9C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;YAC3C,OAAO;QACX,CAAC;QAED,MAAM,IAAI,GAAa,MAAM,qBAAqB,CAAC,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAC9I,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAExH,MAAM,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAC;QAC9C,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;IACrD,CAAC;CACJ"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readPropertyTagArray } from "../codec/PropertyValue.js";
|
|
2
|
+
import { resolvePropertyValues, writePropertyValueSafely } from "./PropertyResolvers.js";
|
|
3
|
+
const ROP_ID_GET_PROPERTIES_SPECIFIC = 0x07;
|
|
4
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle isn't a
|
|
5
|
+
* folder/message (or doesn't exist)" - the same constant `RopGetHierarchyTableHandler`/`RopGetContentsTableHandler`
|
|
6
|
+
* use for their own analogous checks. Attachment/Logon objects (also spec-valid targets for this ROP) aren't
|
|
7
|
+
* supported in this pragmatic subset. */
|
|
8
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
9
|
+
/** `MAPI_E_TOO_BIG`, for more than `MAX_PROPERTY_TAG_COUNT` requested tags. */
|
|
10
|
+
const ERROR_TOO_BIG = 0x80040305;
|
|
11
|
+
/**
|
|
12
|
+
* `RopGetPropertiesSpecific` (`[MS-OXCPRPT]`/`[MS-OXCROPS]`): fetches named property values for a single
|
|
13
|
+
* already-opened Folder or Message object (`RopOpenFolder`/`RopOpenMessage`), by an explicit `PropertyTags`
|
|
14
|
+
* list - the single-object analog of `RopQueryRows`' per-row column fetch, reusing the exact same
|
|
15
|
+
* `PropertyResolvers` value-resolution logic (a folder/message handle's `entityUid` is already the same
|
|
16
|
+
* `"folder:"`/`"virtual:"`/`"message:"` target-string format a table row uses). Like `RopQueryRows`, this
|
|
17
|
+
* pragmatic subset always emits a `StandardPropertyRow` (`Flags = 0x00`, confirmed as the response's `RowData`
|
|
18
|
+
* format via `[MS-OXCROPS]`'s own "Success Response Buffer" page, which names `[MS-OXCDATA]` §2.8's
|
|
19
|
+
* `PropertyRow` structure directly) - an unsupported property falls back to a type-appropriate default rather
|
|
20
|
+
* than a `FlaggedPropertyRow`'s per-column error signaling, exactly as `RopQueryRows` already does. That includes
|
|
21
|
+
* a client-requested `propertyType` that doesn't match what a tag's own `propertyId` actually resolves to
|
|
22
|
+
* (`PropertyResolvers.writePropertyValueSafely`, shared with `RopQueryRowsHandler`) - not just a `propertyId`
|
|
23
|
+
* this codebase has no data for.
|
|
24
|
+
*
|
|
25
|
+
* `PropertySizeLimit`/`WantUnicode` are decoded (to advance the reader correctly) but not honored - this
|
|
26
|
+
* pragmatic subset never truncates a property value and always encodes strings the same way regardless of the
|
|
27
|
+
* client's Unicode preference (see `PropertyValue.ts`'s own string-encoding doc comments).
|
|
28
|
+
*
|
|
29
|
+
* @author Jean-Philippe Steinmetz
|
|
30
|
+
*/
|
|
31
|
+
export class RopGetPropertiesSpecificHandler {
|
|
32
|
+
constructor() {
|
|
33
|
+
this.ropId = ROP_ID_GET_PROPERTIES_SPECIFIC;
|
|
34
|
+
}
|
|
35
|
+
async handle(reader, writer, context) {
|
|
36
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
37
|
+
const inputHandleIndex = reader.readUInt8();
|
|
38
|
+
reader.readUInt16LE(); // PropertySizeLimit - no truncation support in this pragmatic subset
|
|
39
|
+
reader.readUInt16LE(); // WantUnicode - this pragmatic subset always encodes strings the same way regardless
|
|
40
|
+
const propertyTags = readPropertyTagArray(reader, reader.readUInt16LE());
|
|
41
|
+
const handle = context.session.handles[inputHandleIndex];
|
|
42
|
+
if (!handle || (handle.type !== "folder" && handle.type !== "message") || !propertyTags) {
|
|
43
|
+
writer.writeUInt8(ROP_ID_GET_PROPERTIES_SPECIFIC);
|
|
44
|
+
writer.writeUInt8(inputHandleIndex);
|
|
45
|
+
writer.writeUInt32LE(propertyTags ? ERROR_INVALID_OBJECT : ERROR_TOO_BIG);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const values = await resolvePropertyValues(handle.entityUid, propertyTags, context);
|
|
49
|
+
writer.writeUInt8(ROP_ID_GET_PROPERTIES_SPECIFIC);
|
|
50
|
+
writer.writeUInt8(inputHandleIndex);
|
|
51
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
52
|
+
writer.writeUInt8(0x00); // PropertyRow Flags - StandardPropertyRow, see class doc comment
|
|
53
|
+
propertyTags.forEach((tag, index) => writePropertyValueSafely(writer, tag.propertyType, values[index]));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=RopGetPropertiesSpecificHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopGetPropertiesSpecificHandler.js","sourceRoot":"","sources":["../../../src/rop/RopGetPropertiesSpecificHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAGzF,MAAM,8BAA8B,GAAG,IAAI,CAAC;AAE5C;;;yCAGyC;AACzC,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAExC,+EAA+E;AAC/E,MAAM,aAAa,GAAG,UAAU,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,+BAA+B;IAA5C;QACoB,UAAK,GAAG,8BAA8B,CAAC;IAyB3D,CAAC;IAvBU,KAAK,CAAC,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QAC/E,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,gBAAgB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,qEAAqE;QAC5F,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,qFAAqF;QAC5G,MAAM,YAAY,GAAG,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;QAEzE,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,IAAI,CAAC,YAAY,EAAE,CAAC;YACtF,MAAM,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC;YAClD,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;YACpC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;YAC1E,OAAO;QACX,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,MAAM,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;QAEpF,MAAM,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC;QAClD,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;QACpC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;QACjD,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,iEAAiE;QAC1F,YAAY,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,wBAAwB,CAAC,MAAM,EAAE,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5G,CAAC;CACJ"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { decodeGuid } from "../codec/MapiGuid.js";
|
|
2
|
+
import { assignOrGetNamedPropertyId } from "./NamedPropertyRegistry.js";
|
|
3
|
+
const ROP_ID_GET_PROPERTY_IDS_FROM_NAMES = 0x56;
|
|
4
|
+
/** `PropertyName.Kind` (`[MS-OXCDATA]` §2.6.1) values. */
|
|
5
|
+
const KIND_LID = 0x00;
|
|
6
|
+
const KIND_NAME = 0x01;
|
|
7
|
+
// KIND_NONE = 0xFF ("the property does not have an associated PropertyName field") is a real, spec-defined
|
|
8
|
+
// value but never resolvable to a real named property - handled by readPropertyName()'s default/`undefined`
|
|
9
|
+
// return rather than its own named constant.
|
|
10
|
+
/** The trailing UTF-16LE null-terminator code unit `PropertyName.Name` (Kind=0x01) is documented to always
|
|
11
|
+
* carry - stripped by `charCodeAt`/numeric comparison rather than a string literal containing the null
|
|
12
|
+
* character itself, to avoid embedding a raw control character in source text. */
|
|
13
|
+
const NULL_CODE_UNIT = 0;
|
|
14
|
+
/** The well-known MAPI HRESULT `MAPI_E_INVALID_OBJECT`, reused for "the referenced handle doesn't exist" - the
|
|
15
|
+
* same constant `RopGetPropertiesSpecificHandler` uses for its own analogous check. Real Exchange resolves
|
|
16
|
+
* named properties per-logon regardless of which specific object handle a client happens to call this ROP
|
|
17
|
+
* against (Store/Folder/Message all share one mailbox-wide mapping table) - this pragmatic subset only
|
|
18
|
+
* requires *some* real handle to exist, not a particular type, matching that "any object will do" semantics. */
|
|
19
|
+
const ERROR_INVALID_OBJECT = 0x80070005;
|
|
20
|
+
/** Decodes one `PropertyName` structure (`[MS-OXCDATA]` §2.6.1, confirmed field order this session: `Kind`
|
|
21
|
+
* **then** `GUID` - not the reverse). Returns `undefined` for `Kind = 0xFF` ("no associated PropertyName"),
|
|
22
|
+
* which cannot resolve to any real named property. */
|
|
23
|
+
function readPropertyName(reader) {
|
|
24
|
+
const kind = reader.readUInt8();
|
|
25
|
+
const guid = decodeGuid(reader);
|
|
26
|
+
if (kind === KIND_LID) {
|
|
27
|
+
return { guid, kind: "lid", lid: reader.readUInt32LE() };
|
|
28
|
+
}
|
|
29
|
+
if (kind === KIND_NAME) {
|
|
30
|
+
const nameSize = reader.readUInt8();
|
|
31
|
+
const nameBytes = reader.readBytes(nameSize);
|
|
32
|
+
// The spec text describing NameSize ("the number of bytes in the Name string that follows it") doesn't
|
|
33
|
+
// unambiguously say whether that count includes the field's own trailing 2-zero-byte terminator - this
|
|
34
|
+
// pragmatic subset treats it as inclusive (the common real-world convention for this style of
|
|
35
|
+
// length-prefixed MAPI string) and strips one trailing null code unit if present. Never exercised by
|
|
36
|
+
// this pragmatic subset's own Calendar property table (every property it needs is Kind=LID), so this
|
|
37
|
+
// is defensive completeness for an arbitrary client request, not a property on the critical path.
|
|
38
|
+
let name = nameBytes.toString("utf16le");
|
|
39
|
+
if (name.length > 0 && name.charCodeAt(name.length - 1) === NULL_CODE_UNIT) {
|
|
40
|
+
name = name.slice(0, -1);
|
|
41
|
+
}
|
|
42
|
+
return { guid, kind: "name", name };
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `RopGetPropertyIdsFromNames` (`[MS-OXCPRPT]`/`[MS-OXCROPS]` §2.2.8.1): resolves a list of named properties
|
|
48
|
+
* (`PropertyName` structures - a property-set `GUID` plus a numeric `LID` or string `Name`) into numeric
|
|
49
|
+
* property IDs a subsequent `RopSetProperties`/`RopGetPropertiesSpecific`/`RopQueryRows` call can use directly.
|
|
50
|
+
* This is the mechanism almost every Calendar-specific property (`PidLidAppointmentStartWhole`,
|
|
51
|
+
* `PidLidBusyStatus`, `PidLidAppointmentRecur`, ...) requires, since none of them have a fixed numeric
|
|
52
|
+
* `PidTag*`-style ID the way `PidTagSubject` does - see `NamedPropertyRegistry.ts`'s own doc comment for the
|
|
53
|
+
* session-scoped registry this delegates to.
|
|
54
|
+
*
|
|
55
|
+
* An unresolvable `PropertyName` (`Kind = 0xFF`) maps to `0x0000` in the response, per spec - this pragmatic
|
|
56
|
+
* subset has no other unmappable case (no permission/quota/hard-limit checks), so every `Kind = 0x00`/`0x01`
|
|
57
|
+
* entry always succeeds. `Flags` is decoded to advance past it correctly but not honored - see
|
|
58
|
+
* `assignOrGetNamedPropertyId`'s own doc comment for why "probe" and "assign" collapse to the same behavior
|
|
59
|
+
* for a purely session-scoped registry.
|
|
60
|
+
*
|
|
61
|
+
* @author Jean-Philippe Steinmetz
|
|
62
|
+
*/
|
|
63
|
+
export class RopGetPropertyIdsFromNamesHandler {
|
|
64
|
+
constructor() {
|
|
65
|
+
this.ropId = ROP_ID_GET_PROPERTY_IDS_FROM_NAMES;
|
|
66
|
+
}
|
|
67
|
+
handle(reader, writer, context) {
|
|
68
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
69
|
+
const inputHandleIndex = reader.readUInt8();
|
|
70
|
+
reader.readUInt8(); // Flags - see class doc comment
|
|
71
|
+
const propertyNameCount = reader.readUInt16LE();
|
|
72
|
+
const propertyNames = [];
|
|
73
|
+
for (let i = 0; i < propertyNameCount; i++) {
|
|
74
|
+
propertyNames.push(readPropertyName(reader));
|
|
75
|
+
}
|
|
76
|
+
if (!context.session.handles[inputHandleIndex]) {
|
|
77
|
+
writer.writeUInt8(ROP_ID_GET_PROPERTY_IDS_FROM_NAMES);
|
|
78
|
+
writer.writeUInt8(inputHandleIndex);
|
|
79
|
+
writer.writeUInt32LE(ERROR_INVALID_OBJECT);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const propertyIds = propertyNames.map((name) => (name ? assignOrGetNamedPropertyId(context.session, name) : 0x0000));
|
|
83
|
+
writer.writeUInt8(ROP_ID_GET_PROPERTY_IDS_FROM_NAMES);
|
|
84
|
+
writer.writeUInt8(inputHandleIndex);
|
|
85
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
86
|
+
writer.writeUInt16LE(propertyIds.length);
|
|
87
|
+
for (const id of propertyIds) {
|
|
88
|
+
writer.writeUInt16LE(id);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=RopGetPropertyIdsFromNamesHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopGetPropertyIdsFromNamesHandler.js","sourceRoot":"","sources":["../../../src/rop/RopGetPropertyIdsFromNamesHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { defaultHandleDataStore } from "./HandleDataCache.js";
|
|
2
|
+
/** The `HandleDataStore` a handler should use for `context`. */
|
|
3
|
+
export function handleDataStoreOf(context) {
|
|
4
|
+
return context.handleData ?? defaultHandleDataStore;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=RopHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopHandler.js","sourceRoot":"","sources":["../../../src/rop/RopHandler.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,sBAAsB,EAAwB,MAAM,sBAAsB,CAAC;AA8EpF,gEAAgE;AAChE,MAAM,UAAU,iBAAiB,CAAC,OAAuC;IACrE,OAAO,OAAO,CAAC,UAAU,IAAI,sBAAsB,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { BufferWriter } from "../codec/BufferCursor.js";
|
|
6
|
+
import { encodeGuid } from "../codec/MapiGuid.js";
|
|
7
|
+
import { FolderType } from "@rapidmx/restapi";
|
|
8
|
+
import { assignHandle } from "../MapiSessionManager.js";
|
|
9
|
+
import { assignOrGetFid } from "./FolderTarget.js";
|
|
10
|
+
const ROP_ID_LOGON = 0xfe;
|
|
11
|
+
/** A fixed replica identity shared by every logon in this deployment - legitimate per `[MS-OXCSTOR]`'s own
|
|
12
|
+
* "RopLogon ROP Success Response Buffer for Private Mailbox" section ("If the client did not set
|
|
13
|
+
* USE_PER_MDB_REPLID_MAPPING... this value MUST be identical for all private mailbox logons on the same RPC
|
|
14
|
+
* session"); this pragmatic subset never varies replica identity, so one fixed constant satisfies that. */
|
|
15
|
+
const REPL_ID = 1;
|
|
16
|
+
const REPL_GUID = "00000000-0000-0000-0000-000000000001";
|
|
17
|
+
/**
|
|
18
|
+
* Order matches `[MS-OXCSTOR]` §2.2.1.1.3 ("RopLogon ROP Success Response Buffer for Private Mailbox")'s
|
|
19
|
+
* `FolderIds` list exactly: Root, Deferred Action, Spooler Queue, IPM Subtree, Inbox, Outbox, Sent Items,
|
|
20
|
+
* Deleted Items, Common Views, Schedule, Search, Views, Shortcuts (13 folders total, confirmed against the
|
|
21
|
+
* spec page directly). Folders with a matching `FolderType` resolve to a real `Folder` when one exists for
|
|
22
|
+
* this mailbox; the rest are virtual placeholders. Root/IPM Subtree have no real backing row at all in this
|
|
23
|
+
* data model, matching how EAS's own `FolderSync` already represents "the root" as `ParentId "0"` rather than
|
|
24
|
+
* a real `Folder` - not a new gap this ROP introduces. Deferred Action/Spooler Queue/Common Views/Schedule/
|
|
25
|
+
* Search/Views/Shortcuts are MAPI-internal administrative folders this library's `FolderType` enum has no
|
|
26
|
+
* concept of at all (it models the REST/EAS-relevant folder kinds - Inbox/Sent/Drafts/Deleted/Outbox/Junk/
|
|
27
|
+
* Calendar/Contacts/Tasks/Notes/User - not Exchange's own internal bookkeeping folders). A real client rarely
|
|
28
|
+
* if ever tries to open most of these directly; documented gap, not silently dropped.
|
|
29
|
+
*/
|
|
30
|
+
const SPECIAL_FOLDERS = [
|
|
31
|
+
{ name: "root" },
|
|
32
|
+
{ name: "deferredAction" },
|
|
33
|
+
{ name: "spoolerQueue" },
|
|
34
|
+
{ name: "ipmSubtree" },
|
|
35
|
+
{ name: "inbox", folderType: FolderType.INBOX },
|
|
36
|
+
{ name: "outbox", folderType: FolderType.OUTBOX },
|
|
37
|
+
{ name: "sentItems", folderType: FolderType.SENT_ITEMS },
|
|
38
|
+
{ name: "deletedItems", folderType: FolderType.DELETED_ITEMS },
|
|
39
|
+
{ name: "commonViews" },
|
|
40
|
+
{ name: "schedule" },
|
|
41
|
+
{ name: "search" },
|
|
42
|
+
{ name: "views" },
|
|
43
|
+
{ name: "shortcuts" },
|
|
44
|
+
];
|
|
45
|
+
/**
|
|
46
|
+
* `RopLogon` (`[MS-OXCSTOR]`/`[MS-OXCROPS]`): the first ROP of any session, a prerequisite for every other
|
|
47
|
+
* ROP. This pragmatic subset always performs a private-mailbox logon against the mailbox `BaseMapiEmsmdbRoute`
|
|
48
|
+
* already resolved and authorized at `Connect` time - the request's own `Essdn` field is decoded (to advance
|
|
49
|
+
* past it correctly) but never trusted for mailbox identity, the same principle `BaseMapiEmsmdbRoute.Connect`
|
|
50
|
+
* already applies to that request's `UserDn` field. Because mailbox resolution already happened upstream,
|
|
51
|
+
* this handler has no real failure path to model (`ecUnknownUser`/`ecLoginFailure`/... are all deferred).
|
|
52
|
+
*
|
|
53
|
+
* @author Jean-Philippe Steinmetz
|
|
54
|
+
*/
|
|
55
|
+
export class RopLogonHandler {
|
|
56
|
+
constructor() {
|
|
57
|
+
this.ropId = ROP_ID_LOGON;
|
|
58
|
+
}
|
|
59
|
+
async handle(reader, writer, context) {
|
|
60
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
61
|
+
const outputHandleIndex = reader.readUInt8();
|
|
62
|
+
const logonFlags = reader.readUInt8();
|
|
63
|
+
reader.readUInt32LE(); // OpenFlags - unused; every logon behaves as a plain private-mailbox logon
|
|
64
|
+
reader.readUInt32LE(); // StoreState - per spec, unused and always 0
|
|
65
|
+
const essdnSize = reader.readUInt16LE();
|
|
66
|
+
if (essdnSize > 0) {
|
|
67
|
+
reader.readBytes(essdnSize); // Essdn - never trusted for mailbox identity, see class doc comment
|
|
68
|
+
}
|
|
69
|
+
const fids = await this.assignFolderIds(context);
|
|
70
|
+
assignHandle(context.session, outputHandleIndex, { type: "logon", entityUid: context.mailboxUid });
|
|
71
|
+
writer.writeUInt8(ROP_ID_LOGON);
|
|
72
|
+
writer.writeUInt8(outputHandleIndex);
|
|
73
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
74
|
+
writer.writeUInt8(logonFlags); // echoed back unchanged, per spec
|
|
75
|
+
for (const folder of SPECIAL_FOLDERS) {
|
|
76
|
+
writer.writeBigUInt64LE(BigInt(fids[folder.name]));
|
|
77
|
+
}
|
|
78
|
+
writer.writeUInt8(0x01); // ResponseFlags - Reserved bit only; no OwnerRight/SendAsRight/OOF claims in this subset
|
|
79
|
+
writer.writeBytes(encodeGuid(context.mailboxUid));
|
|
80
|
+
writer.writeUInt16LE(REPL_ID);
|
|
81
|
+
writer.writeBytes(encodeGuid(REPL_GUID));
|
|
82
|
+
writer.writeBytes(encodeLogonTime(new Date()));
|
|
83
|
+
writer.writeBigUInt64LE(1n); // GwartTime - fixed constant; no real GWART/AD concept in this deployment
|
|
84
|
+
writer.writeUInt32LE(0); // StoreState - unused
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Assigns this session's FIDs for the 13 special folders, remembering each one's real-`Folder`-or-virtual-
|
|
88
|
+
* placeholder target in `session.folderIds` so a later `RopOpenFolder` can resolve it back to something
|
|
89
|
+
* real. Delegates to `FolderTarget.assignOrGetFid` - the same "reuse an existing FID for this target if one
|
|
90
|
+
* was already assigned, otherwise mint the next free one" logic `RopGetHierarchyTable`/`RopQueryRows` use
|
|
91
|
+
* for child folders - rather than wholesale-replacing `session.folderIds` with a freshly-numbered map
|
|
92
|
+
* starting back at FID 1. A second `RopLogon` on a live session (spec-legal - a real client can reconnect
|
|
93
|
+
* or re-logon within one session context) previously invalidated every FID a client had already learned
|
|
94
|
+
* for a child folder via `RopGetHierarchyTable`/`RopQueryRows` (all assigned starting at FID 14, since the
|
|
95
|
+
* old code always reset the counter to 1-13 for just these specials) and could even cause FIDs 14+ to be
|
|
96
|
+
* silently re-issued to a *different* folder than the one the client last associated with that number.
|
|
97
|
+
* Reusing `assignOrGetFid` means a re-`RopLogon` is now purely additive: these 13 targets get their
|
|
98
|
+
* existing FIDs back if this is a second logon, and any child-folder FIDs already assigned are left alone.
|
|
99
|
+
*/
|
|
100
|
+
async assignFolderIds(context) {
|
|
101
|
+
// The four real-folder-type lookups (Inbox/Outbox/Sent Items/Deleted Items) are independent of each
|
|
102
|
+
// other, so resolve them concurrently rather than one sequential round trip per special folder -
|
|
103
|
+
// FID *assignment* itself still happens afterward, in SPECIAL_FOLDERS' own fixed order, so which FID
|
|
104
|
+
// ends up bound to which target is unaffected by fetch order.
|
|
105
|
+
const targets = await Promise.all(SPECIAL_FOLDERS.map(async (folder) => {
|
|
106
|
+
if (!folder.folderType) {
|
|
107
|
+
return `virtual:${folder.name}`;
|
|
108
|
+
}
|
|
109
|
+
// The oldest folder of the type, like restapi's findOrCreateWellKnownFolder, so a mailbox that somehow
|
|
110
|
+
// has two gets the same one every time.
|
|
111
|
+
const existing = await context.folderRepo.find({ mailboxUid: context.mailboxUid, type: folder.folderType, sort: { dateCreated: "ASC", uid: "ASC" }, limit: 1 }, { ignoreACL: true, limit: 1 });
|
|
112
|
+
return existing[0] ? `folder:${existing[0].uid}` : `virtual:${folder.name}`;
|
|
113
|
+
}));
|
|
114
|
+
const fids = {};
|
|
115
|
+
SPECIAL_FOLDERS.forEach((folder, index) => {
|
|
116
|
+
fids[folder.name] = assignOrGetFid(context.session, targets[index]);
|
|
117
|
+
});
|
|
118
|
+
return fids;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** `LogonTime` structure (`[MS-OXCROPS]` "LogonTime Structure"): `Seconds`/`Minutes`/`Hour`/`DayOfWeek`
|
|
122
|
+
* (1 byte each, `DayOfWeek` Sunday=0)/`Day`/`Month` (1 byte each, `Month` January=1)/`Year` (2 bytes) - all
|
|
123
|
+
* UTC, confirmed against the spec's own byte-layout table. */
|
|
124
|
+
function encodeLogonTime(date) {
|
|
125
|
+
const writer = new BufferWriter();
|
|
126
|
+
writer.writeUInt8(date.getUTCSeconds());
|
|
127
|
+
writer.writeUInt8(date.getUTCMinutes());
|
|
128
|
+
writer.writeUInt8(date.getUTCHours());
|
|
129
|
+
writer.writeUInt8(date.getUTCDay());
|
|
130
|
+
writer.writeUInt8(date.getUTCDate());
|
|
131
|
+
writer.writeUInt8(date.getUTCMonth() + 1);
|
|
132
|
+
writer.writeUInt16LE(date.getUTCFullYear());
|
|
133
|
+
return writer.toBuffer();
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=RopLogonHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopLogonHandler.js","sourceRoot":"","sources":["../../../src/rop/RopLogonHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { assignHandle } from "../MapiSessionManager.js";
|
|
2
|
+
const ROP_ID_OPEN_FOLDER = 0x02;
|
|
3
|
+
/** The well-known MAPI HRESULT `MAPI_E_NOT_FOUND`, reused as this handler's only failure `ReturnValue` (an
|
|
4
|
+
* unrecognized `FolderId` - one not present in `session.folderIds`, i.e. not something `RopLogon` handed out
|
|
5
|
+
* this session). */
|
|
6
|
+
const ERROR_NOT_FOUND = 0x8004010f;
|
|
7
|
+
/**
|
|
8
|
+
* `RopOpenFolder` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`): opens a folder by FID, producing a new `"folder"` Server
|
|
9
|
+
* object handle later ROPs (`RopGetHierarchyTable`, a future `RopGetContentsTable`) reference by
|
|
10
|
+
* `InputHandleIndex`. The request's own `OpenModeFlags` (`OpenSoftDeleted`, ...) is decoded to advance past
|
|
11
|
+
* it correctly but not acted on - this pragmatic subset has no soft-deleted-folder recovery flow.
|
|
12
|
+
*
|
|
13
|
+
* Private-mailbox-only response shape: `IsGhosted` is always `0` and its conditional
|
|
14
|
+
* `ServerCount`/`CheapServerCount`/`Servers` fields are never present, since `IsGhosted` and its trailing
|
|
15
|
+
* fields are documented as public-folder-only (`[MS-OXCFOLD]`'s own "RopOpenFolder ROP Response Buffer"
|
|
16
|
+
* page). `HasRules` is always `0` - this library has no `[MS-OXORULE]` rules-engine support.
|
|
17
|
+
*
|
|
18
|
+
* @author Jean-Philippe Steinmetz
|
|
19
|
+
*/
|
|
20
|
+
export class RopOpenFolderHandler {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.ropId = ROP_ID_OPEN_FOLDER;
|
|
23
|
+
this.responseHandleIndexOffset = 3; // OutputHandleIndex
|
|
24
|
+
}
|
|
25
|
+
handle(reader, writer, context) {
|
|
26
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
27
|
+
reader.readUInt8(); // InputHandleIndex - the logon handle this open is performed against; only one
|
|
28
|
+
// logon exists per session in this pragmatic subset, so it's consumed to advance the reader correctly
|
|
29
|
+
// but never separately validated.
|
|
30
|
+
const outputHandleIndex = reader.readUInt8();
|
|
31
|
+
reader.readUInt8(); // OpenModeFlags - OpenSoftDeleted not supported in this pragmatic subset
|
|
32
|
+
const folderId = reader.readBigUInt64LE();
|
|
33
|
+
const target = context.session.folderIds[folderId.toString()];
|
|
34
|
+
if (!target) {
|
|
35
|
+
writer.writeUInt8(ROP_ID_OPEN_FOLDER);
|
|
36
|
+
writer.writeUInt8(outputHandleIndex);
|
|
37
|
+
writer.writeUInt32LE(ERROR_NOT_FOUND);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
assignHandle(context.session, outputHandleIndex, { type: "folder", entityUid: target });
|
|
41
|
+
writer.writeUInt8(ROP_ID_OPEN_FOLDER);
|
|
42
|
+
writer.writeUInt8(outputHandleIndex);
|
|
43
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
44
|
+
writer.writeUInt8(0); // HasRules - no rules-engine support
|
|
45
|
+
writer.writeUInt8(0); // IsGhosted - always false for a private mailbox
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=RopOpenFolderHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopOpenFolderHandler.js","sourceRoot":"","sources":["../../../src/rop/RopOpenFolderHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC;;oBAEoB;AACpB,MAAM,eAAe,GAAG,UAAU,CAAC;AAEnC;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,oBAAoB;IAAjC;QACoB,UAAK,GAAG,kBAAkB,CAAC;QAC3B,8BAAyB,GAAG,CAAC,CAAC,CAAC,oBAAoB;IA2BvE,CAAC;IAzBU,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QACzE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,+EAA+E;QACnG,sGAAsG;QACtG,kCAAkC;QAClC,MAAM,iBAAiB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,yEAAyE;QAC7F,MAAM,QAAQ,GAAW,MAAM,CAAC,eAAe,EAAE,CAAC;QAElD,MAAM,MAAM,GAAuB,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClF,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;YACtC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;YACtC,OAAO;QACX,CAAC;QAED,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QAExF,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;QACtC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;QACjD,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,qCAAqC;QAC3D,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,iDAAiD;IAC3E,CAAC;CACJ"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { writeTypedString } from "../codec/TypedString.js";
|
|
2
|
+
import { assignHandle } from "../MapiSessionManager.js";
|
|
3
|
+
import { resolveCalendarEventInfo } from "./CalendarEventTarget.js";
|
|
4
|
+
import { resolveContactInfo } from "./ContactTarget.js";
|
|
5
|
+
import { resolveMessageInfo } from "./MessageTarget.js";
|
|
6
|
+
import { resolveTaskInfo } from "./TaskTarget.js";
|
|
7
|
+
const ROP_ID_OPEN_MESSAGE = 0x03;
|
|
8
|
+
/** The well-known MAPI HRESULT `MAPI_E_NOT_FOUND`, reused as this handler's only failure `ReturnValue` - an
|
|
9
|
+
* unrecognized `MessageId`, i.e. not something a `RopQueryRows` `PidTagMid` column has handed out this session
|
|
10
|
+
* (see `MessageTarget.assignOrGetMid`'s own doc comment). Same constant `RopOpenFolderHandler` uses for its
|
|
11
|
+
* own analogous `FolderId` lookup miss. */
|
|
12
|
+
const ERROR_NOT_FOUND = 0x8004010f;
|
|
13
|
+
/**
|
|
14
|
+
* `RopOpenMessage` (`[MS-OXCMSG]`/`[MS-OXCROPS]`): opens a message by MID, producing a new `"message"` Server
|
|
15
|
+
* object handle later ROPs (`RopGetPropertiesSpecific`, `RopOpenStream`) reference by `InputHandleIndex`. The
|
|
16
|
+
* request's own `FolderId` (the message's expected parent folder) and `CodePageId` are decoded to advance past
|
|
17
|
+
* them correctly but not otherwise acted on - a MID is resolved directly via `session.messageIds`
|
|
18
|
+
* (`MessageTarget.assignOrGetMid`), the same "the session already knows what this small integer refers to"
|
|
19
|
+
* design `RopOpenFolderHandler` uses for FIDs, so cross-checking the caller-supplied `FolderId` would be pure
|
|
20
|
+
* redundancy. `OpenModeFlags` is likewise decoded but not honored - this pragmatic subset only ever opens a
|
|
21
|
+
* message for reading (no `RopSetProperties`/`RopSaveChangesMessage` write-back support yet).
|
|
22
|
+
*
|
|
23
|
+
* **Pragmatic success response**: `HasNamedProperties` is always `false` - a conservative hint value only, not
|
|
24
|
+
* a claim that named properties are unsupported (`RopGetPropertyIdsFromNames`/`PropertyResolvers.ts` do resolve
|
|
25
|
+
* them for a subsequently-opened Calendar item; a real client calls `RopGetPropertyIdsFromNames` unconditionally
|
|
26
|
+
* for a known Calendar message class regardless of this flag, so leaving it `false` doesn't block that path).
|
|
27
|
+
* The `SubjectPrefix`/`NormalizedSubject` `TypedString`s are "no prefix" + the item's plain subject/title (this
|
|
28
|
+
* data model has no separate prefix/normalized-subject split the way real Exchange does), and the recipient
|
|
29
|
+
* table (`RecipientCount`/`ColumnCount`/`RecipientColumns`/`RowCount`/`RecipientRows`) is always empty - reading
|
|
30
|
+
* a message's `To`/`Cc` list is a documented gap in this pass, not silently wrong data.
|
|
31
|
+
*
|
|
32
|
+
* @author Jean-Philippe Steinmetz
|
|
33
|
+
*/
|
|
34
|
+
export class RopOpenMessageHandler {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.ropId = ROP_ID_OPEN_MESSAGE;
|
|
37
|
+
this.responseHandleIndexOffset = 3; // OutputHandleIndex
|
|
38
|
+
}
|
|
39
|
+
async handle(reader, writer, context) {
|
|
40
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
41
|
+
reader.readUInt8(); // InputHandleIndex - the folder/logon handle this open is performed against; not
|
|
42
|
+
// separately validated, matching RopOpenFolderHandler's own treatment of its input handle.
|
|
43
|
+
const outputHandleIndex = reader.readUInt8();
|
|
44
|
+
reader.readUInt16LE(); // CodePageId - this pragmatic subset always encodes strings as UTF-16LE/UTF-8 directly
|
|
45
|
+
reader.readBigUInt64LE(); // FolderId - the message's expected parent folder; not cross-checked, see class doc comment
|
|
46
|
+
reader.readUInt8(); // OpenModeFlags - read-only opens only in this pragmatic subset
|
|
47
|
+
const messageId = reader.readBigUInt64LE();
|
|
48
|
+
const target = context.session.messageIds[messageId.toString()];
|
|
49
|
+
if (!target) {
|
|
50
|
+
writer.writeUInt8(ROP_ID_OPEN_MESSAGE);
|
|
51
|
+
writer.writeUInt8(outputHandleIndex);
|
|
52
|
+
writer.writeUInt32LE(ERROR_NOT_FOUND);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// A calendar/contact/task item's "subject" is its title/display name (PidTagSubject, the same tag a
|
|
56
|
+
// Message uses) - see CalendarEventTarget.ts's own doc comment for why no separate MID-registry
|
|
57
|
+
// mechanism is needed for any of these target kinds.
|
|
58
|
+
let subject;
|
|
59
|
+
if (target.startsWith("calendarEvent:")) {
|
|
60
|
+
subject = (await resolveCalendarEventInfo(target, context.calendarEventRepo)).title;
|
|
61
|
+
}
|
|
62
|
+
else if (target.startsWith("contact:")) {
|
|
63
|
+
subject = context.contactRepo ? (await resolveContactInfo(target, context.contactRepo)).displayName : "";
|
|
64
|
+
}
|
|
65
|
+
else if (target.startsWith("task:")) {
|
|
66
|
+
subject = context.taskRepo ? (await resolveTaskInfo(target, context.taskRepo)).title : "";
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
subject = (await resolveMessageInfo(target, context.messageRepo)).subject;
|
|
70
|
+
}
|
|
71
|
+
assignHandle(context.session, outputHandleIndex, { type: "message", entityUid: target });
|
|
72
|
+
writer.writeUInt8(ROP_ID_OPEN_MESSAGE);
|
|
73
|
+
writer.writeUInt8(outputHandleIndex);
|
|
74
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
75
|
+
writer.writeUInt8(0); // HasNamedProperties - conservative fixed value, see class doc comment
|
|
76
|
+
writeTypedString(writer, undefined); // SubjectPrefix - no prefix/normalized-subject split in this data model
|
|
77
|
+
writeTypedString(writer, subject); // NormalizedSubject
|
|
78
|
+
writer.writeUInt16LE(0); // RecipientCount - reading recipients is a documented gap, see class doc comment
|
|
79
|
+
writer.writeUInt16LE(0); // ColumnCount
|
|
80
|
+
writer.writeUInt8(0); // RowCount
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=RopOpenMessageHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopOpenMessageHandler.js","sourceRoot":"","sources":["../../../src/rop/RopOpenMessageHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { PropertyType, readPropertyTag } from "../codec/PropertyValue.js";
|
|
2
|
+
import { assignHandle } from "../MapiSessionManager.js";
|
|
3
|
+
import { loadStreamBody, PID_TAG_BODY } from "./MessageBodyStream.js";
|
|
4
|
+
const ROP_ID_OPEN_STREAM = 0x2b;
|
|
5
|
+
/** `[MS-OXCPRPT]` §2.2.14.1 `OpenModeFlags` values - `ReadOnly` opens the existing value for reading;
|
|
6
|
+
* `ReadWrite`/`Create` both open for writing (`Create` additionally discards any existing value first, "the
|
|
7
|
+
* mode required for a property that has not been set" per the spec - always true for a fresh
|
|
8
|
+
* `RopCreateMessage` draft's body, which is the only write-mode case this pragmatic subset ever produces). */
|
|
9
|
+
const OPEN_MODE_READ_ONLY = 0x00;
|
|
10
|
+
/** The well-known MAPI HRESULT `MAPI_E_NOT_FOUND`, reused for "the referenced handle isn't a message, or the
|
|
11
|
+
* requested property isn't one this pragmatic subset can stream" - the same constant `RopOpenFolderHandler`
|
|
12
|
+
* uses for its own analogous lookup-miss case. */
|
|
13
|
+
const ERROR_NOT_FOUND = 0x8004010f;
|
|
14
|
+
/**
|
|
15
|
+
* `RopOpenStream` (`[MS-OXCPRPT]`/`[MS-OXCROPS]`): opens a property for streaming access, producing a new
|
|
16
|
+
* `"stream"` Server object handle `RopReadStream`/`RopWriteStream` reads/writes by `InputHandleIndex`.
|
|
17
|
+
* **Pragmatic subset**: only `PidTagBody` (`PtypString`, the plain-text message body) on a `"message"` handle
|
|
18
|
+
* is supported - a real client also streams `PidTagHtml`/`PidTagRtfCompressed` (rich body formats) and
|
|
19
|
+
* folder/attachment binary properties, none of which this pass implements; requesting any other `PropertyTag`,
|
|
20
|
+
* or opening a stream against a non-message handle, fails with `MAPI_E_NOT_FOUND` rather than succeeding with
|
|
21
|
+
* wrong data.
|
|
22
|
+
*
|
|
23
|
+
* `OpenModeFlags` (`[MS-OXCPRPT]` §2.2.14.1) selects which of two independent code paths runs:
|
|
24
|
+
* - `ReadOnly` (`0x00`): the step-7 behavior - resolves the real, already-saved message's body via
|
|
25
|
+
* `MessageBodyStream.resolveMessageBodyBytes()` (`BlobStore` + `mailparser`) and reports its length as
|
|
26
|
+
* `StreamSize`, for `RopReadStream` to read back.
|
|
27
|
+
* - `ReadWrite`/`Create` (`0x01`/`0x02`): a **write-mode** stream, the step-8 addition compose/send needs to
|
|
28
|
+
* accept a message body via `RopWriteStream` - `StreamSize` is always `0` (matching `Create`'s "discards the
|
|
29
|
+
* existing value" semantics; this pragmatic subset never opens `ReadWrite` against a body that already has
|
|
30
|
+
* content) and the new handle's `writeTargetHandleIndex` remembers which message handle to write back into,
|
|
31
|
+
* the link `RopWriteStream`/`RopSaveChangesMessageHandler`/`RopSubmitMessageHandler` use to find it again.
|
|
32
|
+
*
|
|
33
|
+
* @author Jean-Philippe Steinmetz
|
|
34
|
+
*/
|
|
35
|
+
export class RopOpenStreamHandler {
|
|
36
|
+
constructor() {
|
|
37
|
+
this.ropId = ROP_ID_OPEN_STREAM;
|
|
38
|
+
this.responseHandleIndexOffset = 3; // OutputHandleIndex
|
|
39
|
+
}
|
|
40
|
+
async handle(reader, writer, context) {
|
|
41
|
+
reader.readUInt8(); // LogonId - this pragmatic subset doesn't track multiple concurrent logons per session
|
|
42
|
+
const inputHandleIndex = reader.readUInt8();
|
|
43
|
+
const outputHandleIndex = reader.readUInt8();
|
|
44
|
+
const propertyTag = readPropertyTag(reader);
|
|
45
|
+
const openModeFlags = reader.readUInt8();
|
|
46
|
+
const handle = context.session.handles[inputHandleIndex];
|
|
47
|
+
const isSupportedProperty = propertyTag.propertyId === PID_TAG_BODY && propertyTag.propertyType === PropertyType.PtypString;
|
|
48
|
+
if (!handle || handle.type !== "message" || !isSupportedProperty) {
|
|
49
|
+
writer.writeUInt8(ROP_ID_OPEN_STREAM);
|
|
50
|
+
writer.writeUInt8(outputHandleIndex);
|
|
51
|
+
writer.writeUInt32LE(ERROR_NOT_FOUND);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
let streamSize;
|
|
55
|
+
if (openModeFlags === OPEN_MODE_READ_ONLY) {
|
|
56
|
+
const stream = assignHandle(context.session, outputHandleIndex, {
|
|
57
|
+
type: "stream",
|
|
58
|
+
entityUid: handle.entityUid,
|
|
59
|
+
propertyId: propertyTag.propertyId,
|
|
60
|
+
propertyType: propertyTag.propertyType,
|
|
61
|
+
streamPosition: 0,
|
|
62
|
+
});
|
|
63
|
+
// Parsed once here and cached per handle, not re-parsed by every RopReadStream.
|
|
64
|
+
streamSize = (await loadStreamBody(context, outputHandleIndex, stream)).length;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
streamSize = 0;
|
|
68
|
+
assignHandle(context.session, outputHandleIndex, {
|
|
69
|
+
type: "stream",
|
|
70
|
+
entityUid: handle.entityUid,
|
|
71
|
+
propertyId: propertyTag.propertyId,
|
|
72
|
+
propertyType: propertyTag.propertyType,
|
|
73
|
+
writeTargetHandleIndex: inputHandleIndex,
|
|
74
|
+
writeTargetGeneration: handle.generation,
|
|
75
|
+
writeSize: 0,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
writer.writeUInt8(ROP_ID_OPEN_STREAM);
|
|
79
|
+
writer.writeUInt8(outputHandleIndex);
|
|
80
|
+
writer.writeUInt32LE(0); // ReturnValue - success
|
|
81
|
+
writer.writeUInt32LE(streamSize); // StreamSize
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=RopOpenStreamHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopOpenStreamHandler.js","sourceRoot":"","sources":["../../../src/rop/RopOpenStreamHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGtE,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC;;;8GAG8G;AAC9G,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC;;kDAEkD;AAClD,MAAM,eAAe,GAAG,UAAU,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,oBAAoB;IAAjC;QACoB,UAAK,GAAG,kBAAkB,CAAC;QAC3B,8BAAyB,GAAG,CAAC,CAAC,CAAC,oBAAoB;IA+CvE,CAAC;IA7CU,KAAK,CAAC,MAAM,CAAC,MAAoB,EAAE,MAAoB,EAAE,OAAmB;QAC/E,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,uFAAuF;QAC3G,MAAM,gBAAgB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,MAAM,iBAAiB,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QACrD,MAAM,WAAW,GAAgB,eAAe,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,aAAa,GAAW,MAAM,CAAC,SAAS,EAAE,CAAC;QAEjD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACzD,MAAM,mBAAmB,GAAG,WAAW,CAAC,UAAU,KAAK,YAAY,IAAI,WAAW,CAAC,YAAY,KAAK,YAAY,CAAC,UAAU,CAAC;QAC5H,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC/D,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;YACtC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;YACtC,OAAO;QACX,CAAC;QAED,IAAI,UAAkB,CAAC;QACvB,IAAI,aAAa,KAAK,mBAAmB,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE;gBAC5D,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,UAAU,EAAE,WAAW,CAAC,UAAU;gBAClC,YAAY,EAAE,WAAW,CAAC,YAAY;gBACtC,cAAc,EAAE,CAAC;aACpB,CAAC,CAAC;YACH,gFAAgF;YAChF,UAAU,GAAG,CAAC,MAAM,cAAc,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;QACnF,CAAC;aAAM,CAAC;YACJ,UAAU,GAAG,CAAC,CAAC;YACf,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE;gBAC7C,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,UAAU,EAAE,WAAW,CAAC,UAAU;gBAClC,YAAY,EAAE,WAAW,CAAC,YAAY;gBACtC,sBAAsB,EAAE,gBAAgB;gBACxC,qBAAqB,EAAE,MAAM,CAAC,UAAU;gBACxC,SAAS,EAAE,CAAC;aACf,CAAC,CAAC;QACP,CAAC;QAED,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;QACtC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB;QACjD,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa;IACnD,CAAC;CACJ"}
|