@limetech/lime-elements 39.5.0 → 39.5.1
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/CHANGELOG.md +7 -0
- package/dist/cjs/limel-file-viewer.cjs.entry.js +140 -14
- package/dist/collection/components/email-viewer/email-loader.js +150 -14
- package/dist/esm/limel-file-viewer.entry.js +140 -14
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/{p-656b8f6e.entry.js → p-8d265f52.entry.js} +1 -1
- package/dist/types/components/email-viewer/email-loader.d.ts +29 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [39.5.1](https://github.com/Lundalogik/lime-elements/compare/v39.5.0...v39.5.1) (2026-02-26)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
* **email-viewer:** render inline cid logos in .eml previews ([29b85d3](https://github.com/Lundalogik/lime-elements/commit/29b85d3759e81ce0c3209fb911031d5a4f82a292))
|
|
7
|
+
|
|
1
8
|
## [39.5.0](https://github.com/Lundalogik/lime-elements/compare/v39.4.1...v39.5.0) (2026-02-25)
|
|
2
9
|
|
|
3
10
|
### Features
|
|
@@ -4878,11 +4878,13 @@ function extractAttachments(email) {
|
|
|
4878
4878
|
const cidUrlById = new Map();
|
|
4879
4879
|
for (const attachment of email.attachments || []) {
|
|
4880
4880
|
const contentId = normalizeContentId(attachment.contentId);
|
|
4881
|
-
const
|
|
4881
|
+
const hasContentId = Boolean(contentId);
|
|
4882
|
+
const isInline = (hasContentId && attachment.disposition !== 'attachment') ||
|
|
4883
|
+
attachment.related ||
|
|
4884
|
+
attachment.disposition === 'inline';
|
|
4885
|
+
const contentBytes = getAttachmentBytes(attachment.content);
|
|
4882
4886
|
if (!isInline) {
|
|
4883
|
-
const size =
|
|
4884
|
-
? attachment.content.byteLength
|
|
4885
|
-
: undefined;
|
|
4887
|
+
const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
|
|
4886
4888
|
attachments.push({
|
|
4887
4889
|
filename: attachment.filename || undefined,
|
|
4888
4890
|
mimeType: attachment.mimeType || undefined,
|
|
@@ -4890,11 +4892,9 @@ function extractAttachments(email) {
|
|
|
4890
4892
|
});
|
|
4891
4893
|
continue;
|
|
4892
4894
|
}
|
|
4893
|
-
const
|
|
4894
|
-
if (!contentId || !
|
|
4895
|
-
const size =
|
|
4896
|
-
? attachment.content.byteLength
|
|
4897
|
-
: undefined;
|
|
4895
|
+
const hasBinaryContent = Boolean(contentBytes);
|
|
4896
|
+
if (!contentId || !hasBinaryContent) {
|
|
4897
|
+
const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
|
|
4898
4898
|
attachments.push({
|
|
4899
4899
|
filename: attachment.filename || undefined,
|
|
4900
4900
|
mimeType: attachment.mimeType || 'application/octet-stream',
|
|
@@ -4902,8 +4902,8 @@ function extractAttachments(email) {
|
|
|
4902
4902
|
});
|
|
4903
4903
|
continue;
|
|
4904
4904
|
}
|
|
4905
|
-
const mimeType = attachment.mimeType
|
|
4906
|
-
const base64 =
|
|
4905
|
+
const mimeType = resolveDataUrlMimeType(attachment.mimeType, contentBytes, attachment.filename);
|
|
4906
|
+
const base64 = byteArrayToBase64(contentBytes);
|
|
4907
4907
|
const dataUrl = `data:${mimeType};base64,${base64}`;
|
|
4908
4908
|
cidUrlById.set(contentId, dataUrl);
|
|
4909
4909
|
}
|
|
@@ -4930,6 +4930,9 @@ function normalizeContentId(contentId) {
|
|
|
4930
4930
|
return '';
|
|
4931
4931
|
}
|
|
4932
4932
|
let normalized = contentId.trim();
|
|
4933
|
+
if (normalized.toLowerCase().startsWith('cid:')) {
|
|
4934
|
+
normalized = normalized.slice(4).trim();
|
|
4935
|
+
}
|
|
4933
4936
|
if (normalized.startsWith('<')) {
|
|
4934
4937
|
normalized = normalized.slice(1);
|
|
4935
4938
|
}
|
|
@@ -4943,7 +4946,7 @@ function replaceCidReferences(html, cidUrlById) {
|
|
|
4943
4946
|
return html;
|
|
4944
4947
|
}
|
|
4945
4948
|
return html.replaceAll(/(src\s*=\s*["']?)cid:([^"'\s>]+)(["']?)/gi, (match, prefix, cid, suffix) => {
|
|
4946
|
-
const normalized = normalizeContentId(cid);
|
|
4949
|
+
const normalized = normalizeContentId(decodeCidReference(cid));
|
|
4947
4950
|
const replacement = cidUrlById.get(normalized);
|
|
4948
4951
|
if (!replacement) {
|
|
4949
4952
|
return match;
|
|
@@ -4951,8 +4954,24 @@ function replaceCidReferences(html, cidUrlById) {
|
|
|
4951
4954
|
return `${prefix}${replacement}${suffix}`;
|
|
4952
4955
|
});
|
|
4953
4956
|
}
|
|
4954
|
-
function
|
|
4955
|
-
|
|
4957
|
+
function decodeCidReference(cid) {
|
|
4958
|
+
try {
|
|
4959
|
+
return decodeURIComponent(cid);
|
|
4960
|
+
}
|
|
4961
|
+
catch (_a) {
|
|
4962
|
+
return cid;
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
function getAttachmentBytes(content) {
|
|
4966
|
+
if (content instanceof ArrayBuffer) {
|
|
4967
|
+
return new Uint8Array(content);
|
|
4968
|
+
}
|
|
4969
|
+
if (ArrayBuffer.isView(content)) {
|
|
4970
|
+
return new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
4971
|
+
}
|
|
4972
|
+
return undefined;
|
|
4973
|
+
}
|
|
4974
|
+
function byteArrayToBase64(bytes) {
|
|
4956
4975
|
if (typeof btoa === 'function') {
|
|
4957
4976
|
let binary = '';
|
|
4958
4977
|
for (const byte of bytes) {
|
|
@@ -4963,6 +4982,113 @@ function arrayBufferToBase64(buffer) {
|
|
|
4963
4982
|
// Jest/Node fallback
|
|
4964
4983
|
return globalThis.Buffer.from(bytes).toString('base64');
|
|
4965
4984
|
}
|
|
4985
|
+
function resolveDataUrlMimeType(mimeType, bytes, filename) {
|
|
4986
|
+
const normalizedMimeType = typeof mimeType === 'string' ? mimeType.trim().toLowerCase() : '';
|
|
4987
|
+
const detectedFromBytes = detectImageMimeTypeFromBytes(bytes);
|
|
4988
|
+
if (normalizedMimeType.startsWith('image/')) {
|
|
4989
|
+
if (isTrustedDeclaredImageMimeType(normalizedMimeType, detectedFromBytes)) {
|
|
4990
|
+
return normalizedMimeType;
|
|
4991
|
+
}
|
|
4992
|
+
if (detectedFromBytes) {
|
|
4993
|
+
return detectedFromBytes;
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
if (detectedFromBytes) {
|
|
4997
|
+
return detectedFromBytes;
|
|
4998
|
+
}
|
|
4999
|
+
const detectedFromFilename = detectImageMimeTypeFromFilename(filename);
|
|
5000
|
+
if (detectedFromFilename) {
|
|
5001
|
+
return detectedFromFilename;
|
|
5002
|
+
}
|
|
5003
|
+
return normalizedMimeType || 'application/octet-stream';
|
|
5004
|
+
}
|
|
5005
|
+
function isTrustedDeclaredImageMimeType(declaredMimeType, detectedMimeType) {
|
|
5006
|
+
if (!detectedMimeType) {
|
|
5007
|
+
return true;
|
|
5008
|
+
}
|
|
5009
|
+
if (declaredMimeType === detectedMimeType) {
|
|
5010
|
+
return true;
|
|
5011
|
+
}
|
|
5012
|
+
if (declaredMimeType === 'image/jpg' && detectedMimeType === 'image/jpeg') {
|
|
5013
|
+
return true;
|
|
5014
|
+
}
|
|
5015
|
+
return (declaredMimeType === 'image/vnd.microsoft.icon' &&
|
|
5016
|
+
detectedMimeType === 'image/x-icon');
|
|
5017
|
+
}
|
|
5018
|
+
function detectImageMimeTypeFromBytes(bytes) {
|
|
5019
|
+
var _a, _b, _c, _d, _e;
|
|
5020
|
+
return ((_e = (_d = (_c = (_b = (_a = detectPngMimeType(bytes)) !== null && _a !== void 0 ? _a : detectJpegMimeType(bytes)) !== null && _b !== void 0 ? _b : detectGifMimeType(bytes)) !== null && _c !== void 0 ? _c : detectWebpMimeType(bytes)) !== null && _d !== void 0 ? _d : detectIconMimeType(bytes)) !== null && _e !== void 0 ? _e : detectSvgMimeType(bytes));
|
|
5021
|
+
}
|
|
5022
|
+
function detectPngMimeType(bytes) {
|
|
5023
|
+
if (startsWithBytes(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
|
|
5024
|
+
return 'image/png';
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
function detectJpegMimeType(bytes) {
|
|
5028
|
+
if (startsWithBytes(bytes, [0xff, 0xd8, 0xff])) {
|
|
5029
|
+
return 'image/jpeg';
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
function detectGifMimeType(bytes) {
|
|
5033
|
+
const firstSix = bytesToAscii(bytes, 0, 6);
|
|
5034
|
+
if (firstSix === 'GIF87a' || firstSix === 'GIF89a') {
|
|
5035
|
+
return 'image/gif';
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
function detectWebpMimeType(bytes) {
|
|
5039
|
+
const riff = bytesToAscii(bytes, 0, 4);
|
|
5040
|
+
const webp = bytesToAscii(bytes, 8, 12);
|
|
5041
|
+
if (riff === 'RIFF' && webp === 'WEBP') {
|
|
5042
|
+
return 'image/webp';
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
function detectIconMimeType(bytes) {
|
|
5046
|
+
if (startsWithBytes(bytes, [0x00, 0x00, 0x01, 0x00])) {
|
|
5047
|
+
return 'image/x-icon';
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
function detectSvgMimeType(bytes) {
|
|
5051
|
+
if (bytes.length < 5) {
|
|
5052
|
+
return;
|
|
5053
|
+
}
|
|
5054
|
+
const utf8Prefix = new TextDecoder('utf8', { fatal: false }).decode(bytes.slice(0, Math.min(bytes.length, 256)));
|
|
5055
|
+
const normalizedPrefix = utf8Prefix.trimStart().toLowerCase();
|
|
5056
|
+
if (normalizedPrefix.startsWith('<svg') ||
|
|
5057
|
+
(normalizedPrefix.startsWith('<?xml') &&
|
|
5058
|
+
normalizedPrefix.includes('<svg'))) {
|
|
5059
|
+
return 'image/svg+xml';
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
function startsWithBytes(bytes, prefix) {
|
|
5063
|
+
if (bytes.length < prefix.length) {
|
|
5064
|
+
return false;
|
|
5065
|
+
}
|
|
5066
|
+
return prefix.every((value, index) => bytes[index] === value);
|
|
5067
|
+
}
|
|
5068
|
+
function bytesToAscii(bytes, start, endExclusive) {
|
|
5069
|
+
if (bytes.length < endExclusive) {
|
|
5070
|
+
return '';
|
|
5071
|
+
}
|
|
5072
|
+
return String.fromCodePoint(...bytes.slice(start, endExclusive));
|
|
5073
|
+
}
|
|
5074
|
+
function detectImageMimeTypeFromFilename(filename) {
|
|
5075
|
+
const normalized = filename === null || filename === void 0 ? void 0 : filename.trim().toLowerCase();
|
|
5076
|
+
if (!normalized || !normalized.includes('.')) {
|
|
5077
|
+
return;
|
|
5078
|
+
}
|
|
5079
|
+
const extension = normalized.slice(normalized.lastIndexOf('.') + 1);
|
|
5080
|
+
const mimeTypes = {
|
|
5081
|
+
png: 'image/png',
|
|
5082
|
+
jpg: 'image/jpeg',
|
|
5083
|
+
jpeg: 'image/jpeg',
|
|
5084
|
+
gif: 'image/gif',
|
|
5085
|
+
webp: 'image/webp',
|
|
5086
|
+
svg: 'image/svg+xml',
|
|
5087
|
+
ico: 'image/x-icon',
|
|
5088
|
+
bmp: 'image/bmp',
|
|
5089
|
+
};
|
|
5090
|
+
return mimeTypes[extension];
|
|
5091
|
+
}
|
|
4966
5092
|
function isNonEmptyString(value) {
|
|
4967
5093
|
return typeof value === 'string' && value.length > 0;
|
|
4968
5094
|
}
|
|
@@ -66,11 +66,13 @@ function extractAttachments(email) {
|
|
|
66
66
|
const cidUrlById = new Map();
|
|
67
67
|
for (const attachment of email.attachments || []) {
|
|
68
68
|
const contentId = normalizeContentId(attachment.contentId);
|
|
69
|
-
const
|
|
69
|
+
const hasContentId = Boolean(contentId);
|
|
70
|
+
const isInline = (hasContentId && attachment.disposition !== 'attachment') ||
|
|
71
|
+
attachment.related ||
|
|
72
|
+
attachment.disposition === 'inline';
|
|
73
|
+
const contentBytes = getAttachmentBytes(attachment.content);
|
|
70
74
|
if (!isInline) {
|
|
71
|
-
const size =
|
|
72
|
-
? attachment.content.byteLength
|
|
73
|
-
: undefined;
|
|
75
|
+
const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
|
|
74
76
|
attachments.push({
|
|
75
77
|
filename: attachment.filename || undefined,
|
|
76
78
|
mimeType: attachment.mimeType || undefined,
|
|
@@ -78,11 +80,9 @@ function extractAttachments(email) {
|
|
|
78
80
|
});
|
|
79
81
|
continue;
|
|
80
82
|
}
|
|
81
|
-
const
|
|
82
|
-
if (!contentId || !
|
|
83
|
-
const size =
|
|
84
|
-
? attachment.content.byteLength
|
|
85
|
-
: undefined;
|
|
83
|
+
const hasBinaryContent = Boolean(contentBytes);
|
|
84
|
+
if (!contentId || !hasBinaryContent) {
|
|
85
|
+
const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
|
|
86
86
|
attachments.push({
|
|
87
87
|
filename: attachment.filename || undefined,
|
|
88
88
|
mimeType: attachment.mimeType || 'application/octet-stream',
|
|
@@ -90,8 +90,8 @@ function extractAttachments(email) {
|
|
|
90
90
|
});
|
|
91
91
|
continue;
|
|
92
92
|
}
|
|
93
|
-
const mimeType = attachment.mimeType
|
|
94
|
-
const base64 =
|
|
93
|
+
const mimeType = resolveDataUrlMimeType(attachment.mimeType, contentBytes, attachment.filename);
|
|
94
|
+
const base64 = byteArrayToBase64(contentBytes);
|
|
95
95
|
const dataUrl = `data:${mimeType};base64,${base64}`;
|
|
96
96
|
cidUrlById.set(contentId, dataUrl);
|
|
97
97
|
}
|
|
@@ -118,6 +118,9 @@ function normalizeContentId(contentId) {
|
|
|
118
118
|
return '';
|
|
119
119
|
}
|
|
120
120
|
let normalized = contentId.trim();
|
|
121
|
+
if (normalized.toLowerCase().startsWith('cid:')) {
|
|
122
|
+
normalized = normalized.slice(4).trim();
|
|
123
|
+
}
|
|
121
124
|
if (normalized.startsWith('<')) {
|
|
122
125
|
normalized = normalized.slice(1);
|
|
123
126
|
}
|
|
@@ -131,7 +134,7 @@ function replaceCidReferences(html, cidUrlById) {
|
|
|
131
134
|
return html;
|
|
132
135
|
}
|
|
133
136
|
return html.replaceAll(/(src\s*=\s*["']?)cid:([^"'\s>]+)(["']?)/gi, (match, prefix, cid, suffix) => {
|
|
134
|
-
const normalized = normalizeContentId(cid);
|
|
137
|
+
const normalized = normalizeContentId(decodeCidReference(cid));
|
|
135
138
|
const replacement = cidUrlById.get(normalized);
|
|
136
139
|
if (!replacement) {
|
|
137
140
|
return match;
|
|
@@ -139,8 +142,24 @@ function replaceCidReferences(html, cidUrlById) {
|
|
|
139
142
|
return `${prefix}${replacement}${suffix}`;
|
|
140
143
|
});
|
|
141
144
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
145
|
+
function decodeCidReference(cid) {
|
|
146
|
+
try {
|
|
147
|
+
return decodeURIComponent(cid);
|
|
148
|
+
}
|
|
149
|
+
catch (_a) {
|
|
150
|
+
return cid;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function getAttachmentBytes(content) {
|
|
154
|
+
if (content instanceof ArrayBuffer) {
|
|
155
|
+
return new Uint8Array(content);
|
|
156
|
+
}
|
|
157
|
+
if (ArrayBuffer.isView(content)) {
|
|
158
|
+
return new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
function byteArrayToBase64(bytes) {
|
|
144
163
|
if (typeof btoa === 'function') {
|
|
145
164
|
let binary = '';
|
|
146
165
|
for (const byte of bytes) {
|
|
@@ -151,6 +170,113 @@ function arrayBufferToBase64(buffer) {
|
|
|
151
170
|
// Jest/Node fallback
|
|
152
171
|
return globalThis.Buffer.from(bytes).toString('base64');
|
|
153
172
|
}
|
|
173
|
+
function resolveDataUrlMimeType(mimeType, bytes, filename) {
|
|
174
|
+
const normalizedMimeType = typeof mimeType === 'string' ? mimeType.trim().toLowerCase() : '';
|
|
175
|
+
const detectedFromBytes = detectImageMimeTypeFromBytes(bytes);
|
|
176
|
+
if (normalizedMimeType.startsWith('image/')) {
|
|
177
|
+
if (isTrustedDeclaredImageMimeType(normalizedMimeType, detectedFromBytes)) {
|
|
178
|
+
return normalizedMimeType;
|
|
179
|
+
}
|
|
180
|
+
if (detectedFromBytes) {
|
|
181
|
+
return detectedFromBytes;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (detectedFromBytes) {
|
|
185
|
+
return detectedFromBytes;
|
|
186
|
+
}
|
|
187
|
+
const detectedFromFilename = detectImageMimeTypeFromFilename(filename);
|
|
188
|
+
if (detectedFromFilename) {
|
|
189
|
+
return detectedFromFilename;
|
|
190
|
+
}
|
|
191
|
+
return normalizedMimeType || 'application/octet-stream';
|
|
192
|
+
}
|
|
193
|
+
function isTrustedDeclaredImageMimeType(declaredMimeType, detectedMimeType) {
|
|
194
|
+
if (!detectedMimeType) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
if (declaredMimeType === detectedMimeType) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
if (declaredMimeType === 'image/jpg' && detectedMimeType === 'image/jpeg') {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
return (declaredMimeType === 'image/vnd.microsoft.icon' &&
|
|
204
|
+
detectedMimeType === 'image/x-icon');
|
|
205
|
+
}
|
|
206
|
+
function detectImageMimeTypeFromBytes(bytes) {
|
|
207
|
+
var _a, _b, _c, _d, _e;
|
|
208
|
+
return ((_e = (_d = (_c = (_b = (_a = detectPngMimeType(bytes)) !== null && _a !== void 0 ? _a : detectJpegMimeType(bytes)) !== null && _b !== void 0 ? _b : detectGifMimeType(bytes)) !== null && _c !== void 0 ? _c : detectWebpMimeType(bytes)) !== null && _d !== void 0 ? _d : detectIconMimeType(bytes)) !== null && _e !== void 0 ? _e : detectSvgMimeType(bytes));
|
|
209
|
+
}
|
|
210
|
+
function detectPngMimeType(bytes) {
|
|
211
|
+
if (startsWithBytes(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
|
|
212
|
+
return 'image/png';
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function detectJpegMimeType(bytes) {
|
|
216
|
+
if (startsWithBytes(bytes, [0xff, 0xd8, 0xff])) {
|
|
217
|
+
return 'image/jpeg';
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function detectGifMimeType(bytes) {
|
|
221
|
+
const firstSix = bytesToAscii(bytes, 0, 6);
|
|
222
|
+
if (firstSix === 'GIF87a' || firstSix === 'GIF89a') {
|
|
223
|
+
return 'image/gif';
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function detectWebpMimeType(bytes) {
|
|
227
|
+
const riff = bytesToAscii(bytes, 0, 4);
|
|
228
|
+
const webp = bytesToAscii(bytes, 8, 12);
|
|
229
|
+
if (riff === 'RIFF' && webp === 'WEBP') {
|
|
230
|
+
return 'image/webp';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function detectIconMimeType(bytes) {
|
|
234
|
+
if (startsWithBytes(bytes, [0x00, 0x00, 0x01, 0x00])) {
|
|
235
|
+
return 'image/x-icon';
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function detectSvgMimeType(bytes) {
|
|
239
|
+
if (bytes.length < 5) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const utf8Prefix = new TextDecoder('utf8', { fatal: false }).decode(bytes.slice(0, Math.min(bytes.length, 256)));
|
|
243
|
+
const normalizedPrefix = utf8Prefix.trimStart().toLowerCase();
|
|
244
|
+
if (normalizedPrefix.startsWith('<svg') ||
|
|
245
|
+
(normalizedPrefix.startsWith('<?xml') &&
|
|
246
|
+
normalizedPrefix.includes('<svg'))) {
|
|
247
|
+
return 'image/svg+xml';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function startsWithBytes(bytes, prefix) {
|
|
251
|
+
if (bytes.length < prefix.length) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
return prefix.every((value, index) => bytes[index] === value);
|
|
255
|
+
}
|
|
256
|
+
function bytesToAscii(bytes, start, endExclusive) {
|
|
257
|
+
if (bytes.length < endExclusive) {
|
|
258
|
+
return '';
|
|
259
|
+
}
|
|
260
|
+
return String.fromCodePoint(...bytes.slice(start, endExclusive));
|
|
261
|
+
}
|
|
262
|
+
function detectImageMimeTypeFromFilename(filename) {
|
|
263
|
+
const normalized = filename === null || filename === void 0 ? void 0 : filename.trim().toLowerCase();
|
|
264
|
+
if (!normalized || !normalized.includes('.')) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const extension = normalized.slice(normalized.lastIndexOf('.') + 1);
|
|
268
|
+
const mimeTypes = {
|
|
269
|
+
png: 'image/png',
|
|
270
|
+
jpg: 'image/jpeg',
|
|
271
|
+
jpeg: 'image/jpeg',
|
|
272
|
+
gif: 'image/gif',
|
|
273
|
+
webp: 'image/webp',
|
|
274
|
+
svg: 'image/svg+xml',
|
|
275
|
+
ico: 'image/x-icon',
|
|
276
|
+
bmp: 'image/bmp',
|
|
277
|
+
};
|
|
278
|
+
return mimeTypes[extension];
|
|
279
|
+
}
|
|
154
280
|
function isNonEmptyString(value) {
|
|
155
281
|
return typeof value === 'string' && value.length > 0;
|
|
156
282
|
}
|
|
@@ -208,3 +334,13 @@ function quoteDisplayNameIfNeeded(name) {
|
|
|
208
334
|
const escaped = name.replaceAll('\\', '\\\\').replaceAll('"', '\\' + '"');
|
|
209
335
|
return `"${escaped}"`;
|
|
210
336
|
}
|
|
337
|
+
export const emailLoaderHelpers = {
|
|
338
|
+
normalizeContentId,
|
|
339
|
+
decodeCidReference,
|
|
340
|
+
replaceCidReferences,
|
|
341
|
+
getAttachmentBytes,
|
|
342
|
+
detectImageMimeTypeFromBytes,
|
|
343
|
+
detectImageMimeTypeFromFilename,
|
|
344
|
+
resolveDataUrlMimeType,
|
|
345
|
+
extractAttachments,
|
|
346
|
+
};
|
|
@@ -4876,11 +4876,13 @@ function extractAttachments(email) {
|
|
|
4876
4876
|
const cidUrlById = new Map();
|
|
4877
4877
|
for (const attachment of email.attachments || []) {
|
|
4878
4878
|
const contentId = normalizeContentId(attachment.contentId);
|
|
4879
|
-
const
|
|
4879
|
+
const hasContentId = Boolean(contentId);
|
|
4880
|
+
const isInline = (hasContentId && attachment.disposition !== 'attachment') ||
|
|
4881
|
+
attachment.related ||
|
|
4882
|
+
attachment.disposition === 'inline';
|
|
4883
|
+
const contentBytes = getAttachmentBytes(attachment.content);
|
|
4880
4884
|
if (!isInline) {
|
|
4881
|
-
const size =
|
|
4882
|
-
? attachment.content.byteLength
|
|
4883
|
-
: undefined;
|
|
4885
|
+
const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
|
|
4884
4886
|
attachments.push({
|
|
4885
4887
|
filename: attachment.filename || undefined,
|
|
4886
4888
|
mimeType: attachment.mimeType || undefined,
|
|
@@ -4888,11 +4890,9 @@ function extractAttachments(email) {
|
|
|
4888
4890
|
});
|
|
4889
4891
|
continue;
|
|
4890
4892
|
}
|
|
4891
|
-
const
|
|
4892
|
-
if (!contentId || !
|
|
4893
|
-
const size =
|
|
4894
|
-
? attachment.content.byteLength
|
|
4895
|
-
: undefined;
|
|
4893
|
+
const hasBinaryContent = Boolean(contentBytes);
|
|
4894
|
+
if (!contentId || !hasBinaryContent) {
|
|
4895
|
+
const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
|
|
4896
4896
|
attachments.push({
|
|
4897
4897
|
filename: attachment.filename || undefined,
|
|
4898
4898
|
mimeType: attachment.mimeType || 'application/octet-stream',
|
|
@@ -4900,8 +4900,8 @@ function extractAttachments(email) {
|
|
|
4900
4900
|
});
|
|
4901
4901
|
continue;
|
|
4902
4902
|
}
|
|
4903
|
-
const mimeType = attachment.mimeType
|
|
4904
|
-
const base64 =
|
|
4903
|
+
const mimeType = resolveDataUrlMimeType(attachment.mimeType, contentBytes, attachment.filename);
|
|
4904
|
+
const base64 = byteArrayToBase64(contentBytes);
|
|
4905
4905
|
const dataUrl = `data:${mimeType};base64,${base64}`;
|
|
4906
4906
|
cidUrlById.set(contentId, dataUrl);
|
|
4907
4907
|
}
|
|
@@ -4928,6 +4928,9 @@ function normalizeContentId(contentId) {
|
|
|
4928
4928
|
return '';
|
|
4929
4929
|
}
|
|
4930
4930
|
let normalized = contentId.trim();
|
|
4931
|
+
if (normalized.toLowerCase().startsWith('cid:')) {
|
|
4932
|
+
normalized = normalized.slice(4).trim();
|
|
4933
|
+
}
|
|
4931
4934
|
if (normalized.startsWith('<')) {
|
|
4932
4935
|
normalized = normalized.slice(1);
|
|
4933
4936
|
}
|
|
@@ -4941,7 +4944,7 @@ function replaceCidReferences(html, cidUrlById) {
|
|
|
4941
4944
|
return html;
|
|
4942
4945
|
}
|
|
4943
4946
|
return html.replaceAll(/(src\s*=\s*["']?)cid:([^"'\s>]+)(["']?)/gi, (match, prefix, cid, suffix) => {
|
|
4944
|
-
const normalized = normalizeContentId(cid);
|
|
4947
|
+
const normalized = normalizeContentId(decodeCidReference(cid));
|
|
4945
4948
|
const replacement = cidUrlById.get(normalized);
|
|
4946
4949
|
if (!replacement) {
|
|
4947
4950
|
return match;
|
|
@@ -4949,8 +4952,24 @@ function replaceCidReferences(html, cidUrlById) {
|
|
|
4949
4952
|
return `${prefix}${replacement}${suffix}`;
|
|
4950
4953
|
});
|
|
4951
4954
|
}
|
|
4952
|
-
function
|
|
4953
|
-
|
|
4955
|
+
function decodeCidReference(cid) {
|
|
4956
|
+
try {
|
|
4957
|
+
return decodeURIComponent(cid);
|
|
4958
|
+
}
|
|
4959
|
+
catch (_a) {
|
|
4960
|
+
return cid;
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
function getAttachmentBytes(content) {
|
|
4964
|
+
if (content instanceof ArrayBuffer) {
|
|
4965
|
+
return new Uint8Array(content);
|
|
4966
|
+
}
|
|
4967
|
+
if (ArrayBuffer.isView(content)) {
|
|
4968
|
+
return new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
4969
|
+
}
|
|
4970
|
+
return undefined;
|
|
4971
|
+
}
|
|
4972
|
+
function byteArrayToBase64(bytes) {
|
|
4954
4973
|
if (typeof btoa === 'function') {
|
|
4955
4974
|
let binary = '';
|
|
4956
4975
|
for (const byte of bytes) {
|
|
@@ -4961,6 +4980,113 @@ function arrayBufferToBase64(buffer) {
|
|
|
4961
4980
|
// Jest/Node fallback
|
|
4962
4981
|
return globalThis.Buffer.from(bytes).toString('base64');
|
|
4963
4982
|
}
|
|
4983
|
+
function resolveDataUrlMimeType(mimeType, bytes, filename) {
|
|
4984
|
+
const normalizedMimeType = typeof mimeType === 'string' ? mimeType.trim().toLowerCase() : '';
|
|
4985
|
+
const detectedFromBytes = detectImageMimeTypeFromBytes(bytes);
|
|
4986
|
+
if (normalizedMimeType.startsWith('image/')) {
|
|
4987
|
+
if (isTrustedDeclaredImageMimeType(normalizedMimeType, detectedFromBytes)) {
|
|
4988
|
+
return normalizedMimeType;
|
|
4989
|
+
}
|
|
4990
|
+
if (detectedFromBytes) {
|
|
4991
|
+
return detectedFromBytes;
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
if (detectedFromBytes) {
|
|
4995
|
+
return detectedFromBytes;
|
|
4996
|
+
}
|
|
4997
|
+
const detectedFromFilename = detectImageMimeTypeFromFilename(filename);
|
|
4998
|
+
if (detectedFromFilename) {
|
|
4999
|
+
return detectedFromFilename;
|
|
5000
|
+
}
|
|
5001
|
+
return normalizedMimeType || 'application/octet-stream';
|
|
5002
|
+
}
|
|
5003
|
+
function isTrustedDeclaredImageMimeType(declaredMimeType, detectedMimeType) {
|
|
5004
|
+
if (!detectedMimeType) {
|
|
5005
|
+
return true;
|
|
5006
|
+
}
|
|
5007
|
+
if (declaredMimeType === detectedMimeType) {
|
|
5008
|
+
return true;
|
|
5009
|
+
}
|
|
5010
|
+
if (declaredMimeType === 'image/jpg' && detectedMimeType === 'image/jpeg') {
|
|
5011
|
+
return true;
|
|
5012
|
+
}
|
|
5013
|
+
return (declaredMimeType === 'image/vnd.microsoft.icon' &&
|
|
5014
|
+
detectedMimeType === 'image/x-icon');
|
|
5015
|
+
}
|
|
5016
|
+
function detectImageMimeTypeFromBytes(bytes) {
|
|
5017
|
+
var _a, _b, _c, _d, _e;
|
|
5018
|
+
return ((_e = (_d = (_c = (_b = (_a = detectPngMimeType(bytes)) !== null && _a !== void 0 ? _a : detectJpegMimeType(bytes)) !== null && _b !== void 0 ? _b : detectGifMimeType(bytes)) !== null && _c !== void 0 ? _c : detectWebpMimeType(bytes)) !== null && _d !== void 0 ? _d : detectIconMimeType(bytes)) !== null && _e !== void 0 ? _e : detectSvgMimeType(bytes));
|
|
5019
|
+
}
|
|
5020
|
+
function detectPngMimeType(bytes) {
|
|
5021
|
+
if (startsWithBytes(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
|
|
5022
|
+
return 'image/png';
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
function detectJpegMimeType(bytes) {
|
|
5026
|
+
if (startsWithBytes(bytes, [0xff, 0xd8, 0xff])) {
|
|
5027
|
+
return 'image/jpeg';
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
function detectGifMimeType(bytes) {
|
|
5031
|
+
const firstSix = bytesToAscii(bytes, 0, 6);
|
|
5032
|
+
if (firstSix === 'GIF87a' || firstSix === 'GIF89a') {
|
|
5033
|
+
return 'image/gif';
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
5036
|
+
function detectWebpMimeType(bytes) {
|
|
5037
|
+
const riff = bytesToAscii(bytes, 0, 4);
|
|
5038
|
+
const webp = bytesToAscii(bytes, 8, 12);
|
|
5039
|
+
if (riff === 'RIFF' && webp === 'WEBP') {
|
|
5040
|
+
return 'image/webp';
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
5043
|
+
function detectIconMimeType(bytes) {
|
|
5044
|
+
if (startsWithBytes(bytes, [0x00, 0x00, 0x01, 0x00])) {
|
|
5045
|
+
return 'image/x-icon';
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
function detectSvgMimeType(bytes) {
|
|
5049
|
+
if (bytes.length < 5) {
|
|
5050
|
+
return;
|
|
5051
|
+
}
|
|
5052
|
+
const utf8Prefix = new TextDecoder('utf8', { fatal: false }).decode(bytes.slice(0, Math.min(bytes.length, 256)));
|
|
5053
|
+
const normalizedPrefix = utf8Prefix.trimStart().toLowerCase();
|
|
5054
|
+
if (normalizedPrefix.startsWith('<svg') ||
|
|
5055
|
+
(normalizedPrefix.startsWith('<?xml') &&
|
|
5056
|
+
normalizedPrefix.includes('<svg'))) {
|
|
5057
|
+
return 'image/svg+xml';
|
|
5058
|
+
}
|
|
5059
|
+
}
|
|
5060
|
+
function startsWithBytes(bytes, prefix) {
|
|
5061
|
+
if (bytes.length < prefix.length) {
|
|
5062
|
+
return false;
|
|
5063
|
+
}
|
|
5064
|
+
return prefix.every((value, index) => bytes[index] === value);
|
|
5065
|
+
}
|
|
5066
|
+
function bytesToAscii(bytes, start, endExclusive) {
|
|
5067
|
+
if (bytes.length < endExclusive) {
|
|
5068
|
+
return '';
|
|
5069
|
+
}
|
|
5070
|
+
return String.fromCodePoint(...bytes.slice(start, endExclusive));
|
|
5071
|
+
}
|
|
5072
|
+
function detectImageMimeTypeFromFilename(filename) {
|
|
5073
|
+
const normalized = filename === null || filename === void 0 ? void 0 : filename.trim().toLowerCase();
|
|
5074
|
+
if (!normalized || !normalized.includes('.')) {
|
|
5075
|
+
return;
|
|
5076
|
+
}
|
|
5077
|
+
const extension = normalized.slice(normalized.lastIndexOf('.') + 1);
|
|
5078
|
+
const mimeTypes = {
|
|
5079
|
+
png: 'image/png',
|
|
5080
|
+
jpg: 'image/jpeg',
|
|
5081
|
+
jpeg: 'image/jpeg',
|
|
5082
|
+
gif: 'image/gif',
|
|
5083
|
+
webp: 'image/webp',
|
|
5084
|
+
svg: 'image/svg+xml',
|
|
5085
|
+
ico: 'image/x-icon',
|
|
5086
|
+
bmp: 'image/bmp',
|
|
5087
|
+
};
|
|
5088
|
+
return mimeTypes[extension];
|
|
5089
|
+
}
|
|
4964
5090
|
function isNonEmptyString(value) {
|
|
4965
5091
|
return typeof value === 'string' && value.length > 0;
|
|
4966
5092
|
}
|