/* -*- 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 "nsNntpIncomingServer.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsNewsFolder.h" #include "nsIMsgFolder.h" #include "nsIFile.h" #include "nsCOMPtr.h" #include "nsINntpService.h" #include "nsINNTPProtocol.h" #include "nsMsgNewsCID.h" #include "nsNNTPProtocol.h" #include "nsMailDirServiceDefs.h" #include "nsMsgUtils.h" #include "nsIPrompt.h" #include "nsIStringBundle.h" #include "nntpCore.h" #include "nsIWindowWatcher.h" #include "nsTreeColumns.h" #include "nsMsgFolderFlags.h" #include "nsMsgI18N.h" #include "nsUnicharUtils.h" #include "nsILineInputStream.h" #include "nsNetUtil.h" #include "nsISimpleEnumerator.h" #include "nsMsgUtils.h" #include "mozilla/Services.h" #include "mozilla/dom/Element.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/XULTreeElement.h" #include "mozilla/dom/DataTransfer.h" #include "mozilla/LoadInfo.h" #include "mozilla/Utf8.h" #define INVALID_VERSION 0 #define VALID_VERSION 2 #define NEW_NEWS_DIR_NAME "News" #define PREF_MAIL_NEWSRC_ROOT "mail.newsrc_root" #define PREF_MAIL_NEWSRC_ROOT_REL "mail.newsrc_root-rel" #define HOSTINFO_FILE_NAME "hostinfo.dat" #define NEWS_DELIMITER '.' // this platform specific junk is so the newsrc filenames we create // will resemble the migrated newsrc filenames. #if defined(XP_UNIX) # define NEWSRC_FILE_PREFIX "newsrc-" # define NEWSRC_FILE_SUFFIX "" #else # define NEWSRC_FILE_PREFIX "" # define NEWSRC_FILE_SUFFIX ".rc" #endif /* XP_UNIX */ // ###tw This really ought to be the most // efficient file reading size for the current // operating system. #define HOSTINFO_FILE_BUFFER_SIZE 1024 #include "nsMsgUtils.h" /** * A comparator class to do cases insensitive comparisons for nsTArray.Sort() */ class nsCStringLowerCaseComparator { public: bool Equals(const nsCString& a, const nsCString& b) const { return a.Equals(b, nsCaseInsensitiveCStringComparator); } bool LessThan(const nsCString& a, const nsCString& b) const { return Compare(a, b, nsCaseInsensitiveCStringComparator) < 0; } }; static NS_DEFINE_CID(kSubscribableServerCID, NS_SUBSCRIBABLESERVER_CID); NS_IMPL_ADDREF_INHERITED(nsNntpIncomingServer, nsMsgIncomingServer) NS_IMPL_RELEASE_INHERITED(nsNntpIncomingServer, nsMsgIncomingServer) NS_INTERFACE_MAP_BEGIN(nsNntpIncomingServer) NS_INTERFACE_MAP_ENTRY(nsINntpIncomingServer) NS_INTERFACE_MAP_ENTRY(nsIUrlListener) NS_INTERFACE_MAP_ENTRY(nsISubscribableServer) NS_INTERFACE_MAP_ENTRY(nsITreeView) NS_INTERFACE_MAP_END_INHERITING(nsMsgIncomingServer) nsNntpIncomingServer::nsNntpIncomingServer() { mNewsrcHasChanged = false; mGetOnlyNew = true; mHostInfoLoaded = false; mHostInfoHasChanged = false; mVersion = INVALID_VERSION; mLastGroupDate = 0; mUniqueId = 0; mHasSeenBeginGroups = false; mPostingAllowed = false; mLastUpdatedTime = 0; // we have server wide and per group filters m_canHaveFilters = true; SetupNewsrcSaveTimer(); } nsNntpIncomingServer::~nsNntpIncomingServer() { mozilla::DebugOnly rv; if (mNewsrcSaveTimer) { mNewsrcSaveTimer->Cancel(); mNewsrcSaveTimer = nullptr; } rv = ClearInner(); NS_ASSERTION(NS_SUCCEEDED(rv), "ClearInner failed"); rv = CloseCachedConnections(); NS_ASSERTION(NS_SUCCEEDED(rv), "CloseCachedConnections failed"); } NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, NotifyOn, "notify.on") NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, MarkOldRead, "mark_old_read") NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, Abbreviate, "abbreviate") NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, PushAuth, "always_authenticate") NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, SingleSignon, "singleSignon") NS_IMPL_SERVERPREF_INT(nsNntpIncomingServer, MaxArticles, "max_articles") nsresult nsNntpIncomingServer::CreateRootFolderFromUri( const nsACString& serverUri, nsIMsgFolder** rootFolder) { nsMsgNewsFolder* newRootFolder = new nsMsgNewsFolder; NS_ADDREF(*rootFolder = newRootFolder); newRootFolder->Init(serverUri); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetNewsrcFilePath(nsIFile** aNewsrcFilePath) { nsresult rv; if (mNewsrcFilePath) { NS_IF_ADDREF(*aNewsrcFilePath = mNewsrcFilePath); return NS_OK; } rv = GetFileValue("newsrc.file-rel", "newsrc.file", aNewsrcFilePath); if (NS_SUCCEEDED(rv) && *aNewsrcFilePath) { mNewsrcFilePath = *aNewsrcFilePath; return rv; } rv = GetNewsrcRootPath(getter_AddRefs(mNewsrcFilePath)); if (NS_FAILED(rv)) return rv; nsCString hostname; rv = GetHostName(hostname); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString newsrcFileName(NEWSRC_FILE_PREFIX); newsrcFileName.Append(hostname); newsrcFileName.Append(NEWSRC_FILE_SUFFIX); rv = mNewsrcFilePath->AppendNative(newsrcFileName); rv = mNewsrcFilePath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644); NS_ENSURE_SUCCESS(rv, rv); rv = SetNewsrcFilePath(mNewsrcFilePath); NS_ENSURE_SUCCESS(rv, rv); NS_ADDREF(*aNewsrcFilePath = mNewsrcFilePath); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetNewsrcFilePath(nsIFile* aFile) { NS_ENSURE_ARG_POINTER(aFile); bool exists; nsresult rv = aFile->Exists(&exists); if (!exists) { rv = aFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0664); if (NS_FAILED(rv)) return rv; } return SetFileValue("newsrc.file-rel", "newsrc.file", aFile); } NS_IMETHODIMP nsNntpIncomingServer::GetLocalStoreType(nsACString& type) { type.AssignLiteral("news"); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetLocalDatabaseType(nsACString& type) { type.AssignLiteral("news"); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetNewsrcRootPath(nsIFile* aNewsrcRootPath) { NS_ENSURE_ARG(aNewsrcRootPath); return NS_SetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT, aNewsrcRootPath); } NS_IMETHODIMP nsNntpIncomingServer::GetNewsrcRootPath(nsIFile** aNewsrcRootPath) { NS_ENSURE_ARG_POINTER(aNewsrcRootPath); *aNewsrcRootPath = nullptr; bool havePref; nsresult rv = NS_GetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT, NS_APP_NEWS_50_DIR, havePref, aNewsrcRootPath); NS_ENSURE_SUCCESS(rv, rv); bool exists; rv = (*aNewsrcRootPath)->Exists(&exists); if (NS_SUCCEEDED(rv) && !exists) rv = (*aNewsrcRootPath)->Create(nsIFile::DIRECTORY_TYPE, 0775); NS_ENSURE_SUCCESS(rv, rv); if (!havePref || !exists) { rv = NS_SetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT, *aNewsrcRootPath); NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref."); } return rv; } /* static */ void nsNntpIncomingServer::OnNewsrcSaveTimer( nsITimer* timer, void* voidIncomingServer) { nsNntpIncomingServer* incomingServer = (nsNntpIncomingServer*)voidIncomingServer; incomingServer->WriteNewsrcFile(); } nsresult nsNntpIncomingServer::SetupNewsrcSaveTimer() { int64_t ms(300000); // hard code, 5 minutes. // Convert biffDelay into milliseconds uint32_t timeInMSUint32 = (uint32_t)ms; // Can't currently reset a timer when it's in the process of // calling Notify. So, just release the timer here and create a new one. if (mNewsrcSaveTimer) mNewsrcSaveTimer->Cancel(); mNewsrcSaveTimer = do_CreateInstance("@mozilla.org/timer;1"); mNewsrcSaveTimer->InitWithNamedFuncCallback( OnNewsrcSaveTimer, (void*)this, timeInMSUint32, nsITimer::TYPE_REPEATING_SLACK, "nsNntpIncomingServer::OnNewsrcSaveTimer"); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetCharset(const nsACString& aCharset) { return SetCharValue("charset", aCharset); } NS_IMETHODIMP nsNntpIncomingServer::GetCharset(nsACString& aCharset) { // first we get the per-server settings mail.server..charset nsresult rv = GetCharValue("charset", aCharset); NS_ENSURE_SUCCESS(rv, rv); // if the per-server setting is empty, default to UTF-8 and set it as // per-server preference. if (aCharset.IsEmpty()) { aCharset.AssignLiteral("UTF-8"); SetCharset(aCharset); } return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::WriteNewsrcFile() { nsresult rv; bool newsrcHasChanged; rv = GetNewsrcHasChanged(&newsrcHasChanged); if (NS_FAILED(rv)) return rv; #ifdef DEBUG_NEWS nsCString hostname; rv = GetHostName(hostname); if (NS_FAILED(rv)) return rv; #endif /* DEBUG_NEWS */ if (newsrcHasChanged) { #ifdef DEBUG_NEWS printf("write newsrc file for %s\n", hostname.get()); #endif nsCOMPtr newsrcFile; rv = GetNewsrcFilePath(getter_AddRefs(newsrcFile)); if (NS_FAILED(rv)) return rv; nsCOMPtr newsrcStream; nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(newsrcStream), newsrcFile, -1, 00600); if (NS_FAILED(rv)) return rv; nsCOMPtr rootFolder; rv = GetRootFolder(getter_AddRefs(rootFolder)); if (NS_FAILED(rv)) return rv; nsCOMPtr newsFolder = do_QueryInterface(rootFolder, &rv); if (NS_FAILED(rv)) return rv; uint32_t bytesWritten; nsCString optionLines; rv = newsFolder->GetOptionLines(optionLines); if (NS_SUCCEEDED(rv) && !optionLines.IsEmpty()) { newsrcStream->Write(optionLines.get(), optionLines.Length(), &bytesWritten); #ifdef DEBUG_NEWS printf("option lines:\n%s", optionLines.get()); #endif /* DEBUG_NEWS */ } #ifdef DEBUG_NEWS else { printf("no option lines to write out\n"); } #endif /* DEBUG_NEWS */ nsCString unsubscribedLines; rv = newsFolder->GetUnsubscribedNewsgroupLines(unsubscribedLines); if (NS_SUCCEEDED(rv) && !unsubscribedLines.IsEmpty()) { newsrcStream->Write(unsubscribedLines.get(), unsubscribedLines.Length(), &bytesWritten); #ifdef DEBUG_NEWS printf("unsubscribedLines:\n%s", unsubscribedLines.get()); #endif /* DEBUG_NEWS */ } #ifdef DEBUG_NEWS else { printf("no unsubscribed lines to write out\n"); } #endif /* DEBUG_NEWS */ nsTArray> subFolders; rv = rootFolder->GetSubFolders(subFolders); NS_ENSURE_SUCCESS(rv, rv); for (nsIMsgFolder* child : subFolders) { newsFolder = do_QueryInterface(child, &rv); if (NS_SUCCEEDED(rv) && newsFolder) { nsCString newsrcLine; rv = newsFolder->GetNewsrcLine(newsrcLine); if (NS_SUCCEEDED(rv) && !newsrcLine.IsEmpty()) { // write the line to the newsrc file newsrcStream->Write(newsrcLine.get(), newsrcLine.Length(), &bytesWritten); } } } newsrcStream->Close(); rv = SetNewsrcHasChanged(false); if (NS_FAILED(rv)) return rv; } #ifdef DEBUG_NEWS else { printf("no need to write newsrc file for %s, it was not dirty\n", (hostname.get())); } #endif /* DEBUG_NEWS */ return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetNewsrcHasChanged(bool aNewsrcHasChanged) { mNewsrcHasChanged = aNewsrcHasChanged; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetNewsrcHasChanged(bool* aNewsrcHasChanged) { if (!aNewsrcHasChanged) return NS_ERROR_NULL_POINTER; *aNewsrcHasChanged = mNewsrcHasChanged; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::CloseCachedConnections() { nsresult rv; nsCOMPtr connection; // iterate through the connection cache and close the connections. int32_t cnt = mConnectionCache.Count(); for (int32_t i = 0; i < cnt; ++i) { connection = mConnectionCache[0]; if (connection) { rv = connection->CloseConnection(); // We need to do this instead of RemoveObjectAt(0) because the // above call will likely cause the object to be removed from the // array anyway mConnectionCache.RemoveObject(connection); } } rv = WriteNewsrcFile(); if (NS_FAILED(rv)) return rv; if (!mGetOnlyNew && !mHostInfoLoaded) { rv = WriteHostInfoFile(); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetMaximumConnectionsNumber(int32_t* aMaxConnections) { NS_ENSURE_ARG_POINTER(aMaxConnections); nsresult rv = GetIntValue("max_cached_connections", aMaxConnections); // Get our maximum connection count. We need at least 1. If the value is 0, // we use the default. If it's negative, we treat that as 1. if (NS_SUCCEEDED(rv) && *aMaxConnections > 0) return NS_OK; *aMaxConnections = (NS_FAILED(rv) || (*aMaxConnections == 0)) ? 2 : 1; (void)SetMaximumConnectionsNumber(*aMaxConnections); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetMaximumConnectionsNumber(int32_t aMaxConnections) { return SetIntValue("max_cached_connections", aMaxConnections); } bool nsNntpIncomingServer::ConnectionTimeOut(nsINNTPProtocol* aConnection) { bool retVal = false; if (!aConnection) return retVal; PRTime lastActiveTimeStamp; if (NS_FAILED(aConnection->GetLastActiveTimeStamp(&lastActiveTimeStamp))) return retVal; if (PR_Now() - lastActiveTimeStamp >= PRTime(170) * PR_USEC_PER_SEC) { #ifdef DEBUG_seth printf( "XXX connection timed out, close it, and remove it from the connection " "cache\n"); #endif aConnection->CloseConnection(); mConnectionCache.RemoveObject(aConnection); retVal = true; } return retVal; } nsresult nsNntpIncomingServer::CreateProtocolInstance( nsINNTPProtocol** aNntpConnection, nsIURI* url, nsIMsgWindow* aMsgWindow) { // create a new connection and add it to the connection cache // we may need to flag the protocol connection as busy so we don't get // a race // condition where someone else goes through this code nsNNTPProtocol* protocolInstance = new nsNNTPProtocol(this, url, aMsgWindow); if (!protocolInstance) return NS_ERROR_OUT_OF_MEMORY; nsresult rv = protocolInstance->QueryInterface(NS_GET_IID(nsINNTPProtocol), (void**)aNntpConnection); // take the protocol instance and add it to the connectionCache if (NS_SUCCEEDED(rv) && *aNntpConnection) mConnectionCache.AppendObject(*aNntpConnection); return rv; } /** * Find an available nsNNTPConnection. Can create new connections as long as * we stay under the maximum connection limit. * If none are available, returns NS_OK and nullptr. */ nsresult nsNntpIncomingServer::GetNntpConnection( nsIURI* aUri, nsIMsgWindow* aMsgWindow, nsINNTPProtocol** aNntpConnection) { int32_t maxConnections; (void)GetMaximumConnectionsNumber(&maxConnections); // Find a non-busy connection nsCOMPtr connection; int32_t cnt = mConnectionCache.Count(); for (int32_t i = 0; i < cnt; i++) { connection = mConnectionCache[i]; if (connection) { bool isBusy; connection->GetIsBusy(&isBusy); if (!isBusy) break; connection = nullptr; } } if (ConnectionTimeOut(connection)) { connection = nullptr; // We have one less connection, since we closed this one. --cnt; } if (connection) { // We've got a connection, ready to go. connection.forget(aNntpConnection); } else if (cnt < maxConnections) { // We have room for another connection. Create this connection and return // it to the caller. nsresult rv = CreateProtocolInstance(aNntpConnection, aUri, aMsgWindow); NS_ENSURE_SUCCESS(rv, rv); } else { // We maxed out our connection count. The caller must therefore enqueue the // call. *aNntpConnection = nullptr; return NS_OK; } // Initialize the URI here and now. return (*aNntpConnection)->Initialize(aUri, aMsgWindow); } /** * Returns an nsIChannel to run the given URI. * The returned channel might be either an nsNNTPProtocol or nsNntpMockChannel, * representing a real connection or a queued command, respectively. */ NS_IMETHODIMP nsNntpIncomingServer::GetNntpChannel(nsIURI* aURI, nsIMsgWindow* aMsgWindow, nsIChannel** aChannel) { NS_ENSURE_ARG_POINTER(aChannel); nsCOMPtr protocol; nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol)); NS_ENSURE_SUCCESS(rv, rv); if (protocol) return CallQueryInterface(protocol, aChannel); // No protocol? We need our mock channel. nsNntpMockChannel* channel = new nsNntpMockChannel(aURI, aMsgWindow); NS_ADDREF(*aChannel = channel); m_queuedChannels.AppendElement(channel); return NS_OK; } /** * Submits a news URI to run. If no connections are free, the command will * be queued to run when one becomes available. */ NS_IMETHODIMP nsNntpIncomingServer::LoadNewsUrl(nsIURI* aURI, nsIMsgWindow* aMsgWindow, nsISupports* aConsumer) { nsCOMPtr protocol; nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr loadInfo = new mozilla::net::LoadInfo( nsContentUtils::GetSystemPrincipal(), nullptr, nullptr, nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, nsIContentPolicy::TYPE_OTHER); if (protocol) { nsCOMPtr chan = do_QueryInterface(protocol, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = chan->SetLoadInfo(loadInfo); NS_ENSURE_SUCCESS(rv, rv); return protocol->LoadNewsUrl(aURI, aConsumer); } // No protocol? We need our mock channel. nsNntpMockChannel* channel = new nsNntpMockChannel(aURI, aMsgWindow, aConsumer); if (!channel) return NS_ERROR_OUT_OF_MEMORY; rv = channel->SetLoadInfo(loadInfo); NS_ENSURE_SUCCESS(rv, rv); m_queuedChannels.AppendElement(channel); return NS_OK; } /** * Called when an nsNNTPProtocol finishes running a command and is ready to be * assigned a new one. */ NS_IMETHODIMP nsNntpIncomingServer::PrepareForNextUrl(nsNNTPProtocol* aConnection) { NS_ENSURE_ARG(aConnection); // Start the connection on the next URL in the queue. If it can't get a URL to // work, drop that URL (the channel will handle failure notification) and move // on. while (m_queuedChannels.Length() > 0) { RefPtr channel = m_queuedChannels[0]; m_queuedChannels.RemoveElementAt(0); nsresult rv = channel->AttachNNTPConnection(*aConnection); // If this succeeded, the connection is now running the URL. if (NS_SUCCEEDED(rv)) return NS_OK; } // No queued uris. return NS_OK; } /* void RemoveConnection (in nsINNTPProtocol aNntpConnection); */ NS_IMETHODIMP nsNntpIncomingServer::RemoveConnection( nsINNTPProtocol* aNntpConnection) { if (aNntpConnection) mConnectionCache.RemoveObject(aNntpConnection); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::PerformExpand(nsIMsgWindow* aMsgWindow) { // Get news.update_unread_on_expand pref nsresult rv; bool updateUnreadOnExpand = true; nsCOMPtr prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) prefBranch->GetBoolPref("news.update_unread_on_expand", &updateUnreadOnExpand); // Only if news.update_unread_on_expand is true do we update the unread counts if (updateUnreadOnExpand) return DownloadMail(aMsgWindow); return NS_OK; } nsresult nsNntpIncomingServer::DownloadMail(nsIMsgWindow* aMsgWindow) { nsCOMPtr rootFolder; nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); NS_ENSURE_SUCCESS(rv, rv); nsTArray> groups; rv = rootFolder->GetSubFolders(groups); NS_ENSURE_SUCCESS(rv, rv); for (nsIMsgFolder* group : groups) { rv = group->GetNewMessages(aMsgWindow, nullptr); NS_ENSURE_SUCCESS(rv, rv); } return rv; } NS_IMETHODIMP nsNntpIncomingServer::DisplaySubscribedGroup(nsIMsgNewsFolder* aMsgFolder, int32_t aFirstMessage, int32_t aLastMessage, int32_t aTotalMessages) { nsresult rv; if (!aMsgFolder) return NS_ERROR_NULL_POINTER; #ifdef DEBUG_NEWS printf("DisplaySubscribedGroup(...,%ld,%ld,%ld)\n", aFirstMessage, aLastMessage, aTotalMessages); #endif rv = aMsgFolder->UpdateSummaryFromNNTPInfo(aFirstMessage, aLastMessage, aTotalMessages); return rv; } NS_IMETHODIMP nsNntpIncomingServer::PerformBiff(nsIMsgWindow* aMsgWindow) { // Biff will force a download of the messages. If the user doesn't want this // (e.g., there is a lot of high-traffic newsgroups), the better option is to // just ignore biff. return PerformExpand(aMsgWindow); } NS_IMETHODIMP nsNntpIncomingServer::GetServerRequiresPasswordForBiff( bool* aServerRequiresPasswordForBiff) { NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff); *aServerRequiresPasswordForBiff = false; // for news, biff is getting the unread counts return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::OnStartRunningUrl(nsIURI* url) { return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::OnStopRunningUrl(nsIURI* url, nsresult exitCode) { nsresult rv; rv = UpdateSubscribed(); if (NS_FAILED(rv)) return rv; rv = StopPopulating(mMsgWindow); if (NS_FAILED(rv)) return rv; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::ContainsNewsgroup(const nsACString& aName, bool* containsGroup) { NS_ENSURE_ARG_POINTER(containsGroup); NS_ENSURE_FALSE(aName.IsEmpty(), NS_ERROR_FAILURE); if (mSubscribedNewsgroups.Length() == 0) { // If this is empty, we may need to discover folders nsCOMPtr rootFolder; GetRootFolder(getter_AddRefs(rootFolder)); if (rootFolder) { nsTArray> dummy; rootFolder->GetSubFolders(dummy); } } nsAutoCString unescapedName; MsgUnescapeString(aName, 0, unescapedName); *containsGroup = mSubscribedNewsgroups.Contains(aName); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SubscribeToNewsgroup(const nsACString& aName) { NS_ASSERTION(!aName.IsEmpty(), "no name"); NS_ENSURE_FALSE(aName.IsEmpty(), NS_ERROR_FAILURE); // If we already have this newsgroup, do nothing and report success. bool containsGroup = false; nsresult rv = ContainsNewsgroup(aName, &containsGroup); NS_ENSURE_SUCCESS(rv, rv); if (containsGroup) return NS_OK; nsCOMPtr msgfolder; rv = GetRootMsgFolder(getter_AddRefs(msgfolder)); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(msgfolder, NS_ERROR_FAILURE); return msgfolder->CreateSubfolder(NS_ConvertUTF8toUTF16(aName), nullptr); } bool writeGroupToHostInfoFile(nsCString& aElement, void* aData) { nsIOutputStream* stream; stream = (nsIOutputStream*)aData; NS_ASSERTION(stream, "no stream"); if (!stream) { // stop, something is bad. return false; } return true; } void nsNntpIncomingServer::WriteLine(nsIOutputStream* stream, nsCString& str) { uint32_t bytesWritten; str.Append(MSG_LINEBREAK); stream->Write(str.get(), str.Length(), &bytesWritten); } nsresult nsNntpIncomingServer::WriteHostInfoFile() { if (!mHostInfoHasChanged) return NS_OK; mLastUpdatedTime = uint32_t(PR_Now() / PR_USEC_PER_SEC); nsCString hostname; nsresult rv = GetHostName(hostname); NS_ENSURE_SUCCESS(rv, rv); if (!mHostInfoFile) return NS_ERROR_UNEXPECTED; nsCOMPtr hostInfoStream; rv = MsgNewBufferedFileOutputStream(getter_AddRefs(hostInfoStream), mHostInfoFile, -1, 00600); NS_ENSURE_SUCCESS(rv, rv); // XXX TODO: missing some formatting, see the 4.x code nsAutoCString header("# News host information file."); WriteLine(hostInfoStream, header); header.AssignLiteral("# This is a generated file! Do not edit."); WriteLine(hostInfoStream, header); header.Truncate(); WriteLine(hostInfoStream, header); nsAutoCString version("version="); version.AppendInt(VALID_VERSION); WriteLine(hostInfoStream, version); nsAutoCString newsrcname("newsrcname="); newsrcname.Append(hostname); WriteLine(hostInfoStream, hostname); nsAutoCString dateStr("lastgroupdate="); dateStr.AppendInt(mLastUpdatedTime); WriteLine(hostInfoStream, dateStr); dateStr = "uniqueid="; dateStr.AppendInt(mUniqueId); WriteLine(hostInfoStream, dateStr); header.Assign(MSG_LINEBREAK "begingroups"); WriteLine(hostInfoStream, header); // XXX TODO: sort groups first? uint32_t length = mGroupsOnServer.Length(); for (uint32_t i = 0; i < length; ++i) { uint32_t bytesWritten; hostInfoStream->Write(mGroupsOnServer[i].get(), mGroupsOnServer[i].Length(), &bytesWritten); hostInfoStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten); } hostInfoStream->Close(); mHostInfoHasChanged = false; return NS_OK; } nsresult nsNntpIncomingServer::LoadHostInfoFile() { nsresult rv; // we haven't loaded it yet mHostInfoLoaded = false; rv = GetLocalPath(getter_AddRefs(mHostInfoFile)); if (NS_FAILED(rv)) return rv; if (!mHostInfoFile) return NS_ERROR_FAILURE; rv = mHostInfoFile->AppendNative(nsLiteralCString(HOSTINFO_FILE_NAME)); if (NS_FAILED(rv)) return rv; bool exists; rv = mHostInfoFile->Exists(&exists); if (NS_FAILED(rv)) return rv; // it is ok if the hostinfo.dat file does not exist. if (!exists) return NS_OK; nsCOMPtr fileStream; rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), mHostInfoFile); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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; HandleLine(line.get(), line.Length()); } mHasSeenBeginGroups = false; fileStream->Close(); return UpdateSubscribed(); } NS_IMETHODIMP nsNntpIncomingServer::StartPopulatingWithUri(nsIMsgWindow* aMsgWindow, bool aForceToServer, const nsACString& uri) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); rv = mInner->StartPopulatingWithUri(aMsgWindow, aForceToServer, uri); NS_ENSURE_SUCCESS(rv, rv); rv = StopPopulating(mMsgWindow); if (NS_FAILED(rv)) return rv; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SubscribeCleanup() { nsresult rv = NS_OK; rv = ClearInner(); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::StartPopulating(nsIMsgWindow* aMsgWindow, bool aForceToServer, bool aGetOnlyNew) { mMsgWindow = aMsgWindow; nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); rv = mInner->StartPopulating(aMsgWindow, aForceToServer, aGetOnlyNew); NS_ENSURE_SUCCESS(rv, rv); rv = SetDelimiter(NEWS_DELIMITER); if (NS_FAILED(rv)) return rv; rv = SetShowFullName(true); if (NS_FAILED(rv)) return rv; nsCOMPtr nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); mHostInfoLoaded = false; mVersion = INVALID_VERSION; mGroupsOnServer.Clear(); mGetOnlyNew = aGetOnlyNew; if (!aForceToServer) { rv = LoadHostInfoFile(); if (NS_FAILED(rv)) return rv; } // mHostInfoLoaded can be false if we failed to load anything if (aForceToServer || !mHostInfoLoaded || (mVersion != VALID_VERSION)) { // set these to true, so when we are done and we call WriteHostInfoFile() // we'll write out to hostinfo.dat mHostInfoHasChanged = true; mVersion = VALID_VERSION; mGroupsOnServer.Clear(); rv = nntpService->GetListOfGroupsOnServer(this, aMsgWindow, aGetOnlyNew); if (NS_FAILED(rv)) return rv; } else { rv = StopPopulating(aMsgWindow); if (NS_FAILED(rv)) return rv; } return NS_OK; } /** * This method is the entry point for |nsNNTPProtocol| class. |aName| is now * encoded in the serverside character encoding, but we need to handle * newsgroup names in UTF-8 internally, So we convert |aName| to * UTF-8 here for later use. **/ NS_IMETHODIMP nsNntpIncomingServer::AddNewsgroupToList(const char* aName) { nsresult rv; nsAutoString newsgroupName; nsAutoCString dataCharset; rv = GetCharset(dataCharset); NS_ENSURE_SUCCESS(rv, rv); rv = nsMsgI18NConvertToUnicode(dataCharset, nsDependentCString(aName), newsgroupName); #ifdef DEBUG_jungshik NS_ASSERTION(NS_SUCCEEDED(rv), "newsgroup name conversion failed"); #endif if (NS_FAILED(rv)) { CopyASCIItoUTF16(nsDependentCString(aName), newsgroupName); } rv = AddTo(NS_ConvertUTF16toUTF8(newsgroupName), false, true, true); if (NS_FAILED(rv)) return rv; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetIncomingServer(nsIMsgIncomingServer* aServer) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->SetIncomingServer(aServer); } NS_IMETHODIMP nsNntpIncomingServer::SetShowFullName(bool showFullName) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->SetShowFullName(showFullName); } nsresult nsNntpIncomingServer::ClearInner() { nsresult rv = NS_OK; if (mInner) { rv = mInner->SetSubscribeListener(nullptr); NS_ENSURE_SUCCESS(rv, rv); rv = mInner->SetIncomingServer(nullptr); NS_ENSURE_SUCCESS(rv, rv); mInner = nullptr; } return NS_OK; } nsresult nsNntpIncomingServer::EnsureInner() { nsresult rv = NS_OK; if (mInner) return NS_OK; mInner = do_CreateInstance(kSubscribableServerCID, &rv); NS_ENSURE_SUCCESS(rv, rv); if (!mInner) return NS_ERROR_FAILURE; rv = SetIncomingServer(this); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetDelimiter(char* aDelimiter) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->GetDelimiter(aDelimiter); } NS_IMETHODIMP nsNntpIncomingServer::SetDelimiter(char aDelimiter) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->SetDelimiter(aDelimiter); } NS_IMETHODIMP nsNntpIncomingServer::SetAsSubscribed(const nsACString& path) { mTempSubscribed.AppendElement(path); if (mGetOnlyNew && (!mGroupsOnServer.Contains(path))) return NS_OK; nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->SetAsSubscribed(path); } NS_IMETHODIMP nsNntpIncomingServer::UpdateSubscribed() { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); mTempSubscribed.Clear(); uint32_t length = mSubscribedNewsgroups.Length(); for (uint32_t i = 0; i < length; ++i) SetAsSubscribed(mSubscribedNewsgroups[i]); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::AddTo(const nsACString& aName, bool addAsSubscribed, bool aSubscribable, bool changeIfExists) { NS_ASSERTION(mozilla::IsUtf8(aName), "Non-UTF-8 newsgroup name"); nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); rv = AddGroupOnServer(aName); NS_ENSURE_SUCCESS(rv, rv); rv = mInner->AddTo(aName, addAsSubscribed, aSubscribable, changeIfExists); NS_ENSURE_SUCCESS(rv, rv); return rv; } NS_IMETHODIMP nsNntpIncomingServer::StopPopulating(nsIMsgWindow* aMsgWindow) { nsresult rv = NS_OK; rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); rv = mInner->StopPopulating(aMsgWindow); NS_ENSURE_SUCCESS(rv, rv); if (!mGetOnlyNew && !mHostInfoLoaded) { rv = WriteHostInfoFile(); NS_ENSURE_SUCCESS(rv, rv); } // XXX TODO: when do I set this to null? // rv = ClearInner(); // NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetSubscribeListener(nsISubscribeListener* aListener) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->SetSubscribeListener(aListener); } NS_IMETHODIMP nsNntpIncomingServer::GetSubscribeListener(nsISubscribeListener** aListener) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->GetSubscribeListener(aListener); } NS_IMETHODIMP nsNntpIncomingServer::Subscribe(const char16_t* aUnicharName) { return SubscribeToNewsgroup(NS_ConvertUTF16toUTF8(aUnicharName)); } NS_IMETHODIMP nsNntpIncomingServer::Unsubscribe(const char16_t* aUnicharName) { NS_ENSURE_ARG_POINTER(aUnicharName); nsresult rv; nsCOMPtr serverFolder; rv = GetRootMsgFolder(getter_AddRefs(serverFolder)); if (NS_FAILED(rv)) return rv; if (!serverFolder) return NS_ERROR_FAILURE; nsCOMPtr newsgroupFolder; rv = serverFolder->GetChildNamed(nsDependentString(aUnicharName), getter_AddRefs(newsgroupFolder)); if (NS_FAILED(rv)) return rv; if (!newsgroupFolder) return NS_ERROR_FAILURE; rv = serverFolder->PropagateDelete(newsgroupFolder, true /* delete storage */, nullptr); if (NS_FAILED(rv)) return rv; // since we've unsubscribed to a newsgroup, the newsrc needs to be written out rv = SetNewsrcHasChanged(true); if (NS_FAILED(rv)) return rv; return NS_OK; } nsresult nsNntpIncomingServer::HandleLine(const char* line, uint32_t line_size) { NS_ASSERTION(line, "line is null"); if (!line) return NS_OK; // skip blank lines and comments if (line[0] == '#' || line[0] == '\0') return NS_OK; // XXX TODO: make this truly const, maybe pass in an nsCString & if (mHasSeenBeginGroups) { // v1 hostinfo files had additional data fields delimited by commas. // with v2 hostinfo files, the additional data fields are removed. char* commaPos = (char*)PL_strchr(line, ','); if (commaPos) *commaPos = 0; // newsrc entries are all in UTF-8 #ifdef DEBUG_jungshik NS_ASSERTION(mozilla::IsUtf8(nsDependentCString(line)), "newsrc line is not utf-8"); #endif nsresult rv = AddTo(nsDependentCString(line), false, true, true); NS_ASSERTION(NS_SUCCEEDED(rv), "failed to add line"); if (NS_SUCCEEDED(rv)) { // since we've seen one group, we can claim we've loaded the // hostinfo file mHostInfoLoaded = true; } } else { if (PL_strncmp(line, "begingroups", 11) == 0) { mHasSeenBeginGroups = true; } char* equalPos = (char*)PL_strchr(line, '='); if (equalPos) { *equalPos++ = '\0'; if (PL_strcmp(line, "lastgroupdate") == 0) { mLastUpdatedTime = strtoul(equalPos, nullptr, 10); } else if (PL_strcmp(line, "uniqueid") == 0) { mUniqueId = strtol(equalPos, nullptr, 16); } else if (PL_strcmp(line, "version") == 0) { mVersion = strtol(equalPos, nullptr, 16); } } } return NS_OK; } nsresult nsNntpIncomingServer::AddGroupOnServer(const nsACString& aName) { mGroupsOnServer.AppendElement(aName); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::AddNewsgroup(const nsAString& aName) { // handle duplicates? mSubscribedNewsgroups.AppendElement(NS_ConvertUTF16toUTF8(aName)); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::RemoveNewsgroup(const nsAString& aName) { // handle duplicates? mSubscribedNewsgroups.RemoveElement(NS_ConvertUTF16toUTF8(aName)); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetState(const nsACString& path, bool state, bool* stateChanged) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); rv = mInner->SetState(path, state, stateChanged); if (*stateChanged) { if (state) mTempSubscribed.AppendElement(path); else mTempSubscribed.RemoveElement(path); } return rv; } NS_IMETHODIMP nsNntpIncomingServer::HasChildren(const nsACString& path, bool* aHasChildren) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->HasChildren(path, aHasChildren); } NS_IMETHODIMP nsNntpIncomingServer::IsSubscribed(const nsACString& path, bool* aIsSubscribed) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->IsSubscribed(path, aIsSubscribed); } NS_IMETHODIMP nsNntpIncomingServer::IsSubscribable(const nsACString& path, bool* aIsSubscribable) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->IsSubscribable(path, aIsSubscribable); } NS_IMETHODIMP nsNntpIncomingServer::GetLeafName(const nsACString& path, nsAString& aLeafName) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->GetLeafName(path, aLeafName); } NS_IMETHODIMP nsNntpIncomingServer::GetFirstChildURI(const nsACString& path, nsACString& aResult) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->GetFirstChildURI(path, aResult); } NS_IMETHODIMP nsNntpIncomingServer::GetChildURIs(const nsACString& aPath, nsTArray& aResult) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->GetChildURIs(aPath, aResult); } NS_IMETHODIMP nsNntpIncomingServer::CommitSubscribeChanges() { // we force the newrc to be dirty, so it will get written out when // we call WriteNewsrcFile() nsresult rv = SetNewsrcHasChanged(true); NS_ENSURE_SUCCESS(rv, rv); return WriteNewsrcFile(); } NS_IMETHODIMP nsNntpIncomingServer::ForgetPassword() { // clear password of root folder (for the news account) nsCOMPtr rootFolder; nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); NS_ENSURE_SUCCESS(rv, rv); if (!rootFolder) return NS_ERROR_FAILURE; nsCOMPtr newsFolder = do_QueryInterface(rootFolder, &rv); NS_ENSURE_SUCCESS(rv, rv); if (!newsFolder) return NS_ERROR_FAILURE; rv = newsFolder->ForgetAuthenticationCredentials(); NS_ENSURE_SUCCESS(rv, rv); // clear password of all child folders nsTArray> subFolders; rv = rootFolder->GetSubFolders(subFolders); NS_ENSURE_SUCCESS(rv, rv); nsresult return_rv = NS_OK; for (nsIMsgFolder* child : subFolders) { newsFolder = do_QueryInterface(child, &rv); if (NS_SUCCEEDED(rv) && newsFolder) { rv = newsFolder->ForgetAuthenticationCredentials(); if (NS_FAILED(rv)) return_rv = rv; } else { return_rv = NS_ERROR_FAILURE; } } return return_rv; } NS_IMETHODIMP nsNntpIncomingServer::GetSupportsExtensions(bool* aSupportsExtensions) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::SetSupportsExtensions(bool aSupportsExtensions) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::AddExtension(const char* extension) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::QueryExtension(const char* extension, bool* result) { #ifdef DEBUG_seth printf("no extension support yet\n"); #endif *result = false; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetPostingAllowed(bool* aPostingAllowed) { *aPostingAllowed = mPostingAllowed; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetPostingAllowed(bool aPostingAllowed) { mPostingAllowed = aPostingAllowed; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetLastUpdatedTime(uint32_t* aLastUpdatedTime) { *aLastUpdatedTime = mLastUpdatedTime; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetLastUpdatedTime(uint32_t aLastUpdatedTime) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::AddPropertyForGet(const char* name, const char* value) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::QueryPropertyForGet(const char* name, char** value) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::AddSearchableGroup(const nsAString& name) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::QuerySearchableGroup(const nsAString& name, bool* result) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::AddSearchableHeader(const char* name) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::QuerySearchableHeader(const char* name, bool* result) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::FindGroup(const nsACString& name, nsIMsgNewsFolder** result) { NS_ENSURE_ARG_POINTER(result); nsresult rv; nsCOMPtr serverFolder; rv = GetRootMsgFolder(getter_AddRefs(serverFolder)); NS_ENSURE_SUCCESS(rv, rv); if (!serverFolder) return NS_ERROR_FAILURE; // Escape the name for using FindSubFolder nsAutoCString escapedName; rv = MsgEscapeString(name, nsINetUtil::ESCAPE_URL_PATH, escapedName); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr subFolder; rv = serverFolder->FindSubFolder(escapedName, getter_AddRefs(subFolder)); NS_ENSURE_SUCCESS(rv, rv); if (!subFolder) return NS_ERROR_FAILURE; rv = subFolder->QueryInterface(NS_GET_IID(nsIMsgNewsFolder), (void**)result); NS_ENSURE_SUCCESS(rv, rv); if (!*result) return NS_ERROR_FAILURE; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetFirstGroupNeedingExtraInfo(nsACString& result) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::SetGroupNeedsExtraInfo(const nsACString& name, bool needsExtraInfo) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::GroupNotFound(nsIMsgWindow* aMsgWindow, const nsAString& aName, bool aOpening) { nsresult rv; nsCOMPtr prompt; if (aMsgWindow) { rv = aMsgWindow->GetPromptDialog(getter_AddRefs(prompt)); NS_ASSERTION(NS_SUCCEEDED(rv), "no prompt from the msg window"); } if (!prompt) { nsCOMPtr wwatch( do_GetService(NS_WINDOWWATCHER_CONTRACTID)); rv = wwatch->GetNewPrompter(nullptr, getter_AddRefs(prompt)); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr bundleService = mozilla::services::GetStringBundleService(); NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); nsCOMPtr bundle; rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); nsCString hostname; rv = GetRealHostName(hostname); NS_ENSURE_SUCCESS(rv, rv); nsString groupName(aName); AutoTArray formatStrings = {groupName}; CopyUTF8toUTF16(hostname, *formatStrings.AppendElement()); nsString confirmText; rv = bundle->FormatStringFromName("autoUnsubscribeText", formatStrings, confirmText); NS_ENSURE_SUCCESS(rv, rv); bool confirmResult = false; rv = prompt->Confirm(nullptr, confirmText.get(), &confirmResult); NS_ENSURE_SUCCESS(rv, rv); if (confirmResult) { rv = Unsubscribe(groupName.get()); NS_ENSURE_SUCCESS(rv, rv); } return rv; } NS_IMETHODIMP nsNntpIncomingServer::SetPrettyNameForGroup(const nsAString& name, const nsAString& prettyName) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::GetCanCompactFoldersOnServer( bool* canCompactFoldersOnServer) { NS_ENSURE_ARG_POINTER(canCompactFoldersOnServer); // Initialize canCompactFoldersOnServer false, a default value for NNTP *canCompactFoldersOnServer = false; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetCanSearchMessages(bool* canSearchMessages) { NS_ENSURE_ARG_POINTER(canSearchMessages); *canSearchMessages = true; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetOfflineSupportLevel(int32_t* aSupportLevel) { NS_ENSURE_ARG_POINTER(aSupportLevel); nsresult rv; rv = GetIntValue("offline_support_level", aSupportLevel); if (*aSupportLevel != OFFLINE_SUPPORT_LEVEL_UNDEFINED) return rv; // set default value *aSupportLevel = OFFLINE_SUPPORT_LEVEL_EXTENDED; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetDefaultCopiesAndFoldersPrefsToServer( bool* aCopiesAndFoldersOnServer) { NS_ENSURE_ARG_POINTER(aCopiesAndFoldersOnServer); /** * When a news account is created, the copies and folder prefs for the * associated identity don't point to folders on the server. * This makes sense, since there is no "Drafts" folder on a news server. * They'll point to the ones on "Local Folders" */ *aCopiesAndFoldersOnServer = false; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetCanCreateFoldersOnServer( bool* aCanCreateFoldersOnServer) { NS_ENSURE_ARG_POINTER(aCanCreateFoldersOnServer); // No folder creation on news servers. Return false. *aCanCreateFoldersOnServer = false; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetSearchValue(const nsAString& aSearchValue) { nsCString searchValue = NS_ConvertUTF16toUTF8(aSearchValue); searchValue.CompressWhitespace(); if (mTree) { mTree->BeginUpdateBatch(); mTree->RowCountChanged( 0, -static_cast(mSubscribeSearchResult.Length())); } nsTArray searchStringParts; if (!searchValue.IsEmpty()) ParseString(searchValue, ' ', searchStringParts); mSubscribeSearchResult.Clear(); uint32_t length = mGroupsOnServer.Length(); for (uint32_t i = 0; i < length; i++) { // check that all parts of the search string occur bool found = true; for (uint32_t j = 0; j < searchStringParts.Length(); ++j) { if (mGroupsOnServer[i].Find(searchStringParts[j], true, 0) == kNotFound) { found = false; break; } } if (found) mSubscribeSearchResult.AppendElement(mGroupsOnServer[i]); } nsCStringLowerCaseComparator comparator; mSubscribeSearchResult.Sort(comparator); if (mTree) { mTree->RowCountChanged(0, mSubscribeSearchResult.Length()); mTree->EndUpdateBatch(); } return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetSupportsSubscribeSearch(bool* retVal) { *retVal = true; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetFolderView(nsITreeView** aView) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv, rv); return mInner->GetFolderView(aView); } NS_IMETHODIMP nsNntpIncomingServer::GetRowCount(int32_t* aRowCount) { *aRowCount = mSubscribeSearchResult.Length(); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetSelection(nsITreeSelection** aSelection) { NS_IF_ADDREF(*aSelection = mTreeSelection); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetSelection(nsITreeSelection* aSelection) { mTreeSelection = aSelection; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetRowProperties(int32_t index, nsAString& properties) { return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetCellProperties(int32_t row, nsTreeColumn* col, nsAString& properties) { if (!IsValidRow(row)) return NS_ERROR_UNEXPECTED; NS_ENSURE_ARG_POINTER(col); const nsAString& colID = col->GetId(); if (colID.IsEmpty()) return NS_OK; if (colID.First() == 's') { // if is in our temporary list of subscribed groups // add the "subscribed-true" property so the check mark shows up // in the "subscribedColumn2" if (mSearchResultSortDescending) row = mSubscribeSearchResult.Length() - 1 - row; if (mTempSubscribed.Contains(mSubscribeSearchResult.ElementAt(row))) { properties.AssignLiteral("subscribed-true"); } } else if (colID.First() == 'n') { // add the "serverType-nntp" property to the "nameColumn2" // so we get the news folder icon in the search view properties.AssignLiteral("serverType-nntp"); } return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetColumnProperties(nsTreeColumn* col, nsAString& properties) { return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::IsContainer(int32_t index, bool* _retval) { *_retval = false; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::IsContainerOpen(int32_t index, bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::IsContainerEmpty(int32_t index, bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::IsSeparator(int32_t index, bool* _retval) { *_retval = false; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::IsSorted(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::CanDrop(int32_t index, int32_t orientation, mozilla::dom::DataTransfer* dataTransfer, bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::Drop(int32_t row, int32_t orientation, mozilla::dom::DataTransfer* dataTransfer) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::GetParentIndex(int32_t rowIndex, int32_t* _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::GetLevel(int32_t index, int32_t* _retval) { *_retval = 0; return NS_OK; } bool nsNntpIncomingServer::IsValidRow(int32_t row) { return ((row >= 0) && (row < (int32_t)mSubscribeSearchResult.Length())); } NS_IMETHODIMP nsNntpIncomingServer::GetImageSrc(int32_t row, nsTreeColumn* col, nsAString& _retval) { return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetCellValue(int32_t row, nsTreeColumn* col, nsAString& _retval) { if (!IsValidRow(row)) return NS_ERROR_UNEXPECTED; NS_ENSURE_ARG_POINTER(col); const nsAString& colID = col->GetId(); nsresult rv = NS_OK; if (!colID.IsEmpty() && colID.First() == 'n') { nsAutoCString str; if (mSearchResultSortDescending) row = mSubscribeSearchResult.Length() - 1 - row; _retval.Assign( NS_ConvertASCIItoUTF16(mSubscribeSearchResult.ElementAt(row))); } return rv; } NS_IMETHODIMP nsNntpIncomingServer::GetCellText(int32_t row, nsTreeColumn* col, nsAString& _retval) { if (!IsValidRow(row)) return NS_ERROR_UNEXPECTED; NS_ENSURE_ARG_POINTER(col); const nsAString& colID = col->GetId(); nsresult rv = NS_OK; if (!colID.IsEmpty() && colID.First() == 'n') { nsAutoCString str; if (mSearchResultSortDescending) row = mSubscribeSearchResult.Length() - 1 - row; // some servers have newsgroup names that are non ASCII. we store // those as escaped. unescape here so the UI is consistent rv = NS_MsgDecodeUnescapeURLPath(mSubscribeSearchResult.ElementAt(row), _retval); } return rv; } NS_IMETHODIMP nsNntpIncomingServer::SetTree(mozilla::dom::XULTreeElement* tree) { mTree = tree; if (!tree) return NS_OK; RefPtr cols = tree->GetColumns(); if (!cols) return NS_OK; RefPtr col = cols->GetKeyColumn(); if (!col) return NS_OK; RefPtr element = col->Element(); if (!element) return NS_OK; nsAutoString dir; element->GetAttribute(u"sortDirection"_ns, dir); mSearchResultSortDescending = dir.EqualsLiteral("descending"); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::ToggleOpenState(int32_t index) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::CycleHeader(nsTreeColumn* col) { NS_ENSURE_ARG_POINTER(col); bool cycler = col->Cycler(); if (!cycler) { constexpr auto dir = u"sortDirection"_ns; RefPtr element = col->Element(); mSearchResultSortDescending = !mSearchResultSortDescending; mozilla::IgnoredErrorResult rv2; element->SetAttribute( dir, mSearchResultSortDescending ? u"descending"_ns : u"ascending"_ns, rv2); mTree->Invalidate(); } return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SelectionChangedXPCOM() { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::CycleCell(int32_t row, nsTreeColumn* col) { return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::IsEditable(int32_t row, nsTreeColumn* col, bool* _retval) { *_retval = false; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetCellValue(int32_t row, nsTreeColumn* col, const nsAString& value) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::SetCellText(int32_t row, nsTreeColumn* col, const nsAString& value) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::GetCanFileMessagesOnServer( bool* aCanFileMessagesOnServer) { NS_ENSURE_ARG_POINTER(aCanFileMessagesOnServer); // No folder creation on news servers. Return false. *aCanFileMessagesOnServer = false; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetFilterScope(nsMsgSearchScopeValue* filterScope) { NS_ENSURE_ARG_POINTER(filterScope); *filterScope = nsMsgSearchScope::newsFilter; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetSearchScope(nsMsgSearchScopeValue* searchScope) { NS_ENSURE_ARG_POINTER(searchScope); if (WeAreOffline()) { // This value is set to the localNewsBody scope to be compatible with // the legacy default value. *searchScope = nsMsgSearchScope::localNewsBody; } else { *searchScope = nsMsgSearchScope::news; } return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetSocketType(int32_t* aSocketType) { NS_ENSURE_ARG_POINTER(aSocketType); if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED; nsresult rv = mPrefBranch->GetIntPref("socketType", aSocketType); if (NS_FAILED(rv)) { if (!mDefPrefBranch) return NS_ERROR_NOT_INITIALIZED; rv = mDefPrefBranch->GetIntPref("socketType", aSocketType); if (NS_FAILED(rv)) *aSocketType = nsMsgSocketType::plain; } // nsMsgIncomingServer::GetSocketType migrates old isSecure to socketType // style for mail. Unfortunately, a bug caused news socketType 0 to be stored // in the prefs even for isSecure true, so the migration wouldn't happen :( // Now that we know the socket, make sure isSecure true + socketType 0 // doesn't mix. Migrate if that's the case here. if (*aSocketType == nsMsgSocketType::plain) { bool isSecure = false; nsresult rv2 = mPrefBranch->GetBoolPref("isSecure", &isSecure); if (NS_SUCCEEDED(rv2) && isSecure) { *aSocketType = nsMsgSocketType::SSL; // Don't call virtual method in case overrides call GetSocketType. nsMsgIncomingServer::SetSocketType(*aSocketType); } } return rv; } NS_IMETHODIMP nsNntpIncomingServer::SetSocketType(int32_t aSocketType) { if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED; nsresult rv = nsMsgIncomingServer::SetSocketType(aSocketType); if (NS_SUCCEEDED(rv)) { bool isSecure = false; if (NS_SUCCEEDED(mPrefBranch->GetBoolPref("isSecure", &isSecure))) { // Must keep isSecure in sync since we migrate based on it... if it's set. rv = mPrefBranch->SetBoolPref("isSecure", aSocketType == nsMsgSocketType::SSL); NS_ENSURE_SUCCESS(rv, rv); } } return rv; } NS_IMETHODIMP nsNntpIncomingServer::OnUserOrHostNameChanged(const nsACString& oldName, const nsACString& newName, bool hostnameChanged) { nsresult rv; // 1. Do common things in the base class. rv = nsMsgIncomingServer::OnUserOrHostNameChanged(oldName, newName, hostnameChanged); NS_ENSURE_SUCCESS(rv, rv); // 2. Remove file hostinfo.dat so that the new subscribe // list will be reloaded from the new server. nsCOMPtr hostInfoFile; rv = GetLocalPath(getter_AddRefs(hostInfoFile)); NS_ENSURE_SUCCESS(rv, rv); rv = hostInfoFile->AppendNative(nsLiteralCString(HOSTINFO_FILE_NAME)); NS_ENSURE_SUCCESS(rv, rv); hostInfoFile->Remove(false); // 3.Unsubscribe and then subscribe the existing groups to clean up the // article numbers // in the rc file (this is because the old and new servers may maintain // different numbers for the same articles if both servers handle the same // groups). nsCOMPtr serverFolder; rv = GetRootMsgFolder(getter_AddRefs(serverFolder)); NS_ENSURE_SUCCESS(rv, rv); nsTArray> subFolders; rv = serverFolder->GetSubFolders(subFolders); NS_ENSURE_SUCCESS(rv, rv); // Prepare the group list nsTArray groupList(subFolders.Length()); for (nsIMsgFolder* newsgroupFolder : subFolders) { nsString folderName; rv = newsgroupFolder->GetName(folderName); NS_ENSURE_SUCCESS(rv, rv); groupList.AppendElement(folderName); } // If nothing subscribed then we're done. if (groupList.IsEmpty()) return NS_OK; // Now unsubscribe & subscribe. uint32_t i; uint32_t cnt = groupList.Length(); nsAutoCString cname; for (i = 0; i < cnt; i++) { // unsubscribe. rv = Unsubscribe(groupList[i].get()); NS_ENSURE_SUCCESS(rv, rv); } for (i = 0; i < cnt; i++) { // subscribe. rv = SubscribeToNewsgroup(NS_ConvertUTF16toUTF8(groupList[i])); NS_ENSURE_SUCCESS(rv, rv); } // Force updating the rc file. return CommitSubscribeChanges(); } NS_IMETHODIMP nsNntpIncomingServer::GetSortOrder(int32_t* aSortOrder) { NS_ENSURE_ARG_POINTER(aSortOrder); *aSortOrder = 500000000; return NS_OK; }