@aaronsb/google-workspace-mcp 3.0.0 → 4.0.0
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 +201 -21
- package/LICENSE-MIT +21 -0
- package/NOTICE +24 -0
- package/README.md +71 -52
- package/build/accounts/auth.d.ts +1 -1
- package/build/accounts/auth.js +1 -1
- package/build/accounts/oauth.js +3 -3
- package/build/accounts/oauth.js.map +1 -1
- package/build/accounts/token-service.js +1 -1
- package/build/accounts/token-service.js.map +1 -1
- package/build/coverage/analyze.d.ts +1 -1
- package/build/coverage/analyze.js +6 -4
- package/build/coverage/analyze.js.map +1 -1
- package/build/coverage/baseline.js +37 -20
- package/build/coverage/baseline.js.map +1 -1
- package/build/coverage/compare.d.ts +1 -1
- package/build/coverage/compare.js +19 -10
- package/build/coverage/compare.js.map +1 -1
- package/build/coverage/discover.d.ts +5 -5
- package/build/coverage/discover.js +71 -155
- package/build/coverage/discover.js.map +1 -1
- package/build/coverage/report.js +3 -3
- package/build/coverage/report.js.map +1 -1
- package/build/coverage/types.d.ts +19 -4
- package/build/executor/index.d.ts +0 -3
- package/build/executor/index.js +1 -2
- package/build/executor/index.js.map +1 -1
- package/build/factory/defaults.js +30 -4
- package/build/factory/defaults.js.map +1 -1
- package/build/factory/generator.d.ts +10 -2
- package/build/factory/generator.js +54 -69
- package/build/factory/generator.js.map +1 -1
- package/build/factory/manifest/README.md +4 -3
- package/build/factory/manifest/calendar.yaml +1 -11
- package/build/factory/manifest/docs.yaml +15 -6
- package/build/factory/manifest/drive.yaml +1 -5
- package/build/factory/manifest/gmail.yaml +9 -24
- package/build/factory/manifest/meet.yaml +1 -1
- package/build/factory/manifest/sheets.yaml +2 -5
- package/build/factory/manifest/tasks.yaml +40 -2
- package/build/factory/patches.js +2 -0
- package/build/factory/patches.js.map +1 -1
- package/build/factory/safety.js +4 -4
- package/build/factory/safety.js.map +1 -1
- package/build/factory/types.d.ts +16 -15
- package/build/google/client.d.ts +45 -0
- package/build/google/client.js +232 -0
- package/build/google/client.js.map +1 -0
- package/build/google/descriptor.d.ts +40 -0
- package/build/google/descriptor.js +37 -0
- package/build/google/descriptor.js.map +1 -0
- package/build/google/descriptor.json +5271 -0
- package/build/google/errors.d.ts +45 -0
- package/build/google/errors.js +44 -0
- package/build/google/errors.js.map +1 -0
- package/build/google/methods.d.ts +37 -0
- package/build/google/methods.js +13 -0
- package/build/google/methods.js.map +1 -0
- package/build/index.js +3 -3
- package/build/index.js.map +1 -1
- package/build/node-floor.js +1 -1
- package/build/node-floor.js.map +1 -1
- package/build/server/formatting/markdown.d.ts +1 -1
- package/build/server/formatting/markdown.js +1 -1
- package/build/server/formatting/next-steps.js +1 -1
- package/build/server/formatting/next-steps.js.map +1 -1
- package/build/server/handlers/accounts.js +2 -2
- package/build/server/handlers/accounts.js.map +1 -1
- package/build/server/scratchpad/adapters/import-doc.js +15 -13
- package/build/server/scratchpad/adapters/import-doc.js.map +1 -1
- package/build/server/scratchpad/adapters/import-drive.js +16 -11
- package/build/server/scratchpad/adapters/import-drive.js.map +1 -1
- package/build/server/scratchpad/adapters/import-email.js +3 -15
- package/build/server/scratchpad/adapters/import-email.js.map +1 -1
- package/build/server/scratchpad/adapters/import-meet.js +5 -17
- package/build/server/scratchpad/adapters/import-meet.js.map +1 -1
- package/build/server/scratchpad/adapters/import-sheet.js +5 -6
- package/build/server/scratchpad/adapters/import-sheet.js.map +1 -1
- package/build/server/scratchpad/adapters/send-calendar.js +18 -13
- package/build/server/scratchpad/adapters/send-calendar.js.map +1 -1
- package/build/server/scratchpad/adapters/send-doc.js +22 -17
- package/build/server/scratchpad/adapters/send-doc.js.map +1 -1
- package/build/server/scratchpad/adapters/send-email-draft.d.ts +1 -1
- package/build/server/scratchpad/adapters/send-email-draft.js +17 -25
- package/build/server/scratchpad/adapters/send-email-draft.js.map +1 -1
- package/build/server/scratchpad/adapters/send-email.d.ts +2 -2
- package/build/server/scratchpad/adapters/send-email.js +21 -25
- package/build/server/scratchpad/adapters/send-email.js.map +1 -1
- package/build/server/scratchpad/adapters/send-sheet.js +10 -10
- package/build/server/scratchpad/adapters/send-sheet.js.map +1 -1
- package/build/server/scratchpad/adapters/send-task.js +9 -8
- package/build/server/scratchpad/adapters/send-task.js.map +1 -1
- package/build/server/scratchpad/handler.js +22 -28
- package/build/server/scratchpad/handler.js.map +1 -1
- package/build/server/scratchpad/manager.d.ts +1 -1
- package/build/server/server.js +13 -10
- package/build/server/server.js.map +1 -1
- package/build/server/session/tracker.js +21 -25
- package/build/server/session/tracker.js.map +1 -1
- package/build/server/tools.d.ts +1 -1
- package/build/server/tools.js +3 -3
- package/build/server/tools.js.map +1 -1
- package/build/services/calendar/patch.js +178 -68
- package/build/services/calendar/patch.js.map +1 -1
- package/build/services/docs/patch.d.ts +5 -3
- package/build/services/docs/patch.js +89 -19
- package/build/services/docs/patch.js.map +1 -1
- package/build/services/drive/patch.d.ts +1 -1
- package/build/services/drive/patch.js +115 -143
- package/build/services/drive/patch.js.map +1 -1
- package/build/services/gmail/attachments.js +10 -15
- package/build/services/gmail/attachments.js.map +1 -1
- package/build/services/gmail/mail.d.ts +34 -0
- package/build/services/gmail/mail.js +245 -0
- package/build/services/gmail/mail.js.map +1 -0
- package/build/services/gmail/mime.d.ts +45 -4
- package/build/services/gmail/mime.js +138 -4
- package/build/services/gmail/mime.js.map +1 -1
- package/build/services/gmail/patch.js +69 -83
- package/build/services/gmail/patch.js.map +1 -1
- package/build/services/meet/patch.js +24 -31
- package/build/services/meet/patch.js.map +1 -1
- package/build/services/sheets/patch.js +30 -35
- package/build/services/sheets/patch.js.map +1 -1
- package/build/services/tasks/patch.d.ts +18 -0
- package/build/services/tasks/patch.js +37 -0
- package/build/services/tasks/patch.js.map +1 -0
- package/build/version.d.ts +1 -1
- package/build/version.js +1 -1
- package/package.json +11 -6
- package/build/executor/errors.d.ts +0 -15
- package/build/executor/errors.js +0 -37
- package/build/executor/errors.js.map +0 -1
- package/build/executor/gws.d.ts +0 -26
- package/build/executor/gws.js +0 -197
- package/build/executor/gws.js.map +0 -1
- package/build/server/handlers/calendar.d.ts +0 -2
- package/build/server/handlers/calendar.js +0 -97
- package/build/server/handlers/calendar.js.map +0 -1
- package/build/server/handlers/drive.d.ts +0 -2
- package/build/server/handlers/drive.js +0 -58
- package/build/server/handlers/drive.js.map +0 -1
- package/build/server/handlers/email.d.ts +0 -2
- package/build/server/handlers/email.js +0 -119
- package/build/server/handlers/email.js.map +0 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound mail: send, reply, reply-all, forward.
|
|
3
|
+
*
|
|
4
|
+
* The recipient logic below looks fussy. It is fussy because getting it wrong is
|
|
5
|
+
* silent and embarrassing: a reply-all that mails the sender their own reply, or
|
|
6
|
+
* that drops the one person who needed to see it. The rules are spelled out in
|
|
7
|
+
* docs/design-notes/adr-103-helper-semantics.md — read them before changing any
|
|
8
|
+
* of it.
|
|
9
|
+
*/
|
|
10
|
+
import { readFile } from 'node:fs/promises';
|
|
11
|
+
import { basename } from 'node:path';
|
|
12
|
+
import { call, upload } from '../../google/client.js';
|
|
13
|
+
import { resolveWorkspacePath, verifyPathSafety } from '../../executor/workspace.js';
|
|
14
|
+
import { buildMimeMessage, lookupMimeType } from './mime.js';
|
|
15
|
+
/**
|
|
16
|
+
* Gmail's declared ceiling is 36,700,160 bytes for the ENCODED message. Base64
|
|
17
|
+
* inflates by 4/3, so we cap the RAW attachment bytes well under it and let the
|
|
18
|
+
* client's own descriptor-driven check (which knows the real declared maxSize) be
|
|
19
|
+
* the backstop. Refusing here gives the caller a sentence instead of a 400.
|
|
20
|
+
*/
|
|
21
|
+
const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
22
|
+
/** Read workspace attachments, fenced by our own path safety check. */
|
|
23
|
+
async function loadAttachments(names) {
|
|
24
|
+
const parts = await Promise.all(names.map(async (name) => {
|
|
25
|
+
const filePath = resolveWorkspacePath(name);
|
|
26
|
+
await verifyPathSafety(filePath); // the workspace fence — attachments cannot escape it
|
|
27
|
+
const data = await readFile(filePath);
|
|
28
|
+
if (data.length === 0) {
|
|
29
|
+
throw new Error(`Attachment "${name}" is empty (0 bytes). Gmail rejects empty parts.`);
|
|
30
|
+
}
|
|
31
|
+
return { filename: basename(filePath), contentType: lookupMimeType(filePath), data };
|
|
32
|
+
}));
|
|
33
|
+
const total = parts.reduce((n, p) => n + p.data.length, 0);
|
|
34
|
+
if (total > MAX_ATTACHMENT_BYTES) {
|
|
35
|
+
throw new Error(`Attachments total ${(total / 1e6).toFixed(1)} MB; the limit is ` +
|
|
36
|
+
`${(MAX_ATTACHMENT_BYTES / 1e6).toFixed(0)} MB of raw bytes (base64 inflates by ~4/3 ` +
|
|
37
|
+
`and Gmail's encoded ceiling is 35 MB).`);
|
|
38
|
+
}
|
|
39
|
+
return parts;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Deliver a built message.
|
|
43
|
+
*
|
|
44
|
+
* Uses the RESUMABLE upload endpoint, always — never `uploadType=multipart`, which
|
|
45
|
+
* has no chunking and degrades at size. Resumable is verified (ADR-103 item 4) to
|
|
46
|
+
* carry a 25 MB attachment — 34.2 MB encoded, 93% of Google's declared cap — and to
|
|
47
|
+
* round-trip byte-for-byte.
|
|
48
|
+
*/
|
|
49
|
+
async function deliver(message, account, draft, threadId) {
|
|
50
|
+
const resource = draft ? 'users.drafts.create' : 'users.messages.send';
|
|
51
|
+
const metadata = threadId
|
|
52
|
+
? (draft ? { message: { threadId } } : { threadId })
|
|
53
|
+
: {};
|
|
54
|
+
return await upload('gmail', resource, { userId: 'me' }, {
|
|
55
|
+
account,
|
|
56
|
+
media: message,
|
|
57
|
+
contentType: 'message/rfc822',
|
|
58
|
+
metadata,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export async function sendMail(account, opts) {
|
|
62
|
+
const attachments = opts.attachments?.length ? await loadAttachments(opts.attachments) : [];
|
|
63
|
+
const message = buildMimeMessage({
|
|
64
|
+
to: opts.to, subject: opts.subject, body: opts.body,
|
|
65
|
+
from: opts.from, cc: opts.cc, bcc: opts.bcc, html: opts.html,
|
|
66
|
+
attachments,
|
|
67
|
+
});
|
|
68
|
+
return deliver(message, account, opts.draft === true);
|
|
69
|
+
}
|
|
70
|
+
const headerOf = (headers, name) => headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? '';
|
|
71
|
+
/** Walk the payload for the first text/plain body. */
|
|
72
|
+
function plainBody(payload) {
|
|
73
|
+
const mime = String(payload.mimeType ?? '');
|
|
74
|
+
const body = payload.body;
|
|
75
|
+
if (mime === 'text/plain' && body?.data) {
|
|
76
|
+
return Buffer.from(body.data, 'base64url').toString('utf-8');
|
|
77
|
+
}
|
|
78
|
+
for (const part of (payload.parts ?? [])) {
|
|
79
|
+
const found = plainBody(part);
|
|
80
|
+
if (found)
|
|
81
|
+
return found;
|
|
82
|
+
}
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
async function fetchOriginal(account, messageId) {
|
|
86
|
+
const msg = await call('gmail', 'users.messages.get', { userId: 'me', id: messageId, format: 'full' }, { account });
|
|
87
|
+
const payload = msg.payload;
|
|
88
|
+
const headers = (payload.headers ?? []);
|
|
89
|
+
return {
|
|
90
|
+
id: String(msg.id),
|
|
91
|
+
threadId: String(msg.threadId),
|
|
92
|
+
messageId: headerOf(headers, 'Message-ID') || undefined,
|
|
93
|
+
references: headerOf(headers, 'References') || undefined,
|
|
94
|
+
subject: headerOf(headers, 'Subject'),
|
|
95
|
+
from: headerOf(headers, 'From'),
|
|
96
|
+
to: headerOf(headers, 'To'),
|
|
97
|
+
cc: headerOf(headers, 'Cc'),
|
|
98
|
+
replyTo: headerOf(headers, 'Reply-To') || undefined,
|
|
99
|
+
date: headerOf(headers, 'Date'),
|
|
100
|
+
bodyText: plainBody(payload),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** `a@b.com`, `"Name" <a@b.com>` -> `a@b.com`, lowercased. The identity we compare on. */
|
|
104
|
+
function emailOf(address) {
|
|
105
|
+
const angled = address.match(/<([^>]+)>/);
|
|
106
|
+
return (angled ? angled[1] : address).trim().toLowerCase();
|
|
107
|
+
}
|
|
108
|
+
const splitAddresses = (list) => list.split(',').map((a) => a.trim()).filter(Boolean);
|
|
109
|
+
/**
|
|
110
|
+
* Enforce To > Cc > Bcc precedence and drop excluded/duplicate addresses.
|
|
111
|
+
*
|
|
112
|
+
* An address that appears in two fields survives only in the highest — otherwise
|
|
113
|
+
* a recipient is mailed twice, and Gmail shows them in both lines.
|
|
114
|
+
*/
|
|
115
|
+
function dedupeRecipients(to, cc, excluded) {
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
const keep = (list) => list.filter((a) => {
|
|
118
|
+
const key = emailOf(a);
|
|
119
|
+
if (!key || excluded.has(key) || seen.has(key))
|
|
120
|
+
return false;
|
|
121
|
+
seen.add(key);
|
|
122
|
+
return true;
|
|
123
|
+
});
|
|
124
|
+
return { to: keep(to), cc: keep(cc) };
|
|
125
|
+
}
|
|
126
|
+
/** `Re: ` unless it is already a reply. Case-insensitive, like Gmail. */
|
|
127
|
+
const rePrefix = (subject) => /^re:/i.test(subject.trim()) ? subject : `Re: ${subject}`;
|
|
128
|
+
const fwdPrefix = (subject) => /^fwd:/i.test(subject.trim()) ? subject : `Fwd: ${subject}`;
|
|
129
|
+
/** Quote the original, plain-text style. */
|
|
130
|
+
function quotePlain(o) {
|
|
131
|
+
const attribution = `On ${o.date}, ${o.from} wrote:`;
|
|
132
|
+
const quoted = o.bodyText.split('\n').map((l) => `> ${l}`).join('\n');
|
|
133
|
+
return `${attribution}\n${quoted}`;
|
|
134
|
+
}
|
|
135
|
+
export async function replyMail(account, opts) {
|
|
136
|
+
const original = await fetchOriginal(account, opts.messageId);
|
|
137
|
+
// Who gets it. `Reply-To` wins over `From` when the sender asked for it.
|
|
138
|
+
const primaryTo = original.replyTo || original.from;
|
|
139
|
+
let to = splitAddresses(primaryTo);
|
|
140
|
+
let cc = opts.cc ? splitAddresses(opts.cc) : [];
|
|
141
|
+
const excluded = new Set();
|
|
142
|
+
if (opts.all) {
|
|
143
|
+
// Never reply-all to yourself. The account's own address is excluded, or you
|
|
144
|
+
// mail yourself a copy of your own reply — visibly, in the To line.
|
|
145
|
+
excluded.add(emailOf(account));
|
|
146
|
+
const iSentIt = emailOf(original.from) === emailOf(account);
|
|
147
|
+
if (iSentIt) {
|
|
148
|
+
// Replying-all to your OWN message: the people to reach are the ones you
|
|
149
|
+
// originally wrote to, not yourself. Matches Gmail's web behaviour, and
|
|
150
|
+
// Reply-To is ignored here on purpose.
|
|
151
|
+
to = splitAddresses(original.to);
|
|
152
|
+
cc = [...splitAddresses(original.cc), ...cc];
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Everyone else on the thread moves to Cc; the sender stays in To.
|
|
156
|
+
cc = [...splitAddresses(original.to), ...splitAddresses(original.cc), ...cc];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const recipients = dedupeRecipients(to, cc, excluded);
|
|
160
|
+
if (recipients.to.length === 0) {
|
|
161
|
+
throw new Error('No recipient remains after removing your own address. Nobody would receive this reply.');
|
|
162
|
+
}
|
|
163
|
+
const quoted = opts.html
|
|
164
|
+
? `${opts.body}<br><blockquote>${original.bodyText}</blockquote>`
|
|
165
|
+
: `${opts.body}\n\n${quotePlain(original)}`;
|
|
166
|
+
// Threading. In-Reply-To points at the parent; References carries the chain so
|
|
167
|
+
// clients that do not know threadId can still build the tree.
|
|
168
|
+
const references = [original.references, original.messageId].filter(Boolean).join(' ');
|
|
169
|
+
const message = buildMimeMessage({
|
|
170
|
+
to: recipients.to.join(', '),
|
|
171
|
+
cc: recipients.cc.length ? recipients.cc.join(', ') : undefined,
|
|
172
|
+
subject: rePrefix(original.subject),
|
|
173
|
+
body: quoted,
|
|
174
|
+
html: opts.html,
|
|
175
|
+
attachments: opts.attachments?.length ? await loadAttachments(opts.attachments) : [],
|
|
176
|
+
inReplyTo: original.messageId,
|
|
177
|
+
references: references || undefined,
|
|
178
|
+
});
|
|
179
|
+
return deliver(message, account, opts.draft === true, original.threadId);
|
|
180
|
+
}
|
|
181
|
+
/** Pull the original's real attachment bytes back out of Gmail. */
|
|
182
|
+
async function originalAttachments(account, original) {
|
|
183
|
+
const msg = await call('gmail', 'users.messages.get', { userId: 'me', id: original.id, format: 'full' }, { account });
|
|
184
|
+
const found = [];
|
|
185
|
+
const walk = (part) => {
|
|
186
|
+
const body = part.body;
|
|
187
|
+
if (part.filename && body?.attachmentId) {
|
|
188
|
+
found.push({
|
|
189
|
+
filename: String(part.filename),
|
|
190
|
+
contentType: String(part.mimeType ?? 'application/octet-stream'),
|
|
191
|
+
attachmentId: body.attachmentId,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
for (const p of (part.parts ?? []))
|
|
195
|
+
walk(p);
|
|
196
|
+
};
|
|
197
|
+
walk(msg.payload);
|
|
198
|
+
return Promise.all(found.map(async (f) => {
|
|
199
|
+
const att = await call('gmail', 'users.messages.attachments.get', { userId: 'me', messageId: original.id, id: f.attachmentId }, { account });
|
|
200
|
+
return {
|
|
201
|
+
filename: f.filename,
|
|
202
|
+
contentType: f.contentType,
|
|
203
|
+
data: Buffer.from(att.data, 'base64url'),
|
|
204
|
+
};
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
export async function forwardMail(account, opts) {
|
|
208
|
+
const original = await fetchOriginal(account, opts.messageId);
|
|
209
|
+
const attachments = opts.includeAttachments === false
|
|
210
|
+
? []
|
|
211
|
+
: await originalAttachments(account, original);
|
|
212
|
+
const header = `---------- Forwarded message ---------\n` +
|
|
213
|
+
`From: ${original.from}\n` +
|
|
214
|
+
(original.date ? `Date: ${original.date}\n` : '') +
|
|
215
|
+
`Subject: ${original.subject}\n` +
|
|
216
|
+
`To: ${original.to}\n` +
|
|
217
|
+
(original.cc ? `Cc: ${original.cc}\n` : '') +
|
|
218
|
+
`\n`;
|
|
219
|
+
const body = `${opts.body ? `${opts.body}\n\n` : ''}${header}${original.bodyText}`;
|
|
220
|
+
// A forward THREADS. It is a continuation of the message's identity, not a new
|
|
221
|
+
// conversation, and this is what every mail client actually does.
|
|
222
|
+
//
|
|
223
|
+
// Measured, not assumed — an earlier version of this function did NOT thread,
|
|
224
|
+
// on the confident but INVENTED belief that Gmail's web client starts a new
|
|
225
|
+
// thread on forward. It does not. Checking real mail settled it: every forward
|
|
226
|
+
// sent from this account carries In-Reply-To + References and shares the
|
|
227
|
+
// original's threadId, and 20/20 forwards RECEIVED from other clients — Gmail,
|
|
228
|
+
// Outlook/Exchange, Yahoo — carry the same headers. RFC 5322 §3.6.4 is the
|
|
229
|
+
// mechanism; universal practice is the evidence.
|
|
230
|
+
//
|
|
231
|
+
// The forward lives in the sender's own thread, in the sender's own mailbox. The
|
|
232
|
+
// original recipients never see it.
|
|
233
|
+
const references = [original.references, original.messageId].filter(Boolean).join(' ');
|
|
234
|
+
const message = buildMimeMessage({
|
|
235
|
+
to: opts.to,
|
|
236
|
+
subject: fwdPrefix(original.subject),
|
|
237
|
+
body,
|
|
238
|
+
html: opts.html,
|
|
239
|
+
attachments,
|
|
240
|
+
inReplyTo: original.messageId,
|
|
241
|
+
references: references || undefined,
|
|
242
|
+
});
|
|
243
|
+
return deliver(message, account, opts.draft === true, original.threadId);
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=mail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail.js","sourceRoot":"","sources":["../../../src/services/gmail/mail.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MIME type lookup
|
|
2
|
+
* MIME: type lookup, and the RFC 5322 message builder.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Two constraints worth stating, because getting either wrong is silent:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Header injection.** A `To:` or `Subject:` carrying CR or LF splits the
|
|
7
|
+
* header block and lets a caller forge headers (Bcc, From) or inject a body.
|
|
8
|
+
* Every header value is stripped of control characters. This is not
|
|
9
|
+
* theoretical: the values come from an LLM, through a tool call.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Inline images belong in multipart/related, not multipart/mixed.** Gmail
|
|
12
|
+
* rewrites `Content-Disposition: inline` to `attachment` when a CID part sits
|
|
13
|
+
* in a `mixed` container, so an inline image silently becomes a dangling
|
|
14
|
+
* attachment.
|
|
7
15
|
*/
|
|
8
16
|
/** Look up MIME type by filename extension. */
|
|
9
17
|
export declare function lookupMimeType(filename: string): string;
|
|
18
|
+
export interface MimeAttachment {
|
|
19
|
+
filename: string;
|
|
20
|
+
contentType: string;
|
|
21
|
+
data: Buffer;
|
|
22
|
+
/** Present for inline images. Forces a multipart/related container — see the header note. */
|
|
23
|
+
contentId?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface MimeMessage {
|
|
26
|
+
to: string;
|
|
27
|
+
subject: string;
|
|
28
|
+
body: string;
|
|
29
|
+
from?: string;
|
|
30
|
+
cc?: string;
|
|
31
|
+
bcc?: string;
|
|
32
|
+
html?: boolean;
|
|
33
|
+
attachments?: MimeAttachment[];
|
|
34
|
+
/** Threading (reply/forward). */
|
|
35
|
+
inReplyTo?: string;
|
|
36
|
+
references?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build an RFC 5322 message, base64url-encoded for Gmail's `raw` field or for a
|
|
40
|
+
* `message/rfc822` media upload.
|
|
41
|
+
*
|
|
42
|
+
* Structure is chosen by content, not by habit:
|
|
43
|
+
* body only -> text/plain or text/html
|
|
44
|
+
* body + inline images -> multipart/related (NOT mixed — Gmail rewrites inline->attachment in mixed)
|
|
45
|
+
* body + attachments -> multipart/mixed
|
|
46
|
+
* body + inline + attachments-> multipart/mixed [ multipart/related [ body, inline… ], attachments… ]
|
|
47
|
+
*/
|
|
48
|
+
export declare function buildMimeMessage(msg: MimeMessage): Buffer;
|
|
49
|
+
/** Gmail's `raw` field wants base64url with no padding. */
|
|
50
|
+
export declare function toRawField(message: Buffer): string;
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MIME type lookup
|
|
2
|
+
* MIME: type lookup, and the RFC 5322 message builder.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Two constraints worth stating, because getting either wrong is silent:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Header injection.** A `To:` or `Subject:` carrying CR or LF splits the
|
|
7
|
+
* header block and lets a caller forge headers (Bcc, From) or inject a body.
|
|
8
|
+
* Every header value is stripped of control characters. This is not
|
|
9
|
+
* theoretical: the values come from an LLM, through a tool call.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Inline images belong in multipart/related, not multipart/mixed.** Gmail
|
|
12
|
+
* rewrites `Content-Disposition: inline` to `attachment` when a CID part sits
|
|
13
|
+
* in a `mixed` container, so an inline image silently becomes a dangling
|
|
14
|
+
* attachment.
|
|
7
15
|
*/
|
|
8
16
|
/** Common extension → MIME type map. Falls back to application/octet-stream. */
|
|
9
17
|
const MIME_TYPES = {
|
|
@@ -46,4 +54,130 @@ export function lookupMimeType(filename) {
|
|
|
46
54
|
const ext = filename.slice(dotIndex).toLowerCase();
|
|
47
55
|
return MIME_TYPES[ext] ?? 'application/octet-stream';
|
|
48
56
|
}
|
|
57
|
+
// --- RFC 5322 message construction ---
|
|
58
|
+
const CRLF = '\r\n';
|
|
59
|
+
/**
|
|
60
|
+
* Strip CR/LF and other control characters from a header value.
|
|
61
|
+
*
|
|
62
|
+
* A newline in a header value ENDS THE HEADER and starts a new one. A caller who
|
|
63
|
+
* gets `\r\nBcc: attacker@evil.com` into a subject has forged a Bcc. Values here
|
|
64
|
+
* originate from a model's tool call, so this is a real input, not a hypothetical.
|
|
65
|
+
*/
|
|
66
|
+
function sanitizeHeader(value) {
|
|
67
|
+
// eslint-disable-next-line no-control-regex
|
|
68
|
+
return value.replace(/[\x00-\x1F\x7F]/g, '');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Encode a header value that may contain non-ASCII, per RFC 2047.
|
|
72
|
+
* A bare UTF-8 subject is not legal in a header; Gmail tolerates it, other
|
|
73
|
+
* receivers mangle it. Encode only when needed, so plain subjects stay readable.
|
|
74
|
+
*/
|
|
75
|
+
function encodeHeaderValue(value) {
|
|
76
|
+
const clean = sanitizeHeader(value);
|
|
77
|
+
// eslint-disable-next-line no-control-regex
|
|
78
|
+
if (!/[^\x00-\x7F]/.test(clean))
|
|
79
|
+
return clean;
|
|
80
|
+
return `=?UTF-8?B?${Buffer.from(clean, 'utf-8').toString('base64')}?=`;
|
|
81
|
+
}
|
|
82
|
+
/** Wrap base64 at 76 chars, as RFC 2045 requires. */
|
|
83
|
+
function wrapBase64(data) {
|
|
84
|
+
return data.toString('base64').replace(/(.{76})/g, `$1${CRLF}`);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A boundary that cannot collide with the content it delimits.
|
|
88
|
+
*
|
|
89
|
+
* If a boundary string happens to occur inside an attachment's base64 (or inside
|
|
90
|
+
* a body), the message silently truncates at that point. Random suffix, and the
|
|
91
|
+
* marker is not something base64 can produce (it contains `=` only at the end and
|
|
92
|
+
* `_`), so a collision would have to be deliberate.
|
|
93
|
+
*/
|
|
94
|
+
function makeBoundary(tag) {
|
|
95
|
+
return `----gwsmcp_${tag}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build an RFC 5322 message, base64url-encoded for Gmail's `raw` field or for a
|
|
99
|
+
* `message/rfc822` media upload.
|
|
100
|
+
*
|
|
101
|
+
* Structure is chosen by content, not by habit:
|
|
102
|
+
* body only -> text/plain or text/html
|
|
103
|
+
* body + inline images -> multipart/related (NOT mixed — Gmail rewrites inline->attachment in mixed)
|
|
104
|
+
* body + attachments -> multipart/mixed
|
|
105
|
+
* body + inline + attachments-> multipart/mixed [ multipart/related [ body, inline… ], attachments… ]
|
|
106
|
+
*/
|
|
107
|
+
export function buildMimeMessage(msg) {
|
|
108
|
+
const headers = [];
|
|
109
|
+
const add = (name, value) => {
|
|
110
|
+
if (value)
|
|
111
|
+
headers.push(`${name}: ${encodeHeaderValue(value)}`);
|
|
112
|
+
};
|
|
113
|
+
add('From', msg.from);
|
|
114
|
+
add('To', msg.to);
|
|
115
|
+
add('Cc', msg.cc);
|
|
116
|
+
add('Bcc', msg.bcc);
|
|
117
|
+
add('Subject', msg.subject);
|
|
118
|
+
// Message-ID / References must NOT be RFC-2047 encoded — they are addr-spec
|
|
119
|
+
// tokens, not display text. Sanitize only.
|
|
120
|
+
if (msg.inReplyTo)
|
|
121
|
+
headers.push(`In-Reply-To: ${sanitizeHeader(msg.inReplyTo)}`);
|
|
122
|
+
if (msg.references)
|
|
123
|
+
headers.push(`References: ${sanitizeHeader(msg.references)}`);
|
|
124
|
+
headers.push('MIME-Version: 1.0');
|
|
125
|
+
const bodyType = msg.html ? 'text/html' : 'text/plain';
|
|
126
|
+
const bodyPart = () => `Content-Type: ${bodyType}; charset="UTF-8"${CRLF}` +
|
|
127
|
+
`Content-Transfer-Encoding: base64${CRLF}${CRLF}` +
|
|
128
|
+
wrapBase64(Buffer.from(msg.body, 'utf-8'));
|
|
129
|
+
const attachmentPart = (a) => `Content-Type: ${a.contentType}; name="${sanitizeHeader(a.filename)}"${CRLF}` +
|
|
130
|
+
(a.contentId
|
|
131
|
+
? `Content-Disposition: inline; filename="${sanitizeHeader(a.filename)}"${CRLF}` +
|
|
132
|
+
`Content-ID: <${sanitizeHeader(a.contentId)}>${CRLF}`
|
|
133
|
+
: `Content-Disposition: attachment; filename="${sanitizeHeader(a.filename)}"${CRLF}`) +
|
|
134
|
+
`Content-Transfer-Encoding: base64${CRLF}${CRLF}` +
|
|
135
|
+
wrapBase64(a.data);
|
|
136
|
+
const all = msg.attachments ?? [];
|
|
137
|
+
const inline = all.filter((a) => a.contentId);
|
|
138
|
+
const files = all.filter((a) => !a.contentId);
|
|
139
|
+
let content;
|
|
140
|
+
if (all.length === 0) {
|
|
141
|
+
headers.push(`Content-Type: ${bodyType}; charset="UTF-8"`);
|
|
142
|
+
headers.push('Content-Transfer-Encoding: base64');
|
|
143
|
+
content = wrapBase64(Buffer.from(msg.body, 'utf-8'));
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// The body, possibly wrapped with its inline images in a `related` container.
|
|
147
|
+
let bodyBlock;
|
|
148
|
+
if (inline.length > 0) {
|
|
149
|
+
const rel = makeBoundary('rel');
|
|
150
|
+
bodyBlock =
|
|
151
|
+
`Content-Type: multipart/related; boundary="${rel}"${CRLF}${CRLF}` +
|
|
152
|
+
`--${rel}${CRLF}${bodyPart()}${CRLF}` +
|
|
153
|
+
inline.map((a) => `--${rel}${CRLF}${attachmentPart(a)}${CRLF}`).join('') +
|
|
154
|
+
`--${rel}--`;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
bodyBlock = bodyPart();
|
|
158
|
+
}
|
|
159
|
+
if (files.length === 0) {
|
|
160
|
+
// Inline images only: the related container IS the message.
|
|
161
|
+
const rel = makeBoundary('rel');
|
|
162
|
+
headers.push(`Content-Type: multipart/related; boundary="${rel}"`);
|
|
163
|
+
content =
|
|
164
|
+
`--${rel}${CRLF}${bodyPart()}${CRLF}` +
|
|
165
|
+
inline.map((a) => `--${rel}${CRLF}${attachmentPart(a)}${CRLF}`).join('') +
|
|
166
|
+
`--${rel}--`;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const mixed = makeBoundary('mix');
|
|
170
|
+
headers.push(`Content-Type: multipart/mixed; boundary="${mixed}"`);
|
|
171
|
+
content =
|
|
172
|
+
`--${mixed}${CRLF}${bodyBlock}${CRLF}` +
|
|
173
|
+
files.map((a) => `--${mixed}${CRLF}${attachmentPart(a)}${CRLF}`).join('') +
|
|
174
|
+
`--${mixed}--`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return Buffer.from(headers.join(CRLF) + CRLF + CRLF + content + CRLF, 'utf-8');
|
|
178
|
+
}
|
|
179
|
+
/** Gmail's `raw` field wants base64url with no padding. */
|
|
180
|
+
export function toRawField(message) {
|
|
181
|
+
return message.toString('base64url');
|
|
182
|
+
}
|
|
49
183
|
//# sourceMappingURL=mime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../../../src/services/gmail/mime.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../../../src/services/gmail/mime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,gFAAgF;AAChF,MAAM,UAAU,GAA2B;IACzC,KAAK,EAAE,eAAe;IACtB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,iBAAiB;IACzB,KAAK,EAAE,kBAAkB;IACzB,MAAM,EAAE,mBAAmB;IAC3B,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,eAAe;IACvB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,oBAAoB;IAC5B,OAAO,EAAE,yEAAyE;IAClF,MAAM,EAAE,0BAA0B;IAClC,OAAO,EAAE,mEAAmE;IAC5E,MAAM,EAAE,+BAA+B;IACvC,OAAO,EAAE,2EAA2E;IACpF,OAAO,EAAE,oBAAoB;IAC7B,MAAM,EAAE,oBAAoB;IAC5B,KAAK,EAAE,wBAAwB;IAC/B,KAAK,EAAE,mBAAmB;IAC1B,KAAK,EAAE,eAAe;IACtB,KAAK,EAAE,kBAAkB;CAC1B,CAAC;AAEF,+CAA+C;AAC/C,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,QAAQ,KAAK,CAAC,CAAC;QAAE,OAAO,0BAA0B,CAAC;IACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACnD,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AACvD,CAAC;AAED,wCAAwC;AAExC,MAAM,IAAI,GAAG,MAAM,CAAC;AAEpB;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,KAAa;IACnC,4CAA4C;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,KAAa;IACtC,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,4CAA4C;IAC5C,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,aAAa,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;AACzE,CAAC;AAED,qDAAqD;AACrD,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,YAAY,CAAC,GAAW;IAC/B,OAAO,cAAc,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AAC9F,CAAC;AAwBD;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAgB;IAC/C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,KAAc,EAAE,EAAE;QAC3C,IAAI,KAAK;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC;IAEF,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IACpB,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,4EAA4E;IAC5E,2CAA2C;IAC3C,IAAI,GAAG,CAAC,SAAS;QAAE,OAAO,CAAC,IAAI,CAAC,gBAAgB,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACjF,IAAI,GAAG,CAAC,UAAU;QAAE,OAAO,CAAC,IAAI,CAAC,eAAe,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAClF,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAElC,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC;IACvD,MAAM,QAAQ,GAAG,GAAW,EAAE,CAC5B,iBAAiB,QAAQ,oBAAoB,IAAI,EAAE;QACnD,oCAAoC,IAAI,GAAG,IAAI,EAAE;QACjD,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAE7C,MAAM,cAAc,GAAG,CAAC,CAAiB,EAAU,EAAE,CACnD,iBAAiB,CAAC,CAAC,WAAW,WAAW,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE;QAC7E,CAAC,CAAC,CAAC,SAAS;YACV,CAAC,CAAC,0CAA0C,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE;gBAC9E,gBAAgB,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,IAAI,EAAE;YACvD,CAAC,CAAC,8CAA8C,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;QACvF,oCAAoC,IAAI,GAAG,IAAI,EAAE;QACjD,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAErB,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAE9C,IAAI,OAAe,CAAC;IAEpB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,iBAAiB,QAAQ,mBAAmB,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;QAClD,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD,CAAC;SAAM,CAAC;QACN,8EAA8E;QAC9E,IAAI,SAAiB,CAAC;QACtB,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YAChC,SAAS;gBACP,8CAA8C,GAAG,IAAI,IAAI,GAAG,IAAI,EAAE;oBAClE,KAAK,GAAG,GAAG,IAAI,GAAG,QAAQ,EAAE,GAAG,IAAI,EAAE;oBACrC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,GAAG,IAAI,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;oBACxE,KAAK,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,SAAS,GAAG,QAAQ,EAAE,CAAC;QACzB,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,4DAA4D;YAC5D,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC,8CAA8C,GAAG,GAAG,CAAC,CAAC;YACnE,OAAO;gBACL,KAAK,GAAG,GAAG,IAAI,GAAG,QAAQ,EAAE,GAAG,IAAI,EAAE;oBACrC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,GAAG,IAAI,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;oBACxE,KAAK,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC,4CAA4C,KAAK,GAAG,CAAC,CAAC;YACnE,OAAO;gBACL,KAAK,KAAK,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,EAAE;oBACtC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,GAAG,IAAI,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzE,KAAK,KAAK,IAAI,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACjF,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACvC,CAAC"}
|