2024年3月27日 星期三

PHP 開發筆記 - 追蹤 osTicket v1.18.1 在 PHP8 環境處理信件的流程

公司用 osTicket 系統幾年了,最近把環境升級到 PHP8 和最新版 osTicket v1.18.1 後,同事回報踩到信件內容的 cid 圖片沒正常處理,就先追蹤一下信件處理流程,主要先追到 MIMEDecode 即可。

從官網文件 POP3/IMAP Settings Guide 得知,信件處理的觸發有從 crontab 和 api 的管道,目前就從 crontab 來追:

$ 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 是否少了遞迴解,還是有什麼限制了:

沒有留言:

張貼留言