Bug 495318 - Implement LIST-EXTENDED (RFC 5258) in impad.js. r=bienvenu
authorDavid Lechner <david@lechnology.com>
Tue, 09 Oct 2012 10:37:11 -0500
changeset 14242 f3c76cb43c7754b5574a309dd39f01251c9ac5d9
parent 14241 4991059f9bbbef2ffc8d58dc8897749a41024555
child 14243 ae19df26b793199c6d7c419d673ed5c27b5f5496
push id804
push userbugzilla@standard8.plus.com
push dateMon, 07 Jan 2013 21:31:35 +0000
treeherdercomm-beta@9fdc5c67cdb1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbienvenu
bugs495318
Bug 495318 - Implement LIST-EXTENDED (RFC 5258) in impad.js. r=bienvenu
mailnews/base/public/nsMsgFolderFlags.idl
mailnews/base/test/unit/test_testsuite_fakeserver_imapd_gmail.js
mailnews/base/test/unit/test_testsuite_fakeserver_imapd_list-extended.js
mailnews/base/test/unit/xpcshell.ini
mailnews/imap/test/unit/test_gmailAttributes.js
mailnews/imap/test/unit/test_gmailOfflineMsgStore.js
mailnews/imap/test/unit/test_listSubscribed.js
mailnews/imap/test/unit/xpcshell.ini
mailnews/test/fakeserver/imapd.js
mailnews/test/resources/IMAPpump.js
--- a/mailnews/base/public/nsMsgFolderFlags.idl
+++ b/mailnews/base/public/nsMsgFolderFlags.idl
@@ -53,17 +53,17 @@ interface nsMsgFolderFlags {
    *  not have the nsMsgFolderFlags::Directory or
    *  nsMsgFolderFlags::Elided bits set.
    *  @{
    */
   /// Whether this is the trash folder.
   const nsMsgFolderFlagType Trash           = 0x00000100;
   /// Whether this is a folder that sent mail gets delivered to.
   const nsMsgFolderFlagType SentMail        = 0x00000200;
-  /// Whether this is the folder in which unfinised, unsent messages are saved for later editing.
+  /// Whether this is the folder in which unfinished, unsent messages are saved for later editing.
   const nsMsgFolderFlagType Drafts          = 0x00000400;
   /// Whether this is the folder in which messages are queued for later delivery.
   const nsMsgFolderFlagType Queue           = 0x00000800;
   /// Whether this is the primary inbox folder.
   const nsMsgFolderFlagType Inbox           = 0x00001000;
   /// Whether this folder on online IMAP
   const nsMsgFolderFlagType ImapBox         = 0x00002000;
   /// Whether this is an archive folder
new file mode 100644
--- /dev/null
+++ b/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_gmail.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that imapd.js fakeserver correctly emulates GMail server
+// That means X-GM-EXT-1 capability and GMail flavor XLIST
+// per https://developers.google.com/google-apps/gmail/imap_extensions
+
+// async support
+load("../../../resources/logHelper.js");
+load("../../../resources/mailTestUtils.js");
+load("../../../resources/asyncTestUtils.js");
+load("../../../resources/alertTestUtils.js");
+
+// IMAP pump
+load("../../../resources/IMAPpump.js");
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+
+setupIMAPPump("GMail");
+// create our own hander so that we can call imapd functions directly
+var handler;
+
+// Definition of tests
+var tests = [
+  setupMailboxes,
+  testXlist,
+  endTest
+]
+
+// mbox mailboxes cannot contain both child mailboxes and messages, so this will
+// be one test case.
+function setupMailboxes()
+{
+  gIMAPMailbox.specialUseFlag = "\\Inbox";
+  gIMAPDaemon.createMailbox("[Gmail]", {flags : ["\\Noselect"]});
+  gIMAPDaemon.createMailbox("[Gmail]/All Mail", {specialUseFlag : "\\AllMail"});
+  gIMAPDaemon.createMailbox("[Gmail]/Drafts", {specialUseFlag : "\\Drafts"});
+  gIMAPDaemon.createMailbox("[Gmail]/Sent", {specialUseFlag : "\\Sent"});
+  gIMAPDaemon.createMailbox("[Gmail]/Spam", {specialUseFlag : "\\Spam"});
+  gIMAPDaemon.createMailbox("[Gmail]/Starred", {specialUseFlag : "\\Starred"});
+  gIMAPDaemon.createMailbox("[Gmail]/Trash", {specialUseFlag : "\\Trash"});
+  gIMAPDaemon.createMailbox("test", {});
+
+  handler = gIMAPServer._readers[0]._handler;
+
+  // wait for imap pump to do its thing or else we get memory leaks
+  gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
+  yield false;
+}
+
+// test that 'XLIST "" "*"' returns the proper responses
+function testXlist()
+{
+  let response = handler.onError('2', 'XLIST "" "*"');
+dump("\n\n\n" + response + "\n\n\n");
+  do_check_true(response.indexOf('* LIST (\\HasNoChildren \\Inbox) "/" "INBOX"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\Noselect \\HasChildren) "/" "[Gmail]"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\HasNoChildren \\AllMail) "/" "[Gmail]/All Mail"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\HasNoChildren \\Drafts) "/" "[Gmail]/Drafts"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\HasNoChildren \\Sent) "/" "[Gmail]/Sent"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\HasNoChildren \\Spam) "/" "[Gmail]/Spam"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\HasNoChildren \\Starred) "/" "[Gmail]/Starred"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\HasNoChildren \\Trash) "/" "[Gmail]/Trash"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\HasNoChildren) "/" "test"') >= 0);
+
+  yield true;
+}
+
+// Cleanup at end
+function endTest()
+{
+  teardownIMAPPump();
+}
+
+function run_test()
+{
+  Services.prefs.setBoolPref("mail.server.server1.autosync_offline_stores", false);
+  async_run_tests(tests);
+}
+
+/*
+ * helper functions
+ */
+
+function recursiveDeleteMailboxes(aMailbox)
+{
+  for each (var child in aMailbox.allChildren) {
+    recursiveDeleteMailboxes(child);
+  }
+  gIMAPDaemon.deleteMailbox(aMailbox);
+}
new file mode 100644
--- /dev/null
+++ b/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_list-extended.js
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that imapd.js fakeserver correctly implements LIST-EXTENDED imap
+// extension (RFC 5258 - http://tools.ietf.org/html/rfc5258)
+
+// async support
+load("../../../resources/logHelper.js");
+load("../../../resources/mailTestUtils.js");
+load("../../../resources/asyncTestUtils.js");
+load("../../../resources/alertTestUtils.js");
+
+// IMAP pump
+load("../../../resources/IMAPpump.js");
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// Globals
+
+
+// Dovecot is one of the servers that supports LIST-EXTENDED
+setupIMAPPump("Dovecot");
+// create our own hander so that we can call imapd functions directly
+var handler;
+
+// Definition of tests
+var tests = [
+  setupMailboxes,
+  testList,
+  testListSelectSubscribed,
+  testListReturnChilderen,
+  testListReturnSubscribed,
+  testListSelectMultiple,
+  endTest
+]
+
+// mbox mailboxes cannot contain both child mailboxes and messages, so this will
+// be one test case.
+function setupMailboxes()
+{
+  gIMAPMailbox.flags = ["\\Marked", "\\NoInferiors"];
+  gIMAPMailbox.subscribed = true;
+  gIMAPDaemon.createMailbox("Fruit", {});
+  gIMAPDaemon.createMailbox("Fruit/Apple", {});
+  gIMAPDaemon.createMailbox("Fruit/Banana", {subscribed : true});
+  gIMAPDaemon.createMailbox("Fruit/Peach", {nonExistent : true,
+                                            subscribed : true});
+  gIMAPDaemon.createMailbox("Tofu", {});
+  gIMAPDaemon.createMailbox("Vegetable", {subscribed : true});
+  gIMAPDaemon.createMailbox("Vegetable/Broccoli", {subscribed : true});
+  gIMAPDaemon.createMailbox("Vegetable/Corn", {});
+
+  handler = gIMAPServer._readers[0]._handler;
+
+  // wait for imap pump to do it's thing or else we get memory leaks
+  gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
+  yield false;
+}
+
+// test that 'LIST "" "*"' returns the proper responses (standard LIST usage)
+function testList()
+{
+  let response = handler.onError('2', 'LIST "" "*"');
+
+  do_check_true(response.indexOf('* LIST (\\Marked \\NoInferiors) "/" "INBOX"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Fruit"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Fruit/Apple"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Fruit/Banana"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Tofu"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Vegetable"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Vegetable/Broccoli"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Vegetable/Corn"') >= 0);
+  do_check_true(response.indexOf('Peach') == -1);
+
+  yield true;
+}
+
+// test that 'LIST (SUBSCRIBED) "" "*"' returns the proper responses
+function testListSelectSubscribed()
+{
+  let response = handler.onError('3', 'LIST (SUBSCRIBED) "" "*"');
+
+  do_check_true(response.indexOf('* LIST (\\Marked \\NoInferiors \\Subscribed) "/" "INBOX"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\Subscribed) "/" "Fruit/Banana"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\Subscribed \\NonExistent) "/" "Fruit/Peach"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\Subscribed) "/" "Vegetable"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\Subscribed) "/" "Vegetable/Broccoli"') >= 0);
+  do_check_true(response.indexOf('"Fruit"') == -1);
+  do_check_true(response.indexOf('Apple') == -1);
+  do_check_true(response.indexOf('Tofu') == -1);
+  do_check_true(response.indexOf('Corn') == -1);
+
+  yield true;
+}
+
+// test that 'LIST "" "%" RETURN (CHILDEREN)' returns the proper responses
+function testListReturnChilderen()
+{
+  let response = handler.onError('4', 'LIST "" "%" RETURN (CHILDREN)');
+
+  do_check_true(response.indexOf('* LIST (\\Marked \\NoInferiors) "/" "INBOX"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\HasChildren) "/" "Fruit"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\HasNoChildren) "/" "Tofu"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\HasChildren) "/" "Vegetable"') >= 0);
+  do_check_true(response.indexOf('Apple') == -1);
+  do_check_true(response.indexOf('Banana') == -1);
+  do_check_true(response.indexOf('Peach') == -1);
+  do_check_true(response.indexOf('Broccoli') == -1);
+  do_check_true(response.indexOf('Corn') == -1);
+
+  yield true;
+}
+
+// test that 'LIST "" "*" RETURN (SUBSCRIBED)' returns the proper responses
+function testListReturnSubscribed()
+{
+  let response = handler.onError('5', 'LIST "" "*" RETURN (SUBSCRIBED)');
+
+  do_check_true(response.indexOf('* LIST (\\Marked \\NoInferiors \\Subscribed) "/" "INBOX"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Fruit"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Fruit/Apple"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\Subscribed) "/" "Fruit/Banana"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Tofu"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\Subscribed) "/" "Vegetable"') >= 0);
+  do_check_true(response.indexOf('* LIST (\\Subscribed) "/" "Vegetable/Broccoli"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Vegetable/Corn"') >= 0);
+  do_check_true(response.indexOf('Peach') == -1);
+
+  yield true;
+}
+
+// test that 'LIST "" ("INBOX" "Tofu" "Vegetable/%")' returns the proper responses
+function testListSelectMultiple()
+{
+  let response = handler._dispatchCommand('LIST', ['', '("INBOX" "Tofu" "Vegetable/%")']);
+
+  do_check_true(response.indexOf('* LIST (\\Marked \\NoInferiors) "/" "INBOX"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Tofu"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Vegetable/Broccoli"') >= 0);
+  do_check_true(response.indexOf('* LIST () "/" "Vegetable/Corn"') >= 0);
+  do_check_true(response.indexOf('"Vegetable"') == -1);
+  do_check_true(response.indexOf('Fruit') == -1);
+  do_check_true(response.indexOf('Peach') == -1);
+
+  yield true;
+}
+
+// Cleanup at end
+function endTest()
+{
+  handler = null;
+  teardownIMAPPump();
+}
+
+function run_test()
+{
+  Services.prefs.setBoolPref("mail.server.server1.autosync_offline_stores", false);
+  async_run_tests(tests);
+}
+
+/*
+ * helper functions
+ */
+
+function recursiveDeleteMailboxes(aMailbox)
+{
+  for each (var child in aMailbox.allChildren) {
+    recursiveDeleteMailboxes(child);
+  }
+  gIMAPDaemon.deleteMailbox(aMailbox);
+}
--- a/mailnews/base/test/unit/xpcshell.ini
+++ b/mailnews/base/test/unit/xpcshell.ini
@@ -57,11 +57,13 @@ skip-if = os != 'mac'
 [test_searchBoolean.js]
 [test_searchChaining.js]
 [test_searchCustomTerm.js]
 [test_searchJunk.js]
 [test_searchLocalizationStrings.js]
 [test_searchTag.js]
 [test_searchUint32HdrProperty.js]
 [test_testsuite_base64.js]
+[test_testsuite_fakeserver_imapd_gmail.js]
+[test_testsuite_fakeserver_imapd_list-extended.js]
 [test_testsuite_fakeserverAuth.js]
 [test_viewSortByAddresses.js]
 [test_formatFileSize.js]
--- a/mailnews/imap/test/unit/test_gmailAttributes.js
+++ b/mailnews/imap/test/unit/test_gmailAttributes.js
@@ -26,35 +26,40 @@ Components.utils.import("resource://gre/
 
 // Messages to load must have CRLF line endings, that is Windows style
 const gMessage = "bugmail10"; // message file used as the test message
 
 const gXGmMsgid = "1278455344230334865";
 const gXGmThrid = "1266894439832287888";
 const gXGmLabels = '(\\Inbox \\Sent Important "Muy Importante" foo)';
 
-var gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"]
-  .createInstance(Ci.nsIMsgWindow);
+setupIMAPPump("GMail");
+
+gIMAPMailbox.specialUseFlag = "\\Inbox";
+gIMAPMailbox.subscribed = true;
 
-setupIMAPPump("GMail");
+// need all mail folder to identify this as gmail server.
+gIMAPDaemon.createMailbox("[Gmail]", {flags : ["\\NoSelect"] });
+gIMAPDaemon.createMailbox("[Gmail]/All Mail", {subscribed : true,
+                                               specialUseFlag : "\\AllMail"});
 
 // Definition of tests
 var tests = [
   loadImapMessage,
   testFetchXGmMsgid,
   testFetchXGmThrid,
   testFetchXGmLabels,
   endTest
 ]
 
 // load and update a message in the imap fake server
 function loadImapMessage()
 {
   let message = new imapMessage(specForFileName(gMessage),
-                          gIMAPMailbox.uidnext++, []);
+                                gIMAPMailbox.uidnext++, []);
   message.xGmMsgid = gXGmMsgid;
   message.xGmThrid = gXGmThrid;
   message.xGmLabels = gXGmLabels;
   gIMAPMailbox.addMessage(message);
   gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
   yield false;
 }
 
--- a/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js
+++ b/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js
@@ -1,39 +1,34 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
- * Test to ensure that, in case of GMail server, fetching of a message,
- * which is already present in offline store of some folder, from a folder
- * doesn't make us add it to the offline store twice(in this case, in general it can
- * be any number of times).
+ * Test to ensure that, in case of GMail server, fetching of a message, which is
+ * already present in offline store of some folder, from a folder doesn't make
+ * us add it to the offline store twice(in this case, in general it can be any
+ * number of times).
  *
  * Bug 721316
  *
  * See https://bugzilla.mozilla.org/show_bug.cgi?id=721316
  * for more info.
  *
  * Original Author: Atul Jangra<atuljangra66@gmail.com>
  */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 load("../../../resources/logHelper.js");
 load("../../../resources/mailTestUtils.js");
 load("../../../resources/asyncTestUtils.js");
 
-load("../../../resources/messageGenerator.js");
-load("../../../resources/messageModifier.js");
-load("../../../resources/messageInjection.js");
 load("../../../resources/IMAPpump.js");
 
-var gMessageGenerator = new MessageGenerator();
-var gScenarioFactory = new MessageScenarioFactory(gMessageGenerator);
 
 // Messages to load must have CRLF line endings, that is Windows style
 
 const gMessage1 = "bugmail10"; // message file used as the test message for Inbox and fooFolder
 const gXGmMsgid1 = "1278455344230334865";
 const gXGmThrid1 = "1266894439832287888";
 // We need to have different X-GM-LABELS for different folders. I am doing it here manually, but this issue will be tackled in Bug 781443.
 const gXGmLabels11 = '( \"\\\\Sent\" foo bar)'; // for message in Inbox
@@ -44,26 +39,188 @@ const gMessage2 = "bugmail11" // message
 const gMsgId2 = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
 const gXGmMsgid2 = "1278455345230334555";
 const gXGmThrid2 = "1266894639832287111";
 const gXGmLabels2 = '(\"\\\\Sent\")';
 
 const nsMsgMessageFlags = Ci.nsMsgMessageFlags;
 
 var fooBox;
-var imapInbox;
 var fooFolder;
-var rootFolder;
 
 var gImapInboxOfflineStoreSizeInitial;
 var gImapInboxOfflineStoreSizeFinal;
 
 var gFooOfflineStoreSizeInitial;
 var gFooOfflineStoreSizeFinal;
-// We use this as a display consumer
+
+var tests = [
+  setup,
+  updateFolder,
+  selectInboxMsg,
+  StreamMessageInbox,
+  createAndUpdate,
+  addFoo,
+  updateFoo,
+  selectFooMsg,
+  StreamMessageFoo,
+  crossStreaming,
+  teardown
+]
+
+function setup() {
+  // We aren't interested in downloading messages automatically
+  Services.prefs.setBoolPref("mail.server.server1.autosync_offline_stores", false);
+  Services.prefs.setBoolPref("mail.server.server1.offline_download", true);
+  Services.prefs.setBoolPref("mail.biff.alert.show_preview", false);
+
+  setupIMAPPump("GMail");
+
+  gIMAPMailbox.specialUseFlag = "\\Inbox";
+  gIMAPMailbox.subscribed = true;
+
+  // need all mail folder to identify this as gmail server.
+  gIMAPDaemon.createMailbox("[Gmail]", {flags : ["\\NoSelect"] });
+  gIMAPDaemon.createMailbox("[Gmail]/All Mail", {subscribed : true,
+                                                 specialUseFlag : "\\AllMail"});
+
+  // Creating the mailbox "foo"
+  gIMAPDaemon.createMailbox("foo", {subscribed : true});
+  fooBox = gIMAPDaemon.getMailbox("foo");
+
+  // Add message1 to inbox.
+  message = new imapMessage(specForFileName(gMessage1),
+                            gIMAPMailbox.uidnext++, []);
+  message.messageId = gMsgId1;
+  message.xGmMsgid = gXGmMsgid1;
+  message.xGmThrid = gXGmThrid1;
+  message.xGmLabels = gXGmLabels11; // With labels excluding "//INBOX"
+  gIMAPMailbox.addMessage(message);
+}
+
+function updateFolder() {
+  gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
+  yield false;
+}
+
+function selectInboxMsg() {
+  // Select mesasage1 from inbox which makes message1 available in offline store.
+  let imapService = Cc["@mozilla.org/messenger/messageservice;1?type=imap"]
+                        .getService(Ci.nsIMsgMessageService);
+  let db = gIMAPInbox.msgDatabase;
+  let msg1 = db.getMsgHdrForMessageID(gMsgId1);
+  let url = new Object;
+  imapService.DisplayMessage(gIMAPInbox.getUriForMsg(msg1), streamListener,
+                             null, asyncUrlListener, null, url);
+  yield false;
+}
+
+function StreamMessageInbox() {
+  // Stream message1 from inbox
+  let newMsgHdr = gIMAPInbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+  let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+  let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+  let msgServ = messenger.messageServiceFromURI(msgURI);
+  msgServ.streamMessage(msgURI, gStreamListener, null, null, false, "", false);
+  gImapInboxOfflineStoreSizeInitial = gIMAPInbox.filePath.fileSize; // Initial Size of Inbox
+  yield false;
+}
+
+function createAndUpdate() {
+  let rootFolder = gIMAPIncomingServer.rootFolder;
+  fooFolder =  rootFolder.getChildNamed("foo").QueryInterface(Ci.nsIMsgImapMailFolder); // We have created the mailbox earlier.
+  fooFolder.updateFolderWithListener(null, asyncUrlListener);
+  yield false;
+}
+
+function addFoo() {
+  // Adding our test message
+  let message = new imapMessage(specForFileName(gMessage1),
+                                fooBox.uidnext++, []);
+  message.messageId = gMsgId1;
+  message.xGmMsgid = gXGmMsgid1;
+  message.xGmThrid = gXGmThrid1;
+  message.xGmLabels = gXGmLabels12; // With labels excluding "foo"
+  fooBox.addMessage(message);
+  // Adding another message so that fooFolder behaves as LocalFolder while calculating it's size.
+  let message1 = new imapMessage(specForFileName(gMessage2),
+                                 fooBox.uidnext++, []);
+  message1.messageId = gMsgId2;
+  message1.xGmMsgid = gXGmMsgid2;
+  message1.xGmThrid = gXGmThrid2;
+  message1.xGmLabels = gXGmLabels2;
+  fooBox.addMessage(message1);
+}
+
+function updateFoo() {
+  fooFolder.updateFolderWithListener(null, asyncUrlListener);
+  yield false;
+}
+
+function selectFooMsg() {
+  // Select message2 from fooFolder, which makes fooFolder a local folder.
+  let imapService = Cc["@mozilla.org/messenger/messageservice;1?type=imap"]
+                       .getService(Ci.nsIMsgMessageService);
+  let msg1 = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+  let url = new Object;
+  imapService.DisplayMessage(fooFolder.getUriForMsg(msg1), streamListener,
+                             null, asyncUrlListener, null, url);
+  yield false;
+}
+
+function StreamMessageFoo() {
+  // Stream message2 from fooFolder
+  let newMsgHdr = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+  let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+  let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+  let msgServ = messenger.messageServiceFromURI(msgURI);
+  msgServ.streamMessage(msgURI, gStreamListener, null, null, false, "", false);
+  gFooOfflineStoreSizeInitial = fooFolder.filePath.fileSize;
+  yield false;
+}
+
+function crossStreaming() {
+  /**
+   * Streaming message1 from fooFolder. message1 is present in
+   * offline store of inbox. We now test that streaming the message1
+   * from fooFolder does not make us add message1 to offline store of
+   * fooFolder. We check this by comparing the sizes of inbox and fooFolder
+   * before and after streaming.
+   */
+  let msg2 = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+  do_check_neq(msg2, null);
+  let msgURI = fooFolder.getUriForMsg(msg2);
+  let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+  let msgServ = messenger.messageServiceFromURI(msgURI);
+  // pass true for aLocalOnly since message should be in offline store of Inbox.
+  msgServ.streamMessage(msgURI, gStreamListener, null, null, false, "", true);
+  gFooOfflineStoreSizeFinal = fooFolder.filePath.fileSize;
+  gImapInboxOfflineStoreSizeFinal = gIMAPInbox.filePath.fileSize;
+  do_check_eq(gFooOfflineStoreSizeFinal, gFooOfflineStoreSizeInitial);
+  do_check_eq(gImapInboxOfflineStoreSizeFinal,gImapInboxOfflineStoreSizeInitial);
+  yield false;
+}
+
+function teardown() {
+  teardownIMAPPump();
+}
+
+function run_test() {
+  async_run_tests(tests);
+}
+
+/*
+ * helper functions
+ */
+
+asyncUrlListener.callback = function(aUrl, aExitCode) {
+  do_check_eq(aExitCode, 0);
+};
+
+ // We use this as a display consumer
 var streamListener =
 {
   _data: "",
 
   QueryInterface:
     XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIRequestObserver]),
 
   // nsIRequestObserver
