Bug 721316. Patch contains all the changes required to implement the new message storage scheme. Patch also contains three new tests and one modified test, to test the new message storage scheme. r=bienvenu,sr=Standard8,a=Standard8
authorAtul Jangra <atuljangra66@gmail.com>
Thu, 27 Sep 2012 21:41:16 +0100
changeset 13465 7c63a5c1a41a71034364b457c7b1907544c71f13
parent 13464 c9696e33af8e1cf953aca9aef24f8efc872b91fa
child 13466 f7a4ab1f831950c2be43fbfaee872ccb39749eee
push id702
push userbugzilla@standard8.plus.com
push dateTue, 09 Oct 2012 20:31:02 +0000
treeherdercomm-beta@f7a4ab1f8319 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbienvenu, Standard8, Standard8
bugs721316
Bug 721316. Patch contains all the changes required to implement the new message storage scheme. Patch also contains three new tests and one modified test, to test the new message storage scheme. r=bienvenu,sr=Standard8,a=Standard8
mailnews/db/msgdb/public/nsIMsgDatabase.idl
mailnews/db/msgdb/src/nsMsgDatabase.cpp
mailnews/imap/public/nsIImapFlagAndUidState.idl
mailnews/imap/public/nsIImapProtocol.idl
mailnews/imap/src/nsImapFlagAndUidState.cpp
mailnews/imap/src/nsImapFlagAndUidState.h
mailnews/imap/src/nsImapMailFolder.cpp
mailnews/imap/src/nsImapMailFolder.h
mailnews/imap/src/nsImapProtocol.cpp
mailnews/imap/src/nsImapProtocol.h
mailnews/imap/src/nsImapServerResponseParser.cpp
mailnews/imap/src/nsImapServerResponseParser.h
mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js
mailnews/imap/test/unit/test_fetchCustomAttribute.js
mailnews/imap/test/unit/test_gmailAttributes.js
mailnews/imap/test/unit/test_gmailOfflineMsgStore.js
mailnews/imap/test/unit/xpcshell.ini
mailnews/test/fakeserver/imapd.js
mailnews/test/resources/IMAPpump.js
--- a/mailnews/db/msgdb/public/nsIMsgDatabase.idl
+++ b/mailnews/db/msgdb/public/nsIMsgDatabase.idl
@@ -40,17 +40,17 @@ interface nsIMsgKeyArray;
 interface nsIOutputStream;
 interface nsIUrlListener;
 interface nsIFile;
 interface nsIArray;
 
 typedef unsigned long nsMsgRetainByPreference;
 
 
-[scriptable, uuid(b01d6b3b-78c2-4958-b836-2259c76dbc24)]
+[scriptable, uuid(0bbe00df-17f8-4e36-8917-40172c3a95fc)]
 
 interface nsIMsgRetentionSettings : nsISupports
 {
   const unsigned long nsMsgRetainAll = 1;
   const unsigned long nsMsgRetainByAge = 2;
   const unsigned long nsMsgRetainByNumHeaders = 3;
 
   attribute boolean useServerDefaults;
@@ -264,16 +264,21 @@ interface nsIMsgDatabase : nsIDBChangeAn
    * nsIMsgFolder.msgDatabase can set the last use time.
    */
   attribute PRTime lastUseTime;
 
   // get a message header for the given key. Caller must release()!
 
   nsIMsgDBHdr GetMsgHdrForKey(in nsMsgKey key);
   nsIMsgDBHdr getMsgHdrForMessageID(in string messageID);
+
+  /**
+   * get a message header for a Gmail message with the given X-GM-MSGID.
+   */
+  nsIMsgDBHdr GetMsgHdrForGMMsgID(in string aGmailMessageID);
   //Returns whether or not this database contains the given key
   boolean ContainsKey(in nsMsgKey key);
 
 /**
  * Must call AddNewHdrToDB after creating. The idea is that you create
  * a new header, fill in its properties, and then call AddNewHdrToDB.
  * AddNewHdrToDB will send notifications to any listeners.
  *
--- a/mailnews/db/msgdb/src/nsMsgDatabase.cpp
+++ b/mailnews/db/msgdb/src/nsMsgDatabase.cpp
@@ -4588,16 +4588,55 @@ NS_IMETHODIMP nsMsgDatabase::GetMsgHdrFo
       hdrRow->Release();
     else
       rv = CreateMsgHdr(hdrRow, key, &msgHdr);
   }
   *aHdr = msgHdr; // already addreffed above.
   return NS_OK; // it's not an error not to find a msg hdr.
 }
 
+NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForGMMsgID(const char *aGMMsgId, nsIMsgDBHdr **aHdr)
+{
+  NS_ENSURE_ARG_POINTER(aGMMsgId);
+  NS_ENSURE_ARG_POINTER(aHdr);
+  nsIMsgDBHdr *msgHdr = nullptr;
+  nsresult rv = NS_OK;
+  mdbYarn gMailMessageIdYarn;
+  gMailMessageIdYarn.mYarn_Buf = (void *) aGMMsgId;
+  gMailMessageIdYarn.mYarn_Fill = strlen(aGMMsgId);
+  gMailMessageIdYarn.mYarn_Form = 0;
+  gMailMessageIdYarn.mYarn_Size = gMailMessageIdYarn.mYarn_Fill;
+
+  nsIMdbRow *hdrRow;
+  mdbOid outRowId;
+  mdb_err result;
+  mdb_token property_token;
+  NS_ENSURE_TRUE(m_mdbStore, NS_ERROR_NULL_POINTER);
+  result = m_mdbStore->StringToToken(GetEnv(), "X-GM-MSGID",
+    &property_token);
+  NS_ENSURE_SUCCESS(result, result);
+  result = m_mdbStore->FindRow(GetEnv(), m_hdrRowScopeToken,
+    property_token, &gMailMessageIdYarn, &outRowId, &hdrRow);
+  if (NS_SUCCEEDED(result) && hdrRow)
+  {
+    // Get key from row
+    mdbOid outOid;
+    rv = hdrRow->GetOid(GetEnv(), &outOid);
+    NS_ENSURE_SUCCESS(rv, rv);
+    nsMsgKey key = outOid.mOid_Id;
+    rv = GetHdrFromUseCache(key, &msgHdr);
+    if ((NS_SUCCEEDED(rv) && msgHdr))
+      hdrRow->Release();
+    else
+      rv = CreateMsgHdr(hdrRow, key, &msgHdr);
+  }
+  *aHdr = msgHdr;
+  return NS_OK; // it's not an error not to find a msg hdr.
+}
+
 nsIMsgDBHdr *nsMsgDatabase::GetMsgHdrForSubject(nsCString &subject)
 {
   nsIMsgDBHdr *msgHdr = nullptr;
   nsresult rv = NS_OK;
   mdbYarn subjectYarn;
 
   subjectYarn.mYarn_Buf = (void*)subject.get();
   subjectYarn.mYarn_Fill = PL_strlen(subject.get());
--- a/mailnews/imap/public/nsIImapFlagAndUidState.idl
+++ b/mailnews/imap/public/nsIImapFlagAndUidState.idl
@@ -1,17 +1,17 @@
 /* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(444e0d79-874d-4174-b7f3-189db317473c)]
-interface nsIImapFlagAndUidState : nsISupports 
+[scriptable, uuid(290412eb-5824-4087-8984-05450c9397be)]
+interface nsIImapFlagAndUidState : nsISupports
 {
   readonly attribute long numberOfMessages;
   readonly attribute long numberOfRecentMessages;
 
   /**
    * If a full update, the total number of deleted messages
    * in the folder; if a partial update, the number of deleted
    * messages in the partial update
@@ -45,10 +45,30 @@ interface nsIImapFlagAndUidState : nsISu
   void getMessageFlags(in long zeroBasedIndex, out unsigned short result);
   void setMessageFlags(in long zeroBasedIndex, in unsigned short flags);
   void expungeByIndex(in unsigned long zeroBasedIndex);
   void addUidFlagPair(in unsigned long uid, in unsigned short flags, in unsigned long zeroBasedIndex);
   void addUidCustomFlagPair(in unsigned long uid, in string customFlag);
   string getCustomFlags(in unsigned long uid); // returns space-separated keywords
   void reset();
   void clearCustomFlags(in unsigned long uid);
+  /**
+   * Adds custom attributes to a hash table for the purpose of storing them
+   * them.
+   * @param aUid   UID of the associated msg
+   * @param aCustomAttributeName   Name of the custom attribute value
+   * @param aCustomAttributeValue   Value of the attribute,
+   */
+  void setCustomAttribute(in unsigned long aUid,
+                          in ACString aCustomAttributeName,
+                          in ACString aCustomAttributeValue);
+
+  /**
+   * Gets the custom attributes from the hash table where they were stored earlier
+   * them.
+   * @param aUid   UID of the associated msg
+   * @param aCustomAttributeName   Name of the custom attribute value
+   * @param aCustomAttributeValue   Value of the attribute,
+   */
+  ACString getCustomAttribute(in unsigned long aUid,
+                              in ACString aCustomAttributeName);
 };
 
