@brandlab-digital/site-next 1.0.13 → 1.2.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/dist/analytics.d.ts +5 -0
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +7 -3
- package/dist/analytics.js.map +1 -1
- package/dist/contact/attachments-client.d.ts.map +1 -1
- package/dist/contact/attachments-client.js +36 -52
- package/dist/contact/attachments-client.js.map +1 -1
- package/dist/contact/client.d.ts +0 -2
- package/dist/contact/client.d.ts.map +1 -1
- package/dist/contact/client.js +1 -16
- package/dist/contact/client.js.map +1 -1
- package/dist/contact/contract.d.ts.map +1 -1
- package/dist/contact/contract.js +2 -2
- package/dist/contact/contract.js.map +1 -1
- package/dist/contact/image-sanitizer.d.ts +0 -1
- package/dist/contact/image-sanitizer.d.ts.map +1 -1
- package/dist/contact/image-sanitizer.js +4 -22
- package/dist/contact/image-sanitizer.js.map +1 -1
- package/dist/contact/index.d.ts +1 -1
- package/dist/contact/index.d.ts.map +1 -1
- package/dist/contact/index.js +1 -1
- package/dist/contact/index.js.map +1 -1
- package/dist/contact/pdf-sanitizer.d.ts +5 -0
- package/dist/contact/pdf-sanitizer.d.ts.map +1 -0
- package/dist/contact/pdf-sanitizer.js +39 -0
- package/dist/contact/pdf-sanitizer.js.map +1 -0
- package/dist/contact/server.d.ts.map +1 -1
- package/dist/contact/server.js +43 -8
- package/dist/contact/server.js.map +1 -1
- package/dist/instrumentation-client.d.ts +7 -1
- package/dist/instrumentation-client.d.ts.map +1 -1
- package/dist/instrumentation-client.js +5 -1
- package/dist/instrumentation-client.js.map +1 -1
- package/dist/ui-client.d.ts +1 -1
- package/dist/ui-client.d.ts.map +1 -1
- package/dist/ui-client.js +1 -1
- package/dist/ui-client.js.map +1 -1
- package/package.json +4 -10
- package/dist/image-runtime.d.ts +0 -2
- package/dist/image-runtime.d.ts.map +0 -1
- package/dist/image-runtime.js +0 -22
- package/dist/image-runtime.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/contact/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iCAAiC,EACjC,qBAAqB,EACrB,4BAA4B,EAC5B,8BAA8B,GAI/B,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EACL,qBAAqB,GAGtB,MAAM,aAAa,CAAA;AAWpB,MAAM,QAAQ,GAAgF;IAC5F,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,OAAO;IACZ,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;CACrB,CAAA;AAED,SAAS,+BAA+B,CAAC,KAAa;IACpD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;IACrE,CAAC;IACD,OAAO,8BAA8B,CAAC,KAAK,CAAC,CAAA;AAC9C,CAAC;AAED,MAAM,WAAW,GAAiE;IAChF,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE;IACzC,yBAAyB,EAAE,+BAA+B;CAC3D,CAAA;AAED,MAAM,UAAU,mCAAmC,CACjD,QAAiC,EACjC,OAAwC;IAExC,qBAAqB,CAAC,QAAQ,CAAC,CAAA;IAC/B,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,CAAA;IACzD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAA;QACvC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,0DAA0D,KAAK,CAAC,GAAG,IAAI,CAAC,CAAA;QACpG,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,GAAG;YACf,KAAK;YACL,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;YAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxE,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU;gBAC3B,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,YAAa,CAAC,EAAE;gBACpC,CAAC,CAAC,KAAK,CAAC,OAAO;oBACb,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE;oBAC5B,CAAC,CAAC,EAAE,CAAC;YACT,GAAG,CAAC,KAAK,CAAC,aAAa;gBACrB,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE;gBACjD,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,YAAY;gBAClC,CAAC,CAAC;oBACE,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE,CAC1B,4BAA4B,CAAC,KAAK,CAAC;wBACjC,CAAC,CAAC,SAAS;wBACX,CAAC,CAAC,wCAAwC;iBAC/C;gBACH,CAAC,CAAC,EAAE,CAAC;SACR,CAAA;IACH,CAAC,CAAC,CAAA;IACF,MAAM,WAAW,GACf,QAAQ,CAAC,WAAW,CAAC,IAAI,KAAK,MAAM;QAClC,CAAC,CAAC,KAAK;QACP,CAAC,CAAC;YACE,GAAG,iCAAiC;
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/contact/contract.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_CONTACT_ATTACHMENT_POLICY,\n DOCUMENT_CONTACT_ATTACHMENT_POLICY,\n defineContactContract,\n isValidAustralianPhoneNumber,\n normalizeAustralianPhoneDigits,\n type BrandlabContactContract,\n type ContactAttachmentPolicy,\n type ContactFieldNormalization,\n} from '@brandlab-digital/site-core/contact'\nimport {\n defineBrandlabContact,\n type BrandlabContactDefinition,\n type ContactFieldKind,\n} from './shared.js'\n\nexport type ContactRuntimeDefinitionOptions = Pick<\n BrandlabContactDefinition,\n 'brandName' | 'siteUrl' | 'deploymentMode' | 'bot'\n> & {\n labels: Readonly<Record<string, string>>\n attachment?: Partial<ContactAttachmentPolicy> & { fieldPrefix?: string }\n email?: NonNullable<BrandlabContactDefinition['email']>\n}\n\nconst KIND_MAP: Record<BrandlabContactContract['fields'][number]['kind'], ContactFieldKind> = {\n text: 'text',\n email: 'email',\n tel: 'phone',\n textarea: 'textarea',\n select: 'select',\n checkbox: 'checkbox',\n}\n\nfunction normalizeAustralianContactPhone(value: string) {\n if (!/^[+()\\d\\s.-]+$/.test(value)) {\n throw new Error('The phone value contains unsupported characters.')\n }\n return normalizeAustralianPhoneDigits(value)\n}\n\nconst NORMALIZERS: Record<ContactFieldNormalization, (value: string) => string> = {\n lowercase: (value) => value.toLowerCase(),\n 'australian-phone-digits': normalizeAustralianContactPhone,\n}\n\nexport function createContactDefinitionFromContract(\n contract: BrandlabContactContract,\n options: ContactRuntimeDefinitionOptions,\n) {\n defineContactContract(contract)\n const { labels, attachment, email, ...runtime } = options\n const fields = contract.fields.map((field) => {\n const label = labels[field.key]?.trim()\n if (!label) throw new Error(`A customer-facing label is required for contact field \"${field.key}\".`)\n return {\n name: field.key,\n label,\n role: field.role,\n kind: KIND_MAP[field.kind],\n required: field.required,\n ...(field.maxLength !== undefined ? { maxLength: field.maxLength } : {}),\n ...(field.kind === 'checkbox'\n ? { options: [field.checkedValue!] }\n : field.options\n ? { options: field.options }\n : {}),\n ...(field.normalization\n ? { normalize: NORMALIZERS[field.normalization] }\n : {}),\n ...(field.phoneRule === 'australian'\n ? {\n validate: (value: string) =>\n isValidAustralianPhoneNumber(value)\n ? undefined\n : 'Enter a valid Australian phone number.',\n }\n : {}),\n }\n })\n const attachments =\n contract.attachments.mode === 'none'\n ? false\n : {\n ...(contract.attachments.mode === 'images-and-pdfs-optional' ? DOCUMENT_CONTACT_ATTACHMENT_POLICY : DEFAULT_CONTACT_ATTACHMENT_POLICY),\n ...attachment,\n required: contract.attachments.mode === 'images-required',\n }\n\n return defineBrandlabContact({\n ...runtime,\n projectId: contract.projectId,\n fields,\n attachments,\n ...(email ? { email } : {}),\n })\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-sanitizer.d.ts","sourceRoot":"","sources":["../../src/contact/image-sanitizer.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"image-sanitizer.d.ts","sourceRoot":"","sources":["../../src/contact/image-sanitizer.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAIpB,OAAO,KAAK,EACV,0BAA0B,EAE3B,MAAM,aAAa,CAAA;AAqGpB;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,0BAA0B,CAiBhF"}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import 'server-only';
|
|
2
|
-
import '
|
|
2
|
+
import { sanitizeContactPdf } from './pdf-sanitizer.js';
|
|
3
3
|
import sharp, {} from 'sharp';
|
|
4
|
-
import { detectContactTransportAttachmentType } from '@brandlab-digital/site-core/contact';
|
|
5
|
-
sharp.block({ operation: ['VipsForeignLoadHeif'] });
|
|
6
4
|
const MAX_INPUT_PIXELS = 50_000_000;
|
|
7
5
|
const MAX_OUTPUT_DIMENSION = 4_096;
|
|
8
6
|
const PROCESSING_TIMEOUT_SECONDS = 5;
|
|
@@ -12,6 +10,8 @@ const INPUT_FORMAT_BY_MIME = Object.freeze({
|
|
|
12
10
|
'image/png': 'png',
|
|
13
11
|
'image/gif': 'gif',
|
|
14
12
|
'image/webp': 'webp',
|
|
13
|
+
'image/heic': 'heif',
|
|
14
|
+
'image/heif': 'heif',
|
|
15
15
|
});
|
|
16
16
|
function expectedInputFormat(contentType) {
|
|
17
17
|
return INPUT_FORMAT_BY_MIME[contentType.trim().toLowerCase()];
|
|
@@ -53,12 +53,6 @@ async function sanitizeOne(attachment) {
|
|
|
53
53
|
}
|
|
54
54
|
// Buffer.from(Uint8Array) copies untrusted request bytes before native work.
|
|
55
55
|
const input = Buffer.from(attachment.content);
|
|
56
|
-
const detected = detectContactTransportAttachmentType(input);
|
|
57
|
-
if (!detected || expectedInputFormat(detected) !== expectedFormat) {
|
|
58
|
-
throw new Error('The uploaded bytes do not match a supported image type.');
|
|
59
|
-
}
|
|
60
|
-
// Independent of Next's lazy optimizer initialization. Never unblock a loader.
|
|
61
|
-
sharp.block({ operation: ['VipsForeignLoadHeif'] });
|
|
62
56
|
const image = sharp(input, {
|
|
63
57
|
animated: false,
|
|
64
58
|
failOn: 'warning',
|
|
@@ -100,22 +94,10 @@ export function createBrandlabContactImageSanitizer() {
|
|
|
100
94
|
if (!Array.isArray(attachments))
|
|
101
95
|
return { ok: false };
|
|
102
96
|
try {
|
|
103
|
-
// Match the existing absolute request envelope; per-form limits are
|
|
104
|
-
// enforced by the server before and after processing.
|
|
105
|
-
if (attachments.length > 20)
|
|
106
|
-
return { ok: false };
|
|
107
|
-
let total = 0;
|
|
108
|
-
for (const attachment of attachments) {
|
|
109
|
-
if (!(attachment?.content instanceof Uint8Array))
|
|
110
|
-
return { ok: false };
|
|
111
|
-
total += attachment.content.byteLength;
|
|
112
|
-
if (attachment.content.byteLength < 1 || attachment.content.byteLength > 4_500_000 || total > 4_500_000)
|
|
113
|
-
return { ok: false };
|
|
114
|
-
}
|
|
115
97
|
const sanitized = [];
|
|
116
98
|
// Sequential processing bounds peak native memory for multiple photos.
|
|
117
99
|
for (const attachment of attachments) {
|
|
118
|
-
sanitized.push(await sanitizeOne(attachment));
|
|
100
|
+
sanitized.push(attachment.contentType === 'application/pdf' ? await sanitizeContactPdf(attachment) : await sanitizeOne(attachment));
|
|
119
101
|
}
|
|
120
102
|
return { ok: true, attachments: Object.freeze(sanitized) };
|
|
121
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-sanitizer.js","sourceRoot":"","sources":["../../src/contact/image-sanitizer.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,OAAO,
|
|
1
|
+
{"version":3,"file":"image-sanitizer.js","sourceRoot":"","sources":["../../src/contact/image-sanitizer.ts"],"names":[],"mappings":"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","sourcesContent":["import 'server-only'\n\nimport { sanitizeContactPdf } from './pdf-sanitizer.js'\nimport sharp, { type Metadata, type Sharp } from 'sharp'\nimport type {\n ContactAttachmentProcessor,\n ContactDeliveryAttachment,\n} from './shared.js'\n\nconst MAX_INPUT_PIXELS = 50_000_000\nconst MAX_OUTPUT_DIMENSION = 4_096\nconst PROCESSING_TIMEOUT_SECONDS = 5\n\nconst INPUT_FORMAT_BY_MIME = Object.freeze({\n 'image/jpeg': 'jpeg',\n 'image/jpg': 'jpeg',\n 'image/png': 'png',\n 'image/gif': 'gif',\n 'image/webp': 'webp',\n 'image/heic': 'heif',\n 'image/heif': 'heif',\n} as const)\n\nfunction expectedInputFormat(contentType: string) {\n return INPUT_FORMAT_BY_MIME[\n contentType.trim().toLowerCase() as keyof typeof INPUT_FORMAT_BY_MIME\n ]\n}\n\nfunction assertSingleImage(metadata: Metadata, expectedFormat: string) {\n if (\n metadata.format !== expectedFormat ||\n !Number.isSafeInteger(metadata.width) ||\n !Number.isSafeInteger(metadata.height) ||\n !metadata.width ||\n !metadata.height ||\n (metadata.pages ?? 1) !== 1\n ) {\n throw new Error('The uploaded file is not one supported still image.')\n }\n}\n\nfunction outputFilename(filename: string) {\n const stem = filename.replace(/\\.[a-z0-9]+$/i, '')\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(stem)) {\n throw new Error('The validated attachment filename is invalid.')\n }\n return `${stem}.jpg`\n}\n\nfunction outputPipeline(image: Sharp) {\n return image\n .autoOrient()\n .resize({\n width: MAX_OUTPUT_DIMENSION,\n height: MAX_OUTPUT_DIMENSION,\n fit: 'inside',\n withoutEnlargement: true,\n })\n .flatten({ background: '#ffffff' })\n .timeout({ seconds: PROCESSING_TIMEOUT_SECONDS })\n .jpeg({ quality: 85, progressive: true, mozjpeg: true })\n}\n\nasync function sanitizeOne(\n attachment: ContactDeliveryAttachment,\n): Promise<ContactDeliveryAttachment> {\n const expectedFormat = expectedInputFormat(attachment.contentType)\n if (!expectedFormat || !(attachment.content instanceof Uint8Array)) {\n throw new Error('The uploaded file type is unsupported.')\n }\n\n // Buffer.from(Uint8Array) copies untrusted request bytes before native work.\n const input = Buffer.from(attachment.content)\n const image = sharp(input, {\n animated: false,\n failOn: 'warning',\n limitInputPixels: MAX_INPUT_PIXELS,\n pages: 1,\n sequentialRead: true,\n unlimited: false,\n })\n const metadata = await image.metadata()\n assertSingleImage(metadata, expectedFormat)\n\n const { data, info } = await outputPipeline(image).toBuffer({\n resolveWithObject: true,\n })\n if (\n data.byteLength < 1 ||\n info.format !== 'jpeg' ||\n !Number.isSafeInteger(info.width) ||\n !Number.isSafeInteger(info.height) ||\n info.width < 1 ||\n info.height < 1 ||\n info.width > MAX_OUTPUT_DIMENSION ||\n info.height > MAX_OUTPUT_DIMENSION\n ) {\n throw new Error('The uploaded image could not be normalised safely.')\n }\n\n return Object.freeze({\n filename: outputFilename(attachment.filename),\n content: new Uint8Array(data),\n contentType: 'image/jpeg',\n })\n}\n\n/**\n * The production image boundary for Contact Contract v4. Every accepted image\n * is fully decoded, bounded, auto-oriented and re-encoded without source\n * metadata. Project code cannot replace this processor in production.\n */\nexport function createBrandlabContactImageSanitizer(): ContactAttachmentProcessor {\n return Object.freeze({\n capability: 'sanitizes-contact-images-v1' as const,\n async process({ attachments }: Parameters<ContactAttachmentProcessor['process']>[0]) {\n if (!Array.isArray(attachments)) return { ok: false as const }\n try {\n const sanitized: ContactDeliveryAttachment[] = []\n // Sequential processing bounds peak native memory for multiple photos.\n for (const attachment of attachments) {\n sanitized.push(attachment.contentType === 'application/pdf' ? await sanitizeContactPdf(attachment) : await sanitizeOne(attachment))\n }\n return { ok: true as const, attachments: Object.freeze(sanitized) }\n } catch {\n return { ok: false as const }\n }\n },\n })\n}\n"]}
|
package/dist/contact/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from './shared.js';
|
|
2
2
|
export * from './contract.js';
|
|
3
|
-
export { createContactHttpSubmissionController, normalizeContactUploadFile,
|
|
3
|
+
export { createContactHttpSubmissionController, normalizeContactUploadFile, parseContactActionResult, shouldTrackContactResult, trackDeliveredContactResult, type ContactHttpSubmissionControllerOptions, type ContactSubmissionController, type ContactSubmissionPhase, type ContactSubmissionState, } from './client.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contact/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,OAAO,EACL,qCAAqC,EACrC,0BAA0B,EAC1B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contact/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,OAAO,EACL,qCAAqC,EACrC,0BAA0B,EAC1B,wBAAwB,EACxB,wBAAwB,EACxB,2BAA2B,EAC3B,KAAK,sCAAsC,EAC3C,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAA"}
|
package/dist/contact/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from './shared.js';
|
|
2
2
|
export * from './contract.js';
|
|
3
|
-
export { createContactHttpSubmissionController, normalizeContactUploadFile,
|
|
3
|
+
export { createContactHttpSubmissionController, normalizeContactUploadFile, parseContactActionResult, shouldTrackContactResult, trackDeliveredContactResult, } from './client.js';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contact/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,OAAO,EACL,qCAAqC,EACrC,0BAA0B,EAC1B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contact/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,OAAO,EACL,qCAAqC,EACrC,0BAA0B,EAC1B,wBAAwB,EACxB,wBAAwB,EACxB,2BAA2B,GAK5B,MAAM,aAAa,CAAA","sourcesContent":["export * from './shared.js'\nexport * from './contract.js'\nexport {\n createContactHttpSubmissionController,\n normalizeContactUploadFile,\n parseContactActionResult,\n shouldTrackContactResult,\n trackDeliveredContactResult,\n type ContactHttpSubmissionControllerOptions,\n type ContactSubmissionController,\n type ContactSubmissionPhase,\n type ContactSubmissionState,\n} from './client.js'\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ContactDeliveryAttachment } from './shared.js';
|
|
2
|
+
/** Accept bounded, static plans only. Rebuild page graphics without annotations,
|
|
3
|
+
* document actions, attachments, forms, links or source metadata. */
|
|
4
|
+
export declare function sanitizeContactPdf(attachment: ContactDeliveryAttachment): Promise<ContactDeliveryAttachment>;
|
|
5
|
+
//# sourceMappingURL=pdf-sanitizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf-sanitizer.d.ts","sourceRoot":"","sources":["../../src/contact/pdf-sanitizer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAG5D;qEACqE;AACrE,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,yBAAyB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAwBlH"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { PDFDocument, PDFDict, PDFName, PDFArray } from 'pdf-lib';
|
|
2
|
+
const REJECTED_KEYS = new Set(['JS', 'JavaScript', 'AA', 'OpenAction', 'Launch', 'EmbeddedFiles', 'EmbeddedFile', 'XFA', 'RichMedia', 'SubmitForm', 'ImportData', 'GoToR', 'GoToE', 'AcroForm', 'AF']);
|
|
3
|
+
/** Accept bounded, static plans only. Rebuild page graphics without annotations,
|
|
4
|
+
* document actions, attachments, forms, links or source metadata. */
|
|
5
|
+
export async function sanitizeContactPdf(attachment) {
|
|
6
|
+
if (!(attachment.content instanceof Uint8Array) || attachment.content.byteLength > 4_000_000 || !/^document-\d+\.pdf$/.test(attachment.filename))
|
|
7
|
+
throw new Error('Invalid document attachment.');
|
|
8
|
+
const input = await PDFDocument.load(attachment.content, { ignoreEncryption: false, updateMetadata: false, throwOnInvalidObject: true });
|
|
9
|
+
if (input.isEncrypted || input.getPageCount() < 1 || input.getPageCount() > 100)
|
|
10
|
+
throw new Error('Unsupported PDF document.');
|
|
11
|
+
for (const [, object] of input.context.enumerateIndirectObjects()) {
|
|
12
|
+
if (object instanceof PDFDict) {
|
|
13
|
+
for (const [key, value] of object.entries()) {
|
|
14
|
+
if (key.decodeText() === 'Annots') {
|
|
15
|
+
const annotations = input.context.lookup(value);
|
|
16
|
+
if (!(annotations instanceof PDFArray) || annotations.size() > 0)
|
|
17
|
+
throw new Error('Annotated PDF documents must be flattened before upload.');
|
|
18
|
+
}
|
|
19
|
+
if (REJECTED_KEYS.has(key.decodeText()) || (value instanceof PDFName && REJECTED_KEYS.has(value.decodeText())))
|
|
20
|
+
throw new Error('Interactive PDF documents are not supported.');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const output = await PDFDocument.create({ updateMetadata: false });
|
|
25
|
+
for (const page of input.getPages()) {
|
|
26
|
+
const { width, height } = page.getSize();
|
|
27
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0 || width > 20_000 || height > 20_000)
|
|
28
|
+
throw new Error('Unsupported PDF page dimensions.');
|
|
29
|
+
const embedded = await output.embedPage(page);
|
|
30
|
+
const copied = output.addPage([width, height]);
|
|
31
|
+
copied.drawPage(embedded);
|
|
32
|
+
copied.setRotation(page.getRotation());
|
|
33
|
+
}
|
|
34
|
+
const content = await output.save({ useObjectStreams: true, addDefaultPage: false });
|
|
35
|
+
if (!content.byteLength || content.byteLength > 4_000_000)
|
|
36
|
+
throw new Error('PDF output exceeds the attachment limit.');
|
|
37
|
+
return Object.freeze({ filename: attachment.filename, content, contentType: 'application/pdf' });
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=pdf-sanitizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf-sanitizer.js","sourceRoot":"","sources":["../../src/contact/pdf-sanitizer.ts"],"names":[],"mappings":"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","sourcesContent":["import { PDFDocument, PDFDict, PDFName, PDFArray } from 'pdf-lib'\nimport type { ContactDeliveryAttachment } from './shared.js'\n\nconst REJECTED_KEYS = new Set(['JS','JavaScript','AA','OpenAction','Launch','EmbeddedFiles','EmbeddedFile','XFA','RichMedia','SubmitForm','ImportData','GoToR','GoToE','AcroForm','AF'])\n/** Accept bounded, static plans only. Rebuild page graphics without annotations,\n * document actions, attachments, forms, links or source metadata. */\nexport async function sanitizeContactPdf(attachment: ContactDeliveryAttachment): Promise<ContactDeliveryAttachment> {\n if (!(attachment.content instanceof Uint8Array) || attachment.content.byteLength > 4_000_000 || !/^document-\\d+\\.pdf$/.test(attachment.filename)) throw new Error('Invalid document attachment.')\n const input = await PDFDocument.load(attachment.content, { ignoreEncryption: false, updateMetadata: false, throwOnInvalidObject: true })\n if (input.isEncrypted || input.getPageCount() < 1 || input.getPageCount() > 100) throw new Error('Unsupported PDF document.')\n for (const [, object] of input.context.enumerateIndirectObjects()) {\n if (object instanceof PDFDict) {\n for (const [key, value] of object.entries()) {\n if (key.decodeText() === 'Annots') { const annotations = input.context.lookup(value); if (!(annotations instanceof PDFArray) || annotations.size() > 0) throw new Error('Annotated PDF documents must be flattened before upload.') }\n if (REJECTED_KEYS.has(key.decodeText()) || (value instanceof PDFName && REJECTED_KEYS.has(value.decodeText()))) throw new Error('Interactive PDF documents are not supported.')\n }\n }\n }\n const output = await PDFDocument.create({ updateMetadata: false })\n for (const page of input.getPages()) {\n const {width,height} = page.getSize()\n if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0 || width > 20_000 || height > 20_000) throw new Error('Unsupported PDF page dimensions.')\n const embedded = await output.embedPage(page)\n const copied = output.addPage([width,height])\n copied.drawPage(embedded)\n copied.setRotation(page.getRotation())\n }\n const content = await output.save({ useObjectStreams: true, addDefaultPage: false })\n if (!content.byteLength || content.byteLength > 4_000_000) throw new Error('PDF output exceeds the attachment limit.')\n return Object.freeze({filename:attachment.filename,content,contentType:'application/pdf'})\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/contact/server.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,OAAO,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/contact/server.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,OAAO,EAKL,KAAK,uBAAuB,EAI7B,MAAM,qCAAqC,CAAA;AAI5C,OAAO,EACL,mCAAmC,EACnC,KAAK,+BAA+B,EACrC,MAAM,eAAe,CAAA;AAGtB,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,aAAa,CAAA;AACpB,OAAO,EAIL,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,EAClC,KAAK,0BAA0B,EAE/B,KAAK,eAAe,EAIpB,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,mCAAmC,EAAE,MAAM,sBAAsB,CAAA;AAC1E,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AACxD,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAE5E,eAAO,MAAM,wBAAwB,EAAG,qBAA8B,CAAA;AACtE,eAAO,MAAM,4BAA4B,EAAG,gBAAyB,CAAA;AACrE,eAAO,MAAM,kCAAkC,UAAY,CAAA;AAE3D,wBAAgB,mCAAmC,CACjD,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GACxD,6BAA6B,CAK/B;AA2BD,MAAM,MAAM,uCAAuC,GAAG,IAAI,CACxD,+BAA+B,EAC/B,gBAAgB,CACjB,CAAA;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,uBAAuB,CAAA;IACjC,OAAO,EAAE,uCAAuC,CAAA;IAChD,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;IAC1D,cAAc,CAAC,EAAE,6BAA6B,CAAA;IAC9C,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,iBAAiB,CAAA;IACvD,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,OAAO,wBAAwB,CAAA;IAC5C,UAAU,EAAE,UAAU,CAAC,OAAO,mCAAmC,CAAC,CAAA;IAClE,mBAAmB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC7D;AAED,UAAU,0BAA0B;IAClC,UAAU,EAAE,yBAAyB,CAAA;IACrC,SAAS,CAAC,EAAE,gBAAgB,CAAA;IAC5B,mBAAmB,CAAC,EAAE,0BAA0B,CAAA;IAChD,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;CACjB;AA4ID,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,yBAAyB;;;;EAQ5E;AAylBD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,IAa7D,SAAS,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CA6BnD;AAkCD,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,6BAA6B,GACrC,sBAAsB,CAmCxB"}
|
package/dist/contact/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import 'server-only';
|
|
2
|
-
import { DEFAULT_CONTACT_ATTACHMENT_POLICY,
|
|
2
|
+
import { DEFAULT_CONTACT_ATTACHMENT_POLICY, createContactAttachmentFilename, defineContactContract, parseContactEmailRecipients, } from '@brandlab-digital/site-core/contact';
|
|
3
3
|
import { isBrandlabCanonicalEntityId } from '@brandlab-digital/site-core/entity-id';
|
|
4
4
|
import { normalizeBrandlabRoutePath } from '@brandlab-digital/site-core/routes';
|
|
5
5
|
import { normalizeContactAttributionSummary } from './attribution.js';
|
|
@@ -35,6 +35,7 @@ const MAX_CLOCK_SKEW_MS = 5 * 60 * 1_000;
|
|
|
35
35
|
const DEFAULT_MAX_ENTRIES = 64;
|
|
36
36
|
const DEFAULT_MAX_STRING_BYTES = 64 * 1_024;
|
|
37
37
|
const SUPPORTED_ATTACHMENT_MIME_TYPES = new Set([
|
|
38
|
+
'application/pdf',
|
|
38
39
|
'image/jpeg',
|
|
39
40
|
'image/jpg',
|
|
40
41
|
'image/png',
|
|
@@ -158,10 +159,10 @@ function assertDefinition(definition) {
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
function attachmentPolicy(definition) {
|
|
161
|
-
return
|
|
162
|
+
return {
|
|
162
163
|
...DEFAULT_CONTACT_ATTACHMENT_POLICY,
|
|
163
164
|
...(definition.attachments || {}),
|
|
164
|
-
}
|
|
165
|
+
};
|
|
165
166
|
}
|
|
166
167
|
export function getContactRequestLimits(definition) {
|
|
167
168
|
return {
|
|
@@ -266,6 +267,7 @@ function validateFields(definition, formData) {
|
|
|
266
267
|
return { values, fieldErrors };
|
|
267
268
|
}
|
|
268
269
|
const EXTENSION_TYPES = {
|
|
270
|
+
pdf: 'application/pdf',
|
|
269
271
|
jpg: 'image/jpeg',
|
|
270
272
|
jpeg: 'image/jpeg',
|
|
271
273
|
png: 'image/png',
|
|
@@ -278,6 +280,39 @@ function normalizeMimeType(value) {
|
|
|
278
280
|
const normalized = value.trim().toLowerCase();
|
|
279
281
|
return normalized === 'image/jpg' ? 'image/jpeg' : normalized;
|
|
280
282
|
}
|
|
283
|
+
function startsWithBytes(bytes, signature) {
|
|
284
|
+
return signature.every((byte, index) => bytes[index] === byte);
|
|
285
|
+
}
|
|
286
|
+
function ascii(bytes, start, length) {
|
|
287
|
+
return String.fromCharCode(...bytes.slice(start, start + length));
|
|
288
|
+
}
|
|
289
|
+
function detectAttachmentType(bytes) {
|
|
290
|
+
if (ascii(bytes, 0, 5) === '%PDF-')
|
|
291
|
+
return 'application/pdf';
|
|
292
|
+
if (startsWithBytes(bytes, [0xff, 0xd8, 0xff]))
|
|
293
|
+
return 'image/jpeg';
|
|
294
|
+
if (startsWithBytes(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
|
|
295
|
+
return 'image/png';
|
|
296
|
+
}
|
|
297
|
+
const gif = ascii(bytes, 0, 6);
|
|
298
|
+
if (gif === 'GIF87a' || gif === 'GIF89a')
|
|
299
|
+
return 'image/gif';
|
|
300
|
+
if (ascii(bytes, 0, 4) === 'RIFF' && ascii(bytes, 8, 4) === 'WEBP')
|
|
301
|
+
return 'image/webp';
|
|
302
|
+
if (ascii(bytes, 4, 4) === 'ftyp') {
|
|
303
|
+
const brands = new Set();
|
|
304
|
+
for (let offset = 8; offset + 4 <= Math.min(bytes.length, 64); offset += 4) {
|
|
305
|
+
brands.add(ascii(bytes, offset, 4));
|
|
306
|
+
}
|
|
307
|
+
if (['heic', 'heix', 'hevc', 'hevx'].some((brand) => brands.has(brand))) {
|
|
308
|
+
return 'image/heic';
|
|
309
|
+
}
|
|
310
|
+
if (['heif', 'mif1', 'msf1'].some((brand) => brands.has(brand))) {
|
|
311
|
+
return 'image/heif';
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
281
316
|
async function readValidatedAttachments(definition, formData) {
|
|
282
317
|
if (!definition.attachments)
|
|
283
318
|
return [];
|
|
@@ -310,11 +345,11 @@ async function readValidatedAttachments(definition, formData) {
|
|
|
310
345
|
if (totalBytes > policy.maxTotalSizeBytes)
|
|
311
346
|
return undefined;
|
|
312
347
|
const content = new Uint8Array(await file.arrayBuffer());
|
|
313
|
-
if (normalizeMimeType(
|
|
348
|
+
if (normalizeMimeType(detectAttachmentType(content.slice(0, 64)) ?? '') !== declaredType) {
|
|
314
349
|
return undefined;
|
|
315
350
|
}
|
|
316
351
|
attachments.push({
|
|
317
|
-
filename: createContactAttachmentFilename({ index }),
|
|
352
|
+
filename: createContactAttachmentFilename({ index, contentType: declaredType }),
|
|
318
353
|
content,
|
|
319
354
|
contentType: declaredType,
|
|
320
355
|
});
|
|
@@ -330,12 +365,12 @@ function validateProcessedAttachments(definition, attachments) {
|
|
|
330
365
|
return false;
|
|
331
366
|
return attachments.every((attachment, index) => {
|
|
332
367
|
totalBytes += attachment.content.byteLength;
|
|
333
|
-
return (attachment.filename === createContactAttachmentFilename({ index }) &&
|
|
334
|
-
attachment.contentType === 'image/jpeg' &&
|
|
368
|
+
return (attachment.filename === createContactAttachmentFilename({ index, contentType: attachment.contentType }) &&
|
|
369
|
+
(attachment.contentType === 'image/jpeg' || (attachment.contentType === 'application/pdf' && policy.allowedTypes.includes('application/pdf'))) &&
|
|
335
370
|
attachment.content.byteLength > 0 &&
|
|
336
371
|
attachment.content.byteLength <= policy.maxFileSizeBytes &&
|
|
337
372
|
totalBytes <= policy.maxTotalSizeBytes &&
|
|
338
|
-
|
|
373
|
+
detectAttachmentType(attachment.content.slice(0, 64)) === attachment.contentType);
|
|
339
374
|
});
|
|
340
375
|
}
|
|
341
376
|
function validationResult(fieldErrors) {
|