作者 邓超

m

... ... @@ -210,7 +210,7 @@ class Home extends Base {
if(!empty($v['to_name'][0]['email'])){
$v['to'] = $v['to_name'][0]['email'];
}
$v['to_name'] = $v['to_name'][0]['name']??'';
$v['to_name'] = MailFun::mb_coding($v['to_name'][0]['name']??'');
}
if(is_array($v['to_name'])){
$v['to_name'] = '';
... ... @@ -840,6 +840,13 @@ class Home extends Base {
$data['cc'] = $data['cc'] ? json_decode($data['cc'],true) : [];
$data['bcc'] = $data['bcc'] ? json_decode($data['bcc'],true) : [];
$data['to_name'] = array_map(function ($t){
if(empty($t['name'])){
$t['name'] = MailFun::mb_coding($t['name']);
}
return $t;
},$data['to_name']);
// 是否再次 重新获取
$reload = app()->request('reload',0,'intval');
if($reload){
... ...
... ... @@ -5,6 +5,7 @@ namespace Controller\fob_ai;
use Controller\Base;
use Lib\Mail\MailFun;
use Lib\Verify;
use Model\folderSql;
use function Swoole\Coroutine\Http\request;
... ... @@ -246,7 +247,7 @@ class MailListV2 extends Base {
if(!empty($v['to_name'][0]['email'])){
$v['to'] = $v['to_name'][0]['email'];
}
$v['to_name'] = $v['to_name'][0]['name']??'';
$v['to_name'] = MailFun::mb_coding($v['to_name'][0]['name']??'');
}
if(is_array($v['to_name'])){
$v['to_name'] = '';
... ...
<?php
namespace Lib\Imap;
/**
* 数据
* @author:dc
* @time 2024/9/21 9:27
* Class DataArray
* @package Lib\Imap
*/
class DataArray {
/**
* 存储的数据
* @var array
*/
private array $attribute = [];
/**
* @param string $name
* @param mixed $val
* @param bool $append
* @author:dc
* @time 2024/9/21 9:33
*/
public function set(string $name, mixed $val, bool $append = false): void
{
$name = strtolower($name);
if($append && is_string($val) && $this->get($name)!==null ){
$this->attribute[$name] .= $val;
}else{
$this->attribute[$name] = $val;
}
}
/**
* @param string $name
* @return mixed
* @author:dc
* @time 2024/9/21 9:34
*/
public function get(string $name): mixed
{
if(isset($this->attribute[$name])){
return $this->attribute[$name];
}
foreach ($this->attribute as $key=>$value){
// 不区分大小写对比
if(strcasecmp($key,$name)===0){
return $value;
}
}
return null;
}
/**
* 不区分大小写比较字符串
* @param string $name
* @param mixed $value
* @return bool
* @author:dc
* @time 2024/9/21 9:57
*/
public function eq(string $name,mixed $value):bool {
return strcasecmp($this->get($name),$value) === 0;
}
public function __get(string $name): mixed
{
return $this->get($name);
}
public function __set(string $name, $value): void
{
$this->set($name,$value);
}
}
\ No newline at end of file
... ...
... ... @@ -2,6 +2,8 @@
namespace Lib\Imap\Parse;
use Lib\Imap\DataArray;
/**
* 邮件内容
* @author:dc
... ... @@ -18,18 +20,28 @@ class Body {
protected string $raw = '';
/**
* 解析后的数据
* @var array
* 消息结构,解析后的邮件体 header
* @var MessageItem
*/
protected array $item = [];
protected MessageItem $msg;
/**
* 解析后的body数据
* @var DataArray[]
*/
private array $items = [];
public function __construct(string $result)
/**
* Body constructor.
* @param string $result
* @param MessageItem $msg
*/
public function __construct(string $result, MessageItem $msg)
{
// 匹配出id和数据
if(preg_match("/\* (\d+) FETCH \(/",$result)){
$this->raw = $result;
}
$this->msg = $msg;
$this->parse();
}
... ... @@ -42,11 +54,146 @@ class Body {
* @time 2024/9/14 17:35
*/
protected function parse(){
// 是否是多段
$boundary = $this->msg->header->getBoundary();
if($boundary){
// 切割成块
$items = explode($boundary,$this->raw);
// 第一个块和最后一块 是没用的块
array_shift($items);array_pop($items);
foreach ($items as $item){
$this->items[] = $this->parseItem($item);
}
}
}
/**
* 解析每个 块
* @param string $body 块字符串
* @return DataArray
* @author:dc
* @time 2024/9/21 9:51
*/
protected function parseItem(string $body):DataArray {
list($mime_header,$text) = explode("\r\n\r\n",$body,2);
$data = $this->parseMimeHeader($mime_header);
// 处理body体 的编码
switch ($data->get('Content-Transfer-Encoding')){
case 'quoted-printable':{
$text = quoted_printable_decode($text);break;
}
case 'base64':{
$text = base64_decode($text);break;
}
}
$data->body = $text;
return $data;
}
/**
* 解析邮件体里面的每个块 头部
* @param string $header
* @return DataArray
* @author:dc
* @time 2024/9/21 9:18
*/
protected function parseMimeHeader(string $header):DataArray {
// 处理 描述信息,
$header = explode("\r\n",trim($header));
$data = new DataArray();
$name = '';
foreach ($header as $head){
// 判断是否是上一行的
if(str_starts_with($head,' ') || str_starts_with($head,"\t")){
$data->set($name,' '.$head,true);
}else{
list($name,$value) = explode(":",$head,2);
$data->set($name,trim($value));
}
}
// 处理编码
if($data->get('Content-Type')){
// 切割成 每个小块 Content-Type: text/html;charset=utf-8
$contentType = explode(' ',trim(str_replace(';',' ',$data->get('Content-Type'))));
foreach ($contentType as $ct){
$ct = trim($ct);
if(str_contains($ct,'/')){
$data->set('Content-Type',$ct);
}elseif (str_contains($ct,'=')){
list($name,$val) = explode('=',$ct);
$data->set($name,trim($val));
}
}
}
// 默认编码
if(!$data->Charset) $data->Charset = 'utf-8';
return $data;
}
/**
* 读取纯文本的内容
* @author:dc
* @time 2024/9/21 9:55
*/
public function getText():string {
foreach ($this->items as $item){
if($item->eq('content-type','text/plain')){
return $item->body;
}
}
// 没找到 text
return strip_tags($this->getHtml());
}
/**
* 读取 html文本
* @return string
* @author:dc
* @time 2024/9/21 10:02
*/
public function getHtml():string {
foreach ($this->items as $item){
if($item->eq('content-type','text/html')){
return $item->body;
}
}
}
/**
* 有些邮件里面的图片是通过附件的形式发来的
* <img src="cid:xxxx" /> 这种就是附件图片,需要替换的
* @return array
* @author:dc
* @time 2024/9/21 10:53
*/
public function getHtmlAndImg():array {
$html = $this->getHtml();
}
/**
* 读取附件
* @return DataArray[]
* @author:dc
* @time 2024/9/21 10:05
*/
public function getAttachment():array {
}
... ...
<?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;
}
}
... ... @@ -67,11 +67,9 @@ class Header{
// 判断是否是上一行的
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));
}elseif(str_contains($str,':')){
list($name, $str) = explode(":",$str,2);
$this->setAttribute($name,$str);
}
}
... ...
... ... @@ -146,7 +146,13 @@ class MessageItem {
* @time 2024/9/20 15:14
*/
public function getBody():Body {
return $this->msg->folder->getBody($this->uid,true);
if(isset($this->body)){
return $this->body;
}
$this->body = $this->msg->getBody($this);
return $this->body;
}
... ...
... ... @@ -154,11 +154,11 @@ class Messager implements \IteratorAggregate {
$header = substr($header,strlen($num[0]),strlen($header));
}else{
// 以3个\r\n为结束
$str = explode("\r\n\r\n\r\n",trim($str));
// 以2个\r\n为结束
$str = explode("\r\n\r\n",trim($str));
$header = array_shift($str);
// 剩余的字符串
$tokens = explode(' ',implode("\r\n\r\n\r\n",$str));
$tokens = explode(' ',implode("\r\n\r\n",$str));
}
$this->item[$msgno]->header = new Header($header);
... ... @@ -183,6 +183,22 @@ class Messager implements \IteratorAggregate {
}
/**
* 获取一条消息
* @return bool|MessageItem
* @author:dc
* @time 2024/9/20 16:48
*/
public function first():bool|MessageItem {
if($this->item){
return $this->item[array_key_first($this->item)];
}
return false;
}
}
... ...
<?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
... ... @@ -4,6 +4,7 @@
namespace Lib\Imap\Request;
use Lib\Imap\Imap;
use Lib\Imap\Parse\MessageItem;
/**
* 登录
... ... @@ -218,20 +219,5 @@ class Folder extends Request{
}
/**
* 读取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
... ...
... ... @@ -5,6 +5,8 @@ namespace Lib\Imap\Request;
use Lib\Imap\Imap;
use Lib\Imap\ImapSearch;
use Lib\Imap\Parse\Body as ParseBody;
use Lib\Imap\Parse\MessageItem;
use Lib\Imap\Parse\Messager;
/**
... ... @@ -62,27 +64,27 @@ class Msg extends Request{
/**
* 使用uid进行查询
* @param array $uids
* @param array|int $uids
* @return $this
* @author:dc
* @time 2024/9/14 14:06
*/
public function uid(array $uids):static {
public function uid(array|int $uids):static {
$this->isUid = true;
$this->number = $uids;
$this->number = is_array($uids) ? $uids : [$uids];
return $this;
}
/**
* 使用编号进行查询
* @param array $msgno
* @param array|int $msgno
* @return $this
* @author:dc
* @time 2024/9/14 14:06
*/
public function msgno(array $msgno):static {
public function msgno(array|int $msgno):static {
$this->isUid = false;
$this->number = $msgno;
$this->number = is_array($msgno)?$msgno:[$msgno];
return $this;
}
... ... @@ -126,15 +128,20 @@ class Msg extends Request{
}
public function getBody(){
/**
* 读取body 体
* @param MessageItem $msg
* @return ParseBody
* @author:dc
* @time 2024/9/20 17:09
*/
public function getBody(MessageItem $msg){
$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);
$this->cmd("UID FETCH %s (RFC822.TEXT)", $msg->uid);
return (new ParseBody(implode('',$this->result),$msg));
}
... ...
... ... @@ -15,7 +15,7 @@ use Lib\Imap\Imap;
abstract class Request {
public Imap $imap;
protected Imap $imap;
/**
... ...
... ... @@ -75,6 +75,16 @@ if($login->isOk()){// 登录成功
// 获取某个字段
$msg->header->get('subject');
// html内容
$msg->body->getHtml();
// 纯文本内容
$msg->body->getText();
// 获取附件
foreach ($msg->body->getAttachment() as $attachment){
// 存起来
file_put_contents($attachment->get('filename'),$attachment->get('body'));
}
});
... ...