Aleksander Machniak
2015-09-20 82dcbb7488ce1625ba4f41fbdc8e6319d3da9691
program/js/app.js
@@ -156,8 +156,8 @@
    var n;
    this.task = this.env.task;
    // check browser
    if (this.env.server_error != 409 && (!bw.dom || !bw.xmlhttp_test() || (bw.mz && bw.vendver < 1.9) || (bw.ie && bw.vendver < 7))) {
    // check browser capabilities (never use version checks here)
    if (this.env.server_error != 409 && (!bw.dom || !bw.xmlhttp_test())) {
      this.goto_url('error', '_code=0x199');
      return;
    }
@@ -325,7 +325,9 @@
        else if (this.env.action == 'get')
          this.enable_command('download', 'print', true);
        // show printing dialog
        else if (this.env.action == 'print' && this.env.uid) {
        else if (this.env.action == 'print' && this.env.uid
          && !this.env.is_pgp_content && !this.env.pgp_mime_part
        ) {
          this.print_dialog();
        }
@@ -375,6 +377,8 @@
          }
          this.http_post(postact, postdata);
        }
        this.check_mailvelope(this.env.action);
        // detect browser capabilities
        if (!this.is_framed() && !this.env.extwin)
@@ -510,8 +514,11 @@
        break;
      case 'login':
        var input_user = $('#rcmloginuser');
        input_user.bind('keyup', function(e){ return ref.login_user_keyup(e); });
        var tz, tz_name, jstz = window.jstz,
            input_user = $('#rcmloginuser'),
            input_tz = $('#rcmlogintz');
        input_user.bind('keyup', function(e) { return ref.login_user_keyup(e); });
        if (input_user.val() == '')
          input_user.focus();
