作者 邓超

新imap

<?php
namespace Lib\Imap;
use Lib\Imap\Request\Folder;
use Lib\Imap\Request\Folders;
use Lib\Imap\Request\Login;
use Lib\Imap\Request\Noop;
class Imap {
/**
* 配置
* @var ImapConfig
*/
public ImapConfig $config;
/**
* @var ImapClient
*/
public ImapClient $client;
/**
* 当前在那个文件夹下面目录
* @var string
*/
protected string $checkedFolder = '';
/**
* Imap constructor.
* @param ImapConfig $config
*/
public function __construct(ImapConfig $config)
{
$this->config = $config;
$this->client = new ImapClient($this->config->getHost());
}
/**
* 设置是否调试
* @param bool $debug
* @return $this
* @author:dc
* @time 2024/9/19 9:41
*/
public function debug(bool $debug = true):static {
$this->client->setDebug($debug);
return $this;
}
/**
* @param string $host
* @param string $email
* @param string $password
* @return Login
* @throws \Exception
* @author:dc
* @time 2024/9/13 17:45
*/
public function login():Login {
// 打开连接
$this->client->open();
//有的服务器是强制要求设置id属性 已知 163 有的服务器没有id命令 这里就不处理结果了
$this->client->request('ID ("name" "测试本地 Client" "version" "1" "os" "测试本地" "os-version" "1.0")');
// 登录
return (new Login($this))->exec();
}
/**
* 选择文件夹
* @param string|\Lib\Imap\Parse\Folder\Folder $folder 目录
* @param bool $utf7 是否是utf7编码
* @return Folder
* @author:dc
* @time 2024/9/19 9:57
*/
public function folder(string|\Lib\Imap\Parse\Folder\Folder $folder, bool $utf7 = true):Folder {
if($folder instanceof \Lib\Imap\Parse\Folder\Folder){
$folder = $folder->folder;
}else{
if(!$utf7) $folder = mb_convert_encoding($folder,"UTF7-IMAP","UTF-8");
}
return new Folder($this,$folder);
}
/**
* 读取文件夹
* @return \Lib\Imap\Parse\Folder\Folders
* @author:dc
* @time 2024/9/19 14:10
*/
public function getFolders():\Lib\Imap\Parse\Folder\Folders {
$folders = new Folders($this);
return $folders->get();
}
/**
* 类似ping
* @return bool
* @author:dc
* @time 2024/9/20 14:45
*/
public function noop():bool {
return (new Noop($this))->exec()->isOk();
}
/**
* 读取或验证是否在当前目录下面
* @param string $folder
* @return bool|string
* @author:dc
* @time 2024/9/20 13:58
*/
public function checkedFolder(string $folder = ''):bool|string {
if($folder){
return $folder == $this->checkedFolder;
}
return $this->checkedFolder;
}
/**
* 设置当前进入那个文件夹
* @param string $folder
* @author:dc
* @time 2024/9/20 14:00
*/
public function setCheckedFolder(string $folder):void {
$this->checkedFolder = $folder;
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap;
class ImapClient {
/**
* 资源
* @var
*/
private $socket;
protected bool $debug = false;
protected string $host;
/**
* 每请求一次就会+1
* @var int
*/
public int $tagNum = 0;
public function __construct(string $host){
$this->host = $host;
}
public function open(){
$content = stream_context_create([
'ssl' => [
'verify_peer' => false, // 有的证书和域名不匹配,这里关闭认证
'verify_peer_name' => false,// 有的证书和域名不匹配,这里关闭认证
]
]);
$flags = STREAM_CLIENT_CONNECT;
$this->socket = stream_socket_client(
$this->host,
$errno,
$error,
30,
$flags,
$content
);
if($error){
throw new \Exception("socket error: {$error}");
}
if (!$this->socket) {
throw new \Exception($this->host." connection fail.");
}
}
/**
* @param bool $debug
* @return $this
* @author:dc
* @time 2024/9/18 9:57
*/
public function setDebug(bool $debug = true): static
{
$this->debug = $debug;
return $this;
}
/**
* 写
* @param string $cmd
* @return false|int
* @author:dc
* @time 2024/9/13 15:47
*/
protected function write(string $cmd){
if($this->debug){
echo 'write: '.$cmd;
}
return fwrite($this->socket,$cmd);
}
/**
* 读
* @return false|string
* @author:dc
* @time 2024/9/13 15:49
*/
protected function readLine(){
$str = fgets($this->socket,2048);
if($this->debug){
echo 'read: '.$str;
}
return $str;
}
/**
*
* @param string $cmd 执行的命令
* @return array
* @author:dc
* @time 2024/9/13 15:57
*/
public function request(string $cmd):array {
$this->tagNum++;
$tag = 'TAG'.$this->tagNum;
// 请求数据
$writeNumber = $this->write($tag.' '.$cmd."\r\n");
$result = [];
if($writeNumber){
// 读取数据
while (1){
$line = $this->readLine();
$result[] = $line;
list($token) = explode(' ',$line,2);
// 结束了
if($token == $tag || $line === false){
break;
}
}
}
return $result;
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap;
class ImapConfig {
protected string $host = '';
protected string $password = '';
protected string $email = '';
/**
* @param string $email
*/
public function setEmail(string $email): static
{
$this->email = $email;
return $this;
}
/**
* @param string $host
*/
public function setHost(string $host): static
{
$this->host = $host;
return $this;
}
/**
* @param string $password
*/
public function setPassword(string $password): static
{
$this->password = $password;
return $this;
}
/**
* @return string
*/
public function getEmail(): string
{
return $this->email;
}
/**
* @return string
*/
public function getHost(): string
{
return $this->host;
}
/**
* @return string
*/
public function getPassword(): string
{
return $this->password;
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap;
class ImapPool {
/**
* @var Imap[]
*/
private static array $pool;
/**
* 获取连接
* @param ImapConfig $config
* @return Imap
* @author:dc
* @time 2024/9/14 9:19
*/
public static function get(ImapConfig $config){
if(!isset(static::$pool[$config->getEmail()])){
static::$pool[$config->getEmail()] = new Imap($config);
}
return static::$pool[$config->getEmail()];
}
public static function release(Imap $imap){
}
public function noop(){
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap;
/**
* 搜索邮箱
* TODO:: 有些邮箱服务器并不支持搜索 或者 某些字段搜索
* 日期类搜索是没问题的
* @author:dc
* @time 2024/9/14 15:48
* Class ImapSearch
* @package Lib\Imap
*/
class ImapSearch {
/**
* @var array
*/
protected array $where = [];
/**
* 搜索已标记为 已回复 的邮件
* @param bool $a 是否已标记为 已回复
* @return $this
* @author:dc
* @time 2024/9/14 16:22
*/
public function answered(bool $a = true):static {
$this->where[$a?'ANSWERED':'UNANSWERED'] = true;
return $this;
}
/**
* 搜索已标记为删除的
* Messages with the \Deleted flag set.
* @param bool $del 是否已删除的
* @return $this
* @author:dc
* @time 2024/9/14 16:21
*/
public function deleted(bool $del = true):static {
$this->where[$del?'DELETED':'UNDELETED'] = true;
return $this;
}
/**
* 搜索标记为草稿的
* Messages with the \Draft flag set.
* @param bool $draft 是否是草稿
* @return $this
* @author:dc
* @time 2024/9/14 16:20
*/
public function draft(bool $draft = true):static {
$this->where[$draft?'DRAFT':'UNDRAFT'] = true;
return $this;
}
/**
* 搜索标记为星标的邮件
* @param bool $flagged 是否带星标
* @return $this
* @author:dc
* @time 2024/9/14 16:19
*/
public function flagged(bool $flagged = true):static {
$this->where[$flagged?'FLAGGED':'UNFLAGGED'] = true;
return $this;
}
/**
* 搜索未读 还是已读
* @param bool $seen 是否标记为已读
* @return $this
* @author:dc
* @time 2024/9/14 16:23
*/
public function seen(bool $seen = true):static {
$this->where[$seen?'SEEN':'UNSEEN'] = true;
return $this;
}
/**
* 搜索密送 中有这个邮箱的
* @param string $bcc 邮箱
* @return $this
* @author:dc
* @time 2024/9/14 15:57
*/
public function bcc(string $bcc):static {
$this->where['BCC'] = $bcc;
return $this;
}
/**
* 搜索抄送 中有这个邮箱的
* Messages that contain the specified string in the envelope structure's CC field.
* @param string $cc 邮箱
* @return $this
* @author:dc
* @time 2024/9/14 16:06
*/
public function cc(string $cc):static {
$this->where['CC'] = $cc;
return $this;
}
/**
* 搜索 发件人
* @param string $from
* @return $this
* @author:dc
* @time 2024/9/14 16:10
*/
public function from(string $from):static {
$this->where['FROM'] = $from;
return $this;
}
/**
* 搜索收件人
* @param string $to
* @return $this
* @author:dc
* @time 2024/9/14 16:17
*/
public function to(string $to):static {
$this->where['TO'] = $to;
return $this;
}
/**
* 搜索内容
* Messages that contain the specified string in the body of the message.
* @param string $body
* @return $this
* @author:dc
* @time 2024/9/14 16:05
*/
public function body(string $body):static {
$this->where['BODY'] = $body;
return $this;
}
/**
* 搜索 日期 小于某个日期的
* Messages whose internal date (disregarding time and timezone) is earlier than the specified date.
* @param string $date 日期
* @author:dc
* @time 2024/9/14 16:03
*/
public function dateLt(string $date):static{
$this->where['BEFORE'] = date('d-M-Y',strtotime($date));
return $this;
}
/**
* 搜索指定日期的邮件
* Messages whose internal date (disregarding time and timezone) is within the specified date.
* @param string $date
* @author:dc
* @time 2024/9/14 16:12
*/
public function date(string $date):static{
$this->where['ON'] = date('d-M-Y',strtotime($date));
return $this;
}
/**
* 搜索指定日期后的邮件 如 2024-04-04 显示这个日期过后的
* Messages whose internal date (disregarding time and timezone) is within or later than the specified date.
* @param string $date
* @author:dc
* @time 2024/9/14 16:14
*/
public function dateGt(string $date):static{
$this->where['SINCE'] = date('d-M-Y',strtotime($date));
return $this;
}
/**
* 模糊搜索主题
* @param string $subject 主题
* @author:dc
* @time 2024/9/14 16:16
*/
public function subject(string $subject):static {
$this->where['SUBJECT'] = $subject;
return $this;
}
/**
* 返回搜索字符串
* @author:dc
* @time 2024/9/14 16:27
*/
public function toString():string {
$arr = [];
foreach ($this->where as $k=>$v){
if(is_bool($v)){
$arr[] = $k;
}else{
$arr[] = $k.' "'.addcslashes($v,'"').'"';
}
}
return implode(' ',$arr);
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap\Parse;
/**
* @author:dc
* @time 2024/9/11 11:17
* Class Address
* @package Imap\Parse
*/
class Address {
public string $name = '';
public string $email = '';
private string $raw;
private function __construct(string $address){
$this->raw = $address;
if($this->raw){
$this->parse();
}
}
/**
* 创建一个地址类
* @param string $address
* @return static
* @author:dc
* @time 2024/9/11 11:37
*/
public static function make(string $address):self {
return new self($address);
}
/**
* 解析地址
* @author:dc
* @time 2024/9/11 11:39
*/
private function parse(){
$email = self::pregEmail($this->raw);
if(!empty($email)){
$this->email = $email;
$this->name = trim(str_replace([$email,'"','<','>','&gt;','&lt;'],'',$this->raw));
}
if($this->name){
$this->name = DeCode::decode($this->name);
}else{
$this->name = explode('@',$this->email)[0]??'';
}
}
/**
* 匹配邮箱
* @param $str
* @return string
* @author:dc
* @time 2024/9/11 11:43
*/
private function pregEmail(string $str):string {
preg_match('/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/',$str,$email);
if(empty($email[0])){
// 邮箱2
preg_match('/[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/',$str,$email);
}
return str_replace(['<','>'],'',$email[0]??'');
}
/**
* @return string
*/
public function getRaw(): string
{
return $this->raw;
}
}
... ...
<?php
namespace Lib\Imap\Parse;
/**
* 邮件内容
* @author:dc
* @time 2024/9/20 14:57
* Class Body
* @package Lib\Imap\Parse
*/
class Body {
/**
* 原始数据
* @var string
*/
protected string $raw = '';
/**
* 解析后的数据
* @var array
*/
protected array $item = [];
public function __construct(string $result)
{
// 匹配出id和数据
if(preg_match("/\* (\d+) FETCH \(/",$result)){
}
$this->parse();
}
/**
* 解析
* @author:dc
* @time 2024/9/14 17:35
*/
protected function parse(){
}
}
... ...
<?php
namespace Lib\Imap\Parse;
/**
* 解析邮件
* @author:dc
* @time 2024/9/10 16:54
* Class Header
*/
class DeCode{
/**
* Fallback Encoding
*
* @var string
*/
public $fallback_encoding = 'UTF-8';
/**
* 进行解码
* @author:dc
* @time 2024/9/10 16:56
*/
public static function decode(string $value){
$obj = new self();
$value = trim($value);
$original_value = $value;
$is_utf8_base = $obj->is_uft8($value);
if ($is_utf8_base) {
$value = mb_decode_mimeheader($value);
}
if ($obj->notDecoded($original_value, $value)) {
$decoded_value = $obj->mime_header_decode($value);
if (count($decoded_value) > 0) {
if (property_exists($decoded_value[0], "text")) {
$value = $decoded_value[0]->text;
}
}
}
return $value;
}
/**
* Decode MIME header elements
* @link https://php.net/manual/en/function.imap-mime-header-decode.php
* @param string $text The MIME text
*
* @return array The decoded elements are returned in an array of objects, where each
* object has two properties, charset and text.
*/
private function mime_header_decode(string $text): array {
$charset = $this->getEncoding($text);
return [(object)[
"charset" => $charset,
"text" => $this->convertEncoding($text, $charset)
]];
}
/**
* Convert the encoding
* @param $str
* @param string $from
* @param string $to
*
* @return mixed|string
*/
public function convertEncoding($str, $from = "ISO-8859-2", $to = "UTF-8") {
$str = mb_decode_mimeheader($str);
$from = EncodingAliases::get($from, $this->fallback_encoding);
$to = EncodingAliases::get($to, $this->fallback_encoding);
if ($from === $to) {
return $str;
}
// We don't need to do convertEncoding() if charset is ASCII (us-ascii):
// ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
// https://stackoverflow.com/a/11303410
//
// us-ascii is the same as ASCII:
// ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
// prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
// based on the typographical symbols predominantly in use there.
// https://en.wikipedia.org/wiki/ASCII
//
// convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
if (strtolower($from) == 'us-ascii' && $to == 'UTF-8') {
return $str;
}
try {
if(mb_detect_encoding($str)=='UTF-8'){
return $str;
}
if (!$from) {
return mb_convert_encoding($str, $to);
}
return mb_convert_encoding($str, $to, $from);
} catch (\Exception $e) {
if (strstr($from, '-')) {
$from = str_replace('-', '', $from);
return $this->convertEncoding($str, $from, $to);
} else {
return $str;
}
}
}
/**
* Get the encoding of a given abject
* @param object|string $structure
*
* @return string
*/
private function getEncoding($structure): string {
if (property_exists($structure, 'parameters')) {
foreach ($structure->parameters as $parameter) {
if (strtolower($parameter->attribute) == "charset") {
return EncodingAliases::get($parameter->value, $this->fallback_encoding);
}
}
} elseif (property_exists($structure, 'charset')) {
return EncodingAliases::get($structure->charset, $this->fallback_encoding);
} elseif (is_string($structure) === true) {
preg_match("/^=\?([a-z0-9-]{3,})\?/i",$structure,$code);
if(!empty($code[1])){
$code = EncodingAliases::get($code[1],'');
if($code){
return $code;
}
}
$result = mb_detect_encoding($structure);
return $result === false ? $this->fallback_encoding : $result;
}
return $this->fallback_encoding;
}
/**
* Check if a given pair of strings has been decoded
* @param $encoded
* @param $decoded
*
* @return bool
*/
private function notDecoded($encoded, $decoded): bool {
return 0 === strpos($decoded, '=?')
&& strlen($decoded) - 2 === strpos($decoded, '?=')
&& false !== strpos($encoded, $decoded);
}
/**
* Test if a given value is utf-8 encoded
* @param $value
*
* @return bool
*/
private function is_uft8($value): bool {
return strpos(strtolower($value), '=?utf-8?') === 0;
}
}
... ...
<?php
namespace Lib\Imap\Parse;
/**
* Class EncodingAliases
*
* @package Webklex\PHPIMAP
*/
class EncodingAliases {
/**
* Contains email encoding mappings
*
* @var array
*/
private static $aliases = [
/*
|--------------------------------------------------------------------------
| Email encoding aliases
|--------------------------------------------------------------------------
|
| Email encoding aliases used to convert to iconv supported charsets
|
|
| This Source Code Form is subject to the terms of the Mozilla Public
| License, v. 2.0. If a copy of the MPL was not distributed with this
| file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
| This Original Code has been modified by IBM Corporation.
| Modifications made by IBM described herein are
| Copyright (c) International Business Machines
| Corporation, 1999
|
| Modifications to Mozilla code or documentation
| identified per MPL Section 3.3
|
| Date Modified by Description of modification
| 12/09/1999 IBM Corp. Support for IBM codepages - 850,852,855,857,862,864
|
| Rule of this file:
| 1. key should always be in lower case ascii so we can do case insensitive
| comparison in the code faster.
| 2. value should be the one used in unicode converter
|
| 3. If the charset is not used for document charset, but font charset
| (e.g. XLFD charset- such as JIS x0201, JIS x0208), don't put here
|
*/
"ascii" => "us-ascii",
"us-ascii" => "us-ascii",
"ansi_x3.4-1968" => "us-ascii",
"646" => "us-ascii",
"iso-8859-1" => "ISO-8859-1",
"iso-8859-2" => "ISO-8859-2",
"iso-8859-3" => "ISO-8859-3",
"iso-8859-4" => "ISO-8859-4",
"iso-8859-5" => "ISO-8859-5",
"iso-8859-6" => "ISO-8859-6",
"iso-8859-6-i" => "ISO-8859-6-I",
"iso-8859-6-e" => "ISO-8859-6-E",
"iso-8859-7" => "ISO-8859-7",
"iso-8859-8" => "ISO-8859-8",
"iso-8859-8-i" => "ISO-8859-8-I",
"iso-8859-8-e" => "ISO-8859-8-E",
"iso-8859-9" => "ISO-8859-9",
"iso-8859-10" => "ISO-8859-10",
"iso-8859-11" => "ISO-8859-11",
"iso-8859-13" => "ISO-8859-13",
"iso-8859-14" => "ISO-8859-14",
"iso-8859-15" => "ISO-8859-15",
"iso-8859-16" => "ISO-8859-16",
"iso-ir-111" => "ISO-IR-111",
"iso-2022-cn" => "ISO-2022-CN",
"iso-2022-cn-ext" => "ISO-2022-CN",
"iso-2022-kr" => "ISO-2022-KR",
"iso-2022-jp" => "ISO-2022-JP",
"utf-16be" => "UTF-16BE",
"utf-16le" => "UTF-16LE",
"utf-16" => "UTF-16",
"windows-1250" => "windows-1250",
"windows-1251" => "windows-1251",
"windows-1252" => "windows-1252",
"windows-1253" => "windows-1253",
"windows-1254" => "windows-1254",
"windows-1255" => "windows-1255",
"windows-1256" => "windows-1256",
"windows-1257" => "windows-1257",
"windows-1258" => "windows-1258",
"ibm866" => "IBM866",
"ibm850" => "IBM850",
"ibm852" => "IBM852",
"ibm855" => "IBM855",
"ibm857" => "IBM857",
"ibm862" => "IBM862",
"ibm864" => "IBM864",
"utf-8" => "UTF-8",
"utf-7" => "UTF-7",
"shift_jis" => "Shift_JIS",
"big5" => "Big5",
"euc-jp" => "EUC-JP",
"euc-kr" => "EUC-KR",
"gb2312" => "GB2312",
"gb18030" => "gb18030",
"viscii" => "VISCII",
"koi8-r" => "KOI8-R",
"koi8_r" => "KOI8-R",
"cskoi8r" => "KOI8-R",
"koi" => "KOI8-R",
"koi8" => "KOI8-R",
"koi8-u" => "KOI8-U",
"tis-620" => "TIS-620",
"t.61-8bit" => "T.61-8bit",
"hz-gb-2312" => "HZ-GB-2312",
"big5-hkscs" => "Big5-HKSCS",
"gbk" => "gbk",
"cns11643" => "x-euc-tw",
//
// Aliases for ISO-8859-1
//
"latin1" => "ISO-8859-1",
"iso_8859-1" => "ISO-8859-1",
"iso8859-1" => "ISO-8859-1",
"iso8859-2" => "ISO-8859-2",
"iso8859-3" => "ISO-8859-3",
"iso8859-4" => "ISO-8859-4",
"iso8859-5" => "ISO-8859-5",
"iso8859-6" => "ISO-8859-6",
"iso8859-7" => "ISO-8859-7",
"iso8859-8" => "ISO-8859-8",
"iso8859-9" => "ISO-8859-9",
"iso8859-10" => "ISO-8859-10",
"iso8859-11" => "ISO-8859-11",
"iso8859-13" => "ISO-8859-13",
"iso8859-14" => "ISO-8859-14",
"iso8859-15" => "ISO-8859-15",
"iso_8859-1:1987" => "ISO-8859-1",
"iso-ir-100" => "ISO-8859-1",
"l1" => "ISO-8859-1",
"ibm819" => "ISO-8859-1",
"cp819" => "ISO-8859-1",
"csisolatin1" => "ISO-8859-1",
//
// Aliases for ISO-8859-2
//
"latin2" => "ISO-8859-2",
"iso_8859-2" => "ISO-8859-2",
"iso_8859-2:1987" => "ISO-8859-2",
"iso-ir-101" => "ISO-8859-2",
"l2" => "ISO-8859-2",
"csisolatin2" => "ISO-8859-2",
//
// Aliases for ISO-8859-3
//
"latin3" => "ISO-8859-3",
"iso_8859-3" => "ISO-8859-3",
"iso_8859-3:1988" => "ISO-8859-3",
"iso-ir-109" => "ISO-8859-3",
"l3" => "ISO-8859-3",
"csisolatin3" => "ISO-8859-3",
//
// Aliases for ISO-8859-4
//
"latin4" => "ISO-8859-4",
"iso_8859-4" => "ISO-8859-4",
"iso_8859-4:1988" => "ISO-8859-4",
"iso-ir-110" => "ISO-8859-4",
"l4" => "ISO-8859-4",
"csisolatin4" => "ISO-8859-4",
//
// Aliases for ISO-8859-5
//
"cyrillic" => "ISO-8859-5",
"iso_8859-5" => "ISO-8859-5",
"iso_8859-5:1988" => "ISO-8859-5",
"iso-ir-144" => "ISO-8859-5",
"csisolatincyrillic" => "ISO-8859-5",
//
// Aliases for ISO-8859-6
//
"arabic" => "ISO-8859-6",
"iso_8859-6" => "ISO-8859-6",
"iso_8859-6:1987" => "ISO-8859-6",
"iso-ir-127" => "ISO-8859-6",
"ecma-114" => "ISO-8859-6",
"asmo-708" => "ISO-8859-6",
"csisolatinarabic" => "ISO-8859-6",
//
// Aliases for ISO-8859-6-I
//
"csiso88596i" => "ISO-8859-6-I",
//
// Aliases for ISO-8859-6-E",
//
"csiso88596e" => "ISO-8859-6-E",
//
// Aliases for ISO-8859-7",
//
"greek" => "ISO-8859-7",
"greek8" => "ISO-8859-7",
"sun_eu_greek" => "ISO-8859-7",
"iso_8859-7" => "ISO-8859-7",
"iso_8859-7:1987" => "ISO-8859-7",
"iso-ir-126" => "ISO-8859-7",
"elot_928" => "ISO-8859-7",
"ecma-118" => "ISO-8859-7",
"csisolatingreek" => "ISO-8859-7",
//
// Aliases for ISO-8859-8",
//
"hebrew" => "ISO-8859-8",
"iso_8859-8" => "ISO-8859-8",
"visual" => "ISO-8859-8",
"iso_8859-8:1988" => "ISO-8859-8",
"iso-ir-138" => "ISO-8859-8",
"csisolatinhebrew" => "ISO-8859-8",
//
// Aliases for ISO-8859-8-I",
//
"csiso88598i" => "ISO-8859-8-I",
"iso-8859-8i" => "ISO-8859-8-I",
"logical" => "ISO-8859-8-I",
//
// Aliases for ISO-8859-8-E",
//
"csiso88598e" => "ISO-8859-8-E",
//
// Aliases for ISO-8859-9",
//
"latin5" => "ISO-8859-9",
"iso_8859-9" => "ISO-8859-9",
"iso_8859-9:1989" => "ISO-8859-9",
"iso-ir-148" => "ISO-8859-9",
"l5" => "ISO-8859-9",
"csisolatin5" => "ISO-8859-9",
//
// Aliases for UTF-8",
//
"unicode-1-1-utf-8" => "UTF-8",
// nl_langinfo(CODESET) in HP/UX returns 'utf8' under UTF-8 locales",
"utf8" => "UTF-8",
//
// Aliases for Shift_JIS",
//
"x-sjis" => "Shift_JIS",
"shift-jis" => "Shift_JIS",
"ms_kanji" => "Shift_JIS",
"csshiftjis" => "Shift_JIS",
"windows-31j" => "Shift_JIS",
"cp932" => "Shift_JIS",
"sjis" => "Shift_JIS",
//
// Aliases for EUC_JP",
//
"cseucpkdfmtjapanese" => "EUC-JP",
"x-euc-jp" => "EUC-JP",
//
// Aliases for ISO-2022-JP",
//
"csiso2022jp" => "ISO-2022-JP",
// The following are really not aliases ISO-2022-JP, but sharing the same decoder",
"iso-2022-jp-2" => "ISO-2022-JP",
"csiso2022jp2" => "ISO-2022-JP",
//
// Aliases for Big5",
//
"csbig5" => "Big5",
"cn-big5" => "Big5",
// x-x-big5 is not really a alias for Big5, add it only for MS FrontPage",
"x-x-big5" => "Big5",
// Sun Solaris",
"zh_tw-big5" => "Big5",
//
// Aliases for EUC-KR",
//
"cseuckr" => "EUC-KR",
"ks_c_5601-1987" => "EUC-KR",
"iso-ir-149" => "EUC-KR",
"ks_c_5601-1989" => "EUC-KR",
"ksc_5601" => "EUC-KR",
"ksc5601" => "EUC-KR",
"korean" => "EUC-KR",
"csksc56011987" => "EUC-KR",
"5601" => "EUC-KR",
"windows-949" => "EUC-KR",
//
// Aliases for GB2312",
//
// The following are really not aliases GB2312, add them only for MS FrontPage",
"gb_2312-80" => "GB2312",
"iso-ir-58" => "GB2312",
"chinese" => "GB2312",
"csiso58gb231280" => "GB2312",
"csgb2312" => "GB2312",
"zh_cn.euc" => "GB2312",
// Sun Solaris",
"gb_2312" => "GB2312",
//
// Aliases for windows-125x ",
//
"x-cp1250" => "windows-1250",
"x-cp1251" => "windows-1251",
"x-cp1252" => "windows-1252",
"x-cp1253" => "windows-1253",
"x-cp1254" => "windows-1254",
"x-cp1255" => "windows-1255",
"x-cp1256" => "windows-1256",
"x-cp1257" => "windows-1257",
"x-cp1258" => "windows-1258",
//
// Aliases for windows-874 ",
//
"windows-874" => "windows-874",
"ibm874" => "windows-874",
"dos-874" => "windows-874",
//
// Aliases for macintosh",
//
"macintosh" => "macintosh",
"x-mac-roman" => "macintosh",
"mac" => "macintosh",
"csmacintosh" => "macintosh",
//
// Aliases for IBM866",
//
"cp866" => "IBM866",
"cp-866" => "IBM866",
"866" => "IBM866",
"csibm866" => "IBM866",
//
// Aliases for IBM850",
//
"cp850" => "IBM850",
"850" => "IBM850",
"csibm850" => "IBM850",
//
// Aliases for IBM852",
//
"cp852" => "IBM852",
"852" => "IBM852",
"csibm852" => "IBM852",
//
// Aliases for IBM855",
//
"cp855" => "IBM855",
"855" => "IBM855",
"csibm855" => "IBM855",
//
// Aliases for IBM857",
//
"cp857" => "IBM857",
"857" => "IBM857",
"csibm857" => "IBM857",
//
// Aliases for IBM862",
//
"cp862" => "IBM862",
"862" => "IBM862",
"csibm862" => "IBM862",
//
// Aliases for IBM864",
//
"cp864" => "IBM864",
"864" => "IBM864",
"csibm864" => "IBM864",
"ibm-864" => "IBM864",
//
// Aliases for T.61-8bit",
//
"t.61" => "T.61-8bit",
"iso-ir-103" => "T.61-8bit",
"csiso103t618bit" => "T.61-8bit",
//
// Aliases for UTF-7",
//
"x-unicode-2-0-utf-7" => "UTF-7",
"unicode-2-0-utf-7" => "UTF-7",
"unicode-1-1-utf-7" => "UTF-7",
"csunicode11utf7" => "UTF-7",
//
// Aliases for ISO-10646-UCS-2",
//
"csunicode" => "UTF-16BE",
"csunicode11" => "UTF-16BE",
"iso-10646-ucs-basic" => "UTF-16BE",
"csunicodeascii" => "UTF-16BE",
"iso-10646-unicode-latin1" => "UTF-16BE",
"csunicodelatin1" => "UTF-16BE",
"iso-10646" => "UTF-16BE",
"iso-10646-j-1" => "UTF-16BE",
//
// Aliases for ISO-8859-10",
//
"latin6" => "ISO-8859-10",
"iso-ir-157" => "ISO-8859-10",
"l6" => "ISO-8859-10",
// Currently .properties cannot handle : in key",
//iso_8859-10:1992" => "ISO-8859-10",
"csisolatin6" => "ISO-8859-10",
//
// Aliases for ISO-8859-15",
//
"iso_8859-15" => "ISO-8859-15",
"csisolatin9" => "ISO-8859-15",
"l9" => "ISO-8859-15",
//
// Aliases for ISO-IR-111",
//
"ecma-cyrillic" => "ISO-IR-111",
"csiso111ecmacyrillic" => "ISO-IR-111",
//
// Aliases for ISO-2022-KR",
//
"csiso2022kr" => "ISO-2022-KR",
//
// Aliases for VISCII",
//
"csviscii" => "VISCII",
//
// Aliases for x-euc-tw",
//
"zh_tw-euc" => "x-euc-tw",
//
// Following names appears in unix nl_langinfo(CODESET)",
// They can be compiled as platform specific if necessary",
// DONT put things here if it does not look generic enough (like hp15CN)",
//
"iso88591" => "ISO-8859-1",
"iso88592" => "ISO-8859-2",
"iso88593" => "ISO-8859-3",
"iso88594" => "ISO-8859-4",
"iso88595" => "ISO-8859-5",
"iso88596" => "ISO-8859-6",
"iso88597" => "ISO-8859-7",
"iso88598" => "ISO-8859-8",
"iso88599" => "ISO-8859-9",
"iso885910" => "ISO-8859-10",
"iso885911" => "ISO-8859-11",
"iso885912" => "ISO-8859-12",
"iso885913" => "ISO-8859-13",
"iso885914" => "ISO-8859-14",
"iso885915" => "ISO-8859-15",
"cp1250" => "windows-1250",
"cp1251" => "windows-1251",
"cp1252" => "windows-1252",
"cp1253" => "windows-1253",
"cp1254" => "windows-1254",
"cp1255" => "windows-1255",
"cp1256" => "windows-1256",
"cp1257" => "windows-1257",
"cp1258" => "windows-1258",
"x-gbk" => "gbk",
"windows-936" => "gbk",
"ansi-1251" => "windows-1251",
];
/**
* Returns proper encoding mapping, if exsists. If it doesn't, return unchanged $encoding
* @param string|null $encoding
* @param string|null $fallback
*
* @return string
*/
public static function get($encoding, string $fallback = null): string {
if (isset(self::$aliases[strtolower($encoding ?? '')])) {
return self::$aliases[strtolower($encoding ?? '')];
}
return $fallback !== null ? $fallback : $encoding;
}
}
... ...
<?php
namespace Lib\Imap\Parse;
/**
* 解析邮件
* @author:dc
* @time 2024/9/10 16:54
* Class Header
*/
class Fetch{
/**
* @var array
*/
private array $attributes = [];
/**
* 原始头信息
* @var string
*/
protected string $raw_header;
/**
* Header constructor.
* @param string $raw_header
*/
public function __construct(string $raw_header)
{
$this->raw_header = $raw_header;
$this->rfc822_parse_headers();
}
/**
* @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);
}else{
$str = explode(":",$str);
$name = $str[0];
unset($str[0]);
$this->setAttribute($name,implode(':',$str));
}
}
foreach ($this->attributes as $name => $attribute){
$attribute = trim($attribute);
$this->attributes[$name] = $attribute;
}
}
public function __get(string $name)
{
return $this->get($name);
}
/**
* 读取字段
* @param string $name
* @return mixed
* @author:dc
* @time 2024/9/11 15:06
*/
public function get(string $name):mixed {
$name = strtolower($name);
$m = 'get'.str_replace(' ','',ucwords(str_replace('-',' ',$name)));
if(method_exists($this,$m)){
return $this->{$m}();
}
return $this->attributes[$name]??'';
}
/**
* 获取收件地址 可能是假地址
* @param bool $is_obj 是否解析成对象 Address
* @return Address[]
* @author:dc
* @time 2024/9/11 15:03
*/
public function getTo() {
// 如果有这个字段,就用这个字段 to字段的邮箱可能被伪造
if(!empty($this->attributes['delivered-to'])){
$to = $this->attributes['delivered-to'];
}else{
$to = $this->attributes['to']??'';
}
return $this->parseAddress($to);
}
/**
* 解析地址
* @param string $address
* @return array
* @author:dc
* @time 2024/9/11 15:53
*/
private function parseAddress(string $address){
$arr = [];
foreach (explode(',',$address) as $str){
$arr[] = Address::make($str);
}
return $arr;
}
/**
* 抄送人
* @return array
* @author:dc
* @time 2024/9/11 15:53
*/
public function getCc()
{
return $this->parseAddress($this->attributes['cc']??'');
}
/**
* 密送人
* @return array
* @author:dc
* @time 2024/9/11 15:54
*/
public function getBcc()
{
return $this->parseAddress($this->attributes['bcc']??'');
}
/**
* 发件人 可能是欺炸 发件人
* @return Address
* @author:dc
* @time 2024/9/11 15:19
*/
public function getFrom():Address {
return Address::make($this->attributes['from']??'');
}
/**
* 获取解码后的主题
* @return string
* @author:dc
* @time 2024/9/11 15:22
*/
public function getSubject():string {
$subject = explode("\n",$this->attributes['subject']??'');
foreach ($subject as $ak=>$str){
$subject[$ak] = DeCode::decode($str);
}
return implode("\n",$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);
}
return $arr;
}
}
... ...
<?php
namespace Lib\Imap\Parse\Folder;
/**
* 文件夹
* @author:dc
* @time 2024/9/20 9:33
* Class Folder
* @package Lib\Imap\Parse
*/
class Folder{
/**
* 文件夹名称 utf-7编码 全路径
* @var string
*/
public string $folder;
/**
* 是否可以选择
* @var bool
*/
public bool $isSelect;
/**
* 上级解析结构
* @var Folders
*/
protected Folders $parent;
/**
* 标签
* 有些邮箱把发件箱 send 改成了 send message 这里就有send标签,代表这个文件夹是发件箱
* 草稿,垃圾箱。回收站,等默认几个文件夹都有可能被更改
* @var array
*/
public array $flags;
public function __construct(string $folder,array $flags,bool $isSelect,Folders $parent)
{
$this->folder = $folder;
$this->flags = $flags;
$this->isSelect = $isSelect;
}
/**
* 把文件夹编码转换成utf8的
* @author:dc
* @time 2024/9/20 10:00
*/
public function getParseFolder(){
return mb_convert_encoding($this->folder, 'UTF-8', 'UTF7-IMAP');
}
/**
* 获取子目录
* @return static[]
* @author:dc
* @time 2024/9/20 11:55
*/
public function getChild():array {
return $this->parent->findChild($this);
}
}
... ...
<?php
namespace Lib\Imap\Parse\Folder;
/**
* 文件夹
* @author:dc
* @time 2024/9/20 9:33
* Class Folder
* @package Lib\Imap\Parse
*/
class Folders{
/**
* @var Folder[]
*/
protected array $item = [];
/**
* 原始头信息
* @var string
*/
protected array $raw = [];
/**
* Header constructor.
* @param array $raw
*/
public function __construct(array $raw)
{
$this->raw = $raw;
$this->parse();
}
/**
* 解析目录
* @author:dc
* @time 2024/9/20 10:07
*/
private function parse(){
foreach ($this->raw as $item){
// 解析源数据
if(preg_match('/^\*\sLIST\s\(([\\a-z\s]{0,})\)\s\"(.*)\"\s\"?(.*)\"?$/Ui',$item,$m)){
if($m[1]){
$flags = explode(' ',$m[1]);
$flags = array_map(function ($v){
$v = trim($v);
$v = trim($v,'\\');
return $v;
},$flags);
}else{
$flags = [];
}
$this->item[] = new Folder(trim(trim($m[3]),'"'),$flags, !str_contains($m[1], 'NoSelect'), $this);
}
}
}
/**
* 获取顶级栏目 一级目录
* @return Folder[]
* @author:dc
* @time 2024/9/20 11:39
*/
public function getTopFolder():array {
return array_filter($this->item,function ($folder){
return substr_count($folder->folder,'/') == 0;
});
}
/**
* 查找当前目录的下级目录
* @param Folder $folder
* @return Folder[]
* @author:dc
* @time 2024/9/19 17:54
*/
public function findChild(Folder $folder):array {
$fs = [];
foreach ($this->item as $item){
// 跳过相同目录
if($folder->folder == $item->folder) continue;
if(strpos($item->folder,$folder->folder) !== 0) continue;
$f = substr($item->folder,strlen($folder->folder),999);
$f = ltrim($f,'/');
if(substr_count($f,'/') == 0){
// $item['child'] = $this->findChild($item['folder']);
$fs[] = $item;
}
}
return $fs;
}
/**
* 获取所有目录 list 不是tree结构
* @return Folder[]
* @author:dc
* @time 2024/9/20 11:43
*/
public function all():array{
return $this->item;
}
}
... ...
<?php
namespace Lib\Imap\Parse;
/**
* 解析邮件
* @author:dc
* @time 2024/9/10 16:54
* Class Header
*/
class Header{
/**
* @var array
*/
private array $attributes = [];
/**
* 原始头信息
* @var string
*/
protected string $raw_header;
/**
* Header constructor.
* @param string $raw_header
*/
public function __construct(string $raw_header)
{
$this->raw_header = $raw_header;
$this->rfc822_parse_headers();
}
/**
* @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);
}else{
$str = explode(":",$str);
$name = $str[0];
unset($str[0]);
$this->setAttribute($name,implode(':',$str));
}
}
foreach ($this->attributes as $name => $attribute){
$attribute = trim($attribute);
$this->attributes[$name] = $attribute;
}
}
public function __get(string $name)
{
return $this->get($name);
}
/**
* 读取字段
* @param string $name
* @return mixed
* @author:dc
* @time 2024/9/11 15:06
*/
public function get(string $name):mixed {
$name = strtolower($name);
$m = 'get'.str_replace(' ','',ucwords(str_replace('-',' ',$name)));
if(method_exists($this,$m)){
return $this->{$m}();
}
return $this->attributes[$name]??'';
}
/**
* 获取收件地址 可能是假地址
* @param bool $is_obj 是否解析成对象 Address
* @return Address[]
* @author:dc
* @time 2024/9/11 15:03
*/
public function getTo() {
// 如果有这个字段,就用这个字段 to字段的邮箱可能被伪造
if(!empty($this->attributes['delivered-to'])){
$to = $this->attributes['delivered-to'];
}else{
$to = $this->attributes['to']??'';
}
return $this->parseAddress($to);
}
/**
* 解析地址
* @param string $address
* @return array
* @author:dc
* @time 2024/9/11 15:53
*/
private function parseAddress(string $address){
$arr = [];
foreach (explode(',',$address) as $str){
$arr[] = Address::make($str);
}
return $arr;
}
/**
* 抄送人
* @return array
* @author:dc
* @time 2024/9/11 15:53
*/
public function getCc()
{
return $this->parseAddress($this->attributes['cc']??'');
}
/**
* 密送人
* @return array
* @author:dc
* @time 2024/9/11 15:54
*/
public function getBcc()
{
return $this->parseAddress($this->attributes['bcc']??'');
}
/**
* 发件人 可能是欺炸 发件人
* @return Address
* @author:dc
* @time 2024/9/11 15:19
*/
public function getFrom():Address {
return Address::make($this->attributes['from']??'');
}
/**
* 获取解码后的主题
* @return string
* @author:dc
* @time 2024/9/11 15:22
*/
public function getSubject():string {
$subject = explode("\n",$this->attributes['subject']??'');
foreach ($subject as $ak=>$str){
$subject[$ak] = DeCode::decode($str);
}
return implode("\n",$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);
}
return $arr;
}
}
... ...
<?php
namespace Lib\Imap\Parse;
use Lib\Imap\Request\Msg;
/**
* 邮件
* @author:dc
* @time 2024/9/18 11:30
* @property Body $body
* Class MessageItem
* @package Lib\Imap\Parse
*/
class MessageItem {
/**
* 邮件的唯一id 文件夹下面唯一
* @var int
*/
public int $uid = 0;
/**
* 邮件编号
* @var int
*/
public int $msgno = 0;
/**
* 这个是邮件收到的时间
* @var string
*/
public string $date = '';
/**
* 邮件大小
* @var int
*/
public int $size = 0;
/**
* 标记
* @var array
*/
public array $flags = [];
/**
* 头部信息 主题 发件 收件 发送时间等消息
* @var Header
*/
public Header $header;
/**
* @var Msg
*/
protected Msg $msg;
/**
* MessageItem constructor.
* @param int $msgno
*/
public function __construct(int $msgno, Msg $msg)
{
$this->msg = $msg;
$this->msgno = $msgno;
}
/**
* 是否已被标记为删除了
* @return bool
* @author:dc
* @time 2024/9/18 11:52
*/
public function isDeleted():bool {
return in_array('deleted',$this->flags);
}
/**
* 已读
* @return bool
* @author:dc
* @time 2024/9/18 17:53
*/
public function isSeen():bool {
return in_array('seen',$this->flags);
}
/**
* 草稿
* @return bool
* @author:dc
* @time 2024/9/18 17:53
*/
public function isDraft():bool {
return in_array('seen',$this->flags);
}
/**
* 星标
* @return bool
* @author:dc
* @time 2024/9/18 17:53
*/
public function isFlagged():bool {
return in_array('flagged',$this->flags);
}
/**
* 已回复
* @return bool
* @author:dc
* @time 2024/9/18 17:52
*/
public function isAnswered():bool {
return in_array('answered',$this->flags);
}
/**
* 最近
* @return bool
* @author:dc
* @time 2024/9/18 17:54
*/
public function isRecent():bool {
return in_array('recent',$this->flags);
}
/**
* 获取邮件体 字段 内容
* @param string $name
* @return mixed
* @author:dc
* @time 2024/9/20 15:13
*/
public function get(string $name){
return $this->header->get($name);
}
/**
* 获取body体
* @return Body
* @author:dc
* @time 2024/9/20 15:14
*/
public function getBody():Body {
return $this->msg->folder->getBody($this->uid,true);
}
public function __get(string $name)
{
if($name=='body'){
return $this->getBody();
}else{
$this->get($name);
}
}
}
... ...
<?php
namespace Lib\Imap\Parse;
use Lib\Imap\Request\Msg;
/**
* 邮件列表
* @author:dc
* @time 2024/9/14 15:33
* Class Messager
* @package Lib\Imap\Parse
*/
class Messager implements \IteratorAggregate {
/**
* 原始数据
* @var array
*/
protected array $result = [];
/**
* 解析后的数据
* @var array
*/
protected array $item = [];
/**
* 读取消息的
* @var Msg
*/
protected Msg $msg;
public function __construct(array $result, Msg $msg)
{
$this->msg = $msg;
$k = 0;
foreach ($result as $item){
// 匹配出id和数据
if(preg_match("/\* (\d+) FETCH \(/",$item)){
$k++;
$this->result[$k] = $item;
}else{
if(isset($this->result[$k])){
$this->result[$k] .= $item;
}
}
}
$this->parse();
}
/**
* 实现 使用 foreach功能
* @return \ArrayIterator
* @author:dc
* @time 2024/9/18 10:43
*/
public function getIterator() {
return new \ArrayIterator($this->item);
}
/**
* 解析
* @author:dc
* @time 2024/9/14 17:35
*/
protected function parse(){
foreach ($this->result as $item){
$item = trim($item);
// 匹配出id和数据
if(preg_match("/\* (\d+) FETCH \(([\w\s\d\r\n\W]{1,})\)$/i",$item,$line)){
$this->parseFetch($line[1],$line[2]);
}
$line = null;
}
}
/**
* 解析头部信息,只能解析固定字段的属性
* @param int $msgno
* @param string $line
* UID 1568602720 INTERNALDATE "16-Sep-2019 10:58:40 +0800" FLAGS (\Seen) RFC822.HEADER {816}
Received: from localhost (unknown [10.110.4.165])
by mfast8 (Coremail) with SMTP id t8CowAA3Za5g+n5d1b4tCA--.53220S2;
Mon, 16 Sep 2019 10:58:40 +0800 (CST)
Date: Mon, 16 Sep 2019 10:58:40 +0800 (GMT+08:00)
From: =?utf-8?B?572R5piT6YKu566x5aSn5biI5Zui6Zif?= <developer.MailMaster@service.netease.com>
To: zhlong0616@163.com
Message-ID: <1803973093.340445.1568602720140.JavaMail.developer.MailMaster@service.netease.com>
Subject: =?utf-8?B?5qyi6L+O5L2/55So572R5piT6YKu566x5aSn5biI?=
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_Part_340444_1698792253.1568602720140"
X-CM-TRANSID:t8CowAA3Za5g+n5d1b4tCA--.53220S2
X-Coremail-Antispam: 1Uf129KBjDUn29KB7ZKAUJUUBvt529EdanIXcx71UUUUU7v73
VFW2AGmfu7bjvjm3AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvj4RJUUUUUUUU
X-Originating-IP: [10.110.4.165]
RFC822.SIZE 5111
* @author:dc
* @time 2024/9/18 11:46
*/
protected function parseFetch(int $msgno, string $line) {
$this->item[$msgno] = new MessageItem($msgno, $this->msg);
$tokens = explode(' ',$line);
while ($tokens){
$key = trim(array_shift($tokens));
switch ($key){
case 'UID':{
$this->item[$msgno]->uid = array_shift($tokens);
break;
}
case 'FLAGS':{
$flags = [];
while (1){
$flags[] = array_shift($tokens);
if(substr(end($flags),-1)==')') break;
}
$this->item[$msgno]->flags = array_map(function ($v){
return strtolower(str_replace(['\\','(',')'],'',$v));
},$flags);
break;
}
case 'INTERNALDATE':{
// 内部时间
$date = '';
while (1){
$date .= array_shift($tokens);
if(substr($date,-1)=='"') break;
$date.= ' ';
}
$this->item[$msgno]->date = trim($date,'"');
break;
}
case 'RFC822.SIZE':{
$this->item[$msgno]->size = array_shift($tokens);
break;
}
case 'RFC822.HEADER':{
$str = trim(implode(' ',$tokens));
// 是否带有字符数数量
if(preg_match("/^\{(\d+)\}/",$str,$num)){
$len = $num[1]+strlen($num[0]);
$header = substr($str,0,$len);
// 剩下的字符串
$tokens = explode(' ',substr($str,$len,9999));
// 踢出长度的头
$header = substr($header,strlen($num[0]),strlen($header));
}else{
// 以3个\r\n为结束
$str = explode("\r\n\r\n\r\n",trim($str));
$header = array_shift($str);
// 剩余的字符串
$tokens = explode(' ',implode("\r\n\r\n\r\n",$str));
}
$this->item[$msgno]->header = new Header($header);
break;
}
default:{break;}
}
}
}
/**
* 循环结果
* @param \Closure $closure
* @author:dc
* @time 2024/9/18 11:41
*/
public function each(\Closure $closure):void {
array_map($closure,$this->item);
}
}
... ...
<?php
namespace Lib\Imap\Request;
use Lib\Imap\Imap;
use Lib\Imap\Parse\Body as ParseBody;
/**
* body邮件内容
* @author:dc
* @time 2024/9/20 14:53
* Class Body
* @package Lib\Imap\Request
*/
class Body extends Request {
/**
* @var Folder
*/
protected Folder $folder;
public function __construct(Imap $imap, Folder $folder)
{
parent::__construct($imap);
$this->folder = $folder;
}
public function exec(): static{return $this;}
/**
* 读取邮件body
* @param int $num
* @param bool $is_uid
* @return ParseBody
* @author:dc
* @time 2024/9/20 15:40
*/
public function get(int $num, bool $is_uid = false):ParseBody {
$this->folder->exec(); // 防止在其他文件夹下面
$this->cmd("%sFETCH %s (RFC822.BODY)", $is_uid ? 'UID ' : '', $num);
return (new ParseBody($this->result ? $this->result[0] : ''));
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap\Request;
use Lib\Imap\Imap;
/**
* 登录
* @author:dc
* @time 2024/9/13 17:09
* Class Login
* @package Lib\Imap\Request
*/
class Folder extends Request{
protected string $folder;
/**
* 未读数量 有的邮箱是不会返回未读数量的
* @var int
*/
protected int $seen = 0;
/**
* 总数
* @var int
*/
protected int $total = 0;
/**
* 最近邮件的数量
* @var int
*/
protected int $recent = 0;
/**
* 标签
* @var array
*/
protected array $flags = [];
/**
* 是否初始了,就是是否select 文件夹
* @var bool
*/
private bool $isInit = false;
/**
* Folder constructor.
* @param Imap $imap
* @param string $folder
*/
public function __construct(Imap $imap, string $folder)
{
parent::__construct($imap);
$this->folder = $folder;
}
public function exec(): static
{
if(!$this->imap->checkedFolder($this->folder)){
$this->cmd('SELECT "%s"',$this->folder);
// ["* 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"]
if($this->isOk()){
$this->isInit = true;
foreach ($this->result as $item){
$item = trim($item);
// 总数量
if(preg_match("/^\* (\d+) EXISTS$/i",$item,$m)){
$this->total = (int) $m[1];
}
// 最近的
elseif (preg_match("/^\* (\d+) RECENT$/i",$item,$m)){
$this->total = (int) $m[1];
}
// 未读
elseif (preg_match("/^\*.*\[UNSEEN (\d+)\]/i",$item,$m)){
$this->seen = (int) $m[1];
}
// tag
elseif (preg_match("/^\* FLAGS \((.*)\)$/i",$item,$m)){
$this->flags = explode(' ',$m[1]);
}
}
// 设置
$this->imap->setCheckedFolder($this->folder);
}
}
return $this;
}
/**
* 邮件列表
* @return Msg
* @author:dc
* @time 2024/9/14 11:36
*/
public function msg():Msg {
return new Msg($this->imap,$this);
}
/**
* @return array
*/
public function getFlags(): array
{
if(!$this->isInit){
$this->exec();
}
return $this->flags;
}
/**
* @return int
*/
public function getRecent(): int
{
if(!$this->isInit){
$this->exec();
}
return $this->recent;
}
/**
* @return int
*/
public function getSeen(): int
{
if(!$this->isInit){
$this->exec();
}
return $this->seen;
}
/**
* @return int
*/
public function getTotal(): int
{
if(!$this->isInit){
$this->exec();
}
return $this->total;
}
/**
* 关闭当前目录 一般情况用不到
* @return bool
* @author:dc
* @time 2024/9/19 14:04
*/
public function close():bool {
// 必须初始了 并且在当前文件夹下面
if($this->isInit && $this->imap->checkedFolder($this->folder)){
$this->cmd('CLOSE');
return $this->isOk();
}
return true;
}
/**
* 创建目录
* @return bool
* @author:dc
* @time 2024/9/20 13:43
*/
public function create():bool {
$this->cmd('CREATE "%s"',$this->folder);
return $this->isOk();
}
/**
* 修改目录
* @param string $newFolder 新的文件夹名称
* @param bool $is_utf7 是否是utf7的编码
* @return bool
* @author:dc
* @time 2024/9/20 13:47
*/
public function rename(string $newFolder, bool $is_utf7 = true):bool {
// 需要转码
if(!$is_utf7) $newFolder = mb_convert_encoding($newFolder,'UTF7-IMAP','UTF-8');
// RENAME oldfolder newfolder
$this->cmd('RENAME "%s" "%s"',$this->folder,$newFolder);
return $this->isOk();
}
/**
* 删除 目录
* @return bool
* @author:dc
* @time 2024/9/20 13:50
*/
public function delete():bool {
$this->cmd('DELETE "%s"',$this->folder);
return $this->isOk();
}
/**
* 读取body体
* @param int $num
* @param false $is_uid
* @return \Lib\Imap\Parse\Body
* @author:dc
* @time 2024/9/20 15:37
*/
public function getBody(int $num, bool $is_uid = false):\Lib\Imap\Parse\Body{
$body = (new Body($this->imap,$this));
return $body->get($num, $is_uid = false);
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap\Request;
use Lib\Imap\Imap;
/**
* 文件夹列表
* @author:dc
* @time 2024/9/19 13:56
* Class Folders
* @package Lib\Imap\Request
*/
class Folders extends Request{
public function __construct(Imap $imap)
{
parent::__construct($imap);
$this->exec();
}
public function exec(): static
{
$this->cmd('LIST "" *');
return $this;
}
/**
* @return \Lib\Imap\Parse\Folder\Folders
* @author:dc
* @time 2024/9/20 10:24
*/
public function get():\Lib\Imap\Parse\Folder\Folders {
return new \Lib\Imap\Parse\Folder\Folders($this->result);
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap\Request;
/**
* 登录
* @author:dc
* @time 2024/9/13 17:09
* Class Login
* @package Lib\Imap\Request
*/
class Login extends Request{
public function exec(): static
{
$pwd = addcslashes($this->imap->config->getPassword(),'"');
$this->cmd('LOGIN "%s" "%s"',$this->imap->config->getEmail(),$pwd);
return $this;
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap\Request;
use Lib\Imap\Imap;
use Lib\Imap\ImapSearch;
use Lib\Imap\Parse\Messager;
/**
* 登录
* @author:dc
* @time 2024/9/13 17:09
* Class Login
* @package Lib\Imap\Request
*/
class Msg extends Request{
/**
* @var Folder
*/
public Folder $folder;
/**
* 读取列表条件
* @var array
*/
protected array $number = [];
/**
* 是否是uid
* @var bool
*/
private bool $isUid = false;
public function __construct(Imap $imap, Folder $folder)
{
parent::__construct($imap);
$this->folder = $folder;
}
public function exec(): static{return $this;}
/**
* 分页读取
* @param int $p
* @param int $limit
* @return $this
* @author:dc
* @time 2024/9/14 14:06
*/
public function forPage(int $p=1, int $limit=20):static {
$this->msgno(range(($p-1)*$limit+1,$p*$limit));
return $this;
}
/**
* 使用uid进行查询
* @param array $uids
* @return $this
* @author:dc
* @time 2024/9/14 14:06
*/
public function uid(array $uids):static {
$this->isUid = true;
$this->number = $uids;
return $this;
}
/**
* 使用编号进行查询
* @param array $msgno
* @return $this
* @author:dc
* @time 2024/9/14 14:06
*/
public function msgno(array $msgno):static {
$this->isUid = false;
$this->number = $msgno;
return $this;
}
/**
* 搜索
* @param ImapSearch $search
* @return $this
* @author:dc
* @time 2024/9/14 15:48
*/
public function search(ImapSearch $search):static {
$this->folder->exec(); // 防止在其他文件夹下面
$this->cmd("UID SEARCH ",$search->toString());
$uids = [];
foreach ($this->result as $item){
// 匹配uid * SEARCH 2 84 882
if(preg_match("/^\* SEARCH ([0-9\s]{1,})$/i",$item,$m)){
$uids = array_merge($uids,explode(' ',$m[1]));
}
}
$this->uid($uids);
return $this;
}
/**
* 读取邮件列表
* @return Messager
* @author:dc
* @time 2024/9/14 15:34
*/
public function get():Messager{
$this->folder->exec(); // 防止在其他文件夹下面
$this->cmd(
"%sFETCH %s (UID FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER)",
$this->isUid?'UID ':'',
implode(',',$this->number)
);
return new Messager($this->result, $this);
}
public function getBody(){
$this->folder->exec(); // 防止在其他文件夹下面
$this->cmd(
"%sFETCH %s (UID FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER)",
$this->isUid?'UID ':'',
implode(',',$this->number)
);
return new Messager($this->result, $this);
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap\Request;
/**
* 类似 ping 命令
* 保持通讯用的
* @author:dc
* @time 2024/9/20 14:19
* Class Noop
* @package Lib\Imap\Request
*/
class Noop extends Request{
public function exec(): static
{
$this->cmd('NOOP');
return $this;
}
}
\ No newline at end of file
... ...
<?php
namespace Lib\Imap\Request;
use Lib\Imap\Imap;
/**
* 命令组装 请求 返回处理
* @author:dc
* @time 2024/9/18 10:12
* Class Request
* @package Lib\Imap\Request
*/
abstract class Request {
public Imap $imap;
/**
* 命令执行结果
* @var array
*/
protected array $result = [];
/**
* 执行结果状态 YES OK NO BAD
* @var string
*/
protected string $status = '';
/**
* 这条命令执行的tag
* @var string
*/
protected string $tag = '';
/**
* 最后的消息
* @var string
*/
protected string $message = '';
public function __construct(Imap $imap)
{
$this->imap = $imap;
}
/**
* 执行命令
* @param string $cmd 命令
* @param mixed ...$params 命令参数
* @author:dc
* @time 2024/9/13 17:36
*/
protected final function cmd(string $cmd,...$params){
$this->result = $this->imap->client->request($params?sprintf($cmd,...$params):$cmd);
// 删除最后一行的状态
$end = array_pop($this->result);
// 状态处理
list($this->tag,$this->status,$this->message) = explode(' ',$end.' ',3);
$this->message = trim($this->message);
}
abstract public function exec():static ;
/**
* @return string
*/
public function getMessage(): string
{
return $this->message;
}
/**
* @return string
*/
public function getStatus(): string
{
return $this->status;
}
/**
* @return string
*/
public function getTag(): string
{
return $this->tag;
}
/**
* 是否成功
* @return bool
* @author:dc
* @time 2024/9/14 9:10
*/
public function isOk(){
return $this->status == 'OK';
}
}
\ No newline at end of file
... ...
## 目录结果
>> Request 目录是组装命令并执行
>> Parse 目录是解析数据的目录
... ...
<?php
// 实例一个配置
$config = (new \Lib\Imap\ImapConfig())
// 邮箱
->setEmail('zhlong0616@163.com')
// 密码
->setPassword(base64_decode('VEVKSVdPTUJQS09TSFJHSg=='))
// 必须是 协议+域名+端口
->setHost('ssl://imap.163.com:993');
// 获取一个imap连接 必须传入 \Lib\Imap\ImapConfig类
// 返回的是 Lib\Imap\Imap 类
$imap = \Lib\Imap\ImapPool::get($config);
// 登录 必须先登录
$login = $imap->login();
if($login->isOk()){// 登录成功
/******************** start 文件夹 ************************/
// 获取文件夹对象
$folders = $imap->getFolders();
// 获取 所有目录
foreach ($folders->all() as $folder){
$folder->getParseFolder(); // 获取解析过后的目录名称
$folder->folder; // 获取没有解析的目录名称
}
// 获取顶级目录
foreach ($folders->getTopFolder() as $folder){
$childs = $folder->getChild(); // 获取 子级目录
foreach ($childs as $child){
$child->getChild();
}
}
// 创建一级文件夹
$imap->folder('你好',false)->create();
// 创建 二级文件夹
$imap->folder('你好/大家好',false)->create();
// 删除 你好 文件夹
$imap->folder('你好',false)->delete();
// 把 你好 改成 我好
$imap->folder('你好',false)->rename('我好',false);
/******************** end 文件夹 ************************/
/************************ start 获取某个文件夹下面的邮件 *******************************/
// 选择文件夹 预备读取 使用分页 查询
$msgs = $imap->folder('INBOX')->msg()->forPage()->get();
// 1.使用foreach
foreach ($msgs as $msg){
/** @var \Lib\Imap\Parse\MessageItem $msg **/
}
// 2.使用 each
$msgs->each(function (\Lib\Imap\Parse\MessageItem $msg){
$msg->msgno;// 邮件编号
$msg->uid;// 邮件唯一id
$msg->size;// 邮件大小
$msg->date;// 邮件内部时间,也就是服务器收到邮件的时间
$msg->flags;// 邮件标签 已读,已回,星标,删除,草稿 最近
$msg->isAnswered();//已回
$msg->isDeleted();//删除
$msg->isDraft();//草稿
$msg->isFlagged();//星标
$msg->isRecent();//最近
$msg->isSeen();// 已读
/** 头部信息 @see \Lib\Imap\Parse\Header */
$msg->header->getSubject();// 已解析的主题
$msg->header->getFrom();// 已解析的发件人
$msg->header->getTo();// 已解析的收件人
// 获取某个字段
$msg->header->get('subject');
});
// 搜索邮箱
$msg = $imap->folder('INBOX')->msg()->search(
// 搜索未读 并且 主题有 你好 的
(new \Lib\Imap\ImapSearch())
->seen(false)
->subject('你好')
)->get();
/*******************************************************/
}else{
// 登录失败 获取错误消息 $login->getMessage()
throw new Exception($login->getMessage());
}
... ...