@schemavaults/send-email 0.4.0 → 0.4.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/README.md +6 -6
- package/dist/cli.js +38 -5
- package/dist/send-email-request-body-schema.js +16 -1
- package/dist/send-email-request-body-schema.js.map +1 -1
- package/dist/validators/email-attachment-schema.d.ts +7 -4
- package/dist/validators/email-attachment-schema.js +35 -7
- package/dist/validators/email-attachment-schema.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,9 +121,9 @@ const attachment = createEmailAttachment({
|
|
|
121
121
|
// => { filename: "logo.png", content: "iVBORw0KGgo...", contentType: "image/png", contentId: "logo" }
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
Setting `contentId` marks the attachment as inline so the HTML body can embed it via a `cid:` URL; pass the bare identifier without angle brackets. `contentType` is optional -- when omitted, the mail-server's transport derives it from the filename's extension.
|
|
124
|
+
Setting `contentId` marks the attachment as inline so the HTML body can embed it via a `cid:` URL; pass the bare identifier without angle brackets, in printable ASCII. `contentType` is optional -- when omitted, the mail-server's transport derives it from the filename's extension. Non-ASCII names belong in `filename`, not in a `contentType` parameter.
|
|
125
125
|
|
|
126
|
-
Limits enforced client-side (and exported as `MAX_ATTACHMENTS_PER_EMAIL` / `MAX_TOTAL_ATTACHMENT_BYTES`): at most 20 attachments per email, totalling at most 25 MiB once decoded. Filenames may not contain path separators or control characters. The mail-server, its hosting platform, and the receiving mail systems may enforce lower limits of their own.
|
|
126
|
+
Limits enforced client-side (and exported as `MAX_ATTACHMENTS_PER_EMAIL` / `MAX_TOTAL_ATTACHMENT_BYTES`): at most 20 attachments per email, totalling at most 25 MiB once decoded. Filenames may not contain path separators, control characters, or bidirectional formatting characters (which can disguise `invoice\u202Efdp.exe` as `invoiceexe.pdf`). `contentType` and `contentId` must be printable ASCII, and `subject` must not contain line breaks or other control characters, so no value can break out of its MIME header. The mail-server, its hosting platform, and the receiving mail systems may enforce lower limits of their own.
|
|
127
127
|
|
|
128
128
|
From the CLI, `--attach <path>` is repeatable on both `send` and `send-to-mailing-list`, and may be combined with `--body-file` (the files are appended to any `attachments` already in the JSON):
|
|
129
129
|
|
|
@@ -286,15 +286,15 @@ type Message =
|
|
|
286
286
|
| { text: string; html: string }; // both required
|
|
287
287
|
|
|
288
288
|
type EmailAttachment = {
|
|
289
|
-
filename: string; // 1-255 chars; no path separators or
|
|
289
|
+
filename: string; // 1-255 chars; no path separators, control or bidi formatting characters
|
|
290
290
|
content: string; // base64-encoded bytes (standard alphabet, padded)
|
|
291
|
-
contentType?: string; // MIME type; derived from the filename when omitted
|
|
292
|
-
contentId?: string; // marks the attachment inline; reference as cid:<contentId>
|
|
291
|
+
contentType?: string; // MIME type, printable ASCII; derived from the filename when omitted
|
|
292
|
+
contentId?: string; // printable ASCII; marks the attachment inline; reference as cid:<contentId>
|
|
293
293
|
};
|
|
294
294
|
|
|
295
295
|
type SendEmailRequestBody = {
|
|
296
296
|
to: string | string[]; // email address(es) or mailing list UUID
|
|
297
|
-
subject: string;
|
|
297
|
+
subject: string; // no line breaks or control characters (tabs allowed)
|
|
298
298
|
message: Message;
|
|
299
299
|
from?: string; // defaults to mail-server's configured sender
|
|
300
300
|
replyTo?: string;
|
package/dist/cli.js
CHANGED
|
@@ -18021,7 +18021,27 @@ function base64DecodedByteLength(base643) {
|
|
|
18021
18021
|
function hasControlCharacters(value) {
|
|
18022
18022
|
for (let i = 0; i < value.length; i++) {
|
|
18023
18023
|
const code = value.charCodeAt(i);
|
|
18024
|
-
if (code < 32 || code
|
|
18024
|
+
if (code < 32 || code >= 127 && code <= 159) return true;
|
|
18025
|
+
}
|
|
18026
|
+
return false;
|
|
18027
|
+
}
|
|
18028
|
+
var BIDI_FORMATTING = /* @__PURE__ */ new Set([
|
|
18029
|
+
1564,
|
|
18030
|
+
8206,
|
|
18031
|
+
8207,
|
|
18032
|
+
8234,
|
|
18033
|
+
8235,
|
|
18034
|
+
8236,
|
|
18035
|
+
8237,
|
|
18036
|
+
8238,
|
|
18037
|
+
8294,
|
|
18038
|
+
8295,
|
|
18039
|
+
8296,
|
|
18040
|
+
8297
|
|
18041
|
+
]);
|
|
18042
|
+
function hasBidiFormatting(value) {
|
|
18043
|
+
for (let i = 0; i < value.length; i++) {
|
|
18044
|
+
if (BIDI_FORMATTING.has(value.charCodeAt(i))) return true;
|
|
18025
18045
|
}
|
|
18026
18046
|
return false;
|
|
18027
18047
|
}
|
|
@@ -18031,18 +18051,22 @@ var attachmentFilenameSchema = external_exports.string().min(1, "Attachment file
|
|
|
18031
18051
|
message: "Attachment filename must not contain path separators!"
|
|
18032
18052
|
}).refine((name) => !hasControlCharacters(name), {
|
|
18033
18053
|
message: "Attachment filename must not contain control characters!"
|
|
18054
|
+
}).refine((name) => !hasBidiFormatting(name), {
|
|
18055
|
+
message: "Attachment filename must not contain bidirectional formatting characters!"
|
|
18034
18056
|
});
|
|
18035
18057
|
var attachmentContentSchema = external_exports.base64("Attachment content must be base64-encoded!").min(1, "Attachment content must not be empty!");
|
|
18036
18058
|
var MIME_TOKEN = "[A-Za-z0-9!#$&^_.+-]+";
|
|
18059
|
+
var MIME_PARAM_VALUE = '(?:"[\\x20\\x21\\x23-\\x7E]*"|[\\x21\\x23-\\x3A\\x3C-\\x7E]+)';
|
|
18037
18060
|
var attachmentContentTypeSchema = external_exports.string().min(1, "Attachment content type must be non-empty!").max(255, "Attachment content type must be at most 255 characters!").regex(
|
|
18061
|
+
// `[ \t]*`, not `\s*`: `\s` matches CR/LF.
|
|
18038
18062
|
new RegExp(
|
|
18039
|
-
`^${MIME_TOKEN}\\/${MIME_TOKEN}(
|
|
18063
|
+
`^${MIME_TOKEN}\\/${MIME_TOKEN}(?:[ \\t]*;[ \\t]*${MIME_TOKEN}=${MIME_PARAM_VALUE})*$`
|
|
18040
18064
|
),
|
|
18041
18065
|
"Attachment content type must be a MIME type such as 'application/pdf'!"
|
|
18042
18066
|
);
|
|
18043
18067
|
var attachmentContentIdSchema = external_exports.string().min(1, "Attachment content ID must be non-empty!").max(255, "Attachment content ID must be at most 255 characters!").regex(
|
|
18044
|
-
/^[
|
|
18045
|
-
"Attachment content ID must
|
|
18068
|
+
/^[\x21-\x3B\x3D\x3F-\x7E]+$/,
|
|
18069
|
+
"Attachment content ID must be printable ASCII without spaces or angle brackets!"
|
|
18046
18070
|
);
|
|
18047
18071
|
var emailAttachmentSchema = external_exports.object({
|
|
18048
18072
|
filename: attachmentFilenameSchema,
|
|
@@ -18084,6 +18108,13 @@ var sendRawEmailOptions = external_exports.object({
|
|
|
18084
18108
|
html: true
|
|
18085
18109
|
}).strict();
|
|
18086
18110
|
var MAX_RECIPIENTS = 50;
|
|
18111
|
+
function hasHeaderUnsafeCharacters(value) {
|
|
18112
|
+
for (let i = 0; i < value.length; i++) {
|
|
18113
|
+
const code = value.charCodeAt(i);
|
|
18114
|
+
if (code < 32 && code !== 9 || code === 127) return true;
|
|
18115
|
+
}
|
|
18116
|
+
return false;
|
|
18117
|
+
}
|
|
18087
18118
|
function createRecipientSchema(allow_mailing_list_ids_as_recipients = false) {
|
|
18088
18119
|
if (allow_mailing_list_ids_as_recipients) {
|
|
18089
18120
|
return external_exports.union([
|
|
@@ -18107,7 +18138,9 @@ function createSendEmailRequestBodySchema(allow_mailing_list_ids_as_recipients =
|
|
|
18107
18138
|
return external_exports.object({
|
|
18108
18139
|
to: createRecipientSchema(allow_mailing_list_ids_as_recipients),
|
|
18109
18140
|
from: external_exports.string().email().optional(),
|
|
18110
|
-
subject: external_exports.string().nonempty(),
|
|
18141
|
+
subject: external_exports.string().nonempty().refine((subject) => !hasHeaderUnsafeCharacters(subject), {
|
|
18142
|
+
message: "Subject must not contain line breaks or control characters!"
|
|
18143
|
+
}),
|
|
18111
18144
|
message: external_exports.union([sendEmailTemplateOptions, sendRawEmailOptions]),
|
|
18112
18145
|
replyTo: external_exports.string().email().optional(),
|
|
18113
18146
|
cc: external_exports.union([
|
|
@@ -22,6 +22,16 @@ const sendRawEmailOptions = z
|
|
|
22
22
|
})
|
|
23
23
|
.strict();
|
|
24
24
|
const MAX_RECIPIENTS = 50;
|
|
25
|
+
// C0 controls except HTAB, plus DEL: a line break in a header value can
|
|
26
|
+
// start a new header (e.g. `Bcc:`).
|
|
27
|
+
function hasHeaderUnsafeCharacters(value) {
|
|
28
|
+
for (let i = 0; i < value.length; i++) {
|
|
29
|
+
const code = value.charCodeAt(i);
|
|
30
|
+
if ((code < 0x20 && code !== 0x09) || code === 0x7f)
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
25
35
|
export function createRecipientSchema(allow_mailing_list_ids_as_recipients = false) {
|
|
26
36
|
if (allow_mailing_list_ids_as_recipients) {
|
|
27
37
|
return z.union([
|
|
@@ -47,7 +57,12 @@ export function createSendEmailRequestBodySchema(allow_mailing_list_ids_as_recip
|
|
|
47
57
|
.object({
|
|
48
58
|
to: createRecipientSchema(allow_mailing_list_ids_as_recipients),
|
|
49
59
|
from: z.string().email().optional(),
|
|
50
|
-
subject: z
|
|
60
|
+
subject: z
|
|
61
|
+
.string()
|
|
62
|
+
.nonempty()
|
|
63
|
+
.refine((subject) => !hasHeaderUnsafeCharacters(subject), {
|
|
64
|
+
message: "Subject must not contain line breaks or control characters!",
|
|
65
|
+
}),
|
|
51
66
|
message: z.union([sendEmailTemplateOptions, sendRawEmailOptions]),
|
|
52
67
|
replyTo: z.string().email().optional(),
|
|
53
68
|
cc: z
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-email-request-body-schema.js","sourceRoot":"","sources":["../src/send-email-request-body-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAE9E,MAAM,wBAAwB,GAAG,CAAC;KAC/B,MAAM,CAAC;IACN,WAAW,EAAE,qBAAqB;IAClC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE;CAC5B,CAAC;KACD,QAAQ,CAAC;IACR,WAAW,EAAE,IAAI;CAClB,CAAC;KACD,MAAM,EAAE,CAAC;AAEZ,MAAM,mBAAmB,GAAG,CAAC;KAC1B,MAAM,CAAC;IACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC;KACD,QAAQ,CAAC;IACR,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;CACX,CAAC;KACD,MAAM,EAAE,CAAC;AAEZ,MAAM,cAAc,GAAW,EAAE,CAAC;AAElC,MAAM,UAAU,qBAAqB,CACnC,uCAAgD,KAAK;IAErD,IAAI,oCAAoC,EAAE,CAAC;QACzC,OAAO,CAAC,CAAC,KAAK,CAAC;YACb,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,mBAAmB;YACvC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,kBAAkB;YACzE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,+BAA+B;SACnD,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,CAAC,KAAK,CAAC;YACb,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,mBAAmB;YACvC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,kBAAkB;SAC1E,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gCAAgC,CAC9C,uCAAgD,KAAK;IAErD,OAAO,CAAC;SACL,MAAM,CAAC;QACN,EAAE,EAAE,qBAAqB,CAAC,oCAAoC,CAAC;QAC/D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;QACnC,OAAO,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"send-email-request-body-schema.js","sourceRoot":"","sources":["../src/send-email-request-body-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,7 +18,8 @@ export declare const MAX_TOTAL_ATTACHMENT_BYTES: number;
|
|
|
18
18
|
export declare function base64DecodedByteLength(base64: string): number;
|
|
19
19
|
/**
|
|
20
20
|
* Name the recipient's mail client shows for the attached file. Unicode is
|
|
21
|
-
* allowed; path separators
|
|
21
|
+
* allowed; path separators, control characters and bidirectional formatting
|
|
22
|
+
* characters are not.
|
|
22
23
|
*/
|
|
23
24
|
export declare const attachmentFilenameSchema: z.ZodString;
|
|
24
25
|
/**
|
|
@@ -29,14 +30,16 @@ export declare const attachmentFilenameSchema: z.ZodString;
|
|
|
29
30
|
export declare const attachmentContentSchema: z.ZodBase64;
|
|
30
31
|
/**
|
|
31
32
|
* MIME type of the attachment, e.g. "application/pdf" or
|
|
32
|
-
* "text/csv; charset=utf-8". When omitted, the
|
|
33
|
-
* derives it from the filename's extension.
|
|
33
|
+
* "text/csv; charset=utf-8". Printable ASCII only. When omitted, the
|
|
34
|
+
* mail-server's transport derives it from the filename's extension.
|
|
34
35
|
*/
|
|
35
36
|
export declare const attachmentContentTypeSchema: z.ZodString;
|
|
36
37
|
/**
|
|
37
38
|
* Content-ID for inline attachments. Setting it marks the attachment as
|
|
38
39
|
* inline so the HTML body can reference it as `<img src="cid:<contentId>">`.
|
|
39
|
-
* Pass the bare identifier -- without the angle brackets.
|
|
40
|
+
* Pass the bare identifier -- without the angle brackets. Content-ID is an
|
|
41
|
+
* ASCII header; anything else is emitted as an encoded-word that no longer
|
|
42
|
+
* matches the HTML's `cid:` reference.
|
|
40
43
|
*/
|
|
41
44
|
export declare const attachmentContentIdSchema: z.ZodString;
|
|
42
45
|
/**
|
|
@@ -25,17 +25,34 @@ export function base64DecodedByteLength(base64) {
|
|
|
25
25
|
padding = 1;
|
|
26
26
|
return Math.floor((base64.length * 3) / 4) - padding;
|
|
27
27
|
}
|
|
28
|
+
// C0, DEL and C1 controls.
|
|
28
29
|
function hasControlCharacters(value) {
|
|
29
30
|
for (let i = 0; i < value.length; i++) {
|
|
30
31
|
const code = value.charCodeAt(i);
|
|
31
|
-
if (code < 0x20 || code
|
|
32
|
+
if (code < 0x20 || (code >= 0x7f && code <= 0x9f))
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// Bidirectional formatting characters (ALM, LRM, RLM, LRE through RLO, LRI
|
|
38
|
+
// through PDI) let a filename like "invoice<U+202E>fdp.exe" display as
|
|
39
|
+
// "invoiceexe.pdf". Listed as code points because the characters are
|
|
40
|
+
// invisible in source.
|
|
41
|
+
const BIDI_FORMATTING = new Set([
|
|
42
|
+
0x061c, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d, 0x202e, 0x2066,
|
|
43
|
+
0x2067, 0x2068, 0x2069,
|
|
44
|
+
]);
|
|
45
|
+
function hasBidiFormatting(value) {
|
|
46
|
+
for (let i = 0; i < value.length; i++) {
|
|
47
|
+
if (BIDI_FORMATTING.has(value.charCodeAt(i)))
|
|
32
48
|
return true;
|
|
33
49
|
}
|
|
34
50
|
return false;
|
|
35
51
|
}
|
|
36
52
|
/**
|
|
37
53
|
* Name the recipient's mail client shows for the attached file. Unicode is
|
|
38
|
-
* allowed; path separators
|
|
54
|
+
* allowed; path separators, control characters and bidirectional formatting
|
|
55
|
+
* characters are not.
|
|
39
56
|
*/
|
|
40
57
|
export const attachmentFilenameSchema = z
|
|
41
58
|
.string()
|
|
@@ -49,6 +66,9 @@ export const attachmentFilenameSchema = z
|
|
|
49
66
|
})
|
|
50
67
|
.refine((name) => !hasControlCharacters(name), {
|
|
51
68
|
message: "Attachment filename must not contain control characters!",
|
|
69
|
+
})
|
|
70
|
+
.refine((name) => !hasBidiFormatting(name), {
|
|
71
|
+
message: "Attachment filename must not contain bidirectional formatting characters!",
|
|
52
72
|
});
|
|
53
73
|
/**
|
|
54
74
|
* The attached file's bytes, base64-encoded (standard alphabet, padded, no
|
|
@@ -59,26 +79,34 @@ export const attachmentContentSchema = z
|
|
|
59
79
|
.base64("Attachment content must be base64-encoded!")
|
|
60
80
|
.min(1, "Attachment content must not be empty!");
|
|
61
81
|
const MIME_TOKEN = "[A-Za-z0-9!#$&^_.+-]+";
|
|
82
|
+
// Printable ASCII only, so no value can carry CR/LF or other controls out of
|
|
83
|
+
// its header. Quoted: anything printable but `"`. Unquoted: printable minus
|
|
84
|
+
// space, `"` and `;`.
|
|
85
|
+
const MIME_PARAM_VALUE = '(?:"[\\x20\\x21\\x23-\\x7E]*"|[\\x21\\x23-\\x3A\\x3C-\\x7E]+)';
|
|
62
86
|
/**
|
|
63
87
|
* MIME type of the attachment, e.g. "application/pdf" or
|
|
64
|
-
* "text/csv; charset=utf-8". When omitted, the
|
|
65
|
-
* derives it from the filename's extension.
|
|
88
|
+
* "text/csv; charset=utf-8". Printable ASCII only. When omitted, the
|
|
89
|
+
* mail-server's transport derives it from the filename's extension.
|
|
66
90
|
*/
|
|
67
91
|
export const attachmentContentTypeSchema = z
|
|
68
92
|
.string()
|
|
69
93
|
.min(1, "Attachment content type must be non-empty!")
|
|
70
94
|
.max(255, "Attachment content type must be at most 255 characters!")
|
|
71
|
-
.regex(
|
|
95
|
+
.regex(
|
|
96
|
+
// `[ \t]*`, not `\s*`: `\s` matches CR/LF.
|
|
97
|
+
new RegExp(`^${MIME_TOKEN}\\/${MIME_TOKEN}(?:[ \\t]*;[ \\t]*${MIME_TOKEN}=${MIME_PARAM_VALUE})*$`), "Attachment content type must be a MIME type such as 'application/pdf'!");
|
|
72
98
|
/**
|
|
73
99
|
* Content-ID for inline attachments. Setting it marks the attachment as
|
|
74
100
|
* inline so the HTML body can reference it as `<img src="cid:<contentId>">`.
|
|
75
|
-
* Pass the bare identifier -- without the angle brackets.
|
|
101
|
+
* Pass the bare identifier -- without the angle brackets. Content-ID is an
|
|
102
|
+
* ASCII header; anything else is emitted as an encoded-word that no longer
|
|
103
|
+
* matches the HTML's `cid:` reference.
|
|
76
104
|
*/
|
|
77
105
|
export const attachmentContentIdSchema = z
|
|
78
106
|
.string()
|
|
79
107
|
.min(1, "Attachment content ID must be non-empty!")
|
|
80
108
|
.max(255, "Attachment content ID must be at most 255 characters!")
|
|
81
|
-
.regex(/^[
|
|
109
|
+
.regex(/^[\x21-\x3B\x3D\x3F-\x7E]+$/, "Attachment content ID must be printable ASCII without spaces or angle brackets!");
|
|
82
110
|
/**
|
|
83
111
|
* A single file attached to an email, in the shape accepted by the
|
|
84
112
|
* `@schemavaults/mail-server` `/api/send` route.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email-attachment-schema.js","sourceRoot":"","sources":["../../src/validators/email-attachment-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAW,EAAE,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAW,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEnE;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAClC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,CAAC;SAClC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;AACvD,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAa;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,IAAI,GAAG,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"email-attachment-schema.js","sourceRoot":"","sources":["../../src/validators/email-attachment-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAW,EAAE,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAW,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEnE;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAClC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,CAAC;SAClC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;AACvD,CAAC;AAED,2BAA2B;AAC3B,SAAS,oBAAoB,CAAC,KAAa;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACjE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,2EAA2E;AAC3E,uEAAuE;AACvE,qEAAqE;AACrE,uBAAuB;AACvB,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IACtE,MAAM,EAAE,MAAM,EAAE,MAAM;CACvB,CAAC,CAAC;AAEH,SAAS,iBAAiB,CAAC,KAAa;IACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IAC5D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC;KACtC,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,wCAAwC,CAAC;KAChD,GAAG,CAAC,GAAG,EAAE,qDAAqD,CAAC;KAC/D,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,EAAE;IAC/C,OAAO,EAAE,8CAA8C;CACxD,CAAC;KACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;IAC7D,OAAO,EAAE,uDAAuD;CACjE,CAAC;KACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE;IAC7C,OAAO,EAAE,0DAA0D;CACpE,CAAC;KACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE;IAC1C,OAAO,EACL,2EAA2E;CAC9E,CAAC,CAAC;AAEL;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,MAAM,CAAC,4CAA4C,CAAC;KACpD,GAAG,CAAC,CAAC,EAAE,uCAAuC,CAAC,CAAC;AAEnD,MAAM,UAAU,GAAG,uBAAuB,CAAC;AAC3C,6EAA6E;AAC7E,4EAA4E;AAC5E,sBAAsB;AACtB,MAAM,gBAAgB,GACpB,+DAA+D,CAAC;AAElE;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC;KACzC,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,4CAA4C,CAAC;KACpD,GAAG,CAAC,GAAG,EAAE,yDAAyD,CAAC;KACnE,KAAK;AACJ,2CAA2C;AAC3C,IAAI,MAAM,CACR,IAAI,UAAU,MAAM,UAAU,qBAAqB,UAAU,IAAI,gBAAgB,KAAK,CACvF,EACD,wEAAwE,CACzE,CAAC;AAEJ;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC;KACvC,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,0CAA0C,CAAC;KAClD,GAAG,CAAC,GAAG,EAAE,uDAAuD,CAAC;KACjE,KAAK,CACJ,6BAA6B,EAC7B,iFAAiF,CAClF,CAAC;AAEJ;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC;KACnC,MAAM,CAAC;IACN,QAAQ,EAAE,wBAAwB;IAClC,OAAO,EAAE,uBAAuB;IAChC,WAAW,EAAE,2BAA2B,CAAC,QAAQ,EAAE;IACnD,SAAS,EAAE,yBAAyB,CAAC,QAAQ,EAAE;CAChD,CAAC;KACD,QAAQ,CAAC;IACR,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,IAAI;CACd,CAAC;KACD,MAAM,EAAE,CAAC;AAIZ;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC;KACpC,KAAK,CAAC,qBAAqB,CAAC;KAC5B,GAAG,CAAC,CAAC,EAAE,kEAAkE,CAAC;KAC1E,GAAG,CACF,yBAAyB,EACzB,6BAA6B,yBAAyB,eAAe,CACtE;KACA,WAAW,CAAC,CAAC,WAAW,EAAE,GAAG,EAAE,EAAE;IAChC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,UAAU,IAAI,uBAAuB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,UAAU,GAAG,0BAA0B,EAAE,CAAC;QAC5C,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,kCAAkC,0BAA0B,WAAW,0BAA0B,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,gCAAgC,UAAU,SAAS;SAC9K,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,eAAe,qBAAqB,CAAC"}
|
package/package.json
CHANGED