@@ -16,6 +16,36 @@ export const isAllowedTimeline = (timeline: string): timeline is ContactTimeline
1616 return ( contactTimelineValues as readonly string [ ] ) . includes ( timeline )
1717}
1818
19+ const allowedAttachmentTypes = [
20+ 'image/jpeg' ,
21+ 'image/png' ,
22+ 'image/gif' ,
23+ 'image/webp' ,
24+ 'application/pdf' ,
25+ 'application/msword' ,
26+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ,
27+ 'audio/mpeg' ,
28+ 'audio/mp4' ,
29+ 'audio/ogg' ,
30+ 'audio/wav' ,
31+ 'audio/webm' ,
32+ 'video/mp4' ,
33+ 'video/quicktime' ,
34+ 'video/webm' ,
35+ 'application/zip' ,
36+ 'text/plain' ,
37+ ] as const
38+
39+ type AllowedAttachmentType = ( typeof allowedAttachmentTypes ) [ number ]
40+
41+ const isAllowedAttachmentType = ( mimeType : string ) : mimeType is AllowedAttachmentType => {
42+ return allowedAttachmentTypes . some ( allowedAttachmentType => allowedAttachmentType === mimeType )
43+ }
44+
45+ const normalizeMimeType = ( mimeType : string ) : string => {
46+ return mimeType . split ( ';' ) [ 0 ] ?. trim ( ) . toLowerCase ( ) || ''
47+ }
48+
1949const readInputString = ( input : Record < string , unknown > , key : string ) : string => {
2050 const value = input [ key ]
2151 return typeof value === 'string' ? value : ''
@@ -34,6 +64,7 @@ export function generateEmailContent(data: ContactFormData, files: FileAttachmen
3464 `<p><strong>Email:</strong> ${ escapeHtml ( data . email ) } </p>` ,
3565 ]
3666
67+ if ( data . company ) fields . push ( `<p><strong>Company:</strong> ${ escapeHtml ( data . company ) } </p>` )
3768 if ( data . phone ) fields . push ( `<p><strong>Phone:</strong> ${ escapeHtml ( data . phone ) } </p>` )
3869 if ( data . service ) fields . push ( `<p><strong>Service:</strong> ${ escapeHtml ( data . service ) } </p>` )
3970 if ( data . budget ) fields . push ( `<p><strong>Budget:</strong> ${ escapeHtml ( data . budget ) } </p>` )
@@ -81,6 +112,7 @@ export function getFormDataFromInput(input: Record<string, unknown>): ContactFor
81112 consent : readInputBoolean ( input , 'consent' ) ,
82113 }
83114
115+ const company = readInputString ( input , 'company' )
84116 const phone = readInputString ( input , 'phone' )
85117 const budget = readInputString ( input , 'budget' )
86118 const timeline = readInputString ( input , 'timeline' )
@@ -93,6 +125,7 @@ export function getFormDataFromInput(input: Record<string, unknown>): ContactFor
93125
94126 const dataSubjectId = readInputString ( input , 'DataSubjectId' )
95127
128+ if ( company ) formData . company = company
96129 if ( phone ) formData . phone = phone
97130 if ( budget ) formData . budget = budget
98131 // Assertion OK because it is validated by Zod
@@ -106,21 +139,6 @@ export function getFormDataFromInput(input: Record<string, unknown>): ContactFor
106139
107140export async function parseAttachments ( form : FormData ) : Promise < FileAttachment [ ] > {
108141 const files : FileAttachment [ ] = [ ]
109- const allowedTypes = [
110- 'image/jpeg' ,
111- 'image/png' ,
112- 'image/gif' ,
113- 'image/webp' ,
114- 'application/pdf' ,
115- 'application/msword' ,
116- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ,
117- 'audio/mpeg' ,
118- 'audio/wav' ,
119- 'video/mp4' ,
120- 'video/quicktime' ,
121- 'application/zip' ,
122- 'text/plain' ,
123- ]
124142 const maxFileSize = 10 * 1024 * 1024
125143 const maxFiles = 5
126144
@@ -137,15 +155,17 @@ export async function parseAttachments(form: FormData): Promise<FileAttachment[]
137155 throw new ActionsFunctionError ( `File ${ value . name } exceeds 10MB limit` , { status : 400 } )
138156 }
139157
140- if ( ! allowedTypes . includes ( value . type ) ) {
158+ const normalizedType = normalizeMimeType ( value . type )
159+
160+ if ( ! isAllowedAttachmentType ( normalizedType ) ) {
141161 throw new ActionsFunctionError ( `File type ${ value . type } not allowed` , { status : 400 } )
142162 }
143163
144164 const buffer = Buffer . from ( await value . arrayBuffer ( ) )
145165 files . push ( {
146166 filename : value . name ,
147167 content : buffer ,
148- contentType : value . type ,
168+ contentType : normalizedType ,
149169 size : value . size ,
150170 } )
151171 }
@@ -158,21 +178,6 @@ export async function parseAttachmentsFromInput(
158178 input : Record < string , unknown >
159179) : Promise < FileAttachment [ ] > {
160180 const files : FileAttachment [ ] = [ ]
161- const allowedTypes = [
162- 'image/jpeg' ,
163- 'image/png' ,
164- 'image/gif' ,
165- 'image/webp' ,
166- 'application/pdf' ,
167- 'application/msword' ,
168- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ,
169- 'audio/mpeg' ,
170- 'audio/wav' ,
171- 'video/mp4' ,
172- 'video/quicktime' ,
173- 'application/zip' ,
174- 'text/plain' ,
175- ]
176181 const maxFileSize = 10 * 1024 * 1024
177182 const maxFiles = 5
178183
@@ -190,15 +195,17 @@ export async function parseAttachmentsFromInput(
190195 throw new ActionsFunctionError ( `File ${ value . name } exceeds 10MB limit` , { status : 400 } )
191196 }
192197
193- if ( ! allowedTypes . includes ( value . type ) ) {
198+ const normalizedType = normalizeMimeType ( value . type )
199+
200+ if ( ! isAllowedAttachmentType ( normalizedType ) ) {
194201 throw new ActionsFunctionError ( `File type ${ value . type } not allowed` , { status : 400 } )
195202 }
196203
197204 const buffer = Buffer . from ( await value . arrayBuffer ( ) )
198205 files . push ( {
199206 filename : value . name ,
200207 content : buffer ,
201- contentType : value . type ,
208+ contentType : normalizedType ,
202209 size : value . size ,
203210 } )
204211 }
0 commit comments