summaryrefslogtreecommitdiffstats
path: root/mailnews/news/src/nsNewsFolder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/news/src/nsNewsFolder.cpp')
-rw-r--r--mailnews/news/src/nsNewsFolder.cpp1897
1 files changed, 1897 insertions, 0 deletions
diff --git a/mailnews/news/src/nsNewsFolder.cpp b/mailnews/news/src/nsNewsFolder.cpp
new file mode 100644
index 000000000..3af5ae43f
--- /dev/null
+++ b/mailnews/news/src/nsNewsFolder.cpp
@@ -0,0 +1,1897 @@
+/* -*- 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/. */
+
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "prlog.h"
+
+#include "msgCore.h" // precompiled header...
+#include "nntpCore.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsNewsFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "MailNewsTypes.h"
+#include "prprf.h"
+#include "prsystem.h"
+#include "nsIArray.h"
+#include "nsIServiceManager.h"
+#include "nsINntpService.h"
+#include "nsIFolderListener.h"
+#include "nsCOMPtr.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgNewsCID.h"
+#include "nsMsgUtils.h"
+#include "nsNewsUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsINntpIncomingServer.h"
+#include "nsINewsDatabase.h"
+#include "nsMsgBaseCID.h"
+#include "nsILineInputStream.h"
+
+#include "nsIMsgWindow.h"
+#include "nsIDocShell.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+
+#include "nsNetUtil.h"
+#include "nsIAuthPrompt.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsINntpUrl.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsArrayEnumerator.h"
+#include "nsNewsDownloader.h"
+#include "nsIStringBundle.h"
+#include "nsMsgI18N.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIMsgAccountManager.h"
+#include "nsArrayUtils.h"
+#include "nsIMsgAsyncPrompter.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMutableArray.h"
+#include "nsILoginInfo.h"
+#include "nsILoginManager.h"
+#include "nsIPromptService.h"
+#include "nsEmbedCID.h"
+#include "nsIDOMWindow.h"
+#include "mozilla/Services.h"
+#include "nsAutoPtr.h"
+#include "nsIInputStream.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+// ###tw This really ought to be the most
+// efficient file reading size for the current
+// operating system.
+#define NEWSRC_FILE_BUFFER_SIZE 1024
+
+#define kNewsSortOffset 9000
+
+#define NEWS_SCHEME "news:"
+#define SNEWS_SCHEME "snews:"
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsMsgNewsFolder::nsMsgNewsFolder(void) :
+ mExpungedBytes(0), mGettingNews(false),
+ mInitialized(false),
+ m_downloadMessageForOfflineUse(false), m_downloadingMultipleMessages(false),
+ mReadSet(nullptr), mSortOrder(kNewsSortOffset)
+{
+ MOZ_COUNT_CTOR(nsMsgNewsFolder); // double count these for now.
+ mFolderSize = kSizeUnknown;
+}
+
+nsMsgNewsFolder::~nsMsgNewsFolder(void)
+{
+ MOZ_COUNT_DTOR(nsMsgNewsFolder);
+ delete mReadSet;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsMsgNewsFolder, nsMsgDBFolder)
+NS_IMPL_RELEASE_INHERITED(nsMsgNewsFolder, nsMsgDBFolder)
+
+NS_IMETHODIMP nsMsgNewsFolder::QueryInterface(REFNSIID aIID, void** aInstancePtr)
+{
+ if (!aInstancePtr)
+ return NS_ERROR_NULL_POINTER;
+ *aInstancePtr = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsIMsgNewsFolder)))
+ *aInstancePtr = static_cast<nsIMsgNewsFolder*>(this);
+ if(*aInstancePtr)
+ {
+ AddRef();
+ return NS_OK;
+ }
+
+ return nsMsgDBFolder::QueryInterface(aIID, aInstancePtr);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsMsgNewsFolder::CreateSubFolders(nsIFile *path)
+{
+ nsresult rv;
+ bool isNewsServer = false;
+ rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isNewsServer)
+ {
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nntpServer->GetNewsrcFilePath(getter_AddRefs(mNewsrcFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = LoadNewsrcFileAndCreateNewsgroups();
+ }
+ else // is not a host, so it has no newsgroups. (what about categories??)
+ rv = NS_OK;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::AddNewsgroup(const nsACString &name, const nsACString& setStr,
+ nsIMsgFolder **child)
+{
+ NS_ENSURE_ARG_POINTER(child);
+ nsresult rv;
+ nsCOMPtr <nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+ // URI should use UTF-8
+ // (see RFC2396 Uniform Resource Identifiers (URI): Generic Syntax)
+
+ // we are handling newsgroup names in UTF-8
+ NS_ConvertUTF8toUTF16 nameUtf16(name);
+
+ nsAutoCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(nameUtf16, escapedName);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = nntpServer->AddNewsgroup(nameUtf16);
+ if (NS_FAILED(rv)) return rv;
+
+ uri.Append(escapedName);
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgNewsFolder> newsFolder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ // cache this for when we open the db
+ rv = newsFolder->SetReadSetFromStr(setStr);
+
+ rv = folder->SetParent(this);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // this what shows up in the UI
+ rv = folder->SetName(nameUtf16);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = folder->SetFlag(nsMsgFolderFlags::Newsgroup);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t numExistingGroups = mSubFolders.Count();
+
+ // add kNewsSortOffset (9000) to prevent this problem: 1,10,11,2,3,4,5
+ // We use 9000 instead of 1000 so newsgroups will sort to bottom of flat folder views
+ rv = folder->SetSortOrder(numExistingGroups + kNewsSortOffset);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ mSubFolders.AppendObject(folder);
+ folder->SetParent(this);
+ folder.swap(*child);
+ return rv;
+}
+
+nsresult nsMsgNewsFolder::ParseFolder(nsIFile *path)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsMsgNewsFolder::AddDirectorySeparator(nsIFile *path)
+{
+ // don't concat the full separator with .sbd
+ return (mURI.Equals(kNewsRootURI)) ?
+ NS_OK :
+ nsMsgDBFolder::AddDirectorySeparator(path);
+}
+
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetSubFolders(nsISimpleEnumerator **aResult)
+{
+ if (!mInitialized)
+ {
+ // do this first, so we make sure to do it, even on failure.
+ // see bug #70494
+ mInitialized = true;
+
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CreateSubFolders(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // force ourselves to get initialized from cache
+ // Don't care if it fails. this will fail the first time after
+ // migration, but we continue on. see #66018
+ (void)UpdateSummaryTotals(false);
+ }
+
+ return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER;
+}
+
+//Makes sure the database is open and exists. If the database is valid then
+//returns NS_OK. Otherwise returns a failure error value.
+nsresult nsMsgNewsFolder::GetDatabase()
+{
+ nsresult rv;
+ if (!mDatabase)
+ {
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Get the database, blowing it away if it's out of date.
+ rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase));
+ if (NS_FAILED(rv))
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if(mAddListener)
+ rv = mDatabase->AddListener(this);
+
+ nsCOMPtr<nsINewsDatabase> db = do_QueryInterface(mDatabase, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = db->SetReadSet(mReadSet);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = UpdateSummaryTotals(true);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetDatabaseWithoutCache(nsIMsgDatabase **db)
+{
+ NS_ENSURE_ARG_POINTER(db);
+
+ // The simplest way to perform this operation is to get the database normally
+ // and then clear our information about it if we didn't already hold it open.
+ bool wasCached = !!mDatabase;
+ nsresult rv = GetDatabase();
+ NS_IF_ADDREF(*db = mDatabase);
+
+ // If the DB was not open before, close our reference to it now.
+ if (!wasCached && mDatabase)
+ {
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::UpdateFolder(nsIMsgWindow *aWindow)
+{
+ // Get news.get_messages_on_select pref
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool getMessagesOnSelect = true;
+ prefBranch->GetBoolPref("news.get_messages_on_select", &getMessagesOnSelect);
+
+ // Only if news.get_messages_on_select is true do we get new messages automatically
+ if (getMessagesOnSelect)
+ {
+ rv = GetDatabase(); // want this cached...
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mDatabase)
+ {
+ nsCOMPtr<nsIMsgRetentionSettings> retentionSettings;
+ nsresult rv = GetRetentionSettings(getter_AddRefs(retentionSettings));
+ if (NS_SUCCEEDED(rv))
+ rv = mDatabase->ApplyRetentionSettings(retentionSettings, false);
+ }
+ rv = AutoCompact(aWindow);
+ NS_ENSURE_SUCCESS(rv,rv);
+ // GetNewMessages has to be the last rv set before we get to the next check, so
+ // that we'll have rv set to NS_MSG_ERROR_OFFLINE when offline and send
+ // a folder loaded notification to the front end.
+ rv = GetNewMessages(aWindow, nullptr);
+ }
+ if (rv != NS_MSG_ERROR_OFFLINE)
+ return rv;
+ }
+ // We're not getting messages because either get_messages_on_select is
+ // false or we're offline. Send an immediate folder loaded notification.
+ NotifyFolderEvent(mFolderLoadedAtom);
+ (void) RefreshSizeOnDisk();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanSubscribe(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ bool isNewsServer = false;
+ nsresult rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ // you can only subscribe to news servers, not news groups
+ *aResult = isNewsServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanFileMessages(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // you can't file messages into a news server or news group
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanCreateSubfolders(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // you can't create subfolders on a news server or a news group
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanRename(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // you can't rename a news server or a news group
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanCompact(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // you can't compact a news server or a news group
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetMessages(nsISimpleEnumerator **result)
+{
+ nsresult rv = GetDatabase();
+ *result = nullptr;
+
+ if(NS_SUCCEEDED(rv))
+ rv = mDatabase->EnumerateMessages(result);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetFolderURL(nsACString& aUrl)
+{
+ nsCString hostName;
+ nsresult rv = GetHostname(hostName);
+ nsString groupName;
+ rv = GetName(groupName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port;
+ rv = server->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ const char *newsScheme = (socketType == nsMsgSocketType::SSL) ?
+ SNEWS_SCHEME : NEWS_SCHEME;
+ nsCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(groupName, escapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString tmpStr;
+ tmpStr.Adopt(PR_smprintf("%s//%s:%ld/%s", newsScheme, hostName.get(), port,
+ escapedName.get()));
+ aUrl.Assign(tmpStr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetNewsrcHasChanged(bool newsrcHasChanged)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+ return nntpServer->SetNewsrcHasChanged(newsrcHasChanged);
+}
+
+nsresult nsMsgNewsFolder::CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder)
+{
+ nsMsgNewsFolder *newFolder = new nsMsgNewsFolder;
+ if (!newFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*folder = newFolder);
+ newFolder->Init(uri.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CreateSubfolder(const nsAString& newsgroupName,
+ nsIMsgWindow *msgWindow)
+{
+ nsresult rv = NS_OK;
+ if (newsgroupName.IsEmpty())
+ return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ nsCOMPtr<nsIMsgFolder> child;
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDatabase> newsDBFactory;
+ nsCOMPtr <nsIMsgDatabase> newsDB;
+
+ //Now let's create the actual new folder
+ rv = AddNewsgroup(NS_ConvertUTF16toUTF8(newsgroupName), EmptyCString(), getter_AddRefs(child));
+
+ if (NS_SUCCEEDED(rv))
+ SetNewsrcHasChanged(true); // subscribe UI does this - but maybe we got here through auto-subscribe
+
+ if(NS_SUCCEEDED(rv) && child){
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString dataCharset;
+ rv = nntpServer->GetCharset(dataCharset);
+ if (NS_FAILED(rv)) return rv;
+
+ child->SetCharset(dataCharset);
+ NotifyItemAdded(child);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderAdded(child);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Delete()
+{
+ nsresult rv = GetDatabase();
+
+ if(NS_SUCCEEDED(rv))
+ {
+ mDatabase->ForceClosed();
+ mDatabase = nullptr;
+ }
+
+ nsCOMPtr<nsIFile> folderPath;
+ rv = GetFilePath(getter_AddRefs(folderPath));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIFile> summaryPath;
+ rv = GetSummaryFileLocation(folderPath, getter_AddRefs(summaryPath));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool exists = false;
+ rv = folderPath->Exists(&exists);
+
+ if (NS_SUCCEEDED(rv) && exists)
+ rv = folderPath->Remove(false);
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed to remove News Folder");
+
+ rv = summaryPath->Exists(&exists);
+
+ if (NS_SUCCEEDED(rv) && exists)
+ rv = summaryPath->Remove(false);
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed to remove News Folder Summary File");
+ }
+ }
+
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString name;
+ rv = GetUnicodeName(name);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nntpServer->RemoveNewsgroup(name);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ (void) RefreshSizeOnDisk();
+
+ return SetNewsrcHasChanged(true);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Rename(const nsAString& newName, nsIMsgWindow *msgWindow)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetAbbreviatedName(nsAString& aAbbreviatedName)
+{
+ nsresult rv;
+
+ rv = nsMsgDBFolder::GetPrettyName(aAbbreviatedName);
+ if(NS_FAILED(rv)) return rv;
+
+ // only do this for newsgroup names, not for newsgroup hosts.
+ bool isNewsServer = false;
+ rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!isNewsServer) {
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ bool abbreviate = true;
+ rv = nntpServer->GetAbbreviate(&abbreviate);
+ if (NS_FAILED(rv)) return rv;
+
+ if (abbreviate)
+ rv = AbbreviatePrettyName(aAbbreviatedName, 1 /* hardcoded for now */);
+ }
+ return rv;
+}
+
+// original code from Oleg Rekutin
+// rekusha@asan.com
+// Public domain, created by Oleg Rekutin
+//
+// takes a newsgroup name, number of words from the end to leave unabberviated
+// the newsgroup name, will get reset to the following format:
+// x.x.x, where x is the first letter of each word and with the
+// exception of last 'fullwords' words, which are left intact.
+// If a word has a dash in it, it is abbreviated as a-b, where
+// 'a' is the first letter of the part of the word before the
+// dash and 'b' is the first letter of the part of the word after
+// the dash
+nsresult nsMsgNewsFolder::AbbreviatePrettyName(nsAString& prettyName, int32_t fullwords)
+{
+ nsAutoString name(prettyName);
+ int32_t totalwords = 0; // total no. of words
+
+ // get the total no. of words
+ int32_t pos = 0;
+ while(1)
+ {
+ pos = name.FindChar('.', pos);
+ if(pos == -1)
+ {
+ totalwords++;
+ break;
+ }
+ else
+ {
+ totalwords++;
+ pos++;
+ }
+ }
+
+ // get the no. of words to abbreviate
+ int32_t abbrevnum = totalwords - fullwords;
+ if (abbrevnum < 1)
+ return NS_OK; // nothing to abbreviate
+
+ // build the ellipsis
+ nsAutoString out;
+ out += name[0];
+
+ int32_t length = name.Length();
+ int32_t newword = 0; // == 2 if done with all abbreviated words
+
+ fullwords = 0;
+ char16_t currentChar;
+ for (int32_t i = 1; i < length; i++)
+ {
+ // this temporary assignment is needed to fix an intel mac compiler bug.
+ // See Bug #327037 for details.
+ currentChar = name[i];
+ if (newword < 2) {
+ switch (currentChar) {
+ case '.':
+ fullwords++;
+ // check if done with all abbreviated words...
+ if (fullwords == abbrevnum)
+ newword = 2;
+ else
+ newword = 1;
+ break;
+ case '-':
+ newword = 1;
+ break;
+ default:
+ if (newword)
+ newword = 0;
+ else
+ continue;
+ }
+ }
+ out.Append(currentChar);
+ }
+ prettyName = out;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db)
+{
+ NS_ENSURE_ARG_POINTER(folderInfo);
+ NS_ENSURE_ARG_POINTER(db);
+ nsresult openErr;
+ openErr = GetDatabase();
+ *db = mDatabase;
+ if (mDatabase) {
+ NS_ADDREF(*db);
+ if (NS_SUCCEEDED(openErr))
+ openErr = (*db)->GetDBFolderInfo(folderInfo);
+ }
+ return openErr;
+}
+
+/* this used to be MSG_FolderInfoNews::UpdateSummaryFromNNTPInfo() */
+NS_IMETHODIMP
+nsMsgNewsFolder::UpdateSummaryFromNNTPInfo(int32_t oldest, int32_t youngest, int32_t total)
+{
+ /* First, mark all of the articles now known to be expired as read. */
+ if (oldest > 1)
+ {
+ nsCString oldSet;
+ nsCString newSet;
+ mReadSet->Output(getter_Copies(oldSet));
+ mReadSet->AddRange(1, oldest - 1);
+ mReadSet->Output(getter_Copies(newSet));
+ }
+
+ /* Now search the newsrc line and figure out how many of these messages are marked as unread. */
+
+ /* make sure youngest is a least 1. MSNews seems to return a youngest of 0. */
+ if (youngest == 0)
+ youngest = 1;
+
+ int32_t unread = mReadSet->CountMissingInRange(oldest, youngest);
+ NS_ASSERTION(unread >= 0,"CountMissingInRange reported unread < 0");
+ if (unread < 0)
+ // servers can send us stuff like "211 0 41 40 nz.netstatus"
+ // we should handle it gracefully.
+ unread = 0;
+
+ if (unread > total)
+ {
+ /* This can happen when the newsrc file shows more unread than exist in the group (total is not necessarily `end - start'.) */
+ unread = total;
+ int32_t deltaInDB = mNumTotalMessages - mNumUnreadMessages;
+ //int32_t deltaInDB = m_totalInDB - m_unreadInDB;
+ /* if we know there are read messages in the db, subtract that from the unread total */
+ if (deltaInDB > 0)
+ unread -= deltaInDB;
+ }
+
+ bool dbWasOpen = mDatabase != nullptr;
+ int32_t pendingUnreadDelta = unread - mNumUnreadMessages - mNumPendingUnreadMessages;
+ int32_t pendingTotalDelta = total - mNumTotalMessages - mNumPendingTotalMessages;
+ ChangeNumPendingUnread(pendingUnreadDelta);
+ ChangeNumPendingTotalMessages(pendingTotalDelta);
+ if (!dbWasOpen && mDatabase)
+ {
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetExpungedBytesCount(int64_t *count)
+{
+ NS_ENSURE_ARG_POINTER(count);
+ *count = mExpungedBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetDeletable(bool *deletable)
+{
+ NS_ENSURE_ARG_POINTER(deletable);
+
+ *deletable = false;
+ // For legacy reasons, there can be Saved search folders under news accounts.
+ // Allow deleting those.
+ GetFlag(nsMsgFolderFlags::Virtual, deletable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::RefreshSizeOnDisk()
+{
+ uint64_t oldFolderSize = mFolderSize;
+ // We set size to unknown to force it to get recalculated from disk.
+ mFolderSize = kSizeUnknown;
+ if (NS_SUCCEEDED(GetSizeOnDisk(&mFolderSize)))
+ NotifyIntPropertyChanged(kFolderSizeAtom, oldFolderSize, mFolderSize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetSizeOnDisk(int64_t *size)
+{
+ NS_ENSURE_ARG_POINTER(size);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ // If this is the rootFolder, return 0 as a safe value.
+ if (NS_FAILED(rv) || isServer)
+ mFolderSize = 0;
+
+ // 0 is a valid folder size (meaning empty file with no offline messages),
+ // but 1 is not. So use -1 as a special value meaning no file size was fetched
+ // from disk yet.
+ if (mFolderSize == kSizeUnknown)
+ {
+ nsCOMPtr<nsIFile> diskFile;
+ nsresult rv = GetFilePath(getter_AddRefs(diskFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there were no news messages downloaded for offline use, the folder file
+ // may not exist yet. In that case size is 0.
+ bool exists = false;
+ rv = diskFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ {
+ mFolderSize = 0;
+ }
+ else
+ {
+ int64_t fileSize;
+ rv = diskFile->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mFolderSize = fileSize;
+ }
+ }
+
+ *size = mFolderSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::DeleteMessages(nsIArray *messages, nsIMsgWindow *aMsgWindow,
+ bool deleteStorage, bool isMove,
+ nsIMsgCopyServiceListener* listener,
+ bool allowUndo)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG_POINTER(messages);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ if (!isMove)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsDeleted(messages);
+ }
+
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = EnableNotifications(allMessageCountNotifications, false, true);
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t count = 0;
+ rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < count && NS_SUCCEEDED(rv); i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i, &rv);
+ if (msgHdr)
+ rv = mDatabase->DeleteHeader(msgHdr, nullptr, true, true);
+ }
+ EnableNotifications(allMessageCountNotifications, true, true);
+ }
+
+ if (!isMove)
+ NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom :
+ mDeleteOrMoveMsgFailedAtom);
+
+ (void) RefreshSizeOnDisk();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CancelMessage(nsIMsgDBHdr *msgHdr,
+ nsIMsgWindow *aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ nsresult rv;
+
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // for cancel, we need to
+ // turn "newsmessage://sspitzer@news.mozilla.org/netscape.test#5428"
+ // into "news://sspitzer@news.mozilla.org/23423@netscape.com"
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString serverURI;
+ rv = server->GetServerURI(serverURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageID;
+ rv = msgHdr->GetMessageId(getter_Copies(messageID));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // we need to escape the message ID,
+ // it might contain characters which will mess us up later, like #
+ // see bug #120502
+ nsCString escapedMessageID;
+ MsgEscapeString(messageID, nsINetUtil::ESCAPE_URL_PATH, escapedMessageID);
+
+ nsAutoCString cancelURL(serverURI.get());
+ cancelURL += '/';
+ cancelURL += escapedMessageID;
+ cancelURL += "?cancel";
+
+ nsCString messageURI;
+ rv = GetUriForMsg(msgHdr, messageURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return nntpService->CancelMessage(cancelURL.get(), messageURI.get(), nullptr /* consumer */, nullptr,
+ aMsgWindow, nullptr);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetNewMessages(nsIMsgWindow *aMsgWindow, nsIUrlListener *aListener)
+{
+ return GetNewsMessages(aMsgWindow, false, aListener);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetNextNMessages(nsIMsgWindow *aMsgWindow)
+{
+ return GetNewsMessages(aMsgWindow, true, nullptr);
+}
+
+nsresult nsMsgNewsFolder::GetNewsMessages(nsIMsgWindow *aMsgWindow, bool aGetOld, nsIUrlListener *aUrlListener)
+{
+ nsresult rv = NS_OK;
+
+ bool isNewsServer = false;
+ rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isNewsServer)
+ // get new messages only works on a newsgroup, not a news server
+ return NS_OK;
+
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIURI> resultUri;
+ rv = nntpService->GetNewNews(nntpServer, mURI.get(), aGetOld, this,
+ aMsgWindow, getter_AddRefs(resultUri));
+ if (aUrlListener && NS_SUCCEEDED(rv) && resultUri)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(resultUri));
+ if (msgUrl)
+ msgUrl->RegisterListener(aUrlListener);
+ }
+ return rv;
+}
+
+nsresult
+nsMsgNewsFolder::LoadNewsrcFileAndCreateNewsgroups()
+{
+ nsresult rv = NS_OK;
+ if (!mNewsrcFilePath) return NS_ERROR_FAILURE;
+
+ bool exists;
+ rv = mNewsrcFilePath->Exists(&exists);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!exists)
+ // it is ok for the newsrc file to not exist yet
+ return NS_OK;
+
+ nsCOMPtr<nsIInputStream> fileStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), mNewsrcFilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsCString line;
+
+ while (more && NS_SUCCEEDED(rv))
+ {
+ rv = lineInputStream->ReadLine(line, &more);
+ if (line.IsEmpty())
+ continue;
+ HandleNewsrcLine(line.get(), line.Length());
+ }
+
+ fileStream->Close();
+ return rv;
+}
+
+int32_t
+nsMsgNewsFolder::HandleNewsrcLine(const char * line, uint32_t line_size)
+{
+ nsresult rv;
+
+ /* guard against blank line lossage */
+ if (line[0] == '#' || line[0] == '\r' || line[0] == '\n') return 0;
+
+ if ((line[0] == 'o' || line[0] == 'O') &&
+ !PL_strncasecmp (line, "options", 7))
+ return RememberLine(nsDependentCString(line));
+
+ const char *s = nullptr;
+ const char *setStr = nullptr;
+ const char *end = line + line_size;
+
+ for (s = line; s < end; s++)
+ if ((*s == ':') || (*s == '!'))
+ break;
+
+ if (*s == 0)
+ /* What is this?? Well, don't just throw it away... */
+ return RememberLine(nsDependentCString(line));
+
+ bool subscribed = (*s == ':');
+ setStr = s+1;
+
+ if (*line == '\0')
+ return 0;
+
+ // previous versions of Communicator poluted the
+ // newsrc files with articles
+ // (this would happen when you clicked on a link like
+ // news://news.mozilla.org/3746EF3F.6080309@netscape.com)
+ //
+ // legal newsgroup names can't contain @ or %
+ //
+ // News group names are structured into parts separated by dots,
+ // for example "netscape.public.mozilla.mail-news".
+ // Each part may be up to 14 characters long, and should consist
+ // only of letters, digits, "+" and "-", with at least one letter
+ //
+ // @ indicates an article and %40 is @ escaped.
+ // previous versions of Communicator also dumped
+ // the escaped version into the newsrc file
+ //
+ // So lines like this in a newsrc file should be ignored:
+ // 3746EF3F.6080309@netscape.com:
+ // 3746EF3F.6080309%40netscape.com:
+ if (PL_strchr(line, '@') || PL_strstr(line, "%40"))
+ // skipping, it contains @ or %40
+ subscribed = false;
+
+ if (subscribed)
+ {
+ // we're subscribed, so add it
+ nsCOMPtr <nsIMsgFolder> child;
+
+ rv = AddNewsgroup(Substring(line, s), nsDependentCString(setStr), getter_AddRefs(child));
+ if (NS_FAILED(rv)) return -1;
+ }
+ else {
+ rv = RememberUnsubscribedGroup(nsDependentCString(line), nsDependentCString(setStr));
+ if (NS_FAILED(rv)) return -1;
+ }
+
+ return 0;
+}
+
+
+nsresult
+nsMsgNewsFolder::RememberUnsubscribedGroup(const nsACString& newsgroup, const nsACString& setStr)
+{
+ mUnsubscribedNewsgroupLines.Append(newsgroup);
+ mUnsubscribedNewsgroupLines.AppendLiteral("! ");
+ if (!setStr.IsEmpty())
+ mUnsubscribedNewsgroupLines.Append(setStr);
+ else
+ mUnsubscribedNewsgroupLines.Append(MSG_LINEBREAK);
+ return NS_OK;
+}
+
+int32_t
+nsMsgNewsFolder::RememberLine(const nsACString& line)
+{
+ mOptionLines = line;
+ mOptionLines.Append(MSG_LINEBREAK);
+ return 0;
+}
+
+nsresult nsMsgNewsFolder::ForgetLine()
+{
+ mOptionLines.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetGroupUsername(nsACString& aGroupUsername)
+{
+ aGroupUsername = mGroupUsername;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetGroupUsername(const nsACString& aGroupUsername)
+{
+ mGroupUsername = aGroupUsername;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetGroupPassword(nsACString& aGroupPassword)
+{
+ aGroupPassword = mGroupPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetGroupPassword(const nsACString& aGroupPassword)
+{
+ mGroupPassword = aGroupPassword;
+ return NS_OK;
+}
+
+nsresult nsMsgNewsFolder::CreateNewsgroupUrlForSignon(const char *ref,
+ nsAString &result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ bool singleSignon = true;
+ rv = nntpServer->GetSingleSignon(&singleSignon);
+
+ if (singleSignon)
+ {
+ nsCString serverURI;
+ rv = server->GetServerURI(serverURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->SetSpec(serverURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ rv = url->SetSpec(mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ int32_t port = 0;
+ rv = url->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (port <= 0)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ nsresult rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only set this for ssl newsgroups as for non-ssl connections, we don't
+ // need to specify the port as it is the default for the protocol and
+ // password manager "blanks" those out.
+ if (socketType == nsMsgSocketType::SSL)
+ {
+ rv = url->SetPort(nsINntpUrl::DEFAULT_NNTPS_PORT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsCString rawResult;
+ if (ref)
+ {
+ rv = url->SetRef(nsDependentCString(ref));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->GetSpec(rawResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ // If the url doesn't have a path, make sure we don't get a '/' on the end
+ // as that will confuse searching in password manager.
+ nsCString spec;
+ rv = url->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!spec.IsEmpty() && spec[spec.Length() - 1] == '/')
+ rawResult = StringHead(spec, spec.Length() - 1);
+ else
+ rawResult = spec;
+ }
+ result = NS_ConvertASCIItoUTF16(rawResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetAuthenticationCredentials(nsIMsgWindow *aMsgWindow,
+ bool mayPrompt, bool mustPrompt, bool *validCredentials)
+{
+ // Not strictly necessary, but it would help consumers to realize that this is
+ // a rather nonsensical combination.
+ NS_ENSURE_FALSE(mustPrompt && !mayPrompt, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_ARG_POINTER(validCredentials);
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString signonUrl;
+ rv = CreateNewsgroupUrlForSignon(nullptr, signonUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we don't have a username or password, try to load it via the login mgr.
+ // Do this even if mustPrompt is true, to prefill the dialog.
+ if (mGroupUsername.IsEmpty() || mGroupPassword.IsEmpty())
+ {
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numLogins = 0;
+ nsILoginInfo **logins = nullptr;
+ rv = loginMgr->FindLogins(&numLogins, signonUrl, EmptyString(), signonUrl,
+ &logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (numLogins > 0)
+ {
+ nsString uniUsername, uniPassword;
+ logins[0]->GetUsername(uniUsername);
+ logins[0]->GetPassword(uniPassword);
+ mGroupUsername = NS_LossyConvertUTF16toASCII(uniUsername);
+ mGroupPassword = NS_LossyConvertUTF16toASCII(uniPassword);
+
+ *validCredentials = true;
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numLogins, logins);
+ }
+
+ // Show the prompt if we need to
+ if (mustPrompt ||
+ (mayPrompt && (mGroupUsername.IsEmpty() || mGroupPassword.IsEmpty())))
+ {
+ nsCOMPtr<nsIAuthPrompt> dialog;
+ if (aMsgWindow)
+ {
+ rv = aMsgWindow->GetAuthPrompt(getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch)
+ wwatch->GetNewAuthPrompter(0, getter_AddRefs(dialog));
+ if (!dialog) return NS_ERROR_FAILURE;
+ }
+
+ NS_ASSERTION(dialog, "We didn't get a net prompt");
+ if (dialog)
+ {
+ // Format the prompt text strings
+ nsString promptTitle, promptText;
+ bundle->GetStringFromName(u"enterUserPassTitle",
+ getter_Copies(promptTitle));
+
+ nsString serverName;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ server->GetPrettyName(serverName);
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool singleSignon = true;
+ nntpServer->GetSingleSignon(&singleSignon);
+
+ const char16_t *params[2];
+ params[0] = mName.get();
+ params[1] = serverName.get();
+ if (singleSignon)
+ bundle->FormatStringFromName(
+ u"enterUserPassServer",
+ &params[1], 1, getter_Copies(promptText));
+ else
+ bundle->FormatStringFromName(
+ u"enterUserPassGroup",
+ params, 2, getter_Copies(promptText));
+
+ // Fill the signon url for the dialog
+ nsString signonURL;
+ rv = CreateNewsgroupUrlForSignon(nullptr, signonURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Prefill saved username/password
+ char16_t *uniGroupUsername = ToNewUnicode(
+ NS_ConvertASCIItoUTF16(mGroupUsername));
+ char16_t *uniGroupPassword = ToNewUnicode(
+ NS_ConvertASCIItoUTF16(mGroupPassword));
+
+ // Prompt for the dialog
+ rv = dialog->PromptUsernameAndPassword(promptTitle.get(),
+ promptText.get(), signonURL.get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ &uniGroupUsername, &uniGroupPassword, validCredentials);
+
+ nsAutoString uniPasswordAdopted, uniUsernameAdopted;
+ uniPasswordAdopted.Adopt(uniGroupPassword);
+ uniUsernameAdopted.Adopt(uniGroupUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only use the username/password if the user didn't cancel.
+ if (*validCredentials)
+ {
+ SetGroupUsername(NS_LossyConvertUTF16toASCII(uniUsernameAdopted));
+ SetGroupPassword(NS_LossyConvertUTF16toASCII(uniPasswordAdopted));
+ }
+ else
+ {
+ mGroupUsername.Truncate();
+ mGroupPassword.Truncate();
+ }
+ }
+ }
+
+ *validCredentials = !(mGroupUsername.IsEmpty() || mGroupPassword.IsEmpty());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::ForgetAuthenticationCredentials()
+{
+ nsString signonUrl;
+ nsresult rv = CreateNewsgroupUrlForSignon(nullptr, signonUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ nsILoginInfo** logins;
+
+ rv = loginMgr->FindLogins(&count, signonUrl, EmptyString(), signonUrl,
+ &logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // There should only be one-login stored for this url, however just in case
+ // there isn't.
+ for (uint32_t i = 0; i < count; ++i)
+ loginMgr->RemoveLogin(logins[i]);
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins);
+
+ // Clear out the saved passwords for anyone else who tries to call.
+ mGroupUsername.Truncate();
+ mGroupPassword.Truncate();
+
+ return NS_OK;
+}
+
+// change order of subfolders (newsgroups)
+// aOrientation = -1 ... aNewsgroupToMove aRefNewsgroup ...
+// aOrientation = 1 ... aRefNewsgroup aNewsgroupToMove ...
+NS_IMETHODIMP nsMsgNewsFolder::MoveFolder(nsIMsgFolder *aNewsgroupToMove, nsIMsgFolder *aRefNewsgroup, int32_t aOrientation)
+{
+ // if folders are identical do nothing
+ if (aNewsgroupToMove == aRefNewsgroup)
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+
+ // get index for aNewsgroupToMove
+ int32_t indexNewsgroupToMove = mSubFolders.IndexOf(aNewsgroupToMove);
+ if (indexNewsgroupToMove == -1)
+ // aNewsgroupToMove is no subfolder of this folder
+ return NS_ERROR_INVALID_ARG;
+
+ // get index for aRefNewsgroup
+ int32_t indexRefNewsgroup = mSubFolders.IndexOf(aRefNewsgroup);
+ if (indexRefNewsgroup == -1)
+ // aRefNewsgroup is no subfolder of this folder
+ return NS_ERROR_INVALID_ARG;
+
+ // set new index for NewsgroupToMove
+ uint32_t indexMin, indexMax;
+ if (indexNewsgroupToMove < indexRefNewsgroup)
+ {
+ if (aOrientation < 0)
+ indexRefNewsgroup--;
+ indexMin = indexNewsgroupToMove;
+ indexMax = indexRefNewsgroup;
+ }
+ else
+ {
+ if (aOrientation > 0)
+ indexRefNewsgroup++;
+ indexMin = indexRefNewsgroup;
+ indexMax = indexNewsgroupToMove;
+ }
+
+ // move NewsgroupToMove to new index and set new sort order
+ NotifyItemRemoved(aNewsgroupToMove);
+
+ if (indexNewsgroupToMove != indexRefNewsgroup)
+ {
+ nsCOMPtr<nsIMsgFolder> newsgroup = mSubFolders[indexNewsgroupToMove];
+
+ mSubFolders.RemoveObjectAt(indexNewsgroupToMove);
+
+ // indexRefNewsgroup is already set up correctly.
+ mSubFolders.InsertObjectAt(newsgroup, indexRefNewsgroup);
+ }
+
+ for (uint32_t i = indexMin; i <= indexMax; i++)
+ mSubFolders[i]->SetSortOrder(kNewsSortOffset + i);
+
+ NotifyItemAdded(aNewsgroupToMove);
+
+ // write changes back to file
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nntpServer->SetNewsrcHasChanged(true);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nntpServer->WriteNewsrcFile();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return rv;
+}
+
+nsresult nsMsgNewsFolder::CreateBaseMessageURI(const nsACString& aURI)
+{
+ return nsCreateNewsBaseMessageURI(nsCString(aURI).get(), mBaseMessageURI);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetNewsrcLine(nsACString& newsrcLine)
+{
+ nsresult rv;
+ nsString newsgroupNameUtf16;
+ rv = GetName(newsgroupNameUtf16);
+ if (NS_FAILED(rv)) return rv;
+ NS_ConvertUTF16toUTF8 newsgroupName(newsgroupNameUtf16);
+
+ newsrcLine = newsgroupName;
+ newsrcLine.Append(':');
+
+ if (mReadSet) {
+ nsCString setStr;
+ mReadSet->Output(getter_Copies(setStr));
+ if (NS_SUCCEEDED(rv))
+ {
+ newsrcLine.Append(' ');
+ newsrcLine.Append(setStr);
+ newsrcLine.AppendLiteral(MSG_LINEBREAK);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetReadSetFromStr(const nsACString& newsrcLine)
+{
+ delete mReadSet;
+ mReadSet = nsMsgKeySet::Create(nsCString(newsrcLine).get());
+ NS_ENSURE_TRUE(mReadSet, NS_ERROR_OUT_OF_MEMORY);
+
+ // Now that mReadSet is recreated, make sure it's stored in the db as well.
+ nsCOMPtr<nsINewsDatabase> db = do_QueryInterface(mDatabase);
+ if (db) // it's ok not to have a db here.
+ db->SetReadSet(mReadSet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetUnsubscribedNewsgroupLines(nsACString& aUnsubscribedNewsgroupLines)
+{
+ aUnsubscribedNewsgroupLines = mUnsubscribedNewsgroupLines;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetOptionLines(nsACString& optionLines)
+{
+ optionLines = mOptionLines;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::OnReadChanged(nsIDBChangeListener * aInstigator)
+{
+ return SetNewsrcHasChanged(true);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetUnicodeName(nsAString& aName)
+{
+ return GetName(aName);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetRawName(nsACString & aRawName)
+{
+ nsresult rv;
+ if (mRawName.IsEmpty())
+ {
+ nsString name;
+ rv = GetName(name);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // convert to the server-side encoding
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString dataCharset;
+ rv = nntpServer->GetCharset(dataCharset);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = nsMsgI18NConvertFromUnicode(dataCharset.get(), name, mRawName);
+
+ if (NS_FAILED(rv))
+ LossyCopyUTF16toASCII(name, mRawName);
+ }
+ aRawName = mRawName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetNntpServer(nsINntpIncomingServer **result)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = server->QueryInterface(NS_GET_IID(nsINntpIncomingServer),
+ getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv))
+ return rv;
+ nntpServer.swap(*result);
+ return NS_OK;
+}
+
+// this gets called after the message actually gets cancelled
+// it removes the cancelled message from the db
+NS_IMETHODIMP nsMsgNewsFolder::RemoveMessage(nsMsgKey key)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv); // if GetDatabase succeeds, mDatabase will be non-null
+
+ // Notify listeners of a delete for a single message
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> msgHdrs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ msgHdrs->AppendElement(msgHdr, false);
+
+ notifier->NotifyMsgsDeleted(msgHdrs);
+ }
+ return mDatabase->DeleteMessage(key, nullptr, false);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::RemoveMessages(nsTArray<nsMsgKey> &aMsgKeys)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv); // if GetDatabase succeeds, mDatabase will be non-null
+
+ // Notify listeners of a multiple message delete
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+
+ if (notifier)
+ {
+ nsCOMPtr<nsIMutableArray> msgHdrs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ rv = MsgGetHeadersFromKeys(mDatabase, aMsgKeys, msgHdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ notifier->NotifyMsgsDeleted(msgHdrs);
+ }
+
+ return mDatabase->DeleteMessages(aMsgKeys.Length(), aMsgKeys.Elements(), nullptr);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CancelComplete()
+{
+ NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CancelFailed()
+{
+ NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetSaveArticleOffline(bool *aBool)
+{
+ NS_ENSURE_ARG(aBool);
+ *aBool = m_downloadMessageForOfflineUse;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetSaveArticleOffline(bool aBool)
+{
+ m_downloadMessageForOfflineUse = aBool;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow)
+{
+ nsTArray<nsMsgKey> srcKeyArray;
+ SetSaveArticleOffline(true);
+ nsresult rv = NS_OK;
+
+ // build up message keys.
+ if (mDatabase)
+ {
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ bool hasMore;
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports);
+ if (pHeader && NS_SUCCEEDED(rv))
+ {
+ bool shouldStoreMsgOffline = false;
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline);
+ if (shouldStoreMsgOffline)
+ srcKeyArray.AppendElement(msgKey);
+ }
+ }
+ }
+ }
+ RefPtr<DownloadNewsArticlesToOfflineStore> downloadState =
+ new DownloadNewsArticlesToOfflineStore(msgWindow, mDatabase, this);
+ m_downloadingMultipleMessages = true;
+ rv = downloadState->DownloadArticles(msgWindow, this, &srcKeyArray);
+ (void) RefreshSizeOnDisk();
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::DownloadMessagesForOffline(nsIArray *messages, nsIMsgWindow *window)
+{
+ nsTArray<nsMsgKey> srcKeyArray;
+ SetSaveArticleOffline(true); // ### TODO need to clear this when we've finished
+ uint32_t count = 0;
+ uint32_t i;
+ nsresult rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // build up message keys.
+ for (i = 0; i < count; i++)
+ {
+ nsMsgKey key;
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr = do_QueryElementAt(messages, i, &rv);
+ if (msgDBHdr)
+ rv = msgDBHdr->GetMessageKey(&key);
+ if (NS_SUCCEEDED(rv))
+ srcKeyArray.AppendElement(key);
+ }
+ RefPtr<DownloadNewsArticlesToOfflineStore> downloadState =
+ new DownloadNewsArticlesToOfflineStore(window, mDatabase, this);
+ m_downloadingMultipleMessages = true;
+
+ rv = downloadState->DownloadArticles(window, this, &srcKeyArray);
+ (void) RefreshSizeOnDisk();
+ return rv;
+}
+
+// line does not have a line terminator (e.g., CR or CRLF)
+NS_IMETHODIMP nsMsgNewsFolder::NotifyDownloadedLine(const char *line, nsMsgKey keyOfArticle)
+{
+ nsresult rv = NS_OK;
+ if (m_downloadMessageForOfflineUse)
+ {
+ if (!m_offlineHeader)
+ {
+ GetMessageHeader(keyOfArticle, getter_AddRefs(m_offlineHeader));
+ rv = StartNewOfflineMessage();
+ }
+ m_numOfflineMsgLines++;
+ }
+
+ if (m_tempMessageStream)
+ {
+ // line now contains the linebreak.
+ if (line[0] == '.' && line[MSG_LINEBREAK_LEN + 1] == 0)
+ {
+ // end of article.
+ if (m_offlineHeader)
+ EndNewOfflineMessage();
+
+ if (m_tempMessageStream && !m_downloadingMultipleMessages)
+ {
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+ }
+ }
+ else
+ {
+ uint32_t count = 0;
+ rv = m_tempMessageStream->Write(line, strlen(line), &count);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::NotifyFinishedDownloadinghdrs()
+{
+ bool wasCached = !!mDatabase;
+ ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
+ ChangeNumPendingUnread(-mNumPendingUnreadMessages);
+ bool filtersRun;
+ // run the bayesian spam filters, if enabled.
+ CallFilterPlugins(nullptr, &filtersRun);
+
+ // If the DB was not open before, close our reference to it now.
+ if (!wasCached && mDatabase)
+ {
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ mDatabase->RemoveListener(this);
+ // This also clears all of the cached headers that may have been added while
+ // we were downloading messages (and those clearing refcount cycles in the
+ // database).
+ mDatabase->ClearCachedHdrs();
+ mDatabase = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ rv = GetDatabase();
+ if (mDatabase)
+ ApplyRetentionSettings();
+ (void) RefreshSizeOnDisk();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::ApplyRetentionSettings()
+{
+ return nsMsgDBFolder::ApplyRetentionSettings(false);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetMessageIdForKey(nsMsgKey key, nsACString& result)
+{
+ nsresult rv = GetDatabase();
+ if (!mDatabase)
+ return rv;
+ nsCOMPtr <nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCString id;
+ rv = hdr->GetMessageId(getter_Copies(id));
+ result.Assign(id);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetSortOrder(int32_t order)
+{
+ int32_t oldOrder = mSortOrder;
+
+ mSortOrder = order;
+ nsCOMPtr<nsIAtom> sortOrderAtom = MsgGetAtom("SortOrder");
+ // What to do if the atom can't be allocated?
+ NotifyIntPropertyChanged(sortOrderAtom, oldOrder, order);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetSortOrder(int32_t *order)
+{
+ NS_ENSURE_ARG_POINTER(order);
+ *order = mSortOrder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Shutdown(bool shutdownChildren)
+{
+ if (mFilterList)
+ {
+ // close the filter log stream
+ nsresult rv = mFilterList->SetLogStream(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mFilterList = nullptr;
+ }
+
+ mInitialized = false;
+ if (mReadSet) {
+ // the nsINewsDatabase holds a weak ref to the readset,
+ // and we outlive the db, so it's safe to delete it here.
+ nsCOMPtr<nsINewsDatabase> db = do_QueryInterface(mDatabase);
+ if (db)
+ db->SetReadSet(nullptr);
+ delete mReadSet;
+ mReadSet = nullptr;
+ }
+ return nsMsgDBFolder::Shutdown(shutdownChildren);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::SetFilterList(nsIMsgFilterList *aFilterList)
+{
+ if (mIsServer)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return server->SetFilterList(aFilterList);
+ }
+
+ mFilterList = aFilterList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ if (mIsServer)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return server->GetFilterList(aMsgWindow, aResult);
+ }
+
+ if (!mFilterList)
+ {
+ nsCOMPtr<nsIFile> thisFolder;
+ nsresult rv = GetFilePath(getter_AddRefs(thisFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIFile> filterFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);;
+ rv = filterFile->InitWithFile(thisFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // in 4.x, the news filter file was
+ // C:\Program Files\Netscape\Users\meer\News\host-news.mcom.com\mcom.test.dat
+ // where the summary file was
+ // C:\Program Files\Netscape\Users\meer\News\host-news.mcom.com\mcom.test.snm
+ // we make the rules file ".dat" in mozilla, so that migration works.
+
+ // NOTE:
+ // we don't we need to call NS_MsgHashIfNecessary()
+ // it's already been hashed, if necessary
+ nsCString filterFileName;
+ rv = filterFile->GetNativeLeafName(filterFileName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ filterFileName.AppendLiteral(".dat");
+
+ rv = filterFile->SetNativeLeafName(filterFileName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filterService->OpenFilterList(filterFile, this, aMsgWindow, getter_AddRefs(mFilterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*aResult = mFilterList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetEditableFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ // We don't support pluggable filter list types for news.
+ return GetFilterList(aMsgWindow, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::SetEditableFilterList(nsIMsgFilterList *aFilterList)
+{
+ return SetFilterList(aFilterList);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ if (m_tempMessageStream)
+ {
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+ }
+ m_downloadingMultipleMessages = false;
+ return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetIncomingServerType(nsACString& serverType)
+{
+ serverType.AssignLiteral("nntp");
+ return NS_OK;
+}
+