mail page imap connection mime 3
This commit is contained in:
parent
b1662fce90
commit
75aa93e286
@ -42,19 +42,123 @@ interface Email {
|
||||
}
|
||||
|
||||
// Improved MIME Decoder Implementation for Infomaniak
|
||||
function decodeInfomaniakEmail(rawEmail: string) {
|
||||
// Check if the email is multipart
|
||||
const boundaryMatch = rawEmail.match(/boundary="?([^"\r\n;]+)"?/i);
|
||||
function extractBoundary(headers: string): string | null {
|
||||
const boundaryMatch = headers.match(/boundary="?([^"\r\n;]+)"?/i) ||
|
||||
headers.match(/boundary=([^\r\n;]+)/i);
|
||||
|
||||
return boundaryMatch ? boundaryMatch[1].trim() : null;
|
||||
}
|
||||
|
||||
function decodeQuotedPrintable(text: string, charset: string): string {
|
||||
if (!text) return '';
|
||||
|
||||
// Replace soft line breaks (=\r\n or =\n or =\r)
|
||||
let decoded = text.replace(/=(?:\r\n|\n|\r)/g, '');
|
||||
|
||||
// Replace quoted-printable encoded characters (including non-ASCII characters)
|
||||
decoded = decoded.replace(/=([0-9A-F]{2})/gi, (match, p1) => {
|
||||
return String.fromCharCode(parseInt(p1, 16));
|
||||
});
|
||||
|
||||
// Handle character encoding
|
||||
try {
|
||||
// For browsers with TextDecoder support
|
||||
if (typeof TextDecoder !== 'undefined') {
|
||||
// Convert string to array of byte values
|
||||
const bytes = new Uint8Array(Array.from(decoded).map(c => c.charCodeAt(0)));
|
||||
return new TextDecoder(charset).decode(bytes);
|
||||
}
|
||||
|
||||
// Fallback for older browsers or when charset handling is not critical
|
||||
return decoded;
|
||||
} catch (e) {
|
||||
console.warn('Charset conversion error:', e);
|
||||
return decoded;
|
||||
}
|
||||
}
|
||||
|
||||
function parseFullEmail(emailRaw: string) {
|
||||
// Check if this is a multipart message by looking for boundary definition
|
||||
const boundaryMatch = emailRaw.match(/boundary="?([^"\r\n;]+)"?/i) ||
|
||||
emailRaw.match(/boundary=([^\r\n;]+)/i);
|
||||
|
||||
if (boundaryMatch) {
|
||||
// Handle multipart email
|
||||
return processMultipartEmail(rawEmail, boundaryMatch[1]);
|
||||
const boundary = boundaryMatch[1].trim();
|
||||
|
||||
// Check if there's a preamble before the first boundary
|
||||
let mainHeaders = '';
|
||||
let mainContent = emailRaw;
|
||||
|
||||
// Extract the headers before the first boundary if they exist
|
||||
const firstBoundaryPos = emailRaw.indexOf('--' + boundary);
|
||||
if (firstBoundaryPos > 0) {
|
||||
const headerSeparatorPos = emailRaw.indexOf('\r\n\r\n');
|
||||
if (headerSeparatorPos > 0 && headerSeparatorPos < firstBoundaryPos) {
|
||||
mainHeaders = emailRaw.substring(0, headerSeparatorPos);
|
||||
}
|
||||
}
|
||||
|
||||
return processMultipartEmail(emailRaw, boundary, mainHeaders);
|
||||
} else {
|
||||
// Handle simple email
|
||||
return processSinglePartEmail(rawEmail);
|
||||
// This is a single part message
|
||||
return processSinglePartEmail(emailRaw);
|
||||
}
|
||||
}
|
||||
|
||||
function processMultipartEmail(emailRaw: string, boundary: string, mainHeaders: string = ''): {
|
||||
text: string;
|
||||
html: string;
|
||||
attachments: { filename: string; contentType: string; encoding: string; content: string; }[];
|
||||
headers?: string;
|
||||
} {
|
||||
const result = {
|
||||
text: '',
|
||||
html: '',
|
||||
attachments: [] as { filename: string; contentType: string; encoding: string; content: string; }[],
|
||||
headers: mainHeaders
|
||||
};
|
||||
|
||||
// Split by boundary (more robust pattern)
|
||||
const boundaryRegex = new RegExp(`--${boundary}(?:--)?(\\r?\\n|$)`, 'g');
|
||||
|
||||
// Get all boundary positions
|
||||
const matches = Array.from(emailRaw.matchAll(boundaryRegex));
|
||||
const boundaryPositions = matches.map(match => match.index!);
|
||||
|
||||
// Extract content between boundaries
|
||||
for (let i = 0; i < boundaryPositions.length - 1; i++) {
|
||||
const startPos = boundaryPositions[i] + matches[i][0].length;
|
||||
const endPos = boundaryPositions[i + 1];
|
||||
|
||||
if (endPos > startPos) {
|
||||
const partContent = emailRaw.substring(startPos, endPos).trim();
|
||||
|
||||
if (partContent) {
|
||||
const decoded = processSinglePartEmail(partContent);
|
||||
|
||||
if (decoded.contentType.includes('text/plain')) {
|
||||
result.text = decoded.text || '';
|
||||
} else if (decoded.contentType.includes('text/html')) {
|
||||
result.html = cleanHtml(decoded.html || '');
|
||||
} else if (
|
||||
decoded.contentType.startsWith('image/') ||
|
||||
decoded.contentType.startsWith('application/')
|
||||
) {
|
||||
const filename = extractFilename(partContent);
|
||||
result.attachments.push({
|
||||
filename,
|
||||
contentType: decoded.contentType,
|
||||
encoding: decoded.raw?.headers ? parseEmailHeaders(decoded.raw.headers).encoding : '7bit',
|
||||
content: decoded.raw?.body || ''
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function processSinglePartEmail(rawEmail: string) {
|
||||
// Split headers and body
|
||||
const headerBodySplit = rawEmail.split(/\r?\n\r?\n/);
|
||||
@ -82,72 +186,6 @@ function processSinglePartEmail(rawEmail: string) {
|
||||
};
|
||||
}
|
||||
|
||||
function processMultipartEmail(rawEmail: string, boundary: string): {
|
||||
text: string;
|
||||
html: string;
|
||||
attachments: { filename: string; contentType: string; encoding: string; content: string; }[];
|
||||
subject?: string;
|
||||
from?: string;
|
||||
to?: string;
|
||||
date?: string;
|
||||
} {
|
||||
// Split headers and body
|
||||
const headerBodySplit = rawEmail.split(/\r?\n\r?\n/);
|
||||
const headers = headerBodySplit[0];
|
||||
const fullBody = headerBodySplit.slice(1).join('\n\n');
|
||||
|
||||
// Create the result object
|
||||
const result = {
|
||||
subject: extractHeader(headers, 'Subject'),
|
||||
from: extractHeader(headers, 'From'),
|
||||
to: extractHeader(headers, 'To'),
|
||||
date: extractHeader(headers, 'Date'),
|
||||
text: '',
|
||||
html: '',
|
||||
attachments: [] as { filename: string; contentType: string; encoding: string; content: string; }[]
|
||||
};
|
||||
|
||||
// Split the body by boundary
|
||||
const boundaryRegex = new RegExp(`--${boundary}\\r?\\n|--${boundary}--\\r?\\n?`, 'g');
|
||||
const parts = fullBody.split(boundaryRegex).filter(part => part.trim());
|
||||
|
||||
// Process each part
|
||||
parts.forEach(part => {
|
||||
if (!part.trim()) return;
|
||||
|
||||
// Split headers and content for this part
|
||||
const partHeadersEnd = part.match(/\r?\n\r?\n/);
|
||||
if (!partHeadersEnd) return;
|
||||
|
||||
const partHeadersEndPos = partHeadersEnd.index!;
|
||||
const partHeaders = part.substring(0, partHeadersEndPos);
|
||||
const partContent = part.substring(partHeadersEndPos + partHeadersEnd[0].length);
|
||||
|
||||
// Get content info for this part
|
||||
const partInfo = parseEmailHeaders(partHeaders);
|
||||
|
||||
// Handle different content types
|
||||
if (partInfo.contentType.includes('text/plain')) {
|
||||
result.text = decodeMIME(partContent, partInfo.encoding, partInfo.charset);
|
||||
} else if (partInfo.contentType.includes('text/html')) {
|
||||
result.html = cleanHtml(decodeMIME(partContent, partInfo.encoding, partInfo.charset));
|
||||
} else if (
|
||||
partInfo.contentType.startsWith('image/') ||
|
||||
partInfo.contentType.startsWith('application/')
|
||||
) {
|
||||
const filename = extractFilename(partHeaders);
|
||||
result.attachments.push({
|
||||
filename,
|
||||
contentType: partInfo.contentType,
|
||||
encoding: partInfo.encoding,
|
||||
content: partContent
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function extractHeader(headers: string, headerName: string): string {
|
||||
const regex = new RegExp(`^${headerName}:\\s*(.+?)(?:\\r?\\n(?!\\s)|$)`, 'im');
|
||||
const match = headers.match(regex);
|
||||
@ -212,22 +250,6 @@ function decodeMIME(text: string, encoding?: string, charset: string = 'utf-8'):
|
||||
}
|
||||
}
|
||||
|
||||
function decodeQuotedPrintable(text: string, charset: string): string {
|
||||
// Replace soft line breaks
|
||||
let decoded = text.replace(/=(?:\r\n|\n)/g, '');
|
||||
|
||||
// Replace quoted-printable hex sequences
|
||||
decoded = decoded.replace(/=([0-9A-F]{2})/gi, (match, p1) => {
|
||||
return String.fromCharCode(parseInt(p1, 16));
|
||||
});
|
||||
|
||||
// Handle Infomaniak specific issues with special characters
|
||||
decoded = decoded.replace(/\xA0/g, ' ');
|
||||
|
||||
// Handle character set conversion
|
||||
return convertCharset(decoded, charset);
|
||||
}
|
||||
|
||||
function decodeBase64(text: string, charset: string): string {
|
||||
const cleanText = text.replace(/\s/g, '');
|
||||
|
||||
@ -312,20 +334,25 @@ function decodeMimeContent(content: string): string {
|
||||
if (!content) return '';
|
||||
|
||||
try {
|
||||
// Try to decode as a complete email first
|
||||
const decoded = decodeInfomaniakEmail(content);
|
||||
|
||||
// If we have HTML content, prefer that
|
||||
if (decoded.html) {
|
||||
return extractHtmlBody(decoded.html);
|
||||
// Handle the special case with InfomaniakPhpMail boundary
|
||||
if (content.includes('---InfomaniakPhpMail')) {
|
||||
const boundaryMatch = content.match(/---InfomaniakPhpMail[\w\d]+/);
|
||||
if (boundaryMatch) {
|
||||
const boundary = boundaryMatch[0];
|
||||
const result = processMultipartEmail(content, boundary);
|
||||
return result.html || result.text || content;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise use the text content
|
||||
if (decoded.text) {
|
||||
return decoded.text;
|
||||
// Regular email parsing
|
||||
const result = parseFullEmail(content);
|
||||
if ('html' in result && result.html) {
|
||||
return extractHtmlBody(result.html);
|
||||
} else if ('text' in result && result.text) {
|
||||
return result.text;
|
||||
}
|
||||
|
||||
// If neither HTML nor text was found, try simple decoding
|
||||
// If parsing fails, try simple decoding
|
||||
if (content.includes('Content-Type:') || content.includes('Content-Transfer-Encoding:')) {
|
||||
const simpleDecoded = processSinglePartEmail(content);
|
||||
return simpleDecoded.text || simpleDecoded.html || content;
|
||||
@ -338,12 +365,6 @@ function decodeMimeContent(content: string): string {
|
||||
return decodeMIME(content, 'quoted-printable', 'utf-8');
|
||||
}
|
||||
|
||||
// If no specific encoding is detected, try quoted-printable first
|
||||
const qpDecoded = decodeMIME(content, 'quoted-printable', 'utf-8');
|
||||
if (qpDecoded !== content) {
|
||||
return qpDecoded;
|
||||
}
|
||||
|
||||
// If nothing else worked, return the original content
|
||||
return content;
|
||||
} catch (error) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user