@@ -71,191 +228,25 @@ var streamListener =
   },
   onStopRequest: function(aRequest, aContext, aStatusCode) {
     do_check_eq(aStatusCode, 0);
   },
 
   // nsIStreamListener
   onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
     let scriptStream = Cc["@mozilla.org/scriptableinputstream;1"]
-                         .createInstance(Ci.nsIScriptableInputStream);
+                          .createInstance(Ci.nsIScriptableInputStream);
 
     scriptStream.init(aInputStream);
 
     scriptStream.read(aCount);
   }
 };
 
-function setup()
-{
-  // We aren't interested in downloading messages automatically
-  Services.prefs.setBoolPref("mail.server.server1.autosync_offline_stores", false);
-  Services.prefs.setBoolPref("mail.server.server1.offline_download", true);
-  Services.prefs.setBoolPref("mail.biff.alert.show_preview", false);
-
-  setupIMAPPump("GMail");
-
-  // these hacks are required because we've created the inbox before
-  // running initial folder discovery, and adding the folder bails
-  // out before we set it as verified online, so we bail out, and
-  // then remove the INBOX folder since it's not verified.
-  gIMAPInbox.hierarchyDelimiter = '/';
-  gIMAPInbox.verifiedAsOnlineFolder = true;
-
-  let imapInbox =  gIMAPDaemon.getMailbox("INBOX");
-
-  // Creating the mailbox "foo"
-  gIMAPDaemon.createMailbox("foo", {subscribed : true}, {"uidnext": 150});
-  fooBox = gIMAPDaemon.getMailbox("foo");
-
-  // Add message1 to inbox.
-  message = new imapMessage(specForFileName(gMessage1),
-                          imapInbox.uidnext++, []);
-  message.messageId = gMsgId1;
-  message.xGmMsgid = gXGmMsgid1;
-  message.xGmThrid = gXGmThrid1;
-  message.xGmLabels = gXGmLabels11; // With labels excluding "//INBOX"
-  imapInbox.addMessage(message);
-}
-
-function addFoo()
-{
-  // Adding our test message
-  message = new imapMessage(specForFileName(gMessage1),
-                          fooBox.uidnext++, []);
-  message.messageId = gMsgId1;
-  message.xGmMsgid = gXGmMsgid1;
-  message.xGmThrid = gXGmThrid1;
-  message.xGmLabels = gXGmLabels12; // With labels excluding "foo"
-  fooBox.addMessage(message);
-  // Adding another message so that fooFolder behaves as LocalFolder while calculating it's size.
-  message1 = new imapMessage(specForFileName(gMessage2),
-                          fooBox.uidnext++, []);
-  message1.messageId = gMsgId2;
-  message1.xGmMsgid = gXGmMsgid2;
-  message1.xGmThrid = gXGmThrid2;
-  message1.xGmLabels = gXGmLabels2;
-  fooBox.addMessage(message1);
-}
-
-var gIMAPService;
-
-var tests = [
-  setup,
-  function updateFolder()
-  {
-    gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
-    yield false;
-  },
-  function selectInboxMsg()
-  {
-    // Select mesasage1 from inbox which makes message1 available in offline store.
-    gIMAPService = Cc["@mozilla.org/messenger/messageservice;1?type=imap"]
-                       .getService(Ci.nsIMsgMessageService);
-    let db = gIMAPInbox.msgDatabase;
-    let msg1 = db.getMsgHdrForMessageID(gMsgId1);
-    let url = new Object;
-    gIMAPService.DisplayMessage(gIMAPInbox.getUriForMsg(msg1),
-                                            streamListener,
-                                            null,
-                                            asyncUrlListener,
-                                            null,
-                                            url);
-    yield false;
-  },
-  function StreamMessageInbox()
-  {
-    // Stream message1 from inbox
-    let newMsgHdr = gIMAPInbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
-    let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
-    let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
-    let msgServ = messenger.messageServiceFromURI(msgURI);
-    msgServ.streamMessage(msgURI, gStreamListener, null, null, false, "", false);
-    gImapInboxOfflineStoreSizeInitial = gIMAPInbox.filePath.fileSize; // Initial Size of Inbox
-    yield false;
-  },
-  function createAndUpdate()
-  {
-    rootFolder = gIMAPIncomingServer.rootFolder;
-    fooFolder =  rootFolder.getChildNamed("foo").QueryInterface(Ci.nsIMsgImapMailFolder); // We have created the mailbox earlier.
-    fooFolder.updateFolderWithListener(null, asyncUrlListener);
-    yield false;
-  },
-  addFoo,
-  function updateFoo() {
-    fooFolder.updateFolderWithListener(null, asyncUrlListener);
-    yield false;
-  },
-  function selectFooMsg()
-  {
-    // Select message2 from fooFolder, which makes fooFolder a local folder.
-    gIMAPService = Cc["@mozilla.org/messenger/messageservice;1?type=imap"]
-                       .getService(Ci.nsIMsgMessageService);
-    let msg1 = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId2);
-    let url = new Object;
-    gIMAPService.DisplayMessage(fooFolder.getUriForMsg(msg1),
-                                            streamListener,
-                                            null,
-                                            asyncUrlListener,
-                                            null,
-                                            url);
-    yield false;
-  },
-  function StreamMessageFoo()
-  {
-    // Stream message2 from fooFolder
-    let newMsgHdr = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId2);
-    let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
-    let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
-    let msgServ = messenger.messageServiceFromURI(msgURI);
-    msgServ.streamMessage(msgURI, gStreamListener, null, null, false, "", false);
-    gFooOfflineStoreSizeInitial = fooFolder.filePath.fileSize;
-    yield false;
-  },
-  function crossStreaming()
-  {
-    /**
-     * Streaming message1 from fooFolder. message1 is present in
-     * offline store of inbox. We now test that streaming the message1
-     * from fooFolder does not make us add message1 to offline store of
-     * fooFolder. We check this by comparing the sizes of inbox and fooFolder
-     * before and after streaming.
-     */
-    let msg2 = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId1);
-    do_check_neq(msg2, null);
-    let msgURI = fooFolder.getUriForMsg(msg2);
-    let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
-    let msgServ = messenger.messageServiceFromURI(msgURI);
-    // pass true for aLocalOnly since message should be in offline store of Inbox.
-    msgServ.streamMessage(msgURI, gStreamListener, null, null, false, "", true);
-    gFooOfflineStoreSizeFinal = fooFolder.filePath.fileSize;
-    gImapInboxOfflineStoreSizeFinal = gIMAPInbox.filePath.fileSize;
-    do_check_eq(gFooOfflineStoreSizeFinal, gFooOfflineStoreSizeInitial);
-    do_check_eq(gImapInboxOfflineStoreSizeFinal,gImapInboxOfflineStoreSizeInitial);
-    yield false;
-  },
-  teardown
-]
-
-asyncUrlListener.callback = function(aUrl, aExitCode) {
-  do_check_eq(aExitCode, 0);
-};
-
-function teardown() {
-  teardownIMAPPump();
-}
-
-function run_test() {
-  async_run_tests(tests);
-}
-
-/*
- * helper functions
- */
-gStreamListener = {
+ gStreamListener = {
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIStreamListener]),
   _stream : null,
   _data : null,
   onStartRequest : function (aRequest, aContext) {
     this._data = "";
   },
   onStopRequest : function (aRequest, aContext, aStatusCode) {
     async_driver();
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/test/unit/test_listSubscribed.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that listing subscribed mailboxes uses LIST (SUBSCRIBED) instead of LSUB
+// for servers that have LIST-EXTENDED capability
+// see: bug 495318
+// see: RFC 5258 - http://tools.ietf.org/html/rfc5258
+
+// async support
+load("../../../resources/logHelper.js");
+load("../../../resources/mailTestUtils.js");
+load("../../../resources/asyncTestUtils.js");
+load("../../../resources/alertTestUtils.js");
+
+// IMAP pump
+load("../../../resources/IMAPpump.js");
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// Globals
+
+
+// Dovecot is one of the servers that supports LIST-EXTENDED
+setupIMAPPump("Dovecot");
+
+// Definition of tests
+var tests = [
+  setupMailboxes,
+  testListSubscribed,
+  endTest
+]
+
+// setup the mailboxes that will be used for this test
+function setupMailboxes()
+{
+  gIMAPMailbox.subscribed = true;
+  gIMAPDaemon.createMailbox("folder1", {subscribed : true, flags : ["\\Noselect"]});
+  gIMAPDaemon.createMailbox("folder1/folder11", {subscribed : true, flags : ["\\Noinferiors"]});
+  gIMAPDaemon.createMailbox("folder2", {subscribed : true, nonExistent : true});
+  gIMAPDaemon.createMailbox("folder3", {});
+
+  // select the inbox to force folder discovery, etc.
+  gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
+  yield false;
+}
+
+// tests that LIST (SUBSCRIBED) returns the proper response
+function testListSubscribed()
+{
+  let nsMsgFolderFlags = Ci.nsMsgFolderFlags;
+
+  // check that we have \Noselect and \Noinferiors flags - these would not have
+  // been returned if we had used LSUB instead of LIST(SUBSCRIBED)
+  let rootFolder = gIMAPIncomingServer.rootFolder;
+  let folder1 = rootFolder.getChildNamed("folder1");
+  do_check_true(folder1.getFlag(nsMsgFolderFlags.ImapNoselect));
+  do_check_false(folder1.getFlag(nsMsgFolderFlags.ImapNoinferiors));
+
+  // make sure the above test was not a fluke
+  let folder11 = folder1.getChildNamed("folder11");
+  do_check_false(folder11.getFlag(nsMsgFolderFlags.ImapNoselect));
+  do_check_true(folder11.getFlag(nsMsgFolderFlags.ImapNoinferiors));
+
+  // test that \NonExistent implies \Noselect
+  let folder2 = rootFolder.getChildNamed("folder2");
+  do_check_true(folder1.getFlag(nsMsgFolderFlags.ImapNoselect));
+
+  // should not get a folder3 since it is not subscribed
+  let folder3;
+  try {
+    folder3 = rootFolder.getChildNamed("folder3");
+  } catch (ex) {}
+  //do_check_false(folder1.getFlag(nsMsgFolderFlags.Subscribed));
+  do_check_null(folder3);
+
+  yield true;
+}
+
+// Cleanup at end
+function endTest()
+{
+  teardownIMAPPump();
+}
+
+function run_test()
+{
+  Services.prefs.setBoolPref("mail.server.server1.autosync_offline_stores", false);
+  async_run_tests(tests);
+}
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName)
+{
+  let file = do_get_file("../../../data/" + aFileName);
+  let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+  return msgfileuri.spec;
+}
+
+function recursiveDeleteMailboxes(aMailbox)
+{
+  for each (var child in aMailbox.allChildren) {
+    recursiveDeleteMailboxes(child);
+  }
+  gIMAPDaemon.deleteMailbox(aMailbox);
+}
--- a/mailnews/imap/test/unit/xpcshell.ini
+++ b/mailnews/imap/test/unit/xpcshell.ini
@@ -1,31 +1,33 @@
 [DEFAULT]
 head = head_server.js
 tail = tail_imap.js
 
 [test_autosync_date_constraints.js]
 [test_bccProperty.js]
 [test_bug460636.js]
