PHP展示EML文件时内嵌图片无法加载问题求助
使用PHP代码展示EML文件内容时,邮件正文显示正常,但内嵌图片无法加载,且正文结束后会显示图片相关的原始文本内容,示例如下:
--0000000000003cea95058bbee080-- --0000000000003cea99058bbee082 Content-Type: image/jpeg; name="=?UTF-8?B?16rXnNeV16DXlCDXqNen15XXkdenINeh16jXp9eZ15Qu? =?UTF-8?B?anBn?=" Content-Disposition: attachment; filename="=?UTF-8?B?16rXnNeV16DXlCDXqNen15XXkdenINeh16jXp9eZ15QuanBn?=" Content-Transfer-Encoding: base64 X-Attachment-Id: f_jx4j1uzv0 Content-ID: /9j/4AAQSkZJRgABAQEASwBLAAD/4RDqRXhpZgAATU0AKgAAAAgABgALAAIAAAAmAAAIYgESAAMA AAABAAMAAAExAAIAAAAmAAAIiAEyAAIAAAAUAAAIrodpAAQAAAABAAAIwuocAAcAAAgMAAAAVgAA AAAc6gAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
由于使用共享服务器,无法安装PHP类库或扩展包,需要排查代码问题并修复。
原使用的PHP代码:
<?php error_reporting(-1); $EML_FILE_PATH = ''; $PICTURE_DIRECTORY_PATH = 'img/'; $filename = 'sample.eml'; $eml_file = $EML_FILE_PATH . $filename; // Improved error handling when opening and reading the file. if (!$handle = fopen($eml_file, 'r')) { die('Cannot open file (' . $eml_file . ')'); } $content = fread($handle, filesize($eml_file)); fclose($handle); if ($content === false) { die('Error reading file (' . $eml_file . ')'); } // Define separators for different sections of the email. if (strpos($content, '------_') !== false) { $separator = '------_'; } else { $separator = '------=_'; } $aContent = explode($separator, $content); $aImages = array(); $thisHTMLContent = ''; foreach ($aContent as $thisContent) { if (strpos($thisContent, 'Content-Type: text/html') !== false) { // Correct handling of the email HTML body. $thisContent = substr($thisContent, strpos($thisContent, '<!DOCTYPE')); $thisHTMLContent = quoted_printable_decode($thisContent); } // Function to process images (DRY principle). function processImage($thisContent, $imgType) { $begin = strpos($thisContent, 'Content-ID: <') + 13; $long = strpos(substr($thisContent, $begin), '>'); $img_id = substr($thisContent, $begin, $long); $img_name = substr($thisContent, strpos($thisContent, 'name="') + 6, strpos($thisContent, ".$imgType\"") - strpos($thisContent, 'name="') - 6); $img_location = $img_name; // Simplified logic assuming name is used as location. $searched = 'Content-ID: <' . $img_id . '>'; $Content_ID_pos = strpos($thisContent, $searched); $img_base64 = substr($thisContent, $Content_ID_pos + strlen($searched) + 1); return array('id' => $img_id, 'name' => $img_name, 'location' => $img_location, 'type' => $imgType, 'base64' => $img_base64); } if (strpos($thisContent, 'Content-Type: image/gif;') !== false) { $aImages[] = processImage($thisContent, 'gif'); } if (strpos($thisContent, 'Content-Type: image/jpeg;') !== false) { $aImages[] = processImage($thisContent, 'jpg'); } } foreach ($aImages as $image) { $imageData = 'data:image/' . $image['type'] . ';base64,' . trim($image['base64']); $thisHTMLContent = str_replace('cid:' . $image['id'], $imageData, $thisHTMLContent); } echo $thisHTMLContent; ?>
1. 修复分隔符处理逻辑
邮件MIME分隔符以--开头,需以此拆分内容,并过滤空内容和结束标记:
// 替换原分隔符逻辑 $separator = '--'; $aContent = explode($separator, $content); // 过滤空内容和结束标记 $aContent = array_filter($aContent, function($item) { $trimmed = trim($item); return !empty($trimmed) && $trimmed !== '--'; });
2. 兼容不同格式的Content-ID
处理带<>和不带<>的Content-ID格式:
// 修改processImage函数中的Content-ID提取逻辑 $cidPos = strpos($thisContent, 'Content-ID:'); if ($cidPos === false) return false; $cidLine = substr($thisContent, $cidPos); $cidLine = substr($cidLine, 0, strpos($cidLine, "\n")); $img_id = trim(str_replace(['Content-ID:', '<', '>'], '', $cidLine));
3. 解码文件名的UTF-8编码
解析=?UTF-8?B?...?=格式的文件名,使用mb_decode_mimeheader解码:
// 修改文件名提取逻辑 $namePos = strpos($thisContent, 'name="'); if ($namePos !== false) { $nameEnd = strpos($thisContent, '"', $namePos + 6); $encodedName = substr($thisContent, $namePos + 6, $nameEnd - $namePos -6); $img_name = mb_decode_mimeheader($encodedName); } else { $img_name = 'unknown.' . $imgType; }
4. 正确提取Base64内容
找到Content-Transfer-Encoding: base64之后的空行,提取后续的Base64内容,并过滤空白行:
// 修改Base64内容提取逻辑 $base64Pos = strpos($thisContent, 'Content-Transfer-Encoding: base64'); if ($base64Pos === false) return false; // 找到base64行后的第一个空行 $emptyLinePos = strpos($thisContent, "\n\n", $base64Pos); if ($emptyLinePos === false) $emptyLinePos = strpos($thisContent, "\r\n\r\n", $base64Pos); $img_base64 = substr($thisContent, $emptyLinePos + 2); // 过滤空白行和末尾的分隔符残留 $img_base64 = preg_replace('/\s+/', '', $img_base64); $img_base64 = trim($img_base64, "\r\n");
5. 调整函数定义位置
将processImage函数移到循环外部,避免重复定义警告。
6. 正确提取HTML正文
找到Content-Type: text/html后的空行,提取后续的HTML内容:
// 修改HTML内容提取逻辑 if (strpos($thisContent, 'Content-Type: text/html') !== false) { $htmlStart = strpos($thisContent, "\n\n"); if ($htmlStart === false) $htmlStart = strpos($thisContent, "\r\n\r\n"); if ($htmlStart !== false) { $htmlContent = substr($thisContent, $htmlStart + 2); $thisHTMLContent = quoted_printable_decode($htmlContent); } }
<?php error_reporting(-1); $EML_FILE_PATH = ''; $filename = 'sample.eml'; $eml_file = $EML_FILE_PATH . $filename; // 读取EML文件 if (!$handle = fopen($eml_file, 'r')) { die('Cannot open file (' . $eml_file . ')'); } $content = fread($handle, filesize($eml_file)); fclose($handle); if ($content === false) { die('Error reading file (' . $eml_file . ')'); } // 拆分MIME部分 $separator = '--'; $aContent = explode($separator, $content); $aContent = array_filter($aContent, function($item) { $trimmed = trim($item); return !empty($trimmed) && $trimmed !== '--'; }); $aImages = array(); $thisHTMLContent = ''; // 处理图片的函数,移到循环外 function processImage($thisContent, $imgType) { // 提取Content-ID $cidPos = strpos($thisContent, 'Content-ID:'); if ($cidPos === false) return false; $cidLine = substr($thisContent, $cidPos); $cidLine = substr($cidLine, 0, strpos($cidLine, "\n")); $img_id = trim(str_replace(['Content-ID:', '<', '>'], '', $cidLine)); // 提取并解码文件名 $img_name = 'unknown.' . $imgType; $namePos = strpos($thisContent, 'name="'); if ($namePos !== false) { $nameEnd = strpos($thisContent, '"', $namePos + 6); $encodedName = substr($thisContent, $namePos + 6, $nameEnd - $namePos -6); $img_name = mb_decode_mimeheader($encodedName); } // 提取Base64内容 $base64Pos = strpos($thisContent, 'Content-Transfer-Encoding: base64'); if ($base64Pos === false) return false; $emptyLinePos = strpos($thisContent, "\n\n", $base64Pos); if ($emptyLinePos === false) $emptyLinePos = strpos($thisContent, "\r\n\r\n", $base64Pos); if ($emptyLinePos === false) return false; $img_base64 = substr($thisContent, $emptyLinePos + 2); $img_base64 = preg_replace('/\s+/', '', $img_base64); $img_base64 = trim($img_base64, "\r\n"); return [ 'id' => $img_id, 'name' => $img_name, 'type' => $imgType, 'base64' => $img_base64 ]; } // 遍历处理每个MIME部分 foreach ($aContent as $thisContent) { // 处理HTML正文 if (strpos($thisContent, 'Content-Type: text/html') !== false) { $htmlStart = strpos($thisContent, "\n\n"); if ($htmlStart === false) $htmlStart = strpos($thisContent, "\r\n\r\n"); if ($htmlStart !== false) { $htmlContent = substr($thisContent, $htmlStart + 2); $thisHTMLContent = quoted_printable_decode($htmlContent); } } // 处理GIF图片 if (strpos($thisContent, 'Content-Type: image/gif') !== false) { $imgData = processImage($thisContent, 'gif'); if ($imgData) $aImages[] = $imgData; } // 处理JPEG图片 if (strpos($thisContent, 'Content-Type: image/jpeg') !== false) { $imgData = processImage($thisContent, 'jpg'); if ($imgData) $aImages[] = $imgData; } } // 替换HTML中的cid引用为base64数据 foreach ($aImages as $image) { if (!empty($image['id']) && !empty($image['base64'])) { $imageData = 'data:image/' . $image['type'] . ';base64,' . $image['base64']; $thisHTMLContent = str_replace('cid:' . $image['id'], $imageData, $thisHTMLContent); } } echo $thisHTMLContent; ?>
内容的提问来源于stack exchange,提问作者Suneth Kalhara

