@@ -7,6 +7,25 @@ import * as Utils from '@utils/index.ts'
77 * @description Formats headers, body, and multipart sections.
88 */
99export class SmtpMessage {
10+ /** Allowed custom header name pattern */
11+ private readonly customHeaderNamePattern = / ^ [ ! # $ % & ' * + \- . ^ _ ` | ~ 0 - 9 A - Z a - z ] + $ /
12+ /** Reserved custom headers to block */
13+ private readonly blockedCustomHeaderNames = new Set ( [
14+ 'bcc' ,
15+ 'cc' ,
16+ 'content-disposition' ,
17+ 'content-id' ,
18+ 'content-transfer-encoding' ,
19+ 'content-type' ,
20+ 'date' ,
21+ 'from' ,
22+ 'message-id' ,
23+ 'mime-version' ,
24+ 'reply-to' ,
25+ 'subject' ,
26+ 'to'
27+ ] )
28+
1029 /**
1130 * Create message formatter.
1231 * @description Stores SMTP config for generated headers.
@@ -83,7 +102,7 @@ export class SmtpMessage {
83102 headers . push ( `Reply-To: ${ SMTP . SmtpAddress . formatAddressForHeader ( replyToAddress ) } ` )
84103 if ( message . headers ) {
85104 for ( const [ key , value ] of Object . entries ( message . headers ) ) {
86- headers . push ( ` ${ key } : ${ value } ` )
105+ headers . push ( this . formatCustomHeader ( key , value ) )
87106 }
88107 }
89108 return headers
@@ -178,6 +197,35 @@ export class SmtpMessage {
178197 return parts . join ( '\r\n' )
179198 }
180199
200+ /**
201+ * Format custom header.
202+ * @description Validates custom header name and value safety.
203+ * @param customHeaderKey - Custom header name
204+ * @param customHeaderValue - Custom header value
205+ * @returns Safe custom header string
206+ * @throws {Error } When custom header key or value is invalid
207+ */
208+ private formatCustomHeader ( customHeaderKey : string , customHeaderValue : string ) : string {
209+ const trimmedHeaderKey = customHeaderKey . trim ( )
210+ const normalizedHeaderKey = trimmedHeaderKey . toLowerCase ( )
211+ if ( trimmedHeaderKey . length === 0 ) {
212+ throw new Error ( 'Custom header name cannot be empty' )
213+ }
214+ if ( this . blockedCustomHeaderNames . has ( normalizedHeaderKey ) ) {
215+ throw new Error ( `Custom header "${ trimmedHeaderKey } " is reserved and cannot be overridden` )
216+ }
217+ if ( ! this . customHeaderNamePattern . test ( trimmedHeaderKey ) ) {
218+ throw new Error ( `Custom header "${ trimmedHeaderKey } " contains invalid characters` )
219+ }
220+ if ( trimmedHeaderKey . includes ( '\r' ) || trimmedHeaderKey . includes ( '\n' ) ) {
221+ throw new Error ( `Custom header "${ trimmedHeaderKey } " contains line break characters` )
222+ }
223+ if ( customHeaderValue . includes ( '\r' ) || customHeaderValue . includes ( '\n' ) ) {
224+ throw new Error ( `Custom header "${ trimmedHeaderKey } " value contains line break characters` )
225+ }
226+ return `${ trimmedHeaderKey } : ${ customHeaderValue } `
227+ }
228+
181229 /**
182230 * Format embedded images section.
183231 * @description Builds related parts and inline image payloads.
0 commit comments