Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
imap.php
1<?php
2
3namespace Bitrix\Mail;
4
10
11Loc::loadMessages(__FILE__);
12
13class Imap
14{
15 const LOG_LEVEL_WRITE = 1;
16 const LOG_LEVEL_READ = 2;
17
18 const ERR_CONNECT = 101;
19 const ERR_REJECTED = 102;
20 const ERR_COMMUNICATE = 103;
21 const ERR_EMPTY_RESPONSE = 104;
22 const ERR_BAD_SERVER = 105;
23
24 const ERR_STARTTLS = 201;
26 const ERR_CAPABILITY = 203;
27 const ERR_AUTH = 204;
28 const ERR_AUTH_MECH = 205;
29 const ERR_AUTH_OAUTH = 206;
30 const ERR_LIST = 207;
31 const ERR_SELECT = 208;
32 const ERR_SEARCH = 209;
33 const ERR_FETCH = 210;
34 const ERR_APPEND = 211;
35 const ERR_STORE = 212;
36
37 protected $stream, $errors;
39 protected $logLevel = 0, $logPath;
40
41 protected $options = array();
42
43 protected static $atomRegex = '[^\x00-\x20\x22\x25\x28-\x2a\x5c\x5d\x7b\x7f-\xff]+';
44 protected static $qcharRegex = '[^\x00\x0a\x0d\x22\x5c\x80-\xff]|\x5c[\x5c\x22]';
45 protected static $qcharExtRegex = '[^\x00\x0a\x0d\x22\x5c]|\x5c[\x5c\x22]'; // #119098
46 protected static $astringRegex = '[^\x00-\x20\x22\x25\x28-\x2a\x5c\x7b\x7f-\xff]+';
47
48 public function __construct($host, $port, $tls, $strict, $login, $password, $encoding = null)
49 {
50 $this->reset();
51
52 $strict = (bool) $strict;
53
54 $this->options = array(
55 'host' => $host,
56 'port' => $port,
57 'tls' => $tls,
58 'socket' => sprintf('%s://%s:%s', ($tls ? 'ssl' : 'tcp'), $host, $port),
59 'timeout' => \COption::getOptionInt('mail', 'connect_timeout', B_MAIL_TIMEOUT),
60 'context' => stream_context_create(array(
61 'ssl' => array(
62 'verify_peer' => $strict,
63 'verify_peer_name' => $strict,
64 'crypto_method' => STREAM_CRYPTO_METHOD_ANY_CLIENT,
65 )
66 )),
67 'login' => $login,
68 'password' => $password,
69 'encoding' => $encoding ?: LANG_CHARSET,
70 );
71
72 $logParams = Main\Config\Configuration::getValue('imap');
73 if(isset($logParams["log_level"]) && $logParams["log_level"] > 0)
74 {
75 $this->logLevel = $logParams["log_level"];
76 if(isset($logParams["log_path"]) && $logParams["log_path"] <> '')
77 {
78 $this->logPath = $logParams["log_path"];
79 }
80 }
81 }
82
83 public function __destruct()
84 {
85 $this->disconnect();
86 }
87
88 protected function disconnect()
89 {
90 if (!is_null($this->stream))
91 {
92 @fclose($this->stream);
93 unset($this->stream);
94 }
95 }
96
97 protected function reset()
98 {
99 $this->disconnect();
100
101 unset($this->sessState);
102 unset($this->sessCapability);
103 $this->sessCounter = 0;
104 $this->sessUntagged = array();
105 $this->sessMailbox = array(
106 'name' => null,
107 'exists' => null,
108 'uidvalidity' => null,
109 'permanentflags' => null,
110 );
111 $this->errors = new Main\ErrorCollection();
112 }
113
114 public function getState()
115 {
116 return $this->sessState;
117 }
118
119 public function connect(&$error)
120 {
121 $error = null;
122
123 if (!empty($this->sessState))
124 return true;
125
126 $resource = @stream_socket_client(
127 $this->options['socket'], $errno, $errstr, $this->options['timeout'],
128 STREAM_CLIENT_CONNECT, $this->options['context']
129 );
130
131 if ($resource === false)
132 {
133 $error = $this->errorMessage(Imap::ERR_CONNECT, $errno ?: null);
134 return false;
135 }
136
137 $this->stream = $resource;
138
139 if ($this->options['timeout'] > 0)
140 stream_set_timeout($this->stream, $this->options['timeout']);
141
142 $prompt = $this->readLine();
143
144 if ($prompt !== false && preg_match('/^\* (OK|PREAUTH)/i', $prompt, $matches))
145 {
146 if ($matches[1] == 'OK')
147 $this->sessState = 'no_auth';
148 elseif ($matches[1] == 'PREAUTH')
149 $this->sessState = 'auth';
150 }
151 else
152 {
153 if ($prompt === false)
154 $error = Imap::ERR_EMPTY_RESPONSE;
155 elseif (preg_match('/^\* BYE/i', $prompt))
156 $error = Imap::ERR_REJECTED;
157 else
158 $error = Imap::ERR_BAD_SERVER;
159
160 $error = $this->errorMessage(array(Imap::ERR_CONNECT, $error));
161
162 return false;
163 }
164
165 if (!$this->capability($error))
166 return false;
167
168 if (!$this->options['tls'] && preg_match('/ \x20 STARTTLS ( \x20 | \r\n ) /ix', $this->sessCapability))
169 {
170 if (!$this->starttls($error))
171 return false;
172 }
173
174 return true;
175 }
176
177 protected function starttls(&$error)
178 {
179 $error = null;
180
181 if (!$this->sessState)
182 {
183 $error = $this->errorMessage(Imap::ERR_STARTTLS);
184 return false;
185 }
186
187 $response = $this->executeCommand('STARTTLS', $error);
188
189 if ($error)
190 {
191 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
192 $error = $this->errorMessage(array(Imap::ERR_STARTTLS, $error), $response);
193
194 return false;
195 }
196
197 if (stream_socket_enable_crypto($this->stream, true, STREAM_CRYPTO_METHOD_ANY_CLIENT))
198 {
199 if (!$this->capability($error))
200 {
201 return false;
202 }
203 }
204 else
205 {
206 $this->reset();
207
208 $error = $this->errorMessage(Imap::ERR_STARTTLS);
209 return false;
210 }
211
212 return true;
213 }
214
215 protected function capability(&$error)
216 {
217 $error = null;
218
219 if (!$this->sessState)
220 {
221 $error = $this->errorMessage(Imap::ERR_CAPABILITY);
222 return false;
223 }
224
225 $response = $this->executeCommand('CAPABILITY', $error);
226
227 if ($error)
228 {
229 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
230 $error = $this->errorMessage(array(Imap::ERR_CAPABILITY, $error), $response);
231
232 return false;
233 }
234
235 $regex = '/^ \* \x20 CAPABILITY /ix';
236 foreach ($this->getUntagged($regex, true) as $item)
237 $this->sessCapability = $item[0];
238
239 return true;
240 }
241
242 public function authenticate(&$error)
243 {
244 $error = null;
245
246 if (!$this->connect($error))
247 return false;
248 if (in_array($this->sessState, array('auth', 'select')))
249 return true;
250
251 $mech = false;
252 $token = null;
253
254 if (preg_match('/ \x20 AUTH=XOAUTH2 ( \x20 | \r\n ) /ix', $this->sessCapability))
255 {
256 $token = Helper\OAuth::getTokenByMeta($this->options['password']);
257
258 if (!empty($token))
259 {
260 $mech = 'oauth';
261 }
262 else if (false === $token)
263 {
264 $error = $this->errorMessage(array(Imap::ERR_AUTH, Imap::ERR_AUTH_OAUTH));
265 return false;
266 }
267 }
268
269 if ($mech == false)
270 {
271 if (preg_match('/ \x20 AUTH=PLAIN ( \x20 | \r\n ) /ix', $this->sessCapability))
272 $mech = 'plain';
273 elseif (!preg_match('/ \x20 LOGINDISABLED ( \x20 | \r\n ) /ix', $this->sessCapability))
274 $mech = 'login';
275 }
276
277 if (!$mech)
278 {
279 $error = $this->errorMessage(array(Imap::ERR_AUTH, Imap::ERR_AUTH_MECH));
280 return false;
281 }
282
283 if ($mech == 'oauth')
284 {
285 $response = $this->executeCommand('AUTHENTICATE XOAUTH2', $error);
286
287 if (mb_strpos($response, '+') !== 0)
288 {
289 $error = $error == Imap::ERR_COMMAND_REJECTED ? Imap::ERR_AUTH_MECH : $error;
290 $error = $this->errorMessage(array(Imap::ERR_AUTH, $error), $response);
291
292 return false;
293 }
294
295 $response = $this->exchange(base64_encode(sprintf(
296 "user=%s\x01auth=Bearer %s\x01\x01", $this->options['login'], $token
297 )), $error);
298
299 if (mb_strpos($response, '+') === 0)
300 $response = $this->exchange("\r\n", $error);
301 }
302 elseif ($mech == 'plain')
303 {
304 $response = $this->executeCommand('AUTHENTICATE PLAIN', $error);
305
306 if (mb_strpos($response, '+') !== 0)
307 {
308 $error = $error == Imap::ERR_COMMAND_REJECTED ? Imap::ERR_AUTH_MECH : $error;
309 $error = $this->errorMessage(array(Imap::ERR_AUTH, $error), $response);
310
311 return false;
312 }
313
314 $response = $this->exchange(base64_encode(sprintf(
315 "\x00%s\x00%s",
316 Encoding::convertEncoding($this->options['login'], $this->options['encoding'], 'UTF-8'),
317 Encoding::convertEncoding($this->options['password'], $this->options['encoding'], 'UTF-8')
318 )), $error);
319 }
320 else // if ($mech == 'login')
321 {
322 $response = $this->executeCommand(sprintf(
323 'LOGIN %s %s',
324 static::prepareString($this->options['login']),
325 static::prepareString($this->options['password'])
326 ), $error);
327 }
328
329 if ($error)
330 {
331 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
332 $error = $this->errorMessage(array(Imap::ERR_AUTH, $error), $response);
333
334 return false;
335 }
336
337 $this->sessState = 'auth';
338
339 if (!$this->capability($error))
340 return false;
341
342 return true;
343 }
344
345 public function select($mailbox, &$error)
346 {
347 $error = null;
348
349 if (!$this->authenticate($error))
350 return false;
351 if ($this->sessState == 'select' && $mailbox == $this->sessMailbox['name'])
352 return $this->sessMailbox;
353
354 $response = $this->executeCommand(sprintf(
355 'SELECT "%s"', static::escapeQuoted($this->encodeUtf7Imap($mailbox))
356 ), $error);
357
358 if ($error)
359 {
360 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
361 $error = $this->errorMessage(array(Imap::ERR_SELECT, $error), $response);
362
363 return false;
364 }
365
366 $this->sessState = 'select';
367 $this->sessMailbox = array(
368 'name' => $mailbox,
369 'exists' => null,
370 'uidvalidity' => null,
371 );
372
373 $regex = '/^ \* \x20 ( \d+ ) \x20 EXISTS /ix';
374 foreach ($this->getUntagged($regex, true) as $item)
375 $this->sessMailbox['exists'] = $item[1][1];
376
377 $regex = '/^ \* \x20 OK \x20 \[ UIDVALIDITY \x20 ( \d+ ) \] /ix';
378 foreach ($this->getUntagged($regex, true) as $item)
379 $this->sessMailbox['uidvalidity'] = $item[1][1];
380
381 $regex = sprintf(
382 '/^ \* \x20 OK \x20 \[ PERMANENTFLAGS \x20 \‍( ( ( \x5c? %1$s | \x5c \* ) ( \x20 (?2) )* )? \‍) \] /ix',
383 self::$atomRegex
384 );
385 foreach ($this->getUntagged($regex, true) as $item)
386 {
387 $this->sessMailbox['permanentflags'] = explode("\x20", $item[1][1]);
388 }
389
390 if (!$this->capability($error))
391 return false;
392
393 return $this->sessMailbox;
394 }
395
396 public function examine($mailbox, &$error)
397 {
398 $error = null;
399
400 if (!$this->authenticate($error))
401 return false;
402
403 $response = $this->executeCommand(sprintf(
404 'EXAMINE "%s"', static::escapeQuoted($this->encodeUtf7Imap($mailbox))
405 ), $error);
406
407 if ($error)
408 {
409 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
410 $error = $this->errorMessage(array(Imap::ERR_SELECT, $error), $response);
411
412 return false;
413 }
414
415 $result = array();
416
417 $regex = '/^ \* \x20 ( \d+ ) \x20 EXISTS /ix';
418 foreach ($this->getUntagged($regex, true) as $item)
419 $result['exists'] = $item[1][1];
420
421 $regex = '/^ \* \x20 OK \x20 \[ UIDVALIDITY \x20 ( \d+ ) \] /ix';
422 foreach ($this->getUntagged($regex, true) as $item)
423 $result['uidvalidity'] = $item[1][1];
424
425 $regex = sprintf(
426 '/^ \* \x20 OK \x20 \[ PERMANENTFLAGS \x20 \‍( ( ( \x5c? %1$s | \x5c \* ) ( \x20 (?2) )* )? \‍) \] /ix',
427 self::$atomRegex
428 );
429 foreach ($this->getUntagged($regex, true) as $item)
430 {
431 $result['permanentflags'] = explode("\x20", $item[1][1]);
432 }
433
434 return $result;
435 }
436
443 public function singin(&$error)
444 {
445 $error = null;
446
447 return $this->authenticate($error);
448 }
449
459 public function fetch($uid, $mailbox, $range, $select, &$error, $outputFormat = 'smart')
460 {
461 $error = null;
462
463 if (!preg_match('/(([1-9]\d*|\*)(:(?2))?)(,(?1))*/', $range))
464 {
465 return false;
466 }
467
468 if (empty($select))
469 {
470 $select = '(FLAGS)';
471 }
472 else if (is_array($select))
473 {
474 $select = sprintf('(%s)', join(' ', $select));
475 }
476
477 if (!$this->select($mailbox, $error))
478 {
479 return false;
480 }
481
482 $list = array();
483
484 if ($this->sessMailbox['exists'] > 0)
485 {
486 $fetchUntaggedRegex = '/^ \* \x20 ( \d+ ) \x20 FETCH \x20 \‍( ( .+ ) \‍) \r\n $/isx';
487 $this->getUntagged($fetchUntaggedRegex, true);
488
489 $response = $this->executeCommand(
490 sprintf('%sFETCH %s %s', $uid ? 'UID ' : '', $range, $select),
491 $error
492 );
493
494 if ($error)
495 {
496 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
497 $error = $this->errorMessage(array(Imap::ERR_FETCH, $error), $response);
498
499 return false;
500 }
501
502 $shiftName = function (&$item)
503 {
504 $result = false;
505
506 // #120949
507 $regex = sprintf(
508 '/^
509 (
510 [a-z0-9]+ (?: \. [a-z0-9]+ )*
511 (?: \[ (?: [a-z0-9]+ (?: \. [a-z0-9]* )* (?: \x20 %s )* )? \] )?
512 (?: < \d+ > )?
513 )
514 \x20
515 /ix',
516 self::$astringRegex
517 );
518
519 if (preg_match($regex, $item, $matches))
520 {
521 $result = $matches[1];
522
523 $item = BinaryString::getSubstring($item, BinaryString::getLength($matches[0]));
524 }
525
526 return $result;
527 };
528
529 $shiftValue = function (&$item) use (&$shiftValue)
530 {
531 $result = false;
532
533 $tail = ' (?= [\x20)] | $ ) \x20? ';
534
535 if (BinaryString::getSubstring($item, 0, 1) === '(')
536 {
537 $item = BinaryString::getSubstring($item, 1);
538
539 $result = array();
540
541 while (BinaryString::getLength($item) > 0 && BinaryString::getSubstring($item, 0, 1) !== ')')
542 {
543 $subresult = $shiftValue($item);
544
545 if (false !== $subresult)
546 {
547 $result[] = $subresult;
548 }
549 else
550 {
551 return false;
552 }
553 }
554
555 if (preg_match('/^ \‍) (?= [\x20()] | $ ) \x20? /ix', $item, $matches))
556 {
557 $item = BinaryString::getSubstring($item, BinaryString::getLength($matches[0]));
558 }
559 else
560 {
561 return false;
562 }
563 }
564 else if (preg_match('/^ { ( \d+ ) } \r\n /ix', $item, $matches))
565 {
566 $item = BinaryString::getSubstring($item, BinaryString::getLength($matches[0]));
567
568 if (BinaryString::getLength($item) >= $matches[1])
569 {
570 $result = BinaryString::getSubstring($item, 0, $matches[1]);
571
572 $item = BinaryString::getSubstring($item, $matches[1]);
573
574 if (preg_match(sprintf('/^ %s /ix', $tail), $item, $matches))
575 {
576 $item = BinaryString::getSubstring($item, BinaryString::getLength($matches[0]));
577 }
578 else
579 {
580 return false;
581 }
582 }
583 }
584 else if (preg_match(sprintf('/^ NIL %s /ix', $tail), $item, $matches))
585 {
586 $result = null;
587
588 $item = BinaryString::getSubstring($item, BinaryString::getLength($matches[0]));
589 }
590 else if (preg_match(sprintf('/^ " ( (?: %s )* ) " %s /ix', self::$qcharExtRegex, $tail), $item, $matches))
591 {
592 $result = self::unescapeQuoted($matches[1]);
593
594 $item = BinaryString::getSubstring($item, BinaryString::getLength($matches[0]));
595 }
596 else if (preg_match(sprintf('/^ ( \x5c? %s ) %s /ix', self::$astringRegex, $tail), $item, $matches))
597 {
598 $result = $matches[1];
599
600 $item = BinaryString::getSubstring($item, BinaryString::getLength($matches[0]));
601 }
602 else if (preg_match(sprintf('/^ %s /ix', $tail), $item, $matches))
603 {
604 $result = '';
605
606 $item = BinaryString::getSubstring($item, BinaryString::getLength($matches[0]));
607 }
608
609 return $result;
610 };
611
612 $bodystructure = function (&$value) use (&$bodystructure)
613 {
614 if (!is_array($value) || !is_array($value[0]))
615 {
616 return $value;
617 }
618
619 $value[0] = $bodystructure($value[0]);
620 $value[0] = array($value[0]);
621
622 while (array_key_exists(1, $value) && is_array($value[1]))
623 {
624 $value[0][] = $bodystructure($value[1]);
625
626 array_splice($value, 1, 1);
627 }
628
629 while (count($value[0]) == 1 && count($value[0][0]) == 1)
630 {
631 $value[0] = $value[0][0];
632 }
633
634 return $value;
635 };
636
637 foreach ($this->getUntagged($fetchUntaggedRegex, true) as $item)
638 {
639 $data = array(
640 'id' => $item[1][1],
641 );
642
643 while (BinaryString::getLength($item[1][2]) > 0)
644 {
645 if (($name = $shiftName($item[1][2])) !== false)
646 {
647 if (($value = $shiftValue($item[1][2])) !== false)
648 {
649 if (in_array(mb_strtoupper($name), array('BODY', 'BODYSTRUCTURE')))
650 {
651 $value = $bodystructure($value);
652 }
653
654 $data[$name] = $value;
655
656 continue;
657 }
658 }
659
660 break;
661 }
662
663 $list[$data['id']] = $data;
664 }
665 }
666
667 ksort($list);
668
669 // todo remove the different format of the data output
670 if ($outputFormat === 'smart' && !preg_match('/[:,]/', $range))
671 {
672 $list = reset($list);
673 }
674
675 return $list;
676 }
677
678 public function getUIDsForSpecificDay($dirPath, $internalDate)
679 {
680 $error = [];
681
682 if (!$this->select($dirPath, $error))
683 {
684 return false;
685 }
686
687 //since some mail services (example mail.ru ) do not support the 'on' search criterion
688 $command = 'UID SEARCH SINCE '.date("j-M-Y", strtotime($internalDate)).' BEFORE '.date('j-M-Y', strtotime($internalDate.' +1 day'));
689
690 $response = $this->executeCommand($command, $error);
691
692 if ($error)
693 {
694 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
695 $error = $this->errorMessage(array(Imap::ERR_SEARCH, $error), $response);
696
697 return false;
698 }
699
700 $UIDs = [];
701 $regex = '/^ \* \x20 SEARCH \x20 ( .+ ) \r\n $ /ix';
702 foreach ($this->getUntagged($regex, true) as $item)
703 {
704 preg_match_all('/\d+/', $item[1][1],$UIDs);
705 }
706
707 if(count($UIDs) === 0 )
708 {
709 return [];
710 }
711
712 return $UIDs[0];
713 }
714
724 public function getUnseen($dirPath, &$error, $startInternalDate = null)
725 {
726 $error = null;
727
728 if (!$this->select($dirPath, $error))
729 {
730 return false;
731 }
732
733 $unseen = 0;
734
735 if (!($this->sessMailbox['exists'] > 0))
736 {
737 return $unseen;
738 }
739
740 $command = 'SEARCH UNSEEN';
741
742 if(!is_null($startInternalDate))
743 {
744 $command .= (' SINCE '.$startInternalDate->format('j-M-Y'));
745 }
746 $response = $this->executeCommand($command, $error);
747
748 if ($error)
749 {
750 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
751 $error = $this->errorMessage(array(Imap::ERR_SEARCH, $error), $response);
752
753 return false;
754 }
755
756 $regex = '/^ \* \x20 SEARCH \x20 ( .+ ) \r\n $ /ix';
757 foreach ($this->getUntagged($regex, true) as $item)
758 {
759 $unseen = preg_match_all('/\d+/', $item[1][1]);
760 }
761
762 return $unseen;
763 }
764
765 public function getNew($mailbox, $uidMin, $uidMax, &$error)
766 {
767 $error = null;
768
769 if (!($uidMin <= $uidMax))
770 {
771 return false;
772 }
773
774 if (!$this->select($mailbox, $error))
775 {
776 return false;
777 }
778
779 $new = 0;
780
781 if (!($this->sessMailbox['exists'] > 0))
782 {
783 return $new;
784 }
785
786 if ($uidMax < 1)
787 {
788 return $this->sessMailbox['exists'];
789 }
790
791 $range = $this->getUidRange($mailbox, $error);
792
793 if (empty($range))
794 {
795 return false;
796 }
797
798 [$min, $max] = $range;
799
800 $searches = array();
801
802 if ($uidMin > 1 && $uidMin > $min)
803 {
804 $searches[] = sprintf('%u:%u', $min, $uidMin - 1);
805 }
806
807 if ($uidMax > 0 && $uidMax < $max)
808 {
809 $searches[] = sprintf('%u:%u', $uidMax + 1, $max);
810 }
811
812 if (!empty($searches))
813 {
814 $response = $this->executeCommand(sprintf('SEARCH UID %s', join(',', $searches)), $error);
815
816 if ($error)
817 {
818 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
819 $error = $this->errorMessage(array(Imap::ERR_SEARCH, $error), $response);
820
821 return false;
822 }
823
824 $regex = '/^ \* \x20 SEARCH \x20 ( .+ ) \r\n $ /ix';
825 foreach ($this->getUntagged($regex, true) as $item)
826 {
827 $new = preg_match_all('/\d+/', $item[1][1]);
828 }
829 }
830
831 return $new;
832 }
833
834 public function getUidRange($mailbox, &$error)
835 {
836 $error = null;
837
838 if (!$this->select($mailbox, $error))
839 {
840 return false;
841 }
842
843 if (!($this->sessMailbox['exists'] > 0))
844 {
845 return false;
846 }
847
848 $range = $this->fetch(false, $mailbox, sprintf('1,%u', $this->sessMailbox['exists']), '(UID)', $error);
849
850 if (empty($range) || empty($range[1]))
851 {
852 return false;
853 }
854
855 return array(
856 $range[1]['UID'],
857 end($range)['UID'],
858 );
859 }
860
861 public function listex($reference, $pattern, &$error)
862 {
863 $error = null;
864
865 if (!$this->authenticate($error))
866 {
867 return false;
868 }
869
870 $response = $this->executeCommand(sprintf(
871 'LIST "%s" "%s"',
872 static::escapeQuoted($this->encodeUtf7Imap($reference)),
873 static::escapeQuoted($this->encodeUtf7Imap($pattern))
874 ), $error);
875
876 if ($error)
877 {
878 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
879 $error = $this->errorMessage(array(Imap::ERR_LIST, $error), $response);
880
881 return false;
882 }
883
884 $list = array();
885
886 $regex = sprintf(
887 '/^ \* \x20 LIST \x20
888 \‍( (?<flags> ( \x5c? %1$s ( \x20 \x5c? %1$s )* )? ) \‍) \x20
889 (?<delim> NIL | " ( %2$s ) " ) \x20
890 (?<name> \{ \d+ \} | " ( %2$s )* " | %3$s ) \r\n
891 (?<ext> .* )
892 /ix',
893 self::$atomRegex, self::$qcharRegex, self::$astringRegex
894 );
895 foreach ($this->getUntagged($regex, true) as $item)
896 {
897 [$item, $matches] = $item;
898
899 $sflags = $matches['flags'];
900 $sdelim = $matches['delim'];
901 $sname = $matches['name'];
902
903 if (preg_match('/^ " ( .+ ) " $/ix', $sdelim, $quoted))
904 {
905 $sdelim = static::unescapeQuoted($quoted[1]);
906 }
907
908 if (preg_match('/^ \{ ( \d+ ) \} $/ix', $sname, $literal))
909 {
910 $sname = substr($matches['ext'], 0, $literal[1]);
911 }
912 else if (preg_match('/^ " ( .* ) " $/ix', $sname, $quoted))
913 {
914 $sname = static::unescapeQuoted($quoted[1]);
915 }
916
917 $sname = $this->decodeUtf7Imap($sname);
918
919 // #79498
920 if (mb_strtoupper($sdelim) != 'NIL')
921 $sname = rtrim($sname, $sdelim);
922
923 $list[] = array(
924 'name' => $sname,
925 'delim' => mb_strtoupper($sdelim) == 'NIL' ? 'NIL' : $sdelim,
926 'flags' => preg_split('/\s+/i', $sflags, -1, PREG_SPLIT_NO_EMPTY),
927 );
928 }
929
930 return $list;
931 }
932
940 public function listMailboxes($pattern, &$error, $flat = false)
941 {
942 $error = null;
943
944 $listGetter = function ($parent = null, $level = 0) use (&$listGetter, &$delimiter, &$error)
945 {
946 $pattern = $parent ? sprintf('%s%s%%', $parent, $delimiter) : '%';
947
948 $list = $this->listex('', $pattern, $error);
949
950 if (false === $list)
951 {
952 return false;
953 }
954
955 foreach ($list as $i => $item)
956 {
957 $item['title'] = $item['name'];
958 $item['level'] = $level;
959
960 if ($parent)
961 {
962 $regex = sprintf(
963 '/^%s%s(.)/',
964 preg_quote($parent, '/'),
965 preg_quote($delimiter, '/')
966 );
967
968 if (!preg_match($regex, $item['name']))
969 {
970 unset($list[$i]);
971 continue;
972 }
973
974 $item['title'] = preg_replace($regex, '\1', $item['name']);
975 }
976
977 if ($item['name'] == $parent)
978 {
979 continue;
980 }
981
982 if ($item['delim'] === null)
983 {
984 continue;
985 }
986
987 $delimiter = $item['delim'];
988
989 if (!preg_grep('/^ \x5c ( Noinferiors | HasNoChildren ) $/ix', $item['flags']))
990 {
991 $children = $listGetter($item['name'], $level + 1);
992
993 if ($children === false)
994 {
995 return false;
996 }
997
998 if (!empty($children))
999 {
1000 $item['children'] = $children;
1001 }
1002 }
1003
1004 $list[$i] = $item;
1005 }
1006
1007 return array_values($list);
1008 };
1009
1010 $list = $listGetter();
1011
1012 if (false === $list)
1013 {
1014 return false;
1015 }
1016
1017 $regex = sprintf(
1018 '/^%s$/i',
1019 preg_replace(
1020 array('/ ( \x5c \* )+ /x', '/ ( \% )+ /x'),
1021 array('.*', $delimiter ? sprintf('[^\x%s]*', bin2hex($delimiter)) : '.*'),
1022 preg_quote($pattern, '/')
1023 )
1024 );
1025
1026 $listFilter = function ($list) use (&$listFilter, $regex)
1027 {
1028 foreach ($list as $i => $item)
1029 {
1030 if (!preg_match($regex, $item['name']))
1031 {
1032 if (empty($item['children']))
1033 {
1034 unset($list[$i]);
1035 continue;
1036 }
1037 }
1038
1039 if (!empty($item['children']))
1040 {
1041 $item['children'] = $listFilter($item['children']);
1042
1043 if (empty($item['children']))
1044 {
1045 unset($item['children']);
1046 }
1047 }
1048
1049 $list[$i] = $item;
1050 }
1051
1052 $list = array_values($list);
1053
1054 for ($i = 0; $i < count($list); $i++)
1055 {
1056 $item = $list[$i];
1057
1058 if (!preg_match($regex, $item['name']))
1059 {
1060 $children = empty($item['children']) ? array() : $item['children'];
1061
1062 array_splice($list, $i, 1, $children);
1063 $i += count($children) - 1;
1064 }
1065 }
1066
1067 return $list;
1068 };
1069
1070 $list = $listFilter($list);
1071
1072 $listHandler = function ($list, $path = array()) use (&$listHandler, $regex, $flat)
1073 {
1074 for ($i = 0; $i < count($list); $i++)
1075 {
1076 $item = $list[$i];
1077
1078 $item['path'] = array_merge($path, array($item['title']));
1079
1080 if (!empty($item['children']))
1081 {
1082 $item['children'] = $listHandler($item['children'], $item['path']);
1083 }
1084
1085 $list[$i] = $item;
1086
1087 if ($flat && !empty($item['children']))
1088 {
1089 unset($list[$i]['children']);
1090
1091 array_splice($list, $i + 1, 0, $item['children']);
1092 $i += count($item['children']);
1093 }
1094 }
1095
1096 return array_values($list);
1097 };
1098
1099 $list = $listHandler($list);
1100
1101 return $list;
1102 }
1103
1104 public function listMessages($mailbox, &$uidtoken, &$error)
1105 {
1106 $error = null;
1107
1108 $params = array(
1109 'offset' => 0,
1110 'limit' => -1,
1111 );
1112
1113 if (is_array($mailbox))
1114 {
1115 $params = array_merge($params, $mailbox);
1116 $mailbox = $mailbox['mailbox'];
1117 }
1118
1119 if (!($params['offset'] > 0))
1120 {
1121 $params['offset'] = 0;
1122 }
1123
1124 if (!($params['limit'] > 0))
1125 {
1126 $params['limit'] = -1;
1127 }
1128
1129 if (!$this->select($mailbox, $error))
1130 {
1131 return false;
1132 }
1133
1134 if (!($this->sessMailbox['exists'] > 0) || $params['offset'] + 1 > $this->sessMailbox['exists'])
1135 {
1136 return array();
1137 }
1138
1139 if ($params['limit'] > 0 && $params['offset'] + $params['limit'] > $this->sessMailbox['exists'])
1140 {
1141 $params['limit'] = $this->sessMailbox['exists'] - $params['offset'];
1142 }
1143
1144 $uidtoken = $this->sessMailbox['uidvalidity'];
1145
1146 $list = $this->fetch(
1147 false,
1148 $mailbox,
1149 sprintf(
1150 '%u:%s',
1151 $params['offset'] + 1,
1152 $params['limit'] > 0 ? (int) ($params['offset']+$params['limit']) : '*'
1153 ),
1154 array_merge(
1155 !is_null($uidtoken) ? array('UID') : array(),
1156 array('INTERNALDATE', 'RFC822.SIZE', 'FLAGS')
1157 ),
1158 $error
1159 );
1160
1161 foreach ($list as $id => $data)
1162 {
1163 $list[$id] = array(
1164 'id' => $id,
1165 'uid' => array_key_exists('UID', $data) ? $data['UID'] : null,
1166 'date' => array_key_exists('INTERNALDATE', $data) ? $data['INTERNALDATE'] : null,
1167 'size' => array_key_exists('RFC822.SIZE', $data) ? $data['RFC822.SIZE'] : null,
1168 'flags' => array_key_exists('FLAGS', $data) ? $data['FLAGS'] : array(),
1169 );
1170 }
1171
1172 $list = array_filter(
1173 $list,
1174 function ($item)
1175 {
1176 return isset($item['date'], $item['size'], $item['flags']);
1177 }
1178 );
1179
1180 return $list;
1181 }
1182
1191 public function addMessage($mailbox, $data, &$error)
1192 {
1193 $error = null;
1194
1195 return $this->append($mailbox, array('\Seen'), new \DateTime, $data, $error);
1196 }
1197
1198 public function searchByHeader($uid, $mailbox, array $header, &$error)
1199 {
1200 $error = null;
1201
1202 if (!$this->select($mailbox, $error))
1203 {
1204 return false;
1205 }
1206
1207 if (empty($header))
1208 {
1209 return false;
1210 }
1211
1212 $result = array();
1213
1214 $response = $this->executeCommand(
1215 $ccc = sprintf(
1216 '%sSEARCH %s', $uid ? 'UID ' : '',
1217 join(
1218 ' ',
1219 array_map(
1220 function ($name, $value)
1221 {
1222 return sprintf(
1223 'HEADER %s %s',
1224 static::prepareString($name),
1225 static::prepareString($value)
1226 );
1227 },
1228 array_keys($header),
1229 array_values($header)
1230 )
1231 )
1232 ),
1233 $error
1234 );
1235
1236 if ($error)
1237 {
1238 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1239 $error = $this->errorMessage(array(Imap::ERR_SEARCH, $error), $response);
1240
1241 return false;
1242 }
1243
1244 $regex = '/^ \* \x20 SEARCH \x20 ( .+ ) \r\n $ /ix';
1245 foreach ($this->getUntagged($regex, true) as $item)
1246 {
1247 $result = preg_match_all('/\d+/', $item[1][1]);
1248 }
1249
1250 return $result;
1251 }
1252
1253 public function append($mailbox, array $flags, \DateTime $internaldate, $data, &$error)
1254 {
1255 $error = null;
1256
1257 if (!$this->authenticate($error))
1258 {
1259 return false;
1260 }
1261
1262 foreach ($flags as $k => $item)
1263 {
1264 if (!preg_match(sprintf('/ ^ \x5c? %s $ /ix', self::$atomRegex), $item))
1265 {
1266 unset($flags[$k]);
1267 }
1268 }
1269
1270 $response = $this->executeCommand(sprintf(
1271 'APPEND "%s" (%s) "%26s" %s',
1272 static::escapeQuoted($this->encodeUtf7Imap($mailbox)),
1273 join(' ', $flags),
1274 $internaldate->format('j-M-Y H:i:s O'),
1275 static::prepareString($data)
1276 ), $error);
1277
1278 if ($error)
1279 {
1280 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1281 $error = $this->errorMessage(array(Imap::ERR_APPEND, $error), $response);
1282
1283 return false;
1284 }
1285
1286 $regex = sprintf('/^ OK \x20 \[ APPENDUID \x20 ( \d+ ) \x20 ( \d+ ) \] /ix', $this->getTag());
1287 if (preg_match($regex, $response, $matches))
1288 {
1289 return sprintf('%u:%u', $matches[1], $matches[2]);
1290 }
1291
1292 return true;
1293 }
1294
1295 public function moveMails($ids, $folderFrom, $folderTo)
1296 {
1297 $folderTo = Emoji::decode($folderTo);
1298 $error = null;
1299 $result = new Main\Result();
1300 if (!$this->authenticate($error))
1301 {
1302 return $result->addError(new Main\Error(''));
1303 }
1304
1305 if (preg_match('/ \x20 MOVE ( \x20 | \r\n ) /ix', $this->sessCapability))
1306 {
1307 $result = $this->move($ids, $folderFrom, $folderTo);
1308 }
1309 else
1310 {
1311 $result = $this->copyMailToFolder($ids, $folderFrom, $folderTo);
1312 if ($result->isSuccess())
1313 {
1314 $result = $this->delete($ids, $folderFrom);
1315 }
1316 }
1317 return $result;
1318 }
1319
1320 public function move($ids, $folderFrom, $folderTo)
1321 {
1322 $error = null;
1323 $result = new Main\Result();
1324 if (!$this->authenticate($error))
1325 {
1326 return $result->addError(new Main\Error(''));
1327 }
1328 if (!$this->select($folderFrom, $error))
1329 {
1330 return $result->addError(new Main\Error(''));
1331 }
1332 $response = $this->executeCommand(sprintf('UID MOVE %s "%s"', $this->prepareIdsParam($ids), static::escapeQuoted($this->encodeUtf7Imap($folderTo))), $error);
1333 if ($error)
1334 {
1335 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1336 $error = $this->errorMessage([Imap::ERR_STORE, $error], $response);
1337 return $result->addError(new Main\Error($error));
1338 }
1339
1340 return $result;
1341 }
1342
1343 public function copyMailToFolder($ids, $mailboxName, $folder)
1344 {
1345 $error = null;
1346 $result = new Main\Result();
1347 if (!$this->authenticate($error))
1348 {
1349 return $result->addError(new Main\Error(''));
1350 }
1351 if (!$this->select($mailboxName, $error))
1352 {
1353 return $result->addError(new Main\Error(''));
1354 }
1355 $response = $this->executeCommand(sprintf('UID COPY %s "%s"', $this->prepareIdsParam($ids), static::escapeQuoted($this->encodeUtf7Imap($folder))), $error);
1356 if ($error)
1357 {
1358 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1359 $error = $this->errorMessage(array(Imap::ERR_STORE, $error), $response);
1360 return $result->addError(new Main\Error($error));
1361 }
1362
1363 return $result;
1364 }
1365
1366 public function unseen($ids, $folder)
1367 {
1368 $error = null;
1369 $result = new Main\Result();
1370 if (!$this->authenticate($error))
1371 {
1372 return $result->addError(new Main\Error(''));
1373 }
1374 if (!$this->select($folder, $error))
1375 {
1376 return $result->addError(new Main\Error(''));
1377 }
1378
1379 $response = $this->store($ids, ['\Seen'], $error, true, true);
1380 if ($error)
1381 {
1382 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1383 $error = $this->errorMessage([Imap::ERR_STORE, $error], $response);
1384 return $result->addError(new Main\Error($error));
1385 }
1386 return $result;
1387 }
1388
1389 public function seen($ids, $folder)
1390 {
1391 $error = null;
1392 $result = new Main\Result();
1393 if (!$this->authenticate($error))
1394 {
1395 return $result->addError(new Main\Error(''));
1396 }
1397 if (!$this->select($folder, $error))
1398 {
1399 return $result->addError(new Main\Error(''));
1400 }
1401
1402 $response = $this->store($ids, ['\Seen'], $error);
1403 if ($error)
1404 {
1405 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1406 $error = $this->errorMessage([Imap::ERR_STORE, $error], $response);
1407 return $result->addError(new Main\Error($error));
1408 }
1409 return $result;
1410 }
1411
1417 public function delete($id, $mailboxName)
1418 {
1419 $error = null;
1420 $result = new Main\Result();
1421 if (!$this->authenticate($error))
1422 {
1423 return $result->addError(new Main\Error(''));
1424 }
1425 if (!$this->select($mailboxName, $error))
1426 {
1427 return $result->addError(new Main\Error(''));
1428 }
1429 $response = $this->store($id, ['\Deleted'], $error);
1430 if ($error)
1431 {
1432 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1433 $error = $this->errorMessage(array(Imap::ERR_STORE, $error), $response);
1434 return $result->addError(new Main\Error($error));
1435 }
1436
1437 $response = $this->expunge($error);
1438 if ($error)
1439 {
1440 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1441 $error = $this->errorMessage(array(Imap::ERR_STORE, $error), $response);
1442 return $result->addError(new Main\Error($error));
1443 }
1444 return $result;
1445 }
1446
1452 private function expunge(&$error)
1453 {
1454 return $this->executeCommand('EXPUNGE', $error);
1455 }
1456
1465 private function store($ids, $flags, &$error, $isUid = true, $isRemoveFlags = false)
1466 {
1467 $command = sprintf('STORE %s ', $this->prepareIdsParam($ids));
1468 $command .= $isRemoveFlags ? '-' : '+';
1469 $command = $command . sprintf('FLAGS (%s)', join(' ', $flags));
1470
1471 if ($isUid)
1472 {
1473 $command = 'UID ' . $command;
1474 }
1475
1476 return $this->executeCommand($command, $error);
1477 }
1478
1479 public function updateMessageFlags($mailbox, $id, $flags, &$error)
1480 {
1481 $error = null;
1482 $response = '';
1483
1484 if (!$this->select($mailbox, $error))
1485 {
1486 return false;
1487 }
1488
1489 $addFlags = array();
1490 $delFlags = array();
1491 foreach ($flags as $name => $value)
1492 {
1493 if (preg_match(sprintf('/ ^ \x5c? %s $ /ix', self::$atomRegex), $name))
1494 {
1495 if ($value)
1496 {
1497 $addFlags[] = $name;
1498 }
1499 else
1500 {
1501 $delFlags[] = $name;
1502 }
1503 }
1504 }
1505
1506 if ($addFlags)
1507 {
1508 $response = $this->store($id, $addFlags, $error, false);
1509 }
1510
1511 if (!$error && $delFlags)
1512 {
1513 $response = $this->store($id, $delFlags, $error, false, true);
1514 }
1515
1516 if ($error)
1517 {
1518 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1519 $error = $this->errorMessage(array(Imap::ERR_STORE, $error), $response);
1520
1521 return false;
1522 }
1523
1524 $this->getUntagged(sprintf('/^ \* \x20 %u \x20 FETCH \x20 \‍( .+ \‍) \r\n $/isx', $id), true);
1525
1526 return true;
1527 }
1528
1538 public function getMessage($mailbox, $id, $section, &$error)
1539 {
1540 $error = null;
1541
1542 $section = mb_strtoupper($section);
1543
1544 if (!in_array(mb_strtoupper($section), array('HEADER', 'TEXT')))
1545 {
1546 $section = '';
1547 }
1548
1549 if (!$this->select($mailbox, $error))
1550 {
1551 return false;
1552 }
1553
1554 $response = $this->fetch(false, $mailbox, (int) $id, sprintf('BODY.PEEK[%s]', $section), $error);
1555
1556 return $response[sprintf('BODY[%s]', $section)];
1557 }
1558
1559 public function isExistsDir($mailbox, &$error)
1560 {
1561 $error = null;
1562
1563 $dirs = $this->listex('', $mailbox, $error);
1564
1565 if (is_array($dirs) && empty($dirs))
1566 {
1567 return false;
1568 }
1569
1570 return true;
1571 }
1572
1573 public function ensureEmpty($mailbox, &$error)
1574 {
1575 $error = null;
1576
1577 if (!$this->select($mailbox, $error))
1578 {
1579 return false;
1580 }
1581
1582 if ($this->sessMailbox['exists'] > 0)
1583 {
1584 return false;
1585 }
1586
1587 $searchUntaggedRegex = '/^ \* \x20 SEARCH \x20 ( .+ ) \r\n $ /ix';
1588 $this->getUntagged($searchUntaggedRegex, true);
1589
1590 $response = $this->executeCommand('UID SEARCH 1', $error);
1591
1592 if ($error)
1593 {
1594 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1595 $error = $this->errorMessage(array(Imap::ERR_SEARCH, $error), $response);
1596
1597 return false;
1598 }
1599
1600 $matches = 0;
1601 foreach ($this->getUntagged($searchUntaggedRegex, true) as $item)
1602 {
1603 $matches = preg_match_all('/\d+/', $item[1][1]);
1604 }
1605
1606 if ($matches > 0)
1607 {
1608 addMessage2Log(
1609 sprintf(
1610 'IMAP: invalid mailbox (search>exists) (%s:%s:%s:%u)',
1611 $this->options['host'], $this->options['login'], $mailbox, $this->sessMailbox['uidvalidity']
1612 ),
1613 'mail', 0, false
1614 );
1615
1616 return false;
1617 }
1618
1619 $fetchUntaggedRegex = '/^ \* \x20 ( \d+ ) \x20 FETCH \x20 \‍( ( .+ ) \‍) \r\n $/isx';
1620 $this->getUntagged($fetchUntaggedRegex, true);
1621
1622 $response = $this->executeCommand('FETCH 1 (UID)', $error);
1623
1624 if ($error)
1625 {
1626 $error = $error == Imap::ERR_COMMAND_REJECTED ? null : $error;
1627 $error = $this->errorMessage(array(Imap::ERR_FETCH, $error), $response);
1628
1629 return false;
1630 }
1631
1632 $matches = 0;
1633 foreach ($this->getUntagged($fetchUntaggedRegex, true) as $item)
1634 {
1635 $matches = $item[1][1];
1636 }
1637
1638 if ($matches > 0)
1639 {
1640 addMessage2Log(
1641 sprintf(
1642 'IMAP: invalid mailbox (fetch>exists) (%s:%s:%s:%u)',
1643 $this->options['host'], $this->options['login'], $mailbox, $this->sessMailbox['uidvalidity']
1644 ),
1645 'mail', 0, false
1646 );
1647
1648 return false;
1649 }
1650
1651 return true;
1652 }
1653
1654 protected function getUntagged($regex, $unset = false)
1655 {
1656 $result = array();
1657
1658 $length = count($this->sessUntagged);
1659 for ($i = 0; $i < $length; $i++)
1660 {
1661 if (!preg_match($regex, $this->sessUntagged[$i], $matches))
1662 continue;
1663
1664 unset($matches[0]);
1665 $result[] = array($this->sessUntagged[$i], $matches);
1666
1667 if ($unset)
1668 unset($this->sessUntagged[$i]);
1669 }
1670
1671 if ($unset && !empty($result))
1672 $this->sessUntagged = array_values($this->sessUntagged);
1673
1674 return $result;
1675 }
1676
1677 protected function getTag($next = false)
1678 {
1679 if ($next)
1680 $this->sessCounter++;
1681
1682 return sprintf('A%03u', $this->sessCounter);
1683 }
1684
1685 protected function executeCommand($command, &$error)
1686 {
1687 $error = null;
1688 $response = false;
1689
1690 $chunks = explode("\x00", sprintf('%s %s', $this->getTag(true), $command));
1691
1692 $k = count($chunks);
1693 foreach ($chunks as $chunk)
1694 {
1695 $k--;
1696
1697 $response = $this->exchange($chunk, $error);
1698
1699 if ($k > 0 && mb_strpos($response, '+') !== 0)
1700 break;
1701 }
1702
1703 return $response;
1704 }
1705
1706 protected function exchange($data, &$error)
1707 {
1708 $error = null;
1709
1710 if ($this->sendData(sprintf("%s\r\n", $data)) === false)
1711 {
1712 $error = Imap::ERR_COMMUNICATE;
1713 return false;
1714 }
1715
1716 $response = $this->readResponse();
1717
1718 if ($response === false)
1719 {
1720 $error = Imap::ERR_EMPTY_RESPONSE;
1721 return false;
1722 }
1723
1724 $response = trim($response);
1725
1726 if (!preg_match(sprintf('/^ %s \x20 OK /ix', $this->getTag()), $response))
1727 {
1728 if (preg_match(sprintf('/^ %s \x20 ( NO | BAD ) /ix', $this->getTag()), $response))
1729 $error = Imap::ERR_COMMAND_REJECTED;
1730 else
1731 $error = Imap::ERR_BAD_SERVER;
1732 }
1733
1734 return preg_replace(sprintf('/^ %s \x20 /ix', $this->getTag()), '', $response);
1735 }
1736
1737 protected function sendData($data)
1738 {
1739 $logData = null;
1740 if(($this->logLevel & self::LOG_LEVEL_WRITE))
1741 {
1742 $logData = $data;
1743 }
1744
1745 $fails = 0;
1746 while (strlen($data) > 0 && !feof($this->stream))
1747 {
1748 $bytes = @fputs($this->stream, $data);
1749
1750 if (false == $bytes)
1751 {
1752 if (false === $bytes || ++$fails >= 3)
1753 {
1754 break;
1755 }
1756
1757 continue;
1758 }
1759
1760 $fails = 0;
1761
1762 $data = substr($data, $bytes);
1763 }
1764
1765 if (strlen($data) > 0)
1766 {
1767 $this->reset();
1768 return false;
1769 }
1770
1771 if($logData !== null)
1772 {
1773 $this->writeToLog($logData);
1774 }
1775
1776 return true;
1777 }
1778
1779 protected function readBytes($bytes)
1780 {
1781 $data = '';
1782
1783 while ($bytes > 0 && !feof($this->stream))
1784 {
1785 $buffer = @fread($this->stream, $bytes);
1786 if ($buffer === false)
1787 break;
1788
1789 $meta = $this->options['timeout'] > 0
1790 ? stream_get_meta_data($this->stream)
1791 : array('timed_out' => false);
1792
1793 $data .= $buffer;
1794 $bytes -= strlen($buffer);
1795
1796 if ($meta['timed_out'])
1797 break;
1798 }
1799
1800 if ($bytes > 0)
1801 {
1802 $this->reset();
1803 return false;
1804 }
1805
1806 return $data;
1807 }
1808
1809 protected function readLine()
1810 {
1811 $line = '';
1812
1813 while (!feof($this->stream))
1814 {
1815 $buffer = @fgets($this->stream, 4096);
1816 if ($buffer === false)
1817 break;
1818
1819 $meta = $this->options['timeout'] > 0
1820 ? stream_get_meta_data($this->stream)
1821 : array('timed_out' => false);
1822
1823 $line .= $buffer;
1824
1825 $eolRegex = '/ (?<literal> \{ (?<bytes> \d+ ) \} )? \r\n $ /x';
1826 if (preg_match($eolRegex, $line, $matches))
1827 {
1828 if (empty($matches['literal']))
1829 break;
1830
1831 if ($meta['timed_out'])
1832 return false;
1833
1834 $data = $this->readBytes($matches['bytes']);
1835 if ($data === false)
1836 return false;
1837
1838 $line .= $data;
1839 }
1840
1841 if ($meta['timed_out'])
1842 break;
1843 }
1844
1845 if (!preg_match('/\r\n$/', $line, $matches))
1846 {
1847 $this->reset();
1848 return false;
1849 }
1850
1851 if(($this->logLevel & self::LOG_LEVEL_READ))
1852 {
1853 $this->writeToLog($line);
1854 }
1855
1856 return $line;
1857 }
1858
1859 protected function readResponse()
1860 {
1861 do
1862 {
1863 $line = $this->readLine();
1864 if ($line === false)
1865 return false;
1866
1867 if (mb_strpos($line, '*') === 0)
1868 $this->sessUntagged[] = $line;
1869 }
1870 while (mb_strpos($line, '*') === 0);
1871
1872 if ('select' == $this->sessState)
1873 {
1874 $regex = '/^ \* \x20 ( \d+ ) \x20 EXISTS /ix';
1875 foreach ($this->getUntagged($regex) as $item)
1876 {
1877 $this->sessMailbox['exists'] = $item[1][1];
1878 }
1879 }
1880
1881 return $line;
1882 }
1883
1884 protected static function prepareString($data)
1885 {
1886 if (preg_match('/^[^\x00\x0a\x0d\x80-\xff]*$/', $data))
1887 return sprintf('"%s"', static::escapeQuoted($data));
1888 else
1889 return sprintf("{%u}\x00%s", strlen($data), $data);
1890 }
1891
1892 protected static function escapeQuoted($data)
1893 {
1894 return str_replace(array('\\', '"'), array('\\\\', '\\"'), $data);
1895 }
1896
1897 protected static function unescapeQuoted($data)
1898 {
1899 return str_replace(array('\\\\', '\\"'), array('\\', '"'), $data);
1900 }
1901
1902 protected function encodeUtf7Imap($data)
1903 {
1904 if (!$data)
1905 return $data;
1906
1907 $result = Encoding::convertEncoding($data, $this->options['encoding'], 'UTF7-IMAP');
1908
1909 if ($result === false)
1910 {
1911 $result = $data;
1912
1913 $result = Encoding::convertEncoding($result, $this->options['encoding'], 'UTF-8');
1914 $result = str_replace('&', '&-', $result);
1915
1916 $result = preg_replace_callback('/[\x00-\x1f\x7f-\xff]+/', function($matches)
1917 {
1918 $result = $matches[0];
1919
1920 $result = Encoding::convertEncoding($result, 'UTF-8', 'UTF-16BE');
1921 $result = base64_encode($result);
1922 $result = str_replace('/', ',', $result);
1923 $result = str_replace('=', '', $result);
1924 $result = '&' . $result . '-';
1925
1926 return $result;
1927 }, $result);
1928 }
1929
1930 return $result;
1931 }
1932
1933 protected function decodeUtf7Imap($data)
1934 {
1935 if (!$data)
1936 return $data;
1937
1938 $result = Encoding::convertEncoding($data, 'UTF7-IMAP', $this->options['encoding']);
1939
1940 if ($result === false)
1941 {
1942 $result = $data;
1943
1944 $result = preg_replace_callback('/&([\x2b\x2c\x30-\x39\x41-\x5a\x61-\x7a]+)-/', function($matches)
1945 {
1946 $result = $matches[1];
1947
1948 $result = str_replace(',', '/', $result);
1949 $result = base64_decode($result);
1950 $result = Encoding::convertEncoding($result, 'UTF-16BE', 'UTF-8');
1951
1952 return $result;
1953 }, $result);
1954
1955 $result = str_replace('&-', '&', $result);
1956 $result = Encoding::convertEncoding($result, 'UTF-8', $this->options['encoding']);
1957 }
1958
1959 return $result;
1960 }
1961
1962 private function prepareIdsParam($idsData)
1963 {
1964 if (is_array($idsData))
1965 {
1966 return implode(',', array_map('intval', $idsData));
1967 }
1968 else
1969 {
1970 return intval($idsData);
1971 }
1972 }
1973
1974 protected function errorMessage($errors, $details = null)
1975 {
1976 $errors = array_filter((array) $errors);
1977 $details = array_filter((array) $details);
1978
1979 foreach ($errors as $i => $error)
1980 {
1981 $errors[$i] = static::decodeError($error);
1982 $this->errors->setError(new Main\Error((string) $errors[$i], $error > 0 ? $error : 0));
1983 }
1984
1985 $error = join(': ', $errors);
1986 if ($details)
1987 {
1988 $error .= sprintf(' (IMAP: %s)', join(': ', $details));
1989
1990 $this->errors->setError(new Main\Error('IMAP', -1));
1991 foreach ($details as $item)
1992 {
1993 $this->errors->setError(new Main\Error((string) $item, -1));
1994 }
1995 }
1996
1997 return $error;
1998 }
1999
2000 public function getErrors()
2001 {
2002 return $this->errors;
2003 }
2004
2011 public static function decodeError($code)
2012 {
2013 switch ($code)
2014 {
2015 case self::ERR_CONNECT:
2016 return Loc::getMessage('MAIL_IMAP_ERR_CONNECT');
2017 case self::ERR_REJECTED:
2018 return Loc::getMessage('MAIL_IMAP_ERR_REJECTED');
2019 case self::ERR_COMMUNICATE:
2020 return Loc::getMessage('MAIL_IMAP_ERR_COMMUNICATE');
2021 case self::ERR_EMPTY_RESPONSE:
2022 return Loc::getMessage('MAIL_IMAP_ERR_EMPTY_RESPONSE');
2023 case self::ERR_BAD_SERVER:
2024 return Loc::getMessage('MAIL_IMAP_ERR_BAD_SERVER');
2025 case self::ERR_STARTTLS:
2026 return Loc::getMessage('MAIL_IMAP_ERR_STARTTLS');
2027 case self::ERR_COMMAND_REJECTED:
2028 return Loc::getMessage('MAIL_IMAP_ERR_COMMAND_REJECTED');
2029 case self::ERR_CAPABILITY:
2030 return Loc::getMessage('MAIL_IMAP_ERR_CAPABILITY');
2031 case self::ERR_AUTH:
2032 case self::ERR_SELECT:
2033 return Loc::getMessage('MAIL_IMAP_ERR_AUTH');
2034 case self::ERR_AUTH_MECH:
2035 return Loc::getMessage('MAIL_IMAP_ERR_AUTH_MECH');
2036 case self::ERR_AUTH_OAUTH:
2037 return Loc::getMessage('MAIL_IMAP_ERR_AUTH_OAUTH');
2038 case self::ERR_LIST:
2039 return Loc::getMessage('MAIL_IMAP_ERR_LIST');
2040 case self::ERR_SEARCH:
2041 return Loc::getMessage('MAIL_IMAP_ERR_SEARCH');
2042 case self::ERR_FETCH:
2043 return Loc::getMessage('MAIL_IMAP_ERR_FETCH');
2044 case self::ERR_APPEND:
2045 return Loc::getMessage('MAIL_IMAP_ERR_APPEND');
2046 case self::ERR_STORE:
2047 return Loc::getMessage('MAIL_IMAP_ERR_STORE');
2048
2049 default:
2050 return Loc::getMessage('MAIL_IMAP_ERR_DEFAULT');
2051 }
2052 }
2053
2054 protected function writeToLog($data)
2055 {
2056 if($this->logPath <> '')
2057 {
2058 $fileName = $this->options["host"].".".$this->options["login"].".log";
2059 file_put_contents($this->logPath."/".$fileName, $data, FILE_APPEND);
2060 }
2061 }
2062}
listMailboxes($pattern, &$error, $flat=false)
Definition imap.php:940
getUIDsForSpecificDay($dirPath, $internalDate)
Definition imap.php:678
static $atomRegex
Definition imap.php:43
getNew($mailbox, $uidMin, $uidMax, &$error)
Definition imap.php:765
const ERR_LIST
Definition imap.php:30
const LOG_LEVEL_READ
Definition imap.php:16
const ERR_APPEND
Definition imap.php:34
const ERR_REJECTED
Definition imap.php:19
seen($ids, $folder)
Definition imap.php:1389
const ERR_SELECT
Definition imap.php:31
capability(&$error)
Definition imap.php:215
getUnseen($dirPath, &$error, $startInternalDate=null)
Definition imap.php:724
const ERR_COMMAND_REJECTED
Definition imap.php:25
static $qcharExtRegex
Definition imap.php:45
const ERR_CONNECT
Definition imap.php:18
singin(&$error)
Definition imap.php:443
getUntagged($regex, $unset=false)
Definition imap.php:1654
encodeUtf7Imap($data)
Definition imap.php:1902
moveMails($ids, $folderFrom, $folderTo)
Definition imap.php:1295
writeToLog($data)
Definition imap.php:2054
static unescapeQuoted($data)
Definition imap.php:1897
const ERR_AUTH_MECH
Definition imap.php:28
listMessages($mailbox, &$uidtoken, &$error)
Definition imap.php:1104
errorMessage($errors, $details=null)
Definition imap.php:1974
getUidRange($mailbox, &$error)
Definition imap.php:834
ensureEmpty($mailbox, &$error)
Definition imap.php:1573
const ERR_EMPTY_RESPONSE
Definition imap.php:21
updateMessageFlags($mailbox, $id, $flags, &$error)
Definition imap.php:1479
const ERR_AUTH
Definition imap.php:27
const ERR_FETCH
Definition imap.php:33
const ERR_STORE
Definition imap.php:35
select($mailbox, &$error)
Definition imap.php:345
starttls(&$error)
Definition imap.php:177
const ERR_AUTH_OAUTH
Definition imap.php:29
static escapeQuoted($data)
Definition imap.php:1892
copyMailToFolder($ids, $mailboxName, $folder)
Definition imap.php:1343
const ERR_SEARCH
Definition imap.php:32
fetch($uid, $mailbox, $range, $select, &$error, $outputFormat='smart')
Definition imap.php:459
decodeUtf7Imap($data)
Definition imap.php:1933
connect(&$error)
Definition imap.php:119
const ERR_CAPABILITY
Definition imap.php:26
addMessage($mailbox, $data, &$error)
Definition imap.php:1191
static $qcharRegex
Definition imap.php:44
authenticate(&$error)
Definition imap.php:242
listex($reference, $pattern, &$error)
Definition imap.php:861
exchange($data, &$error)
Definition imap.php:1706
isExistsDir($mailbox, &$error)
Definition imap.php:1559
searchByHeader($uid, $mailbox, array $header, &$error)
Definition imap.php:1198
executeCommand($command, &$error)
Definition imap.php:1685
const LOG_LEVEL_WRITE
Definition imap.php:15
static decodeError($code)
Definition imap.php:2011
sendData($data)
Definition imap.php:1737
unseen($ids, $folder)
Definition imap.php:1366
static prepareString($data)
Definition imap.php:1884
append($mailbox, array $flags, \DateTime $internaldate, $data, &$error)
Definition imap.php:1253
move($ids, $folderFrom, $folderTo)
Definition imap.php:1320
examine($mailbox, &$error)
Definition imap.php:396
const ERR_STARTTLS
Definition imap.php:24
__construct($host, $port, $tls, $strict, $login, $password, $encoding=null)
Definition imap.php:48
const ERR_COMMUNICATE
Definition imap.php:20
static $astringRegex
Definition imap.php:46
const ERR_BAD_SERVER
Definition imap.php:22
getMessage($mailbox, $id, $section, &$error)
Definition imap.php:1538
readBytes($bytes)
Definition imap.php:1779
getTag($next=false)
Definition imap.php:1677
static loadMessages($file)
Definition loc.php:64
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29