--- a/mailnews/imap/public/nsIImapProtocol.idl
+++ b/mailnews/imap/public/nsIImapProtocol.idl
@@ -9,18 +9,19 @@
 interface nsIUrlListener;
 interface nsIURI;
 interface nsIImapUrl;
 interface nsIImapProtocol;
 interface nsIImapIncomingServer;
 interface nsIMsgFolder;
 interface nsIImapHostSessionList;
 interface nsIMsgWindow;
+interface nsIImapFlagAndUidState;
 
-[scriptable, uuid(177f4140-37ad-4828-880c-42d4ee5d2015)]
+[scriptable, uuid(290412eb-5824-4087-8984-05450c9397be)]
 interface nsIImapProtocol : nsISupports {
   void LoadImapUrl(in nsIURI aUrl, in nsISupports aConsumer);
 
   /**
    * IsBusy returns true if the connection is currently processing a url
    * and false otherwise.
    */
   void IsBusy(out boolean aIsConnectionBusy,
@@ -46,16 +47,17 @@ interface nsIImapProtocol : nsISupports 
   // methods to get data from the imap parser flag state.
   void GetFlagsForUID(in unsigned long uid, out boolean foundIt, out unsigned short flags, out string customFlags);
   void GetSupportedUserFlags(out unsigned short flags);
 
   void GetRunningImapURL(out nsIImapUrl aImapUrl);
 
   void GetRunningUrl(out nsIURI aUrl);
 
+  readonly attribute nsIImapFlagAndUidState flagAndUidState;
   /**
    * Tell thread to die - only call from the UI thread
    *
    * @param aIsSafeToClose false if we're dropping a timed out connection.
    */
   void tellThreadToDie(in boolean aIsSafeToClose);
 
   // Get last active time stamp
--- a/mailnews/imap/src/nsImapFlagAndUidState.cpp
+++ b/mailnews/imap/src/nsImapFlagAndUidState.cpp
@@ -80,16 +80,17 @@ nsImapFlagAndUidState::nsImapFlagAndUidS
   : fUids(numberOfMessages),
     fFlags(numberOfMessages),
     mLock("nsImapFlagAndUidState.mLock")
 {
   fSupportedUserFlags = 0;
   fNumberDeleted = 0;
   fPartialUIDFetch = true;
   m_customFlagsHash.Init(10);
+  m_customAttributesHash.Init(10);  
 }
 
 /* static */PLDHashOperator nsImapFlagAndUidState::FreeCustomFlags(const uint32_t &aKey, char *aData,
                                         void *closure)
 {
   PR_Free(aData);
   return PL_DHASH_NEXT;
 }
@@ -301,8 +302,38 @@ NS_IMETHODIMP nsImapFlagAndUidState::Get
 
 NS_IMETHODIMP nsImapFlagAndUidState::ClearCustomFlags(uint32_t uid)
 {
   MutexAutoLock mon(mLock);
   m_customFlagsHash.Remove(uid);
   return NS_OK;
 }
 
+NS_IMETHODIMP nsImapFlagAndUidState::SetCustomAttribute(uint32_t aUid,
+                                                        const nsACString &aCustomAttributeName,
+                                                        const nsACString &aCustomAttributeValue)
+{
+  if (!m_customAttributesHash.IsInitialized())
+    return NS_ERROR_OUT_OF_MEMORY;
+  nsCString key;
+  key.AppendInt((int64_t)aUid);
+  key.Append(aCustomAttributeName);
+  nsCString value;
+  value.Assign(aCustomAttributeValue);
+  m_customAttributesHash.Put(key, value);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetCustomAttribute(uint32_t aUid,
+                                                        const nsACString &aCustomAttributeName,
+                                                        nsACString &aCustomAttributeValue)
+{
+  if (m_customAttributesHash.IsInitialized())
+  {
+    nsCString key;
+    key.AppendInt((int64_t)aUid);
+    key.Append(aCustomAttributeName);
+    nsCString val;
+    m_customAttributesHash.Get(key, &val);
+    aCustomAttributeValue.Assign(val);
+  }
+  return NS_OK;
+}
--- a/mailnews/imap/src/nsImapFlagAndUidState.h
+++ b/mailnews/imap/src/nsImapFlagAndUidState.h
@@ -37,16 +37,18 @@ public:
 
 private:
 
   static PLDHashOperator FreeCustomFlags(const uint32_t &aKey, char *aData, void *closure);
     nsTArray<nsMsgKey>      fUids;
     nsTArray<imapMessageFlagsType> fFlags;
     // Hash table, mapping uids to extra flags
     nsDataHashtable<nsUint32HashKey, char *> m_customFlagsHash;
+    // Hash table, mapping UID+customAttributeName to customAttributeValue.
+    nsDataHashtable<nsCStringHashKey, nsCString> m_customAttributesHash;
     uint16_t                fSupportedUserFlags;
     int32_t                 fNumberDeleted;
     bool                    fPartialUIDFetch;
     mozilla::Mutex mLock;
 };
 
 
 
--- a/mailnews/imap/src/nsImapMailFolder.cpp
+++ b/mailnews/imap/src/nsImapMailFolder.cpp
@@ -3060,16 +3060,21 @@ nsresult nsImapMailFolder::NormalEndHead
     msgUrl = do_QueryInterface(imapUrl, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
     msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
   }
 
   nsCOMPtr<nsIMsgIncomingServer> server;
   rv = GetServer(getter_AddRefs(server));
   NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+  rv = imapServer->GetIsGMailServer(&m_isGmailServer);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
   newMsgHdr->SetMessageKey(m_curMsgUid);
   TweakHeaderFlags(aProtocol, newMsgHdr);
   uint32_t messageSize;
   if (NS_SUCCEEDED(newMsgHdr->GetMessageSize(&messageSize)))
     mFolderSize += messageSize;
   m_msgMovedByFilter = false;
 
   // If this is the inbox, try to apply filters. Otherwise, test the inherited
@@ -3180,16 +3185,31 @@ nsresult nsImapMailFolder::NormalEndHead
     nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
     nsMsgKey highestUID;
     mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
     dbFolderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0, &highestUID);
     if (m_curMsgUid > highestUID)
       dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName, m_curMsgUid);
 
   }
