From 10e2dbbb9c49f1721b4d740bc102c10c742a7b76 Mon Sep 17 00:00:00 2001
From: thomascube <thomas@roundcube.net>
Date: Wed, 23 Nov 2011 13:53:58 -0500
Subject: [PATCH] Improve clickjacking protection: bust frame or disable all form elements and abort UI initialization

---
 program/js/app.js |   97 +++++++++++++++++++++++++++++++++---------------
 1 files changed, 66 insertions(+), 31 deletions(-)

diff --git a/program/js/app.js b/program/js/app.js
index 1be2009..cc1eeef 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');
 
@@ -144,6 +144,22 @@
     // find all registered 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();
@@ -1202,6 +1218,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         *********/
@@ -1359,31 +1393,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 +1427,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 +2020,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
@@ -4066,7 +4098,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 +4114,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 +4133,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))
@@ -4283,7 +4315,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 +4338,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 +4370,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 +4603,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]};
 
@@ -5142,6 +5174,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 +5331,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 +5431,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 +5439,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');
       }
 
@@ -5414,13 +5449,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);
     }
 
@@ -5534,7 +5569,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,7 +5581,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];
       }

--
Gitblit v1.9.1