( source: Buffer, bodyStructure: FetchMessageObject['bodyStructure'] )
| 418 | } |
| 419 | |
| 420 | function extractAttachmentsFromSource( |
| 421 | source: Buffer, |
| 422 | bodyStructure: FetchMessageObject['bodyStructure'] |
| 423 | ): ImapAttachment[] { |
| 424 | const attachments: ImapAttachment[] = [] |
| 425 | if (!bodyStructure) return attachments |
| 426 | |
| 427 | const content = source.toString('utf-8') |
| 428 | const parts = content.split(/--[^\r\n]+/) |
| 429 | |
| 430 | for (const part of parts) { |
| 431 | const lowerPart = part.toLowerCase() |
| 432 | |
| 433 | const dispositionMatch = part.match( |
| 434 | /content-disposition:\s*attachment[^;]*;\s*filename="?([^"\r\n]+)"?/i |
| 435 | ) |
| 436 | const filenameMatch = part.match(/name="?([^"\r\n]+)"?/i) |
| 437 | const contentTypeMatch = part.match(/content-type:\s*([^;\r\n]+)/i) |
| 438 | |
| 439 | if ( |
| 440 | dispositionMatch || |
| 441 | (filenameMatch && !lowerPart.includes('text/plain') && !lowerPart.includes('text/html')) |
| 442 | ) { |
| 443 | const filename = dispositionMatch?.[1] || filenameMatch?.[1] || 'attachment' |
| 444 | const mimeType = contentTypeMatch?.[1]?.trim() || 'application/octet-stream' |
| 445 | |
| 446 | const dataMatch = part.match(/\r?\n\r?\n([\s\S]*?)$/i) |
| 447 | if (dataMatch) { |
| 448 | const data = dataMatch[1].trim() |
| 449 | |
| 450 | if (lowerPart.includes('base64')) { |
| 451 | try { |
| 452 | const buffer = Buffer.from(data.replace(/\s/g, ''), 'base64') |
| 453 | attachments.push({ |
| 454 | name: filename, |
| 455 | data: buffer, |
| 456 | mimeType, |
| 457 | size: buffer.length, |
| 458 | }) |
| 459 | } catch {} |
| 460 | } |
| 461 | } |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | return attachments |
| 466 | } |
| 467 | |
| 468 | function hasAttachmentsInBodyStructure(structure: FetchMessageObject['bodyStructure']): boolean { |
| 469 | if (!structure) return false |
no test coverage detected