Header.php 8.6 KB
<?php

namespace Lib\Imap\Parse;


use Lib\Imap\Fun;

/**
 * 解析邮件
 * @author:dc
 * @time 2024/9/10 16:54
 * Class Header
 */
class Header{

    /**
     * @var array
     */
    private array $attributes = [];

    /**
     * 原始头信息
     * @var string
     */
    protected string $raw_header;

    /**
     * header消息体里面带内容的 qq的
     * @var string|mixed
     */
    protected  $raw_body;

    /**
     * Header constructor.
     * @param string $raw_header
     */
    public function __construct(string $raw_header)
    {
        $raw_header = str_replace("\r\n\n","\r\n\r\n",trim($raw_header));
        $raw_header = explode("\r\n\r\n",trim($raw_header),2);

        $this->raw_header = $raw_header[0];
        // qq的会把退信 内容以header形式返回
        $this->raw_body = $raw_header[1]??'';

        $this->rfc822_parse_headers();
    }

    /**
     * qq邮箱会这么干
     * 头信息里面的内容消息体
     * @return false|Body
     * @author:dc
     * @time 2025/1/10 11:01
     */
    public function body(){
        if($this->raw_body){
            if(!($this->raw_body instanceof Body)){
                $this->raw_body = new Body($this->raw_body,$this);
            }
            return $this->raw_body;
        }
        return false;
    }


    /**
     * @param string $name
     * @param string $value
     * @param bool $append
     * @author:dc
     * @time 2024/9/11 16:24
     */
    public function setAttribute(string $name, string $value, bool $append = true){
        $name = strtolower($name);

        if(!$append){
            $this->attributes[$name] = $value;
        }else{
            if(isset($this->attributes[$name]))
                $this->attributes[$name] .= "\r\n".$value;
            else
                $this->attributes[$name] = $value;
        }
    }


    /**
     * 解析邮件头部
     * @author:dc
     * @time 2024/9/10 16:29
     */
    private function rfc822_parse_headers(){
        $header = explode("\r\n",$this->raw_header);
        $name = '';
        foreach ($header as $str){
            // 判断是否是上一行的
            if(str_starts_with($str,' ') || str_starts_with($str,"\t")){
                $this->setAttribute($name,$str);
            }elseif(str_contains($str,':')){
                list($name, $str) = explode(":",$str,2);
                $this->setAttribute($name,$str);
            }

        }

        // 头信息content-type
        $contentType = $this->get('content-type');
        if($contentType){
            preg_match("/charset=\"?([a-z0-9-._]{2,})\"?/i",$contentType,$charset);
            if(!empty($charset[1])){
                // 编码
                $this->setAttribute('charset',$charset[1]);
            }
        }

        // 编码
        $charset = $this->get('charset');

        foreach ($this->attributes as $name => $attribute){
            if($charset){
                $attribute = Fun::mb_convert_encoding($attribute,'UTF-8',$charset);
            }
            $attribute = trim($attribute);
            $this->attributes[$name] = $attribute;
        }
    }


    public function __get(string $name)
    {
        return $this->get($name);
    }

    /**
     * 读取字段
     * @param string $name 字段名称
     * @param mixed ...$params 额外参数
     * @return mixed
     * @author:dc
     * @time 2024/12/11 11:50
     */
    public function get(string $name,...$params):mixed {
        $name = strtolower($name);

        $m = 'get'.str_replace(' ','',ucwords(str_replace('-',' ',$name)));

        if(method_exists($this,$m)){
            return $this->{$m}(...$params);
        }

        return $this->attributes[$name]??'';
    }


    /**
     * 获取收件地址 可能是假地址
     * @param bool $is_obj 是否解析成对象 Address
     * @return Address[]
     * @author:dc
     * @time 2024/9/11 15:03
     */
    public function getTo(bool $isArray = false):array {
        // 如果有这个字段,就用这个字段  to字段的邮箱可能被伪造
        $to = $this->attributes['to']??($this->attributes['delivered-to']??'');

        return $this->parseAddress($to,$isArray);
    }

