From 197203727417a03d87053a47e5aa5175a76e3e0b Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Thu, 17 Oct 2013 04:24:53 -0400 Subject: [PATCH] Fix vulnerability in handling _session argument of utils/save-prefs (#1489382) --- program/js/app.js | 439 +++++++++++++++++++++++++++++------------------------- 1 files changed, 235 insertions(+), 204 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index ce0f52f..8ec2095 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -20,7 +20,7 @@ function rcube_webmail() { - this.env = {}; + this.env = { recipients_separator:',', recipients_delimiter:', ' }; this.labels = {}; this.buttons = {}; this.buttons_sel = {}; @@ -37,7 +37,7 @@ // webmail client settings this.dblclick_time = 500; - this.message_time = 2000; + this.message_time = 4000; this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi'); @@ -128,7 +128,7 @@ // initialize webmail client this.init = function() { - var p = this; + var n, p = this; this.task = this.env.task; // check browser @@ -138,12 +138,28 @@ } // find all registered gui containers - for (var n in this.gui_containers) + for (n in this.gui_containers) this.gui_containers[n] = $('#'+this.gui_containers[n]); // find all registered gui objects - for (var n in this.gui_objects) + for (n in this.gui_objects) this.gui_objects[n] = rcube_find_object(this.gui_objects[n]); + + // clickjacking protection + if (this.env.x_frame_options) { + try { + // bust frame if not allowed + if (this.env.x_frame_options == 'deny' && top.location.href != self.location.href) + top.location.href = self.location.href; + else if (top.location.hostname != self.location.hostname) + throw 1; + } catch (e) { + // possible clickjacking attack: disable all form elements + $('form').each(function(){ ref.lock_form(this, true); }); + this.display_message("Blocked: possible clickjacking attack!", 'error'); + return; + } + } // init registered buttons this.init_buttons(); @@ -155,7 +171,7 @@ } // enable general commands - this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'compose', 'undo', true); + this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'compose', 'undo', 'switch-task', true); if (this.env.permaurl) this.enable_command('permaurl', true); @@ -207,12 +223,13 @@ 'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource', 'download', 'print', 'load-attachment', 'load-headers', 'forward-attachment']; - if (this.env.action=='show' || this.env.action=='preview') { + if (this.env.action == 'show' || this.env.action == 'preview') { this.enable_command(this.env.message_commands, this.env.uid); this.enable_command('reply-list', this.env.list_post); if (this.env.action == 'show') { - this.http_request('pagenav', '_uid='+this.env.uid+'&_mbox='+urlencode(this.env.mailbox), + this.http_request('pagenav', '_uid='+this.env.uid+'&_mbox='+urlencode(this.env.mailbox) + + (this.env.search_request ? '&_search='+this.env.search_request : ''), this.display_message('', 'loading')); } @@ -252,7 +269,7 @@ // show printing dialog else if (this.env.action == 'print' && this.env.uid) if (bw.safari) - window.setTimeout('window.print()', 10); + setTimeout('window.print()', 10); else window.print(); @@ -380,8 +397,10 @@ $('#rcmloginpwd').focus(); // detect client timezone - var tz = new Date().getTimezoneOffset() / -60; - var stdtz = new Date().getStdTimezoneOffset() / -60; + var dt = new Date(), + tz = dt.getTimezoneOffset() / -60, + stdtz = dt.getStdTimezoneOffset() / -60; + $('#rcmlogintz').val(stdtz); $('#rcmlogindst').val(tz > stdtz ? 1 : 0); @@ -442,7 +461,7 @@ // execute a specific command on the web client this.command = function(command, props, obj) { - var ret; + var ret, uid, cid, url, flag; if (obj && obj.blur) obj.blur(); @@ -460,7 +479,7 @@ } // check input before leaving compose step - if (this.task=='mail' && this.env.action=='compose' && $.inArray(command, this.env.compose_commands)<0) { + if (this.task == 'mail' && this.env.action == 'compose' && $.inArray(command, this.env.compose_commands)<0) { if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning'))) return false; } @@ -517,7 +536,6 @@ return false; case 'open': - var uid; if (uid = this.get_single_uid()) { obj.href = '?_task='+this.env.task+'&_action=show&_mbox='+urlencode(this.env.mailbox)+'&_uid='+uid; return true; @@ -586,7 +604,7 @@ // common commands used in multiple tasks case 'show': if (this.task == 'mail') { - var uid = this.get_single_uid(); + uid = this.get_single_uid(); if (uid && (!this.env.uid || uid != this.env.uid)) { if (this.env.mailbox == this.env.drafts_mailbox) this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true); @@ -595,7 +613,7 @@ } } else if (this.task == 'addressbook') { - var cid = props ? props : this.get_single_cid(); + cid = props ? props : this.get_single_cid(); if (cid && !(this.env.action == 'show' && cid == this.env.cid)) this.load_contact(cid, 'show'); } @@ -611,13 +629,12 @@ break; case 'edit': - var cid; if (this.task=='addressbook' && (cid = this.get_single_cid())) this.load_contact(cid, 'edit'); else if (this.task=='settings' && props) this.load_identity(props, 'edit-identity'); else if (this.task=='mail' && (cid = this.get_single_uid())) { - var url = (this.env.mailbox == this.env.drafts_mailbox) ? '_draft_uid=' : '_uid='; + url = (this.env.mailbox == this.env.drafts_mailbox) ? '_draft_uid=' : '_uid='; this.goto_url('compose', url+cid+'&_mbox='+urlencode(this.env.mailbox), true); } break; @@ -695,7 +712,7 @@ if (props && !props._row) break; - var uid, flag = 'read'; + flag = 'read'; if (props._row.uid) { uid = props._row.uid; @@ -715,7 +732,7 @@ if (props && !props._row) break; - var uid, flag = 'flagged'; + flag = 'flagged'; if (props._row.uid) { uid = props._row.uid; @@ -729,7 +746,7 @@ case 'always-load': if (this.env.uid && this.env.sender) { this.add_contact(urlencode(this.env.sender)); - window.setTimeout(function(){ ref.command('load-images'); }, 300); + setTimeout(function(){ ref.command('load-images'); }, 300); break; } @@ -747,7 +764,7 @@ qstring += '&_safe=1'; this.attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment'); if (this.attachment_win) { - window.setTimeout(function(){ ref.attachment_win.focus(); }, 10); + setTimeout(function(){ ref.attachment_win.focus(); }, 10); break; } } @@ -811,17 +828,11 @@ break; case 'compose': - var url = this.url('mail/compose'); + url = this.url('mail/compose'); if (this.task == 'mail') { url += '&_mbox='+urlencode(this.env.mailbox); - - if (this.env.mailbox == this.env.drafts_mailbox) { - var uid; - if (uid = this.get_single_uid()) - url += '&_draft_uid='+uid; - } - else if (props) + if (props) url += '&_to='+urlencode(props); } // modify url if we're in addressbook @@ -868,19 +879,25 @@ break; case 'savedraft': + var form = this.gui_objects.messageform, msgid; + // Reset the auto-save timer - self.clearTimeout(this.save_timer); + clearTimeout(this.save_timer); - if (!this.gui_objects.messageform) + // saving Drafts is disabled + if (!form) break; - // if saving Drafts is disabled in main.inc.php - // or if compose form did not change - if (!this.env.drafts_mailbox || this.cmp_hash == this.compose_field_hash()) + // compose form did not change + if (this.cmp_hash == this.compose_field_hash()) { + this.auto_save_start(); break; + } - var form = this.gui_objects.messageform, - msgid = this.set_busy(true, 'savingmessage'); + // re-set keep-alive timeout + this.start_keepalive(); + + msgid = this.set_busy(true, 'savingmessage'); form.target = "savetarget"; form._draft.value = '1'; @@ -892,11 +909,11 @@ if (!this.gui_objects.messageform) break; - if (!this.check_compose_input()) + if (!props.nocheck && !this.check_compose_input(command)) break; // Reset the auto-save timer - self.clearTimeout(this.save_timer); + clearTimeout(this.save_timer); // all checks passed, send message var lang = this.spellcheck_lang(), @@ -915,7 +932,7 @@ case 'send-attachment': // Reset the auto-save timer - self.clearTimeout(this.save_timer); + clearTimeout(this.save_timer); this.upload_file(props) break; @@ -927,9 +944,8 @@ case 'reply-all': case 'reply-list': case 'reply': - var uid; if (uid = this.get_single_uid()) { - var url = '_reply_uid='+uid+'&_mbox='+urlencode(this.env.mailbox); + url = '_reply_uid='+uid+'&_mbox='+urlencode(this.env.mailbox); if (command == 'reply-all') // do reply-list, when list is detected and popup menu wasn't used url += '&_all=' + (!props && this.commands['reply-list'] ? 'list' : 'all'); @@ -942,7 +958,6 @@ case 'forward-attachment': case 'forward': - var uid, url; if (uid = this.get_single_uid()) { url = '_forward_uid='+uid+'&_mbox='+urlencode(this.env.mailbox); if (command == 'forward-attachment' || (!props && this.env.forward_attachment)) @@ -952,11 +967,10 @@ break; case 'print': - var uid; if (uid = this.get_single_uid()) { ref.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : '')); if (this.printwin) { - window.setTimeout(function(){ ref.printwin.focus(); }, 20); + setTimeout(function(){ ref.printwin.focus(); }, 20); if (this.env.action != 'show') this.mark_message('read', uid); } @@ -964,16 +978,14 @@ break; case 'viewsource': - var uid; if (uid = this.get_single_uid()) { ref.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)); if (this.sourcewin) - window.setTimeout(function(){ ref.sourcewin.focus(); }, 20); + setTimeout(function(){ ref.sourcewin.focus(); }, 20); } break; case 'download': - var uid; if (uid = this.get_single_uid()) this.goto_url('viewsource', '&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+'&_save=1'); break; @@ -1070,10 +1082,10 @@ // set command(s) enabled or disabled this.enable_command = function() { - var args = Array.prototype.slice.call(arguments), + var i, n, args = Array.prototype.slice.call(arguments), enable = args.pop(), cmd; - for (var n=0; n<args.length; n++) { + for (n=0; n<args.length; n++) { cmd = args[n]; // argument of type array if (typeof cmd === 'string') { @@ -1082,7 +1094,7 @@ } // push array elements into commands array else { - for (var i in cmd) + for (i in cmd) args.push(cmd[i]); } } @@ -1114,7 +1126,7 @@ // set timer for requests if (a && this.env.request_timeout) - this.request_timer = window.setTimeout(function(){ ref.request_timed_out(); }, this.env.request_timeout * 1000); + this.request_timer = setTimeout(function(){ ref.request_timed_out(); }, this.env.request_timeout * 1000); return id; }; @@ -1166,7 +1178,7 @@ if (this.is_framed()) parent.rcmail.reload(delay); else if (delay) - window.setTimeout(function(){ rcmail.reload(); }, delay); + setTimeout(function(){ rcmail.reload(); }, delay); else if (window.location) location.href = this.env.comm_path + (this.env.action ? '&_action='+this.env.action : ''); }; @@ -1207,6 +1219,24 @@ this.env[prop.env] = prop.value; this.http_post('save-pref', request); + }; + + this.html_identifier = function(str, encode) + { + str = String(str); + if (encode) + return Base64.encode(str).replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_'); + else + return str.replace(this.identifier_expr, '_'); + }; + + this.html_identifier_decode = function(str) + { + str = String(str).replace(/-/g, '+').replace(/_/g, '/'); + + while (str.length % 4) str += '='; + + return Base64.decode(str); }; @@ -1255,13 +1285,14 @@ this.initialBodyScrollTop = bw.ie ? 0 : window.pageYOffset; this.initialListScrollTop = this.gui_objects.folderlist.parentNode.scrollTop; - var li, pos, list, height; - list = $(this.gui_objects.folderlist); - pos = list.offset(); + var k, li, height, + list = $(this.gui_objects.folderlist); + pos = list.offset(); + this.env.folderlist_coords = { x1:pos.left, y1:pos.top, x2:pos.left + list.width(), y2:pos.top + list.height() }; this.env.folder_coords = []; - for (var k in model) { + for (k in model) { if (li = this.get_folder_li(k)) { // only visible folders if (height = li.firstChild.offsetHeight) { @@ -1280,7 +1311,7 @@ this.env.last_folder_target = null; if (this.folder_auto_timer) { - window.clearTimeout(this.folder_auto_timer); + clearTimeout(this.folder_auto_timer); this.folder_auto_timer = null; this.folder_auto_expand = null; } @@ -1297,19 +1328,18 @@ this.drag_move = function(e) { if (this.gui_objects.folderlist && this.env.folder_coords) { - // offsets to compensate for scrolling while dragging a message - var boffset = bw.ie ? -document.documentElement.scrollTop : this.initialBodyScrollTop; - var moffset = this.initialListScrollTop-this.gui_objects.folderlist.parentNode.scrollTop; - var toffset = -moffset-boffset; - var li, div, pos, mouse, check, oldclass, - layerclass = 'draglayernormal'; + var k, li, div, check, oldclass, + layerclass = 'draglayernormal', + mouse = rcube_event.get_mouse_pos(e), + pos = this.env.folderlist_coords, + // offsets to compensate for scrolling while dragging a message + boffset = bw.ie ? -document.documentElement.scrollTop : this.initialBodyScrollTop, + moffset = this.initialListScrollTop-this.gui_objects.folderlist.parentNode.scrollTop; if (this.contact_list && this.contact_list.draglayer) oldclass = this.contact_list.draglayer.attr('class'); - mouse = rcube_event.get_mouse_pos(e); - pos = this.env.folderlist_coords; - mouse.y += toffset; + mouse.y += -moffset-boffset; // if mouse pointer is outside of folderlist if (mouse.x < pos.x1 || mouse.x >= pos.x2 || mouse.y < pos.y1 || mouse.y >= pos.y2) { @@ -1324,25 +1354,25 @@ } // over the folders - for (var k in this.env.folder_coords) { + for (k in this.env.folder_coords) { pos = this.env.folder_coords[k]; if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.y >= pos.y1 && mouse.y < pos.y2){ - if ((check = this.check_droptarget(k))) { + if ((check = this.check_droptarget(k))) { li = this.get_folder_li(k); div = $(li.getElementsByTagName('div')[0]); // if the folder is collapsed, expand it after 1sec and restart the drag & drop process. if (div.hasClass('collapsed')) { if (this.folder_auto_timer) - window.clearTimeout(this.folder_auto_timer); + clearTimeout(this.folder_auto_timer); - this.folder_auto_expand = k; - this.folder_auto_timer = window.setTimeout(function() { - rcmail.command('collapse-folder', rcmail.folder_auto_expand); - rcmail.drag_start(null); - }, 1000); + this.folder_auto_expand = this.env.mailboxes[k].id; + this.folder_auto_timer = setTimeout(function() { + rcmail.command('collapse-folder', rcmail.folder_auto_expand); + rcmail.drag_start(null); + }, 1000); } else if (this.folder_auto_timer) { - window.clearTimeout(this.folder_auto_timer); + clearTimeout(this.folder_auto_timer); this.folder_auto_timer = null; this.folder_auto_expand = null; } @@ -1366,31 +1396,30 @@ } }; - this.collapse_folder = function(id) + this.collapse_folder = function(name) { - var li = this.get_folder_li(id), - div = $(li.getElementsByTagName('div')[0]); - - if (!div || (!div.hasClass('collapsed') && !div.hasClass('expanded'))) - return; - - var ul = $(li.getElementsByTagName('ul')[0]); + var li = this.get_folder_li(name, '', true), + div = $('div:first', li), + ul = $('ul:first', li); if (div.hasClass('collapsed')) { ul.show(); div.removeClass('collapsed').addClass('expanded'); - var reg = new RegExp('&'+urlencode(id)+'&'); + var reg = new RegExp('&'+urlencode(name)+'&'); this.env.collapsed_folders = this.env.collapsed_folders.replace(reg, ''); } - else { + else if (div.hasClass('expanded')) { ul.hide(); div.removeClass('expanded').addClass('collapsed'); - this.env.collapsed_folders = this.env.collapsed_folders+'&'+urlencode(id)+'&'; + this.env.collapsed_folders = this.env.collapsed_folders+'&'+urlencode(name)+'&'; - // select parent folder if one of its childs is currently selected - if (this.env.mailbox.indexOf(id + this.env.delimiter) == 0) - this.command('list', id); + // select the folder if one of its childs is currently selected + // don't select if it's virtual (#1488346) + if (this.env.mailbox.indexOf(name + this.env.delimiter) == 0 && !$(li).hasClass('virtual')) + this.command('list', name); } + else + return; // Work around a bug in IE6 and IE7, see #1485309 if (bw.ie6 || bw.ie7) { @@ -1402,7 +1431,7 @@ } this.command('save-pref', { name: 'collapsed_folders', value: this.env.collapsed_folders }); - this.set_unread_count_display(id, false); + this.set_unread_count_display(name, false); }; this.doc_mouse_up = function(e) @@ -1495,7 +1524,7 @@ // start timer for message preview (wait for double click) if (selected && this.env.contentframe && !list.multi_selecting && !this.dummy_select) - this.preview_timer = window.setTimeout(function(){ ref.msglist_get_preview(); }, 200); + this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200); else if (this.env.contentframe) this.show_contentframe(false); }; @@ -1512,7 +1541,7 @@ clearTimeout(this.preview_timer); if (this.preview_read_timer) clearTimeout(this.preview_read_timer); - this.preview_timer = window.setTimeout(function(){ ref.msglist_get_preview(); }, 200); + this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200); } } }; @@ -1571,7 +1600,7 @@ for (i=0; i<cols.length; i++) if (cols[i].id && cols[i].id.match(/^rcm/)) { name = cols[i].id.replace(/^rcm/, ''); - this.env.coltypes.push(name == 'to' ? 'from' : name); + this.env.coltypes.push(name); } if ((found = $.inArray('flag', this.env.coltypes)) >= 0) @@ -1781,8 +1810,11 @@ else if (c == 'threads') html = expando; else if (c == 'subject') { - if (bw.ie) + if (bw.ie) { col.onmouseover = function() { rcube_webmail.long_subject_title_ie(this, message.depth+1); }; + if (bw.ie8) + tree = '<span></span>' + tree; // #1487821 + } html = tree + cols[c]; } else if (c == 'priority') { @@ -1843,7 +1875,7 @@ // make sure new columns are added at the end of the list var i, idx, name, newcols = [], oldcols = this.env.coltypes; for (i=0; i<oldcols.length; i++) { - name = oldcols[i] == 'to' ? 'from' : oldcols[i]; + name = oldcols[i]; idx = $.inArray(name, cols); if (idx != -1) { newcols.push(name); @@ -1893,7 +1925,7 @@ // mark as read and change mbox unread counter if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) { - this.preview_read_timer = window.setTimeout(function() { + this.preview_read_timer = setTimeout(function() { ref.set_message(id, 'unread', false); ref.update_thread_root(id, 'read'); if (ref.env.unread_counts[ref.env.mailbox]) { @@ -1995,7 +2027,7 @@ if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort)) url += '&_refresh=1'; - this.select_folder(mbox); + this.select_folder(mbox, '', true); this.env.mailbox = mbox; // load message list remotely @@ -2926,12 +2958,14 @@ this.init_address_input_events = function(obj, props) { + this.env.recipients_delimiter = this.env.recipients_separator + ' '; + obj[bw.ie || bw.safari || bw.chrome ? 'keydown' : 'keypress'](function(e) { return ref.ksearch_keydown(e, this, props); }) .attr('autocomplete', 'off'); }; // checks the input fields before sending a message - this.check_compose_input = function() + this.check_compose_input = function(cmd) { // check input fields var ed, input_to = $("[name='_to']"), @@ -2966,15 +3000,28 @@ // display localized warning for missing subject if (input_subject.val() == '') { - var subject = prompt(this.get_label('nosubjectwarning'), this.get_label('nosubject')); + var myprompt = $('<div class="prompt">').html('<div class="message">' + this.get_label('nosubjectwarning') + '</div>').appendTo(document.body); + var prompt_value = $('<input>').attr('type', 'text').attr('size', 30).appendTo(myprompt).val(this.get_label('nosubject')); - // user hit cancel, so don't send - if (!subject && subject !== '') { + var buttons = {}; + buttons[this.get_label('cancel')] = function(){ input_subject.focus(); - return false; - } - else - input_subject.val((subject ? subject : this.get_label('nosubject'))); + $(this).dialog('close'); + }; + buttons[this.get_label('sendmessage')] = function(){ + input_subject.val(prompt_value.val()); + $(this).dialog('close'); + ref.command(cmd, { nocheck:true }); // repeat command which triggered this + }; + + myprompt.dialog({ + modal: true, + resizable: false, + buttons: buttons, + close: function(event, ui) { $(this).remove() } + }); + prompt_value.select(); + return false; } // Apply spellcheck changes if spell checker is active @@ -3045,7 +3092,7 @@ if (!vis) this.stop_spellchecking(); - $(this.env.spellcheck.spell_container).css('visibility', vis ? 'visible' : 'hidden'); + $(this.env.spellcheck.spell_container)[vis ? 'show' : 'hide'](); } }; @@ -3093,7 +3140,7 @@ this.auto_save_start = function() { if (this.env.draft_autosave) - this.save_timer = self.setTimeout(function(){ ref.command("savedraft"); }, this.env.draft_autosave * 1000); + this.save_timer = setTimeout(function(){ ref.command("savedraft"); }, this.env.draft_autosave * 1000); // Unlock interface now that saving is complete this.busy = false; @@ -3353,16 +3400,8 @@ this.remove_from_attachment_list = function(name) { - if (this.env.attachments[name]) - delete this.env.attachments[name]; - - if (!this.gui_objects.attachmentlist) - return false; - - var list = this.gui_objects.attachmentlist.getElementsByTagName("li"); - for (i=0; i<list.length; i++) - if (list[i].id == name) - this.gui_objects.attachmentlist.removeChild(list[i]); + delete this.env.attachments[name]; + $('#'+name).remove(); }; this.remove_attachment = function(name) @@ -3385,7 +3424,7 @@ this.upload_progress_start = function(action, name) { - window.setTimeout(function() { rcmail.http_request(action, {_progress: name}); }, + setTimeout(function() { rcmail.http_request(action, {_progress: name}); }, this.env.upload_progress_time * 1000); }; @@ -3485,7 +3524,7 @@ { this.display_message(msg, type); // before redirect we need to wait some time for Chrome (#1486177) - window.setTimeout(function(){ ref.list_mailbox(); }, 500); + setTimeout(function(){ ref.list_mailbox(); }, 500); }; @@ -3504,9 +3543,9 @@ mod = rcube_event.get_modifier(e); switch (key) { - case 38: // key up - case 40: // key down - if (!this.ksearch_pane) + case 38: // arrow up + case 40: // arrow down + if (!this.ksearch_visible()) break; var dir = key==38 ? 1 : 0; @@ -3543,11 +3582,11 @@ case 37: // left case 39: // right if (mod != SHIFT_KEY) - return; + return; } // start timer - this.ksearch_timer = window.setTimeout(function(){ ref.ksearch_get_results(props); }, 200); + this.ksearch_timer = setTimeout(function(){ ref.ksearch_get_results(props); }, 200); this.ksearch_input = obj; return true; @@ -3590,13 +3629,13 @@ // insert all members of a group if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].id) { - insert += this.env.contacts[id].name + ', '; + insert += this.env.contacts[id].name + this.env.recipients_delimiter; this.group2expand = $.extend({}, this.env.contacts[id]); this.group2expand.input = this.ksearch_input; this.http_request('mail/group-expand', '_source='+urlencode(this.env.contacts[id].source)+'&_gid='+urlencode(this.env.contacts[id].id), false); } else if (typeof this.env.contacts[id] === 'string') { - insert = this.env.contacts[id] + ', '; + insert = this.env.contacts[id] + this.env.recipients_delimiter; trigger = true; } @@ -3633,7 +3672,7 @@ // get string from current cursor pos to last comma var cpos = this.get_caret_pos(this.ksearch_input), - p = inp_value.lastIndexOf(',', cpos-1), + p = inp_value.lastIndexOf(this.env.recipients_separator, cpos-1), q = inp_value.substring(p+1, cpos), min = this.env.autocomplete_min_length, ac = this.ksearch_data; @@ -3699,7 +3738,7 @@ return; // display search results - var ul, li, text, init, + var i, len, ul, li, text, init, value = this.ksearch_value, data = this.ksearch_data, maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15; @@ -3730,8 +3769,8 @@ } // add each result line to list - if (results && results.length) { - for (i=0; i < results.length && maxlen > 0; i++) { + if (results && (len = results.length)) { + for (i=0; i < len && maxlen > 0; i++) { text = typeof results[i] === 'object' ? results[i].name : results[i]; li = document.createElement('LI'); li.innerHTML = text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%').replace(/</g, '<').replace(/>/g, '>').replace(/##([^%]+)%%/g, '<b>$1</b>'); @@ -3752,7 +3791,7 @@ } } - if (results && results.length) + if (len) this.env.contacts = this.env.contacts.concat(results); // run next parallel search @@ -3855,7 +3894,7 @@ source = this.env.source ? this.env.address_sources[this.env.source] : null; if (id = list.get_single_selection()) - this.preview_timer = window.setTimeout(function(){ ref.load_contact(id, 'show'); }, 200); + this.preview_timer = setTimeout(function(){ ref.load_contact(id, 'show'); }, 200); else if (this.env.contentframe) this.show_contentframe(false); @@ -4035,10 +4074,10 @@ this.delete_contacts = function() { - // exit if no mailbox specified or if selection is empty var selection = this.contact_list.get_selection(), - undelete = this.env.address_sources[this.env.source].undelete; + undelete = this.env.source && this.env.address_sources[this.env.source].undelete; + // exit if no mailbox specified or if selection is empty if (!(selection.length || this.env.cid) || (!undelete && !confirm(this.get_label('deletecontactconfirm')))) return; @@ -4079,7 +4118,7 @@ { var c, row, list = this.contact_list; - cid = String(cid).replace(this.identifier_expr, '_'); + cid = this.html_identifier(cid); // when in searching mode, concat cid with the source name if (!list.rows[cid]) { @@ -4095,7 +4134,7 @@ // cid change if (newcid) { - newcid = String(newcid).replace(this.identifier_expr, '_'); + newcid = this.html_identifier(newcid); row.id = 'rcmrow' + newcid; list.remove_row(cid); list.init_row(row); @@ -4114,7 +4153,7 @@ var c, list = this.contact_list, row = document.createElement('tr'); - row.id = 'rcmrow'+String(cid).replace(this.identifier_expr, '_'); + row.id = 'rcmrow'+this.html_identifier(cid); row.className = 'contact'; if (list.in_selection(cid)) @@ -4296,7 +4335,7 @@ .attr('rel', prop.source+':'+prop.id) .click(function() { return rcmail.command('listgroup', prop, this); }) .html(prop.name), - li = $('<li>').attr({id: 'rcmli'+key.replace(this.identifier_expr, '_'), 'class': 'contactgroup'}) + li = $('<li>').attr({id: 'rcmli'+this.html_identifier(key), 'class': 'contactgroup'}) .append(link); this.env.contactfolders[key] = this.env.contactgroups[key] = prop; @@ -4319,7 +4358,7 @@ var newkey = 'G'+prop.source+prop.newid, newprop = $.extend({}, prop);; - li.id = String('rcmli'+newkey).replace(this.identifier_expr, '_'); + li.id = 'rcmli' + this.html_identifier(newkey); this.env.contactfolders[newkey] = this.env.contactfolders[key]; this.env.contactfolders[newkey].id = prop.newid; this.env.group = prop.newid; @@ -4351,7 +4390,7 @@ { var row, name = prop.name.toUpperCase(), sibling = this.get_folder_li(prop.source), - prefix = 'rcmliG'+(prop.source).replace(this.identifier_expr, '_'); + prefix = 'rcmliG' + this.html_identifier(prop.source); // When renaming groups, we need to remove it from DOM and insert it in the proper place if (reloc) { @@ -4422,7 +4461,7 @@ .appendTo(cell); this.init_edit_field(col, input); - + if (colprop.type == 'date' && $.datepicker) input.datepicker(); } @@ -4584,7 +4623,7 @@ .attr('rel', id) .click(function() { return rcmail.command('listsearch', id, this); }) .html(name), - li = $('<li>').attr({id: 'rcmli'+key.replace(this.identifier_expr, '_'), 'class': 'contactsearch'}) + li = $('<li>').attr({id: 'rcmli' + this.html_identifier(key), 'class': 'contactsearch'}) .append(link), prop = {name:name, id:id, li:li[0]}; @@ -5155,17 +5194,18 @@ init_button(cmd, this.buttons[cmd][i]); } } + + // set active task button + this.set_button(this.task, 'sel'); }; // set button to a specific state this.set_button = function(command, state) { - var button, obj, a_buttons = this.buttons[command]; + var n, button, obj, a_buttons = this.buttons[command], + len = a_buttons ? a_buttons.length : 0; - if (!a_buttons || !a_buttons.length) - return false; - - for (var n=0; n<a_buttons.length; n++) { + for (n=0; n<len; n++) { button = a_buttons[n]; obj = document.getElementById(button.id); @@ -5200,15 +5240,14 @@ // display a specific alttext this.set_alttext = function(command, label) { - if (!this.buttons[command] || !this.buttons[command].length) - return; + var n, button, obj, link, a_buttons = this.buttons[command], + len = a_buttons ? a_buttons.length : 0; - var button, obj, link; - for (var n=0; n<this.buttons[command].length; n++) { - button = this.buttons[command][n]; + for (n=0; n<len; n++) { + button = a_buttons[n]; obj = document.getElementById(button.id); - if (button.type=='image' && obj) { + if (button.type == 'image' && obj) { obj.setAttribute('alt', this.get_label(label)); if ((link = obj.parentNode) && link.tagName.toLowerCase() == 'a') link.setAttribute('title', this.get_label(label)); @@ -5221,20 +5260,18 @@ // mouse over button this.button_over = function(command, id) { - var button, elm, a_buttons = this.buttons[command]; + var n, button, obj, a_buttons = this.buttons[command], + len = a_buttons ? a_buttons.length : 0; - if (!a_buttons || !a_buttons.length) - return false; - - for (var n=0; n<a_buttons.length; n++) { + for (n=0; n<len; n++) { button = a_buttons[n]; if (button.id == id && button.status == 'act') { - elm = document.getElementById(button.id); - if (elm && button.over) { + obj = document.getElementById(button.id); + if (obj && button.over) { if (button.type == 'image') - elm.src = button.over; + obj.src = button.over; else - elm.className = button.over; + obj.className = button.over; } } } @@ -5243,20 +5280,18 @@ // mouse down on button this.button_sel = function(command, id) { - var button, elm, a_buttons = this.buttons[command]; + var n, button, obj, a_buttons = this.buttons[command], + len = a_buttons ? a_buttons.length : 0; - if (!a_buttons || !a_buttons.length) - return; - - for (var n=0; n<a_buttons.length; n++) { + for (n=0; n<len; n++) { button = a_buttons[n]; if (button.id == id && button.status == 'act') { - elm = document.getElementById(button.id); - if (elm && button.sel) { + obj = document.getElementById(button.id); + if (obj && button.sel) { if (button.type == 'image') - elm.src = button.sel; + obj.src = button.sel; else - elm.className = button.sel; + obj.className = button.sel; } this.buttons_sel[id] = command; } @@ -5266,25 +5301,22 @@ // mouse out of button this.button_out = function(command, id) { - var button, elm, a_buttons = this.buttons[command]; + var n, button, obj, a_buttons = this.buttons[command], + len = a_buttons ? a_buttons.length : 0; - if (!a_buttons || !a_buttons.length) - return; - - for (var n=0; n<a_buttons.length; n++) { + for (n=0; n<len; n++) { button = a_buttons[n]; if (button.id == id && button.status == 'act') { - elm = document.getElementById(button.id); - if (elm && button.act) { + obj = document.getElementById(button.id); + if (obj && button.act) { if (button.type == 'image') - elm.src = button.act; + obj.src = button.act; else - elm.className = button.act; + obj.className = button.act; } } } }; - this.focus_textfield = function(elem) { @@ -5319,14 +5351,14 @@ if (!this.gui_objects.message) { // save message in order to display after page loaded if (type != 'loading') - this.pending_message = new Array(msg, type, timeout); + this.pending_message = [msg, type, timeout]; return false; } type = type ? type : 'notice'; var ref = this, - key = String(msg).replace(this.identifier_expr, '_'), + key = this.html_identifier(msg), date = new Date(), id = type + date.getTime(); @@ -5351,7 +5383,7 @@ } // add element and set timeout this.messages[key].elements.push(id); - window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout); + setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout); return id; } @@ -5369,7 +5401,7 @@ } if (timeout > 0) - window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout); + setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout); return id; }; @@ -5419,7 +5451,7 @@ }; // mark a mailbox as selected and set environment variable - this.select_folder = function(name, prefix) + this.select_folder = function(name, prefix, encode) { if (this.gui_objects.folderlist) { var current_li, target_li; @@ -5427,7 +5459,7 @@ if ((current_li = $('li.selected', this.gui_objects.folderlist))) { current_li.removeClass('selected').addClass('unfocused'); } - if ((target_li = this.get_folder_li(name, prefix))) { + if ((target_li = this.get_folder_li(name, prefix, encode))) { $(target_li).removeClass('unfocused').addClass('selected'); } @@ -5437,13 +5469,13 @@ }; // helper method to find a folder list item - this.get_folder_li = function(name, prefix) + this.get_folder_li = function(name, prefix, encode) { if (!prefix) prefix = 'rcmli'; if (this.gui_objects.folderlist) { - name = String(name).replace(this.identifier_expr, '_'); + name = this.html_identifier(name, encode); return document.getElementById(prefix+name); } @@ -5452,7 +5484,7 @@ // for reordering column array (Konqueror workaround) // and for setting some message list global variables - this.set_message_coltypes = function(coltypes, repl) + this.set_message_coltypes = function(coltypes, repl, smart_col) { var list = this.message_list, thead = list ? list.list.tHead : null, @@ -5480,7 +5512,7 @@ for (n=0, len=this.env.coltypes.length; n<len; n++) { col = this.env.coltypes[n]; - if ((cell = thead.rows[0].cells[n]) && (col=='from' || col=='to')) { + if ((cell = thead.rows[0].cells[n]) && (col == 'from' || col == 'to' || col == 'fromto')) { cell.id = 'rcm'+col; // if we have links for sorting, it's a bit more complicated... if (cell.firstChild && cell.firstChild.tagName.toLowerCase()=='a') { @@ -5488,7 +5520,7 @@ cell.onclick = function(){ return rcmail.command('sort', this.__col, this); }; cell.__col = col; } - cell.innerHTML = this.get_label(col); + cell.innerHTML = this.get_label(col == 'fromto' ? smart_col : col); } } } @@ -5557,7 +5589,7 @@ { var reg, link, text_obj, item, mycount, childcount, div; - if (item = this.get_folder_li(mbox)) { + if (item = this.get_folder_li(mbox, '', true)) { mycount = this.env.unread_counts[mbox] ? this.env.unread_counts[mbox] : 0; link = $(item).children('a').eq(0); text_obj = link.children('span.unreadcount'); @@ -5569,7 +5601,7 @@ if ((div = item.getElementsByTagName('div')[0]) && div.className.match(/collapsed/)) { // add children's counters - for (var k in this.env.unread_counts) + for (var k in this.env.unread_counts) if (k.indexOf(mbox + this.env.delimiter) == 0) childcount += this.env.unread_counts[k]; } @@ -5610,16 +5642,12 @@ this.toggle_prefer_html = function(checkbox) { - var elem; - if (elem = document.getElementById('rcmfd_addrbook_show_images')) - elem.disabled = !checkbox.checked; + $('#rcmfd_show_images').prop('disabled', !checkbox.checked).val(0); }; this.toggle_preview_pane = function(checkbox) { - var elem; - if (elem = document.getElementById('rcmfd_preview_pane_mark_read')) - elem.disabled = !checkbox.checked; + $('#rcmfd_preview_pane_mark_read').prop('disabled', !checkbox.checked); }; // display fetched raw headers @@ -5735,14 +5763,17 @@ $.ajax({ type: 'POST', url: url, data: htmlText, contentType: 'application/octet-stream', error: function(o, status, err) { rcmail.http_error(o, status, err, lock); }, - success: function(data) { rcmail.set_busy(false, null, lock); $(document.getElementById(id)).val(data); rcmail.log(data); } + success: function(data) { rcmail.set_busy(false, null, lock); $('#'+id).val(data); rcmail.log(data); } }); }; - this.plain2html = function(plainText, id) + this.plain2html = function(plain, id) { var lock = this.set_busy(true, 'converting'); - $(document.getElementById(id)).val('<pre>'+plainText+'</pre>'); + + plain = plain.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); + $('#'+id).val(plain ? '<pre>'+plain+'</pre>' : ''); + this.set_busy(false, null, lock); }; -- Gitblit v1.9.1