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