add support for IMAP ID command, rfc 2971, r=standard, sr=neil, bug 531158
authorDavid Bienvenu <bienvenu@nventure.com>
Sun, 08 May 2011 14:14:29 -0700
changeset 7735 a1c6b295010bdbeb912c2c778c1cc05ff37ffe26
parent 7734 4965cf0026ca316093a53f5869be5b149e175513
child 7736 24154aa031b5c255d88e8766a5bfa2ef08c8cbe3
push id5938
push userbienvenu@nventure.com
push dateSun, 08 May 2011 21:14:32 +0000
treeherdercomm-central@a1c6b295010b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersstandard, neil, bug
bugs531158
add support for IMAP ID command, rfc 2971, r=standard, sr=neil, bug 531158
mailnews/imap/public/nsIImapIncomingServer.idl
mailnews/imap/public/nsIImapServerSink.idl
mailnews/imap/src/nsImapCore.h
mailnews/imap/src/nsImapIncomingServer.cpp
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_imapID.js
mailnews/mailnews.js
mailnews/test/fakeserver/imapd.js
--- a/mailnews/imap/public/nsIImapIncomingServer.idl
+++ b/mailnews/imap/public/nsIImapIncomingServer.idl
@@ -50,37 +50,40 @@ typedef long nsMsgImapDeleteModel;
 [scriptable, uuid(bbfc33de-fe89-11d3-a564-0060b0fc04b7)]
 interface nsMsgImapDeleteModels
 {
   const long IMAPDelete = 0;    /* delete with a big red x */
   const long MoveToTrash = 1;   /* delete moves message to the trash */
   const long DeleteNoTrash = 2; /* delete is shift delete - don't create or use trash */
 };
 