+  if (m_isGmailServer)
+  {
+    nsCOMPtr<nsIImapFlagAndUidState> flagState;
+    aProtocol->GetFlagAndUidState(getter_AddRefs(flagState));
+    nsCString msgIDValue;
+    nsCString threadIDValue;
+    nsCString labelsValue;
+    flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-MSGID"), msgIDValue);
+    flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-THRID"), threadIDValue);
+    flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-LABELS"), labelsValue);
+    newMsgHdr->SetStringProperty("X-GM-MSGID", msgIDValue.get());
+    newMsgHdr->SetStringProperty("X-GM-THRID", threadIDValue.get());
+    newMsgHdr->SetStringProperty("X-GM-LABELS", labelsValue.get());
+  }
+
   m_msgParser->Clear(); // clear out parser, because it holds onto a msg hdr.
   m_msgParser->SetMailDB(nullptr); // tell it to let go of the db too.
   // I don't think we want to do this - it does bad things like set the size incorrectly.
   //    m_msgParser->FinishHeader();
     return NS_OK;
 }
 
 NS_IMETHODIMP nsImapMailFolder::AbortHeaderParseStream(nsIImapProtocol* aProtocol)
@@ -9542,8 +9562,170 @@ void nsImapMailFolder::PlaybackTimerCall
   delete request;
 }
 
 void nsImapMailFolder::InitAutoSyncState()
 {
   if (!m_autoSyncStateObj)
     m_autoSyncStateObj = new nsAutoSyncState(this);
 }
+
+NS_IMETHODIMP nsImapMailFolder::HasMsgOffline(nsMsgKey msgKey, bool *_retval)
+{
+  NS_ENSURE_ARG_POINTER(_retval);
+  *_retval = false;
+  nsCOMPtr<nsIMsgFolder> msgFolder;
+  nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(msgFolder));
+  if (NS_SUCCEEDED(rv) && msgFolder)
+    *_retval = true;
+  return NS_OK;
+
+}
+
+nsresult nsImapMailFolder::GetOfflineMsgFolder(nsMsgKey msgKey, nsIMsgFolder **aMsgFolder)
+{
+  // Check if we have the message in the current folder.
+  NS_ENSURE_ARG_POINTER(aMsgFolder);
+  nsCOMPtr<nsIMsgFolder> subMsgFolder;
+  GetDatabase();
+  if (!mDatabase)
+    return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIMsgDBHdr> hdr;
+  nsresult rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+  if (NS_FAILED(rv))
+    return rv;
+
+  if (hdr)
+  {
+    uint32_t msgFlags = 0;
+    hdr->GetFlags(&msgFlags);
+    // Check if we already have this message body offline
+    if ((msgFlags & nsMsgMessageFlags::Offline))
+    {
+      NS_IF_ADDREF(*aMsgFolder = this);
+      return NS_OK;
+    }
+  }
+
+  if (!*aMsgFolder)
+  {
+    // Checking the existence of message in other folders in case of GMail Server
+    bool isGMail;
+    nsCOMPtr<nsIImapIncomingServer> imapServer;
+    rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = imapServer->GetIsGMailServer(&isGMail);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (isGMail)
+    {
+      nsCString labels;
+      nsTArray<nsCString> labelNames;
+      hdr->GetStringProperty("X-GM-LABELS", getter_Copies(labels));
+      ParseString(labels, ' ', labelNames);
+      nsCOMPtr<nsIMsgFolder> rootFolder;
+      nsCOMPtr<nsIMsgImapMailFolder> subFolder;
+      for (uint32_t i = 0; i < labelNames.Length(); i++)
+      {
+        rv = GetRootFolder(getter_AddRefs(rootFolder));
+        if (NS_SUCCEEDED(rv) && (rootFolder))
+        {
+          nsCOMPtr<nsIMsgImapMailFolder> imapRootFolder = do_QueryInterface(rootFolder);
+          if (labelNames[i].Equals("\"\\\\Draft\""))
+             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts,
+                                                 getter_AddRefs(subMsgFolder));
+          if (labelNames[i].Equals("\"\\\\Inbox\""))
+             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+                                                 getter_AddRefs(subMsgFolder));
+          if (labelNames[i].Equals("\"\\\\All Mail\""))
+             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Archive,
+                                                 getter_AddRefs(subMsgFolder));
+          if (labelNames[i].Equals("\"\\\\Trash\""))
+             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
+                                                 getter_AddRefs(subMsgFolder));
+          if (labelNames[i].Equals("\"\\\\Spam\""))
+             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Junk,
+                                                 getter_AddRefs(subMsgFolder));
+          if (labelNames[i].Equals("\"\\\\Sent\""))
+             rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail,
+                                                 getter_AddRefs(subMsgFolder));
+          if ((labelNames[i].Find(NS_LITERAL_CSTRING("[Imap]/"), true, 0, -1) != -1))
+          {
+            labelNames[i].ReplaceSubstring("[Imap]/", "");
+            imapRootFolder->FindOnlineSubFolder(labelNames[i], getter_AddRefs(subFolder));
+            subMsgFolder = do_QueryInterface(subFolder);
+          }
+          if (!subMsgFolder)
+          {
+            imapRootFolder->FindOnlineSubFolder(labelNames[i], getter_AddRefs(subFolder));
+            subMsgFolder = do_QueryInterface(subFolder);
+          }
+          if (subMsgFolder)
+          {
+            nsCOMPtr<nsIMsgDatabase> db;
+            subMsgFolder->GetMsgDatabase(getter_AddRefs(db));
+            if (db)
+            {
+              nsCOMPtr<nsIMsgDBHdr> retHdr;
+              nsCString gmMsgID;
+              hdr->GetStringProperty("X-GM-MSGID", getter_Copies(gmMsgID));
+              rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(), getter_AddRefs(retHdr));
+              if (NS_FAILED(rv))
+                return rv;
+              if (retHdr)
+              {
+                uint32_t gmFlags = 0;
+                retHdr->GetFlags(&gmFlags);
+                if ((gmFlags & nsMsgMessageFlags::Offline))
+                {
+                  subMsgFolder.forget(aMsgFolder);
+                  // Focus on first positive result.
+                  return NS_OK;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOfflineFileStream(nsMsgKey msgKey, int64_t *offset, uint32_t *size, nsIInputStream **aFileStream)
+{
+  NS_ENSURE_ARG(aFileStream);
+  nsCOMPtr<nsIMsgFolder> offlineFolder;
+  nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(offlineFolder));
+  NS_ENSURE_SUCCESS(rv, rv);
+  if(!offlineFolder)
+    return NS_ERROR_FAILURE;
+
+  GetDatabase();
+  if (!mDatabase)
+    return NS_ERROR_FAILURE;
+
+  if (offlineFolder == this)
+    return nsMsgDBFolder::GetOfflineFileStream(msgKey, offset, size, aFileStream);
+  else
+  {
+    nsresult rv;
+    nsCOMPtr<nsIMsgDBHdr> hdr;
+    rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+    if (NS_FAILED(rv))
+      return rv;
+    if (hdr)
+    {
+      nsCString gmMsgID;
+      hdr->GetStringProperty("X-GM-MSGID", getter_Copies(gmMsgID));
+      nsCOMPtr<nsIMsgDatabase> db;
+      offlineFolder->GetMsgDatabase(getter_AddRefs(db));
+      rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(), getter_AddRefs(hdr));
+      if (NS_FAILED(rv))
+        return rv;
+      nsMsgKey newMsgKey;
+      hdr->GetMessageKey(&newMsgKey);
+      return offlineFolder->GetOfflineFileStream(newMsgKey, offset, size, aFileStream);
+    }
+  }
+  return NS_OK;
+}
--- a/mailnews/imap/src/nsImapMailFolder.h
+++ b/mailnews/imap/src/nsImapMailFolder.h
@@ -283,16 +283,21 @@ public:
                                                  bool aLocalOnly, nsIUrlListener *aUrlListener,
                                                  bool *aAsyncResults);
 
   NS_IMETHOD AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords);
   NS_IMETHOD RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords);
 
   NS_IMETHOD NotifyCompactCompleted();
 
