欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

[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();
    }
}