-[scriptable, uuid(46af9df7-c77a-4107-9ef5-83e3afd418b8)]
+[scriptable, uuid(d4f8f5c7-d413-4c11-b886-abed7f7265a6)]
 interface nsIImapIncomingServer : nsISupports {
 
   attribute long maximumConnectionsNumber;
   attribute long timeOutLimits;
   attribute ACString adminUrl;
   attribute ACString serverDirectory;
   attribute long capabilityPref;
+  /// RFC 2971 ID response stored as a pref
+  attribute ACString serverIDPref;
   attribute boolean cleanupInboxOnExit;
   attribute nsMsgImapDeleteModel deleteModel;
   attribute boolean dualUseFolders;
   attribute long emptyTrashThreshhold;
   attribute ACString personalNamespace;
   attribute ACString publicNamespace;
   attribute ACString otherUsersNamespace;
   attribute boolean offlineDownload;
   attribute boolean overrideNamespaces;
   attribute boolean usingSubscription;
   attribute ACString manageMailAccountUrl;
   attribute boolean fetchByChunks;
   attribute boolean mimePartsOnDemand;
+  attribute boolean sendID;
   attribute boolean isAOLServer;
   attribute boolean useIdle;
   attribute boolean checkAllFoldersForNew;
 
   /// Is this a GMail Server?
   attribute boolean isGMailServer;
 
   /**
--- a/mailnews/imap/public/nsIImapServerSink.idl
+++ b/mailnews/imap/public/nsIImapServerSink.idl
@@ -43,17 +43,17 @@ interface nsIMsgMailNewsUrl;
 interface nsIImapProtocol;
 interface nsIImapUrl;
 interface nsIImapMockChannel;
 
 /**
  * nsIImapServerSink is designed to be used as a proxy to the application's UI
  * thread from the running IMAP threads.
  */
-[scriptable, uuid(1c90d58b-a41b-480d-983f-9d8e83efed0a)]
+[scriptable, uuid(596cbacd-53d2-468e-9ea3-fe2512cb3997)]
 interface nsIImapServerSink : nsISupports {
   /**
    * Check if the given folder path is a possible IMAP mailbox.
    * @param folderPath folder path to check
    * @param hierarchyDelimiter IMAP hierarchy delimiter in canonical format,
    *                           i.e., hierarchy delimiter has been replaced
    *                           with '/'
    * @param boxFlags IMAP folder flags (for subscription, namespaces etc.)
@@ -68,16 +68,18 @@ interface nsIImapServerSink : nsISupport
   void onlineFolderDelete(in ACString folderName);
   void onlineFolderCreateFailed(in ACString aFolderName);
   void onlineFolderRename(in nsIMsgWindow msgWindow, in ACString oldName, in ACString newName);
   boolean folderIsNoSelect(in ACString folderName);
   void setFolderAdminURL(in ACString folderName, in ACString adminUrl);
   boolean folderVerifiedOnline(in ACString folderName);
 
   void setCapability(in unsigned long capability);
+  /// RFC 2971 ID server response
+  void setServerID(in ACString aServerID);
   boolean loadNextQueuedUrl(in nsIImapProtocol protocol);
 
   /**
    * Prepare to retry the given URL.
    * @param imapUrl the url we're going to retry
    * @return channel to associate with the url. We return this because access
    *         to the channel should only happen on the ui thread.
    */
--- a/mailnews/imap/src/nsImapCore.h
+++ b/mailnews/imap/src/nsImapCore.h
@@ -127,20 +127,20 @@ typedef enum {
     kHasAuthOldLoginCapability = 0x00000004, /* original IMAP login method */
     kHasXSenderCapability = 0x00000008,
     kIMAP4Capability = 0x00000010,          /* RFC1734 */
     kIMAP4rev1Capability = 0x00000020,      /* RFC2060 */
     kIMAP4other = 0x00000040,                       /* future rev?? */
     kNoHierarchyRename = 0x00000080,                        /* no hierarchy rename */
     kACLCapability = 0x00000100,          /* ACL extension */
     kNamespaceCapability = 0x00000200,    /* IMAP4 Namespace Extension */
-    kMailboxDataCapability = 0x00000400,  /* MAILBOXDATA SMTP posting extension */
+    kHasIDCapability = 0x00000400,  /* client user agent id extension */
     kXServerInfoCapability = 0x00000800,  /* XSERVERINFO extension for admin urls */
     kHasAuthPlainCapability = 0x00001000, /* new form of auth plain base64 login */
-    kUidplusCapability = 0x00002000,	   /* RFC 2359 UIDPLUS extension */
+    kUidplusCapability = 0x00002000,   /* RFC 2359 UIDPLUS extension */
     kLiteralPlusCapability = 0x00004000, /* RFC 2088 LITERAL+ extension */
     kAOLImapCapability = 0x00008000,     /* aol imap extensions */
     kHasLanguageCapability = 0x00010000, /* language extensions */
     kHasCRAMCapability     = 0x00020000, /* CRAM auth extension */
     kQuotaCapability       = 0x00040000, /* RFC 2087 quota extension */
     kHasIdleCapability       = 0x00080000,  /* RFC 2177 idle extension */
     kHasAuthNTLMCapability = 0x00100000,  /* AUTH NTLM extension */
     kHasAuthMSNCapability = 0x00200000,   /* AUTH MSN extension */
--- a/mailnews/imap/src/nsImapIncomingServer.cpp
+++ b/mailnews/imap/src/nsImapIncomingServer.cpp
@@ -371,31 +371,37 @@ nsImapIncomingServer::SetDeleteModel(PRI
 }
 
 NS_IMPL_SERVERPREF_INT(nsImapIncomingServer, TimeOutLimits,
                        "timeout")
 
 NS_IMPL_SERVERPREF_INT(nsImapIncomingServer, CapabilityPref,
                        "capability")
 
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, ServerIDPref,
+                       "serverIDResponse")
+
 NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, PersonalNamespace,
                        "namespace.personal")
 
 NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, PublicNamespace,
                        "namespace.public")
 
 NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, OtherUsersNamespace,
                        "namespace.other_users")
 
 NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, FetchByChunks,
                        "fetch_by_chunks")
 
 NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, MimePartsOnDemand,
                        "mime_parts_on_demand")
 
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, SendID,
+                       "send_client_info")
+
 NS_IMETHODIMP
 nsImapIncomingServer::GetIsAOLServer(PRBool *aBool)
 {
   NS_ENSURE_ARG_POINTER(aBool);
   *aBool = ((m_capability & kAOLImapCapability) != 0);
   return NS_OK;
 }
 
