此文章为XIUNOX版本重构审计时发现问题,XIUNOX版本已优化修复此问题。分享出来方便后续想基于xiuno bbs4.0.4版本制作维护版本或插件模板等需求的开发者和站长参考。
问题:each() 函数在 PHP 8.0 被移除,发送邮件时 Fatal error
现象
Xiuno BBS 4.0.4 内置的 PHPMailer 兼容类(位于 xiunophp/xn_send_mail.func.php)使用 each() 函数遍历数组。each() 在 PHP 7.2 被废弃,PHP 8.0 起整体移除。任何调用 each() 的代码在 PHP 8 下都会抛出 Fatal error: Uncaught Error: Call to undefined function each(),导致用户注册、找回密码、邮件通知等所有依赖 SMTP 的功能不可用。
源码证据
文件 xiunobbs_4.0.4/xiunophp/xn_send_mail.func.php 共 3 处 each() 调用:
// 第 1827 行:EncodeQPphp() 中遍历 $lines
public function EncodeQPphp( $input = '', $line_max = 76, $space_conv = false) {
$hex = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
$lines = preg_split('/(?:\r\n|\r|\n)/', $input);
$eol = "\r\n";
$escape = '=';
$output = '';
while( list(, $line) = each($lines) ) { // <-- PHP 8 Fatal error
$linlen = strlen($line);
...
// 第 2933 行:邮件行折叠处理
while(list(,$line) = @each($lines)) { // <-- PHP 8 Fatal error
$lines_out = null;
if($line == "" && $in_headers) {
$in_headers = false;
}
// 第 2962 行:发送邮件时拆分行
while(list(,$line_out) = @each($lines_out)) { // <-- PHP 8 Fatal error
if(strlen($line_out) > 0)
{
if(substr($line_out, 0, 1) == ".") {
$line_out = "." . $line_out;
}
}
fputs($this->smtp_conn,$line_out . $this->CRLF);
注:xiunophp/array.func.php 第 135-136 行原本也使用过 each(),作者已注释掉并改写为 foreach,注释明确写道 // php 7.2 deprecated each(),说明作者已知该问题但未同步修复 xn_send_mail.func.php。
// xiunophp/array.func.php 第 134-137 行(已修复)
foreach($orderby as $k=>$v) {
// php 7.2 deprecated each()
//list($k, $v) = each($orderby);
风险等级与结论
兼容障碍(致命)。each() 在 PHP 8.0 被移除,调用即 Fatal error: Call to undefined function each()。
危害:
route/user.php第 5 行include _include(XIUNOPHP_PATH.'xn_send_mail.func.php');—— user 路由文件每次请求都会加载此 PHPMailer 类,但类定义本身不会立即触发错误;当用户开启user_create_email_on或user_resetpw_on并实际调用邮件发送(EncodeQPphp、SMTPfputs流程)时直接 Fatal error。第 1827 行
EncodeQPphp()是 Quoted-Printable 编码路径,几乎所有 HTML 邮件都会触发。第 2933、2962 行在 SMTP 协议层处理行折叠,任何通过 SMTP 发送的邮件都会触发。
修复建议:
将三处
while(list(,$v) = each($arr))改为foreach($arr as $v):第 1827 行:
foreach($lines as $line) {第 2933 行:
foreach($lines as $line) {第 2962 行:
foreach($lines_out as $line_out) {
由于
each()内部会推进数组指针,而foreach不会,需确认循环体内没有依赖数组指针位置的其他调用(当前代码未依赖,可安全替换)。同步升级 PHPMailer 到支持 PHP 8 的版本(≥ 6.5)。
