From bd0551b22076b82a6d49e9f7a2b2e0c90a1b2326 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Fri, 05 Feb 2016 07:25:27 -0500 Subject: [PATCH] Secure also downloads of addressbook exports, managesieve script exports and Enigma keys exports --- program/js/app.js | 228 +++++++++++++++++++++++++++++++-------------------------- 1 files changed, 124 insertions(+), 104 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 46e0857..45fba7e 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -6,8 +6,8 @@ * @licstart The following is the entire license notice for the * JavaScript code in this file. * - * Copyright (C) 2005-2014, The Roundcube Dev Team - * Copyright (C) 2011-2014, Kolab Systems AG + * Copyright (C) 2005-2015, The Roundcube Dev Team + * Copyright (C) 2011-2015, Kolab Systems AG * * The JavaScript code in this page is free software: you can * redistribute it and/or modify it under the terms of the GNU @@ -77,7 +77,7 @@ }); // unload fix - $(window).bind('beforeunload', function() { ref.unload = true; }); + $(window).on('beforeunload', function() { ref.unload = true; }); // set environment variable(s) this.set_env = function(p, value) @@ -274,6 +274,23 @@ this.enable_command('compose', 'add-contact', false); parent.rcmail.show_contentframe(true); } + + // initialize drag-n-drop on attachments, so they can e.g. + // be dropped into mail compose attachments in another window + if (this.gui_objects.attachments) + $('li > a', this.gui_objects.attachments).not('.drop').on('dragstart', function(e) { + var n, href = this.href, dt = e.originalEvent.dataTransfer; + if (dt) { + // inject username to the uri + href = href.replace(/^https?:\/\//, function(m) { return m + urlencode(ref.env.username) + '@'}); + // cleanup the node to get filename without the size test + n = $(this).clone(); + n.children().remove(); + + dt.setData('roundcube-uri', href); + dt.setData('roundcube-name', $.trim(n.text())); + } + }); } else if (this.env.action == 'compose') { this.env.address_group_stack = []; @@ -304,8 +321,8 @@ if (this.gui_objects.responseslist) { $('a.insertresponse', this.gui_objects.responseslist) .attr('unselectable', 'on') - .mousedown(function(e){ return rcube_event.cancel(e); }) - .bind('mouseup keypress', function(e){ + .mousedown(function(e) { return rcube_event.cancel(e); }) + .on('mouseup keypress', function(e) { if (e.type == 'mouseup' || rcube_event.get_keycode(e) == 13) { ref.command('insert-response', $(this).attr('rel')); $(document.body).trigger('mouseup'); // hides the menu @@ -335,7 +352,7 @@ if (this.gui_objects.mailboxlist) { this.env.unread_counts = {}; this.gui_objects.folderlist = this.gui_objects.mailboxlist; - this.http_request('getunread'); + this.http_request('getunread', {_page: this.env.current_page}); } // init address book widget @@ -518,7 +535,7 @@ input_user = $('#rcmloginuser'), input_tz = $('#rcmlogintz'); - input_user.bind('keyup', function(e) { return ref.login_user_keyup(e); }); + input_user.keyup(function(e) { return ref.login_user_keyup(e); }); if (input_user.val() == '') input_user.focus(); @@ -583,17 +600,17 @@ // activate html5 file drop feature (if browser supports it and if configured) if (this.gui_objects.filedrop && this.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) { - $(document.body).bind('dragover dragleave drop', function(e){ return ref.document_drag_hover(e, e.type == 'dragover'); }); + $(document.body).on('dragover dragleave drop', function(e) { return ref.document_drag_hover(e, e.type == 'dragover'); }); $(this.gui_objects.filedrop).addClass('droptarget') - .bind('dragover dragleave', function(e){ return ref.file_drag_hover(e, e.type == 'dragover'); }) - .get(0).addEventListener('drop', function(e){ return ref.file_dropped(e); }, false); + .on('dragover dragleave', function(e) { return ref.file_drag_hover(e, e.type == 'dragover'); }) + .get(0).addEventListener('drop', function(e) { return ref.file_dropped(e); }, false); } // catch document (and iframe) mouse clicks var body_mouseup = function(e){ return ref.doc_mouse_up(e); }; $(document.body) - .bind('mouseup', body_mouseup) - .bind('keydown', function(e){ return ref.doc_keypress(e); }); + .mouseup(body_mouseup) + .keydown(function(e){ return ref.doc_keypress(e); }); $('iframe').on('load', function(e) { try { $(this.contentDocument || this.contentWindow).on('mouseup', body_mouseup); } @@ -1002,7 +1019,7 @@ break; } - this.goto_url('get', qstring+'&_download=1', false); + this.goto_url('get', qstring+'&_download=1', false, true); break; case 'select-all': @@ -1208,10 +1225,10 @@ case 'download': if (this.env.action == 'get') { - location.href = location.href.replace(/_frame=/, '_download='); + location.href = this.secure_url(location.href.replace(/_frame=/, '_download=')); } else if (uid = this.get_single_uid()) { - this.goto_url('viewsource', this.params_from_uid(uid, {_save: 1})); + this.goto_url('viewsource', this.params_from_uid(uid, {_save: 1}), false, true); } break; @@ -1299,13 +1316,13 @@ case 'export': if (this.contact_list.rowcount > 0) { - this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _search: this.env.search_request }); + this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _search: this.env.search_request }, false, true); } break; case 'export-selected': if (this.contact_list.rowcount > 0) { - this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _cid: this.contact_list.get_selection().join(',') }); + this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _cid: this.contact_list.get_selection().join(',') }, false, true); } break; @@ -1420,7 +1437,7 @@ if (task == 'mail') url += '&_mbox=INBOX'; else if (task == 'logout' && !this.env.server_error) { - url += '&_token=' + this.env.request_token; + url = this.secure_url(url); this.clear_compose_data(); } @@ -1468,6 +1485,12 @@ return url + '?' + name + '=' + value; }; + + // append CSRF protection token to the given url + this.secure_url = function(url) + { + return this.add_url(url, '_token', this.env.request_token); + }, this.is_framed = function() { @@ -2497,22 +2520,23 @@ // removes messages that doesn't exists from list selection array this.update_selection = function() { - var selected = this.message_list.selection, - rows = this.message_list.rows, + var list = this.message_list, + selected = list.selection, + rows = list.rows, i, selection = []; for (i in selected) if (rows[selected[i]]) selection.push(selected[i]); - this.message_list.selection = selection; + list.selection = selection; // reset preview frame, if currently previewed message is not selected (has been removed) try { var win = this.get_frame_window(this.env.contentframe), id = win.rcmail.env.uid; - if (id && $.inArray(id, selection) < 0) + if (id && !list.in_selection(id)) this.show_contentframe(false); } catch (e) {}; @@ -2728,8 +2752,9 @@ $('#'+r.id+' .leaf:first') .attr('id', 'rcmexpando' + r.id) .attr('class', (r.obj.style.display != 'none' ? 'expanded' : 'collapsed')) - .bind('mousedown', {uid: r.uid}, - function(e) { return ref.expand_message_row(e, e.data.uid); }); + .mousedown({uid: r.uid}, function(e) { + return ref.expand_message_row(e, e.data.uid); + }); r.unread_children = 0; roots.push(r); @@ -3374,12 +3399,12 @@ mailvelope.getKeyring(keyring).then(function(kr) { ref.mailvelope_keyring = kr; ref.mailvelope_init(action, kr); - }).catch(function(err) { + }, function(err) { // attempt to create a new keyring for this app/user mailvelope.createKeyring(keyring).then(function(kr) { ref.mailvelope_keyring = kr; ref.mailvelope_init(action, kr); - }).catch(function(err) { + }, function(err) { console.error(err); }); }); @@ -3416,8 +3441,6 @@ } else if (action == 'compose') { this.env.compose_commands.push('compose-encrypted'); - // display the toolbar button - $('#' + this.buttons['compose-encrypted'][0].id).show(); var is_html = $('input[name="_is_html"]').val() > 0; @@ -3509,7 +3532,7 @@ ref.remove_from_attachment_list(name); }); } - }).catch(function(err) { + }, function(err) { console.error(err); console.log(options); }); @@ -3632,15 +3655,15 @@ form.submit(); - }).catch(function(err) { + }, function(err) { console.log(err); }); // mailvelope_editor.encrypt() - }).catch(function(err) { + }, function(err) { console.error(err); }); // mailvelope_keyring.validKeyForAddress(senders) - }).catch(function(err) { + }, function(err) { console.error(err); }); // mailvelope_keyring.validKeyForAddress(recipients) @@ -3654,7 +3677,7 @@ $(selector).addClass('mailvelope').children().not('iframe').hide(); ref.hide_message(msgid); setTimeout(function() { $(window).resize(); }, 10); - }).catch(function(err) { + }, function(err) { console.error(err); ref.hide_message(msgid); ref.display_message('Message decryption failed: ' + err.message, 'error') @@ -3710,7 +3733,7 @@ if (missing_keys.length) { ref.display_message(ref.get_label('nopubkeyfor').replace('$email', missing_keys.join(', ')), 'warning'); } - }, function() { + }).fail(function() { console.error('Pubkey lookup failed with', arguments); ref.hide_message(lock); ref.display_message('pubkeysearcherror', 'error'); @@ -3808,7 +3831,7 @@ btn.closest('.key').fadeOut(); ref.display_message(ref.get_label('keyimportsuccess').replace('$key', $key), 'confirmation'); } - }).catch(function(err) { + }, function(err) { console.log(err); }); }); @@ -4297,7 +4320,7 @@ '<textarea name="text" id="ffresponsetext" cols="40" rows="8"></textarea></div>' + '</form>'; - buttons[this.gettext('save')] = function(e) { + buttons[this.get_label('save')] = function(e) { var name = $('#ffresponsename').val(), text = $('#ffresponsetext').val(); @@ -4313,11 +4336,11 @@ $(this).dialog('close'); }; - buttons[this.gettext('cancel')] = function() { + buttons[this.get_label('cancel')] = function() { $(this).dialog('close'); }; - this.show_popup_dialog(html, this.gettext('newresponse'), buttons, {button_classes: ['mainaction']}); + this.show_popup_dialog(html, this.get_label('newresponse'), buttons, {button_classes: ['mainaction']}); $('#ffresponsetext').val(text); $('#ffresponsename').select(); @@ -4337,10 +4360,10 @@ .attr('tabindex', '0') .html(this.quote_html(response.name)) .appendTo(li) - .mousedown(function(e){ + .mousedown(function(e) { return rcube_event.cancel(e); }) - .bind('mouseup keypress', function(e){ + .on('mouseup keypress', function(e) { if (e.type == 'mouseup' || rcube_event.get_keycode(e) == 13) { ref.command('insert-response', $(this).attr('rel')); $(document.body).trigger('mouseup'); // hides the menu @@ -4438,7 +4461,7 @@ if (!this.local_save_timer && window.localStorage && this.env.save_localstorage) { // track typing activity and only save on changes this.compose_type_activity = this.compose_type_activity_last = 0; - $(document).bind('keypress', function(e){ ref.compose_type_activity++; }); + $(document).keypress(function(e) { ref.compose_type_activity++; }); this.local_save_timer = setInterval(function(){ if (ref.compose_type_activity > ref.compose_type_activity_last) { @@ -5553,7 +5576,7 @@ // add link to pop back to parent group if (this.env.address_group_stack.length > 1) { $('<a href="#list">...</a>') - .attr('title', this.gettext('uponelevel')) + .attr('title', this.get_label('uponelevel')) .addClass('poplink') .appendTo(boxtitle) .click(function(e){ return ref.command('popgroup','',this); }); @@ -7877,9 +7900,11 @@ } }; - this.goto_url = function(action, query, lock) + this.goto_url = function(action, query, lock, secure) { - this.redirect(this.url(action, query), lock); + var url = this.url(action, query) + if (secure) url = this.secure_url(url); + this.redirect(url, lock); }; this.location_href = function(url, target, frame) @@ -7908,8 +7933,11 @@ }; // send a http request to the server - this.http_request = function(action, data, lock) + this.http_request = function(action, data, lock, type) { + if (type != 'POST') + type = 'GET'; + if (typeof data !== 'object') data = rcube_parse_query(data); @@ -7933,60 +7961,26 @@ } } - var url = this.url(action, data); - - // send request - this.log('HTTP GET: ' + url); + var url = this.url(action); // reset keep-alive interval this.start_keepalive(); + // send request return $.ajax({ - type: 'GET', url: url, dataType: 'json', + type: type, url: url, data: data, dataType: 'json', success: function(data) { ref.http_response(data); }, error: function(o, status, err) { ref.http_error(o, status, err, lock, action); } }); }; + // send a http GET request to the server + this.http_get = this.http_request; + // send a http POST request to the server this.http_post = function(action, data, lock) { - if (typeof data !== 'object') - data = rcube_parse_query(data); - - data._remote = 1; - data._unlock = lock ? lock : 0; - - // trigger plugin hook - var result = this.triggerEvent('request'+action, data); - - // abort if one of the handlers returned false - if (result === false) { - if (data._unlock) - this.set_busy(false, null, data._unlock); - return false; - } - else if (result !== undefined) { - data = result; - if (data._action) { - action = data._action; - delete data._action; - } - } - - var url = this.url(action); - - // send request - this.log('HTTP POST: ' + url); - - // reset keep-alive interval - this.start_keepalive(); - - return $.ajax({ - type: 'POST', url: url, data: data, dataType: 'json', - success: function(data){ ref.http_response(data); }, - error: function(o, status, err) { ref.http_error(o, status, err, lock, action); } - }); + return this.http_request(action, data, lock, 'POST'); }; // aborts ajax request @@ -8369,7 +8363,7 @@ } // handle upload errors by parsing iframe content in onload - frame.bind('load', {ts:ts}, onload); + frame.on('load', {ts:ts}, onload); $(form).attr({ target: frame_name, @@ -8391,7 +8385,7 @@ // html5 file-drop API this.document_drag_hover = function(e, over) { - e.preventDefault(); + // don't e.preventDefault() here to not block text dragging on the page (#1490619) $(this.gui_objects.filedrop)[(over?'addClass':'removeClass')]('active'); }; @@ -8410,15 +8404,32 @@ this.file_drag_hover(e, false); // prepare multipart form data composition - var files = e.target.files || e.dataTransfer.files, + var uri, files = e.target.files || e.dataTransfer.files, formdata = window.FormData ? new FormData() : null, fieldname = (this.env.filedrop.fieldname || '_file') + (this.env.filedrop.single ? '' : '[]'), boundary = '------multipartformboundary' + (new Date).getTime(), dashdash = '--', crlf = '\r\n', - multipart = dashdash + boundary + crlf; + multipart = dashdash + boundary + crlf, + args = {_id: this.env.compose_id || this.env.cid || '', _remote: 1, _from: this.env.action}; - if (!files || !files.length) + if (!files || !files.length) { + // Roundcube attachment, pass its uri to the backend and attach + if (uri = e.dataTransfer.getData('roundcube-uri')) { + var ts = new Date().getTime(), + // jQuery way to escape filename (#1490530) + content = $('<span>').text(e.dataTransfer.getData('roundcube-name') || this.get_label('attaching')).html(); + + args._uri = uri; + args._uploadid = ts; + + // add to attachments list + if (!this.add2attachment_list(ts, {name: '', html: content, classname: 'uploading', complete: false})) + this.file_upload_id = this.set_busy(true, 'attaching'); + + this.http_post(this.env.filedrop.action || 'upload', args); + } return; + } // inline function to submit the files to the server var submit_data = function() { @@ -8434,10 +8445,12 @@ // complete multipart content and post request multipart += dashdash + boundary + dashdash + crlf; + args._uploadid = ts; + $.ajax({ type: 'POST', dataType: 'json', - url: ref.url(ref.env.filedrop.action || 'upload', {_id: ref.env.compose_id||ref.env.cid||'', _uploadid: ts, _remote: 1, _from: ref.env.action}), + url: ref.url(ref.env.filedrop.action || 'upload', args), contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary, processData: false, timeout: 0, // disable default timeout set in ajaxSetup() @@ -8750,14 +8763,10 @@ if (!this.env.browser_capabilities) this.env.browser_capabilities = {}; - if (this.env.browser_capabilities.pdf === undefined) - this.env.browser_capabilities.pdf = this.pdf_support_check(); - - if (this.env.browser_capabilities.flash === undefined) - this.env.browser_capabilities.flash = this.flash_support_check(); - - if (this.env.browser_capabilities.tif === undefined) - this.tif_support_check(); + $.each(['pdf', 'flash', 'tif'], function() { + if (ref.env.browser_capabilities[this] === undefined) + ref.env.browser_capabilities[this] = ref[this + '_support_check'](); + }); }; // Returns browser capabilities string @@ -8776,11 +8785,14 @@ this.tif_support_check = function() { - var img = new Image(); + window.setTimeout(function() { + var img = new Image(); + img.onload = function() { ref.env.browser_capabilities.tif = 1; }; + img.onerror = function() { ref.env.browser_capabilities.tif = 0; }; + img.src = ref.assets_path('program/resources/blank.tif'); + }, 10); - img.onload = function() { ref.env.browser_capabilities.tif = 1; }; - img.onerror = function() { ref.env.browser_capabilities.tif = 0; }; - img.src = this.assets_path('program/resources/blank.tif'); + return 0; }; this.pdf_support_check = function() @@ -8816,6 +8828,14 @@ return 1; } + window.setTimeout(function() { + $('<object>').css({position: 'absolute', left: '-10000px'}) + .attr({data: ref.assets_path('program/resources/dummy.pdf'), width: 1, height: 1, type: 'application/pdf'}) + .load(function() { ref.env.browser_capabilities.pdf = 1; }) + .error(function() { ref.env.browser_capabilities.pdf = 0; }) + .appendTo($('body')); + }, 10); + return 0; }; -- Gitblit v1.9.1