@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
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import addressparser from "nodemailer/lib/addressparser";
|
|
6
|
+
/**
|
|
7
|
+
* Raw RFC 5322 header-block helpers for the MIME a device composes (`ComposeMailCommand`).
|
|
8
|
+
*
|
|
9
|
+
* **Inline copy of restapi** (`util/MimeHeaderUtils.ts` in restapi's source, not exported by the `@rapidmx/restapi`
|
|
10
|
+
* 0.9.0 this plugin builds against): `extractOriginatorHeaders()`, `hasAddressLikeDisplayName()`,
|
|
11
|
+
* `checkOriginatorHeaders()`, `isPlainAddress()` and `safeDisplayName()` with their private helpers, kept identical - replace them with restapi's exports once the
|
|
12
|
+
* dependency is bumped.
|
|
13
|
+
*
|
|
14
|
+
* **Plugin-side** (`checkComposedOriginators()`, `stripHeader()`): a second, byte-preserving lexer
|
|
15
|
+
* (`lexHeaderFields()`) that is at least as eager as the parser reading the message later (mailparser/mailsplit):
|
|
16
|
+
* the header block ends at the first empty line (`CRLF CRLF` or `LF LF`); CRLF, LF and a bare CR all end a physical
|
|
17
|
+
* line; a line starting with a space or tab continues the previous field; and a field's name is everything before
|
|
18
|
+
* its first colon with surrounding whitespace ignored - mailsplit trims names the same way, so a leading space on the
|
|
19
|
+
* very first line or a form feed before the colon still names a `From`.
|
|
20
|
+
*/
|
|
21
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
22
|
+
// Inline copy of restapi's util/MimeHeaderUtils.ts
|
|
23
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Splits a raw RFC 5322 message into its header block and body, at the first blank line. Decodes/encodes via
|
|
26
|
+
* the `binary` (latin1) encoding so every byte round-trips exactly.
|
|
27
|
+
*/
|
|
28
|
+
function splitRawIntoHeaderAndBody(raw) {
|
|
29
|
+
const text = raw.toString("binary");
|
|
30
|
+
const match = text.match(/\r\n\r\n|\n\n/);
|
|
31
|
+
if (!match || match.index === undefined) {
|
|
32
|
+
return { headerText: text, bodyText: "" };
|
|
33
|
+
}
|
|
34
|
+
return { headerText: text.slice(0, match.index), bodyText: text.slice(match.index + match[0].length) };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Strips quoted strings and (nested) comments out of a structured header value, so what is left is only the part a
|
|
38
|
+
* mail client would interpret as address syntax. An unterminated quote/comment swallows the rest of the value.
|
|
39
|
+
*/
|
|
40
|
+
function stripQuotedStringsAndComments(value) {
|
|
41
|
+
let result = "";
|
|
42
|
+
let inQuote = false;
|
|
43
|
+
let commentDepth = 0;
|
|
44
|
+
for (let i = 0; i < value.length; i++) {
|
|
45
|
+
const ch = value[i];
|
|
46
|
+
if (ch === "\\" && (inQuote || commentDepth > 0)) {
|
|
47
|
+
i++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (inQuote) {
|
|
51
|
+
if (ch === '"') {
|
|
52
|
+
inQuote = false;
|
|
53
|
+
result += " ";
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (ch === "(") {
|
|
58
|
+
commentDepth++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (commentDepth > 0) {
|
|
62
|
+
if (ch === ")") {
|
|
63
|
+
commentDepth--;
|
|
64
|
+
if (commentDepth === 0) {
|
|
65
|
+
result += " ";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (ch === '"') {
|
|
71
|
+
inQuote = true;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
result += ch;
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Collects the text of every quoted string and comment in a structured header value (the counterpart of
|
|
80
|
+
* `stripQuotedStringsAndComments()`) - where a display name or comment shows the reader text of the sender's choice.
|
|
81
|
+
*/
|
|
82
|
+
function quotedStringsAndComments(value) {
|
|
83
|
+
const parts = [];
|
|
84
|
+
let current = "";
|
|
85
|
+
let inQuote = false;
|
|
86
|
+
let commentDepth = 0;
|
|
87
|
+
for (let i = 0; i < value.length; i++) {
|
|
88
|
+
const ch = value[i];
|
|
89
|
+
if (ch === "\\" && (inQuote || commentDepth > 0)) {
|
|
90
|
+
current += value[i + 1] ?? "";
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (inQuote) {
|
|
95
|
+
if (ch === '"') {
|
|
96
|
+
inQuote = false;
|
|
97
|
+
parts.push(current);
|
|
98
|
+
current = "";
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
current += ch;
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (ch === "(") {
|
|
106
|
+
commentDepth++;
|
|
107
|
+
current += commentDepth > 1 ? ch : "";
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (commentDepth > 0) {
|
|
111
|
+
if (ch === ")") {
|
|
112
|
+
commentDepth--;
|
|
113
|
+
if (commentDepth === 0) {
|
|
114
|
+
parts.push(current);
|
|
115
|
+
current = "";
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
current += ch;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (ch === '"') {
|
|
123
|
+
inQuote = true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (inQuote || commentDepth > 0) {
|
|
127
|
+
parts.push(current);
|
|
128
|
+
}
|
|
129
|
+
return parts;
|
|
130
|
+
}
|
|
131
|
+
/** The top-level header block unfolded into logical lines, with CRLF, LF *and* a bare CR all treated as line breaks -
|
|
132
|
+
* the tolerant lexing every originator-header check here shares, so a header can't hide behind a line ending some
|
|
133
|
+
* other parser would honor. Continuation lines are joined with a single space. */
|
|
134
|
+
function lexLogicalHeaderLines(raw) {
|
|
135
|
+
const { headerText } = splitRawIntoHeaderAndBody(raw);
|
|
136
|
+
const logical = [];
|
|
137
|
+
for (const line of headerText.split(/\r\n|\n|\r/)) {
|
|
138
|
+
if (/^[ \t]/.test(line) && logical.length > 0) {
|
|
139
|
+
logical[logical.length - 1] += " " + line.trim();
|
|
140
|
+
}
|
|
141
|
+
else if (line.length > 0) {
|
|
142
|
+
logical.push(line);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return logical;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Every top-level `From` and `Sender` header value of `raw`, found exactly the way `checkOriginatorHeaders()` finds
|
|
149
|
+
* them: header names case-insensitive, whitespace before the colon allowed (the obsolete `From :` form), folded
|
|
150
|
+
* values unfolded, and a bare CR treated as a line break.
|
|
151
|
+
*/
|
|
152
|
+
export function extractOriginatorHeaders(raw) {
|
|
153
|
+
const values = { from: [], sender: [] };
|
|
154
|
+
for (const line of lexLogicalHeaderLines(raw)) {
|
|
155
|
+
const match = line.match(/^(from|sender)[ \t]*:(.*)$/i);
|
|
156
|
+
if (match) {
|
|
157
|
+
values[match[1].toLowerCase()].push(match[2].trim());
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return values;
|
|
161
|
+
}
|
|
162
|
+
/** Decodes RFC 2047 encoded words (B and Q) to UTF-8 text - enough to see what a display name shows the reader. */
|
|
163
|
+
function decodeEncodedWords(text) {
|
|
164
|
+
return text.replace(/=\?[^?]+\?([bBqQ])\?([^?]*)\?=/g, (_match, encoding, data) => {
|
|
165
|
+
if (encoding.toUpperCase() === "B") {
|
|
166
|
+
return Buffer.from(data, "base64").toString("utf8");
|
|
167
|
+
}
|
|
168
|
+
const bytes = Buffer.from(data.replace(/_/g, " ").replace(/=([0-9A-Fa-f]{2})/g, (_m, hex) => String.fromCharCode(parseInt(hex, 16))), "binary");
|
|
169
|
+
return bytes.toString("utf8");
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/** An `@`, or a look-alike a reader would take for one (fullwidth, small and other compatibility forms). */
|
|
173
|
+
const AT_SIGN_LIKE = /[@@﹫]/;
|
|
174
|
+
/** Whether `text` (a raw header fragment, read as latin1 bytes) shows an address-like `@` once decoded. */
|
|
175
|
+
function showsAtSign(text) {
|
|
176
|
+
const asUtf8 = Buffer.from(text, "binary").toString("utf8");
|
|
177
|
+
return AT_SIGN_LIKE.test(decodeEncodedWords(asUtf8)) || AT_SIGN_LIKE.test(decodeEncodedWords(text));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Whether any display name, group name or comment in one `From`/`Sender` header value contains an address-like `@`
|
|
181
|
+
* (RFC 2047 encoded words decoded, look-alike `@` characters included): `"ceo@example.com" <me@example.com>` shows
|
|
182
|
+
* the reader an address the sender doesn't own, though its real address is fine.
|
|
183
|
+
*/
|
|
184
|
+
export function hasAddressLikeDisplayName(value) {
|
|
185
|
+
if (quotedStringsAndComments(value).some(showsAtSign)) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
const visit = (entries) => entries.some((entry) => (typeof entry.name === "string" && showsAtSign(entry.name)) || (Array.isArray(entry.group) && visit(entry.group)));
|
|
189
|
+
return visit(addressparser(value));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Refuses a raw RFC 5322 message whose originator headers name anyone other than an allowed sender. Every `From`
|
|
193
|
+
* and `Sender` header in the top-level header block is checked - header names case-insensitively (including the
|
|
194
|
+
* obsolete `From :` form with whitespace before the colon), folded values unfolded, and a bare CR treated as a line
|
|
195
|
+
* break too, so a header can't be hidden from this scan behind a line ending another parser would honor. Returns a
|
|
196
|
+
* refusal reason, or `undefined` if the message passes. Fails closed on:
|
|
197
|
+
* - no `From` header, more than one `From` header, or more than one `Sender` header;
|
|
198
|
+
* - a `From`/`Sender` value that yields no address at all (e.g. only an empty group);
|
|
199
|
+
* - any parsed entry without an address (a malformed list, e.g. an unquoted display name containing a comma);
|
|
200
|
+
* - any parsed address - group members included - that `isAllowed` rejects;
|
|
201
|
+
* - any addr-spec-looking token outside quoted strings/comments that `isAllowed` rejects - covers the tolerant
|
|
202
|
+
* parser recovering `<me@example.com> <other@example.com>` as one mailbox with the second as its "display name";
|
|
203
|
+
* - with `options.rejectAddressLikeDisplayNames`, any display name, group name or comment containing an address.
|
|
204
|
+
*
|
|
205
|
+
* Quoted display names and RFC 2047 encoded words are never treated as addresses. `isAllowed` receives each address
|
|
206
|
+
* exactly as parsed; normalize (e.g. lowercase) inside it.
|
|
207
|
+
*/
|
|
208
|
+
export function checkOriginatorHeaders(raw, isAllowed, options = {}) {
|
|
209
|
+
const values = extractOriginatorHeaders(raw);
|
|
210
|
+
if (values.from.length === 0) {
|
|
211
|
+
return "The message has no From header.";
|
|
212
|
+
}
|
|
213
|
+
if (values.from.length > 1) {
|
|
214
|
+
return "The message has more than one From header.";
|
|
215
|
+
}
|
|
216
|
+
if (values.sender.length > 1) {
|
|
217
|
+
return "The message has more than one Sender header.";
|
|
218
|
+
}
|
|
219
|
+
const refusal = (name) => `The ${name} header names an address that is not one of the sending mailbox's own addresses.`;
|
|
220
|
+
for (const [name, headerValues] of [
|
|
221
|
+
["From", values.from],
|
|
222
|
+
["Sender", values.sender],
|
|
223
|
+
]) {
|
|
224
|
+
for (const value of headerValues) {
|
|
225
|
+
const parsed = addressparser(value, { flatten: true });
|
|
226
|
+
if (parsed.length === 0) {
|
|
227
|
+
return `The ${name} header contains no address.`;
|
|
228
|
+
}
|
|
229
|
+
if (parsed.some((entry) => !entry.address || !isAllowed(entry.address))) {
|
|
230
|
+
return refusal(name);
|
|
231
|
+
}
|
|
232
|
+
const tokens = stripQuotedStringsAndComments(value)
|
|
233
|
+
.split(/[\s<>,;:]+/)
|
|
234
|
+
.filter((token) => token.includes("@"));
|
|
235
|
+
if (tokens.some((token) => !isAllowed(token))) {
|
|
236
|
+
return refusal(name);
|
|
237
|
+
}
|
|
238
|
+
if (options.rejectAddressLikeDisplayNames && hasAddressLikeDisplayName(value)) {
|
|
239
|
+
return `The ${name} header's display name or comment contains an address.`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
/** One plain address: no display name, angle brackets, group, comment, list, quoting, control characters or whitespace. */
|
|
246
|
+
const PLAIN_ADDRESS_PATTERN = /^[^\s()<>@,;:\\"[\]]+@[^\s()<>@,;:\\"[\]]+$/;
|
|
247
|
+
/** RFC 5321's address length limit. */
|
|
248
|
+
const MAX_PLAIN_ADDRESS_LENGTH = 320;
|
|
249
|
+
/** Whether `value` holds a control character (C0 or DEL); a tab only counts when `tabCounts`. */
|
|
250
|
+
function hasControlCharacter(value, tabCounts) {
|
|
251
|
+
for (let i = 0; i < value.length; i++) {
|
|
252
|
+
const code = value.charCodeAt(i);
|
|
253
|
+
if ((code < 0x20 && (tabCounts || code !== 0x09)) || code === 0x7f) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
/** Whether `address` is exactly one plain address (`local@domain`, nothing around it), at most 320 characters - safe to
|
|
260
|
+
* hand to a composer as one recipient, e.g. a meeting attendee or organizer. */
|
|
261
|
+
export function isPlainAddress(address) {
|
|
262
|
+
return (typeof address === "string" &&
|
|
263
|
+
address.length <= MAX_PLAIN_ADDRESS_LENGTH &&
|
|
264
|
+
!hasControlCharacter(address, true) &&
|
|
265
|
+
PLAIN_ADDRESS_PATTERN.test(address));
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* `name` as a display name that's safe to put in front of one of our own addresses in a `From` (or an iCalendar `CN`)
|
|
269
|
+
* this server composes: trimmed, or `undefined` - so the caller omits the name - when it isn't a string, is blank,
|
|
270
|
+
* contains a line break or other control character, or shows an address-like `@` (look-alikes and RFC 2047 encoded
|
|
271
|
+
* words included, the same rule as `hasAddressLikeDisplayName()`). A display name like `ceo@example.com` in front of a
|
|
272
|
+
* real address shows the reader an address the sender doesn't own.
|
|
273
|
+
*/
|
|
274
|
+
export function safeDisplayName(name) {
|
|
275
|
+
if (typeof name !== "string" || hasControlCharacter(name, false)) {
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
const clean = name.trim();
|
|
279
|
+
if (clean.length === 0 || AT_SIGN_LIKE.test(clean) || AT_SIGN_LIKE.test(decodeEncodedWords(clean))) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
return clean;
|
|
283
|
+
}
|
|
284
|
+
/** Every top-level header field of `raw`, in order, with byte offsets - see this module's doc comment. */
|
|
285
|
+
function lexHeaderFields(raw) {
|
|
286
|
+
const text = raw.toString("latin1");
|
|
287
|
+
const separator = /\r\n\r\n|\n\n/.exec(text);
|
|
288
|
+
// The header block keeps the terminator of its last line; the empty line itself belongs to the separator.
|
|
289
|
+
const headerEnd = separator ? separator.index + (separator[0] === "\n\n" ? 1 : 2) : text.length;
|
|
290
|
+
const fields = [];
|
|
291
|
+
const lineRegex = /([^\r\n]*)(\r\n|\n|\r|$)/y;
|
|
292
|
+
let position = 0;
|
|
293
|
+
while (position < headerEnd) {
|
|
294
|
+
lineRegex.lastIndex = position;
|
|
295
|
+
const match = lineRegex.exec(text);
|
|
296
|
+
const content = match[1];
|
|
297
|
+
const lineEnd = position + match[0].length;
|
|
298
|
+
const current = fields[fields.length - 1];
|
|
299
|
+
if (/^[ \t]/.test(content) && current) {
|
|
300
|
+
current.end = lineEnd;
|
|
301
|
+
}
|
|
302
|
+
else if (content.length > 0) {
|
|
303
|
+
const colon = content.indexOf(":");
|
|
304
|
+
fields.push({ name: colon === -1 ? "" : content.slice(0, colon).trim().toLowerCase(), start: position, end: lineEnd });
|
|
305
|
+
}
|
|
306
|
+
position = lineEnd;
|
|
307
|
+
}
|
|
308
|
+
return { text, fields };
|
|
309
|
+
}
|
|
310
|
+
/** An addr-spec-looking run inside display text: no whitespace, brackets, quotes, separators or second `@`. */
|
|
311
|
+
const DISPLAY_ADDRESS_TOKEN = /[^\s@<>()[\]",;:\\]+@[^\s@<>()[\]",;:\\]+/g;
|
|
312
|
+
/**
|
|
313
|
+
* Whether every address shown in one `From`/`Sender` value's display names, group names and comments is one
|
|
314
|
+
* `isAllowed` accepts - the case `hasAddressLikeDisplayName()` would refuse although it names only the sender itself
|
|
315
|
+
* (`"me@example.com" <me@example.com>`, as clients do when the display name is the address, e.g. autodiscover's
|
|
316
|
+
* `DisplayName`). Each text is read the way `hasAddressLikeDisplayName()` reads it (as UTF-8 and as latin1, RFC 2047
|
|
317
|
+
* encoded words decoded). A look-alike `@` (fullwidth or small) is never accepted, and neither is an `@` left over once
|
|
318
|
+
* every allowed address is removed (`a@b@c`, a lone encoded `@`, an address split by a quote).
|
|
319
|
+
*/
|
|
320
|
+
function displayTextShowsOnlyAllowedAddresses(value, isAllowed) {
|
|
321
|
+
const texts = [...quotedStringsAndComments(value)];
|
|
322
|
+
const visit = (entries) => {
|
|
323
|
+
for (const entry of entries) {
|
|
324
|
+
if (typeof entry.name === "string") {
|
|
325
|
+
texts.push(entry.name);
|
|
326
|
+
}
|
|
327
|
+
if (Array.isArray(entry.group)) {
|
|
328
|
+
visit(entry.group);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
visit(addressparser(value));
|
|
333
|
+
return texts.every((text) => [decodeEncodedWords(Buffer.from(text, "binary").toString("utf8")), decodeEncodedWords(text)].every((view) => {
|
|
334
|
+
if (/[@﹫]/.test(view)) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
const remainder = view.replace(DISPLAY_ADDRESS_TOKEN, (token) => (isAllowed(token) ? " " : "@"));
|
|
338
|
+
return !remainder.includes("@");
|
|
339
|
+
}));
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* The ActiveSync compose sender check: restapi's `checkOriginatorHeaders()` with `rejectAddressLikeDisplayNames`, plus
|
|
343
|
+
* two plugin-side refusals restapi doesn't make. Returns a refusal reason, or `undefined` if the message passes.
|
|
344
|
+
* - **Relaxed for the sender's own address**: a display name or comment that shows an address is still accepted when
|
|
345
|
+
* every address it shows is one of the mailbox's own (`"me@example.com" <me@example.com>`) - see
|
|
346
|
+
* `displayTextShowsOnlyAllowedAddresses()`. Any other address, or a look-alike `@`, is refused as restapi refuses it.
|
|
347
|
+
* - **A `From`/`Sender` field restapi's lexer can't see** - the tolerant `lexHeaderFields()` counts more of them (e.g.
|
|
348
|
+
* a leading space on the first line, or a form feed before the colon, both of which mailsplit still reads as `From`).
|
|
349
|
+
* - **An empty group** (`victims:;, me@example.com`) - it contributes no address, only text of the sender's choice
|
|
350
|
+
* shown beside the real address, and RFC 5322 doesn't allow groups in `From`/`Sender` at all.
|
|
351
|
+
*/
|
|
352
|
+
export function checkComposedOriginators(raw, isAllowed) {
|
|
353
|
+
const refusal = checkOriginatorHeaders(raw, isAllowed);
|
|
354
|
+
if (refusal !== undefined) {
|
|
355
|
+
return refusal;
|
|
356
|
+
}
|
|
357
|
+
const exact = extractOriginatorHeaders(raw);
|
|
358
|
+
for (const [name, values] of [
|
|
359
|
+
["From", exact.from],
|
|
360
|
+
["Sender", exact.sender],
|
|
361
|
+
]) {
|
|
362
|
+
if (values.some((value) => hasAddressLikeDisplayName(value) && !displayTextShowsOnlyAllowedAddresses(value, isAllowed))) {
|
|
363
|
+
return `The ${name} header's display name or comment contains an address.`;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const fields = lexHeaderFields(raw).fields;
|
|
367
|
+
const count = (name) => fields.filter((field) => field.name === name).length;
|
|
368
|
+
if (count("from") !== exact.from.length || count("sender") !== exact.sender.length) {
|
|
369
|
+
return "The message has a From or Sender header in a form this server doesn't accept.";
|
|
370
|
+
}
|
|
371
|
+
const hasEmptyGroup = (value) => addressparser(value).some((entry) => Array.isArray(entry.group) && entry.group.length === 0);
|
|
372
|
+
if ([...exact.from, ...exact.sender].some(hasEmptyGroup)) {
|
|
373
|
+
return "The From or Sender header contains an empty group.";
|
|
374
|
+
}
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Returns a copy of `raw` with every top-level header field named `name` (case-insensitive, whitespace before the
|
|
379
|
+
* colon allowed, including its folded continuation lines) removed, found with `lexHeaderFields()` - so a field
|
|
380
|
+
* written `Bcc :` is removed too. Only the header block is touched; every other byte is copied verbatim.
|
|
381
|
+
*/
|
|
382
|
+
export function stripHeader(raw, name) {
|
|
383
|
+
const target = name.toLowerCase();
|
|
384
|
+
const { text, fields } = lexHeaderFields(raw);
|
|
385
|
+
let result = "";
|
|
386
|
+
let copiedUpTo = 0;
|
|
387
|
+
for (const field of fields) {
|
|
388
|
+
if (field.name === target) {
|
|
389
|
+
result += text.slice(copiedUpTo, field.start);
|
|
390
|
+
copiedUpTo = field.end;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return Buffer.from(result + text.slice(copiedUpTo), "latin1");
|
|
394
|
+
}
|
|
395
|
+
//# sourceMappingURL=MimeHeaderUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MimeHeaderUtils.js","sourceRoot":"","sources":["../../src/MimeHeaderUtils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { StringUtils } from "@rapidrest/core";
|
|
6
|
+
/**
|
|
7
|
+
* Mirrors `@rapidrest/service-core`'s private `ModelUtils.MAX_PATTERN_LENGTH`: a `regex(...)` query operand
|
|
8
|
+
* longer than this is rejected as a potential ReDoS pattern with `INVALID_REQUEST`. The check runs against the
|
|
9
|
+
* operand AFTER escaping, so a raw client term well under the limit can still exceed it once each regex
|
|
10
|
+
* metacharacter doubles in length.
|
|
11
|
+
*/
|
|
12
|
+
export const MAX_REGEX_PATTERN_LENGTH = 100;
|
|
13
|
+
/**
|
|
14
|
+
* Escapes `value` for a literal-substring `regex(...)` match, truncating the raw term (never mid-escape or
|
|
15
|
+
* mid-code-point) so the escaped result always fits `MAX_REGEX_PATTERN_LENGTH`. Matching on a prefix of an
|
|
16
|
+
* over-long search term still yields a superset of the full term's matches, which is a far better outcome than
|
|
17
|
+
* the whole command failing.
|
|
18
|
+
*/
|
|
19
|
+
export function boundedEscapedPattern(value, maxLength = MAX_REGEX_PATTERN_LENGTH) {
|
|
20
|
+
let pattern = "";
|
|
21
|
+
for (const char of value) {
|
|
22
|
+
const escaped = StringUtils.escapeRegExp(char);
|
|
23
|
+
if (pattern.length + escaped.length > maxLength) {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
pattern += escaped;
|
|
27
|
+
}
|
|
28
|
+
return pattern;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=RegexPatternUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegexPatternUtils.js","sourceRoot":"","sources":["../../src/RegexPatternUtils.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa,EAAE,YAAoB,wBAAwB;IAC7F,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;YAC9C,MAAM;QACV,CAAC;QACD,OAAO,IAAI,OAAO,CAAC;IACvB,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
|
+
import { BaseEntity } from "@rapidrest/service-core";
|
|
7
|
+
/**
|
|
8
|
+
* Inline copies of small restapi helpers that the `@rapidmx/restapi` 0.9.0 this plugin builds against doesn't export
|
|
9
|
+
* yet. Each matches restapi's source exactly - replace the copy with restapi's export once the dependency is bumped.
|
|
10
|
+
*/
|
|
11
|
+
/** The longest value `boundIndexedValue()` stores verbatim (restapi `util/ConversationUtils.ts`). */
|
|
12
|
+
export const MAX_INDEXED_VALUE_LENGTH = 255;
|
|
13
|
+
/**
|
|
14
|
+
* Returns `value` unchanged when it's at most `MAX_INDEXED_VALUE_LENGTH` characters, otherwise
|
|
15
|
+
* `sha256:<64 hex digits>` of its UTF-8 bytes - how restapi stores indexed identifiers from untrusted mail/calendar
|
|
16
|
+
* data (`Message.messageId`, `Message.conversationId`, `CalendarEvent.icalUid`). Idempotent, so a lookup value passed
|
|
17
|
+
* through it matches the stored one. Copy of restapi's `boundIndexedValue()` (`util/ConversationUtils.ts`).
|
|
18
|
+
*/
|
|
19
|
+
export function boundIndexedValue(value) {
|
|
20
|
+
if (typeof value !== "string" || value.length <= MAX_INDEXED_VALUE_LENGTH) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
return `sha256:${createHash("sha256").update(value, "utf8").digest("hex")}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `row` as an instance of `repo`'s model class, for use as the `existing` argument of `RepoUtils.update()`.
|
|
27
|
+
*
|
|
28
|
+
* `update()` only enforces its optimistic lock (a version mismatch -> 409, a version-filtered write, and the
|
|
29
|
+
* `version`/`dateModified` bump) when `existing instanceof BaseEntity`. The Mongo backend's `find()`/`findOne()` return
|
|
30
|
+
* plain documents, so passing one straight through silently turns a version-checked write into an unconditional
|
|
31
|
+
* overwrite that other devices' change streams never see. SQL reads already return entity instances, which pass
|
|
32
|
+
* through as-is. Copy of restapi's `asEntity()` (`util/EntityUtils.ts`).
|
|
33
|
+
*/
|
|
34
|
+
export function asEntity(repo, row) {
|
|
35
|
+
if (row instanceof BaseEntity) {
|
|
36
|
+
return row;
|
|
37
|
+
}
|
|
38
|
+
const modelClass = repo.modelClass;
|
|
39
|
+
return modelClass ? new modelClass(row) : row;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=RestapiCompat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RestapiCompat.js","sourceRoot":"","sources":["../../src/RestapiCompat.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,UAAU,EAAkB,MAAM,yBAAyB,CAAC;AAErE;;;GAGG;AAEH,qGAAqG;AACrG,MAAM,CAAC,MAAM,wBAAwB,GAAW,GAAG,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAsC,KAAQ;IAC3E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,IAAI,wBAAwB,EAAE,CAAC;QACxE,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAO,CAAC;AACrF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAI,IAAoB,EAAE,GAAM;IACpD,IAAI,GAAG,YAAY,UAAU,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC;IACf,CAAC;IACD,MAAM,UAAU,GAAS,IAAY,CAAC,UAAU,CAAC;IACjD,OAAO,UAAU,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAClD,CAAC"}
|