| | |
| | | |
| | | /** |
| | | +-----------------------------------------------------------------------+ |
| | | | program/include/rcube_imap_generic.php | |
| | | | | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 2005-2012, The Roundcube Dev Team | |
| | | | Copyright (C) 2011-2012, Kolab Systems AG | |
| | |
| | | | functionality built-in. | |
| | | | | |
| | | | Based on Iloha IMAP Library. See http://ilohamail.org/ for details | |
| | | | | |
| | | +-----------------------------------------------------------------------+ |
| | | | Author: Aleksander Machniak <alec@alec.pl> | |
| | | | Author: Ryo Chijiiwa <Ryo@IlohaMail.org> | |
| | | +-----------------------------------------------------------------------+ |
| | | */ |
| | | |
| | | |
| | | /** |
| | | * PHP based wrapper class to connect to an IMAP server |
| | |
| | | */ |
| | | function connect($host, $user, $password, $options=null) |
| | | { |
| | | // set options |
| | | if (is_array($options)) { |
| | | $this->prefs = $options; |
| | | } |
| | | // set auth method |
| | | if (!empty($this->prefs['auth_type'])) { |
| | | $auth_method = strtoupper($this->prefs['auth_type']); |
| | | } else { |
| | | $auth_method = 'CHECK'; |
| | | } |
| | | // configure |
| | | $this->set_prefs($options); |
| | | |
| | | $result = false; |
| | | $auth_method = $this->prefs['auth_type']; |
| | | $result = false; |
| | | |
| | | // initialize connection |
| | | $this->error = ''; |
| | |
| | | } |
| | | |
| | | if ($this->prefs['timeout'] <= 0) { |
| | | $this->prefs['timeout'] = ini_get('default_socket_timeout'); |
| | | $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout'))); |
| | | } |
| | | |
| | | // Connect |
| | | $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); |
| | | |
| | | if (!$this->fp) { |
| | | if (!$errstr) { |
| | | $errstr = "Unknown reason (fsockopen() function disabled?)"; |
| | | } |
| | | $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr)); |
| | | return false; |
| | | } |
| | | |
| | | if ($this->prefs['timeout'] > 0) |
| | | if ($this->prefs['timeout'] > 0) { |
| | | stream_set_timeout($this->fp, $this->prefs['timeout']); |
| | | } |
| | | |
| | | $line = trim(fgets($this->fp, 8192)); |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * Initializes environment |
| | | */ |
| | | protected function set_prefs($prefs) |
| | | { |
| | | // set preferences |
| | | if (is_array($prefs)) { |
| | | $this->prefs = $prefs; |
| | | } |
| | | |
| | | // set auth method |
| | | if (!empty($this->prefs['auth_type'])) { |
| | | $this->prefs['auth_type'] = strtoupper($this->prefs['auth_type']); |
| | | } |
| | | else { |
| | | $this->prefs['auth_type'] = 'CHECK'; |
| | | } |
| | | |
| | | // additional message flags |
| | | if (!empty($this->prefs['message_flags'])) { |
| | | $this->flags = array_merge($this->flags, $this->prefs['message_flags']); |
| | | unset($this->prefs['message_flags']); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Checks connection status |
| | | * |
| | | * @return bool True if connection is active and user is logged in, False otherwise. |
| | |
| | | */ |
| | | function closeConnection() |
| | | { |
| | | if ($this->putLine($this->nextTag() . ' LOGOUT')) { |
| | | if ($this->logged && $this->putLine($this->nextTag() . ' LOGOUT')) { |
| | | $this->readReply(); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | if (!$this->data['READ-WRITE']) { |
| | | $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'EXPUNGE'); |
| | | $this->setError(self::ERROR_READONLY, "Mailbox is read-only"); |
| | | return false; |
| | | } |
| | | |
| | |
| | | // * LIST (<options>) <delimiter> <mailbox> |
| | | if ($cmd == 'LIST' || $cmd == 'LSUB') { |
| | | list($opts, $delim, $mailbox) = $this->tokenizeResponse($line, 3); |
| | | |
| | | // Remove redundant separator at the end of folder name, UW-IMAP bug? (#1488879) |
| | | if ($delim) { |
| | | $mailbox = rtrim($mailbox, $delim); |
| | | } |
| | | |
| | | // Add to result array |
| | | if (!$lstatus) { |
| | |
| | | } |
| | | |
| | | if (!$this->data['READ-WRITE']) { |
| | | $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE'); |
| | | $this->setError(self::ERROR_READONLY, "Mailbox is read-only"); |
| | | return false; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | if (!$this->data['READ-WRITE']) { |
| | | $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE'); |
| | | $this->setError(self::ERROR_READONLY, "Mailbox is read-only"); |
| | | return false; |
| | | } |
| | | |
| | |
| | | else if ($name == 'RFC822') { |
| | | $result[$id]->body = $value; |
| | | } |
| | | else if ($name == 'BODY') { |
| | | $body = $this->tokenizeResponse($line, 1); |
| | | if ($value[0] == 'HEADER.FIELDS') |
| | | $headers = $body; |
| | | else if (!empty($value)) |
| | | $result[$id]->bodypart[$value[0]] = $body; |
| | | else if (stripos($name, 'BODY[') === 0) { |
| | | $name = str_replace(']', '', substr($name, 5)); |
| | | |
| | | if ($name == 'HEADER.FIELDS') { |
| | | // skip ']' after headers list |
| | | $this->tokenizeResponse($line, 1); |
| | | $headers = $this->tokenizeResponse($line, 1); |
| | | } |
| | | else if (strlen($name)) |
| | | $result[$id]->bodypart[$name] = $value; |
| | | else |
| | | $result[$id]->body = $body; |
| | | $result[$id]->body = $value; |
| | | } |
| | | } |
| | | |
| | |
| | | return $result; |
| | | } |
| | | |
| | | function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add = '') |
| | | /** |
| | | * Returns message(s) data (flags, headers, etc.) |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * @param mixed $message_set Message(s) sequence identifier(s) or UID(s) |
| | | * @param bool $is_uid True if $message_set contains UIDs |
| | | * @param bool $bodystr Enable to add BODYSTRUCTURE data to the result |
| | | * @param array $add_headers List of additional headers |
| | | * |
| | | * @return bool|array List of rcube_message_header elements, False on error |
| | | */ |
| | | function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = array()) |
| | | { |
| | | $query_items = array('UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE'); |
| | | if ($bodystr) |
| | | $headers = array('DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO', |
| | | 'LIST-POST', 'DISPOSITION-NOTIFICATION-TO', 'X-PRIORITY'); |
| | | |
| | | if (!empty($add_headers)) { |
| | | $add_headers = array_map('strtoupper', $add_headers); |
| | | $headers = array_unique(array_merge($headers, $add_headers)); |
| | | } |
| | | |
| | | if ($bodystr) { |
| | | $query_items[] = 'BODYSTRUCTURE'; |
| | | $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' |
| | | . 'DATE FROM TO SUBJECT CONTENT-TYPE CC REPLY-TO LIST-POST DISPOSITION-NOTIFICATION-TO X-PRIORITY' |
| | | . ($add ? ' ' . trim($add) : '') |
| | | . ')]'; |
| | | } |
| | | |
| | | $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' . implode(' ', $headers) . ')]'; |
| | | |
| | | $result = $this->fetch($mailbox, $message_set, $is_uid, $query_items); |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='') |
| | | /** |
| | | * Returns message data (flags, headers, etc.) |
| | | * |
| | | * @param string $mailbox Mailbox name |
| | | * @param int $id Message sequence identifier or UID |
| | | * @param bool $is_uid True if $id is an UID |
| | | * @param bool $bodystr Enable to add BODYSTRUCTURE data to the result |
| | | * @param array $add_headers List of additional headers |
| | | * |
| | | * @return bool|rcube_message_header Message data, False on error |
| | | */ |
| | | function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = array()) |
| | | { |
| | | $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add); |
| | | $a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers); |
| | | if (is_array($a)) { |
| | | return array_shift($a); |
| | | } |
| | |
| | | $partial = $max_bytes ? sprintf('<0.%d>', $max_bytes) : ''; |
| | | |
| | | // format request |
| | | $key = $this->nextTag(); |
| | | $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)"; |
| | | $key = $this->nextTag(); |
| | | $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)"; |
| | | $result = false; |
| | | $found = false; |
| | | |
| | | // send request |
| | | if (!$this->putLine($request)) { |
| | |
| | | $mode = -1; |
| | | } |
| | | |
| | | // receive reply line |
| | | do { |
| | | $line = rtrim($this->readLine(1024)); |
| | | $a = explode(' ', $line); |
| | | } while (!($end = $this->startsWith($line, $key, true)) && $a[2] != 'FETCH'); |
| | | $line = trim($this->readLine(1024)); |
| | | |
| | | $len = strlen($line); |
| | | $result = false; |
| | | |
| | | if ($a[2] != 'FETCH') { |
| | | } |
| | | // handle empty "* X FETCH ()" response |
| | | else if ($line[$len-1] == ')' && $line[$len-2] != '(') { |
| | | // one line response, get everything between first and last quotes |
| | | if (substr($line, -4, 3) == 'NIL') { |
| | | // NIL response |
| | | $result = ''; |
| | | } else { |
| | | $from = strpos($line, '"') + 1; |
| | | $to = strrpos($line, '"'); |
| | | $len = $to - $from; |
| | | $result = substr($line, $from, $len); |
| | | if (!$line) { |
| | | break; |
| | | } |
| | | |
| | | if ($mode == 1) { |
| | | $result = base64_decode($result); |
| | | } |
| | | else if ($mode == 2) { |
| | | $result = quoted_printable_decode($result); |
| | | } |
| | | else if ($mode == 3) { |
| | | $result = convert_uudecode($result); |
| | | // skip irrelevant untagged responses (we have a result already) |
| | | if ($found || !preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) { |
| | | continue; |
| | | } |
| | | |
| | | } else if ($line[$len-1] == '}') { |
| | | // multi-line request, find sizes of content and receive that many bytes |
| | | $from = strpos($line, '{') + 1; |
| | | $to = strrpos($line, '}'); |
| | | $len = $to - $from; |
| | | $sizeStr = substr($line, $from, $len); |
| | | $bytes = (int)$sizeStr; |
| | | $prev = ''; |
| | | $line = $m[2]; |
| | | |
| | | while ($bytes > 0) { |
| | | $line = $this->readLine(8192); |
| | | // handle one line response |
| | | if ($line[0] == '(' && substr($line, -1) == ')') { |
| | | // tokenize content inside brackets |
| | | $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line)); |
| | | |
| | | if ($line === NULL) { |
| | | break; |
| | | } |
| | | |
| | | $len = strlen($line); |
| | | |
| | | if ($len > $bytes) { |
| | | $line = substr($line, 0, $bytes); |
| | | $len = strlen($line); |
| | | } |
| | | $bytes -= $len; |
| | | |
| | | // BASE64 |
| | | if ($mode == 1) { |
| | | $line = rtrim($line, "\t\r\n\0\x0B"); |
| | | // create chunks with proper length for base64 decoding |
| | | $line = $prev.$line; |
| | | $length = strlen($line); |
| | | if ($length % 4) { |
| | | $length = floor($length / 4) * 4; |
| | | $prev = substr($line, $length); |
| | | $line = substr($line, 0, $length); |
| | | } |
| | | else |
| | | $prev = ''; |
| | | $line = base64_decode($line); |
| | | // QUOTED-PRINTABLE |
| | | } else if ($mode == 2) { |
| | | $line = rtrim($line, "\t\r\0\x0B"); |
| | | $line = quoted_printable_decode($line); |
| | | // UUENCODE |
| | | } else if ($mode == 3) { |
| | | $line = rtrim($line, "\t\r\n\0\x0B"); |
| | | if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) |
| | | continue; |
| | | $line = convert_uudecode($line); |
| | | // default |
| | | } else if ($formatted) { |
| | | $line = rtrim($line, "\t\r\n\0\x0B") . "\n"; |
| | | } |
| | | |
| | | if ($file) { |
| | | if (fwrite($file, $line) === false) |
| | | for ($i=0; $i<count($tokens); $i+=2) { |
| | | if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) { |
| | | $result = $tokens[$i+1]; |
| | | $found = true; |
| | | break; |
| | | } |
| | | } |
| | | else if ($print) |
| | | echo $line; |
| | | else |
| | | $result .= $line; |
| | | } |
| | | } |
| | | |
| | | // read in anything up until last line |
| | | if (!$end) |
| | | do { |
| | | $line = $this->readLine(1024); |
| | | } while (!$this->startsWith($line, $key, true)); |
| | | if ($result !== false) { |
| | | if ($mode == 1) { |
| | | $result = base64_decode($result); |
| | | } |
| | | else if ($mode == 2) { |
| | | $result = quoted_printable_decode($result); |
| | | } |
| | | else if ($mode == 3) { |
| | | $result = convert_uudecode($result); |
| | | } |
| | | } |
| | | } |
| | | // response with string literal |
| | | else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) { |
| | | $bytes = (int) $m[1]; |
| | | $prev = ''; |
| | | $found = true; |
| | | |
| | | // empty body |
| | | if (!$bytes) { |
| | | $result = ''; |
| | | } |
| | | else while ($bytes > 0) { |
| | | $line = $this->readLine(8192); |
| | | |
| | | if ($line === NULL) { |
| | | break; |
| | | } |
| | | |
| | | $len = strlen($line); |
| | | |
| | | if ($len > $bytes) { |
| | | $line = substr($line, 0, $bytes); |
| | | $len = strlen($line); |
| | | } |
| | | $bytes -= $len; |
| | | |
| | | // BASE64 |
| | | if ($mode == 1) { |
| | | $line = rtrim($line, "\t\r\n\0\x0B"); |
| | | // create chunks with proper length for base64 decoding |
| | | $line = $prev.$line; |
| | | $length = strlen($line); |
| | | if ($length % 4) { |
| | | $length = floor($length / 4) * 4; |
| | | $prev = substr($line, $length); |
| | | $line = substr($line, 0, $length); |
| | | } |
| | | else { |
| | | $prev = ''; |
| | | } |
| | | $line = base64_decode($line); |
| | | } |
| | | // QUOTED-PRINTABLE |
| | | else if ($mode == 2) { |
| | | $line = rtrim($line, "\t\r\0\x0B"); |
| | | $line = quoted_printable_decode($line); |
| | | } |
| | | // UUENCODE |
| | | else if ($mode == 3) { |
| | | $line = rtrim($line, "\t\r\n\0\x0B"); |
| | | if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) { |
| | | continue; |
| | | } |
| | | $line = convert_uudecode($line); |
| | | } |
| | | // default |
| | | else if ($formatted) { |
| | | $line = rtrim($line, "\t\r\n\0\x0B") . "\n"; |
| | | } |
| | | |
| | | if ($file) { |
| | | if (fwrite($file, $line) === false) { |
| | | break; |
| | | } |
| | | } |
| | | else if ($print) { |
| | | echo $line; |
| | | } |
| | | else { |
| | | $result .= $line; |
| | | } |
| | | } |
| | | } |
| | | } while (!$this->startsWith($line, $key, true)); |
| | | |
| | | if ($result !== false) { |
| | | if ($file) { |
| | | return fwrite($file, $result); |
| | | } else if ($print) { |
| | | } |
| | | else if ($print) { |
| | | echo $result; |
| | | } else |
| | | return $result; |
| | | return true; |
| | | return true; |
| | | } |
| | | |
| | | return $result; |
| | | } |
| | | |
| | | return false; |
| | |
| | | * @param string $message Message content |
| | | * @param array $flags Message flags |
| | | * @param string $date Message internal date |
| | | * @param bool $binary Enable BINARY append (RFC3516) |
| | | * |
| | | * @return string|bool On success APPENDUID response (if available) or True, False on failure |
| | | */ |
| | | function append($mailbox, &$message, $flags = array(), $date = null) |
| | | function append($mailbox, &$message, $flags = array(), $date = null, $binary = false) |
| | | { |
| | | unset($this->data['APPENDUID']); |
| | | |
| | |
| | | return false; |
| | | } |
| | | |
| | | $message = str_replace("\r", '', $message); |
| | | $message = str_replace("\n", "\r\n", $message); |
| | | $binary = $binary && $this->getCapability('BINARY'); |
| | | $literal_plus = !$binary && $this->prefs['literal+']; |
| | | |
| | | if (!$binary) { |
| | | $message = str_replace("\r", '', $message); |
| | | $message = str_replace("\n", "\r\n", $message); |
| | | } |
| | | |
| | | $len = strlen($message); |
| | | if (!$len) { |
| | |
| | | if (!empty($date)) { |
| | | $request .= ' ' . $this->escape($date); |
| | | } |
| | | $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}'; |
| | | $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}'; |
| | | |
| | | // send APPEND command |
| | | if ($this->putLine($request)) { |
| | | // Do not wait when LITERAL+ is supported |
| | | if (!$this->prefs['literal+']) { |
| | | if (!$literal_plus) { |
| | | $line = $this->readReply(); |
| | | |
| | | if ($line[0] != '+') { |
| | |
| | | * @param string $headers Message headers |
| | | * @param array $flags Message flags |
| | | * @param string $date Message internal date |
| | | * @param bool $binary Enable BINARY append (RFC3516) |
| | | * |
| | | * @return string|bool On success APPENDUID response (if available) or True, False on failure |
| | | */ |
| | | function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null) |
| | | function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false) |
| | | { |
| | | unset($this->data['APPENDUID']); |
| | | |
| | |
| | | $len += strlen($headers) + strlen($body_separator); |
| | | } |
| | | |
| | | $binary = $binary && $this->getCapability('BINARY'); |
| | | $literal_plus = !$binary && $this->prefs['literal+']; |
| | | |
| | | // build APPEND command |
| | | $key = $this->nextTag(); |
| | | $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')'; |
| | | if (!empty($date)) { |
| | | $request .= ' ' . $this->escape($date); |
| | | } |
| | | $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}'; |
| | | $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}'; |
| | | |
| | | // send APPEND command |
| | | if ($this->putLine($request)) { |
| | | // Don't wait when LITERAL+ is supported |
| | | if (!$this->prefs['literal+']) { |
| | | if (!$literal_plus) { |
| | | $line = $this->readReply(); |
| | | |
| | | if ($line[0] != '+') { |
| | |
| | | } |
| | | |
| | | foreach ($entries as $name => $value) { |
| | | $entries[$name] = $this->escape($name) . ' ' . $this->escape($value); |
| | | $entries[$name] = $this->escape($name) . ' ' . $this->escape($value, true); |
| | | } |
| | | |
| | | $entries = implode(' ', $entries); |
| | |
| | | |
| | | // Parenthesized list |
| | | case '(': |
| | | case '[': |
| | | $str = substr($str, 1); |
| | | $result[] = self::tokenizeResponse($str); |
| | | break; |
| | | case ')': |
| | | case ']': |
| | | $str = substr($str, 1); |
| | | return $result; |
| | | break; |
| | | |
| | | // String atom, number, NIL, *, % |
| | | // String atom, number, astring, NIL, *, % |
| | | default: |
| | | // empty string |
| | | if ($str === '' || $str === null) { |
| | | break 2; |
| | | } |
| | | |
| | | // excluded chars: SP, CTL, ), [, ] |
| | | if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) { |
| | | // excluded chars: SP, CTL, ), DEL |
| | | // we do not exclude [ and ] (#1489223) |
| | | if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) { |
| | | $result[] = $m[1] == 'NIL' ? NULL : $m[1]; |
| | | $str = substr($str, strlen($m[1])); |
| | | } |