1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsNntpIncomingServer.h"
7 #include "nsIPrefBranch.h"
8 #include "nsIPrefService.h"
9 #include "nsNewsFolder.h"
10 #include "nsIMsgFolder.h"
11 #include "nsIFile.h"
12 #include "nsCOMPtr.h"
13 #include "nsINntpService.h"
14 #include "nsINNTPProtocol.h"
15 #include "nsMsgNewsCID.h"
16 #include "nsNNTPProtocol.h"
17 #include "nsMailDirServiceDefs.h"
18 #include "nsMsgUtils.h"
19 #include "nsIPrompt.h"
20 #include "nsIStringBundle.h"
21 #include "nntpCore.h"
22 #include "nsIWindowWatcher.h"
23 #include "nsTreeColumns.h"
24 #include "nsMsgFolderFlags.h"
25 #include "nsMsgI18N.h"
26 #include "nsUnicharUtils.h"
27 #include "nsILineInputStream.h"
28 #include "nsNetUtil.h"
29 #include "nsISimpleEnumerator.h"
30 #include "nsMsgUtils.h"
31 #include "mozilla/Services.h"
32 #include "mozilla/dom/Element.h"
33 #include "mozilla/ErrorResult.h"
34 #include "mozilla/dom/XULTreeElement.h"
35 #include "mozilla/dom/DataTransfer.h"
36 #include "mozilla/LoadInfo.h"
37 #include "mozilla/Utf8.h"
38 
39 #define INVALID_VERSION 0
40 #define VALID_VERSION 2
41 #define NEW_NEWS_DIR_NAME "News"
42 #define PREF_MAIL_NEWSRC_ROOT "mail.newsrc_root"
43 #define PREF_MAIL_NEWSRC_ROOT_REL "mail.newsrc_root-rel"
44 #define HOSTINFO_FILE_NAME "hostinfo.dat"
45 
46 #define NEWS_DELIMITER '.'
47 
48 // this platform specific junk is so the newsrc filenames we create
49 // will resemble the migrated newsrc filenames.
50 #if defined(XP_UNIX)
51 #  define NEWSRC_FILE_PREFIX "newsrc-"
52 #  define NEWSRC_FILE_SUFFIX ""
53 #else
54 #  define NEWSRC_FILE_PREFIX ""
55 #  define NEWSRC_FILE_SUFFIX ".rc"
56 #endif /* XP_UNIX */
57 
58 // ###tw  This really ought to be the most
59 // efficient file reading size for the current
60 // operating system.
61 #define HOSTINFO_FILE_BUFFER_SIZE 1024
62 
63 #include "nsMsgUtils.h"
64 
65 /**
66  * A comparator class to do cases insensitive comparisons for nsTArray.Sort()
67  */
68 class nsCStringLowerCaseComparator {
69  public:
Equals(const nsCString & a,const nsCString & b) const70   bool Equals(const nsCString& a, const nsCString& b) const {
71     return a.Equals(b, nsCaseInsensitiveCStringComparator);
72   }
73 
LessThan(const nsCString & a,const nsCString & b) const74   bool LessThan(const nsCString& a, const nsCString& b) const {
75     return Compare(a, b, nsCaseInsensitiveCStringComparator) < 0;
76   }
77 };
78 
79 static NS_DEFINE_CID(kSubscribableServerCID, NS_SUBSCRIBABLESERVER_CID);
80 
NS_IMPL_ADDREF_INHERITED(nsNntpIncomingServer,nsMsgIncomingServer)81 NS_IMPL_ADDREF_INHERITED(nsNntpIncomingServer, nsMsgIncomingServer)
82 NS_IMPL_RELEASE_INHERITED(nsNntpIncomingServer, nsMsgIncomingServer)
83 
84 NS_INTERFACE_MAP_BEGIN(nsNntpIncomingServer)
85   NS_INTERFACE_MAP_ENTRY(nsINntpIncomingServer)
86   NS_INTERFACE_MAP_ENTRY(nsIUrlListener)
87   NS_INTERFACE_MAP_ENTRY(nsISubscribableServer)
88   NS_INTERFACE_MAP_ENTRY(nsITreeView)
89 NS_INTERFACE_MAP_END_INHERITING(nsMsgIncomingServer)
90 
91 nsNntpIncomingServer::nsNntpIncomingServer() {
92   mNewsrcHasChanged = false;
93 
94   mGetOnlyNew = true;
95 
96   mHostInfoLoaded = false;
97   mHostInfoHasChanged = false;
98   mVersion = INVALID_VERSION;
99 
100   mLastGroupDate = 0;
101   mUniqueId = 0;
102   mHasSeenBeginGroups = false;
103   mPostingAllowed = false;
104   mLastUpdatedTime = 0;
105 
106   // we have server wide and per group filters
107   m_canHaveFilters = true;
108 
109   SetupNewsrcSaveTimer();
110 }
111 
~nsNntpIncomingServer()112 nsNntpIncomingServer::~nsNntpIncomingServer() {
113   mozilla::DebugOnly<nsresult> rv;
114 
115   if (mNewsrcSaveTimer) {
116     mNewsrcSaveTimer->Cancel();
117     mNewsrcSaveTimer = nullptr;
118   }
119   rv = ClearInner();
120   NS_ASSERTION(NS_SUCCEEDED(rv), "ClearInner failed");
121 
122   rv = CloseCachedConnections();
123   NS_ASSERTION(NS_SUCCEEDED(rv), "CloseCachedConnections failed");
124 }
125 
126 NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, NotifyOn, "notify.on")
127 NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, MarkOldRead, "mark_old_read")
128 NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, Abbreviate, "abbreviate")
129 NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, PushAuth, "always_authenticate")
130 NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, SingleSignon, "singleSignon")
131 NS_IMPL_SERVERPREF_INT(nsNntpIncomingServer, MaxArticles, "max_articles")
132 
CreateRootFolderFromUri(const nsACString & serverUri,nsIMsgFolder ** rootFolder)133 nsresult nsNntpIncomingServer::CreateRootFolderFromUri(
134     const nsACString& serverUri, nsIMsgFolder** rootFolder) {
135   nsMsgNewsFolder* newRootFolder = new nsMsgNewsFolder;
136   NS_ADDREF(*rootFolder = newRootFolder);
137   newRootFolder->Init(serverUri);
138   return NS_OK;
139 }
140 
141 NS_IMETHODIMP
GetNewsrcFilePath(nsIFile ** aNewsrcFilePath)142 nsNntpIncomingServer::GetNewsrcFilePath(nsIFile** aNewsrcFilePath) {
143   nsresult rv;
144   if (mNewsrcFilePath) {
145     NS_IF_ADDREF(*aNewsrcFilePath = mNewsrcFilePath);
146     return NS_OK;
147   }
148 
149   rv = GetFileValue("newsrc.file-rel", "newsrc.file", aNewsrcFilePath);
150   if (NS_SUCCEEDED(rv) && *aNewsrcFilePath) {
151     mNewsrcFilePath = *aNewsrcFilePath;
152     return rv;
153   }
154 
155   rv = GetNewsrcRootPath(getter_AddRefs(mNewsrcFilePath));
156   if (NS_FAILED(rv)) return rv;
157 
158   nsCString hostname;
159   rv = GetHostName(hostname);
160   NS_ENSURE_SUCCESS(rv, rv);
161 
162   nsAutoCString newsrcFileName(NEWSRC_FILE_PREFIX);
163   newsrcFileName.Append(hostname);
164   newsrcFileName.Append(NEWSRC_FILE_SUFFIX);
165   rv = mNewsrcFilePath->AppendNative(newsrcFileName);
166   rv = mNewsrcFilePath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
167   NS_ENSURE_SUCCESS(rv, rv);
168 
169   rv = SetNewsrcFilePath(mNewsrcFilePath);
170   NS_ENSURE_SUCCESS(rv, rv);
171 
172   NS_ADDREF(*aNewsrcFilePath = mNewsrcFilePath);
173   return NS_OK;
174 }
175 
176 NS_IMETHODIMP
SetNewsrcFilePath(nsIFile * aFile)177 nsNntpIncomingServer::SetNewsrcFilePath(nsIFile* aFile) {
178   NS_ENSURE_ARG_POINTER(aFile);
179 
180   bool exists;
181   nsresult rv = aFile->Exists(&exists);
182   if (!exists) {
183     rv = aFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0664);
184     if (NS_FAILED(rv)) return rv;
185   }
186   return SetFileValue("newsrc.file-rel", "newsrc.file", aFile);
187 }
188 
189 NS_IMETHODIMP
GetLocalStoreType(nsACString & type)190 nsNntpIncomingServer::GetLocalStoreType(nsACString& type) {
191   type.AssignLiteral("news");
192   return NS_OK;
193 }
194 
195 NS_IMETHODIMP
GetLocalDatabaseType(nsACString & type)196 nsNntpIncomingServer::GetLocalDatabaseType(nsACString& type) {
197   type.AssignLiteral("news");
198   return NS_OK;
199 }
200 
201 NS_IMETHODIMP
SetNewsrcRootPath(nsIFile * aNewsrcRootPath)202 nsNntpIncomingServer::SetNewsrcRootPath(nsIFile* aNewsrcRootPath) {
203   NS_ENSURE_ARG(aNewsrcRootPath);
204   return NS_SetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT,
205                               aNewsrcRootPath);
206 }
207 
208 NS_IMETHODIMP
GetNewsrcRootPath(nsIFile ** aNewsrcRootPath)209 nsNntpIncomingServer::GetNewsrcRootPath(nsIFile** aNewsrcRootPath) {
210   NS_ENSURE_ARG_POINTER(aNewsrcRootPath);
211   *aNewsrcRootPath = nullptr;
212 
213   bool havePref;
214   nsresult rv =
215       NS_GetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT,
216                            NS_APP_NEWS_50_DIR, havePref, aNewsrcRootPath);
217 
218   NS_ENSURE_SUCCESS(rv, rv);
219 
220   bool exists;
221   rv = (*aNewsrcRootPath)->Exists(&exists);
222   if (NS_SUCCEEDED(rv) && !exists)
223     rv = (*aNewsrcRootPath)->Create(nsIFile::DIRECTORY_TYPE, 0775);
224   NS_ENSURE_SUCCESS(rv, rv);
225 
226   if (!havePref || !exists) {
227     rv = NS_SetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT,
228                               *aNewsrcRootPath);
229     NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
230   }
231   return rv;
232 }
233 
OnNewsrcSaveTimer(nsITimer * timer,void * voidIncomingServer)234 /* static */ void nsNntpIncomingServer::OnNewsrcSaveTimer(
235     nsITimer* timer, void* voidIncomingServer) {
236   nsNntpIncomingServer* incomingServer =
237       (nsNntpIncomingServer*)voidIncomingServer;
238   incomingServer->WriteNewsrcFile();
239 }
240 
SetupNewsrcSaveTimer()241 nsresult nsNntpIncomingServer::SetupNewsrcSaveTimer() {
242   int64_t ms(300000);  // hard code, 5 minutes.
243   // Convert biffDelay into milliseconds
244   uint32_t timeInMSUint32 = (uint32_t)ms;
245   // Can't currently reset a timer when it's in the process of
246   // calling Notify. So, just release the timer here and create a new one.
247   if (mNewsrcSaveTimer) mNewsrcSaveTimer->Cancel();
248   mNewsrcSaveTimer = do_CreateInstance("@mozilla.org/timer;1");
249   mNewsrcSaveTimer->InitWithNamedFuncCallback(
250       OnNewsrcSaveTimer, (void*)this, timeInMSUint32,
251       nsITimer::TYPE_REPEATING_SLACK,
252       "nsNntpIncomingServer::OnNewsrcSaveTimer");
253   return NS_OK;
254 }
255 
256 NS_IMETHODIMP
SetCharset(const nsACString & aCharset)257 nsNntpIncomingServer::SetCharset(const nsACString& aCharset) {
258   return SetCharValue("charset", aCharset);
259 }
260 
261 NS_IMETHODIMP
GetCharset(nsACString & aCharset)262 nsNntpIncomingServer::GetCharset(nsACString& aCharset) {
263   // first we get the per-server settings mail.server.<serverkey>.charset
264   nsresult rv = GetCharValue("charset", aCharset);
265   NS_ENSURE_SUCCESS(rv, rv);
266 
267   // if the per-server setting is empty, default to UTF-8 and set it as
268   // per-server preference.
269   if (aCharset.IsEmpty()) {
270     aCharset.AssignLiteral("UTF-8");
271     SetCharset(aCharset);
272   }
273   return NS_OK;
274 }
275 
276 NS_IMETHODIMP
WriteNewsrcFile()277 nsNntpIncomingServer::WriteNewsrcFile() {
278   nsresult rv;
279 
280   bool newsrcHasChanged;
281   rv = GetNewsrcHasChanged(&newsrcHasChanged);
282   if (NS_FAILED(rv)) return rv;
283 
284 #ifdef DEBUG_NEWS
285   nsCString hostname;
286   rv = GetHostName(hostname);
287   if (NS_FAILED(rv)) return rv;
288 #endif /* DEBUG_NEWS */
289 
290   if (newsrcHasChanged) {
291 #ifdef DEBUG_NEWS
292     printf("write newsrc file for %s\n", hostname.get());
293 #endif
294     nsCOMPtr<nsIFile> newsrcFile;
295     rv = GetNewsrcFilePath(getter_AddRefs(newsrcFile));
296     if (NS_FAILED(rv)) return rv;
297 
298     nsCOMPtr<nsIOutputStream> newsrcStream;
299     nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(newsrcStream),
300                                                  newsrcFile, -1, 00600);
301     if (NS_FAILED(rv)) return rv;
302 
303     nsCOMPtr<nsIMsgFolder> rootFolder;
304     rv = GetRootFolder(getter_AddRefs(rootFolder));
305     if (NS_FAILED(rv)) return rv;
306 
307     nsCOMPtr<nsIMsgNewsFolder> newsFolder = do_QueryInterface(rootFolder, &rv);
308     if (NS_FAILED(rv)) return rv;
309 
310     uint32_t bytesWritten;
311     nsCString optionLines;
312     rv = newsFolder->GetOptionLines(optionLines);
313     if (NS_SUCCEEDED(rv) && !optionLines.IsEmpty()) {
314       newsrcStream->Write(optionLines.get(), optionLines.Length(),
315                           &bytesWritten);
316 #ifdef DEBUG_NEWS
317       printf("option lines:\n%s", optionLines.get());
318 #endif /* DEBUG_NEWS */
319     }
320 #ifdef DEBUG_NEWS
321     else {
322       printf("no option lines to write out\n");
323     }
324 #endif /* DEBUG_NEWS */
325 
326     nsCString unsubscribedLines;
327     rv = newsFolder->GetUnsubscribedNewsgroupLines(unsubscribedLines);
328     if (NS_SUCCEEDED(rv) && !unsubscribedLines.IsEmpty()) {
329       newsrcStream->Write(unsubscribedLines.get(), unsubscribedLines.Length(),
330                           &bytesWritten);
331 #ifdef DEBUG_NEWS
332       printf("unsubscribedLines:\n%s", unsubscribedLines.get());
333 #endif /* DEBUG_NEWS */
334     }
335 #ifdef DEBUG_NEWS
336     else {
337       printf("no unsubscribed lines to write out\n");
338     }
339 #endif /* DEBUG_NEWS */
340 
341     nsTArray<RefPtr<nsIMsgFolder>> subFolders;
342     rv = rootFolder->GetSubFolders(subFolders);
343     NS_ENSURE_SUCCESS(rv, rv);
344     for (nsIMsgFolder* child : subFolders) {
345       newsFolder = do_QueryInterface(child, &rv);
346       if (NS_SUCCEEDED(rv) && newsFolder) {
347         nsCString newsrcLine;
348         rv = newsFolder->GetNewsrcLine(newsrcLine);
349         if (NS_SUCCEEDED(rv) && !newsrcLine.IsEmpty()) {
350           // write the line to the newsrc file
351           newsrcStream->Write(newsrcLine.get(), newsrcLine.Length(),
352                               &bytesWritten);
353         }
354       }
355     }
356 
357     newsrcStream->Close();
358 
359     rv = SetNewsrcHasChanged(false);
360     if (NS_FAILED(rv)) return rv;
361   }
362 #ifdef DEBUG_NEWS
363   else {
364     printf("no need to write newsrc file for %s, it was not dirty\n",
365            (hostname.get()));
366   }
367 #endif /* DEBUG_NEWS */
368 
369   return NS_OK;
370 }
371 
372 NS_IMETHODIMP
SetNewsrcHasChanged(bool aNewsrcHasChanged)373 nsNntpIncomingServer::SetNewsrcHasChanged(bool aNewsrcHasChanged) {
374   mNewsrcHasChanged = aNewsrcHasChanged;
375   return NS_OK;
376 }
377 
378 NS_IMETHODIMP
GetNewsrcHasChanged(bool * aNewsrcHasChanged)379 nsNntpIncomingServer::GetNewsrcHasChanged(bool* aNewsrcHasChanged) {
380   if (!aNewsrcHasChanged) return NS_ERROR_NULL_POINTER;
381 
382   *aNewsrcHasChanged = mNewsrcHasChanged;
383   return NS_OK;
384 }
385 
386 NS_IMETHODIMP
CloseCachedConnections()387 nsNntpIncomingServer::CloseCachedConnections() {
388   nsresult rv;
389   nsCOMPtr<nsINNTPProtocol> connection;
390 
391   // iterate through the connection cache and close the connections.
392   int32_t cnt = mConnectionCache.Count();
393 
394   for (int32_t i = 0; i < cnt; ++i) {
395     connection = mConnectionCache[0];
396     if (connection) {
397       rv = connection->CloseConnection();
398       // We need to do this instead of RemoveObjectAt(0) because the
399       // above call will likely cause the object to be removed from the
400       // array anyway
401       mConnectionCache.RemoveObject(connection);
402     }
403   }
404 
405   rv = WriteNewsrcFile();
406   if (NS_FAILED(rv)) return rv;
407 
408   if (!mGetOnlyNew && !mHostInfoLoaded) {
409     rv = WriteHostInfoFile();
410     NS_ENSURE_SUCCESS(rv, rv);
411   }
412 
413   return NS_OK;
414 }
415 
416 NS_IMETHODIMP
GetMaximumConnectionsNumber(int32_t * aMaxConnections)417 nsNntpIncomingServer::GetMaximumConnectionsNumber(int32_t* aMaxConnections) {
418   NS_ENSURE_ARG_POINTER(aMaxConnections);
419 
420   nsresult rv = GetIntValue("max_cached_connections", aMaxConnections);
421   // Get our maximum connection count. We need at least 1. If the value is 0,
422   // we use the default. If it's negative, we treat that as 1.
423   if (NS_SUCCEEDED(rv) && *aMaxConnections > 0) return NS_OK;
424 
425   *aMaxConnections = (NS_FAILED(rv) || (*aMaxConnections == 0)) ? 2 : 1;
426   (void)SetMaximumConnectionsNumber(*aMaxConnections);
427 
428   return NS_OK;
429 }
430 
431 NS_IMETHODIMP
SetMaximumConnectionsNumber(int32_t aMaxConnections)432 nsNntpIncomingServer::SetMaximumConnectionsNumber(int32_t aMaxConnections) {
433   return SetIntValue("max_cached_connections", aMaxConnections);
434 }
435 
ConnectionTimeOut(nsINNTPProtocol * aConnection)436 bool nsNntpIncomingServer::ConnectionTimeOut(nsINNTPProtocol* aConnection) {
437   bool retVal = false;
438   if (!aConnection) return retVal;
439 
440   PRTime lastActiveTimeStamp;
441   if (NS_FAILED(aConnection->GetLastActiveTimeStamp(&lastActiveTimeStamp)))
442     return retVal;
443 
444   if (PR_Now() - lastActiveTimeStamp >= PRTime(170) * PR_USEC_PER_SEC) {
445 #ifdef DEBUG_seth
446     printf(
447         "XXX connection timed out, close it, and remove it from the connection "
448         "cache\n");
449 #endif
450     aConnection->CloseConnection();
451     mConnectionCache.RemoveObject(aConnection);
452     retVal = true;
453   }
454   return retVal;
455 }
456 
CreateProtocolInstance(nsINNTPProtocol ** aNntpConnection,nsIURI * url,nsIMsgWindow * aMsgWindow)457 nsresult nsNntpIncomingServer::CreateProtocolInstance(
458     nsINNTPProtocol** aNntpConnection, nsIURI* url, nsIMsgWindow* aMsgWindow) {
459   // create a new connection and add it to the connection cache
460   // we may need to flag the protocol connection as busy so we don't get
461   // a race
462   // condition where someone else goes through this code
463   nsNNTPProtocol* protocolInstance = new nsNNTPProtocol(this, url, aMsgWindow);
464   if (!protocolInstance) return NS_ERROR_OUT_OF_MEMORY;
465 
466   nsresult rv = protocolInstance->QueryInterface(NS_GET_IID(nsINNTPProtocol),
467                                                  (void**)aNntpConnection);
468   // take the protocol instance and add it to the connectionCache
469   if (NS_SUCCEEDED(rv) && *aNntpConnection)
470     mConnectionCache.AppendObject(*aNntpConnection);
471   return rv;
472 }
473 
474 /**
475  * Find an available nsNNTPConnection. Can create new connections as long as
476  * we stay under the maximum connection limit.
477  * If none are available, returns NS_OK and nullptr.
478  */
GetNntpConnection(nsIURI * aUri,nsIMsgWindow * aMsgWindow,nsINNTPProtocol ** aNntpConnection)479 nsresult nsNntpIncomingServer::GetNntpConnection(
480     nsIURI* aUri, nsIMsgWindow* aMsgWindow, nsINNTPProtocol** aNntpConnection) {
481   int32_t maxConnections;
482   (void)GetMaximumConnectionsNumber(&maxConnections);
483 
484   // Find a non-busy connection
485   nsCOMPtr<nsINNTPProtocol> connection;
486   int32_t cnt = mConnectionCache.Count();
487   for (int32_t i = 0; i < cnt; i++) {
488     connection = mConnectionCache[i];
489     if (connection) {
490       bool isBusy;
491       connection->GetIsBusy(&isBusy);
492       if (!isBusy) break;
493       connection = nullptr;
494     }
495   }
496 
497   if (ConnectionTimeOut(connection)) {
498     connection = nullptr;
499     // We have one less connection, since we closed this one.
500     --cnt;
501   }
502 
503   if (connection) {
504     // We've got a connection, ready to go.
505     connection.forget(aNntpConnection);
506   } else if (cnt < maxConnections) {
507     // We have room for another connection. Create this connection and return
508     // it to the caller.
509     nsresult rv = CreateProtocolInstance(aNntpConnection, aUri, aMsgWindow);
510     NS_ENSURE_SUCCESS(rv, rv);
511   } else {
512     // We maxed out our connection count. The caller must therefore enqueue the
513     // call.
514     *aNntpConnection = nullptr;
515     return NS_OK;
516   }
517 
518   // Initialize the URI here and now.
519   return (*aNntpConnection)->Initialize(aUri, aMsgWindow);
520 }
521 
522 /**
523  * Returns an nsIChannel to run the given URI.
524  * The returned channel might be either an nsNNTPProtocol or nsNntpMockChannel,
525  * representing a real connection or a queued command, respectively.
526  */
527 NS_IMETHODIMP
GetNntpChannel(nsIURI * aURI,nsIMsgWindow * aMsgWindow,nsIChannel ** aChannel)528 nsNntpIncomingServer::GetNntpChannel(nsIURI* aURI, nsIMsgWindow* aMsgWindow,
529                                      nsIChannel** aChannel) {
530   NS_ENSURE_ARG_POINTER(aChannel);
531 
532   nsCOMPtr<nsINNTPProtocol> protocol;
533   nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol));
534   NS_ENSURE_SUCCESS(rv, rv);
535 
536   if (protocol) return CallQueryInterface(protocol, aChannel);
537 
538   // No protocol? We need our mock channel.
539   nsNntpMockChannel* channel = new nsNntpMockChannel(aURI, aMsgWindow);
540   NS_ADDREF(*aChannel = channel);
541 
542   m_queuedChannels.AppendElement(channel);
543   return NS_OK;
544 }
545 
546 /**
547  * Submits a news URI to run. If no connections are free, the command will
548  * be queued to run when one becomes available.
549  */
550 NS_IMETHODIMP
LoadNewsUrl(nsIURI * aURI,nsIMsgWindow * aMsgWindow,nsISupports * aConsumer)551 nsNntpIncomingServer::LoadNewsUrl(nsIURI* aURI, nsIMsgWindow* aMsgWindow,
552                                   nsISupports* aConsumer) {
553   nsCOMPtr<nsINNTPProtocol> protocol;
554   nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol));
555   NS_ENSURE_SUCCESS(rv, rv);
556 
557   nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
558       nsContentUtils::GetSystemPrincipal(), nullptr, nullptr,
559       nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
560       nsIContentPolicy::TYPE_OTHER);
561 
562   if (protocol) {
563     nsCOMPtr<nsIChannel> chan = do_QueryInterface(protocol, &rv);
564     NS_ENSURE_SUCCESS(rv, rv);
565     rv = chan->SetLoadInfo(loadInfo);
566     NS_ENSURE_SUCCESS(rv, rv);
567     return protocol->LoadNewsUrl(aURI, aConsumer);
568   }
569 
570   // No protocol? We need our mock channel.
571   nsNntpMockChannel* channel =
572       new nsNntpMockChannel(aURI, aMsgWindow, aConsumer);
573   if (!channel) return NS_ERROR_OUT_OF_MEMORY;
574 
575   rv = channel->SetLoadInfo(loadInfo);
576   NS_ENSURE_SUCCESS(rv, rv);
577   m_queuedChannels.AppendElement(channel);
578   return NS_OK;
579 }
580 
581 /**
582  * Called when an nsNNTPProtocol finishes running a command and is ready to be
583  * assigned a new one.
584  */
585 NS_IMETHODIMP
PrepareForNextUrl(nsNNTPProtocol * aConnection)586 nsNntpIncomingServer::PrepareForNextUrl(nsNNTPProtocol* aConnection) {
587   NS_ENSURE_ARG(aConnection);
588 
589   // Start the connection on the next URL in the queue. If it can't get a URL to
590   // work, drop that URL (the channel will handle failure notification) and move
591   // on.
592   while (m_queuedChannels.Length() > 0) {
593     RefPtr<nsNntpMockChannel> channel = m_queuedChannels[0];
594     m_queuedChannels.RemoveElementAt(0);
595     nsresult rv = channel->AttachNNTPConnection(*aConnection);
596     // If this succeeded, the connection is now running the URL.
597     if (NS_SUCCEEDED(rv)) return NS_OK;
598   }
599 
600   // No queued uris.
601   return NS_OK;
602 }
603 
604 /* void RemoveConnection (in nsINNTPProtocol aNntpConnection); */
RemoveConnection(nsINNTPProtocol * aNntpConnection)605 NS_IMETHODIMP nsNntpIncomingServer::RemoveConnection(
606     nsINNTPProtocol* aNntpConnection) {
607   if (aNntpConnection) mConnectionCache.RemoveObject(aNntpConnection);
608 
609   return NS_OK;
610 }
611 
612 NS_IMETHODIMP
PerformExpand(nsIMsgWindow * aMsgWindow)613 nsNntpIncomingServer::PerformExpand(nsIMsgWindow* aMsgWindow) {
614   // Get news.update_unread_on_expand pref
615   nsresult rv;
616   bool updateUnreadOnExpand = true;
617   nsCOMPtr<nsIPrefBranch> prefBranch =
618       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
619   if (NS_SUCCEEDED(rv))
620     prefBranch->GetBoolPref("news.update_unread_on_expand",
621                             &updateUnreadOnExpand);
622 
623   // Only if news.update_unread_on_expand is true do we update the unread counts
624   if (updateUnreadOnExpand) return DownloadMail(aMsgWindow);
625   return NS_OK;
626 }
627 
DownloadMail(nsIMsgWindow * aMsgWindow)628 nsresult nsNntpIncomingServer::DownloadMail(nsIMsgWindow* aMsgWindow) {
629   nsCOMPtr<nsIMsgFolder> rootFolder;
630   nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
631   NS_ENSURE_SUCCESS(rv, rv);
632 
633   nsTArray<RefPtr<nsIMsgFolder>> groups;
634   rv = rootFolder->GetSubFolders(groups);
635   NS_ENSURE_SUCCESS(rv, rv);
636   for (nsIMsgFolder* group : groups) {
637     rv = group->GetNewMessages(aMsgWindow, nullptr);
638     NS_ENSURE_SUCCESS(rv, rv);
639   }
640   return rv;
641 }
642 
643 NS_IMETHODIMP
DisplaySubscribedGroup(nsIMsgNewsFolder * aMsgFolder,int32_t aFirstMessage,int32_t aLastMessage,int32_t aTotalMessages)644 nsNntpIncomingServer::DisplaySubscribedGroup(nsIMsgNewsFolder* aMsgFolder,
645                                              int32_t aFirstMessage,
646                                              int32_t aLastMessage,
647                                              int32_t aTotalMessages) {
648   nsresult rv;
649 
650   if (!aMsgFolder) return NS_ERROR_NULL_POINTER;
651 #ifdef DEBUG_NEWS
652   printf("DisplaySubscribedGroup(...,%ld,%ld,%ld)\n", aFirstMessage,
653          aLastMessage, aTotalMessages);
654 #endif
655   rv = aMsgFolder->UpdateSummaryFromNNTPInfo(aFirstMessage, aLastMessage,
656                                              aTotalMessages);
657   return rv;
658 }
659 
660 NS_IMETHODIMP
PerformBiff(nsIMsgWindow * aMsgWindow)661 nsNntpIncomingServer::PerformBiff(nsIMsgWindow* aMsgWindow) {
662   // Biff will force a download of the messages. If the user doesn't want this
663   // (e.g., there is a lot of high-traffic newsgroups), the better option is to
664   // just ignore biff.
665   return PerformExpand(aMsgWindow);
666 }
667 
GetServerRequiresPasswordForBiff(bool * aServerRequiresPasswordForBiff)668 NS_IMETHODIMP nsNntpIncomingServer::GetServerRequiresPasswordForBiff(
669     bool* aServerRequiresPasswordForBiff) {
670   NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
671   *aServerRequiresPasswordForBiff =
672       false;  // for news, biff is getting the unread counts
673   return NS_OK;
674 }
675 
676 NS_IMETHODIMP
OnStartRunningUrl(nsIURI * url)677 nsNntpIncomingServer::OnStartRunningUrl(nsIURI* url) { return NS_OK; }
678 
679 NS_IMETHODIMP
OnStopRunningUrl(nsIURI * url,nsresult exitCode)680 nsNntpIncomingServer::OnStopRunningUrl(nsIURI* url, nsresult exitCode) {
681   nsresult rv;
682   rv = UpdateSubscribed();
683   if (NS_FAILED(rv)) return rv;
684 
685   rv = StopPopulating(mMsgWindow);
686   if (NS_FAILED(rv)) return rv;
687 
688   return NS_OK;
689 }
690 
691 NS_IMETHODIMP
ContainsNewsgroup(const nsACString & aName,bool * containsGroup)692 nsNntpIncomingServer::ContainsNewsgroup(const nsACString& aName,
693                                         bool* containsGroup) {
694   NS_ENSURE_ARG_POINTER(containsGroup);
695   NS_ENSURE_FALSE(aName.IsEmpty(), NS_ERROR_FAILURE);
696 
697   if (mSubscribedNewsgroups.Length() == 0) {
698     // If this is empty, we may need to discover folders
699     nsCOMPtr<nsIMsgFolder> rootFolder;
700     GetRootFolder(getter_AddRefs(rootFolder));
701     if (rootFolder) {
702       nsTArray<RefPtr<nsIMsgFolder>> dummy;
703       rootFolder->GetSubFolders(dummy);
704     }
705   }
706   nsAutoCString unescapedName;
707   MsgUnescapeString(aName, 0, unescapedName);
708   *containsGroup = mSubscribedNewsgroups.Contains(aName);
709   return NS_OK;
710 }
711 
712 NS_IMETHODIMP
SubscribeToNewsgroup(const nsACString & aName)713 nsNntpIncomingServer::SubscribeToNewsgroup(const nsACString& aName) {
714   NS_ASSERTION(!aName.IsEmpty(), "no name");
715   NS_ENSURE_FALSE(aName.IsEmpty(), NS_ERROR_FAILURE);
716 
717   // If we already have this newsgroup, do nothing and report success.
718   bool containsGroup = false;
719   nsresult rv = ContainsNewsgroup(aName, &containsGroup);
720   NS_ENSURE_SUCCESS(rv, rv);
721   if (containsGroup) return NS_OK;
722 
723   nsCOMPtr<nsIMsgFolder> msgfolder;
724   rv = GetRootMsgFolder(getter_AddRefs(msgfolder));
725   NS_ENSURE_SUCCESS(rv, rv);
726   NS_ENSURE_TRUE(msgfolder, NS_ERROR_FAILURE);
727 
728   return msgfolder->CreateSubfolder(NS_ConvertUTF8toUTF16(aName), nullptr);
729 }
730 
writeGroupToHostInfoFile(nsCString & aElement,void * aData)731 bool writeGroupToHostInfoFile(nsCString& aElement, void* aData) {
732   nsIOutputStream* stream;
733   stream = (nsIOutputStream*)aData;
734   NS_ASSERTION(stream, "no stream");
735   if (!stream) {
736     // stop, something is bad.
737     return false;
738   }
739   return true;
740 }
741 
WriteLine(nsIOutputStream * stream,nsCString & str)742 void nsNntpIncomingServer::WriteLine(nsIOutputStream* stream, nsCString& str) {
743   uint32_t bytesWritten;
744   str.Append(MSG_LINEBREAK);
745   stream->Write(str.get(), str.Length(), &bytesWritten);
746 }
WriteHostInfoFile()747 nsresult nsNntpIncomingServer::WriteHostInfoFile() {
748   if (!mHostInfoHasChanged) return NS_OK;
749 
750   mLastUpdatedTime = uint32_t(PR_Now() / PR_USEC_PER_SEC);
751 
752   nsCString hostname;
753   nsresult rv = GetHostName(hostname);
754   NS_ENSURE_SUCCESS(rv, rv);
755 
756   if (!mHostInfoFile) return NS_ERROR_UNEXPECTED;
757   nsCOMPtr<nsIOutputStream> hostInfoStream;
758   rv = MsgNewBufferedFileOutputStream(getter_AddRefs(hostInfoStream),
759                                       mHostInfoFile, -1, 00600);
760   NS_ENSURE_SUCCESS(rv, rv);
761 
762   // XXX TODO: missing some formatting, see the 4.x code
763   nsAutoCString header("# News host information file.");
764   WriteLine(hostInfoStream, header);
765   header.AssignLiteral("# This is a generated file!  Do not edit.");
766   WriteLine(hostInfoStream, header);
767   header.Truncate();
768   WriteLine(hostInfoStream, header);
769   nsAutoCString version("version=");
770   version.AppendInt(VALID_VERSION);
771   WriteLine(hostInfoStream, version);
772   nsAutoCString newsrcname("newsrcname=");
773   newsrcname.Append(hostname);
774   WriteLine(hostInfoStream, hostname);
775   nsAutoCString dateStr("lastgroupdate=");
776   dateStr.AppendInt(mLastUpdatedTime);
777   WriteLine(hostInfoStream, dateStr);
778   dateStr = "uniqueid=";
779   dateStr.AppendInt(mUniqueId);
780   WriteLine(hostInfoStream, dateStr);
781   header.Assign(MSG_LINEBREAK "begingroups");
782   WriteLine(hostInfoStream, header);
783 
784   // XXX TODO: sort groups first?
785   uint32_t length = mGroupsOnServer.Length();
786   for (uint32_t i = 0; i < length; ++i) {
787     uint32_t bytesWritten;
788     hostInfoStream->Write(mGroupsOnServer[i].get(), mGroupsOnServer[i].Length(),
789                           &bytesWritten);
790     hostInfoStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten);
791   }
792 
793   hostInfoStream->Close();
794   mHostInfoHasChanged = false;
795   return NS_OK;
796 }
797 
LoadHostInfoFile()798 nsresult nsNntpIncomingServer::LoadHostInfoFile() {
799   nsresult rv;
800   // we haven't loaded it yet
801   mHostInfoLoaded = false;
802 
803   rv = GetLocalPath(getter_AddRefs(mHostInfoFile));
804   if (NS_FAILED(rv)) return rv;
805   if (!mHostInfoFile) return NS_ERROR_FAILURE;
806 
807   rv = mHostInfoFile->AppendNative(nsLiteralCString(HOSTINFO_FILE_NAME));
808   if (NS_FAILED(rv)) return rv;
809 
810   bool exists;
811   rv = mHostInfoFile->Exists(&exists);
812   if (NS_FAILED(rv)) return rv;
813 
814   // it is ok if the hostinfo.dat file does not exist.
815   if (!exists) return NS_OK;
816 
817   nsCOMPtr<nsIInputStream> fileStream;
818   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), mHostInfoFile);
819   NS_ENSURE_SUCCESS(rv, rv);
820 
821   nsCOMPtr<nsILineInputStream> lineInputStream(
822       do_QueryInterface(fileStream, &rv));
823   NS_ENSURE_SUCCESS(rv, rv);
824 
825   bool more = true;
826   nsCString line;
827 
828   while (more && NS_SUCCEEDED(rv)) {
829     rv = lineInputStream->ReadLine(line, &more);
830     if (line.IsEmpty()) continue;
831     HandleLine(line.get(), line.Length());
832   }
833   mHasSeenBeginGroups = false;
834   fileStream->Close();
835 
836   return UpdateSubscribed();
837 }
838 
839 NS_IMETHODIMP
StartPopulatingWithUri(nsIMsgWindow * aMsgWindow,bool aForceToServer,const nsACString & uri)840 nsNntpIncomingServer::StartPopulatingWithUri(nsIMsgWindow* aMsgWindow,
841                                              bool aForceToServer,
842                                              const nsACString& uri) {
843   nsresult rv = EnsureInner();
844   NS_ENSURE_SUCCESS(rv, rv);
845   rv = mInner->StartPopulatingWithUri(aMsgWindow, aForceToServer, uri);
846   NS_ENSURE_SUCCESS(rv, rv);
847 
848   rv = StopPopulating(mMsgWindow);
849   if (NS_FAILED(rv)) return rv;
850 
851   return NS_OK;
852 }
853 
854 NS_IMETHODIMP
SubscribeCleanup()855 nsNntpIncomingServer::SubscribeCleanup() {
856   nsresult rv = NS_OK;
857   rv = ClearInner();
858   NS_ENSURE_SUCCESS(rv, rv);
859   return NS_OK;
860 }
861 
862 NS_IMETHODIMP
StartPopulating(nsIMsgWindow * aMsgWindow,bool aForceToServer,bool aGetOnlyNew)863 nsNntpIncomingServer::StartPopulating(nsIMsgWindow* aMsgWindow,
864                                       bool aForceToServer, bool aGetOnlyNew) {
865   mMsgWindow = aMsgWindow;
866 
867   nsresult rv = EnsureInner();
868   NS_ENSURE_SUCCESS(rv, rv);
869 
870   rv = mInner->StartPopulating(aMsgWindow, aForceToServer, aGetOnlyNew);
871   NS_ENSURE_SUCCESS(rv, rv);
872 
873   rv = SetDelimiter(NEWS_DELIMITER);
874   if (NS_FAILED(rv)) return rv;
875 
876   rv = SetShowFullName(true);
877   if (NS_FAILED(rv)) return rv;
878 
879   nsCOMPtr<nsINntpService> nntpService =
880       do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
881   NS_ENSURE_SUCCESS(rv, rv);
882 
883   mHostInfoLoaded = false;
884   mVersion = INVALID_VERSION;
885   mGroupsOnServer.Clear();
886   mGetOnlyNew = aGetOnlyNew;
887 
888   if (!aForceToServer) {
889     rv = LoadHostInfoFile();
890     if (NS_FAILED(rv)) return rv;
891   }
892 
893   // mHostInfoLoaded can be false if we failed to load anything
894   if (aForceToServer || !mHostInfoLoaded || (mVersion != VALID_VERSION)) {
895     // set these to true, so when we are done and we call WriteHostInfoFile()
896     // we'll write out to hostinfo.dat
897     mHostInfoHasChanged = true;
898     mVersion = VALID_VERSION;
899 
900     mGroupsOnServer.Clear();
901     rv = nntpService->GetListOfGroupsOnServer(this, aMsgWindow, aGetOnlyNew);
902     if (NS_FAILED(rv)) return rv;
903   } else {
904     rv = StopPopulating(aMsgWindow);
905     if (NS_FAILED(rv)) return rv;
906   }
907 
908   return NS_OK;
909 }
910 
911 /**
912  * This method is the entry point for |nsNNTPProtocol| class. |aName| is now
913  * encoded in the serverside character encoding, but we need to handle
914  * newsgroup names in UTF-8 internally, So we convert |aName| to
915  * UTF-8 here for later use.
916  **/
917 NS_IMETHODIMP
AddNewsgroupToList(const char * aName)918 nsNntpIncomingServer::AddNewsgroupToList(const char* aName) {
919   nsresult rv;
920 
921   nsAutoString newsgroupName;
922   nsAutoCString dataCharset;
923   rv = GetCharset(dataCharset);
924   NS_ENSURE_SUCCESS(rv, rv);
925 
926   rv = nsMsgI18NConvertToUnicode(dataCharset, nsDependentCString(aName),
927                                  newsgroupName);
928 #ifdef DEBUG_jungshik
929   NS_ASSERTION(NS_SUCCEEDED(rv), "newsgroup name conversion failed");
930 #endif
931   if (NS_FAILED(rv)) {
932     CopyASCIItoUTF16(nsDependentCString(aName), newsgroupName);
933   }
934 
935   rv = AddTo(NS_ConvertUTF16toUTF8(newsgroupName), false, true, true);
936   if (NS_FAILED(rv)) return rv;
937   return NS_OK;
938 }
939 
940 NS_IMETHODIMP
SetIncomingServer(nsIMsgIncomingServer * aServer)941 nsNntpIncomingServer::SetIncomingServer(nsIMsgIncomingServer* aServer) {
942   nsresult rv = EnsureInner();
943   NS_ENSURE_SUCCESS(rv, rv);
944   return mInner->SetIncomingServer(aServer);
945 }
946 
947 NS_IMETHODIMP
SetShowFullName(bool showFullName)948 nsNntpIncomingServer::SetShowFullName(bool showFullName) {
949   nsresult rv = EnsureInner();
950   NS_ENSURE_SUCCESS(rv, rv);
951   return mInner->SetShowFullName(showFullName);
952 }
953 
ClearInner()954 nsresult nsNntpIncomingServer::ClearInner() {
955   nsresult rv = NS_OK;
956 
957   if (mInner) {
958     rv = mInner->SetSubscribeListener(nullptr);
959     NS_ENSURE_SUCCESS(rv, rv);
960 
961     rv = mInner->SetIncomingServer(nullptr);
962     NS_ENSURE_SUCCESS(rv, rv);
963 
964     mInner = nullptr;
965   }
966   return NS_OK;
967 }
968 
EnsureInner()969 nsresult nsNntpIncomingServer::EnsureInner() {
970   nsresult rv = NS_OK;
971 
972   if (mInner) return NS_OK;
973 
974   mInner = do_CreateInstance(kSubscribableServerCID, &rv);
975   NS_ENSURE_SUCCESS(rv, rv);
976   if (!mInner) return NS_ERROR_FAILURE;
977 
978   rv = SetIncomingServer(this);
979   NS_ENSURE_SUCCESS(rv, rv);
980 
981   return NS_OK;
982 }
983 
984 NS_IMETHODIMP
GetDelimiter(char * aDelimiter)985 nsNntpIncomingServer::GetDelimiter(char* aDelimiter) {
986   nsresult rv = EnsureInner();
987   NS_ENSURE_SUCCESS(rv, rv);
988   return mInner->GetDelimiter(aDelimiter);
989 }
990 
991 NS_IMETHODIMP
SetDelimiter(char aDelimiter)992 nsNntpIncomingServer::SetDelimiter(char aDelimiter) {
993   nsresult rv = EnsureInner();
994   NS_ENSURE_SUCCESS(rv, rv);
995   return mInner->SetDelimiter(aDelimiter);
996 }
997 
998 NS_IMETHODIMP
SetAsSubscribed(const nsACString & path)999 nsNntpIncomingServer::SetAsSubscribed(const nsACString& path) {
1000   mTempSubscribed.AppendElement(path);
1001   if (mGetOnlyNew && (!mGroupsOnServer.Contains(path))) return NS_OK;
1002 
1003   nsresult rv = EnsureInner();
1004   NS_ENSURE_SUCCESS(rv, rv);
1005   return mInner->SetAsSubscribed(path);
1006 }
1007 
1008 NS_IMETHODIMP
UpdateSubscribed()1009 nsNntpIncomingServer::UpdateSubscribed() {
1010   nsresult rv = EnsureInner();
1011   NS_ENSURE_SUCCESS(rv, rv);
1012   mTempSubscribed.Clear();
1013   uint32_t length = mSubscribedNewsgroups.Length();
1014   for (uint32_t i = 0; i < length; ++i)
1015     SetAsSubscribed(mSubscribedNewsgroups[i]);
1016   return NS_OK;
1017 }
1018 
1019 NS_IMETHODIMP
AddTo(const nsACString & aName,bool addAsSubscribed,bool aSubscribable,bool changeIfExists)1020 nsNntpIncomingServer::AddTo(const nsACString& aName, bool addAsSubscribed,
1021                             bool aSubscribable, bool changeIfExists) {
1022   NS_ASSERTION(mozilla::IsUtf8(aName), "Non-UTF-8 newsgroup name");
1023   nsresult rv = EnsureInner();
1024   NS_ENSURE_SUCCESS(rv, rv);
1025 
1026   rv = AddGroupOnServer(aName);
1027   NS_ENSURE_SUCCESS(rv, rv);
1028 
1029   rv = mInner->AddTo(aName, addAsSubscribed, aSubscribable, changeIfExists);
1030   NS_ENSURE_SUCCESS(rv, rv);
1031 
1032   return rv;
1033 }
1034 
1035 NS_IMETHODIMP
StopPopulating(nsIMsgWindow * aMsgWindow)1036 nsNntpIncomingServer::StopPopulating(nsIMsgWindow* aMsgWindow) {
1037   nsresult rv = NS_OK;
1038 
1039   rv = EnsureInner();
1040   NS_ENSURE_SUCCESS(rv, rv);
1041   rv = mInner->StopPopulating(aMsgWindow);
1042   NS_ENSURE_SUCCESS(rv, rv);
1043 
1044   if (!mGetOnlyNew && !mHostInfoLoaded) {
1045     rv = WriteHostInfoFile();
1046     NS_ENSURE_SUCCESS(rv, rv);
1047   }
1048 
1049   // XXX TODO: when do I set this to null?
1050   // rv = ClearInner();
1051   // NS_ENSURE_SUCCESS(rv,rv);
1052   return NS_OK;
1053 }
1054 
1055 NS_IMETHODIMP
SetSubscribeListener(nsISubscribeListener * aListener)1056 nsNntpIncomingServer::SetSubscribeListener(nsISubscribeListener* aListener) {
1057   nsresult rv = EnsureInner();
1058   NS_ENSURE_SUCCESS(rv, rv);
1059   return mInner->SetSubscribeListener(aListener);
1060 }
1061 
1062 NS_IMETHODIMP
GetSubscribeListener(nsISubscribeListener ** aListener)1063 nsNntpIncomingServer::GetSubscribeListener(nsISubscribeListener** aListener) {
1064   nsresult rv = EnsureInner();
1065   NS_ENSURE_SUCCESS(rv, rv);
1066   return mInner->GetSubscribeListener(aListener);
1067 }
1068 
1069 NS_IMETHODIMP
Subscribe(const char16_t * aUnicharName)1070 nsNntpIncomingServer::Subscribe(const char16_t* aUnicharName) {
1071   return SubscribeToNewsgroup(NS_ConvertUTF16toUTF8(aUnicharName));
1072 }
1073 
1074 NS_IMETHODIMP
Unsubscribe(const char16_t * aUnicharName)1075 nsNntpIncomingServer::Unsubscribe(const char16_t* aUnicharName) {
1076   NS_ENSURE_ARG_POINTER(aUnicharName);
1077 
1078   nsresult rv;
1079 
1080   nsCOMPtr<nsIMsgFolder> serverFolder;
1081   rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
1082   if (NS_FAILED(rv)) return rv;
1083   if (!serverFolder) return NS_ERROR_FAILURE;
1084 
1085   nsCOMPtr<nsIMsgFolder> newsgroupFolder;
1086   rv = serverFolder->GetChildNamed(nsDependentString(aUnicharName),
1087                                    getter_AddRefs(newsgroupFolder));
1088   if (NS_FAILED(rv)) return rv;
1089   if (!newsgroupFolder) return NS_ERROR_FAILURE;
1090 
1091   rv = serverFolder->PropagateDelete(newsgroupFolder, true /* delete storage */,
1092                                      nullptr);
1093   if (NS_FAILED(rv)) return rv;
1094 
1095   // since we've unsubscribed to a newsgroup, the newsrc needs to be written out
1096   rv = SetNewsrcHasChanged(true);
1097   if (NS_FAILED(rv)) return rv;
1098 
1099   return NS_OK;
1100 }
1101 
HandleLine(const char * line,uint32_t line_size)1102 nsresult nsNntpIncomingServer::HandleLine(const char* line,
1103                                           uint32_t line_size) {
1104   NS_ASSERTION(line, "line is null");
1105   if (!line) return NS_OK;
1106 
1107   // skip blank lines and comments
1108   if (line[0] == '#' || line[0] == '\0') return NS_OK;
1109   // XXX TODO: make this truly const, maybe pass in an nsCString &
1110 
1111   if (mHasSeenBeginGroups) {
1112     // v1 hostinfo files had additional data fields delimited by commas.
1113     // with v2 hostinfo files, the additional data fields are removed.
1114     char* commaPos = (char*)PL_strchr(line, ',');
1115     if (commaPos) *commaPos = 0;
1116 
1117       // newsrc entries are all in UTF-8
1118 #ifdef DEBUG_jungshik
1119     NS_ASSERTION(mozilla::IsUtf8(nsDependentCString(line)),
1120                  "newsrc line is not utf-8");
1121 #endif
1122     nsresult rv = AddTo(nsDependentCString(line), false, true, true);
1123     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to add line");
1124     if (NS_SUCCEEDED(rv)) {
1125       // since we've seen one group, we can claim we've loaded the
1126       // hostinfo file
1127       mHostInfoLoaded = true;
1128     }
1129   } else {
1130     if (PL_strncmp(line, "begingroups", 11) == 0) {
1131       mHasSeenBeginGroups = true;
1132     }
1133     char* equalPos = (char*)PL_strchr(line, '=');
1134     if (equalPos) {
1135       *equalPos++ = '\0';
1136       if (PL_strcmp(line, "lastgroupdate") == 0) {
1137         mLastUpdatedTime = strtoul(equalPos, nullptr, 10);
1138       } else if (PL_strcmp(line, "uniqueid") == 0) {
1139         mUniqueId = strtol(equalPos, nullptr, 16);
1140       } else if (PL_strcmp(line, "version") == 0) {
1141         mVersion = strtol(equalPos, nullptr, 16);
1142       }
1143     }
1144   }
1145 
1146   return NS_OK;
1147 }
1148 
AddGroupOnServer(const nsACString & aName)1149 nsresult nsNntpIncomingServer::AddGroupOnServer(const nsACString& aName) {
1150   mGroupsOnServer.AppendElement(aName);
1151   return NS_OK;
1152 }
1153 
1154 NS_IMETHODIMP
AddNewsgroup(const nsAString & aName)1155 nsNntpIncomingServer::AddNewsgroup(const nsAString& aName) {
1156   // handle duplicates?
1157   mSubscribedNewsgroups.AppendElement(NS_ConvertUTF16toUTF8(aName));
1158   return NS_OK;
1159 }
1160 
1161 NS_IMETHODIMP
RemoveNewsgroup(const nsAString & aName)1162 nsNntpIncomingServer::RemoveNewsgroup(const nsAString& aName) {
1163   // handle duplicates?
1164   mSubscribedNewsgroups.RemoveElement(NS_ConvertUTF16toUTF8(aName));
1165   return NS_OK;
1166 }
1167 
1168 NS_IMETHODIMP
SetState(const nsACString & path,bool state,bool * stateChanged)1169 nsNntpIncomingServer::SetState(const nsACString& path, bool state,
1170                                bool* stateChanged) {
1171   nsresult rv = EnsureInner();
1172   NS_ENSURE_SUCCESS(rv, rv);
1173 
1174   rv = mInner->SetState(path, state, stateChanged);
1175   if (*stateChanged) {
1176     if (state)
1177       mTempSubscribed.AppendElement(path);
1178     else
1179       mTempSubscribed.RemoveElement(path);
1180   }
1181   return rv;
1182 }
1183 
1184 NS_IMETHODIMP
HasChildren(const nsACString & path,bool * aHasChildren)1185 nsNntpIncomingServer::HasChildren(const nsACString& path, bool* aHasChildren) {
1186   nsresult rv = EnsureInner();
1187   NS_ENSURE_SUCCESS(rv, rv);
1188   return mInner->HasChildren(path, aHasChildren);
1189 }
1190 
1191 NS_IMETHODIMP
IsSubscribed(const nsACString & path,bool * aIsSubscribed)1192 nsNntpIncomingServer::IsSubscribed(const nsACString& path,
1193                                    bool* aIsSubscribed) {
1194   nsresult rv = EnsureInner();
1195   NS_ENSURE_SUCCESS(rv, rv);
1196   return mInner->IsSubscribed(path, aIsSubscribed);
1197 }
1198 
1199 NS_IMETHODIMP
IsSubscribable(const nsACString & path,bool * aIsSubscribable)1200 nsNntpIncomingServer::IsSubscribable(const nsACString& path,
1201                                      bool* aIsSubscribable) {
1202   nsresult rv = EnsureInner();
1203   NS_ENSURE_SUCCESS(rv, rv);
1204   return mInner->IsSubscribable(path, aIsSubscribable);
1205 }
1206 
1207 NS_IMETHODIMP
GetLeafName(const nsACString & path,nsAString & aLeafName)1208 nsNntpIncomingServer::GetLeafName(const nsACString& path,
1209                                   nsAString& aLeafName) {
1210   nsresult rv = EnsureInner();
1211   NS_ENSURE_SUCCESS(rv, rv);
1212   return mInner->GetLeafName(path, aLeafName);
1213 }
1214 
1215 NS_IMETHODIMP
GetFirstChildURI(const nsACString & path,nsACString & aResult)1216 nsNntpIncomingServer::GetFirstChildURI(const nsACString& path,
1217                                        nsACString& aResult) {
1218   nsresult rv = EnsureInner();
1219   NS_ENSURE_SUCCESS(rv, rv);
1220   return mInner->GetFirstChildURI(path, aResult);
1221 }
1222 
1223 NS_IMETHODIMP
GetChildURIs(const nsACString & aPath,nsTArray<nsCString> & aResult)1224 nsNntpIncomingServer::GetChildURIs(const nsACString& aPath,
1225                                    nsTArray<nsCString>& aResult) {
1226   nsresult rv = EnsureInner();
1227   NS_ENSURE_SUCCESS(rv, rv);
1228   return mInner->GetChildURIs(aPath, aResult);
1229 }
1230 
1231 NS_IMETHODIMP
CommitSubscribeChanges()1232 nsNntpIncomingServer::CommitSubscribeChanges() {
1233   // we force the newrc to be dirty, so it will get written out when
1234   // we call WriteNewsrcFile()
1235   nsresult rv = SetNewsrcHasChanged(true);
1236   NS_ENSURE_SUCCESS(rv, rv);
1237   return WriteNewsrcFile();
1238 }
1239 
1240 NS_IMETHODIMP
ForgetPassword()1241 nsNntpIncomingServer::ForgetPassword() {
1242   // clear password of root folder (for the news account)
1243   nsCOMPtr<nsIMsgFolder> rootFolder;
1244   nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
1245   NS_ENSURE_SUCCESS(rv, rv);
1246   if (!rootFolder) return NS_ERROR_FAILURE;
1247 
1248   nsCOMPtr<nsIMsgNewsFolder> newsFolder = do_QueryInterface(rootFolder, &rv);
1249   NS_ENSURE_SUCCESS(rv, rv);
1250   if (!newsFolder) return NS_ERROR_FAILURE;
1251 
1252   rv = newsFolder->ForgetAuthenticationCredentials();
1253   NS_ENSURE_SUCCESS(rv, rv);
1254 
1255   // clear password of all child folders
1256   nsTArray<RefPtr<nsIMsgFolder>> subFolders;
1257   rv = rootFolder->GetSubFolders(subFolders);
1258   NS_ENSURE_SUCCESS(rv, rv);
1259 
1260   nsresult return_rv = NS_OK;
1261   for (nsIMsgFolder* child : subFolders) {
1262     newsFolder = do_QueryInterface(child, &rv);
1263     if (NS_SUCCEEDED(rv) && newsFolder) {
1264       rv = newsFolder->ForgetAuthenticationCredentials();
1265       if (NS_FAILED(rv)) return_rv = rv;
1266     } else {
1267       return_rv = NS_ERROR_FAILURE;
1268     }
1269   }
1270 
1271   return return_rv;
1272 }
1273 
1274 NS_IMETHODIMP
GetSupportsExtensions(bool * aSupportsExtensions)1275 nsNntpIncomingServer::GetSupportsExtensions(bool* aSupportsExtensions) {
1276   return NS_ERROR_NOT_IMPLEMENTED;
1277 }
1278 
1279 NS_IMETHODIMP
SetSupportsExtensions(bool aSupportsExtensions)1280 nsNntpIncomingServer::SetSupportsExtensions(bool aSupportsExtensions) {
1281   return NS_ERROR_NOT_IMPLEMENTED;
1282 }
1283 
1284 NS_IMETHODIMP
AddExtension(const char * extension)1285 nsNntpIncomingServer::AddExtension(const char* extension) {
1286   return NS_ERROR_NOT_IMPLEMENTED;
1287 }
1288 
1289 NS_IMETHODIMP
QueryExtension(const char * extension,bool * result)1290 nsNntpIncomingServer::QueryExtension(const char* extension, bool* result) {
1291 #ifdef DEBUG_seth
1292   printf("no extension support yet\n");
1293 #endif
1294   *result = false;
1295   return NS_OK;
1296 }
1297 
1298 NS_IMETHODIMP
GetPostingAllowed(bool * aPostingAllowed)1299 nsNntpIncomingServer::GetPostingAllowed(bool* aPostingAllowed) {
1300   *aPostingAllowed = mPostingAllowed;
1301   return NS_OK;
1302 }
1303 
1304 NS_IMETHODIMP
SetPostingAllowed(bool aPostingAllowed)1305 nsNntpIncomingServer::SetPostingAllowed(bool aPostingAllowed) {
1306   mPostingAllowed = aPostingAllowed;
1307   return NS_OK;
1308 }
1309 
1310 NS_IMETHODIMP
GetLastUpdatedTime(uint32_t * aLastUpdatedTime)1311 nsNntpIncomingServer::GetLastUpdatedTime(uint32_t* aLastUpdatedTime) {
1312   *aLastUpdatedTime = mLastUpdatedTime;
1313   return NS_OK;
1314 }
1315 
1316 NS_IMETHODIMP
SetLastUpdatedTime(uint32_t aLastUpdatedTime)1317 nsNntpIncomingServer::SetLastUpdatedTime(uint32_t aLastUpdatedTime) {
1318   return NS_ERROR_NOT_IMPLEMENTED;
1319 }
1320 
1321 NS_IMETHODIMP
AddPropertyForGet(const char * name,const char * value)1322 nsNntpIncomingServer::AddPropertyForGet(const char* name, const char* value) {
1323   return NS_ERROR_NOT_IMPLEMENTED;
1324 }
1325 
1326 NS_IMETHODIMP
QueryPropertyForGet(const char * name,char ** value)1327 nsNntpIncomingServer::QueryPropertyForGet(const char* name, char** value) {
1328   return NS_ERROR_NOT_IMPLEMENTED;
1329 }
1330 
1331 NS_IMETHODIMP
AddSearchableGroup(const nsAString & name)1332 nsNntpIncomingServer::AddSearchableGroup(const nsAString& name) {
1333   return NS_ERROR_NOT_IMPLEMENTED;
1334 }
1335 
1336 NS_IMETHODIMP
QuerySearchableGroup(const nsAString & name,bool * result)1337 nsNntpIncomingServer::QuerySearchableGroup(const nsAString& name,
1338                                            bool* result) {
1339   return NS_ERROR_NOT_IMPLEMENTED;
1340 }
1341 
1342 NS_IMETHODIMP
AddSearchableHeader(const char * name)1343 nsNntpIncomingServer::AddSearchableHeader(const char* name) {
1344   return NS_ERROR_NOT_IMPLEMENTED;
1345 }
1346 
1347 NS_IMETHODIMP
QuerySearchableHeader(const char * name,bool * result)1348 nsNntpIncomingServer::QuerySearchableHeader(const char* name, bool* result) {
1349   return NS_ERROR_NOT_IMPLEMENTED;
1350 }
1351 
1352 NS_IMETHODIMP
FindGroup(const nsACString & name,nsIMsgNewsFolder ** result)1353 nsNntpIncomingServer::FindGroup(const nsACString& name,
1354                                 nsIMsgNewsFolder** result) {
1355   NS_ENSURE_ARG_POINTER(result);
1356 
1357   nsresult rv;
1358   nsCOMPtr<nsIMsgFolder> serverFolder;
1359   rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
1360   NS_ENSURE_SUCCESS(rv, rv);
1361 
1362   if (!serverFolder) return NS_ERROR_FAILURE;
1363 
1364   // Escape the name for using FindSubFolder
1365   nsAutoCString escapedName;
1366   rv = MsgEscapeString(name, nsINetUtil::ESCAPE_URL_PATH, escapedName);
1367   NS_ENSURE_SUCCESS(rv, rv);
1368 
1369   nsCOMPtr<nsIMsgFolder> subFolder;
1370   rv = serverFolder->FindSubFolder(escapedName, getter_AddRefs(subFolder));
1371   NS_ENSURE_SUCCESS(rv, rv);
1372   if (!subFolder) return NS_ERROR_FAILURE;
1373 
1374   rv = subFolder->QueryInterface(NS_GET_IID(nsIMsgNewsFolder), (void**)result);
1375   NS_ENSURE_SUCCESS(rv, rv);
1376   if (!*result) return NS_ERROR_FAILURE;
1377   return NS_OK;
1378 }
1379 
1380 NS_IMETHODIMP
GetFirstGroupNeedingExtraInfo(nsACString & result)1381 nsNntpIncomingServer::GetFirstGroupNeedingExtraInfo(nsACString& result) {
1382   return NS_ERROR_NOT_IMPLEMENTED;
1383 }
1384 
1385 NS_IMETHODIMP
SetGroupNeedsExtraInfo(const nsACString & name,bool needsExtraInfo)1386 nsNntpIncomingServer::SetGroupNeedsExtraInfo(const nsACString& name,
1387                                              bool needsExtraInfo) {
1388   return NS_ERROR_NOT_IMPLEMENTED;
1389 }
1390 
1391 NS_IMETHODIMP
GroupNotFound(nsIMsgWindow * aMsgWindow,const nsAString & aName,bool aOpening)1392 nsNntpIncomingServer::GroupNotFound(nsIMsgWindow* aMsgWindow,
1393                                     const nsAString& aName, bool aOpening) {
1394   nsresult rv;
1395   nsCOMPtr<nsIPrompt> prompt;
1396 
1397   if (aMsgWindow) {
1398     rv = aMsgWindow->GetPromptDialog(getter_AddRefs(prompt));
1399     NS_ASSERTION(NS_SUCCEEDED(rv), "no prompt from the msg window");
1400   }
1401 
1402   if (!prompt) {
1403     nsCOMPtr<nsIWindowWatcher> wwatch(
1404         do_GetService(NS_WINDOWWATCHER_CONTRACTID));
1405     rv = wwatch->GetNewPrompter(nullptr, getter_AddRefs(prompt));
1406     NS_ENSURE_SUCCESS(rv, rv);
1407   }
1408 
1409   nsCOMPtr<nsIStringBundleService> bundleService =
1410       mozilla::services::GetStringBundleService();
1411   NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
1412 
1413   nsCOMPtr<nsIStringBundle> bundle;
1414   rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
1415   NS_ENSURE_SUCCESS(rv, rv);
1416 
1417   nsCString hostname;
1418   rv = GetRealHostName(hostname);
1419   NS_ENSURE_SUCCESS(rv, rv);
1420 
1421   nsString groupName(aName);
1422   AutoTArray<nsString, 2> formatStrings = {groupName};
1423   CopyUTF8toUTF16(hostname, *formatStrings.AppendElement());
1424   nsString confirmText;
1425   rv = bundle->FormatStringFromName("autoUnsubscribeText", formatStrings,
1426                                     confirmText);
1427   NS_ENSURE_SUCCESS(rv, rv);
1428 
1429   bool confirmResult = false;
1430   rv = prompt->Confirm(nullptr, confirmText.get(), &confirmResult);
1431   NS_ENSURE_SUCCESS(rv, rv);
1432 
1433   if (confirmResult) {
1434     rv = Unsubscribe(groupName.get());
1435     NS_ENSURE_SUCCESS(rv, rv);
1436   }
1437 
1438   return rv;
1439 }
1440 
1441 NS_IMETHODIMP
SetPrettyNameForGroup(const nsAString & name,const nsAString & prettyName)1442 nsNntpIncomingServer::SetPrettyNameForGroup(const nsAString& name,
1443                                             const nsAString& prettyName) {
1444   return NS_ERROR_NOT_IMPLEMENTED;
1445 }
1446 
1447 NS_IMETHODIMP
GetCanCompactFoldersOnServer(bool * canCompactFoldersOnServer)1448 nsNntpIncomingServer::GetCanCompactFoldersOnServer(
1449     bool* canCompactFoldersOnServer) {
1450   NS_ENSURE_ARG_POINTER(canCompactFoldersOnServer);
1451   // Initialize canCompactFoldersOnServer false, a default value for NNTP
1452   *canCompactFoldersOnServer = false;
1453   return NS_OK;
1454 }
1455 
1456 NS_IMETHODIMP
GetCanSearchMessages(bool * canSearchMessages)1457 nsNntpIncomingServer::GetCanSearchMessages(bool* canSearchMessages) {
1458   NS_ENSURE_ARG_POINTER(canSearchMessages);
1459   *canSearchMessages = true;
1460   return NS_OK;
1461 }
1462 
1463 NS_IMETHODIMP
GetOfflineSupportLevel(int32_t * aSupportLevel)1464 nsNntpIncomingServer::GetOfflineSupportLevel(int32_t* aSupportLevel) {
1465   NS_ENSURE_ARG_POINTER(aSupportLevel);
1466   nsresult rv;
1467 
1468   rv = GetIntValue("offline_support_level", aSupportLevel);
1469   if (*aSupportLevel != OFFLINE_SUPPORT_LEVEL_UNDEFINED) return rv;
1470 
1471   // set default value
1472   *aSupportLevel = OFFLINE_SUPPORT_LEVEL_EXTENDED;
1473   return NS_OK;
1474 }
1475 
1476 NS_IMETHODIMP
GetDefaultCopiesAndFoldersPrefsToServer(bool * aCopiesAndFoldersOnServer)1477 nsNntpIncomingServer::GetDefaultCopiesAndFoldersPrefsToServer(
1478     bool* aCopiesAndFoldersOnServer) {
1479   NS_ENSURE_ARG_POINTER(aCopiesAndFoldersOnServer);
1480 
1481   /**
1482    * When a news account is created, the copies and folder prefs for the
1483    * associated identity don't point to folders on the server.
1484    * This makes sense, since there is no "Drafts" folder on a news server.
1485    * They'll point to the ones on "Local Folders"
1486    */
1487 
1488   *aCopiesAndFoldersOnServer = false;
1489   return NS_OK;
1490 }
1491 
1492 NS_IMETHODIMP
GetCanCreateFoldersOnServer(bool * aCanCreateFoldersOnServer)1493 nsNntpIncomingServer::GetCanCreateFoldersOnServer(
1494     bool* aCanCreateFoldersOnServer) {
1495   NS_ENSURE_ARG_POINTER(aCanCreateFoldersOnServer);
1496 
1497   // No folder creation on news servers. Return false.
1498   *aCanCreateFoldersOnServer = false;
1499   return NS_OK;
1500 }
1501 
1502 NS_IMETHODIMP
SetSearchValue(const nsAString & aSearchValue)1503 nsNntpIncomingServer::SetSearchValue(const nsAString& aSearchValue) {
1504   nsCString searchValue = NS_ConvertUTF16toUTF8(aSearchValue);
1505   searchValue.CompressWhitespace();
1506 
1507   if (mTree) {
1508     mTree->BeginUpdateBatch();
1509     mTree->RowCountChanged(
1510         0, -static_cast<int32_t>(mSubscribeSearchResult.Length()));
1511   }
1512 
1513   nsTArray<nsCString> searchStringParts;
1514   if (!searchValue.IsEmpty()) ParseString(searchValue, ' ', searchStringParts);
1515 
1516   mSubscribeSearchResult.Clear();
1517   uint32_t length = mGroupsOnServer.Length();
1518   for (uint32_t i = 0; i < length; i++) {
1519     // check that all parts of the search string occur
1520     bool found = true;
1521     for (uint32_t j = 0; j < searchStringParts.Length(); ++j) {
1522       if (mGroupsOnServer[i].Find(searchStringParts[j], true, 0) == kNotFound) {
1523         found = false;
1524         break;
1525       }
1526     }
1527 
1528     if (found) mSubscribeSearchResult.AppendElement(mGroupsOnServer[i]);
1529   }
1530 
1531   nsCStringLowerCaseComparator comparator;
1532   mSubscribeSearchResult.Sort(comparator);
1533 
1534   if (mTree) {
1535     mTree->RowCountChanged(0, mSubscribeSearchResult.Length());
1536     mTree->EndUpdateBatch();
1537   }
1538 
1539   return NS_OK;
1540 }
1541 
1542 NS_IMETHODIMP
GetSupportsSubscribeSearch(bool * retVal)1543 nsNntpIncomingServer::GetSupportsSubscribeSearch(bool* retVal) {
1544   *retVal = true;
1545   return NS_OK;
1546 }
1547 
1548 NS_IMETHODIMP
GetFolderView(nsITreeView ** aView)1549 nsNntpIncomingServer::GetFolderView(nsITreeView** aView) {
1550   nsresult rv = EnsureInner();
1551   NS_ENSURE_SUCCESS(rv, rv);
1552   return mInner->GetFolderView(aView);
1553 }
1554 
1555 NS_IMETHODIMP
GetRowCount(int32_t * aRowCount)1556 nsNntpIncomingServer::GetRowCount(int32_t* aRowCount) {
1557   *aRowCount = mSubscribeSearchResult.Length();
1558   return NS_OK;
1559 }
1560 
1561 NS_IMETHODIMP
GetSelection(nsITreeSelection ** aSelection)1562 nsNntpIncomingServer::GetSelection(nsITreeSelection** aSelection) {
1563   NS_IF_ADDREF(*aSelection = mTreeSelection);
1564   return NS_OK;
1565 }
1566 
1567 NS_IMETHODIMP
SetSelection(nsITreeSelection * aSelection)1568 nsNntpIncomingServer::SetSelection(nsITreeSelection* aSelection) {
1569   mTreeSelection = aSelection;
1570   return NS_OK;
1571 }
1572 
1573 NS_IMETHODIMP
GetRowProperties(int32_t index,nsAString & properties)1574 nsNntpIncomingServer::GetRowProperties(int32_t index, nsAString& properties) {
1575   return NS_OK;
1576 }
1577 
1578 NS_IMETHODIMP
GetCellProperties(int32_t row,nsTreeColumn * col,nsAString & properties)1579 nsNntpIncomingServer::GetCellProperties(int32_t row, nsTreeColumn* col,
1580                                         nsAString& properties) {
1581   if (!IsValidRow(row)) return NS_ERROR_UNEXPECTED;
1582 
1583   NS_ENSURE_ARG_POINTER(col);
1584 
1585   const nsAString& colID = col->GetId();
1586   if (colID.IsEmpty()) return NS_OK;
1587 
1588   if (colID.First() == 's') {
1589     // if <name> is in our temporary list of subscribed groups
1590     // add the "subscribed-true" property so the check mark shows up
1591     // in the "subscribedColumn2"
1592     if (mSearchResultSortDescending)
1593       row = mSubscribeSearchResult.Length() - 1 - row;
1594     if (mTempSubscribed.Contains(mSubscribeSearchResult.ElementAt(row))) {
1595       properties.AssignLiteral("subscribed-true");
1596     }
1597   } else if (colID.First() == 'n') {
1598     // add the "serverType-nntp" property to the "nameColumn2"
1599     // so we get the news folder icon in the search view
1600     properties.AssignLiteral("serverType-nntp");
1601   }
1602   return NS_OK;
1603 }
1604 
1605 NS_IMETHODIMP
GetColumnProperties(nsTreeColumn * col,nsAString & properties)1606 nsNntpIncomingServer::GetColumnProperties(nsTreeColumn* col,
1607                                           nsAString& properties) {
1608   return NS_OK;
1609 }
1610 
1611 NS_IMETHODIMP
IsContainer(int32_t index,bool * _retval)1612 nsNntpIncomingServer::IsContainer(int32_t index, bool* _retval) {
1613   *_retval = false;
1614   return NS_OK;
1615 }
1616 
1617 NS_IMETHODIMP
IsContainerOpen(int32_t index,bool * _retval)1618 nsNntpIncomingServer::IsContainerOpen(int32_t index, bool* _retval) {
1619   return NS_ERROR_NOT_IMPLEMENTED;
1620 }
1621 
1622 NS_IMETHODIMP
IsContainerEmpty(int32_t index,bool * _retval)1623 nsNntpIncomingServer::IsContainerEmpty(int32_t index, bool* _retval) {
1624   return NS_ERROR_NOT_IMPLEMENTED;
1625 }
1626 
1627 NS_IMETHODIMP
IsSeparator(int32_t index,bool * _retval)1628 nsNntpIncomingServer::IsSeparator(int32_t index, bool* _retval) {
1629   *_retval = false;
1630   return NS_OK;
1631 }
1632 
1633 NS_IMETHODIMP
IsSorted(bool * _retval)1634 nsNntpIncomingServer::IsSorted(bool* _retval) {
1635   return NS_ERROR_NOT_IMPLEMENTED;
1636 }
1637 
1638 NS_IMETHODIMP
CanDrop(int32_t index,int32_t orientation,mozilla::dom::DataTransfer * dataTransfer,bool * _retval)1639 nsNntpIncomingServer::CanDrop(int32_t index, int32_t orientation,
1640                               mozilla::dom::DataTransfer* dataTransfer,
1641                               bool* _retval) {
1642   return NS_ERROR_NOT_IMPLEMENTED;
1643 }
1644 
1645 NS_IMETHODIMP
Drop(int32_t row,int32_t orientation,mozilla::dom::DataTransfer * dataTransfer)1646 nsNntpIncomingServer::Drop(int32_t row, int32_t orientation,
1647                            mozilla::dom::DataTransfer* dataTransfer) {
1648   return NS_ERROR_NOT_IMPLEMENTED;
1649 }
1650 
1651 NS_IMETHODIMP
GetParentIndex(int32_t rowIndex,int32_t * _retval)1652 nsNntpIncomingServer::GetParentIndex(int32_t rowIndex, int32_t* _retval) {
1653   return NS_ERROR_NOT_IMPLEMENTED;
1654 }
1655 
1656 NS_IMETHODIMP
HasNextSibling(int32_t rowIndex,int32_t afterIndex,bool * _retval)1657 nsNntpIncomingServer::HasNextSibling(int32_t rowIndex, int32_t afterIndex,
1658                                      bool* _retval) {
1659   return NS_ERROR_NOT_IMPLEMENTED;
1660 }
1661 
1662 NS_IMETHODIMP
GetLevel(int32_t index,int32_t * _retval)1663 nsNntpIncomingServer::GetLevel(int32_t index, int32_t* _retval) {
1664   *_retval = 0;
1665   return NS_OK;
1666 }
1667 
IsValidRow(int32_t row)1668 bool nsNntpIncomingServer::IsValidRow(int32_t row) {
1669   return ((row >= 0) && (row < (int32_t)mSubscribeSearchResult.Length()));
1670 }
1671 
1672 NS_IMETHODIMP
GetImageSrc(int32_t row,nsTreeColumn * col,nsAString & _retval)1673 nsNntpIncomingServer::GetImageSrc(int32_t row, nsTreeColumn* col,
1674                                   nsAString& _retval) {
1675   return NS_OK;
1676 }
1677 
1678 NS_IMETHODIMP
GetCellValue(int32_t row,nsTreeColumn * col,nsAString & _retval)1679 nsNntpIncomingServer::GetCellValue(int32_t row, nsTreeColumn* col,
1680                                    nsAString& _retval) {
1681   if (!IsValidRow(row)) return NS_ERROR_UNEXPECTED;
1682 
1683   NS_ENSURE_ARG_POINTER(col);
1684 
1685   const nsAString& colID = col->GetId();
1686   nsresult rv = NS_OK;
1687   if (!colID.IsEmpty() && colID.First() == 'n') {
1688     nsAutoCString str;
1689     if (mSearchResultSortDescending)
1690       row = mSubscribeSearchResult.Length() - 1 - row;
1691     _retval.Assign(
1692         NS_ConvertASCIItoUTF16(mSubscribeSearchResult.ElementAt(row)));
1693   }
1694   return rv;
1695 }
1696 
1697 NS_IMETHODIMP
GetCellText(int32_t row,nsTreeColumn * col,nsAString & _retval)1698 nsNntpIncomingServer::GetCellText(int32_t row, nsTreeColumn* col,
1699                                   nsAString& _retval) {
1700   if (!IsValidRow(row)) return NS_ERROR_UNEXPECTED;
1701 
1702   NS_ENSURE_ARG_POINTER(col);
1703 
1704   const nsAString& colID = col->GetId();
1705   nsresult rv = NS_OK;
1706   if (!colID.IsEmpty() && colID.First() == 'n') {
1707     nsAutoCString str;
1708     if (mSearchResultSortDescending)
1709       row = mSubscribeSearchResult.Length() - 1 - row;
1710     // some servers have newsgroup names that are non ASCII.  we store
1711     // those as escaped. unescape here so the UI is consistent
1712     rv = NS_MsgDecodeUnescapeURLPath(mSubscribeSearchResult.ElementAt(row),
1713                                      _retval);
1714   }
1715   return rv;
1716 }
1717 
1718 NS_IMETHODIMP
SetTree(mozilla::dom::XULTreeElement * tree)1719 nsNntpIncomingServer::SetTree(mozilla::dom::XULTreeElement* tree) {
1720   mTree = tree;
1721   if (!tree) return NS_OK;
1722 
1723   RefPtr<nsTreeColumns> cols = tree->GetColumns();
1724   if (!cols) return NS_OK;
1725 
1726   RefPtr<nsTreeColumn> col = cols->GetKeyColumn();
1727   if (!col) return NS_OK;
1728 
1729   RefPtr<mozilla::dom::Element> element = col->Element();
1730   if (!element) return NS_OK;
1731 
1732   nsAutoString dir;
1733   element->GetAttribute(u"sortDirection"_ns, dir);
1734   mSearchResultSortDescending = dir.EqualsLiteral("descending");
1735   return NS_OK;
1736 }
1737 
1738 NS_IMETHODIMP
ToggleOpenState(int32_t index)1739 nsNntpIncomingServer::ToggleOpenState(int32_t index) {
1740   return NS_ERROR_NOT_IMPLEMENTED;
1741 }
1742 
1743 NS_IMETHODIMP
CycleHeader(nsTreeColumn * col)1744 nsNntpIncomingServer::CycleHeader(nsTreeColumn* col) {
1745   NS_ENSURE_ARG_POINTER(col);
1746 
1747   bool cycler = col->Cycler();
1748   if (!cycler) {
1749     constexpr auto dir = u"sortDirection"_ns;
1750     RefPtr<mozilla::dom::Element> element = col->Element();
1751     mSearchResultSortDescending = !mSearchResultSortDescending;
1752     mozilla::IgnoredErrorResult rv2;
1753     element->SetAttribute(
1754         dir, mSearchResultSortDescending ? u"descending"_ns : u"ascending"_ns,
1755         rv2);
1756     mTree->Invalidate();
1757   }
1758   return NS_OK;
1759 }
1760 
1761 NS_IMETHODIMP
SelectionChangedXPCOM()1762 nsNntpIncomingServer::SelectionChangedXPCOM() {
1763   return NS_ERROR_NOT_IMPLEMENTED;
1764 }
1765 
1766 NS_IMETHODIMP
CycleCell(int32_t row,nsTreeColumn * col)1767 nsNntpIncomingServer::CycleCell(int32_t row, nsTreeColumn* col) {
1768   return NS_OK;
1769 }
1770 
1771 NS_IMETHODIMP
IsEditable(int32_t row,nsTreeColumn * col,bool * _retval)1772 nsNntpIncomingServer::IsEditable(int32_t row, nsTreeColumn* col,
1773                                  bool* _retval) {
1774   *_retval = false;
1775   return NS_OK;
1776 }
1777 
1778 NS_IMETHODIMP
SetCellValue(int32_t row,nsTreeColumn * col,const nsAString & value)1779 nsNntpIncomingServer::SetCellValue(int32_t row, nsTreeColumn* col,
1780                                    const nsAString& value) {
1781   return NS_ERROR_NOT_IMPLEMENTED;
1782 }
1783 
1784 NS_IMETHODIMP
SetCellText(int32_t row,nsTreeColumn * col,const nsAString & value)1785 nsNntpIncomingServer::SetCellText(int32_t row, nsTreeColumn* col,
1786                                   const nsAString& value) {
1787   return NS_ERROR_NOT_IMPLEMENTED;
1788 }
1789 
1790 NS_IMETHODIMP
GetCanFileMessagesOnServer(bool * aCanFileMessagesOnServer)1791 nsNntpIncomingServer::GetCanFileMessagesOnServer(
1792     bool* aCanFileMessagesOnServer) {
1793   NS_ENSURE_ARG_POINTER(aCanFileMessagesOnServer);
1794 
1795   // No folder creation on news servers. Return false.
1796   *aCanFileMessagesOnServer = false;
1797   return NS_OK;
1798 }
1799 
1800 NS_IMETHODIMP
GetFilterScope(nsMsgSearchScopeValue * filterScope)1801 nsNntpIncomingServer::GetFilterScope(nsMsgSearchScopeValue* filterScope) {
1802   NS_ENSURE_ARG_POINTER(filterScope);
1803 
1804   *filterScope = nsMsgSearchScope::newsFilter;
1805   return NS_OK;
1806 }
1807 
1808 NS_IMETHODIMP
GetSearchScope(nsMsgSearchScopeValue * searchScope)1809 nsNntpIncomingServer::GetSearchScope(nsMsgSearchScopeValue* searchScope) {
1810   NS_ENSURE_ARG_POINTER(searchScope);
1811 
1812   if (WeAreOffline()) {
1813     // This value is set to the localNewsBody scope to be compatible with
1814     // the legacy default value.
1815     *searchScope = nsMsgSearchScope::localNewsBody;
1816   } else {
1817     *searchScope = nsMsgSearchScope::news;
1818   }
1819   return NS_OK;
1820 }
1821 
1822 NS_IMETHODIMP
GetSocketType(int32_t * aSocketType)1823 nsNntpIncomingServer::GetSocketType(int32_t* aSocketType) {
1824   NS_ENSURE_ARG_POINTER(aSocketType);
1825   if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
1826 
1827   nsresult rv = mPrefBranch->GetIntPref("socketType", aSocketType);
1828   if (NS_FAILED(rv)) {
1829     if (!mDefPrefBranch) return NS_ERROR_NOT_INITIALIZED;
1830     rv = mDefPrefBranch->GetIntPref("socketType", aSocketType);
1831     if (NS_FAILED(rv)) *aSocketType = nsMsgSocketType::plain;
1832   }
1833 
1834   // nsMsgIncomingServer::GetSocketType migrates old isSecure to socketType
1835   // style for mail. Unfortunately, a bug caused news socketType 0 to be stored
1836   // in the prefs even for isSecure true, so the migration wouldn't happen :(
1837 
1838   // Now that we know the socket, make sure isSecure true + socketType 0
1839   // doesn't mix. Migrate if that's the case here.
1840   if (*aSocketType == nsMsgSocketType::plain) {
1841     bool isSecure = false;
1842     nsresult rv2 = mPrefBranch->GetBoolPref("isSecure", &isSecure);
1843     if (NS_SUCCEEDED(rv2) && isSecure) {
1844       *aSocketType = nsMsgSocketType::SSL;
1845       // Don't call virtual method in case overrides call GetSocketType.
1846       nsMsgIncomingServer::SetSocketType(*aSocketType);
1847     }
1848   }
1849   return rv;
1850 }
1851 
1852 NS_IMETHODIMP
SetSocketType(int32_t aSocketType)1853 nsNntpIncomingServer::SetSocketType(int32_t aSocketType) {
1854   if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
1855   nsresult rv = nsMsgIncomingServer::SetSocketType(aSocketType);
1856   if (NS_SUCCEEDED(rv)) {
1857     bool isSecure = false;
1858     if (NS_SUCCEEDED(mPrefBranch->GetBoolPref("isSecure", &isSecure))) {
1859       // Must keep isSecure in sync since we migrate based on it... if it's set.
1860       rv = mPrefBranch->SetBoolPref("isSecure",
1861                                     aSocketType == nsMsgSocketType::SSL);
1862       NS_ENSURE_SUCCESS(rv, rv);
1863     }
1864   }
1865   return rv;
1866 }
1867 
1868 NS_IMETHODIMP
OnUserOrHostNameChanged(const nsACString & oldName,const nsACString & newName,bool hostnameChanged)1869 nsNntpIncomingServer::OnUserOrHostNameChanged(const nsACString& oldName,
1870                                               const nsACString& newName,
1871                                               bool hostnameChanged) {
1872   nsresult rv;
1873   // 1. Do common things in the base class.
1874   rv = nsMsgIncomingServer::OnUserOrHostNameChanged(oldName, newName,
1875                                                     hostnameChanged);
1876   NS_ENSURE_SUCCESS(rv, rv);
1877 
1878   // 2. Remove file hostinfo.dat so that the new subscribe
1879   //    list will be reloaded from the new server.
1880   nsCOMPtr<nsIFile> hostInfoFile;
1881   rv = GetLocalPath(getter_AddRefs(hostInfoFile));
1882   NS_ENSURE_SUCCESS(rv, rv);
1883   rv = hostInfoFile->AppendNative(nsLiteralCString(HOSTINFO_FILE_NAME));
1884   NS_ENSURE_SUCCESS(rv, rv);
1885   hostInfoFile->Remove(false);
1886 
1887   // 3.Unsubscribe and then subscribe the existing groups to clean up the
1888   // article numbers
1889   //   in the rc file (this is because the old and new servers may maintain
1890   //   different numbers for the same articles if both servers handle the same
1891   //   groups).
1892   nsCOMPtr<nsIMsgFolder> serverFolder;
1893   rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
1894   NS_ENSURE_SUCCESS(rv, rv);
1895 
1896   nsTArray<RefPtr<nsIMsgFolder>> subFolders;
1897   rv = serverFolder->GetSubFolders(subFolders);
1898   NS_ENSURE_SUCCESS(rv, rv);
1899 
1900   // Prepare the group list
1901   nsTArray<nsString> groupList(subFolders.Length());
1902   for (nsIMsgFolder* newsgroupFolder : subFolders) {
1903     nsString folderName;
1904     rv = newsgroupFolder->GetName(folderName);
1905     NS_ENSURE_SUCCESS(rv, rv);
1906     groupList.AppendElement(folderName);
1907   }
1908 
1909   // If nothing subscribed then we're done.
1910   if (groupList.IsEmpty()) return NS_OK;
1911 
1912   // Now unsubscribe & subscribe.
1913   uint32_t i;
1914   uint32_t cnt = groupList.Length();
1915   nsAutoCString cname;
1916   for (i = 0; i < cnt; i++) {
1917     // unsubscribe.
1918     rv = Unsubscribe(groupList[i].get());
1919     NS_ENSURE_SUCCESS(rv, rv);
1920   }
1921 
1922   for (i = 0; i < cnt; i++) {
1923     // subscribe.
1924     rv = SubscribeToNewsgroup(NS_ConvertUTF16toUTF8(groupList[i]));
1925     NS_ENSURE_SUCCESS(rv, rv);
1926   }
1927 
1928   // Force updating the rc file.
1929   return CommitSubscribeChanges();
1930 }
1931 
1932 NS_IMETHODIMP
GetSortOrder(int32_t * aSortOrder)1933 nsNntpIncomingServer::GetSortOrder(int32_t* aSortOrder) {
1934   NS_ENSURE_ARG_POINTER(aSortOrder);
1935   *aSortOrder = 500000000;
1936   return NS_OK;
1937 }
1938