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