/** * This function cleanup_addr() has been used a large part of function * rcmail_email_input_format() in program/steps/mail/sendmail.inc of * Roundcube core at version 0.9. */ function cleanup_addr($mailto) { global $RCMAIL; // simplified email regexp, supporting quoted local part $email_regexp = '(\\S+|("[^"]+"))@\\S+'; $delim = trim($RCMAIL->config->get('recipients_separator', ',')); $regexp = array("/[,;{$delim}]\\s*[\r\n]+/", '/[\\r\\n]+/', "/[,;{$delim}]\\s*\$/m", '/;/', '/(\\S{1})(<' . $email_regexp . '>)/U'); $replace = array($delim . ' ', ', ', '', $delim, '\\1 \\2'); // replace new lines and strip ending ', ', make address input more valid $mailto = trim(preg_replace($regexp, $replace, $mailto)); $result = array(); $items = rcube_explode_quoted_string($delim, $mailto); foreach ($items as $item) { $item = trim($item); // address in brackets without name (do nothing) if (preg_match('/^<' . $email_regexp . '>$/', $item)) { $item = rcube_idn_to_ascii(trim($item, '<>')); $result[] = $item; // address without brackets and without name (add brackets) } else { if (preg_match('/^' . $email_regexp . '$/', $item)) { $item = rcube_idn_to_ascii($item); $result[] = $item; // address with name (handle name) } else { if (preg_match('/<*' . $email_regexp . '>*$/', $item, $matches)) { $address = $matches[0]; $name = trim(str_replace($address, '', $item)); if ($name[0] == '"' && $name[count($name) - 1] == '"') { $name = substr($name, 1, -1); } $name = stripcslashes($name); $address = rcube_idn_to_ascii(trim($address, '<>')); $result[] = $address; $item = $address; } else { if (trim($item)) { continue; } } } } } return implode(', ', $result); }
/** * @access private */ private function _parse_address_list($str, $decode = true) { // remove any newlines and carriage returns before $a = rcube_explode_quoted_string('[,;]', preg_replace("/[\r\n]/", " ", $str)); $result = array(); foreach ($a as $key => $val) { $val = preg_replace("/([\"\\w])</", "\$1 <", $val); $sub_a = rcube_explode_quoted_string(' ', $decode ? $this->decode_header($val) : $val); $result[$key]['name'] = ''; foreach ($sub_a as $k => $v) { // use angle brackets in regexp to not handle names with @ sign if (preg_match('/^<\\S+@\\S+>$/', $v)) { $result[$key]['address'] = trim($v, '<>'); } else { $result[$key]['name'] .= (empty($result[$key]['name']) ? '' : ' ') . str_replace("\"", '', stripslashes($v)); } } if (empty($result[$key]['name'])) { $result[$key]['name'] = $result[$key]['address']; } elseif (empty($result[$key]['address'])) { $result[$key]['address'] = $result[$key]['name']; } } return $result; }
/** * Take a set of recipients and parse them, returning an array of * bare addresses (forward paths) that can be passed to sendmail * or an smtp server with the rcpt to: command. * * @param mixed Either a comma-seperated list of recipients * (RFC822 compliant), or an array of recipients, * each RFC822 valid. * * @return array An array of forward paths (bare addresses). * @access private */ private function _parse_rfc822($recipients) { // if we're passed an array, assume addresses are valid and implode them before parsing. if (is_array($recipients)) { $recipients = implode(', ', $recipients); } $addresses = array(); $recipients = rcube_explode_quoted_string(',', $recipients); reset($recipients); while (list($k, $recipient) = each($recipients)) { $a = explode(" ", $recipient); while (list($k2, $word) = each($a)) { if (strpos($word, "@") > 0 && $word[strlen($word) - 1] != '"') { $word = preg_replace('/^<|>$/', '', trim($word)); if (in_array($word, $addresses) === false) { array_push($addresses, $word); } } } } return $addresses; }
function listSubscribed($ref, $mailbox) { if (empty($mailbox)) { $mailbox = '*'; } if (empty($ref) && $this->rootdir) { $ref = $this->rootdir; } $folders = array(); // send command if (!$this->putLine('lsb LSUB "' . $this->escape($ref) . '" "' . $this->escape($mailbox) . '"')) { $this->error = "Couldn't send LSUB command"; return false; } $i = 0; // get folder list do { $line = $this->readLine(500); $line = $this->multLine($line, true); $a = explode(' ', $line); if ($line[0] == '*' && ($a[1] == 'LSUB' || $a[1] == 'LIST')) { $line = rtrim($line); // split one line $a = rcube_explode_quoted_string(' ', $line); // last string is folder name $folder = preg_replace(array('/^"/', '/"$/'), '', $this->UnEscape($a[count($a) - 1])); // @TODO: do we need this check??? if (!in_array($folder, $folders)) { $folders[$i] = $folder; } // second from last is delimiter $delim = trim($a[count($a) - 2], '"'); // is it a container? $i++; } } while (!$this->startsWith($line, 'lsb', true)); if (is_array($folders)) { if (!empty($ref)) { // if rootdir was specified, make sure it's the first element // some IMAP servers (i.e. Courier) won't return it if ($ref[strlen($ref) - 1] == $delim) { $ref = substr($ref, 0, strlen($ref) - 1); } if ($folders[0] != $ref) { array_unshift($folders, $ref); } } return $folders; } $this->error = $line; return false; }
private function rcmail_email_input_format($mailto, $count = false, $check = true) { $regexp = array('/[,;]\\s*[\\r\\n]+/', '/[\\r\\n]+/', '/[,;]\\s*$/m', '/;/', '/(\\S{1})(<\\S+@\\S+>)/U'); $replace = array(', ', ', ', '', ',', '\\1 \\2'); // replace new lines and strip ending ', ', make address input more valid $mailto = trim(preg_replace($regexp, $replace, $mailto)); $result = array(); $items = rcube_explode_quoted_string(',', $mailto); foreach ($items as $item) { $item = trim($item); // address in brackets without name (do nothing) if (preg_match('/^<\\S+@\\S+>$/', $item)) { $item = idn_to_ascii($item); $result[] = $item; // address without brackets and without name (add brackets) } else { if (preg_match('/^\\S+@\\S+$/', $item)) { $item = idn_to_ascii($item); $result[] = '<' . $item . '>'; // address with name (handle name) } else { if (preg_match('/\\S+@\\S+>*$/', $item, $matches)) { $address = $matches[0]; $name = str_replace($address, '', $item); $name = trim($name); if ($name && ($name[0] != '"' || $name[strlen($name) - 1] != '"') && preg_match('/[\\(\\)\\<\\>\\\\.\\[\\]@,;:"]/', $name)) { $name = '"' . addcslashes($name, '"') . '"'; } $address = idn_to_ascii($address); if (!preg_match('/^<\\S+@\\S+>$/', $address)) { $address = '<' . $address . '>'; } $result[] = $name . ' ' . $address; $item = $address; } else { if (trim($item)) { continue; } } } } // check address format $item = trim($item, '<>'); if ($item && $check && !check_email($item)) { $this->email_format_error = $item; return; } } if ($count) { $this->recipient_count += count($result); } return implode(', ', $result); }
/** * Encodes a header as per RFC2047 * * @param array $input The header data to encode * @param array $params Extra build parameters * @return array Encoded data * @access private * @override */ function _encodeHeaders($input, $params = array()) { $maxlen = 73; $params += $this->_build_params; foreach ($input as $hdr_name => $hdr_value) { // if header contains e-mail addresses if (preg_match('/\\s<.+@[a-z0-9\\-\\.]+\\.[a-z]+>/U', $hdr_value)) { $chunks = rcube_explode_quoted_string(',', $hdr_value); } else { $chunks = array($hdr_value); } $hdr_value = ''; $line_len = 0; foreach ($chunks as $i => $value) { $value = trim($value); //This header contains non ASCII chars and should be encoded. if (preg_match('/[\\x80-\\xFF]{1}/', $value)) { $suffix = ''; // Don't encode e-mail address if (preg_match('/(.+)\\s(<.+@[a-z0-9\\-\\.]+>)$/Ui', $value, $matches)) { $value = $matches[1]; $suffix = ' ' . $matches[2]; } switch ($params['head_encoding']) { case 'base64': // Base64 encoding has been selected. $mode = 'B'; $encoded = base64_encode($value); break; case 'quoted-printable': default: // quoted-printable encoding has been selected $mode = 'Q'; // replace ?, =, _ and spaces $encoded = str_replace(array('=', '_', '?', ' '), array('=3D', '=5F', '=3F', '_'), $value); $encoded = preg_replace('/([\\x80-\\xFF])/e', "'='.sprintf('%02X', ord('\\1'))", $encoded); } $value = '=?' . $params['head_charset'] . '?' . $mode . '?' . $encoded . '?=' . $suffix; } // add chunk to output string by regarding the header maxlen $len = strlen($value); if ($i == 0 || $line_len + $len < $maxlen) { $hdr_value .= ($i > 0 ? ', ' : '') . $value; $line_len += $len + ($i > 0 ? 2 : 0); } else { $hdr_value .= ($i > 0 ? ', ' : '') . "\n " . $value; $line_len = $len; } } $input[$hdr_name] = wordwrap($hdr_value, 990, "\n", true); // hard limit header length } return $input; }
function fetchHeaders($mailbox, $message_set, $uidfetch = false, $bodystr = false, $add = '') { $result = array(); if (!$this->select($mailbox)) { return false; } $message_set = $this->compressMessageSet($message_set); if ($add) { $add = ' ' . trim($add); } /* FETCH uid, size, flags and headers */ $key = $this->nextTag(); $request = $key . ($uidfetch ? ' UID' : '') . " FETCH {$message_set} "; $request .= "(UID RFC822.SIZE FLAGS INTERNALDATE "; if ($bodystr) { $request .= "BODYSTRUCTURE "; } $request .= "BODY.PEEK[HEADER.FIELDS (DATE FROM TO SUBJECT CONTENT-TYPE "; $request .= "LIST-POST DISPOSITION-NOTIFICATION-TO" . $add . ")])"; if (!$this->putLine($request)) { $this->setError(self::ERROR_COMMAND, "Unable to send command: {$request}"); return false; } do { $line = $this->readLine(4096); $line = $this->multLine($line); if (!$line) { break; } if (preg_match('/^\\* ([0-9]+) FETCH/', $line, $m)) { $id = intval($m[1]); $result[$id] = new rcube_mail_header(); $result[$id]->id = $id; $result[$id]->subject = ''; $result[$id]->messageID = 'mid:' . $id; $lines = array(); $ln = 0; // Sample reply line: // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen) // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...) // BODY[HEADER.FIELDS ... if (preg_match('/^\\* [0-9]+ FETCH \\((.*) BODY/sU', $line, $matches)) { $str = $matches[1]; // swap parents with quotes, then explode $str = preg_replace('/[()]/', '"', $str); $a = rcube_explode_quoted_string(' ', $str); // did we get the right number of replies? $parts_count = count($a); if ($parts_count >= 6) { for ($i = 0; $i < $parts_count; $i = $i + 2) { if ($a[$i] == 'UID') { $result[$id]->uid = intval($a[$i + 1]); } else { if ($a[$i] == 'RFC822.SIZE') { $result[$id]->size = intval($a[$i + 1]); } else { if ($a[$i] == 'INTERNALDATE') { $time_str = $a[$i + 1]; } else { if ($a[$i] == 'FLAGS') { $flags_str = $a[$i + 1]; } } } } } $time_str = str_replace('"', '', $time_str); // if time is gmt... $time_str = str_replace('GMT', '+0000', $time_str); $result[$id]->internaldate = $time_str; $result[$id]->timestamp = $this->StrToTime($time_str); $result[$id]->date = $time_str; } // BODYSTRUCTURE if ($bodystr) { while (!preg_match('/ BODYSTRUCTURE (.*) BODY\\[HEADER.FIELDS/sU', $line, $m)) { $line2 = $this->readLine(1024); $line .= $this->multLine($line2, true); } $result[$id]->body_structure = $m[1]; } // the rest of the result if (preg_match('/ BODY\\[HEADER.FIELDS \\(.*?\\)\\]\\s*(.*)$/s', $line, $m)) { $reslines = explode("\n", trim($m[1], '"')); // re-parse (see below) foreach ($reslines as $resln) { if (ord($resln[0]) <= 32) { $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln); } else { $lines[++$ln] = trim($resln); } } } } // Start parsing headers. The problem is, some header "lines" take up multiple lines. // So, we'll read ahead, and if the one we're reading now is a valid header, we'll // process the previous line. Otherwise, we'll keep adding the strings until we come // to the next valid header line. do { $line = rtrim($this->readLine(300), "\r\n"); // The preg_match below works around communigate imap, which outputs " UID <number>)". // Without this, the while statement continues on and gets the "FH0 OK completed" message. // If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249. // This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing // If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin // An alternative might be: // if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break; // however, unsure how well this would work with all imap clients. if (preg_match("/^\\s*UID [0-9]+\\)\$/", $line)) { break; } // handle FLAGS reply after headers (AOL, Zimbra?) if (preg_match('/\\s+FLAGS \\((.*)\\)\\)$/', $line, $matches)) { $flags_str = $matches[1]; break; } if (ord($line[0]) <= 32) { $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($line); } else { $lines[++$ln] = trim($line); } // patch from "Maksim Rubis" <*****@*****.**> } while ($line[0] != ')' && !$this->startsWith($line, $key, true)); if (strncmp($line, $key, strlen($key))) { // process header, fill rcube_mail_header obj. // initialize if (is_array($headers)) { reset($headers); while (list($k, $bar) = each($headers)) { $headers[$k] = ''; } } // create array with header field:data while (list($lines_key, $str) = each($lines)) { list($field, $string) = $this->splitHeaderLine($str); $field = strtolower($field); $string = preg_replace('/\\n\\s*/', ' ', $string); switch ($field) { case 'date': $result[$id]->date = $string; $result[$id]->timestamp = $this->strToTime($string); break; case 'from': $result[$id]->from = $string; break; case 'to': $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string); break; case 'subject': $result[$id]->subject = $string; break; case 'reply-to': $result[$id]->replyto = $string; break; case 'cc': $result[$id]->cc = $string; break; case 'bcc': $result[$id]->bcc = $string; break; case 'content-transfer-encoding': $result[$id]->encoding = $string; break; case 'content-type': $ctype_parts = preg_split('/[; ]/', $string); $result[$id]->ctype = strtolower(array_shift($ctype_parts)); if (preg_match('/charset\\s*=\\s*"?([a-z0-9\\-\\.\\_]+)"?/i', $string, $regs)) { $result[$id]->charset = $regs[1]; } break; case 'in-reply-to': $result[$id]->in_reply_to = str_replace(array("\n", '<', '>'), '', $string); break; case 'references': $result[$id]->references = $string; break; case 'return-receipt-to': case 'disposition-notification-to': case 'x-confirm-reading-to': $result[$id]->mdn_to = $string; break; case 'message-id': $result[$id]->messageID = $string; break; case 'x-priority': if (preg_match('/^(\\d+)/', $string, $matches)) { $result[$id]->priority = intval($matches[1]); } break; default: if (strlen($field) > 2) { $result[$id]->others[$field] = $string; } break; } // end switch () } // end while () } // process flags if (!empty($flags_str)) { $flags_str = preg_replace('/[\\\\"]/', '', $flags_str); $flags_a = explode(' ', $flags_str); if (is_array($flags_a)) { foreach ($flags_a as $flag) { $flag = strtoupper($flag); if ($flag == 'SEEN') { $result[$id]->seen = true; } else { if ($flag == 'DELETED') { $result[$id]->deleted = true; } else { if ($flag == 'RECENT') { $result[$id]->recent = true; } else { if ($flag == 'ANSWERED') { $result[$id]->answered = true; } else { if ($flag == '$FORWARDED') { $result[$id]->forwarded = true; } else { if ($flag == 'DRAFT') { $result[$id]->is_draft = true; } else { if ($flag == '$MDNSENT') { $result[$id]->mdn_sent = true; } else { if ($flag == 'FLAGGED') { $result[$id]->flagged = true; } } } } } } } } } $result[$id]->flags = $flags_a; } } } } while (!$this->startsWith($line, $key, true)); return $result; }