@@ -519,14 +526,10 @@
          $('#rcmloginpwd').focus();
        // detect client timezone
        if (window.jstz) {
          var timezone = jstz.determine();
          if (timezone.name())
            $('#rcmlogintz').val(timezone.name());
        }
        else {
          $('#rcmlogintz').val(new Date().getStdTimezoneOffset() / -60);
        }
        if (jstz && (tz = jstz.determine()))
          tz_name = tz.name();
        input_tz.val(tz_name ? tz_name : (new Date().getStdTimezoneOffset() / -60));
        // display 'loading' message on form submit, lock submit button
        $('form').submit(function () {
@@ -592,7 +595,7 @@
      .bind('mouseup', body_mouseup)
      .bind('keydown', function(e){ return ref.doc_keypress(e); });
    $('iframe').load(function(e) {
    $('iframe').on('load', function(e) {
        try { $(this.contentDocument || this.contentWindow).on('mouseup', body_mouseup);  }
        catch (e) {/* catch possible "Permission denied" error in IE */ }
      })
@@ -653,7 +656,9 @@
    }
    // check input before leaving compose step
    if (this.task == 'mail' && this.env.action == 'compose' && $.inArray(command, this.env.compose_commands) < 0 && !this.env.server_error) {
    if (this.task == 'mail' && this.env.action == 'compose' && !this.env.server_error && command != 'save-pref'
      && $.inArray(command, this.env.compose_commands) < 0
    ) {
      if (!this.env.is_sent && this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
        return false;
@@ -762,7 +767,7 @@
      case 'open':
        if (uid = this.get_single_uid()) {
          obj.href = this.url('show', {_mbox: this.get_message_mailbox(uid), _uid: uid});
          obj.href = this.url('show', this.params_from_uid(uid));
          return true;
        }
        break;
@@ -1188,8 +1193,8 @@
          this.gui_objects.messagepartframe.contentWindow.print();
        }
        else if (uid = this.get_single_uid()) {
          url = '&_action=print&_uid='+uid+'&_mbox='+urlencode(this.get_message_mailbox(uid))+(this.env.safemode ? '&_safe=1' : '');
          if (this.open_window(this.env.comm_path + url, true, true)) {
          url = this.url('print', this.params_from_uid(uid, {_safe: this.env.safemode ? 1 : 0}));
          if (this.open_window(url, true, true)) {
            if (this.env.action != 'show')
              this.mark_message('read', uid);
          }
@@ -1198,7 +1203,7 @@
      case 'viewsource':
        if (uid = this.get_single_uid())
          this.open_window(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true, true);
          this.open_window(this.url('viewsource', this.params_from_uid(uid)), true, true);
        break;
      case 'download':
@@ -1206,7 +1211,7 @@
          location.href = location.href.replace(/_frame=/, '_download=');
        }
        else if (uid = this.get_single_uid()) {
          this.goto_url('viewsource', { _uid: uid, _mbox: this.get_message_mailbox(uid), _save: 1 });
          this.goto_url('viewsource', this.params_from_uid(uid, {_save: 1}));
        }
        break;
@@ -1268,7 +1273,7 @@
        $('input[name="_unlock"]', form).val(importlock);
        if (!(flag = this.upload_file(form, 'import'))) {
        if (!(flag = this.upload_file(form, 'import', importlock))) {
          this.set_busy(false, null, importlock);
          if (flag !== false)
            alert(this.get_label('selectimportfile'));
@@ -1615,8 +1620,8 @@
      // select the folder if one of its childs is currently selected
      // don't select if it's virtual (#1488346)
      if (!node.virtual && this.env.mailbox && this.env.mailbox.startsWith(name + this.env.delimiter))
        this.command('list', name);
      if (!node.virtual && this.env.mailbox && this.env.mailbox.startsWith(node.id + this.env.delimiter))
        this.command('list', node.id);
    }
    else {
      var reg = new RegExp('&'+urlencode(node.id)+'&');
@@ -2173,10 +2178,16 @@
  this.set_list_sorting = function(sort_col, sort_order)
  {
    var sort_old = this.env.sort_col == 'arrival' ? 'date' : this.env.sort_col,
      sort_new = sort_col == 'arrival' ? 'date' : sort_col;
    // set table header class
    $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase()));
    if (sort_col)
      $('#rcm'+sort_col).addClass('sorted'+sort_order);
    $('#rcm' + sort_old).removeClass('sorted' + this.env.sort_order.toUpperCase());
    if (sort_new)
      $('#rcm' + sort_new).addClass('sorted' + sort_order);
    // if sorting by 'arrival' is selected, click on date column should not switch to 'date'
    $('#rcmdate > a').prop('rel', sort_col == 'arrival' ? 'arrival' : 'date');
    this.env.sort_col = sort_col;
    this.env.sort_order = sort_order;
@@ -2233,35 +2244,33 @@
      return;
    var win, target = window,
      action = preview ? 'preview': 'show',
      url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.get_message_mailbox(id));
      url = this.params_from_uid(id, {_caps: this.browser_capabilities()});
    if (preview && (win = this.get_frame_window(this.env.contentframe))) {
      target = win;
      url += '&_framed=1';
      url._framed = 1;
    }
    if (safe)
      url += '&_safe=1';
      url._safe = 1;
    // also send search request to get the right messages
    if (this.env.search_request)
      url += '&_search='+this.env.search_request;
    // add browser capabilities, so we can properly handle attachments
    url += '&_caps='+urlencode(this.browser_capabilities());
      url._search = this.env.search_request;
    if (this.env.extwin)
      url += '&_extwin=1';
      url._extwin = 1;
    url = this.url(preview ? 'preview': 'show', url);
    if (preview && String(target.location.href).indexOf(url) >= 0) {
      this.show_contentframe(true);
    }
    else {
      if (!preview && this.env.message_extwin && !this.env.extwin)
        this.open_window(this.env.comm_path+url, true);
        this.open_window(url, true);
      else
        this.location_href(this.env.comm_path+url, target, true);
        this.location_href(url, target, true);
      // mark as read and change mbox unread counter
      if (preview && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read > 0) {
@@ -3287,27 +3296,24 @@
        $(this).keydown();
      })
      // keyboard navigation
      .on('keydown keyup', function(e) {
      .on('keydown keyup click', function(e) {
        var current, selector = $('#pagejump-selector'),
          ul = $('ul', selector),
          list = $('li', ul),
          height = ul.height(),
          p = parseInt(this.value);
        if (e.which != 27 && e.which != 9 && e.which != 13 && !selector.is(':visible'))
          return ref.show_menu('pagejump-selector', true, e);
        if (e.type == 'keydown') {
          // arrow-down
          if (e.which == 40) {
            if (!selector.is(':visible'))
              return ref.show_menu('pagejump-selector', true, e);
            if (list.length > p)
              this.value = (p += 1);
          }
          // arrow-up
          else if (e.which == 38) {
            if (!selector.is(':visible'))
              return ref.show_menu('pagejump-selector', true, e);
            if (p > 1 && list.length > p - 1)
              this.value = (p -= 1);
          }
@@ -3343,6 +3349,473 @@
  {
    $('input.rcpagejumper').val(this.env.current_page).prop('disabled', this.env.pagecount < 2);
  };
  // check for mailvelope API
  this.check_mailvelope = function(action)
  {
    if (typeof window.mailvelope !== 'undefined') {
      this.mailvelope_load(action);
    }
    else {
      $(window).on('mailvelope', function() {
        ref.mailvelope_load(action);
      });
    }
  };
  // Load Mailvelope functionality (and initialize keyring if needed)
  this.mailvelope_load = function(action)
  {
    if (this.env.browser_capabilities)
      this.env.browser_capabilities['pgpmime'] = 1;
    var keyring = this.env.user_id;
    mailvelope.getKeyring(keyring).then(function(kr) {
      ref.mailvelope_keyring = kr;
      ref.mailvelope_init(action, kr);
    }).catch(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) {
        console.error(err);
      });
    });
  };
  // Initializes Mailvelope editor or display container
  this.mailvelope_init = function(action, keyring)
  {
    if (!window.mailvelope)
      return;
    if (action == 'show' || action == 'preview' || action == 'print') {
      // decrypt text body
      if (this.env.is_pgp_content) {
        var data = $(this.env.is_pgp_content).text();
        ref.mailvelope_display_container(this.env.is_pgp_content, data, keyring);
      }
      // load pgp/mime message and pass it to the mailvelope display container
      else if (this.env.pgp_mime_part) {
        var msgid = this.display_message(this.get_label('loadingdata'), 'loading'),
          selector = this.env.pgp_mime_container;
        $.ajax({
          type: 'GET',
          url: this.url('get', { '_mbox': this.env.mailbox, '_uid': this.env.uid, '_part': this.env.pgp_mime_part }),
          error: function(o, status, err) {
            ref.http_error(o, status, err, msgid);
          },
          success: function(data) {
            ref.mailvelope_display_container(selector, data, keyring, msgid);
          }
        });
      }
    }
    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;
      if (this.env.pgp_mime_message) {
        // fetch PGP/Mime part and open load into Mailvelope editor
        var lock = this.set_busy(true, this.get_label('loadingdata'));
        $.ajax({
          type: 'GET',
          url: this.url('get', this.env.pgp_mime_message),
          error: function(o, status, err) {
            ref.http_error(o, status, err, lock);
            ref.enable_command('compose-encrypted', !is_html);
          },
          success: function(data) {
            ref.set_busy(false, null, lock);
            if (is_html) {
              ref.command('toggle-editor', {html: false, noconvert: true});
              $('#' + ref.env.composebody).val('');
            }
            ref.compose_encrypted({ quotedMail: data });
            ref.enable_command('compose-encrypted', true);
          }
        });
      }
      else {
        // enable encrypted compose toggle
        this.enable_command('compose-encrypted', !is_html);
      }
    }
  };
  // handler for the 'compose-encrypted' command
  this.compose_encrypted = function(props)
  {
    var options, container = $('#' + this.env.composebody).parent();
    // remove Mailvelope editor if active
    if (ref.mailvelope_editor) {
      ref.mailvelope_editor = null;
      ref.compose_skip_unsavedcheck = false;
      ref.set_button('compose-encrypted', 'act');
      container.removeClass('mailvelope')
        .find('iframe:not([aria-hidden=true])').remove();
      $('#' + ref.env.composebody).show();
      $("[name='_pgpmime']").remove();
      // disable commands that operate on the compose body
      ref.enable_command('spellcheck', 'insert-sig', 'toggle-editor', 'insert-response', 'save-response', true);
      ref.triggerEvent('compose-encrypted', { active:false });
    }
    // embed Mailvelope editor container
    else {
      if (this.spellcheck_state())
        this.editor.spellcheck_stop();
      if (props.quotedMail) {
        options = { quotedMail: props.quotedMail, quotedMailIndent: false };
      }
      else {
        options = { predefinedText: $('#' + this.env.composebody).val() };
      }
      if (this.env.compose_mode == 'reply') {
        options.quotedMailIndent = true;
        options.quotedMailHeader = this.env.compose_reply_header;
      }
      mailvelope.createEditorContainer('#' + container.attr('id'), ref.mailvelope_keyring, options).then(function(editor) {
        ref.mailvelope_editor = editor;
        ref.compose_skip_unsavedcheck = true;
        ref.set_button('compose-encrypted', 'sel');
        container.addClass('mailvelope');
        $('#' + ref.env.composebody).hide();
        // disable commands that operate on the compose body
        ref.enable_command('spellcheck', 'insert-sig', 'toggle-editor', 'insert-response', 'save-response', false);
        ref.triggerEvent('compose-encrypted', { active:true });
        // notify user about loosing attachments
        if (ref.env.attachments && !$.isEmptyObject(ref.env.attachments)) {
          alert(ref.get_label('encryptnoattachments'));
          $.each(ref.env.attachments, function(name, attach) {
            ref.remove_from_attachment_list(name);
          });
        }
      }).catch(function(err) {
        console.error(err);
        console.log(options);
      });
    }
  };
  // callback to replace the message body with the full armored
  this.mailvelope_submit_messageform = function(draft, saveonly)
  {
    // get recipients
    var recipients = [];
    $.each(['to', 'cc', 'bcc'], function(i,field) {
      var pos, rcpt, val = $.trim($('[name="_' + field + '"]').val());
      while (val.length && rcube_check_email(val, true)) {
        rcpt = RegExp.$2;
        recipients.push(rcpt);
        val = val.substr(val.indexOf(rcpt) + rcpt.length + 1).replace(/^\s*,\s*/, '');
      }
    });
    // check if we have keys for all recipients
    var isvalid = recipients.length > 0;
    ref.mailvelope_keyring.validKeyForAddress(recipients).then(function(status) {
      var missing_keys = [];
      $.each(status, function(k,v) {
        if (v === false) {
          isvalid = false;
          missing_keys.push(k);
        }
      });
      // list recipients with missing keys
      if (!isvalid && missing_keys.length) {
        // load publickey.js
        if (!$('script#publickeyjs').length) {
          $('<script>')
            .attr('id', 'publickeyjs')
            .attr('src', ref.assets_path('program/js/publickey.js'))
            .appendTo(document.body);
        }
        // display dialog with missing keys
        ref.show_popup_dialog(
          ref.get_label('nopubkeyfor').replace('$email', missing_keys.join(', ')) +
          '<p>' + ref.get_label('searchpubkeyservers') + '</p>',
          ref.get_label('encryptedsendialog'),
          [{
            text: ref.get_label('search'),
            'class': 'mainaction',
            click: function() {
              var $dialog = $(this);
              ref.mailvelope_search_pubkeys(missing_keys, function() {
                $dialog.dialog('close')
              });
            }
          },
          {
            text: ref.get_label('cancel'),
            click: function(){
              $(this).dialog('close');
            }
          }]
        );
        return false;
      }
      if (!isvalid) {
        if (!recipients.length) {
          alert(ref.get_label('norecipientwarning'));
          $("[name='_to']").focus();
        }
        return false;
      }
      // add sender identity to recipients to be able to decrypt our very own message
      var senders = [], selected_sender = ref.env.identities[$("[name='_from'] option:selected").val()];
      $.each(ref.env.identities, function(k, sender) {
        senders.push(sender.email);
      });
      ref.mailvelope_keyring.validKeyForAddress(senders).then(function(status) {
        valid_sender = null;
        $.each(status, function(k,v) {
          if (v !== false) {
            valid_sender = k;
            if (valid_sender == selected_sender) {
              return false;  // break
            }
          }
        });
        if (!valid_sender) {
          if (!confirm(ref.get_label('nopubkeyforsender'))) {
            return false;
          }
        }
        recipients.push(valid_sender);
        ref.mailvelope_editor.encrypt(recipients).then(function(armored) {
          // all checks passed, send message
          var form = ref.gui_objects.messageform,
            hidden = $("[name='_pgpmime']", form),
            msgid = ref.set_busy(true, draft || saveonly ? 'savingmessage' : 'sendingmessage')
          form.target = 'savetarget';
          form._draft.value = draft ? '1' : '';
          form.action = ref.add_url(form.action, '_unlock', msgid);
          form.action = ref.add_url(form.action, '_framed', 1);
          if (saveonly) {
            form.action = ref.add_url(form.action, '_saveonly', 1);
          }
          // send pgp conent via hidden field
          if (!hidden.length) {
            hidden = $('<input type="hidden" name="_pgpmime">').appendTo(form);
          }
          hidden.val(armored);
          form.submit();
        }).catch(function(err) {
          console.log(err);
        });  // mailvelope_editor.encrypt()
      }).catch(function(err) {
        console.error(err);
      });  // mailvelope_keyring.validKeyForAddress(senders)
    }).catch(function(err) {
      console.error(err);
    });  // mailvelope_keyring.validKeyForAddress(recipients)
    return false;
  };
  // wrapper for the mailvelope.createDisplayContainer API call
  this.mailvelope_display_container = function(selector, data, keyring, msgid)
  {
    mailvelope.createDisplayContainer(selector, data, keyring, { showExternalContent: this.env.safemode }).then(function() {
      $(selector).addClass('mailvelope').children().not('iframe').hide();
      ref.hide_message(msgid);
      setTimeout(function() { $(window).resize(); }, 10);
    }).catch(function(err) {
      console.error(err);
      ref.hide_message(msgid);
      ref.display_message('Message decryption failed: ' + err.message, 'error')
    });
  };
  // subroutine to query keyservers for public keys
  this.mailvelope_search_pubkeys = function(emails, resolve)
  {
    // query with publickey.js
    var deferreds = [],
      pk = new PublicKey(),
      lock = ref.display_message(ref.get_label('loading'), 'loading');
    $.each(emails, function(i, email) {
      var d = $.Deferred();
      pk.search(email, function(results, errorCode) {
        if (errorCode !== null) {
          // rejecting would make all fail
          // d.reject(email);
          d.resolve([email]);
        }
        else {
          d.resolve([email].concat(results));
        }
      });
      deferreds.push(d);
    });
    $.when.apply($, deferreds).then(function() {
      var missing_keys = [],
        key_selection = [];
      // alanyze results of all queries
      $.each(arguments, function(i, result) {
        var email = result.shift();
        if (!result.length) {
          missing_keys.push(email);
        }
        else {
          key_selection = key_selection.concat(result);
        }
      });
      ref.hide_message(lock);
      resolve(true);
      // show key import dialog
      if (key_selection.length) {
        ref.mailvelope_key_import_dialog(key_selection);
      }
      // some keys could not be found
      if (missing_keys.length) {
        ref.display_message(ref.get_label('nopubkeyfor').replace('$email', missing_keys.join(', ')), 'warning');
      }
    }, function() {
      console.error('Pubkey lookup failed with', arguments);
      ref.hide_message(lock);
      ref.display_message('pubkeysearcherror', 'error');
      resolve(false);
    });
  };
  // list the given public keys in a dialog with options to import
  // them into the local Maivelope keyring
  this.mailvelope_key_import_dialog = function(candidates)
  {
    var ul = $('<div>').addClass('listing mailvelopekeyimport');
    $.each(candidates, function(i, keyrec) {
      var li = $('<div>').addClass('key');
      if (keyrec.revoked)  li.addClass('revoked');
      if (keyrec.disabled) li.addClass('disabled');
      if (keyrec.expired)  li.addClass('expired');
      li.append($('<label>').addClass('keyid').text(ref.get_label('keyid')));
      li.append($('<a>').text(keyrec.keyid.substr(-8).toUpperCase())
        .attr('href', keyrec.info)
        .attr('target', '_blank')
        .attr('tabindex', '-1'));
      li.append($('<label>').addClass('keylen').text(ref.get_label('keylength')));
      li.append($('<span>').text(keyrec.keylen));
      if (keyrec.expirationdate) {
        li.append($('<label>').addClass('keyexpired').text(ref.get_label('keyexpired')));
        li.append($('<span>').text(new Date(keyrec.expirationdate * 1000).toDateString()));
      }
      if (keyrec.revoked) {
        li.append($('<span>').addClass('keyrevoked').text(ref.get_label('keyrevoked')));
      }
      var ul_ = $('<ul>').addClass('uids');
      $.each(keyrec.uids, function(j, uid) {
        var li_ = $('<li>').addClass('uid');
        if (uid.revoked)  li_.addClass('revoked');
        if (uid.disabled) li_.addClass('disabled');
        if (uid.expired)  li_.addClass('expired');
        ul_.append(li_.text(uid.uid));
      });
      li.append(ul_);
      li.append($('<input>')
        .attr('type', 'button')
        .attr('rel', keyrec.keyid)
        .attr('value', ref.get_label('import'))
        .addClass('button importkey')
        .prop('disabled', keyrec.revoked || keyrec.disabled || keyrec.expired));
      ul.append(li);
    });
    // display dialog with missing keys
    ref.show_popup_dialog(
      $('<div>')
        .append($('<p>').html(ref.get_label('encryptpubkeysfound')))
        .append(ul),
      ref.get_label('importpubkeys'),
      [{
        text: ref.get_label('close'),
        click: function(){
          $(this).dialog('close');
        }
      }]
    );
    // delegate handler for import button clicks
    ul.on('click', 'input.button.importkey', function() {
      var btn = $(this),
        keyid = btn.attr('rel'),
        pk = new PublicKey(),
        lock = ref.display_message(ref.get_label('loading'), 'loading');
        // fetch from keyserver and import to Mailvelope keyring
        pk.get(keyid, function(armored, errorCode) {
          ref.hide_message(lock);
          if (errorCode) {
            ref.display_message(ref.get_label('keyservererror'), 'error');
            return;
          }
          // import to keyring
          ref.mailvelope_keyring.importPublicKey(armored).then(function(status) {
            if (status === 'REJECTED') {
              // alert(ref.get_label('Key import was rejected'));
            }
            else {
              var $key = keyid.substr(-8).toUpperCase();
              btn.closest('.key').fadeOut();
              ref.display_message(ref.get_label('keyimportsuccess').replace('$key', $key), 'confirmation');
            }
          }).catch(function(err) {
            console.log(err);
          });
        });
    });
  };
  /*********************************************************/
  /*********       mailbox folders methods         *********/
@@ -3615,6 +4088,11 @@
      );
    }
    // delegate sending to Mailvelope routine
    if (this.mailvelope_editor) {
      return this.mailvelope_submit_messageform(draft, saveonly);
    }
    // all checks passed, send message
    var msgid = this.set_busy(true, draft || saveonly ? 'savingmessage' : 'sendingmessage'),
      lang = this.spellcheck_lang(),
@@ -3684,7 +4162,7 @@
      var oldval = input.val(), rx = new RegExp(RegExp.escape(delim) + '\\s*$');
      if (oldval && !rx.test(oldval))
        oldval += delim + ' ';
      input.val(oldval + recipients.join(delim + ' ') + delim + ' ');
      input.val(oldval + recipients.join(delim + ' ') + delim + ' ').change();
      this.triggerEvent('add-recipient', { field:field, recipients:recipients });
    }
@@ -3788,6 +4266,8 @@
    if (result) {
      // update internal format flag
      $("input[name='_is_html']").val(props.html ? 1 : 0);
      // enable encrypted compose toggle
      this.enable_command('compose-encrypted', !props.html);
    }
    return result;
@@ -3932,7 +4412,7 @@
      // reset history of hidden iframe used for saving draft (#1489643)
      // but don't do this on timer-triggered draft-autosaving (#1489789)
      if (window.frames['savetarget'] && window.frames['savetarget'].history && !this.draft_autosave_submit) {
      if (window.frames['savetarget'] && window.frames['savetarget'].history && !this.draft_autosave_submit && !this.mailvelope_editor) {
        window.frames['savetarget'].history.back();
      }
@@ -3967,7 +4447,7 @@
        }
      }, 5000);
      $(window).unload(function() {
      $(window).on('unload', function() {
        // remove copy from local storage if compose screen is left after warning
        if (!ref.env.server_error)
          ref.remove_compose_data(ref.env.compose_id);
@@ -4001,6 +4481,11 @@
    if (this.env.attachments)
      for (id in this.env.attachments)
        str += id;
    // we can't detect changes in the Mailvelope editor so assume it changed
    if (this.mailvelope_editor) {
      str += ';' + new Date().getTime();
    }
    if (save)
      this.cmp_hash = str;
@@ -4182,7 +4667,7 @@
  };
  // upload (attachment) file
  this.upload_file = function(form, action)
  this.upload_file = function(form, action, lock)
  {
    if (!form)
      return;
@@ -4224,6 +4709,9 @@
          if (!content.match(/display_message/))
            ref.display_message(ref.get_label('fileuploaderror'), 'error');
          ref.remove_from_attachment_list(e.data.ts);
          if (lock)
            ref.set_busy(false, null, lock);
        }
        // Opera hack: handle double onload
        if (bw.opera)
@@ -7151,7 +7639,7 @@
    if (show) {
      // truncate stack down to the one containing the ref link
      for (var i = this.menu_stack.length - 1; stack && i >= 0; i--) {
        if (!$(ref).parents('#'+this.menu_stack[i]).length)
        if (!$(ref).parents('#'+this.menu_stack[i]).length && $(event.target).parent().attr('role') != 'menuitem')
          this.hide_menu(this.menu_stack[i], event);
      }
      if (stack && this.menu_stack.length) {
@@ -7614,7 +8102,7 @@
              }
              this.enable_command('set-listmode', this.env.threads && !is_multifolder);
              if (list.rowcount > 0)
              if (list.rowcount > 0 && !$(document.activeElement).is('input,textarea'))
                list.focus();
              this.msglist_select(list);
            }
@@ -7630,7 +8118,7 @@
            this.enable_command('search-create', this.env.source == '');
            this.enable_command('search-delete', this.env.search_id);
            this.update_group_commands();
            if (this.contact_list.rowcount > 0)
            if (this.contact_list.rowcount > 0 && !$(document.activeElement).is('input,textarea'))
              this.contact_list.focus();
            this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
          }
@@ -7916,7 +8404,8 @@
    var submit_data = function() {
      var multiple = files.length > 1,
        ts = new Date().getTime(),
        content = '<span>' + (multiple ? ref.get_label('uploadingmany') : files[0].name) + '</span>';
        // jQuery way to escape filename (#1490530)
        content = $('<span>').text(multiple ? ref.get_label('uploadingmany') : files[0].name).html();
      // add to attachments list
      if (!ref.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false }))
@@ -8130,10 +8619,22 @@
  // get the IMP mailbox of the message with the given UID
  this.get_message_mailbox = function(uid)
  {
    var msg = this.env.messages ? this.env.messages[uid] : {};
    var msg = (this.env.messages && uid ? this.env.messages[uid] : null) || {};
    return msg.mbox || this.env.mailbox;
  };
  // build request parameters from single message id (maybe with mailbox name)
  this.params_from_uid = function(uid, params)
  {
    if (!params)
      params = {};
    params._uid = String(uid).split('-')[0];
    params._mbox = this.get_message_mailbox(uid);
    return params;
  };
  // gets cursor position
  this.get_caret_pos = function(obj)
  {