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'$status$ls = $imap->fetch(array(),1)">array('uid','internaldate','rfc822.size')); foreach($ls as $k=>$i){ $info=array($k),1)">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 = 1const PARAM_SINGLE = 2const PARAM_PAIR = 4const PARAM_LIST = 8const PARAM_STRING = 16const PARAM_NUMBER = 32const PARAM_DATE = 64const PARAM_FLAG = 128const PARAM_SEQUENCE = 256const PARAM_SEARCH = 512const PARAM_BODY = 1024const PARAM_PARTIAL = 2048const PARAM_EXCLUSIVE = 4096private static $statusKeywords = ( 'MESSAGES' => self::PARAM_NO,'RECENT' => self::PARAM_NO,'UIDNEXT' => self::PARAM_NO,'UIDVALIDITY' => self::PARAM_NO,'UNSEEN' => self::PARAM_NO,1)"> ); $searchKeywords = ( 'ALL' => self::PARAM_NO,'ANSWERED' => self::PARAM_NO,'BCC' => 18,1)">// self::PARAM_SINGLE | self::PARAM_STRING, 'BEFORE' => 66,1)"> self::PARAM_SINGLE | self::PARAM_DATE, 'BODY' => 18, 'CC' => 18, 'DELETED' => self::PARAM_NO,'DRAFT' => self::PARAM_NO,'FLAGGED' => self::PARAM_NO,'FROM' => 18, 'HEADER' => 20,1)"> self::PARAM_PAIR | self::PARAM_STRING, 'KEYWORD' => 130,1)"> self::PARAM_SINGLE | self::PARAM_FLAG, 'LARGER' => 34,1)"> self::PARAM_SINGLE | self::PARAM_NUMBER, 'NEW' => self::PARAM_NO,'NOT' => 514,1)"> self::PARAM_SINGLE | self::PARAM_SEARCH, 'OLD' => self::PARAM_NO,'ON' => 66, 'OR' => 516,1)"> self::PARAM_PAIR | self::PARAM_SEARCH, 'RECENT' => self::PARAM_NO,'SEEN' => self::PARAM_NO,'SENTBEFORE' => 66, 'SENTON' => 66, 'SENTSINCE' => 66, 'SINCE' => 66, 'SMALLER' => 34, 'SUBJECT' => 18, 'TEXT' => 18, 'TO' => 18, 'UID' => 258,1)"> self::PARAM_SINGLE | self::PARAM_SEQUENCE, 'UNANSWERED' => self::PARAM_NO,'UNDELETED' => self::PARAM_NO,'UNDRAFT' => self::PARAM_NO,'UNFLAGGED' => self::PARAM_NO,'UNKEYWORD' => 130, 'UNSEEN' => self::PARAM_NO,1)">$fetchKeywords = ( 'ALL' => 4097,1)"> self::PARAM_NO | self::PARAM_EXCLUSIVE, 'FAST' => 4097, 'FULL' => 4097, 'BODY' => 3075,1)"> self::PARAM_NO | self::PARAM_SINGLE | self::PARAM_BODY | self::PARAM_PARTIAL, 'BODY.PEEK' => 3074,1)"> 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,1)">private $sock = null$timeout = 120$ts = 0$tagName = 'A'$tagId = 0$capabilities = (); $folders = $currentFolder = $currentCommand = $lastSend = ''$lastRecv = ''public function __construct($uri,$timeout = null,1)">$connTimeout = ) { $this->sock = new Socket($timeout); $t = intval($timeout); // if ($t > 0) { // $this->timeout = $t; // } $this->connect($connTimeout); } function __destruct() { } function connect() { $this->sock->connect(); $this->getResponse(); } capability() { $res = $this->request('capability'); if (isset($res[0][0]) && $res[0][0] == '*' && $res[0][1]) && strcasecmp($res[0][1],'capability') == 0) { for ($i = 2,1)">$n = count($res[0]); $i < $n; ++) { $this->capabilities[strtoupper($res[0][$i])] = true; } } } function id($data) { $this->capabilities['ID'])) { $this->request('id',1)">)); } } function login($username,1)">$passwordtry { $this->request('login',1)">)); } catch (Exception $exthrow new Exception($ex->getMessage(),1)">$ex->getCode()); } } logout() { $this->request('logout'); } function getList($reference = '',1)">$wildcard = ''$this->request('list',1)">$reference,1)">$wildcard)); foreach ($res as &$r$r[0]) && $r[0] == '*' && $r[1]) && $r[1],'list') == 0 && $r[4])) { $this->folders[$r[4]] = ( 'id' => $r[4],'name' => mb_convert_encoding($r[3],'attr' => $r[2],1)"> ); } } return folders; } function status($folder,1)">$args = $this->formatArgsForCommand($data,self::$statusKeywords); $this->request('status',1)">$args)); $status = (); if (!empty($res)) { ) { $r[0] == '*' && $r[3]) && is_array($r[3])) { $i = 0,1)">$r[3]); $n; $i += 2) { $status[$r[3][$i]] = $i + 1]; } } } } ; } function select($folder$this->request('select',1)">)) { $r[0] == '*') { $r[2])) { ])) { $i); ) { $r[2][]; } } elseif (ctype_digit($r[1])) { $r[2]] = ]; } else { $r[1]] = ]; } } } } } $this->currentFolder = ; function search() { $searchKeywords,1)">); $this->request('search',1)">$ls = $r); ) { $ls[] = $r[]; } } } $lsfunction fetch($seq,1)">) { $seqStr = $this->formatSequence($seq$fetchKeywords); $this->request('fetch',1)">$seqStr,1)">)); var_dump($res); $r[0] == '*' && is_numeric($r[1]) && $r[2]) && $a = (); $key = ]; if ((($key,'BODY') == 0 && $args['BODY']) && $args['BODY'])) || ($args['BODY.PEEK']) && $args['BODY.PEEK']))) && ])) { $key = trim($this->formatRequestArray($key => $i + 1]),1)">$placeHolder,0),'()'); $i++; } { ]; } $a[$key] = ]; } $a)) { $ls[; } } } nextTag() { $this->tagId++return sprintf('%s%d',1)">$this->tagName,1)">tagId); } function request($cmd,1)">$args = ()) { $this->currentCommand = strtoupper($cmd$tag = nextTag(); $req = $tag . ' ' . currentCommand; 格式化参数列表 $strSeqList = )) { $argStr = $this->formatRequestArray($args,1)">$strSeqList); } { $this->formatRequestString(); } $argStr = $this->makeRequest($args,$strSeqList); $subReqs = $argStr[0])) { $req .= ' ' . $argStr; 如果参数中包括需要序列化的数据,根据序列化标识{length}将命令拆分成多条 $strSeqList) && preg_match_all(self::PATTERN_REQUEST_STRING_SEQUENCE,1)">$req,1)">$matches,1)"> PREG_OFFSET_CAPTURE)) { $p = 0; $matches[0] $m$e = $m[1] + strlen($m[0]); $subReqs[] = substr($p,1)">$e - $p); $p = $e; } ); 校验序列化标识与需要序列化的参数列表数量是否一致 $subReqs) != $strSeqList) + 1; } } } $subReqs 处理单条命令 $this->sock->writeLine($req); $this->lastSend = ; $this->getResponse($tag); } { 处理多条命令 $this->lastSend = ''$subReqs $id => ); $this->lastSend .= ; ); $res[0][0] == '+'$this->sock->write($strSeqList[$id]); $this->lastSend .= "\r\n" . ]; } { 如果服务器端返回其他相应,则定制后续执行 break; } } } ; } function formatRequestString($s,&$s = $s$needQuote = false$s[0; } elseif ($this->currentCommand == 'ID') { { 参数包含多行时,需要进行序列化 strpos(false || ) { $strSeqList[] = sprintf('{%d}',1)">)); } { 参数包含双引号或空格时,需要将使用双引号括起来 addcslashes(); ; } if ($needQuotesprintf('"%s"',1)">; } } function formatRequestArray($arr,1)">$strSeqList,1)">$level = -1$arr $k => $v) { $isBody = ; $supportPartial = $partialStr = ''$this->currentCommand == 'FETCH' 识别是否body命令,是否可以包含<partial> $kw = $k); isset(self::$fetchKeywords[$kw]) && (self::$kw] & self::PARAM_BODY) > 0$kw] & self::PARAM_PARTIAL) > 0; } } )) { $supportPartial && $v[self::PARTIAL_PARAM_NAME]) && $v[self::PARTIAL_PARAM_NAME])) { 处理包含<partial>的命令 $v[self::PARTIAL_PARAM_NAME] $spos => $mlen$partialStr = sprintf('<%d.%d>',1)">$spos,1)">); } unset(PARTIAL_PARAM_NAME]); } $s = $v,1)">$level + 1); } { ); } )) { 字典方式需要包含键名 $k = $k,1)">$isBody$k . ; } { $k . ' ' . 包含<partial> $supportPartial$s .= $partialStr; } } $a[] = ; } $level < 0implode(' ',1)">elseif (($level % 2) == 0sprintf('(%s)',1)">sprintf('[%s]',1)">)); } } function formatSequence($n == 0return '1:*'; } $n == 1$seq[0])) { strval(]); } $seq $k . ':' . ; } } } implode(',',1)">); } } function formatArgsForCommand(&$fields,1)">$asList = $data ) { 无值参数 $name = $fields[$name 对于排他性属性,直接返回 if (($name] & self::PARAM_EXCLUSIVE) > 0) { $name] & self::PARAM_NO) > 0) { $args[] = ; } } } $k == self::SEQUENCE_PARAM_NAME) { 序列 ); } ])) { $paramType = ]; 格式化参数类型 $paramType & self::PARAM_DATE) > 0$v = date('j-M-Y',1)">); } $paramType & self::PARAM_SEQUENCE) > 0$v = ); } 根据参数定义拼组参数列表 $paramType & self::PARAM_SINGLE) > 0 单值参数 $asList) { ; ; } $args[$name] = ; } } $paramType & self::PARAM_PAIR) > 0 键值对参数 )) { $v $x => $y$pk = $x; $pv = ; ; } } $pv = ''; } $pk$pv$name] = $pk => ); } } $paramType & self::PARAM_LIST) > 0 列表参数 ; $paramType & self::PARAM_NO) > 0 无值参数 ; } } } } function getResponse($tag = $r = (); $readMore = ; while ($readMore$ln = $this->sock->readLine()); $ln[0 connection closed or read empty string,throw exception to avoid dead loop and reconnect Exception('read response Failed'); } $matches = $strSeqKey = $strSeq = preg_match(self::PATTERN_RESPONSE_STRING_SEQUENCE,1)">$ln,1)">$matches)) { $strSeqKey = $matches[0]; $this->readSequence($strSeq,1)">$matches[1]); } $this->lastRecv = $ln; 区分处理不同种响应 switch (]) { case '*': $r[] = $this->parseLine($strSeqKey,1)">$strSeq); if (!; } case $this->tagName: ); { } case '+': ; default: ; } } var_dump($this->lastSend,$this->lastRecv); // 无响应数据 Exception('no response'); } $last = $r[$r) - 1]; $last[0]) && $last[0] == '+' 继续发送请求数据 } $last[0]) || $last[0],1)">$tag) != 0) { Exception('tag no match'); } } Exception('untag no match'); } } $last[1 处理响应出错的情况 $last[1],'bad') == 0) { Exception($last)); } )); } } $this->currentCommand = null; } ; } function readSequence(&$seqLength 对于字符串序列,读取完整内容后再拼接响应 $readLen = 0; $st = microtime( 网络请求多次读取字符串序列内容,直到读好为止 $readLen < $sb = $this->sock->read($seqLength - $readLen); $sb[0$strSeq .= $sb$readLen = ); } if ((true) - $st) > timeout) { Exception('read sequence timeout'); } } 读取字符串序列后的剩余命令 $leftLn = rtrim(readLine()); $ln = $ln . $leftLnfunction parseLine($p =& $stack = $token = ''$escape = $inQuote = $ln); $ch = $ln[]; $ch == '"' 处理双引号括起的字符串 $inQuote; } ; } } 对于括起的字符串,处理双引号转义 $ch == '\\'$escape && $i + 1]) && $i + 1] == '"') { $token .= '"'; $token .= $ch$escape = !$escape; } } $ch == ' ' || $ch == '(' || $ch == ')' || $ch == '[' || $ch == ']' 处理子列表 $token[0 将字符串序列标识:{length},替换为真实字符串 $strSeqKey && $token == $strSeqKey$p[] = $token; } ; } $ch == '[') { $p[] = (); $stack[] =& ; $p[$p) - 1]; } $ch == ')' || ) { $stack[$stack) - 1array_pop($stack); } } { 处理字符串字面量 ; } } ])) { 将字符串序列标识:{length},替换为真实字符串 ; } ; } } ; } } end of PHP
socket.PHP
<? Socket{ const DEFAULT_READ_SIZE = 8192const CRTL = "\r\n"$uri = $connected = ){ $this->uri = $uri$this->timeout = $this->formatTimeout(); } $retryTimes = ){ connected) { close(); } $connTimeout = $timeout,1)">timeout); intval($retryTimes$retryTimes < 1$retryTimes = 1$i = 0; $retryTimes; ++stream_socket_client( $this->uri,1)">$errno,1)">$error,1)">sock) { sock) { } stream_set_timeout($this->sock,1)">$this->connected = ; } function read($sizeassert(connected); $buf = fread($buf === handleReadError(); } $buf readLine(){ connected); $buf = ''while (fgets(DEFAULT_READ_SIZE); $s === checkReadTimeout(); ; } $n$buf .= $s[$n - 1] == "\n"; } } readAll(){ handleReadError(); } ; } ; } function write($w = 0$w < $w,1)">DEFAULT_READ_SIZE); fwrite(close(); } $w += function writeLine($this->write($s . self::CRTL); } close() { connected) { fclose(sock); function formatTimeout($default = $t = $t <= 0$defaultini_get('default_socket_timeout'$t = $t checkReadTimeout(){ $Meta = stream_get_Meta_data(sock); $Meta['timed_out'close(); } } handleReadError(){ checkReadTimeout(); close(); } }