    /**
     * 解析地址
     * @param string $address
     * @param false $isArray 是否返回数组
     * @return array
     * @author:dc
     * @time 2024/9/29 14:54
     */
    private function parseAddress(string $address, $isArray = false){
        $arr = [];
        $address = trim($address);
        if($address){
            $address = explode(',',$address);
            $s = '';
            foreach ($address as $k=>$str){
                // 是否是邮箱
                if (!(str_contains($str, '@') && str_contains($str,'.'))){
                    $s .= $str.',';
                    continue;
                }
                $str = $s.$str;
                $s = '';

                $arr[$k] = Address::make($str);
                if($isArray) $arr[$k] =  $arr[$k]->toArray();
            }
        }

        return array_values($arr);
    }

    /**
     * 抄送人
     * @return array
     * @author:dc
     * @time 2024/9/11 15:53
     */
    public function getCc(bool $isArray = false):array
    {
        return $this->parseAddress($this->attributes['cc']??'',$isArray);
    }

    /**
     * 密送人
     * @return array
     * @author:dc
     * @time 2024/9/11 15:54
     */
    public function getBcc(bool $isArray = false):array
    {
        return $this->parseAddress($this->attributes['bcc']??'',$isArray);
    }


    /**
     * 发件人 可能是欺炸 发件人
     * @param bool $isArray 是否返回数组
     * @return Address|array
     * @author:dc
     * @time 2024/12/11 11:52
     */
    public function getFrom(bool $isArray = false):Address|array {
        $address = Address::make($this->attributes['from']??'');
        if($isArray){
            return $address->toArray();
        }
        return $address;
    }

    /**
     * 获取解码后的主题
     * @return string
     * @author:dc
     * @time 2024/9/11 15:22
     */
    public function getSubject():string {
        return static::mime_decode($this->attributes['subject']??'');
    }


    /**
     * Boundary
     * @param string $str
     * @return string
     * @author:dc
     * @time 2024/9/11 16:05
     */
    public function getBoundary(string $str = ''): string
    {
        $pre = "/boundary=(.*?(?=;)|(.*))/i";
        // 在内容类型中读取 大多少情况都在这里面
        $contentType = $str ? $str: ($this->attributes['content-type']??'');
        if($contentType){
            preg_match($pre,$contentType,$b);
            if(!empty($b[1])){
                return trim(str_replace(['"',';'],'',$b[1]));
            }
        }
        if($this->raw_header && !$str){
            return $this->getBoundary($this->raw_header);
        }

        return '';
    }

    /**
     * 这个是是否加急 1加急 3普通 5不急
     * @return string
     */
    public function getPriority(): string
    {
        if(!empty($this->attributes['priority'])){
            return $this->attributes['priority'];
        }

        if(!empty($this->attributes['x-priority'])){
            return $this->attributes['x-priority'];
        }

        return '3';
    }



    /**
     * @return array
     */
    public function getAttributes(): array
    {
        return $this->attributes;
    }

    /**
     * @return array
     * @author:dc
     * @time 2024/9/11 16:15
     */
    public function toArray():array {
        $arr = [];
        foreach ($this->attributes as $key=>$attr){
            $arr[$key] = $this->get($key,true);
        }

        return $arr;
    }


    /**
     * @return string
     */
    public function getRaw(): string
    {
        return $this->raw_header;
    }

    /**
     * 解析加密字符
     * @param string $str
     * @return string
     * @author:dc
     * @time 2024/9/29 14:21
     */
    public static function mime_decode(string $str):string {

        $str = trim($str);
        $str = explode("\r\n",$str);
        $str = implode('',$str);
        preg_match_all("/=\?[a-z0-9-.]{3,}\?[bq]\?.*\?=/Ui",$str,$codes);
        foreach ($codes[0] as $c){
            if(preg_match("/^=\?([a-z0-9-.]{3,})\?[bq]\?.*\?=$/i",$c,$cc)){
                // 解码 这个函数好像已经转码了,
//            iso-8859-8-i php 好像没有这个编码 ,阿拉伯 iso-8859-8
//            =?iso-8859-8-i?B?4eTu+eog7PTw6en66iDg7CDn4fj6IERjb20gLSD08OnkIO7xIDgyNzUz?=
                $s = str_replace("=?{$cc[1]}?",'=?'.Fun::getEncodingAliases($cc[1]).'?',$c);

                $s = mb_decode_mimeheader($s);

                $str = str_replace($c,$s,$str);
            }
        }
        return $str;
    }


}