此文章为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)。