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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+    $('#'+id).val(plain ? '<pre>'+plain+'</pre>' : '');
+
     this.set_busy(false, null, lock);
   };
 

--
Gitblit v1.9.1