@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.
Files changed (204) hide show
  1. package/LICENSE +382 -0
  2. package/README.md +60 -0
  3. package/dist/lib/BaseMapiEmsmdbRoute.js +489 -0
  4. package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -0
  5. package/dist/lib/BaseMapiNspiRoute.js +114 -0
  6. package/dist/lib/BaseMapiNspiRoute.js.map +1 -0
  7. package/dist/lib/MapiSessionManager.js +475 -0
  8. package/dist/lib/MapiSessionManager.js.map +1 -0
  9. package/dist/lib/RegexPatternUtils.js +30 -0
  10. package/dist/lib/RegexPatternUtils.js.map +1 -0
  11. package/dist/lib/RopDispatcher.js +145 -0
  12. package/dist/lib/RopDispatcher.js.map +1 -0
  13. package/dist/lib/codec/AppointmentRecurrence.js +315 -0
  14. package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
  15. package/dist/lib/codec/BufferCursor.js +242 -0
  16. package/dist/lib/codec/BufferCursor.js.map +1 -0
  17. package/dist/lib/codec/GlobalObjectId.js +98 -0
  18. package/dist/lib/codec/GlobalObjectId.js.map +1 -0
  19. package/dist/lib/codec/MapiGuid.js +41 -0
  20. package/dist/lib/codec/MapiGuid.js.map +1 -0
  21. package/dist/lib/codec/MapiTimeZone.js +126 -0
  22. package/dist/lib/codec/MapiTimeZone.js.map +1 -0
  23. package/dist/lib/codec/PropertyValue.js +240 -0
  24. package/dist/lib/codec/PropertyValue.js.map +1 -0
  25. package/dist/lib/codec/RopBuffer.js +38 -0
  26. package/dist/lib/codec/RopBuffer.js.map +1 -0
  27. package/dist/lib/codec/TypedString.js +54 -0
  28. package/dist/lib/codec/TypedString.js.map +1 -0
  29. package/dist/lib/index.js +99 -0
  30. package/dist/lib/index.js.map +1 -0
  31. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +85 -0
  32. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
  33. package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
  34. package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
  35. package/dist/lib/mongo/index.js +7 -0
  36. package/dist/lib/mongo/index.js.map +1 -0
  37. package/dist/lib/mongo.js +6 -0
  38. package/dist/lib/mongo.js.map +1 -0
  39. package/dist/lib/nspi/NspiBindHandler.js +54 -0
  40. package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
  41. package/dist/lib/nspi/NspiCodec.js +106 -0
  42. package/dist/lib/nspi/NspiCodec.js.map +1 -0
  43. package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
  44. package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
  45. package/dist/lib/rop/AddressList.js +114 -0
  46. package/dist/lib/rop/AddressList.js.map +1 -0
  47. package/dist/lib/rop/CalendarEventTarget.js +30 -0
  48. package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
  49. package/dist/lib/rop/CalendarNamedProperties.js +57 -0
  50. package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
  51. package/dist/lib/rop/ContactTarget.js +19 -0
  52. package/dist/lib/rop/ContactTarget.js.map +1 -0
  53. package/dist/lib/rop/ContentsTable.js +60 -0
  54. package/dist/lib/rop/ContentsTable.js.map +1 -0
  55. package/dist/lib/rop/ExecuteBudget.js +82 -0
  56. package/dist/lib/rop/ExecuteBudget.js.map +1 -0
  57. package/dist/lib/rop/FastTransferStream.js +195 -0
  58. package/dist/lib/rop/FastTransferStream.js.map +1 -0
  59. package/dist/lib/rop/FolderTarget.js +118 -0
  60. package/dist/lib/rop/FolderTarget.js.map +1 -0
  61. package/dist/lib/rop/HandleDataCache.js +412 -0
  62. package/dist/lib/rop/HandleDataCache.js.map +1 -0
  63. package/dist/lib/rop/MeetingMessageClassHandler.js +241 -0
  64. package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
  65. package/dist/lib/rop/MessageBodyStream.js +68 -0
  66. package/dist/lib/rop/MessageBodyStream.js.map +1 -0
  67. package/dist/lib/rop/MessageTarget.js +57 -0
  68. package/dist/lib/rop/MessageTarget.js.map +1 -0
  69. package/dist/lib/rop/NamedPropertyRegistry.js +69 -0
  70. package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
  71. package/dist/lib/rop/PropertyResolvers.js +353 -0
  72. package/dist/lib/rop/PropertyResolvers.js.map +1 -0
  73. package/dist/lib/rop/RepoPaging.js +54 -0
  74. package/dist/lib/rop/RepoPaging.js.map +1 -0
  75. package/dist/lib/rop/RestapiRules.js +52 -0
  76. package/dist/lib/rop/RestapiRules.js.map +1 -0
  77. package/dist/lib/rop/RopCreateMessageHandler.js +55 -0
  78. package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
  79. package/dist/lib/rop/RopDeleteFolderHandler.js +174 -0
  80. package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
  81. package/dist/lib/rop/RopDeleteMessagesHandler.js +110 -0
  82. package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
  83. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
  84. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
  85. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +60 -0
  86. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
  87. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +76 -0
  88. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
  89. package/dist/lib/rop/RopGetContentsTableHandler.js +55 -0
  90. package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
  91. package/dist/lib/rop/RopGetHierarchyTableHandler.js +42 -0
  92. package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
  93. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +56 -0
  94. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
  95. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
  96. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
  97. package/dist/lib/rop/RopHandler.js +6 -0
  98. package/dist/lib/rop/RopHandler.js.map +1 -0
  99. package/dist/lib/rop/RopLogonHandler.js +135 -0
  100. package/dist/lib/rop/RopLogonHandler.js.map +1 -0
  101. package/dist/lib/rop/RopOpenFolderHandler.js +48 -0
  102. package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
  103. package/dist/lib/rop/RopOpenMessageHandler.js +83 -0
  104. package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
  105. package/dist/lib/rop/RopOpenStreamHandler.js +84 -0
  106. package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
  107. package/dist/lib/rop/RopQueryRowsHandler.js +120 -0
  108. package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
  109. package/dist/lib/rop/RopReadStreamHandler.js +73 -0
  110. package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
  111. package/dist/lib/rop/RopReleaseHandler.js +23 -0
  112. package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
  113. package/dist/lib/rop/RopSaveChangesMessageHandler.js +269 -0
  114. package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
  115. package/dist/lib/rop/RopSetColumnsHandler.js +50 -0
  116. package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
  117. package/dist/lib/rop/RopSetPropertiesHandler.js +173 -0
  118. package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
  119. package/dist/lib/rop/RopSubmitMessageHandler.js +342 -0
  120. package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
  121. package/dist/lib/rop/RopWriteStreamHandler.js +72 -0
  122. package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
  123. package/dist/lib/rop/TaskNamedProperties.js +29 -0
  124. package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
  125. package/dist/lib/rop/TaskTarget.js +15 -0
  126. package/dist/lib/rop/TaskTarget.js.map +1 -0
  127. package/dist/lib/rop/TransportSend.js +16 -0
  128. package/dist/lib/rop/TransportSend.js.map +1 -0
  129. package/dist/lib/sql/MapiEmsmdbRouteSQL.js +85 -0
  130. package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
  131. package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
  132. package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
  133. package/dist/lib/sql/index.js +7 -0
  134. package/dist/lib/sql/index.js.map +1 -0
  135. package/dist/lib/sql.js +6 -0
  136. package/dist/lib/sql.js.map +1 -0
  137. package/dist/types/BaseMapiEmsmdbRoute.d.ts +139 -0
  138. package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
  139. package/dist/types/MapiSessionManager.d.ts +285 -0
  140. package/dist/types/RegexPatternUtils.d.ts +14 -0
  141. package/dist/types/RopDispatcher.d.ts +53 -0
  142. package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
  143. package/dist/types/codec/BufferCursor.d.ts +92 -0
  144. package/dist/types/codec/GlobalObjectId.d.ts +15 -0
  145. package/dist/types/codec/MapiGuid.d.ts +6 -0
  146. package/dist/types/codec/MapiTimeZone.d.ts +7 -0
  147. package/dist/types/codec/PropertyValue.d.ts +87 -0
  148. package/dist/types/codec/RopBuffer.d.ts +32 -0
  149. package/dist/types/codec/TypedString.d.ts +17 -0
  150. package/dist/types/index.d.ts +94 -0
  151. package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +20 -0
  152. package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
  153. package/dist/types/mongo/index.d.ts +2 -0
  154. package/dist/types/mongo.d.ts +1 -0
  155. package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
  156. package/dist/types/nspi/NspiCodec.d.ts +59 -0
  157. package/dist/types/nspi/NspiGetMatchesHandler.d.ts +26 -0
  158. package/dist/types/rop/AddressList.d.ts +48 -0
  159. package/dist/types/rop/CalendarEventTarget.d.ts +33 -0
  160. package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
  161. package/dist/types/rop/ContactTarget.d.ts +29 -0
  162. package/dist/types/rop/ContentsTable.d.ts +16 -0
  163. package/dist/types/rop/ExecuteBudget.d.ts +48 -0
  164. package/dist/types/rop/FastTransferStream.d.ts +57 -0
  165. package/dist/types/rop/FolderTarget.d.ts +64 -0
  166. package/dist/types/rop/HandleDataCache.d.ts +145 -0
  167. package/dist/types/rop/MeetingMessageClassHandler.d.ts +30 -0
  168. package/dist/types/rop/MessageBodyStream.d.ts +35 -0
  169. package/dist/types/rop/MessageTarget.d.ts +45 -0
  170. package/dist/types/rop/NamedPropertyRegistry.d.ts +50 -0
  171. package/dist/types/rop/PropertyResolvers.d.ts +86 -0
  172. package/dist/types/rop/RepoPaging.d.ts +30 -0
  173. package/dist/types/rop/RestapiRules.d.ts +32 -0
  174. package/dist/types/rop/RopCreateMessageHandler.d.ts +24 -0
  175. package/dist/types/rop/RopDeleteFolderHandler.d.ts +53 -0
  176. package/dist/types/rop/RopDeleteMessagesHandler.d.ts +43 -0
  177. package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +25 -0
  178. package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +25 -0
  179. package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +22 -0
  180. package/dist/types/rop/RopGetContentsTableHandler.d.ts +25 -0
  181. package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +18 -0
  182. package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +26 -0
  183. package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
  184. package/dist/types/rop/RopHandler.d.ts +106 -0
  185. package/dist/types/rop/RopLogonHandler.d.ts +31 -0
  186. package/dist/types/rop/RopOpenFolderHandler.d.ts +20 -0
  187. package/dist/types/rop/RopOpenMessageHandler.d.ts +28 -0
  188. package/dist/types/rop/RopOpenStreamHandler.d.ts +28 -0
  189. package/dist/types/rop/RopQueryRowsHandler.d.ts +43 -0
  190. package/dist/types/rop/RopReadStreamHandler.d.ts +20 -0
  191. package/dist/types/rop/RopReleaseHandler.d.ts +15 -0
  192. package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +59 -0
  193. package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
  194. package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
  195. package/dist/types/rop/RopSubmitMessageHandler.d.ts +80 -0
  196. package/dist/types/rop/RopWriteStreamHandler.d.ts +31 -0
  197. package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
  198. package/dist/types/rop/TaskTarget.d.ts +24 -0
  199. package/dist/types/rop/TransportSend.d.ts +10 -0
  200. package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +20 -0
  201. package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
  202. package/dist/types/sql/index.d.ts +2 -0
  203. package/dist/types/sql.d.ts +1 -0
  204. 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"}