Mail: fix opening messages (contentId is not selectable on the attachment list; fetch inline pictures whole)
build-container / build (push) Successful in 20m42s
build-container / build (push) Successful in 20m42s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d8d4f4d82
commit
5f639eec3c
2 files changed
+9
-7
No files matched your search
@@ -143,11 +143,12 @@ async function folders() {
|
||||
const dropFolderCache = () => folderCache.delete(users.current().id);
|
||||
|
||||
async function withInline(html, attachments, mid) {
|
||||
const inl = (attachments || []).filter(a => a.isInline && a.contentId && /^image\//i.test(a.contentType || '') && (a.size || 0) < 1.5 * 1024 * 1024).slice(0, 15);
|
||||
// (contentId only exists on file attachments, so it cannot be selected in the list: fetch each small inline picture whole)
|
||||
const inl = (attachments || []).filter(a => a.isInline && /^image\//i.test(a.contentType || '') && (a.size || 0) < 1.5 * 1024 * 1024).slice(0, 15);
|
||||
for (const a of inl) {
|
||||
try {
|
||||
const full = await graph(`/me/messages/${id(mid)}/attachments/${id(a.id)}?$select=contentBytes,contentType`);
|
||||
if (full && full.contentBytes) html = html.split('cid:' + String(a.contentId).replace(/^<|>$/g, '')).join(`data:${full.contentType};base64,${full.contentBytes}`);
|
||||
const full = await graph(`/me/messages/${id(mid)}/attachments/${id(a.id)}`);
|
||||
if (full && full.contentBytes && full.contentId) html = html.split('cid:' + String(full.contentId).replace(/^<|>$/g, '')).join(`data:${full.contentType || a.contentType};base64,${full.contentBytes}`);
|
||||
} catch { /* leave that picture out */ }
|
||||
}
|
||||
return html;
|
||||
@@ -207,11 +208,11 @@ async function handle(req, res) {
|
||||
}
|
||||
if (p === '/api/mail/message') {
|
||||
const mid = q.get('id');
|
||||
const m = await graph(`/me/messages/${id(mid)}?$select=id,subject,from,toRecipients,ccRecipients,receivedDateTime,isRead,body,hasAttachments,flag,parentFolderId&$expand=attachments($select=id,name,contentType,size,isInline,contentId)`);
|
||||
const m = await graph(`/me/messages/${id(mid)}?$select=id,subject,from,toRecipients,ccRecipients,receivedDateTime,isRead,body,hasAttachments,flag,parentFolderId&$expand=attachments($select=id,name,contentType,size,isInline)`);
|
||||
let html = m.body && m.body.contentType === 'html' ? m.body.content || '' : textToHtml((m.body && m.body.content) || '');
|
||||
html = await withInline(html, m.attachments, mid);
|
||||
return json(res, 200, { id: m.id, subject: m.subject || '(No subject)', from: addr(m.from), to: (m.toRecipients || []).map(addr), cc: (m.ccRecipients || []).map(addr), date: m.receivedDateTime, read: !!m.isRead, flagged: !!(m.flag && m.flag.flagStatus === 'flagged'),
|
||||
html, attachments: (m.attachments || []).filter(a => !(a.isInline && a.contentId)).map(a => ({ id: a.id, name: a.name, type: a.contentType, size: a.size })) });
|
||||
html, attachments: (m.attachments || []).filter(a => !a.isInline).map(a => ({ id: a.id, name: a.name, type: a.contentType, size: a.size })) });
|
||||
}
|
||||
if (p === '/api/mail/attachment') {
|
||||
const mid = q.get('id'), aid = q.get('aid');
|
||||
|
||||
+3
-2
@@ -47,14 +47,15 @@ const SECRET = 'mail-test-secret-'.repeat(3);
|
||||
if ((m = /^\/me\/mailFolders\/(\w+)$/.exec(g))) { const f = { inbox: folder('inbox', 'Inbox', 3, 10), drafts: folder('drafts', 'Drafts', 0, 1), sentitems: folder('sentitems', 'Sent Items', 0, 4), archive: folder('archive', 'Archive', 0, 0), junkemail: folder('junkemail', 'Junk Email', 0, 0), deleteditems: folder('deleteditems', 'Deleted Items', 0, 2) }[m[1]]; return f ? out(rs, 200, f) : out(rs, 404, { error: { message: 'no folder' } }); }
|
||||
if (g === '/me/mailFolders') return out(rs, 200, { value: [folder('inbox', 'Inbox', 3, 10), { id: 'CUSTOM1', displayName: 'Receipts', unreadItemCount: 1, totalItemCount: 5 }, { id: 'CUSTOM2', displayName: 'Outbox', unreadItemCount: 0, totalItemCount: 0 }] });
|
||||
if ((m = /^\/me\/mailFolders\/([\w=-]+)\/messages$/.exec(g))) { const all = u.searchParams.get('$search') ? msgs.filter(x => x.subject.toLowerCase().includes(u.searchParams.get('$search').replace(/"/g, '').toLowerCase())) : msgs; return out(rs, 200, { value: all, ...(u.searchParams.get('$skip') === '0' && all.length >= 2 ? { '@odata.nextLink': 'x' } : {}) }); }
|
||||
if (/\$expand=attachments\([^)]*contentId/.test(decodeURIComponent(u.search))) return out(rs, 400, { error: { message: "Parsing OData Select and Expand failed: Could not find a property named 'contentId' on type 'microsoft.graph.attachment'." } });
|
||||
if ((m = /^\/me\/messages\/([\w=-]+)$/.exec(g))) {
|
||||
if (rq.method === 'PATCH') { ms.patched = JSON.parse(b); return out(rs, 200, {}); }
|
||||
if (rq.method === 'DELETE') { ms.deleted = m[1]; return out(rs, 204); }
|
||||
const x = msgs.find(y => y.id === m[1]); if (!x) return out(rs, 404, { error: { message: 'gone' } });
|
||||
return out(rs, 200, { ...x, toRecipients: [{ emailAddress: { name: 'Nate', address: 'nate@outlook.com' } }], ccRecipients: [], body: { contentType: 'html', content: '<p>Hi <img src="cid:logo1"></p><script>alert(1)</script>' },
|
||||
attachments: [{ id: 'A1', name: 'report.pdf', contentType: 'application/pdf', size: 5, isInline: false }, { id: 'A2', name: 'logo.png', contentType: 'image/png', size: 4, isInline: true, contentId: '<logo1>' }] });
|
||||
attachments: [{ id: 'A1', name: 'report.pdf', contentType: 'application/pdf', size: 5, isInline: false }, { id: 'A2', name: 'logo.png', contentType: 'image/png', size: 4, isInline: true }] });
|
||||
}
|
||||
if ((m = /^\/me\/messages\/([\w=-]+)\/attachments\/(\w+)$/.exec(g))) return out(rs, 200, m[2] === 'A2' ? { contentBytes: Buffer.from('PNG!').toString('base64'), contentType: 'image/png' } : { name: 'rep"ort\r\n.pdf', contentType: 'application/pdf', size: 5 });
|
||||
if ((m = /^\/me\/messages\/([\w=-]+)\/attachments\/(\w+)$/.exec(g))) return out(rs, 200, m[2] === 'A2' ? { contentBytes: Buffer.from('PNG!').toString('base64'), contentType: 'image/png', contentId: '<logo1>', isInline: true } : { name: 'rep"ort\r\n.pdf', contentType: 'application/pdf', size: 5 });
|
||||
if ((m = /^\/me\/messages\/([\w=-]+)\/attachments\/(\w+)\/\$value$/.exec(g))) { rs.writeHead(200, { 'content-type': 'application/pdf' }); return rs.end('%PDF-'); }
|
||||
if ((m = /^\/me\/messages\/([\w=-]+)\/move$/.exec(g))) { ms.moved = [m[1], JSON.parse(b).destinationId]; return out(rs, 201, {}); }
|
||||
if (g === '/me/sendMail') { ms.sent = JSON.parse(b); return out(rs, 202); }
|
||||
|
||||
Reference in new issue
Block a user