Changeset c2c820c in github
- Timestamp:
- Dec 9, 2010 9:56:06 AM (2 years ago)
- Branches:
- master, HEAD, courier-fix, dev-browser-capabilities, pdo, release-0.6, release-0.7, release-0.8
- Children:
- 3ee5a72
- Parents:
- ad399a9
- File:
-
- 1 edited
-
program/include/rcube_imap_generic.php (modified) (82 diffs)
Legend:
- Unmodified
- Added
- Removed
-
program/include/rcube_imap_generic.php
rad399a9 rc2c820c 1 1 <?php 2 2 3 /* 3 /** 4 4 +-----------------------------------------------------------------------+ 5 5 | program/include/rcube_imap_generic.php | … … 30 30 * Struct representing an e-mail message header 31 31 * 32 * @package 33 * @author Aleksander Machniak <alec@alec.pl>32 * @package Mail 33 * @author Aleksander Machniak <alec@alec.pl> 34 34 */ 35 35 class rcube_mail_header 36 36 { 37 public $id;38 public $uid;39 public $subject;40 public $from;41 public $to;42 public $cc;43 public $replyto;44 public $in_reply_to;45 public $date;46 public $messageID;47 public $size;48 public $encoding;49 public $charset;50 public $ctype;51 public $flags;52 public $timestamp;53 public $body_structure;54 public $internaldate;55 public $references;56 public $priority;57 public $mdn_to;58 public $mdn_sent = false;59 public $is_draft = false;60 public $seen = false;61 public $deleted = false;62 public $recent = false;63 public $answered = false;64 public $forwarded = false;65 public $junk = false;66 public $flagged = false;67 public $has_children = false;68 public $depth = 0;69 public $unread_children = 0;70 public $others = array();37 public $id; 38 public $uid; 39 public $subject; 40 public $from; 41 public $to; 42 public $cc; 43 public $replyto; 44 public $in_reply_to; 45 public $date; 46 public $messageID; 47 public $size; 48 public $encoding; 49 public $charset; 50 public $ctype; 51 public $flags; 52 public $timestamp; 53 public $body_structure; 54 public $internaldate; 55 public $references; 56 public $priority; 57 public $mdn_to; 58 public $mdn_sent = false; 59 public $is_draft = false; 60 public $seen = false; 61 public $deleted = false; 62 public $recent = false; 63 public $answered = false; 64 public $forwarded = false; 65 public $junk = false; 66 public $flagged = false; 67 public $has_children = false; 68 public $depth = 0; 69 public $unread_children = 0; 70 public $others = array(); 71 71 } 72 72 … … 79 79 * PHP based wrapper class to connect to an IMAP server 80 80 * 81 * @package 82 * @author Aleksander Machniak <alec@alec.pl>81 * @package Mail 82 * @author Aleksander Machniak <alec@alec.pl> 83 83 */ 84 84 class rcube_imap_generic … … 102 102 103 103 private $selected; 104 private $fp;105 private $host;106 private $logged = false;107 private $capability = array();108 private $capability_readed = false;104 private $fp; 105 private $host; 106 private $logged = false; 107 private $capability = array(); 108 private $capability_readed = false; 109 109 private $prefs; 110 110 private $cmd_tag; … … 143 143 return false; 144 144 145 if (!empty($this->prefs['debug_mode'])) {146 write_log('imap', 'C: '. rtrim($string));147 }145 if (!empty($this->prefs['debug_mode'])) { 146 write_log('imap', 'C: '. rtrim($string)); 147 } 148 148 149 149 $res = fwrite($this->fp, $string . ($endln ? "\r\n" : '')); 150 150 151 if ($res === false) {152 @fclose($this->fp);153 $this->fp = null;154 }151 if ($res === false) { 152 @fclose($this->fp); 153 $this->fp = null; 154 } 155 155 156 156 return $res; … … 171 171 return false; 172 172 173 if ($endln)174 $string .= "\r\n";175 176 $res = 0;177 if ($parts = preg_split('/(\{[0-9]+\}\r\n)/m', $string, -1, PREG_SPLIT_DELIM_CAPTURE)) {178 for ($i=0, $cnt=count($parts); $i<$cnt; $i++) {179 if (preg_match('/^\{[0-9]+\}\r\n$/', $parts[$i+1])) {173 if ($endln) 174 $string .= "\r\n"; 175 176 $res = 0; 177 if ($parts = preg_split('/(\{[0-9]+\}\r\n)/m', $string, -1, PREG_SPLIT_DELIM_CAPTURE)) { 178 for ($i=0, $cnt=count($parts); $i<$cnt; $i++) { 179 if (preg_match('/^\{[0-9]+\}\r\n$/', $parts[$i+1])) { 180 180 // LITERAL+ support 181 181 if ($this->prefs['literal+']) 182 182 $parts[$i+1] = preg_replace('/([0-9]+)/', '\\1+', $parts[$i+1]); 183 183 184 $bytes = $this->putLine($parts[$i].$parts[$i+1], false);184 $bytes = $this->putLine($parts[$i].$parts[$i+1], false); 185 185 if ($bytes === false) 186 186 return false; … … 189 189 // don't wait if server supports LITERAL+ capability 190 190 if (!$this->prefs['literal+']) { 191 $line = $this->readLine(1000);192 // handle error in command193 if ($line[0] != '+')194 return false;195 }191 $line = $this->readLine(1000); 192 // handle error in command 193 if ($line[0] != '+') 194 return false; 195 } 196 196 $i++; 197 }198 else {199 $bytes = $this->putLine($parts[$i], false);197 } 198 else { 199 $bytes = $this->putLine($parts[$i], false); 200 200 if ($bytes === false) 201 201 return false; 202 202 $res += $bytes; 203 203 } 204 }205 }206 207 return $res;204 } 205 } 206 207 return $res; 208 208 } 209 209 210 210 function readLine($size=1024) 211 211 { 212 $line = '';213 214 if (!$this->fp) {215 return NULL;216 }217 218 if (!$size) {219 $size = 1024;220 }221 222 do {223 if (feof($this->fp)) {224 return $line ? $line : NULL;225 }226 227 $buffer = fgets($this->fp, $size);228 229 if ($buffer === false) {230 @fclose($this->fp);231 $this->fp = null;232 break;233 }234 if (!empty($this->prefs['debug_mode'])) {235 write_log('imap', 'S: '. rtrim($buffer));236 }212 $line = ''; 213 214 if (!$this->fp) { 215 return NULL; 216 } 217 218 if (!$size) { 219 $size = 1024; 220 } 221 222 do { 223 if (feof($this->fp)) { 224 return $line ? $line : NULL; 225 } 226 227 $buffer = fgets($this->fp, $size); 228 229 if ($buffer === false) { 230 @fclose($this->fp); 231 $this->fp = null; 232 break; 233 } 234 if (!empty($this->prefs['debug_mode'])) { 235 write_log('imap', 'S: '. rtrim($buffer)); 236 } 237 237 $line .= $buffer; 238 } while ($buffer[strlen($buffer)-1] != "\n");239 240 return $line;238 } while ($buffer[strlen($buffer)-1] != "\n"); 239 240 return $line; 241 241 } 242 242 243 243 function multLine($line, $escape=false) 244 244 { 245 $line = rtrim($line);246 if (preg_match('/\{[0-9]+\}$/', $line)) {247 $out = '';248 249 preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a);250 $bytes = $a[2][0];251 while (strlen($out) < $bytes) {252 $line = $this->readBytes($bytes);253 if ($line === NULL)254 break;255 $out .= $line;256 }257 258 $line = $a[1][0] . ($escape ? $this->escape($out) : $out);259 }245 $line = rtrim($line); 246 if (preg_match('/\{[0-9]+\}$/', $line)) { 247 $out = ''; 248 249 preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a); 250 $bytes = $a[2][0]; 251 while (strlen($out) < $bytes) { 252 $line = $this->readBytes($bytes); 253 if ($line === NULL) 254 break; 255 $out .= $line; 256 } 257 258 $line = $a[1][0] . ($escape ? $this->escape($out) : $out); 259 } 260 260 261 261 return $line; … … 264 264 function readBytes($bytes) 265 265 { 266 $data = '';267 $len = 0;268 while ($len < $bytes && !feof($this->fp))269 {270 $d = fread($this->fp, $bytes-$len);271 if (!empty($this->prefs['debug_mode'])) {272 write_log('imap', 'S: '. $d);266 $data = ''; 267 $len = 0; 268 while ($len < $bytes && !feof($this->fp)) 269 { 270 $d = fread($this->fp, $bytes-$len); 271 if (!empty($this->prefs['debug_mode'])) { 272 write_log('imap', 'S: '. $d); 273 273 } 274 274 $data .= $d; 275 $data_len = strlen($data);276 if ($len == $data_len) {277 break; // nothing was read -> exit to avoid apache lockups278 }279 $len = $data_len;280 }281 282 return $data;275 $data_len = strlen($data); 276 if ($len == $data_len) { 277 break; // nothing was read -> exit to avoid apache lockups 278 } 279 $len = $data_len; 280 } 281 282 return $data; 283 283 } 284 284 285 285 function readReply(&$untagged=null) 286 286 { 287 do {288 $line = trim($this->readLine(1024));287 do { 288 $line = trim($this->readLine(1024)); 289 289 // store untagged response lines 290 if ($line[0] == '*')290 if ($line[0] == '*') 291 291 $untagged[] = $line; 292 } while ($line[0] == '*');292 } while ($line[0] == '*'); 293 293 294 294 if ($untagged) 295 295 $untagged = join("\n", $untagged); 296 296 297 return $line;297 return $line; 298 298 } 299 299 300 300 function parseResult($string, $err_prefix='') 301 301 { 302 if (preg_match('/^[a-z0-9*]+ (OK|NO|BAD|BYE)(.*)$/i', trim($string), $matches)) {303 $res = strtoupper($matches[1]);302 if (preg_match('/^[a-z0-9*]+ (OK|NO|BAD|BYE)(.*)$/i', trim($string), $matches)) { 303 $res = strtoupper($matches[1]); 304 304 $str = trim($matches[2]); 305 305 306 if ($res == 'OK') {307 $this->errornum = self::ERROR_OK;308 } else if ($res == 'NO') {306 if ($res == 'OK') { 307 $this->errornum = self::ERROR_OK; 308 } else if ($res == 'NO') { 309 309 $this->errornum = self::ERROR_NO; 310 } else if ($res == 'BAD') {311 $this->errornum = self::ERROR_BAD;312 } else if ($res == 'BYE') {310 } else if ($res == 'BAD') { 311 $this->errornum = self::ERROR_BAD; 312 } else if ($res == 'BYE') { 313 313 @fclose($this->fp); 314 314 $this->fp = null; 315 $this->errornum = self::ERROR_BYE;316 }315 $this->errornum = self::ERROR_BYE; 316 } 317 317 318 318 if ($str) { … … 333 333 } 334 334 335 return $this->errornum;336 }337 return self::ERROR_UNKNOWN;335 return $this->errornum; 336 } 337 return self::ERROR_UNKNOWN; 338 338 } 339 339 … … 347 347 function startsWith($string, $match, $error=false, $nonempty=false) 348 348 { 349 $len = strlen($match);350 if ($len == 0) {351 return false;352 }349 $len = strlen($match); 350 if ($len == 0) { 351 return false; 352 } 353 353 if (!$this->fp) { 354 354 return true; 355 355 } 356 if (strncmp($string, $match, $len) == 0) {357 return true;358 }359 if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) {356 if (strncmp($string, $match, $len) == 0) { 357 return true; 358 } 359 if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) { 360 360 if (strtoupper($m[1]) == 'BYE') { 361 361 @fclose($this->fp); 362 362 $this->fp = null; 363 363 } 364 return true;365 }364 return true; 365 } 366 366 if ($nonempty && !strlen($string)) { 367 367 return true; 368 368 } 369 return false;369 return false; 370 370 } 371 371 372 372 function getCapability($name) 373 373 { 374 if (in_array($name, $this->capability)) {375 return true;376 }377 else if ($this->capability_readed) {378 return false;379 }380 381 // get capabilities (only once) because initial382 // optional CAPABILITY response may differ374 if (in_array($name, $this->capability)) { 375 return true; 376 } 377 else if ($this->capability_readed) { 378 return false; 379 } 380 381 // get capabilities (only once) because initial 382 // optional CAPABILITY response may differ 383 383 $result = $this->execute('CAPABILITY'); 384 384 … … 387 387 } 388 388 389 $this->capability_readed = true;390 391 if (in_array($name, $this->capability)) {392 return true;393 }394 395 return false;389 $this->capability_readed = true; 390 391 if (in_array($name, $this->capability)) { 392 return true; 393 } 394 395 return false; 396 396 } 397 397 398 398 function clearCapability() 399 399 { 400 $this->capability = array();401 $this->capability_readed = false;400 $this->capability = array(); 401 $this->capability_readed = false; 402 402 } 403 403 … … 417 417 $this->setError(self::ERROR_BYE, 418 418 "The Auth_SASL package is required for DIGEST-MD5 authentication"); 419 return self::ERROR_BAD;420 } 421 422 $this->putLine($this->nextTag() . " AUTHENTICATE $type");423 $line = trim($this->readReply());424 425 if ($line[0] == '+') {426 $challenge = substr($line, 2);419 return self::ERROR_BAD; 420 } 421 422 $this->putLine($this->nextTag() . " AUTHENTICATE $type"); 423 $line = trim($this->readReply()); 424 425 if ($line[0] == '+') { 426 $challenge = substr($line, 2); 427 427 } 428 428 else { 429 429 return $this->parseResult($line); 430 }430 } 431 431 432 432 if ($type == 'CRAM-MD5') { … … 474 474 475 475 if ($line[0] == '+') { 476 $challenge = substr($line, 2);476 $challenge = substr($line, 2); 477 477 } 478 478 else { … … 512 512 } 513 513 else { 514 $this->putLine($this->nextTag() . " AUTHENTICATE PLAIN");515 $line = trim($this->readReply());516 517 if ($line[0] != '+') {518 return $this->parseResult($line);519 }514 $this->putLine($this->nextTag() . " AUTHENTICATE PLAIN"); 515 $line = trim($this->readReply()); 516 517 if ($line[0] != '+') { 518 return $this->parseResult($line); 519 } 520 520 521 521 // send result, get reply and process it … … 527 527 528 528 if ($result == self::ERROR_OK) { 529 // optional CAPABILITY response530 if ($line && preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {531 $this->parseCapability($matches[1], true);532 }529 // optional CAPABILITY response 530 if ($line && preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { 531 $this->parseCapability($matches[1], true); 532 } 533 533 return $this->fp; 534 534 } … … 554 554 555 555 // re-set capabilities list if untagged CAPABILITY response provided 556 if (preg_match('/\* CAPABILITY (.+)/i', $response, $matches)) {557 $this->parseCapability($matches[1], true);558 }556 if (preg_match('/\* CAPABILITY (.+)/i', $response, $matches)) { 557 $this->parseCapability($matches[1], true); 558 } 559 559 560 560 if ($code == self::ERROR_OK) { … … 572 572 function getHierarchyDelimiter() 573 573 { 574 if ($this->prefs['delimiter']) {575 return $this->prefs['delimiter'];576 }577 578 // try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8)579 list($code, $response) = $this->execute('LIST',580 array($this->escape(''), $this->escape('')));574 if ($this->prefs['delimiter']) { 575 return $this->prefs['delimiter']; 576 } 577 578 // try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8) 579 list($code, $response) = $this->execute('LIST', 580 array($this->escape(''), $this->escape(''))); 581 581 582 582 if ($code == self::ERROR_OK) { … … 584 584 $delimiter = $args[3]; 585 585 586 if (strlen($delimiter) > 0) {587 return ($this->prefs['delimiter'] = $delimiter);588 }586 if (strlen($delimiter) > 0) { 587 return ($this->prefs['delimiter'] = $delimiter); 588 } 589 589 } 590 590 … … 604 604 605 605 if (!$this->getCapability('NAMESPACE')) { 606 return self::ERROR_BAD;607 }608 609 list($code, $response) = $this->execute('NAMESPACE');610 611 if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) {612 $data = $this->tokenizeResponse(substr($response, 11));613 }614 615 if (!is_array($data)) {616 return $code;617 }606 return self::ERROR_BAD; 607 } 608 609 list($code, $response) = $this->execute('NAMESPACE'); 610 611 if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) { 612 $data = $this->tokenizeResponse(substr($response, 11)); 613 } 614 615 if (!is_array($data)) { 616 return $code; 617 } 618 618 619 619 $this->prefs['namespace'] = array( … … 628 628 function connect($host, $user, $password, $options=null) 629 629 { 630 // set options631 if (is_array($options)) {630 // set options 631 if (is_array($options)) { 632 632 $this->prefs = $options; 633 633 } … … 635 635 if (!empty($this->prefs['auth_method'])) { 636 636 $auth_method = strtoupper($this->prefs['auth_method']); 637 } else {638 $auth_method = 'CHECK';639 } 640 641 $result = false;642 643 // initialize connection644 $this->error = '';645 $this->errornum = self::ERROR_OK;646 $this->selected = '';647 $this->user = $user;648 $this->host = $host;637 } else { 638 $auth_method = 'CHECK'; 639 } 640 641 $result = false; 642 643 // initialize connection 644 $this->error = ''; 645 $this->errornum = self::ERROR_OK; 646 $this->selected = ''; 647 $this->user = $user; 648 $this->host = $host; 649 649 $this->logged = false; 650 650 651 // check input652 if (empty($host)) {653 $this->setError(self::ERROR_BAD, "Empty host");654 return false;655 }651 // check input 652 if (empty($host)) { 653 $this->setError(self::ERROR_BAD, "Empty host"); 654 return false; 655 } 656 656 if (empty($user)) { 657 $this->setError(self::ERROR_NO, "Empty user");658 return false;659 }660 if (empty($password)) {661 $this->setError(self::ERROR_NO, "Empty password");662 return false;663 }664 665 if (!$this->prefs['port']) {666 $this->prefs['port'] = 143;667 }668 // check for SSL669 if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') {670 $host = $this->prefs['ssl_mode'] . '://' . $host;671 }657 $this->setError(self::ERROR_NO, "Empty user"); 658 return false; 659 } 660 if (empty($password)) { 661 $this->setError(self::ERROR_NO, "Empty password"); 662 return false; 663 } 664 665 if (!$this->prefs['port']) { 666 $this->prefs['port'] = 143; 667 } 668 // check for SSL 669 if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') { 670 $host = $this->prefs['ssl_mode'] . '://' . $host; 671 } 672 672 673 673 // Connect 674 674 if ($this->prefs['timeout'] > 0) 675 $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);676 else677 $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr);678 679 if (!$this->fp) {680 $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr));681 return false;682 }675 $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); 676 else 677 $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr); 678 679 if (!$this->fp) { 680 $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr)); 681 return false; 682 } 683 683 684 684 if ($this->prefs['timeout'] > 0) 685 stream_set_timeout($this->fp, $this->prefs['timeout']);686 687 $line = trim(fgets($this->fp, 8192));688 689 if ($this->prefs['debug_mode'] && $line) {690 write_log('imap', 'S: '. $line);691 } 692 693 // Connected to wrong port or connection error?694 if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) {695 if ($line)696 $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line);697 else698 $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']);699 700 $this->setError(self::ERROR_BAD, $error);685 stream_set_timeout($this->fp, $this->prefs['timeout']); 686 687 $line = trim(fgets($this->fp, 8192)); 688 689 if ($this->prefs['debug_mode'] && $line) { 690 write_log('imap', 'S: '. $line); 691 } 692 693 // Connected to wrong port or connection error? 694 if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) { 695 if ($line) 696 $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line); 697 else 698 $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']); 699 700 $this->setError(self::ERROR_BAD, $error); 701 701 $this->closeConnection(); 702 return false;703 }704 705 // RFC3501 [7.1] optional CAPABILITY response706 if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {707 $this->parseCapability($matches[1], true);708 }709 710 // TLS connection711 if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) {712 if (version_compare(PHP_VERSION, '5.1.0', '>=')) {713 $res = $this->execute('STARTTLS');702 return false; 703 } 704 705 // RFC3501 [7.1] optional CAPABILITY response 706 if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { 707 $this->parseCapability($matches[1], true); 708 } 709 710 // TLS connection 711 if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) { 712 if (version_compare(PHP_VERSION, '5.1.0', '>=')) { 713 $res = $this->execute('STARTTLS'); 714 714 715 715 if ($res[0] != self::ERROR_OK) { … … 718 718 } 719 719 720 if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {721 $this->setError(self::ERROR_BAD, "Unable to negotiate TLS");720 if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { 721 $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); 722 722 $this->closeConnection(); 723 return false;724 }725 726 // Now we're secure, capabilities need to be reread727 $this->clearCapability();728 }729 }730 731 $auth_methods = array();723 return false; 724 } 725 726 // Now we're secure, capabilities need to be reread 727 $this->clearCapability(); 728 } 729 } 730 731 $auth_methods = array(); 732 732 $result = null; 733 733 734 // check for supported auth methods735 if ($auth_method == 'CHECK') {736 if ($this->getCapability('AUTH=DIGEST-MD5')) {737 $auth_methods[] = 'DIGEST-MD5';738 }739 if ($this->getCapability('AUTH=CRAM-MD5') || $this->getCapability('AUTH=CRAM_MD5')) {740 $auth_methods[] = 'CRAM-MD5';741 }742 if ($this->getCapability('AUTH=PLAIN')) {743 $auth_methods[] = 'PLAIN';744 }734 // check for supported auth methods 735 if ($auth_method == 'CHECK') { 736 if ($this->getCapability('AUTH=DIGEST-MD5')) { 737 $auth_methods[] = 'DIGEST-MD5'; 738 } 739 if ($this->getCapability('AUTH=CRAM-MD5') || $this->getCapability('AUTH=CRAM_MD5')) { 740 $auth_methods[] = 'CRAM-MD5'; 741 } 742 if ($this->getCapability('AUTH=PLAIN')) { 743 $auth_methods[] = 'PLAIN'; 744 } 745 745 // RFC 2595 (LOGINDISABLED) LOGIN disabled when connection is not secure 746 if (!$this->getCapability('LOGINDISABLED')) {747 $auth_methods[] = 'LOGIN';748 }749 }746 if (!$this->getCapability('LOGINDISABLED')) { 747 $auth_methods[] = 'LOGIN'; 748 } 749 } 750 750 else { 751 751 // Prevent from sending credentials in plain text when connection is not secure 752 if ($auth_method == 'LOGIN' && $this->getCapability('LOGINDISABLED')) {753 $this->setError(self::ERROR_BAD, "Login disabled by IMAP server");752 if ($auth_method == 'LOGIN' && $this->getCapability('LOGINDISABLED')) { 753 $this->setError(self::ERROR_BAD, "Login disabled by IMAP server"); 754 754 $this->closeConnection(); 755 return false;755 return false; 756 756 } 757 757 // replace AUTH with CRAM-MD5 for backward compat. … … 767 767 case 'DIGEST-MD5': 768 768 case 'CRAM-MD5': 769 case 'PLAIN':770 $result = $this->authenticate($user, $password, $method);771 break;769 case 'PLAIN': 770 $result = $this->authenticate($user, $password, $method); 771 break; 772 772 case 'LOGIN': 773 $result = $this->login($user, $password);773 $result = $this->login($user, $password); 774 774 break; 775 775 default: … … 777 777 } 778 778 779 if (is_resource($result)) {780 break;781 }782 }779 if (is_resource($result)) { 780 break; 781 } 782 } 783 783 784 784 // Connected and authenticated 785 if (is_resource($result)) {785 if (is_resource($result)) { 786 786 if ($this->prefs['force_caps']) { 787 $this->clearCapability();787 $this->clearCapability(); 788 788 } 789 789 $this->logged = true; 790 790 791 return true;791 return true; 792 792 } 793 793 … … 799 799 function connected() 800 800 { 801 return ($this->fp && $this->logged) ? true : false;801 return ($this->fp && $this->logged) ? true : false; 802 802 } 803 803 804 804 function closeConnection() 805 805 { 806 if ($this->putLine($this->nextTag() . ' LOGOUT')) {807 $this->readReply();808 } 809 810 @fclose($this->fp);811 $this->fp = false;806 if ($this->putLine($this->nextTag() . ' LOGOUT')) { 807 $this->readReply(); 808 } 809 810 @fclose($this->fp); 811 $this->fp = false; 812 812 } 813 813 … … 822 822 function select($mailbox) 823 823 { 824 if (!strlen($mailbox)) {825 return false;826 }827 828 if ($this->selected == $mailbox) {829 return true;830 }824 if (!strlen($mailbox)) { 825 return false; 826 } 827 828 if ($this->selected == $mailbox) { 829 return true; 830 } 831 831 /* 832 832 Temporary commented out because Courier returns \Noselect for INBOX … … 844 844 $response = explode("\r\n", $response); 845 845 foreach ($response as $line) { 846 if (preg_match('/^\* ([0-9]+) (EXISTS|RECENT)$/i', $line, $m)) {847 $this->data[strtoupper($m[2])] = (int) $m[1];848 }849 else if (preg_match('/^\* OK \[(UIDNEXT|UIDVALIDITY|UNSEEN) ([0-9]+)\]/i', $line, $match)) {850 $this->data[strtoupper($match[1])] = (int) $match[2];851 }852 else if (preg_match('/^\* OK \[PERMANENTFLAGS \(([^\)]+)\)\]/iU', $line, $match)) {853 $this->data['PERMANENTFLAGS'] = explode(' ', $match[1]);854 }846 if (preg_match('/^\* ([0-9]+) (EXISTS|RECENT)$/i', $line, $m)) { 847 $this->data[strtoupper($m[2])] = (int) $m[1]; 848 } 849 else if (preg_match('/^\* OK \[(UIDNEXT|UIDVALIDITY|UNSEEN) ([0-9]+)\]/i', $line, $match)) { 850 $this->data[strtoupper($match[1])] = (int) $match[2]; 851 } 852 else if (preg_match('/^\* OK \[PERMANENTFLAGS \(([^\)]+)\)\]/iU', $line, $match)) { 853 $this->data['PERMANENTFLAGS'] = explode(' ', $match[1]); 854 } 855 855 } 856 856 857 857 $this->data['READ-WRITE'] = $this->resultcode != 'READ-ONLY'; 858 858 859 $this->selected = $mailbox;860 return true;861 }859 $this->selected = $mailbox; 860 return true; 861 } 862 862 863 863 return false; … … 878 878 function status($mailbox, $items=array()) 879 879 { 880 if (!strlen($mailbox)) {881 return false;882 }880 if (!strlen($mailbox)) { 881 return false; 882 } 883 883 884 884 if (!in_array('MESSAGES', $items)) { … … 904 904 $this->data['STATUS:'.$mailbox] = $result; 905 905 906 return $result;907 }906 return $result; 907 } 908 908 909 909 return false; … … 921 921 function expunge($mailbox, $messages=NULL) 922 922 { 923 if (!$this->select($mailbox)) {923 if (!$this->select($mailbox)) { 924 924 return false; 925 925 } … … 933 933 unset($this->data['STATUS:'.$mailbox]); 934 934 935 if ($messages)936 $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE);937 else938 $result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE);939 940 if ($result == self::ERROR_OK) {941 $this->selected = ''; // state has changed, need to reselect942 return true;943 }944 945 return false;935 if ($messages) 936 $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE); 937 else 938 $result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE); 939 940 if ($result == self::ERROR_OK) { 941 $this->selected = ''; // state has changed, need to reselect 942 return true; 943 } 944 945 return false; 946 946 } 947 947 … … 962 962 } 963 963 964 return false;964 return false; 965 965 } 966 966 … … 975 975 function subscribe($mailbox) 976 976 { 977 $result = $this->execute('SUBSCRIBE', array($this->escape($mailbox)),978 self::COMMAND_NORESPONSE);979 980 return ($result == self::ERROR_OK);977 $result = $this->execute('SUBSCRIBE', array($this->escape($mailbox)), 978 self::COMMAND_NORESPONSE); 979 980 return ($result == self::ERROR_OK); 981 981 } 982 982 … … 991 991 function unsubscribe($mailbox) 992 992 { 993 $result = $this->execute('UNSUBSCRIBE', array($this->escape($mailbox)),994 self::COMMAND_NORESPONSE);995 996 return ($result == self::ERROR_OK);993 $result = $this->execute('UNSUBSCRIBE', array($this->escape($mailbox)), 994 self::COMMAND_NORESPONSE); 995 996 return ($result == self::ERROR_OK); 997 997 } 998 998 … … 1008 1008 { 1009 1009 $result = $this->execute('DELETE', array($this->escape($mailbox)), 1010 self::COMMAND_NORESPONSE);1011 1012 return ($result == self::ERROR_OK);1010 self::COMMAND_NORESPONSE); 1011 1012 return ($result == self::ERROR_OK); 1013 1013 } 1014 1014 … … 1023 1023 function clearFolder($mailbox) 1024 1024 { 1025 $num_in_trash = $this->countMessages($mailbox);1026 if ($num_in_trash > 0) {1027 $res = $this->delete($mailbox, '1:*');1028 }1025 $num_in_trash = $this->countMessages($mailbox); 1026 if ($num_in_trash > 0) { 1027 $res = $this->delete($mailbox, '1:*'); 1028 } 1029 1029 1030 1030 if ($res) { … … 1032 1032 $res = $this->close(); 1033 1033 else 1034 $res = $this->expunge($mailbox);1035 } 1036 1037 return $res;1034 $res = $this->expunge($mailbox); 1035 } 1036 1037 return $res; 1038 1038 } 1039 1039 … … 1048 1048 function countMessages($mailbox, $refresh = false) 1049 1049 { 1050 if ($refresh) {1051 $this->selected = '';1052 }1053 1054 if ($this->selected == $mailbox) {1055 return $this->data['EXISTS'];1056 }1050 if ($refresh) { 1051 $this->selected = ''; 1052 } 1053 1054 if ($this->selected == $mailbox) { 1055 return $this->data['EXISTS']; 1056 } 1057 1057 1058 1058 // Check internal cache … … 1081 1081 function countRecent($mailbox) 1082 1082 { 1083 if (!strlen($mailbox)) {1084 $mailbox = 'INBOX';1085 }1086 1087 $this->select($mailbox);1088 1089 if ($this->selected == $mailbox) {1090 return $this->data['RECENT'];1091 }1092 1093 return false;1083 if (!strlen($mailbox)) { 1084 $mailbox = 'INBOX'; 1085 } 1086 1087 $this->select($mailbox); 1088 1089 if ($this->selected == $mailbox) { 1090 return $this->data['RECENT']; 1091 } 1092 1093 return false; 1094 1094 } 1095 1095 … … 1127 1127 function sort($mailbox, $field, $add='', $is_uid=FALSE, $encoding = 'US-ASCII') 1128 1128 { 1129 $field = strtoupper($field);1130 if ($field == 'INTERNALDATE') {1131 $field = 'ARRIVAL';1132 }1133 1134 $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,1129 $field = strtoupper($field); 1130 if ($field == 'INTERNALDATE') { 1131 $field = 'ARRIVAL'; 1132 } 1133 1134 $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1, 1135 1135 'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1); 1136 1136 1137 if (!$fields[$field]) {1138 return false;1139 }1140 1141 if (!$this->select($mailbox)) {1142 return false;1143 }1144 1145 // message IDs1146 if (!empty($add))1147 $add = $this->compressMessageSet($add);1148 1149 list($code, $response) = $this->execute($is_uid ? 'UID SORT' : 'SORT',1150 array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : '')));1151 1152 if ($code == self::ERROR_OK) {1153 // remove prefix and \r\n from raw response1137 if (!$fields[$field]) { 1138 return false; 1139 } 1140 1141 if (!$this->select($mailbox)) { 1142 return false; 1143 } 1144 1145 // message IDs 1146 if (!empty($add)) 1147 $add = $this->compressMessageSet($add); 1148 1149 list($code, $response) = $this->execute($is_uid ? 'UID SORT' : 'SORT', 1150 array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : ''))); 1151 1152 if ($code == self::ERROR_OK) { 1153 // remove prefix and \r\n from raw response 1154 1154 $response = str_replace("\r\n", '', substr($response, 7)); 1155 return preg_split('/\s+/', $response, -1, PREG_SPLIT_NO_EMPTY);1156 }1155 return preg_split('/\s+/', $response, -1, PREG_SPLIT_NO_EMPTY); 1156 } 1157 1157 1158 1158 return false; … … 1161 1161 function fetchHeaderIndex($mailbox, $message_set, $index_field='', $skip_deleted=true, $uidfetch=false) 1162 1162 { 1163 if (is_array($message_set)) {1164 if (!($message_set = $this->compressMessageSet($message_set)))1165 return false;1166 } else {1167 list($from_idx, $to_idx) = explode(':', $message_set);1168 if (empty($message_set) ||1169 (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) {1170 return false;1171 }1172 }1173 1174 $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field);1175 1176 $fields_a['DATE'] = 1;1177 $fields_a['INTERNALDATE'] = 4;1178 $fields_a['ARRIVAL']= 4;1179 $fields_a['FROM'] = 1;1180 $fields_a['REPLY-TO'] = 1;1181 $fields_a['SENDER'] = 1;1182 $fields_a['TO'] = 1;1183 $fields_a['CC'] = 1;1184 $fields_a['SUBJECT'] = 1;1185 $fields_a['UID'] = 2;1186 $fields_a['SIZE'] = 2;1187 $fields_a['SEEN'] = 3;1188 $fields_a['RECENT'] = 3;1189 $fields_a['DELETED'] = 3;1190 1191 if (!($mode = $fields_a[$index_field])) {1192 return false;1193 }1194 1195 /* Do "SELECT" command */1196 if (!$this->select($mailbox)) {1197 return false;1198 }1199 1200 // build FETCH command string1201 $key = $this->nextTag();1202 $cmd = $uidfetch ? 'UID FETCH' : 'FETCH';1203 $deleted = $skip_deleted ? ' FLAGS' : '';1204 1205 if ($mode == 1 && $index_field == 'DATE')1206 $request = " $cmd $message_set (INTERNALDATE BODY.PEEK[HEADER.FIELDS (DATE)]$deleted)";1207 else if ($mode == 1)1208 $request = " $cmd $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)]$deleted)";1209 else if ($mode == 2) {1210 if ($index_field == 'SIZE')1211 $request = " $cmd $message_set (RFC822.SIZE$deleted)";1212 else1213 $request = " $cmd $message_set ($index_field$deleted)";1214 } else if ($mode == 3)1215 $request = " $cmd $message_set (FLAGS)";1216 else // 41217 $request = " $cmd $message_set (INTERNALDATE$deleted)";1218 1219 $request = $key . $request;1220 1221 if (!$this->putLine($request)) {1163 if (is_array($message_set)) { 1164 if (!($message_set = $this->compressMessageSet($message_set))) 1165 return false; 1166 } else { 1167 list($from_idx, $to_idx) = explode(':', $message_set); 1168 if (empty($message_set) || 1169 (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) { 1170 return false; 1171 } 1172 } 1173 1174 $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field); 1175 1176 $fields_a['DATE'] = 1; 1177 $fields_a['INTERNALDATE'] = 4; 1178 $fields_a['ARRIVAL'] = 4; 1179 $fields_a['FROM'] = 1; 1180 $fields_a['REPLY-TO'] = 1; 1181 $fields_a['SENDER'] = 1; 1182 $fields_a['TO'] = 1; 1183 $fields_a['CC'] = 1; 1184 $fields_a['SUBJECT'] = 1; 1185 $fields_a['UID'] = 2; 1186 $fields_a['SIZE'] = 2; 1187 $fields_a['SEEN'] = 3; 1188 $fields_a['RECENT'] = 3; 1189 $fields_a['DELETED'] = 3; 1190 1191 if (!($mode = $fields_a[$index_field])) { 1192 return false; 1193 } 1194 1195 /* Do "SELECT" command */ 1196 if (!$this->select($mailbox)) { 1197 return false; 1198 } 1199 1200 // build FETCH command string 1201 $key = $this->nextTag(); 1202 $cmd = $uidfetch ? 'UID FETCH' : 'FETCH'; 1203 $deleted = $skip_deleted ? ' FLAGS' : ''; 1204 1205 if ($mode == 1 && $index_field == 'DATE') 1206 $request = " $cmd $message_set (INTERNALDATE BODY.PEEK[HEADER.FIELDS (DATE)]$deleted)"; 1207 else if ($mode == 1) 1208 $request = " $cmd $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)]$deleted)"; 1209 else if ($mode == 2) { 1210 if ($index_field == 'SIZE') 1211 $request = " $cmd $message_set (RFC822.SIZE$deleted)"; 1212 else 1213 $request = " $cmd $message_set ($index_field$deleted)"; 1214 } else if ($mode == 3) 1215 $request = " $cmd $message_set (FLAGS)"; 1216 else // 4 1217 $request = " $cmd $message_set (INTERNALDATE$deleted)"; 1218 1219 $request = $key . $request; 1220 1221 if (!$this->putLine($request)) { 1222 1222 $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); 1223 return false;1224 } 1225 1226 $result = array();1227 1228 do {1229 $line = rtrim($this->readLine(200));1230 $line = $this->multLine($line);1231 1232 if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {1233 $id = $m[1];1234 $flags = NULL;1235 1236 if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {1237 $flags = explode(' ', strtoupper($matches[1]));1238 if (in_array('\\DELETED', $flags)) {1239 $deleted[$id] = $id;1240 continue;1241 }1242 }1243 1244 if ($mode == 1 && $index_field == 'DATE') {1245 if (preg_match('/BODY\[HEADER\.FIELDS \("*DATE"*\)\] (.*)/', $line, $matches)) {1246 $value = preg_replace(array('/^"*[a-z]+:/i'), '', $matches[1]);1247 $value = trim($value);1248 $result[$id] = $this->strToTime($value);1249 }1250 // non-existent/empty Date: header, use INTERNALDATE1251 if (empty($result[$id])) {1252 if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches))1253 $result[$id] = $this->strToTime($matches[1]);1254 else1255 $result[$id] = 0;1256 }1257 } else if ($mode == 1) {1258 if (preg_match('/BODY\[HEADER\.FIELDS \("?(FROM|REPLY-TO|SENDER|TO|SUBJECT)"?\)\] (.*)/', $line, $matches)) {1259 $value = preg_replace(array('/^"*[a-z]+:/i', '/\s+$/sm'), array('', ''), $matches[2]);1260 $result[$id] = trim($value);1261 } else {1262 $result[$id] = '';1263 }1264 } else if ($mode == 2) {1265 if (preg_match('/\((UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) {1266 $result[$id] = trim($matches[2]);1267 } else {1268 $result[$id] = 0;1269 }1270 } else if ($mode == 3) {1271 if (!$flags && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {1272 $flags = explode(' ', $matches[1]);1273 }1274 $result[$id] = in_array('\\'.$index_field, $flags) ? 1 : 0;1275 } else if ($mode == 4) {1276 if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) {1277 $result[$id] = $this->strToTime($matches[1]);1278 } else {1279 $result[$id] = 0;1280 }1281 }1282 }1283 } while (!$this->startsWith($line, $key, true, true));1284 1285 return $result;1223 return false; 1224 } 1225 1226 $result = array(); 1227 1228 do { 1229 $line = rtrim($this->readLine(200)); 1230 $line = $this->multLine($line); 1231 1232 if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { 1233 $id = $m[1]; 1234 $flags = NULL; 1235 1236 if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { 1237 $flags = explode(' ', strtoupper($matches[1])); 1238 if (in_array('\\DELETED', $flags)) { 1239 $deleted[$id] = $id; 1240 continue; 1241 } 1242 } 1243 1244 if ($mode == 1 && $index_field == 'DATE') { 1245 if (preg_match('/BODY\[HEADER\.FIELDS \("*DATE"*\)\] (.*)/', $line, $matches)) { 1246 $value = preg_replace(array('/^"*[a-z]+:/i'), '', $matches[1]); 1247 $value = trim($value); 1248 $result[$id] = $this->strToTime($value); 1249 } 1250 // non-existent/empty Date: header, use INTERNALDATE 1251 if (empty($result[$id])) { 1252 if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) 1253 $result[$id] = $this->strToTime($matches[1]); 1254 else 1255 $result[$id] = 0; 1256 } 1257 } else if ($mode == 1) { 1258 if (preg_match('/BODY\[HEADER\.FIELDS \("?(FROM|REPLY-TO|SENDER|TO|SUBJECT)"?\)\] (.*)/', $line, $matches)) { 1259 $value = preg_replace(array('/^"*[a-z]+:/i', '/\s+$/sm'), array('', ''), $matches[2]); 1260 $result[$id] = trim($value); 1261 } else { 1262 $result[$id] = ''; 1263 } 1264 } else if ($mode == 2) { 1265 if (preg_match('/\((UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) { 1266 $result[$id] = trim($matches[2]); 1267 } else { 1268 $result[$id] = 0; 1269 } 1270 } else if ($mode == 3) { 1271 if (!$flags && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { 1272 $flags = explode(' ', $matches[1]); 1273 } 1274 $result[$id] = in_array('\\'.$index_field, $flags) ? 1 : 0; 1275 } else if ($mode == 4) { 1276 if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) { 1277 $result[$id] = $this->strToTime($matches[1]); 1278 } else { 1279 $result[$id] = 0; 1280 } 1281 } 1282 } 1283 } while (!$this->startsWith($line, $key, true, true)); 1284 1285 return $result; 1286 1286 } 1287 1287 1288 1288 static function compressMessageSet($messages, $force=false) 1289 1289 { 1290 // given a comma delimited list of independent mid's,1291 // compresses by grouping sequences together1290 // given a comma delimited list of independent mid's, 1291 // compresses by grouping sequences together 1292 1292 1293 1293 if (!is_array($messages)) { 1294 // if less than 255 bytes long, let's not bother1295 if (!$force && strlen($messages)<255) {1296 return $messages;1297 }1298 1299 // see if it's already been compressed1300 if (strpos($messages, ':') !== false) {1301 return $messages;1302 }1303 1304 // separate, then sort1305 $messages = explode(',', $messages);1306 } 1307 1308 sort($messages);1309 1310 $result = array();1311 $start = $prev = $messages[0];1312 1313 foreach ($messages as $id) {1314 $incr = $id - $prev;1315 if ($incr > 1) { //found a gap1316 if ($start == $prev) {1317 $result[] = $prev; //push single id1318 } else {1319 $result[] = $start . ':' . $prev; //push sequence as start_id:end_id1320 }1321 $start = $id; //start of new sequence1322 }1323 $prev = $id;1324 }1325 1326 // handle the last sequence/id1327 if ($start == $prev) {1328 $result[] = $prev;1329 } else {1330 $result[] = $start.':'.$prev;1331 }1332 1333 // return as comma separated string1334 return implode(',', $result);1294 // if less than 255 bytes long, let's not bother 1295 if (!$force && strlen($messages)<255) { 1296 return $messages; 1297 } 1298 1299 // see if it's already been compressed 1300 if (strpos($messages, ':') !== false) { 1301 return $messages; 1302 } 1303 1304 // separate, then sort 1305 $messages = explode(',', $messages); 1306 } 1307 1308 sort($messages); 1309 1310 $result = array(); 1311 $start = $prev = $messages[0]; 1312 1313 foreach ($messages as $id) { 1314 $incr = $id - $prev; 1315 if ($incr > 1) { // found a gap 1316 if ($start == $prev) { 1317 $result[] = $prev; // push single id 1318 } else { 1319 $result[] = $start . ':' . $prev; // push sequence as start_id:end_id 1320 } 1321 $start = $id; // start of new sequence 1322 } 1323 $prev = $id; 1324 } 1325 1326 // handle the last sequence/id 1327 if ($start == $prev) { 1328 $result[] = $prev; 1329 } else { 1330 $result[] = $start.':'.$prev; 1331 } 1332 1333 // return as comma separated string 1334 return implode(',', $result); 1335 1335 } 1336 1336 1337 1337 static function uncompressMessageSet($messages) 1338 1338 { 1339 $result = array();1340 $messages = explode(',', $messages);1339 $result = array(); 1340 $messages = explode(',', $messages); 1341 1341 1342 1342 foreach ($messages as $part) { … … 1363 1363 function UID2ID($mailbox, $uid) 1364 1364 { 1365 if ($uid > 0) {1366 $id_a = $this->search($mailbox, "UID $uid");1367 if (is_array($id_a) && count($id_a) == 1) {1368 return (int) $id_a[0];1369 }1370 }1371 return null;1365 if ($uid > 0) { 1366 $id_a = $this->search($mailbox, "UID $uid"); 1367 if (is_array($id_a) && count($id_a) == 1) { 1368 return (int) $id_a[0]; 1369 } 1370 } 1371 return null; 1372 1372 } 1373 1373 … … 1383 1383 function ID2UID($mailbox, $id) 1384 1384 { 1385 if (empty($id) || $id < 0) {1386 return null;1387 }1388 1389 if (!$this->select($mailbox)) {1385 if (empty($id) || $id < 0) { 1386 return null; 1387 } 1388 1389 if (!$this->select($mailbox)) { 1390 1390 return null; 1391 1391 } … … 1394 1394 1395 1395 if ($code == self::ERROR_OK && preg_match("/^\* $id FETCH \(UID (.*)\)/i", $response, $m)) { 1396 return (int) $m[1];1397 } 1398 1399 return null;1396 return (int) $m[1]; 1397 } 1398 1399 return null; 1400 1400 } 1401 1401 1402 1402 function fetchUIDs($mailbox, $message_set=null) 1403 1403 { 1404 if (is_array($message_set))1405 $message_set = join(',', $message_set);1404 if (is_array($message_set)) 1405 $message_set = join(',', $message_set); 1406 1406 else if (empty($message_set)) 1407 $message_set = '1:*';1408 1409 return $this->fetchHeaderIndex($mailbox, $message_set, 'UID', false);1407 $message_set = '1:*'; 1408 1409 return $this->fetchHeaderIndex($mailbox, $message_set, 'UID', false); 1410 1410 } 1411 1411 1412 1412 function fetchHeaders($mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='') 1413 1413 { 1414 $result = array();1415 1416 if (!$this->select($mailbox)) {1417 return false;1418 }1419 1420 $message_set = $this->compressMessageSet($message_set);1421 1422 if ($add)1423 $add = ' '.trim($add);1424 1425 /* FETCH uid, size, flags and headers */1426 $key= $this->nextTag();1427 $request = $key . ($uidfetch ? ' UID' : '') . " FETCH $message_set ";1428 $request .= "(UID RFC822.SIZE FLAGS INTERNALDATE ";1429 if ($bodystr)1430 $request .= "BODYSTRUCTURE ";1431 $request .= "BODY.PEEK[HEADER.FIELDS (DATE FROM TO SUBJECT CONTENT-TYPE ";1432 $request .= "LIST-POST DISPOSITION-NOTIFICATION-TO".$add.")])";1433 1434 if (!$this->putLine($request)) {1414 $result = array(); 1415 1416 if (!$this->select($mailbox)) { 1417 return false; 1418 } 1419 1420 $message_set = $this->compressMessageSet($message_set); 1421 1422 if ($add) 1423 $add = ' '.trim($add); 1424 1425 /* FETCH uid, size, flags and headers */ 1426 $key = $this->nextTag(); 1427 $request = $key . ($uidfetch ? ' UID' : '') . " FETCH $message_set "; 1428 $request .= "(UID RFC822.SIZE FLAGS INTERNALDATE "; 1429 if ($bodystr) 1430 $request .= "BODYSTRUCTURE "; 1431 $request .= "BODY.PEEK[HEADER.FIELDS (DATE FROM TO SUBJECT CONTENT-TYPE "; 1432 $request .= "LIST-POST DISPOSITION-NOTIFICATION-TO".$add.")])"; 1433 1434 if (!$this->putLine($request)) { 1435 1435 $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); 1436 return false;1437 }1438 do {1439 $line = $this->readLine(4096);1440 $line = $this->multLine($line);1436 return false; 1437 } 1438 do { 1439 $line = $this->readLine(4096); 1440 $line = $this->multLine($line); 1441 1441 1442 1442 if (!$line) 1443 1443 break; 1444 1444 1445 if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { 1446 $id = intval($m[1]); 1447 1448 $result[$id] = new rcube_mail_header; 1449 $result[$id]->id = $id; 1450 $result[$id]->subject = ''; 1451 $result[$id]->messageID = 'mid:' . $id; 1452 1453 $lines = array(); 1454 $ln = 0; 1455 1456 // Sample reply line: 1457 // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen) 1458 // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...) 1459 // BODY[HEADER.FIELDS ... 1460 1461 if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/s', $line, $matches)) { 1462 $str = $matches[1]; 1463 1464 // swap parents with quotes, then explode 1465 $str = preg_replace('/[()]/', '"', $str); 1466 $a = rcube_explode_quoted_string(' ', $str); 1467 1468 // did we get the right number of replies? 1469 $parts_count = count($a); 1470 if ($parts_count>=6) { 1471 for ($i=0; $i<$parts_count; $i=$i+2) { 1472 if ($a[$i] == 'UID') 1473 $result[$id]->uid = intval($a[$i+1]); 1474 else if ($a[$i] == 'RFC822.SIZE') 1475 $result[$id]->size = intval($a[$i+1]); 1476 else if ($a[$i] == 'INTERNALDATE') 1477 $time_str = $a[$i+1]; 1478 else if ($a[$i] == 'FLAGS') 1479 $flags_str = $a[$i+1]; 1480 } 1481 1482 $time_str = str_replace('"', '', $time_str); 1483 1484 // if time is gmt... 1485 $time_str = str_replace('GMT','+0000',$time_str); 1486 1487 $result[$id]->internaldate = $time_str; 1488 $result[$id]->timestamp = $this->StrToTime($time_str); 1489 $result[$id]->date = $time_str; 1490 } 1491 1492 // BODYSTRUCTURE 1493 if($bodystr) { 1494 while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/s', $line, $m)) { 1495 $line2 = $this->readLine(1024); 1496 $line .= $this->multLine($line2, true); 1497 } 1498 $result[$id]->body_structure = $m[1]; 1499 } 1500 1501 // the rest of the result 1502 preg_match('/ BODY\[HEADER.FIELDS \(.*?\)\]\s*(.*)$/s', $line, $m); 1503 $reslines = explode("\n", trim($m[1], '"')); 1504 // re-parse (see below) 1505 foreach ($reslines as $resln) { 1506 if (ord($resln[0])<=32) { 1507 $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($resln); 1508 } else { 1509 $lines[++$ln] = trim($resln); 1510 } 1511 } 1512 } 1513 1514 // Start parsing headers. The problem is, some header "lines" take up multiple lines. 1515 // So, we'll read ahead, and if the one we're reading now is a valid header, we'll 1516 // process the previous line. Otherwise, we'll keep adding the strings until we come 1517 // to the next valid header line. 1518 1519 do { 1520 $line = rtrim($this->readLine(300), "\r\n"); 1521 1522 // The preg_match below works around communigate imap, which outputs " UID <number>)". 1523 // Without this, the while statement continues on and gets the "FH0 OK completed" message. 1524 // If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249. 1525 // This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing 1526 // If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin 1527 // An alternative might be: 1528 // if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break; 1529 // however, unsure how well this would work with all imap clients. 1530 if (preg_match("/^\s*UID [0-9]+\)$/", $line)) { 1531 break; 1532 } 1533 1534 // handle FLAGS reply after headers (AOL, Zimbra?) 1535 if (preg_match('/\s+FLAGS \((.*)\)\)$/', $line, $matches)) { 1536 $flags_str = $matches[1]; 1537 break; 1538 } 1539 1540 if (ord($line[0])<=32) { 1541 $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($line); 1542 } else { 1543 $lines[++$ln] = trim($line); 1544 } 1545 // patch from "Maksim Rubis" <siburny@hotmail.com> 1546 } while ($line[0] != ')' && !$this->startsWith($line, $key, true)); 1547 1548 if (strncmp($line, $key, strlen($key))) { 1549 // process header, fill rcube_mail_header obj. 1550 // initialize 1551 if (is_array($headers)) { 1552 reset($headers); 1553 while (list($k, $bar) = each($headers)) { 1554 $headers[$k] = ''; 1555 } 1556 } 1557 1558 // create array with header field:data 1559 while ( list($lines_key, $str) = each($lines) ) { 1560 list($field, $string) = $this->splitHeaderLine($str); 1561 1562 $field = strtolower($field); 1563 $string = preg_replace('/\n\s*/', ' ', $string); 1564 1565 switch ($field) { 1566 case 'date'; 1567 $result[$id]->date = $string; 1568 $result[$id]->timestamp = $this->strToTime($string); 1569 break; 1570 case 'from': 1571 $result[$id]->from = $string; 1572 break; 1573 case 'to': 1574 $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string); 1575 break; 1576 case 'subject': 1577 $result[$id]->subject = $string; 1578 break; 1579 case 'reply-to': 1580 $result[$id]->replyto = $string; 1581 break; 1582 case 'cc': 1583 $result[$id]->cc = $string; 1584 break; 1585 case 'bcc': 1586 $result[$id]->bcc = $string; 1587 break; 1588 case 'content-transfer-encoding': 1589 $result[$id]->encoding = $string; 1590 break; 1591 case 'content-type': 1592 $ctype_parts = preg_split('/[; ]/', $string); 1593 $result[$id]->ctype = array_shift($ctype_parts); 1594 if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) { 1595 $result[$id]->charset = $regs[1]; 1596 } 1597 break; 1598 case 'in-reply-to': 1599 $result[$id]->in_reply_to = str_replace(array("\n", '<', '>'), '', $string); 1600 break; 1601 case 'references': 1602 $result[$id]->references = $string; 1603 break; 1604 case 'return-receipt-to': 1605 case 'disposition-notification-to': 1606 case 'x-confirm-reading-to': 1607 $result[$id]->mdn_to = $string; 1608 break; 1609 case 'message-id': 1610 $result[$id]->messageID = $string; 1611 break; 1612 case 'x-priority': 1613 if (preg_match('/^(\d+)/', $string, $matches)) 1614 $result[$id]->priority = intval($matches[1]); 1615 break; 1616 default: 1617 if (strlen($field) > 2) 1618 $result[$id]->others[$field] = $string; 1619 break; 1620 } // end switch () 1621 } // end while () 1622 } 1623 1624 // process flags 1625 if (!empty($flags_str)) { 1626 $flags_str = preg_replace('/[\\\"]/', '', $flags_str); 1627 $flags_a = explode(' ', $flags_str); 1628 1629 if (is_array($flags_a)) { 1630 foreach($flags_a as $flag) { 1631 $flag = strtoupper($flag); 1632 if ($flag == 'SEEN') { 1633 $result[$id]->seen = true; 1634 } else if ($flag == 'DELETED') { 1635 $result[$id]->deleted = true; 1636 } else if ($flag == 'RECENT') { 1637 $result[$id]->recent = true; 1638 } else if ($flag == 'ANSWERED') { 1639 $result[$id]->answered = true; 1640 } else if ($flag == '$FORWARDED') { 1641 $result[$id]->forwarded = true; 1642 } else if ($flag == 'DRAFT') { 1643 $result[$id]->is_draft = true; 1644 } else if ($flag == '$MDNSENT') { 1645 $result[$id]->mdn_sent = true; 1646 } else if ($flag == 'FLAGGED') { 1647 $result[$id]->flagged = true; 1648 } 1649 } 1650 $result[$id]->flags = $flags_a; 1651 } 1652 } 1653 } 1654 } while (!$this->startsWith($line, $key, true)); 1655 1656 return $result; 1445 if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { 1446 $id = intval($m[1]); 1447 1448 $result[$id] = new rcube_mail_header; 1449 $result[$id]->id = $id; 1450 $result[$id]->subject = ''; 1451 $result[$id]->messageID = 'mid:' . $id; 1452 1453 $lines = array(); 1454 $ln = 0; 1455 1456 // Sample reply line: 1457 // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen) 1458 // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...) 1459 // BODY[HEADER.FIELDS ... 1460 1461 if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/s', $line, $matches)) { 1462 $str = $matches[1]; 1463 1464 // swap parents with quotes, then explode 1465 $str = preg_replace('/[()]/', '"', $str); 1466 $a = rcube_explode_quoted_string(' ', $str); 1467 1468 // did we get the right number of replies? 1469 $parts_count = count($a); 1470 if ($parts_count>=6) { 1471 for ($i=0; $i<$parts_count; $i=$i+2) { 1472 if ($a[$i] == 'UID') { 1473 $result[$id]->uid = intval($a[$i+1]); 1474 } 1475 else if ($a[$i] == 'RFC822.SIZE') { 1476 $result[$id]->size = intval($a[$i+1]); 1477 } 1478 else if ($a[$i] == 'INTERNALDATE') { 1479 $time_str = $a[$i+1]; 1480 } 1481 else if ($a[$i] == 'FLAGS') { 1482 $flags_str = $a[$i+1]; 1483 } 1484 } 1485 1486 $time_str = str_replace('"', '', $time_str); 1487 1488 // if time is gmt... 1489 $time_str = str_replace('GMT','+0000',$time_str); 1490 1491 $result[$id]->internaldate = $time_str; 1492 $result[$id]->timestamp = $this->StrToTime($time_str); 1493 $result[$id]->date = $time_str; 1494 } 1495 1496 // BODYSTRUCTURE 1497 if ($bodystr) { 1498 while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/s', $line, $m)) { 1499 $line2 = $this->readLine(1024); 1500 $line .= $this->multLine($line2, true); 1501 } 1502 $result[$id]->body_structure = $m[1]; 1503 } 1504 1505 // the rest of the result 1506 if (preg_match('/ BODY\[HEADER.FIELDS \(.*?\)\]\s*(.*)$/s', $line, $m)) { 1507 $reslines = explode("\n", trim($m[1], '"')); 1508 // re-parse (see below) 1509 foreach ($reslines as $resln) { 1510 if (ord($resln[0])<=32) { 1511 $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($resln); 1512 } else { 1513 $lines[++$ln] = trim($resln); 1514 } 1515 } 1516 } 1517 } 1518 1519 // Start parsing headers. The problem is, some header "lines" take up multiple lines. 1520 // So, we'll read ahead, and if the one we're reading now is a valid header, we'll 1521 // process the previous line. Otherwise, we'll keep adding the strings until we come 1522 // to the next valid header line. 1523 1524 do { 1525 $line = rtrim($this->readLine(300), "\r\n"); 1526 1527 // The preg_match below works around communigate imap, which outputs " UID <number>)". 1528 // Without this, the while statement continues on and gets the "FH0 OK completed" message. 1529 // If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249. 1530 // This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing 1531 // If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin 1532 // An alternative might be: 1533 // if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break; 1534 // however, unsure how well this would work with all imap clients. 1535 if (preg_match("/^\s*UID [0-9]+\)$/", $line)) { 1536 break; 1537 } 1538 1539 // handle FLAGS reply after headers (AOL, Zimbra?) 1540 if (preg_match('/\s+FLAGS \((.*)\)\)$/', $line, $matches)) { 1541 $flags_str = $matches[1]; 1542 break; 1543 } 1544 1545 if (ord($line[0])<=32) { 1546 $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($line); 1547 } else { 1548 $lines[++$ln] = trim($line); 1549 } 1550 // patch from "Maksim Rubis" <siburny@hotmail.com> 1551 } while ($line[0] != ')' && !$this->startsWith($line, $key, true)); 1552 1553 if (strncmp($line, $key, strlen($key))) { 1554 // process header, fill rcube_mail_header obj. 1555 // initialize 1556 if (is_array($headers)) { 1557 reset($headers); 1558 while (list($k, $bar) = each($headers)) { 1559 $headers[$k] = ''; 1560 } 1561 } 1562 1563 // create array with header field:data 1564 while (list($lines_key, $str) = each($lines)) { 1565 list($field, $string) = $this->splitHeaderLine($str); 1566 1567 $field = strtolower($field); 1568 $string = preg_replace('/\n\s*/', ' ', $string); 1569 1570 switch ($field) { 1571 case 'date'; 1572 $result[$id]->date = $string; 1573 $result[$id]->timestamp = $this->strToTime($string); 1574 break; 1575 case 'from': 1576 $result[$id]->from = $string; 1577 break; 1578 case 'to': 1579 $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string); 1580 break; 1581 case 'subject': 1582 $result[$id]->subject = $string; 1583 break; 1584 case 'reply-to': 1585 $result[$id]->replyto = $string; 1586 break; 1587 case 'cc': 1588 $result[$id]->cc = $string; 1589 break; 1590 case 'bcc': 1591 $result[$id]->bcc = $string; 1592 break; 1593 case 'content-transfer-encoding': 1594 $result[$id]->encoding = $string; 1595 break; 1596 case 'content-type': 1597 $ctype_parts = preg_split('/[; ]/', $string); 1598 $result[$id]->ctype = array_shift($ctype_parts); 1599 if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) { 1600 $result[$id]->charset = $regs[1]; 1601 } 1602 break; 1603 case 'in-reply-to': 1604 $result[$id]->in_reply_to = str_replace(array("\n", '<', '>'), '', $string); 1605 break; 1606 case 'references': 1607 $result[$id]->references = $string; 1608 break; 1609 case 'return-receipt-to': 1610 case 'disposition-notification-to': 1611 case 'x-confirm-reading-to': 1612 $result[$id]->mdn_to = $string; 1613 break; 1614 case 'message-id': 1615 $result[$id]->messageID = $string; 1616 break; 1617 case 'x-priority': 1618 if (preg_match('/^(\d+)/', $string, $matches)) { 1619 $result[$id]->priority = intval($matches[1]); 1620 } 1621 break; 1622 default: 1623 if (strlen($field) > 2) { 1624 $result[$id]->others[$field] = $string; 1625 } 1626 break; 1627 } // end switch () 1628 } // end while () 1629 } 1630 1631 // process flags 1632 if (!empty($flags_str)) { 1633 $flags_str = preg_replace('/[\\\"]/', '', $flags_str); 1634 $flags_a = explode(' ', $flags_str); 1635 1636 if (is_array($flags_a)) { 1637 foreach($flags_a as $flag) { 1638 $flag = strtoupper($flag); 1639 if ($flag == 'SEEN') { 1640 $result[$id]->seen = true; 1641 } else if ($flag == 'DELETED') { 1642 $result[$id]->deleted = true; 1643 } else if ($flag == 'RECENT') { 1644 $result[$id]->recent = true; 1645 } else if ($flag == 'ANSWERED') { 1646 $result[$id]->answered = true; 1647 } else if ($flag == '$FORWARDED') { 1648 $result[$id]->forwarded = true; 1649 } else if ($flag == 'DRAFT') { 1650 $result[$id]->is_draft = true; 1651 } else if ($flag == '$MDNSENT') { 1652 $result[$id]->mdn_sent = true; 1653 } else if ($flag == 'FLAGGED') { 1654 $result[$id]->flagged = true; 1655 } 1656 } 1657 $result[$id]->flags = $flags_a; 1658 } 1659 } 1660 } 1661 } while (!$this->startsWith($line, $key, true)); 1662 1663 return $result; 1657 1664 } 1658 1665 1659 1666 function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='') 1660 1667 { 1661 $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add);1662 if (is_array($a)) {1663 return array_shift($a);1664 }1665 return false;1668 $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add); 1669 if (is_array($a)) { 1670 return array_shift($a); 1671 } 1672 return false; 1666 1673 } 1667 1674 1668 1675 function sortHeaders($a, $field, $flag) 1669 1676 { 1670 if (empty($field)) {1671 $field = 'uid';1672 }1677 if (empty($field)) { 1678 $field = 'uid'; 1679 } 1673 1680 else { 1674 $field = strtolower($field);1675 } 1676 1677 if ($field == 'date' || $field == 'internaldate') {1678 $field = 'timestamp';1679 }1680 1681 if (empty($flag)) {1682 $flag = 'ASC';1683 } else {1684 $flag = strtoupper($flag);1685 } 1686 1687 $c = count($a);1688 if ($c > 0) {1689 // Strategy:1690 // First, we'll create an "index" array.1691 // Then, we'll use sort() on that array,1692 // and use that to sort the main array.1693 1694 // create "index" array1695 $index = array();1696 reset($a);1697 while (list($key, $val) = each($a)) {1698 if ($field == 'timestamp') {1699 $data = $this->strToTime($val->date);1700 if (!$data) {1701 $data = $val->timestamp;1702 }1703 } else {1704 $data = $val->$field;1705 if (is_string($data)) {1706 $data = str_replace('"', '', $data);1707 if ($field == 'subject') {1708 $data = preg_replace('/^(Re: \s*|Fwd:\s*|Fw:\s*)+/i', '', $data);1681 $field = strtolower($field); 1682 } 1683 1684 if ($field == 'date' || $field == 'internaldate') { 1685 $field = 'timestamp'; 1686 } 1687 1688 if (empty($flag)) { 1689 $flag = 'ASC'; 1690 } else { 1691 $flag = strtoupper($flag); 1692 } 1693 1694 $c = count($a); 1695 if ($c > 0) { 1696 // Strategy: 1697 // First, we'll create an "index" array. 1698 // Then, we'll use sort() on that array, 1699 // and use that to sort the main array. 1700 1701 // create "index" array 1702 $index = array(); 1703 reset($a); 1704 while (list($key, $val) = each($a)) { 1705 if ($field == 'timestamp') { 1706 $data = $this->strToTime($val->date); 1707 if (!$data) { 1708 $data = $val->timestamp; 1709 } 1710 } else { 1711 $data = $val->$field; 1712 if (is_string($data)) { 1713 $data = str_replace('"', '', $data); 1714 if ($field == 'subject') { 1715 $data = preg_replace('/^(Re: \s*|Fwd:\s*|Fw:\s*)+/i', '', $data); 1709 1716 } 1710 $data = strtoupper($data);1711 }1712 }1713 $index[$key] = $data;1714 }1715 1716 // sort index1717 if ($flag == 'ASC') {1718 asort($index);1719 } else {1720 arsort($index);1721 }1722 1723 // form new array based on index1724 $result = array();1725 reset($index);1726 while (list($key, $val) = each($index)) {1727 $result[$key] = $a[$key];1728 }1729 }1730 1731 return $result;1717 $data = strtoupper($data); 1718 } 1719 } 1720 $index[$key] = $data; 1721 } 1722 1723 // sort index 1724 if ($flag == 'ASC') { 1725 asort($index); 1726 } else { 1727 arsort($index); 1728 } 1729 1730 // form new array based on index 1731 $result = array(); 1732 reset($index); 1733 while (list($key, $val) = each($index)) { 1734 $result[$key] = $a[$key]; 1735 } 1736 } 1737 1738 return $result; 1732 1739 } 1733 1740 … … 1735 1742 function modFlag($mailbox, $messages, $flag, $mod) 1736 1743 { 1737 if ($mod != '+' && $mod != '-') {1744 if ($mod != '+' && $mod != '-') { 1738 1745 $mod = '+'; 1739 }1740 1741 if (!$this->select($mailbox)) {1742 return false;1743 }1746 } 1747 1748 if (!$this->select($mailbox)) { 1749 return false; 1750 } 1744 1751 1745 1752 if (!$this->data['READ-WRITE']) { … … 1753 1760 } 1754 1761 1755 $flag = $this->flags[strtoupper($flag)];1762 $flag = $this->flags[strtoupper($flag)]; 1756 1763 $result = $this->execute('UID STORE', array( 1757 1764 $this->compressMessageSet($messages), $mod . 'FLAGS.SILENT', "($flag)"), 1758 1765 self::COMMAND_NORESPONSE); 1759 1766 1760 return ($result == self::ERROR_OK);1767 return ($result == self::ERROR_OK); 1761 1768 } 1762 1769 1763 1770 function flag($mailbox, $messages, $flag) { 1764 return $this->modFlag($mailbox, $messages, $flag, '+');1771 return $this->modFlag($mailbox, $messages, $flag, '+'); 1765 1772 } 1766 1773 1767 1774 function unflag($mailbox, $messages, $flag) { 1768 return $this->modFlag($mailbox, $messages, $flag, '-');1775 return $this->modFlag($mailbox, $messages, $flag, '-'); 1769 1776 } 1770 1777 1771 1778 function delete($mailbox, $messages) { 1772 return $this->modFlag($mailbox, $messages, 'DELETED', '+');1779 return $this->modFlag($mailbox, $messages, 'DELETED', '+'); 1773 1780 } 1774 1781 1775 1782 function copy($messages, $from, $to) 1776 1783 { 1777 if (!$this->select($from)) {1778 return false;1779 }1784 if (!$this->select($from)) { 1785 return false; 1786 } 1780 1787 1781 1788 // Clear internal status cache … … 1786 1793 self::COMMAND_NORESPONSE); 1787 1794 1788 return ($result == self::ERROR_OK);1795 return ($result == self::ERROR_OK); 1789 1796 } 1790 1797 1791 1798 function move($messages, $from, $to) 1792 1799 { 1793 if (!$this->select($from)) {1794 return false;1795 }1800 if (!$this->select($from)) { 1801 return false; 1802 } 1796 1803 1797 1804 if (!$this->data['READ-WRITE']) { … … 1816 1823 private function parseThread($str, $begin, $end, $root, $parent, $depth, &$depthmap, &$haschildren) 1817 1824 { 1818 $node = array();1819 if ($str[$begin] != '(') {1820 $stop = $begin + strspn($str, '1234567890', $begin, $end - $begin);1821 $msg = substr($str, $begin, $stop - $begin);1822 if ($msg == 0)1823 return $node;1824 if (is_null($root))1825 $root = $msg;1826 $depthmap[$msg] = $depth;1827 $haschildren[$msg] = false;1828 if (!is_null($parent))1829 $haschildren[$parent] = true;1830 if ($stop + 1 < $end)1831 $node[$msg] = $this->parseThread($str, $stop + 1, $end, $root, $msg, $depth + 1, $depthmap, $haschildren);1832 else1833 $node[$msg] = array();1834 } else {1835 $off = $begin;1836 while ($off < $end) {1837 $start = $off;1838 $off++;1839 $n = 1;1840 while ($n > 0) {1841 $p = strpos($str, ')', $off);1842 if ($p === false) {1843 error_log('Mismatched brackets parsing IMAP THREAD response:');1844 error_log(substr($str, ($begin < 10) ? 0 : ($begin - 10), $end - $begin + 20));1845 error_log(str_repeat(' ', $off - (($begin < 10) ? 0 : ($begin - 10))));1846 return $node;1847 }1848 $p1 = strpos($str, '(', $off);1849 if ($p1 !== false && $p1 < $p) {1850 $off = $p1 + 1;1851 $n++;1852 } else {1853 $off = $p + 1;1854 $n--;1855 }1856 }1857 $node += $this->parseThread($str, $start + 1, $off - 1, $root, $parent, $depth, $depthmap, $haschildren);1858 }1859 }1860 1861 return $node;1825 $node = array(); 1826 if ($str[$begin] != '(') { 1827 $stop = $begin + strspn($str, '1234567890', $begin, $end - $begin); 1828 $msg = substr($str, $begin, $stop - $begin); 1829 if ($msg == 0) 1830 return $node; 1831 if (is_null($root)) 1832 $root = $msg; 1833 $depthmap[$msg] = $depth; 1834 $haschildren[$msg] = false; 1835 if (!is_null($parent)) 1836 $haschildren[$parent] = true; 1837 if ($stop + 1 < $end) 1838 $node[$msg] = $this->parseThread($str, $stop + 1, $end, $root, $msg, $depth + 1, $depthmap, $haschildren); 1839 else 1840 $node[$msg] = array(); 1841 } else { 1842 $off = $begin; 1843 while ($off < $end) { 1844 $start = $off; 1845 $off++; 1846 $n = 1; 1847 while ($n > 0) { 1848 $p = strpos($str, ')', $off); 1849 if ($p === false) { 1850 error_log("Mismatched brackets parsing IMAP THREAD response:"); 1851 error_log(substr($str, ($begin < 10) ? 0 : ($begin - 10), $end - $begin + 20)); 1852 error_log(str_repeat(' ', $off - (($begin < 10) ? 0 : ($begin - 10)))); 1853 return $node; 1854 } 1855 $p1 = strpos($str, '(', $off); 1856 if ($p1 !== false && $p1 < $p) { 1857 $off = $p1 + 1; 1858 $n++; 1859 } else { 1860 $off = $p + 1; 1861 $n--; 1862 } 1863 } 1864 $node += $this->parseThread($str, $start + 1, $off - 1, $root, $parent, $depth, $depthmap, $haschildren); 1865 } 1866 } 1867 1868 return $node; 1862 1869 } 1863 1870 … … 1866 1873 $old_sel = $this->selected; 1867 1874 1868 if (!$this->select($mailbox)) {1869 return false;1870 }1875 if (!$this->select($mailbox)) { 1876 return false; 1877 } 1871 1878 1872 1879 // return empty result when folder is empty and we're just after SELECT 1873 1880 if ($old_sel != $mailbox && !$this->data['EXISTS']) { 1874 1881 return array(array(), array(), array()); 1875 }1876 1877 $encoding = $encoding ? trim($encoding) : 'US-ASCII';1878 $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES';1879 $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL';1882 } 1883 1884 $encoding = $encoding ? trim($encoding) : 'US-ASCII'; 1885 $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES'; 1886 $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL'; 1880 1887 $data = ''; 1881 1888 … … 1883 1890 $algorithm, $encoding, $criteria)); 1884 1891 1885 if ($code == self::ERROR_OK && preg_match('/^\* THREAD /i', $response)) {1886 // remove prefix and \r\n from raw response1887 $response = str_replace("\r\n", '', substr($response, 9));1892 if ($code == self::ERROR_OK && preg_match('/^\* THREAD /i', $response)) { 1893 // remove prefix and \r\n from raw response 1894 $response = str_replace("\r\n", '', substr($response, 9)); 1888 1895 $depthmap = array(); 1889 1896 $haschildren = array(); … … 1893 1900 1894 1901 return array($tree, $depthmap, $haschildren); 1895 }1896 1897 return false;1902 } 1903 1904 return false; 1898 1905 } 1899 1906 … … 1912 1919 $old_sel = $this->selected; 1913 1920 1914 if (!$this->select($mailbox)) {1915 return false;1916 }1921 if (!$this->select($mailbox)) { 1922 return false; 1923 } 1917 1924 1918 1925 // return empty result when folder is empty and we're just after SELECT … … 1922 1929 else 1923 1930 return array(); 1924 }1931 } 1925 1932 1926 1933 $esearch = empty($items) ? false : $this->getCapability('ESEARCH'); … … 1939 1946 } 1940 1947 1941 list($code, $response) = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH',1942 array($params));1943 1944 if ($code == self::ERROR_OK) {1945 // remove prefix and \r\n from raw response1948 list($code, $response) = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH', 1949 array($params)); 1950 1951 if ($code == self::ERROR_OK) { 1952 // remove prefix and \r\n from raw response 1946 1953 $response = substr($response, $esearch ? 10 : 9); 1947 $response = str_replace("\r\n", '', $response);1954 $response = str_replace("\r\n", '', $response); 1948 1955 1949 1956 if ($esearch) { … … 1963 1970 return $result; 1964 1971 } 1965 else {1972 else { 1966 1973 $response = preg_split('/\s+/', $response, -1, PREG_SPLIT_NO_EMPTY); 1967 1974 1968 1975 if (!empty($items)) { 1969 1976 $result = array(); 1970 if (in_array('COUNT', $items)) 1977 if (in_array('COUNT', $items)) { 1971 1978 $result['COUNT'] = count($response); 1972 if (in_array('MIN', $items)) 1979 } 1980 if (in_array('MIN', $items)) { 1973 1981 $result['MIN'] = !empty($response) ? min($response) : 0; 1974 if (in_array('MAX', $items)) 1982 } 1983 if (in_array('MAX', $items)) { 1975 1984 $result['MAX'] = !empty($response) ? max($response) : 0; 1976 if (in_array('ALL', $items)) 1985 } 1986 if (in_array('ALL', $items)) { 1977 1987 $result['ALL'] = $this->compressMessageSet($response, true); 1988 } 1978 1989 1979 1990 return $result; … … 1982 1993 return $response; 1983 1994 } 1984 }1985 } 1986 1987 return false;1995 } 1996 } 1997 1998 return false; 1988 1999 } 1989 2000 … … 2039 2050 $status_opts=array(), $select_opts=array()) 2040 2051 { 2041 if (!strlen($mailbox)) {2042 $mailbox = '*';2043 }2052 if (!strlen($mailbox)) { 2053 $mailbox = '*'; 2054 } 2044 2055 2045 2056 $args = array(); … … 2073 2084 // Add to result array 2074 2085 if (!$lstatus) { 2075 $folders[] = $mailbox;2086 $folders[] = $mailbox; 2076 2087 } 2077 2088 else { … … 2097 2108 } 2098 2109 } 2099 }2110 } 2100 2111 2101 2112 return $folders; 2102 2113 } 2103 2114 2104 return false;2115 return false; 2105 2116 } 2106 2117 2107 2118 function fetchMIMEHeaders($mailbox, $id, $parts, $mime=true) 2108 2119 { 2109 if (!$this->select($mailbox)) {2110 return false;2111 }2112 2113 $result = false;2114 $parts = (array) $parts;2115 $key = $this->nextTag();2116 $peeks = '';2117 $idx = 0;2120 if (!$this->select($mailbox)) { 2121 return false; 2122 } 2123 2124 $result = false; 2125 $parts = (array) $parts; 2126 $key = $this->nextTag(); 2127 $peeks = ''; 2128 $idx = 0; 2118 2129 $type = $mime ? 'MIME' : 'HEADER'; 2119 2130 2120 // format request 2121 foreach($parts as $part) 2122 $peeks[] = "BODY.PEEK[$part.$type]"; 2123 2124 $request = "$key FETCH $id (" . implode(' ', $peeks) . ')'; 2125 2126 // send request 2127 if (!$this->putLine($request)) { 2131 // format request 2132 foreach($parts as $part) { 2133 $peeks[] = "BODY.PEEK[$part.$type]"; 2134 } 2135 2136 $request = "$key FETCH $id (" . implode(' ', $peeks) . ')'; 2137 2138 // send request 2139 if (!$this->putLine($request)) { 2128 2140 $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); 2129 return false;2130 }2131 2132 do {2133 $line = $this->readLine(1024);2134 $line = $this->multLine($line);2135 2136 if (preg_match('/BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) {2137 $idx = $matches[1];2138 $result[$idx] = preg_replace('/^(\* '.$id.' FETCH \()?\s*BODY\['.$idx.'\.'.$type.'\]\s+/', '', $line);2139 $result[$idx] = trim($result[$idx], '"');2140 $result[$idx] = rtrim($result[$idx], "\t\r\n\0\x0B");2141 }2142 } while (!$this->startsWith($line, $key, true));2143 2144 return $result;2141 return false; 2142 } 2143 2144 do { 2145 $line = $this->readLine(1024); 2146 $line = $this->multLine($line); 2147 2148 if (preg_match('/BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) { 2149 $idx = $matches[1]; 2150 $result[$idx] = preg_replace('/^(\* '.$id.' FETCH \()?\s*BODY\['.$idx.'\.'.$type.'\]\s+/', '', $line); 2151 $result[$idx] = trim($result[$idx], '"'); 2152 $result[$idx] = rtrim($result[$idx], "\t\r\n\0\x0B"); 2153 } 2154 } while (!$this->startsWith($line, $key, true)); 2155 2156 return $result; 2145 2157 } 2146 2158 2147 2159 function fetchPartHeader($mailbox, $id, $is_uid=false, $part=NULL) 2148 2160 { 2149 $part = empty($part) ? 'HEADER' : $part.'.MIME';2161 $part = empty($part) ? 'HEADER' : $part.'.MIME'; 2150 2162 2151 2163 return $this->handlePartBody($mailbox, $id, $is_uid, $part); … … 2154 2166 function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL) 2155 2167 { 2156 if (!$this->select($mailbox)) {2157 return false; 2158 } 2159 2160 switch ($encoding) {2161 case 'base64':2162 $mode = 1;2163 break;2164 case 'quoted-printable':2165 $mode = 2;2166 break;2167 case 'x-uuencode':2168 case 'x-uue':2169 case 'uue':2170 case 'uuencode':2171 $mode = 3;2172 break;2173 default:2174 $mode = 0;2175 }2176 2177 // format request2178 $reply_key = '* ' . $id;2179 $key = $this->nextTag();2180 $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id (BODY.PEEK[$part])";2181 2182 // send request2183 if (!$this->putLine($request)) {2168 if (!$this->select($mailbox)) { 2169 return false; 2170 } 2171 2172 switch ($encoding) { 2173 case 'base64': 2174 $mode = 1; 2175 break; 2176 case 'quoted-printable': 2177 $mode = 2; 2178 break; 2179 case 'x-uuencode': 2180 case 'x-uue': 2181 case 'uue': 2182 case 'uuencode': 2183 $mode = 3; 2184 break; 2185 default: 2186 $mode = 0; 2187 } 2188 2189 // format request 2190 $reply_key = '* ' . $id; 2191 $key = $this->nextTag(); 2192 $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id (BODY.PEEK[$part])"; 2193 2194 // send request 2195 if (!$this->putLine($request)) { 2184 2196 $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); 2185 return false; 2186 } 2187 2188 // receive reply line 2189 do { 2190 $line = rtrim($this->readLine(1024)); 2191 $a = explode(' ', $line); 2192 } while (!($end = $this->startsWith($line, $key, true)) && $a[2] != 'FETCH'); 2193 2194 $len = strlen($line); 2195 $result = false; 2196 2197 // handle empty "* X FETCH ()" response 2198 if ($line[$len-1] == ')' && $line[$len-2] != '(') { 2199 // one line response, get everything between first and last quotes 2200 if (substr($line, -4, 3) == 'NIL') { 2201 // NIL response 2202 $result = ''; 2203 } else { 2204 $from = strpos($line, '"') + 1; 2205 $to = strrpos($line, '"'); 2206 $len = $to - $from; 2207 $result = substr($line, $from, $len); 2208 } 2209 2210 if ($mode == 1) 2211 $result = base64_decode($result); 2212 else if ($mode == 2) 2213 $result = quoted_printable_decode($result); 2214 else if ($mode == 3) 2215 $result = convert_uudecode($result); 2216 2217 } else if ($line[$len-1] == '}') { 2218 // multi-line request, find sizes of content and receive that many bytes 2219 $from = strpos($line, '{') + 1; 2220 $to = strrpos($line, '}'); 2221 $len = $to - $from; 2222 $sizeStr = substr($line, $from, $len); 2223 $bytes = (int)$sizeStr; 2224 $prev = ''; 2225 2226 while ($bytes > 0) { 2227 $line = $this->readLine(4096); 2228 2229 if ($line === NULL) 2230 break; 2231 2232 $len = strlen($line); 2233 2234 if ($len > $bytes) { 2235 $line = substr($line, 0, $bytes); 2236 $len = strlen($line); 2237 } 2238 $bytes -= $len; 2197 return false; 2198 } 2199 2200 // receive reply line 2201 do { 2202 $line = rtrim($this->readLine(1024)); 2203 $a = explode(' ', $line); 2204 } while (!($end = $this->startsWith($line, $key, true)) && $a[2] != 'FETCH'); 2205 2206 $len = strlen($line); 2207 $result = false; 2208 2209 // handle empty "* X FETCH ()" response 2210 if ($line[$len-1] == ')' && $line[$len-2] != '(') { 2211 // one line response, get everything between first and last quotes 2212 if (substr($line, -4, 3) == 'NIL') { 2213 // NIL response 2214 $result = ''; 2215 } else { 2216 $from = strpos($line, '"') + 1; 2217 $to = strrpos($line, '"'); 2218 $len = $to - $from; 2219 $result = substr($line, $from, $len); 2220 } 2221 2222 if ($mode == 1) { 2223 $result = base64_decode($result); 2224 } 2225 else if ($mode == 2) { 2226 $result = quoted_printable_decode($result); 2227 } 2228 else if ($mode == 3) { 2229 $result = convert_uudecode($result); 2230 } 2231 2232 } else if ($line[$len-1] == '}') { 2233 // multi-line request, find sizes of content and receive that many bytes 2234 $from = strpos($line, '{') + 1; 2235 $to = strrpos($line, '}'); 2236 $len = $to - $from; 2237 $sizeStr = substr($line, $from, $len); 2238 $bytes = (int)$sizeStr; 2239 $prev = ''; 2240 2241 while ($bytes > 0) { 2242 $line = $this->readLine(4096); 2243 2244 if ($line === NULL) { 2245 break; 2246 } 2247 2248 $len = strlen($line); 2249 2250 if ($len > $bytes) { 2251 $line = substr($line, 0, $bytes); 2252 $len = strlen($line); 2253 } 2254 $bytes -= $len; 2239 2255 2240 2256 // BASE64 2241 if ($mode == 1) {2242 $line = rtrim($line, "\t\r\n\0\x0B");2243 // create chunks with proper length for base64 decoding2244 $line = $prev.$line;2245 $length = strlen($line);2246 if ($length % 4) {2247 $length = floor($length / 4) * 4;2248 $prev = substr($line, $length);2249 $line = substr($line, 0, $length);2250 }2251 else2252 $prev = '';2253 $line = base64_decode($line);2257 if ($mode == 1) { 2258 $line = rtrim($line, "\t\r\n\0\x0B"); 2259 // create chunks with proper length for base64 decoding 2260 $line = $prev.$line; 2261 $length = strlen($line); 2262 if ($length % 4) { 2263 $length = floor($length / 4) * 4; 2264 $prev = substr($line, $length); 2265 $line = substr($line, 0, $length); 2266 } 2267 else 2268 $prev = ''; 2269 $line = base64_decode($line); 2254 2270 // QUOTED-PRINTABLE 2255 } else if ($mode == 2) {2256 $line = rtrim($line, "\t\r\0\x0B");2271 } else if ($mode == 2) { 2272 $line = rtrim($line, "\t\r\0\x0B"); 2257 2273 $line = quoted_printable_decode($line); 2258 2274 // Remove NULL characters (#1486189) 2259 2275 $line = str_replace("\x00", '', $line); 2260 2276 // UUENCODE 2261 } else if ($mode == 3) {2262 $line = rtrim($line, "\t\r\n\0\x0B");2263 if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line))2264 continue;2277 } else if ($mode == 3) { 2278 $line = rtrim($line, "\t\r\n\0\x0B"); 2279 if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) 2280 continue; 2265 2281 $line = convert_uudecode($line); 2266 2282 // default 2267 } else {2268 $line = rtrim($line, "\t\r\n\0\x0B") . "\n";2269 }2270 2271 if ($file)2272 fwrite($file, $line);2273 else if ($print)2274 echo $line;2275 else2276 $result .= $line;2277 }2278 }2283 } else { 2284 $line = rtrim($line, "\t\r\n\0\x0B") . "\n"; 2285 } 2286 2287 if ($file) 2288 fwrite($file, $line); 2289 else if ($print) 2290 echo $line; 2291 else 2292 $result .= $line; 2293 } 2294 } 2279 2295 2280 2296 // read in anything up until last line 2281 if (!$end)2282 do {2283 $line = $this->readLine(1024);2284 } while (!$this->startsWith($line, $key, true));2285 2286 if ($result !== false) {2287 if ($file) {2288 fwrite($file, $result);2289 } else if ($print) {2290 echo $result;2291 } else2292 return $result;2293 return true;2294 }2295 2296 return false;2297 if (!$end) 2298 do { 2299 $line = $this->readLine(1024); 2300 } while (!$this->startsWith($line, $key, true)); 2301 2302 if ($result !== false) { 2303 if ($file) { 2304 fwrite($file, $result); 2305 } else if ($print) { 2306 echo $result; 2307 } else 2308 return $result; 2309 return true; 2310 } 2311 2312 return false; 2297 2313 } 2298 2314 … … 2300 2316 { 2301 2317 $result = $this->execute('CREATE', array($this->escape($mailbox)), 2302 self::COMMAND_NORESPONSE);2303 2304 return ($result == self::ERROR_OK);2318 self::COMMAND_NORESPONSE); 2319 2320 return ($result == self::ERROR_OK); 2305 2321 } 2306 2322 … … 2308 2324 { 2309 2325 $result = $this->execute('RENAME', array($this->escape($from), $this->escape($to)), 2310 self::COMMAND_NORESPONSE);2311 2312 return ($result == self::ERROR_OK);2326 self::COMMAND_NORESPONSE); 2327 2328 return ($result == self::ERROR_OK); 2313 2329 } 2314 2330 2315 2331 function append($mailbox, &$message) 2316 2332 { 2317 if (!$mailbox) {2318 return false;2319 }2320 2321 $message = str_replace("\r", '', $message);2322 $message = str_replace("\n", "\r\n", $message);2323 2324 $len = strlen($message);2325 if (!$len) {2326 return false;2327 }2333 if (!$mailbox) { 2334 return false; 2335 } 2336 2337 $message = str_replace("\r", '', $message); 2338 $message = str_replace("\n", "\r\n", $message); 2339 2340 $len = strlen($message); 2341 if (!$len) { 2342 return false; 2343 } 2328 2344 2329 2345 $key = $this->nextTag(); 2330 $request = sprintf("$key APPEND %s (\\Seen) {%d%s}", $this->escape($mailbox),2346 $request = sprintf("$key APPEND %s (\\Seen) {%d%s}", $this->escape($mailbox), 2331 2347 $len, ($this->prefs['literal+'] ? '+' : '')); 2332 2348 2333 if ($this->putLine($request)) {2349 if ($this->putLine($request)) { 2334 2350 // Don't wait when LITERAL+ is supported 2335 2351 if (!$this->prefs['literal+']) { 2336 $line = $this->read Line(512);2337 2338 if ($line[0] != '+') {2339 $this->parseResult($line, 'APPEND: ');2340 return false;2341 }2342 } 2343 2344 if (!$this->putLine($message)) {2352 $line = $this->readReply(); 2353 2354 if ($line[0] != '+') { 2355 $this->parseResult($line, 'APPEND: '); 2356 return false; 2357 } 2358 } 2359 2360 if (!$this->putLine($message)) { 2345 2361 return false; 2346 2362 } 2347 2363 2348 do {2349 $line = $this->readLine();2350 } while (!$this->startsWith($line, $key, true, true));2364 do { 2365 $line = $this->readLine(); 2366 } while (!$this->startsWith($line, $key, true, true)); 2351 2367 2352 2368 // Clear internal status cache 2353 2369 unset($this->data['STATUS:'.$mailbox]); 2354 2370 2355 return ($this->parseResult($line, 'APPEND: ') == self::ERROR_OK);2356 }2371 return ($this->parseResult($line, 'APPEND: ') == self::ERROR_OK); 2372 } 2357 2373 else { 2358 2374 $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); 2359 2375 } 2360 2376 2361 return false;2377 return false; 2362 2378 } 2363 2379 2364 2380 function appendFromFile($mailbox, $path, $headers=null) 2365 2381 { 2366 if (!$mailbox) {2367 return false;2368 }2369 2370 // open message file2371 $in_fp = false;2372 if (file_exists(realpath($path))) {2373 $in_fp = fopen($path, 'r');2374 }2375 if (!$in_fp) {2376 $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");2377 return false;2378 }2382 if (!$mailbox) { 2383 return false; 2384 } 2385 2386 // open message file 2387 $in_fp = false; 2388 if (file_exists(realpath($path))) { 2389 $in_fp = fopen($path, 'r'); 2390 } 2391 if (!$in_fp) { 2392 $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading"); 2393 return false; 2394 } 2379 2395 2380 2396 $body_separator = "\r\n\r\n"; 2381 $len = filesize($path);2382 2383 if (!$len) {2384 return false;2385 }2397 $len = filesize($path); 2398 2399 if (!$len) { 2400 return false; 2401 } 2386 2402 2387 2403 if ($headers) { … … 2390 2406 } 2391 2407 2392 // send APPEND command2393 $key = $this->nextTag();2394 $request = sprintf("$key APPEND %s (\\Seen) {%d%s}", $this->escape($mailbox),2408 // send APPEND command 2409 $key = $this->nextTag(); 2410 $request = sprintf("$key APPEND %s (\\Seen) {%d%s}", $this->escape($mailbox), 2395 2411 $len, ($this->prefs['literal+'] ? '+' : '')); 2396 2412 2397 if ($this->putLine($request)) {2413 if ($this->putLine($request)) { 2398 2414 // Don't wait when LITERAL+ is supported 2399 2415 if (!$this->prefs['literal+']) { 2400 $line = $this->readLine(512);2401 2402 if ($line[0] != '+') {2403 $this->parseResult($line, 'APPEND: ');2404 return false;2405 }2416 $line = $this->readReply(); 2417 2418 if ($line[0] != '+') { 2419 $this->parseResult($line, 'APPEND: '); 2420 return false; 2421 } 2406 2422 } 2407 2423 2408 2424 // send headers with body separator 2409 2425 if ($headers) { 2410 $this->putLine($headers . $body_separator, false);2411 } 2412 2413 // send file2414 while (!feof($in_fp) && $this->fp) {2415 $buffer = fgets($in_fp, 4096);2416 $this->putLine($buffer, false);2417 }2418 fclose($in_fp);2419 2420 if (!$this->putLine('')) { // \r\n2426 $this->putLine($headers . $body_separator, false); 2427 } 2428 2429 // send file 2430 while (!feof($in_fp) && $this->fp) { 2431 $buffer = fgets($in_fp, 4096); 2432 $this->putLine($buffer, false); 2433 } 2434 fclose($in_fp); 2435 2436 if (!$this->putLine('')) { // \r\n 2421 2437 return false; 2422 2438 } 2423 2439 2424 // read response2425 do {2426 $line = $this->readLine();2427 } while (!$this->startsWith($line, $key, true, true));2440 // read response 2441 do { 2442 $line = $this->readLine(); 2443 } while (!$this->startsWith($line, $key, true, true)); 2428 2444 2429 2445 // Clear internal status cache 2430 2446 unset($this->data['STATUS:'.$mailbox]); 2431 2447 2432 return ($this->parseResult($line, 'APPEND: ') == self::ERROR_OK);2433 }2448 return ($this->parseResult($line, 'APPEND: ') == self::ERROR_OK); 2449 } 2434 2450 else { 2435 2451 $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); 2436 2452 } 2437 2453 2438 return false;2454 return false; 2439 2455 } 2440 2456 2441 2457 function fetchStructureString($mailbox, $id, $is_uid=false) 2442 2458 { 2443 if (!$this->select($mailbox)) {2444 return false; 2445 } 2446 2447 $key = $this->nextTag();2448 $result = false;2459 if (!$this->select($mailbox)) { 2460 return false; 2461 } 2462 2463 $key = $this->nextTag(); 2464 $result = false; 2449 2465 $command = $key . ($is_uid ? ' UID' : '') ." FETCH $id (BODYSTRUCTURE)"; 2450 2466 2451 if ($this->putLine($command)) {2452 do {2453 $line = $this->readLine(5000);2454 $line = $this->multLine($line, true);2455 if (!preg_match("/^$key /", $line))2456 $result .= $line;2457 } while (!$this->startsWith($line, $key, true, true));2458 2459 $result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -1));2460 }2467 if ($this->putLine($command)) { 2468 do { 2469 $line = $this->readLine(5000); 2470 $line = $this->multLine($line, true); 2471 if (!preg_match("/^$key /", $line)) 2472 $result .= $line; 2473 } while (!$this->startsWith($line, $key, true, true)); 2474 2475 $result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -1)); 2476 } 2461 2477 else { 2462 2478 $this->setError(self::ERROR_COMMAND, "Unable to send command: $command"); 2463 2479 } 2464 2480 2465 return $result;2481 return $result; 2466 2482 } 2467 2483 … … 2474 2490 * OK Completed 2475 2491 */ 2476 $result = false;2477 $quota_lines = array();2478 $key = $this->nextTag();2492 $result = false; 2493 $quota_lines = array(); 2494 $key = $this->nextTag(); 2479 2495 $command = $key . ' GETQUOTAROOT INBOX'; 2480 2496 2481 // get line(s) containing quota info2482 if ($this->putLine($command)) {2483 do {2484 $line = rtrim($this->readLine(5000));2485 if (preg_match('/^\* QUOTA /', $line)) {2486 $quota_lines[] = $line;2487 }2488 } while (!$this->startsWith($line, $key, true, true));2489 }2497 // get line(s) containing quota info 2498 if ($this->putLine($command)) { 2499 do { 2500 $line = rtrim($this->readLine(5000)); 2501 if (preg_match('/^\* QUOTA /', $line)) { 2502 $quota_lines[] = $line; 2503 } 2504 } while (!$this->startsWith($line, $key, true, true)); 2505 } 2490 2506 else { 2491 2507 $this->setError(self::ERROR_COMMAND, "Unable to send command: $command"); 2492 2508 } 2493 2509 2494 // return false if not found, parse if found2495 $min_free = PHP_INT_MAX;2496 foreach ($quota_lines as $key => $quota_line) {2497 $quota_line = str_replace(array('(', ')'), '', $quota_line);2498 $parts = explode(' ', $quota_line);2499 $storage_part = array_search('STORAGE', $parts);2500 2501 if (!$storage_part) 2510 // return false if not found, parse if found 2511 $min_free = PHP_INT_MAX; 2512 foreach ($quota_lines as $key => $quota_line) { 2513 $quota_line = str_replace(array('(', ')'), '', $quota_line); 2514 $parts = explode(' ', $quota_line); 2515 $storage_part = array_search('STORAGE', $parts); 2516 2517 if (!$storage_part) { 2502 2518 continue; 2503 2504 $used = intval($parts[$storage_part+1]); 2505 $total = intval($parts[$storage_part+2]); 2506 $free = $total - $used; 2507 2508 // return lowest available space from all quotas 2509 if ($free < $min_free) { 2510 $min_free = $free; 2511 $result['used'] = $used; 2512 $result['total'] = $total; 2513 $result['percent'] = min(100, round(($used/max(1,$total))*100)); 2514 $result['free'] = 100 - $result['percent']; 2515 } 2516 } 2517 2518 return $result; 2519 } 2520 2521 $used = intval($parts[$storage_part+1]); 2522 $total = intval($parts[$storage_part+2]); 2523 $free = $total - $used; 2524 2525 // return lowest available space from all quotas 2526 if ($free < $min_free) { 2527 $min_free = $free; 2528 $result['used'] = $used; 2529 $result['total'] = $total; 2530 $result['percent'] = min(100, round(($used/max(1,$total))*100)); 2531 $result['free'] = 100 - $result['percent']; 2532 } 2533 } 2534 2535 return $result; 2519 2536 } 2520 2537 … … 2541 2558 self::COMMAND_NORESPONSE); 2542 2559 2543 return ($result == self::ERROR_OK);2560 return ($result == self::ERROR_OK); 2544 2561 } 2545 2562 … … 2561 2578 self::COMMAND_NORESPONSE); 2562 2579 2563 return ($result == self::ERROR_OK);2580 return ($result == self::ERROR_OK); 2564 2581 } 2565 2582 … … 2681 2698 2682 2699 foreach ($entries as $name => $value) { 2683 if ($value === null) 2700 if ($value === null) { 2684 2701 $value = 'NIL'; 2685 else 2702 } 2703 else { 2686 2704 $value = sprintf("{%d}\r\n%s", strlen($value), $value); 2687 2705 } 2688 2706 $entries[$name] = $this->escape($name) . ' ' . $value; 2689 2707 } … … 2710 2728 function deleteMetadata($mailbox, $entries) 2711 2729 { 2712 if (!is_array($entries) && !empty($entries)) 2730 if (!is_array($entries) && !empty($entries)) { 2713 2731 $entries = explode(' ', $entries); 2732 } 2714 2733 2715 2734 if (empty($entries)) { … … 2718 2737 } 2719 2738 2720 foreach ($entries as $entry) 2739 foreach ($entries as $entry) { 2721 2740 $data[$entry] = NULL; 2741 } 2722 2742 2723 2743 return $this->setMetadata($mailbox, $data); … … 2755 2775 $opts = array(); 2756 2776 2757 if (!empty($options['MAXSIZE'])) 2777 if (!empty($options['MAXSIZE'])) { 2758 2778 $opts[] = 'MAXSIZE '.intval($options['MAXSIZE']); 2759 if (!empty($options['DEPTH'])) 2779 } 2780 if (!empty($options['DEPTH'])) { 2760 2781 $opts[] = 'DEPTH '.intval($options['DEPTH']); 2761 2762 if ($opts) 2782 } 2783 2784 if ($opts) { 2763 2785 $optlist = '(' . implode(' ', $opts) . ')'; 2786 } 2764 2787 } 2765 2788 … … 2828 2851 $value = $entry[2]; 2829 2852 2830 if ($value === null) 2853 if ($value === null) { 2831 2854 $value = 'NIL'; 2832 else 2855 } 2856 else { 2833 2857 $value = sprintf("{%d}\r\n%s", strlen($value), $value); 2858 } 2834 2859 2835 2860 $entries[] = sprintf('%s (%s %s)', … … 2923 2948 $attr = $attribs[$x++]; 2924 2949 $value = $attribs[$x++]; 2925 if ($attr == 'value.priv') 2950 if ($attr == 'value.priv') { 2926 2951 $res['/private' . $entry] = $value; 2927 else if ($attr == 'value.shared') 2952 } 2953 else if ($attr == 'value.shared') { 2928 2954 $res['/shared' . $entry] = $value; 2955 } 2929 2956 } 2930 2957 } … … 2974 3001 $response = $noresp ? null : ''; 2975 3002 2976 if (!empty($arguments)) 3003 if (!empty($arguments)) { 2977 3004 $query .= ' ' . implode(' ', $arguments); 3005 } 2978 3006 2979 3007 // Send command 2980 if (!$this->putLineC($query)) {3008 if (!$this->putLineC($query)) { 2981 3009 $this->setError(self::ERROR_COMMAND, "Unable to send command: $query"); 2982 return $noresp ? self::ERROR_COMMAND : array(self::ERROR_COMMAND, '');2983 }3010 return $noresp ? self::ERROR_COMMAND : array(self::ERROR_COMMAND, ''); 3011 } 2984 3012 2985 3013 // Parse response 2986 do { 2987 $line = $this->readLine(4096); 2988 if ($response !== null) 2989 $response .= $line; 2990 } while (!$this->startsWith($line, $tag . ' ', true, true)); 2991 2992 $code = $this->parseResult($line, $command . ': '); 3014 do { 3015 $line = $this->readLine(4096); 3016 if ($response !== null) { 3017 $response .= $line; 3018 } 3019 } while (!$this->startsWith($line, $tag . ' ', true, true)); 3020 3021 $code = $this->parseResult($line, $command . ': '); 2993 3022 2994 3023 // Remove last line from response 2995 if ($response) {2996 $line_len = min(strlen($response), strlen($line) + 2);3024 if ($response) { 3025 $line_len = min(strlen($response), strlen($line) + 2); 2997 3026 $response = substr($response, 0, -$line_len); 2998 3027 } 2999 3028 3000 // optional CAPABILITY response3001 if (($options & self::COMMAND_CAPABILITY) && $code == self::ERROR_OK3029 // optional CAPABILITY response 3030 if (($options & self::COMMAND_CAPABILITY) && $code == self::ERROR_OK 3002 3031 && preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches) 3003 3032 ) { 3004 $this->parseCapability($matches[1], true);3005 }3033 $this->parseCapability($matches[1], true); 3034 } 3006 3035 3007 3036 // return last line only (without command tag, result and response code) … … 3010 3039 } 3011 3040 3012 return $noresp ? $code : array($code, $response);3041 return $noresp ? $code : array($code, $response); 3013 3042 } 3014 3043 … … 3044 3073 // Advance the string 3045 3074 $str = substr($str, $epos + 3 + $bytes); 3046 break;3075 break; 3047 3076 3048 3077 // Quoted string … … 3066 3095 $result[] = stripslashes(substr($str, 1, $pos - 1)); 3067 3096 $str = substr($str, $pos + 1); 3068 break;3097 break; 3069 3098 3070 3099 // Parenthesized list … … 3072 3101 $str = substr($str, 1); 3073 3102 $result[] = self::tokenizeResponse($str); 3074 break;3103 break; 3075 3104 case ')': 3076 3105 $str = substr($str, 1); 3077 3106 return $result; 3078 break;3107 break; 3079 3108 3080 3109 // String atom, number, NIL, *, % … … 3095 3124 $str = substr($str, strlen($m[1])); 3096 3125 } 3097 break;3126 break; 3098 3127 } 3099 3128 } … … 3104 3133 private function _xor($string, $string2) 3105 3134 { 3106 $result = ''; 3107 $size = strlen($string); 3108 for ($i=0; $i<$size; $i++) { 3109 $result .= chr(ord($string[$i]) ^ ord($string2[$i])); 3110 } 3111 return $result; 3135 $result = ''; 3136 $size = strlen($string); 3137 3138 for ($i=0; $i<$size; $i++) { 3139 $result .= chr(ord($string[$i]) ^ ord($string2[$i])); 3140 } 3141 3142 return $result; 3112 3143 } 3113 3144 … … 3121 3152 private function strToTime($date) 3122 3153 { 3123 // support non-standard "GMTXXXX" literal3124 $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);3154 // support non-standard "GMTXXXX" literal 3155 $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date); 3125 3156 // if date parsing fails, we have a date in non-rfc format. 3126 // remove token from the end and try again 3127 while ((($ts = @strtotime($date))===false) || ($ts < 0)) { 3128 $d = explode(' ', $date); 3129 array_pop($d); 3130 if (!$d) break; 3131 $date = implode(' ', $d); 3132 } 3133 3134 $ts = (int) $ts; 3135 3136 return $ts < 0 ? 0 : $ts; 3157 // remove token from the end and try again 3158 while ((($ts = @strtotime($date))===false) || ($ts < 0)) { 3159 $d = explode(' ', $date); 3160 array_pop($d); 3161 if (!$d) { 3162 break; 3163 } 3164 $date = implode(' ', $d); 3165 } 3166 3167 $ts = (int) $ts; 3168 3169 return $ts < 0 ? 0 : $ts; 3137 3170 } 3138 3171 3139 3172 private function splitHeaderLine($string) 3140 3173 { 3141 $pos = strpos($string, ':');3142 if ($pos>0) {3143 $res[0] = substr($string, 0, $pos);3144 $res[1] = trim(substr($string, $pos+1));3145 return $res;3146 }3147 return $string;3174 $pos = strpos($string, ':'); 3175 if ($pos>0) { 3176 $res[0] = substr($string, 0, $pos); 3177 $res[1] = trim(substr($string, $pos+1)); 3178 return $res; 3179 } 3180 return $string; 3148 3181 } 3149 3182 … … 3173 3206 static function escape($string) 3174 3207 { 3175 // NIL3176 3208 if ($string === null) { 3177 3209 return 'NIL'; 3178 3210 } 3179 // empty string3180 3211 else if ($string === '') { 3181 3212 return '""'; 3182 3213 } 3183 // string: special chars: SP, CTL, (, ), {, %, *, ", \, ]3184 3214 else if (preg_match('/([\x00-\x20\x28-\x29\x7B\x25\x2A\x22\x5C\x5D\x7F]+)/', $string)) { 3185 return '"' . strtr($string, array('"'=>'\\"', '\\' => '\\\\')) . '"'; 3215 // string: special chars: SP, CTL, (, ), {, %, *, ", \, ] 3216 return '"' . strtr($string, array('"'=>'\\"', '\\' => '\\\\')) . '"'; 3186 3217 } 3187 3218 … … 3192 3223 static function unEscape($string) 3193 3224 { 3194 return strtr($string, array('\\"'=>'"', '\\\\' => '\\'));3225 return strtr($string, array('\\"'=>'"', '\\\\' => '\\')); 3195 3226 } 3196 3227
Note: See TracChangeset
for help on using the changeset viewer.
