diff options
Diffstat (limited to 'program/steps')
27 files changed, 933 insertions, 534 deletions
diff --git a/program/steps/addressbook/delete.inc b/program/steps/addressbook/delete.inc index 81b8a0970..56118583c 100644 --- a/program/steps/addressbook/delete.inc +++ b/program/steps/addressbook/delete.inc @@ -93,7 +93,7 @@ if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$searc while ($row = $result->next()) { $row['sourceid'] = $s; - $key = rcmail_contact_key($row, $sort_col); + $key = rcube_addressbook::compose_contact_key($row, $sort_col); $records[$key] = $row; } unset($result); diff --git a/program/steps/addressbook/export.inc b/program/steps/addressbook/export.inc index 850795c85..761f26b75 100644 --- a/program/steps/addressbook/export.inc +++ b/program/steps/addressbook/export.inc @@ -40,11 +40,31 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search // get records $result = $source->list_records(); - while ($row = $result->next()) { - $row['sourceid'] = $s; - $key = rcmail_contact_key($row, $sort_col); - $records[$key] = $row; + while ($record = $result->next()) { + // because vcard_map is per-source we need to create vcard here + if (empty($record['vcard']) || empty($record['name'])) { + $vcard = new rcube_vcard(); + $vcard->extend_fieldmap($source->vcard_map); + $vcard->load($record['vcard']); + $vcard->reset(); + + foreach ($record as $key => $values) { + list($field, $section) = explode(':', $key); + foreach ((array)$values as $value) { + if (is_array($value) || @strlen($value)) { + $vcard->set($field, $value, strtoupper($section)); + } + } + } + + $record['vcard'] = $vcard->export(true); + } + + $record['sourceid'] = $s; + $key = rcube_addressbook::compose_contact_key($record, $sort_col); + $records[$key] = $record; } + unset($result); } @@ -56,6 +76,51 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search $result = new rcube_result_set($count); $result->records = array_values($records); } +// selected contacts +else if (!empty($_REQUEST['_cid'])) { + $sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name'); + $records = array(); + + // Selected contact IDs (with multi-source support) + $cids = rcmail_get_cids(); + + foreach ($cids as $s => $ids) { + $source = $RCMAIL->get_address_book($s); + $result = $source->search('ID', $ids, 1, true, true); + + while ($record = $result->next()) { + // because vcard_map is per-source we need to create vcard here + if (empty($record['vcard']) || empty($record['name'])) { + $vcard = new rcube_vcard(); + $vcard->extend_fieldmap($source->vcard_map); + $vcard->load($record['vcard']); + $vcard->reset(); + + foreach ($record as $key => $values) { + list($field, $section) = explode(':', $key); + foreach ((array)$values as $value) { + if (is_array($value) || @strlen($value)) { + $vcard->set($field, $value, strtoupper($section)); + } + } + } + + $record['vcard'] = $vcard->export(true); + } + + $record['sourceid'] = $s; + $key = rcube_addressbook::compose_contact_key($record, $sort_col); + $records[$key] = $record; + } + } + + ksort($records, SORT_LOCALE_STRING); + + // create resultset object + $count = count($records); + $result = new rcube_result_set($count); + $result->records = array_values($records); +} // selected directory/group else { $CONTACTS = rcmail_contact_source(null, true); @@ -68,12 +133,14 @@ else { // send downlaod headers header('Content-Type: text/x-vcard; charset='.RCMAIL_CHARSET); -header('Content-Disposition: attachment; filename="rcube_contacts.vcf"'); +header('Content-Disposition: attachment; filename="contacts.vcf"'); while ($result && ($row = $result->next())) { // we already have a vcard record if ($row['vcard'] && $row['name']) { - $row['vcard'] = preg_replace('/\r?\n/', rcube_vcard::$eol, $row['vcard']); + // fix folding and end-of-line chars + $row['vcard'] = preg_replace('/\r|\n\s+/', '', $row['vcard']); + $row['vcard'] = preg_replace('/\n/', rcube_vcard::$eol, $row['vcard']); echo rcube_vcard::rfc2425_fold($row['vcard']) . rcube_vcard::$eol; } // copy values into vcard object diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc index fded9a819..ffc0b3b92 100644 --- a/program/steps/addressbook/func.inc +++ b/program/steps/addressbook/func.inc @@ -26,7 +26,7 @@ $CONTACT_COLTYPES = array( 'name' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('name'), 'category' => 'main'), 'firstname' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('firstname'), 'category' => 'main'), 'surname' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('surname'), 'category' => 'main'), - 'email' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => rcube_label('email'), 'subtypes' => array('home','work','other'), 'category' => 'main'), + 'email' => array('type' => 'text', 'size' => 40, 'maxlength' => 254, 'label' => rcube_label('email'), 'subtypes' => array('home','work','other'), 'category' => 'main'), 'middlename' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('middlename'), 'category' => 'main'), 'prefix' => array('type' => 'text', 'size' => 8, 'maxlength' => 20, 'limit' => 1, 'label' => rcube_label('nameprefix'), 'category' => 'main'), 'suffix' => array('type' => 'text', 'size' => 8, 'maxlength' => 20, 'limit' => 1, 'label' => rcube_label('namesuffix'), 'category' => 'main'), @@ -187,7 +187,7 @@ function rcmail_directory_list($attrib) $jsdata = array(); $line_templ = html::tag('li', array( - 'id' => 'rcmli%s', 'class' => '%s'), + 'id' => 'rcmli%s', 'class' => '%s', 'noclose' => true), html::a(array('href' => '%s', 'rel' => '%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s')); @@ -213,7 +213,7 @@ function rcmail_directory_list($attrib) $name = !empty($source['name']) ? $source['name'] : $id; $out .= sprintf($line_templ, - html_identifier($id), + rcube_utils::html_identifier($id, true), $class_name, Q(rcmail_url(null, array('_source' => $id))), $source['id'], @@ -224,10 +224,11 @@ function rcmail_directory_list($attrib) $groupdata = rcmail_contact_groups($groupdata); $jsdata = $groupdata['jsdata']; $out = $groupdata['out']; + $out .= '</li>'; } $line_templ = html::tag('li', array( - 'id' => 'rcmliS%s', 'class' => '%s'), + 'id' => 'rcmli%s', 'class' => '%s'), html::a(array('href' => '#', 'rel' => 'S%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('listsearch', '%s', this)"), '%s')); @@ -245,14 +246,17 @@ function rcmail_directory_list($attrib) $class_name .= ' ' . $source['class_name']; $out .= sprintf($line_templ, - html_identifier($id), + rcube_utils::html_identifier('S'.$id, true), $class_name, $id, $js_id, (!empty($source['name']) ? Q($source['name']) : Q($id))); } $OUTPUT->set_env('contactgroups', $jsdata); + $OUTPUT->set_env('collapsed_abooks', (string)$RCMAIL->config->get('collapsed_abooks','')); $OUTPUT->add_gui_object('folderlist', $attrib['id']); + $OUTPUT->include_script('treelist.js'); + // add some labels to client $OUTPUT->add_label('deletegroupconfirm', 'groupdeleting', 'addingmember', 'removingmember'); @@ -264,19 +268,25 @@ function rcmail_contact_groups($args) { global $RCMAIL; + $groups_html = ''; $groups = $RCMAIL->get_address_book($args['source'])->list_groups(); + $js_id = $RCMAIL->JQ($args['source']); if (!empty($groups)) { $line_templ = html::tag('li', array( - 'id' => 'rcmliG%s', 'class' => 'contactgroup'), + 'id' => 'rcmli%s', 'class' => 'contactgroup'), html::a(array('href' => '#', 'rel' => '%s:%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('listgroup',{'source':'%s','id':'%s'},this)"), '%s')); + // append collapse/expand toggle and open a new <ul> + $is_collapsed = strpos($RCMAIL->config->get('collapsed_abooks',''), '&'.rawurlencode($args['source']).'&') !== false; + $args['out'] .= html::div('treetoggle ' . ($is_collapsed ? 'collapsed' : 'expanded'), ' '); + $jsdata = array(); foreach ($groups as $group) { - $args['out'] .= sprintf($line_templ, - html_identifier($args['source'] . $group['ID']), + $groups_html .= sprintf($line_templ, + rcube_utils::html_identifier('G' . $args['source'] . $group['ID'], true), $args['source'], $group['ID'], $args['source'], $group['ID'], Q($group['name']) ); @@ -286,6 +296,10 @@ function rcmail_contact_groups($args) } } + $args['out'] .= html::tag('ul', + array('class' => 'groups', 'style' => ($is_collapsed ? "display:none;" : null)), + $groups_html); + return $args; } @@ -733,30 +747,12 @@ function rcmail_format_date_col($val) } -function rcmail_contact_key($row, $sort_col) -{ - $key = $row[$sort_col] . ':' . $row['sourceid']; - - // add email to a key to not skip contacts with the same name (#1488375) - if (!empty($row['email'])) { - if (is_array($row['email'])) { - $key .= ':' . implode(':', $row['email']); - } - else { - $key .= ':' . $row['email']; - } - } - - return $key; -} - - /** * Returns contact ID(s) and source(s) from GET/POST data * * @return array List of contact IDs per-source */ -function rcmail_get_cids() +function rcmail_get_cids($filter = null) { // contact ID (or comma-separated list of IDs) is provided in two // forms. If _source is an empty string then the ID is a string @@ -765,6 +761,10 @@ function rcmail_get_cids() $cid = get_input_value('_cid', RCUBE_INPUT_GPC); $source = (string) get_input_value('_source', RCUBE_INPUT_GPC); + if (is_array($cid)) { + return $cid; + } + if (!preg_match('/^[a-zA-Z0-9\+\/=_-]+(,[a-zA-Z0-9\+\/=_-]+)*$/', $cid)) { return array(); } @@ -775,24 +775,29 @@ function rcmail_get_cids() // create per-source contact IDs array foreach ($cid as $id) { - // if _source is not specified we'll find it from decoded ID + // extract source ID from contact ID (it's there in search mode) + // see #1488959 and #1488862 for reference if (!$got_source) { if ($sep = strrpos($id, '-')) { $contact_id = substr($id, 0, $sep); - $source_id = substr($id, $sep+1); + $source_id = (string) substr($id, $sep+1); if (strlen($source_id)) { - $result[(string)$source_id][] = $contact_id; + $result[$source_id][] = $contact_id; } } } else { + if (substr($id, -($got_source+1)) == "-$source") { + $id = substr($id, 0, -($got_source+1)); + } $result[$source][] = $id; } } - return $result; + return $filter !== null ? $result[$filter] : $result; } + // register UI objects $OUTPUT->add_handlers(array( 'directorylist' => 'rcmail_directory_list', diff --git a/program/steps/addressbook/groups.inc b/program/steps/addressbook/groups.inc index b70453889..3b9288a2b 100644 --- a/program/steps/addressbook/groups.inc +++ b/program/steps/addressbook/groups.inc @@ -20,7 +20,7 @@ */ $source = get_input_value('_source', RCUBE_INPUT_GPC); -$CONTACTS = rcmail_contact_source($source, true); +$CONTACTS = rcmail_contact_source($source); if ($CONTACTS->readonly || !$CONTACTS->groups) { $OUTPUT->show_message('sourceisreadonly', 'warning'); @@ -28,11 +28,11 @@ if ($CONTACTS->readonly || !$CONTACTS->groups) { } if ($RCMAIL->action == 'group-addmembers') { - if (($gid = get_input_value('_gid', RCUBE_INPUT_POST)) && ($ids = get_input_value('_cid', RCUBE_INPUT_POST))) { + if (($gid = get_input_value('_gid', RCUBE_INPUT_POST)) && ($ids = rcmail_get_cids($source))) { $plugin = $RCMAIL->plugins->exec_hook('group_addmembers', array('group_id' => $gid, 'ids' => $ids, 'source' => $source)); $CONTACTS->set_group($gid); - $num2add = count(explode(',', $plugin['ids'])); + $num2add = count($plugin['ids']); if (!$plugin['abort']) { if (($maxnum = $RCMAIL->config->get('max_group_members', 0)) && ($CONTACTS->count()->count + $num2add > $maxnum)) { @@ -55,7 +55,7 @@ if ($RCMAIL->action == 'group-addmembers') { } else if ($RCMAIL->action == 'group-delmembers') { - if (($gid = get_input_value('_gid', RCUBE_INPUT_POST)) && ($ids = get_input_value('_cid', RCUBE_INPUT_POST))) { + if (($gid = get_input_value('_gid', RCUBE_INPUT_POST)) && ($ids = rcmail_get_cids($source))) { $plugin = $RCMAIL->plugins->exec_hook('group_delmembers', array('group_id' => $gid, 'ids' => $ids, 'source' => $source)); if (!$plugin['abort']) @@ -63,10 +63,11 @@ else if ($RCMAIL->action == 'group-delmembers') { else $result = $plugin['result']; - if ($result){ + if ($result) { $OUTPUT->show_message('contactremovedfromgroup'); $OUTPUT->command('remove_group_contacts',array('source' => $source, 'gid' => $gid)); - }else{ + } + else { $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error'); } } diff --git a/program/steps/addressbook/import.inc b/program/steps/addressbook/import.inc index df07d64bc..72da15078 100644 --- a/program/steps/addressbook/import.inc +++ b/program/steps/addressbook/import.inc @@ -209,6 +209,15 @@ if (is_array($_FILES['_file'])) { foreach ($vcards as $vcard) { $a_record = $vcard->get_assoc(); + // Generate contact's display name (must be before validation), the same we do in save.inc + if (empty($a_record['name'])) { + $a_record['name'] = rcube_addressbook::compose_display_name($a_record, true); + // Reset it if equals to email address (from compose_display_name()) + if ($a_record['name'] == $a_record['email'][0]) { + $a_record['name'] = ''; + } + } + // skip invalid (incomplete) entries if (!$CONTACTS->validate($a_record, true)) { $IMPORT_STATS->invalid++; @@ -250,7 +259,7 @@ if (is_array($_FILES['_file'])) { if ($success) { $IMPORT_STATS->inserted++; - $IMPORT_STATS->names[] = $vcard->displayname ? $vcard->displayname : $email; + $IMPORT_STATS->names[] = $a_record['name'] ? $a_record['name'] : $email; } else { $IMPORT_STATS->errors++; diff --git a/program/steps/addressbook/list.inc b/program/steps/addressbook/list.inc index 06a1e10a3..1bb28658b 100644 --- a/program/steps/addressbook/list.inc +++ b/program/steps/addressbook/list.inc @@ -49,7 +49,7 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search while ($row = $result->next()) { $row['sourceid'] = $s; - $key = rcmail_contact_key($row, $sort_col); + $key = rcube_addressbook::compose_contact_key($row, $sort_col); $records[$key] = $row; } unset($result); diff --git a/program/steps/addressbook/save.inc b/program/steps/addressbook/save.inc index 901ea0190..8cab6e817 100644 --- a/program/steps/addressbook/save.inc +++ b/program/steps/addressbook/save.inc @@ -82,7 +82,7 @@ if (empty($a_record['name'])) { // do input checks (delegated to $CONTACTS instance) if (!$CONTACTS->validate($a_record)) { $err = (array)$CONTACTS->get_error(); - $OUTPUT->show_message($err['message'] ? $err['message'] : 'formincomplete', 'warning'); + $OUTPUT->show_message($err['message'] ? Q($err['message']) : 'formincomplete', 'warning'); $GLOBALS['EDIT_RECORD'] = $a_record; // store submitted data to be used in edit form rcmail_overwrite_action($return_action); return; diff --git a/program/steps/addressbook/search.inc b/program/steps/addressbook/search.inc index 851325070..d153c255a 100644 --- a/program/steps/addressbook/search.inc +++ b/program/steps/addressbook/search.inc @@ -184,7 +184,7 @@ function rcmail_contact_search() while ($row = $result->next()) { $row['sourceid'] = $s['id']; - $key = rcmail_contact_key($row, $sort_col); + $key = rcube_addressbook::compose_contact_key($row, $sort_col); $records[$key] = $row; } @@ -300,9 +300,13 @@ function rcmail_contact_search_form($attrib) $label = isset($colprop['label']) ? $colprop['label'] : rcube_label($col); $category = $colprop['category'] ? $colprop['category'] : 'other'; - if ($ftype == 'text') + // load jquery UI datepicker for date fields + if ($colprop['type'] == 'date') + $colprop['class'] .= ($colprop['class'] ? ' ' : '') . 'datepicker'; + else if ($ftype == 'text') $colprop['size'] = $i_size; + $content = html::div('row', html::div('contactfieldlabel label', Q($label)) . html::div('contactfieldcontent', rcmail_get_edit_field('search_'.$col, '', $colprop, $ftype))); diff --git a/program/steps/mail/attachments.inc b/program/steps/mail/attachments.inc index 180fc0bb9..f83f6892e 100644 --- a/program/steps/mail/attachments.inc +++ b/program/steps/mail/attachments.inc @@ -27,8 +27,10 @@ if (!empty($_GET['_progress'])) { $COMPOSE_ID = get_input_value('_id', RCUBE_INPUT_GPC); $COMPOSE = null; -if ($COMPOSE_ID && $_SESSION['compose_data_'.$COMPOSE_ID]) - $COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID]; +if ($COMPOSE_ID && $_SESSION['compose_data_' . $COMPOSE_ID]) { + $SESSION_KEY = 'compose_data_' . $COMPOSE_ID; + $COMPOSE =& $_SESSION[$SESSION_KEY]; +} if (!$COMPOSE) { die("Invalid session var!"); @@ -45,7 +47,7 @@ if ($RCMAIL->action=='remove-attachment') $attachment = $RCMAIL->plugins->exec_hook('attachment_delete', $attachment); if ($attachment['status']) { if (is_array($COMPOSE['attachments'][$id])) { - unset($COMPOSE['attachments'][$id]); + $RCMAIL->session->remove($SESSION_KEY.'.attachments.'.$id); $OUTPUT->command('remove_from_attachment_list', "rcmfile$id"); } } @@ -77,11 +79,7 @@ if ($RCMAIL->action=='display-attachment') exit; } -// attachment upload action - -if (!is_array($COMPOSE['attachments'])) { - $COMPOSE['attachments'] = array(); -} +/***** attachment upload action *****/ // clear all stored output properties (like scripts and env vars) $OUTPUT->reset(); @@ -112,7 +110,7 @@ if (is_array($_FILES['_attachments']['tmp_name'])) { // store new attachment in session unset($attachment['status'], $attachment['abort']); - $COMPOSE['attachments'][$id] = $attachment; + $RCMAIL->session->append($SESSION_KEY.'.attachments', $id, $attachment); if (($icon = $COMPOSE['deleteicon']) && is_file($icon)) { $button = html::img(array( diff --git a/program/steps/mail/check_recent.inc b/program/steps/mail/check_recent.inc index 90d17c15b..3649d148c 100644 --- a/program/steps/mail/check_recent.inc +++ b/program/steps/mail/check_recent.inc @@ -25,7 +25,7 @@ if (empty($_REQUEST['_folderlist']) && empty($_REQUEST['_list'])) { return; } -$current = $RCMAIL->storage->get_folder(); +$current = $RCMAIL->storage->get_folder(); $check_all = $RCMAIL->action != 'refresh' || (bool)$RCMAIL->config->get('check_all_folders'); // list of folders to check @@ -34,10 +34,15 @@ if ($check_all) { } else { $a_mailboxes = (array) $current; - if ($a_mailboxes[0] != 'INBOX') + if ($current != 'INBOX') { $a_mailboxes[] = 'INBOX'; + } } +// Control folders list from a plugin +$plugin = $RCMAIL->plugins->exec_hook('check_recent', array('folders' => $a_mailboxes, 'all' => $check_all)); +$a_mailboxes = $plugin['folders']; + // check recent/unseen counts foreach ($a_mailboxes as $mbox_name) { $is_current = $mbox_name == $current; @@ -47,12 +52,12 @@ foreach ($a_mailboxes as $mbox_name) { } // Get mailbox status - $status = $RCMAIL->storage->folder_status($mbox_name); + $status = $RCMAIL->storage->folder_status($mbox_name, $diff); if ($status & 1) { // trigger plugin hook $RCMAIL->plugins->exec_hook('new_messages', - array('mailbox' => $mbox_name, 'is_current' => $is_current)); + array('mailbox' => $mbox_name, 'is_current' => $is_current, 'diff' => $diff)); } rcmail_send_unread_count($mbox_name, true, null, @@ -70,13 +75,15 @@ foreach ($a_mailboxes as $mbox_name) { if (!empty($_GET['_quota'])) $OUTPUT->command('set_quota', rcmail_quota_content()); + $OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox_name, 'EXISTS')); + // "No-list" mode, don't get messages if (empty($_GET['_list'])) continue; // get overall message count; allow caching because rcube_storage::folder_status() did a refresh $list_mode = $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL'; - $all_count = $RCMAIL->storage->count(null, $list_mode, false, false); + $all_count = $RCMAIL->storage->count($mbox_name, $list_mode, false, false); $page = $RCMAIL->storage->get_page(); $page_size = $RCMAIL->storage->get_pagesize(); @@ -108,4 +115,7 @@ foreach ($a_mailboxes as $mbox_name) { } } +// trigger refresh hook +$RCMAIL->plugins->exec_hook('refresh', array()); + $OUTPUT->send(); diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 92ec88f1b..36c6d9622 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -20,10 +20,10 @@ */ // define constants for message compose mode -define('RCUBE_COMPOSE_REPLY', 0x0106); -define('RCUBE_COMPOSE_FORWARD', 0x0107); -define('RCUBE_COMPOSE_DRAFT', 0x0108); -define('RCUBE_COMPOSE_EDIT', 0x0109); +define('RCUBE_COMPOSE_REPLY', 'reply'); +define('RCUBE_COMPOSE_FORWARD', 'forward'); +define('RCUBE_COMPOSE_DRAFT', 'draft'); +define('RCUBE_COMPOSE_EDIT', 'edit'); $MESSAGE_FORM = null; $COMPOSE_ID = get_input_value('_id', RCUBE_INPUT_GET); @@ -139,7 +139,6 @@ if (!empty($CONFIG['drafts_mbox'])) { } // set current mailbox in client environment $OUTPUT->set_env('mailbox', $RCMAIL->storage->get_folder()); -$OUTPUT->set_env('sig_above', $RCMAIL->config->get('sig_above', false)); $OUTPUT->set_env('top_posting', intval($RCMAIL->config->get('reply_mode')) > 0); $OUTPUT->set_env('recipients_separator', trim($RCMAIL->config->get('recipients_separator', ','))); @@ -151,34 +150,51 @@ if ($font && !is_array($font)) { // get reference message and set compose mode if ($msg_uid = $COMPOSE['param']['draft_uid']) { - $RCMAIL->storage->set_folder($CONFIG['drafts_mbox']); $compose_mode = RCUBE_COMPOSE_DRAFT; + $OUTPUT->set_env('draft_id', $msg_uid); + $RCMAIL->storage->set_folder($CONFIG['drafts_mbox']); } -else if ($msg_uid = $COMPOSE['param']['reply_uid']) +else if ($msg_uid = $COMPOSE['param']['reply_uid']) { $compose_mode = RCUBE_COMPOSE_REPLY; -else if ($msg_uid = $COMPOSE['param']['forward_uid']) +} +else if ($msg_uid = $COMPOSE['param']['forward_uid']) { $compose_mode = RCUBE_COMPOSE_FORWARD; -else if ($msg_uid = $COMPOSE['param']['uid']) + $COMPOSE['forward_uid'] = $msg_uid; + $COMPOSE['as_attachment'] = !empty($COMPOSE['param']['attachment']); +} +else if ($msg_uid = $COMPOSE['param']['uid']) { $compose_mode = RCUBE_COMPOSE_EDIT; +} +$OUTPUT->set_env('compose_mode', $compose_mode); $config_show_sig = $RCMAIL->config->get('show_sig', 1); -if ($config_show_sig == 1) +if ($compose_mode == RCUBE_COMPOSE_EDIT || $compose_mode == RCUBE_COMPOSE_DRAFT) { + // don't add signature in draft/edit mode, we'll also not remove the old-one +} +else if ($config_show_sig == 1) $OUTPUT->set_env('show_sig', true); -else if ($config_show_sig == 2 && (empty($compose_mode) || $compose_mode == RCUBE_COMPOSE_EDIT || $compose_mode == RCUBE_COMPOSE_DRAFT)) +else if ($config_show_sig == 2 && empty($compose_mode)) $OUTPUT->set_env('show_sig', true); else if ($config_show_sig == 3 && ($compose_mode == RCUBE_COMPOSE_REPLY || $compose_mode == RCUBE_COMPOSE_FORWARD)) $OUTPUT->set_env('show_sig', true); -else - $OUTPUT->set_env('show_sig', false); // set line length for body wrapping $LINE_LENGTH = $RCMAIL->config->get('line_length', 72); -if (!empty($msg_uid)) +if (!empty($msg_uid) && empty($COMPOSE['as_attachment'])) { - // similar as in program/steps/mail/show.inc - // re-set 'prefer_html' to have possibility to use html part for compose - $CONFIG['prefer_html'] = $CONFIG['prefer_html'] || $CONFIG['htmleditor'] || $compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT; + $mbox_name = $RCMAIL->storage->get_folder(); + + // set format before rcube_message construction + // use the same format as for the message view + if (isset($_SESSION['msg_formats'][$mbox_name.':'.$msg_uid])) { + $RCMAIL->config->set('prefer_html', $_SESSION['msg_formats'][$mbox_name.':'.$msg_uid]); + } + else { + $prefer_html = $CONFIG['prefer_html'] || $CONFIG['htmleditor'] || $compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT; + $RCMAIL->config->set('prefer_html', $prefer_html); + } + $MESSAGE = new rcube_message($msg_uid); // make sure message is marked as read @@ -188,8 +204,7 @@ if (!empty($msg_uid)) if (!empty($MESSAGE->headers->charset)) $RCMAIL->storage->set_charset($MESSAGE->headers->charset); - if ($compose_mode == RCUBE_COMPOSE_REPLY) - { + if ($compose_mode == RCUBE_COMPOSE_REPLY) { $COMPOSE['reply_uid'] = $msg_uid; $COMPOSE['reply_msgid'] = $MESSAGE->headers->messageID; $COMPOSE['references'] = trim($MESSAGE->headers->references . " " . $MESSAGE->headers->messageID); @@ -197,8 +212,6 @@ if (!empty($msg_uid)) if (!empty($COMPOSE['param']['all'])) $MESSAGE->reply_all = $COMPOSE['param']['all']; - $OUTPUT->set_env('compose_mode', 'reply'); - // Save the sent message in the same folder of the message being replied to if ($RCMAIL->config->get('reply_same_folder') && ($sent_folder = $COMPOSE['mailbox']) && rcmail_check_sent_folder($sent_folder, false) @@ -206,10 +219,8 @@ if (!empty($msg_uid)) $COMPOSE['param']['sent_mbox'] = $sent_folder; } } - else if ($compose_mode == RCUBE_COMPOSE_DRAFT) - { - if ($MESSAGE->headers->others['x-draft-info']) - { + else if ($compose_mode == RCUBE_COMPOSE_DRAFT) { + if ($MESSAGE->headers->others['x-draft-info']) { // get reply_uid/forward_uid to flag the original message when sending $info = rcmail_draftinfo_decode($MESSAGE->headers->others['x-draft-info']); @@ -233,14 +244,6 @@ if (!empty($msg_uid)) $COMPOSE['references'] = $MESSAGE->headers->references; } - else if ($compose_mode == RCUBE_COMPOSE_FORWARD) - { - $COMPOSE['forward_uid'] = $msg_uid; - $OUTPUT->set_env('compose_mode', 'forward'); - - if (!empty($COMPOSE['param']['attachment'])) - $MESSAGE->forward_attachment = true; - } } else { $MESSAGE = new stdClass(); @@ -249,18 +252,7 @@ else { $MESSAGE->compose = array(); // get user's identities -$MESSAGE->identities = $RCMAIL->user->list_identities(); -if (count($MESSAGE->identities)) -{ - foreach ($MESSAGE->identities as $idx => $ident) { - $ident['email'] = format_email($ident['email']); - $email = format_email(rcube_idn_to_utf8($ident['email'])); - - $MESSAGE->identities[$idx]['email_ascii'] = $ident['email']; - $MESSAGE->identities[$idx]['ident'] = format_email_recipient($ident['email'], $ident['name']); - $MESSAGE->identities[$idx]['email'] = $email; - } -} +$MESSAGE->identities = $RCMAIL->user->list_identities(null, true); // Set From field value if (!empty($_POST['_from'])) { @@ -270,83 +262,10 @@ else if (!empty($COMPOSE['param']['from'])) { $MESSAGE->compose['from'] = $COMPOSE['param']['from']; } else if (count($MESSAGE->identities)) { - $a_recipients = array(); - $a_names = array(); - - // extract all recipients of the reply-message - if (is_object($MESSAGE->headers) && in_array($compose_mode, array(RCUBE_COMPOSE_REPLY, RCUBE_COMPOSE_FORWARD))) - { - $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset); - foreach ($a_to as $addr) { - if (!empty($addr['mailto'])) { - $a_recipients[] = format_email($addr['mailto']); - $a_names[] = $addr['name']; - } - } - - if (!empty($MESSAGE->headers->cc)) { - $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset); - foreach ($a_cc as $addr) { - if (!empty($addr['mailto'])) { - $a_recipients[] = format_email($addr['mailto']); - $a_names[] = $addr['name']; - } - } - } - } - - $from_idx = null; - $found_idx = null; - $default_identity = 0; // default identity is always first on the list - $return_path = $MESSAGE->headers->others['return-path']; - - // Select identity - foreach ($MESSAGE->identities as $idx => $ident) { - // use From header - if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) { - if ($MESSAGE->headers->from == $ident['ident']) { - $from_idx = $idx; - break; - } - } - // reply to yourself - else if ($compose_mode == RCUBE_COMPOSE_REPLY && $MESSAGE->headers->from == $ident['ident']) { - $from_idx = $idx; - break; - } - // use replied message recipients - else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) { - if ($found_idx === null) { - $found_idx = $idx; - } - // match identity name - if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) { - $from_idx = $idx; - break; - } - } - } - - // If matching by name+address doesn't found any amtches, get first found address (identity) - if ($from_idx === null) { - $from_idx = $found_idx; - } - - // Fallback using Return-Path - if ($from_idx === null && $return_path) { - foreach ($MESSAGE->identities as $idx => $ident) { - if (strpos($return_path, str_replace('@', '=', $ident['email_ascii']).'@') !== false) { - $from_idx = $idx; - break; - } - } - } - - $ident = $MESSAGE->identities[$from_idx !== null ? $from_idx : $default_identity]; - $from_id = $ident['identity_id']; + $ident = rcmail_identity_select($MESSAGE, $MESSAGE->identities, $compose_mode); $MESSAGE->compose['from_email'] = $ident['email']; - $MESSAGE->compose['from'] = $from_id; + $MESSAGE->compose['from'] = $ident['identity_id']; } // Set other headers @@ -542,7 +461,7 @@ function rcmail_compose_header_from($attrib) if (count($MESSAGE->identities)) { $a_signatures = array(); - $separator = $RCMAIL->config->get('sig_above') + $separator = intval($RCMAIL->config->get('reply_mode')) > 0 && ($compose_mode == RCUBE_COMPOSE_REPLY || $compose_mode == RCUBE_COMPOSE_FORWARD) ? '---' : '-- '; $field_attrib['onchange'] = JS_OBJECT_NAME.".change_identity(this)"; @@ -560,7 +479,7 @@ function rcmail_compose_header_from($attrib) $text = $html = $sql_arr['signature']; if ($sql_arr['html_signature']) { - $h2t = new html2text($sql_arr['signature'], false, false); + $h2t = new rcube_html2text($sql_arr['signature'], false, false); $text = trim($h2t->get_text()); } else { @@ -599,7 +518,7 @@ function rcmail_compose_header_from($attrib) function rcmail_compose_editor_mode() { - global $RCMAIL, $MESSAGE, $compose_mode; + global $RCMAIL, $compose_mode; static $useHtml; if ($useHtml !== null) @@ -611,13 +530,13 @@ function rcmail_compose_editor_mode() $useHtml = !empty($_POST['_is_html']); } else if ($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT) { - $useHtml = $MESSAGE->has_html_part(false, true); + $useHtml = rcmail_message_is_html(); } else if ($compose_mode == RCUBE_COMPOSE_REPLY) { - $useHtml = ($html_editor == 1 || ($html_editor >= 2 && $MESSAGE->has_html_part(false, true))); + $useHtml = ($html_editor == 1 || ($html_editor >= 2 && rcmail_message_is_html())); } else if ($compose_mode == RCUBE_COMPOSE_FORWARD) { - $useHtml = ($html_editor == 1 || ($html_editor == 3 && $MESSAGE->has_html_part(false, true))); + $useHtml = ($html_editor == 1 || ($html_editor == 3 && rcmail_message_is_html())); } else { $useHtml = ($html_editor == 1); @@ -626,6 +545,11 @@ function rcmail_compose_editor_mode() return $useHtml; } +function rcmail_message_is_html() +{ + global $RCMAIL, $MESSAGE; + return $RCMAIL->config->get('prefer_html') && ($MESSAGE instanceof rcube_message) && $MESSAGE->has_html_part(true); +} function rcmail_prepare_message_body() { @@ -641,11 +565,10 @@ function rcmail_prepare_message_body() $isHtml = false; } // forward as attachment - else if ($compose_mode == RCUBE_COMPOSE_FORWARD && $MESSAGE->forward_attachment) { + else if ($compose_mode == RCUBE_COMPOSE_FORWARD && $COMPOSE['as_attachment']) { $isHtml = rcmail_compose_editor_mode(); $body = ''; - if (empty($COMPOSE['attachments'])) - rcmail_write_forward_attachment($MESSAGE); + rcmail_write_forward_attachments(); } // reply/edit/draft/forward else if ($compose_mode && ($compose_mode != RCUBE_COMPOSE_REPLY || $RCMAIL->config->get('reply_mode') != -1)) { @@ -731,13 +654,14 @@ function rcmail_compose_part_body($part, $isHtml = false) if ($part->ctype_secondary == 'html') { } else if ($part->ctype_secondary == 'enriched') { - require_once(INSTALL_PATH . 'program/lib/enriched.inc'); - $body = enriched_to_html($body); + $body = rcube_enriched::to_html($body); } else { // try to remove the signature - if ($RCMAIL->config->get('strip_existing_sig', true)) { - $body = rcmail_remove_signature($body); + if ($compose_mode != RCUBE_COMPOSE_DRAFT && $compose_mode != RCUBE_COMPOSE_EDIT) { + if ($RCMAIL->config->get('strip_existing_sig', true)) { + $body = rcmail_remove_signature($body); + } } // add HTML formatting $body = rcmail_plain_body($body); @@ -748,8 +672,7 @@ function rcmail_compose_part_body($part, $isHtml = false) } else { if ($part->ctype_secondary == 'enriched') { - require_once(INSTALL_PATH . 'program/lib/enriched.inc'); - $body = enriched_to_html($body); + $body = rcube_enriched::to_html($body); $part->ctype_secondary = 'html'; } @@ -757,21 +680,19 @@ function rcmail_compose_part_body($part, $isHtml = false) // use html part if it has been used for message (pre)viewing // decrease line length for quoting $len = $compose_mode == RCUBE_COMPOSE_REPLY ? $LINE_LENGTH-2 : $LINE_LENGTH; - $txt = new html2text($body, false, true, $len); + $txt = new rcube_html2text($body, false, true, $len); $body = $txt->get_text(); } - else if ($part->ctype_secondary == 'enriched') { - require_once(INSTALL_PATH . 'program/lib/enriched.inc'); - $body = enriched_to_html($body); - } else { if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') { $body = rcube_mime::unfold_flowed($body); } // try to remove the signature - if ($RCMAIL->config->get('strip_existing_sig', true)) { - $body = rcmail_remove_signature($body); + if ($compose_mode != RCUBE_COMPOSE_DRAFT && $compose_mode != RCUBE_COMPOSE_EDIT) { + if ($RCMAIL->config->get('strip_existing_sig', true)) { + $body = rcmail_remove_signature($body); + } } } } @@ -1067,14 +988,21 @@ function rcmail_write_compose_attachments(&$message, $bodyIsHtml) { global $RCMAIL, $COMPOSE, $compose_mode; + $loaded_attachments = array(); + foreach ((array)$COMPOSE['attachments'] as $id => $attachment) { + $loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment; + } + $cid_map = $messages = array(); foreach ((array)$message->mime_parts as $pid => $part) { if ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename) { - if ($part->ctype_primary == 'message' || $part->ctype_primary == 'multipart') { + // skip parts that aren't valid attachments + if ($part->ctype_primary == 'multipart' || $part->mimetype == 'application/ms-tnef') { continue; } - if ($part->mimetype == 'application/ms-tnef') { + // skip message attachments in reply mode + if ($part->ctype_primary == 'message' && $compose_mode == RCUBE_COMPOSE_REPLY) { continue; } // skip inline images when forwarding in plain text @@ -1094,7 +1022,8 @@ function rcmail_write_compose_attachments(&$message, $bodyIsHtml) } } - if (!$skip && ($attachment = rcmail_save_attachment($message, $pid))) { + if (!$skip && (($attachment = $loaded_attachments[rcmail_attachment_name($part) . $part->mimetype]) + || ($attachment = rcmail_save_attachment($message, $pid)))) { $COMPOSE['attachments'][$attachment['id']] = $attachment; if ($bodyIsHtml && ($part->content_id || $part->content_location)) { $url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s', @@ -1136,55 +1065,95 @@ function rcmail_write_inline_attachments(&$message) return $cid_map; } -// Creates an attachment from the forwarded message -function rcmail_write_forward_attachment(&$message) +// Creates attachment(s) from the forwarded message(s) +function rcmail_write_forward_attachments() { - global $RCMAIL, $COMPOSE; + global $RCMAIL, $COMPOSE, $MESSAGE; + + $storage = $RCMAIL->get_storage(); + $mem_limit = parse_bytes(ini_get('memory_limit')); + $curr_mem = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB + $names = array(); - if (strlen($message->subject)) { - $name = mb_substr($message->subject, 0, 64) . '.eml'; + $loaded_attachments = array(); + foreach ((array)$COMPOSE['attachments'] as $id => $attachment) { + $loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment; + } + + if ($COMPOSE['forward_uid'] == '*') { + $index = $storage->index(null, rcmail_sort_column(), rcmail_sort_order()); + $COMPOSE['forward_uid'] = $index->get(); } else { - $name = 'message_rfc822.eml'; + $COMPOSE['forward_uid'] = explode(',', $COMPOSE['forward_uid']); } - $mem_limit = parse_bytes(ini_get('memory_limit')); - $curr_mem = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB - $data = $path = null; + foreach ((array)$COMPOSE['forward_uid'] as $uid) { + $message = new rcube_message($uid); - // don't load too big attachments into memory - if ($mem_limit > 0 && $message->size > $mem_limit - $curr_mem) { - $temp_dir = unslashify($RCMAIL->config->get('temp_dir')); - $path = tempnam($temp_dir, 'rcmAttmnt'); - if ($fp = fopen($path, 'w')) { - $RCMAIL->storage->get_raw_body($message->uid, $fp); - fclose($fp); - } else - return false; - } else { - $data = $RCMAIL->storage->get_raw_body($message->uid); - } + if (empty($message->headers)) { + continue; + } - $attachment = array( - 'group' => $COMPOSE['id'], - 'name' => $name, - 'mimetype' => 'message/rfc822', - 'data' => $data, - 'path' => $path, - 'size' => $path ? filesize($path) : strlen($data), - ); + if (!empty($message->headers->charset)) { + $storage->set_charset($message->headers->charset); + } - $attachment = $RCMAIL->plugins->exec_hook('attachment_save', $attachment); + if (empty($MESSAGE->subject)) { + $MESSAGE->subject = $message->subject; + } - if ($attachment['status']) { - unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']); - $COMPOSE['attachments'][$attachment['id']] = $attachment; - return true; - } else if ($path) { - @unlink($path); - } + // generate (unique) attachment name + $name = strlen($message->subject) ? mb_substr($message->subject, 0, 64) : 'message_rfc822'; + if (!empty($names[$name])) { + $names[$name]++; + $name .= '_' . $names[$name]; + } + $names[$name] = 1; + $name .= '.eml'; - return false; + $data = $path = null; + + if (!empty($loaded_attachments[$name . 'message/rfc822'])) { + continue; + } + + // don't load too big attachments into memory + if ($mem_limit > 0 && $message->size > $mem_limit - $curr_mem) { + $temp_dir = unslashify($RCMAIL->config->get('temp_dir')); + $path = tempnam($temp_dir, 'rcmAttmnt'); + if ($fp = fopen($path, 'w')) { + $storage->get_raw_body($message->uid, $fp); + fclose($fp); + } + else { + return false; + } + } + else { + $data = $storage->get_raw_body($message->uid); + $curr_mem += $message->size; + } + + $attachment = array( + 'group' => $COMPOSE['id'], + 'name' => $name, + 'mimetype' => 'message/rfc822', + 'data' => $data, + 'path' => $path, + 'size' => $path ? filesize($path) : strlen($data), + ); + + $attachment = $RCMAIL->plugins->exec_hook('attachment_save', $attachment); + + if ($attachment['status']) { + unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']); + $COMPOSE['attachments'][$attachment['id']] = $attachment; + } + else if ($path) { + @unlink($path); + } + } } @@ -1212,16 +1181,7 @@ function rcmail_save_attachment(&$message, $pid) } $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary; - $filename = $part->filename; - if (!strlen($filename)) { - if ($mimetype == 'text/html') { - $filename = rcube_label('htmlmessage'); - } - else { - $filename = 'Part_'.$pid; - } - $filename .= '.' . $part->ctype_secondary; - } + $filename = rcmail_attachment_name($part); $attachment = array( 'group' => $COMPOSE['id'], @@ -1625,7 +1585,7 @@ function rcmail_addressbook_list($attrib = array()) $class_name .= ' ' . $source['class_name']; $out .= sprintf($line_templ, - html_identifier($id), + html_identifier($id,true), $class_name, $source['id'], $js_id, (!empty($source['name']) ? $source['name'] : $id)); diff --git a/program/steps/mail/folders.inc b/program/steps/mail/folders.inc index c56c914cd..574d6e975 100644 --- a/program/steps/mail/folders.inc +++ b/program/steps/mail/folders.inc @@ -65,6 +65,7 @@ else if ($RCMAIL->action == 'purge') if (!empty($_REQUEST['_reload'])) { $OUTPUT->set_env('messagecount', 0); $OUTPUT->set_env('pagecount', 0); + $OUTPUT->set_env('exists', 0); $OUTPUT->command('message_list.clear'); $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text(), $mbox); $OUTPUT->command('set_unread_count', $mbox, 0); diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index cb1a5ddae..274c40b5c 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -5,7 +5,7 @@ | program/steps/mail/func.inc | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2010, The Roundcube Dev Team | + | Copyright (C) 2005-2012, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -89,11 +89,12 @@ if (empty($RCMAIL->action) || $RCMAIL->action == 'list') { } $threading = (bool) $RCMAIL->storage->get_threading(); + $delimiter = $RCMAIL->storage->get_hierarchy_delimiter(); // set current mailbox and some other vars in client environment $OUTPUT->set_env('mailbox', $mbox_name); $OUTPUT->set_env('pagesize', $RCMAIL->storage->get_pagesize()); - $OUTPUT->set_env('delimiter', $RCMAIL->storage->get_hierarchy_delimiter()); + $OUTPUT->set_env('delimiter', $delimiter); $OUTPUT->set_env('threading', $threading); $OUTPUT->set_env('threads', $threading || $RCMAIL->storage->get_capability('THREAD')); $OUTPUT->set_env('preview_pane_mark_read', $RCMAIL->config->get('preview_pane_mark_read', 0)); @@ -121,7 +122,10 @@ if (empty($RCMAIL->action) || $RCMAIL->action == 'list') { 'movingmessage', 'copyingmessage', 'deletingmessage', 'markingmessage', 'copy', 'move', 'quota'); - $OUTPUT->set_pagetitle(rcmail_localize_foldername($RCMAIL->storage->mod_folder($mbox_name))); + $pagetitle = $RCMAIL->localize_foldername($RCMAIL->storage->mod_folder($mbox_name), true); + $pagetitle = str_replace($delimiter, " \xC2\xBB ", $pagetitle); + + $OUTPUT->set_pagetitle($pagetitle); } /** @@ -628,39 +632,6 @@ function rcmail_wash_html($html, $p, $cid_replaces) $p += array('safe' => false, 'inline_html' => true); - // special replacements (not properly handled by washtml class) - $html_search = array( - '/(<\/nobr>)(\s+)(<nobr>)/i', // space(s) between <NOBR> - '/<title[^>]*>[^<]*<\/title>/i', // PHP bug #32547 workaround: remove title tag - '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/', // byte-order mark (only outlook?) - '/<html\s[^>]+>/i', // washtml/DOMDocument cannot handle xml namespaces - ); - $html_replace = array( - '\\1'.' '.'\\3', - '', - '', - '<html>', - ); - $html = preg_replace($html_search, $html_replace, trim($html)); - - // PCRE errors handling (#1486856), should we use something like for every preg_* use? - if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) { - $errstr = "Could not clean up HTML message! PCRE Error: $preg_error."; - - if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) - $errstr .= " Consider raising pcre.backtrack_limit!"; - if ($preg_error == PREG_RECURSION_LIMIT_ERROR) - $errstr .= " Consider raising pcre.recursion_limit!"; - - raise_error(array('code' => 620, 'type' => 'php', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => $errstr), true, false); - return ''; - } - - // fix (unknown/malformed) HTML tags before "wash" - $html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', 'rcmail_html_tag_callback', $html); - // charset was converted to UTF-8 in rcube_storage::get_message_part(), // change/add charset specification in HTML accordingly, // washtml cannot work without that @@ -674,9 +645,6 @@ function rcmail_wash_html($html, $p, $cid_replaces) $html = '<head>' . $meta . '</head>' . $html; } - // turn relative into absolute urls - $html = rcmail_resolve_base($html); - // clean HTML with washhtml by Frederic Motte $wash_opts = array( 'show_washed' => false, @@ -702,7 +670,7 @@ function rcmail_wash_html($html, $p, $cid_replaces) $wash_opts['html_attribs'] = $p['html_attribs']; // initialize HTML washer - $washer = new washtml($wash_opts); + $washer = new rcube_washtml($wash_opts); if (!$p['skip_washer_form_callback']) $washer->add_callback('form', 'rcmail_washtml_callback'); @@ -739,8 +707,11 @@ function rcmail_print_body($part, $p = array()) + $p + array('safe' => false, 'plain' => false, 'inline_html' => true)); // convert html to text/plain - if ($data['type'] == 'html' && $data['plain']) { - $txt = new html2text($data['body'], false, true); + if ($data['plain'] && ($data['type'] == 'html' || $data['type'] == 'enriched')) { + if ($data['type'] == 'enriched') { + $data['body'] = rcube_enriched::to_html($data['body']); + } + $txt = new rcube_html2text($data['body'], false, true); $body = $txt->get_text(); $part->ctype_secondary = 'plain'; } @@ -751,8 +722,7 @@ function rcmail_print_body($part, $p = array()) } // text/enriched else if ($data['type'] == 'enriched') { - require_once(INSTALL_PATH . 'program/lib/enriched.inc'); - $body = enriched_to_html($data['body']); + $body = rcube_enriched::to_html($data['body']); $body = rcmail_wash_html($body, $data, $part->replaces); $part->ctype_secondary = 'html'; } @@ -790,7 +760,8 @@ function rcmail_plain_body($body, $flowed=false) global $RCMAIL; // make links and email-addresses clickable - $replacer = new rcmail_string_replacer; + $attribs = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank')); + $replacer = new rcmail_string_replacer($attribs); // search for patterns like links and e-mail addresses and replace with tokens $body = $replacer->replace($body); @@ -922,31 +893,15 @@ function rcmail_washtml_callback($tagname, $attrib, $content, $washtml) /** - * Callback function for HTML tags fixing - */ -function rcmail_html_tag_callback($matches) -{ - $tagname = $matches[2]; - - $tagname = preg_replace(array( - '/:.*$/', // Microsoft's Smart Tags <st1:xxxx> - '/[^a-z0-9_\[\]\!-]/i', // forbidden characters - ), '', $tagname); - - return $matches[1].$tagname; -} - - -/** * return table with message headers */ -function rcmail_message_headers($attrib, $headers=NULL) +function rcmail_message_headers($attrib, $headers=null) { global $OUTPUT, $MESSAGE, $PRINT_MODE, $RCMAIL; static $sa_attrib; // keep header table attrib - if (is_array($attrib) && !$sa_attrib) + if (is_array($attrib) && !$sa_attrib && !$attrib['valueof']) $sa_attrib = $attrib; else if (!is_array($attrib) && is_array($sa_attrib)) $attrib = $sa_attrib; @@ -955,11 +910,20 @@ function rcmail_message_headers($attrib, $headers=NULL) return FALSE; // get associative array of headers object - if (!$headers) - $headers = is_object($MESSAGE->headers) ? get_object_vars($MESSAGE->headers) : $MESSAGE->headers; + if (!$headers) { + $headers_obj = $MESSAGE->headers; + $headers = get_object_vars($MESSAGE->headers); + } + else if (is_object($headers)) { + $headers_obj = $headers; + $headers = get_object_vars($headers_obj); + } + else { + $headers_obj = rcube_message_header::from_array($headers); + } // show these headers - $standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto', + $standard_headers = array('subject', 'from', 'sender', 'to', 'cc', 'bcc', 'replyto', 'mail-reply-to', 'mail-followup-to', 'date', 'priority'); $exclude_headers = $attrib['exclude'] ? explode(',', $attrib['exclude']) : array(); $output_headers = array(); @@ -1010,6 +974,14 @@ function rcmail_message_headers($attrib, $headers=NULL) else continue; } + else if ($hkey == 'sender') { + if ($headers['sender'] != $headers['from']) { + $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title); + $ishtml = true; + } + else + continue; + } else if ($hkey == 'mail-followup-to') { $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title); $ishtml = true; @@ -1032,7 +1004,7 @@ function rcmail_message_headers($attrib, $headers=NULL) } $plugin = $RCMAIL->plugins->exec_hook('message_headers_output', - array('output' => $output_headers, 'headers' => $MESSAGE->headers, 'exclude' => $exclude_headers)); + array('output' => $output_headers, 'headers' => $headers_obj, 'exclude' => $exclude_headers)); // single header value is requested if (!empty($attrib['valueof'])) @@ -1111,12 +1083,13 @@ function rcmail_message_body($attrib) if (!empty($MESSAGE->parts)) { foreach ($MESSAGE->parts as $i => $part) { - if ($part->type == 'headers') - $out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part->headers); + if ($part->type == 'headers') { + $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers)); + } else if ($part->type == 'content') { - // unsapported + // unsupported (e.g. encrypted) if ($part->realtype) { - if ($part->realtype == 'multipart/encrypted') { + if ($part->realtype == 'multipart/encrypted' || $part->realtype == 'application/pkcs7-mime') { $out .= html::span('part-notice', rcube_label('encryptedmessage')); } continue; @@ -1140,6 +1113,15 @@ function rcmail_message_body($attrib) if (!isset($part->body)) $part->body = $MESSAGE->get_part_content($part->mime_id); + // extract headers from message/rfc822 parts + if ($part->mimetype == 'message/rfc822') { + $msgpart = rcube_mime::parse_message($part->body); + if (!empty($msgpart->headers)) { + $part = $msgpart; + $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers)); + } + } + // message is cached but not exists (#1485443), or other error if ($part->body === false) { rcmail_message_error($MESSAGE->uid); @@ -1204,10 +1186,9 @@ function rcmail_message_body($attrib) $show_link = array( 'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false), 'onclick' => sprintf( - 'return %s.command(\'load-attachment\',{part:\'%s\', mimetype:\'%s\'},this)', + 'return %s.command(\'load-attachment\',\'%s\',this)', JS_OBJECT_NAME, - $attach_prop->mime_id, - $mimetype) + $attach_prop->mime_id) ); $out .= html::p('image-attachment', html::a($show_link + array('class' => 'image-link', 'style' => sprintf('width:%dpx', $thumbnail_size)), @@ -1264,7 +1245,7 @@ function rcmail_part_image_type($part) // Content-Type: image/*... if (preg_match($mime_regex, $part->mimetype)) { - return $part->mimetype; + return rcmail_fix_mimetype($part->mimetype); } // Many clients use application/octet-stream, we'll detect mimetype @@ -1293,20 +1274,6 @@ function rcmail_part_image_type($part) } } -/** - * Convert all relative URLs according to a <base> in HTML - */ -function rcmail_resolve_base($body) -{ - // check for <base href=...> - if (preg_match('!(<base.*href=["\']?)([hftps]{3,5}://[a-z0-9/.%-]+)!i', $body, $regs)) { - $replacer = new rcube_base_replacer($regs[2]); - $body = $replacer->replace($body); - } - - return $body; -} - /** * modify a HTML message that it can be displayed inside a HTML page @@ -1407,7 +1374,7 @@ function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null /** - * parse link attributes and set correct target + * parse link (a, link, area) attributes and set correct target */ function rcmail_alter_html_link($matches) { @@ -1416,9 +1383,9 @@ function rcmail_alter_html_link($matches) // Support unicode/punycode in top-level domain part $EMAIL_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))'; - $tag = $matches[1]; + $tag = strtolower($matches[1]); $attrib = parse_attrib_string($matches[2]); - $end = '>'; + $end = '>'; // Remove non-printable characters in URL (#1487805) if ($attrib['href']) @@ -1445,6 +1412,11 @@ function rcmail_alter_html_link($matches) $attrib['target'] = '_blank'; } + // Better security by adding rel="noreferrer" (#1484686) + if (($tag == 'a' || $tag == 'area') && $attrib['href'] && $attrib['href'][0] != '#') { + $attrib['rel'] = 'noreferrer'; + } + // allowed attributes for a|link|area tags $allow = array('href','name','target','onclick','id','class','style','title', 'rel','type','media','alt','coords','nohref','hreflang','shape'); @@ -1480,6 +1452,11 @@ function rcmail_address_string($input, $max=null, $linked=false, $addicon=null, $mailto = $part['mailto']; $string = $part['string']; + // phishing email prevention (#1488981), e.g. "valid@email.addr <phishing@email.addr>" + if ($name && $name != $mailto && strpos($name, '@')) { + $name = ''; + } + // IDNA ASCII to Unicode if ($name == $mailto) $name = rcube_idn_to_utf8($name); @@ -1510,7 +1487,7 @@ function rcmail_address_string($input, $max=null, $linked=false, $addicon=null, if ($addicon && $_SESSION['writeable_abook']) { $address .= html::a(array( 'href' => "#add", - 'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, $string), + 'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, JQ($string)), 'title' => rcube_label('addtoaddressbook'), 'class' => 'rcmaddcontact', ), @@ -1638,10 +1615,7 @@ function rcmail_message_part_controls($attrib) $part = $MESSAGE->mime_parts[$part]; $table = new html_table(array('cols' => 3)); - $filename = $part->filename; - if (empty($filename) && $attach_prop->mimetype == 'text/html') { - $filename = rcube_label('htmlmessage'); - } + $filename = rcmail_attachment_name($part); if (!empty($filename)) { $table->add('title', Q(rcube_label('filename'))); @@ -1656,7 +1630,6 @@ function rcmail_message_part_controls($attrib) } - function rcmail_message_part_frame($attrib) { global $MESSAGE; @@ -1702,11 +1675,11 @@ function rcmail_send_mdn($message, &$smtp_error) if ($message->headers->mdn_to && empty($message->headers->flags['MDNSENT']) && ($RCMAIL->storage->check_permflag('MDNSENT') || $RCMAIL->storage->check_permflag('*'))) { - $identity = $RCMAIL->user->get_identity(); - $sender = format_email_recipient($identity['email'], $identity['name']); + $identity = rcmail_identity_select($message); + $sender = format_email_recipient($identity['email'], $identity['name']); $recipient = array_shift(rcube_mime::decode_address_list( $message->headers->mdn_to, 1, true, $message->headers->charset)); - $mailto = $recipient['mailto']; + $mailto = $recipient['mailto']; $compose = new Mail_mime("\r\n"); @@ -1731,6 +1704,9 @@ function rcmail_send_mdn($message, &$smtp_error) if ($agent = $RCMAIL->config->get('useragent')) $headers['User-Agent'] = $agent; + if ($RCMAIL->config->get('mdn_use_from')) + $options['mdn_use_from'] = true; + $body = rcube_label("yourmessage") . "\r\n\r\n" . "\t" . rcube_label("to") . ': ' . rcube_mime::decode_mime_string($message->headers->to, $message->headers->charset) . "\r\n" . "\t" . rcube_label("subject") . ': ' . $message->subject . "\r\n" . @@ -1752,7 +1728,7 @@ function rcmail_send_mdn($message, &$smtp_error) $compose->setTXTBody(rc_wordwrap($body, 75, "\r\n")); $compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline'); - $sent = rcmail_deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file); + $sent = rcmail_deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file, $options); if ($sent) { @@ -1764,6 +1740,107 @@ function rcmail_send_mdn($message, &$smtp_error) return false; } +/** + * Detect recipient identity from specified message + */ +function rcmail_identity_select($MESSAGE, $identities = null, $compose_mode = 'reply') +{ + $a_recipients = array(); + $a_names = array(); + + if ($identities === null) { + $identities = rcmail::get_instance()->user->list_identities(null, true); + } + + // extract all recipients of the reply-message + if (is_object($MESSAGE->headers) && in_array($compose_mode, array('reply', 'forward'))) { + $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset); + foreach ($a_to as $addr) { + if (!empty($addr['mailto'])) { + $a_recipients[] = format_email($addr['mailto']); + $a_names[] = $addr['name']; + } + } + + if (!empty($MESSAGE->headers->cc)) { + $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset); + foreach ($a_cc as $addr) { + if (!empty($addr['mailto'])) { + $a_recipients[] = format_email($addr['mailto']); + $a_names[] = $addr['name']; + } + } + } + } + + $from_idx = null; + $found_idx = null; + $default_identity = 0; // default identity is always first on the list + + // Select identity + foreach ($identities as $idx => $ident) { + // use From header + if (in_array($compose_mode, array('draft', 'edit'))) { + if ($MESSAGE->headers->from == $ident['ident']) { + $from_idx = $idx; + break; + } + } + // reply to yourself + else if ($compose_mode == 'reply' && $MESSAGE->headers->from == $ident['ident']) { + $from_idx = $idx; + break; + } + // use replied message recipients + else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) { + if ($found_idx === null) { + $found_idx = $idx; + } + // match identity name + if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) { + $from_idx = $idx; + break; + } + } + } + + // If matching by name+address doesn't found any matches, get first found address (identity) + if ($from_idx === null) { + $from_idx = $found_idx; + } + + // Try Return-Path + if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) { + foreach ($identities as $idx => $ident) { + if (strpos($return_path, str_replace('@', '=', $ident['email_ascii']).'@') !== false) { + $from_idx = $idx; + break; + } + } + } + + // Fallback using Delivered-To + if ($from_idx === null && ($delivered_to = $MESSAGE->headers->others['delivered-to'])) { + foreach ($identities as $idx => $ident) { + if (in_array($ident['email_ascii'], (array)$delivered_to)) { + $from_idx = $idx; + break; + } + } + } + + // Fallback using Envelope-To + if ($from_idx === null && ($envelope_to = $MESSAGE->headers->others['envelope-to'])) { + foreach ($identities as $idx => $ident) { + if (in_array($ident['email_ascii'], (array)$envelope_to)) { + $from_idx = $idx; + break; + } + } + } + + return $identities[$from_idx !== null ? $from_idx : $default_identity]; +} // Fixes some content-type names function rcmail_fix_mimetype($name) @@ -1773,9 +1850,44 @@ function rcmail_fix_mimetype($name) if (preg_match('/^application\/pdf.+/', $name)) $name = 'application/pdf'; + // treat image/pjpeg as image/jpeg + else if (preg_match('/^image\/p?jpe?g$/', $name)) + $name = 'image/jpeg'; + return $name; } +// return attachment filename, handle empty filename case +function rcmail_attachment_name($attachment, $display = false) +{ + $filename = $attachment->filename; + + if ($filename === null || $filename === '') { + if ($attachment->mimetype == 'text/html') { + $filename = rcube_label('htmlmessage'); + } + else { + $ext = (array) rcube_mime::get_mime_extensions($attachment->mimetype); + $ext = array_shift($ext); + $filename = rcube_label('messagepart') . ' ' . $attachment->mime_id; + if ($ext) { + $filename .= '.' . $ext; + } + } + } + + $filename = preg_replace('[\r\n]', '', $filename); + + // Display smart names for some known mimetypes + if ($display) { + if (preg_match('/application\/(pgp|pkcs7)-signature/i', $attachment->mimetype)) { + $filename = rcube_label('digitalsig'); + } + } + + return $filename; +} + function rcmail_search_filter($attrib) { global $OUTPUT, $CONFIG; diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc index 71a5e1b02..23dc22b7c 100644 --- a/program/steps/mail/get.inc +++ b/program/steps/mail/get.inc @@ -35,6 +35,7 @@ if (!empty($_GET['_preload'])) { ob_end_clean(); + // similar code as in program/steps/mail/show.inc if (!empty($_GET['_uid'])) { $RCMAIL->config->set('prefer_html', true); @@ -47,13 +48,7 @@ check_storage_status(); // show part page if (!empty($_GET['_frame'])) { if (($part_id = get_input_value('_part', RCUBE_INPUT_GPC)) && ($part = $MESSAGE->mime_parts[$part_id])) { - $filename = $part->filename; - if (empty($filename) && $part->mimetype == 'text/html') { - $filename = rcube_label('htmlmessage'); - } - if (!empty($filename)) { - $OUTPUT->set_pagetitle($filename); - } + $OUTPUT->set_pagetitle(rcmail_attachment_name($part)); } $OUTPUT->send('messagepart'); @@ -65,11 +60,11 @@ else if ($_GET['_thumb']) { $pid = get_input_value('_part', RCUBE_INPUT_GET); if ($part = $MESSAGE->mime_parts[$pid]) { $thumbnail_size = $RCMAIL->config->get('image_thumbnail_size', 240); - $temp_dir = $RCMAIL->config->get('temp_dir'); - list(,$ext) = explode('/', $part->mimetype); + $temp_dir = $RCMAIL->config->get('temp_dir'); + list(,$ext) = explode('/', $part->mimetype); $cache_basename = $temp_dir . '/' . md5($MESSAGE->headers->messageID . $part->mime_id . ':' . $RCMAIL->user->ID . ':' . $thumbnail_size); - $cache_file = $cache_basename . '.' . $ext; - $mimetype = $part->mimetype; + $cache_file = $cache_basename . '.' . $ext; + $mimetype = $part->mimetype; // render thumbnail image if not done yet if (!is_file($cache_file)) { @@ -78,7 +73,7 @@ else if ($_GET['_thumb']) { fclose($fp); $image = new rcube_image($orig_name); - if ($imgtype = $image->resize($RCMAIL->config->get('image_thumbnail_size', 240), $cache_file, true)) { + if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) { $mimetype = 'image/' . $imgtype; unlink($orig_name); } @@ -126,7 +121,7 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) { $valid = $file_extension && in_array($file_extension, (array)$extensions); // 2. detect the real mimetype of the attachment part and compare it with the stated mimetype and filename extension - if ($valid || !$file_extension || $mimetype == 'application/octet-stream') { + if ($valid || !$file_extension || $mimetype == 'application/octet-stream' || $mimetype == 'text/plain') { if ($part->body) // part body is already loaded $body = $part->body; else if ($part->size && $part->size < 1024*1024) // load the entire part if it's small enough @@ -138,6 +133,10 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) { $real_mimetype = rcube_mime::file_content_type($body, $part->filename, $mimetype, true, true); list($real_ctype_primary, $real_ctype_secondary) = explode('/', $real_mimetype); + // accept text/plain with any extension + if ($real_mimetype == 'text/plain' && $real_mimetype == $mimetype) + $file_extension = 'txt'; + // ignore differences in text/* mimetypes. Filetype detection isn't very reliable here if ($real_ctype_primary == 'text' && strpos($mimetype, $real_ctype_primary) === 0) $real_mimetype = $mimetype; @@ -159,19 +158,20 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) { // show warning if validity checks failed if (!$valid) { $OUTPUT = new rcmail_html_page(); - $OUTPUT->write(html::tag('html', null, html::tag('body', array('style' => 'font-family:sans-serif; margin:1em'), - html::div(array('class' => 'warning', 'style' => 'border:2px solid #ffdf0e; background:#fef893; padding:1em 1em 0 1em;'), + $OUTPUT->write(html::tag('html', null, html::tag('body', 'embed', + html::div(array('class' => 'rcmail-inline-message rcmail-inline-warning'), rcube_label(array( 'name' => 'attachmentvalidationerror', - 'vars' => array('expected' => "$mimetype (.$file_extension)", 'detected' => "$real_mimetype (.$extensions[0])") + 'vars' => array( + 'expected' => $mimetype . ($file_extension ? "(.$file_extension)" : ''), + 'detected' => $real_mimetype . ($extensions[0] ? "(.$extensions[0])" : ''), + ) )) . - html::p('buttons', - html::tag('button', null, - html::a(array( - 'href' => $RCMAIL->url(array_merge($_GET, array('_nocheck' => 1))), - 'style' => 'text-decoration:none;color:#000', - ), rcube_label('showanyway'))) - )) + html::p(array('class' => 'rcmail-inline-buttons'), + html::tag('button', + array('onclick' => "location.href='" . $RCMAIL->url(array_merge($_GET, array('_nocheck' => 1))) . "'"), + rcube_label('showanyway'))) + ) ))); exit; } @@ -222,7 +222,27 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) { if (!$part->body) $part->body = $MESSAGE->get_part_content($part->mime_id); + // show images? + rcmail_check_safe($MESSAGE); + + // render HTML body $out = rcmail_print_body($part, array('safe' => $MESSAGE->is_safe, 'inline_html' => false)); + + // insert remote objects warning into HTML body + if ($REMOTE_OBJECTS) { + $body_start = 0; + if ($body_pos = strpos($out, '<body')) { + $body_start = strpos($out, '>', $body_pos) + 1; + } + $out = substr($out, 0, $body_start) . + html::div(array('class' => 'rcmail-inline-message rcmail-inline-warning'), + Q(rcube_label('blockedimages')) . ' ' . + html::tag('button', + array('onclick' => "location.href='" . $RCMAIL->url(array_merge($_GET, array('_safe' => 1))) . "'"), + Q(rcube_label('showimages'))) + ) . + substr($out, $body_start); + } } // check connection status @@ -237,18 +257,7 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) { // don't kill the connection if download takes more than 30 sec. @set_time_limit(0); - if ($part->filename) { - $filename = $part->filename; - } - else if ($part->mimetype == 'text/html') { - $filename = rcube_label('htmlmessage'); - } - else { - $ext = '.' . ($mimetype == 'text/plain' ? 'txt' : $ctype_secondary); - $filename = ($MESSAGE->subject ? $MESSAGE->subject : 'roundcube') . $ext; - } - - $filename = preg_replace('[\r\n]', '', $filename); + $filename = rcmail_attachment_name($part); if ($browser->ie && $browser->ver < 7) $filename = rawurlencode(abbreviate_string($filename, 55)); @@ -259,6 +268,18 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) { $disposition = !empty($plugin['download']) ? 'attachment' : 'inline'; + // Workaround for nasty IE bug (#1488844) + // If Content-Disposition header contains string "attachment" e.g. in filename + // IE handles data as attachment not inline + if ($disposition == 'inline' && $browser->ie && $browser->ver < 9) { + $filename = str_ireplace('attachment', 'attach', $filename); + } + + // add filename extension if missing + if (!pathinfo($filename, PATHINFO_EXTENSION) && ($extensions = rcube_mime::get_mime_extensions($mimetype))) { + $filename .= '.' . $extensions[0]; + } + header("Content-Disposition: $disposition; filename=\"$filename\""); // handle tiff to jpeg conversion @@ -302,7 +323,7 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) { // send part as-it-is else { if ($part->body) { - header("Content-Length: " . sizeof($part->body)); + header("Content-Length: " . strlen($part->body)); echo $part->body; $sent = true; } diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc index b433f81fc..a2380131a 100644 --- a/program/steps/mail/list.inc +++ b/program/steps/mail/list.inc @@ -95,8 +95,8 @@ $OUTPUT->set_env('messagecount', $count); $OUTPUT->set_env('pagecount', $pages); $OUTPUT->set_env('threading', $threading); $OUTPUT->set_env('current_page', $count ? $RCMAIL->storage->get_page() : 1); +$OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox_name, 'EXISTS')); $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count), $mbox_name); -$OUTPUT->command('set_mailboxname', rcmail_get_mailbox_name_text()); // add message rows rcmail_js_message_list($a_headers, FALSE, $cols); diff --git a/program/steps/mail/list_contacts.inc b/program/steps/mail/list_contacts.inc index 9347190da..7e3b349cd 100644 --- a/program/steps/mail/list_contacts.inc +++ b/program/steps/mail/list_contacts.inc @@ -5,7 +5,7 @@ | program/steps/mail/list_contacts.inc | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2012, The Roundcube Dev Team | + | Copyright (C) 2012-2013, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -19,72 +19,117 @@ +-----------------------------------------------------------------------+ */ -$jsenv = array(); -$source = get_input_value('_source', RCUBE_INPUT_GPC); -$CONTACTS = $RCMAIL->get_address_book($source); -$PAGE_SIZE = $RCMAIL->config->get('addressbook_pagesize', $RCMAIL->config->get('pagesize', 50)); - -if ($CONTACTS && $CONTACTS->ready) { - // set list properties - $CONTACTS->set_pagesize($PAGE_SIZE); - $CONTACTS->set_page(max(1, intval($_GET['_page']))); - - // list groups of this source (on page one) - if ($CONTACTS->groups && $CONTACTS->list_page == 1) { - foreach ($CONTACTS->list_groups() as $group) { - $CONTACTS->reset(); - $CONTACTS->set_group($group['ID']); - $group_prop = $CONTACTS->get_group($group['ID']); - - // group (distribution list) with email address(es) - if ($group_prop['email']) { - foreach ((array)$group_prop['email'] as $email) { - $row_id = 'G'.$group['ID']; - $jsresult[$row_id] = format_email_recipient($email, $group['name']); +$afields = $RCMAIL->config->get('contactlist_fields'); +$sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name'); +$page_size = $RCMAIL->config->get('addressbook_pagesize', $RCMAIL->config->get('pagesize', 50)); +$page = max(1, intval($_GET['_page'])); + +// Use search result +if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']])) { + $search = (array)$_SESSION['search'][$_REQUEST['_search']]; + + // get records from all sources + foreach ($search as $s => $set) { + $CONTACTS = $RCMAIL->get_address_book($s); + + // reset page + $CONTACTS->set_page(1); + $CONTACTS->set_pagesize(9999); + $CONTACTS->set_search_set($set); + + // get records + $result = $CONTACTS->list_records($afields); + + while ($row = $result->next()) { + $row['sourceid'] = $s; + $key = rcube_addressbook::compose_contact_key($row, $sort_col); + $records[$key] = $row; + } + unset($result); + } + + // sort the records + ksort($records, SORT_LOCALE_STRING); + + // create resultset object + $count = count($records); + $first = ($page-1) * $page_size; + $result = new rcube_result_set($count, $first); + + // we need only records for current page + if ($page_size < $count) { + $records = array_slice($records, $first, $page_size); + } + + $result->records = array_values($records); +} +// list contacts from selected source +else { + $source = get_input_value('_source', RCUBE_INPUT_GPC); + $CONTACTS = $RCMAIL->get_address_book($source); + + if ($CONTACTS && $CONTACTS->ready) { + // set list properties + $CONTACTS->set_pagesize($page_size); + $CONTACTS->set_page($page); + + // list groups of this source (on page one) + if ($CONTACTS->groups && $CONTACTS->list_page == 1) { + foreach ($CONTACTS->list_groups() as $group) { + $CONTACTS->reset(); + $CONTACTS->set_group($group['ID']); + $group_prop = $CONTACTS->get_group($group['ID']); + + // group (distribution list) with email address(es) + if ($group_prop['email']) { + foreach ((array)$group_prop['email'] as $email) { + $row_id = 'G'.$group['ID']; + $jsresult[$row_id] = format_email_recipient($email, $group['name']); + $OUTPUT->command('add_contact_row', $row_id, array( + 'contactgroup' => html::span(array('title' => $email), Q($group['name']))), 'group'); + } + } + // show group with count + else if (($result = $CONTACTS->count()) && $result->count) { + $row_id = 'E'.$group['ID']; + $jsresult[$row_id] = $group['name']; $OUTPUT->command('add_contact_row', $row_id, array( - 'contactgroup' => html::span(array('title' => $email), Q($group['name']))), 'group'); + 'contactgroup' => Q($group['name'] . ' (' . intval($result->count) . ')')), 'group'); } } - // show group with count - else if (($result = $CONTACTS->count()) && $result->count) { - $row_id = 'E'.$group['ID']; - $jsresult[$row_id] = $group['name']; - $OUTPUT->command('add_contact_row', $row_id, array( - 'contactgroup' => Q($group['name'] . ' (' . intval($result->count) . ')')), 'group'); - } } + + // get contacts for this user + $CONTACTS->set_group(0); + $result = $CONTACTS->list_records($afields); } +} - // get contacts for this user - $CONTACTS->set_group(0); - $afields = $RCMAIL->config->get('contactlist_fields'); - $result = $CONTACTS->list_records($afields); +if (!empty($result) && !$result->count && $result->searchonly) { + $OUTPUT->show_message('contactsearchonly', 'notice'); +} +else if (!empty($result) && $result->count > 0) { + // create javascript list + while ($row = $result->next()) { + $name = rcube_addressbook::compose_list_name($row); - if (!$result->count && $result->searchonly) { - $OUTPUT->show_message('contactsearchonly', 'notice'); - } - else if (!empty($result) && $result->count > 0) { - // create javascript list - while ($row = $result->next()) { - $name = rcube_addressbook::compose_list_name($row); - - // add record for every email address of the contact - $emails = $CONTACTS->get_col_values('email', $row, true); - foreach ($emails as $i => $email) { - $row_id = $row['ID'].$i; - $jsresult[$row_id] = format_email_recipient($email, $name); - $OUTPUT->command('add_contact_row', $row_id, array( - 'contact' => html::span(array('title' => $email), Q($name ? $name : $email) . - ($name && count($emails) > 1 ? ' ' . html::span('email', Q($email)) : '') - )), 'person'); - } + // add record for every email address of the contact + $emails = $CONTACTS->get_col_values('email', $row, true); + foreach ($emails as $i => $email) { + $row_id = $row['ID'].$i; + $jsresult[$row_id] = format_email_recipient($email, $name); + $OUTPUT->command('add_contact_row', $row_id, array( + 'contact' => html::span(array('title' => $email), Q($name ? $name : $email) . + ($name && count($emails) > 1 ? ' ' . html::span('email', Q($email)) : '') + )), 'person'); } } } + // update env $OUTPUT->set_env('contactdata', $jsresult); -$OUTPUT->set_env('pagecount', ceil($result->count / $PAGE_SIZE)); +$OUTPUT->set_env('pagecount', ceil($result->count / $page_size)); $OUTPUT->command('set_page_buttons'); // send response diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc index da43b4000..3fc6ac5a7 100644 --- a/program/steps/mail/move_del.inc +++ b/program/steps/mail/move_del.inc @@ -29,18 +29,19 @@ $old_count = $RCMAIL->storage->count(NULL, $threading ? 'THREADS' : 'ALL'); $old_pages = ceil($old_count / $RCMAIL->storage->get_pagesize()); // move messages -if ($RCMAIL->action=='moveto' && !empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) { - $count = sizeof(explode(',', ($uids = get_input_value('_uid', RCUBE_INPUT_POST)))); +if ($RCMAIL->action == 'moveto' && !empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) { + $count = sizeof(explode(',', ($uids = get_input_value('_uid', RCUBE_INPUT_POST)))); $target = get_input_value('_target_mbox', RCUBE_INPUT_POST, true); - $mbox = get_input_value('_mbox', RCUBE_INPUT_POST, true); + $mbox = get_input_value('_mbox', RCUBE_INPUT_POST, true); + $trash = $RCMAIL->config->get('trash_mbox'); $moved = $RCMAIL->storage->move_message($uids, $target, $mbox); if (!$moved) { // send error message - if ($_POST['_from'] != 'show') + if ($_POST['_from'] != 'show') $OUTPUT->command('list_mailbox'); - rcmail_display_server_error('errormoving'); + rcmail_display_server_error('errormoving', null, $target == $trash ? 'delete' : ''); $OUTPUT->send(); exit; } @@ -59,7 +60,7 @@ else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) { if (!$del) { // send error message - if ($_POST['_from'] != 'show') + if ($_POST['_from'] != 'show') $OUTPUT->command('list_mailbox'); rcmail_display_server_error('errordeleting'); $OUTPUT->send(); @@ -111,6 +112,7 @@ else $OUTPUT->set_env('messagecount', $msg_count); $OUTPUT->set_env('current_page', $page); $OUTPUT->set_env('pagecount', $pages); + $OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox, 'EXISTS', true)); // update mailboxlist $mbox = $RCMAIL->storage->get_folder(); @@ -144,5 +146,3 @@ else // send response $OUTPUT->send(); - - diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc index db5424b3b..fb1b48797 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -69,7 +69,7 @@ else if (preg_match("/^subject:.*/i", $str)) else if (preg_match("/^body:.*/i", $str)) { list(,$srch) = explode(":", $str); - $subject['text'] = "TEXT"; + $subject['body'] = "BODY"; } else if (strlen(trim($str))) { @@ -81,7 +81,7 @@ else if (strlen(trim($str))) break; } else { - $subject[$header] = 'HEADER '.strtoupper($header); + $subject[$header] = ($header != 'body' ? 'HEADER ' : '') . strtoupper($header); } } @@ -89,7 +89,8 @@ else if (strlen(trim($str))) $search_mods = $RCMAIL->config->get('search_mods', $SEARCH_MODS_DEFAULT); $search_mods[$mbox] = array_fill_keys(array_keys($subject), 1); $RCMAIL->user->save_prefs(array('search_mods' => $search_mods)); - } else { + } + else { // search in subject by default $subject['subject'] = 'HEADER SUBJECT'; } @@ -143,5 +144,6 @@ else { $OUTPUT->set_env('search_request', $search_str ? $search_request : ''); $OUTPUT->set_env('messagecount', $count); $OUTPUT->set_env('pagecount', ceil($count/$RCMAIL->storage->get_pagesize())); +$OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox_name, 'EXISTS')); $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count, 1), $mbox); $OUTPUT->send(); diff --git a/program/steps/mail/search_contacts.inc b/program/steps/mail/search_contacts.inc new file mode 100644 index 000000000..2e6bb12f8 --- /dev/null +++ b/program/steps/mail/search_contacts.inc @@ -0,0 +1,112 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/steps/mail/search_contacts.inc | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2013, The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Search contacts from the adress book widget | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ +*/ + +$search = get_input_value('_q', RCUBE_INPUT_GPC, true); +$sources = $RCMAIL->get_address_sources(); +$search_mode = (int) $RCMAIL->config->get('addressbook_search_mode'); +$sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name'); +$afields = $RCMAIL->config->get('contactlist_fields'); + +$page = 1; +$page_size = $RCMAIL->config->get('addressbook_pagesize', $RCMAIL->config->get('pagesize', 50)); + +$records = $search_set = array(); +foreach ($sources as $s) { + $source = $RCMAIL->get_address_book($s['id']); + $source->set_page(1); + $source->set_pagesize(9999); + + // get contacts count + $result = $source->search($afields, $search, $search_mode, true, true, 'email'); + + if (!$result->count) { + continue; + } + + // get records + $result = $source->list_records($afields); + + while ($row = $result->next()) { + $row['sourceid'] = $s['id']; + $key = rcube_addressbook::compose_contact_key($row, $sort_col); + $records[$key] = $row; + } + + $search_set[$s['id']] = $source->get_search_set(); + unset($result); +} + +// sort the records +ksort($records, SORT_LOCALE_STRING); + +// create resultset object +$count = count($records); +$result = new rcube_result_set($count); + +// select the requested page +if ($page_size < $count) { + $records = array_slice($records, $result->first, $page_size); +} + +$result->records = array_values($records); + +if (!empty($result) && $result->count > 0) { + // create javascript list + while ($row = $result->next()) { + $name = rcube_addressbook::compose_list_name($row); + + // add record for every email address of the contact + // (same as in list_contacts.inc) + $emails = $source->get_col_values('email', $row, true); + foreach ($emails as $i => $email) { + $row_id = $row['ID'].$i; + $jsresult[$row_id] = format_email_recipient($email, $name); + $OUTPUT->command('add_contact_row', $row_id, array( + 'contact' => html::span(array('title' => $email), Q($name ? $name : $email) . + ($name && count($emails) > 1 ? ' ' . html::span('email', Q($email)) : '') + )), 'person'); + } + } + + // search request ID + $search_request = md5('composeaddr' . $search); + + // save search settings in session + $_SESSION['search'][$search_request] = $search_set; + $_SESSION['search_params'] = array('id' => $search_request, 'data' => array($afields, $search)); + + $OUTPUT->show_message('contactsearchsuccessful', 'confirmation', array('nr' => $result->count)); + + $OUTPUT->command('set_env', 'search_request', $search_request); + $OUTPUT->command('set_env', 'source', ''); + $OUTPUT->command('unselect_directory'); +} +else { + $OUTPUT->show_message('nocontactsfound', 'notice'); +} + +// update env +$OUTPUT->set_env('contactdata', $jsresult); +$OUTPUT->set_env('pagecount', ceil($result->count / $page_size)); +$OUTPUT->command('set_page_buttons'); + +// send response +$OUTPUT->send(); diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index c26d774a2..2f96e930f 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -219,11 +219,11 @@ function rcmail_email_input_format($mailto, $count=false, $check=true) // address in brackets without name (do nothing) if (preg_match('/^<'.$email_regexp.'>$/', $item)) { $item = rcube_idn_to_ascii(trim($item, '<>')); - $result[] = '<' . $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 . '>'; + $result[] = $item; // address with name (handle name) } else if (preg_match('/<*'.$email_regexp.'>*$/', $item, $matches)) { $address = $matches[0]; @@ -255,6 +255,33 @@ function rcmail_email_input_format($mailto, $count=false, $check=true) } +function rcmail_generic_message_footer($isHtml) +{ + global $CONFIG; + + if ($isHtml && !empty($CONFIG['generic_message_footer_html'])) { + $file = $CONFIG['generic_message_footer_html']; + $html_footer = true; + } + else { + $file = $CONFIG['generic_message_footer']; + $html_footer = false; + } + + if ($file && realpath($file)) { + // sanity check + if (!preg_match('/\.(php|ini|conf)$/', $file) && strpos($file, '/etc/') === false) { + $footer = file_get_contents($file); + if ($isHtml && !$html_footer) + $footer = '<pre>' . $footer . '</pre>'; + return $footer; + } + } + + return false; +} + + /****** compose message ********/ if (strlen($_POST['_draft_saveid']) > 3) @@ -466,7 +493,7 @@ if (!$savedraft) { $message_body = preg_replace('/\s*id="_rc_sig"/', '', $message_body); // add inline css for blockquotes - $bstyle = 'padding-left:5px; border-left:#1010ff 2px solid; margin-left:5px; width:100%'; + $bstyle = 'padding-left:5px; border-left:#1010ff 2px solid; margin-left:5px'; $message_body = preg_replace('/<blockquote>/', '<blockquote type="cite" style="'.$bstyle.'">', $message_body); } @@ -490,19 +517,10 @@ if (!$savedraft) { } // generic footer for all messages - if ($isHtml && !empty($CONFIG['generic_message_footer_html'])) { - $footer = file_get_contents(realpath($CONFIG['generic_message_footer_html'])); - $footer = rcube_charset_convert($footer, RCMAIL_CHARSET, $message_charset); - } - else if (!empty($CONFIG['generic_message_footer'])) { - $footer = file_get_contents(realpath($CONFIG['generic_message_footer'])); + if ($footer = rcmail_generic_message_footer($isHtml)) { $footer = rcube_charset_convert($footer, RCMAIL_CHARSET, $message_charset); - if ($isHtml) - $footer = '<pre>'.$footer.'</pre>'; - } - - if ($footer) $message_body .= "\r\n" . $footer; + } } if ($isHtml) { @@ -559,7 +577,7 @@ if ($isHtml) { $plugin['body'] = rcmail_replace_emoticons($plugin['body']); // add a plain text version of the e-mail as an alternative part. - $h2t = new html2text($plugin['body'], false, true, 0, $message_charset); + $h2t = new rcube_html2text($plugin['body'], false, true, 0, $message_charset); $plainTextPart = rc_wordwrap($h2t->get_text(), $LINE_LENGTH, "\r\n", false, $message_charset); $plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true); @@ -617,13 +635,12 @@ if (is_array($COMPOSE['attachments'])) $ctype = str_replace('image/pjpeg', 'image/jpeg', $attachment['mimetype']); // #1484914 $file = $attachment['data'] ? $attachment['data'] : $attachment['path']; - // .eml attachments send inline $MAIL_MIME->addAttachment($file, $ctype, $attachment['name'], ($attachment['data'] ? false : true), ($ctype == 'message/rfc822' ? '8bit' : 'base64'), - ($ctype == 'message/rfc822' ? 'inline' : 'attachment'), + 'attachment', '', '', '', $CONFIG['mime_param_folding'] ? 'quoted-printable' : NULL, $CONFIG['mime_param_folding'] == 2 ? 'quoted-printable' : NULL, @@ -821,6 +838,6 @@ else { if ($store_folder && !$saved) $OUTPUT->command('sent_successfully', 'error', rcube_label('errorsavingsent')); else - $OUTPUT->command('sent_successfully', 'confirmation', rcube_label('messagesent')); + $OUTPUT->command('sent_successfully', 'confirmation', rcube_label('messagesent'), $store_target); $OUTPUT->send('iframe'); } diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index 82594f3e4..552c180f5 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -19,7 +19,7 @@ +-----------------------------------------------------------------------+ */ -$PRINT_MODE = $RCMAIL->action=='print' ? TRUE : FALSE; +$PRINT_MODE = $RCMAIL->action == 'print' ? TRUE : FALSE; // Read browser capabilities and store them in session if ($caps = get_input_value('_caps', RCUBE_INPUT_GET)) { @@ -31,8 +31,21 @@ if ($caps = get_input_value('_caps', RCUBE_INPUT_GET)) { $_SESSION['browser_caps'] = $browser_caps; } +$uid = get_input_value('_uid', RCUBE_INPUT_GET); +$mbox_name = $RCMAIL->storage->get_folder(); + // similar code as in program/steps/mail/get.inc -if ($uid = get_input_value('_uid', RCUBE_INPUT_GET)) { +if ($uid) { + // set message format (need to be done before rcube_message construction) + if (!empty($_GET['_format'])) { + $prefer_html = $_GET['_format'] == 'html'; + $RCMAIL->config->set('prefer_html', $prefer_html); + $_SESSION['msg_formats'][$mbox_name.':'.$uid] = $prefer_html; + } + else if (isset($_SESSION['msg_formats'][$mbox_name.':'.$uid])) { + $RCMAIL->config->set('prefer_html', $_SESSION['msg_formats'][$mbox_name.':'.$uid]); + } + $MESSAGE = new rcube_message($uid); // if message not found (wrong UID)... @@ -40,7 +53,6 @@ if ($uid = get_input_value('_uid', RCUBE_INPUT_GET)) { rcmail_message_error($uid); } - $mbox_name = $RCMAIL->storage->get_folder(); // show images? rcmail_check_safe($MESSAGE); @@ -79,7 +91,7 @@ if ($uid = get_input_value('_uid', RCUBE_INPUT_GET)) { } } - $OUTPUT->set_env('mimetypes', $mimetypes); + $OUTPUT->set_env('mimetypes', array_values($mimetypes)); if ($CONFIG['drafts_mbox']) $OUTPUT->set_env('drafts_mailbox', $CONFIG['drafts_mbox']); @@ -106,6 +118,11 @@ if ($uid = get_input_value('_uid', RCUBE_INPUT_GET)) { $OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash', 'movingmessage', 'deletingmessage', 'markingmessage'); + $prefer_html = $RCMAIL->config->get('prefer_html'); + if ($MESSAGE->has_html_part()) { + $OUTPUT->set_env('optional_format', $prefer_html ? 'text' : 'html'); + } + // check for unset disposition notification if ($MESSAGE->headers->mdn_to && empty($MESSAGE->headers->flags['MDNSENT']) @@ -147,13 +164,11 @@ function rcmail_message_attachments($attrib) global $PRINT_MODE, $MESSAGE, $RCMAIL; $out = $ol = ''; + $attachments = array(); if (sizeof($MESSAGE->attachments)) { foreach ($MESSAGE->attachments as $attach_prop) { - $filename = $attach_prop->filename; - if (empty($filename) && $attach_prop->mimetype == 'text/html') { - $filename = rcube_label('htmlmessage'); - } + $filename = rcmail_attachment_name($attach_prop, true); if ($PRINT_MODE) { $size = $RCMAIL->message_part_size($attach_prop); @@ -161,28 +176,30 @@ function rcmail_message_attachments($attrib) } else { if (mb_strlen($filename) > 50) { + $title = $filename; $filename = abbreviate_string($filename, 50); - $title = $filename; } else { $title = ''; } - $ol .= html::tag('li', rcmail_filetype2classname($attach_prop->mimetype, $filename), - html::a(array( + $mimetype = rcmail_fix_mimetype($attach_prop->mimetype); + $class = rcmail_filetype2classname($mimetype, $filename); + $id = 'attach' . $attach_prop->mime_id; + $link = html::a(array( 'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false), - 'onclick' => sprintf( - 'return %s.command(\'load-attachment\',{part:\'%s\', mimetype:\'%s\'},this)', - JS_OBJECT_NAME, - $attach_prop->mime_id, - rcmail_fix_mimetype($attach_prop->mimetype)), - 'title' => Q($title), - ), - Q($filename))); + 'onclick' => sprintf('return %s.command(\'load-attachment\',\'%s\',this)', + JS_OBJECT_NAME, $attach_prop->mime_id), + 'title' => Q($title), + ), Q($filename)); + $ol .= html::tag('li', array('class' => $class, 'id' => $id), $link); + + $attachments[$attach_prop->mime_id] = $mimetype; } } $out = html::tag('ul', $attrib, $ol, html::$common_attrib); + $RCMAIL->output->set_env('attachments', $attachments); } return $out; @@ -288,9 +305,9 @@ $OUTPUT->add_handlers(array( )); -if ($RCMAIL->action=='print' && $OUTPUT->template_exists('messageprint')) +if ($RCMAIL->action == 'print' && $OUTPUT->template_exists('messageprint')) $OUTPUT->send('messageprint', false); -else if ($RCMAIL->action=='preview' && $OUTPUT->template_exists('messagepreview')) +else if ($RCMAIL->action == 'preview' && $OUTPUT->template_exists('messagepreview')) $OUTPUT->send('messagepreview', false); else $OUTPUT->send('message', false); diff --git a/program/steps/settings/edit_identity.inc b/program/steps/settings/edit_identity.inc index 39076f408..d70a7aef7 100644 --- a/program/steps/settings/edit_identity.inc +++ b/program/steps/settings/edit_identity.inc @@ -99,6 +99,13 @@ function rcube_identity_form($attrib) $form['addressing']['content']['email']['class'] = 'disabled'; } + if (IDENTITIES_LEVEL == 4) { + foreach($form['addressing']['content'] as $formfield => $value){ + $form['addressing']['content'][$formfield]['disabled'] = true; + $form['addressing']['content'][$formfield]['class'] = 'disabled'; + } + } + $IDENTITY_RECORD['email'] = rcube_idn_to_utf8($IDENTITY_RECORD['email']); // Allow plugins to modify identity form content diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index 3bcca21bf..319c58db9 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -483,8 +483,8 @@ function rcmail_user_prefs($current=null) $blocks = array( 'main' => array('name' => Q(rcube_label('mainoptions'))), - 'spellcheck' => array('name' => Q(rcube_label('spellcheckoptions'))), 'sig' => array('name' => Q(rcube_label('signatureoptions'))), + 'spellcheck' => array('name' => Q(rcube_label('spellcheckoptions'))), ); // show checkbox to compose messages in a new window @@ -581,8 +581,7 @@ function rcmail_user_prefs($current=null) if (!isset($no_override['reply_mode'])) { $field_id = 'rcmfd_reply_mode'; - $select_replymode = new html_select(array('name' => '_reply_mode', 'id' => $field_id, - 'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex<2)")); + $select_replymode = new html_select(array('name' => '_reply_mode', 'id' => $field_id)); $select_replymode->add(rcube_label('replyempty'), -1); $select_replymode->add(rcube_label('replybottomposting'), 0); $select_replymode->add(rcube_label('replytopposting'), 1); @@ -631,18 +630,6 @@ function rcmail_user_prefs($current=null) ); } - if (!isset($no_override['sig_above'])) { - $field_id = 'rcmfd_sig_above'; - $select_sigabove = new html_select(array('name' => '_sig_above', 'id' => $field_id, 'disabled' => $config['reply_mode'] < 1)); - $select_sigabove->add(rcube_label('belowquote'), 0); - $select_sigabove->add(rcube_label('abovequote'), 1); - - $blocks['sig']['options']['sig_above'] = array( - 'title' => html::label($field_id, Q(rcube_label('replysignaturepos'))), - 'content' => $select_sigabove->show($config['sig_above']?1:0), - ); - } - if (!isset($no_override['strip_existing_sig'])) { $field_id = 'rcmfd_strip_existing_sig'; $input_stripexistingsig = new html_checkbox(array('name' => '_strip_existing_sig', 'id' => $field_id, 'value' => 1)); diff --git a/program/steps/settings/save_identity.inc b/program/steps/settings/save_identity.inc index d579ee61f..34d8be268 100644 --- a/program/steps/settings/save_identity.inc +++ b/program/steps/settings/save_identity.inc @@ -26,17 +26,14 @@ $a_boolean_cols = array('standard', 'html_signature'); $updated = $default_id = false; // check input -if (empty($_POST['_name']) || (empty($_POST['_email']) && IDENTITIES_LEVEL != 1 && IDENTITIES_LEVEL != 3)) -{ +if (IDENTITIES_LEVEL != 4 && (empty($_POST['_name']) || (empty($_POST['_email']) && IDENTITIES_LEVEL != 1 && IDENTITIES_LEVEL != 3))) { $OUTPUT->show_message('formincomplete', 'warning'); rcmail_overwrite_action('edit-identity'); return; } - $save_data = array(); -foreach ($a_save_cols as $col) -{ +foreach ($a_save_cols as $col) { $fname = '_'.$col; if (isset($_POST[$fname])) $save_data[$col] = get_input_value($fname, RCUBE_INPUT_POST, true); @@ -44,16 +41,24 @@ foreach ($a_save_cols as $col) // set "off" values for checkboxes that were not checked, and therefore // not included in the POST body. -foreach ($a_boolean_cols as $col) -{ +foreach ($a_boolean_cols as $col) { $fname = '_' . $col; if (!isset($_POST[$fname])) $save_data[$col] = 0; } // unset email address if user has no rights to change it -if (IDENTITIES_LEVEL == 1 || IDENTITIES_LEVEL == 3) +if (IDENTITIES_LEVEL == 1 || IDENTITIES_LEVEL == 3) { unset($save_data['email']); +} +// unset all fields except signature +else if (IDENTITIES_LEVEL == 4) { + foreach ($save_data as $idx => $value) { + if ($idx != 'signature' && $idx != 'html_signature') { + unset($save_data[$idx]); + } + } +} // Validate e-mail addresses $email_checks = array(rcube_idn_to_ascii($save_data['email'])); @@ -72,9 +77,16 @@ foreach ($email_checks as $email) { } // update an existing contact -if ($_POST['_iid']) -{ +if ($_POST['_iid']) { $iid = get_input_value('_iid', RCUBE_INPUT_POST); + + if (in_array(IDENTITIES_LEVEL, array(1,3,4))) { + // merge with old identity data, fixes #1488834 + $identity = $RCMAIL->user->get_identity($iid); + $save_data = array_merge($identity, $save_data); + unset($save_data['changed'], $save_data['del'], $save_data['user_id'], $save_data['identity_id']); + } + $plugin = $RCMAIL->plugins->exec_hook('identity_update', array('id' => $iid, 'record' => $save_data)); $save_data = $plugin['record']; @@ -88,8 +100,8 @@ if ($_POST['_iid']) if ($updated) { $OUTPUT->show_message('successfullysaved', 'confirmation'); - if (!empty($_POST['_standard'])) - $default_id = get_input_value('_iid', RCUBE_INPUT_POST); + if (!empty($save_data['standard'])) + $default_id = $iid; if ($_POST['_framed']) { // update the changed col in list @@ -105,8 +117,7 @@ if ($_POST['_iid']) } // insert a new identity record -else if (IDENTITIES_LEVEL < 2) -{ +else if (IDENTITIES_LEVEL < 2) { if (IDENTITIES_LEVEL == 1) { $save_data['email'] = $RCMAIL->get_user_email(); } @@ -127,7 +138,7 @@ else if (IDENTITIES_LEVEL < 2) $_GET['_iid'] = $insert_id; - if (!empty($_POST['_standard'])) + if (!empty($save_data['standard'])) $default_id = $insert_id; if ($_POST['_framed']) { diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc index 5daab0d24..140f173c6 100644 --- a/program/steps/settings/save_prefs.inc +++ b/program/steps/settings/save_prefs.inc @@ -86,7 +86,6 @@ switch ($CURR_SECTION) 'show_sig' => isset($_POST['_show_sig']) ? intval($_POST['_show_sig']) : 1, 'reply_mode' => isset($_POST['_reply_mode']) ? intval($_POST['_reply_mode']) : 0, 'strip_existing_sig' => isset($_POST['_strip_existing_sig']), - 'sig_above' => !empty($_POST['_sig_above']) && $_POST['_reply_mode'] > 0, 'default_font' => get_input_value('_default_font', RCUBE_INPUT_POST), 'forward_attachment' => !empty($_POST['_forward_attachment']), ); diff --git a/program/steps/utils/html2text.inc b/program/steps/utils/html2text.inc index e17665fec..c6481b197 100644 --- a/program/steps/utils/html2text.inc +++ b/program/steps/utils/html2text.inc @@ -24,10 +24,8 @@ $html = $HTTP_RAW_POST_DATA; // Replace emoticon images with its text representation $html = rcmail_replace_emoticons($html); -$converter = new html2text($html, false, true, 0); +$converter = new rcube_html2text($html, false, true, 0); header('Content-Type: text/plain; charset=UTF-8'); print rtrim($converter->get_text()); exit; - - diff --git a/program/steps/utils/save_pref.inc b/program/steps/utils/save_pref.inc index b550ad7ef..7def8733d 100644 --- a/program/steps/utils/save_pref.inc +++ b/program/steps/utils/save_pref.inc @@ -21,6 +21,22 @@ $name = get_input_value('_name', RCUBE_INPUT_POST); $value = get_input_value('_value', RCUBE_INPUT_POST); +$whitelist = array( + 'preview_pane', + 'list_cols', + 'collapsed_folders', + 'collapsed_abooks', +); + +if (!in_array($name, array_merge($whitelist, $RCMAIL->plugins->allowed_prefs))) { + raise_error(array('code' => 500, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => sprintf("Hack attempt detected (user: %s)", $RCMAIL->get_user_name())), + true, false); + + $OUTPUT->reset(); + $OUTPUT->send(); +} // save preference value $RCMAIL->user->save_prefs(array($name => $value)); |