+  // overrides nsMsgDBFolder::HasMsgOffline()
+  NS_IMETHOD HasMsgOffline(nsMsgKey msgKey, bool *_retval);
+  // overrides nsMsgDBFolder::GetOfflineFileStream()
+  NS_IMETHOD GetOfflineFileStream(nsMsgKey msgKey, int64_t *offset, uint32_t *size, nsIInputStream **aFileStream);
+
   NS_DECL_NSIMSGIMAPMAILFOLDER
   NS_DECL_NSIIMAPMAILFOLDERSINK
   NS_DECL_NSIIMAPMESSAGESINK
   NS_DECL_NSICOPYMESSAGELISTENER
   NS_DECL_NSIREQUESTOBSERVER
 
   // nsIUrlListener methods
   NS_IMETHOD OnStartRunningUrl(nsIURI * aUrl);
@@ -300,16 +305,25 @@ public:
 
   NS_DECL_NSIMSGFILTERHITNOTIFY
   NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER
 
   NS_IMETHOD IsCommandEnabled(const nsACString& command, bool *result);
   NS_IMETHOD SetFilterList(nsIMsgFilterList *aMsgFilterList);
   NS_IMETHOD GetCustomIdentity(nsIMsgIdentity **aIdentity);
 
+ /**
+  * This method is used to locate a folder where a msg could be present, not just
+  * the folder where the message first arrives, this method searches for the existence
+  * of msg in all the folders/labels that we retrieve from X-GM-LABELS also.
+  *  @param msgKey key  of the msg for which we are trying to get the folder;
+  *  @param aMsgFolder  required folder;
+  */
+  nsresult GetOfflineMsgFolder(nsMsgKey msgKey, nsIMsgFolder **aMsgFolder);
+
   nsresult AddSubfolderWithPath(nsAString& name, nsIFile *dbPath, nsIMsgFolder **child, bool brandNew = false);
   nsresult MoveIncorporatedMessage(nsIMsgDBHdr *mailHdr,
                                   nsIMsgDatabase *sourceDB,
                                   const nsACString& destFolder,
                                   nsIMsgFilter *filter,
                                   nsIMsgWindow *msgWindow);
 
   // send notification to copy service listener.
@@ -479,16 +493,18 @@ protected:
   // when to send a done notification.
   bool m_compactingOfflineStore;
   bool m_expunging;
   bool m_applyIncomingFilters; // apply filters to this folder, even if not the inbox
   nsMsgIMAPFolderACL *m_folderACL;
   uint32_t     m_aclFlags;
   uint32_t     m_supportedUserFlags;
 
+  // determines if we are on GMail server
+  bool m_isGmailServer;
   // offline imap support
   bool m_downloadingFolderForOfflineUse;
   bool m_filterListRequiresBody;
 
   // auto-sync (automatic message download) support
   nsRefPtr<nsAutoSyncState> m_autoSyncStateObj;
 
   // Quota support
--- a/mailnews/imap/src/nsImapProtocol.cpp
+++ b/mailnews/imap/src/nsImapProtocol.cpp
@@ -368,16 +368,17 @@ nsImapProtocol::nsImapProtocol() : nsMsg
   m_failedAuthMethods = 0;
   m_currentAuthMethod = kCapabilityUndefined;
   m_socketType = nsMsgSocketType::trySTARTTLS;
   m_connectionStatus = NS_OK;
   m_safeToCloseConnection = false;
   m_hostSessionList = nullptr;
   m_flagState = nullptr;
   m_fetchBodyIdList = nullptr;
+  m_isGmailServer = false;
 
   nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
   NS_ASSERTION(prefBranch, "FAILED to create the preference service");
 
   // read in the accept languages preference
   if (prefBranch)
   {
     if (!gInitialized)
@@ -658,16 +659,17 @@ nsresult nsImapProtocol::SetupWithUrl(ns
         folderInfo->GetNumMessages(&mFolderTotalMsgCount);
         folderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0, &mFolderHighestUID);
         folderInfo->GetImapUidValidity(&m_uidValidity);
       }
     }
     nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
     nsCOMPtr<nsIStreamListener> aRealStreamListener = do_QueryInterface(aConsumer);
     m_runningUrl->GetMockChannel(getter_AddRefs(m_mockChannel));
