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

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命令,是否可以包含

$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])) {

// 处理包含的命令

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;

}

// 包含

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

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

}

}

 

相关链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。