[PHP] PHP调用IMAP协议读取邮件类库
程序员文章站
2022-05-26 21:56:40
socket.php 为连接socket的类库 imap.php 基于socket的imap协议封装 test.php 进行测试 imap.php socket.php ......
socket.php 为连接socket的类库
imap.php 基于socket的imap协议封装
test.php 进行测试
require_once 'socket.php'; require_once 'imap.php'; $imap=new sina_mail_net_imap("imap.sina.net:143",30,30); $imap->capability(); $imap->id(array( 'name' => 'sinamail othermail client', 'version' => '1', 'os' => 'sinamail othermail', 'os-version' => '1.0', )); $imap->login("xxxx@xxxxx","xxxx"); $folders=$imap->getlist('', '*'); var_dump($folders); $status = $imap->select('sent'); var_dump($status); $ls = $imap->fetch(array(), array('uid', 'internaldate', 'rfc822.size')); foreach($ls as $k=>$i){ $info=$imap->fetch(array($k), array('rfc822')); }
imap.php
<?php class sina_mail_net_imap { const max_read_size = 100000000; const pattern_request_string_sequence = '/{\d+}/'; const pattern_response_string_sequence = '/{(\d+)}$/'; const sequence_param_name = '[]'; const partial_param_name = '<>'; const param_no = 1; const param_single = 2; const param_pair = 4; const param_list = 8; const param_string = 16; const param_number = 32; const param_date = 64; const param_flag = 128; const param_sequence = 256; const param_search = 512; const param_body = 1024; const param_partial = 2048; const param_exclusive = 4096; private static $statuskeywords = array( 'messages' => self::param_no, 'recent' => self::param_no, 'uidnext' => self::param_no, 'uidvalidity' => self::param_no, 'unseen' => self::param_no, ); private static $searchkeywords = array( 'all' => self::param_no, 'answered' => self::param_no, 'bcc' => 18, // self::param_single | self::param_string, 'before' => 66, // self::param_single | self::param_date, 'body' => 18, // self::param_single | self::param_string, 'cc' => 18, // self::param_single | self::param_string, 'deleted' => self::param_no, 'draft' => self::param_no, 'flagged' => self::param_no, 'from' => 18, // self::param_single | self::param_string, 'header' => 20, // self::param_pair | self::param_string, 'keyword' => 130, // self::param_single | self::param_flag, 'larger' => 34, // self::param_single | self::param_number, 'new' => self::param_no, 'not' => 514, // self::param_single | self::param_search, 'old' => self::param_no, 'on' => 66, // self::param_single | self::param_date, 'or' => 516, // self::param_pair | self::param_search, 'recent' => self::param_no, 'seen' => self::param_no, 'sentbefore' => 66, // self::param_single | self::param_date, 'senton' => 66, // self::param_single | self::param_date, 'sentsince' => 66, // self::param_single | self::param_date, 'since' => 66, // self::param_single | self::param_date, 'smaller' => 34, // self::param_single | self::param_number, 'subject' => 18, // self::param_single | self::param_string, 'text' => 18, // self::param_single | self::param_string, 'to' => 18, // self::param_single | self::param_string, 'uid' => 258, // self::param_single | self::param_sequence, 'unanswered' => self::param_no, 'undeleted' => self::param_no, 'undraft' => self::param_no, 'unflagged' => self::param_no, 'unkeyword' => 130, // self::param_single | self::param_flag, 'unseen' => self::param_no, ); private static $fetchkeywords = array( 'all' => 4097, // self::param_no | self::param_exclusive, 'fast' => 4097, // self::param_no | self::param_exclusive, 'full' => 4097, // self::param_no | self::param_exclusive, 'body' => 3075, // self::param_no | self::param_single | self::param_body | self::param_partial, 'body.peek' => 3074, // self::param_single | self::param_body | self::param_partial, 'bodystructure' => self::param_no, 'envelope' => self::param_no, 'flags' => self::param_no, 'internaldate' => self::param_no, 'rfc822' => self::param_no, 'rfc822.header' => self::param_no, 'rfc822.size' => self::param_no, 'rfc822.text' => self::param_no, 'uid' => self::param_no, ); private $sock = null; private $timeout = 120; private $ts = 0; private $tagname = 'a'; private $tagid = 0; private $capabilities = array(); private $folders = array(); private $currentfolder = null; private $currentcommand = null; private $lastsend = ''; private $lastrecv = ''; public function __construct($uri, $timeout = null, $conntimeout = null) { $this->sock = new socket($uri, $timeout); // $t = intval($timeout); // if ($t > 0) { // $this->timeout = $t; // } $this->connect($conntimeout); } public function __destruct() { } public function connect($timeout) { $this->sock->connect($timeout); $this->getresponse(); } public function capability() { $res = $this->request('capability'); if (isset($res[0][0]) && $res[0][0] == '*' && isset($res[0][1]) && strcasecmp($res[0][1], 'capability') == 0) { for ($i = 2, $n = count($res[0]); $i < $n; ++$i) { $this->capabilities[strtoupper($res[0][$i])] = true; } } } public function id($data) { if (isset($this->capabilities['id'])) { $this->request('id', array($data)); } } public function login($username, $password) { try { $this->request('login', array($username, $password)); } catch (exception $ex) { throw new exception($ex->getmessage(), $ex->getcode()); } } public function logout() { $this->request('logout'); } public function getlist($reference = '', $wildcard = '') { $res = $this->request('list', array($reference, $wildcard)); foreach ($res as &$r) { if (isset($r[0]) && $r[0] == '*' && isset($r[1]) && strcasecmp($r[1], 'list') == 0 && isset($r[4])) { $this->folders[$r[4]] = array( 'id' => $r[4], 'name' => mb_convert_encoding($r[4], 'utf-8', 'utf7-imap'), 'path' => $r[3], 'attr' => $r[2], ); } } return $this->folders; } public function status($folder, $data) { $args = $this->formatargsforcommand($data, self::$statuskeywords); $res = $this->request('status', array($folder, $args)); $status = array(); if (!empty($res)) { foreach ($res as &$r) { if (isset($r[0]) && $r[0] == '*' && isset($r[1]) && strcasecmp($r[1], 'status') == 0 && isset($r[3]) && is_array($r[3])) { for ($i = 0, $n = count($r[3]); $i < $n; $i += 2) { $status[$r[3][$i]] = $r[3][$i + 1]; } } } } return $status; } public function select($folder) { $res = $this->request('select', array($folder)); $status = array(); if (!empty($res)) { foreach ($res as $r) { if (isset($r[0]) && $r[0] == '*') { if (isset($r[1]) && isset($r[2])) { if (strcasecmp($r[1], 'ok') == 0 && is_array($r[2])) { for ($i = 0, $n = count($i); $i < $n; $i += 2) { $status[$r[2][$i]] = $r[2][$i + 1]; } } elseif (ctype_digit($r[1])) { $status[$r[2]] = $r[1]; } else { $status[$r[1]] = $r[2]; } } } } } $this->currentfolder = $folder; return $status; } public function search($data) { $args = $this->formatargsforcommand($data, self::$searchkeywords, true); $res = $this->request('search', $args); $ls = array(); foreach ($res as &$r) { if (isset($r[0]) && $r[0] == '*' && isset($r[1]) && strcasecmp($r[1], 'search') == 0) { for ($i = 2, $n = count($r); $i < $n; ++$i) { $ls[] = $r[$i]; } } } return $ls; } public function fetch($seq, $data) { $seqstr = $this->formatsequence($seq); $args = $this->formatargsforcommand($data, self::$fetchkeywords); $res = $this->request('fetch', array($seqstr, $args)); // var_dump($res); $ls = array(); foreach ($res as &$r) { if (isset($r[0]) && $r[0] == '*' && isset($r[1]) && is_numeric($r[1]) && isset($r[2]) && strcasecmp($r[2], 'fetch') == 0 && isset($r[3]) && is_array($r[3])) { $a = array(); for ($i = 0, $n = count($r[3]); $i < $n; $i += 2) { $key = $r[3][$i]; if (((strcasecmp($key, 'body') == 0 && isset($args['body']) && is_array($args['body'])) || (strcasecmp($key, 'body.peek') == 0 && isset($args['body.peek']) && is_array($args['body.peek']))) && is_array($r[3][$i + 1])) { $key = trim($this->formatrequestarray(array($key => $r[3][$i + 1]), $placeholder, 0), '()'); $i++; } else { $key = $r[3][$i]; } $a[$key] = $r[3][$i + 1]; } if (!empty($a)) { $ls[$r[1]] = $a; } } } return $ls; } private function nexttag() { $this->tagid++; return sprintf('%s%d', $this->tagname, $this->tagid); } private function request($cmd, $args = array()) { $this->currentcommand = strtoupper(trim($cmd)); $tag = $this->nexttag(); $req = $tag . ' ' . $this->currentcommand; // 格式化参数列表 $strseqlist = array(); if (is_array($args)) { $argstr = $this->formatrequestarray($args, $strseqlist); } else { $argstr = $this->formatrequeststring($args, $strseqlist); } //$argstr = $this->makerequest($args, $strseqlist); $subreqs = array(); if (isset($argstr[0])) { $req .= ' ' . $argstr; // 如果参数中包括需要序列化的数据,根据序列化标识{length}将命令拆分成多条 if (!empty($strseqlist) && preg_match_all(self::pattern_request_string_sequence, $req, $matches, preg_offset_capture)) { $p = 0; foreach ($matches[0] as $m) { $e = $m[1] + strlen($m[0]); $subreqs[] = substr($req, $p, $e - $p); $p = $e; } $subreqs[] = substr($req, $p); // 校验序列化标识与需要序列化的参数列表数量是否一致 if (count($subreqs) != count($strseqlist) + 1) { $subreqs = null; } } } if (empty($subreqs)) { // 处理单条命令 $this->sock->writeline($req); $this->lastsend = $req; $res = $this->getresponse($tag); } else { // 处理多条命令 $this->lastsend = ''; foreach ($subreqs as $id => $req) { $this->sock->writeline($req); $this->lastsend .= $req; $res = $this->getresponse($tag); if (isset($res[0][0]) && $res[0][0] == '+') { $this->sock->write($strseqlist[$id]); $this->lastsend .= "\r\n" . $strseqlist[$id]; } else { // 如果服务器端返回其他相应,则定制后续执行 break; } } } return $res; } private function formatrequeststring($s, &$strseqlist) { $s = trim($s); $needquote = false; if (!isset($s[0])) { $needquote = true; } elseif ($this->currentcommand == 'id') { $needquote = true; } else { // 参数包含多行时,需要进行序列化 if (strpos($s, "\r") !== false || strpos($s, "\n") !== false) { $strseqlist[] = $s; $s = sprintf('{%d}', strlen($s)); } else { // 参数包含双引号或空格时,需要将使用双引号括起来 if (strpos($s, '"') !== false) { $s = addcslashes($s, '"'); $needquote = true; } if (strpos($s, ' ') !== false) { $needquote = true; } } } if ($needquote) { return sprintf('"%s"', $s); } else { return $s; } } private function formatrequestarray($arr, &$strseqlist, $level = -1) { $a = array(); foreach ($arr as $k => $v) { $isbody = false; $supportpartial = false; $partialstr = ''; if ($this->currentcommand == 'fetch') { // 识别是否body命令,是否可以包含<partial> $kw = strtoupper($k); if (isset(self::$fetchkeywords[$kw]) && (self::$fetchkeywords[$kw] & self::param_body) > 0) { $isbody = true; } if (isset(self::$fetchkeywords[$kw]) && (self::$fetchkeywords[$kw] & self::param_partial) > 0) { $supportpartial = true; } } if (is_array($v)) { if ($supportpartial && isset($v[self::partial_param_name]) && is_array($v[self::partial_param_name])) { // 处理包含<partial>的命令 foreach ($v[self::partial_param_name] as $spos => $mlen) { $partialstr = sprintf('<%d.%d>', $spos, $mlen); } unset($v[self::partial_param_name]); } $s = $this->formatrequestarray($v, $strseqlist, $level + 1); } else { $s = $this->formatrequeststring($v, $strseqlist); } if (!is_numeric($k)) { // 字典方式需要包含键名 $k = $this->formatrequeststring($k, $strseqlist); if ($isbody) { $s = $k . $s; } else { $s = $k . ' ' . $s; } // 包含<partial> if ($supportpartial) { $s .= $partialstr; } } $a[] = $s; } if ($level < 0) { return implode(' ', $a); } elseif (($level % 2) == 0) { return sprintf('(%s)', implode(' ', $a)); } else { return sprintf('[%s]', implode(' ', $a)); } } private function formatsequence($seq) { $n = count($seq); if ($n == 0) { return '1:*'; } elseif ($n == 1) { if (isset($seq[0])) { return strval($seq[0]); } else { foreach ($seq as $k => $v) { return $k . ':' . $v; } } } else { return implode(',', $seq); } } private function formatargsforcommand(&$data, &$fields, $aslist = false) { $args = array(); foreach ($data as $k => $v) { if (is_numeric($k)) { // 无值参数 $name = strtoupper($v); if (isset($fields[$name])) { // 对于排他性属性,直接返回 if (($fields[$name] & self::param_exclusive) > 0) { return $name; } elseif (($fields[$name] & self::param_no) > 0) { $args[] = $name; } } } elseif ($k == self::sequence_param_name) { // 序列 $args[] = $this->formatsequence($v); } else { $name = strtoupper($k); if (isset($fields[$name])) { $paramtype = $fields[$name]; // 格式化参数类型 if (($paramtype & self::param_date) > 0) { $v = date('j-m-y', $v); } elseif (($paramtype & self::param_sequence) > 0) { $v = $this->formatsequence($v); } // 根据参数定义拼组参数列表 if (($paramtype & self::param_single) > 0) { // 单值参数 if ($aslist) { $args[] = $name; $args[] = $v; } else { $args[$name] = $v; } } elseif (($paramtype & self::param_pair) > 0) { // 键值对参数 if (is_array($v)) { foreach ($v as $x => $y) { $pk = $x; $pv = $y; break; } } else { $pk = $v; $pv = ''; } if ($aslist) { $args[] = $name; $args[] = $pk; $args[] = $pv; } else { $args[$name] = array($pk => $pv); } } elseif (($paramtype & self::param_list) > 0) { // 列表参数 if ($aslist) { $args[] = $name; foreach ($v as $i) { $args[] = $i; } } else { $args[$name] = $v; } } elseif (($paramtype & self::param_no) > 0) { // 无值参数 $args[] = $name; } } } } return $args; } private function getresponse($tag = null) { $r = array(); $readmore = true; while ($readmore) { $ln = trim($this->sock->readline()); if (!isset($ln[0])) { // connection closed or read empty string, throw exception to avoid dead loop and reconnect throw new exception('read response failed'); } $matches = null; $strseqkey = null; $strseq = null; if (preg_match(self::pattern_response_string_sequence, $ln, $matches)) { $strseqkey = $matches[0]; $this->readsequence($ln, $strseq, $matches[1]); } $this->lastrecv = $ln; // 区分处理不同种响应 switch ($ln[0]) { case '*': $r[] = $this->parseline($ln, $strseqkey, $strseq); if (!$tag) { $readmore = false; } break; case $this->tagname: $r[] = $this->parseline($ln); if ($tag) { $readmore = false; } else { } break; case '+': $r[] = $this->parseline($ln); $readmore = false; break; default: $r[] = $ln; break; } } //var_dump($this->lastsend, $this->lastrecv); // 无响应数据 if (empty($r)) { throw new exception('no response'); } $last = $r[count($r) - 1]; if (isset($last[0]) && $last[0] == '+') { // 继续发送请求数据 } else { if ($tag) { if (!isset($last[0]) || strcasecmp($last[0], $tag) != 0) { throw new exception('tag no match'); } } else { if (!isset($last[0]) || strcasecmp($last[0], '*') != 0) { throw new exception('untag no match'); } } if (isset($last[1])) { // 处理响应出错的情况 if (strcasecmp($last[1], 'bad') == 0) { throw new exception(implode(' ', $last)); } elseif (strcasecmp($last[1], 'no') == 0) { throw new exception(implode(' ', $last)); } } //$this->currentcommand = null; } return $r; } private function readsequence(&$ln, &$strseq, $seqlength) { // 对于字符串序列,读取完整内容后再拼接响应 $readlen = 0; $st = microtime(true); // 网络请求多次读取字符串序列内容,直到读好为止 while ($readlen < $seqlength) { $sb = $this->sock->read($seqlength - $readlen); if (isset($sb[0])) { $strseq .= $sb; $readlen = strlen($strseq); } if ((microtime(true) - $st) > $this->timeout) { throw new exception('read sequence timeout'); } } // 读取字符串序列后的剩余命令 $leftln = rtrim($this->sock->readline()); $ln = $ln . $leftln; } private function parseline($ln, $strseqkey = null, $strseq = null) { $r = array(); $p =& $r; $stack = array(); $token = ''; $escape = false; $inquote = false; for ($i = 0, $n = strlen($ln); $i < $n; ++$i) { $ch = $ln[$i]; if ($ch == '"') { // 处理双引号括起的字符串 if (!$inquote) { $inquote = true; } else { $inquote = false; } } elseif ($inquote) { // 对于括起的字符串,处理双引号转义 if ($ch == '\\') { if (!$escape && isset($ln[$i + 1]) && $ln[$i + 1] == '"') { $token .= '"'; $i++; } else { $token .= $ch; $escape = !$escape; } } else { $token .= $ch; } } elseif ($ch == ' ' || $ch == '(' || $ch == ')' || $ch == '[' || $ch == ']') { // 处理子列表 if (isset($token[0])) { // 将字符串序列标识:{length},替换为真实字符串 if ($strseqkey && $token == $strseqkey) { $p[] = $strseq; } else { $p[] = $token; } $token = ''; } if ($ch == '(' || $ch == '[') { $p[] = array(); $stack[] =& $p; $p =& $p[count($p) - 1]; } elseif ($ch == ')' || $ch == ']') { $p =& $stack[count($stack) - 1]; array_pop($stack); } } else { // 处理字符串字面量 $token .= $ch; } } if (isset($token[0])) { // 将字符串序列标识:{length},替换为真实字符串 if ($strseqkey && $token == $strseqkey) { $p[] = $strseq; } else { $p[] = $token; } } return $r; } } // end of php
socket.php
<?php class socket{ const default_read_size = 8192; const crtl = "\r\n"; private $uri = null; private $timeout = null; private $sock = null; private $connected = false; public function __construct($uri, $timeout = null){ $this->uri = $uri; $this->timeout = $this->formattimeout($timeout); } public function connect($timeout = null, $retrytimes = null){ if ($this->connected) { $this->close(); } $conntimeout = $this->formattimeout($timeout, $this->timeout); $retrytimes = intval($retrytimes); if ($retrytimes < 1) { $retrytimes = 1; } for ($i = 0; $i < $retrytimes; ++$i) { $this->sock = stream_socket_client( $this->uri, $errno, $error, $conntimeout); if ($this->sock) { break; } } if (!$this->sock) { } stream_set_timeout($this->sock, $this->timeout); $this->connected = true; } public function read($size){ assert($this->connected); $buf = fread($this->sock, $size); if ($buf === false) { $this->handlereaderror(); } return $buf; } public function readline(){ assert($this->connected); $buf = ''; while (true) { $s = fgets($this->sock, self::default_read_size); if ($s === false) { $this->checkreadtimeout(); break; } $n = strlen($s); if (!$n) { break; } $buf .= $s; if ($s[$n - 1] == "\n") { break; } } return $buf; } public function readall(){ assert($this->connected); $buf = ''; while (true) { $s = fread($this->sock, self::default_read_size); if ($s === false) { $this->handlereaderror(); } if (!isset($s[0])) { break; } $buf .= $s; } return $buf; } public function write($s){ assert($this->connected); $n = strlen($s); $w = 0; while ($w < $n) { $buf = substr($s, $w, self::default_read_size); $r = fwrite($this->sock, $buf); if (!$r) { $this->close(); } $w += $r; } } public function writeline($s){ $this->write($s . self::crtl); } public function close() { if ($this->connected) { fclose($this->sock); $this->connected = false; } } private function formattimeout($timeout, $default = null){ $t = intval($timeout); if ($t <= 0) { if (!$default) { $t = ini_get('default_socket_timeout'); } else { $t = $default; } } return $t; } private function checkreadtimeout(){ $meta = stream_get_meta_data($this->sock); if (isset($meta['timed_out'])) { $this->close(); } } private function handlereaderror(){ $this->checkreadtimeout(); $this->close(); } }