@rapidmx/activesync-plugin 1.0.0-beta.2 → 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/README.md +4 -4
- package/dist/lib/BaseDeviceSyncStateRoute.js +32 -0
- package/dist/lib/BaseDeviceSyncStateRoute.js.map +1 -1
- package/dist/lib/BaseEasRoute.js +91 -15
- package/dist/lib/BaseEasRoute.js.map +1 -1
- package/dist/lib/EasAuditLog.js +56 -0
- package/dist/lib/EasAuditLog.js.map +1 -0
- package/dist/lib/EasCollectionLease.js +199 -0
- package/dist/lib/EasCollectionLease.js.map +1 -0
- package/dist/lib/EasCollectionStore.js +102 -0
- package/dist/lib/EasCollectionStore.js.map +1 -0
- package/dist/lib/EasCollectionSync.js +294 -0
- package/dist/lib/EasCollectionSync.js.map +1 -0
- package/dist/lib/EasSyncKeyUtils.js +125 -62
- package/dist/lib/EasSyncKeyUtils.js.map +1 -1
- package/dist/lib/MessageMoveRules.js +100 -0
- package/dist/lib/MessageMoveRules.js.map +1 -0
- package/dist/lib/MimeHeaderUtils.js +395 -0
- package/dist/lib/MimeHeaderUtils.js.map +1 -0
- package/dist/lib/RegexPatternUtils.js +30 -0
- package/dist/lib/RegexPatternUtils.js.map +1 -0
- package/dist/lib/RestapiCompat.js +41 -0
- package/dist/lib/RestapiCompat.js.map +1 -0
- package/dist/lib/adapters/CalendarSyncAdapter.js +142 -33
- package/dist/lib/adapters/CalendarSyncAdapter.js.map +1 -1
- package/dist/lib/adapters/EmailSyncAdapter.js +114 -27
- package/dist/lib/adapters/EmailSyncAdapter.js.map +1 -1
- package/dist/lib/adapters/mongo/EmailSyncAdapterMongo.js +2 -1
- package/dist/lib/adapters/mongo/EmailSyncAdapterMongo.js.map +1 -1
- package/dist/lib/adapters/sql/EmailSyncAdapterSQL.js +2 -1
- package/dist/lib/adapters/sql/EmailSyncAdapterSQL.js.map +1 -1
- package/dist/lib/codec/WbxmlDecoder.js +86 -34
- package/dist/lib/codec/WbxmlDecoder.js.map +1 -1
- package/dist/lib/codec/WbxmlEncoder.js +103 -22
- package/dist/lib/codec/WbxmlEncoder.js.map +1 -1
- package/dist/lib/commands/ComposeMailCommand.js +105 -31
- package/dist/lib/commands/ComposeMailCommand.js.map +1 -1
- package/dist/lib/commands/FolderSyncCommand.js +67 -27
- package/dist/lib/commands/FolderSyncCommand.js.map +1 -1
- package/dist/lib/commands/GetItemEstimateCommand.js +80 -59
- package/dist/lib/commands/GetItemEstimateCommand.js.map +1 -1
- package/dist/lib/commands/ItemOperationsCommand.js +219 -56
- package/dist/lib/commands/ItemOperationsCommand.js.map +1 -1
- package/dist/lib/commands/MeetingResponseCommand.js +212 -53
- package/dist/lib/commands/MeetingResponseCommand.js.map +1 -1
- package/dist/lib/commands/MoveItemsCommand.js +54 -21
- package/dist/lib/commands/MoveItemsCommand.js.map +1 -1
- package/dist/lib/commands/PingCommand.js +288 -56
- package/dist/lib/commands/PingCommand.js.map +1 -1
- package/dist/lib/commands/ProvisionCommand.js +34 -17
- package/dist/lib/commands/ProvisionCommand.js.map +1 -1
- package/dist/lib/commands/ResolveRecipientsCommand.js +43 -5
- package/dist/lib/commands/ResolveRecipientsCommand.js.map +1 -1
- package/dist/lib/commands/SearchCommand.js +92 -17
- package/dist/lib/commands/SearchCommand.js.map +1 -1
- package/dist/lib/commands/SettingsCommand.js +2 -1
- package/dist/lib/commands/SettingsCommand.js.map +1 -1
- package/dist/lib/commands/SmartForwardCommand.js +2 -1
- package/dist/lib/commands/SmartForwardCommand.js.map +1 -1
- package/dist/lib/commands/SmartReplyCommand.js +2 -1
- package/dist/lib/commands/SmartReplyCommand.js.map +1 -1
- package/dist/lib/commands/SyncCommand.js +481 -244
- package/dist/lib/commands/SyncCommand.js.map +1 -1
- package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js +6 -1
- package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js +3 -1
- package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js +2 -1
- package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/PingCommandMongo.js +23 -0
- package/dist/lib/commands/mongo/PingCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/SearchCommandMongo.js +3 -1
- package/dist/lib/commands/mongo/SearchCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/SendMailCommandMongo.js +2 -1
- package/dist/lib/commands/mongo/SendMailCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/SmartForwardCommandMongo.js +2 -1
- package/dist/lib/commands/mongo/SmartForwardCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/SmartReplyCommandMongo.js +2 -1
- package/dist/lib/commands/mongo/SmartReplyCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/SyncCommandMongo.js +7 -1
- package/dist/lib/commands/mongo/SyncCommandMongo.js.map +1 -1
- package/dist/lib/commands/mongo/index.js +1 -0
- package/dist/lib/commands/mongo/index.js.map +1 -1
- package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js +6 -1
- package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/ItemOperationsCommandSQL.js +3 -1
- package/dist/lib/commands/sql/ItemOperationsCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/MeetingResponseCommandSQL.js +2 -1
- package/dist/lib/commands/sql/MeetingResponseCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/PingCommandSQL.js +23 -0
- package/dist/lib/commands/sql/PingCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/SearchCommandSQL.js +3 -1
- package/dist/lib/commands/sql/SearchCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/SendMailCommandSQL.js +2 -1
- package/dist/lib/commands/sql/SendMailCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/SmartForwardCommandSQL.js +2 -1
- package/dist/lib/commands/sql/SmartForwardCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/SmartReplyCommandSQL.js +2 -1
- package/dist/lib/commands/sql/SmartReplyCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/SyncCommandSQL.js +7 -1
- package/dist/lib/commands/sql/SyncCommandSQL.js.map +1 -1
- package/dist/lib/commands/sql/index.js +1 -0
- package/dist/lib/commands/sql/index.js.map +1 -1
- package/dist/lib/index.js +5 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/jobs/EasDeviceStateCleanupJob.js +44 -2
- package/dist/lib/jobs/EasDeviceStateCleanupJob.js.map +1 -1
- package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js +4 -0
- package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js.map +1 -1
- package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js +9 -0
- package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js.map +1 -1
- package/dist/lib/models/EasCollectionChunk.js +2 -0
- package/dist/lib/models/EasCollectionChunk.js.map +1 -0
- package/dist/lib/models/EasCollectionState.js +2 -0
- package/dist/lib/models/EasCollectionState.js.map +1 -0
- package/dist/lib/models/mongo/DeviceSyncStateMongo.js +7 -0
- package/dist/lib/models/mongo/DeviceSyncStateMongo.js.map +1 -1
- package/dist/lib/models/mongo/EasCollectionChunkMongo.js +83 -0
- package/dist/lib/models/mongo/EasCollectionChunkMongo.js.map +1 -0
- package/dist/lib/models/mongo/EasCollectionStateMongo.js +162 -0
- package/dist/lib/models/mongo/EasCollectionStateMongo.js.map +1 -0
- package/dist/lib/models/sql/DeviceSyncStateSQL.js +7 -0
- package/dist/lib/models/sql/DeviceSyncStateSQL.js.map +1 -1
- package/dist/lib/models/sql/EasCollectionChunkSQL.js +83 -0
- package/dist/lib/models/sql/EasCollectionChunkSQL.js.map +1 -0
- package/dist/lib/models/sql/EasCollectionStateSQL.js +162 -0
- package/dist/lib/models/sql/EasCollectionStateSQL.js.map +1 -0
- package/dist/lib/mongo/EasRouteMongo.js +2 -2
- package/dist/lib/mongo/EasRouteMongo.js.map +1 -1
- package/dist/lib/mongo.js +2 -0
- package/dist/lib/mongo.js.map +1 -1
- package/dist/lib/sql/EasRouteSQL.js +2 -2
- package/dist/lib/sql/EasRouteSQL.js.map +1 -1
- package/dist/lib/sql.js +2 -0
- package/dist/lib/sql.js.map +1 -1
- package/dist/types/BaseDeviceSyncStateRoute.d.ts +10 -0
- package/dist/types/BaseEasRoute.d.ts +15 -4
- package/dist/types/EasAuditLog.d.ts +47 -0
- package/dist/types/EasCollectionLease.d.ts +60 -0
- package/dist/types/EasCollectionStore.d.ts +51 -0
- package/dist/types/EasCollectionSync.d.ts +96 -0
- package/dist/types/EasCommandHandler.d.ts +7 -4
- package/dist/types/EasSyncKeyUtils.d.ts +73 -42
- package/dist/types/MessageMoveRules.d.ts +45 -0
- package/dist/types/MimeHeaderUtils.d.ts +73 -0
- package/dist/types/RegexPatternUtils.d.ts +14 -0
- package/dist/types/RestapiCompat.d.ts +24 -0
- package/dist/types/adapters/CalendarSyncAdapter.d.ts +47 -18
- package/dist/types/adapters/EasCollectionSyncAdapter.d.ts +18 -2
- package/dist/types/adapters/EmailSyncAdapter.d.ts +24 -7
- package/dist/types/adapters/mongo/EmailSyncAdapterMongo.d.ts +1 -0
- package/dist/types/adapters/sql/EmailSyncAdapterSQL.d.ts +1 -0
- package/dist/types/codec/WbxmlDecoder.d.ts +39 -0
- package/dist/types/codec/WbxmlEncoder.d.ts +36 -1
- package/dist/types/commands/ComposeMailCommand.d.ts +33 -15
- package/dist/types/commands/FolderSyncCommand.d.ts +4 -1
- package/dist/types/commands/GetItemEstimateCommand.d.ts +20 -15
- package/dist/types/commands/ItemOperationsCommand.d.ts +29 -4
- package/dist/types/commands/MeetingResponseCommand.d.ts +52 -13
- package/dist/types/commands/MoveItemsCommand.d.ts +13 -3
- package/dist/types/commands/PingCommand.d.ts +72 -14
- package/dist/types/commands/ProvisionCommand.d.ts +16 -11
- package/dist/types/commands/ResolveRecipientsCommand.d.ts +4 -0
- package/dist/types/commands/SearchCommand.d.ts +13 -1
- package/dist/types/commands/SyncCommand.d.ts +102 -62
- package/dist/types/commands/mongo/GetItemEstimateCommandMongo.d.ts +3 -0
- package/dist/types/commands/mongo/ItemOperationsCommandMongo.d.ts +2 -0
- package/dist/types/commands/mongo/MeetingResponseCommandMongo.d.ts +1 -0
- package/dist/types/commands/mongo/PingCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/SearchCommandMongo.d.ts +2 -0
- package/dist/types/commands/mongo/SendMailCommandMongo.d.ts +1 -0
- package/dist/types/commands/mongo/SmartForwardCommandMongo.d.ts +1 -0
- package/dist/types/commands/mongo/SmartReplyCommandMongo.d.ts +1 -0
- package/dist/types/commands/mongo/SyncCommandMongo.d.ts +4 -0
- package/dist/types/commands/mongo/index.d.ts +1 -0
- package/dist/types/commands/sql/GetItemEstimateCommandSQL.d.ts +3 -0
- package/dist/types/commands/sql/ItemOperationsCommandSQL.d.ts +2 -0
- package/dist/types/commands/sql/MeetingResponseCommandSQL.d.ts +1 -0
- package/dist/types/commands/sql/PingCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/SearchCommandSQL.d.ts +2 -0
- package/dist/types/commands/sql/SendMailCommandSQL.d.ts +1 -0
- package/dist/types/commands/sql/SmartForwardCommandSQL.d.ts +1 -0
- package/dist/types/commands/sql/SmartReplyCommandSQL.d.ts +1 -0
- package/dist/types/commands/sql/SyncCommandSQL.d.ts +4 -0
- package/dist/types/commands/sql/index.d.ts +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/jobs/EasDeviceStateCleanupJob.d.ts +15 -0
- package/dist/types/jobs/mongo/EasDeviceStateCleanupJobMongo.d.ts +2 -0
- package/dist/types/jobs/sql/EasDeviceStateCleanupJobSQL.d.ts +4 -0
- package/dist/types/models/DeviceSyncState.d.ts +13 -2
- package/dist/types/models/EasCollectionChunk.d.ts +20 -0
- package/dist/types/models/EasCollectionState.d.ts +79 -0
- package/dist/types/models/mongo/DeviceSyncStateMongo.d.ts +1 -0
- package/dist/types/models/mongo/EasCollectionChunkMongo.d.ts +16 -0
- package/dist/types/models/mongo/EasCollectionStateMongo.d.ts +27 -0
- package/dist/types/models/sql/DeviceSyncStateSQL.d.ts +1 -0
- package/dist/types/models/sql/EasCollectionChunkSQL.d.ts +16 -0
- package/dist/types/models/sql/EasCollectionStateSQL.d.ts +27 -0
- package/dist/types/mongo.d.ts +2 -0
- package/dist/types/sql.d.ts +2 -0
- package/package.json +267 -238
|
@@ -14,11 +14,18 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
14
14
|
import * as crypto from "crypto";
|
|
15
15
|
import { simpleParser } from "mailparser";
|
|
16
16
|
import { ApiError, ObjectDecorators } from "@rapidrest/core";
|
|
17
|
-
import { ACLAction, ACLUtils, ApiErrorMessages, ApiErrors } from "@rapidrest/service-core";
|
|
17
|
+
import { ACLAction, ACLUtils, ApiErrorMessages, ApiErrors, RepoUtils } from "@rapidrest/service-core";
|
|
18
18
|
import { ScanPipeline } from "@rapidmx/restapi/scan";
|
|
19
|
-
import { findOrCreateWellKnownFolder, FolderType, MessageImportance, RecipientType, RecoverableRepoUtils, scanAndRelay, } from "@rapidmx/restapi";
|
|
20
|
-
import {
|
|
21
|
-
|
|
19
|
+
import { findOrCreateWellKnownFolder, FolderType, MessageImportance, prependHeaders, RecipientType, RecoverableRepoUtils, scanAndRelay, } from "@rapidmx/restapi";
|
|
20
|
+
import { WbxmlCodePage } from "../codec/WbxmlCodePages.js";
|
|
21
|
+
import { childText, element, findChild, textElement } from "../codec/WbxmlElement.js";
|
|
22
|
+
import { checkComposedOriginators, extractOriginatorHeaders, stripHeader } from "../MimeHeaderUtils.js";
|
|
23
|
+
import { boundIndexedValue } from "../RestapiCompat.js";
|
|
24
|
+
const { Init, Inject, Logger } = ObjectDecorators;
|
|
25
|
+
/** Most envelope recipients (To + Cc + Bcc) one composed message may carry. */
|
|
26
|
+
export const MAX_COMPOSE_RECIPIENTS = 500;
|
|
27
|
+
/** [MS-ASCMD] common Status 119, MessageHasNoRecipient. */
|
|
28
|
+
const STATUS_NO_RECIPIENT = "119";
|
|
22
29
|
/** Flattens mailparser's `AddressObject | AddressObject[] | undefined` union (grouped addresses can nest an
|
|
23
30
|
* `AddressObject` per group) into a plain list of SMTP addresses, dropping any entry with no address (a
|
|
24
31
|
* pure-group header with no direct member). */
|
|
@@ -40,6 +47,7 @@ function collectAddresses(entry, out) {
|
|
|
40
47
|
collectAddresses(grouped, out);
|
|
41
48
|
}
|
|
42
49
|
}
|
|
50
|
+
export { stripHeader };
|
|
43
51
|
/**
|
|
44
52
|
* Shared implementation for EAS `SendMail`, `SmartForward`, and `SmartReply` (MS-ASCMD `ComposeMail` namespace)
|
|
45
53
|
* — all three submit a client-composed raw MIME body directly (`<Mime>`, opaque WBXML content) rather than
|
|
@@ -47,23 +55,33 @@ function collectAddresses(entry, out) {
|
|
|
47
55
|
* `BaseMessageRoute.send()`, which this class's `scanAndRelay()` call shares its scan-then-relay core with via
|
|
48
56
|
* `MailSendUtils.ts`).
|
|
49
57
|
*
|
|
58
|
+
* **Sender and envelope checks** (the MIME is entirely device-controlled): the raw bytes pass restapi's own originator
|
|
59
|
+
* rules before anything parses them (`MimeHeaderUtils.checkComposedOriginators`: an inline copy of restapi's
|
|
60
|
+
* `checkOriginatorHeaders` with `rejectAddressLikeDisplayNames`): exactly one `From`, at most one `Sender` (found by a tolerant lexer - `From :`, folded lines and bare-CR
|
|
61
|
+
* line breaks included), every address in them - group members too - the caller's own mailbox's primary or alias
|
|
62
|
+
* address, no address-like text outside an address (`<me@x> <victim@y>`), and no address in a display name or comment
|
|
63
|
+
* (`"ceo@y" <me@x>`, `me@x (victim@y)`), and - stricter than restapi - no empty group (`victims:;, me@x`). Anything
|
|
64
|
+
* else is HTTP 403 (a MIME with no `From` at all is HTTP 400), so a device can't send as, or appear to be, anyone else;
|
|
65
|
+
* the envelope sender is the validated `From`. A message with no To/Cc/Bcc address is answered with the command's own
|
|
66
|
+
* `Status` 119 (MessageHasNoRecipient), nothing relayed. The envelope is capped at `MAX_COMPOSE_RECIPIENTS`
|
|
67
|
+
* recipients (HTTP 400). `Bcc` recipients are delivered via the envelope, but every `Bcc` header field (found by the
|
|
68
|
+
* same lexer, so `Bcc :` too) is stripped from the relayed copy so other recipients never see it (the Sent Items copy
|
|
69
|
+
* keeps it). The Sent Items copy records the relay's own `Message-ID`/`conversationId` (bounded like restapi's), so
|
|
70
|
+
* recall and threading match what recipients received.
|
|
71
|
+
*
|
|
50
72
|
* **Pragmatic subset, deliberately not the full MS-ASCMD semantics**:
|
|
51
73
|
* - `SmartForward`/`SmartReply`'s `<Source>` (the message being forwarded/replied to) is used only to thread
|
|
52
74
|
* the outgoing message (`inReplyTo`/`references`) and to flip the original's `Answered`/`Forwarded` flag - the
|
|
53
|
-
* real spec has the *server* splice the original message's full content into the outgoing MIME
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* fidelity" precedent elsewhere (e.g. `FolderSyncCommand`'s SyncKey replay-protection gap).
|
|
75
|
+
* real spec has the *server* splice the original message's full content into the outgoing MIME; this subset
|
|
76
|
+
* expects the client's own `<Mime>` to already be the complete outgoing message. The flag flip needs `UPDATE` on
|
|
77
|
+
* the original's folder and is best-effort: the message has already been sent, so a denied or conflicting flag
|
|
78
|
+
* update is logged, never turned into a failed request.
|
|
58
79
|
* - `ReplaceMime`/`AccountId`/`InstanceId` are not read - single-account, non-recurring-meeting compose only.
|
|
59
|
-
* - Attachments present in the composed MIME are relayed correctly
|
|
60
|
-
*
|
|
61
|
-
* copy - `Message.hasAttachments` is still set correctly from the parsed MIME, just not each attachment's own
|
|
62
|
-
* row (deferred, matching `ItemOperationsCommand`'s own future `Fetch`-of-Sent-Items scope).
|
|
80
|
+
* - Attachments present in the composed MIME are relayed correctly but are not additionally persisted as
|
|
81
|
+
* `Attachment` records on the saved Sent Items copy (`Message.hasAttachments` is still set).
|
|
63
82
|
*
|
|
64
|
-
* `folderClass`/`messageClass` are supplied by the Mongo/SQL concrete subclasses, and
|
|
65
|
-
* `
|
|
66
|
-
* `<Source>` to act on).
|
|
83
|
+
* `folderClass`/`messageClass`/`mailboxClass` are supplied by the Mongo/SQL concrete subclasses, and
|
|
84
|
+
* `markOriginal()` by the `SmartForwardCommand`/`SmartReplyCommand` subclasses.
|
|
67
85
|
*
|
|
68
86
|
* @author Jean-Philippe Steinmetz
|
|
69
87
|
*/
|
|
@@ -77,15 +95,19 @@ export class ComposeMailCommand {
|
|
|
77
95
|
name: this.messageClass.name,
|
|
78
96
|
args: [this.messageClass],
|
|
79
97
|
});
|
|
98
|
+
this.mailboxRepo = await this._objectFactory.newInstance(RepoUtils, {
|
|
99
|
+
name: this.mailboxClass.name,
|
|
100
|
+
args: [this.mailboxClass],
|
|
101
|
+
});
|
|
80
102
|
}
|
|
81
|
-
/** Called once the outgoing message has been sent, only when the request carried a `<Source>`
|
|
82
|
-
*
|
|
83
|
-
*
|
|
103
|
+
/** Called once the outgoing message has been sent, only when the request carried a `<Source>` the caller may
|
|
104
|
+
* update - flips the referenced original message's own `Answered`/`Forwarded` flag. A no-op here; overridden by
|
|
105
|
+
* the two subclasses that need it. */
|
|
84
106
|
async markOriginal(_ctx, _original) {
|
|
85
|
-
// No-op by default (
|
|
107
|
+
// No-op by default (plain SendMail has nothing to flag).
|
|
86
108
|
}
|
|
87
109
|
async handle(ctx) {
|
|
88
|
-
if (!this.folderRepo || !this.messageRepo || !this.blobStore || !this.mailTransport || !this.scanPipeline) {
|
|
110
|
+
if (!this.folderRepo || !this.messageRepo || !this.mailboxRepo || !this.blobStore || !this.mailTransport || !this.scanPipeline) {
|
|
89
111
|
throw new ApiError(ApiErrors.INTERNAL_ERROR, 500, ApiErrorMessages.INTERNAL_ERROR);
|
|
90
112
|
}
|
|
91
113
|
if (!ctx.request) {
|
|
@@ -99,8 +121,7 @@ export class ComposeMailCommand {
|
|
|
99
121
|
throw new ApiError(ApiErrors.INVALID_REQUEST, 400, "A SendMail/SmartForward/SmartReply request must include a MIME body.");
|
|
100
122
|
}
|
|
101
123
|
// A `SmartForward`/`SmartReply` request identifies the message being acted on via `<Source><ItemId>` -
|
|
102
|
-
// the same `Message.uid` this library already exposes as `ServerId` in Sync/FolderSync responses
|
|
103
|
-
// no separate lookup table is needed. `SendMail` never carries a `<Source>` at all.
|
|
124
|
+
// the same `Message.uid` this library already exposes as `ServerId` in Sync/FolderSync responses.
|
|
104
125
|
let original;
|
|
105
126
|
const sourceEl = findChild(ctx.request, "Source");
|
|
106
127
|
if (sourceEl) {
|
|
@@ -117,28 +138,69 @@ export class ComposeMailCommand {
|
|
|
117
138
|
}
|
|
118
139
|
original = found;
|
|
119
140
|
}
|
|
141
|
+
const mailbox = await this.mailboxRepo.findOne(ctx.mailboxUid, { ignoreACL: true });
|
|
142
|
+
if (!mailbox) {
|
|
143
|
+
throw new ApiError(ApiErrors.NOT_FOUND, 404, ApiErrorMessages.NOT_FOUND);
|
|
144
|
+
}
|
|
145
|
+
const ownAddresses = new Set([mailbox.primarySmtpAddress, ...(mailbox.aliasAddresses ?? [])].filter((a) => typeof a === "string").map((a) => a.toLowerCase()));
|
|
146
|
+
const isAllowed = (address) => ownAddresses.has(address.toLowerCase());
|
|
147
|
+
// Checked on the raw bytes before parsing, with restapi's own sender rules (see `MimeHeaderUtils`): mailparser
|
|
148
|
+
// keeps just one of several `From`/`Sender` fields and recovers malformed address lists tolerantly, so checking
|
|
149
|
+
// only its parse would validate something other than what a recipient may be shown.
|
|
150
|
+
if (extractOriginatorHeaders(raw).from.length === 0) {
|
|
151
|
+
throw new ApiError(ApiErrors.INVALID_REQUEST, 400, "The composed Mime has no resolvable From/To address.");
|
|
152
|
+
}
|
|
153
|
+
if (checkComposedOriginators(raw, isAllowed) !== undefined) {
|
|
154
|
+
throw new ApiError(ApiErrors.AUTH_PERMISSION_FAILURE, 403, "The composed message's From address is not one of this mailbox's addresses.");
|
|
155
|
+
}
|
|
120
156
|
const parsed = await simpleParser(raw);
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
|
|
157
|
+
const fromAddresses = addressesOf(parsed.from);
|
|
158
|
+
const envelopeFrom = fromAddresses[0];
|
|
159
|
+
const envelopeTo = [...addressesOf(parsed.to), ...addressesOf(parsed.cc), ...addressesOf(parsed.bcc)].filter((address) => address.trim().length > 0);
|
|
160
|
+
/* v8 ignore start -- checkComposedOriginators() already required a From address mailparser reads too */
|
|
161
|
+
if (!envelopeFrom) {
|
|
124
162
|
throw new ApiError(ApiErrors.INVALID_REQUEST, 400, "The composed Mime has no resolvable From/To address.");
|
|
125
163
|
}
|
|
126
|
-
|
|
164
|
+
/* v8 ignore stop */
|
|
165
|
+
if (envelopeTo.length === 0) {
|
|
166
|
+
// Like restapi's send() (400 for a message with no To/Cc/Bcc recipient), refused before anything is relayed -
|
|
167
|
+
// reported the ActiveSync way, [MS-ASCMD] Status 119 (MessageHasNoRecipient) in the command's own response.
|
|
168
|
+
return element(WbxmlCodePage.ComposeMail, this.command, [textElement(WbxmlCodePage.ComposeMail, "Status", STATUS_NO_RECIPIENT)]);
|
|
169
|
+
}
|
|
170
|
+
if (envelopeTo.length > MAX_COMPOSE_RECIPIENTS) {
|
|
171
|
+
throw new ApiError(ApiErrors.INVALID_REQUEST, 400, `A composed message may have at most ${MAX_COMPOSE_RECIPIENTS} recipients.`);
|
|
172
|
+
}
|
|
173
|
+
// Defense in depth: the raw checks above already cover every address mailparser can report.
|
|
174
|
+
/* v8 ignore start -- unreachable while checkOriginatorHeaders() passes; kept in case the two parsers ever disagree */
|
|
175
|
+
const senderAddresses = addressesOf(parsed.headers.get("sender"));
|
|
176
|
+
if ([...fromAddresses, ...senderAddresses].some((address) => !isAllowed(address))) {
|
|
177
|
+
throw new ApiError(ApiErrors.AUTH_PERMISSION_FAILURE, 403, "The composed message's From address is not one of this mailbox's addresses.");
|
|
178
|
+
}
|
|
179
|
+
/* v8 ignore stop */
|
|
180
|
+
const stripped = stripHeader(raw, "bcc");
|
|
181
|
+
const relayed = await scanAndRelay(stripped, envelopeFrom, envelopeTo, this.scanPipeline, this.mailTransport, this.blobStore);
|
|
127
182
|
if (findChild(ctx.request, "SaveInSentItems")) {
|
|
128
183
|
const bodyBlobKey = `bodies/${crypto.randomUUID()}`;
|
|
129
|
-
|
|
184
|
+
// The Sent Items copy keeps its Bcc header, but must carry the `Message-ID` the message was actually relayed
|
|
185
|
+
// with: `scanAndRelay()` injects one when the device's MIME had none, and recall/threading match on it.
|
|
186
|
+
const stored = relayed.raw !== stripped ? prependHeaders(raw, [{ name: "Message-ID", value: `<${relayed.messageId}>` }]) : raw;
|
|
187
|
+
await this.blobStore.put(bodyBlobKey, stored, { contentType: "message/rfc822" });
|
|
130
188
|
const sentFolder = await findOrCreateWellKnownFolder(this.folderRepo, this.folderClass, ctx.mailboxUid, FolderType.SENT_ITEMS, ctx.user);
|
|
131
189
|
await this.messageRepo.create(new this.messageClass({
|
|
132
190
|
folderUid: sentFolder.uid,
|
|
133
191
|
mailboxUid: ctx.mailboxUid,
|
|
134
|
-
|
|
192
|
+
// The relay's own values (angle brackets stripped, as every recipient's ingest stores them), bounded
|
|
193
|
+
// the way restapi stores indexed identifiers - not mailparser's bracketed `Message-ID`.
|
|
194
|
+
messageId: boundIndexedValue(relayed.messageId),
|
|
195
|
+
conversationId: boundIndexedValue(relayed.conversationId),
|
|
135
196
|
subject: parsed.subject ?? "",
|
|
136
197
|
from: { address: envelopeFrom, type: RecipientType.TO },
|
|
137
198
|
recipients: buildRecipients(parsed),
|
|
138
199
|
sentDate: new Date(),
|
|
139
200
|
receivedDate: new Date(),
|
|
140
201
|
bodyBlobKey,
|
|
141
|
-
sanitizedHtmlBlobKey,
|
|
202
|
+
sanitizedHtmlBlobKey: relayed.sanitizedHtmlBlobKey,
|
|
203
|
+
encrypted: relayed.encrypted,
|
|
142
204
|
bodyPreview: (parsed.text ?? "").slice(0, 200),
|
|
143
205
|
flags: { read: true, flagged: false, answered: false, forwarded: false },
|
|
144
206
|
importance: MessageImportance.NORMAL,
|
|
@@ -148,7 +210,15 @@ export class ComposeMailCommand {
|
|
|
148
210
|
}), { ignoreACL: true, user: ctx.user });
|
|
149
211
|
}
|
|
150
212
|
if (original) {
|
|
151
|
-
|
|
213
|
+
// The message is already on its way - flagging the original is best-effort bookkeeping.
|
|
214
|
+
try {
|
|
215
|
+
if (await this.aclUtils.hasPermission(ctx.user, original.folderUid, ACLAction.UPDATE)) {
|
|
216
|
+
await this.markOriginal(ctx, original);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
this.logger?.warn(`${this.command}: failed to flag original message ${original.uid}: ${err?.message}`);
|
|
221
|
+
}
|
|
152
222
|
}
|
|
153
223
|
// Per MS-ASCMD: a successful SendMail/SmartForward/SmartReply response is an empty HTTP 200 body, not
|
|
154
224
|
// a Status-coded WBXML document like FolderSync/Sync/Provision return.
|
|
@@ -171,6 +241,10 @@ __decorate([
|
|
|
171
241
|
Inject(ACLUtils),
|
|
172
242
|
__metadata("design:type", ACLUtils)
|
|
173
243
|
], ComposeMailCommand.prototype, "aclUtils", void 0);
|
|
244
|
+
__decorate([
|
|
245
|
+
Logger,
|
|
246
|
+
__metadata("design:type", Object)
|
|
247
|
+
], ComposeMailCommand.prototype, "logger", void 0);
|
|
174
248
|
__decorate([
|
|
175
249
|
Init,
|
|
176
250
|
__metadata("design:type", Function),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComposeMailCommand.js","sourceRoot":"","sources":["../../../src/commands/ComposeMailCommand.ts"],"names":[],"mappings":";;;;;;;;;AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,YAAY,EAA0D,MAAM,YAAY,CAAC;AAClG,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,SAAS,EAAiB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"ComposeMailCommand.js","sourceRoot":"","sources":["../../../src/commands/ComposeMailCommand.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@ import { ApiError, ObjectDecorators } from "@rapidrest/core";
|
|
|
15
15
|
import { ApiErrorMessages, ApiErrors, RepoUtils } from "@rapidrest/service-core";
|
|
16
16
|
import { WbxmlCodePage } from "../codec/WbxmlCodePages.js";
|
|
17
17
|
import { childText, element, textElement } from "../codec/WbxmlElement.js";
|
|
18
|
-
import { computeChanges, formatSyncKey, persistDeviceSyncState, resolveSyncKey } from "../EasSyncKeyUtils.js";
|
|
18
|
+
import { computeChanges, epochCursor, formatSyncKey, persistDeviceSyncState, resolveSyncKey, } from "../EasSyncKeyUtils.js";
|
|
19
19
|
import { FolderType } from "@rapidmx/restapi";
|
|
20
20
|
const { Config, Init } = ObjectDecorators;
|
|
21
21
|
/** Maps this library's `FolderType` to the closest MS-ASCMD folder type code. Several of this app's types
|
|
@@ -47,17 +47,28 @@ const ROOT_PARENT_ID = "0";
|
|
|
47
47
|
* particular folder) key `DeviceSyncState.folderSyncKeys` is stored under for this cursor - distinct from any
|
|
48
48
|
* real `Folder.uid`, which is exactly why using the mailbox's own uid here would be ambiguous. */
|
|
49
49
|
const FOLDER_HIERARCHY_CURSOR_KEY = "$foldersync";
|
|
50
|
+
/** Where the key issued before the current one is kept, so a client that never received the last response can retry
|
|
51
|
+
* with it. */
|
|
52
|
+
const FOLDER_HIERARCHY_PREVIOUS_KEY = "$foldersyncPrevious";
|
|
53
|
+
/** [MS-ASCMD] FolderSync Status 9: the synchronization key is invalid or doesn't match - restart from `SyncKey 0`. */
|
|
54
|
+
const STATUS_INVALID_SYNC_KEY = "9";
|
|
50
55
|
/** Caps how many folder changes are enumerated per round - real mailboxes rarely have more than a few dozen
|
|
51
56
|
* folders, so this is generous, not a real-world binding constraint; it exists so `computeChanges()`'s
|
|
52
57
|
* `MoreAvailable` mechanism is exercised the same way it will be for `SyncCommand`'s much larger item
|
|
53
58
|
* collections. */
|
|
54
59
|
const DEFAULT_WINDOW_SIZE = 512;
|
|
60
|
+
/** Safety bound on how many `windowSize` pages a `SyncKey "0"` request reads while returning the full hierarchy. */
|
|
61
|
+
const MAX_INITIAL_PAGES = 100;
|
|
55
62
|
/**
|
|
56
63
|
* Handles EAS `FolderSync`: enumerates `Add`/`Update`/`Delete`s for the caller's mailbox's `Folder` hierarchy
|
|
57
64
|
* since the device's last `FolderSync`, using the shared watermark-based cursor mechanism in
|
|
58
65
|
* `EasSyncKeyUtils.ts` (scoped by `mailboxUid` over the `Folder` collection, rather than `folderUid` over a
|
|
59
66
|
* per-folder item collection the way `SyncCommand` will be).
|
|
60
67
|
*
|
|
68
|
+
* **Keys**: an unknown `SyncKey` is answered with Status 9 ([MS-ASCMD] FolderSync: "synchronization key mismatch or
|
|
69
|
+
* invalid synchronization key"; the client restarts from `0`). The key issued before the current one is still
|
|
70
|
+
* accepted: the key itself encodes its cursor, so a retry simply recomputes that round's changes.
|
|
71
|
+
*
|
|
61
72
|
* `folderClass` is supplied by the Mongo/SQL concrete subclasses, following the exact one-line-per-backend
|
|
62
73
|
* pattern used throughout this library's other routes/jobs.
|
|
63
74
|
*
|
|
@@ -79,31 +90,64 @@ export class FolderSyncCommand {
|
|
|
79
90
|
throw new ApiError(ApiErrors.INTERNAL_ERROR, 500, ApiErrorMessages.INTERNAL_ERROR);
|
|
80
91
|
}
|
|
81
92
|
const clientSyncKey = ctx.request ? childText(ctx.request, "SyncKey") : undefined;
|
|
82
|
-
const storedSyncKey = ctx.deviceSyncState.folderSyncKeys[FOLDER_HIERARCHY_CURSOR_KEY];
|
|
83
|
-
const
|
|
93
|
+
const storedSyncKey = ctx.deviceSyncState.folderSyncKeys?.[FOLDER_HIERARCHY_CURSOR_KEY];
|
|
94
|
+
const previousSyncKey = ctx.deviceSyncState.folderSyncKeys?.[FOLDER_HIERARCHY_PREVIOUS_KEY];
|
|
95
|
+
let resolution = resolveSyncKey(clientSyncKey, storedSyncKey);
|
|
96
|
+
let retry = false;
|
|
97
|
+
if (resolution.kind === "invalid" && previousSyncKey !== undefined) {
|
|
98
|
+
resolution = resolveSyncKey(clientSyncKey, previousSyncKey);
|
|
99
|
+
retry = resolution.kind === "valid";
|
|
100
|
+
}
|
|
84
101
|
if (resolution.kind === "invalid") {
|
|
85
102
|
return element(WbxmlCodePage.FolderHierarchy, "FolderSync", [
|
|
86
|
-
textElement(WbxmlCodePage.FolderHierarchy, "Status",
|
|
103
|
+
textElement(WbxmlCodePage.FolderHierarchy, "Status", STATUS_INVALID_SYNC_KEY),
|
|
87
104
|
]);
|
|
88
105
|
}
|
|
106
|
+
let generation;
|
|
107
|
+
let adds;
|
|
108
|
+
let updates = [];
|
|
109
|
+
let deletes = [];
|
|
110
|
+
let cursor;
|
|
89
111
|
if (resolution.kind === "initial") {
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
// [MS-ASCMD] FolderSync: a SyncKey "0" request returns the entire folder hierarchy as Adds together
|
|
113
|
+
// with the new key - there is no separate "empty handshake" round (unlike Sync), and FolderSync has no
|
|
114
|
+
// MoreAvailable, so every page is read here rather than leaving the rest for a later round (where a
|
|
115
|
+
// folder created before that round's cursor would wrongly be reported as an Update).
|
|
116
|
+
generation = 0;
|
|
117
|
+
adds = [];
|
|
118
|
+
cursor = epochCursor();
|
|
119
|
+
for (let page = 0; page < MAX_INITIAL_PAGES; page++) {
|
|
120
|
+
const changes = await computeChanges(this.folderRepo, "mailboxUid", ctx.mailboxUid, cursor, this.windowSize);
|
|
121
|
+
adds.push(...changes.adds, ...changes.changes);
|
|
122
|
+
cursor = changes.cursor;
|
|
123
|
+
if (!changes.moreAvailable) {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
generation = resolution.key.generation;
|
|
130
|
+
const changes = await computeChanges(this.folderRepo, "mailboxUid", ctx.mailboxUid, { date: resolution.key.watermark, uid: resolution.key.uid ?? "" }, this.windowSize);
|
|
131
|
+
adds = changes.adds;
|
|
132
|
+
updates = changes.changes;
|
|
133
|
+
deletes = changes.deletes;
|
|
134
|
+
cursor = changes.cursor;
|
|
102
135
|
}
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
136
|
+
const newKey = formatSyncKey({ generation: generation + 1, watermark: cursor.date, uid: cursor.uid });
|
|
137
|
+
// A retry keeps the previous key as it was; a normal round makes the key just consumed the previous one; a
|
|
138
|
+
// restart forgets it.
|
|
139
|
+
const previous = retry ? previousSyncKey : resolution.kind === "valid" ? clientSyncKey : undefined;
|
|
140
|
+
await persistDeviceSyncState(ctx.deviceSyncState, ctx.deviceSyncStateRepo, (current) => {
|
|
141
|
+
const { [FOLDER_HIERARCHY_PREVIOUS_KEY]: _dropped, ...rest } = current.folderSyncKeys ?? {};
|
|
142
|
+
return {
|
|
143
|
+
folderSyncKeys: {
|
|
144
|
+
...rest,
|
|
145
|
+
[FOLDER_HIERARCHY_CURSOR_KEY]: newKey,
|
|
146
|
+
...(previous !== undefined ? { [FOLDER_HIERARCHY_PREVIOUS_KEY]: previous } : {}),
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
const totalChanges = adds.length + updates.length + deletes.length;
|
|
107
151
|
if (totalChanges === 0) {
|
|
108
152
|
return element(WbxmlCodePage.FolderHierarchy, "FolderSync", [
|
|
109
153
|
textElement(WbxmlCodePage.FolderHierarchy, "Status", "1"),
|
|
@@ -111,9 +155,9 @@ export class FolderSyncCommand {
|
|
|
111
155
|
]);
|
|
112
156
|
}
|
|
113
157
|
const changeElements = [
|
|
114
|
-
...
|
|
115
|
-
...
|
|
116
|
-
...
|
|
158
|
+
...adds.map((folder) => this.folderToChangeElement("Add", folder)),
|
|
159
|
+
...updates.map((folder) => this.folderToChangeElement("Update", folder)),
|
|
160
|
+
...deletes.map((folder) => element(WbxmlCodePage.FolderHierarchy, "Delete", [
|
|
117
161
|
textElement(WbxmlCodePage.FolderHierarchy, "ServerId", folder.uid),
|
|
118
162
|
])),
|
|
119
163
|
];
|
|
@@ -137,10 +181,6 @@ export class FolderSyncCommand {
|
|
|
137
181
|
textElement(WbxmlCodePage.FolderHierarchy, "Type", FOLDER_TYPE_CODES[folder.type] ?? FOLDER_TYPE_CODES[FolderType.USER]),
|
|
138
182
|
]);
|
|
139
183
|
}
|
|
140
|
-
async persistSyncKey(ctx, newKey) {
|
|
141
|
-
const folderSyncKeys = { ...ctx.deviceSyncState.folderSyncKeys, [FOLDER_HIERARCHY_CURSOR_KEY]: newKey };
|
|
142
|
-
await persistDeviceSyncState(ctx.deviceSyncState, ctx.deviceSyncStateRepo, { folderSyncKeys });
|
|
143
|
-
}
|
|
144
184
|
}
|
|
145
185
|
__decorate([
|
|
146
186
|
Config("mail:eas:foldersync_window_size", DEFAULT_WINDOW_SIZE),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FolderSyncCommand.js","sourceRoot":"","sources":["../../../src/commands/FolderSyncCommand.ts"],"names":[],"mappings":";;;;;;;;;AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAiB,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAChG,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAqB,MAAM,0BAA0B,CAAC;AAC9F,OAAO,
|
|
1
|
+
{"version":3,"file":"FolderSyncCommand.js","sourceRoot":"","sources":["../../../src/commands/FolderSyncCommand.ts"],"names":[],"mappings":"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"}
|