From 7c2a9310c4104f51fcf56379dcc3511fa5bfae2d Mon Sep 17 00:00:00 2001 From: thomascube <thomas@roundcube.net> Date: Mon, 02 Jan 2012 09:44:28 -0500 Subject: [PATCH] Use iframes for identity management --- program/js/app.js | 231 ++++++++++++++++++++++++++++++++++++++++----------------- 1 files changed, 160 insertions(+), 71 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 4b4187d..27af1ff 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -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'); @@ -145,6 +145,22 @@ 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(); @@ -212,7 +228,8 @@ 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')); } @@ -334,11 +351,18 @@ this.enable_command('preferences', 'identities', 'save', 'folders', true); if (this.env.action == 'identities') { - this.enable_command('add', this.env.identities_level < 2); + this.enable_command('add', 'delete', this.env.identities_level < 2); } else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') { this.enable_command('add', this.env.identities_level < 2); - this.enable_command('save', 'delete', 'edit', 'toggle-editor', true); + this.enable_command('save', 'edit', 'toggle-editor', true); + if (this.is_framed() && this.env.identities_level < 2) + this.set_button('delete', 'act'); // activate button but delegate command to parent + else + this.enable_command('delete', this.env.identities_level < 2); + + if (this.env.action == 'add-identity') + $("input[type='text']").first().select(); } else if (this.env.action == 'folders') { this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true); @@ -890,7 +914,7 @@ 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 @@ -914,8 +938,8 @@ case 'send-attachment': // Reset the auto-save timer self.clearTimeout(this.save_timer); - - this.upload_file(props) + + this.upload_file(props || this.gui_objects.uploadform); break; case 'insert-sig': @@ -1026,7 +1050,7 @@ break; case 'upload-photo': - this.upload_contact_photo(props); + this.upload_contact_photo(props || this.gui_objects.uploadform); break; case 'delete-photo': @@ -1202,6 +1226,24 @@ 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); + }; + /*********************************************************/ /********* event handling methods *********/ @@ -1329,7 +1371,7 @@ if (this.folder_auto_timer) window.clearTimeout(this.folder_auto_timer); - this.folder_auto_expand = k; + this.folder_auto_expand = this.env.mailboxes[k].id; this.folder_auto_timer = window.setTimeout(function() { rcmail.command('collapse-folder', rcmail.folder_auto_expand); rcmail.drag_start(null); @@ -1359,31 +1401,29 @@ } }; - 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); + if (this.env.mailbox.indexOf(name + this.env.delimiter) == 0) + this.command('list', name); } + else + return; // Work around a bug in IE6 and IE7, see #1485309 if (bw.ie6 || bw.ie7) { @@ -1395,7 +1435,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) @@ -1988,7 +2028,8 @@ if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort)) url += '&_refresh=1'; - this.select_folder(mbox); + this.select_folder(mbox, '', true); + this.unmark_folder(mbox, 'recent', '', true); this.env.mailbox = mbox; // load message list remotely @@ -2926,7 +2967,7 @@ }; // 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']"), @@ -2961,15 +3002,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 @@ -3001,6 +3055,11 @@ this.display_spellcheck_controls(false); this.plain2html($('#'+props.id).val(), props.id); tinyMCE.execCommand('mceAddControl', false, props.id); + + if (this.env.default_font) + window.setTimeout(function() { + $(tinyMCE.get(props.id).getBody()).css('font-family', rcmail.env.default_font); + }, 500); } else { var thisMCE = tinyMCE.get(props.id), existingHtml; @@ -3040,7 +3099,7 @@ if (!vis) this.stop_spellchecking(); - $(this.env.spellcheck.spell_container).css('visibility', vis ? 'visible' : 'hidden'); + $(this.env.spellcheck.spell_container)[vis ? 'show' : 'hide'](); } }; @@ -3305,10 +3364,10 @@ ts = frame_name.replace(/^rcmupload/, ''); if (this.env.loadingicon) - content = '<img src="'+this.env.loadingicon+'" alt="" />'+content; + content = '<img src="'+this.env.loadingicon+'" alt="" class="uploading" />'+content; if (this.env.cancelicon) - content = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+ts+'\', \''+frame_name+'\');" href="#cancelupload"><img src="'+this.env.cancelicon+'" alt="" /></a>'+content; - this.add2attachment_list(ts, { name:'', html:content, complete:false }); + content = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+ts+'\', \''+frame_name+'\');" href="#cancelupload" class="cancelupload"><img src="'+this.env.cancelicon+'" alt="" /></a>'+content; + this.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false }); // upload progress support if (this.env.upload_progress_time) { @@ -3328,7 +3387,7 @@ if (!this.gui_objects.attachmentlist) return false; - var indicator, li = $('<li>').attr('id', name).html(att.html); + var indicator, li = $('<li>').attr('id', name).addClass(att.classname).html(att.html); // replace indicator's li if (upload_id && (indicator = document.getElementById(upload_id))) { @@ -4023,7 +4082,7 @@ this.delete_contacts = function() { 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')))) @@ -4066,7 +4125,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]) { @@ -4082,7 +4141,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); @@ -4101,7 +4160,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)) @@ -4148,7 +4207,6 @@ yearRange: '-100:+10', showOtherMonths: true, selectOtherMonths: true, - monthNamesShort: this.env.month_names, onSelect: function(dateText) { $(this).focus().val(dateText) } }); $('input.datepicker').datepicker(); @@ -4283,7 +4341,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; @@ -4306,7 +4364,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; @@ -4338,7 +4396,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) { @@ -4571,7 +4629,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]}; @@ -4719,10 +4777,27 @@ if (!id) id = this.env.iid ? this.env.iid : selection[0]; - // append token to request - this.goto_url('delete-identity', '_iid='+id+'&_token='+this.env.request_token, true); + // submit request with appended token + if (confirm(this.get_label('deleteidentityconfirm'))) + this.goto_url('delete-identity', '_iid='+id+'&_token='+this.env.request_token, true); return true; + }; + + this.update_identity_row = function(id, name, add) + { + var row, col, list = this.identity_list, + rid = this.html_identifier(id); + + if (list.rows[rid] && (row = list.rows[rid].obj)) { + $(row.cells[0]).html(name); + } + else if (add) { + row = $('<tr>').attr('id', 'rcmrow'+rid).get(0); + col = $('<td>').addClass('mail').html(name).appendTo(row); + list.insert_row(row); + list.select(rid); + } }; @@ -5142,6 +5217,9 @@ init_button(cmd, this.buttons[cmd][i]); } } + + // set active task button + this.set_button(this.task, 'sel'); }; // set button to a specific state @@ -5296,14 +5374,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(); @@ -5396,7 +5474,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; @@ -5404,7 +5482,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'); } @@ -5413,14 +5491,26 @@ } }; + // adds a class to selected folder + this.mark_folder = function(name, class_name, prefix, encode) + { + $(this.get_folder_li(name, prefix, encode)).addClass(class_name); + }; + + // adds a class to selected folder + this.unmark_folder = function(name, class_name, prefix, encode) + { + $(this.get_folder_li(name, prefix, encode)).removeClass(class_name); + }; + // 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); } @@ -5515,18 +5605,24 @@ if (typeof content === 'object' && content.type == 'image') this.percent_indicator(this.gui_objects.quotadisplay, content); else - $(this.gui_objects.quotadisplay).html(content); + $(this.gui_objects.quotadisplay).html(content.percent+'%').attr('title', content.title); } + this.triggerEvent('setquota', content); }; // update the mailboxlist - this.set_unread_count = function(mbox, count, set_title) + this.set_unread_count = function(mbox, count, set_title, mark) { if (!this.gui_objects.mailboxlist) return false; this.env.unread_counts[mbox] = count; this.set_unread_count_display(mbox, set_title); + + if (mark) + this.mark_folder(mbox, mark, '', true); + else if (!count) + this.unmark_folder(mbox, 'recent', '', true); }; // update the mailbox count display @@ -5534,7 +5630,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'); @@ -5546,13 +5642,13 @@ 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]; } if (mycount && text_obj.length) - text_obj.html(' ('+mycount+')'); + text_obj.html(this.env.unreadwrap.replace(/%[sd]/, mycount)); else if (text_obj.length) text_obj.remove(); @@ -5583,16 +5679,6 @@ this.set_pagetitle(new_title); } - }; - - this.toggle_prefer_html = function(checkbox) - { - $('#rcmfd_addrbook_show_images').prop('disabled', !checkbox.checked); - }; - - this.toggle_preview_pane = function(checkbox) - { - $('#rcmfd_preview_pane_mark_read').prop('disabled', !checkbox.checked); }; // display fetched raw headers @@ -5712,10 +5798,13 @@ }); }; - this.plain2html = function(plainText, id) + this.plain2html = function(plain, id) { var lock = this.set_busy(true, 'converting'); - $('#'+id).val(plainText ? '<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