+[test_chunkLastLF.js]
 [test_compactOfflineStore.js]
 [test_copyThenMove.js]
 [test_customCommandReturnsFetchResponse.js]
 [test_dod.js]
 [test_dontStatNoSelect.js]
 [test_downloadOffline.js]
 [test_fetchCustomAttribute.js]
 [test_filterCustomHeaders.js]
 [test_filterNeedsBody.js]
 [test_gmailAttributes.js]
 [test_gmailOfflineMsgStore.js]
 [test_imapAttachmentSaves.js]
 [test_imapAuthMethods.js]
 # Disabled per bug 553764
 skip-if = true
 [test_imapAutoSync.js]
+[test_imapChunks.js]
 [test_imapContentLength.js]
 [test_imapCopyTimeout.js]
 [test_imapFilterActions.js]
 [test_imapFlagChange.js]
 [test_imapFolderCopy.js]
 [test_imapHdrChunking.js]
 [test_imapHdrStreaming.js]
 [test_imapHighWater.js]
@@ -33,32 +35,31 @@ skip-if = true
 [test_imapMove.js]
 [test_imapPasswordFailure.js]
 [test_imapProtocols.js]
 [test_imapSearch.js]
 [test_imapStatusCloseDBs.js]
 [test_imapStoreMsgOffline.js]
 [test_imapUndo.js]
 [test_imapUrls.js]
