Bug 560793 - Hook up NNTP to the msgAsyncPrompt service. r=Standard8
--- 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);
}