+    imapServer->GetIsGMailServer(&m_isGmailServer);
     if (!m_mockChannel)
     {
       // there are several imap operations that aren't initiated via a nsIChannel::AsyncOpen call on the mock channel.
       // such as selecting a folder. nsImapProtocol now insists on a mock channel when processing a url.
       nsCOMPtr<nsIChannel> channel;
       rv = NS_NewChannel(getter_AddRefs(channel), aURL, nullptr, nullptr, nullptr, 0);
       m_mockChannel = do_QueryInterface(channel);
 
@@ -3406,16 +3408,18 @@ nsImapProtocol::FetchMessage(const nsCSt
         if (gUseEnvelopeCmd)
           what = PR_smprintf(" ENVELOPE BODY.PEEK[HEADER.FIELDS (%s)])", headersToDL);
         else
           what = PR_smprintf(" BODY.PEEK[HEADER.FIELDS (%s)])",headersToDL);
         NS_Free(headersToDL);
         if (what)
         {
           commandString.Append(" %s (UID ");
+           if (m_isGmailServer)
+            commandString.Append("X-GM-MSGID X-GM-THRID X-GM-LABELS ");
           if (aolImapServer)
             commandString.Append(" XAOL.SIZE") ;
           else
             commandString.Append("RFC822.SIZE");
           commandString.Append(" FLAGS");
           commandString.Append(what);
           PR_Free(what);
         }
@@ -4216,16 +4220,23 @@ NS_IMETHODIMP nsImapProtocol::GetFlagsFo
   {
     *resultFlags = flags;
     if ((flags & kImapMsgCustomKeywordFlag) && customFlags)
       m_flagState->GetCustomFlags(uid, customFlags);
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP nsImapProtocol::GetFlagAndUidState(nsIImapFlagAndUidState **aFlagState)
+{
+  NS_ENSURE_ARG_POINTER(aFlagState);
+  NS_IF_ADDREF(*aFlagState = m_flagState);
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsImapProtocol::GetSupportedUserFlags(uint16_t *supportedFlags)
 {
   if (!supportedFlags)
     return NS_ERROR_NULL_POINTER;
 
   *supportedFlags = m_flagState->GetSupportedUserFlags();
   return NS_OK;
 }
@@ -4802,16 +4813,17 @@ nsImapProtocol::DiscoverMailboxSpec(nsIm
       m_specialXListMailboxes.Put(mailboxName, adoptedBoxSpec->mBoxFlags);
       // Remember hierarchy delimiter in case this is the first time we've
       // connected to the server and we need it to be correct for the two-level
       // XLIST we send (INBOX is guaranteed to be in the first response).
       if (adoptedBoxSpec->mBoxFlags & kImapInbox)
         m_runningUrl->SetOnlineSubDirSeparator(adoptedBoxSpec->mHierarchySeparator);
 
     }
+    NS_IF_RELEASE(adoptedBoxSpec);
     break;
   case kListingForCreate:
   case kNoOperationInProgress:
   case kDiscoverTrashFolderInProgress:
   case kListingForInfoAndDiscovery:
     {
       if (ns && nsPrefix) // if no personal namespace, there can be no Trash folder
       {
--- a/mailnews/imap/src/nsImapProtocol.h
+++ b/mailnews/imap/src/nsImapProtocol.h
@@ -570,16 +570,17 @@ private:
   bool m_useCompressDeflate; 
   // these come from the nsIDBFolderInfo in the msgDatabase and
   // are initialized in nsImapProtocol::SetupWithUrl.
   uint64_t mFolderLastModSeq;
   int32_t mFolderTotalMsgCount;
   uint32_t mFolderHighestUID;
   uint32_t mFolderNumDeleted;
 
+  bool m_isGmailServer;
   nsTArray<nsCString> mCustomDBHeaders;
   bool    m_trackingTime;
   PRTime  m_startTime;
   PRTime  m_endTime;
   PRTime  m_lastActiveTime;
   int32_t m_tooFastTime;
   int32_t m_idealTime;
   int32_t m_chunkAddSize;
--- a/mailnews/imap/src/nsImapServerResponseParser.cpp
+++ b/mailnews/imap/src/nsImapServerResponseParser.cpp
@@ -1253,16 +1253,72 @@ void nsImapServerResponseParser::msg_fet
         if (! fNextToken) 
           SetSyntaxError(true);
         else
         {
           fXSenderInfo = CreateAstring(); 
           AdvanceToNextToken();
         }
       }
+      else if (!PL_strcasecmp(fNextToken, "X-GM-MSGID"))
+      {
+        AdvanceToNextToken();
+        if (!fNextToken)
+          SetSyntaxError(true);
+        else
+        {
+          fMsgID = CreateAtom();
+          AdvanceToNextToken();
+          nsCString msgIDValue;
+          msgIDValue.Assign(fMsgID);
+          if (fCurrentResponseUID == 0)
+            fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
+          fFlagState->SetCustomAttribute(fCurrentResponseUID,
+                                         NS_LITERAL_CSTRING("X-GM-MSGID"), msgIDValue);
+          PR_FREEIF(fMsgID);
+        }
+      }
+      else if (!PL_strcasecmp(fNextToken, "X-GM-THRID"))
+      {
+        AdvanceToNextToken();
+        if (!fNextToken)
+          SetSyntaxError(true);
+        else
+        {
+          fThreadID = CreateAtom();
+          AdvanceToNextToken();
+          nsCString threadIDValue;
+          threadIDValue.Assign(fThreadID);
+          if (fCurrentResponseUID == 0)
+            fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
+          fFlagState->SetCustomAttribute(fCurrentResponseUID,
+                                         NS_LITERAL_CSTRING("X-GM-THRID"), threadIDValue);
+          PR_FREEIF(fThreadID);
+        }
+      }
+      else if (!PL_strcasecmp(fNextToken, "X-GM-LABELS"))
+      {
+        AdvanceToNextToken();
+        if (!fNextToken)
+          SetSyntaxError(true);
+        else
+        {
+          fLabels = CreateParenGroup();
+          nsCString labelsValue;
+          labelsValue.Assign(fLabels);
+          labelsValue.Cut(0, 1);
+          labelsValue.Cut(labelsValue.Length()-1, 1);
+          if (fCurrentResponseUID == 0)
+            fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
+          fFlagState->SetCustomAttribute(fCurrentResponseUID,
+                                         NS_LITERAL_CSTRING("X-GM-LABELS"), labelsValue);
+          PR_FREEIF(fLabels);
+        }
+      }
+
       // I only fetch RFC822 so I should never see these BODY responses
       else if (!PL_strcasecmp(fNextToken, "BODY"))
         skip_to_CRLF(); // I never ask for this
       else if (!PL_strcasecmp(fNextToken, "BODYSTRUCTURE"))
       {
         if (fCurrentResponseUID == 0)
           fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
         bodystructure_data();
@@ -1307,25 +1363,25 @@ void nsImapServerResponseParser::msg_fet
           return;
         fServerConnection.GetCurrentUrl()->GetImapAction(&imapAction);
         nsCAutoString userDefinedFetchAttribute;
         fServerConnection.GetCurrentUrl()->GetCustomAttributeToFetch(userDefinedFetchAttribute);
         if ((imapAction == nsIImapUrl::nsImapUserDefinedFetchAttribute && !strcmp(userDefinedFetchAttribute.get(), fNextToken)) ||
             imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand)
         {
           AdvanceToNextToken();
-          char *fetchResult;
-          if (fNextToken[0] == '(') 
-            // look through the tokens until we find the closing ')'
-            // we can have a result like the following:
-            // ((A B) (C D) (E F))
-            fetchResult = CreateParenGroup();
-          else {
-            fetchResult = CreateAstring();
-            AdvanceToNextToken();
+          char *fetchResult;
+          if (fNextToken[0] == '(')
+            // look through the tokens until we find the closing ')'
+            // we can have a result like the following:
+            // ((A B) (C D) (E F))
+            fetchResult = CreateParenGroup();
+          else {
+            fetchResult = CreateAstring();
+            AdvanceToNextToken();
           }
           if (imapAction == nsIImapUrl::nsImapUserDefinedFetchAttribute)
             fServerConnection.GetCurrentUrl()->SetCustomAttributeResult(nsDependentCString(fetchResult));
           if (imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand)
             fServerConnection.GetCurrentUrl()->SetCustomCommandResult(nsDependentCString(fetchResult));
           PR_Free(fetchResult);
         }
         else
--- a/mailnews/imap/src/nsImapServerResponseParser.h
+++ b/mailnews/imap/src/nsImapServerResponseParser.h
@@ -236,16 +236,19 @@ private:
 
   eIMAPstate               fIMAPstate;
 
   eIMAPCapabilityFlags      fCapabilityFlag;
   nsCString     fMailAccountUrl;
   char          *fNetscapeServerVersionString;
   char          *fXSenderInfo; /* changed per message download */
   char          *fLastAlert; /* used to avoid displaying the same alert over and over */
+  char          *fMsgID; /* MessageID for Gmail only (X-GM-MSGID) */
+  char          *fThreadID; /* ThreadID for Gmail only (X-GM-THRID) */
+  char          *fLabels; /* Labels for Gmail only (X-GM-LABELS) [will include parens, removed while passing to hashTable ]*/
   nsCString     fManageListsUrl;
   nsCString    fManageFiltersUrl;
   char          *fFolderAdminUrl;
   nsCString    fServerIdResponse; // RFC 
 
   int32_t fFetchResponseIndex;
 
   // used for aborting a fetch stream when we're pseudo-Interrupted
--- a/mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js
+++ b/mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js
@@ -1,129 +1,128 @@
 /* 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 imap customCommandResult function works properly
- * Bug ?????? 
- * uses Gmail extensions as test case - also useful for bug 721316
+ * Bug 778246
  */
 
-// async support 
+// async support
 load("../../../resources/logHelper.js");
 load("../../../resources/mailTestUtils.js");
 load("../../../resources/asyncTestUtils.js");
 
 // IMAP pump
 load("../../../resources/IMAPpump.js");
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 // Globals
 
 // Messages to load must have CRLF line endings, that is Windows style
 const gMessageFileName = "bugmail10"; // message file used as the test message
 var gMessage, gExpectedLength;
 
-var gXGmLabels = ['\\\\Inbox', '\\\\Sent', 'Important', '"Muy Importante"', 'foo'];
+var gCustomList = ['Custom1', 'Custom2', 'Custom3'];
 
 var gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"]
   .createInstance(Ci.nsIMsgWindow);
 
-setupIMAPPump("GMail");
+setupIMAPPump("CUSTOM1");
 
 // Definition of tests
 var tests = [
   loadImapMessage,
-  testStoreXGMLabel,
-  testStoreMinusXGmLabel,
-  testStorePlusXGmLabel,
+  testStoreCustomList,
+  testStoreMinusCustomList,
+  testStorePlusCustomList,
   endTest
 ]
 
 // load and update a message in the imap fake server
 function loadImapMessage()
 {
   gMessage = new imapMessage(specForFileName(gMessageFileName),
     gIMAPMailbox.uidnext++, []);
-  gMessage.xGmLabels = [];
+  gMessage.xCustomList = [];
   gIMAPMailbox.addMessage(gMessage);
   gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
   yield false;
 }
 
-function testStoreXGMLabel()
+function testStoreCustomList()
 {
   let msgHdr = firstMsgHdr(gIMAPInbox);
-  gExpectedLength = gXGmLabels.length;
+  gExpectedLength = gCustomList.length;
   let uri = gIMAPInbox.issueCommandOnMsgs("STORE", msgHdr.messageKey +
-    " X-GM-LABELS (" + gXGmLabels.join(" ") + ")", gMsgWindow);
+    " X-CUSTOM-LIST (" + gCustomList.join(" ") + ")", gMsgWindow);
   uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
-  uri.RegisterListener(xGmLabelsSetListener);
+  uri.RegisterListener(storeCustomListSetListener);
   yield false;
 }
 
-// listens for response from customCommandResult request for X-GM-MSGID
-var xGmLabelsSetListener = {
+// listens for response from customCommandResult request for X-CUSTOM-LIST
+var storeCustomListSetListener = {
   OnStartRunningUrl: function (aUrl) {},
 
   OnStopRunningUrl: function (aUrl, aExitCode) {
     aUrl.QueryInterface(Ci.nsIImapUrl);
     do_check_eq(aUrl.customCommandResult,
-      "(" + gMessage.xGmLabels.join(" ") + ")");
-    do_check_eq(gMessage.xGmLabels.length, gExpectedLength);
+      "(" + gMessage.xCustomList.join(" ") + ")");
+    do_check_eq(gMessage.xCustomList.length, gExpectedLength);
     async_driver();
   }
 };
 
-function testStoreMinusXGmLabel()
+function testStoreMinusCustomList()
 {
   let msgHdr = firstMsgHdr(gIMAPInbox);
   gExpectedLength--;
   let uri = gIMAPInbox.issueCommandOnMsgs("STORE", msgHdr.messageKey +
-    " -X-GM-LABELS (" + gXGmLabels[0] + ")", gMsgWindow);
+    " -X-CUSTOM-LIST (" + gCustomList[0] + ")", gMsgWindow);
   uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
-  uri.RegisterListener(xGmLabelRemovedListener);
+  uri.RegisterListener(storeCustomListRemovedListener);
   yield false;
 }
 
-// listens for response from customCommandResult request for X-GM-MSGID
-var xGmLabelRemovedListener = {
+// listens for response from customCommandResult request for X-CUSTOM-LIST
+var storeCustomListRemovedListener = {
   OnStartRunningUrl: function (aUrl) {},
 
   OnStopRunningUrl: function (aUrl, aExitCode) {
     aUrl.QueryInterface(Ci.nsIImapUrl);
     do_check_eq(aUrl.customCommandResult,
-      "(" + gMessage.xGmLabels.join(" ") + ")");      
-    do_check_eq(gMessage.xGmLabels.length, gExpectedLength);
+      "(" + gMessage.xCustomList.join(" ") + ")");
+    do_check_eq(gMessage.xCustomList.length, gExpectedLength);
     async_driver();
   }
 };
 
-function testStorePlusXGmLabel()
+function testStorePlusCustomList()
 {
   let msgHdr = firstMsgHdr(gIMAPInbox);
   gExpectedLength++;
   let uri = gIMAPInbox.issueCommandOnMsgs("STORE", msgHdr.messageKey +
-    ' +X-GM-LABELS ("New Label")', gMsgWindow);
+    ' +X-CUSTOM-LIST ("Custom4")', gMsgWindow);
   uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
-  uri.RegisterListener(xGmLabelAddedListener);
+  uri.RegisterListener(storeCustomListAddedListener);
   yield false;
 }
 
-// listens for response from customCommandResult request for X-GM-THRID
-var xGmLabelAddedListener = {
+// listens for response from customCommandResult request for X-CUSTOM-LIST
+var storeCustomListAddedListener = {
   OnStartRunningUrl: function (aUrl) {},
 
   OnStopRunningUrl: function (aUrl, aExitCode) {
     aUrl.QueryInterface(Ci.nsIImapUrl);
     do_check_eq(aUrl.customCommandResult,
-      "(" + gMessage.xGmLabels.join(" ") + ")");
-    do_check_eq(gMessage.xGmLabels.length, gExpectedLength);
+      "(" + gMessage.xCustomList.join(" ") + ")");
+    do_check_eq(gMessage.xCustomList.length, gExpectedLength);
     async_driver();
   }
 };
 
 
 // Cleanup at end
 function endTest()
 {
--- a/mailnews/imap/test/unit/test_fetchCustomAttribute.js
+++ b/mailnews/imap/test/unit/test_fetchCustomAttribute.js
@@ -1,120 +1,98 @@
 /* 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 imap fetchCustomMsgAttribute function works properly
- * Bug 750012 
- * uses Gmail extensions as test case - also useful for bug 721316
  */
 
-// async support 
+// async support
 load("../../../resources/logHelper.js");
 load("../../../resources/mailTestUtils.js");
 load("../../../resources/asyncTestUtils.js");
 
 // IMAP pump
 load("../../../resources/IMAPpump.js");
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 // Globals
 
 // 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)';
+const gCustomValue = "Custom";
+const gCustomList = ["Custom1", "Custom2", "Custom3"];
 
 var gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"]
   .createInstance(Ci.nsIMsgWindow);
 
-setupIMAPPump();
+setupIMAPPump("CUSTOM1");
 
 // Definition of tests
 var tests = [
   loadImapMessage,
-  testFetchXGmMsgid,
-  testFetchXGmThrid,
-  testFetchXGmLabels,
+  testFetchCustomValue,
+  testFetchCustomList,
   endTest
 ]
 
 // load and update a message in the imap fake server
 function loadImapMessage()
 {
   let message = new imapMessage(specForFileName(gMessage),
                           gIMAPMailbox.uidnext++, []);
-  message.xGmMsgid = gXGmMsgid;
-  message.xGmThrid = gXGmThrid;
-  message.xGmLabels = gXGmLabels;
+  message.xCustomValue = gCustomValue;
+  message.xCustomList = gCustomList;
   gIMAPMailbox.addMessage(message);
   gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
   yield false;
 }
 
-function testFetchXGmMsgid()
+// Used to verify that nsIServerResponseParser.msg_fetch() can handle
+// not in a parenthesis group - Bug 750012
+function testFetchCustomValue()
 {
   let msgHdr = firstMsgHdr(gIMAPInbox);
-  let uri = gIMAPInbox.fetchCustomMsgAttribute("X-GM-MSGID", msgHdr.messageKey, gMsgWindow);
+  let uri = gIMAPInbox.fetchCustomMsgAttribute("X-CUSTOM-VALUE", msgHdr.messageKey, gMsgWindow);
   uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
-  uri.RegisterListener(xGmMsgidListener);
+  uri.RegisterListener(fetchCustomValueListener);
   yield false;
 }
 
-// listens for respone from fetchCustomMsgAttribute request for X-GM-MSGID
-var xGmMsgidListener = {
+// listens for resposne from fetchCustomMsgAttribute request for X-CUSTOM-VALUE
+var fetchCustomValueListener = {
   OnStartRunningUrl: function (aUrl) {},
 
   OnStopRunningUrl: function (aUrl, aExitCode) {
     aUrl.QueryInterface(Ci.nsIImapUrl);
-    do_check_eq(aUrl.customAttributeResult, gXGmMsgid);
+    do_check_eq(aUrl.customAttributeResult, gCustomValue);
     async_driver();
   }
 };
 
-function testFetchXGmThrid()
+// Used to verify that nsIServerResponseParser.msg_fetch() can handle a parenthesis group - Bug 735542
+function testFetchCustomList()
 {
   let msgHdr = firstMsgHdr(gIMAPInbox);
-  let uri = gIMAPInbox.fetchCustomMsgAttribute("X-GM-THRID", msgHdr.messageKey, gMsgWindow);
+  let uri = gIMAPInbox.fetchCustomMsgAttribute("X-CUSTOM-LIST", msgHdr.messageKey, gMsgWindow);
   uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
-  uri.RegisterListener(xGmThridListener);
+  uri.RegisterListener(fetchCustomListListener);
   yield false;
 }
 
-// listens for respone from fetchCustomMsgAttribute request for X-GM-THRID
-var xGmThridListener = {
+// listens for response from fetchCustomMsgAttribute request for X-CUSTOM-LIST
+var fetchCustomListListener = {
   OnStartRunningUrl: function (aUrl) {},
 
   OnStopRunningUrl: function (aUrl, aExitCode) {
     aUrl.QueryInterface(Ci.nsIImapUrl);
-    do_check_eq(aUrl.customAttributeResult, gXGmThrid);
-    async_driver();
-  }
-};
-
-function testFetchXGmLabels()
-{
-  let msgHdr = firstMsgHdr(gIMAPInbox);
-  let uri = gIMAPInbox.fetchCustomMsgAttribute("X-GM-LABELS", msgHdr.messageKey, gMsgWindow);
-  uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
-  uri.RegisterListener(xGmLabelsListener);
-  yield false;
-}
-
-// listens for respone from fetchCustomMsgAttribute request for X-GM-LABELS
-var xGmLabelsListener = {
-  OnStartRunningUrl: function (aUrl) {},
-
-  OnStopRunningUrl: function (aUrl, aExitCode) {
-    aUrl.QueryInterface(Ci.nsIImapUrl);
-    do_check_eq(aUrl.customAttributeResult, gXGmLabels);
+    do_check_eq(aUrl.customAttributeResult, "(" + gCustomList.join(" ") + ")");
     async_driver();
   }
 };
 
 // Cleanup at end
 function endTest()
 {
   teardownIMAPPump();
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/test/unit/test_gmailAttributes.js
@@ -0,0 +1,108 @@
+/* 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 custom GMail
+ * attributes works properly.
+ *
+ * Bug 721316
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=721316
+ * for more info.
+ *
+ * Original Author: Atul Jangra<atuljangra66@gmail.com>
+ */
+
+// async support
+load("../../../resources/logHelper.js");
+load("../../../resources/mailTestUtils.js");
+load("../../../resources/asyncTestUtils.js");
+
+// IMAP pump
+load("../../../resources/IMAPpump.js");
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// 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");
+
+// 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++, []);
+  message.xGmMsgid = gXGmMsgid;
+  message.xGmThrid = gXGmThrid;
+  message.xGmLabels = gXGmLabels;
+  gIMAPMailbox.addMessage(message);
+  gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
+  yield false;
+}
+
+function testFetchXGmMsgid()
+{
+  let msgHdr = firstMsgHdr(gIMAPInbox);
+  let val = msgHdr.getStringProperty("X-GM-MSGID");
+  do_check_eq(val, gXGmMsgid);
+}
+
+function testFetchXGmThrid()
+{
+  let msgHdr = firstMsgHdr(gIMAPInbox);
+  let val = msgHdr.getStringProperty("X-GM-THRID");
+  do_check_eq(val, gXGmThrid);
+}
+
+function testFetchXGmLabels()
+{
+  let msgHdr = firstMsgHdr(gIMAPInbox);
+  let val = msgHdr.getStringProperty("X-GM-LABELS");
+   // We need to remove the starting "(" and ending ")" from gXGmLabels while comparing
+  do_check_eq(val, gXGmLabels.substring(1 ,gXGmLabels.length - 1));
+}
+
+// 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 = Cc["@mozilla.org/network/io-service;1"]
+                     .getService(Ci.nsIIOService)
+                     .newFileURI(file)
+                     .QueryInterface(Ci.nsIFileURL);
+  return msgfileuri.spec;
+}
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js
@@ -0,0 +1,282 @@
+/* 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).
+ *
+ * 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
+const gXGmLabels12 = '(\"\\\\Inbox\" \"\\\\Sent\" bar)'; // for message in fooFolder
+const gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+
+const gMessage2 = "bugmail11" // message file used as the test message for fooFolder
+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 streamListener =
+{
+  _data: "",
+
+  QueryInterface:
+    XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIRequestObserver]),
+
+  // nsIRequestObserver
+  onStartRequest: function(aRequest, aContext) {
+  },
+  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);
+
+    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 = {
+  QueryInterface : XPCOMUtils.generateQI([Ci.nsIStreamListener]),
+  _stream : null,
+  _data : null,
+  onStartRequest : function (aRequest, aContext) {
+    this._data = "";
+  },
+  onStopRequest : function (aRequest, aContext, aStatusCode) {
+    async_driver();
+  },
+  onDataAvailable : function (aRequest, aContext, aInputStream, aOff, aCount) {
+    if (this._stream == null) {
+      this._stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+      this._stream.init(aInputStream);
+    }
+    this._data += this._stream.read(aCount);
+  },
+};
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName)
+{
+  let file = do_get_file("../../../data/" + aFileName);
+  let msgfileuri = Cc["@mozilla.org/network/io-service;1"]
+                     .getService(Ci.nsIIOService)
+                     .newFileURI(file)
+                     .QueryInterface(Ci.nsIFileURL);
+  return msgfileuri.spec;
+}
+
--- a/mailnews/imap/test/unit/xpcshell.ini
+++ b/mailnews/imap/test/unit/xpcshell.ini
@@ -2,23 +2,25 @@
 head = head_server.js
 tail = tail_imap.js
 
 [test_autosync_date_constraints.js]
 [test_bccProperty.js]
 [test_bug460636.js]
 [test_compactOfflineStore.js]
 [test_copyThenMove.js]
+[test_customCommandReturnsFetchResponse.js]
 [test_dod.js]
 [test_dontStatNoSelect.js]
 [test_downloadOffline.js]
 [test_fetchCustomAttribute.js]
-[test_customCommandReturnsFetchResponse.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_imapContentLength.js]
 [test_imapCopyTimeout.js]
 [test_imapFilterActions.js]
--- a/mailnews/test/fakeserver/imapd.js
+++ b/mailnews/test/fakeserver/imapd.js
@@ -1210,18 +1210,18 @@ 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, "_"); // '-' is not allowed in js identifiers;
+
         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;
         }
@@ -1593,17 +1593,17 @@ IMAP_RFC3501_handler.prototype = {
     }
   },
   _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   //
 // RFC 3516-compliant, but a server might only implement one of them), they   //
@@ -1620,18 +1620,18 @@ IMAP_RFC3501_handler.prototype = {
 // MOVE extension) because it changes behavior of that extension.
 var configurations = {
   Cyrus: ["RFC2342", "RFC2195"],
   UW: ["RFC2342", "RFC2195"],
   Dovecot: ["RFC2195"],
   Zimbra: ["RFC2342", "RFC2195"],
   Exchange: ["RFC2342", "RFC2195"],
   LEMONADE: ["RFC2342", "RFC2195"],
-  CUSTOM1: ["RFCMOVE", "RFC4315"],
-  GMail: ["RFCGMAIL", "RFC2197", "RFC4315"]
+  CUSTOM1: ["RFCMOVE", "RFC4315", "RFCCUSTOM"],
+  GMail: ["XLIST", "RFCGMAIL", "RFC2197", "RFC4315"]
 };
 
 function mixinExtension(handler, extension) {
   if (extension.preload)
     extension.preload(handler);
 
   for (var property in extension) {
     if (property == 'preload')
@@ -1681,17 +1681,57 @@ var IMAP_RFC2342_extension = {
     return response;
   },
   kCapabilities : ["NAMESPACE"],
   _argFormat : { NAMESPACE : [] },
   // Enabled in AUTHED and SELECTED states
   _enabledCommands : { 1 : ["NAMESPACE"], 2 : ["NAMESPACE"] }
 };
 
-// Proposed MOVE extension (imapPump requires the string "RFC").
+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});
+    }
+    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?";
 
@@ -1717,21 +1757,21 @@ var IMAP_RFCMOVE_extension = {
   },
   kCapabilities: ["MOVE"],
   kUidCommands: ["MOVE"],
   _argFormat: { MOVE: ["number", "mailbox"] },
   // Enabled in SELECTED state
   _enabledCommands: { 2: ["MOVE"] }
 };
 
-// Support for Gmail extensions (imapPump requires the string "RFC").
-var IMAP_RFCGMAIL_extension = {  
+// Support for Gmail extensions.
+var IMAP_RFCGMAIL_extension = {
   preload: function (toBeThis) {
     toBeThis._preRFCGMAIL_STORE = toBeThis.STORE;
-    toBeThis._preRFCGMAIL_STORE_argFormat = toBeThis._argFormat.STORE;  
+    toBeThis._preRFCGMAIL_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._preRFCGMAIL_STORE_argFormat;
       args = this._treatArgs(args, "STORE");
@@ -1739,67 +1779,146 @@ var IMAP_RFCGMAIL_extension = {
     }
     // 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] + '"';
-      }      
+      }
     }
     let response = "";
     for (let i = 0; i < messages.length; i++) {
       let message = messages[i];
       switch (args[1]) {
       case "X-GM-LABELS":
         if (message.xGmLabels) {
-          do_print("replacing [" + message.xGmLabels + "] with [" + args[2] + "]");
           message.xGmLabels = args[2];
-          do_print(message.xGmLabels);
         } else {
           return "BAD can't store X-GM-LABELS";
         }
         break;
       case "+X-GM-LABELS":
         if (message.xGmLabels) {
-          do_print("adding [" + args[2] + "] to [" + message.xGmLabels + "]");
           message.xGmLabels = message.xGmLabels.concat(args[2]);
-          do_print(message.xGmLabels);
         } else {
           return "BAD can't store X-GM-LABELS";
         }
         break;
-      case "-X-GM-LABELS":      
+      case "-X-GM-LABELS":
         if (message.xGmLabels) {
-          do_print("removing [" + args[2] + "] from [" + message.xGmLabels + "]");
           for (let i = 0; i < args[2].length; i++) {
             let idx = message.xGmLabels.indexOf(args[2][i]);
             if (idx != -1) {
               message.xGmLabels.splice(idx,1);
             }
           }
-          do_print(message.xGmLabels);
         } else {
           return "BAD can't store X-GM-LABELS";
         }
         break;
       default:
         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"]
 };
 
-
+// Provides methods for testing fetchCustomAttribute and issueCustomCommand
+var IMAP_RFCCUSTOM_extension = {
+  preload: function (toBeThis) {
+    toBeThis._preRFCCUSTOM_STORE = toBeThis.STORE;
+    toBeThis._preRFCCUSTOM_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;
+      args = this._treatArgs(args, "STORE");
+      return this._preRFCCUSTOM_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] + '"';
+      }
+    }
+    let response = "";
+    for (let i = 0; i < messages.length; i++) {
+      let message = messages[i];
+      switch (args[1]) {
+      case "X-CUSTOM-VALUE":
+        if (message.xCustomValue && args[2].length == 1) {
+          message.xCustomValue = args[2][0];
+        } else {
+          return "BAD can't store X-CUSTOM-VALUE";
+        }
+        break;
+      case "X-CUSTOM-LIST":
+        if (message.xCustomList) {
+          message.xCustomList = args[2];
+        } else {
+          return "BAD can't store X-CUSTOM-LIST";
+        }
+        break;
+      case "+X-CUSTOM-LIST":
+        if (message.xCustomList) {
+          message.xCustomList = message.xCustomList.concat(args[2]);
+        } else {
+          return "BAD can't store X-CUSTOM-LIST";
+        }
+        break;
+      case "-X-CUSTOM-LIST":
+        if (message.xCustomList) {
+          for (let i = 0; i < args[2].length; i++) {
+            let idx = message.xCustomList.indexOf(args[2][i]);
+            if (idx != -1) {
+              message.xCustomList.splice(idx,1);
+            }
+          }
+        } else {
+          return "BAD can't store X-CUSTOM-LIST";
+        }
+        break;
+      default:
+        return "BAD change what now?";
+      }
+      response += "* " + ids[i] + " FETCH (X-CUSTOM-LIST (";
+      response += message.xCustomList.join(' ');
+      response += '))\0';
+    }
+    return response + 'OK STORE completed';
+  },
+  _FETCH_X_CUSTOM_VALUE : function (message) {
+    if (message.xCustomValue) {
+        return "X-CUSTOM-VALUE " + message.xCustomValue;
+    } else {
+        return "BAD can't fetch X-CUSTOM-VALUE";
+    }
+  },
+  _FETCH_X_CUSTOM_LIST : function (message) {
+    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 += ")";
--- a/mailnews/test/resources/IMAPpump.js
+++ b/mailnews/test/resources/IMAPpump.js
@@ -53,18 +53,17 @@ function setupIMAPPump(extensions)
 
     function createHandler(d) {
       var handler = new IMAP_RFC3501_handler(d);
       if (!infoString)
         infoString = "RFC2195";
 
       var parts = infoString.split(/ *, */);
       for each (var part in parts) {
-        if (part.substring(0, 3) == "RFC")
-          mixinExtension(handler, eval("IMAP_" + part + "_extension"));
+        mixinExtension(handler, eval("IMAP_" + part + "_extension"));
       }
       return handler;
     }
     var server = new nsMailServer(createHandler, daemon);
     server.start(IMAP_PORT);
     return server;
   }