-[test_offlineCopy.js]
 [test_largeOfflineStore.js]
 skip-if = os == 'mac'
 [test_listClosesDB.js]
+[test_listSubscribed.js]
 [test_localToImapFilter.js]
 # Disabled due to intermittent failures, bug 502928
 skip-if = true
 [test_localToImapFilterQuarantine.js]
 [test_mailboxes.js]
 [test_nsIMsgFolderListenerIMAP.js]
+[test_offlineCopy.js]
 [test_offlinePlayback.js]
 [test_offlineStoreLocking.js]
 # Doesn't currently work on Windows, bug 782625
 skip-if = os == "win"
 [test_partsOnDemand.js]
 [test_preserveDataOnMove.js]
 [test_saveImapDraft.js]
 [test_saveTemplate.js]
 [test_starttlsFailure.js]
 [test_stopMovingToLocalFolder.js]
 [test_syncChanges.js]
 [test_trustSpamAssassin.js]
-[test_imapChunks.js]
-[test_chunkLastLF.js]
--- a/mailnews/test/fakeserver/imapd.js
+++ b/mailnews/test/fakeserver/imapd.js
@@ -13,17 +13,17 @@
 // see similar problems, so as to make the server widely unusable. In any     //
 // case, if someone complains about not working on bugzilla, it can be added  //
 // to the test suite.                                                         //
 // So, with that in mind, this is the basic layout of the daemon:             //
 // DAEMON                                                                     //
 // + Namespaces: parentless mailboxes whose names are the namespace name. The //
 //     type of the namespace is specified by the type attribute.              //
 // + Mailboxes: imapMailbox objects with several properties. If a mailbox     //
