From: <sender@example.com>
To: <recipient@example.com>
Subject: =?UTF-8?B?5L2g5aW977yM5LiW55WM?= (MIME Encoded-Word)
Date: Mon, 1 Jan 2024 10:00:00 +0800
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="----=_Part_001_002"
Message-ID: <abc123@example.com>
------=_Part_001_002
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: base64
SGVsbG8gV29ybGQh
------=_Part_001_002
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
<html><body><p>Hello =E4=B8=96=E7=95=8C!</p></body></html>
------=_Part_001_002--
=?charset?encoding?encoded-text?= 格式 (B=Base64, Q=Quoted-Printable)。Content-Transfer-Encoding 解碼內容 (base64 / quoted-printable / 7bit / 8bit / binary)。Content-Disposition: attachment; filename="...",解碼後儲存。Content-ID: <part1@...>,HTML 中 <img src="cid:part1@..."> 對應替換。<?php
// 1. MIME Encoded-Word 解碼 (Subject 等標頭)
function decodeMimeWord(string $encoded): string
{
return iconv_mime_decode($encoded, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8');
}
// 2. Content-Transfer-Encoding 解碼
function decodeBody(string $body, string $encoding): string
{
$encoding = strtolower(trim($encoding));
return match ($encoding) {
'base64' => base64_decode($body),
'quoted-printable' => quoted_printable_decode($body),
'7bit', '8bit', 'binary' => $body,
default => $body, // 未知編碼嘗試原樣返回
};
}
// 3. 解析 Content-Type 取得 charset 與 boundary
function parseContentType(string $header): array
{
$result = ['charset' => 'UTF-8', 'boundary' => null];
if (preg_match('/charset=["\']?([^"\';]+)/i', $header, $m)) {
$result['charset'] = strtoupper($m[1]);
}
if (preg_match('/boundary=["\']?([^"\';]+)/i', $header, $m)) {
$result['boundary'] = $m[1];
}
return $result;
}
// 4. 簡易郵件解析器 (教學用,生產請用成熟庫)
class SimpleMailParser
{
public function parse(string $rawMessage): array
{
// 分離標頭與主體
list($headerBlock, $bodyBlock) = explode("\r\n\r\n", $rawMessage, 2) + ['', ''];
// 解析標頭
$headers = [];
foreach (explode("\r\n", $headerBlock) as $line) {
if (preg_match('/^([^:]+):\s*(.*)$/', $line, $m)) {
$headers[strtolower($m[1])] = $m[2];
}
}
// 解碼 Subject
$subject = decodeMimeWord($headers['subject'] ?? '');
// 處理主體
$contentType = $headers['content-type'] ?? 'text/plain; charset=US-ASCII';
$ctInfo = parseContentType($contentType);
$body = $this->processPart($bodyBlock, $ctInfo);
return [
'headers' => $headers,
'subject' => $subject,
'from' => $headers['from'] ?? '',
'to' => $headers['to'] ?? '',
'date' => $headers['date'] ?? '',
'body' => $body, // 結構化陣列:['text' => ..., 'html' => ..., 'attachments' => [...]]
];
}
private function processPart(string $data, array $ctInfo): array
{
$encoding = '7bit';
// 簡化:實際需從部分標頭解析 encoding
// 這裡假設單一部分
$decoded = decodeBody($data, $encoding);
$charset = $ctInfo['charset'];
if ($charset !== 'UTF-8') {
$decoded = mb_convert_encoding($decoded, 'UTF-8', $charset);
}
return [
'text' => stripos($ctInfo['type'] ?? '', 'text/plain') !== false ? $decoded : '',
'html' => stripos($ctInfo['type'] ?? '', 'text/html') !== false ? $decoded : '',
];
}
}
?>
| 庫 | 特點 | Composer |
|---|---|---|
| PHP-MIME-Mail-Parser (php-mime-mail-parser/php-mime-mail-parser) | 流行、支援附件、內嵌圖片、多層巢狀 | composer require php-mime-mail-parser/php-mime-mail-parser |
| Symfony Mime (symfony/mime) | 現代、標準化、整合 Symfony 生態 | composer require symfony/mime |
| Zend\Mail (laminas/laminas-mail) | 企業級、完整 MIME 支援 | composer require laminas/laminas-mail |
<?php
require 'vendor/autoload.php';
use PhpMimeMailParser\Parser;
$parser = new Parser();
$parser->setText($rawEmail); // 或 setPath($emlFile)
$subject = $parser->getHeader('subject'); // 已自動解碼
$from = $parser->getHeader('from');
$to = $parser->getHeader('to');
$date = $parser->getHeader('date');
$textBody = $parser->getMessageBody('text'); // 純文本
$htmlBody = $parser->getMessageBody('html'); // HTML
// 附件處理
$attachments = $parser->getAttachments();
foreach ($attachments as $att) {
$filename = $att->getFilename();
$content = $att->getContent(); // 二進位內容
$mime = $att->getContentType();
// 儲存附件...
file_put_contents("attachments/$filename", $content);
}
// 內嵌圖片 CID 對應
$embedded = $parser->getEmbeddedImages();
// $embedded['cid:part1@example.com'] = 二進位圖片內容
// HTML 中
需手動替換為 data URI 或儲存後路徑
?>
郵件 MIME 結構複雜 (巢狀 multipart、編碼混雜、字元集轉換、RFC 違規郵件客戶端產出的異常格式)。**手寫解析器極易踩坑**,生產環境**務必使用成熟庫** (PHP-MIME-Mail-Parser、Symfony Mime)。理解底層結構 (Encoded-Word、Transfer-Encoding、Boundary、CID) 有助於除錯與自訂需求擴充。