@@ -2160,23 +2166,28 @@ nsImapIncomingServer::PromptPassword(nsI
 
   rv = GetPasswordWithUI(passwordText, passwordTitle, aMsgWindow, aPassword);
   if (NS_SUCCEEDED(rv))
     m_password = aPassword;
   return rv;
 }
 
 // for the nsIImapServerSink interface
-NS_IMETHODIMP  nsImapIncomingServer::SetCapability(PRUint32 capability)
+NS_IMETHODIMP nsImapIncomingServer::SetCapability(PRUint32 capability)
 {
   m_capability = capability;
   SetCapabilityPref(capability);
   return NS_OK;
 }
 
+NS_IMETHODIMP nsImapIncomingServer::SetServerID(const nsACString &aServerID)
+{
+  return SetServerIDPref(aServerID);
+}
+
 NS_IMETHODIMP  nsImapIncomingServer::CommitNamespaces()
 {
   nsresult rv;
   nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionListCID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
   return hostSession->CommitNamespacesForHost(this);
 }
 
--- a/mailnews/imap/src/nsImapProtocol.cpp
+++ b/mailnews/imap/src/nsImapProtocol.cpp
@@ -88,16 +88,18 @@
 #include "nsIDocShellLoadInfo.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsIMessengerWindowService.h"
 #include "nsIWindowMediator.h"
 #include "nsIWindowWatcher.h"
 #include "nsCOMPtr.h"
 #include "nsMimeTypes.h"
 #include "nsIInterfaceRequestor.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsIXULAppInfo.h"
 
 PRLogModuleInfo *IMAP;
 
 // netlib required files
 #include "nsIStreamListener.h"
 #include "nsIMsgIncomingServer.h"
 #include "nsIImapIncomingServer.h"
 #include "nsIPrefBranch.h"
@@ -331,16 +333,20 @@ static PRInt32 gResponseTimeout = 60;
 static const PRInt32 kAutoExpungeDeleteModel = 0;
 // Expunge whenever the folder is opened
 static const PRInt32 kAutoExpungeAlways = 1;
 // Expunge when over the threshhold, independent of the delete model.
 static const PRInt32 kAutoExpungeOnThreshold = 2;
 static PRInt32 gExpungeOption = kAutoExpungeDeleteModel;
 static PRInt32 gExpungeThreshold = 20;
 
+const PRInt32 kAppBufSize = 100;
+// can't use static nsCString because it shows up as a leak.
+static char gAppName[kAppBufSize];
+static char gAppVersion[kAppBufSize];
 
 nsresult nsImapProtocol::GlobalInitialization(nsIPrefBranch *aPrefBranch)
 {
     gInitialized = PR_TRUE;
 
     aPrefBranch->GetIntPref("mail.imap.chunk_fast", &gTooFastTime);   // secs we read too little too fast
     aPrefBranch->GetIntPref("mail.imap.chunk_ideal", &gIdealTime);    // secs we read enough in good time
     aPrefBranch->GetIntPref("mail.imap.chunk_add", &gChunkAddSize);   // buffer size to add when wasting time
@@ -359,16 +365,26 @@ nsresult nsImapProtocol::GlobalInitializ
     aPrefBranch->GetBoolPref("mail.imap.expunge_after_delete",
                              &gExpungeAfterDelete);
     aPrefBranch->GetBoolPref("mail.imap.check_deleted_before_expunge",
                              &gCheckDeletedBeforeExpunge);
     aPrefBranch->GetIntPref("mail.imap.expunge_option", &gExpungeOption);
     aPrefBranch->GetIntPref("mail.imap.expunge_threshold_number",
                             &gExpungeThreshold);
     aPrefBranch->GetIntPref("mailnews.tcptimeout", &gResponseTimeout);
+    nsCOMPtr<nsIXULAppInfo> appInfo(do_GetService(XULAPPINFO_SERVICE_CONTRACTID));
+
+    if (appInfo)
+    {
+      nsCString appName, appVersion;
+      appInfo->GetName(appName);
+      appInfo->GetVersion(appVersion);
+      PL_strncpyz(gAppName, appName.get(), kAppBufSize);
+      PL_strncpyz(gAppVersion, appVersion.get(), kAppBufSize);
+    }
     return NS_OK;
 }
 
 nsImapProtocol::nsImapProtocol() : nsMsgProtocol(nsnull),
     mLock("nsImapProtocol.mLock"),
     m_dataAvailableMonitor("imapDataAvailable"),
     m_urlReadyToRunMonitor("imapUrlReadyToRun"),
     m_pseudoInterruptMonitor("imapPseudoInterrupt"),
@@ -439,16 +455,17 @@ nsImapProtocol::nsImapProtocol() : nsMsg
   LL_I2L(m_lastProgressTime, 0);
   ResetProgressInfo();
 
   m_tooFastTime = 0;
   m_idealTime = 0;
   m_chunkAddSize = 0;
   m_chunkStartSize = 0;
   m_fetchByChunks = PR_TRUE;
+  m_sendID = PR_TRUE;
   m_chunkSize = 0;
   m_chunkThreshold = 0;
   m_fromHeaderSeen = PR_FALSE;
   m_closeNeededBeforeSelect = PR_FALSE;
   m_needNoop = PR_FALSE;
   m_noopCount = 0;
   m_fetchMsgListIsNew = PR_FALSE;
   m_fetchBodyListIsNew = PR_FALSE;
@@ -783,18 +800,18 @@ nsresult nsImapProtocol::SetupWithUrl(ns
     InitPrefAuthMethods(authMethod);
     (void) server->GetSocketType(&m_socketType);
     PRBool shuttingDown;
     (void) imapServer->GetShuttingDown(&shuttingDown);
     if (!shuttingDown)
       (void) imapServer->GetUseIdle(&m_useIdle);
     else
       m_useIdle = PR_FALSE;
-    if (imapServer)
-      imapServer->GetFetchByChunks(&m_fetchByChunks);
+    imapServer->GetFetchByChunks(&m_fetchByChunks);
+    imapServer->GetSendID(&m_sendID);
 
     nsAutoString trashFolderName;
     if (NS_SUCCEEDED(imapServer->GetTrashFolderName(trashFolderName)))
       CopyUTF16toMUTF7(trashFolderName, m_trashFolderName);
 
     nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
     if (prefBranch)
     {
@@ -5389,16 +5406,33 @@ void nsImapProtocol::Capability()
       if (capabilityFlag & kLiteralPlusCapability)
       {
         GetServerStateParser().SetCapabilityFlag(capabilityFlag & ~kLiteralPlusCapability);
         m_hostSessionList->SetCapabilityForHost(GetImapServerKey(), capabilityFlag & ~kLiteralPlusCapability);
       }
     }
 }
 
+void nsImapProtocol::ID()
+{
+  if (!gAppName[0])
+    return;
+  IncrementCommandTagNumber();
+  nsCString command(GetServerCommandTag());
+  command.Append(" ID (\"name\" \"");
+  command.Append(gAppName);
+  command.Append("\" \"version\" \"");
+  command.Append(gAppVersion);
+  command.Append("\")"CRLF);
+
+  nsresult rv = SendData(command.get());
+  if (NS_SUCCEEDED(rv))
+    ParseIMAPandCheckForNewMail();
+}
+
 void nsImapProtocol::EnableCondStore()
 {
   IncrementCommandTagNumber();
   nsCString command(GetServerCommandTag());
   
   command.Append(" ENABLE CONDSTORE" CRLF);
   
   nsresult rv = SendData(command.get());
@@ -7975,16 +8009,23 @@ void nsImapProtocol::ProcessAfterAuthent
   // backend.  If we enable compression early the proxy
   // will be confused.
   if (UseCompressDeflate())
     StartCompressDeflate();
 
   if ((GetServerStateParser().GetCapabilityFlag() & kHasEnableCapability) &&
        UseCondStore())
     EnableCondStore();
+  if ((GetServerStateParser().GetCapabilityFlag() & kHasIDCapability) &&
+       m_sendID)
+  {
+    ID();
+    if (m_imapServerSink && !GetServerStateParser().GetServerID().IsEmpty())
+      m_imapServerSink->SetServerID(GetServerStateParser().GetServerID());
+  }
 }
 
 void nsImapProtocol::SetupMessageFlagsString(nsCString& flagString,
                                              imapMessageFlagsType flags,
                                              PRUint16 userFlags)
 {
     if (flags & kImapMsgSeenFlag)
         flagString.Append("\\Seen ");
--- a/mailnews/imap/src/nsImapProtocol.h
+++ b/mailnews/imap/src/nsImapProtocol.h
@@ -515,16 +515,17 @@ private:
   nsresult GetPassword(nsCString &password, PRBool aNewPasswordRequested);
   void InitPrefAuthMethods(PRInt32 authMethodPrefValue);
   nsresult ChooseAuthMethod();
   void MarkAuthMethodAsFailed(PRInt32 failedAuthMethod);
   void ResetAuthMethods();
 
   // All of these methods actually issue protocol
   void Capability(); // query host for capabilities.
+  void ID(); // send RFC 2971 app info to server
   void EnableCondStore(); 
   void StartCompressDeflate();
   nsresult BeginCompressing();
   void Language(); // set the language on the server if it supports it
   void Namespace();
   void InsecureLogin(const char *userName, const nsCString &password);
   nsresult AuthLogin(const char *userName, const nsCString &password, eIMAPCapabilityFlag flag);
   void ProcessAuthenticatedStateURL();
@@ -631,16 +632,17 @@ private:
   PRTime  m_startTime;
   PRTime  m_endTime;
   PRTime  m_lastActiveTime;
   PRInt32 m_tooFastTime;
   PRInt32 m_idealTime;
   PRInt32 m_chunkAddSize;
   PRInt32 m_chunkStartSize;
   PRBool  m_fetchByChunks;
+  PRBool  m_sendID;
   PRInt32 m_curFetchSize;
   PRBool  m_ignoreExpunges;
   PRInt32 m_prefAuthMethods; // set of capability flags (in nsImapCore.h) for auth methods
   PRInt32 m_failedAuthMethods; // ditto
   eIMAPCapabilityFlag m_currentAuthMethod; // exactly one capability flag, or 0
   PRInt32 m_socketType;
   PRInt32 m_chunkSize;
   PRInt32 m_chunkThreshold;
--- a/mailnews/imap/src/nsImapServerResponseParser.cpp
+++ b/mailnews/imap/src/nsImapServerResponseParser.cpp
@@ -748,16 +748,19 @@ void nsImapServerResponseParser::respons
       }
       break;
     case 'Q':
       if (!PL_strcasecmp(fNextToken, "QUOTAROOT")  || !PL_strcasecmp(fNextToken, "QUOTA"))
         quota_data();
       else
         SetSyntaxError(PR_TRUE);
       break;
+    case 'I':
+      id_data();
+      break;
     default:
       if (IsNumericString(fNextToken))
         numeric_mailbox_data();
       else
         SetSyntaxError(PR_TRUE);
       break;
     }
     
@@ -2235,18 +2238,18 @@ void nsImapServerResponseParser::capabil
       else if (Substring(token,0,5).Equals("IMAP4", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kIMAP4other;
       else if (token.Equals("X-NO-ATOMIC-RENAME", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kNoHierarchyRename;
       else if (token.Equals("X-NON-HIERARCHICAL-RENAME", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kNoHierarchyRename;
       else if (token.Equals("NAMESPACE", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kNamespaceCapability;
-      else if (token.Equals("MAILBOXDATA", nsCaseInsensitiveCStringComparator()))
-        fCapabilityFlag |= kMailboxDataCapability;
+      else if (token.Equals("ID", nsCaseInsensitiveCStringComparator()))
+        fCapabilityFlag |= kHasIDCapability;
       else if (token.Equals("ACL", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kACLCapability;
       else if (token.Equals("XSERVERINFO", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kXServerInfoCapability;
       else if (token.Equals("UIDPLUS", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kUidplusCapability;
       else if (token.Equals("LITERAL+", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kLiteralPlusCapability;
@@ -3012,16 +3015,26 @@ void nsImapServerResponseParser::quota_d
       else
         HandleMemoryFailure();
     }
   }
   else
     SetSyntaxError(PR_TRUE);
 }
 
+void nsImapServerResponseParser::id_data()
+{
+  AdvanceToNextToken();
+  if (!PL_strcasecmp(fNextToken, "NIL"))
+    AdvanceToNextToken();
+  else
+    fServerIdResponse.Adopt(CreateParenGroup());
+  skip_to_CRLF();
+}
+
 PRBool nsImapServerResponseParser::GetFillingInShell()
 {
   return (m_shell != nsnull);
 }
 
 PRBool nsImapServerResponseParser::GetDownloadingHeaders()
 {
   return fDownloadingHeaders;
--- a/mailnews/imap/src/nsImapServerResponseParser.h
+++ b/mailnews/imap/src/nsImapServerResponseParser.h
@@ -132,17 +132,17 @@ public:
   void ResetCapabilityFlag() ;
 
   nsCString& GetMailAccountUrl() { return fMailAccountUrl; }
   const char *GetXSenderInfo() { return fXSenderInfo; }
   void FreeXSenderInfo() { PR_FREEIF(fXSenderInfo); }
   nsCString& GetManageListsUrl() { return fManageListsUrl; }
   nsCString& GetManageFiltersUrl() {return fManageFiltersUrl;}
   const char *GetManageFolderUrl() {return fFolderAdminUrl;}
-
+  nsCString &GetServerID() {return fServerIdResponse;}
 
   // Call this when adding a pipelined command to the session
   void IncrementNumberOfTaggedResponsesExpected(const char *newExpectedTag);
 
   // Interrupt a Fetch, without really Interrupting (through netlib)
   PRBool GetLastFetchChunkReceived();
   void ClearLastFetchChunkReceived();
   virtual PRUint16	SupportsUserFlags() { return fSupportsUserDefinedFlags; }
@@ -175,16 +175,17 @@ protected:
   virtual void    enable_data();
   virtual void    language_data();
   virtual void    authChallengeResponse_data();
   virtual void    resp_text_code();
   virtual void    response_done();
   virtual void    response_tagged();
   virtual void    response_fatal();
   virtual void    resp_cond_bye();
+  virtual void    id_data();
   virtual void    mailbox_data();
   virtual void    numeric_mailbox_data();
   virtual void    capability_data();
   virtual void    xserverinfo_data();
   virtual void    xmailboxinfo_data();
   virtual void    namespace_data();
   virtual void    myrights_data(PRBool unsolicited);
   virtual void    acl_data();
@@ -273,16 +274,17 @@ private:
   PRUint32      fCapabilityFlag;
   nsCString     fMailAccountUrl;
   char          *fNetscapeServerVersionString;
   char          *fXSenderInfo; /* changed per message download */
   char          *fLastAlert; /* used to avoid displaying the same alert over and over */
   nsCString     fManageListsUrl;
   nsCString    fManageFiltersUrl;
   char          *fFolderAdminUrl;
+  nsCString    fServerIdResponse; // RFC 
 
   PRInt32 fFetchResponseIndex;
 
   // used for aborting a fetch stream when we're pseudo-Interrupted
   PRInt32 numberOfCharsInThisChunk;
   PRInt32 charsReadSoFar;
   PRBool fLastChunk;
 
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/test/unit/test_imapID.js
@@ -0,0 +1,158 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test to ensure that we handle the RFC2197 ID command.
+ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var gIMAPDaemon, gServer, gIMAPIncomingServer;
+
+const gIMAPService = Cc["@mozilla.org/messenger/messageservice;1?type=imap"]
+                       .getService(Ci.nsIMsgMessageService);
+
+const kIDResponse = "(\"name\" \"GImap\" \"vendor\" \"Google, Inc.\" \"support-url\" \"http://mail.google.com/support\")";
+var gIMAPInbox;
+var gTest;
+
+const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
+const XULAPPINFO_CID = Components.ID("{7e10a36e-1085-4302-9e3f-9571fc003ee0}");
+
+var gAppInfo = null;
+
+function createAppInfo(id, name, version, platformVersion) {
+  gAppInfo = {
+    // nsIXULAppInfo
+    vendor: "Mozilla",
+    name: name,
+    ID: id,
+    version: version,
+    appBuildID: "2007010101",
+    platformVersion: platformVersion,
+    platformBuildID: "2007010101",
+
+    QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIXULAppInfo,
+                                           Components.interfaces.nsISupports])
+  };
+
+  var XULAppInfoFactory = {
+    createInstance: function (outer, iid) {
+      if (outer != null)
+        throw Components.results.NS_ERROR_NO_AGGREGATION;
+      return gAppInfo.QueryInterface(iid);
+    }
+  };
+  var registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+  registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo",
+                            XULAPPINFO_CONTRACTID, XULAppInfoFactory);
+
+}
+
+const gTestArray =
+[
+  function updateInbox() {
+    let rootFolder = gIMAPIncomingServer.rootFolder;
+    gIMAPInbox.updateFolderWithListener(null, UrlListener);
+  },
+  function checkIDHandling() {
+    do_check_eq(gIMAPDaemon.clientID, "(\"name\" \"XPCShell\" \"version\" \"5\")");
+    do_check_eq(gIMAPIncomingServer.serverIDPref, kIDResponse);
+    doTest(++gTest);
+  },
+]
+
+function run_test()
+{
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "5", "2.0");
+  gTest = 0;
+  loadLocalMailAccount();
+
+  /*
+   * Set up an IMAP server.
+   */
+  gIMAPDaemon = new imapDaemon();
+  gIMAPDaemon.idResponse = kIDResponse;
+
+  gServer = makeServer(gIMAPDaemon, "GMail");
+  gIMAPIncomingServer = createLocalIMAPServer();
+  gIMAPIncomingServer.maximumConnectionsNumber = 1;
+
+  // We need an identity so that updateFolder doesn't fail
+  let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
+  .getService(Ci.nsIMsgAccountManager);
+  let localAccount = acctMgr.createAccount();
+  let identity = acctMgr.createIdentity();
+  localAccount.addIdentity(identity);
+  localAccount.defaultIdentity = identity;
+  localAccount.incomingServer = gLocalIncomingServer;
+  acctMgr.defaultAccount = localAccount;
+  
+  // Let's also have another account, using the same identity
+  let imapAccount = acctMgr.createAccount();
+  imapAccount.addIdentity(identity);
+  imapAccount.defaultIdentity = identity;
+  imapAccount.incomingServer = gIMAPIncomingServer;
+  
+  // pref tuning: one connection only, turn off notifications
+  let prefBranch = Cc["@mozilla.org/preferences-service;1"]
+                     .getService(Ci.nsIPrefBranch);
+  prefBranch.setBoolPref("mail.biff.play_sound", false);
+  prefBranch.setBoolPref("mail.biff.show_alert", false);
+  prefBranch.setBoolPref("mail.biff.show_tray_icon", false);
+  prefBranch.setBoolPref("mail.biff.animate_dock_icon", false);
+
+  do_test_pending();
+
+  // Get the IMAP inbox...
+  let rootFolder = gIMAPIncomingServer.rootFolder;
+  gIMAPInbox = rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox)
+                         .QueryInterface(Ci.nsIMsgImapMailFolder);
+
+  // update folder to kick start tests.
+  gIMAPInbox.updateFolderWithListener(null, UrlListener);
+}
+
+var UrlListener = 
+{
+  OnStartRunningUrl: function(url) { },
+  OnStopRunningUrl: function(url, rc)
+  {
+    // Check for ok status.
+    do_check_eq(rc, 0);
+    // chain tests with interval in between so we'll go idle.
+    do_timeout_function(100, function(){doTest(++gTest)});
+  }
+};
+
+function doTest(test)
+{
+  if (test <= gTestArray.length)
+  {
+    dump("Doing test " + test + "\n");
+    gTest = test;
+
+    var testFn = gTestArray[test - 1];
+    // Set a limit of ten seconds; if the notifications haven't arrived by then there's a problem.
+    try {
+      testFn();
+    } catch(ex) {
+      gServer.stop();
+      do_throw ('TEST FAILED ' + ex);
+    }
+  }
+  else
+  {
+    do_timeout_function(1000, endTest);
+  }
+}
+
+function endTest()
+{
+  gIMAPIncomingServer.closeCachedConnections();
+  gServer.stop();
+
+  var thread = gThreadManager.currentThread;
+  while (thread.hasPendingEvents())
+    thread.processNextEvent(true);
+
+  do_test_finished();
+}
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -415,17 +415,18 @@ pref("mail.server.default.isSecure", fal
 pref("mail.server.default.authMethod", 3); // cleartext password. @see nsIMsgIncomingServer.authMethod.
 pref("mail.server.default.socketType", 0); // @see nsIMsgIncomingServer.socketType
 pref("mail.server.default.override_namespaces", true);
 pref("mail.server.default.deferred_to_account", "");
 
 pref("mail.server.default.delete_model", 1);
 pref("mail.server.default.fetch_by_chunks", true);
 pref("mail.server.default.mime_parts_on_demand", true);
-
+// Send IMAP RFC 2971 ID Info to server
+pref("mail.server.default.send_client_info", true);
 pref("mail.server.default.always_authenticate", false);
 pref("mail.server.default.singleSignon", true);
 pref("mail.server.default.max_articles", 500);
 pref("mail.server.default.notify.on", true);
 pref("mail.server.default.mark_old_read", false);
 pref("mail.server.default.empty_trash_on_exit", false);
 // 0 = Keep Dupes, leave them alone
 // 1 = delete dupes
--- a/mailnews/test/fakeserver/imapd.js
+++ b/mailnews/test/fakeserver/imapd.js
@@ -40,16 +40,17 @@
 // |   a synchronizing function to allow manipulating code the chance to      //
 // |   perform various (potentially expensive) actions.                       //
 // + Messages: A message is represented internally as an annotated URI.       //
 ////////////////////////////////////////////////////////////////////////////////
 function imapDaemon(flags, syncFunc) {
   this._flags = flags;
 
   this.namespaces = [];
+  this.idResponse = "NIL";
   this.root = new imapMailbox("", null, {type : IMAP_NAMESPACE_PERSONAL});
   this.uidvalidity = Math.round(Date.now()/1000);
   this.inbox = new imapMailbox("INBOX", null, this.uidvalidity++);
   this.root.addMailbox(this.inbox);
   this.namespaces.push(this.root);
   this.syncFunc = syncFunc;
 }
 imapDaemon.prototype = {
@@ -1597,16 +1598,17 @@ IMAP_RFC3501_handler.prototype = {
 var configurations = {
   Cyrus: ["RFC2342", "RFC2195"],
   UW: ["RFC2342", "RFC2195"],
   Dovecot: ["RFC2195"],
   Zimbra: ["RFC2342", "RFC2195"],
   Exchange: ["RFC2342", "RFC2195"],
   LEMONADE: ["RFC2342", "RFC2195"],
   CUSTOM1: ["RFCMOVE", "RFC4315"],
+  GMail: ["RFC2197", "RFC4315"]
 };
 
 function mixinExtension(handler, extension) {
   if (extension.preload)
     extension.preload(handler);
 
   for (var property in extension) {
     if (property == 'preload')
@@ -1692,16 +1694,38 @@ var IMAP_RFCMOVE_extension = {
   },
   kCapabilities: ["MOVE"],
   kUidCommands: ["MOVE"],
   _argFormat: { MOVE: ["number", "mailbox"] },
   // Enabled in SELECTED state
   _enabledCommands: { 2: ["MOVE"] }
 };
 
+// RFC 2197: ID
+var IMAP_RFC2197_extension = {
+  ID: function (args) {
+    let clientID = "(";
+    for each (let i in args)
+      clientID += "\"" + i + "\"";
+
+    clientID += ")";
+    let clientStrings = clientID.split(",");
+    clientID = "";
+    for each (let i in clientStrings)
+      clientID += "\"" + i + "\" "
+    clientID = clientID.slice(1, clientID.length - 3);
+    clientID += ")";
+    this._daemon.clientID = clientID;
+    return "* ID " + this._daemon.idResponse + "\0OK Success";
+  },
+  kCapabilities: ["ID"],
+  _argFormat: { ID: ["(string)"] },
+  _enabledCommands : { 1 : ["ID"], 2 : ["ID"] }
+};
+
 // 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;
   },