From f0b2ed87fc67f8bbba522a60395099cb0d622c6a Mon Sep 17 00:00:00 2001 From: Thomas <thomas@roundcube.net> Date: Thu, 17 Oct 2013 03:12:01 -0400 Subject: [PATCH] Revert "Backported the canned responses feature to this release branch" --- program/include/rcmail.php | 386 ++++++++++++++++++++++++++++++++++++------------------ 1 files changed, 257 insertions(+), 129 deletions(-) diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 7f9d7af..01f7d1c 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -19,9 +19,6 @@ | Author: Thomas Bruederli <roundcube@gmail.com> | | Author: Aleksander Machniak <alec@alec.pl> | +-----------------------------------------------------------------------+ - - $Id$ - */ @@ -59,7 +56,11 @@ private $action_map = array(); - const JS_OBJECT_NAME = 'rcmail'; + const ERROR_STORAGE = -2; + const ERROR_INVALID_REQUEST = 1; + const ERROR_INVALID_HOST = 2; + const ERROR_COOKIES_DISABLED = 3; + /** * This implements the 'singleton' design pattern @@ -91,16 +92,16 @@ // create user object $this->set_user(new rcube_user($_SESSION['user_id'])); - // configure session (after user config merge!) - $this->session_configure(); - // set task and action properties $this->set_task(rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC)); $this->action = asciiwords(rcube_utils::get_input_value('_action', rcube_utils::INPUT_GPC)); // reset some session parameters when changing task if ($this->task != 'utils') { - if ($this->session && $_SESSION['task'] != $this->task) + // we reset list page when switching to another task + // but only to the main task interface - empty action (#1489076) + // this will prevent from unintentional page reset on cross-task requests + if ($this->session && $_SESSION['task'] != $this->task && empty($this->action)) $this->session->remove('page'); // set current task to session $_SESSION['task'] = $this->task; @@ -154,21 +155,23 @@ $this->config->set_user_prefs((array)$this->user->get_prefs()); } - $_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language'])); + $lang = $this->language_prop($this->config->get('language', $_SESSION['language'])); + $_SESSION['language'] = $this->user->language = $lang; // set localization - setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8'); + setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8'); // workaround for http://bugs.php.net/bug.php?id=18556 - if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ'))) - setlocale(LC_CTYPE, 'en_US' . '.utf8'); + if (in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) { + setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8'); + } } /** * Return instance of the internal address book class * - * @param string Address book identifier + * @param string Address book identifier (-1 for default addressbook) * @param boolean True if the address book needs to be writeable * * @return rcube_contacts Address book object @@ -177,17 +180,17 @@ { $contacts = null; $ldap_config = (array)$this->config->get('ldap_public'); - $abook_type = strtolower($this->config->get('address_book_type')); // 'sql' is the alias for '0' used by autocomplete if ($id == 'sql') - $id = '0'; + $id = '0'; + else if ($id == -1) { + $id = $this->config->get('default_addressbook'); + $default = true; + } // use existing instance - if (isset($this->address_books[$id]) && is_object($this->address_books[$id]) - && is_a($this->address_books[$id], 'rcube_addressbook') - && (!$writeable || !$this->address_books[$id]->readonly) - ) { + if (isset($this->address_books[$id]) && ($this->address_books[$id] instanceof rcube_addressbook)) { $contacts = $this->address_books[$id]; } else if ($id && $ldap_config[$id]) { @@ -203,18 +206,33 @@ if ($plugin['instance'] instanceof rcube_addressbook) { $contacts = $plugin['instance']; } - // get first source from the list - else if (!$id) { - $source = reset($this->get_address_sources($writeable)); - if (!empty($source)) { - $contacts = $this->get_address_book($source['id']); - if ($contacts) - $id = $source['id']; + } + + // when user requested default writeable addressbook + // we need to check if default is writeable, if not we + // will return first writeable book (if any exist) + if ($contacts && $default && $contacts->readonly && $writeable) { + $contacts = null; + } + + // Get first addressbook from the list if configured default doesn't exist + // This can happen when user deleted the addressbook (e.g. Kolab folder) + if (!$contacts && (!$id || $default)) { + $source = reset($this->get_address_sources($writeable, !$default)); + if (!empty($source)) { + $contacts = $this->get_address_book($source['id']); + if ($contacts) { + $id = $source['id']; } } } if (!$contacts) { + // there's no default, just return + if ($default) { + return null; + } + self::raise_error(array( 'code' => 700, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, @@ -222,14 +240,36 @@ true, true); } - // set configured sort order - if ($sort_col = $this->config->get('addressbook_sort_col')) - $contacts->set_sort_order($sort_col); - // add to the 'books' array for shutdown function $this->address_books[$id] = $contacts; + if ($writeable && $contacts->readonly) { + return null; + } + + // set configured sort order + if ($sort_col = $this->config->get('addressbook_sort_col')) { + $contacts->set_sort_order($sort_col); + } + return $contacts; + } + + + /** + * Return identifier of the address book object + * + * @param rcube_addressbook Addressbook source object + * + * @return string Source identifier + */ + public function get_address_book_id($object) + { + foreach ($this->address_books as $index => $book) { + if ($book === $object) { + return $index; + } + } } @@ -237,18 +277,19 @@ * Return address books list * * @param boolean True if the address book needs to be writeable + * @param boolean True if the address book needs to be not hidden * * @return array Address books array */ - public function get_address_sources($writeable = false) + public function get_address_sources($writeable = false, $skip_hidden = false) { $abook_type = strtolower($this->config->get('address_book_type')); $ldap_config = $this->config->get('ldap_public'); $autocomplete = (array) $this->config->get('autocomplete_addressbooks'); $list = array(); - // We are using the DB address book - if ($abook_type != 'ldap') { + // We are using the DB address book or a plugin address book + if ($abook_type != 'ldap' && $abook_type != '') { if (!isset($this->address_books['0'])) $this->address_books['0'] = new rcube_contacts($this->db, $this->get_user_id()); $list['0'] = array( @@ -270,8 +311,8 @@ } $list[$id] = array( 'id' => $id, - 'name' => $prop['name'], - 'groups' => is_array($prop['groups']), + 'name' => html::quote($prop['name']), + 'groups' => !empty($prop['groups']) || !empty($prop['group_filters']), 'readonly' => !$prop['writable'], 'hidden' => $prop['hidden'], 'autocomplete' => in_array($id, $autocomplete) @@ -284,11 +325,17 @@ foreach ($list as $idx => $item) { // register source for shutdown function - if (!is_object($this->address_books[$item['id']])) + if (!is_object($this->address_books[$item['id']])) { $this->address_books[$item['id']] = $item; + } // remove from list if not writeable as requested - if ($writeable && $item['readonly']) + if ($writeable && $item['readonly']) { unset($list[$idx]); + } + // remove from list if hidden as requested + else if ($skip_hidden && $item['hidden']) { + unset($list[$idx]); + } } return $list; @@ -297,22 +344,21 @@ /** * Init output object for GUI and add common scripts. - * This will instantiate a rcmail_template object and set + * This will instantiate a rcmail_output_html object and set * environment vars according to the current session and configuration * * @param boolean True if this request is loaded in a (i)frame - * @return rcube_output_html Reference to HTML output object + * @return rcube_output Reference to HTML output object */ public function load_gui($framed = false) { // init output page - if (!($this->output instanceof rcube_output_html)) - $this->output = new rcube_output_html($this->task, $framed); + if (!($this->output instanceof rcmail_output_html)) + $this->output = new rcmail_output_html($this->task, $framed); - // set keep-alive/check-recent interval - if ($this->session && ($keep_alive = $this->session->get_keep_alive())) { - $this->output->set_env('keep_alive', $keep_alive); - } + // set refresh interval + $this->output->set_env('refresh_interval', $this->config->get('refresh_interval', 0)); + $this->output->set_env('session_lifetime', $this->config->get('session_lifetime', 0) * 60); if ($framed) { $this->comm_path .= '&_framed=1'; @@ -322,10 +368,10 @@ $this->output->set_env('task', $this->task); $this->output->set_env('action', $this->action); $this->output->set_env('comm_path', $this->comm_path); - $this->output->set_charset(RCMAIL_CHARSET); + $this->output->set_charset(RCUBE_CHARSET); // add some basic labels to client - $this->output->add_label('loading', 'servererror', 'requesttimedout'); + $this->output->add_label('loading', 'servererror', 'requesttimedout', 'refreshing'); return $this->output; } @@ -334,12 +380,12 @@ /** * Create an output object for JSON responses * - * @return rcube_output_json Reference to JSON output object + * @return rcube_output Reference to JSON output object */ public function json_init() { - if (!($this->output instanceof rcube_output_json)) - $this->output = new rcube_output_json($this->task); + if (!($this->output instanceof rcmail_output_json)) + $this->output = new rcmail_output_json($this->task); return $this->output; } @@ -369,12 +415,20 @@ * @param string Mail storage (IMAP) user name * @param string Mail storage (IMAP) password * @param string Mail storage (IMAP) host + * @param bool Enables cookie check * * @return boolean True on success, False on failure */ - function login($username, $pass, $host=NULL) + function login($username, $pass, $host = null, $cookiecheck = false) { + $this->login_error = null; + if (empty($username)) { + return false; + } + + if ($cookiecheck && empty($_COOKIE)) { + $this->login_error = self::ERROR_COOKIES_DISABLED; return false; } @@ -394,11 +448,18 @@ break; } } - if (!$allowed) - return false; + if (!$allowed) { + $host = null; } - else if (!empty($config['default_host']) && $host != rcube_utils::parse_host($config['default_host'])) + } + else if (!empty($config['default_host']) && $host != rcube_utils::parse_host($config['default_host'])) { + $host = null; + } + + if (!$host) { + $this->login_error = self::ERROR_INVALID_HOST; return false; + } // parse $host URL $a_host = parse_url($host); @@ -426,10 +487,21 @@ $username .= '@'.rcube_utils::parse_host($config['username_domain'], $host); } + if (!isset($config['login_lc'])) { + $config['login_lc'] = 2; // default + } + // Convert username to lowercase. If storage backend // is case-insensitive we need to store always the same username (#1487113) if ($config['login_lc']) { - $username = mb_strtolower($username); + if ($config['login_lc'] == 2 || $config['login_lc'] === true) { + $username = mb_strtolower($username); + } + else if (strpos($username, '@')) { + // lowercase domain name + list($local, $domain) = explode('@', $username); + $username = $local . '@' . mb_strtolower($domain); + } } // try to resolve email address from virtuser table @@ -439,36 +511,18 @@ // Here we need IDNA ASCII // Only rcube_contacts class is using domain names in Unicode - $host = rcube_utils::idn_to_ascii($host); - if (strpos($username, '@')) { - // lowercase domain name - list($local, $domain) = explode('@', $username); - $username = $local . '@' . mb_strtolower($domain); - $username = rcube_utils::idn_to_ascii($username); - } + $host = rcube_utils::idn_to_ascii($host); + $username = rcube_utils::idn_to_ascii($username); // user already registered -> overwrite username - if ($user = rcube_user::query($username, $host)) + if ($user = rcube_user::query($username, $host)) { $username = $user->data['username']; + } $storage = $this->get_storage(); // try to log in - if (!($login = $storage->connect($host, $username, $pass, $port, $ssl))) { - // try with lowercase - $username_lc = mb_strtolower($username); - if ($username_lc != $username) { - // try to find user record again -> overwrite username - if (!$user && ($user = rcube_user::query($username_lc, $host))) - $username_lc = $user->data['username']; - - if ($login = $storage->connect($host, $username_lc, $pass, $port, $ssl)) - $username = $username_lc; - } - } - - // exit if login failed - if (!$login) { + if (!$storage->connect($host, $username, $pass, $port, $ssl)) { return false; } @@ -503,7 +557,6 @@ // Configure environment $this->set_user($user); $this->set_storage_prop(); - $this->session_configure(); // fix some old settings according to namespace prefix $this->fix_namespace_settings($user); @@ -523,9 +576,7 @@ $_SESSION['login_time'] = time(); if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_') - $_SESSION['timezone'] = floatval($_REQUEST['_timezone']); - if (isset($_REQUEST['_dstactive']) && $_REQUEST['_dstactive'] != '_default_') - $_SESSION['dst_active'] = intval($_REQUEST['_dstactive']); + $_SESSION['timezone'] = rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_GPC); // force reloading complete list of subscribed mailboxes $storage->clear_cache('mailboxes', true); @@ -535,6 +586,23 @@ return false; } + + + /** + * Returns error code of last login operation + * + * @return int Error code + */ + public function login_error() + { + if ($this->login_error) { + return $this->login_error; + } + + if ($this->storage && $this->storage->get_error_code() < -1) { + return self::ERROR_STORAGE; + } + } /** @@ -549,15 +617,16 @@ if (is_array($default_host)) { $post_host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST); + $post_user = rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST); + + list($user, $domain) = explode('@', $post_user); // direct match in default_host array if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) { $host = $post_host; } - // try to select host by mail domain - list($user, $domain) = explode('@', rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST)); - if (!empty($domain)) { + else if (!empty($domain)) { foreach ($default_host as $storage_host => $mail_domains) { if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) { $host = $storage_host; @@ -684,8 +753,12 @@ */ public function url($p) { - if (!is_array($p)) + if (!is_array($p)) { + if (strpos($p, 'http') === 0) + return $p; + $p = array('_action' => @func_get_arg(0)); + } $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task); $p['_task'] = $task; @@ -736,6 +809,7 @@ } } + /** * Registers action aliases for current task * @@ -750,6 +824,7 @@ } } + /** * Returns current action filename * @@ -763,6 +838,7 @@ return strtr($this->action, '-', '_') . '.inc'; } + /** * Fixes some user preferences according to namespace handling change. @@ -883,15 +959,30 @@ * @param object $message Reference to Mail_MIME object * @param string $from Sender address string * @param array $mailto Array of recipient address strings - * @param array $smtp_error SMTP error array (reference) + * @param array $error SMTP error array (reference) * @param string $body_file Location of file with saved message body (reference), * used when delay_file_io is enabled - * @param array $smtp_opts SMTP options (e.g. DSN request) + * @param array $options SMTP options (e.g. DSN request) * * @return boolean Send status. */ - public function deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file = null, $smtp_opts = null) + public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null) { + $plugin = $this->plugins->exec_hook('message_before_send', array( + 'message' => $message, + 'from' => $from, + 'mailto' => $mailto, + 'options' => $options, + )); + + if ($plugin['abort']) { + return isset($plugin['result']) ? $plugin['result'] : false; + } + + $from = $plugin['from']; + $mailto = $plugin['mailto']; + $options = $plugin['options']; + $message = $plugin['message']; $headers = $message->headers(); // send thru SMTP server using custom SMTP library @@ -934,15 +1025,15 @@ $this->smtp_init(true); } - $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts); - $smtp_response = $this->smtp->get_response(); - $smtp_error = $this->smtp->get_error(); + $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options); + $response = $this->smtp->get_response(); + $error = $this->smtp->get_error(); // log error if (!$sent) { self::raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__, - 'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE); + 'message' => "SMTP error: ".join("\n", $response)), TRUE, FALSE); } } // send mail using PHP's mail() function @@ -984,7 +1075,7 @@ $subject = str_replace("\r\n", $delim, $subject); } - if (ini_get('safe_mode')) + if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN)) $sent = mail($to, $subject, $msg_body, $header_str); else $sent = mail($to, $subject, $msg_body, $header_str, "-f$from"); @@ -1010,7 +1101,7 @@ $this->user->get_username(), $_SERVER['REMOTE_ADDR'], $mailto, - !empty($smtp_response) ? join('; ', $smtp_response) : '')); + !empty($response) ? join('; ', $response) : '')); } } @@ -1158,7 +1249,7 @@ } else { if (!empty($date)) { - $timestamp = rcube_strtotime($date); + $timestamp = rcube_utils::strtotime($date); } if (empty($timestamp)) { @@ -1329,11 +1420,12 @@ $attrib = $hook['attribs']; if ($type == 'select') { + $attrib['is_escaped'] = true; $select = new html_select($attrib); // add no-selection option if ($attrib['noselection']) { - $select->add($rcmail->gettext($attrib['noselection']), ''); + $select->add(html::quote($rcmail->gettext($attrib['noselection'])), ''); } $rcmail->render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']); @@ -1343,6 +1435,7 @@ $js_mailboxlist = array(); $out = html::tag('ul', $attrib, $rcmail->render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib); + $rcmail->output->include_script('treelist.js'); $rcmail->output->add_gui_object('mailboxlist', $attrib['id']); $rcmail->output->set_env('mailboxes', $js_mailboxlist); $rcmail->output->set_env('unreadwrap', $attrib['unreadwrap']); @@ -1362,7 +1455,7 @@ */ public function folder_selector($p = array()) { - $p += array('maxlength' => 100, 'realnames' => false); + $p += array('maxlength' => 100, 'realnames' => false, 'is_escaped' => true); $a_mailboxes = array(); $storage = $this->get_storage(); @@ -1388,7 +1481,7 @@ $select = new html_select($p); if ($p['noselection']) { - $select->add($p['noselection'], ''); + $select->add(html::quote($p['noselection']), ''); } $this->render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p); @@ -1512,7 +1605,7 @@ $html_name = $this->Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : ''); $link_attrib = $folder['virtual'] ? array() : array( 'href' => $this->url(array('_mbox' => $folder['id'])), - 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail::JS_OBJECT_NAME, $js_name), + 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail_output::JS_OBJECT_NAME, $js_name), 'rel' => $folder['id'], 'title' => $title, ); @@ -1521,14 +1614,13 @@ 'id' => "rcmli".$folder_id, 'class' => join(' ', $classes), 'noclose' => true), - html::a($link_attrib, $html_name) . - (!empty($folder['folders']) ? html::div(array( - 'class' => ($is_collapsed ? 'collapsed' : 'expanded'), - 'style' => "position:absolute", - 'onclick' => sprintf("%s.command('collapse-folder', '%s')", rcmail::JS_OBJECT_NAME, $js_name) - ), ' ') : '')); + html::a($link_attrib, $html_name)); - $jslist[$folder_id] = array( + if (!empty($folder['folders'])) { + $out .= html::div('treetoggle ' . ($is_collapsed ? 'collapsed' : 'expanded'), ' '); + } + + $jslist[$folder['id']] = array( 'id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual'] @@ -1579,7 +1671,7 @@ } } - $select->add(str_repeat(' ', $nestLevel*4) . $foldername, $folder['id']); + $select->add(str_repeat(' ', $nestLevel*4) . html::quote($foldername), $folder['id']); if (!empty($folder['folders'])) { $out .= $this->render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, @@ -1722,10 +1814,7 @@ $err_code = $this->storage->get_error_code(); $res_code = $this->storage->get_response_code(); - if ($err_code < 0) { - $this->output->show_message('storageerror', 'error'); - } - else if ($res_code == rcube_storage::NOPERM) { + if ($res_code == rcube_storage::NOPERM) { $this->output->show_message('errornoperm', 'error'); } else if ($res_code == rcube_storage::READONLY) { @@ -1739,6 +1828,9 @@ else { $this->output->show_message('servererrormsg', 'error', array('msg' => $err_str)); } + } + else if ($err_code < 0) { + $this->output->show_message('storageerror', 'error'); } else if ($fallback) { $this->output->show_message($fallback, 'error', $fallback_args); @@ -1863,7 +1955,8 @@ public function upload_init() { // Enable upload progress bar - if (($seconds = $this->config->get('upload_progress')) && ini_get('apc.rfc1867')) { + $rfc1867 = filter_var(ini_get('apc.rfc1867'), FILTER_VALIDATE_BOOLEAN); + if ($rfc1867 && ($seconds = $this->config->get('upload_progress'))) { if ($field_name = ini_get('apc.rfc1867_name')) { $this->output->set_env('upload_progress_name', $field_name); $this->output->set_env('upload_progress_time', (int) $seconds); @@ -1975,26 +2068,27 @@ /** - * Quote a given string. - * Shortcut function for rcube_utils::rep_specialchars_output() + * Returns real size (calculated) of the message part * - * @return string HTML-quoted string - */ - public static function Q($str, $mode = 'strict', $newlines = true) - { - return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines); - } - - - /** - * Quote a given string for javascript output. - * Shortcut function for rcube_utils::rep_specialchars_output() + * @param rcube_message_part Message part * - * @return string JS-quoted string + * @return string Part size (and unit) */ - public static function JQ($str) + public function message_part_size($part) { - return rcube_utils::rep_specialchars_output($str, 'js'); + if (isset($part->d_parameters['size'])) { + $size = $this->show_bytes((int)$part->d_parameters['size']); + } + else { + $size = $part->size; + if ($part->encoding == 'base64') { + $size = $size / 1.33; + } + + $size = '~' . $this->show_bytes($size); + } + + return $size; } @@ -2011,4 +2105,38 @@ { return $this->storage_connect(); } + + public function imap_init() + { + return $this->storage_init(); + } + + /** + * Connect to the mail storage server with stored session data + * + * @return bool True on success, False on error + */ + public function storage_connect() + { + $storage = $this->get_storage(); + + if ($_SESSION['storage_host'] && !$storage->is_connected()) { + $host = $_SESSION['storage_host']; + $user = $_SESSION['username']; + $port = $_SESSION['storage_port']; + $ssl = $_SESSION['storage_ssl']; + $pass = $this->decrypt($_SESSION['password']); + + if (!$storage->connect($host, $user, $pass, $port, $ssl)) { + if (is_object($this->output)) { + $this->output->show_message('storageerror', 'error'); + } + } + else { + $this->set_storage_prop(); + } + } + + return $storage->is_connected(); + } } -- Gitblit v1.9.1