萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 實現 POP3 郵件接收與 MIME 解碼 (一):原始郵件結構解析與 Base64/Quoted-Printable 解碼

郵件原始結構 (RFC 5322 / MIME RFC 2045)

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--

關鍵解碼步驟

  1. 解析標頭:提取 Subject、From、To、Date、Content-Type、boundary 等。
  2. 解碼 Encoded-Word:=?charset?encoding?encoded-text?= 格式 (B=Base64, Q=Quoted-Printable)。
  3. 依 boundary 分割 MIME 部分:multipart/alternative (純文本+HTML)、multipart/mixed (含附件)、multipart/related (內嵌圖片)。
  4. 遞歸處理每個部分:解析部分標頭、根據 Content-Transfer-Encoding 解碼內容 (base64 / quoted-printable / 7bit / 8bit / binary)。
  5. 處理附件:Content-Disposition: attachment; filename="...",解碼後儲存。
  6. 處理內嵌資源:Content-ID: <part1@...>,HTML 中 <img src="cid:part1@..."> 對應替換。

PHP 核心解碼函數

<?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-MIME-Mail-Parser 範例

<?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) 有助於除錯與自訂需求擴充。

copyright © 萬盛學電腦網 all rights reserved