Pop3.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Mail
  17. * @subpackage Protocol
  18. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * @category Zend
  24. * @package Zend_Mail
  25. * @subpackage Protocol
  26. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  27. * @license http://framework.zend.com/license/new-bsd New BSD License
  28. */
  29. class Zend_Mail_Protocol_Pop3
  30. {
  31. /**
  32. * Default timeout in seconds for initiating session
  33. */
  34. const TIMEOUT_CONNECTION = 30;
  35. /**
  36. * saves if server supports top
  37. * @var null|bool
  38. */
  39. public $hasTop = null;
  40. /**
  41. * socket to pop3
  42. * @var null|resource
  43. */
  44. protected $_socket;
  45. /**
  46. * greeting timestamp for apop
  47. * @var null|string
  48. */
  49. protected $_timestamp;
  50. /**
  51. * Public constructor
  52. *
  53. * @param string $host hostname or IP address of POP3 server, if given connect() is called
  54. * @param int|null $port port of POP3 server, null for default (110 or 995 for ssl)
  55. * @param bool|string $ssl use ssl? 'SSL', 'TLS' or false
  56. * @throws Zend_Mail_Protocol_Exception
  57. */
  58. public function __construct($host = '', $port = null, $ssl = false)
  59. {
  60. if ($host) {
  61. $this->connect($host, $port, $ssl);
  62. }
  63. }
  64. /**
  65. * Public destructor
  66. */
  67. public function __destruct()
  68. {
  69. $this->logout();
  70. }
  71. /**
  72. * Open connection to POP3 server
  73. *
  74. * @param string $host hostname or IP address of POP3 server
  75. * @param int|null $port of POP3 server, default is 110 (995 for ssl)
  76. * @param string|bool $ssl use 'SSL', 'TLS' or false
  77. * @return string welcome message
  78. * @throws Zend_Mail_Protocol_Exception
  79. */
  80. public function connect($host, $port = null, $ssl = false)
  81. {
  82. if ($ssl == 'SSL') {
  83. $host = 'ssl://' . $host;
  84. }
  85. if ($port === null) {
  86. $port = $ssl == 'SSL' ? 995 : 110;
  87. }
  88. $errno = 0;
  89. $errstr = '';
  90. $this->_socket = @fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION);
  91. if (!$this->_socket) {
  92. /**
  93. * @see Zend_Mail_Protocol_Exception
  94. */
  95. #require_once 'Zend/Mail/Protocol/Exception.php';
  96. throw new Zend_Mail_Protocol_Exception('cannot connect to host; error = ' . $errstr .
  97. ' (errno = ' . $errno . ' )');
  98. }
  99. $welcome = $this->readResponse();
  100. strtok($welcome, '<');
  101. $this->_timestamp = strtok('>');
  102. if (!strpos($this->_timestamp, '@')) {
  103. $this->_timestamp = null;
  104. } else {
  105. $this->_timestamp = '<' . $this->_timestamp . '>';
  106. }
  107. if ($ssl === 'TLS') {
  108. $this->request('STLS');
  109. // TODO: Add STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT in the future when it is supported by PHP
  110. $result = stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
  111. if (!$result) {
  112. /**
  113. * @see Zend_Mail_Protocol_Exception
  114. */
  115. #require_once 'Zend/Mail/Protocol/Exception.php';
  116. throw new Zend_Mail_Protocol_Exception('cannot enable TLS');
  117. }
  118. }
  119. return $welcome;
  120. }
  121. /**
  122. * Send a request
  123. *
  124. * @param string $request your request without newline
  125. * @return null
  126. * @throws Zend_Mail_Protocol_Exception
  127. */
  128. public function sendRequest($request)
  129. {
  130. $result = @fputs($this->_socket, $request . "\r\n");
  131. if (!$result) {
  132. /**
  133. * @see Zend_Mail_Protocol_Exception
  134. */
  135. #require_once 'Zend/Mail/Protocol/Exception.php';
  136. throw new Zend_Mail_Protocol_Exception('send failed - connection closed?');
  137. }
  138. }
  139. /**
  140. * read a response
  141. *
  142. * @param boolean $multiline response has multiple lines and should be read until "<nl>.<nl>"
  143. * @return string response
  144. * @throws Zend_Mail_Protocol_Exception
  145. */
  146. public function readResponse($multiline = false)
  147. {
  148. $result = @fgets($this->_socket);
  149. if (!is_string($result)) {
  150. /**
  151. * @see Zend_Mail_Protocol_Exception
  152. */
  153. #require_once 'Zend/Mail/Protocol/Exception.php';
  154. throw new Zend_Mail_Protocol_Exception('read failed - connection closed?');
  155. }
  156. $result = trim($result);
  157. if (strpos($result, ' ')) {
  158. list($status, $message) = explode(' ', $result, 2);
  159. } else {
  160. $status = $result;
  161. $message = '';
  162. }
  163. if ($status != '+OK') {
  164. /**
  165. * @see Zend_Mail_Protocol_Exception
  166. */
  167. #require_once 'Zend/Mail/Protocol/Exception.php';
  168. throw new Zend_Mail_Protocol_Exception('last request failed');
  169. }
  170. if ($multiline) {
  171. $message = '';
  172. $line = fgets($this->_socket);
  173. while ($line && rtrim($line, "\r\n") != '.') {
  174. if ($line[0] == '.') {
  175. $line = substr($line, 1);
  176. }
  177. $message .= $line;
  178. $line = fgets($this->_socket);
  179. };
  180. }
  181. return $message;
  182. }
  183. /**
  184. * Send request and get resposne
  185. *
  186. * @see sendRequest(), readResponse()
  187. *
  188. * @param string $request request
  189. * @param bool $multiline multiline response?
  190. * @return string result from readResponse()
  191. * @throws Zend_Mail_Protocol_Exception
  192. */
  193. public function request($request, $multiline = false)
  194. {
  195. $this->sendRequest($request);
  196. return $this->readResponse($multiline);
  197. }
  198. /**
  199. * End communication with POP3 server (also closes socket)
  200. *
  201. * @return null
  202. */
  203. public function logout()
  204. {
  205. if (!$this->_socket) {
  206. return;
  207. }
  208. try {
  209. $this->request('QUIT');
  210. } catch (Zend_Mail_Protocol_Exception $e) {
  211. // ignore error - we're closing the socket anyway
  212. }
  213. fclose($this->_socket);
  214. $this->_socket = null;
  215. }
  216. /**
  217. * Get capabilities from POP3 server
  218. *
  219. * @return array list of capabilities
  220. * @throws Zend_Mail_Protocol_Exception
  221. */
  222. public function capa()
  223. {
  224. $result = $this->request('CAPA', true);
  225. return explode("\n", $result);
  226. }
  227. /**
  228. * Login to POP3 server. Can use APOP
  229. *
  230. * @param string $user username
  231. * @param string $password password
  232. * @param bool $try_apop should APOP be tried?
  233. * @return void
  234. * @throws Zend_Mail_Protocol_Exception
  235. */
  236. public function login($user, $password, $tryApop = true)
  237. {
  238. if ($tryApop && $this->_timestamp) {
  239. try {
  240. $this->request("APOP $user " . md5($this->_timestamp . $password));
  241. return;
  242. } catch (Zend_Mail_Protocol_Exception $e) {
  243. // ignore
  244. }
  245. }
  246. $result = $this->request("USER $user");
  247. $result = $this->request("PASS $password");
  248. }
  249. /**
  250. * Make STAT call for message count and size sum
  251. *
  252. * @param int $messages out parameter with count of messages
  253. * @param int $octets out parameter with size in octects of messages
  254. * @return void
  255. * @throws Zend_Mail_Protocol_Exception
  256. */
  257. public function status(&$messages, &$octets)
  258. {
  259. $messages = 0;
  260. $octets = 0;
  261. $result = $this->request('STAT');
  262. list($messages, $octets) = explode(' ', $result);
  263. }
  264. /**
  265. * Make LIST call for size of message(s)
  266. *
  267. * @param int|null $msgno number of message, null for all
  268. * @return int|array size of given message or list with array(num => size)
  269. * @throws Zend_Mail_Protocol_Exception
  270. */
  271. public function getList($msgno = null)
  272. {
  273. if ($msgno !== null) {
  274. $result = $this->request("LIST $msgno");
  275. list(, $result) = explode(' ', $result);
  276. return (int)$result;
  277. }
  278. $result = $this->request('LIST', true);
  279. $messages = array();
  280. $line = strtok($result, "\n");
  281. while ($line) {
  282. list($no, $size) = explode(' ', trim($line));
  283. $messages[(int)$no] = (int)$size;
  284. $line = strtok("\n");
  285. }
  286. return $messages;
  287. }
  288. /**
  289. * Make UIDL call for getting a uniqueid
  290. *
  291. * @param int|null $msgno number of message, null for all
  292. * @return string|array uniqueid of message or list with array(num => uniqueid)
  293. * @throws Zend_Mail_Protocol_Exception
  294. */
  295. public function uniqueid($msgno = null)
  296. {
  297. if ($msgno !== null) {
  298. $result = $this->request("UIDL $msgno");
  299. list(, $result) = explode(' ', $result);
  300. return $result;
  301. }
  302. $result = $this->request('UIDL', true);
  303. $result = explode("\n", $result);
  304. $messages = array();
  305. foreach ($result as $line) {
  306. if (!$line) {
  307. continue;
  308. }
  309. list($no, $id) = explode(' ', trim($line), 2);
  310. $messages[(int)$no] = $id;
  311. }
  312. return $messages;
  313. }
  314. /**
  315. * Make TOP call for getting headers and maybe some body lines
  316. * This method also sets hasTop - before it it's not known if top is supported
  317. *
  318. * The fallback makes normale RETR call, which retrieves the whole message. Additional
  319. * lines are not removed.
  320. *
  321. * @param int $msgno number of message
  322. * @param int $lines number of wanted body lines (empty line is inserted after header lines)
  323. * @param bool $fallback fallback with full retrieve if top is not supported
  324. * @return string message headers with wanted body lines
  325. * @throws Zend_Mail_Protocol_Exception
  326. */
  327. public function top($msgno, $lines = 0, $fallback = false)
  328. {
  329. if ($this->hasTop === false) {
  330. if ($fallback) {
  331. return $this->retrieve($msgno);
  332. } else {
  333. /**
  334. * @see Zend_Mail_Protocol_Exception
  335. */
  336. #require_once 'Zend/Mail/Protocol/Exception.php';
  337. throw new Zend_Mail_Protocol_Exception('top not supported and no fallback wanted');
  338. }
  339. }
  340. $this->hasTop = true;
  341. $lines = (!$lines || $lines < 1) ? 0 : (int)$lines;
  342. try {
  343. $result = $this->request("TOP $msgno $lines", true);
  344. } catch (Zend_Mail_Protocol_Exception $e) {
  345. $this->hasTop = false;
  346. if ($fallback) {
  347. $result = $this->retrieve($msgno);
  348. } else {
  349. throw $e;
  350. }
  351. }
  352. return $result;
  353. }
  354. /**
  355. * Make a RETR call for retrieving a full message with headers and body
  356. *
  357. * @deprecated since 1.1.0; this method has a typo - please use retrieve()
  358. * @param int $msgno message number
  359. * @return string message
  360. * @throws Zend_Mail_Protocol_Exception
  361. */
  362. public function retrive($msgno)
  363. {
  364. return $this->retrieve($msgno);
  365. }
  366. /**
  367. * Make a RETR call for retrieving a full message with headers and body
  368. *
  369. * @param int $msgno message number
  370. * @return string message
  371. * @throws Zend_Mail_Protocol_Exception
  372. */
  373. public function retrieve($msgno)
  374. {
  375. $result = $this->request("RETR $msgno", true);
  376. return $result;
  377. }
  378. /**
  379. * Make a NOOP call, maybe needed for keeping the server happy
  380. *
  381. * @return null
  382. * @throws Zend_Mail_Protocol_Exception
  383. */
  384. public function noop()
  385. {
  386. $this->request('NOOP');
  387. }
  388. /**
  389. * Make a DELE count to remove a message
  390. *
  391. * @return null
  392. * @throws Zend_Mail_Protocol_Exception
  393. */
  394. public function delete($msgno)
  395. {
  396. $this->request("DELE $msgno");
  397. }
  398. /**
  399. * Make RSET call, which rollbacks delete requests
  400. *
  401. * @return null
  402. * @throws Zend_Mail_Protocol_Exception
  403. */
  404. public function undelete()
  405. {
  406. $this->request('RSET');
  407. }
  408. }