/* -*- 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 "nsIDirectoryService.h" #include "nsMailDirServiceDefs.h" #include "nsMsgUtils.h" #include "nsIPrompt.h" #include "nsIStringBundle.h" #include "nntpCore.h" #include "nsIWindowWatcher.h" #include "nsITreeColumns.h" #include "nsIDOMElement.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 "nsITreeBoxObject.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 PREF_MAILNEWS_VIEW_DEFAULT_CHARSET "mailnews.view_default_charset" #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; // these atoms are used for subscribe search mSubscribedAtom = MsgGetAtom("subscribed"); mNntpAtom = MsgGetAtom("nntp"); // 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 nsCString &serverUri, nsIMsgFolder **rootFolder) { nsMsgNewsFolder *newRootFolder = new nsMsgNewsFolder; if (!newRootFolder) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*rootFolder = newRootFolder); newRootFolder->Init(serverUri.get()); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetNewsrcFilePath(nsIFile **aNewsrcFilePath) { nsresult rv; if (mNewsrcFilePath) { *aNewsrcFilePath = mNewsrcFilePath; NS_IF_ADDREF(*aNewsrcFilePath); 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->InitWithFuncCallback(OnNewsrcSaveTimer, (void*)this, timeInMSUint32, nsITimer::TYPE_REPEATING_SLACK); 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,we get the default charset from //mailnews.view_default_charset setting and set it as per-server preference. if (aCharset.IsEmpty()) { nsString defaultCharset; rv = NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, PREF_MAILNEWS_VIEW_DEFAULT_CHARSET, NS_LITERAL_STRING("ISO-8859-1"), defaultCharset); NS_ENSURE_SUCCESS(rv, rv); LossyCopyUTF16toASCII(defaultCharset, aCharset); 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 subFolders; 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 */ rv = rootFolder->GetSubFolders(getter_AddRefs(subFolders)); if (NS_FAILED(rv)) return rv; bool moreFolders; while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && moreFolders) { nsCOMPtr child; rv = subFolders->GetNext(getter_AddRefs(child)); if (NS_SUCCEEDED(rv) && child) { 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; } 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) { NS_IF_ADDREF(*aNntpConnection = connection); connection->SetIsCachedConnection(true); } 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); } 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); if (!channel) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*aChannel = channel); m_queuedChannels.AppendElement(channel); return NS_OK; } 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); if (protocol) 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; m_queuedChannels.AppendElement(channel); return NS_OK; } 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); nsCOMPtr groups; rv = rootFolder->GetSubFolders(getter_AddRefs(groups)); NS_ENSURE_SUCCESS(rv, rv); bool hasNext; while (NS_SUCCEEDED(rv = groups->HasMoreElements(&hasNext)) && hasNext) { nsCOMPtr nextGroup; rv = groups->GetNext(getter_AddRefs(nextGroup)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr group(do_QueryInterface(nextGroup)); 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) { nsCOMPtr subfolders; rootFolder->GetSubFolders(getter_AddRefs(subfolders)); } } 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.Assign("# 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(NS_LITERAL_CSTRING(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 char *uri) { #ifdef DEBUG_seth printf("StartPopulatingWithUri(%s)\n",uri); #endif 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.get(), 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(MsgIsUTF8(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; nsCOMPtr listener; rv = GetSubscribeListener(getter_AddRefs(listener)); NS_ENSURE_SUCCESS(rv,rv); if (!listener) return NS_ERROR_FAILURE; rv = listener->OnDonePopulating(); NS_ENSURE_SUCCESS(rv,rv); 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; // to handle non-ASCII newsgroup names, we store them internally as escaped. // so we need to escape and encode the name, in order to find it. nsAutoCString escapedName; rv = NS_MsgEscapeEncodeURLPath(nsDependentString(aUnicharName), escapedName); nsCOMPtr newsgroupFolder; rv = serverFolder->FindSubFolder(escapedName, 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(MsgIsUTF8(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::GetChildren(const nsACString &aPath, nsISimpleEnumerator **aResult) { nsresult rv = EnsureInner(); NS_ENSURE_SUCCESS(rv,rv); return mInner->GetChildren(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 nsCOMPtr subFolders; rv = rootFolder->GetSubFolders(getter_AddRefs(subFolders)); NS_ENSURE_SUCCESS(rv,rv); bool moreFolders = false; nsresult return_rv = NS_OK; while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && moreFolders) { nsCOMPtr child; rv = subFolders->GetNext(getter_AddRefs(child)); if (NS_SUCCEEDED(rv) && child) { 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); NS_ConvertUTF8toUTF16 hostStr(hostname); nsString groupName(aName); const char16_t *formatStrings[2] = { groupName.get(), hostStr.get() }; nsString confirmText; rv = bundle->FormatStringFromName( u"autoUnsubscribeText", formatStrings, 2, getter_Copies(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::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); MsgCompressWhitespace(searchValue); 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 (MsgFind(mGroupsOnServer[i], 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::GetRowCount(int32_t *aRowCount) { *aRowCount = mSubscribeSearchResult.Length(); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetSelection(nsITreeSelection * *aSelection) { *aSelection = mTreeSelection; NS_IF_ADDREF(*aSelection); 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, nsITreeColumn* col, nsAString& properties) { if (!IsValidRow(row)) return NS_ERROR_UNEXPECTED; NS_ENSURE_ARG_POINTER(col); const char16_t* colID; col->GetIdConst(&colID); if (colID[0] == 's') { // if is in our temporary list of subscribed groups // add the "subscribed" property so the check mark shows up // in the "subscribedCol" if (mSearchResultSortDescending) row = mSubscribeSearchResult.Length() - 1 - row; if (mTempSubscribed.Contains(mSubscribeSearchResult.ElementAt(row))) { properties.AssignLiteral("subscribed"); } } else if (colID[0] == 'n') { // add the "nntp" property to the "nameCol" // so we get the news folder icon in the search view properties.AssignLiteral("nntp"); } return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetColumnProperties(nsITreeColumn* 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, nsIDOMDataTransfer *dataTransfer, bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::Drop(int32_t row, int32_t orientation, nsIDOMDataTransfer *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, nsITreeColumn* col, nsAString& _retval) { return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval) { return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval) { return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval) { if (!IsValidRow(row)) return NS_ERROR_UNEXPECTED; NS_ENSURE_ARG_POINTER(col); const char16_t* colID; col->GetIdConst(&colID); nsresult rv = NS_OK; if (colID[0] == '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(nsITreeBoxObject *tree) { mTree = tree; if (!tree) return NS_OK; nsCOMPtr cols; tree->GetColumns(getter_AddRefs(cols)); if (!cols) return NS_OK; nsCOMPtr col; cols->GetKeyColumn(getter_AddRefs(col)); if (!col) return NS_OK; nsCOMPtr element; col->GetElement(getter_AddRefs(element)); if (!element) return NS_OK; nsAutoString dir; element->GetAttribute(NS_LITERAL_STRING("sortDirection"), dir); mSearchResultSortDescending = dir.EqualsLiteral("descending"); return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::ToggleOpenState(int32_t index) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::CycleHeader(nsITreeColumn* col) { NS_ENSURE_ARG_POINTER(col); bool cycler; col->GetCycler(&cycler); if (!cycler) { NS_NAMED_LITERAL_STRING(dir, "sortDirection"); nsCOMPtr element; col->GetElement(getter_AddRefs(element)); mSearchResultSortDescending = !mSearchResultSortDescending; element->SetAttribute(dir, mSearchResultSortDescending ? NS_LITERAL_STRING("descending") : NS_LITERAL_STRING("ascending")); mTree->Invalidate(); } return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SelectionChanged() { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::CycleCell(int32_t row, nsITreeColumn* col) { return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::IsEditable(int32_t row, nsITreeColumn* col, bool *_retval) { *_retval = false; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::IsSelectable(int32_t row, nsITreeColumn* col, bool *_retval) { *_retval = false; return NS_OK; } NS_IMETHODIMP nsNntpIncomingServer::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::PerformAction(const char16_t *action) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::PerformActionOnRow(const char16_t *action, int32_t row) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsNntpIncomingServer::PerformActionOnCell(const char16_t *action, int32_t row, nsITreeColumn* col) { 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(NS_LITERAL_CSTRING(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); nsCOMPtr subFolders; rv = serverFolder->GetSubFolders(getter_AddRefs(subFolders)); NS_ENSURE_SUCCESS(rv,rv); nsTArray groupList; nsString folderName; // Prepare the group list bool hasMore; while (NS_SUCCEEDED(subFolders->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr item; subFolders->GetNext(getter_AddRefs(item)); nsCOMPtr newsgroupFolder(do_QueryInterface(item)); if (!newsgroupFolder) continue; rv = newsgroupFolder->GetName(folderName); NS_ENSURE_SUCCESS(rv,rv); groupList.AppendElement(folderName); } // If nothing subscribed then we're done. if (groupList.Length() == 0) 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; }