@rapidmx/mapi-plugin 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +382 -0
- package/README.md +60 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js +297 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -0
- package/dist/lib/BaseMapiNspiRoute.js +114 -0
- package/dist/lib/BaseMapiNspiRoute.js.map +1 -0
- package/dist/lib/MapiSessionManager.js +119 -0
- package/dist/lib/MapiSessionManager.js.map +1 -0
- package/dist/lib/RopDispatcher.js +34 -0
- package/dist/lib/RopDispatcher.js.map +1 -0
- package/dist/lib/codec/AppointmentRecurrence.js +314 -0
- package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
- package/dist/lib/codec/BufferCursor.js +213 -0
- package/dist/lib/codec/BufferCursor.js.map +1 -0
- package/dist/lib/codec/GlobalObjectId.js +92 -0
- package/dist/lib/codec/GlobalObjectId.js.map +1 -0
- package/dist/lib/codec/MapiGuid.js +41 -0
- package/dist/lib/codec/MapiGuid.js.map +1 -0
- package/dist/lib/codec/MapiTimeZone.js +93 -0
- package/dist/lib/codec/MapiTimeZone.js.map +1 -0
- package/dist/lib/codec/PropertyValue.js +214 -0
- package/dist/lib/codec/PropertyValue.js.map +1 -0
- package/dist/lib/codec/RopBuffer.js +26 -0
- package/dist/lib/codec/RopBuffer.js.map +1 -0
- package/dist/lib/codec/TypedString.js +54 -0
- package/dist/lib/codec/TypedString.js.map +1 -0
- package/dist/lib/index.js +99 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +84 -0
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
- package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
- package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
- package/dist/lib/mongo/index.js +7 -0
- package/dist/lib/mongo/index.js.map +1 -0
- package/dist/lib/mongo.js +6 -0
- package/dist/lib/mongo.js.map +1 -0
- package/dist/lib/nspi/NspiBindHandler.js +54 -0
- package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
- package/dist/lib/nspi/NspiCodec.js +106 -0
- package/dist/lib/nspi/NspiCodec.js.map +1 -0
- package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
- package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
- package/dist/lib/rop/CalendarEventTarget.js +27 -0
- package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
- package/dist/lib/rop/CalendarNamedProperties.js +57 -0
- package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
- package/dist/lib/rop/ContactTarget.js +23 -0
- package/dist/lib/rop/ContactTarget.js.map +1 -0
- package/dist/lib/rop/FastTransferStream.js +98 -0
- package/dist/lib/rop/FastTransferStream.js.map +1 -0
- package/dist/lib/rop/FolderTarget.js +111 -0
- package/dist/lib/rop/FolderTarget.js.map +1 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js +95 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
- package/dist/lib/rop/MessageBodyStream.js +42 -0
- package/dist/lib/rop/MessageBodyStream.js.map +1 -0
- package/dist/lib/rop/MessageTarget.js +44 -0
- package/dist/lib/rop/MessageTarget.js.map +1 -0
- package/dist/lib/rop/NamedPropertyRegistry.js +66 -0
- package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
- package/dist/lib/rop/PropertyResolvers.js +313 -0
- package/dist/lib/rop/PropertyResolvers.js.map +1 -0
- package/dist/lib/rop/RopCreateMessageHandler.js +53 -0
- package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js +133 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js +70 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +61 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +75 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js +76 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js +40 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +55 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
- package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
- package/dist/lib/rop/RopHandler.js +2 -0
- package/dist/lib/rop/RopHandler.js.map +1 -0
- package/dist/lib/rop/RopLogonHandler.js +132 -0
- package/dist/lib/rop/RopLogonHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenFolderHandler.js +46 -0
- package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenMessageHandler.js +81 -0
- package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenStreamHandler.js +81 -0
- package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopQueryRowsHandler.js +91 -0
- package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
- package/dist/lib/rop/RopReadStreamHandler.js +55 -0
- package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopReleaseHandler.js +20 -0
- package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js +210 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopSetColumnsHandler.js +52 -0
- package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js +162 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js +314 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopWriteStreamHandler.js +45 -0
- package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
- package/dist/lib/rop/TaskNamedProperties.js +29 -0
- package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
- package/dist/lib/rop/TaskTarget.js +19 -0
- package/dist/lib/rop/TaskTarget.js.map +1 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js +84 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
- package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
- package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
- package/dist/lib/sql/index.js +7 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql.js +6 -0
- package/dist/lib/sql.js.map +1 -0
- package/dist/types/BaseMapiEmsmdbRoute.d.ts +82 -0
- package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
- package/dist/types/MapiSessionManager.d.ts +130 -0
- package/dist/types/RopDispatcher.d.ts +17 -0
- package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
- package/dist/types/codec/BufferCursor.d.ts +78 -0
- package/dist/types/codec/GlobalObjectId.d.ts +11 -0
- package/dist/types/codec/MapiGuid.d.ts +6 -0
- package/dist/types/codec/MapiTimeZone.d.ts +7 -0
- package/dist/types/codec/PropertyValue.d.ts +77 -0
- package/dist/types/codec/RopBuffer.d.ts +25 -0
- package/dist/types/codec/TypedString.d.ts +17 -0
- package/dist/types/index.d.ts +94 -0
- package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +19 -0
- package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
- package/dist/types/mongo/index.d.ts +2 -0
- package/dist/types/mongo.d.ts +1 -0
- package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
- package/dist/types/nspi/NspiCodec.d.ts +59 -0
- package/dist/types/nspi/NspiGetMatchesHandler.d.ts +24 -0
- package/dist/types/rop/CalendarEventTarget.d.ts +29 -0
- package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
- package/dist/types/rop/ContactTarget.d.ts +29 -0
- package/dist/types/rop/FastTransferStream.d.ts +18 -0
- package/dist/types/rop/FolderTarget.d.ts +63 -0
- package/dist/types/rop/MeetingMessageClassHandler.d.ts +36 -0
- package/dist/types/rop/MessageBodyStream.d.ts +26 -0
- package/dist/types/rop/MessageTarget.d.ts +38 -0
- package/dist/types/rop/NamedPropertyRegistry.d.ts +46 -0
- package/dist/types/rop/PropertyResolvers.d.ts +60 -0
- package/dist/types/rop/RopCreateMessageHandler.d.ts +23 -0
- package/dist/types/rop/RopDeleteFolderHandler.d.ts +38 -0
- package/dist/types/rop/RopDeleteMessagesHandler.d.ts +31 -0
- package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +24 -0
- package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +24 -0
- package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +23 -0
- package/dist/types/rop/RopGetContentsTableHandler.d.ts +31 -0
- package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +17 -0
- package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +23 -0
- package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
- package/dist/types/rop/RopHandler.d.ts +67 -0
- package/dist/types/rop/RopLogonHandler.d.ts +31 -0
- package/dist/types/rop/RopOpenFolderHandler.d.ts +19 -0
- package/dist/types/rop/RopOpenMessageHandler.d.ts +27 -0
- package/dist/types/rop/RopOpenStreamHandler.d.ts +27 -0
- package/dist/types/rop/RopQueryRowsHandler.d.ts +36 -0
- package/dist/types/rop/RopReadStreamHandler.d.ts +18 -0
- package/dist/types/rop/RopReleaseHandler.d.ts +14 -0
- package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +44 -0
- package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
- package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
- package/dist/types/rop/RopSubmitMessageHandler.d.ts +70 -0
- package/dist/types/rop/RopWriteStreamHandler.d.ts +21 -0
- package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
- package/dist/types/rop/TaskTarget.d.ts +24 -0
- package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +19 -0
- package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
- package/dist/types/sql/index.d.ts +2 -0
- package/dist/types/sql.d.ts +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
15
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
16
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
17
|
+
import { ApiError, ObjectDecorators } from "@rapidrest/core";
|
|
18
|
+
import { ApiErrorMessages, ApiErrors, RepoUtils, RouteDecorators, } from "@rapidrest/service-core";
|
|
19
|
+
import { BufferReader, BufferWriter } from "./codec/BufferCursor.js";
|
|
20
|
+
import { decodeRopBuffer, encodeRopBuffer } from "./codec/RopBuffer.js";
|
|
21
|
+
import { MapiSessionManager } from "./MapiSessionManager.js";
|
|
22
|
+
import { dispatchRops } from "./RopDispatcher.js";
|
|
23
|
+
import { ScanPipeline } from "@rapidmx/restapi/scan";
|
|
24
|
+
import { RecoverableRepoUtils, resolveCallerMailboxUid } from "@rapidmx/restapi";
|
|
25
|
+
const { Init, Inject, Logger } = ObjectDecorators;
|
|
26
|
+
const { Auth, Post, Request, Response, User: AuthUser } = RouteDecorators;
|
|
27
|
+
/** The well-known MAPI HRESULT `MAPI_E_LOGON_FAILED`, reused here to signal "no such session - reconnect"
|
|
28
|
+
* via `X-ResponseCode`/`ErrorCode`. Not a claim of exact `[MS-OXCRPC]` return-value-table parity for this
|
|
29
|
+
* specific condition - a real client only needs a non-zero code to know to re-`Connect`, not a precise one. */
|
|
30
|
+
const ERROR_SESSION_NOT_FOUND = 0x80040111;
|
|
31
|
+
function firstHeader(req, name) {
|
|
32
|
+
const value = req.headers[name];
|
|
33
|
+
return Array.isArray(value) ? value[0] : value;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Abstract base for the single fixed EMSMDB endpoint (`POST /mapi/emsmdb` by `[MS-OXCMAPIHTTP]` convention,
|
|
37
|
+
* though the concrete path is left to the consuming application to mount via `@Route(...)` - see
|
|
38
|
+
* `BaseEasRoute.ts` for the identical undecorated-base-class pattern this follows). Like EAS, MAPI/HTTP
|
|
39
|
+
* multiplexes several request types against one URL - here via the `X-RequestType` **header**
|
|
40
|
+
* (`Connect`/`Execute`/`Disconnect`/`NotificationWait`) rather than a query parameter - so there is exactly
|
|
41
|
+
* one `@Post()` method, not one per request type.
|
|
42
|
+
*
|
|
43
|
+
* **Auth**: `@Auth(["jwt"])` - unchanged from every other route in this app. Real, modern Exchange Server's
|
|
44
|
+
* own MAPI virtual directory genuinely supports `OAuth` as a configured `IISAuthenticationMethods` value
|
|
45
|
+
* alongside `NTLM`/`Negotiate`, so Bearer-token auth on this exact endpoint is real current Exchange
|
|
46
|
+
* behavior, not a deviation this library invents - see the architecture plan's "Auth" section.
|
|
47
|
+
*
|
|
48
|
+
* **Connect/Disconnect are complete**: `Connect` establishes a real `MapiSessionContext` (via
|
|
49
|
+
* `MapiSessionManager`) and sets the two spec-fixed session cookies (`MapiContext`/`MapiSequence`);
|
|
50
|
+
* `Disconnect` releases the session. **`Execute` dispatches real ROPs** (starting with `RopLogon`/`RopRelease`
|
|
51
|
+
* - build-order step 4) via `RopDispatcher`, using whichever `RopHandler`s `ropHandlerClasses` registers;
|
|
52
|
+
* an unrecognized `RopId` simply stops processing (see `RopDispatcher`'s own doc comment for why), the same
|
|
53
|
+
* "real, functional, no commands implemented yet" stance `BaseEasRoute`'s own skeleton step took for its
|
|
54
|
+
* empty `commandHandlerClasses` before `ProvisionCommand` landed - here scoped per-ROP instead of per-request.
|
|
55
|
+
*
|
|
56
|
+
* **Always non-chunked**: every response here uses `Content-Length` (via `res.status(200).send(buffer)`),
|
|
57
|
+
* never `Transfer-Encoding: chunked` - both are equally spec-valid per `[MS-OXCMAPIHTTP]`'s own "Common
|
|
58
|
+
* Response Format", and skipping the `PROCESSING`/`PENDING`/`DONE` keep-alive meta-tag streaming matches
|
|
59
|
+
* every other route in this library's own response idiom. See the architecture plan's "Protocol facts"
|
|
60
|
+
* section for the full reasoning and its documented limitation.
|
|
61
|
+
*
|
|
62
|
+
* `mailboxClass`/`folderClass` are supplied by the Mongo/SQL concrete subclasses, following the exact
|
|
63
|
+
* one-line-per-backend pattern used throughout this library's other routes/jobs. `folderRepo` is built once
|
|
64
|
+
* here (not per-handler, unlike EAS's per-command repo pattern) and threaded through `RopContext` to every
|
|
65
|
+
* `RopHandler` - see `RopHandler.ts`'s own doc comment for why.
|
|
66
|
+
*
|
|
67
|
+
* @author Jean-Philippe Steinmetz
|
|
68
|
+
*/
|
|
69
|
+
export class BaseMapiEmsmdbRoute {
|
|
70
|
+
constructor() {
|
|
71
|
+
/** ROP handler classes to instantiate (one each) in `@Init`, keyed by their own `ropId`. Empty until a
|
|
72
|
+
* concrete `RopHandler` lands - every ROP is then simply left unprocessed (see `RopDispatcher`'s own doc
|
|
73
|
+
* comment), the correct, honest behavior for a transport skeleton with no ROPs implemented yet. */
|
|
74
|
+
this.ropHandlerClasses = [];
|
|
75
|
+
this.ropHandlers = new Map();
|
|
76
|
+
}
|
|
77
|
+
async init() {
|
|
78
|
+
this.mailboxRepo = await this._objectFactory.newInstance(RepoUtils, {
|
|
79
|
+
name: this.mailboxClass.name,
|
|
80
|
+
args: [this.mailboxClass],
|
|
81
|
+
});
|
|
82
|
+
this.folderRepo = await this._objectFactory.newInstance(RecoverableRepoUtils, {
|
|
83
|
+
name: this.folderClass.name,
|
|
84
|
+
args: [this.folderClass],
|
|
85
|
+
});
|
|
86
|
+
this.messageRepo = await this._objectFactory.newInstance(RecoverableRepoUtils, {
|
|
87
|
+
name: this.messageClass.name,
|
|
88
|
+
args: [this.messageClass],
|
|
89
|
+
});
|
|
90
|
+
this.calendarEventRepo = await this._objectFactory.newInstance(RecoverableRepoUtils, {
|
|
91
|
+
name: this.calendarEventClass.name,
|
|
92
|
+
args: [this.calendarEventClass],
|
|
93
|
+
});
|
|
94
|
+
this.contactRepo = await this._objectFactory.newInstance(RecoverableRepoUtils, {
|
|
95
|
+
name: this.contactClass.name,
|
|
96
|
+
args: [this.contactClass],
|
|
97
|
+
});
|
|
98
|
+
this.taskRepo = await this._objectFactory.newInstance(RecoverableRepoUtils, {
|
|
99
|
+
name: this.taskClass.name,
|
|
100
|
+
args: [this.taskClass],
|
|
101
|
+
});
|
|
102
|
+
// Label extends plain BaseEntity, not RecoverableBaseEntity (see restapi's own model doc comment) - no
|
|
103
|
+
// soft-delete support to preserve, so a plain RepoUtils is correct here, matching mailboxRepo's own
|
|
104
|
+
// choice for the identical reason.
|
|
105
|
+
this.labelRepo = await this._objectFactory.newInstance(RepoUtils, {
|
|
106
|
+
name: this.labelClass.name,
|
|
107
|
+
args: [this.labelClass],
|
|
108
|
+
});
|
|
109
|
+
this.sessionManager = await this._objectFactory.newInstance(MapiSessionManager);
|
|
110
|
+
for (const HandlerClass of this.ropHandlerClasses) {
|
|
111
|
+
const handler = await this._objectFactory.newInstance(HandlerClass);
|
|
112
|
+
this.ropHandlers.set(handler.ropId, handler);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async dispatch(req, res, user) {
|
|
116
|
+
if (!this.mailboxRepo ||
|
|
117
|
+
!this.folderRepo ||
|
|
118
|
+
!this.messageRepo ||
|
|
119
|
+
!this.calendarEventRepo ||
|
|
120
|
+
!this.contactRepo ||
|
|
121
|
+
!this.taskRepo ||
|
|
122
|
+
!this.labelRepo ||
|
|
123
|
+
!this.sessionManager ||
|
|
124
|
+
!this.blobStore ||
|
|
125
|
+
!this.scanPipeline ||
|
|
126
|
+
!this.mailTransport) {
|
|
127
|
+
throw new ApiError(ApiErrors.INTERNAL_ERROR, 500, ApiErrorMessages.INTERNAL_ERROR);
|
|
128
|
+
}
|
|
129
|
+
if (!user) {
|
|
130
|
+
throw new ApiError(ApiErrors.AUTH_PERMISSION_FAILURE, 403, ApiErrorMessages.AUTH_PERMISSION_FAILURE);
|
|
131
|
+
}
|
|
132
|
+
const requestType = firstHeader(req, "x-requesttype");
|
|
133
|
+
const clientInfo = firstHeader(req, "x-clientinfo") ?? "";
|
|
134
|
+
const requestId = firstHeader(req, "x-requestid") ?? "";
|
|
135
|
+
res.setHeader("Content-Type", "application/mapi-http")
|
|
136
|
+
.setHeader("X-RequestType", requestType ?? "")
|
|
137
|
+
.setHeader("X-RequestId", requestId)
|
|
138
|
+
.setHeader("X-ResponseCode", "0")
|
|
139
|
+
.setHeader("X-ClientInfo", clientInfo)
|
|
140
|
+
.setHeader("X-ServerApplication", "RapidREST-Mail");
|
|
141
|
+
switch (requestType) {
|
|
142
|
+
case "Connect":
|
|
143
|
+
await this.handleConnect(req, res, user);
|
|
144
|
+
return;
|
|
145
|
+
case "Execute":
|
|
146
|
+
await this.handleExecute(req, res);
|
|
147
|
+
return;
|
|
148
|
+
case "Disconnect":
|
|
149
|
+
await this.handleDisconnect(req, res);
|
|
150
|
+
return;
|
|
151
|
+
case "NotificationWait":
|
|
152
|
+
// Real, spec-defined request type, deliberately deferred - no push-notification support in
|
|
153
|
+
// this pass (see the architecture plan's ROP scope table). Distinct from an unrecognized
|
|
154
|
+
// X-RequestType value (400 below), the same distinction BaseEasRoute draws between 501
|
|
155
|
+
// ("recognized but deferred") and 400 ("malformed request").
|
|
156
|
+
res.status(501).send();
|
|
157
|
+
return;
|
|
158
|
+
default:
|
|
159
|
+
throw new ApiError(ApiErrors.INVALID_REQUEST, 400, ApiErrorMessages.INVALID_REQUEST);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* `Connect` establishes a new Session Context - never trusts the request body's own `UserDn` field for
|
|
164
|
+
* mailbox identity (the same "never a client-supplied mailbox, always resolved from the authenticated
|
|
165
|
+
* JWT" principle `BaseEasRoute`/`BaseSearchRoute` already apply via `resolveCallerMailboxUid`) - so the
|
|
166
|
+
* request body is never decoded at all here.
|
|
167
|
+
*/
|
|
168
|
+
async handleConnect(req, res, user) {
|
|
169
|
+
const mailboxUid = await resolveCallerMailboxUid(this.mailboxRepo, user);
|
|
170
|
+
if (!mailboxUid) {
|
|
171
|
+
throw new ApiError(ApiErrors.NOT_FOUND, 404, ApiErrorMessages.NOT_FOUND);
|
|
172
|
+
}
|
|
173
|
+
const mailbox = await this.mailboxRepo.findOne(mailboxUid, { ignoreACL: true });
|
|
174
|
+
const session = await this.sessionManager.create(mailboxUid, user.uid);
|
|
175
|
+
res.appendHeader("Set-Cookie", `MapiContext=${session.uid}`);
|
|
176
|
+
res.appendHeader("Set-Cookie", `MapiSequence=0`);
|
|
177
|
+
const body = new BufferWriter();
|
|
178
|
+
body.writeUInt32LE(0); // StatusCode
|
|
179
|
+
body.writeUInt32LE(0); // ErrorCode
|
|
180
|
+
body.writeUInt32LE(60000); // PollsMax (ms) - pragmatic constant, not a tuned server policy value
|
|
181
|
+
body.writeUInt32LE(3); // RetryCount - pragmatic constant
|
|
182
|
+
body.writeUInt32LE(5000); // RetryDelay (ms) - pragmatic constant
|
|
183
|
+
// DnPrefix: this library addresses mailboxes by UID/SMTP-address, never real X.500 DNs, so this is a
|
|
184
|
+
// fixed, unused placeholder rather than a real DN prefix a client could build recipients from.
|
|
185
|
+
body.writeNullTerminatedString8("/");
|
|
186
|
+
body.writeNullTerminatedUtf16LE(mailbox?.displayName ?? "");
|
|
187
|
+
body.writeUInt32LE(0); // AuxiliaryBufferSize
|
|
188
|
+
res.status(200).send(body.toBuffer());
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* `Execute` decodes the outer envelope (`Flags`/`RopBufferSize`/`RopBuffer`/...) and the inner ROP buffer
|
|
192
|
+
* framing (`RopBuffer.ts`), dispatches every contained ROP via `RopDispatcher`, and re-encodes the
|
|
193
|
+
* collected responses - preserving the incoming `handleTable` unchanged (this pragmatic subset never
|
|
194
|
+
* allocates/frees table-wide handle slots at the framing level; individual `RopHandler`s manage their own
|
|
195
|
+
* entries within `session.handles` instead).
|
|
196
|
+
*/
|
|
197
|
+
async handleExecute(req, res) {
|
|
198
|
+
const sessionId = req.cookies["MapiContext"];
|
|
199
|
+
const session = sessionId
|
|
200
|
+
? await this.sessionManager.load(sessionId)
|
|
201
|
+
: undefined;
|
|
202
|
+
if (!session) {
|
|
203
|
+
res.setHeader("X-ResponseCode", String(ERROR_SESSION_NOT_FOUND));
|
|
204
|
+
const body = new BufferWriter();
|
|
205
|
+
body.writeUInt32LE(0); // StatusCode
|
|
206
|
+
body.writeUInt32LE(ERROR_SESSION_NOT_FOUND); // ErrorCode
|
|
207
|
+
body.writeUInt32LE(0); // Flags
|
|
208
|
+
body.writeUInt32LE(0); // RopBufferSize
|
|
209
|
+
body.writeUInt32LE(0); // AuxiliaryBufferSize
|
|
210
|
+
res.status(200).send(body.toBuffer());
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const requestReader = new BufferReader(req.rawBody ?? Buffer.alloc(0));
|
|
214
|
+
requestReader.readUInt32LE(); // Flags - unused by this pragmatic subset (no client ROP-response hints honored)
|
|
215
|
+
const ropBufferSize = requestReader.readUInt32LE();
|
|
216
|
+
const ropBufferBytes = requestReader.readBytes(ropBufferSize);
|
|
217
|
+
// MaxRopOut/AuxiliaryBufferSize/AuxiliaryBuffer intentionally left unread - no output-size capping or
|
|
218
|
+
// auxiliary-payload support in this pragmatic subset.
|
|
219
|
+
const { ropsList, handleTable } = decodeRopBuffer(ropBufferBytes);
|
|
220
|
+
const context = {
|
|
221
|
+
mailboxUid: session.mailboxUid,
|
|
222
|
+
userUid: session.userUid,
|
|
223
|
+
session,
|
|
224
|
+
mailboxRepo: this.mailboxRepo,
|
|
225
|
+
folderRepo: this.folderRepo,
|
|
226
|
+
messageRepo: this.messageRepo,
|
|
227
|
+
calendarEventRepo: this.calendarEventRepo,
|
|
228
|
+
contactRepo: this.contactRepo,
|
|
229
|
+
taskRepo: this.taskRepo,
|
|
230
|
+
labelRepo: this.labelRepo,
|
|
231
|
+
folderClass: this.folderClass,
|
|
232
|
+
messageClass: this.messageClass,
|
|
233
|
+
calendarEventClass: this.calendarEventClass,
|
|
234
|
+
contactClass: this.contactClass,
|
|
235
|
+
taskClass: this.taskClass,
|
|
236
|
+
labelClass: this.labelClass,
|
|
237
|
+
blobStore: this.blobStore,
|
|
238
|
+
scanPipeline: this.scanPipeline,
|
|
239
|
+
mailTransport: this.mailTransport,
|
|
240
|
+
};
|
|
241
|
+
const responseRopsList = await dispatchRops(ropsList, this.ropHandlers, context);
|
|
242
|
+
const responseRopBuffer = encodeRopBuffer({ ropsList: responseRopsList, handleTable });
|
|
243
|
+
await this.sessionManager.save(session);
|
|
244
|
+
const body = new BufferWriter();
|
|
245
|
+
body.writeUInt32LE(0); // StatusCode
|
|
246
|
+
body.writeUInt32LE(0); // ErrorCode
|
|
247
|
+
body.writeUInt32LE(0); // Flags
|
|
248
|
+
body.writeUInt32LE(responseRopBuffer.length);
|
|
249
|
+
body.writeBytes(responseRopBuffer);
|
|
250
|
+
body.writeUInt32LE(0); // AuxiliaryBufferSize
|
|
251
|
+
res.status(200).send(body.toBuffer());
|
|
252
|
+
}
|
|
253
|
+
async handleDisconnect(req, res) {
|
|
254
|
+
const sessionId = req.cookies["MapiContext"];
|
|
255
|
+
if (sessionId) {
|
|
256
|
+
await this.sessionManager.destroy(sessionId);
|
|
257
|
+
}
|
|
258
|
+
const body = new BufferWriter();
|
|
259
|
+
body.writeUInt32LE(0); // StatusCode
|
|
260
|
+
body.writeUInt32LE(0); // ErrorCode
|
|
261
|
+
body.writeUInt32LE(0); // AuxiliaryBufferSize
|
|
262
|
+
res.status(200).send(body.toBuffer());
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
__decorate([
|
|
266
|
+
Inject("BlobStore"),
|
|
267
|
+
__metadata("design:type", Object)
|
|
268
|
+
], BaseMapiEmsmdbRoute.prototype, "blobStore", void 0);
|
|
269
|
+
__decorate([
|
|
270
|
+
Inject(ScanPipeline),
|
|
271
|
+
__metadata("design:type", ScanPipeline)
|
|
272
|
+
], BaseMapiEmsmdbRoute.prototype, "scanPipeline", void 0);
|
|
273
|
+
__decorate([
|
|
274
|
+
Inject("MailTransport"),
|
|
275
|
+
__metadata("design:type", Object)
|
|
276
|
+
], BaseMapiEmsmdbRoute.prototype, "mailTransport", void 0);
|
|
277
|
+
__decorate([
|
|
278
|
+
Logger,
|
|
279
|
+
__metadata("design:type", Object)
|
|
280
|
+
], BaseMapiEmsmdbRoute.prototype, "logger", void 0);
|
|
281
|
+
__decorate([
|
|
282
|
+
Init,
|
|
283
|
+
__metadata("design:type", Function),
|
|
284
|
+
__metadata("design:paramtypes", []),
|
|
285
|
+
__metadata("design:returntype", Promise)
|
|
286
|
+
], BaseMapiEmsmdbRoute.prototype, "init", null);
|
|
287
|
+
__decorate([
|
|
288
|
+
Auth(["jwt"]),
|
|
289
|
+
Post(),
|
|
290
|
+
__param(0, Request),
|
|
291
|
+
__param(1, Response),
|
|
292
|
+
__param(2, AuthUser),
|
|
293
|
+
__metadata("design:type", Function),
|
|
294
|
+
__metadata("design:paramtypes", [Object, Object, Object]),
|
|
295
|
+
__metadata("design:returntype", Promise)
|
|
296
|
+
], BaseMapiEmsmdbRoute.prototype, "dispatch", null);
|
|
297
|
+
//# sourceMappingURL=BaseMapiEmsmdbRoute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseMapiEmsmdbRoute.js","sourceRoot":"","sources":["../../src/BaseMapiEmsmdbRoute.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
15
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
16
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
17
|
+
import { ApiError, ObjectDecorators } from "@rapidrest/core";
|
|
18
|
+
import { ApiErrorMessages, ApiErrors, RepoUtils, RouteDecorators } from "@rapidrest/service-core";
|
|
19
|
+
import { handleNspiBind, handleNspiUnbind } from "./nspi/NspiBindHandler.js";
|
|
20
|
+
import { handleNspiGetMatches } from "./nspi/NspiGetMatchesHandler.js";
|
|
21
|
+
import { resolveCallerMailboxUid } from "@rapidmx/restapi";
|
|
22
|
+
const { Init } = ObjectDecorators;
|
|
23
|
+
const { Auth, Post, Request, Response, User: AuthUser } = RouteDecorators;
|
|
24
|
+
function firstHeader(req, name) {
|
|
25
|
+
const value = req.headers[name];
|
|
26
|
+
return Array.isArray(value) ? value[0] : value;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Abstract base for the single fixed NSPI endpoint (`POST /mapi/nspi` by `[MS-OXCMAPIHTTP]` convention, though
|
|
30
|
+
* the concrete path is left to the consuming application to mount via `@Route(...)`, the identical
|
|
31
|
+
* undecorated-base-class pattern `BaseMapiEmsmdbRoute`/`BaseEasRoute` already establish). Dispatches on the
|
|
32
|
+
* `X-RequestType` header, the same multiplexing mechanism EMSMDB uses - but unlike EMSMDB, there is no ROP
|
|
33
|
+
* buffer framing here: each `X-RequestType` value (`Bind`/`Unbind`/`GetMatches`) is its own flat, self-
|
|
34
|
+
* contained request/response body (`[MS-OXCMAPIHTTP]` §2.2.5), so this route dispatches directly to a small
|
|
35
|
+
* handler function per operation instead of decoding a multiplexed stream.
|
|
36
|
+
*
|
|
37
|
+
* **Pragmatic subset scope**: only `Bind`/`Unbind`/`GetMatches` are implemented - real NSPI's other dozen-plus
|
|
38
|
+
* request types (`QueryRows`, `ResolveNames`, `GetProps`, `ModProps`, ...) are genuine full address-book/
|
|
39
|
+
* directory-replication operations this deployment's own minimal GAL-lookup use case doesn't need; an
|
|
40
|
+
* unrecognized `X-RequestType` gets a `501`, the same "recognized-but-deferred vs malformed" distinction
|
|
41
|
+
* `BaseMapiEmsmdbRoute`'s own `NotificationWait` case already draws. See `NspiBindHandler.ts`'s own doc comment
|
|
42
|
+
* for why no real NSPI session state is created or tracked between calls.
|
|
43
|
+
*
|
|
44
|
+
* **Auth**: `@Auth(["jwt"])`, unchanged from every other route in this app - the same reasoning
|
|
45
|
+
* `BaseMapiEmsmdbRoute`'s own doc comment already gives for why Bearer-token auth on an address-book endpoint
|
|
46
|
+
* is real, current Exchange behavior, not a deviation this library invents.
|
|
47
|
+
*
|
|
48
|
+
* `mailboxClass`/`contactClass` are supplied by the Mongo/SQL concrete subclasses. GAL search itself (see
|
|
49
|
+
* `NspiGetMatchesHandler.ts`) uses `RepoUtils`'s `regex(...)` operator, which compiles identically on both
|
|
50
|
+
* backends - no per-backend hook needed here.
|
|
51
|
+
*
|
|
52
|
+
* @author Jean-Philippe Steinmetz
|
|
53
|
+
*/
|
|
54
|
+
export class BaseMapiNspiRoute {
|
|
55
|
+
async init() {
|
|
56
|
+
this.mailboxRepo = await this._objectFactory.newInstance(RepoUtils, {
|
|
57
|
+
name: this.mailboxClass.name,
|
|
58
|
+
args: [this.mailboxClass],
|
|
59
|
+
});
|
|
60
|
+
this.contactRepo = await this._objectFactory.newInstance(RepoUtils, {
|
|
61
|
+
name: this.contactClass.name,
|
|
62
|
+
args: [this.contactClass],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async dispatch(req, res, user) {
|
|
66
|
+
if (!this.mailboxRepo || !this.contactRepo) {
|
|
67
|
+
throw new ApiError(ApiErrors.INTERNAL_ERROR, 500, ApiErrorMessages.INTERNAL_ERROR);
|
|
68
|
+
}
|
|
69
|
+
if (!user) {
|
|
70
|
+
throw new ApiError(ApiErrors.AUTH_PERMISSION_FAILURE, 403, ApiErrorMessages.AUTH_PERMISSION_FAILURE);
|
|
71
|
+
}
|
|
72
|
+
const requestType = firstHeader(req, "x-requesttype");
|
|
73
|
+
res.setHeader("Content-Type", "application/mapi-http")
|
|
74
|
+
.setHeader("X-RequestType", requestType ?? "")
|
|
75
|
+
.setHeader("X-ResponseCode", "0")
|
|
76
|
+
.setHeader("X-ServerApplication", "RapidREST-Mail");
|
|
77
|
+
switch (requestType) {
|
|
78
|
+
case "Bind":
|
|
79
|
+
await handleNspiBind(res, user, this.mailboxRepo);
|
|
80
|
+
return;
|
|
81
|
+
case "Unbind":
|
|
82
|
+
handleNspiUnbind(res);
|
|
83
|
+
return;
|
|
84
|
+
case "GetMatches": {
|
|
85
|
+
const mailboxUid = await resolveCallerMailboxUid(this.mailboxRepo, user);
|
|
86
|
+
if (!mailboxUid) {
|
|
87
|
+
throw new ApiError(ApiErrors.NOT_FOUND, 404, ApiErrorMessages.NOT_FOUND);
|
|
88
|
+
}
|
|
89
|
+
await handleNspiGetMatches(req, res, mailboxUid, this.contactRepo);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
res.status(501).send();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
__decorate([
|
|
99
|
+
Init,
|
|
100
|
+
__metadata("design:type", Function),
|
|
101
|
+
__metadata("design:paramtypes", []),
|
|
102
|
+
__metadata("design:returntype", Promise)
|
|
103
|
+
], BaseMapiNspiRoute.prototype, "init", null);
|
|
104
|
+
__decorate([
|
|
105
|
+
Auth(["jwt"]),
|
|
106
|
+
Post(),
|
|
107
|
+
__param(0, Request),
|
|
108
|
+
__param(1, Response),
|
|
109
|
+
__param(2, AuthUser),
|
|
110
|
+
__metadata("design:type", Function),
|
|
111
|
+
__metadata("design:paramtypes", [Object, Object, Object]),
|
|
112
|
+
__metadata("design:returntype", Promise)
|
|
113
|
+
], BaseMapiNspiRoute.prototype, "dispatch", null);
|
|
114
|
+
//# sourceMappingURL=BaseMapiNspiRoute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseMapiNspiRoute.js","sourceRoot":"","sources":["../../src/BaseMapiNspiRoute.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
12
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
import { ObjectDecorators } from "@rapidrest/core";
|
|
15
|
+
import { RedisCache, SimpleEntity } from "@rapidrest/service-core";
|
|
16
|
+
const { Init } = ObjectDecorators;
|
|
17
|
+
/** A pragmatic idle-session lifetime. The spec leaves session lifetime to server policy, not a fixed wire
|
|
18
|
+
* value - a real client re-establishes (`Connect`) transparently whenever its session context has expired. */
|
|
19
|
+
const SESSION_TTL_SECONDS = 15 * 60;
|
|
20
|
+
/**
|
|
21
|
+
* The MAPI/HTTP `Session Context` (`[MS-OXCMAPIHTTP]` §3.1.1.1): everything a `Connect`-established session
|
|
22
|
+
* needs across subsequent `Execute` requests. Never persisted to a real database - purely an ephemeral,
|
|
23
|
+
* TTL-bound cache entry (see `MapiSessionManager` below), so this deliberately extends `SimpleEntity` (just a
|
|
24
|
+
* `uid`) rather than this library's own `BaseEntity`, which would add ACL/soft-delete/optimistic-locking
|
|
25
|
+
* machinery this object has no use for.
|
|
26
|
+
*
|
|
27
|
+
* `createdAt` is a plain ISO-8601 string, not a `Date` - `RedisCache`'s Redis-backed path round-trips values
|
|
28
|
+
* through `JSON.stringify`/`JSON.parse`, which silently turns a `Date` into a string on the way out without
|
|
29
|
+
* reviving it back to a `Date` on the way in; storing it as a string from the start keeps the shape identical
|
|
30
|
+
* whether a session happens to be served from the in-memory or the Redis-backed path.
|
|
31
|
+
*/
|
|
32
|
+
export class MapiSessionContext extends SimpleEntity {
|
|
33
|
+
/** `mailboxUid`/`userUid` are always known at construction time (the only call site is
|
|
34
|
+
* `MapiSessionManager.create()`, which resolves both up front) - required here rather than optional with
|
|
35
|
+
* a same-value fallback, which would just be dead code no real caller ever takes the other branch of. */
|
|
36
|
+
constructor(other) {
|
|
37
|
+
super(other);
|
|
38
|
+
this.handles = {};
|
|
39
|
+
this.nextHandleIndex = 1;
|
|
40
|
+
this.createdAt = new Date().toISOString();
|
|
41
|
+
/** This session's FID assignments for the 13 `RopLogon` special folders, keyed by FID (decimal string),
|
|
42
|
+
* valued `"virtual:<name>"` or `"folder:<uid>"` - see `RopLogonHandler`'s own doc comment. Populated by
|
|
43
|
+
* `RopLogon`, read back by a later `RopOpenFolder`. */
|
|
44
|
+
this.folderIds = {};
|
|
45
|
+
/** The reverse of `folderIds` (target -> FID) plus a monotonic counter, maintained alongside it by
|
|
46
|
+
* `FolderTarget.assignOrGetFid`/`RopLogonHandler` so "does this target already have a FID" is an O(1)
|
|
47
|
+
* lookup instead of a linear scan of `folderIds` repeated once per table row - see `assignOrGetFid`'s own
|
|
48
|
+
* doc comment for why the scan this replaces was a genuine (not just theoretical) quadratic cost across a
|
|
49
|
+
* session's lifetime. Small and purely additive to session state (one more string-keyed map of numbers,
|
|
50
|
+
* the same order of magnitude as `folderIds` itself), unlike caching whole resolved rows would be. */
|
|
51
|
+
this.folderTargetIds = {};
|
|
52
|
+
this.nextFolderId = 1;
|
|
53
|
+
/** This session's MID assignments, keyed by MID (decimal string), valued `"message:<uid>"` - the message
|
|
54
|
+
* analog of `folderIds` above. Unlike `folderIds` (pre-populated by `RopLogon`), a MID only ever comes into
|
|
55
|
+
* existence lazily, the first time a `RopQueryRows` row exposes a message's `PidTagMid` column (see
|
|
56
|
+
* `MessageTarget.assignOrGetMid`), read back by a later `RopOpenMessage`. */
|
|
57
|
+
this.messageIds = {};
|
|
58
|
+
/** The reverse of `messageIds` (target -> MID) plus a monotonic counter - the message analog of
|
|
59
|
+
* `folderTargetIds`/`nextFolderId` above, for the identical O(1)-lookup reason. */
|
|
60
|
+
this.messageTargetIds = {};
|
|
61
|
+
this.nextMessageId = 1;
|
|
62
|
+
/** This session's `RopGetPropertyIdsFromNames` mapping table (`[MS-OXCPRPT]` §2.2.12), keyed by a JSON
|
|
63
|
+
* string encoding of the `{guid, kind, lid|name}` `PropertyName` the numeric ID was assigned to - see
|
|
64
|
+
* `NamedPropertyRegistry.ts`'s own doc comment for why a JSON string (rather than a delimiter-joined one)
|
|
65
|
+
* is the safest key shape here. A real client resolves every named property (almost every Calendar-specific
|
|
66
|
+
* one - `PidLidAppointmentStartWhole`, `PidLidBusyStatus`, `PidLidAppointmentRecur`, ...) through this table
|
|
67
|
+
* once per session before ever setting/reading it via `RopSetProperties`/`RopGetPropertiesSpecific`. */
|
|
68
|
+
this.namedProperties = {};
|
|
69
|
+
/** The reverse of `namedProperties` (numeric ID -> `PropertyName` JSON key) plus a monotonic counter -
|
|
70
|
+
* `NamedPropertyRegistry.ts`'s own doc comment covers why both the O(n) forward scan this replaces and the
|
|
71
|
+
* `Math.max(...spread)` id-allocation it used are real problems, not just theoretical ones, at real
|
|
72
|
+
* mailbox/session scale. */
|
|
73
|
+
this.namedPropertyIds = {};
|
|
74
|
+
this.nextNamedPropertyId = 0x8000;
|
|
75
|
+
this.mailboxUid = other.mailboxUid;
|
|
76
|
+
this.userUid = other.userUid;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Stores/loads `MapiSessionContext`s, keyed by the opaque session id that also becomes the `MapiContext`
|
|
81
|
+
* cookie value. Reuses `@rapidrest/service-core`'s existing generic `RedisCache<T>` (Redis-backed when a
|
|
82
|
+
* `cache` datastore is configured, transparent in-memory fallback otherwise) rather than either a bespoke
|
|
83
|
+
* Redis client (unlike `PingCommand`'s own throwaway pub/sub client - fine for ephemeral fan-out, not a good
|
|
84
|
+
* fit for stateful session data) or the framework's generic cookie/session middleware (built for a whole-app
|
|
85
|
+
* `req.session` object, not this protocol's specific handle-table shape). `RedisCache` needs no real
|
|
86
|
+
* persisted entity - only a class with a `.name` for its Redis key prefix - so a purely ephemeral,
|
|
87
|
+
* never-persisted class like `MapiSessionContext` is a perfectly valid `type` for it.
|
|
88
|
+
*
|
|
89
|
+
* @author Jean-Philippe Steinmetz
|
|
90
|
+
*/
|
|
91
|
+
export class MapiSessionManager {
|
|
92
|
+
async init() {
|
|
93
|
+
this.cache = await this._objectFactory.newInstance(RedisCache, {
|
|
94
|
+
name: MapiSessionContext.name,
|
|
95
|
+
args: [MapiSessionContext],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async create(mailboxUid, userUid) {
|
|
99
|
+
const context = new MapiSessionContext({ mailboxUid, userUid });
|
|
100
|
+
await this.cache.save(context.uid, context, SESSION_TTL_SECONDS);
|
|
101
|
+
return context;
|
|
102
|
+
}
|
|
103
|
+
async load(sessionId) {
|
|
104
|
+
return this.cache.load(sessionId);
|
|
105
|
+
}
|
|
106
|
+
async save(context) {
|
|
107
|
+
await this.cache.save(context.uid, context, SESSION_TTL_SECONDS);
|
|
108
|
+
}
|
|
109
|
+
async destroy(sessionId) {
|
|
110
|
+
await this.cache.delete(sessionId);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
__decorate([
|
|
114
|
+
Init,
|
|
115
|
+
__metadata("design:type", Function),
|
|
116
|
+
__metadata("design:paramtypes", []),
|
|
117
|
+
__metadata("design:returntype", Promise)
|
|
118
|
+
], MapiSessionManager.prototype, "init", null);
|
|
119
|
+
//# sourceMappingURL=MapiSessionManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapiSessionManager.js","sourceRoot":"","sources":["../../src/MapiSessionManager.ts"],"names":[],"mappings":";;;;;;;;;AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAiB,UAAU,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAClF,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;AAElC;8GAC8G;AAC9G,MAAM,mBAAmB,GAAG,EAAE,GAAG,EAAE,CAAC;AAgDpC;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,kBAAmB,SAAQ,YAAY;IA8ChD;;6GAEyG;IACzG,YAAmB,KAAsE;QACrF,KAAK,CAAC,KAAK,CAAC,CAAC;QA/CV,YAAO,GAAqC,EAAE,CAAC;QAC/C,oBAAe,GAAG,CAAC,CAAC;QACpB,cAAS,GAAW,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACpD;;+DAEuD;QAChD,cAAS,GAA2B,EAAE,CAAC;QAE9C;;;;;8GAKsG;QAC/F,oBAAe,GAA2B,EAAE,CAAC;QAC7C,iBAAY,GAAG,CAAC,CAAC;QAExB;;;qFAG6E;QACtE,eAAU,GAA2B,EAAE,CAAC;QAE/C;2FACmF;QAC5E,qBAAgB,GAA2B,EAAE,CAAC;QAC9C,kBAAa,GAAG,CAAC,CAAC;QAEzB;;;;;gHAKwG;QACjG,oBAAe,GAA2B,EAAE,CAAC;QAEpD;;;oCAG4B;QACrB,qBAAgB,GAA2B,EAAE,CAAC;QAC9C,wBAAmB,GAAG,MAAM,CAAC;QAOhC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IACjC,CAAC;CACJ;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,kBAAkB;IAOd,AAAN,KAAK,CAAC,IAAI;QACb,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,cAAe,CAAC,WAAW,CAAC,UAAU,EAAE;YAC5D,IAAI,EAAE,kBAAkB,CAAC,IAAI;YAC7B,IAAI,EAAE,CAAC,kBAAkB,CAAC;SAC7B,CAAC,CAAC;IACP,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,UAAkB,EAAE,OAAe;QACnD,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;QAChE,MAAM,IAAI,CAAC,KAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC;QAClE,OAAO,OAAO,CAAC;IACnB,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,SAAiB;QAC/B,OAAO,IAAI,CAAC,KAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,OAA2B;QACzC,MAAM,IAAI,CAAC,KAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC;IACtE,CAAC;IAEM,KAAK,CAAC,OAAO,CAAC,SAAiB;QAClC,MAAM,IAAI,CAAC,KAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;CACJ;AAxBgB;IADZ,IAAI;;;;8CAMJ"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { BufferReader, BufferWriter } from "./codec/BufferCursor.js";
|
|
6
|
+
/**
|
|
7
|
+
* Walks a decoded `RopBuffer`'s `ropsList` blob, dispatching each contained ROP - identified by its own
|
|
8
|
+
* leading `RopId` byte - to the matching registered handler in turn, collecting every response into one
|
|
9
|
+
* output buffer. Deliberately ROP-agnostic: it knows nothing about any specific ROP's own fields, only how to
|
|
10
|
+
* find the *next* one, via the handler's own advanced `reader` position once `handle()` returns - see
|
|
11
|
+
* `RopBuffer.ts`'s doc comment for why a fully generic split isn't possible for this wire format.
|
|
12
|
+
*
|
|
13
|
+
* An unrecognized `RopId` stops processing immediately: the bytes belonging to that ROP (and everything after
|
|
14
|
+
* it) can't be skipped without knowing that ROP's own layout, so there is no safe way to resync and continue.
|
|
15
|
+
* This is an inherent constraint of the wire format, not a design choice this library is choosing not to
|
|
16
|
+
* handle - a real client only ever sends ROPs the server has already told it (via its supported-command
|
|
17
|
+
* surface) that it understands.
|
|
18
|
+
*
|
|
19
|
+
* @author Jean-Philippe Steinmetz
|
|
20
|
+
*/
|
|
21
|
+
export async function dispatchRops(ropsList, handlers, context) {
|
|
22
|
+
const reader = new BufferReader(ropsList);
|
|
23
|
+
const writer = new BufferWriter();
|
|
24
|
+
while (reader.hasMore()) {
|
|
25
|
+
const ropId = reader.readUInt8();
|
|
26
|
+
const handler = handlers.get(ropId);
|
|
27
|
+
if (!handler) {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
await handler.handle(reader, writer, context);
|
|
31
|
+
}
|
|
32
|
+
return writer.toBuffer();
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=RopDispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RopDispatcher.js","sourceRoot":"","sources":["../../src/RopDispatcher.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGrE;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAC9B,QAAgB,EAChB,QAAiC,EACjC,OAAmB;IAEnB,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;IAClC,OAAO,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,MAAM;QACV,CAAC;QACD,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC7B,CAAC"}
|