diff options
Diffstat (limited to 'mailnews/local/src/nsPop3Protocol.cpp')
-rw-r--r-- | mailnews/local/src/nsPop3Protocol.cpp | 4176 |
1 files changed, 4176 insertions, 0 deletions
diff --git a/mailnews/local/src/nsPop3Protocol.cpp b/mailnews/local/src/nsPop3Protocol.cpp new file mode 100644 index 000000000..825f45ab3 --- /dev/null +++ b/mailnews/local/src/nsPop3Protocol.cpp @@ -0,0 +1,4176 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. + * + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Jason Eager <jce2@po.cwru.edu> + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + * 06/07/2000 Jason Eager Added check for out of disk space + */ + +#include "nscore.h" +#include "msgCore.h" // precompiled header... +#include "nsNetUtil.h" +#include "nspr.h" +#include "plbase64.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsISafeOutputStream.h" +#include "nsPop3Protocol.h" +#include "MailNewsTypes.h" +#include "nsStringGlue.h" +#include "nsIPrompt.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMsgPluggableStore.h" +#include "nsTextFormatter.h" +#include "nsCOMPtr.h" +#include "nsIMsgWindow.h" +#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later... +#include "nsIMsgLocalMailFolder.h" +#include "nsIDocShell.h" +#include "nsMsgUtils.h" +#include "nsISocketTransport.h" +#include "nsISSLSocketControl.h" +#include "nsILineInputStream.h" +#include "nsIInterfaceRequestor.h" +#include "nsMsgMessageFlags.h" +#include "nsMsgBaseCID.h" +#include "nsIProxyInfo.h" +#include "nsCRT.h" +#include "mozilla/Services.h" +#include "mozilla/Logging.h" +#include "mozilla/Attributes.h" + +using namespace mozilla; + +PRLogModuleInfo *POP3LOGMODULE = nullptr; +#define POP3LOG(str) "%s: [this=%p] " str, POP3LOGMODULE->name, this + +static int +net_pop3_remove_messages_marked_delete(PLHashEntry* he, + int msgindex, + void *arg) +{ + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + return (uidlEntry->status == DELETE_CHAR) + ? HT_ENUMERATE_REMOVE : HT_ENUMERATE_NEXT; +} + +uint32_t TimeInSecondsFromPRTime(PRTime prTime) +{ + return (uint32_t)(prTime / PR_USEC_PER_SEC); +} + +static void +put_hash(PLHashTable* table, const char* key, char value, uint32_t dateReceived) +{ + // don't put not used slots or empty uid into hash + if (key && *key) + { + Pop3UidlEntry* tmp = PR_NEWZAP(Pop3UidlEntry); + if (tmp) + { + tmp->uidl = PL_strdup(key); + if (tmp->uidl) + { + tmp->dateReceived = dateReceived; + tmp->status = value; + PL_HashTableAdd(table, (const void *)tmp->uidl, (void*) tmp); + } + else + PR_Free(tmp); + } + } +} + +static int +net_pop3_copy_hash_entries(PLHashEntry* he, int msgindex, void *arg) +{ + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + put_hash((PLHashTable *) arg, uidlEntry->uidl, uidlEntry->status, uidlEntry->dateReceived); + return HT_ENUMERATE_NEXT; +} + +static void * +AllocUidlTable(void * /* pool */, size_t size) +{ + return PR_MALLOC(size); +} + +static void +FreeUidlTable(void * /* pool */, void *item) +{ + PR_Free(item); +} + +static PLHashEntry * +AllocUidlInfo(void *pool, const void *key) +{ + return PR_NEWZAP(PLHashEntry); +} + +static void +FreeUidlInfo(void * /* pool */, PLHashEntry *he, unsigned flag) +{ + if (flag == HT_FREE_ENTRY) + { + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + if (uidlEntry) + { + PR_Free(uidlEntry->uidl); + PR_Free(uidlEntry); + } + PR_Free(he); + } +} + +static PLHashAllocOps gHashAllocOps = { + AllocUidlTable, FreeUidlTable, + AllocUidlInfo, FreeUidlInfo +}; + + +static Pop3UidlHost* +net_pop3_load_state(const char* searchhost, + const char* searchuser, + nsIFile *mailDirectory) +{ + Pop3UidlHost* result = nullptr; + Pop3UidlHost* current = nullptr; + Pop3UidlHost* tmp; + + result = PR_NEWZAP(Pop3UidlHost); + if (!result) + return nullptr; + result->host = PL_strdup(searchhost); + result->user = PL_strdup(searchuser); + result->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, &gHashAllocOps, nullptr); + + if (!result->host || !result->user || !result->hash) + { + PR_Free(result->host); + PR_Free(result->user); + if (result->hash) + PL_HashTableDestroy(result->hash); + PR_Free(result); + return nullptr; + } + + nsCOMPtr <nsIFile> popState; + mailDirectory->Clone(getter_AddRefs(popState)); + if (!popState) + return nullptr; + popState->AppendNative(NS_LITERAL_CSTRING("popstate.dat")); + + nsCOMPtr<nsIInputStream> fileStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), popState); + NS_ENSURE_SUCCESS(rv, result); + + nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv)); + NS_ENSURE_SUCCESS(rv, result); + + bool more = true; + nsCString line; + + while (more && NS_SUCCEEDED(rv)) + { + lineInputStream->ReadLine(line, &more); + if (line.IsEmpty()) + continue; + char firstChar = line.CharAt(0); + if (firstChar == '#') + continue; + if (firstChar == '*') { + /* It's a host&user line. */ + current = nullptr; + char *lineBuf = line.BeginWriting() + 1; // ok because we know the line isn't empty + char *host = NS_strtok(" \t\r\n", &lineBuf); + /* without space to also get realnames - see bug 225332 */ + char *user = NS_strtok("\t\r\n", &lineBuf); + if (!host || !user) + continue; + for (tmp = result ; tmp ; tmp = tmp->next) + { + if (!strcmp(host, tmp->host) && !strcmp(user, tmp->user)) + { + current = tmp; + break; + } + } + if (!current) + { + current = PR_NEWZAP(Pop3UidlHost); + if (current) + { + current->host = strdup(host); + current->user = strdup(user); + current->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, &gHashAllocOps, nullptr); + if (!current->host || !current->user || !current->hash) + { + PR_Free(current->host); + PR_Free(current->user); + if (current->hash) + PL_HashTableDestroy(current->hash); + PR_Free(current); + } + else + { + current->next = result->next; + result->next = current; + } + } + } + } + else + { + /* It's a line with a UIDL on it. */ + if (current) + { + for (int32_t pos = line.FindChar('\t'); pos != -1; pos = line.FindChar('\t', pos)) + line.Replace(pos, 1, ' '); + + nsTArray<nsCString> lineElems; + ParseString(line, ' ', lineElems); + if (lineElems.Length() < 2) + continue; + nsCString *flags = &lineElems[0]; + nsCString *uidl = &lineElems[1]; + uint32_t dateReceived = TimeInSecondsFromPRTime(PR_Now()); // if we don't find a date str, assume now. + if (lineElems.Length() > 2) + dateReceived = atoi(lineElems[2].get()); + if (!flags->IsEmpty() && !uidl->IsEmpty()) + { + char flag = flags->CharAt(0); + if ((flag == KEEP) || (flag == DELETE_CHAR) || + (flag == TOO_BIG) || (flag == FETCH_BODY)) + { + put_hash(current->hash, uidl->get(), flag, dateReceived); + } + else + { + NS_ASSERTION(false, "invalid flag in popstate.dat"); + } + } + } + } + } + fileStream->Close(); + + return result; +} + +static int +hash_clear_mapper(PLHashEntry* he, int msgindex, void* arg) +{ + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + PR_Free(uidlEntry->uidl); + PR_Free(uidlEntry); + he->value = nullptr; + + return HT_ENUMERATE_REMOVE; +} + +static int +hash_empty_mapper(PLHashEntry* he, int msgindex, void* arg) +{ + *((bool*) arg) = false; + return HT_ENUMERATE_STOP; +} + +static bool +hash_empty(PLHashTable* hash) +{ + bool result = true; + PL_HashTableEnumerateEntries(hash, hash_empty_mapper, (void *)&result); + return result; +} + + +static int +net_pop3_write_mapper(PLHashEntry* he, int msgindex, void* arg) +{ + nsIOutputStream* file = (nsIOutputStream*) arg; + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + NS_ASSERTION((uidlEntry->status == KEEP) || + (uidlEntry->status == DELETE_CHAR) || + (uidlEntry->status == FETCH_BODY) || + (uidlEntry->status == TOO_BIG), "invalid status"); + char* tmpBuffer = PR_smprintf("%c %s %d" MSG_LINEBREAK, uidlEntry->status, (char*) + uidlEntry->uidl, uidlEntry->dateReceived); + PR_ASSERT(tmpBuffer); + uint32_t numBytesWritten; + file->Write(tmpBuffer, strlen(tmpBuffer), &numBytesWritten); + PR_Free(tmpBuffer); + return HT_ENUMERATE_NEXT; +} + +static int +net_pop3_delete_old_msgs_mapper(PLHashEntry* he, int msgindex, void* arg) +{ + PRTime cutOffDate = (PRTime) arg; + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) he->value; + if (uidlEntry->dateReceived < cutOffDate) + uidlEntry->status = DELETE_CHAR; // mark for deletion + return HT_ENUMERATE_NEXT; +} + +static void +net_pop3_write_state(Pop3UidlHost* host, nsIFile *mailDirectory) +{ + int32_t len = 0; + nsCOMPtr <nsIFile> popState; + + mailDirectory->Clone(getter_AddRefs(popState)); + if (!popState) + return; + popState->AppendNative(NS_LITERAL_CSTRING("popstate.dat")); + + nsCOMPtr<nsIOutputStream> fileOutputStream; + nsresult rv = MsgNewSafeBufferedFileOutputStream(getter_AddRefs(fileOutputStream), popState, -1, 00600); + if (NS_FAILED(rv)) + return; + + const char tmpBuffer[] = + "# POP3 State File" MSG_LINEBREAK + "# This is a generated file! Do not edit." MSG_LINEBREAK + MSG_LINEBREAK; + + uint32_t numBytesWritten; + fileOutputStream->Write(tmpBuffer, strlen(tmpBuffer), &numBytesWritten); + + for (; host && (len >= 0); host = host->next) + { + if (!hash_empty(host->hash)) + { + fileOutputStream->Write("*", 1, &numBytesWritten); + fileOutputStream->Write(host->host, strlen(host->host), &numBytesWritten); + fileOutputStream->Write(" ", 1, &numBytesWritten); + fileOutputStream->Write(host->user, strlen(host->user), &numBytesWritten); + fileOutputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &numBytesWritten); + PL_HashTableEnumerateEntries(host->hash, net_pop3_write_mapper, (void *)fileOutputStream); + } + } + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(fileOutputStream); + NS_ASSERTION(safeStream, "expected a safe output stream!"); + if (safeStream) { + rv = safeStream->Finish(); + if (NS_FAILED(rv)) { + NS_WARNING("failed to save pop state! possible data loss"); + } + } +} + +static void +net_pop3_free_state(Pop3UidlHost* host) +{ + Pop3UidlHost* h; + while (host) + { + h = host->next; + PR_Free(host->host); + PR_Free(host->user); + PL_HashTableDestroy(host->hash); + PR_Free(host); + host = h; + } +} + +/* +Look for a specific UIDL string in our hash tables, if we have it then we need +to mark the message for deletion so that it can be deleted later. If the uidl of the +message is not found, then the message was downloaded completely and already deleted +from the server. So this only applies to messages kept on the server or too big +for download. */ +/* static */ +void nsPop3Protocol::MarkMsgInHashTable(PLHashTable *hashTable, const Pop3UidlEntry *uidlE, bool *changed) +{ + if (uidlE->uidl) + { + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(hashTable, uidlE->uidl); + if (uidlEntry) + { + if (uidlEntry->status != uidlE->status) + { + uidlEntry->status = uidlE->status; + *changed = true; + } + } + } +} + +/* static */ +nsresult +nsPop3Protocol::MarkMsgForHost(const char *hostName, const char *userName, + nsIFile *mailDirectory, + nsTArray<Pop3UidlEntry*> &UIDLArray) +{ + if (!hostName || !userName || !mailDirectory) + return NS_ERROR_NULL_POINTER; + + Pop3UidlHost *uidlHost = net_pop3_load_state(hostName, userName, mailDirectory); + if (!uidlHost) + return NS_ERROR_OUT_OF_MEMORY; + + bool changed = false; + + uint32_t count = UIDLArray.Length(); + for (uint32_t i = 0; i < count; i++) + { + MarkMsgInHashTable(uidlHost->hash, UIDLArray[i], &changed); + } + + if (changed) + net_pop3_write_state(uidlHost, mailDirectory); + net_pop3_free_state(uidlHost); + return NS_OK; +} + + + +NS_IMPL_ADDREF_INHERITED(nsPop3Protocol, nsMsgProtocol) +NS_IMPL_RELEASE_INHERITED(nsPop3Protocol, nsMsgProtocol) + + + +NS_INTERFACE_MAP_BEGIN(nsPop3Protocol) + NS_INTERFACE_MAP_ENTRY(nsIPop3Protocol) + NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener) +NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol) + +// nsPop3Protocol class implementation + +nsPop3Protocol::nsPop3Protocol(nsIURI* aURL) +: nsMsgProtocol(aURL), + m_bytesInMsgReceived(0), + m_totalFolderSize(0), + m_totalDownloadSize(0), + m_totalBytesReceived(0), + m_lineStreamBuffer(nullptr), + m_pop3ConData(nullptr) +{ +} + +nsresult nsPop3Protocol::Initialize(nsIURI * aURL) +{ + nsresult rv = NS_OK; + if (!POP3LOGMODULE) + POP3LOGMODULE = PR_NewLogModule("POP3"); + + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Initialize()"))); + + m_pop3ConData = (Pop3ConData *)PR_NEWZAP(Pop3ConData); + if(!m_pop3ConData) + return NS_ERROR_OUT_OF_MEMORY; + + m_totalBytesReceived = 0; + m_bytesInMsgReceived = 0; + m_totalFolderSize = 0; + m_totalDownloadSize = 0; + m_totalBytesReceived = 0; + m_tlsEnabled = false; + m_socketType = nsMsgSocketType::trySTARTTLS; + m_prefAuthMethods = POP3_AUTH_MECH_UNDEFINED; + m_failedAuthMethods = 0; + m_password_already_sent = false; + m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED; + m_needToRerunUrl = false; + + if (aURL) + { + // extract out message feedback if there is any. + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL); + if (mailnewsUrl) + { + nsCOMPtr<nsIMsgIncomingServer> server; + mailnewsUrl->GetServer(getter_AddRefs(server)); + NS_ENSURE_TRUE(server, NS_MSG_INVALID_OR_MISSING_SERVER); + + rv = server->GetSocketType(&m_socketType); + NS_ENSURE_SUCCESS(rv,rv); + + int32_t authMethod = 0; + rv = server->GetAuthMethod(&authMethod); + NS_ENSURE_SUCCESS(rv,rv); + InitPrefAuthMethods(authMethod); + + m_pop3Server = do_QueryInterface(server); + if (m_pop3Server) + m_pop3Server->GetPop3CapabilityFlags(&m_pop3ConData->capability_flags); + } + + m_url = do_QueryInterface(aURL); + + // When we are making a secure connection, we need to make sure that we + // pass an interface requestor down to the socket transport so that PSM can + // retrieve a nsIPrompt instance if needed. + nsCOMPtr<nsIInterfaceRequestor> ir; + if (m_socketType != nsMsgSocketType::plain) + { + nsCOMPtr<nsIMsgWindow> msgwin; + mailnewsUrl->GetMsgWindow(getter_AddRefs(msgwin)); + if (!msgwin) + GetTopmostMsgWindow(getter_AddRefs(msgwin)); + if (msgwin) + { + nsCOMPtr<nsIDocShell> docshell; + msgwin->GetRootDocShell(getter_AddRefs(docshell)); + ir = do_QueryInterface(docshell); + nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks; + msgwin->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks)); + if (notificationCallbacks) + { + nsCOMPtr<nsIInterfaceRequestor> aggregrateIR; + MsgNewInterfaceRequestorAggregation(notificationCallbacks, ir, getter_AddRefs(aggregrateIR)); + ir = aggregrateIR; + } + } + } + + int32_t port = 0; + nsCString hostName; + aURL->GetPort(&port); + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) + server->GetRealHostName(hostName); + + nsCOMPtr<nsIProxyInfo> proxyInfo; + rv = MsgExamineForProxy(this, getter_AddRefs(proxyInfo)); + if (NS_FAILED(rv)) proxyInfo = nullptr; + + const char *connectionType = nullptr; + if (m_socketType == nsMsgSocketType::SSL) + connectionType = "ssl"; + else if (m_socketType == nsMsgSocketType::trySTARTTLS || + m_socketType == nsMsgSocketType::alwaysSTARTTLS) + connectionType = "starttls"; + + rv = OpenNetworkSocketWithInfo(hostName.get(), port, connectionType, proxyInfo, ir); + if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS) + { + m_socketType = nsMsgSocketType::plain; + rv = OpenNetworkSocketWithInfo(hostName.get(), port, nullptr, proxyInfo, ir); + } + + if(NS_FAILED(rv)) + return rv; + } // if we got a url... + + m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true); + if(!m_lineStreamBuffer) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + return bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(mLocalBundle)); +} + +nsPop3Protocol::~nsPop3Protocol() +{ + Cleanup(); + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("~nsPop3Protocol()"))); +} + +void nsPop3Protocol::Cleanup() +{ + if (m_pop3ConData->newuidl) + { + PL_HashTableDestroy(m_pop3ConData->newuidl); + m_pop3ConData->newuidl = nullptr; + } + + net_pop3_free_state(m_pop3ConData->uidlinfo); + + FreeMsgInfo(); + PR_Free(m_pop3ConData->only_uidl); + PR_Free(m_pop3ConData); + + delete m_lineStreamBuffer; + m_lineStreamBuffer = nullptr; +} + +void nsPop3Protocol::SetCapFlag(uint32_t flag) +{ + m_pop3ConData->capability_flags |= flag; +} + +void nsPop3Protocol::ClearCapFlag(uint32_t flag) +{ + m_pop3ConData->capability_flags &= ~flag; +} + +bool nsPop3Protocol::TestCapFlag(uint32_t flag) +{ + return m_pop3ConData->capability_flags & flag; +} + +uint32_t nsPop3Protocol::GetCapFlags() +{ + return m_pop3ConData->capability_flags; +} + +nsresult nsPop3Protocol::FormatCounterString(const nsString &stringName, + uint32_t count1, + uint32_t count2, + nsString &resultString) +{ + nsAutoString count1String; + count1String.AppendInt(count1); + + nsAutoString count2String; + count2String.AppendInt(count2); + + const char16_t *formatStrings[] = { + count1String.get(), + count2String.get() + }; + + return mLocalBundle->FormatStringFromName(stringName.get(), + formatStrings, 2, + getter_Copies(resultString)); +} + +void nsPop3Protocol::UpdateStatus(const char16_t *aStatusName) +{ + nsString statusMessage; + mLocalBundle->GetStringFromName(aStatusName, + getter_Copies(statusMessage)); + UpdateStatusWithString(statusMessage.get()); +} + +void nsPop3Protocol::UpdateStatusWithString(const char16_t *aStatusString) +{ + if (mProgressEventSink) + { + mozilla::DebugOnly<nsresult> rv = + mProgressEventSink->OnStatus(this, m_channelContext, + NS_OK, aStatusString); // XXX i18n message + NS_ASSERTION(NS_SUCCEEDED(rv), "dropping error result"); + } +} + +void nsPop3Protocol::UpdateProgressPercent(int64_t totalDone, int64_t total) +{ + if (mProgressEventSink) + mProgressEventSink->OnProgress(this, m_channelContext, totalDone, total); +} + +// note: SetUsername() expects an unescaped string +// do not pass in an escaped string +void nsPop3Protocol::SetUsername(const char* name) +{ + NS_ASSERTION(name, "no name specified!"); + if (name) + m_username = name; +} + +nsresult nsPop3Protocol::RerunUrl() +{ + nsCOMPtr<nsIURI> url = do_QueryInterface(m_url); + ClearFlag(POP3_PASSWORD_FAILED); + m_pop3Server->SetRunningProtocol(nullptr); + Cleanup(); + return LoadUrl(url, nullptr); +} + +Pop3StatesEnum nsPop3Protocol::GetNextPasswordObtainState() +{ + switch (m_pop3ConData->next_state) + { + case POP3_OBTAIN_PASSWORD_EARLY: + return POP3_FINISH_OBTAIN_PASSWORD_EARLY; + case POP3_SEND_USERNAME: + case POP3_OBTAIN_PASSWORD_BEFORE_USERNAME: + return POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME; + case POP3_SEND_PASSWORD: + case POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD: + return POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD; + default: + // Should never get here. + NS_NOTREACHED("Invalid next_state in GetNextPasswordObtainState"); + } + return POP3_ERROR_DONE; +} + +nsresult nsPop3Protocol::StartGetAsyncPassword(Pop3StatesEnum aNextState) +{ + nsresult rv; + + // Try and avoid going async if possible - if we haven't got into a password + // failure state and the server has a password stored for this session, then + // use it. + if (!TestFlag(POP3_PASSWORD_FAILED)) + { + nsCOMPtr<nsIMsgIncomingServer> server = + do_QueryInterface(m_pop3Server, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = server->GetPassword(m_passwordResult); + if (NS_SUCCEEDED(rv) && !m_passwordResult.IsEmpty()) + { + m_pop3ConData->next_state = GetNextPasswordObtainState(); + return NS_OK; + } + } + + // We're now going to need to do something that will end up with us either + // poking the login manger or prompting the user. We need to ensure we only + // do one prompt at a time (and loging manager could cause a master password + // prompt), so we need to use the async prompter. + nsCOMPtr<nsIMsgAsyncPrompter> asyncPrompter = + do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + m_pop3ConData->next_state = aNextState; + + // Although we're not actually pausing for a read, we'll do so anyway to let + // the async prompt run. Once it is our turn again we'll call back into + // ProcessProtocolState. + m_pop3ConData->pause_for_read = true; + + nsCString server("unknown"); + m_url->GetPrePath(server); + + rv = asyncPrompter->QueueAsyncAuthPrompt(server, false, this); + // Explict NS_ENSURE_SUCCESS for debug purposes as errors tend to get + // hidden. + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +NS_IMETHODIMP nsPop3Protocol::OnPromptStart(bool *aResult) +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("OnPromptStart()"))); + + *aResult = false; + + nsresult rv; + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString passwordResult; + + // pass the failed password into the password prompt so that + // it will be pre-filled, in case it failed because of a + // server problem and not because it was wrong. + if (!m_lastPasswordSent.IsEmpty()) + passwordResult = m_lastPasswordSent; + + // Set up some items that we're going to need for the prompting. + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + nsCOMPtr<nsIMsgWindow> msgWindow; + if (mailnewsUrl) + mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + + nsCString userName; + server->GetRealUsername(userName); + + nsCString hostName; + server->GetRealHostName(hostName); + + nsString passwordPrompt; + NS_ConvertUTF8toUTF16 userNameUTF16(userName); + NS_ConvertUTF8toUTF16 hostNameUTF16(hostName); + const char16_t* passwordParams[] = { userNameUTF16.get(), + hostNameUTF16.get() }; + + // if the last prompt got us a bad password then show a special dialog + if (TestFlag(POP3_PASSWORD_FAILED)) + { + // Biff case (no msgWindow) shouldn't cause prompts or passwords to get forgotten at all + // TODO shouldn't we skip the new password prompt below as well for biff? Just exit here? + if (msgWindow) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, + (POP3LOG("POP: ask user what to do (after password failed): new password, retry or cancel"))); + + int32_t buttonPressed = 0; + if (NS_SUCCEEDED(MsgPromptLoginFailed(msgWindow, hostName, + &buttonPressed))) + { + if (buttonPressed == 1) // Cancel button + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, (POP3LOG("cancel button pressed"))); + // Abort quickly and stop trying for now. + + // If we haven't actually connected yet (i.e. we're doing an early + // attempt to get the username/password but we've previously failed + // for some reason), then skip straight to POP3_FREE as it isn't an + // error in this connection, and just ends up with us closing the + // socket and saying we've aborted the bind. Otherwise, pretend this + // is an error and move on. + m_pop3ConData->next_state = + m_pop3ConData->next_state == POP3_OBTAIN_PASSWORD_EARLY ? + POP3_FREE : POP3_ERROR_DONE; + + // Clear the password we're going to return to force failure in + // the get mail instance. + passwordResult.Truncate(); + + // We also have to clear the password failed flag, otherwise we'll + // automatically try again. + ClearFlag(POP3_PASSWORD_FAILED); + + // As we're async, calling ProcessProtocolState gets things going + // again. + ProcessProtocolState(nullptr, nullptr, 0, 0); + return NS_OK; + } + else if (buttonPressed == 2) // "New password" button + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, (POP3LOG("new password button pressed"))); + // Forget the stored password + // and we'll prompt for a new one next time around. + rv = server->ForgetPassword(); + NS_ENSURE_SUCCESS(rv, rv); + + // try all methods again with new password + ResetAuthMethods(); + // ... apart from GSSAPI, which doesn't care about passwords + MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI); + if (m_needToRerunUrl) + return RerunUrl(); + } + else if (buttonPressed == 0) // "Retry" button + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Warning, (POP3LOG("retry button pressed"))); + // try all methods again, including GSSAPI + ResetAuthMethods(); + ClearFlag(POP3_PASSWORD_FAILED|POP3_AUTH_FAILURE); + + if (m_needToRerunUrl) + return RerunUrl(); + + // It is a bit strange that we're going onto the next state that + // would essentially send the password. However in resetting the + // auth methods above, we're setting up SendUsername, SendPassword + // and friends to abort and return to the POP3_SEND_CAPA state. + // Hence we can do this safely. + m_pop3ConData->next_state = GetNextPasswordObtainState(); + // As we're async, calling ProcessProtocolState gets things going + // again. + ProcessProtocolState(nullptr, nullptr, 0, 0); + return NS_OK; + } + } + } + mLocalBundle->FormatStringFromName( + u"pop3PreviouslyEnteredPasswordIsInvalidPrompt", + passwordParams, 2, getter_Copies(passwordPrompt)); + } + else + // Otherwise this is the first time we've asked about the server's + // password so show a first time prompt. + mLocalBundle->FormatStringFromName( + u"pop3EnterPasswordPrompt", + passwordParams, 2, getter_Copies(passwordPrompt)); + + nsString passwordTitle; + mLocalBundle->GetStringFromName( + u"pop3EnterPasswordPromptTitle", + getter_Copies(passwordTitle)); + + // Now go and get the password. + if (!passwordPrompt.IsEmpty() && !passwordTitle.IsEmpty()) + rv = server->GetPasswordWithUI(passwordPrompt, passwordTitle, + msgWindow, passwordResult); + ClearFlag(POP3_PASSWORD_FAILED|POP3_AUTH_FAILURE); + + // If it failed or the user cancelled the prompt, just abort the + // connection. + if (NS_FAILED(rv) || + rv == NS_MSG_PASSWORD_PROMPT_CANCELLED) + { + m_pop3ConData->next_state = POP3_ERROR_DONE; + m_passwordResult.Truncate(); + *aResult = false; + } + else + { + m_passwordResult = passwordResult; + m_pop3ConData->next_state = GetNextPasswordObtainState(); + *aResult = true; + } + // Because this was done asynchronously, now call back into + // ProcessProtocolState to get the protocol going again. + ProcessProtocolState(nullptr, nullptr, 0, 0); + return NS_OK; +} + +NS_IMETHODIMP nsPop3Protocol::OnPromptAuthAvailable() +{ + NS_NOTREACHED("Did not expect to get POP3 protocol queuing up auth " + "connections for same server"); + return NS_OK; +} + +NS_IMETHODIMP nsPop3Protocol::OnPromptCanceled() +{ + // A prompt was cancelled, so just abort out the connection + m_pop3ConData->next_state = POP3_ERROR_DONE; + // As we're async, calling ProcessProtocolState gets things going again. + ProcessProtocolState(nullptr, nullptr, 0, 0); + return NS_OK; +} + +NS_IMETHODIMP nsPop3Protocol::OnTransportStatus(nsITransport *aTransport, nsresult aStatus, int64_t aProgress, int64_t aProgressMax) +{ + return nsMsgProtocol::OnTransportStatus(aTransport, aStatus, aProgress, aProgressMax); +} + +// stop binding is a "notification" informing us that the stream associated with aURL is going away. +NS_IMETHODIMP nsPop3Protocol::OnStopRequest(nsIRequest *aRequest, nsISupports * aContext, nsresult aStatus) +{ + // If the server dropped the connection, m_socketIsOpen will be true, before + // we call nsMsgProtocol::OnStopRequest. The call will force a close socket, + // but we still want to go through the state machine one more time to cleanup + // the protocol object. + if (m_socketIsOpen) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(m_url); + + // Check if the connection was dropped before getting back an auth error. + // If we got the auth error, the next state would be + // POP3_OBTAIN_PASSWORD_EARLY. + if ((m_pop3ConData->next_state_after_response == POP3_NEXT_AUTH_STEP || + m_pop3ConData->next_state_after_response == POP3_AUTH_LOGIN_RESPONSE) && + m_pop3ConData->next_state != POP3_OBTAIN_PASSWORD_EARLY) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("dropped connection before auth error"))); + SetFlag(POP3_AUTH_FAILURE); + m_pop3ConData->command_succeeded = false; + m_needToRerunUrl = true; + m_pop3ConData->next_state = POP3_NEXT_AUTH_STEP; + ProcessProtocolState(nullptr, nullptr, 0, 0); + } + // We can't call nsMsgProtocol::OnStopRequest because it calls SetUrlState, + // which notifies the URLListeners, but we need to do a bit of cleanup + // before running the url again. + CloseSocket(); + if (m_loadGroup) + m_loadGroup->RemoveRequest(static_cast<nsIRequest *>(this), nullptr, aStatus); + m_pop3ConData->next_state = POP3_ERROR_DONE; + ProcessProtocolState(nullptr, nullptr, 0, 0); + + if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED) + nsMsgProtocol::ShowAlertMessage(msgUrl, aStatus); + + return NS_OK; + } + nsresult rv = nsMsgProtocol::OnStopRequest(aRequest, aContext, aStatus); + + // turn off the server busy flag on stop request - we know we're done, right? + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("Clearing server busy in nsPop3Protocol::OnStopRequest"))); + server->SetServerBusy(false); // the server is not busy + } + if(m_pop3ConData->list_done) + CommitState(true); + if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED) + Abort(); + return rv; +} + +void nsPop3Protocol::Abort() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Abort"))); + + if(m_pop3ConData->msg_closure) + { + m_nsIPop3Sink->IncorporateAbort(m_pop3ConData->only_uidl != nullptr); + m_pop3ConData->msg_closure = nullptr; + } + // need this to close the stream on the inbox. + m_nsIPop3Sink->AbortMailDelivery(this); + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("Clearing running protocol in nsPop3Protocol::Abort()"))); + m_pop3Server->SetRunningProtocol(nullptr); +} + +NS_IMETHODIMP nsPop3Protocol::Cancel(nsresult status) // handle stop button +{ + Abort(); + return nsMsgProtocol::Cancel(NS_BINDING_ABORTED); +} + + +nsresult nsPop3Protocol::LoadUrl(nsIURI* aURL, nsISupports * /* aConsumer */) +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("LoadUrl()"))); + + nsresult rv = Initialize(aURL); + NS_ENSURE_SUCCESS(rv, rv); + if (aURL) + m_url = do_QueryInterface(aURL); + else + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIURL> url = do_QueryInterface(aURL, &rv); + if (NS_FAILED(rv)) return rv; + + int32_t port; + rv = url->GetPort(&port); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_CheckPortSafety(port, "pop"); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString queryPart; + rv = url->GetQuery(queryPart); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get the url spect"); + + m_pop3ConData->only_check_for_new_mail = (PL_strcasestr(queryPart.get(), "check") != nullptr); + m_pop3ConData->verify_logon = (PL_strcasestr(queryPart.get(), "verifyLogon") != nullptr); + m_pop3ConData->get_url = (PL_strcasestr(queryPart.get(), "gurl") != nullptr); + + bool deleteByAgeFromServer = false; + int32_t numDaysToLeaveOnServer = -1; + if (!m_pop3ConData->verify_logon) + { + // Pick up pref setting regarding leave messages on server, message size limit + + m_pop3Server->GetLeaveMessagesOnServer(&m_pop3ConData->leave_on_server); + m_pop3Server->GetHeadersOnly(&m_pop3ConData->headers_only); + bool limitMessageSize = false; + + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) + { + // size limits are superseded by headers_only mode + if (!m_pop3ConData->headers_only) + { + server->GetLimitOfflineMessageSize(&limitMessageSize); + if (limitMessageSize) + { + int32_t max_size = 0; // default size + server->GetMaxMessageSize(&max_size); + m_pop3ConData->size_limit = (max_size) ? max_size * 1024 : 50 * 1024; + } + } + m_pop3Server->GetDeleteByAgeFromServer(&deleteByAgeFromServer); + if (deleteByAgeFromServer) + m_pop3Server->GetNumDaysToLeaveOnServer(&numDaysToLeaveOnServer); + } + } + + // UIDL stuff + nsCOMPtr<nsIPop3URL> pop3Url = do_QueryInterface(m_url); + if (pop3Url) + pop3Url->GetPop3Sink(getter_AddRefs(m_nsIPop3Sink)); + + nsCOMPtr<nsIFile> mailDirectory; + + nsCString hostName; + nsCString userName; + + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) + { + rv = server->GetLocalPath(getter_AddRefs(mailDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + server->SetServerBusy(true); // the server is now busy + server->GetHostName(hostName); + server->GetUsername(userName); + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, + (POP3LOG("Connecting to server %s:%d"), hostName.get(), port)); + + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("Setting server busy in nsPop3Protocol::LoadUrl()"))); + } + + if (!m_pop3ConData->verify_logon) + m_pop3ConData->uidlinfo = net_pop3_load_state(hostName.get(), userName.get(), mailDirectory); + + m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_NoMail; + + if (m_pop3ConData->uidlinfo && numDaysToLeaveOnServer > 0) + { + uint32_t nowInSeconds = TimeInSecondsFromPRTime(PR_Now()); + uint32_t cutOffDay = nowInSeconds - (60 * 60 * 24 * numDaysToLeaveOnServer); + + PL_HashTableEnumerateEntries(m_pop3ConData->uidlinfo->hash, net_pop3_delete_old_msgs_mapper, (void *)(uintptr_t) cutOffDay); + } + const char* uidl = PL_strcasestr(queryPart.get(), "uidl="); + PR_FREEIF(m_pop3ConData->only_uidl); + + if (uidl) + { + uidl += 5; + nsCString unescapedData; + MsgUnescapeString(nsDependentCString(uidl), 0, unescapedData); + m_pop3ConData->only_uidl = PL_strdup(unescapedData.get()); + + mSuppressListenerNotifications = true; // suppress on start and on stop because this url won't have any content to display + } + + m_pop3ConData->next_state = POP3_START_CONNECT; + m_pop3ConData->next_state_after_response = POP3_FINISH_CONNECT; + if (NS_SUCCEEDED(rv)) + { + m_pop3Server->SetRunningProtocol(this); + return nsMsgProtocol::LoadUrl(aURL); + } + else + return rv; +} + +void +nsPop3Protocol::FreeMsgInfo() +{ + int i; + if (m_pop3ConData->msg_info) + { + for (i=0 ; i<m_pop3ConData->number_of_messages ; i++) + { + if (m_pop3ConData->msg_info[i].uidl) + PR_Free(m_pop3ConData->msg_info[i].uidl); + m_pop3ConData->msg_info[i].uidl = nullptr; + } + PR_Free(m_pop3ConData->msg_info); + m_pop3ConData->msg_info = nullptr; + } +} + +int32_t +nsPop3Protocol::WaitForStartOfConnectionResponse(nsIInputStream* aInputStream, + uint32_t length) +{ + char * line = nullptr; + uint32_t line_length = 0; + bool pauseForMoreData = false; + nsresult rv; + line = m_lineStreamBuffer->ReadNextLine(aInputStream, line_length, pauseForMoreData, &rv); + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + if (NS_FAILED(rv)) + return -1; + + if(pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; /* pause */ + PR_Free(line); + return(line_length); + } + + if(*line == '+') + { + m_pop3ConData->command_succeeded = true; + if(PL_strlen(line) > 4) + m_commandResponse = line + 4; + else + m_commandResponse = line; + + if (m_prefAuthMethods & POP3_HAS_AUTH_APOP) + { + if (NS_SUCCEEDED(GetApopTimestamp())) + SetCapFlag(POP3_HAS_AUTH_APOP); + } + else + ClearCapFlag(POP3_HAS_AUTH_APOP); + + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + m_pop3ConData->pause_for_read = false; /* don't pause */ + } + + PR_Free(line); + return(1); /* everything ok */ +} + +int32_t +nsPop3Protocol::WaitForResponse(nsIInputStream* inputStream, uint32_t length) +{ + char * line; + uint32_t ln = 0; + bool pauseForMoreData = false; + nsresult rv; + line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if(pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; /* pause */ + + PR_Free(line); + return(ln); + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + if(*line == '+') + { + m_pop3ConData->command_succeeded = true; + if(PL_strlen(line) > 4) + { + if(!PL_strncasecmp(line, "+OK", 3)) + m_commandResponse = line + 4; + else // challenge answer to AUTH CRAM-MD5 and LOGIN username/password + m_commandResponse = line + 2; + } + else + m_commandResponse = line; + } + else + { + m_pop3ConData->command_succeeded = false; + if(PL_strlen(line) > 5) + m_commandResponse = line + 5; + else + m_commandResponse = line; + + // search for the response codes (RFC 2449, chapter 8 and RFC 3206) + if(TestCapFlag(POP3_HAS_RESP_CODES | POP3_HAS_AUTH_RESP_CODE)) + { + // code for authentication failure due to the user's credentials + if(m_commandResponse.Find("[AUTH", true) >= 0) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("setting auth failure"))); + SetFlag(POP3_AUTH_FAILURE); + } + + // codes for failures due to other reasons + if(m_commandResponse.Find("[LOGIN-DELAY", true) >= 0 || + m_commandResponse.Find("[IN-USE", true) >= 0 || + m_commandResponse.Find("[SYS", true) >= 0) + SetFlag(POP3_STOPLOGIN); + + // remove the codes from the response string presented to the user + int32_t i = m_commandResponse.FindChar(']'); + if(i >= 0) + m_commandResponse.Cut(0, i + 2); + } + } + + m_pop3ConData->next_state = m_pop3ConData->next_state_after_response; + m_pop3ConData->pause_for_read = false; /* don't pause */ + + PR_Free(line); + return(1); /* everything ok */ +} + +int32_t +nsPop3Protocol::Error(const char* err_code, + const char16_t **params, + uint32_t length) +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("ERROR: %s"), err_code)); + + // the error code is just the resource name for the error string... + // so print out that error message! + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + nsString accountName; + nsresult rv = server->GetPrettyName(accountName); + NS_ENSURE_SUCCESS(rv, -1); + const char16_t *titleParams[] = { accountName.get() }; + nsString dialogTitle; + mLocalBundle->FormatStringFromName( + u"pop3ErrorDialogTitle", + titleParams, 1, getter_Copies(dialogTitle)); + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + // we handle "pop3TmpDownloadError" earlier... + if (strcmp(err_code, "pop3TmpDownloadError") && NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIPrompt> dialog; + rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); //it is ok to have null msgWindow, for example when biffing + if (NS_SUCCEEDED(rv) && msgWindow) + { + rv = msgWindow->GetPromptDialog(getter_AddRefs(dialog)); + if (NS_SUCCEEDED(rv)) + { + nsString alertString; + // Format the alert string if parameter list isn't empty + if (params) + mLocalBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(err_code).get(), + params, length, getter_Copies(alertString)); + else + mLocalBundle->GetStringFromName(NS_ConvertASCIItoUTF16(err_code).get(), + getter_Copies(alertString)); + if (m_pop3ConData->command_succeeded) //not a server error message + dialog->Alert(dialogTitle.get(), alertString.get()); + else + { + nsString serverSaidPrefix; + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + nsCString hostName; + // Fomat string with hostname. + if (server) + rv = server->GetRealHostName(hostName); + if (NS_SUCCEEDED(rv)) + { + nsAutoString hostStr; + CopyASCIItoUTF16(hostName, hostStr); + const char16_t *params[] = { hostStr.get() }; + mLocalBundle->FormatStringFromName( + u"pop3ServerSaid", + params, 1, getter_Copies(serverSaidPrefix)); + } + + nsAutoString message(alertString); + message.AppendLiteral(" "); + message.Append(serverSaidPrefix); + message.AppendLiteral(" "); + message.Append(NS_ConvertASCIItoUTF16(m_commandResponse)); + dialog->Alert(dialogTitle.get(), message.get()); + } + } + } + } + m_pop3ConData->next_state = POP3_ERROR_DONE; + m_pop3ConData->pause_for_read = false; + return -1; +} + +int32_t nsPop3Protocol::Pop3SendData(const char * dataBuffer, bool aSuppressLogging) +{ + // remove any leftover bytes in the line buffer + // this can happen if the last message line doesn't end with a (CR)LF + // or a server sent two reply lines + m_lineStreamBuffer->ClearBuffer(); + + nsresult result = nsMsgProtocol::SendData(dataBuffer); + + if (!aSuppressLogging) + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("SEND: %s"), dataBuffer)); + else + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, + (POP3LOG("Logging suppressed for this command (it probably contained authentication information)"))); + + if (NS_SUCCEEDED(result)) + { + m_pop3ConData->pause_for_read = true; + m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE; + return 0; + } + + m_pop3ConData->next_state = POP3_ERROR_DONE; + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("Pop3SendData failed: %lx"), result)); + return -1; +} + +/* + * POP3 AUTH extension + */ + +int32_t nsPop3Protocol::SendAuth() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendAuth()"))); + + if(!m_pop3ConData->command_succeeded) + return Error("pop3ServerError"); + + nsAutoCString command("AUTH" CRLF); + + m_pop3ConData->next_state_after_response = POP3_AUTH_RESPONSE; + return Pop3SendData(command.get()); +} + +int32_t nsPop3Protocol::AuthResponse(nsIInputStream* inputStream, + uint32_t length) +{ + char * line; + uint32_t ln = 0; + nsresult rv; + + if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED)) + { + ClearCapFlag(POP3_AUTH_MECH_UNDEFINED); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + if (!m_pop3ConData->command_succeeded) + { + /* AUTH command not implemented + * so no secure mechanisms available + */ + m_pop3ConData->command_succeeded = true; + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + m_pop3ConData->next_state = POP3_SEND_CAPA; + return 0; + } + + bool pauseForMoreData = false; + line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if(pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; /* pause */ + PR_Free(line); + return(0); + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + if (!PL_strcmp(line, ".")) + { + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + + // now that we've read all the AUTH responses, go for it + m_pop3ConData->next_state = POP3_SEND_CAPA; + m_pop3ConData->pause_for_read = false; /* don't pause */ + } + else if (!PL_strcasecmp (line, "CRAM-MD5")) + SetCapFlag(POP3_HAS_AUTH_CRAM_MD5); + else if (!PL_strcasecmp (line, "NTLM")) + SetCapFlag(POP3_HAS_AUTH_NTLM); + else if (!PL_strcasecmp (line, "MSN")) + SetCapFlag(POP3_HAS_AUTH_NTLM|POP3_HAS_AUTH_MSN); + else if (!PL_strcasecmp (line, "GSSAPI")) + SetCapFlag(POP3_HAS_AUTH_GSSAPI); + else if (!PL_strcasecmp (line, "PLAIN")) + SetCapFlag(POP3_HAS_AUTH_PLAIN); + else if (!PL_strcasecmp (line, "LOGIN")) + SetCapFlag(POP3_HAS_AUTH_LOGIN); + + PR_Free(line); + return 0; +} + +/* + * POP3 CAPA extension, see RFC 2449, chapter 5 + */ + +int32_t nsPop3Protocol::SendCapa() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendCapa()"))); + if(!m_pop3ConData->command_succeeded) + return Error("pop3ServerError"); + + nsAutoCString command("CAPA" CRLF); + + m_pop3ConData->next_state_after_response = POP3_CAPA_RESPONSE; + return Pop3SendData(command.get()); +} + +int32_t nsPop3Protocol::CapaResponse(nsIInputStream* inputStream, + uint32_t length) +{ + char * line; + uint32_t ln = 0; + + if (!m_pop3ConData->command_succeeded) + { + /* CAPA command not implemented */ + m_pop3ConData->command_succeeded = true; + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + return 0; + } + + bool pauseForMoreData = false; + nsresult rv; + line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if(pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; /* pause */ + PR_Free(line); + return(0); + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + if (!PL_strcmp(line, ".")) + { + // now that we've read all the CAPA responses, go for it + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + m_pop3ConData->pause_for_read = false; /* don't pause */ + } + else + if (!PL_strcasecmp(line, "XSENDER")) + { + SetCapFlag(POP3_HAS_XSENDER); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + else + // see RFC 2449, chapter 6.4 + if (!PL_strcasecmp(line, "RESP-CODES")) + { + SetCapFlag(POP3_HAS_RESP_CODES); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + else + // see RFC 3206, chapter 6 + if (!PL_strcasecmp(line, "AUTH-RESP-CODE")) + { + SetCapFlag(POP3_HAS_AUTH_RESP_CODE); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + else + // see RFC 2595, chapter 4 + if (!PL_strcasecmp(line, "STLS")) + { + SetCapFlag(POP3_HAS_STLS); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + else + // see RFC 2449, chapter 6.3 + if (!PL_strncasecmp(line, "SASL", 4) && strlen(line) > 6) + { + nsAutoCString responseLine; + responseLine.Assign(line + 5); + + if (responseLine.Find("PLAIN", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_PLAIN); + + if (responseLine.Find("LOGIN", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_LOGIN); + + if (responseLine.Find("GSSAPI", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_GSSAPI); + + if (responseLine.Find("CRAM-MD5", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_CRAM_MD5); + + if (responseLine.Find("NTLM", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_NTLM); + + if (responseLine.Find("MSN", CaseInsensitiveCompare) >= 0) + SetCapFlag(POP3_HAS_AUTH_NTLM|POP3_HAS_AUTH_MSN); + + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + PR_Free(line); + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Capability entry processed"))); + return 0; +} + +int32_t nsPop3Protocol::SendTLSResponse() +{ + // only tear down our existing connection and open a new one if we received + // a +OK response from the pop server after we issued the STLS command + nsresult rv = NS_OK; + if (m_pop3ConData->command_succeeded) + { + nsCOMPtr<nsISupports> secInfo; + nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport, &rv); + if (NS_FAILED(rv)) + return -1; + + rv = strans->GetSecurityInfo(getter_AddRefs(secInfo)); + + if (NS_SUCCEEDED(rv) && secInfo) + { + nsCOMPtr<nsISSLSocketControl> sslControl = do_QueryInterface(secInfo, &rv); + + if (NS_SUCCEEDED(rv) && sslControl) + rv = sslControl->StartTLS(); + } + + if (NS_SUCCEEDED(rv)) + { + m_pop3ConData->next_state = POP3_SEND_AUTH; + m_tlsEnabled = true; + + // certain capabilities like POP3_HAS_AUTH_APOP should be + // preserved across the connections. + uint32_t preservedCapFlags = m_pop3ConData->capability_flags & POP3_HAS_AUTH_APOP; + m_pop3ConData->capability_flags = // resetting the flags + POP3_AUTH_MECH_UNDEFINED | + POP3_HAS_AUTH_USER | // should be always there + POP3_GURL_UNDEFINED | + POP3_UIDL_UNDEFINED | + POP3_TOP_UNDEFINED | + POP3_XTND_XLST_UNDEFINED | + preservedCapFlags; + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + return 0; + } + } + + ClearFlag(POP3_HAS_STLS); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + + return (NS_SUCCEEDED(rv) ? 0 : -1); +} + +void nsPop3Protocol::InitPrefAuthMethods(int32_t authMethodPrefValue) +{ + // for m_prefAuthMethods, using the same flags as server capablities. + switch (authMethodPrefValue) + { + case nsMsgAuthMethod::none: + m_prefAuthMethods = POP3_HAS_AUTH_NONE; + break; + case nsMsgAuthMethod::old: + m_prefAuthMethods = POP3_HAS_AUTH_USER; + break; + case nsMsgAuthMethod::passwordCleartext: + m_prefAuthMethods = POP3_HAS_AUTH_USER | + POP3_HAS_AUTH_LOGIN | POP3_HAS_AUTH_PLAIN; + break; + case nsMsgAuthMethod::passwordEncrypted: + m_prefAuthMethods = POP3_HAS_AUTH_CRAM_MD5 | + POP3_HAS_AUTH_APOP; + break; + case nsMsgAuthMethod::NTLM: + m_prefAuthMethods = POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN; + break; + case nsMsgAuthMethod::GSSAPI: + m_prefAuthMethods = POP3_HAS_AUTH_GSSAPI; + break; + case nsMsgAuthMethod::secure: + m_prefAuthMethods = POP3_HAS_AUTH_APOP | + POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_GSSAPI | + POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN; + break; + default: + NS_ASSERTION(false, "POP: authMethod pref invalid"); + // TODO log to error console + MOZ_LOG(POP3LOGMODULE, LogLevel::Error, + (POP3LOG("POP: bad pref authMethod = %d\n"), authMethodPrefValue)); + // fall to any + MOZ_FALLTHROUGH; + case nsMsgAuthMethod::anything: + m_prefAuthMethods = POP3_HAS_AUTH_USER | + POP3_HAS_AUTH_LOGIN | POP3_HAS_AUTH_PLAIN | + POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP | + POP3_HAS_AUTH_GSSAPI | + POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN; + // TODO needed? + break; + } + NS_ASSERTION(m_prefAuthMethods != POP3_AUTH_MECH_UNDEFINED, + "POP: InitPrefAuthMethods() didn't work"); +} + +/** + * Changes m_currentAuthMethod to pick the best one + * which is allowed by server and prefs and not marked failed. + * The order of preference and trying of auth methods is encoded here. + */ +nsresult nsPop3Protocol::ChooseAuthMethod() +{ + int32_t availCaps = GetCapFlags() & m_prefAuthMethods & ~m_failedAuthMethods; + + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("POP auth: server caps 0x%X, pref 0x%X, failed 0x%X, avail caps 0x%X"), + GetCapFlags(), m_prefAuthMethods, m_failedAuthMethods, availCaps)); + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("(GSSAPI = 0x%X, CRAM = 0x%X, APOP = 0x%X, NTLM = 0x%X, " + "MSN = 0x%X, PLAIN = 0x%X, LOGIN = 0x%X, USER/PASS = 0x%X)"), + POP3_HAS_AUTH_GSSAPI, POP3_HAS_AUTH_CRAM_MD5, POP3_HAS_AUTH_APOP, + POP3_HAS_AUTH_NTLM, POP3_HAS_AUTH_MSN, POP3_HAS_AUTH_PLAIN, + POP3_HAS_AUTH_LOGIN, POP3_HAS_AUTH_USER)); + + if (POP3_HAS_AUTH_GSSAPI & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_GSSAPI; + else if (POP3_HAS_AUTH_CRAM_MD5 & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_CRAM_MD5; + else if (POP3_HAS_AUTH_APOP & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_APOP; + else if (POP3_HAS_AUTH_NTLM & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_NTLM; + else if (POP3_HAS_AUTH_MSN & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_MSN; + else if (POP3_HAS_AUTH_PLAIN & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_PLAIN; + else if (POP3_HAS_AUTH_LOGIN & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_LOGIN; + else if (POP3_HAS_AUTH_USER & availCaps) + m_currentAuthMethod = POP3_HAS_AUTH_USER; + else + { + // there are no matching login schemes at all, per server and prefs + m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED; + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("no auth method remaining"))); + return NS_ERROR_FAILURE; + } + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("trying auth method 0x%X"), m_currentAuthMethod)); + return NS_OK; +} + +void nsPop3Protocol::MarkAuthMethodAsFailed(int32_t failedAuthMethod) +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("marking auth method 0x%X failed"), failedAuthMethod)); + m_failedAuthMethods |= failedAuthMethod; +} + +/** + * Start over, trying all auth methods again + */ +void nsPop3Protocol::ResetAuthMethods() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("resetting (failed) auth methods"))); + m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED; + m_failedAuthMethods = 0; +} + +/** + * state POP3_PROCESS_AUTH + * Called when we should try to authenticate to the server. + * Also called when one auth method fails and we want to try and start + * the next best auth method. + */ +int32_t nsPop3Protocol::ProcessAuth() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("ProcessAuth()"))); + + // Try to upgrade to STARTTLS -- TODO move into its own function + if (!m_tlsEnabled) + { + if(TestCapFlag(POP3_HAS_STLS)) + { + if (m_socketType == nsMsgSocketType::trySTARTTLS || + m_socketType == nsMsgSocketType::alwaysSTARTTLS) + { + nsAutoCString command("STLS" CRLF); + + m_pop3ConData->next_state_after_response = POP3_TLS_RESPONSE; + return Pop3SendData(command.get()); + } + } + else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) + { + m_pop3ConData->next_state = POP3_ERROR_DONE; + return Error("nsErrorCouldNotConnectViaTls"); + } + } + + m_password_already_sent = false; + + nsresult rv = ChooseAuthMethod(); + if (NS_FAILED(rv)) + { + // Pref doesn't match server. Now, find an appropriate error msg. + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("ProcessAuth() early exit because no auth methods"))); + + // AuthGSSAPI* falls in here in case of an auth failure. + // If Kerberos was the only method, assume that + // the user is just not logged in yet, and show an appropriate error. + if (m_prefAuthMethods == POP3_HAS_AUTH_GSSAPI && + m_failedAuthMethods == POP3_HAS_AUTH_GSSAPI) + return Error("pop3GssapiFailure"); + + // pref has plaintext pw & server claims to support encrypted pw + if (m_prefAuthMethods == (POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN | + POP3_HAS_AUTH_PLAIN) && + GetCapFlags() & (POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP)) + // tell user to change to encrypted pw + return Error("pop3AuthChangePlainToEncrypt"); + // pref has encrypted pw & server claims to support plaintext pw + else if (m_prefAuthMethods == (POP3_HAS_AUTH_CRAM_MD5 | + POP3_HAS_AUTH_APOP) && + GetCapFlags() & (POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN | + POP3_HAS_AUTH_PLAIN)) + { + // have SSL + if (m_socketType == nsMsgSocketType::SSL || + m_socketType == nsMsgSocketType::alwaysSTARTTLS) + // tell user to change to plaintext pw + return Error("pop3AuthChangeEncryptToPlainSSL"); + else + // tell user to change to plaintext pw, with big warning + return Error("pop3AuthChangeEncryptToPlainNoSSL"); + } + else + // just "change auth method" + return Error("pop3AuthMechNotSupported"); + } + + switch (m_currentAuthMethod) + { + case POP3_HAS_AUTH_GSSAPI: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP GSSAPI"))); + m_pop3ConData->next_state = POP3_AUTH_GSSAPI; + break; + case POP3_HAS_AUTH_APOP: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP APOP"))); + m_pop3ConData->next_state = POP3_SEND_PASSWORD; + break; + case POP3_HAS_AUTH_CRAM_MD5: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP CRAM"))); + MOZ_FALLTHROUGH; + case POP3_HAS_AUTH_PLAIN: + case POP3_HAS_AUTH_USER: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP username"))); + m_pop3ConData->next_state = POP3_SEND_USERNAME; + break; + case POP3_HAS_AUTH_LOGIN: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP AUTH=LOGIN"))); + m_pop3ConData->next_state = POP3_AUTH_LOGIN; + break; + case POP3_HAS_AUTH_NTLM: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP NTLM"))); + m_pop3ConData->next_state = POP3_AUTH_NTLM; + break; + case POP3_HAS_AUTH_NONE: + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP no auth"))); + m_pop3ConData->command_succeeded = true; + m_pop3ConData->next_state = POP3_NEXT_AUTH_STEP; + break; + default: + MOZ_LOG(POP3LOGMODULE, LogLevel::Error, + (POP3LOG("POP: m_currentAuthMethod has unknown value"))); + return Error("pop3AuthMechNotSupported"); + } + + m_pop3ConData->pause_for_read = false; + + return 0; +} + +/** + * state POP3_NEXT_AUTH_STEP + * This is called when we finished one auth step (e.g. sending username + * or password are separate steps, similarly for AUTH LOGIN, NTLM etc.) + * and want to proceed to the next one. + */ +int32_t nsPop3Protocol::NextAuthStep() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("NextAuthStep()"))); + if (m_pop3ConData->command_succeeded) + { + if (m_password_already_sent || // (also true for GSSAPI) + m_currentAuthMethod == POP3_HAS_AUTH_NONE) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("login succeeded"))); + m_nsIPop3Sink->SetUserAuthenticated(true); + ClearFlag(POP3_PASSWORD_FAILED); + if (m_pop3ConData->verify_logon) + m_pop3ConData->next_state = POP3_SEND_QUIT; + else + m_pop3ConData->next_state = (m_pop3ConData->get_url) + ? POP3_SEND_GURL : POP3_SEND_STAT; + } + else + m_pop3ConData->next_state = POP3_SEND_PASSWORD; + } + else + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("command did not succeed"))); + // response code received shows that login failed not because of + // wrong credential -> stop login without retry or pw dialog, only alert + // parameter list -> user + nsCString userName; + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + nsresult rv = server->GetRealUsername(userName); + NS_ENSURE_SUCCESS(rv, -1); + NS_ConvertUTF8toUTF16 userNameUTF16(userName); + const char16_t* params[] = { userNameUTF16.get() }; + if (TestFlag(POP3_STOPLOGIN)) + { + if (m_password_already_sent) + return Error("pop3PasswordFailed", params, 1); + + return Error("pop3UsernameFailure"); + } + // response code received shows that server is certain about the + // credential was wrong -> no fallback, show alert and pw dialog + if (TestFlag(POP3_AUTH_FAILURE)) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("auth failure, setting password failed"))); + if (m_password_already_sent) + Error("pop3PasswordFailed", params, 1); + else + Error("pop3UsernameFailure"); + SetFlag(POP3_PASSWORD_FAILED); + ClearFlag(POP3_AUTH_FAILURE); + return 0; + } + + // We have no certain response code -> fallback and try again. + // Mark the auth method failed, to use a different method next round. + MarkAuthMethodAsFailed(m_currentAuthMethod); + + if (m_currentAuthMethod == POP3_HAS_AUTH_USER && + !m_password_already_sent) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("USER username failed"))); + // if USER auth method failed before sending the password, + // the username was wrong. + // no fallback but return error + return Error("pop3UsernameFailure"); + } + + // If we have no auth method left, ask user to try with new password + rv = ChooseAuthMethod(); + if (NS_FAILED(rv)) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Error, + (POP3LOG("POP: no auth methods remaining, setting password failure"))); + /* Sever the connection and go back to the `read password' state, + which, upon success, will re-open the connection. Set a flag + which causes the prompt to be different that time (to indicate + that the old password was bogus.) + + But if we're just checking for new mail (biff) then don't bother + prompting the user for a password: just fail silently. + */ + SetFlag(POP3_PASSWORD_FAILED); + Error("pop3PasswordFailed", params, 1); + return 0; + } + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("still have some auth methods to try"))); + + // TODO needed? + //m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + + m_pop3ConData->command_succeeded = true; + + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + } + + if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED)) + { + ClearCapFlag(POP3_AUTH_MECH_UNDEFINED); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + m_pop3ConData->pause_for_read = false; + + return 0; +} + +// LOGIN consists of three steps not two as USER/PASS or CRAM-MD5, +// so we've to start here and continue in SendUsername if the server +// responds + to "AUTH LOGIN" +int32_t nsPop3Protocol::AuthLogin() +{ + nsAutoCString command("AUTH LOGIN" CRLF); + m_pop3ConData->next_state_after_response = POP3_AUTH_LOGIN_RESPONSE; + m_pop3ConData->pause_for_read = true; + + return Pop3SendData(command.get()); +} + +int32_t nsPop3Protocol::AuthLoginResponse() +{ + // need the test to be here instead in NextAuthStep() to + // differentiate between command AUTH LOGIN failed and + // sending username using LOGIN mechanism failed. + if (!m_pop3ConData->command_succeeded) + { + // we failed with LOGIN, remove it + MarkAuthMethodAsFailed(POP3_HAS_AUTH_LOGIN); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + } + else + m_pop3ConData->next_state = POP3_SEND_USERNAME; + + m_pop3ConData->pause_for_read = false; + + return 0; +} + +// NTLM, like LOGIN consists of three steps not two as USER/PASS or CRAM-MD5, +// so we've to start here and continue in SendUsername if the server +// responds + to "AUTH NTLM" +int32_t nsPop3Protocol::AuthNtlm() +{ + nsAutoCString command (m_currentAuthMethod == POP3_HAS_AUTH_MSN + ? "AUTH MSN" CRLF : "AUTH NTLM" CRLF); + m_pop3ConData->next_state_after_response = POP3_AUTH_NTLM_RESPONSE; + m_pop3ConData->pause_for_read = true; + + return Pop3SendData(command.get()); +} + +int32_t nsPop3Protocol::AuthNtlmResponse() +{ + // need the test to be here instead in NextAuthStep() to + // differentiate between command AUTH NTLM failed and + // sending username using NTLM mechanism failed. + if (!m_pop3ConData->command_succeeded) + { + MarkAuthMethodAsFailed(POP3_HAS_AUTH_NTLM); + MarkAuthMethodAsFailed(POP3_HAS_AUTH_MSN); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + } + else + m_pop3ConData->next_state = POP3_SEND_USERNAME; + + m_pop3ConData->pause_for_read = false; + + return 0; +} + +int32_t nsPop3Protocol::AuthGSSAPI() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("AuthGSSAPI()"))); + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) { + nsAutoCString cmd; + nsAutoCString service("pop@"); + nsCString hostName; + nsresult rv; + server->GetRealHostName(hostName); + service.Append(hostName); + rv = DoGSSAPIStep1(service.get(), m_username.get(), cmd); + if (NS_SUCCEEDED(rv)) { + m_GSSAPICache.Assign(cmd); + m_pop3ConData->next_state_after_response = POP3_AUTH_GSSAPI_FIRST; + m_pop3ConData->pause_for_read = true; + return Pop3SendData("AUTH GSSAPI" CRLF); + } + } + + MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + m_pop3ConData->pause_for_read = false; + return 0; +} + +int32_t nsPop3Protocol::AuthGSSAPIResponse(bool first) +{ + if (!m_pop3ConData->command_succeeded) + { + if (first) + m_GSSAPICache.Truncate(); + MarkAuthMethodAsFailed(POP3_HAS_AUTH_GSSAPI); + m_pop3ConData->next_state = POP3_PROCESS_AUTH; + m_pop3ConData->pause_for_read = false; + return 0; + } + + int32_t result; + + m_pop3ConData->next_state_after_response = POP3_AUTH_GSSAPI_STEP; + m_pop3ConData->pause_for_read = true; + + if (first) { + m_GSSAPICache += CRLF; + result = Pop3SendData(m_GSSAPICache.get()); + m_GSSAPICache.Truncate(); + } + else { + nsAutoCString cmd; + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("GSSAPI step 2"))); + nsresult rv = DoGSSAPIStep2(m_commandResponse, cmd); + if (NS_FAILED(rv)) + cmd = "*"; + if (rv == NS_SUCCESS_AUTH_FINISHED) { + m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP; + m_password_already_sent = true; + } + cmd += CRLF; + result = Pop3SendData(cmd.get()); + } + + return result; +} + +int32_t nsPop3Protocol::SendUsername() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendUsername()"))); + if(m_username.IsEmpty()) + return Error("pop3UsernameUndefined"); + + // <copied from="SendPassword()"> + // Needed for NTLM + + // The POP3_SEND_PASSWORD/POP3_WAIT_SEND_PASSWORD states have already + // got the password - they will have cancelled if necessary. + // If the password is still empty here, don't try to go on. + if (m_passwordResult.IsEmpty()) + { + m_pop3ConData->next_state = POP3_ERROR_DONE; + return Error("pop3PasswordUndefined"); + } + // </copied> + + nsAutoCString cmd; + + if (m_currentAuthMethod == POP3_HAS_AUTH_NTLM) + (void) DoNtlmStep1(m_username.get(), m_passwordResult.get(), cmd); + else if (m_currentAuthMethod == POP3_HAS_AUTH_CRAM_MD5) + cmd = "AUTH CRAM-MD5"; + else if (m_currentAuthMethod == POP3_HAS_AUTH_PLAIN) + cmd = "AUTH PLAIN"; + else if (m_currentAuthMethod == POP3_HAS_AUTH_LOGIN) + { + char *base64Str = PL_Base64Encode(m_username.get(), m_username.Length(), nullptr); + cmd = base64Str; + PR_Free(base64Str); + } + else if (m_currentAuthMethod == POP3_HAS_AUTH_USER) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("USER login"))); + cmd = "USER "; + cmd += m_username; + } + else + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Error, + (POP3LOG("In nsPop3Protocol::SendUsername(), m_currentAuthMethod is 0x%X, " + "but that is unexpected"), m_currentAuthMethod)); + return Error("pop3AuthInternalError"); + } + + cmd += CRLF; + + m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP; + + m_pop3ConData->pause_for_read = true; + + return Pop3SendData(cmd.get()); +} + +int32_t nsPop3Protocol::SendPassword() +{ + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendPassword()"))); + if (m_username.IsEmpty()) + return Error("pop3UsernameUndefined"); + + // <copied to="SendUsername()"> + // Needed here, too, because APOP skips SendUsername() + // The POP3_SEND_PASSWORD/POP3_WAIT_SEND_PASSWORD states have already + // got the password - they will have cancelled if necessary. + // If the password is still empty here, don't try to go on. + if (m_passwordResult.IsEmpty()) + { + m_pop3ConData->next_state = POP3_ERROR_DONE; + return Error("pop3PasswordUndefined"); + } + // </copied> + + nsAutoCString cmd; + nsresult rv; + + if (m_currentAuthMethod == POP3_HAS_AUTH_NTLM) + rv = DoNtlmStep2(m_commandResponse, cmd); + else if (m_currentAuthMethod == POP3_HAS_AUTH_CRAM_MD5) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("CRAM login"))); + char buffer[512]; // TODO nsAutoCString + unsigned char digest[DIGEST_LENGTH]; + + char *decodedChallenge = PL_Base64Decode(m_commandResponse.get(), + m_commandResponse.Length(), nullptr); + + if (decodedChallenge) + rv = MSGCramMD5(decodedChallenge, strlen(decodedChallenge), + m_passwordResult.get(), m_passwordResult.Length(), digest); + else + rv = NS_ERROR_NULL_POINTER; + + if (NS_SUCCEEDED(rv)) + { + nsAutoCString encodedDigest; + char hexVal[8]; + + for (uint32_t j = 0; j < 16; j++) + { + PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)digest[j]); + encodedDigest.Append(hexVal); + } + + PR_snprintf(buffer, sizeof(buffer), "%s %s", m_username.get(), + encodedDigest.get()); + char *base64Str = PL_Base64Encode(buffer, strlen(buffer), nullptr); + cmd = base64Str; + PR_Free(base64Str); + } + + if (NS_FAILED(rv)) + cmd = "*"; + } + else if (m_currentAuthMethod == POP3_HAS_AUTH_APOP) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("APOP login"))); + char buffer[512]; + unsigned char digest[DIGEST_LENGTH]; + + rv = MSGApopMD5(m_ApopTimestamp.get(), m_ApopTimestamp.Length(), + m_passwordResult.get(), m_passwordResult.Length(), digest); + + if (NS_SUCCEEDED(rv)) + { + nsAutoCString encodedDigest; + char hexVal[8]; + + for (uint32_t j=0; j<16; j++) + { + PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)digest[j]); + encodedDigest.Append(hexVal); + } + + PR_snprintf(buffer, sizeof(buffer), "APOP %s %s", m_username.get(), + encodedDigest.get()); + cmd = buffer; + } + + if (NS_FAILED(rv)) + cmd = "*"; + } + else if (m_currentAuthMethod == POP3_HAS_AUTH_PLAIN) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("PLAIN login"))); + // workaround for IPswitch's IMail server software + // this server goes into LOGIN mode even if we send "AUTH PLAIN" + // "VXNlc" is the beginning of the base64 encoded prompt ("Username:") for LOGIN + if (StringBeginsWith(m_commandResponse, NS_LITERAL_CSTRING("VXNlc"))) + { + // disable PLAIN and enable LOGIN (in case it's not already enabled) + ClearCapFlag(POP3_HAS_AUTH_PLAIN); + SetCapFlag(POP3_HAS_AUTH_LOGIN); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + + // reenter authentication again at LOGIN response handler + m_pop3ConData->next_state = POP3_AUTH_LOGIN_RESPONSE; + m_pop3ConData->pause_for_read = false; + return 0; + } + + char plain_string[512]; // TODO nsCString + int len = 1; /* first <NUL> char */ + memset(plain_string, 0, 512); + PR_snprintf(&plain_string[1], 510, "%s", m_username.get()); + len += m_username.Length(); + len++; /* second <NUL> char */ + PR_snprintf(&plain_string[len], 511-len, "%s", m_passwordResult.get()); + len += m_passwordResult.Length(); + + char *base64Str = PL_Base64Encode(plain_string, len, nullptr); + cmd = base64Str; + PR_Free(base64Str); + } + else if (m_currentAuthMethod == POP3_HAS_AUTH_LOGIN) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("LOGIN password"))); + char * base64Str = + PL_Base64Encode(m_passwordResult.get(), m_passwordResult.Length(), + nullptr); + cmd = base64Str; + PR_Free(base64Str); + } + else if (m_currentAuthMethod == POP3_HAS_AUTH_USER) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("PASS password"))); + cmd = "PASS "; + cmd += m_passwordResult; + } + else + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Error, + (POP3LOG("In nsPop3Protocol::SendPassword(), m_currentAuthMethod is %X, " + "but that is unexpected"), m_currentAuthMethod)); + return Error("pop3AuthInternalError"); + } + + cmd += CRLF; + + // TODO needed? + //m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + + m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP; + + m_pop3ConData->pause_for_read = true; + + m_password_already_sent = true; + m_lastPasswordSent = m_passwordResult; + return Pop3SendData(cmd.get(), true); +} + +int32_t nsPop3Protocol::SendStatOrGurl(bool sendStat) +{ + nsAutoCString cmd; + if (sendStat) + { + cmd = "STAT" CRLF; + m_pop3ConData->next_state_after_response = POP3_GET_STAT; + } + else + { + cmd = "GURL" CRLF; + m_pop3ConData->next_state_after_response = POP3_GURL_RESPONSE; + } + return Pop3SendData(cmd.get()); +} + + +int32_t +nsPop3Protocol::SendStat() +{ + return SendStatOrGurl(true); +} + + +int32_t +nsPop3Protocol::GetStat() +{ + // check stat response + if (!m_pop3ConData->command_succeeded) + return Error("pop3StatFail"); + + /* stat response looks like: %d %d + * The first number is the number of articles + * The second number is the number of bytes + * + * grab the first and second arg of stat response + */ + nsCString oldStr (m_commandResponse); + char *newStr = oldStr.BeginWriting(); + char *num = NS_strtok(" ", &newStr); // msg num + if (num) + { + m_pop3ConData->number_of_messages = atol(num); // bytes + num = NS_strtok(" ", &newStr); + m_commandResponse = newStr; + if (num) + m_totalFolderSize = nsCRT::atoll(num); //we always initialize m_totalFolderSize to 0 + } + else + m_pop3ConData->number_of_messages = 0; + + m_pop3ConData->really_new_messages = 0; + m_pop3ConData->real_new_counter = 1; + + m_totalDownloadSize = -1; // Means we need to calculate it, later. + + if (m_pop3ConData->number_of_messages <= 0) + { + // We're all done. We know we have no mail. + m_pop3ConData->next_state = POP3_SEND_QUIT; + PL_HashTableEnumerateEntries(m_pop3ConData->uidlinfo->hash, hash_clear_mapper, nullptr); + // Hack - use nsPop3Sink to wipe out any stale Partial messages + m_nsIPop3Sink->BeginMailDelivery(false, nullptr, nullptr); + m_nsIPop3Sink->AbortMailDelivery(this); + return(0); + } + + /* We're just checking for new mail, and we're not playing any games that + involve keeping messages on the server. Therefore, we now know enough + to finish up. If we had no messages, that would have been handled + above; therefore, we know we have some new messages. + */ + if (m_pop3ConData->only_check_for_new_mail && !m_pop3ConData->leave_on_server) + { + m_nsIPop3Sink->SetBiffStateAndUpdateFE(nsIMsgFolder::nsMsgBiffState_NewMail, + m_pop3ConData->number_of_messages, + true); + m_pop3ConData->next_state = POP3_SEND_QUIT; + return(0); + } + + + if (!m_pop3ConData->only_check_for_new_mail) + { + /* The following was added to prevent the loss of Data when we try and + write to somewhere we don't have write access error to (See bug 62480) + (Note: This is only a temp hack until the underlying XPCOM is fixed + to return errors) */ + + nsresult rv; + nsCOMPtr <nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url); + if (mailnewsUrl) + rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); +// NS_ASSERTION(NS_SUCCEEDED(rv) && msgWindow, "no msg window"); + + rv = m_nsIPop3Sink->BeginMailDelivery(m_pop3ConData->only_uidl != nullptr, msgWindow, + &m_pop3ConData->msg_del_started); + if (NS_FAILED(rv)) + { + m_nsIPop3Sink->AbortMailDelivery(this); + if (rv == NS_MSG_FOLDER_BUSY) { + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + nsString accountName; + rv = server->GetPrettyName(accountName); + NS_ENSURE_SUCCESS(rv, -1); + + const char16_t *params[] = { accountName.get() }; + return Error("pop3ServerBusy", params, 1); + } + + return Error("pop3MessageWriteError"); + } + + if(!m_pop3ConData->msg_del_started) + return Error("pop3MessageWriteError"); + } + + m_pop3ConData->next_state = POP3_SEND_LIST; + return 0; +} + + + +int32_t +nsPop3Protocol::SendGurl() +{ + if (m_pop3ConData->capability_flags == POP3_CAPABILITY_UNDEFINED || + TestCapFlag(POP3_GURL_UNDEFINED | POP3_HAS_GURL)) + return SendStatOrGurl(false); + else + return -1; +} + + +int32_t +nsPop3Protocol::GurlResponse() +{ + ClearCapFlag(POP3_GURL_UNDEFINED); + + if (m_pop3ConData->command_succeeded) + { + SetCapFlag(POP3_HAS_GURL); + if (m_nsIPop3Sink) + m_nsIPop3Sink->SetMailAccountURL(m_commandResponse); + } + else + { + ClearCapFlag(POP3_HAS_GURL); + } + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + m_pop3ConData->next_state = POP3_SEND_QUIT; + + return 0; +} + +int32_t nsPop3Protocol::SendList() +{ + // check for server returning number of messages that will cause the calculation + // of the size of the block for msg_info to + // overflow a 32 bit int, in turn causing us to allocate a block of memory much + // smaller than we think we're allocating, and + // potentially allowing the server to make us overwrite memory outside our heap + // block. + + if (m_pop3ConData->number_of_messages > (int) (0xFFFFF000 / sizeof(Pop3MsgInfo))) + return MK_OUT_OF_MEMORY; + + + m_pop3ConData->msg_info = (Pop3MsgInfo *) + PR_CALLOC(sizeof(Pop3MsgInfo) * m_pop3ConData->number_of_messages); + if (!m_pop3ConData->msg_info) + return(MK_OUT_OF_MEMORY); + m_pop3ConData->next_state_after_response = POP3_GET_LIST; + m_listpos = 0; + return Pop3SendData("LIST" CRLF); +} + + + +int32_t +nsPop3Protocol::GetList(nsIInputStream* inputStream, + uint32_t length) +{ + /* check list response + * This will get called multiple times + * but it's alright since command_succeeded + * will remain constant + */ + if(!m_pop3ConData->command_succeeded) + return Error("pop3ListFailure"); + + uint32_t ln = 0; + bool pauseForMoreData = false; + nsresult rv; + char *line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if (pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; + PR_Free(line); + return(ln); + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + /* parse the line returned from the list command + * it looks like + * #msg_number #bytes + * + * list data is terminated by a ".CRLF" line + */ + if (!PL_strcmp(line, ".")) + { + // limit the list if fewer entries than given in STAT response + if(m_listpos < m_pop3ConData->number_of_messages) + m_pop3ConData->number_of_messages = m_listpos; + m_pop3ConData->next_state = POP3_SEND_UIDL_LIST; + m_pop3ConData->pause_for_read = false; + PR_Free(line); + return(0); + } + + char *newStr = line; + char *token = NS_strtok(" ", &newStr); + if (token) + { + int32_t msg_num = atol(token); + + if (++m_listpos <= m_pop3ConData->number_of_messages) + { + token = NS_strtok(" ", &newStr); + if (token) + { + m_pop3ConData->msg_info[m_listpos-1].size = atol(token); + m_pop3ConData->msg_info[m_listpos-1].msgnum = msg_num; + } + } + } + + PR_Free(line); + return(0); +} + + +/* UIDL and XTND are both unsupported for this mail server. + If not enabled any advanced features, we're able to live + without them. We're simply downloading and deleting everything + on the server. + + Advanced features are: + *'Keep Mail on Server' with aging or deletion support + *'Fetch Headers Only' + *'Limit Message Size' + *only download a specific UID + + These require knowledge of of all messages UID's on the server at + least when it comes to deleting deleting messages on server that + have been deleted on client or vice versa. TOP doesn't help here + without generating huge traffic and is mostly not supported at all + if the server lacks UIDL and XTND XLST. + + In other cases the user has to join the 20th century. + Tell the user this, and refuse to download any messages until + they've gone into preferences and turned off any of the above + prefs. +*/ +int32_t nsPop3Protocol::HandleNoUidListAvailable() +{ + m_pop3ConData->pause_for_read = false; + + if(!m_pop3ConData->leave_on_server && + !m_pop3ConData->headers_only && + m_pop3ConData->size_limit <= 0 && + !m_pop3ConData->only_uidl) + { + m_pop3ConData->next_state = POP3_GET_MSG; + return 0; + } + m_pop3ConData->next_state = POP3_SEND_QUIT; + nsCString hostName; + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + nsresult rv = server->GetRealHostName(hostName); + NS_ENSURE_SUCCESS(rv, -1); + NS_ConvertASCIItoUTF16 hostNameUnicode(hostName); + const char16_t *params[] = { hostNameUnicode.get() }; + return Error("pop3ServerDoesNotSupportUidlEtc", params, 1); +} + + +/* km + * + * net_pop3_send_xtnd_xlst_msgid + * + * Process state: POP3_SEND_XTND_XLST_MSGID + * + * If we get here then UIDL is not supported by the mail server. + * Some mail servers support a similar command: + * + * XTND XLST Message-Id + * + * Here is a sample transaction from a QUALCOMM server + + >>XTND XLST Message-Id + <<+OK xlst command accepted; headers coming. + <<1 Message-ID: <3117E4DC.2699@netscape.invalid> + <<2 Message-Id: <199602062335.PAA19215@lemon.example.com> + + * This function will send the xtnd command and put us into the + * POP3_GET_XTND_XLST_MSGID state + * +*/ +int32_t nsPop3Protocol::SendXtndXlstMsgid() +{ + if (TestCapFlag(POP3_HAS_XTND_XLST | POP3_XTND_XLST_UNDEFINED)) + { + m_pop3ConData->next_state_after_response = POP3_GET_XTND_XLST_MSGID; + m_pop3ConData->pause_for_read = true; + m_listpos = 0; + return Pop3SendData("XTND XLST Message-Id" CRLF); + } + else + return HandleNoUidListAvailable(); +} + + +/* km + * + * net_pop3_get_xtnd_xlst_msgid + * + * This code was created from the net_pop3_get_uidl_list boiler plate. + * The difference is that the XTND reply strings have one more token per + * string than the UIDL reply strings do. + * + */ + +int32_t +nsPop3Protocol::GetXtndXlstMsgid(nsIInputStream* inputStream, + uint32_t length) +{ + /* check list response + * This will get called multiple times + * but it's alright since command_succeeded + * will remain constant + */ + ClearCapFlag(POP3_XTND_XLST_UNDEFINED); + + if (!m_pop3ConData->command_succeeded) + { + ClearCapFlag(POP3_HAS_XTND_XLST); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + HandleNoUidListAvailable(); + return(0); + } + else + { + SetCapFlag(POP3_HAS_XTND_XLST); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + uint32_t ln = 0; + bool pauseForMoreData = false; + nsresult rv; + char *line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if (pauseForMoreData || !line) + { + m_pop3ConData->pause_for_read = true; + PR_Free(line); + return ln; + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + /* parse the line returned from the list command + * it looks like + * 1 Message-ID: <3117E4DC.2699@example.com> + * + * list data is terminated by a ".CRLF" line + */ + if (!PL_strcmp(line, ".")) + { + // limit the list if fewer entries than given in STAT response + if(m_listpos < m_pop3ConData->number_of_messages) + m_pop3ConData->number_of_messages = m_listpos; + m_pop3ConData->list_done = true; + m_pop3ConData->next_state = POP3_GET_MSG; + m_pop3ConData->pause_for_read = false; + PR_Free(line); + return(0); + } + + char *newStr = line; + char *token = NS_strtok(" ", &newStr); // msg num + if (token) + { + int32_t msg_num = atol(token); + if (++m_listpos <= m_pop3ConData->number_of_messages) + { + NS_strtok(" ", &newStr); // eat message ID token + const char *uid = NS_strtok(" ", &newStr); // not really a UID but a unique token -km + if (!uid) + /* This is bad. The server didn't give us a UIDL for this message. + I've seen this happen when somehow the mail spool has a message + that contains a header that reads "X-UIDL: \n". But how that got + there, I have no idea; must be a server bug. Or something. */ + uid = ""; + + // seeking right entry, but try the one that should it be first + int32_t i; + if(m_pop3ConData->msg_info[m_listpos - 1].msgnum == msg_num) + i = m_listpos - 1; + else + for(i = 0; i < m_pop3ConData->number_of_messages && + m_pop3ConData->msg_info[i].msgnum != msg_num; i++) + ; + + // only if found a matching slot + if (i < m_pop3ConData->number_of_messages) + { + // to protect us from memory leak in case of getting a msg num twice + m_pop3ConData->msg_info[i].uidl = PL_strdup(uid); + if (!m_pop3ConData->msg_info[i].uidl) + { + PR_Free(line); + return MK_OUT_OF_MEMORY; + } + } + } + } + + PR_Free(line); + return(0); +} + + +int32_t nsPop3Protocol::SendUidlList() +{ + if (TestCapFlag(POP3_HAS_UIDL | POP3_UIDL_UNDEFINED)) + { + m_pop3ConData->next_state_after_response = POP3_GET_UIDL_LIST; + m_pop3ConData->pause_for_read = true; + m_listpos = 0; + return Pop3SendData("UIDL" CRLF); + } + else + return SendXtndXlstMsgid(); +} + + +int32_t nsPop3Protocol::GetUidlList(nsIInputStream* inputStream, + uint32_t length) +{ + /* check list response + * This will get called multiple times + * but it's alright since command_succeeded + * will remain constant + */ + ClearCapFlag(POP3_UIDL_UNDEFINED); + + if (!m_pop3ConData->command_succeeded) + { + m_pop3ConData->next_state = POP3_SEND_XTND_XLST_MSGID; + m_pop3ConData->pause_for_read = false; + ClearCapFlag(POP3_HAS_UIDL); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + return(0); + } + else + { + SetCapFlag(POP3_HAS_UIDL); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + uint32_t ln = 0; + bool pauseForMoreData = false; + nsresult rv; + char *line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData, &rv); + if (NS_FAILED(rv)) + return -1; + + if (pauseForMoreData || !line) + { + PR_Free(line); + m_pop3ConData->pause_for_read = true; + return ln; + } + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + + /* parse the line returned from the list command + * it looks like + * #msg_number uidl + * + * list data is terminated by a ".CRLF" line + */ + if (!PL_strcmp(line, ".")) + { + // limit the list if fewer entries than given in STAT response + if (m_listpos < m_pop3ConData->number_of_messages) + m_pop3ConData->number_of_messages = m_listpos; + m_pop3ConData->list_done = true; + m_pop3ConData->next_state = POP3_GET_MSG; + m_pop3ConData->pause_for_read = false; + PR_Free(line); + return(0); + } + + char *newStr = line; + char *token = NS_strtok(" ", &newStr); // msg num + if (token) + { + int32_t msg_num = atol(token); + if (++m_listpos <= m_pop3ConData->number_of_messages) + { + const char *uid = NS_strtok(" ", &newStr); // UID + if (!uid) + /* This is bad. The server didn't give us a UIDL for this message. + I've seen this happen when somehow the mail spool has a message + that contains a header that reads "X-UIDL: \n". But how that got + there, I have no idea; must be a server bug. Or something. */ + uid = ""; + + // seeking right entry, but try the one that should it be first + int32_t i; + if(m_pop3ConData->msg_info[m_listpos - 1].msgnum == msg_num) + i = m_listpos - 1; + else + for(i = 0; i < m_pop3ConData->number_of_messages && + m_pop3ConData->msg_info[i].msgnum != msg_num; i++) + ; + + // only if found a matching slot + if (i < m_pop3ConData->number_of_messages) + { + // to protect us from memory leak in case of getting a msg num twice + m_pop3ConData->msg_info[i].uidl = PL_strdup(uid); + if (!m_pop3ConData->msg_info[i].uidl) + { + PR_Free(line); + return MK_OUT_OF_MEMORY; + } + } + } + } + PR_Free(line); + return(0); +} + + + +/* this function decides if we are going to do a + * normal RETR or a TOP. The first time, it also decides the total number + * of bytes we're probably going to get. + */ +int32_t nsPop3Protocol::GetMsg() +{ + int32_t popstateTimestamp = TimeInSecondsFromPRTime(PR_Now()); + + if (m_pop3ConData->last_accessed_msg >= m_pop3ConData->number_of_messages) + { + /* Oh, gee, we're all done. */ + if(m_pop3ConData->msg_del_started) + { + if (!m_pop3ConData->only_uidl) + { + if (m_pop3ConData->only_check_for_new_mail) + m_nsIPop3Sink->SetBiffStateAndUpdateFE(m_pop3ConData->biffstate, m_pop3ConData->really_new_messages, true); + /* update old style biff */ + else + m_nsIPop3Sink->SetBiffStateAndUpdateFE(nsIMsgFolder::nsMsgBiffState_NewMail, m_pop3ConData->really_new_messages, false); + } + m_nsIPop3Sink->EndMailDelivery(this); + } + + m_pop3ConData->next_state = POP3_SEND_QUIT; + return 0; + } + + if (m_totalDownloadSize < 0) + { + /* First time. Figure out how many bytes we're about to get. + If we didn't get any message info, then we are going to get + everything, and it's easy. Otherwise, if we only want one + uidl, than that's the only one we'll get. Otherwise, go + through each message info, decide if we're going to get that + message, and add the number of bytes for it. When a message is too + large (per user's preferences) only add the size we are supposed + to get. */ + m_pop3ConData->really_new_messages = 0; + m_pop3ConData->real_new_counter = 1; + if (m_pop3ConData->msg_info) + { + m_totalDownloadSize = 0; + for (int32_t i = 0; i < m_pop3ConData->number_of_messages; i++) + { + if (m_pop3ConData->only_uidl) + { + if (m_pop3ConData->msg_info[i].uidl && + !PL_strcmp(m_pop3ConData->msg_info[i].uidl, m_pop3ConData->only_uidl)) + { + m_totalDownloadSize = m_pop3ConData->msg_info[i].size; + m_pop3ConData->really_new_messages = 1; + // we are only getting one message + m_pop3ConData->real_new_counter = 1; + break; + } + continue; + } + + char c = 0; + popstateTimestamp = TimeInSecondsFromPRTime(PR_Now()); + if (m_pop3ConData->msg_info[i].uidl) + { + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, + m_pop3ConData->msg_info[i].uidl); + if (uidlEntry) + { + c = uidlEntry->status; + popstateTimestamp = uidlEntry->dateReceived; + } + } + if ((c == KEEP) && !m_pop3ConData->leave_on_server) + { /* This message has been downloaded but kept on server, we + * no longer want to keep it there */ + if (!m_pop3ConData->newuidl) + { + m_pop3ConData->newuidl = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, + PL_CompareValues, &gHashAllocOps, nullptr); + if (!m_pop3ConData->newuidl) + return MK_OUT_OF_MEMORY; + } + c = DELETE_CHAR; + // Mark message to be deleted in new table + put_hash(m_pop3ConData->newuidl, + m_pop3ConData->msg_info[i].uidl, DELETE_CHAR, popstateTimestamp); + // and old one too + put_hash(m_pop3ConData->uidlinfo->hash, + m_pop3ConData->msg_info[i].uidl, DELETE_CHAR, popstateTimestamp); + } + if ((c != KEEP) && (c != DELETE_CHAR) && (c != TOO_BIG)) + { // message left on server + m_totalDownloadSize += m_pop3ConData->msg_info[i].size; + m_pop3ConData->really_new_messages++; + // a message we will really download + } + } + } + else + { + m_totalDownloadSize = m_totalFolderSize; + } + if (m_pop3ConData->only_check_for_new_mail) + { + if (m_totalDownloadSize > 0) + { + m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_NewMail; + m_nsIPop3Sink->SetBiffStateAndUpdateFE(nsIMsgFolder::nsMsgBiffState_NewMail, m_pop3ConData->really_new_messages, true); + } + m_pop3ConData->next_state = POP3_SEND_QUIT; + return(0); + } + /* get the amount of available space on the drive + * and make sure there is enough + */ + if (m_totalDownloadSize > 0) // skip all this if there aren't any messages + { + nsCOMPtr<nsIMsgFolder> folder; + + // Get the current mailbox folder + NS_ENSURE_TRUE(m_nsIPop3Sink, -1); + nsresult rv = m_nsIPop3Sink->GetFolder(getter_AddRefs(folder)); + if (NS_FAILED(rv)) + return -1; + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + if (NS_FAILED(rv) || !mailnewsUrl) + return -1; + + nsCOMPtr<nsIMsgWindow> msgWindow; + (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + + bool spaceNotAvailable = true; + nsCOMPtr<nsIMsgLocalMailFolder> localFolder(do_QueryInterface(folder, &rv)); + if (NS_FAILED(rv) || !localFolder) + return -1; + + // check if we have a reasonable amount of space left + rv = localFolder->WarnIfLocalFileTooBig(msgWindow, m_totalDownloadSize, &spaceNotAvailable); + if (NS_FAILED(rv) || spaceNotAvailable) { +#ifdef DEBUG + printf("Not enough disk space! Raising error!\n"); +#endif + return -1; + } + + // Here we know how many messages we're going to download, so let + // the pop3 sink know. + rv = m_nsIPop3Sink->SetMsgsToDownload(m_pop3ConData->really_new_messages); + } + } + + /* Look at this message, and decide whether to ignore it, get it, just get + the TOP of it, or delete it. */ + + // if this is a message we've seen for the first time, we won't find it in + // m_pop3ConData-uidlinfo->hash. By default, we retrieve messages, unless they have a status, + // or are too big, in which case we figure out what to do. + if (m_prefAuthMethods != POP3_HAS_AUTH_USER && TestCapFlag(POP3_HAS_XSENDER)) + m_pop3ConData->next_state = POP3_SEND_XSENDER; + else + m_pop3ConData->next_state = POP3_SEND_RETR; + m_pop3ConData->truncating_cur_msg = false; + m_pop3ConData->pause_for_read = false; + if (m_pop3ConData->msg_info) + { + Pop3MsgInfo* info = m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg; + if (m_pop3ConData->only_uidl) + { + if (info->uidl == NULL || PL_strcmp(info->uidl, m_pop3ConData->only_uidl)) + m_pop3ConData->next_state = POP3_GET_MSG; + else + m_pop3ConData->next_state = POP3_SEND_RETR; + } + else + { + char c = 0; + if (!m_pop3ConData->newuidl) + { + m_pop3ConData->newuidl = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, &gHashAllocOps, nullptr); + if (!m_pop3ConData->newuidl) + return MK_OUT_OF_MEMORY; + } + if (info->uidl) + { + Pop3UidlEntry *uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, info->uidl); + if (uidlEntry) + { + c = uidlEntry->status; + popstateTimestamp = uidlEntry->dateReceived; + } + } + if (c == DELETE_CHAR) + { + m_pop3ConData->next_state = POP3_SEND_DELE; + } + else if (c == KEEP) + { + // this is a message we've already downloaded and left on server; + // Advance to next message. + m_pop3ConData->next_state = POP3_GET_MSG; + } + else if (c == FETCH_BODY) + { + m_pop3ConData->next_state = POP3_SEND_RETR; + PL_HashTableRemove (m_pop3ConData->uidlinfo->hash, (void*)info->uidl); + } + else if ((c != TOO_BIG) && + (TestCapFlag(POP3_TOP_UNDEFINED | POP3_HAS_TOP)) && + (m_pop3ConData->headers_only || + ((m_pop3ConData->size_limit > 0) && + (info->size > m_pop3ConData->size_limit) && + !m_pop3ConData->only_uidl)) && + info->uidl && *info->uidl) + { + // message is too big + m_pop3ConData->truncating_cur_msg = true; + m_pop3ConData->next_state = POP3_SEND_TOP; + put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG, popstateTimestamp); + } + else if (c == TOO_BIG) + { + /* message previously left on server, see if the max download size + has changed, because we may want to download the message this time + around. Otherwise ignore the message, we have the header. */ + if ((m_pop3ConData->size_limit > 0) && (info->size <= + m_pop3ConData->size_limit)) + PL_HashTableRemove (m_pop3ConData->uidlinfo->hash, (void*)info->uidl); + // remove from our table, and download + else + { + m_pop3ConData->truncating_cur_msg = true; + m_pop3ConData->next_state = POP3_GET_MSG; + // ignore this message and get next one + put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG, popstateTimestamp); + } + } + + if (m_pop3ConData->next_state != POP3_SEND_DELE && + info->uidl) + { + /* This is a message we have decided to keep on the server. Notate + that now for the future. (Don't change the popstate file at all + if only_uidl is set; in that case, there might be brand new messages + on the server that we *don't* want to mark KEEP; we just want to + leave them around until the user next does a GetNewMail.) */ + + /* If this is a message we already know about (i.e., it was + in popstate.dat already), we need to maintain the original + date the message was downloaded. */ + if (m_pop3ConData->truncating_cur_msg) + put_hash(m_pop3ConData->newuidl, info->uidl, TOO_BIG, popstateTimestamp); + else + put_hash(m_pop3ConData->newuidl, info->uidl, KEEP, popstateTimestamp); + } + } + if (m_pop3ConData->next_state == POP3_GET_MSG) + m_pop3ConData->last_accessed_msg++; + // Make sure we check the next message next time! + } + return 0; +} + + +/* start retreiving just the first 20 lines + */ +int32_t nsPop3Protocol::SendTop() +{ + char * cmd = PR_smprintf( "TOP %ld %d" CRLF, + m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum, + m_pop3ConData->headers_only ? 0 : 20); + int32_t status = -1; + if (cmd) + { + m_pop3ConData->next_state_after_response = POP3_TOP_RESPONSE; + m_pop3ConData->cur_msg_size = -1; + + /* zero the bytes received in message in preparation for + * the next + */ + m_bytesInMsgReceived = 0; + status = Pop3SendData(cmd); + } + PR_Free(cmd); + return status; +} + +/* send the xsender command + */ +int32_t nsPop3Protocol::SendXsender() +{ + char * cmd = PR_smprintf("XSENDER %ld" CRLF, m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum); + int32_t status = -1; + if (cmd) + { + m_pop3ConData->next_state_after_response = POP3_XSENDER_RESPONSE; + status = Pop3SendData(cmd); + PR_Free(cmd); + } + return status; +} + +int32_t nsPop3Protocol::XsenderResponse() +{ + m_pop3ConData->seenFromHeader = false; + m_senderInfo = ""; + + if (m_pop3ConData->command_succeeded) { + if (m_commandResponse.Length() > 4) + m_senderInfo = m_commandResponse; + } + else { + ClearCapFlag(POP3_HAS_XSENDER); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + if (m_pop3ConData->truncating_cur_msg) + m_pop3ConData->next_state = POP3_SEND_TOP; + else + m_pop3ConData->next_state = POP3_SEND_RETR; + return 0; +} + +/* retreive the whole message + */ +int32_t +nsPop3Protocol::SendRetr() +{ + + char * cmd = PR_smprintf("RETR %ld" CRLF, m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum); + int32_t status = -1; + if (cmd) + { + m_pop3ConData->next_state_after_response = POP3_RETR_RESPONSE; + m_pop3ConData->cur_msg_size = -1; + + + /* zero the bytes received in message in preparation for + * the next + */ + m_bytesInMsgReceived = 0; + + if (m_pop3ConData->only_uidl) + { + /* Display bytes if we're only downloading one message. */ + PR_ASSERT(!m_pop3ConData->graph_progress_bytes_p); + UpdateProgressPercent(0, m_totalDownloadSize); + m_pop3ConData->graph_progress_bytes_p = true; + } + else + { + nsString finalString; + mozilla::DebugOnly<nsresult> rv = + FormatCounterString(NS_LITERAL_STRING("receivingMessages"), + m_pop3ConData->real_new_counter, + m_pop3ConData->really_new_messages, + finalString); + NS_ASSERTION(NS_SUCCEEDED(rv), "couldn't format string"); + if (mProgressEventSink) { + rv = mProgressEventSink->OnStatus(this, m_channelContext, NS_OK, + finalString.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "dropping error result"); + } + } + + status = Pop3SendData(cmd); + } // if cmd + PR_Free(cmd); + return status; +} + +/* digest the message + */ +int32_t +nsPop3Protocol::RetrResponse(nsIInputStream* inputStream, + uint32_t length) +{ + uint32_t buffer_size; + int32_t flags = 0; + char *uidl = NULL; + nsresult rv; + uint32_t status = 0; + + if(m_pop3ConData->cur_msg_size == -1) + { + /* this is the beginning of a message + * get the response code and byte size + */ + if(!m_pop3ConData->command_succeeded) + return Error("pop3RetrFailure"); + + /* a successful RETR response looks like: #num_bytes Junk + from TOP we only get the +OK and data + */ + if (m_pop3ConData->truncating_cur_msg) + { /* TOP, truncated message */ + flags |= nsMsgMessageFlags::Partial; + } + else + { + nsCString cmdResp(m_commandResponse); + char *newStr = cmdResp.BeginWriting(); + char *num = NS_strtok( " ", &newStr); + if (num) + m_pop3ConData->cur_msg_size = atol(num); + m_commandResponse = newStr; + } + + /* RETR complete message */ + if (!m_senderInfo.IsEmpty()) + flags |= nsMsgMessageFlags::SenderAuthed; + + if(m_pop3ConData->cur_msg_size <= 0) + { + if (m_pop3ConData->msg_info) + m_pop3ConData->cur_msg_size = m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].size; + else + m_pop3ConData->cur_msg_size = 0; + } + + if (m_pop3ConData->msg_info && + m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].uidl) + uidl = m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].uidl; + + m_pop3ConData->parsed_bytes = 0; + m_pop3ConData->pop3_size = m_pop3ConData->cur_msg_size; + m_pop3ConData->assumed_end = false; + + m_pop3Server->GetDotFix(&m_pop3ConData->dot_fix); + + MOZ_LOG(POP3LOGMODULE,LogLevel::Info, + (POP3LOG("Opening message stream: MSG_IncorporateBegin"))); + + /* open the message stream so we have someplace + * to put the data + */ + m_pop3ConData->real_new_counter++; + /* (rb) count only real messages being downloaded */ + rv = m_nsIPop3Sink->IncorporateBegin(uidl, m_url, flags, + &m_pop3ConData->msg_closure); + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("Done opening message stream!"))); + + if(!m_pop3ConData->msg_closure || NS_FAILED(rv)) + return Error("pop3MessageWriteError"); + } + + m_pop3ConData->pause_for_read = true; + + bool pauseForMoreData = false; + char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv, true); + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + if (NS_FAILED(rv)) + return -1; + + buffer_size = status; + + if (status == 0 && !line) // no bytes read in... + return (0); + + if (m_pop3ConData->msg_closure) /* not done yet */ + { + // buffer the line we just read in, and buffer all remaining lines in the stream + status = buffer_size; + do + { + if (m_pop3ConData->msg_closure) + { + rv = HandleLine(line, buffer_size); + if (NS_FAILED(rv)) + return Error("pop3MessageWriteError"); + + // buffer_size already includes MSG_LINEBREAK_LEN so + // subtract and add CRLF + // but not really sure we always had CRLF in input since + // we also treat a single LF as line ending! + m_pop3ConData->parsed_bytes += buffer_size - MSG_LINEBREAK_LEN + 2; + } + + // now read in the next line + PR_Free(line); + line = m_lineStreamBuffer->ReadNextLine(inputStream, buffer_size, + pauseForMoreData, &rv, true); + if (NS_FAILED(rv)) + return -1; + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("RECV: %s"), line)); + // buffer_size already includes MSG_LINEBREAK_LEN so + // subtract and add CRLF + // but not really sure we always had CRLF in input since + // we also treat a single LF as line ending! + status += buffer_size - MSG_LINEBREAK_LEN + 2; + } while (line); + } + + buffer_size = status; // status holds # bytes we've actually buffered so far... + + /* normal read. Yay! */ + if ((int32_t) (m_bytesInMsgReceived + buffer_size) > m_pop3ConData->cur_msg_size) + buffer_size = m_pop3ConData->cur_msg_size - m_bytesInMsgReceived; + + m_bytesInMsgReceived += buffer_size; + m_totalBytesReceived += buffer_size; + + // *** jefft in case of the message size that server tells us is different + // from the actual message size + if (pauseForMoreData && m_pop3ConData->dot_fix && + m_pop3ConData->assumed_end && m_pop3ConData->msg_closure) + { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + nsCOMPtr<nsIMsgWindow> msgWindow; + if (NS_SUCCEEDED(rv)) + rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + rv = m_nsIPop3Sink->IncorporateComplete(msgWindow, + m_pop3ConData->truncating_cur_msg ? m_pop3ConData->cur_msg_size : 0); + + // The following was added to prevent the loss of Data when we try + // and write to somewhere we don't have write access error to (See + // bug 62480) + // (Note: This is only a temp hack until the underlying XPCOM is + // fixed to return errors) + + if (NS_FAILED(rv)) + return Error((rv == NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD) ? + "pop3TmpDownloadError" : + "pop3MessageWriteError"); + + m_pop3ConData->msg_closure = nullptr; + } + + if (!m_pop3ConData->msg_closure) + /* meaning _handle_line read ".\r\n" at end-of-msg */ + { + m_pop3ConData->pause_for_read = false; + + if (m_pop3ConData->truncating_cur_msg || + m_pop3ConData->leave_on_server ) + { + Pop3UidlEntry *uidlEntry = NULL; + Pop3MsgInfo* info = m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg; + + /* Check for filter actions - FETCH or DELETE */ + if ((m_pop3ConData->newuidl) && (info->uidl)) + uidlEntry = (Pop3UidlEntry *)PL_HashTableLookup(m_pop3ConData->newuidl, info->uidl); + + if (uidlEntry && uidlEntry->status == FETCH_BODY && + m_pop3ConData->truncating_cur_msg) + { + /* A filter decided to retrieve this full msg. + Use GetMsg() so the popstate will update correctly, + but don't let this msg get counted twice. */ + m_pop3ConData->next_state = POP3_GET_MSG; + m_pop3ConData->real_new_counter--; + /* Make sure we don't try to come through here again. */ + PL_HashTableRemove (m_pop3ConData->newuidl, (void*)info->uidl); + put_hash(m_pop3ConData->uidlinfo->hash, info->uidl, FETCH_BODY, uidlEntry->dateReceived); + + } else if (uidlEntry && uidlEntry->status == DELETE_CHAR) + { + // A filter decided to delete this msg from the server + m_pop3ConData->next_state = POP3_SEND_DELE; + } else + { + /* We've retrieved all or part of this message, but we want to + keep it on the server. Go on to the next message. */ + m_pop3ConData->last_accessed_msg++; + m_pop3ConData->next_state = POP3_GET_MSG; + } + if (m_pop3ConData->only_uidl) + { + /* GetMsg didn't update this field. Do it now */ + uidlEntry = (Pop3UidlEntry *)PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, m_pop3ConData->only_uidl); + NS_ASSERTION(uidlEntry, "uidl not found in uidlinfo"); + if (uidlEntry) + put_hash(m_pop3ConData->uidlinfo->hash, m_pop3ConData->only_uidl, KEEP, uidlEntry->dateReceived); + } + } + else + { + m_pop3ConData->next_state = POP3_SEND_DELE; + } + + /* if we didn't get the whole message add the bytes that we didn't get + to the bytes received part so that the progress percent stays sane. + */ + if (m_bytesInMsgReceived < m_pop3ConData->cur_msg_size) + m_totalBytesReceived += (m_pop3ConData->cur_msg_size - + m_bytesInMsgReceived); + } + + /* set percent done to portion of total bytes of all messages + that we're going to download. */ + if (m_totalDownloadSize) + UpdateProgressPercent(m_totalBytesReceived, m_totalDownloadSize); + + PR_Free(line); + return(0); +} + + +int32_t +nsPop3Protocol::TopResponse(nsIInputStream* inputStream, uint32_t length) +{ + if (TestCapFlag(POP3_TOP_UNDEFINED)) + { + ClearCapFlag(POP3_TOP_UNDEFINED); + if (m_pop3ConData->command_succeeded) + SetCapFlag(POP3_HAS_TOP); + else + ClearCapFlag(POP3_HAS_TOP); + m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags); + } + + if(m_pop3ConData->cur_msg_size == -1 && /* first line after TOP command sent */ + !m_pop3ConData->command_succeeded) /* and TOP command failed */ + { + /* TOP doesn't work so we can't retrieve the first part of this msg. + So just go download the whole thing, and warn the user. + + Note that the progress bar will not be accurate in this case. + Oops. #### */ + m_pop3ConData->truncating_cur_msg = false; + + nsString statusTemplate; + mLocalBundle->GetStringFromName( + u"pop3ServerDoesNotSupportTopCommand", + getter_Copies(statusTemplate)); + if (!statusTemplate.IsEmpty()) + { + nsAutoCString hostName; + char16_t * statusString = nullptr; + m_url->GetHost(hostName); + + statusString = nsTextFormatter::smprintf(statusTemplate.get(), hostName.get()); + UpdateStatusWithString(statusString); + nsTextFormatter::smprintf_free(statusString); + } + + if (m_prefAuthMethods != POP3_HAS_AUTH_USER && + TestCapFlag(POP3_HAS_XSENDER)) + m_pop3ConData->next_state = POP3_SEND_XSENDER; + else + m_pop3ConData->next_state = POP3_SEND_RETR; + return(0); + } + + /* If TOP works, we handle it in the same way as RETR. */ + return RetrResponse(inputStream, length); +} + +/* line is handed over as null-terminated string with MSG_LINEBREAK */ +nsresult +nsPop3Protocol::HandleLine(char *line, uint32_t line_length) +{ + nsresult rv = NS_OK; + + NS_ASSERTION(m_pop3ConData->msg_closure, "m_pop3ConData->msg_closure is null in nsPop3Protocol::HandleLine()"); + if (!m_pop3ConData->msg_closure) + return NS_ERROR_NULL_POINTER; + + if (!m_senderInfo.IsEmpty() && !m_pop3ConData->seenFromHeader) + { + if (line_length > 6 && !PL_strncasecmp("From: ", line, 6)) + { + m_pop3ConData->seenFromHeader = true; + if (PL_strstr(line, m_senderInfo.get()) == NULL) + m_nsIPop3Sink->SetSenderAuthedFlag(m_pop3ConData->msg_closure, + false); + } + } + + // line contains only a single dot and linebreak -> message end + if (line_length == 1 + MSG_LINEBREAK_LEN && line[0] == '.') + { + m_pop3ConData->assumed_end = true; /* in case byte count from server is */ + /* wrong, mark we may have had the end */ + if (!m_pop3ConData->dot_fix || m_pop3ConData->truncating_cur_msg || + (m_pop3ConData->parsed_bytes >= (m_pop3ConData->pop3_size -3))) + { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + nsCOMPtr<nsIMsgWindow> msgWindow; + if (NS_SUCCEEDED(rv)) + rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + rv = m_nsIPop3Sink->IncorporateComplete(msgWindow, + m_pop3ConData->truncating_cur_msg ? m_pop3ConData->cur_msg_size : 0); + + // The following was added to prevent the loss of Data when we try + // and write to somewhere we don't have write access error to (See + // bug 62480) + // (Note: This is only a temp hack until the underlying XPCOM is + // fixed to return errors) + + if (NS_FAILED(rv)) { + Error((rv == NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD) ? + "pop3TmpDownloadError" : + "pop3MessageWriteError"); + return rv; + } + + m_pop3ConData->msg_closure = nullptr; + return rv; + } + } + /* Check if the line begins with the termination octet. If so + and if another termination octet follows, we step over the + first occurence of it. */ + else if (line_length > 1 && line[0] == '.' && line[1] == '.') { + line++; + line_length--; + + } + + return m_nsIPop3Sink->IncorporateWrite(line, line_length); +} + +int32_t nsPop3Protocol::SendDele() +{ + /* increment the last accessed message since we have now read it + */ + char * cmd = PR_smprintf("DELE %ld" CRLF, m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg].msgnum); + m_pop3ConData->last_accessed_msg++; + int32_t status = -1; + if (cmd) + { + m_pop3ConData->next_state_after_response = POP3_DELE_RESPONSE; + status = Pop3SendData(cmd); + } + PR_Free(cmd); + return status; +} + +int32_t nsPop3Protocol::DeleResponse() +{ + Pop3UidlHost *host = NULL; + + host = m_pop3ConData->uidlinfo; + + /* the return from the delete will come here + */ + if(!m_pop3ConData->command_succeeded) + return Error("pop3DeleFailure"); + + + /* ###chrisf + the delete succeeded. Write out state so that we + keep track of all the deletes which have not yet been + committed on the server. Flush this state upon successful + QUIT. + + We will do this by adding each successfully deleted message id + to a list which we will write out to popstate.dat in + net_pop3_write_state(). + */ + if (host) + { + if (m_pop3ConData->msg_info && + m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl) + { + if (m_pop3ConData->newuidl) + if (m_pop3ConData->leave_on_server) + { + PL_HashTableRemove(m_pop3ConData->newuidl, (void*) + m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl); + } + else + { + put_hash(m_pop3ConData->newuidl, + m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl, DELETE_CHAR, 0); + /* kill message in new hash table */ + } + else + PL_HashTableRemove(host->hash, + (void*) m_pop3ConData->msg_info[m_pop3ConData->last_accessed_msg-1].uidl); + } + } + + m_pop3ConData->next_state = POP3_GET_MSG; + m_pop3ConData->pause_for_read = false; + + return(0); +} + + +int32_t +nsPop3Protocol::CommitState(bool remove_last_entry) +{ + // only use newuidl if we successfully finished looping through all the + // messages in the inbox. + if (m_pop3ConData->newuidl) + { + if (m_pop3ConData->last_accessed_msg >= m_pop3ConData->number_of_messages) + { + PL_HashTableDestroy(m_pop3ConData->uidlinfo->hash); + m_pop3ConData->uidlinfo->hash = m_pop3ConData->newuidl; + m_pop3ConData->newuidl = nullptr; + } + else + { + /* If we are leaving messages on the server, pull out the last + uidl from the hash, because it might have been put in there before + we got it into the database. + */ + if (remove_last_entry && m_pop3ConData->msg_info && + !m_pop3ConData->only_uidl && m_pop3ConData->newuidl->nentries > 0) + { + Pop3MsgInfo* info = m_pop3ConData->msg_info + m_pop3ConData->last_accessed_msg; + if (info && info->uidl) + { + mozilla::DebugOnly<bool> val = PL_HashTableRemove(m_pop3ConData->newuidl, + info->uidl); + NS_ASSERTION(val, "uidl not in hash table"); + } + } + + // Add the entries in newuidl to m_pop3ConData->uidlinfo->hash to keep + // track of the messages we *did* download in this session. + PL_HashTableEnumerateEntries(m_pop3ConData->newuidl, net_pop3_copy_hash_entries, (void *)m_pop3ConData->uidlinfo->hash); + } + } + + if (!m_pop3ConData->only_check_for_new_mail) + { + nsresult rv; + nsCOMPtr<nsIFile> mailDirectory; + + // get the mail directory + nsCOMPtr<nsIMsgIncomingServer> server = + do_QueryInterface(m_pop3Server, &rv); + if (NS_FAILED(rv)) return -1; + + rv = server->GetLocalPath(getter_AddRefs(mailDirectory)); + if (NS_FAILED(rv)) return -1; + + // write the state in the mail directory + net_pop3_write_state(m_pop3ConData->uidlinfo, mailDirectory.get()); + } + return 0; +} + + +/* NET_process_Pop3 will control the state machine that + * loads messages from a pop3 server + * + * returns negative if the transfer is finished or error'd out + * + * returns zero or more if the transfer needs to be continued. + */ +nsresult nsPop3Protocol::ProcessProtocolState(nsIURI * url, nsIInputStream * aInputStream, + uint64_t sourceOffset, uint32_t aLength) +{ + int32_t status = 0; + bool urlStatusSet = false; + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_url); + + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, (POP3LOG("Entering NET_ProcessPop3 %d"), + aLength)); + + m_pop3ConData->pause_for_read = false; /* already paused; reset */ + + if(m_username.IsEmpty()) + { + // net_pop3_block = false; + Error("pop3UsernameUndefined"); + return NS_MSG_SERVER_USERNAME_MISSING; + } + + while(!m_pop3ConData->pause_for_read) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Info, + (POP3LOG("Entering state: %d"), m_pop3ConData->next_state)); + + switch(m_pop3ConData->next_state) + { + case POP3_READ_PASSWORD: + // This is a separate state so that we're waiting for the user to type + // in a password while we don't actually have a connection to the pop + // server open; this saves us from having to worry about the server + // timing out on us while we wait for user input. + if (NS_FAILED(StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_EARLY))) + status = -1; + break; + case POP3_FINISH_OBTAIN_PASSWORD_EARLY: + { + if (m_passwordResult.IsEmpty() || m_username.IsEmpty()) + { + status = MK_POP3_PASSWORD_UNDEFINED; + m_pop3ConData->biffstate = nsIMsgFolder::nsMsgBiffState_Unknown; + m_nsIPop3Sink->SetBiffStateAndUpdateFE(m_pop3ConData->biffstate, 0, false); + + /* update old style biff */ + m_pop3ConData->next_state = POP3_FREE; + m_pop3ConData->pause_for_read = false; + break; + } + + m_pop3ConData->pause_for_read = false; + // we are already connected so just go on and send the username + if (m_prefAuthMethods == POP3_HAS_AUTH_USER) + { + m_currentAuthMethod = POP3_HAS_AUTH_USER; + m_pop3ConData->next_state = POP3_SEND_USERNAME; + } + else + { + if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED)) + m_pop3ConData->next_state = POP3_SEND_AUTH; + else + m_pop3ConData->next_state = POP3_SEND_CAPA; + } + break; + } + + + case POP3_START_CONNECT: + { + m_pop3ConData->next_state = POP3_FINISH_CONNECT; + m_pop3ConData->pause_for_read = false; + break; + } + + case POP3_FINISH_CONNECT: + { + m_pop3ConData->pause_for_read = false; + m_pop3ConData->next_state = POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE; + break; + } + + case POP3_WAIT_FOR_RESPONSE: + status = WaitForResponse(aInputStream, aLength); + break; + + case POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE: + { + status = WaitForStartOfConnectionResponse(aInputStream, aLength); + + if(status) + { + if (m_prefAuthMethods == POP3_HAS_AUTH_USER) + { + m_currentAuthMethod = POP3_HAS_AUTH_USER; + m_pop3ConData->next_state = POP3_SEND_USERNAME; + } + else + { + if (TestCapFlag(POP3_AUTH_MECH_UNDEFINED)) + m_pop3ConData->next_state = POP3_SEND_AUTH; + else + m_pop3ConData->next_state = POP3_SEND_CAPA; + } + } + + break; + } + + case POP3_SEND_AUTH: + status = SendAuth(); + break; + + case POP3_AUTH_RESPONSE: + status = AuthResponse(aInputStream, aLength); + break; + + case POP3_SEND_CAPA: + status = SendCapa(); + break; + + case POP3_CAPA_RESPONSE: + status = CapaResponse(aInputStream, aLength); + break; + + case POP3_TLS_RESPONSE: + status = SendTLSResponse(); + break; + + case POP3_PROCESS_AUTH: + status = ProcessAuth(); + break; + + case POP3_NEXT_AUTH_STEP: + status = NextAuthStep(); + break; + + case POP3_AUTH_LOGIN: + status = AuthLogin(); + break; + + case POP3_AUTH_LOGIN_RESPONSE: + status = AuthLoginResponse(); + break; + + case POP3_AUTH_NTLM: + status = AuthNtlm(); + break; + + case POP3_AUTH_NTLM_RESPONSE: + status = AuthNtlmResponse(); + break; + + case POP3_AUTH_GSSAPI: + status = AuthGSSAPI(); + break; + + case POP3_AUTH_GSSAPI_FIRST: + UpdateStatus(u"hostContact"); + status = AuthGSSAPIResponse(true); + break; + + case POP3_AUTH_GSSAPI_STEP: + status = AuthGSSAPIResponse(false); + break; + + case POP3_SEND_USERNAME: + if (NS_FAILED(StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_BEFORE_USERNAME))) + status = -1; + break; + + case POP3_OBTAIN_PASSWORD_BEFORE_USERNAME: + status = -1; + break; + + case POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME: + UpdateStatus(u"hostContact"); + status = SendUsername(); + break; + + case POP3_SEND_PASSWORD: + if (NS_FAILED(StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD))) + status = -1; + break; + + case POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD: + status = SendPassword(); + break; + + case POP3_SEND_GURL: + status = SendGurl(); + break; + + case POP3_GURL_RESPONSE: + status = GurlResponse(); + break; + + case POP3_SEND_STAT: + status = SendStat(); + break; + + case POP3_GET_STAT: + status = GetStat(); + break; + + case POP3_SEND_LIST: + status = SendList(); + break; + + case POP3_GET_LIST: + status = GetList(aInputStream, aLength); + break; + + case POP3_SEND_UIDL_LIST: + status = SendUidlList(); + break; + + case POP3_GET_UIDL_LIST: + status = GetUidlList(aInputStream, aLength); + break; + + case POP3_SEND_XTND_XLST_MSGID: + status = SendXtndXlstMsgid(); + break; + + case POP3_GET_XTND_XLST_MSGID: + status = GetXtndXlstMsgid(aInputStream, aLength); + break; + + case POP3_GET_MSG: + status = GetMsg(); + break; + + case POP3_SEND_TOP: + status = SendTop(); + break; + + case POP3_TOP_RESPONSE: + status = TopResponse(aInputStream, aLength); + break; + + case POP3_SEND_XSENDER: + status = SendXsender(); + break; + + case POP3_XSENDER_RESPONSE: + status = XsenderResponse(); + break; + + case POP3_SEND_RETR: + status = SendRetr(); + break; + + case POP3_RETR_RESPONSE: + status = RetrResponse(aInputStream, aLength); + break; + + case POP3_SEND_DELE: + status = SendDele(); + break; + + case POP3_DELE_RESPONSE: + status = DeleResponse(); + break; + + case POP3_SEND_QUIT: + /* attempt to send a server quit command. Since this means + everything went well, this is a good time to update the + status file and the FE's biff state. + */ + if (!m_pop3ConData->only_uidl) + { + /* update old style biff */ + if (!m_pop3ConData->only_check_for_new_mail) + { + /* We don't want to pop up a warning message any more (see + bug 54116), so instead we put the "no new messages" or + "retrieved x new messages" + in the status line. Unfortunately, this tends to be running + in a progress pane, so we try to get the real pane and + show the message there. */ + + if (m_totalDownloadSize <= 0) + { + UpdateStatus(u"noNewMessages"); + /* There are no new messages. */ + } + else + { + nsString statusString; + nsresult rv = FormatCounterString(NS_LITERAL_STRING("receivedMsgs"), + m_pop3ConData->real_new_counter - 1, + m_pop3ConData->really_new_messages, + statusString); + if (NS_SUCCEEDED(rv)) + UpdateStatusWithString(statusString.get()); + } + } + } + + status = Pop3SendData("QUIT" CRLF); + m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE; + m_pop3ConData->next_state_after_response = POP3_QUIT_RESPONSE; + break; + + case POP3_QUIT_RESPONSE: + if(m_pop3ConData->command_succeeded) + { + /* the QUIT succeeded. We can now flush the state in popstate.dat which + keeps track of any uncommitted DELE's */ + + /* clear the hash of all our uncommitted deletes */ + if (!m_pop3ConData->leave_on_server && m_pop3ConData->newuidl) + { + PL_HashTableEnumerateEntries(m_pop3ConData->newuidl, + net_pop3_remove_messages_marked_delete, + (void *)m_pop3ConData); + } + m_pop3ConData->next_state = POP3_DONE; + } + else + { + m_pop3ConData->next_state = POP3_ERROR_DONE; + } + break; + + case POP3_DONE: + CommitState(false); + m_pop3ConData->urlStatus = NS_OK; + urlStatusSet = true; + m_pop3ConData->next_state = POP3_FREE; + break; + + case POP3_ERROR_DONE: + /* write out the state */ + if(m_pop3ConData->list_done) + CommitState(true); + + if(m_pop3ConData->msg_closure) + { + m_nsIPop3Sink->IncorporateAbort(m_pop3ConData->only_uidl != nullptr); + m_pop3ConData->msg_closure = NULL; + m_nsIPop3Sink->AbortMailDelivery(this); + } + + if(m_pop3ConData->msg_del_started) + { + nsString statusString; + nsresult rv = FormatCounterString(NS_LITERAL_STRING("receivedMsgs"), + m_pop3ConData->real_new_counter - 1, + m_pop3ConData->really_new_messages, + statusString); + if (NS_SUCCEEDED(rv)) + UpdateStatusWithString(statusString.get()); + + NS_ASSERTION (!TestFlag(POP3_PASSWORD_FAILED), "POP3_PASSWORD_FAILED set when del_started"); + m_nsIPop3Sink->AbortMailDelivery(this); + } + { // this brace is to avoid compiler error about vars in switch case. + nsCOMPtr<nsIMsgWindow> msgWindow; + + if (mailnewsurl) + mailnewsurl->GetMsgWindow(getter_AddRefs(msgWindow)); + // no msgWindow means no re-prompt, so treat as error. + if (TestFlag(POP3_PASSWORD_FAILED) && msgWindow) + { + // We get here because the password was wrong. + if (!m_socketIsOpen && mailnewsurl) + { + // The server dropped the connection, so we're going + // to re-run the url. + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, + (POP3LOG("need to rerun url because connection dropped during auth"))); + m_needToRerunUrl = true; + return NS_OK; + } + m_pop3ConData->next_state = POP3_READ_PASSWORD; + m_pop3ConData->command_succeeded = true; + status = 0; + break; + } + else + /* Else we got a "real" error, so finish up. */ + m_pop3ConData->next_state = POP3_FREE; + } + m_pop3ConData->urlStatus = NS_ERROR_FAILURE; + urlStatusSet = true; + m_pop3ConData->pause_for_read = false; + break; + + case POP3_FREE: + { + UpdateProgressPercent(0,0); // clear out the progress meter + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_pop3Server); + if (server) + { + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Clearing server busy in POP3_FREE"))); + server->SetServerBusy(false); // the server is now not busy + } + MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Clearing running protocol in POP3_FREE"))); + CloseSocket(); + m_pop3Server->SetRunningProtocol(nullptr); + if (mailnewsurl && urlStatusSet) + mailnewsurl->SetUrlState(false, m_pop3ConData->urlStatus); + + m_url = nullptr; + return NS_OK; + } + default: + NS_ERROR("Got to unexpected state in nsPop3Protocol::ProcessProtocolState"); + status = -1; + } /* end switch */ + + if((status < 0) && m_pop3ConData->next_state != POP3_FREE) + { + m_pop3ConData->pause_for_read = false; + m_pop3ConData->next_state = POP3_ERROR_DONE; + } + + } /* end while */ + + return NS_OK; + +} + +NS_IMETHODIMP nsPop3Protocol::MarkMessages(nsTArray<Pop3UidlEntry*> *aUIDLArray) +{ + NS_ENSURE_ARG_POINTER(aUIDLArray); + uint32_t count = aUIDLArray->Length(); + + for (uint32_t i = 0; i < count; i++) + { + bool changed; + if (m_pop3ConData->newuidl) + MarkMsgInHashTable(m_pop3ConData->newuidl, aUIDLArray->ElementAt(i), &changed); + if (m_pop3ConData->uidlinfo) + MarkMsgInHashTable(m_pop3ConData->uidlinfo->hash, aUIDLArray->ElementAt(i), &changed); + } + return NS_OK; +} + +NS_IMETHODIMP nsPop3Protocol::CheckMessage(const char *aUidl, bool *aBool) +{ + Pop3UidlEntry *uidlEntry = nullptr; + + if (aUidl) + { + if (m_pop3ConData->newuidl) + uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->newuidl, aUidl); + else if (m_pop3ConData->uidlinfo) + uidlEntry = (Pop3UidlEntry *) PL_HashTableLookup(m_pop3ConData->uidlinfo->hash, aUidl); + } + + *aBool = uidlEntry ? true : false; + return NS_OK; +} + + +/* Function for finding an APOP Timestamp and simple check + it for its validity. If returning NS_OK m_ApopTimestamp + contains the validated substring of m_commandResponse. */ +nsresult nsPop3Protocol::GetApopTimestamp() +{ + int32_t startMark = m_commandResponse.Length(), endMark = -1; + + while (true) + { + // search for previous < + if ((startMark = MsgRFindChar(m_commandResponse, '<', startMark - 1)) < 0) + return NS_ERROR_FAILURE; + + // search for next > + if ((endMark = m_commandResponse.FindChar('>', startMark)) < 0) + continue; + + // look for an @ between start and end as a raw test + int32_t at = m_commandResponse.FindChar('@', startMark); + if (at < 0 || at >= endMark) + continue; + + // now test if sub only consists of chars in ASCII range + nsCString sub(Substring(m_commandResponse, startMark, endMark - startMark + 1)); + if (NS_IsAscii(sub.get())) + { + // set m_ApopTimestamp to the validated substring + m_ApopTimestamp.Assign(sub); + break; + } + } + + return NS_OK; +} |