作者 邓超

新imap

  1 +<?php
  2 +
  3 +namespace Lib\Imap;
  4 +
  5 +use Lib\Imap\Request\Folder;
  6 +use Lib\Imap\Request\Folders;
  7 +use Lib\Imap\Request\Login;
  8 +use Lib\Imap\Request\Noop;
  9 +
  10 +class Imap {
  11 +
  12 + /**
  13 + * 配置
  14 + * @var ImapConfig
  15 + */
  16 + public ImapConfig $config;
  17 +
  18 + /**
  19 + * @var ImapClient
  20 + */
  21 + public ImapClient $client;
  22 +
  23 + /**
  24 + * 当前在那个文件夹下面目录
  25 + * @var string
  26 + */
  27 + protected string $checkedFolder = '';
  28 +
  29 +
  30 + /**
  31 + * Imap constructor.
  32 + * @param ImapConfig $config
  33 + */
  34 + public function __construct(ImapConfig $config)
  35 + {
  36 + $this->config = $config;
  37 +
  38 + $this->client = new ImapClient($this->config->getHost());
  39 + }
  40 +
  41 + /**
  42 + * 设置是否调试
  43 + * @param bool $debug
  44 + * @return $this
  45 + * @author:dc
  46 + * @time 2024/9/19 9:41
  47 + */
  48 + public function debug(bool $debug = true):static {
  49 + $this->client->setDebug($debug);
  50 + return $this;
  51 + }
  52 +
  53 + /**
  54 + * @param string $host
  55 + * @param string $email
  56 + * @param string $password
  57 + * @return Login
  58 + * @throws \Exception
  59 + * @author:dc
  60 + * @time 2024/9/13 17:45
  61 + */
  62 + public function login():Login {
  63 + // 打开连接
  64 + $this->client->open();
  65 +
  66 + //有的服务器是强制要求设置id属性 已知 163 有的服务器没有id命令 这里就不处理结果了
  67 + $this->client->request('ID ("name" "测试本地 Client" "version" "1" "os" "测试本地" "os-version" "1.0")');
  68 +
  69 + // 登录
  70 + return (new Login($this))->exec();
  71 +
  72 + }
  73 +
  74 + /**
  75 + * 选择文件夹
  76 + * @param string|\Lib\Imap\Parse\Folder\Folder $folder 目录
  77 + * @param bool $utf7 是否是utf7编码
  78 + * @return Folder
  79 + * @author:dc
  80 + * @time 2024/9/19 9:57
  81 + */
  82 + public function folder(string|\Lib\Imap\Parse\Folder\Folder $folder, bool $utf7 = true):Folder {
  83 + if($folder instanceof \Lib\Imap\Parse\Folder\Folder){
  84 + $folder = $folder->folder;
  85 + }else{
  86 + if(!$utf7) $folder = mb_convert_encoding($folder,"UTF7-IMAP","UTF-8");
  87 + }
  88 + return new Folder($this,$folder);
  89 + }
  90 +
  91 + /**
  92 + * 读取文件夹
  93 + * @return \Lib\Imap\Parse\Folder\Folders
  94 + * @author:dc
  95 + * @time 2024/9/19 14:10
  96 + */
  97 + public function getFolders():\Lib\Imap\Parse\Folder\Folders {
  98 + $folders = new Folders($this);
  99 +
  100 + return $folders->get();
  101 + }
  102 +
  103 +
  104 + /**
  105 + * 类似ping
  106 + * @return bool
  107 + * @author:dc
  108 + * @time 2024/9/20 14:45
  109 + */
  110 + public function noop():bool {
  111 + return (new Noop($this))->exec()->isOk();
  112 + }
  113 +
  114 +
  115 + /**
  116 + * 读取或验证是否在当前目录下面
  117 + * @param string $folder
  118 + * @return bool|string
  119 + * @author:dc
  120 + * @time 2024/9/20 13:58
  121 + */
  122 + public function checkedFolder(string $folder = ''):bool|string {
  123 + if($folder){
  124 + return $folder == $this->checkedFolder;
  125 + }
  126 + return $this->checkedFolder;
  127 + }
  128 +
  129 + /**
  130 + * 设置当前进入那个文件夹
  131 + * @param string $folder
  132 + * @author:dc
  133 + * @time 2024/9/20 14:00
  134 + */
  135 + public function setCheckedFolder(string $folder):void {
  136 + $this->checkedFolder = $folder;
  137 + }
  138 +
  139 +
  140 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap;
  4 +
  5 +class ImapClient {
  6 +
  7 + /**
  8 + * 资源
  9 + * @var
  10 + */
  11 + private $socket;
  12 +
  13 + protected bool $debug = false;
  14 +
  15 + protected string $host;
  16 +
  17 +
  18 + /**
  19 + * 每请求一次就会+1
  20 + * @var int
  21 + */
  22 + public int $tagNum = 0;
  23 +
  24 + public function __construct(string $host){
  25 + $this->host = $host;
  26 + }
  27 +
  28 + public function open(){
  29 + $content = stream_context_create([
  30 + 'ssl' => [
  31 + 'verify_peer' => false, // 有的证书和域名不匹配,这里关闭认证
  32 + 'verify_peer_name' => false,// 有的证书和域名不匹配,这里关闭认证
  33 + ]
  34 + ]);
  35 +
  36 + $flags = STREAM_CLIENT_CONNECT;
  37 +
  38 + $this->socket = stream_socket_client(
  39 + $this->host,
  40 + $errno,
  41 + $error,
  42 + 30,
  43 + $flags,
  44 + $content
  45 + );
  46 +
  47 + if($error){
  48 + throw new \Exception("socket error: {$error}");
  49 + }
  50 +
  51 + if (!$this->socket) {
  52 + throw new \Exception($this->host." connection fail.");
  53 + }
  54 + }
  55 +
  56 +
  57 + /**
  58 + * @param bool $debug
  59 + * @return $this
  60 + * @author:dc
  61 + * @time 2024/9/18 9:57
  62 + */
  63 + public function setDebug(bool $debug = true): static
  64 + {
  65 + $this->debug = $debug;
  66 + return $this;
  67 + }
  68 +
  69 +
  70 +
  71 + /**
  72 + * 写
  73 + * @param string $cmd
  74 + * @return false|int
  75 + * @author:dc
  76 + * @time 2024/9/13 15:47
  77 + */
  78 + protected function write(string $cmd){
  79 + if($this->debug){
  80 + echo 'write: '.$cmd;
  81 + }
  82 + return fwrite($this->socket,$cmd);
  83 + }
  84 +
  85 + /**
  86 + * 读
  87 + * @return false|string
  88 + * @author:dc
  89 + * @time 2024/9/13 15:49
  90 + */
  91 + protected function readLine(){
  92 + $str = fgets($this->socket,2048);
  93 + if($this->debug){
  94 + echo 'read: '.$str;
  95 + }
  96 + return $str;
  97 + }
  98 +
  99 + /**
  100 + *
  101 + * @param string $cmd 执行的命令
  102 + * @return array
  103 + * @author:dc
  104 + * @time 2024/9/13 15:57
  105 + */
  106 + public function request(string $cmd):array {
  107 + $this->tagNum++;
  108 + $tag = 'TAG'.$this->tagNum;
  109 + // 请求数据
  110 + $writeNumber = $this->write($tag.' '.$cmd."\r\n");
  111 + $result = [];
  112 + if($writeNumber){
  113 + // 读取数据
  114 + while (1){
  115 + $line = $this->readLine();
  116 + $result[] = $line;
  117 + list($token) = explode(' ',$line,2);
  118 + // 结束了
  119 + if($token == $tag || $line === false){
  120 + break;
  121 + }
  122 + }
  123 + }
  124 +
  125 + return $result;
  126 +
  127 + }
  128 +
  129 +
  130 +
  131 +}
  1 +<?php
  2 +namespace Lib\Imap;
  3 +
  4 +class ImapConfig {
  5 +
  6 + protected string $host = '';
  7 + protected string $password = '';
  8 + protected string $email = '';
  9 +
  10 +
  11 + /**
  12 + * @param string $email
  13 + */
  14 + public function setEmail(string $email): static
  15 + {
  16 + $this->email = $email;
  17 + return $this;
  18 + }
  19 +
  20 +
  21 + /**
  22 + * @param string $host
  23 + */
  24 + public function setHost(string $host): static
  25 + {
  26 + $this->host = $host;
  27 + return $this;
  28 + }
  29 +
  30 + /**
  31 + * @param string $password
  32 + */
  33 + public function setPassword(string $password): static
  34 + {
  35 + $this->password = $password;
  36 + return $this;
  37 + }
  38 +
  39 + /**
  40 + * @return string
  41 + */
  42 + public function getEmail(): string
  43 + {
  44 + return $this->email;
  45 + }
  46 +
  47 + /**
  48 + * @return string
  49 + */
  50 + public function getHost(): string
  51 + {
  52 + return $this->host;
  53 + }
  54 +
  55 + /**
  56 + * @return string
  57 + */
  58 + public function getPassword(): string
  59 + {
  60 + return $this->password;
  61 + }
  62 +
  63 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap;
  4 +
  5 +class ImapPool {
  6 +
  7 + /**
  8 + * @var Imap[]
  9 + */
  10 + private static array $pool;
  11 +
  12 +
  13 + /**
  14 + * 获取连接
  15 + * @param ImapConfig $config
  16 + * @return Imap
  17 + * @author:dc
  18 + * @time 2024/9/14 9:19
  19 + */
  20 + public static function get(ImapConfig $config){
  21 + if(!isset(static::$pool[$config->getEmail()])){
  22 + static::$pool[$config->getEmail()] = new Imap($config);
  23 + }
  24 + return static::$pool[$config->getEmail()];
  25 + }
  26 +
  27 +
  28 + public static function release(Imap $imap){
  29 +
  30 + }
  31 +
  32 +
  33 +
  34 + public function noop(){
  35 +
  36 + }
  37 +
  38 +
  39 +}
  1 +<?php
  2 +namespace Lib\Imap;
  3 +
  4 +/**
  5 + * 搜索邮箱
  6 + * TODO:: 有些邮箱服务器并不支持搜索 或者 某些字段搜索
  7 + * 日期类搜索是没问题的
  8 + * @author:dc
  9 + * @time 2024/9/14 15:48
  10 + * Class ImapSearch
  11 + * @package Lib\Imap
  12 + */
  13 +class ImapSearch {
  14 +
  15 + /**
  16 + * @var array
  17 + */
  18 + protected array $where = [];
  19 +
  20 +
  21 +
  22 + /**
  23 + * 搜索已标记为 已回复 的邮件
  24 + * @param bool $a 是否已标记为 已回复
  25 + * @return $this
  26 + * @author:dc
  27 + * @time 2024/9/14 16:22
  28 + */
  29 + public function answered(bool $a = true):static {
  30 + $this->where[$a?'ANSWERED':'UNANSWERED'] = true;
  31 + return $this;
  32 + }
  33 +
  34 + /**
  35 + * 搜索已标记为删除的
  36 + * Messages with the \Deleted flag set.
  37 + * @param bool $del 是否已删除的
  38 + * @return $this
  39 + * @author:dc
  40 + * @time 2024/9/14 16:21
  41 + */
  42 + public function deleted(bool $del = true):static {
  43 + $this->where[$del?'DELETED':'UNDELETED'] = true;
  44 + return $this;
  45 + }
  46 +
  47 + /**
  48 + * 搜索标记为草稿的
  49 + * Messages with the \Draft flag set.
  50 + * @param bool $draft 是否是草稿
  51 + * @return $this
  52 + * @author:dc
  53 + * @time 2024/9/14 16:20
  54 + */
  55 + public function draft(bool $draft = true):static {
  56 + $this->where[$draft?'DRAFT':'UNDRAFT'] = true;
  57 + return $this;
  58 + }
  59 +
  60 + /**
  61 + * 搜索标记为星标的邮件
  62 + * @param bool $flagged 是否带星标
  63 + * @return $this
  64 + * @author:dc
  65 + * @time 2024/9/14 16:19
  66 + */
  67 + public function flagged(bool $flagged = true):static {
  68 + $this->where[$flagged?'FLAGGED':'UNFLAGGED'] = true;
  69 + return $this;
  70 + }
  71 +
  72 + /**
  73 + * 搜索未读 还是已读
  74 + * @param bool $seen 是否标记为已读
  75 + * @return $this
  76 + * @author:dc
  77 + * @time 2024/9/14 16:23
  78 + */
  79 + public function seen(bool $seen = true):static {
  80 + $this->where[$seen?'SEEN':'UNSEEN'] = true;
  81 + return $this;
  82 + }
  83 +
  84 +
  85 + /**
  86 + * 搜索密送 中有这个邮箱的
  87 + * @param string $bcc 邮箱
  88 + * @return $this
  89 + * @author:dc
  90 + * @time 2024/9/14 15:57
  91 + */
  92 + public function bcc(string $bcc):static {
  93 + $this->where['BCC'] = $bcc;
  94 + return $this;
  95 + }
  96 +
  97 + /**
  98 + * 搜索抄送 中有这个邮箱的
  99 + * Messages that contain the specified string in the envelope structure's CC field.
  100 + * @param string $cc 邮箱
  101 + * @return $this
  102 + * @author:dc
  103 + * @time 2024/9/14 16:06
  104 + */
  105 + public function cc(string $cc):static {
  106 + $this->where['CC'] = $cc;
  107 + return $this;
  108 + }
  109 +
  110 + /**
  111 + * 搜索 发件人
  112 + * @param string $from
  113 + * @return $this
  114 + * @author:dc
  115 + * @time 2024/9/14 16:10
  116 + */
  117 + public function from(string $from):static {
  118 + $this->where['FROM'] = $from;
  119 + return $this;
  120 + }
  121 +
  122 + /**
  123 + * 搜索收件人
  124 + * @param string $to
  125 + * @return $this
  126 + * @author:dc
  127 + * @time 2024/9/14 16:17
  128 + */
  129 + public function to(string $to):static {
  130 + $this->where['TO'] = $to;
  131 + return $this;
  132 + }
  133 +
  134 + /**
  135 + * 搜索内容
  136 + * Messages that contain the specified string in the body of the message.
  137 + * @param string $body
  138 + * @return $this
  139 + * @author:dc
  140 + * @time 2024/9/14 16:05
  141 + */
  142 + public function body(string $body):static {
  143 + $this->where['BODY'] = $body;
  144 + return $this;
  145 + }
  146 +
  147 + /**
  148 + * 搜索 日期 小于某个日期的
  149 + * Messages whose internal date (disregarding time and timezone) is earlier than the specified date.
  150 + * @param string $date 日期
  151 + * @author:dc
  152 + * @time 2024/9/14 16:03
  153 + */
  154 + public function dateLt(string $date):static{
  155 + $this->where['BEFORE'] = date('d-M-Y',strtotime($date));
  156 + return $this;
  157 + }
  158 +
  159 + /**
  160 + * 搜索指定日期的邮件
  161 + * Messages whose internal date (disregarding time and timezone) is within the specified date.
  162 + * @param string $date
  163 + * @author:dc
  164 + * @time 2024/9/14 16:12
  165 + */
  166 + public function date(string $date):static{
  167 + $this->where['ON'] = date('d-M-Y',strtotime($date));
  168 + return $this;
  169 + }
  170 +
  171 + /**
  172 + * 搜索指定日期后的邮件 如 2024-04-04 显示这个日期过后的
  173 + * Messages whose internal date (disregarding time and timezone) is within or later than the specified date.
  174 + * @param string $date
  175 + * @author:dc
  176 + * @time 2024/9/14 16:14
  177 + */
  178 + public function dateGt(string $date):static{
  179 + $this->where['SINCE'] = date('d-M-Y',strtotime($date));
  180 + return $this;
  181 + }
  182 +
  183 + /**
  184 + * 模糊搜索主题
  185 + * @param string $subject 主题
  186 + * @author:dc
  187 + * @time 2024/9/14 16:16
  188 + */
  189 + public function subject(string $subject):static {
  190 + $this->where['SUBJECT'] = $subject;
  191 + return $this;
  192 + }
  193 +
  194 +
  195 + /**
  196 + * 返回搜索字符串
  197 + * @author:dc
  198 + * @time 2024/9/14 16:27
  199 + */
  200 + public function toString():string {
  201 + $arr = [];
  202 + foreach ($this->where as $k=>$v){
  203 + if(is_bool($v)){
  204 + $arr[] = $k;
  205 + }else{
  206 + $arr[] = $k.' "'.addcslashes($v,'"').'"';
  207 + }
  208 + }
  209 + return implode(' ',$arr);
  210 + }
  211 +
  212 +
  213 +
  214 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Parse;
  4 +
  5 +/**
  6 + * @author:dc
  7 + * @time 2024/9/11 11:17
  8 + * Class Address
  9 + * @package Imap\Parse
  10 + */
  11 +class Address {
  12 +
  13 + public string $name = '';
  14 +
  15 + public string $email = '';
  16 +
  17 + private string $raw;
  18 +
  19 + private function __construct(string $address){
  20 + $this->raw = $address;
  21 + if($this->raw){
  22 + $this->parse();
  23 + }
  24 + }
  25 +
  26 + /**
  27 + * 创建一个地址类
  28 + * @param string $address
  29 + * @return static
  30 + * @author:dc
  31 + * @time 2024/9/11 11:37
  32 + */
  33 + public static function make(string $address):self {
  34 + return new self($address);
  35 + }
  36 +
  37 + /**
  38 + * 解析地址
  39 + * @author:dc
  40 + * @time 2024/9/11 11:39
  41 + */
  42 + private function parse(){
  43 +
  44 + $email = self::pregEmail($this->raw);
  45 +
  46 + if(!empty($email)){
  47 + $this->email = $email;
  48 + $this->name = trim(str_replace([$email,'"','<','>','&gt;','&lt;'],'',$this->raw));
  49 + }
  50 + if($this->name){
  51 + $this->name = DeCode::decode($this->name);
  52 + }else{
  53 + $this->name = explode('@',$this->email)[0]??'';
  54 + }
  55 +
  56 + }
  57 +
  58 + /**
  59 + * 匹配邮箱
  60 + * @param $str
  61 + * @return string
  62 + * @author:dc
  63 + * @time 2024/9/11 11:43
  64 + */
  65 + private function pregEmail(string $str):string {
  66 + preg_match('/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/',$str,$email);
  67 + if(empty($email[0])){
  68 + // 邮箱2
  69 + preg_match('/[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/',$str,$email);
  70 + }
  71 + return str_replace(['<','>'],'',$email[0]??'');
  72 + }
  73 +
  74 +
  75 + /**
  76 + * @return string
  77 + */
  78 + public function getRaw(): string
  79 + {
  80 + return $this->raw;
  81 + }
  82 +
  83 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Parse;
  4 +
  5 +/**
  6 + * 邮件内容
  7 + * @author:dc
  8 + * @time 2024/9/20 14:57
  9 + * Class Body
  10 + * @package Lib\Imap\Parse
  11 + */
  12 +class Body {
  13 +
  14 + /**
  15 + * 原始数据
  16 + * @var string
  17 + */
  18 + protected string $raw = '';
  19 +
  20 + /**
  21 + * 解析后的数据
  22 + * @var array
  23 + */
  24 + protected array $item = [];
  25 +
  26 +
  27 + public function __construct(string $result)
  28 + {
  29 + // 匹配出id和数据
  30 + if(preg_match("/\* (\d+) FETCH \(/",$result)){
  31 +
  32 + }
  33 +
  34 + $this->parse();
  35 + }
  36 +
  37 +
  38 +
  39 + /**
  40 + * 解析
  41 + * @author:dc
  42 + * @time 2024/9/14 17:35
  43 + */
  44 + protected function parse(){
  45 +
  46 +
  47 +
  48 + }
  49 +
  50 +
  51 +
  52 +
  53 +}
  54 +
  55 +
  56 +
  57 +
  58 +
  59 +
  60 +
  61 +
  62 +
  63 +
  64 +
  65 +
  66 +
  67 +
  68 +
  69 +
  70 +
  71 +
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Parse;
  4 +
  5 +
  6 +
  7 +/**
  8 + * 解析邮件
  9 + * @author:dc
  10 + * @time 2024/9/10 16:54
  11 + * Class Header
  12 + */
  13 +class DeCode{
  14 +
  15 + /**
  16 + * Fallback Encoding
  17 + *
  18 + * @var string
  19 + */
  20 + public $fallback_encoding = 'UTF-8';
  21 +
  22 +
  23 + /**
  24 + * 进行解码
  25 + * @author:dc
  26 + * @time 2024/9/10 16:56
  27 + */
  28 + public static function decode(string $value){
  29 + $obj = new self();
  30 + $value = trim($value);
  31 + $original_value = $value;
  32 + $is_utf8_base = $obj->is_uft8($value);
  33 + if ($is_utf8_base) {
  34 + $value = mb_decode_mimeheader($value);
  35 + }
  36 + if ($obj->notDecoded($original_value, $value)) {
  37 + $decoded_value = $obj->mime_header_decode($value);
  38 + if (count($decoded_value) > 0) {
  39 + if (property_exists($decoded_value[0], "text")) {
  40 + $value = $decoded_value[0]->text;
  41 + }
  42 + }
  43 + }
  44 + return $value;
  45 + }
  46 +
  47 + /**
  48 + * Decode MIME header elements
  49 + * @link https://php.net/manual/en/function.imap-mime-header-decode.php
  50 + * @param string $text The MIME text
  51 + *
  52 + * @return array The decoded elements are returned in an array of objects, where each
  53 + * object has two properties, charset and text.
  54 + */
  55 + private function mime_header_decode(string $text): array {
  56 + $charset = $this->getEncoding($text);
  57 + return [(object)[
  58 + "charset" => $charset,
  59 + "text" => $this->convertEncoding($text, $charset)
  60 + ]];
  61 + }
  62 +
  63 + /**
  64 + * Convert the encoding
  65 + * @param $str
  66 + * @param string $from
  67 + * @param string $to
  68 + *
  69 + * @return mixed|string
  70 + */
  71 + public function convertEncoding($str, $from = "ISO-8859-2", $to = "UTF-8") {
  72 +
  73 + $str = mb_decode_mimeheader($str);
  74 +
  75 + $from = EncodingAliases::get($from, $this->fallback_encoding);
  76 + $to = EncodingAliases::get($to, $this->fallback_encoding);
  77 +
  78 + if ($from === $to) {
  79 + return $str;
  80 + }
  81 +
  82 + // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
  83 + // ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
  84 + // https://stackoverflow.com/a/11303410
  85 + //
  86 + // us-ascii is the same as ASCII:
  87 + // ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
  88 + // prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
  89 + // based on the typographical symbols predominantly in use there.
  90 + // https://en.wikipedia.org/wiki/ASCII
  91 + //
  92 + // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
  93 + if (strtolower($from) == 'us-ascii' && $to == 'UTF-8') {
  94 + return $str;
  95 + }
  96 +
  97 + try {
  98 +
  99 + if(mb_detect_encoding($str)=='UTF-8'){
  100 + return $str;
  101 + }
  102 +
  103 + if (!$from) {
  104 + return mb_convert_encoding($str, $to);
  105 + }
  106 +
  107 + return mb_convert_encoding($str, $to, $from);
  108 +
  109 + } catch (\Exception $e) {
  110 + if (strstr($from, '-')) {
  111 + $from = str_replace('-', '', $from);
  112 + return $this->convertEncoding($str, $from, $to);
  113 + } else {
  114 + return $str;
  115 + }
  116 + }
  117 + }
  118 +
  119 +
  120 + /**
  121 + * Get the encoding of a given abject
  122 + * @param object|string $structure
  123 + *
  124 + * @return string
  125 + */
  126 + private function getEncoding($structure): string {
  127 + if (property_exists($structure, 'parameters')) {
  128 + foreach ($structure->parameters as $parameter) {
  129 + if (strtolower($parameter->attribute) == "charset") {
  130 + return EncodingAliases::get($parameter->value, $this->fallback_encoding);
  131 + }
  132 + }
  133 + } elseif (property_exists($structure, 'charset')) {
  134 + return EncodingAliases::get($structure->charset, $this->fallback_encoding);
  135 + } elseif (is_string($structure) === true) {
  136 +
  137 + preg_match("/^=\?([a-z0-9-]{3,})\?/i",$structure,$code);
  138 + if(!empty($code[1])){
  139 + $code = EncodingAliases::get($code[1],'');
  140 + if($code){
  141 + return $code;
  142 + }
  143 + }
  144 +
  145 + $result = mb_detect_encoding($structure);
  146 + return $result === false ? $this->fallback_encoding : $result;
  147 + }
  148 +
  149 + return $this->fallback_encoding;
  150 + }
  151 +
  152 +
  153 + /**
  154 + * Check if a given pair of strings has been decoded
  155 + * @param $encoded
  156 + * @param $decoded
  157 + *
  158 + * @return bool
  159 + */
  160 + private function notDecoded($encoded, $decoded): bool {
  161 + return 0 === strpos($decoded, '=?')
  162 + && strlen($decoded) - 2 === strpos($decoded, '?=')
  163 + && false !== strpos($encoded, $decoded);
  164 + }
  165 +
  166 +
  167 +
  168 + /**
  169 + * Test if a given value is utf-8 encoded
  170 + * @param $value
  171 + *
  172 + * @return bool
  173 + */
  174 + private function is_uft8($value): bool {
  175 + return strpos(strtolower($value), '=?utf-8?') === 0;
  176 + }
  177 +
  178 +
  179 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Parse;
  4 +
  5 +/**
  6 + * Class EncodingAliases
  7 + *
  8 + * @package Webklex\PHPIMAP
  9 + */
  10 +class EncodingAliases {
  11 +
  12 + /**
  13 + * Contains email encoding mappings
  14 + *
  15 + * @var array
  16 + */
  17 + private static $aliases = [
  18 + /*
  19 + |--------------------------------------------------------------------------
  20 + | Email encoding aliases
  21 + |--------------------------------------------------------------------------
  22 + |
  23 + | Email encoding aliases used to convert to iconv supported charsets
  24 + |
  25 + |
  26 + | This Source Code Form is subject to the terms of the Mozilla Public
  27 + | License, v. 2.0. If a copy of the MPL was not distributed with this
  28 + | file, You can obtain one at http://mozilla.org/MPL/2.0/.
  29 + |
  30 + | This Original Code has been modified by IBM Corporation.
  31 + | Modifications made by IBM described herein are
  32 + | Copyright (c) International Business Machines
  33 + | Corporation, 1999
  34 + |
  35 + | Modifications to Mozilla code or documentation
  36 + | identified per MPL Section 3.3
  37 + |
  38 + | Date Modified by Description of modification
  39 + | 12/09/1999 IBM Corp. Support for IBM codepages - 850,852,855,857,862,864
  40 + |
  41 + | Rule of this file:
  42 + | 1. key should always be in lower case ascii so we can do case insensitive
  43 + | comparison in the code faster.
  44 + | 2. value should be the one used in unicode converter
  45 + |
  46 + | 3. If the charset is not used for document charset, but font charset
  47 + | (e.g. XLFD charset- such as JIS x0201, JIS x0208), don't put here
  48 + |
  49 + */
  50 + "ascii" => "us-ascii",
  51 + "us-ascii" => "us-ascii",
  52 + "ansi_x3.4-1968" => "us-ascii",
  53 + "646" => "us-ascii",
  54 + "iso-8859-1" => "ISO-8859-1",
  55 + "iso-8859-2" => "ISO-8859-2",
  56 + "iso-8859-3" => "ISO-8859-3",
  57 + "iso-8859-4" => "ISO-8859-4",
  58 + "iso-8859-5" => "ISO-8859-5",
  59 + "iso-8859-6" => "ISO-8859-6",
  60 + "iso-8859-6-i" => "ISO-8859-6-I",
  61 + "iso-8859-6-e" => "ISO-8859-6-E",
  62 + "iso-8859-7" => "ISO-8859-7",
  63 + "iso-8859-8" => "ISO-8859-8",
  64 + "iso-8859-8-i" => "ISO-8859-8-I",
  65 + "iso-8859-8-e" => "ISO-8859-8-E",
  66 + "iso-8859-9" => "ISO-8859-9",
  67 + "iso-8859-10" => "ISO-8859-10",
  68 + "iso-8859-11" => "ISO-8859-11",
  69 + "iso-8859-13" => "ISO-8859-13",
  70 + "iso-8859-14" => "ISO-8859-14",
  71 + "iso-8859-15" => "ISO-8859-15",
  72 + "iso-8859-16" => "ISO-8859-16",
  73 + "iso-ir-111" => "ISO-IR-111",
  74 + "iso-2022-cn" => "ISO-2022-CN",
  75 + "iso-2022-cn-ext" => "ISO-2022-CN",
  76 + "iso-2022-kr" => "ISO-2022-KR",
  77 + "iso-2022-jp" => "ISO-2022-JP",
  78 + "utf-16be" => "UTF-16BE",
  79 + "utf-16le" => "UTF-16LE",
  80 + "utf-16" => "UTF-16",
  81 + "windows-1250" => "windows-1250",
  82 + "windows-1251" => "windows-1251",
  83 + "windows-1252" => "windows-1252",
  84 + "windows-1253" => "windows-1253",
  85 + "windows-1254" => "windows-1254",
  86 + "windows-1255" => "windows-1255",
  87 + "windows-1256" => "windows-1256",
  88 + "windows-1257" => "windows-1257",
  89 + "windows-1258" => "windows-1258",
  90 + "ibm866" => "IBM866",
  91 + "ibm850" => "IBM850",
  92 + "ibm852" => "IBM852",
  93 + "ibm855" => "IBM855",
  94 + "ibm857" => "IBM857",
  95 + "ibm862" => "IBM862",
  96 + "ibm864" => "IBM864",
  97 + "utf-8" => "UTF-8",
  98 + "utf-7" => "UTF-7",
  99 + "shift_jis" => "Shift_JIS",
  100 + "big5" => "Big5",
  101 + "euc-jp" => "EUC-JP",
  102 + "euc-kr" => "EUC-KR",
  103 + "gb2312" => "GB2312",
  104 + "gb18030" => "gb18030",
  105 + "viscii" => "VISCII",
  106 + "koi8-r" => "KOI8-R",
  107 + "koi8_r" => "KOI8-R",
  108 + "cskoi8r" => "KOI8-R",
  109 + "koi" => "KOI8-R",
  110 + "koi8" => "KOI8-R",
  111 + "koi8-u" => "KOI8-U",
  112 + "tis-620" => "TIS-620",
  113 + "t.61-8bit" => "T.61-8bit",
  114 + "hz-gb-2312" => "HZ-GB-2312",
  115 + "big5-hkscs" => "Big5-HKSCS",
  116 + "gbk" => "gbk",
  117 + "cns11643" => "x-euc-tw",
  118 + //
  119 + // Aliases for ISO-8859-1
  120 + //
  121 + "latin1" => "ISO-8859-1",
  122 + "iso_8859-1" => "ISO-8859-1",
  123 + "iso8859-1" => "ISO-8859-1",
  124 + "iso8859-2" => "ISO-8859-2",
  125 + "iso8859-3" => "ISO-8859-3",
  126 + "iso8859-4" => "ISO-8859-4",
  127 + "iso8859-5" => "ISO-8859-5",
  128 + "iso8859-6" => "ISO-8859-6",
  129 + "iso8859-7" => "ISO-8859-7",
  130 + "iso8859-8" => "ISO-8859-8",
  131 + "iso8859-9" => "ISO-8859-9",
  132 + "iso8859-10" => "ISO-8859-10",
  133 + "iso8859-11" => "ISO-8859-11",
  134 + "iso8859-13" => "ISO-8859-13",
  135 + "iso8859-14" => "ISO-8859-14",
  136 + "iso8859-15" => "ISO-8859-15",
  137 + "iso_8859-1:1987" => "ISO-8859-1",
  138 + "iso-ir-100" => "ISO-8859-1",
  139 + "l1" => "ISO-8859-1",
  140 + "ibm819" => "ISO-8859-1",
  141 + "cp819" => "ISO-8859-1",
  142 + "csisolatin1" => "ISO-8859-1",
  143 + //
  144 + // Aliases for ISO-8859-2
  145 + //
  146 + "latin2" => "ISO-8859-2",
  147 + "iso_8859-2" => "ISO-8859-2",
  148 + "iso_8859-2:1987" => "ISO-8859-2",
  149 + "iso-ir-101" => "ISO-8859-2",
  150 + "l2" => "ISO-8859-2",
  151 + "csisolatin2" => "ISO-8859-2",
  152 + //
  153 + // Aliases for ISO-8859-3
  154 + //
  155 + "latin3" => "ISO-8859-3",
  156 + "iso_8859-3" => "ISO-8859-3",
  157 + "iso_8859-3:1988" => "ISO-8859-3",
  158 + "iso-ir-109" => "ISO-8859-3",
  159 + "l3" => "ISO-8859-3",
  160 + "csisolatin3" => "ISO-8859-3",
  161 + //
  162 + // Aliases for ISO-8859-4
  163 + //
  164 + "latin4" => "ISO-8859-4",
  165 + "iso_8859-4" => "ISO-8859-4",
  166 + "iso_8859-4:1988" => "ISO-8859-4",
  167 + "iso-ir-110" => "ISO-8859-4",
  168 + "l4" => "ISO-8859-4",
  169 + "csisolatin4" => "ISO-8859-4",
  170 + //
  171 + // Aliases for ISO-8859-5
  172 + //
  173 + "cyrillic" => "ISO-8859-5",
  174 + "iso_8859-5" => "ISO-8859-5",
  175 + "iso_8859-5:1988" => "ISO-8859-5",
  176 + "iso-ir-144" => "ISO-8859-5",
  177 + "csisolatincyrillic" => "ISO-8859-5",
  178 + //
  179 + // Aliases for ISO-8859-6
  180 + //
  181 + "arabic" => "ISO-8859-6",
  182 + "iso_8859-6" => "ISO-8859-6",
  183 + "iso_8859-6:1987" => "ISO-8859-6",
  184 + "iso-ir-127" => "ISO-8859-6",
  185 + "ecma-114" => "ISO-8859-6",
  186 + "asmo-708" => "ISO-8859-6",
  187 + "csisolatinarabic" => "ISO-8859-6",
  188 + //
  189 + // Aliases for ISO-8859-6-I
  190 + //
  191 + "csiso88596i" => "ISO-8859-6-I",
  192 + //
  193 + // Aliases for ISO-8859-6-E",
  194 + //
  195 + "csiso88596e" => "ISO-8859-6-E",
  196 + //
  197 + // Aliases for ISO-8859-7",
  198 + //
  199 + "greek" => "ISO-8859-7",
  200 + "greek8" => "ISO-8859-7",
  201 + "sun_eu_greek" => "ISO-8859-7",
  202 + "iso_8859-7" => "ISO-8859-7",
  203 + "iso_8859-7:1987" => "ISO-8859-7",
  204 + "iso-ir-126" => "ISO-8859-7",
  205 + "elot_928" => "ISO-8859-7",
  206 + "ecma-118" => "ISO-8859-7",
  207 + "csisolatingreek" => "ISO-8859-7",
  208 + //
  209 + // Aliases for ISO-8859-8",
  210 + //
  211 + "hebrew" => "ISO-8859-8",
  212 + "iso_8859-8" => "ISO-8859-8",
  213 + "visual" => "ISO-8859-8",
  214 + "iso_8859-8:1988" => "ISO-8859-8",
  215 + "iso-ir-138" => "ISO-8859-8",
  216 + "csisolatinhebrew" => "ISO-8859-8",
  217 + //
  218 + // Aliases for ISO-8859-8-I",
  219 + //
  220 + "csiso88598i" => "ISO-8859-8-I",
  221 + "iso-8859-8i" => "ISO-8859-8-I",
  222 + "logical" => "ISO-8859-8-I",
  223 + //
  224 + // Aliases for ISO-8859-8-E",
  225 + //
  226 + "csiso88598e" => "ISO-8859-8-E",
  227 + //
  228 + // Aliases for ISO-8859-9",
  229 + //
  230 + "latin5" => "ISO-8859-9",
  231 + "iso_8859-9" => "ISO-8859-9",
  232 + "iso_8859-9:1989" => "ISO-8859-9",
  233 + "iso-ir-148" => "ISO-8859-9",
  234 + "l5" => "ISO-8859-9",
  235 + "csisolatin5" => "ISO-8859-9",
  236 + //
  237 + // Aliases for UTF-8",
  238 + //
  239 + "unicode-1-1-utf-8" => "UTF-8",
  240 + // nl_langinfo(CODESET) in HP/UX returns 'utf8' under UTF-8 locales",
  241 + "utf8" => "UTF-8",
  242 + //
  243 + // Aliases for Shift_JIS",
  244 + //
  245 + "x-sjis" => "Shift_JIS",
  246 + "shift-jis" => "Shift_JIS",
  247 + "ms_kanji" => "Shift_JIS",
  248 + "csshiftjis" => "Shift_JIS",
  249 + "windows-31j" => "Shift_JIS",
  250 + "cp932" => "Shift_JIS",
  251 + "sjis" => "Shift_JIS",
  252 + //
  253 + // Aliases for EUC_JP",
  254 + //
  255 + "cseucpkdfmtjapanese" => "EUC-JP",
  256 + "x-euc-jp" => "EUC-JP",
  257 + //
  258 + // Aliases for ISO-2022-JP",
  259 + //
  260 + "csiso2022jp" => "ISO-2022-JP",
  261 + // The following are really not aliases ISO-2022-JP, but sharing the same decoder",
  262 + "iso-2022-jp-2" => "ISO-2022-JP",
  263 + "csiso2022jp2" => "ISO-2022-JP",
  264 + //
  265 + // Aliases for Big5",
  266 + //
  267 + "csbig5" => "Big5",
  268 + "cn-big5" => "Big5",
  269 + // x-x-big5 is not really a alias for Big5, add it only for MS FrontPage",
  270 + "x-x-big5" => "Big5",
  271 + // Sun Solaris",
  272 + "zh_tw-big5" => "Big5",
  273 + //
  274 + // Aliases for EUC-KR",
  275 + //
  276 + "cseuckr" => "EUC-KR",
  277 + "ks_c_5601-1987" => "EUC-KR",
  278 + "iso-ir-149" => "EUC-KR",
  279 + "ks_c_5601-1989" => "EUC-KR",
  280 + "ksc_5601" => "EUC-KR",
  281 + "ksc5601" => "EUC-KR",
  282 + "korean" => "EUC-KR",
  283 + "csksc56011987" => "EUC-KR",
  284 + "5601" => "EUC-KR",
  285 + "windows-949" => "EUC-KR",
  286 + //
  287 + // Aliases for GB2312",
  288 + //
  289 + // The following are really not aliases GB2312, add them only for MS FrontPage",
  290 + "gb_2312-80" => "GB2312",
  291 + "iso-ir-58" => "GB2312",
  292 + "chinese" => "GB2312",
  293 + "csiso58gb231280" => "GB2312",
  294 + "csgb2312" => "GB2312",
  295 + "zh_cn.euc" => "GB2312",
  296 + // Sun Solaris",
  297 + "gb_2312" => "GB2312",
  298 + //
  299 + // Aliases for windows-125x ",
  300 + //
  301 + "x-cp1250" => "windows-1250",
  302 + "x-cp1251" => "windows-1251",
  303 + "x-cp1252" => "windows-1252",
  304 + "x-cp1253" => "windows-1253",
  305 + "x-cp1254" => "windows-1254",
  306 + "x-cp1255" => "windows-1255",
  307 + "x-cp1256" => "windows-1256",
  308 + "x-cp1257" => "windows-1257",
  309 + "x-cp1258" => "windows-1258",
  310 + //
  311 + // Aliases for windows-874 ",
  312 + //
  313 + "windows-874" => "windows-874",
  314 + "ibm874" => "windows-874",
  315 + "dos-874" => "windows-874",
  316 + //
  317 + // Aliases for macintosh",
  318 + //
  319 + "macintosh" => "macintosh",
  320 + "x-mac-roman" => "macintosh",
  321 + "mac" => "macintosh",
  322 + "csmacintosh" => "macintosh",
  323 + //
  324 + // Aliases for IBM866",
  325 + //
  326 + "cp866" => "IBM866",
  327 + "cp-866" => "IBM866",
  328 + "866" => "IBM866",
  329 + "csibm866" => "IBM866",
  330 + //
  331 + // Aliases for IBM850",
  332 + //
  333 + "cp850" => "IBM850",
  334 + "850" => "IBM850",
  335 + "csibm850" => "IBM850",
  336 + //
  337 + // Aliases for IBM852",
  338 + //
  339 + "cp852" => "IBM852",
  340 + "852" => "IBM852",
  341 + "csibm852" => "IBM852",
  342 + //
  343 + // Aliases for IBM855",
  344 + //
  345 + "cp855" => "IBM855",
  346 + "855" => "IBM855",
  347 + "csibm855" => "IBM855",
  348 + //
  349 + // Aliases for IBM857",
  350 + //
  351 + "cp857" => "IBM857",
  352 + "857" => "IBM857",
  353 + "csibm857" => "IBM857",
  354 + //
  355 + // Aliases for IBM862",
  356 + //
  357 + "cp862" => "IBM862",
  358 + "862" => "IBM862",
  359 + "csibm862" => "IBM862",
  360 + //
  361 + // Aliases for IBM864",
  362 + //
  363 + "cp864" => "IBM864",
  364 + "864" => "IBM864",
  365 + "csibm864" => "IBM864",
  366 + "ibm-864" => "IBM864",
  367 + //
  368 + // Aliases for T.61-8bit",
  369 + //
  370 + "t.61" => "T.61-8bit",
  371 + "iso-ir-103" => "T.61-8bit",
  372 + "csiso103t618bit" => "T.61-8bit",
  373 + //
  374 + // Aliases for UTF-7",
  375 + //
  376 + "x-unicode-2-0-utf-7" => "UTF-7",
  377 + "unicode-2-0-utf-7" => "UTF-7",
  378 + "unicode-1-1-utf-7" => "UTF-7",
  379 + "csunicode11utf7" => "UTF-7",
  380 + //
  381 + // Aliases for ISO-10646-UCS-2",
  382 + //
  383 + "csunicode" => "UTF-16BE",
  384 + "csunicode11" => "UTF-16BE",
  385 + "iso-10646-ucs-basic" => "UTF-16BE",
  386 + "csunicodeascii" => "UTF-16BE",
  387 + "iso-10646-unicode-latin1" => "UTF-16BE",
  388 + "csunicodelatin1" => "UTF-16BE",
  389 + "iso-10646" => "UTF-16BE",
  390 + "iso-10646-j-1" => "UTF-16BE",
  391 + //
  392 + // Aliases for ISO-8859-10",
  393 + //
  394 + "latin6" => "ISO-8859-10",
  395 + "iso-ir-157" => "ISO-8859-10",
  396 + "l6" => "ISO-8859-10",
  397 + // Currently .properties cannot handle : in key",
  398 + //iso_8859-10:1992" => "ISO-8859-10",
  399 + "csisolatin6" => "ISO-8859-10",
  400 + //
  401 + // Aliases for ISO-8859-15",
  402 + //
  403 + "iso_8859-15" => "ISO-8859-15",
  404 + "csisolatin9" => "ISO-8859-15",
  405 + "l9" => "ISO-8859-15",
  406 + //
  407 + // Aliases for ISO-IR-111",
  408 + //
  409 + "ecma-cyrillic" => "ISO-IR-111",
  410 + "csiso111ecmacyrillic" => "ISO-IR-111",
  411 + //
  412 + // Aliases for ISO-2022-KR",
  413 + //
  414 + "csiso2022kr" => "ISO-2022-KR",
  415 + //
  416 + // Aliases for VISCII",
  417 + //
  418 + "csviscii" => "VISCII",
  419 + //
  420 + // Aliases for x-euc-tw",
  421 + //
  422 + "zh_tw-euc" => "x-euc-tw",
  423 + //
  424 + // Following names appears in unix nl_langinfo(CODESET)",
  425 + // They can be compiled as platform specific if necessary",
  426 + // DONT put things here if it does not look generic enough (like hp15CN)",
  427 + //
  428 + "iso88591" => "ISO-8859-1",
  429 + "iso88592" => "ISO-8859-2",
  430 + "iso88593" => "ISO-8859-3",
  431 + "iso88594" => "ISO-8859-4",
  432 + "iso88595" => "ISO-8859-5",
  433 + "iso88596" => "ISO-8859-6",
  434 + "iso88597" => "ISO-8859-7",
  435 + "iso88598" => "ISO-8859-8",
  436 + "iso88599" => "ISO-8859-9",
  437 + "iso885910" => "ISO-8859-10",
  438 + "iso885911" => "ISO-8859-11",
  439 + "iso885912" => "ISO-8859-12",
  440 + "iso885913" => "ISO-8859-13",
  441 + "iso885914" => "ISO-8859-14",
  442 + "iso885915" => "ISO-8859-15",
  443 + "cp1250" => "windows-1250",
  444 + "cp1251" => "windows-1251",
  445 + "cp1252" => "windows-1252",
  446 + "cp1253" => "windows-1253",
  447 + "cp1254" => "windows-1254",
  448 + "cp1255" => "windows-1255",
  449 + "cp1256" => "windows-1256",
  450 + "cp1257" => "windows-1257",
  451 + "cp1258" => "windows-1258",
  452 + "x-gbk" => "gbk",
  453 + "windows-936" => "gbk",
  454 + "ansi-1251" => "windows-1251",
  455 + ];
  456 +
  457 + /**
  458 + * Returns proper encoding mapping, if exsists. If it doesn't, return unchanged $encoding
  459 + * @param string|null $encoding
  460 + * @param string|null $fallback
  461 + *
  462 + * @return string
  463 + */
  464 + public static function get($encoding, string $fallback = null): string {
  465 + if (isset(self::$aliases[strtolower($encoding ?? '')])) {
  466 + return self::$aliases[strtolower($encoding ?? '')];
  467 + }
  468 + return $fallback !== null ? $fallback : $encoding;
  469 + }
  470 +
  471 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Parse;
  4 +
  5 +
  6 +/**
  7 + * 解析邮件
  8 + * @author:dc
  9 + * @time 2024/9/10 16:54
  10 + * Class Header
  11 + */
  12 +class Fetch{
  13 +
  14 + /**
  15 + * @var array
  16 + */
  17 + private array $attributes = [];
  18 +
  19 + /**
  20 + * 原始头信息
  21 + * @var string
  22 + */
  23 + protected string $raw_header;
  24 +
  25 + /**
  26 + * Header constructor.
  27 + * @param string $raw_header
  28 + */
  29 + public function __construct(string $raw_header)
  30 + {
  31 + $this->raw_header = $raw_header;
  32 +
  33 + $this->rfc822_parse_headers();
  34 + }
  35 +
  36 +
  37 + /**
  38 + * @param string $name
  39 + * @param string $value
  40 + * @param bool $append
  41 + * @author:dc
  42 + * @time 2024/9/11 16:24
  43 + */
  44 + public function setAttribute(string $name, string $value, bool $append = true){
  45 + $name = strtolower($name);
  46 +
  47 + if(!$append){
  48 + $this->attributes[$name] = $value;
  49 + }else{
  50 + if(isset($this->attributes[$name]))
  51 + $this->attributes[$name] .= "\r\n".$value;
  52 + else
  53 + $this->attributes[$name] = $value;
  54 + }
  55 + }
  56 +
  57 +
  58 + /**
  59 + * 解析邮件头部
  60 + * @author:dc
  61 + * @time 2024/9/10 16:29
  62 + */
  63 + private function rfc822_parse_headers(){
  64 + $header = explode("\r\n",$this->raw_header);
  65 + $name = '';
  66 + foreach ($header as $str){
  67 + // 判断是否是上一行的
  68 + if(str_starts_with($str,' ') || str_starts_with($str,"\t")){
  69 + $this->setAttribute($name,$str);
  70 + }else{
  71 + $str = explode(":",$str);
  72 + $name = $str[0];
  73 + unset($str[0]);
  74 + $this->setAttribute($name,implode(':',$str));
  75 + }
  76 +
  77 + }
  78 +
  79 + foreach ($this->attributes as $name => $attribute){
  80 + $attribute = trim($attribute);
  81 + $this->attributes[$name] = $attribute;
  82 + }
  83 + }
  84 +
  85 +
  86 + public function __get(string $name)
  87 + {
  88 + return $this->get($name);
  89 + }
  90 +
  91 + /**
  92 + * 读取字段
  93 + * @param string $name
  94 + * @return mixed
  95 + * @author:dc
  96 + * @time 2024/9/11 15:06
  97 + */
  98 + public function get(string $name):mixed {
  99 + $name = strtolower($name);
  100 +
  101 + $m = 'get'.str_replace(' ','',ucwords(str_replace('-',' ',$name)));
  102 +
  103 + if(method_exists($this,$m)){
  104 + return $this->{$m}();
  105 + }
  106 +
  107 + return $this->attributes[$name]??'';
  108 + }
  109 +
  110 +
  111 + /**
  112 + * 获取收件地址 可能是假地址
  113 + * @param bool $is_obj 是否解析成对象 Address
  114 + * @return Address[]
  115 + * @author:dc
  116 + * @time 2024/9/11 15:03
  117 + */
  118 + public function getTo() {
  119 + // 如果有这个字段,就用这个字段 to字段的邮箱可能被伪造
  120 + if(!empty($this->attributes['delivered-to'])){
  121 + $to = $this->attributes['delivered-to'];
  122 + }else{
  123 + $to = $this->attributes['to']??'';
  124 + }
  125 +
  126 + return $this->parseAddress($to);
  127 + }
  128 +
  129 + /**
  130 + * 解析地址
  131 + * @param string $address
  132 + * @return array
  133 + * @author:dc
  134 + * @time 2024/9/11 15:53
  135 + */
  136 + private function parseAddress(string $address){
  137 + $arr = [];
  138 + foreach (explode(',',$address) as $str){
  139 + $arr[] = Address::make($str);
  140 + }
  141 + return $arr;
  142 + }
  143 +
  144 + /**
  145 + * 抄送人
  146 + * @return array
  147 + * @author:dc
  148 + * @time 2024/9/11 15:53
  149 + */
  150 + public function getCc()
  151 + {
  152 + return $this->parseAddress($this->attributes['cc']??'');
  153 + }
  154 +
  155 + /**
  156 + * 密送人
  157 + * @return array
  158 + * @author:dc
  159 + * @time 2024/9/11 15:54
  160 + */
  161 + public function getBcc()
  162 + {
  163 + return $this->parseAddress($this->attributes['bcc']??'');
  164 + }
  165 +
  166 +
  167 +
  168 + /**
  169 + * 发件人 可能是欺炸 发件人
  170 + * @return Address
  171 + * @author:dc
  172 + * @time 2024/9/11 15:19
  173 + */
  174 + public function getFrom():Address {
  175 + return Address::make($this->attributes['from']??'');
  176 + }
  177 +
  178 + /**
  179 + * 获取解码后的主题
  180 + * @return string
  181 + * @author:dc
  182 + * @time 2024/9/11 15:22
  183 + */
  184 + public function getSubject():string {
  185 + $subject = explode("\n",$this->attributes['subject']??'');
  186 + foreach ($subject as $ak=>$str){
  187 + $subject[$ak] = DeCode::decode($str);
  188 + }
  189 +
  190 + return implode("\n",$subject);
  191 + }
  192 +
  193 +
  194 + /**
  195 + * Boundary
  196 + * @param string $str
  197 + * @return string
  198 + * @author:dc
  199 + * @time 2024/9/11 16:05
  200 + */
  201 + public function getBoundary(string $str = ''): string
  202 + {
  203 + $pre = "/boundary=(.*?(?=;)|(.*))/i";
  204 + // 在内容类型中读取 大多少情况都在这里面
  205 + $contentType = $str ? $str: ($this->attributes['content-type']??'');
  206 + if($contentType){
  207 + preg_match($pre,$contentType,$b);
  208 + if(!empty($b[1])){
  209 + return trim(str_replace(['"',';'],'',$b[1]));
  210 + }
  211 + }
  212 + if($this->raw_header && !$str){
  213 + return $this->getBoundary($this->raw_header);
  214 + }
  215 +
  216 + return '';
  217 + }
  218 +
  219 + /**
  220 + * 这个是是否加急 1加急 3普通 5不急
  221 + * @return string
  222 + */
  223 + public function getPriority(): string
  224 + {
  225 + if(!empty($this->attributes['priority'])){
  226 + return $this->attributes['priority'];
  227 + }
  228 +
  229 + if(!empty($this->attributes['x-priority'])){
  230 + return $this->attributes['x-priority'];
  231 + }
  232 +
  233 + return '3';
  234 + }
  235 +
  236 +
  237 +
  238 + /**
  239 + * @return array
  240 + */
  241 + public function getAttributes(): array
  242 + {
  243 + return $this->attributes;
  244 + }
  245 +
  246 + /**
  247 + * @return array
  248 + * @author:dc
  249 + * @time 2024/9/11 16:15
  250 + */
  251 + public function toArray():array {
  252 + $arr = [];
  253 + foreach ($this->attributes as $key=>$attr){
  254 + $arr[$key] = $this->get($key);
  255 + }
  256 +
  257 + return $arr;
  258 + }
  259 +
  260 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Parse\Folder;
  4 +
  5 +
  6 +/**
  7 + * 文件夹
  8 + * @author:dc
  9 + * @time 2024/9/20 9:33
  10 + * Class Folder
  11 + * @package Lib\Imap\Parse
  12 + */
  13 +class Folder{
  14 +
  15 + /**
  16 + * 文件夹名称 utf-7编码 全路径
  17 + * @var string
  18 + */
  19 + public string $folder;
  20 +
  21 + /**
  22 + * 是否可以选择
  23 + * @var bool
  24 + */
  25 + public bool $isSelect;
  26 +
  27 +
  28 + /**
  29 + * 上级解析结构
  30 + * @var Folders
  31 + */
  32 + protected Folders $parent;
  33 +
  34 + /**
  35 + * 标签
  36 + * 有些邮箱把发件箱 send 改成了 send message 这里就有send标签,代表这个文件夹是发件箱
  37 + * 草稿,垃圾箱。回收站,等默认几个文件夹都有可能被更改
  38 + * @var array
  39 + */
  40 + public array $flags;
  41 +
  42 + public function __construct(string $folder,array $flags,bool $isSelect,Folders $parent)
  43 + {
  44 + $this->folder = $folder;
  45 + $this->flags = $flags;
  46 + $this->isSelect = $isSelect;
  47 + }
  48 +
  49 + /**
  50 + * 把文件夹编码转换成utf8的
  51 + * @author:dc
  52 + * @time 2024/9/20 10:00
  53 + */
  54 + public function getParseFolder(){
  55 + return mb_convert_encoding($this->folder, 'UTF-8', 'UTF7-IMAP');
  56 + }
  57 +
  58 + /**
  59 + * 获取子目录
  60 + * @return static[]
  61 + * @author:dc
  62 + * @time 2024/9/20 11:55
  63 + */
  64 + public function getChild():array {
  65 + return $this->parent->findChild($this);
  66 + }
  67 +
  68 +
  69 +
  70 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Parse\Folder;
  4 +
  5 +
  6 +/**
  7 + * 文件夹
  8 + * @author:dc
  9 + * @time 2024/9/20 9:33
  10 + * Class Folder
  11 + * @package Lib\Imap\Parse
  12 + */
  13 +class Folders{
  14 +
  15 + /**
  16 + * @var Folder[]
  17 + */
  18 + protected array $item = [];
  19 +
  20 +
  21 + /**
  22 + * 原始头信息
  23 + * @var string
  24 + */
  25 + protected array $raw = [];
  26 +
  27 + /**
  28 + * Header constructor.
  29 + * @param array $raw
  30 + */
  31 + public function __construct(array $raw)
  32 + {
  33 + $this->raw = $raw;
  34 +
  35 + $this->parse();
  36 + }
  37 +
  38 + /**
  39 + * 解析目录
  40 + * @author:dc
  41 + * @time 2024/9/20 10:07
  42 + */
  43 + private function parse(){
  44 + foreach ($this->raw as $item){
  45 + // 解析源数据
  46 + if(preg_match('/^\*\sLIST\s\(([\\a-z\s]{0,})\)\s\"(.*)\"\s\"?(.*)\"?$/Ui',$item,$m)){
  47 + if($m[1]){
  48 + $flags = explode(' ',$m[1]);
  49 + $flags = array_map(function ($v){
  50 + $v = trim($v);
  51 + $v = trim($v,'\\');
  52 + return $v;
  53 + },$flags);
  54 + }else{
  55 + $flags = [];
  56 + }
  57 +
  58 +
  59 + $this->item[] = new Folder(trim(trim($m[3]),'"'),$flags, !str_contains($m[1], 'NoSelect'), $this);
  60 + }
  61 + }
  62 +
  63 + }
  64 +
  65 + /**
  66 + * 获取顶级栏目 一级目录
  67 + * @return Folder[]
  68 + * @author:dc
  69 + * @time 2024/9/20 11:39
  70 + */
  71 + public function getTopFolder():array {
  72 + return array_filter($this->item,function ($folder){
  73 + return substr_count($folder->folder,'/') == 0;
  74 + });
  75 + }
  76 +
  77 +
  78 + /**
  79 + * 查找当前目录的下级目录
  80 + * @param Folder $folder
  81 + * @return Folder[]
  82 + * @author:dc
  83 + * @time 2024/9/19 17:54
  84 + */
  85 + public function findChild(Folder $folder):array {
  86 + $fs = [];
  87 + foreach ($this->item as $item){
  88 + // 跳过相同目录
  89 + if($folder->folder == $item->folder) continue;
  90 + if(strpos($item->folder,$folder->folder) !== 0) continue;
  91 +
  92 + $f = substr($item->folder,strlen($folder->folder),999);
  93 + $f = ltrim($f,'/');
  94 + if(substr_count($f,'/') == 0){
  95 +// $item['child'] = $this->findChild($item['folder']);
  96 + $fs[] = $item;
  97 + }
  98 + }
  99 + return $fs;
  100 + }
  101 +
  102 +
  103 + /**
  104 + * 获取所有目录 list 不是tree结构
  105 + * @return Folder[]
  106 + * @author:dc
  107 + * @time 2024/9/20 11:43
  108 + */
  109 + public function all():array{
  110 + return $this->item;
  111 + }
  112 +
  113 +
  114 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Parse;
  4 +
  5 +
  6 +/**
  7 + * 解析邮件
  8 + * @author:dc
  9 + * @time 2024/9/10 16:54
  10 + * Class Header
  11 + */
  12 +class Header{
  13 +
  14 + /**
  15 + * @var array
  16 + */
  17 + private array $attributes = [];
  18 +
  19 + /**
  20 + * 原始头信息
  21 + * @var string
  22 + */
  23 + protected string $raw_header;
  24 +
  25 + /**
  26 + * Header constructor.
  27 + * @param string $raw_header
  28 + */
  29 + public function __construct(string $raw_header)
  30 + {
  31 + $this->raw_header = $raw_header;
  32 +
  33 + $this->rfc822_parse_headers();
  34 + }
  35 +
  36 +
  37 + /**
  38 + * @param string $name
  39 + * @param string $value
  40 + * @param bool $append
  41 + * @author:dc
  42 + * @time 2024/9/11 16:24
  43 + */
  44 + public function setAttribute(string $name, string $value, bool $append = true){
  45 + $name = strtolower($name);
  46 +
  47 + if(!$append){
  48 + $this->attributes[$name] = $value;
  49 + }else{
  50 + if(isset($this->attributes[$name]))
  51 + $this->attributes[$name] .= "\r\n".$value;
  52 + else
  53 + $this->attributes[$name] = $value;
  54 + }
  55 + }
  56 +
  57 +
  58 + /**
  59 + * 解析邮件头部
  60 + * @author:dc
  61 + * @time 2024/9/10 16:29
  62 + */
  63 + private function rfc822_parse_headers(){
  64 + $header = explode("\r\n",$this->raw_header);
  65 + $name = '';
  66 + foreach ($header as $str){
  67 + // 判断是否是上一行的
  68 + if(str_starts_with($str,' ') || str_starts_with($str,"\t")){
  69 + $this->setAttribute($name,$str);
  70 + }else{
  71 + $str = explode(":",$str);
  72 + $name = $str[0];
  73 + unset($str[0]);
  74 + $this->setAttribute($name,implode(':',$str));
  75 + }
  76 +
  77 + }
  78 +
  79 + foreach ($this->attributes as $name => $attribute){
  80 + $attribute = trim($attribute);
  81 + $this->attributes[$name] = $attribute;
  82 + }
  83 + }
  84 +
  85 +
  86 + public function __get(string $name)
  87 + {
  88 + return $this->get($name);
  89 + }
  90 +
  91 + /**
  92 + * 读取字段
  93 + * @param string $name
  94 + * @return mixed
  95 + * @author:dc
  96 + * @time 2024/9/11 15:06
  97 + */
  98 + public function get(string $name):mixed {
  99 + $name = strtolower($name);
  100 +
  101 + $m = 'get'.str_replace(' ','',ucwords(str_replace('-',' ',$name)));
  102 +
  103 + if(method_exists($this,$m)){
  104 + return $this->{$m}();
  105 + }
  106 +
  107 + return $this->attributes[$name]??'';
  108 + }
  109 +
  110 +
  111 + /**
  112 + * 获取收件地址 可能是假地址
  113 + * @param bool $is_obj 是否解析成对象 Address
  114 + * @return Address[]
  115 + * @author:dc
  116 + * @time 2024/9/11 15:03
  117 + */
  118 + public function getTo() {
  119 + // 如果有这个字段,就用这个字段 to字段的邮箱可能被伪造
  120 + if(!empty($this->attributes['delivered-to'])){
  121 + $to = $this->attributes['delivered-to'];
  122 + }else{
  123 + $to = $this->attributes['to']??'';
  124 + }
  125 +
  126 + return $this->parseAddress($to);
  127 + }
  128 +
  129 + /**
  130 + * 解析地址
  131 + * @param string $address
  132 + * @return array
  133 + * @author:dc
  134 + * @time 2024/9/11 15:53
  135 + */
  136 + private function parseAddress(string $address){
  137 + $arr = [];
  138 + foreach (explode(',',$address) as $str){
  139 + $arr[] = Address::make($str);
  140 + }
  141 + return $arr;
  142 + }
  143 +
  144 + /**
  145 + * 抄送人
  146 + * @return array
  147 + * @author:dc
  148 + * @time 2024/9/11 15:53
  149 + */
  150 + public function getCc()
  151 + {
  152 + return $this->parseAddress($this->attributes['cc']??'');
  153 + }
  154 +
  155 + /**
  156 + * 密送人
  157 + * @return array
  158 + * @author:dc
  159 + * @time 2024/9/11 15:54
  160 + */
  161 + public function getBcc()
  162 + {
  163 + return $this->parseAddress($this->attributes['bcc']??'');
  164 + }
  165 +
  166 +
  167 +
  168 + /**
  169 + * 发件人 可能是欺炸 发件人
  170 + * @return Address
  171 + * @author:dc
  172 + * @time 2024/9/11 15:19
  173 + */
  174 + public function getFrom():Address {
  175 + return Address::make($this->attributes['from']??'');
  176 + }
  177 +
  178 + /**
  179 + * 获取解码后的主题
  180 + * @return string
  181 + * @author:dc
  182 + * @time 2024/9/11 15:22
  183 + */
  184 + public function getSubject():string {
  185 + $subject = explode("\n",$this->attributes['subject']??'');
  186 + foreach ($subject as $ak=>$str){
  187 + $subject[$ak] = DeCode::decode($str);
  188 + }
  189 +
  190 + return implode("\n",$subject);
  191 + }
  192 +
  193 +
  194 + /**
  195 + * Boundary
  196 + * @param string $str
  197 + * @return string
  198 + * @author:dc
  199 + * @time 2024/9/11 16:05
  200 + */
  201 + public function getBoundary(string $str = ''): string
  202 + {
  203 + $pre = "/boundary=(.*?(?=;)|(.*))/i";
  204 + // 在内容类型中读取 大多少情况都在这里面
  205 + $contentType = $str ? $str: ($this->attributes['content-type']??'');
  206 + if($contentType){
  207 + preg_match($pre,$contentType,$b);
  208 + if(!empty($b[1])){
  209 + return trim(str_replace(['"',';'],'',$b[1]));
  210 + }
  211 + }
  212 + if($this->raw_header && !$str){
  213 + return $this->getBoundary($this->raw_header);
  214 + }
  215 +
  216 + return '';
  217 + }
  218 +
  219 + /**
  220 + * 这个是是否加急 1加急 3普通 5不急
  221 + * @return string
  222 + */
  223 + public function getPriority(): string
  224 + {
  225 + if(!empty($this->attributes['priority'])){
  226 + return $this->attributes['priority'];
  227 + }
  228 +
  229 + if(!empty($this->attributes['x-priority'])){
  230 + return $this->attributes['x-priority'];
  231 + }
  232 +
  233 + return '3';
  234 + }
  235 +
  236 +
  237 +
  238 + /**
  239 + * @return array
  240 + */
  241 + public function getAttributes(): array
  242 + {
  243 + return $this->attributes;
  244 + }
  245 +
  246 + /**
  247 + * @return array
  248 + * @author:dc
  249 + * @time 2024/9/11 16:15
  250 + */
  251 + public function toArray():array {
  252 + $arr = [];
  253 + foreach ($this->attributes as $key=>$attr){
  254 + $arr[$key] = $this->get($key);
  255 + }
  256 +
  257 + return $arr;
  258 + }
  259 +
  260 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Parse;
  4 +
  5 +use Lib\Imap\Request\Msg;
  6 +
  7 +/**
  8 + * 邮件
  9 + * @author:dc
  10 + * @time 2024/9/18 11:30
  11 + * @property Body $body
  12 + * Class MessageItem
  13 + * @package Lib\Imap\Parse
  14 + */
  15 +class MessageItem {
  16 +
  17 + /**
  18 + * 邮件的唯一id 文件夹下面唯一
  19 + * @var int
  20 + */
  21 + public int $uid = 0;
  22 +
  23 + /**
  24 + * 邮件编号
  25 + * @var int
  26 + */
  27 + public int $msgno = 0;
  28 +
  29 + /**
  30 + * 这个是邮件收到的时间
  31 + * @var string
  32 + */
  33 + public string $date = '';
  34 +
  35 + /**
  36 + * 邮件大小
  37 + * @var int
  38 + */
  39 + public int $size = 0;
  40 +
  41 + /**
  42 + * 标记
  43 + * @var array
  44 + */
  45 + public array $flags = [];
  46 +
  47 + /**
  48 + * 头部信息 主题 发件 收件 发送时间等消息
  49 + * @var Header
  50 + */
  51 + public Header $header;
  52 +
  53 +
  54 + /**
  55 + * @var Msg
  56 + */
  57 + protected Msg $msg;
  58 +
  59 + /**
  60 + * MessageItem constructor.
  61 + * @param int $msgno
  62 + */
  63 + public function __construct(int $msgno, Msg $msg)
  64 + {
  65 +
  66 + $this->msg = $msg;
  67 +
  68 + $this->msgno = $msgno;
  69 + }
  70 +
  71 + /**
  72 + * 是否已被标记为删除了
  73 + * @return bool
  74 + * @author:dc
  75 + * @time 2024/9/18 11:52
  76 + */
  77 + public function isDeleted():bool {
  78 + return in_array('deleted',$this->flags);
  79 + }
  80 +
  81 + /**
  82 + * 已读
  83 + * @return bool
  84 + * @author:dc
  85 + * @time 2024/9/18 17:53
  86 + */
  87 + public function isSeen():bool {
  88 + return in_array('seen',$this->flags);
  89 + }
  90 +
  91 + /**
  92 + * 草稿
  93 + * @return bool
  94 + * @author:dc
  95 + * @time 2024/9/18 17:53
  96 + */
  97 + public function isDraft():bool {
  98 + return in_array('seen',$this->flags);
  99 + }
  100 +
  101 + /**
  102 + * 星标
  103 + * @return bool
  104 + * @author:dc
  105 + * @time 2024/9/18 17:53
  106 + */
  107 + public function isFlagged():bool {
  108 + return in_array('flagged',$this->flags);
  109 + }
  110 +
  111 + /**
  112 + * 已回复
  113 + * @return bool
  114 + * @author:dc
  115 + * @time 2024/9/18 17:52
  116 + */
  117 + public function isAnswered():bool {
  118 + return in_array('answered',$this->flags);
  119 + }
  120 +
  121 + /**
  122 + * 最近
  123 + * @return bool
  124 + * @author:dc
  125 + * @time 2024/9/18 17:54
  126 + */
  127 + public function isRecent():bool {
  128 + return in_array('recent',$this->flags);
  129 + }
  130 +
  131 + /**
  132 + * 获取邮件体 字段 内容
  133 + * @param string $name
  134 + * @return mixed
  135 + * @author:dc
  136 + * @time 2024/9/20 15:13
  137 + */
  138 + public function get(string $name){
  139 + return $this->header->get($name);
  140 + }
  141 +
  142 + /**
  143 + * 获取body体
  144 + * @return Body
  145 + * @author:dc
  146 + * @time 2024/9/20 15:14
  147 + */
  148 + public function getBody():Body {
  149 + return $this->msg->folder->getBody($this->uid,true);
  150 + }
  151 +
  152 +
  153 + public function __get(string $name)
  154 + {
  155 + if($name=='body'){
  156 + return $this->getBody();
  157 + }else{
  158 + $this->get($name);
  159 + }
  160 + }
  161 +
  162 +}
  163 +
  164 +
  165 +
  166 +
  167 +
  168 +
  169 +
  170 +
  171 +
  172 +
  173 +
  174 +
  175 +
  176 +
  177 +
  178 +
  179 +
  180 +
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Parse;
  4 +
  5 +use Lib\Imap\Request\Msg;
  6 +
  7 +/**
  8 + * 邮件列表
  9 + * @author:dc
  10 + * @time 2024/9/14 15:33
  11 + * Class Messager
  12 + * @package Lib\Imap\Parse
  13 + */
  14 +class Messager implements \IteratorAggregate {
  15 +
  16 + /**
  17 + * 原始数据
  18 + * @var array
  19 + */
  20 + protected array $result = [];
  21 +
  22 + /**
  23 + * 解析后的数据
  24 + * @var array
  25 + */
  26 + protected array $item = [];
  27 +
  28 + /**
  29 + * 读取消息的
  30 + * @var Msg
  31 + */
  32 + protected Msg $msg;
  33 +
  34 +
  35 + public function __construct(array $result, Msg $msg)
  36 + {
  37 + $this->msg = $msg;
  38 +
  39 + $k = 0;
  40 + foreach ($result as $item){
  41 + // 匹配出id和数据
  42 + if(preg_match("/\* (\d+) FETCH \(/",$item)){
  43 + $k++;
  44 + $this->result[$k] = $item;
  45 + }else{
  46 + if(isset($this->result[$k])){
  47 + $this->result[$k] .= $item;
  48 + }
  49 + }
  50 + }
  51 +
  52 + $this->parse();
  53 + }
  54 +
  55 + /**
  56 + * 实现 使用 foreach功能
  57 + * @return \ArrayIterator
  58 + * @author:dc
  59 + * @time 2024/9/18 10:43
  60 + */
  61 + public function getIterator() {
  62 + return new \ArrayIterator($this->item);
  63 + }
  64 +
  65 + /**
  66 + * 解析
  67 + * @author:dc
  68 + * @time 2024/9/14 17:35
  69 + */
  70 + protected function parse(){
  71 + foreach ($this->result as $item){
  72 + $item = trim($item);
  73 + // 匹配出id和数据
  74 + if(preg_match("/\* (\d+) FETCH \(([\w\s\d\r\n\W]{1,})\)$/i",$item,$line)){
  75 + $this->parseFetch($line[1],$line[2]);
  76 + }
  77 + $line = null;
  78 + }
  79 +
  80 +
  81 + }
  82 +
  83 +
  84 + /**
  85 + * 解析头部信息,只能解析固定字段的属性
  86 + * @param int $msgno
  87 + * @param string $line
  88 + * UID 1568602720 INTERNALDATE "16-Sep-2019 10:58:40 +0800" FLAGS (\Seen) RFC822.HEADER {816}
  89 + Received: from localhost (unknown [10.110.4.165])
  90 + by mfast8 (Coremail) with SMTP id t8CowAA3Za5g+n5d1b4tCA--.53220S2;
  91 + Mon, 16 Sep 2019 10:58:40 +0800 (CST)
  92 + Date: Mon, 16 Sep 2019 10:58:40 +0800 (GMT+08:00)
  93 + From: =?utf-8?B?572R5piT6YKu566x5aSn5biI5Zui6Zif?= <developer.MailMaster@service.netease.com>
  94 + To: zhlong0616@163.com
  95 + Message-ID: <1803973093.340445.1568602720140.JavaMail.developer.MailMaster@service.netease.com>
  96 + Subject: =?utf-8?B?5qyi6L+O5L2/55So572R5piT6YKu566x5aSn5biI?=
  97 + MIME-Version: 1.0
  98 + Content-Type: multipart/mixed;
  99 + boundary="----=_Part_340444_1698792253.1568602720140"
  100 + X-CM-TRANSID:t8CowAA3Za5g+n5d1b4tCA--.53220S2
  101 + X-Coremail-Antispam: 1Uf129KBjDUn29KB7ZKAUJUUBvt529EdanIXcx71UUUUU7v73
  102 + VFW2AGmfu7bjvjm3AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvj4RJUUUUUUUU
  103 + X-Originating-IP: [10.110.4.165]
  104 +
  105 + RFC822.SIZE 5111
  106 + * @author:dc
  107 + * @time 2024/9/18 11:46
  108 + */
  109 + protected function parseFetch(int $msgno, string $line) {
  110 + $this->item[$msgno] = new MessageItem($msgno, $this->msg);
  111 + $tokens = explode(' ',$line);
  112 + while ($tokens){
  113 + $key = trim(array_shift($tokens));
  114 + switch ($key){
  115 + case 'UID':{
  116 + $this->item[$msgno]->uid = array_shift($tokens);
  117 + break;
  118 + }
  119 + case 'FLAGS':{
  120 + $flags = [];
  121 + while (1){
  122 + $flags[] = array_shift($tokens);
  123 + if(substr(end($flags),-1)==')') break;
  124 + }
  125 + $this->item[$msgno]->flags = array_map(function ($v){
  126 + return strtolower(str_replace(['\\','(',')'],'',$v));
  127 + },$flags);
  128 + break;
  129 + }
  130 + case 'INTERNALDATE':{
  131 + // 内部时间
  132 + $date = '';
  133 + while (1){
  134 + $date .= array_shift($tokens);
  135 + if(substr($date,-1)=='"') break;
  136 + $date.= ' ';
  137 + }
  138 + $this->item[$msgno]->date = trim($date,'"');
  139 + break;
  140 + }
  141 + case 'RFC822.SIZE':{
  142 + $this->item[$msgno]->size = array_shift($tokens);
  143 + break;
  144 + }
  145 + case 'RFC822.HEADER':{
  146 + $str = trim(implode(' ',$tokens));
  147 + // 是否带有字符数数量
  148 + if(preg_match("/^\{(\d+)\}/",$str,$num)){
  149 + $len = $num[1]+strlen($num[0]);
  150 + $header = substr($str,0,$len);
  151 + // 剩下的字符串
  152 + $tokens = explode(' ',substr($str,$len,9999));
  153 + // 踢出长度的头
  154 + $header = substr($header,strlen($num[0]),strlen($header));
  155 +
  156 + }else{
  157 + // 以3个\r\n为结束
  158 + $str = explode("\r\n\r\n\r\n",trim($str));
  159 + $header = array_shift($str);
  160 + // 剩余的字符串
  161 + $tokens = explode(' ',implode("\r\n\r\n\r\n",$str));
  162 + }
  163 +
  164 + $this->item[$msgno]->header = new Header($header);
  165 + break;
  166 + }
  167 + default:{break;}
  168 + }
  169 +
  170 + }
  171 +
  172 + }
  173 +
  174 +
  175 + /**
  176 + * 循环结果
  177 + * @param \Closure $closure
  178 + * @author:dc
  179 + * @time 2024/9/18 11:41
  180 + */
  181 + public function each(\Closure $closure):void {
  182 + array_map($closure,$this->item);
  183 + }
  184 +
  185 +
  186 +}
  187 +
  188 +
  189 +
  190 +
  191 +
  192 +
  193 +
  194 +
  195 +
  196 +
  197 +
  198 +
  199 +
  200 +
  201 +
  202 +
  203 +
  204 +
  1 +<?php
  2 +
  3 +
  4 +namespace Lib\Imap\Request;
  5 +
  6 +use Lib\Imap\Imap;
  7 +
  8 +use Lib\Imap\Parse\Body as ParseBody;
  9 +
  10 +/**
  11 + * body邮件内容
  12 + * @author:dc
  13 + * @time 2024/9/20 14:53
  14 + * Class Body
  15 + * @package Lib\Imap\Request
  16 + */
  17 +class Body extends Request {
  18 +
  19 + /**
  20 + * @var Folder
  21 + */
  22 + protected Folder $folder;
  23 +
  24 +
  25 +
  26 + public function __construct(Imap $imap, Folder $folder)
  27 + {
  28 + parent::__construct($imap);
  29 +
  30 + $this->folder = $folder;
  31 + }
  32 +
  33 +
  34 + public function exec(): static{return $this;}
  35 +
  36 +
  37 + /**
  38 + * 读取邮件body
  39 + * @param int $num
  40 + * @param bool $is_uid
  41 + * @return ParseBody
  42 + * @author:dc
  43 + * @time 2024/9/20 15:40
  44 + */
  45 + public function get(int $num, bool $is_uid = false):ParseBody {
  46 + $this->folder->exec(); // 防止在其他文件夹下面
  47 + $this->cmd("%sFETCH %s (RFC822.BODY)", $is_uid ? 'UID ' : '', $num);
  48 +
  49 + return (new ParseBody($this->result ? $this->result[0] : ''));
  50 + }
  51 +
  52 +
  53 +
  54 +
  55 +}
  1 +<?php
  2 +
  3 +
  4 +namespace Lib\Imap\Request;
  5 +
  6 +use Lib\Imap\Imap;
  7 +
  8 +/**
  9 + * 登录
  10 + * @author:dc
  11 + * @time 2024/9/13 17:09
  12 + * Class Login
  13 + * @package Lib\Imap\Request
  14 + */
  15 +class Folder extends Request{
  16 +
  17 + protected string $folder;
  18 +
  19 + /**
  20 + * 未读数量 有的邮箱是不会返回未读数量的
  21 + * @var int
  22 + */
  23 + protected int $seen = 0;
  24 +
  25 + /**
  26 + * 总数
  27 + * @var int
  28 + */
  29 + protected int $total = 0;
  30 +
  31 + /**
  32 + * 最近邮件的数量
  33 + * @var int
  34 + */
  35 + protected int $recent = 0;
  36 +
  37 +
  38 +
  39 + /**
  40 + * 标签
  41 + * @var array
  42 + */
  43 + protected array $flags = [];
  44 +
  45 + /**
  46 + * 是否初始了,就是是否select 文件夹
  47 + * @var bool
  48 + */
  49 + private bool $isInit = false;
  50 +
  51 +
  52 + /**
  53 + * Folder constructor.
  54 + * @param Imap $imap
  55 + * @param string $folder
  56 + */
  57 + public function __construct(Imap $imap, string $folder)
  58 + {
  59 + parent::__construct($imap);
  60 +
  61 + $this->folder = $folder;
  62 + }
  63 +
  64 +
  65 + public function exec(): static
  66 + {
  67 +
  68 + if(!$this->imap->checkedFolder($this->folder)){
  69 + $this->cmd('SELECT "%s"',$this->folder);
  70 +
  71 +// ["* 128 EXISTS\r\n","* 2 RECENT\r\n","* OK [UIDVALIDITY 1] UIDs valid\r\n","* FLAGS (\\Answered \\Seen \\Deleted \\Draft \\Flagged)\r\n","* OK [PERMANENTFLAGS (\\Answered \\Seen \\Deleted \\Draft \\Flagged)] Limited\r\n","TAG3 OK [READ-WRITE] SELECT completed\r\n"]
  72 + if($this->isOk()){
  73 + $this->isInit = true;
  74 +
  75 + foreach ($this->result as $item){
  76 + $item = trim($item);
  77 + // 总数量
  78 + if(preg_match("/^\* (\d+) EXISTS$/i",$item,$m)){
  79 + $this->total = (int) $m[1];
  80 + }
  81 + // 最近的
  82 + elseif (preg_match("/^\* (\d+) RECENT$/i",$item,$m)){
  83 + $this->total = (int) $m[1];
  84 + }
  85 + // 未读
  86 + elseif (preg_match("/^\*.*\[UNSEEN (\d+)\]/i",$item,$m)){
  87 + $this->seen = (int) $m[1];
  88 + }
  89 + // tag
  90 + elseif (preg_match("/^\* FLAGS \((.*)\)$/i",$item,$m)){
  91 + $this->flags = explode(' ',$m[1]);
  92 + }
  93 +
  94 + }
  95 + // 设置
  96 + $this->imap->setCheckedFolder($this->folder);
  97 + }
  98 +
  99 + }
  100 +
  101 +
  102 + return $this;
  103 + }
  104 +
  105 +
  106 + /**
  107 + * 邮件列表
  108 + * @return Msg
  109 + * @author:dc
  110 + * @time 2024/9/14 11:36
  111 + */
  112 + public function msg():Msg {
  113 + return new Msg($this->imap,$this);
  114 + }
  115 +
  116 +
  117 + /**
  118 + * @return array
  119 + */
  120 + public function getFlags(): array
  121 + {
  122 + if(!$this->isInit){
  123 + $this->exec();
  124 + }
  125 + return $this->flags;
  126 + }
  127 +
  128 + /**
  129 + * @return int
  130 + */
  131 + public function getRecent(): int
  132 + {
  133 + if(!$this->isInit){
  134 + $this->exec();
  135 + }
  136 + return $this->recent;
  137 + }
  138 +
  139 + /**
  140 + * @return int
  141 + */
  142 + public function getSeen(): int
  143 + {
  144 + if(!$this->isInit){
  145 + $this->exec();
  146 + }
  147 + return $this->seen;
  148 + }
  149 +
  150 +
  151 + /**
  152 + * @return int
  153 + */
  154 + public function getTotal(): int
  155 + {
  156 + if(!$this->isInit){
  157 + $this->exec();
  158 + }
  159 + return $this->total;
  160 + }
  161 +
  162 +
  163 + /**
  164 + * 关闭当前目录 一般情况用不到
  165 + * @return bool
  166 + * @author:dc
  167 + * @time 2024/9/19 14:04
  168 + */
  169 + public function close():bool {
  170 + // 必须初始了 并且在当前文件夹下面
  171 + if($this->isInit && $this->imap->checkedFolder($this->folder)){
  172 + $this->cmd('CLOSE');
  173 + return $this->isOk();
  174 + }
  175 + return true;
  176 + }
  177 +
  178 + /**
  179 + * 创建目录
  180 + * @return bool
  181 + * @author:dc
  182 + * @time 2024/9/20 13:43
  183 + */
  184 + public function create():bool {
  185 + $this->cmd('CREATE "%s"',$this->folder);
  186 + return $this->isOk();
  187 + }
  188 +
  189 + /**
  190 + * 修改目录
  191 + * @param string $newFolder 新的文件夹名称
  192 + * @param bool $is_utf7 是否是utf7的编码
  193 + * @return bool
  194 + * @author:dc
  195 + * @time 2024/9/20 13:47
  196 + */
  197 + public function rename(string $newFolder, bool $is_utf7 = true):bool {
  198 + // 需要转码
  199 + if(!$is_utf7) $newFolder = mb_convert_encoding($newFolder,'UTF7-IMAP','UTF-8');
  200 +
  201 +// RENAME oldfolder newfolder
  202 + $this->cmd('RENAME "%s" "%s"',$this->folder,$newFolder);
  203 +
  204 + return $this->isOk();
  205 + }
  206 +
  207 + /**
  208 + * 删除 目录
  209 + * @return bool
  210 + * @author:dc
  211 + * @time 2024/9/20 13:50
  212 + */
  213 + public function delete():bool {
  214 +
  215 + $this->cmd('DELETE "%s"',$this->folder);
  216 +
  217 + return $this->isOk();
  218 + }
  219 +
  220 +
  221 + /**
  222 + * 读取body体
  223 + * @param int $num
  224 + * @param false $is_uid
  225 + * @return \Lib\Imap\Parse\Body
  226 + * @author:dc
  227 + * @time 2024/9/20 15:37
  228 + */
  229 + public function getBody(int $num, bool $is_uid = false):\Lib\Imap\Parse\Body{
  230 + $body = (new Body($this->imap,$this));
  231 + return $body->get($num, $is_uid = false);
  232 + }
  233 +
  234 +
  235 +
  236 +
  237 +}
  1 +<?php
  2 +
  3 +
  4 +namespace Lib\Imap\Request;
  5 +
  6 +use Lib\Imap\Imap;
  7 +
  8 +/**
  9 + * 文件夹列表
  10 + * @author:dc
  11 + * @time 2024/9/19 13:56
  12 + * Class Folders
  13 + * @package Lib\Imap\Request
  14 + */
  15 +class Folders extends Request{
  16 +
  17 + public function __construct(Imap $imap)
  18 + {
  19 + parent::__construct($imap);
  20 + $this->exec();
  21 + }
  22 +
  23 + public function exec(): static
  24 + {
  25 +
  26 + $this->cmd('LIST "" *');
  27 +
  28 + return $this;
  29 + }
  30 +
  31 + /**
  32 + * @return \Lib\Imap\Parse\Folder\Folders
  33 + * @author:dc
  34 + * @time 2024/9/20 10:24
  35 + */
  36 + public function get():\Lib\Imap\Parse\Folder\Folders {
  37 + return new \Lib\Imap\Parse\Folder\Folders($this->result);
  38 + }
  39 +
  40 +}
  1 +<?php
  2 +
  3 +
  4 +namespace Lib\Imap\Request;
  5 +
  6 +/**
  7 + * 登录
  8 + * @author:dc
  9 + * @time 2024/9/13 17:09
  10 + * Class Login
  11 + * @package Lib\Imap\Request
  12 + */
  13 +class Login extends Request{
  14 +
  15 +
  16 + public function exec(): static
  17 + {
  18 +
  19 + $pwd = addcslashes($this->imap->config->getPassword(),'"');
  20 +
  21 + $this->cmd('LOGIN "%s" "%s"',$this->imap->config->getEmail(),$pwd);
  22 +
  23 + return $this;
  24 + }
  25 +
  26 +
  27 +
  28 +
  29 +}
  1 +<?php
  2 +
  3 +
  4 +namespace Lib\Imap\Request;
  5 +
  6 +use Lib\Imap\Imap;
  7 +use Lib\Imap\ImapSearch;
  8 +use Lib\Imap\Parse\Messager;
  9 +
  10 +/**
  11 + * 登录
  12 + * @author:dc
  13 + * @time 2024/9/13 17:09
  14 + * Class Login
  15 + * @package Lib\Imap\Request
  16 + */
  17 +class Msg extends Request{
  18 +
  19 + /**
  20 + * @var Folder
  21 + */
  22 + public Folder $folder;
  23 +
  24 + /**
  25 + * 读取列表条件
  26 + * @var array
  27 + */
  28 + protected array $number = [];
  29 +
  30 +
  31 + /**
  32 + * 是否是uid
  33 + * @var bool
  34 + */
  35 + private bool $isUid = false;
  36 +
  37 +
  38 + public function __construct(Imap $imap, Folder $folder)
  39 + {
  40 + parent::__construct($imap);
  41 +
  42 + $this->folder = $folder;
  43 + }
  44 +
  45 +
  46 + public function exec(): static{return $this;}
  47 +
  48 +
  49 +
  50 + /**
  51 + * 分页读取
  52 + * @param int $p
  53 + * @param int $limit
  54 + * @return $this
  55 + * @author:dc
  56 + * @time 2024/9/14 14:06
  57 + */
  58 + public function forPage(int $p=1, int $limit=20):static {
  59 + $this->msgno(range(($p-1)*$limit+1,$p*$limit));
  60 + return $this;
  61 + }
  62 +
  63 + /**
  64 + * 使用uid进行查询
  65 + * @param array $uids
  66 + * @return $this
  67 + * @author:dc
  68 + * @time 2024/9/14 14:06
  69 + */
  70 + public function uid(array $uids):static {
  71 + $this->isUid = true;
  72 + $this->number = $uids;
  73 + return $this;
  74 + }
  75 +
  76 + /**
  77 + * 使用编号进行查询
  78 + * @param array $msgno
  79 + * @return $this
  80 + * @author:dc
  81 + * @time 2024/9/14 14:06
  82 + */
  83 + public function msgno(array $msgno):static {
  84 + $this->isUid = false;
  85 + $this->number = $msgno;
  86 + return $this;
  87 + }
  88 +
  89 + /**
  90 + * 搜索
  91 + * @param ImapSearch $search
  92 + * @return $this
  93 + * @author:dc
  94 + * @time 2024/9/14 15:48
  95 + */
  96 + public function search(ImapSearch $search):static {
  97 + $this->folder->exec(); // 防止在其他文件夹下面
  98 + $this->cmd("UID SEARCH ",$search->toString());
  99 + $uids = [];
  100 + foreach ($this->result as $item){
  101 + // 匹配uid * SEARCH 2 84 882
  102 + if(preg_match("/^\* SEARCH ([0-9\s]{1,})$/i",$item,$m)){
  103 + $uids = array_merge($uids,explode(' ',$m[1]));
  104 + }
  105 + }
  106 + $this->uid($uids);
  107 + return $this;
  108 + }
  109 +
  110 +
  111 + /**
  112 + * 读取邮件列表
  113 + * @return Messager
  114 + * @author:dc
  115 + * @time 2024/9/14 15:34
  116 + */
  117 + public function get():Messager{
  118 + $this->folder->exec(); // 防止在其他文件夹下面
  119 + $this->cmd(
  120 + "%sFETCH %s (UID FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER)",
  121 + $this->isUid?'UID ':'',
  122 + implode(',',$this->number)
  123 + );
  124 +
  125 + return new Messager($this->result, $this);
  126 + }
  127 +
  128 +
  129 + public function getBody(){
  130 + $this->folder->exec(); // 防止在其他文件夹下面
  131 + $this->cmd(
  132 + "%sFETCH %s (UID FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER)",
  133 + $this->isUid?'UID ':'',
  134 + implode(',',$this->number)
  135 + );
  136 +
  137 + return new Messager($this->result, $this);
  138 + }
  139 +
  140 +
  141 +
  142 +}
  1 +<?php
  2 +
  3 +
  4 +namespace Lib\Imap\Request;
  5 +
  6 +/**
  7 + * 类似 ping 命令
  8 + * 保持通讯用的
  9 + * @author:dc
  10 + * @time 2024/9/20 14:19
  11 + * Class Noop
  12 + * @package Lib\Imap\Request
  13 + */
  14 +class Noop extends Request{
  15 +
  16 +
  17 + public function exec(): static
  18 + {
  19 +
  20 + $this->cmd('NOOP');
  21 +
  22 + return $this;
  23 + }
  24 +
  25 +
  26 +
  27 +
  28 +}
  1 +<?php
  2 +
  3 +namespace Lib\Imap\Request;
  4 +
  5 +
  6 +use Lib\Imap\Imap;
  7 +
  8 +/**
  9 + * 命令组装 请求 返回处理
  10 + * @author:dc
  11 + * @time 2024/9/18 10:12
  12 + * Class Request
  13 + * @package Lib\Imap\Request
  14 + */
  15 +abstract class Request {
  16 +
  17 +
  18 + public Imap $imap;
  19 +
  20 +
  21 + /**
  22 + * 命令执行结果
  23 + * @var array
  24 + */
  25 + protected array $result = [];
  26 +
  27 + /**
  28 + * 执行结果状态 YES OK NO BAD
  29 + * @var string
  30 + */
  31 + protected string $status = '';
  32 +
  33 + /**
  34 + * 这条命令执行的tag
  35 + * @var string
  36 + */
  37 + protected string $tag = '';
  38 +
  39 + /**
  40 + * 最后的消息
  41 + * @var string
  42 + */
  43 + protected string $message = '';
  44 +
  45 +
  46 + public function __construct(Imap $imap)
  47 + {
  48 + $this->imap = $imap;
  49 + }
  50 +
  51 + /**
  52 + * 执行命令
  53 + * @param string $cmd 命令
  54 + * @param mixed ...$params 命令参数
  55 + * @author:dc
  56 + * @time 2024/9/13 17:36
  57 + */
  58 + protected final function cmd(string $cmd,...$params){
  59 + $this->result = $this->imap->client->request($params?sprintf($cmd,...$params):$cmd);
  60 + // 删除最后一行的状态
  61 + $end = array_pop($this->result);
  62 + // 状态处理
  63 + list($this->tag,$this->status,$this->message) = explode(' ',$end.' ',3);
  64 + $this->message = trim($this->message);
  65 +
  66 + }
  67 +
  68 +
  69 + abstract public function exec():static ;
  70 +
  71 +
  72 + /**
  73 + * @return string
  74 + */
  75 + public function getMessage(): string
  76 + {
  77 + return $this->message;
  78 + }
  79 +
  80 +
  81 + /**
  82 + * @return string
  83 + */
  84 + public function getStatus(): string
  85 + {
  86 + return $this->status;
  87 + }
  88 +
  89 +
  90 + /**
  91 + * @return string
  92 + */
  93 + public function getTag(): string
  94 + {
  95 + return $this->tag;
  96 + }
  97 +
  98 +
  99 + /**
  100 + * 是否成功
  101 + * @return bool
  102 + * @author:dc
  103 + * @time 2024/9/14 9:10
  104 + */
  105 + public function isOk(){
  106 + return $this->status == 'OK';
  107 + }
  108 +
  109 +}
  1 +## 目录结果
  2 +
  3 +>> Request 目录是组装命令并执行
  4 +>> Parse 目录是解析数据的目录
  1 +<?php
  2 +
  3 +// 实例一个配置
  4 +$config = (new \Lib\Imap\ImapConfig())
  5 + // 邮箱
  6 + ->setEmail('zhlong0616@163.com')
  7 + // 密码
  8 + ->setPassword(base64_decode('VEVKSVdPTUJQS09TSFJHSg=='))
  9 + // 必须是 协议+域名+端口
  10 + ->setHost('ssl://imap.163.com:993');
  11 +
  12 +// 获取一个imap连接 必须传入 \Lib\Imap\ImapConfig类
  13 +// 返回的是 Lib\Imap\Imap 类
  14 +$imap = \Lib\Imap\ImapPool::get($config);
  15 +
  16 +// 登录 必须先登录
  17 +$login = $imap->login();
  18 +if($login->isOk()){// 登录成功
  19 +
  20 +
  21 + /******************** start 文件夹 ************************/
  22 + // 获取文件夹对象
  23 + $folders = $imap->getFolders();
  24 +
  25 + // 获取 所有目录
  26 + foreach ($folders->all() as $folder){
  27 + $folder->getParseFolder(); // 获取解析过后的目录名称
  28 + $folder->folder; // 获取没有解析的目录名称
  29 + }
  30 + // 获取顶级目录
  31 + foreach ($folders->getTopFolder() as $folder){
  32 + $childs = $folder->getChild(); // 获取 子级目录
  33 + foreach ($childs as $child){
  34 + $child->getChild();
  35 + }
  36 + }
  37 +
  38 + // 创建一级文件夹
  39 + $imap->folder('你好',false)->create();
  40 + // 创建 二级文件夹
  41 + $imap->folder('你好/大家好',false)->create();
  42 + // 删除 你好 文件夹
  43 + $imap->folder('你好',false)->delete();
  44 + // 把 你好 改成 我好
  45 + $imap->folder('你好',false)->rename('我好',false);
  46 +
  47 + /******************** end 文件夹 ************************/
  48 +
  49 +
  50 +
  51 + /************************ start 获取某个文件夹下面的邮件 *******************************/
  52 + // 选择文件夹 预备读取 使用分页 查询
  53 + $msgs = $imap->folder('INBOX')->msg()->forPage()->get();
  54 + // 1.使用foreach
  55 + foreach ($msgs as $msg){
  56 + /** @var \Lib\Imap\Parse\MessageItem $msg **/
  57 + }
  58 + // 2.使用 each
  59 + $msgs->each(function (\Lib\Imap\Parse\MessageItem $msg){
  60 + $msg->msgno;// 邮件编号
  61 + $msg->uid;// 邮件唯一id
  62 + $msg->size;// 邮件大小
  63 + $msg->date;// 邮件内部时间,也就是服务器收到邮件的时间
  64 + $msg->flags;// 邮件标签 已读,已回,星标,删除,草稿 最近
  65 + $msg->isAnswered();//已回
  66 + $msg->isDeleted();//删除
  67 + $msg->isDraft();//草稿
  68 + $msg->isFlagged();//星标
  69 + $msg->isRecent();//最近
  70 + $msg->isSeen();// 已读
  71 + /** 头部信息 @see \Lib\Imap\Parse\Header */
  72 + $msg->header->getSubject();// 已解析的主题
  73 + $msg->header->getFrom();// 已解析的发件人
  74 + $msg->header->getTo();// 已解析的收件人
  75 + // 获取某个字段
  76 + $msg->header->get('subject');
  77 +
  78 + });
  79 +
  80 +
  81 + // 搜索邮箱
  82 + $msg = $imap->folder('INBOX')->msg()->search(
  83 + // 搜索未读 并且 主题有 你好 的
  84 + (new \Lib\Imap\ImapSearch())
  85 + ->seen(false)
  86 + ->subject('你好')
  87 + )->get();
  88 +
  89 + /*******************************************************/
  90 +
  91 +
  92 +
  93 +
  94 +}else{
  95 + // 登录失败 获取错误消息 $login->getMessage()
  96 + throw new Exception($login->getMessage());
  97 +}
  98 +
  99 +
  100 +
  101 +
  102 +