-// | |   property begins with a '_', then it should not be seralized  because //
+// | |   property begins with a '_', then it should not be serialized because //
 // | |   it can be discovered from other means; in particular, a '_' does not //
 // | |   necessarily mean that it is a private property that should not be    //
 // | |   accessed. The parent of a top-level mailbox is null, not "".         //
 // | + I18N names: RFC 3501 specifies a modified UTF-7 form for names.        //
 // | |     However, a draft RFC makes the names UTF-8; it is expected to be   //
 // | |     completed and implemented "soon". Therefore, the correct usage is  //
 // | |     to specify the mailbox names as one normally does in JS and the    //
 // | |     protocol will take care of conversion itself.                      //
@@ -100,28 +100,28 @@ imapDaemon.prototype = {
       if (!mailbox)
         return null;
 
       // Now we continue like normal
       var names = name.split(mailbox.delimiter);
       names.splice(0, 1);
       for each (var part in names) {
         mailbox = mailbox.getChild(part);
-        if (!mailbox)
+        if (!mailbox || mailbox.nonExistent)
           return null;
       }
       return mailbox;
     } else {
       // This is easy, just split it up using the inbox's delimiter
       var names = name.split(this.inbox.delimiter);
       var mailbox = this.root;
 
       for each (var part in names) {
         mailbox = mailbox.getChild(part);
-        if (!mailbox)
+        if (!mailbox || mailbox.nonExistent)
           return null;
       }
       return mailbox;
     }
   },
   createMailbox : function (name, oldBox) {
     var namespace = this.getNamespace(name);
     if (namespace.name != "")
@@ -130,17 +130,17 @@ imapDaemon.prototype = {
     if (prefixes[prefixes.length-1] == '')
       var subName = prefixes.splice(prefixes.length - 2, 2)[0];
     else
       var subName = prefixes.splice(prefixes.length - 1, 1)[0];
     var box = namespace;
     for each (var component in prefixes) {
       box = box.getChild(component);
       // Yes, we won't autocreate intermediary boxes
-      if (box == null || box.flags.indexOf('\\Noinferiors') != -1)
+      if (box == null || box.flags.indexOf('\\NoInferiors') != -1)
         return false;
     }
     // If this is an imapMailbox...
     if (oldBox && oldBox._children) {
       // Only delete now so we don't screw ourselves up if creation fails
       this.deleteMailbox(oldBox);
       oldBox._parent = box == this.root ? null : box;
       let newBox = new imapMailbox(subName, box, this.uidvalidity++);
@@ -163,17 +163,17 @@ imapDaemon.prototype = {
       // And return the new mailbox, since this is being used by people setting
       // up the daemon.
       return childBox;
     } else {
       var creatable = hasFlag(this._flags, IMAP_FLAG_NEEDS_DELIMITER) ?
                       name[name.length - 1] == namespace.delimiter :
                       true;
       var childBox = new imapMailbox(subName, box == this.root ? null : box,
-        { flags : creatable ? [] : ['\\Noinferiors'],
+        { flags : creatable ? [] : ['\\NoInferiors'],
           uidvalidity : this.uidvalidity++ });
       box.addMailbox(childBox);
     }
     return true;
   },
   deleteMailbox : function (mailbox) {
     if (mailbox._children.length == 0) {
       // We don't preserve the subscribed state for deleted mailboxes
@@ -202,18 +202,20 @@ function imapMailbox(name, parent, state
 
   if (!state)
     state = {};
 
   for (var prop in state)
     this[prop] = state[prop];
 
   this.setDefault("subscribed", false);
+  this.setDefault("nonExistent", false);
   this.setDefault("delimiter", "/");
   this.setDefault("flags", []);
+  this.setDefault("specialUseFlag", "");
   this.setDefault("uidnext", 1);
   this.setDefault("msgflags", ["\\Seen", "\\Answered", "\\Flagged",
                                "\\Deleted", "\\Draft"]);
   this.setDefault("permflags", ["\\Seen", "\\Answered", "\\Flagged",
                                 "\\Deleted", "\\Draft", "\\*"]);
 }
 imapMailbox.prototype = {
   setDefault : function(prop, def) {
@@ -604,39 +606,77 @@ function formatArg(argument, spec) {
     argument = new Date(Date.parse(argument.replace(/-(?!\d{4}$)/g, ' ')));
   } else {
     throw "Unknown spec " + spec;
   }
 
   return argument;
 }
 
+// used by RFC 5258 and GMail (labels)
+function parseMailboxList(aList) {
+
+  // strip enclosing parentheses
+  if (aList[0] == '(') {
+    aList = aList.substring(1, aList.length - 1);
+  }
+  let mailboxList = [];
+  for (let i = 0; i < aList.length; i++) {
+    // first, check for literals
+    if (aList[i] == '{') {
+      let endBracketPos = aList.indexOf('}', i);
+      let literalLen = parseInt(aList.substring(i + 1, endBracketPos));
+      // skip CRLF after '}'
+      mailboxList.push(aList.substr(endBracketPos + 3, literalLen));
+      i = endBracketPos + 3 + literalLen;
+    }
+    if (aList[i] == '"') {
+      let endQuotePos = i + aList.substring(i).search(/[^\\]"/);
+      mailboxList.push(aList.substring(i + 1, endQuotePos + 1)
+                       .replace(/\\"/g, '"').replace(/\\\\/g, '\\'));
+      i = endQuotePos + 2;
+    }
+    if (aList[i] != ' ') {
+      let nextSpace = aList.indexOf(' ', i);
+      if (nextSpace == -1) {
+        mailboxList.push(aList.substring(i));
+        i = aList.length;
+      } else {
+        mailboxList.push(aList.substring(i, nextSpace));
+        i = nextSpace;
+      }
+    }
+  }
+  return mailboxList;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 //                              IMAP TEST SERVERS                             //
 ////////////////////////////////////////////////////////////////////////////////
 // Because of IMAP and the LEMONADE RFCs, we have a myriad of different       //
 // server configurations that we should ideally be supporting. We handle them //
 // by defining a core RFC 3501 implementation and then have different server  //
 // extensions subclass the server through functions below. However, we also   //
 // provide standard configurations for best handling.                         //
 // Configurations:                                                            //
 // * Barebones RFC 3501                                                       //
 // * Cyrus                                                                    //
 // * UW IMAP                                                                  //
 // * Courier                                                                  //
 // * Exchange                                                                 //
 // * Dovecot                                                                  //
 // * Zimbra                                                                   //
+// * GMail                                                                    //
 // KNOWN DEVIATIONS FROM RFC 3501:                                            //
 // + The autologout timer is 3 minutes, not 30 minutes. A test with a logout  //
 //   of 30 minutes would take a very long time if it failed.                  //
 // + SEARCH (except for UNDELETED) and STARTTLS are not supported,            //
 //   nor is all of FETCH.                                                     //
 // + Concurrent mailbox access is probably compliant with a rather liberal    //
-//   implentation of RFC 3501, although probably not what one would expect,   //
+//   implementation of RFC 3501, although probably not what one would expect, //
 //   and certainly not what the Dovecot IMAP server tests expect.             //
 ////////////////////////////////////////////////////////////////////////////////
 
 /* IMAP Fakeserver operates in a different manner than the rest of fakeserver
  * because of some differences in the protocol. Commands are dispatched through
  * onError, which parses the message into components. Like other fakeserver
  * implementations, the command property will be called, but this time with an
  * argument that is an array of data items instead of a string representing the
@@ -808,17 +848,20 @@ IMAP_RFC3501_handler.prototype = {
 
       if (spec == "...") {
         treatedArgs = treatedArgs.concat(args);
         args = [];
         break;
       }
 
       if (args.length == 0)
-        throw "BAD not enough arguments";
+        if (spec[0] == '[') // == optional arg
+          continue;
+        else
+          throw "BAD not enough arguments";
 
       if (spec[0] == '[') {
         // We have an optional argument. See if the format matches and move on
         // if it doesn't. Ideally, we'd rethink our decision if a later
         // application turns out to be wrong, but that's ugly to do
         // iteratively. Should any IMAP extension require it, we'll have to
         // come back and change this assumption, though.
         spec = spec.substr(1, spec.length - 2);
@@ -1039,26 +1082,80 @@ IMAP_RFC3501_handler.prototype = {
   },
   UNSUBSCRIBE : function (args) {
     var mailbox = this._daemon.getMailbox(args[0]);
     if (mailbox)
       mailbox.subscribed = false;
     return "OK UNSUBSCRIBE completed";
   },
   LIST : function (args) {
-    var base = this._daemon.getMailbox(args[0]);
+
+    // even though this is the LIST function for RFC 3501, code for
+    // LIST-EXTENDED (RFC 5258) is included here to keep things simple and
+    // avoid duplication. We can get away with this because the _treatArgs
+    // function filters out invalid args for servers that don't support
+    // LIST-EXTENDED before they even get here.
+
+    let listFunctionName = "_LIST";
+    // check for optional list selection options argument used by LIST-EXTENDED
+    // and other related RFCs
+    if (args.length == 3 || (args.length > 3 && args[3] == "RETURN")) {
+      let selectionOptions = args.shift();
+      selectionOptions = selectionOptions.toString().split(' ');
+      selectionOptions.sort();
+      for each (let option in selectionOptions) {
+        listFunctionName += "_" + option.replace(/-/g, "_");
+      }
+    }
+    // check for optional list return options argument used by LIST-EXTENDED
+    // and other related RFCs
+    if ((args[2] == "RETURN") ||
+        this.kCapabilities.indexOf("CHILDREN") >= 0) {
+      listFunctionName += "_RETURN";
+      let returnOptions = args[3] ? args[3].toString().split(' ') : [];
+      if ((this.kCapabilities.indexOf("CHILDREN") >= 0) &&
+          (returnOptions.indexOf("CHILDREN") == -1)) {
+        returnOptions.push("CHILDREN");
+      }
+      returnOptions.sort();
+      for each (let option in returnOptions) {
+        listFunctionName += "_" + option.replace(/-/g, "_");
+      }
+    }
+    if (!this[listFunctionName])
+      return 'BAD unknown LIST request options';
+
+    let base = this._daemon.getMailbox(args[0]);
     if (!base)
       return "NO no such mailbox";
-    var people = base.matchKids(args[1]);
-    var response = "";
-    for each (var box in people)
-      response += '* LIST (' + box.flags.join(" ") + ') "' + box.delimiter +
-                  '" "' + box.displayName + '"\0';
+    let requestedBoxes;
+    // check for multiple mailbox patterns used by LIST-EXTENDED
+    // and other related RFCs
+    if (args[1][0] == "(") {
+      requestedBoxes = parseMailboxList(args[1]);
+    } else {
+      requestedBoxes = [ args[1] ];
+    }
+    let response = "";
+    for each (let requestedBox in requestedBoxes) {
+      let people = base.matchKids(requestedBox);
+      for each (let box in people) {
+        response += this[listFunctionName](box);
+      }
+    }
     return response + "OK LIST completed";
   },
+  // _LIST is the standard LIST command response
+  _LIST : function (aBox) {
+    if (aBox.nonExistent) {
+      return "";
+    }
+    return '* LIST (' + aBox.flags.join(" ") + ') "' + aBox.delimiter +
+           '" "' + aBox.displayName + '"\0';
+  },
   LSUB : function (args) {
     var base = this._daemon.getMailbox(args[0]);
     if (!base)
       return "NO no such mailbox";
     var people = base.matchKids(args[1]);
     var response = "";
     for each (var box in people) {
       if (box.subscribed)
@@ -1210,17 +1307,17 @@ IMAP_RFC3501_handler.prototype = {
       response += "* " + ids[i] + " FETCH (";
       var parts = [];
       for each (var item in items) {
 
         // Brief explanation: an item like BODY[]<> can't be hardcoded easily,
         // so we go for the initial alphanumeric substring, passing in the
         // actual string as an optional second part.
         var front = item.split(/[^A-Z0-9-]/, 1)[0];
-        var functionName = "_FETCH_" + front.replace(/-/g, "_"); // '-' is not allowed in js identifiers;
+        var functionName = "_FETCH_" + front.replace(/-/g, "_");
 
         if (!(functionName in this))
           return "BAD can't fetch " + front;
         try {
           parts.push(this[functionName](messages[i], item));
         } catch (ex) {
 
           return "BAD error in fetching: "+ex;
@@ -1572,66 +1669,45 @@ IMAP_RFC3501_handler.prototype = {
       }
       return "RFC822.SIZE " + length;
     } else {
       throw "Unknown item "+query;
     }
   },
   _FETCH_UID : function (message) {
     return "UID " + message.uid;
-  },
-  _FETCH_X_GM_MSGID : function (message) {
-    if (message.xGmMsgid) {
-        return "X-GM-MSGID " + message.xGmMsgid;
-    } else {
-        return "BAD can't fetch X-GM-MSGID";
-    }
-  },
-  _FETCH_X_GM_THRID : function (message) {
-    if (message.xGmThrid) {
-        return "X-GM-THRID " + message.xGmThrid;
-    } else {
-        return "BAD can't fetch X-GM-THRID";
-    }
-  },
-  _FETCH_X_GM_LABELS : function (message) {
-    if (message.xGmLabels) {
-        return "X-GM-LABELS " + message.xGmLabels;
-    } else {
-        return "BAD can't fetch X-GM-LABELS";
-    }
-   }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //                            IMAP4 RFC extensions                            //
 ////////////////////////////////////////////////////////////////////////////////
 // Since there are so many extensions to IMAP, and since these extensions are //
-// not strictly hierarchial (e.g., an RFC 2342-compliant server can also be   //
+// not strictly hierarchical (e.g., an RFC 2342-compliant server can also be  //
 // RFC 3516-compliant, but a server might only implement one of them), they   //
 // must be handled differently from other fakeserver implementations.         //
 // An extension is defined as follows: it is an object (not a function and    //
 // prototype pair!). This object is "mixed" into the handler via the helper   //
 // function mixinExtension, which applies appropriate magic to make the       //
 // handler compliant to the extension. Functions are added untransformed, but //
 // both arrays and objects are handled by appending the values onto the       //
 // original state of the handler. Semantics apply as for the base itself.     //
 ////////////////////////////////////////////////////////////////////////////////
 
 // Note that UIDPLUS (RFC4315) should be mixed in last (or at least after the
 // MOVE extension) because it changes behavior of that extension.
 var configurations = {
-  Cyrus: ["RFC2342", "RFC2195"],
+  Cyrus: ["RFC2342", "RFC2195", "RFC5258"],
   UW: ["RFC2342", "RFC2195"],
-  Dovecot: ["RFC2195"],
-  Zimbra: ["RFC2342", "RFC2195"],
+  Dovecot: ["RFC2195", "RFC5258"],
+  Zimbra: ["RFC2342", "RFC2195", "RFC5258"],
   Exchange: ["RFC2342", "RFC2195"],
   LEMONADE: ["RFC2342", "RFC2195"],
-  CUSTOM1: ["RFCMOVE", "RFC4315", "RFCCUSTOM"],
-  GMail: ["XLIST", "RFCGMAIL", "RFC2197", "RFC4315"]
+  CUSTOM1: ["MOVE", "RFC4315", "CUSTOM"],
+  GMail: ["GMAIL", "RFC2197", "RFC2342", "RFC3348", "RFC4315"]
 };
 
 function mixinExtension(handler, extension) {
   if (extension.preload)
     extension.preload(handler);
 
   for (var property in extension) {
     if (property == 'preload')
@@ -1650,137 +1726,54 @@ function mixinExtension(handler, extensi
         // Hack to make arrays et al. work recursively
         mixinExtension(handler[property], extension[property]);
       else
         handler[property] = extension[property];
     }
   }
 }
 
-// RFC 2342: IMAP4 Namespace
-var IMAP_RFC2342_extension = {
-  NAMESPACE : function (args) {
-    var namespaces = [[], [], []];
-    for each (var namespace in this._daemon.namespaces)
-      namespaces[namespace.type].push(namespace);
-
-    var response = "* NAMESPACE";
-    for each (var type in namespaces) {
-      if (type.length == 0) {
-        response += " NIL";
-        continue;
-      }
-      response += " (";
-      for each (var namespace in type) {
-        response += "(\"";
-        response += namespace.displayName;
-        response += "\" \"";
-        response += namespace.delimiter;
-        response += "\")";
-      }
-      response += ")";
-    }
-    return response;
+// Support for Gmail extensions: XLIST and X-GM-EXT-1
+var IMAP_GMAIL_extension = {
+  preload: function (toBeThis) {
+    toBeThis._preGMAIL_STORE = toBeThis.STORE;
+    toBeThis._preGMAIL_STORE_argFormat = toBeThis._argFormat.STORE;
+    toBeThis._argFormat.STORE = ["number", "atom", "..."];
   },
-  kCapabilities : ["NAMESPACE"],
-  _argFormat : { NAMESPACE : [] },
-  // Enabled in AUTHED and SELECTED states
-  _enabledCommands : { 1 : ["NAMESPACE"], 2 : ["NAMESPACE"] }
-};
-
-var IMAP_XLIST_extension = {
-  XLIST : function(args) {
-    var base = this._daemon.getMailbox(args[0]);
-    if (!base)
-      return "NO no such mailbox";
-    if(!this._daemon.getMailbox("[Gmail]/All Mail", {subscribed : true})) {
-      // No special mailbox exist, so we will create them.
-      // Creating parent first
-      this._daemon.createMailbox("[Gmail]");
-      // now other folders inside the parent
-      this._daemon.createMailbox("[Gmail]/All Mail", {subscribed : true});
-      this._daemon.createMailbox("[Gmail]/Sent Mail", {subscribed : true});
-      this._daemon.createMailbox("[Gmail]/Drafts", {subscribed : true});
-      this._daemon.createMailbox("[Gmail]/Starred", {subscribed : true});
-      this._daemon.createMailbox("[Gmail]/Spam", {subscribed : true});
+  XLIST : function (args) {
+    // XLIST is really just SPECIAL-USE that does not conform to RFC 6154
+    args.push("RETURN");
+    args.push("SPECIAL-USE");
+    return this.LIST(args);
+  },
+  _LIST_RETURN_CHILDREN : function (aBox) {
+    return IMAP_RFC5258_extension._LIST_RETURN_CHILDREN(aBox);
+  },
+  _LIST_RETURN_CHILDREN_SPECIAL_USE : function (aBox) {
+    if (aBox.nonExistent) {
+      return "";
     }
-    var people = base.matchKids(args[1]);
-    var response = "";
-    var specialFolderFlagsLookupTable = {
-      "[Gmail]/All Mail": "AllMail",
-      "[Gmail]/Drafts": "Drafts",
-      "[Gmail]/Sent Mail": "Sent",
-      "[Gmail]/Starred": "Starred",
-      "[Gmail]/Spam": "Spam",
-      "INBOX": "Inbox"
-
-    };
-    for each (var box in people) {
-      let specialFlag = box.displayName in specialFolderFlagsLookupTable ?
-        ' \\' +specialFolderFlagsLookupTable[box.displayName] : ' ';
-      response += '* XLIST (' + box.flags.join(" ") + specialFlag + ') "' +
-              box.delimiter + '" "' + box.displayName + '"\0';
-    }
-    return response + "OK XLIST completed";
-  },
-  kCapabilities : ["XLIST"],
-  _argFormat : { XLIST : ["mailbox", "mailbox"]},
-  // Enabled in AUTHED and SELECTED states
-  _enabledCommands : {1 : ["XLIST"], 2 : ["XLIST"]}
-};
-
-var IMAP_RFCMOVE_extension = {
-  MOVE: function (args, uid) {
-    let messages = this._parseSequenceSet(args[0], uid);
-
-    let dest = this._daemon.getMailbox(args[1]);
-    if (!dest)
-      return "NO [TRYCREATE] what mailbox?";
-
-    for each (var message in messages) {
-      let newMessage = new imapMessage(message._URI, dest.uidnext++,
-                                       message.flags);
-      newMessage.recent = false;
-      dest.addMessage(newMessage);
-    }
-    let mailbox = this._selectedMailbox;
-    let response = "";
-    for (let i = messages.length - 1; i >= 0; i--) {
-      let msgIndex = mailbox._messages.indexOf(messages[i]);
-      if (msgIndex != -1) {
-        response += "* " + (msgIndex + 1) + " EXPUNGE\0";
-        mailbox._messages.splice(msgIndex, 1);
-      }
-    }
-    if (response.length > 0)
-      delete mailbox.__highestuid;
-
-    return response + "OK MOVE completed";
-  },
-  kCapabilities: ["MOVE"],
-  kUidCommands: ["MOVE"],
-  _argFormat: { MOVE: ["number", "mailbox"] },
-  // Enabled in SELECTED state
-  _enabledCommands: { 2: ["MOVE"] }
-};
-
-// Support for Gmail extensions.
-var IMAP_RFCGMAIL_extension = {
-  preload: function (toBeThis) {
-    toBeThis._preRFCGMAIL_STORE = toBeThis.STORE;
-    toBeThis._preRFCGMAIL_STORE_argFormat = toBeThis._argFormat.STORE;
-    toBeThis._argFormat.STORE = ["number", "atom", "..."];
+    return '* LIST (' + aBox.flags.join(" ") +
+           ((aBox._children.length > 0) ?
+            (((aBox.flags.length > 0) ? ' ' : '') + '\\HasChildren') :
+            ((aBox.flags.indexOf('\\NoInferiors') == -1) ?
+             (((aBox.flags.length > 0) ? ' ' : '') + '\\HasNoChildren') :
+             '')) +
+           ((aBox.specialUseFlag && aBox.specialUseFlag.length > 0) ?
+            (' ' + aBox.specialUseFlag) : '') +
+           ') "' + aBox.delimiter +
+           '" "' + aBox.displayName + '"\0';
   },
   STORE : function (args, uid) {
     let regex = /[+-]?FLAGS.*/;
     if (regex.test(args[1])) {
       // if we are storing flags, use the method that was overridden
-      this._argFormat = this._preRFCGMAIL_STORE_argFormat;
+      this._argFormat = this._preGMAIL_STORE_argFormat;
       args = this._treatArgs(args, "STORE");
-      return this._preRFCGMAIL_STORE(args, uid);
+      return this._preGMAIL_STORE(args, uid);
     }
     // otherwise, handle gmail specific cases
     let ids = [];
     let messages = this._parseSequenceSet(args[0], uid, ids);
     args[2] = formatArg(args[2], "string|(string)");
     for (let i = 0; i < args[2].length; i++) {
       if (args[2][i].indexOf(' ') > -1) {
         args[2][i] = '"' + args[2][i] + '"';
@@ -1820,33 +1813,92 @@ var IMAP_RFCGMAIL_extension = {
         return "BAD change what now?";
       }
       response += "* " + ids[i] + " FETCH (X-GM-LABELS (";
       response += message.xGmLabels.join(' ');
       response += '))\0';
     }
     return response + 'OK STORE completed';
   },
-  kCapabilities: ["XLIST", "X-GM-EXT-1"]
+  _FETCH_X_GM_MSGID : function (message) {
+    if (message.xGmMsgid) {
+        return "X-GM-MSGID " + message.xGmMsgid;
+    } else {
+        return "BAD can't fetch X-GM-MSGID";
+    }
+  },
+  _FETCH_X_GM_THRID : function (message) {
+    if (message.xGmThrid) {
+        return "X-GM-THRID " + message.xGmThrid;
+    } else {
+        return "BAD can't fetch X-GM-THRID";
+    }
+  },
+  _FETCH_X_GM_LABELS : function (message) {
+    if (message.xGmLabels) {
+        return "X-GM-LABELS " + message.xGmLabels;
+    } else {
+        return "BAD can't fetch X-GM-LABELS";
+    }
+  },
+  kCapabilities: ["XLIST", "X-GM-EXT-1"],
+  _argFormat : { XLIST : ["mailbox", "mailbox"] },
+  // Enabled in AUTHED and SELECTED states
+  _enabledCommands : { 1 : ["XLIST"], 2 : ["XLIST"] }
+};
+
+var IMAP_MOVE_extension = {
+  MOVE: function (args, uid) {
+    let messages = this._parseSequenceSet(args[0], uid);
+
+    let dest = this._daemon.getMailbox(args[1]);
+    if (!dest)
+      return "NO [TRYCREATE] what mailbox?";
+
+    for each (var message in messages) {
+      let newMessage = new imapMessage(message._URI, dest.uidnext++,
+                                       message.flags);
+      newMessage.recent = false;
+      dest.addMessage(newMessage);
+    }
+    let mailbox = this._selectedMailbox;
+    let response = "";
+    for (let i = messages.length - 1; i >= 0; i--) {
+      let msgIndex = mailbox._messages.indexOf(messages[i]);
+      if (msgIndex != -1) {
+        response += "* " + (msgIndex + 1) + " EXPUNGE\0";
+        mailbox._messages.splice(msgIndex, 1);
+      }
+    }
+    if (response.length > 0)
+      delete mailbox.__highestuid;
+
+    return response + "OK MOVE completed";
+  },
+  kCapabilities: ["MOVE"],
+  kUidCommands: ["MOVE"],
+  _argFormat: { MOVE: ["number", "mailbox"] },
+  // Enabled in SELECTED state
+  _enabledCommands: { 2: ["MOVE"] }
 };
 
 // Provides methods for testing fetchCustomAttribute and issueCustomCommand
-var IMAP_RFCCUSTOM_extension = {
+var IMAP_CUSTOM_extension = {
   preload: function (toBeThis) {
-    toBeThis._preRFCCUSTOM_STORE = toBeThis.STORE;
-    toBeThis._preRFCCUSTOM_STORE_argFormat = toBeThis._argFormat.STORE;
+    toBeThis._preCUSTOM_STORE = toBeThis.STORE;
+    toBeThis._preCUSTOM_STORE_argFormat = toBeThis._argFormat.STORE;
     toBeThis._argFormat.STORE = ["number", "atom", "..."];
   },
   STORE : function (args, uid) {
     let regex = /[+-]?FLAGS.*/;
     if (regex.test(args[1])) {
       // if we are storing flags, use the method that was overridden
-      this._argFormat = this._preRFCCUSTOM_STORE_argFormat;
+      this._argFormat = this._preCUSTOM_STORE_argFormat;
       args = this._treatArgs(args, "STORE");
-      return this._preRFCCUSTOM_STORE(args, uid);
+      return this._preCUSTOM_STORE(args, uid);
     }
     // otherwise, handle custom attribute
     let ids = [];
     let messages = this._parseSequenceSet(args[0], uid, ids);
     args[2] = formatArg(args[2], "string|(string)");
     for (let i = 0; i < args[2].length; i++) {
       if (args[2][i].indexOf(' ') > -1) {
         args[2][i] = '"' + args[2][i] + '"';
@@ -1909,16 +1961,17 @@ var IMAP_RFCCUSTOM_extension = {
     if (message.xCustomList) {
         return "X-CUSTOM-LIST (" + message.xCustomList.join(' ') + ")";
     } else {
         return "BAD can't fetch X-CUSTOM-LIST";
     }
   },
   kCapabilities: ["X-CUSTOM1"]
 };
+
 // RFC 2197: ID
 var IMAP_RFC2197_extension = {
   ID: function (args) {
     let clientID = "(";
     for each (let i in args)
       clientID += "\"" + i + "\"";
 
     clientID += ")";
@@ -1931,16 +1984,53 @@ var IMAP_RFC2197_extension = {
     this._daemon.clientID = clientID;
     return "* ID " + this._daemon.idResponse + "\0OK Success";
   },
   kCapabilities: ["ID"],
   _argFormat: { ID: ["(string)"] },
   _enabledCommands : { 1 : ["ID"], 2 : ["ID"] }
 };
 
+// RFC 2342: IMAP4 Namespace (NAMESPACE)
+var IMAP_RFC2342_extension = {
+  NAMESPACE : function (args) {
+    var namespaces = [[], [], []];
+    for each (var namespace in this._daemon.namespaces)
+      namespaces[namespace.type].push(namespace);
+
+    var response = "* NAMESPACE";
+    for each (var type in namespaces) {
+      if (type.length == 0) {
+        response += " NIL";
+        continue;
+      }
+      response += " (";
+      for each (var namespace in type) {
+        response += "(\"";
+        response += namespace.displayName;
+        response += "\" \"";
+        response += namespace.delimiter;
+        response += "\")";
+      }
+      response += ")";
+    }
+    response += "\0OK NAMESPACE command completed";
+    return response;
+  },
+  kCapabilities : ["NAMESPACE"],
+  _argFormat : { NAMESPACE : [] },
+  // Enabled in AUTHED and SELECTED states
+  _enabledCommands : { 1 : ["NAMESPACE"], 2 : ["NAMESPACE"] }
+};
+
+// RFC 3348 Child Mailbox (CHILDREN)
+var IMAP_RFC3348_extension = {
+  kCapabilities: ["CHILDREN"]
+}
+
 // RFC 4315: UIDPLUS
 var IMAP_RFC4315_extension = {
   preload: function (toBeThis) {
     toBeThis._preRFC4315UID = toBeThis.UID;
     toBeThis._preRFC4315APPEND = toBeThis.APPEND;
     toBeThis._preRFC4315COPY = toBeThis.COPY;
     toBeThis._preRFC4315MOVE = toBeThis.MOVE;
   },
@@ -1984,16 +2074,57 @@ var IMAP_RFC4315_extension = {
                             " " + args[0] + " " + first + ":" + last + "]",
                          "");
     }
     return response;
   },
   kCapabilities: ["UIDPLUS"]
 };
 
+// RFC 5258: LIST-EXTENDED
+var IMAP_RFC5258_extension = {
+  preload: function (toBeThis) {
+    toBeThis._argFormat.LIST = ["[(atom)]", "mailbox", "mailbox|(mailbox)",
+                                "[atom]", "[(atom)]"];
+  },
+  _LIST_SUBSCRIBED : function (aBox) {
+    if (!aBox.subscribed) {
+      return "";
+    }
+    return '* LIST (' + aBox.flags.join(" ") +
+           ((aBox.flags.length > 0) ? ' ' : '') + '\\Subscribed' +
+           (aBox.nonExistent ? ' \\NonExistent' : '') + ') "' +
+           aBox.delimiter + '" "' + aBox.displayName + '"\0';
+  },
+  _LIST_RETURN_CHILDREN : function (aBox) {
+    if (aBox.nonExistent) {
+      return "";
+    }
+    return '* LIST (' + aBox.flags.join(" ") +
+           ((aBox._children.length > 0) ?
+            (((aBox.flags.length > 0) ? ' ' : '') + '\\HasChildren') :
+            ((aBox.flags.indexOf('\\NoInferiors') == -1) ?
+             (((aBox.flags.length > 0) ? ' ' : '') + '\\HasNoChildren') :
+             '')) + ') "' + aBox.delimiter + '" "' + aBox.displayName + '"\0';
+  },
+  _LIST_RETURN_SUBSCRIBED : function (aBox) {
+    if (aBox.nonExistent) {
+      return "";
+    }
+    return '* LIST (' + aBox.flags.join(" ") +
+           (aBox.subscribed ? (((aBox.flags.length > 0) ? ' ' : '') +
+                               '\\Subscribed') : '') +
+           ') "' + aBox.delimiter + '" "' + aBox.displayName + '"\0';
+  },
+  // TODO implement _LIST_REMOTE, _LIST_RECURSIVEMATCH, _LIST_RETURN_SUBSCRIBED
+  // and all valid combinations thereof. Currently, nsImapServerResponseParser
+  // does not support any of these responses anyway.
+
+  kCapabilities: ["LIST-EXTENDED"]
+};
 
 /**
  * This implements AUTH schemes. Could be moved into RFC3501 actually.
  * The test can en-/disable auth schemes by modifying kAuthSchemes.
  */
 var IMAP_RFC2195_extension = {
   kAuthSchemes : [ "CRAM-MD5" , "PLAIN", "LOGIN" ],
 
--- a/mailnews/test/resources/IMAPpump.js
+++ b/mailnews/test/resources/IMAPpump.js
@@ -95,21 +95,19 @@ function setupIMAPPump(extensions)
 
   // Let's also have another account, using the same identity
   let imapAccount = acctMgr.createAccount();
   imapAccount.addIdentity(identity);
   imapAccount.defaultIdentity = identity;
   imapAccount.incomingServer = gIMAPIncomingServer;
 
   // The server doesn't support more than one connection
-  Services.prefs.setIntPref("mail.server.default.max_cached_connections",
-                            1);
+  Services.prefs.setIntPref("mail.server.default.max_cached_connections", 1);
   // We aren't interested in downloading messages automatically
-  Services.prefs.setBoolPref("mail.server.default.download_on_biff",
-                             false);
+  Services.prefs.setBoolPref("mail.server.default.download_on_biff", false);
   Services.prefs.setBoolPref("mail.biff.play_sound", false);
   Services.prefs.setBoolPref("mail.biff.show_alert", false);
   Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
   Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
   Services.prefs.setBoolPref("mail.biff.alert.show_preview", false);
 
   gIMAPIncomingServer.performExpand(null);