公司用 osTicket 系統幾年了,最近把環境升級到 PHP8 和最新版 osTicket v1.18.1 後,同事回報踩到信件內容的 cid 圖片沒正常處理,就先追蹤一下信件處理流程,主要先追到 MIMEDecode 即可。
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.6 LTS
Release: 20.04
Codename: focal
$ php -v
PHP 8.2.17 (cli) (built: Mar 16 2024 08:41:44) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.17, Copyright (c) Zend Technologies
with Zend OPcache v8.2.17, Copyright (c), by Zend Technologies
$ php api/cron.php
接著開始看 api/cron.php 程式碼,大概追到 include/class.cron.php 時,就可以看到 osTicket\Mail\Fetcher::run(); 和 include/class.email.php 檔案了,從 include/class.mailfetch.php 可以看到:
```php
78 function processMessage(int $i, array $defaults = []) {
79 try {
80 // Please note that the returned object could be anything from
81 // ticket, task to thread entry or a boolean.
82 // Don't let TicketApi call fool you!
83 return $this->getTicketsApi()->processEmail(
84 $this->mbox->getRawEmail($i), $defaults);
85 } catch (\TicketDenied $ex) {
86 // If a ticket is denied we're going to report it as processed
87 // so it can be moved out of the Fetch Folder or Deleted based
88 // on the MailBox settings.
89 return true;
90 } catch (\EmailParseError $ex) {
91 // Upstream we try to create a ticket on email parse error - if
92 // it fails then that means we have invalid headers.
93 // For Debug purposes log the parse error + headers as a warning
94 $this->logWarning(sprintf("%s\n\n%s",
95 $ex->getMessage(),
96 $this->mbox->getRawHeader($i)));
97 }
98 return false;
99 }
```
接著在 processEmail 前埋一個 @file_put_contents('/tmp/debug-mail', print_r($this->mbox->getRawEmail($i), true)); ,如此處理信件時,原始格式就會被記錄在 /tmp/debug-mail 裡,下一刻就能再重現信件格式的處理流程
小改後面:
$ sudo cp api/cron.php api/cron-debug.php
$ tail -n 5 api/cron-debug.php
//LocalCronApiController::call();
//
$obj = new \TicketApiController('cli');
print_r($obj->processEmail(file_get_contents('/tmp/debug-mail'), []));
?>
如此運行時就可以看資訊:
$ php api/cron-debug.php
Ticket Object
(
[ht] => Array
(
[ticket_id] => #
[ticket_pid] =>
[number] => #####
...
接著要來研究信件處理流程,就來到了 include/api.tickets.php 檔案:
```php
function processEmail($data=false, array $defaults = []) {
try {
if (!$data)
$data = $this->getEmailRequest();
elseif (!is_array($data))
$data = $this->parseEmail($data);
print_r($data);
} catch (Exception $ex) {
throw new EmailParseError($ex->getMessage());
}
...
```
繼續追 include/class.api.php 檔案:
```php
238 function parseRequest($stream, $format, $validate=true) {
239 $parser = null;
240 switch(strtolower($format)) {
241 case 'xml':
242 if (!function_exists('xml_parser_create'))
243 return $this->exerr(501, __('XML extension not supported'));
244 $parser = new ApiXmlDataParser();
245 break;
246 case 'json':
247 $parser = new ApiJsonDataParser();
248 break;
249 case 'email':
250 $parser = new ApiEmailDataParser();
251 break;
252 default:
253 return $this->exerr(415, __('Unsupported data format'));
254 }
255
256 if (!($data = $parser->parse($stream)) || !is_array($data)) {
257 $this->exerr(400, $parser->lastError());
258 }
259
260 //Validate structure of the request.
261 if ($validate && $data)
262 $this->validate($data, $format, false);
263
264 return $data;
265 }
266
267 function parseEmail($content) {
268 return $this->parseRequest($content, 'email', false);
269 }
```
繼續追 include/class.mailparse.php 檔案,主要是追蹤 EmailDataParser -> Mail_Parse -> Mail_mimeDecode ,最後就是 include/pear/Mail/mimeDecode.php 的處理 MIMEDecode 的實作了。
剩下的工作就是研究它解析原始信件的流程,看看是 MIMEDecode 是否少了遞迴解,還是有什麼限制了: