Bug 560793 - Hook up NNTP to the msgAsyncPrompt service. r=Standard8
authorJoshua Cranmer <Pidgeot18@gmail.com>
Wed, 22 Feb 2012 11:13:35 -0600
changeset 10915 841928c4a46cc90c48a230d12edd382feecc6d15
parent 10914 5782e865d67de7590ccd8af428b6521bd75f0447
child 10916 acc5c71ae5a39c895f56676788b808be3ff4067c
push id463
push userbugzilla@standard8.plus.com
push dateTue, 24 Apr 2012 17:34:51 +0000
treeherdercomm-beta@e53588e8f7b0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs560793
Bug 560793 - Hook up NNTP to the msgAsyncPrompt service. r=Standard8
mailnews/news/src/nsNNTPProtocol.cpp
mailnews/news/src/nsNNTPProtocol.h
mailnews/news/src/nsNewsFolder.cpp
mailnews/news/test/unit/test_uriParser.js
--- a/mailnews/news/src/nsNNTPProtocol.cpp
+++ b/mailnews/news/src/nsNNTPProtocol.cpp
@@ -229,17 +229,17 @@ const char *const stateLabels[] = {
 "NNTP_LIST_XACTIVE",
 "NNTP_LIST_GROUP",
 "NNTP_LIST_GROUP_RESPONSE",
 "NEWS_DONE",
 "NEWS_POST_DONE",
 "NEWS_ERROR",
 "NNTP_ERROR",
 "NEWS_FREE",
-"NEWS_FINISHED"
+"NNTP_SUSPENDED"
 };
 
 
 /* end logging */
 
 /* Forward declarations */
 
 #define LIST_WANTED     0
@@ -290,24 +290,18 @@ char *MSG_UnEscapeSearchUrl (const char 
   }
   return ToNewCString(result);
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////
 // END OF TEMPORARY HARD CODED FUNCTIONS
 ///////////////////////////////////////////////////////////////////////////////////////////
 
-NS_IMPL_ADDREF_INHERITED(nsNNTPProtocol, nsMsgProtocol)
-NS_IMPL_RELEASE_INHERITED(nsNNTPProtocol, nsMsgProtocol)
-
-NS_INTERFACE_MAP_BEGIN(nsNNTPProtocol)
-  NS_INTERFACE_MAP_ENTRY(nsINNTPProtocol)
-  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
-  NS_INTERFACE_MAP_ENTRY(nsICacheListener)
-NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol)
+NS_IMPL_ISUPPORTS_INHERITED4(nsNNTPProtocol, nsMsgProtocol, nsINNTPProtocol,
+  nsITimerCallback, nsICacheListener, nsIMsgAsyncPromptListener)
 
 nsNNTPProtocol::nsNNTPProtocol(nsINntpIncomingServer *aServer, nsIURI *aURL,
                                nsIMsgWindow *aMsgWindow)
 : nsMsgProtocol(aURL),
   m_connectionBusy(false),
   m_nntpServer(aServer)
 {
   if (!NNTP)
@@ -2382,37 +2376,63 @@ PRInt32 nsNNTPProtocol::BeginAuthorizati
         m_newsFolder = do_QueryInterface(rootFolder);
       }
     }
   }
 
   NS_ASSERTION(m_newsFolder, "no m_newsFolder");
   if (!m_newsFolder)
     return MK_NNTP_AUTH_FAILED;
-  
-  // Force-grab the authentication details...
-  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
-  if (!m_msgWindow && mailnewsurl)
-    mailnewsurl->GetMsgWindow(getter_AddRefs(m_msgWindow));
-  bool validCredentials = false;
-  rv = m_newsFolder->GetAuthenticationCredentials(m_msgWindow, true, false,
-    &validCredentials);
-  if (NS_FAILED(rv) || !validCredentials)
-    return MK_NNTP_AUTH_FAILED;
-
-  nsCString username;
+
+  // We want to get authentication credentials, but it is possible that the
+  // master password prompt will end up being synchronous. In that case, check
+  // to see if we already have the credentials stored.
+  nsCString username, password;
   rv = m_newsFolder->GetGroupUsername(username);
-  if (NS_FAILED(rv) || username.IsEmpty())
-    return MK_NNTP_AUTH_FAILED;
+  NS_ENSURE_SUCCESS(rv, MK_NNTP_AUTH_FAILED);
+  rv = m_newsFolder->GetGroupPassword(password);
+  NS_ENSURE_SUCCESS(rv, MK_NNTP_AUTH_FAILED);
+
+  // If we don't have either a username or a password, queue an asynchronous
+  // prompt.
+  if (username.IsEmpty() || password.IsEmpty())
+  {
+    nsCOMPtr<nsIMsgAsyncPrompter> asyncPrompter =
+      do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, MK_NNTP_AUTH_FAILED);
+
+    // Get the key to coalesce auth prompts.
+    bool singleSignon = false;
+    m_nntpServer->GetSingleSignon(&singleSignon);
+    
+    nsCString queueKey;
+    nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer);
+    server->GetKey(queueKey);
+    if (!singleSignon)
+    {
+      nsCString groupName;
+      m_newsFolder->GetRawName(groupName);
+      queueKey += groupName;
+    }
+
+    rv = asyncPrompter->QueueAsyncAuthPrompt(queueKey, false, this);
+    NS_ENSURE_SUCCESS(rv, MK_NNTP_AUTH_FAILED);
+
+    m_nextState = NNTP_SUSPENDED;
+    if (m_request)
+      m_request->Suspend();
+    return 0;
+  }
 
   NS_MsgSACopy(&command, "AUTHINFO user ");
   PR_LOG(NNTP, PR_LOG_ALWAYS,("(%p) use %s as the username", this, username.get()));
   NS_MsgSACat(&command, username.get());
   NS_MsgSACat(&command, CRLF);
 
+  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
   if (mailnewsurl)
     status = SendData(mailnewsurl, command);
 
   PR_Free(command);
 
   m_nextState = NNTP_RESPONSE;
   m_nextStateAfterResponse = NNTP_AUTHORIZE_RESPONSE;
 
@@ -2533,16 +2553,65 @@ PRInt32 nsNNTPProtocol::PasswordResponse
     HandleAuthenticationFailure();
     return(MK_NNTP_AUTH_FAILED);
   }
 
   NS_ERROR("should never get here");
   return(-1);
 }
 
+NS_IMETHODIMP nsNNTPProtocol::OnPromptStart(bool *authAvailable)
+{
+  NS_ENSURE_ARG_POINTER(authAvailable);
+  NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
+  
+  if (!m_newsFolder)
+  {
+    // If we don't have a news folder, we may have been closed already.
+    NNTP_LOG_NOTE("Canceling queued authentication prompt");
+    *authAvailable = false;
+    return NS_OK;
+  }
+
+  nsresult rv = m_newsFolder->GetAuthenticationCredentials(m_msgWindow,
+    true, false, authAvailable);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // What we do depends on whether or not we have valid credentials
+  return *authAvailable ? OnPromptAuthAvailable() : OnPromptCanceled();
+}
+
+NS_IMETHODIMP nsNNTPProtocol::OnPromptAuthAvailable()
+{
+  NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
+
+  // We previously suspended the request; now resume it to read input
+  if (m_request)
+    m_request->Resume();
+
+  // Now we have our password details accessible from the group, so just call
+  // into the state machine to start the process going again.
+  m_nextState = NNTP_BEGIN_AUTHORIZE;
+  return ProcessProtocolState(nsnull, nsnull, 0, 0);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::OnPromptCanceled()
+{
+  NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
+ 
+  // We previously suspended the request; now resume it to read input
+  if (m_request)
+    m_request->Resume();
+
+  // Since the prompt was canceled, we can no longer continue the connection.
+  // Thus, we need to go to the NNTP_ERROR state.
+  m_nextState = NNTP_ERROR;
+  return ProcessProtocolState(nsnull, nsnull, 0, 0);
+}
+
 PRInt32 nsNNTPProtocol::DisplayNewsgroups()
 {
   m_nextState = NEWS_DONE;
   ClearFlag(NNTP_PAUSE_FOR_READ);
 
   PR_LOG(NNTP,PR_LOG_ALWAYS,("(%p) DisplayNewsgroups()",this));
 
   return(MK_DATA_LOADED);  /* all finished */
@@ -2880,17 +2949,17 @@ PRInt32 nsNNTPProtocol::ReadNewsList(nsI
     rv = mUpdateTimer->InitWithCallback(static_cast<nsITimerCallback*>(this), kUpdateTimerDelay,
       nsITimer::TYPE_ONE_SHOT);
     NS_ASSERTION(NS_SUCCEEDED(rv),"failed to init timer");
     if (NS_FAILED(rv)) {
       PR_Free(lineToFree);
       return -1;
     }
 
-    m_nextState = NEWS_FINISHED;
+    m_nextState = NNTP_SUSPENDED;
 
     // suspend necko request until timeout
     // might not have a request if someone called CloseSocket()
     // see bug #195440
     if (m_request)
       m_request->Suspend();
   }
 
@@ -4691,17 +4760,17 @@ nsresult nsNNTPProtocol::ProcessProtocol
       */
       FinishMemCacheEntry(false);  // cleanup mem cache entry
       if (m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NOTFOUND && m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NONEXIST)
         return CloseConnection();
     case NEWS_FREE:
       // Remember when we last used this connection
       m_lastActiveTimeStamp = PR_Now();
       CleanupAfterRunningUrl();
-    case NEWS_FINISHED:
+    case NNTP_SUSPENDED:
       return NS_OK;
       break;
     default:
       /* big error */
       return NS_ERROR_FAILURE;
 
     } // end switch
 
--- a/mailnews/news/src/nsNNTPProtocol.h
+++ b/mailnews/news/src/nsNNTPProtocol.h
@@ -44,16 +44,17 @@
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsINntpUrl.h"
 #include "nsINntpIncomingServer.h"
 #include "nsINNTPProtocol.h"
 
 #include "nsINNTPNewsgroupList.h"
 #include "nsINNTPArticleList.h"
+#include "nsIMsgAsyncPrompter.h"
 #include "nsIMsgNewsFolder.h"
 #include "nsIMsgWindow.h"
 
 #include "nsMsgLineBuffer.h"
 #include "nsIStringBundle.h"
 #include "nsITimer.h"
 #include "nsICacheListener.h"
 
@@ -141,30 +142,33 @@ NNTP_LIST_XACTIVE,
 NNTP_LIST_XACTIVE_RESPONSE,
 NNTP_LIST_GROUP,
 NNTP_LIST_GROUP_RESPONSE,
 NEWS_DONE,
 NEWS_POST_DONE,
 NEWS_ERROR,
 NNTP_ERROR,
 NEWS_FREE,
-NEWS_FINISHED
+NNTP_SUSPENDED
 } StatesEnum;
 
 class nsICacheEntryDescriptor;
 
-class nsNNTPProtocol : public nsINNTPProtocol, public nsITimerCallback, public nsICacheListener, public nsMsgProtocol
+class nsNNTPProtocol : public nsMsgProtocol,
+                       public nsINNTPProtocol,
+                       public nsITimerCallback,
+                       public nsICacheListener,
+                       public nsIMsgAsyncPromptListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
-    NS_DECL_NSINNTPPROTOCOL
-    NS_DECL_NSICACHELISTENER
-
-    // nsITimerCallback interfaces
-    NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSINNTPPROTOCOL
+  NS_DECL_NSICACHELISTENER
+  NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSIMSGASYNCPROMPTLISTENER
 
   // Creating a protocol instance requires the URL
   // need to call Initialize after we do a new of nsNNTPProtocol
   nsNNTPProtocol(nsINntpIncomingServer *aServer, nsIURI *aURL,
                  nsIMsgWindow *aMsgWindow);
   virtual ~nsNNTPProtocol();
 
   // stop binding is a "notification" informing us that the stream associated with aURL is going away.
@@ -176,17 +180,34 @@ public:
   NS_IMETHOD GetContentType(nsACString &aContentType);
   NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt);
   NS_IMETHOD GetOriginalURI(nsIURI* *aURI);
   NS_IMETHOD SetOriginalURI(nsIURI* aURI);
 
   nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer);
 
 private:
-  // over-rides from nsMsgProtocol
+  /**
+   * Triggers the protocol state machine.
+   * Most of the time, this machine will read as much input as it can before
+   * closing.
+   *
+   * This method additionally handles some states not covered by other methods:
+   * NEWS_DONE: Alias for NEWS_FREE
+   * NEWS_POST_DONE: Alias for NEWS_FREE that cleans up the URL state
+   * NEWS_ERROR: An error which permits further use of the connection
+   * NNTP_ERROR: An error which does not permit further use of the connection
+   * NEWS_FREE: Cleans up from the current URL and prepares for the next one
+   * NNTP_SUSPENDED: A state where the state machine does not read input until
+   *                 reenabled by a non-network related callback
+   *
+   * @note Use of NNTP_SUSPENDED is dangerous: if input comes along the socket,
+   * the code will not read the input stream at all. Therefore, it is strongly
+   * advised to suspend the request before using this state.
+   */
   virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
     PRUint32 sourceOffset, PRUint32 length);
   virtual nsresult CloseSocket();
 
   // we have our own implementation of SendData which writes to the nntp log
   // and then calls the base class to transmit the data
   PRInt32 SendData(nsIURI * aURL, const char * dataBuffer, bool aSuppressLogging = false);
 
@@ -317,19 +338,44 @@ private:
   PRInt32 SendGroupForArticle();
   PRInt32 SendGroupForArticleResponse();
 
   PRInt32 SendArticleNumber();
   PRInt32 BeginArticle();
   PRInt32 ReadArticle(nsIInputStream * inputStream, PRUint32 length);
   PRInt32 DisplayArticle(nsIInputStream * inputStream, PRUint32 length);
 
+  //////////////////////////////////////////////////////////////////////////////
+  // News authentication code
+  //////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Sends the username via AUTHINFO USER, NNTP_BEGIN_AUTHORIZE.
+   * This also handles the step of getting authentication credentials; if this
+   * requires a prompt to run, the connection will be suspended and we will
+   * come back to this point.
+   * Followed by: NNTP_AUTHORIZE_RESPONSE if the username was sent
+   *              NNTP_SUSPENDED          if we need to wait for the password
+   */
   PRInt32 BeginAuthorization();
+  /**
+   * Sends the password if necessary, the state NNTP_AUTHORIZE_RESPONSE.
+   * This also reads the result of the username.
+   * Followed by: NNTP_PASSWORD_RESPONSE  if a password is sent
+   *              NNTP_SEND_MODE_READER   if MODE READER needed auth
+   *              SEND_FIRST_NNTP_COMMAND if any other command needed auth
+   *              NNTP_ERROR              if the username was rejected
+   */
   PRInt32 AuthorizationResponse();
-
+  /**
+   * This state, NNTP_PASSWORD_RESPONSE, reads the password.
+   * Followed by: NNTP_SEND_MODE_READER   if MODE READER needed auth
+   *              SEND_FIRST_NNTP_COMMAND if any other command needed auth
+   *              NNTP_ERROR              if the password was rejected
+   */
   PRInt32 PasswordResponse();
 
   PRInt32 BeginReadNewsList();
   PRInt32 ReadNewsList(nsIInputStream * inputStream, PRUint32 length);
 
   // Newsgroup specific protocol handlers
   PRInt32 DisplayNewsgroups();
   PRInt32 BeginNewsgroups();
--- a/mailnews/news/src/nsNewsFolder.cpp
+++ b/mailnews/news/src/nsNewsFolder.cpp
@@ -85,39 +85,88 @@
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsArrayEnumerator.h"
 #include "nsNewsDownloader.h"
 #include "nsIStringBundle.h"
 #include "nsMsgI18N.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsIMsgAccountManager.h"
 #include "nsArrayUtils.h"
+#include "nsIMsgAsyncPrompter.h"
 #include "nsIMsgFolderNotificationService.h"
 #include "nsIMutableArray.h"
 #include "nsILoginInfo.h"
 #include "nsILoginManager.h"
 #include "nsIPromptService.h"
 #include "nsEmbedCID.h"
 #include "nsIDOMWindow.h"
 #include "mozilla/Services.h"
+#include "nsAutoPtr.h"
 
 static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
 
 // ###tw  This really ought to be the most
 // efficient file reading size for the current
 // operating system.
 #define NEWSRC_FILE_BUFFER_SIZE 1024
 
 #define kNewsSortOffset 9000
 
 #define NEWS_SCHEME "news:"
 #define SNEWS_SCHEME "snews:"
 
 ////////////////////////////////////////////////////////////////////////////////
 
+namespace {
+class AsyncAuthMigrator : public nsIMsgAsyncPromptListener {
+public:
+  AsyncAuthMigrator(nsIMsgNewsFolder *folder) : m_folder(folder) {}
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIMSGASYNCPROMPTLISTENER
+
+  void EnqueuePrompt();
+
+private:
+  nsCOMPtr<nsIMsgNewsFolder> m_folder;
+};
+
+NS_IMPL_ISUPPORTS1(AsyncAuthMigrator, nsIMsgAsyncPromptListener)
+
+NS_IMETHODIMP AsyncAuthMigrator::OnPromptStart(bool *retval)
+{
+  *retval = true;
+  return m_folder->MigrateLegacyCredentials();
+}
+
+NS_IMETHODIMP AsyncAuthMigrator::OnPromptAuthAvailable()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP AsyncAuthMigrator::OnPromptCanceled()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void AsyncAuthMigrator::EnqueuePrompt()
+{
+  nsCOMPtr<nsIMsgAsyncPrompter> prompter =
+    do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID);
+
+  // Make up a fake unique key to prevent coalescing of prompts
+  // The address of this object should be sufficient
+  nsCAutoString queueKey;
+  queueKey.AppendInt((PRInt32)(PRUint64)this);
+  prompter->QueueAsyncAuthPrompt(queueKey, false, this);
+}
+
+}
+
+ 
 
 ////////////////////////////////////////////////////////////////////////////////
 
 nsMsgNewsFolder::nsMsgNewsFolder(void) :
      mExpungedBytes(0), mGettingNews(false),
     mInitialized(false),
     m_downloadMessageForOfflineUse(false), m_downloadingMultipleMessages(false),
     mReadSet(nsnull)
@@ -216,17 +265,18 @@ nsMsgNewsFolder::AddNewsgroup(const nsAC
 
   nsCOMPtr<nsIMsgNewsFolder> newsFolder(do_QueryInterface(res, &rv));
   if (NS_FAILED(rv)) return rv;
 
   // cache this for when we open the db
   rv = newsFolder->SetReadSetFromStr(setStr);
 
   // I don't have a good time to do this, but this is as good as any...
-  newsFolder->MigrateLegacyCredentials();
+  nsRefPtr<AsyncAuthMigrator> delayedPrompt(new AsyncAuthMigrator(newsFolder));
+  delayedPrompt->EnqueuePrompt();
 
   rv = folder->SetParent(this);
   NS_ENSURE_SUCCESS(rv,rv);
 
   // this what shows up in the UI
   rv = folder->SetName(nameUtf16);
   NS_ENSURE_SUCCESS(rv,rv);
 
@@ -1229,16 +1279,20 @@ nsMsgNewsFolder::MigrateLegacyCredential
   if (count > 0)
   {
     rv = logins[0]->GetPassword(password);
     loginMgr->RemoveLogin(logins[0]);
   }
   NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // If there is nothing to migrate, then do othing
+  if (username.IsEmpty() && password.IsEmpty())
+    return NS_OK;
+
   // Make and add the new logon
   nsCOMPtr<nsILoginInfo> newLogin = do_CreateInstance(NS_LOGININFO_CONTRACTID);
   // We need to pass in JS equivalent to "null"; empty ("") isn't good enough
   nsString voidString;
   voidString.SetIsVoid(true);
   newLogin->Init(signonUrl, voidString, signonUrl, username, password,
     EmptyString(), EmptyString());
   return loginMgr->AddLogin(newLogin);
--- a/mailnews/news/test/unit/test_uriParser.js
+++ b/mailnews/news/test/unit/test_uriParser.js
@@ -158,9 +158,16 @@ function run_test() {
     try {
       dump("Checking URL " + fail + " for failure\n");
       nntpService.newURI(fail, null, null);
       do_check_true(false);
     } catch (e) {
       do_check_eq(e.result, Components.results.NS_ERROR_MALFORMED_URI);
     }
   }
+
+  // The password migration is async, so trigger an event to prevent the logon
+  // manager from trying to migrate after shutdown has started.
+  var gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+  var thread = gThreadManager.currentThread;
+  while (thread.hasPendingEvents())
+    thread.processNextEvent(true);
 }