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 "msgCore.h"  // precompiled header...
7 #include "MailNewsTypes.h"
8 #include "nntpCore.h"
9 #include "nsNetUtil.h"
10 #include "nsIMsgMailNewsUrl.h"
11 #include "nsIMsgHdr.h"
12 #include "nsNNTPProtocol.h"
13 #include "nsIOutputStream.h"
14 #include "nsIPipe.h"
15 #include "nsCOMPtr.h"
16 #include "nsMsgI18N.h"
17 #include "nsINNTPNewsgroupPost.h"
18 #include "nsMsgBaseCID.h"
19 #include "nsMsgNewsCID.h"
20 
21 #include "nsINntpUrl.h"
22 #include "prmem.h"
23 #include "prtime.h"
24 #include "prerror.h"
25 #include "nsString.h"
26 #include "mozilla/Attributes.h"
27 #include "mozilla/Logging.h"
28 #include "mozilla/Services.h"
29 #include "mozilla/SlicedInputStream.h"
30 #include "mozilla/mailnews/MimeHeaderParser.h"
31 #include "mozilla/Utf8.h"
32 #include "nsContentUtils.h"
33 #include "nsIURIMutator.h"
34 
35 #include "prprf.h"
36 
37 /* include event sink interfaces for news */
38 
39 #include "nsIMsgSearchSession.h"
40 #include "nsIMsgSearchAdapter.h"
41 #include "nsIMsgStatusFeedback.h"
42 
43 #include "nsMsgKeySet.h"
44 
45 #include "nsNewsUtils.h"
46 #include "nsMsgUtils.h"
47 
48 #include "nsIMsgIdentity.h"
49 #include "nsIMsgAccountManager.h"
50 
51 #include "nsIPrompt.h"
52 #include "nsIMsgStatusFeedback.h"
53 
54 #include "nsIMsgFolder.h"
55 #include "nsIMsgNewsFolder.h"
56 #include "nsIDocShell.h"
57 
58 // for the memory cache...
59 #include "nsICacheEntry.h"
60 #include "nsICacheStorage.h"
61 #include "nsIStreamListener.h"
62 #include "nsNetCID.h"
63 
64 #include "nsIPrefBranch.h"
65 #include "nsIPrefService.h"
66 
67 #include "nsIMsgWindow.h"
68 #include "nsIWindowWatcher.h"
69 
70 #include "nsINntpService.h"
71 #include "nntpCore.h"
72 #include "nsIStreamConverterService.h"
73 #include "nsIStreamListenerTee.h"
74 #include "nsISocketTransport.h"
75 #include "nsICancelable.h"
76 
77 #include "nsIInputStreamPump.h"
78 #include "nsIProxyInfo.h"
79 #include "nsContentSecurityManager.h"
80 
81 #include <time.h>
82 
83 #undef GetPort      // XXX Windows!
84 #undef SetPort      // XXX Windows!
85 #undef PostMessage  // avoid to collision with WinUser.h
86 
87 #define PREF_NEWS_CANCEL_CONFIRM "news.cancel.confirm"
88 #define PREF_NEWS_CANCEL_ALERT_ON_SUCCESS "news.cancel.alert_on_success"
89 #define READ_NEWS_LIST_COUNT_MAX                                              \
90   500 /* number of groups to process at a time when reading the list from the \
91          server */
92 #define READ_NEWS_LIST_TIMEOUT 50 /* uSec to wait until doing more */
93 #define RATE_STR_BUF_LEN 32
94 #define UPDATE_THRESHOLD 25600 /* only update every 25 KB */
95 
96 using namespace mozilla::mailnews;
97 using namespace mozilla;
98 
99 // NNTP extensions are supported yet
100 // until the extension code is ported,
101 // we'll skip right to the first nntp command
102 // after doing "mode reader"
103 // and "pushed" authentication (if necessary),
104 //#define HAVE_NNTP_EXTENSIONS
105 
106 // quiet compiler warnings by defining these function prototypes
107 char* MSG_UnEscapeSearchUrl(const char* commandSpecificData);
108 
109 /* Logging stuff */
110 
111 static mozilla::LazyLogModule NNTP("NNTP");
112 #define out LogLevel::Info
113 
114 #define NNTP_LOG_READ(buf) \
115   MOZ_LOG(NNTP, out, ("(%p) Receiving: %s", this, buf));
116 
117 #define NNTP_LOG_WRITE(buf) MOZ_LOG(NNTP, out, ("(%p) Sending: %s", this, buf));
118 
119 #define NNTP_LOG_NOTE(buf) MOZ_LOG(NNTP, out, ("(%p) %s", this, buf));
120 
121 // clang-format off
122 const char *const stateLabels[] = {
123   "NNTP_RESPONSE",
124 #ifdef BLOCK_UNTIL_AVAILABLE_CONNECTION
125   "NNTP_BLOCK_UNTIL_CONNECTIONS_ARE_AVAILABLE",
126   "NNTP_CONNECTIONS_ARE_AVAILABLE",
127 #endif
128   "NNTP_CONNECT",
129   "NNTP_CONNECT_WAIT",
130   "NNTP_LOGIN_RESPONSE",
131   "NNTP_SEND_MODE_READER",
132   "NNTP_SEND_MODE_READER_RESPONSE",
133   "SEND_LIST_EXTENSIONS",
134   "SEND_LIST_EXTENSIONS_RESPONSE",
135   "SEND_LIST_SEARCHES",
136   "SEND_LIST_SEARCHES_RESPONSE",
137   "NNTP_LIST_SEARCH_HEADERS",
138   "NNTP_LIST_SEARCH_HEADERS_RESPONSE",
139   "NNTP_GET_PROPERTIES",
140   "NNTP_GET_PROPERTIES_RESPONSE",
141   "SEND_LIST_SUBSCRIPTIONS",
142   "SEND_LIST_SUBSCRIPTIONS_RESPONSE",
143   "SEND_FIRST_NNTP_COMMAND",
144   "SEND_FIRST_NNTP_COMMAND_RESPONSE",
145   "SETUP_NEWS_STREAM",
146   "NNTP_BEGIN_AUTHORIZE",
147   "NNTP_AUTHORIZE_RESPONSE",
148   "NNTP_PASSWORD_RESPONSE",
149   "NNTP_READ_LIST_BEGIN",
150   "NNTP_READ_LIST",
151   "DISPLAY_NEWSGROUPS",
152   "NNTP_NEWGROUPS_BEGIN",
153   "NNTP_NEWGROUPS",
154   "NNTP_BEGIN_ARTICLE",
155   "NNTP_READ_ARTICLE",
156   "NNTP_XOVER_BEGIN",
157   "NNTP_FIGURE_NEXT_CHUNK",
158   "NNTP_XOVER_SEND",
159   "NNTP_XOVER_RESPONSE",
160   "NNTP_XOVER",
161   "NEWS_PROCESS_XOVER",
162   "NNTP_XHDR_SEND",
163   "NNTP_XHDR_RESPONSE",
164   "NNTP_READ_GROUP",
165   "NNTP_READ_GROUP_RESPONSE",
166   "NNTP_READ_GROUP_BODY",
167   "NNTP_SEND_GROUP_FOR_ARTICLE",
168   "NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE",
169   "NNTP_SEND_ARTICLE_NUMBER",
170   "NEWS_PROCESS_BODIES",
171   "NNTP_PRINT_ARTICLE_HEADERS",
172   "NNTP_SEND_POST_DATA",
173   "NNTP_SEND_POST_DATA_RESPONSE",
174   "NNTP_CHECK_FOR_MESSAGE",
175   "NEWS_START_CANCEL",
176   "NEWS_DO_CANCEL",
177   "NNTP_XPAT_SEND",
178   "NNTP_XPAT_RESPONSE",
179   "NNTP_SEARCH",
180   "NNTP_SEARCH_RESPONSE",
181   "NNTP_SEARCH_RESULTS",
182   "NNTP_LIST_PRETTY_NAMES",
183   "NNTP_LIST_PRETTY_NAMES_RESPONSE",
184   "NNTP_LIST_XACTIVE_RESPONSE",
185   "NNTP_LIST_XACTIVE",
186   "NNTP_LIST_GROUP",
187   "NNTP_LIST_GROUP_RESPONSE",
188   "NEWS_DONE",
189   "NEWS_POST_DONE",
190   "NEWS_ERROR",
191   "NNTP_ERROR",
192   "NEWS_FREE",
193   "NNTP_SUSPENDED"
194 };
195 // clang-format on
196 
197 /* end logging */
198 
199 /* Forward declarations */
200 
201 #define LIST_WANTED 0
202 #define ARTICLE_WANTED 1
203 #define CANCEL_WANTED 2
204 #define GROUP_WANTED 3
205 #define NEWS_POST 4
206 #define NEW_GROUPS 5
207 #define SEARCH_WANTED 6
208 #define IDS_WANTED 7
209 
210 /* the output_buffer_size must be larger than the largest possible line
211  * 2000 seems good for news
212  *
213  * jwz: I increased this to 4k since it must be big enough to hold the
214  * entire button-bar HTML, and with the new "mailto" format, that can
215  * contain arbitrarily long header fields like "references".
216  *
217  * fortezza: proxy auth is huge, buffer increased to 8k (sigh).
218  */
219 #define OUTPUT_BUFFER_SIZE (4096 * 2)
220 
221 /* the amount of time to subtract from the machine time
222  * for the newgroup command sent to the nntp server
223  */
224 #define NEWGROUPS_TIME_OFFSET 60L * 60L * 12L /* 12 hours */
225 
226 ////////////////////////////////////////////////////////////////////////////////////////////
227 // TEMPORARY HARD CODED FUNCTIONS
228 ///////////////////////////////////////////////////////////////////////////////////////////
229 #define NET_IS_SPACE(x) ((x) == ' ' || (x) == '\t')
230 
231 // turn "\xx" (with xx being hex numbers) in string into chars
MSG_UnEscapeSearchUrl(const char * commandSpecificData)232 char* MSG_UnEscapeSearchUrl(const char* commandSpecificData) {
233   nsAutoCString result(commandSpecificData);
234   int32_t slashpos = 0;
235   while ((slashpos = result.FindChar('\\', slashpos)) != kNotFound) {
236     nsAutoCString hex;
237     hex.Assign(Substring(result, slashpos + 1, 2));
238     int32_t ch;
239     nsresult rv;
240     ch = hex.ToInteger(&rv, 16);
241     result.Replace(slashpos, 3, NS_SUCCEEDED(rv) && ch != 0 ? (char)ch : 'X');
242     slashpos++;
243   }
244   return ToNewCString(result);
245 }
246 
247 ////////////////////////////////////////////////////////////////////////////////////////////
248 // END OF TEMPORARY HARD CODED FUNCTIONS
249 ///////////////////////////////////////////////////////////////////////////////////////////
250 
NS_IMPL_ISUPPORTS_INHERITED(nsNNTPProtocol,nsMsgProtocol,nsINNTPProtocol,nsITimerCallback,nsICacheEntryOpenCallback,nsIMsgAsyncPromptListener,nsIProtocolProxyCallback)251 NS_IMPL_ISUPPORTS_INHERITED(nsNNTPProtocol, nsMsgProtocol, nsINNTPProtocol,
252                             nsITimerCallback, nsICacheEntryOpenCallback,
253                             nsIMsgAsyncPromptListener, nsIProtocolProxyCallback)
254 
255 nsNNTPProtocol::nsNNTPProtocol(nsINntpIncomingServer* aServer, nsIURI* aURL,
256                                nsIMsgWindow* aMsgWindow)
257     : nsMsgProtocol(aURL), m_connectionBusy(false), m_nntpServer(aServer) {
258   m_ProxyServer = nullptr;
259   m_responseText = nullptr;
260   m_dataBuf = nullptr;
261 
262   m_key = nsMsgKey_None;
263 
264   mBytesReceived = 0;
265   mBytesReceivedSinceLastStatusUpdate = 0;
266   m_startTime = PR_Now();
267 
268   if (aMsgWindow) {
269     m_msgWindow = aMsgWindow;
270   }
271 
272   m_runningURL = nullptr;
273   MOZ_LOG(NNTP, LogLevel::Info, ("(%p) creating", this));
274   MOZ_LOG(NNTP, LogLevel::Info,
275           ("(%p) initializing, so unset m_currentGroup", this));
276   m_currentGroup.Truncate();
277   m_lastActiveTimeStamp = 0;
278 }
279 
~nsNNTPProtocol()280 nsNNTPProtocol::~nsNNTPProtocol() {
281   MOZ_LOG(NNTP, LogLevel::Info, ("(%p) destroying", this));
282   if (m_nntpServer) {
283     m_nntpServer->WriteNewsrcFile();
284     m_nntpServer->RemoveConnection(this);
285   }
286   if (mUpdateTimer) {
287     mUpdateTimer->Cancel();
288     mUpdateTimer = nullptr;
289   }
290   Cleanup();
291 }
292 
Cleanup()293 void nsNNTPProtocol::Cleanup()  // free char* member variables
294 {
295   PR_FREEIF(m_responseText);
296   PR_FREEIF(m_dataBuf);
297 }
298 
Initialize(nsIURI * aURL,nsIMsgWindow * aMsgWindow)299 NS_IMETHODIMP nsNNTPProtocol::Initialize(nsIURI* aURL,
300                                          nsIMsgWindow* aMsgWindow) {
301   if (aMsgWindow) {
302     m_msgWindow = aMsgWindow;
303   }
304   nsMsgProtocol::InitFromURI(aURL);
305 
306   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer);
307   NS_ASSERTION(m_nntpServer, "nsNNTPProtocol need an m_nntpServer.");
308   NS_ENSURE_TRUE(m_nntpServer, NS_ERROR_UNEXPECTED);
309 
310   nsresult rv = m_nntpServer->GetMaxArticles(&m_maxArticles);
311   NS_ENSURE_SUCCESS(rv, rv);
312 
313   int32_t socketType;
314   rv = server->GetSocketType(&socketType);
315   NS_ENSURE_SUCCESS(rv, rv);
316 
317   int32_t port = 0;
318   rv = m_url->GetPort(&port);
319   if (NS_FAILED(rv) || (port <= 0)) {
320     rv = server->GetPort(&port);
321     NS_ENSURE_SUCCESS(rv, rv);
322 
323     if (port <= 0) {
324       port = (socketType == nsMsgSocketType::SSL)
325                  ? nsINntpUrl::DEFAULT_NNTPS_PORT
326                  : nsINntpUrl::DEFAULT_NNTP_PORT;
327     }
328 
329     // Don't mutate/clone here.
330     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_url);
331     rv = mailnewsurl->SetPortInternal(port);
332     NS_ENSURE_SUCCESS(rv, rv);
333   }
334 
335   NS_ASSERTION(m_url, "invalid URL passed into NNTP Protocol");
336 
337   m_runningURL = do_QueryInterface(m_url, &rv);
338   NS_ENSURE_SUCCESS(rv, rv);
339   SetIsBusy(true);
340 
341   nsCString group;
342 
343   // Initialize m_newsAction before possible use in ParseURL method
344   m_runningURL->GetNewsAction(&m_newsAction);
345 
346   // parse url to get the msg folder and check if the message is in the folder's
347   // local cache before opening a new socket and trying to download the message
348   rv = ParseURL(m_url, group, m_messageID);
349 
350   if (NS_SUCCEEDED(rv) && m_runningURL) {
351     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
352     if (mailnewsUrl) {
353       if (aMsgWindow) mailnewsUrl->SetMsgWindow(aMsgWindow);
354 
355       if (m_newsAction == nsINntpUrl::ActionFetchArticle ||
356           m_newsAction == nsINntpUrl::ActionFetchPart ||
357           m_newsAction == nsINntpUrl::ActionSaveMessageToDisk) {
358         // Look for the content length
359         nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_runningURL));
360         if (msgUrl) {
361           nsCOMPtr<nsIMsgDBHdr> msgHdr;
362           msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
363           if (msgHdr) {
364             // Note that for attachments, the messageSize is going to be the
365             // size of the entire message
366             uint32_t messageSize;
367             msgHdr->GetMessageSize(&messageSize);
368             SetContentLength(messageSize);
369           }
370         }
371 
372         bool msgIsInLocalCache = false;
373         mailnewsUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
374         if (msgIsInLocalCache || WeAreOffline())
375           return NS_OK;  // probably don't need to do anything else - definitely
376                          // don't want
377         // to open the socket.
378       }
379     }
380   } else {
381     return rv;
382   }
383 
384   if (!m_socketIsOpen) {
385     m_nextState = NNTP_LOGIN_RESPONSE;
386   } else {
387     m_nextState = SEND_FIRST_NNTP_COMMAND;
388   }
389   m_dataBuf = (char*)PR_Malloc(sizeof(char) * OUTPUT_BUFFER_SIZE);
390   m_dataBufSize = OUTPUT_BUFFER_SIZE;
391 
392   if (!m_lineStreamBuffer)
393     m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE,
394                                                    true /* create new lines */);
395 
396   m_typeWanted = 0;
397   m_responseCode = 0;
398   m_previousResponseCode = 0;
399   m_responseText = nullptr;
400 
401   m_firstArticle = 0;
402   m_lastArticle = 0;
403   m_firstPossibleArticle = 0;
404   m_lastPossibleArticle = 0;
405   m_numArticlesLoaded = 0;
406   m_numArticlesWanted = 0;
407 
408   m_key = nsMsgKey_None;
409 
410   m_articleNumber = 0;
411   m_originalContentLength = 0;
412   m_cancelID.Truncate();
413   m_cancelFromHdr.Truncate();
414   m_cancelNewsgroups.Truncate();
415   m_cancelDistribution.Truncate();
416   return NS_OK;
417 }
418 
GetIsBusy(bool * aIsBusy)419 NS_IMETHODIMP nsNNTPProtocol::GetIsBusy(bool* aIsBusy) {
420   NS_ENSURE_ARG_POINTER(aIsBusy);
421   *aIsBusy = m_connectionBusy;
422   return NS_OK;
423 }
424 
SetIsBusy(bool aIsBusy)425 NS_IMETHODIMP nsNNTPProtocol::SetIsBusy(bool aIsBusy) {
426   MOZ_LOG(NNTP, LogLevel::Info, ("(%p) setting busy to %d", this, aIsBusy));
427   m_connectionBusy = aIsBusy;
428 
429   // Maybe we could load another URI.
430   if (!aIsBusy && m_nntpServer) m_nntpServer->PrepareForNextUrl(this);
431 
432   return NS_OK;
433 }
434 
435 /* void GetLastActiveTimeStamp (out PRTime aTimeStamp); */
GetLastActiveTimeStamp(PRTime * aTimeStamp)436 NS_IMETHODIMP nsNNTPProtocol::GetLastActiveTimeStamp(PRTime* aTimeStamp) {
437   NS_ENSURE_ARG_POINTER(aTimeStamp);
438   *aTimeStamp = m_lastActiveTimeStamp;
439   return NS_OK;
440 }
441 
LoadNewsUrl(nsIURI * aURL,nsISupports * aConsumer)442 NS_IMETHODIMP nsNNTPProtocol::LoadNewsUrl(nsIURI* aURL,
443                                           nsISupports* aConsumer) {
444   // clear the previous channel listener and use the new one....
445   // don't reuse an existing channel listener
446   m_channelListener = nullptr;
447   m_channelListener = do_QueryInterface(aConsumer);
448   nsCOMPtr<nsINntpUrl> newsUrl(do_QueryInterface(aURL));
449   newsUrl->GetNewsAction(&m_newsAction);
450 
451   SetupPartExtractorListener(m_channelListener);
452   return LoadUrl(aURL, aConsumer);
453 }
454 
455 // WARNING: the cache stream listener is intended to be accessed from the UI
456 // thread! it will NOT create another proxy for the stream listener that gets
457 // passed in...
458 class nsNntpCacheStreamListener : public nsIStreamListener {
459  public:
460   NS_DECL_ISUPPORTS
461   NS_DECL_NSIREQUESTOBSERVER
462   NS_DECL_NSISTREAMLISTENER
463 
464   nsNntpCacheStreamListener();
465 
466   nsresult Init(nsIStreamListener* aStreamListener, nsIChannel* channel,
467                 nsIMsgMailNewsUrl* aRunningUrl);
468 
469  protected:
470   virtual ~nsNntpCacheStreamListener();
471   nsCOMPtr<nsIChannel> mChannelToUse;
472   nsCOMPtr<nsIStreamListener> mListener;
473   nsCOMPtr<nsIMsgMailNewsUrl> mRunningUrl;
474 };
475 
476 NS_IMPL_ADDREF(nsNntpCacheStreamListener)
NS_IMPL_RELEASE(nsNntpCacheStreamListener)477 NS_IMPL_RELEASE(nsNntpCacheStreamListener)
478 
479 NS_INTERFACE_MAP_BEGIN(nsNntpCacheStreamListener)
480   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
481   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
482   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
483 NS_INTERFACE_MAP_END
484 
485 nsNntpCacheStreamListener::nsNntpCacheStreamListener() {}
486 
~nsNntpCacheStreamListener()487 nsNntpCacheStreamListener::~nsNntpCacheStreamListener() {}
488 
Init(nsIStreamListener * aStreamListener,nsIChannel * channel,nsIMsgMailNewsUrl * aRunningUrl)489 nsresult nsNntpCacheStreamListener::Init(nsIStreamListener* aStreamListener,
490                                          nsIChannel* channel,
491                                          nsIMsgMailNewsUrl* aRunningUrl) {
492   NS_ENSURE_ARG(aStreamListener);
493   NS_ENSURE_ARG(channel);
494 
495   mChannelToUse = channel;
496 
497   mListener = aStreamListener;
498   mRunningUrl = aRunningUrl;
499   return NS_OK;
500 }
501 
502 NS_IMETHODIMP
OnStartRequest(nsIRequest * request)503 nsNntpCacheStreamListener::OnStartRequest(nsIRequest* request) {
504   nsCOMPtr<nsILoadGroup> loadGroup;
505 
506   NS_ASSERTION(mChannelToUse, "null channel in OnStartRequest");
507   if (mChannelToUse) mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
508   if (loadGroup)
509     loadGroup->AddRequest(mChannelToUse, nullptr /* context isupports */);
510   return (mListener) ? mListener->OnStartRequest(mChannelToUse) : NS_OK;
511 }
512 
513 NS_IMETHODIMP
OnStopRequest(nsIRequest * request,nsresult aStatus)514 nsNntpCacheStreamListener::OnStopRequest(nsIRequest* request,
515                                          nsresult aStatus) {
516   nsresult rv = NS_OK;
517   NS_ASSERTION(mListener, "this assertion is for Bug 531794 comment 7");
518   if (mListener) mListener->OnStopRequest(mChannelToUse, aStatus);
519   nsCOMPtr<nsILoadGroup> loadGroup;
520   NS_ASSERTION(mChannelToUse, "null channel in OnStopRequest");
521   if (mChannelToUse) mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
522   if (loadGroup) loadGroup->RemoveRequest(mChannelToUse, nullptr, aStatus);
523 
524   // clear out mem cache entry so we're not holding onto it.
525   if (mRunningUrl) mRunningUrl->SetMemCacheEntry(nullptr);
526 
527   mListener = nullptr;
528   nsCOMPtr<nsINNTPProtocol> nntpProtocol = do_QueryInterface(mChannelToUse);
529   if (nntpProtocol) {
530     rv = nntpProtocol->SetIsBusy(false);
531     NS_ENSURE_SUCCESS(rv, rv);
532   }
533   mChannelToUse = nullptr;
534   return rv;
535 }
536 
537 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsIInputStream * aInStream,uint64_t aSourceOffset,uint32_t aCount)538 nsNntpCacheStreamListener::OnDataAvailable(nsIRequest* request,
539                                            nsIInputStream* aInStream,
540                                            uint64_t aSourceOffset,
541                                            uint32_t aCount) {
542   NS_ENSURE_STATE(mListener);
543   return mListener->OnDataAvailable(mChannelToUse, aInStream, aSourceOffset,
544                                     aCount);
545 }
546 
GetOriginalURI(nsIURI ** aURI)547 NS_IMETHODIMP nsNNTPProtocol::GetOriginalURI(nsIURI** aURI) {
548   // News does not seem to have the notion of an original URI (See Bug #193317)
549   NS_IF_ADDREF(*aURI = m_url);
550   return NS_OK;
551 }
552 
SetOriginalURI(nsIURI * aURI)553 NS_IMETHODIMP nsNNTPProtocol::SetOriginalURI(nsIURI* aURI) {
554   // News does not seem to have the notion of an original URI (See Bug #193317)
555   return NS_OK;  // ignore
556 }
557 
SetupPartExtractorListener(nsIStreamListener * aConsumer)558 nsresult nsNNTPProtocol::SetupPartExtractorListener(
559     nsIStreamListener* aConsumer) {
560   bool convertData = false;
561   nsresult rv = NS_OK;
562 
563   if (m_newsAction == nsINntpUrl::ActionFetchArticle) {
564     nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(m_runningURL, &rv);
565     NS_ENSURE_SUCCESS(rv, rv);
566 
567     nsAutoCString queryStr;
568     rv = msgUrl->GetQuery(queryStr);
569     NS_ENSURE_SUCCESS(rv, rv);
570 
571     // check if this is a filter plugin requesting the message.
572     // in that case, set up a text converter
573     convertData = (queryStr.Find("header=filter") != kNotFound ||
574                    queryStr.Find("header=attach") != kNotFound);
575   } else {
576     convertData = (m_newsAction == nsINntpUrl::ActionFetchPart);
577   }
578   if (convertData) {
579     nsCOMPtr<nsIStreamConverterService> converter =
580         do_GetService("@mozilla.org/streamConverters;1");
581     if (converter && aConsumer) {
582       nsCOMPtr<nsIStreamListener> newConsumer;
583       nsCOMPtr<nsIChannel> channel;
584       QueryInterface(NS_GET_IID(nsIChannel), getter_AddRefs(channel));
585       converter->AsyncConvertData("message/rfc822", "*/*", aConsumer, channel,
586                                   getter_AddRefs(newConsumer));
587       if (newConsumer) m_channelListener = newConsumer;
588     }
589   }
590 
591   return rv;
592 }
593 
ReadFromMemCache(nsICacheEntry * entry)594 nsresult nsNNTPProtocol::ReadFromMemCache(nsICacheEntry* entry) {
595   NS_ENSURE_ARG(entry);
596 
597   nsCOMPtr<nsIInputStream> cacheStream;
598   nsresult rv = entry->OpenInputStream(0, getter_AddRefs(cacheStream));
599 
600   if (NS_SUCCEEDED(rv)) {
601     nsCOMPtr<nsIInputStreamPump> pump;
602     rv = NS_NewInputStreamPump(getter_AddRefs(pump), cacheStream.forget());
603     if (NS_FAILED(rv)) return rv;
604 
605     nsCString group;
606     // do this to get m_key set, so that marking the message read will work.
607     rv = ParseURL(m_url, group, m_messageID);
608 
609     RefPtr<nsNntpCacheStreamListener> cacheListener =
610         new nsNntpCacheStreamListener();
611 
612     SetLoadGroup(m_loadGroup);
613     m_typeWanted = ARTICLE_WANTED;
614 
615     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
616     cacheListener->Init(m_channelListener, static_cast<nsIChannel*>(this),
617                         mailnewsUrl);
618 
619     mContentType = "";  // reset the content type for the upcoming read....
620 
621     rv = pump->AsyncRead(cacheListener);
622 
623     if (NS_SUCCEEDED(rv))  // ONLY if we succeeded in actually starting the read
624                            // should we return
625     {
626       // we're not calling nsMsgProtocol::AsyncRead(), which calls
627       // nsNNTPProtocol::LoadUrl, so we need to take care of some stuff it does.
628       m_channelListener = nullptr;
629       return rv;
630     }
631   }
632 
633   return rv;
634 }
635 
ReadFromNewsConnection()636 nsresult nsNNTPProtocol::ReadFromNewsConnection() {
637   // we might end up here if we thought we had a news message offline
638   // but it turned out not to be so. In which case, we need to
639   // recall Initialize().
640   if (!m_socketIsOpen || !m_dataBuf) {
641     nsresult rv = Initialize(m_url, m_msgWindow);
642     NS_ENSURE_SUCCESS(rv, rv);
643   }
644 
645   return nsMsgProtocol::AsyncOpen(m_channelListener);
646 }
647 
648 // for messages stored in our offline cache, we have special code to handle
649 // that... If it's in the local cache, we return true and we can abort the
650 // download because this method does the rest of the work.
ReadFromLocalCache()651 bool nsNNTPProtocol::ReadFromLocalCache() {
652   bool msgIsInLocalCache = false;
653   nsresult rv = NS_OK;
654   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
655   mailnewsUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
656 
657   if (msgIsInLocalCache) {
658     nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(m_newsFolder);
659     if (folder && NS_SUCCEEDED(rv)) {
660       // we want to create a file channel and read the msg from there.
661       nsCOMPtr<nsIInputStream> fileStream;
662       int64_t offset = 0;
663       uint32_t size = 0;
664       rv = folder->GetOfflineFileStream(m_key, &offset, &size,
665                                         getter_AddRefs(fileStream));
666 
667       // get the file stream from the folder, somehow (through the message or
668       // folder sink?) We also need to set the transfer offset to the message
669       // offset
670       if (NS_SUCCEEDED(rv)) {
671         // dougt - This may break the ablity to "cancel" a read from offline
672         // mail reading. fileChannel->SetLoadGroup(m_loadGroup);
673 
674         m_typeWanted = ARTICLE_WANTED;
675 
676         RefPtr<nsNntpCacheStreamListener> cacheListener =
677             new nsNntpCacheStreamListener();
678 
679         cacheListener->Init(m_channelListener, static_cast<nsIChannel*>(this),
680                             mailnewsUrl);
681 
682         // create a stream pump that will async read the specified amount of
683         // data.
684         // XXX make size 64-bit int
685         RefPtr<SlicedInputStream> slicedStream = new SlicedInputStream(
686             fileStream.forget(), uint64_t(offset), uint64_t(size));
687         nsCOMPtr<nsIInputStreamPump> pump;
688         rv = NS_NewInputStreamPump(getter_AddRefs(pump), slicedStream.forget());
689         if (NS_SUCCEEDED(rv)) rv = pump->AsyncRead(cacheListener);
690 
691         if (NS_SUCCEEDED(rv))  // ONLY if we succeeded in actually starting the
692                                // read should we return
693         {
694           mContentType.Truncate();
695           m_channelListener = nullptr;
696           NNTP_LOG_NOTE("Loading message from offline storage");
697           return true;
698         }
699       } else
700         mailnewsUrl->SetMsgIsInLocalCache(false);
701     }
702   }
703 
704   return false;
705 }
706 
707 NS_IMETHODIMP
OnCacheEntryAvailable(nsICacheEntry * entry,bool aNew,nsresult status)708 nsNNTPProtocol::OnCacheEntryAvailable(nsICacheEntry* entry, bool aNew,
709                                       nsresult status) {
710   nsresult rv = NS_OK;
711 
712   if (NS_SUCCEEDED(status)) {
713     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
714         do_QueryInterface(m_runningURL, &rv);
715     mailnewsUrl->SetMemCacheEntry(entry);
716 
717     // Insert a "stream T" into the flow so data gets written to both.
718     if (aNew) {
719       // use a stream listener Tee to force data into the cache and to our
720       // current channel listener...
721       nsCOMPtr<nsIStreamListener> newListener;
722       nsCOMPtr<nsIStreamListenerTee> tee =
723           do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv);
724       NS_ENSURE_SUCCESS(rv, rv);
725 
726       nsCOMPtr<nsIOutputStream> outStream;
727       rv = entry->OpenOutputStream(0, -1, getter_AddRefs(outStream));
728       NS_ENSURE_SUCCESS(rv, rv);
729 
730       rv = tee->Init(m_channelListener, outStream, nullptr);
731       NS_ENSURE_SUCCESS(rv, rv);
732       m_channelListener = tee;
733     } else {
734       rv = ReadFromMemCache(entry);
735       if (NS_SUCCEEDED(rv)) {
736         entry->MarkValid();
737         return NS_OK;  // kick out if reading from the cache succeeded...
738       }
739     }
740   }  // if we got a valid entry back from the cache...
741 
742   // if reading from the cache failed or if we are writing into the cache,
743   // default to ReadFromNewsConnection.
744   return ReadFromNewsConnection();
745 }
746 
747 NS_IMETHODIMP
OnCacheEntryCheck(nsICacheEntry * entry,uint32_t * aResult)748 nsNNTPProtocol::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* aResult) {
749   *aResult = nsICacheEntryOpenCallback::ENTRY_WANTED;
750   return NS_OK;
751 }
752 
OpenCacheEntry()753 nsresult nsNNTPProtocol::OpenCacheEntry() {
754   nsresult rv = NS_OK;
755   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
756       do_QueryInterface(m_runningURL, &rv);
757   // get the cache session from our nntp service...
758   nsCOMPtr<nsINntpService> nntpService =
759       do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
760   NS_ENSURE_SUCCESS(rv, rv);
761 
762   nsCOMPtr<nsICacheStorage> cacheStorage;
763   rv = nntpService->GetCacheStorage(getter_AddRefs(cacheStorage));
764   NS_ENSURE_SUCCESS(rv, rv);
765 
766   // Open a cache entry with key = url, no extension.
767   nsCOMPtr<nsIURI> uri;
768   rv = mailnewsUrl->GetBaseURI(getter_AddRefs(uri));
769   NS_ENSURE_SUCCESS(rv, rv);
770 
771   // Truncate of the query part so we don't duplicate urls in the cache for
772   // various message parts.
773   nsAutoCString path;
774   uri->GetPathQueryRef(path);
775   int32_t pos = path.FindChar('?');
776   nsCOMPtr<nsIURI> newUri;
777   if (pos != kNotFound) {
778     path.SetLength(pos);
779     rv = NS_MutateURI(uri).SetPathQueryRef(path).Finalize(newUri);
780     NS_ENSURE_SUCCESS(rv, rv);
781   }
782   return cacheStorage->AsyncOpenURI(newUri ? newUri : uri, EmptyCString(),
783                                     nsICacheStorage::OPEN_NORMALLY, this);
784 }
785 
AsyncOpen(nsIStreamListener * aListener)786 NS_IMETHODIMP nsNNTPProtocol::AsyncOpen(nsIStreamListener* aListener) {
787   nsCOMPtr<nsIStreamListener> listener = aListener;
788   nsresult rv =
789       nsContentSecurityManager::doContentSecurityCheck(this, listener);
790   NS_ENSURE_SUCCESS(rv, rv);
791 
792   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
793       do_QueryInterface(m_runningURL, &rv);
794   NS_ENSURE_SUCCESS(rv, rv);
795 
796   int32_t port;
797   rv = mailnewsUrl->GetPort(&port);
798   if (NS_FAILED(rv)) return rv;
799 
800   rv = NS_CheckPortSafety(port, "news");
801   if (NS_FAILED(rv)) return rv;
802 
803   m_isChannel = true;
804   m_channelListener = listener;
805   m_runningURL->GetNewsAction(&m_newsAction);
806 
807   // Before running through the connection, try to see if we can grab the data
808   // from the offline storage or the memory cache. Only actions retrieving
809   // messages can be cached.
810   if (mailnewsUrl && (m_newsAction == nsINntpUrl::ActionFetchArticle ||
811                       m_newsAction == nsINntpUrl::ActionFetchPart ||
812                       m_newsAction == nsINntpUrl::ActionSaveMessageToDisk)) {
813     SetupPartExtractorListener(m_channelListener);
814 
815     // Attempt to get the message from the offline storage cache. If this
816     // succeeds, we don't need to use our connection, so tell the server that we
817     // are ready for the next URL.
818     if (ReadFromLocalCache()) {
819       if (m_nntpServer) m_nntpServer->PrepareForNextUrl(this);
820       return NS_OK;
821     }
822 
823     // If it wasn't offline, try to get the cache from memory. If this call
824     // succeeds, we probably won't need the connection, but the cache might fail
825     // later on. The code there will determine if we need to fallback and will
826     // handle informing the server of our readiness.
827     if (NS_SUCCEEDED(OpenCacheEntry())) return NS_OK;
828   }
829 
830   return nsMsgProtocol::AsyncOpen(
831       listener);  // Context already attached to the channel.
832 }
833 
PostLoadAssertions()834 void nsNNTPProtocol::PostLoadAssertions() {
835   // Make sure that we have the information we need to be able to run the
836   // URLs
837   NS_ASSERTION(m_nntpServer, "Parsing must result in an m_nntpServer");
838   if (m_typeWanted == ARTICLE_WANTED) {
839     if (m_key != nsMsgKey_None)
840       NS_ASSERTION(m_newsFolder, "ARTICLE_WANTED needs m_newsFolder w/ key");
841     else
842       NS_ASSERTION(!m_messageID.IsEmpty(),
843                    "ARTICLE_WANTED needs m_messageID w/o key");
844   } else if (m_typeWanted == CANCEL_WANTED) {
845     NS_ASSERTION(!m_messageID.IsEmpty(), "CANCEL_WANTED needs m_messageID");
846     NS_ASSERTION(m_newsFolder, "CANCEL_WANTED needs m_newsFolder");
847     NS_ASSERTION(m_key != nsMsgKey_None, "CANCEL_WANTED needs m_key");
848   } else if (m_typeWanted == GROUP_WANTED)
849     NS_ASSERTION(m_newsFolder, "GROUP_WANTED needs m_newsFolder");
850   else if (m_typeWanted == SEARCH_WANTED)
851     NS_ASSERTION(!m_searchData.IsEmpty(), "SEARCH_WANTED needs m_searchData");
852   else if (m_typeWanted == IDS_WANTED)
853     NS_ASSERTION(m_newsFolder, "IDS_WANTED needs m_newsFolder");
854 }
855 
LoadUrl(nsIURI * aURL,nsISupports * aConsumer)856 nsresult nsNNTPProtocol::LoadUrl(nsIURI* aURL, nsISupports* aConsumer) {
857   NS_ENSURE_ARG_POINTER(aURL);
858 
859   nsCString group;
860   mContentType.Truncate();
861   nsresult rv = NS_OK;
862 
863   m_runningURL = do_QueryInterface(aURL, &rv);
864   if (NS_FAILED(rv)) return rv;
865   m_runningURL->GetNewsAction(&m_newsAction);
866 
867   SetIsBusy(true);
868 
869   rv = ParseURL(aURL, group, m_messageID);
870   NS_ASSERTION(NS_SUCCEEDED(rv), "failed to parse news url");
871   // if (NS_FAILED(rv)) return rv;
872   // XXX group returned from ParseURL is assumed to be in UTF-8
873   NS_ASSERTION(mozilla::IsUtf8(group), "newsgroup name is not in UTF-8");
874   NS_ASSERTION(m_nntpServer, "Parsing must result in an m_nntpServer");
875 
876   MOZ_LOG(NNTP, LogLevel::Info,
877           ("(%p) m_messageID = %s", this, m_messageID.get()));
878   MOZ_LOG(NNTP, LogLevel::Info, ("(%p) group = %s", this, group.get()));
879   MOZ_LOG(NNTP, LogLevel::Info, ("(%p) m_key = %d", this, m_key));
880 
881   if (m_newsAction == nsINntpUrl::ActionFetchArticle ||
882       m_newsAction == nsINntpUrl::ActionFetchPart ||
883       m_newsAction == nsINntpUrl::ActionSaveMessageToDisk)
884     m_typeWanted = ARTICLE_WANTED;
885   else if (m_newsAction == nsINntpUrl::ActionCancelArticle)
886     m_typeWanted = CANCEL_WANTED;
887   else if (m_newsAction == nsINntpUrl::ActionPostArticle) {
888     m_typeWanted = NEWS_POST;
889     m_messageID = "";
890   } else if (m_newsAction == nsINntpUrl::ActionListIds) {
891     m_typeWanted = IDS_WANTED;
892     rv = m_nntpServer->FindGroup(group, getter_AddRefs(m_newsFolder));
893   } else if (m_newsAction == nsINntpUrl::ActionSearch) {
894     m_typeWanted = SEARCH_WANTED;
895 
896     // Get the search data
897     nsCString commandSpecificData;
898     nsCOMPtr<nsIURL> url = do_QueryInterface(m_runningURL);
899     rv = url->GetQuery(commandSpecificData);
900     NS_ENSURE_SUCCESS(rv, rv);
901     MsgUnescapeString(commandSpecificData, 0, m_searchData);
902 
903     rv = m_nntpServer->FindGroup(group, getter_AddRefs(m_newsFolder));
904     if (!m_newsFolder) goto FAIL;
905   } else if (m_newsAction == nsINntpUrl::ActionGetNewNews) {
906     bool containsGroup = true;
907     rv = m_nntpServer->ContainsNewsgroup(group, &containsGroup);
908     if (NS_FAILED(rv)) goto FAIL;
909 
910     if (!containsGroup) {
911       // We have the name of a newsgroup which we're not subscribed to,
912       // the next step is to ask the user whether we should subscribe to it.
913       nsCOMPtr<nsIPrompt> dialog;
914 
915       if (m_msgWindow) m_msgWindow->GetPromptDialog(getter_AddRefs(dialog));
916 
917       if (!dialog) {
918         nsCOMPtr<nsIWindowWatcher> wwatch(
919             do_GetService(NS_WINDOWWATCHER_CONTRACTID));
920         wwatch->GetNewPrompter(nullptr, getter_AddRefs(dialog));
921       }
922 
923       nsString statusString, confirmText;
924       nsCOMPtr<nsIStringBundle> bundle;
925       nsCOMPtr<nsIStringBundleService> bundleService =
926           mozilla::services::GetStringBundleService();
927 
928       // to handle non-ASCII newsgroup names, we store them internally
929       // as escaped. decode and unescape the newsgroup name so we'll
930       // display a proper name.
931 
932       nsAutoString unescapedName;
933       rv = NS_MsgDecodeUnescapeURLPath(group, unescapedName);
934       NS_ENSURE_SUCCESS(rv, rv);
935 
936       bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
937       AutoTArray<nsString, 1> formatStrings = {unescapedName};
938 
939       rv = bundle->FormatStringFromName("autoSubscribeText", formatStrings,
940                                         confirmText);
941       NS_ENSURE_SUCCESS(rv, rv);
942 
943       bool confirmResult = false;
944       rv = dialog->Confirm(nullptr, confirmText.get(), &confirmResult);
945       NS_ENSURE_SUCCESS(rv, rv);
946 
947       if (confirmResult) {
948         rv = m_nntpServer->SubscribeToNewsgroup(group);
949         containsGroup = true;
950       } else {
951         // XXX FIX ME
952         // the way news is current written, we've already opened the socket
953         // and initialized the connection.
954         //
955         // until that is fixed, when the user cancels an autosubscribe, we've
956         // got to close it and clean up after ourselves
957         //
958         // see bug http://bugzilla.mozilla.org/show_bug.cgi?id=108293
959         // another problem, autosubscribe urls are ending up as cache entries
960         // because the default action on nntp urls is ActionFetchArticle
961         //
962         // see bug http://bugzilla.mozilla.org/show_bug.cgi?id=108294
963         if (m_runningURL)
964           FinishMemCacheEntry(false);  // cleanup mem cache entry
965 
966         return CloseConnection();
967       }
968     }
969 
970     // If we have a group (since before, or just subscribed), set the
971     // m_newsFolder.
972     if (containsGroup) {
973       rv = m_nntpServer->FindGroup(group, getter_AddRefs(m_newsFolder));
974       if (!m_newsFolder) goto FAIL;
975     }
976     m_typeWanted = GROUP_WANTED;
977   } else if (m_newsAction == nsINntpUrl::ActionListGroups)
978     m_typeWanted = LIST_WANTED;
979   else if (m_newsAction == nsINntpUrl::ActionListNewGroups)
980     m_typeWanted = NEW_GROUPS;
981   else if (!m_messageID.IsEmpty() || m_key != nsMsgKey_None)
982     m_typeWanted = ARTICLE_WANTED;
983   else {
984     MOZ_ASSERT_UNREACHABLE("Unknown news action");
985     rv = NS_ERROR_FAILURE;
986   }
987 
988   /* At this point, we're all done parsing the URL, and know exactly
989   what we want to do with it.
990 */
991 
992 FAIL:
993   if (NS_FAILED(rv)) {
994     AlertError(0, nullptr);
995     return rv;
996   } else {
997     if (!m_socketIsOpen) {
998       m_nextStateAfterResponse = m_nextState;
999       m_nextState = NNTP_RESPONSE;
1000 
1001       // Calls LoadUrl in nsNNTPProtocol::OnProxyAvailable
1002       rv = MsgExamineForProxyAsync(this, this, getter_AddRefs(m_proxyRequest));
1003       if (NS_FAILED(rv)) {
1004         rv = LoadUrlInternal(nullptr);
1005       }
1006     } else {
1007       rv = nsMsgProtocol::LoadUrl(aURL, aConsumer);
1008     }
1009     if (NS_SUCCEEDED(rv)) PostLoadAssertions();
1010   }
1011 
1012   return rv;
1013 }
1014 
1015 // nsIProtocolProxyCallback
1016 NS_IMETHODIMP
OnProxyAvailable(nsICancelable * aRequest,nsIChannel * aChannel,nsIProxyInfo * aProxyInfo,nsresult aStatus)1017 nsNNTPProtocol::OnProxyAvailable(nsICancelable* aRequest, nsIChannel* aChannel,
1018                                  nsIProxyInfo* aProxyInfo, nsresult aStatus) {
1019   MOZ_ASSERT(aChannel == this,
1020              "Should never request a proxy for anyone but ourselves");
1021 
1022   // If we're called with NS_BINDING_ABORTED, we came here via Cancel().
1023   // Otherwise, no checking of 'aStatus' here, see
1024   // nsHttpChannel::OnProxyAvailable(). Status is non-fatal and we just kick on.
1025   if (aStatus == NS_BINDING_ABORTED) return NS_ERROR_FAILURE;
1026 
1027   nsresult rv = LoadUrlInternal(aProxyInfo);
1028   if (NS_FAILED(rv)) {
1029     return Cancel(rv);
1030   }
1031 
1032   PostLoadAssertions();
1033 
1034   return rv;
1035 }
1036 
LoadUrlInternal(nsIProxyInfo * aProxyInfo)1037 nsresult nsNNTPProtocol::LoadUrlInternal(nsIProxyInfo* aProxyInfo) {
1038   m_proxyRequest = nullptr;
1039 
1040   nsresult rv;
1041   nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer, &rv);
1042   NS_ENSURE_SUCCESS(rv, rv);
1043 
1044   nsCString hostName;
1045   int32_t port = 0;
1046   int32_t socketType;
1047 
1048   rv = server->GetRealHostName(hostName);
1049   NS_ENSURE_SUCCESS(rv, rv);
1050 
1051   rv = m_url->GetPort(&port);
1052   NS_ENSURE_SUCCESS(rv, rv);
1053 
1054   rv = server->GetSocketType(&socketType);
1055   NS_ENSURE_SUCCESS(rv, rv);
1056 
1057   nsCOMPtr<nsIInterfaceRequestor> ir;
1058   if (socketType != nsMsgSocketType::plain && m_msgWindow) {
1059     nsCOMPtr<nsIDocShell> docShell;
1060     m_msgWindow->GetRootDocShell(getter_AddRefs(docShell));
1061     ir = do_QueryInterface(docShell);
1062   }
1063 
1064   MOZ_LOG(
1065       NNTP, LogLevel::Info,
1066       ("(%p) opening connection to %s on port %d", this, hostName.get(), port));
1067 
1068   rv = OpenNetworkSocketWithInfo(
1069       hostName.get(), port,
1070       (socketType == nsMsgSocketType::SSL) ? "ssl" : nullptr, aProxyInfo, ir);
1071 
1072   rv = nsMsgProtocol::LoadUrl(m_url, m_consumer);
1073   NS_ENSURE_SUCCESS(rv, rv);
1074 
1075   return rv;
1076 }
1077 
FinishMemCacheEntry(bool valid)1078 void nsNNTPProtocol::FinishMemCacheEntry(bool valid) {
1079   nsCOMPtr<nsICacheEntry> memCacheEntry;
1080   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
1081   if (mailnewsurl) mailnewsurl->GetMemCacheEntry(getter_AddRefs(memCacheEntry));
1082   if (memCacheEntry) {
1083     if (valid)
1084       memCacheEntry->MarkValid();
1085     else
1086       memCacheEntry->AsyncDoom(nullptr);
1087   }
1088 }
1089 
1090 // stop binding is a "notification" informing us that the stream associated with
1091 // aURL is going away.
OnStopRequest(nsIRequest * request,nsresult aStatus)1092 NS_IMETHODIMP nsNNTPProtocol::OnStopRequest(nsIRequest* request,
1093                                             nsresult aStatus) {
1094   // either remove mem cache entry, or mark it valid if url successful and
1095   // command succeeded
1096   FinishMemCacheEntry(NS_SUCCEEDED(aStatus) &&
1097                       MK_NNTP_RESPONSE_TYPE(m_responseCode) ==
1098                           MK_NNTP_RESPONSE_TYPE_OK);
1099 
1100   nsMsgProtocol::OnStopRequest(request, aStatus);
1101 
1102   // nsMsgProtocol::OnStopRequest() has called m_channelListener. There is
1103   // no need to be called again in CloseSocket(). Let's clear it here.
1104   if (m_channelListener) {
1105     m_channelListener = nullptr;
1106   }
1107 
1108   // okay, we've been told that the send is done and the connection is going
1109   // away. So we need to release all of our state
1110   return CloseSocket();
1111 }
1112 
Cancel(nsresult status)1113 NS_IMETHODIMP nsNNTPProtocol::Cancel(nsresult status)  // handle stop button
1114 {
1115   if (m_proxyRequest) {
1116     m_proxyRequest->Cancel(NS_BINDING_ABORTED);
1117 
1118     // Note that nsMsgProtocol::Cancel() also calls
1119     // nsProtocolProxyService::Cancel(), so no need to call it twice
1120     // (although it self-protects against multiple calls).
1121     m_proxyRequest = nullptr;
1122   }
1123 
1124   m_nextState = NNTP_ERROR;
1125   return nsMsgProtocol::Cancel(NS_BINDING_ABORTED);
1126 }
1127 
ParseURL(nsIURI * aURL,nsCString & aGroup,nsCString & aMessageID)1128 nsresult nsNNTPProtocol::ParseURL(nsIURI* aURL, nsCString& aGroup,
1129                                   nsCString& aMessageID) {
1130   NS_ENSURE_ARG_POINTER(aURL);
1131 
1132   MOZ_LOG(NNTP, LogLevel::Info, ("(%p) ParseURL", this));
1133 
1134   nsresult rv;
1135   nsCOMPtr<nsIMsgFolder> folder;
1136   nsCOMPtr<nsINntpService> nntpService =
1137       do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
1138   NS_ENSURE_SUCCESS(rv, rv);
1139 
1140   nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningURL, &rv);
1141   NS_ENSURE_SUCCESS(rv, rv);
1142 
1143   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(msgUrl, &rv);
1144   NS_ENSURE_SUCCESS(rv, rv);
1145 
1146   nsCString spec;
1147   rv = msgUrl->GetOriginalSpec(spec);
1148   NS_ENSURE_SUCCESS(rv, rv);
1149 
1150   // if the original spec is non empty, use it to determine m_newsFolder and
1151   // m_key
1152   if (!spec.IsEmpty()) {
1153     MOZ_LOG(NNTP, LogLevel::Info,
1154             ("(%p) original message spec = %s", this, spec.get()));
1155 
1156     rv = nntpService->DecomposeNewsURI(spec, getter_AddRefs(folder), &m_key);
1157     NS_ENSURE_SUCCESS(rv, rv);
1158 
1159     // since we are reading a message in this folder, we can set m_newsFolder
1160     m_newsFolder = do_QueryInterface(folder, &rv);
1161     NS_ENSURE_SUCCESS(rv, rv);
1162 
1163     // if we are cancelling, we aren't done.  we still need to parse out the
1164     // messageID from the url later, we'll use m_newsFolder and m_key to delete
1165     // the message from the DB, if the cancel is successful.
1166     if (m_newsAction != nsINntpUrl::ActionCancelArticle) {
1167       return NS_OK;
1168     }
1169   } else {
1170     // clear this, we'll set it later.
1171     m_newsFolder = nullptr;
1172     m_currentGroup.Truncate();
1173   }
1174 
1175   // Load the values from the URL for parsing.
1176   rv = m_runningURL->GetGroup(aGroup);
1177   NS_ENSURE_SUCCESS(rv, rv);
1178   rv = m_runningURL->GetMessageID(aMessageID);
1179   NS_ENSURE_SUCCESS(rv, rv);
1180 
1181   NS_ASSERTION(aMessageID.IsEmpty() || aMessageID != aGroup,
1182                "something not null");
1183 
1184   // If we are cancelling, we've got our message id, m_key, and m_newsFolder.
1185   // Bail out now to prevent messing those up.
1186   if (m_newsAction == nsINntpUrl::ActionCancelArticle) return NS_OK;
1187 
1188   rv = m_runningURL->GetKey(&m_key);
1189   NS_ENSURE_SUCCESS(rv, rv);
1190 
1191   // Check if the key is in the local cache.
1192   // It's possible that we're have a server/group/key combo that doesn't exist
1193   // (think nntp://server/group/key), so not having the folder isn't a bad
1194   // thing.
1195   if (m_key != nsMsgKey_None) {
1196     rv = mailnewsUrl->GetFolder(getter_AddRefs(folder));
1197     m_newsFolder = do_QueryInterface(folder);
1198 
1199     if (NS_SUCCEEDED(rv) && m_newsFolder) {
1200       bool useLocalCache = false;
1201       rv = folder->HasMsgOffline(m_key, &useLocalCache);
1202       NS_ENSURE_SUCCESS(rv, rv);
1203 
1204       // set message is in local cache
1205       rv = mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
1206       NS_ENSURE_SUCCESS(rv, rv);
1207     }
1208   }
1209 
1210   return NS_OK;
1211 }
1212 /*
1213  * Writes the data contained in dataBuffer into the current output stream. It
1214  * also informs the transport layer that this data is now available for
1215  * transmission. Returns a positive number for success, 0 for failure (not all
1216  * the bytes were written to the stream, etc). We need to make another pass
1217  * through this file to install an error system (mscott)
1218  */
1219 
SendData(const char * dataBuffer,bool aSuppressLogging)1220 nsresult nsNNTPProtocol::SendData(const char* dataBuffer,
1221                                   bool aSuppressLogging) {
1222   if (!aSuppressLogging) {
1223     NNTP_LOG_WRITE(dataBuffer);
1224   } else {
1225     MOZ_LOG(NNTP, out,
1226             ("(%p) Logging suppressed for this command (it probably contained "
1227              "authentication information)",
1228              this));
1229   }
1230 
1231   return nsMsgProtocol::SendData(
1232       dataBuffer);  // base class actually transmits the data
1233 }
1234 
1235 /* gets the response code from the nntp server and the
1236  * response line
1237  *
1238  * returns the TCP return code from the read
1239  */
NewsResponse(nsIInputStream * inputStream,uint32_t length)1240 nsresult nsNNTPProtocol::NewsResponse(nsIInputStream* inputStream,
1241                                       uint32_t length) {
1242   uint32_t status = 0;
1243 
1244   NS_ASSERTION(nullptr != inputStream, "invalid input stream");
1245 
1246   bool pauseForMoreData = false;
1247   char* line =
1248       m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
1249 
1250   NNTP_LOG_READ(line);
1251 
1252   if (pauseForMoreData) {
1253     SetFlag(NNTP_PAUSE_FOR_READ);
1254     return NS_OK;
1255   }
1256 
1257   if (!line) return NS_ERROR_FAILURE;
1258 
1259   ClearFlag(NNTP_PAUSE_FOR_READ); /* don't pause if we got a line */
1260 
1261   /* almost correct */
1262   if (status > 1) {
1263     mBytesReceived += status;
1264     mBytesReceivedSinceLastStatusUpdate += status;
1265   }
1266 
1267   m_previousResponseCode = m_responseCode;
1268 
1269   PR_sscanf(line, "%d", &m_responseCode);
1270 
1271   if (m_responseCode && PL_strlen(line) > 3)
1272     NS_MsgSACopy(&m_responseText, line + 4);
1273   else
1274     NS_MsgSACopy(&m_responseText, line);
1275 
1276   /* authentication required can come at any time
1277    */
1278   if (MK_NNTP_RESPONSE_AUTHINFO_REQUIRE == m_responseCode ||
1279       MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_REQUIRE == m_responseCode) {
1280     m_nextState = NNTP_BEGIN_AUTHORIZE;
1281   } else {
1282     m_nextState = m_nextStateAfterResponse;
1283   }
1284 
1285   PR_FREEIF(line);
1286   return NS_OK;
1287 }
1288 
1289 /* interpret the server response after the connect
1290  *
1291  * returns negative if the server responds unexpectedly
1292  */
1293 
LoginResponse()1294 nsresult nsNNTPProtocol::LoginResponse() {
1295   bool postingAllowed = m_responseCode == MK_NNTP_RESPONSE_POSTING_ALLOWED;
1296 
1297   if (MK_NNTP_RESPONSE_TYPE(m_responseCode) != MK_NNTP_RESPONSE_TYPE_OK) {
1298     AlertError(MK_NNTP_ERROR_MESSAGE, m_responseText);
1299 
1300     m_nextState = NNTP_ERROR;
1301     return NS_ERROR_FAILURE;
1302   }
1303 
1304   m_nntpServer->SetPostingAllowed(postingAllowed);
1305   m_nextState = NNTP_SEND_MODE_READER;
1306   return NS_OK;
1307 }
1308 
SendModeReader()1309 nsresult nsNNTPProtocol::SendModeReader() {
1310   nsresult rv = NS_OK;
1311 
1312   rv = SendData(NNTP_CMD_MODE_READER);
1313   m_nextState = NNTP_RESPONSE;
1314   m_nextStateAfterResponse = NNTP_SEND_MODE_READER_RESPONSE;
1315   SetFlag(NNTP_PAUSE_FOR_READ);
1316 
1317   NS_ENSURE_SUCCESS(rv, rv);
1318   return rv;
1319 }
1320 
SendModeReaderResponse()1321 nsresult nsNNTPProtocol::SendModeReaderResponse() {
1322   SetFlag(NNTP_READER_PERFORMED);
1323 
1324   /* ignore the response code and continue
1325    */
1326   bool pushAuth = false;
1327   nsresult rv = NS_OK;
1328 
1329   NS_ASSERTION(m_nntpServer, "no server, see bug #107797");
1330   if (m_nntpServer) {
1331     rv = m_nntpServer->GetPushAuth(&pushAuth);
1332   }
1333   if (NS_SUCCEEDED(rv) && pushAuth) {
1334     /* if the news host is set up to require volunteered (pushed)
1335      * authentication, do that before we do anything else
1336      */
1337     m_nextState = NNTP_BEGIN_AUTHORIZE;
1338   } else {
1339 #ifdef HAVE_NNTP_EXTENSIONS
1340     m_nextState = SEND_LIST_EXTENSIONS;
1341 #else
1342     m_nextState = SEND_FIRST_NNTP_COMMAND;
1343 #endif /* HAVE_NNTP_EXTENSIONS */
1344   }
1345 
1346   return NS_OK;
1347 }
1348 
SendListExtensions()1349 nsresult nsNNTPProtocol::SendListExtensions() {
1350   nsresult rv = SendData(NNTP_CMD_LIST_EXTENSIONS);
1351 
1352   m_nextState = NNTP_RESPONSE;
1353   m_nextStateAfterResponse = SEND_LIST_EXTENSIONS_RESPONSE;
1354   ClearFlag(NNTP_PAUSE_FOR_READ);
1355   return rv;
1356 }
1357 
SendListExtensionsResponse(nsIInputStream * inputStream,uint32_t length)1358 nsresult nsNNTPProtocol::SendListExtensionsResponse(nsIInputStream* inputStream,
1359                                                     uint32_t length) {
1360   nsresult rv = NS_OK;
1361 
1362   if (MK_NNTP_RESPONSE_TYPE(m_responseCode) == MK_NNTP_RESPONSE_TYPE_OK) {
1363     uint32_t status = 0;
1364     bool pauseForMoreData = false;
1365     char* line = m_lineStreamBuffer->ReadNextLine(inputStream, status,
1366                                                   pauseForMoreData, &rv);
1367 
1368     if (pauseForMoreData) {
1369       SetFlag(NNTP_PAUSE_FOR_READ);
1370       return NS_OK;
1371     }
1372     if (!line) return rv; /* no line yet */
1373 
1374     if ('.' != line[0]) {
1375       m_nntpServer->AddExtension(line);
1376     } else {
1377       /* tell libmsg that it's ok to ask this news host for extensions */
1378       m_nntpServer->SetSupportsExtensions(true);
1379       /* all extensions received */
1380       m_nextState = SEND_LIST_SEARCHES;
1381       ClearFlag(NNTP_PAUSE_FOR_READ);
1382     }
1383   } else {
1384     /* LIST EXTENSIONS not recognized
1385      * tell libmsg not to ask for any more extensions and move on to
1386      * the real NNTP command we were trying to do. */
1387 
1388     m_nntpServer->SetSupportsExtensions(false);
1389     m_nextState = SEND_FIRST_NNTP_COMMAND;
1390   }
1391 
1392   return NS_OK;
1393 }
1394 
SendListSearches()1395 nsresult nsNNTPProtocol::SendListSearches() {
1396   nsresult rv;
1397   bool searchable = false;
1398 
1399   rv = m_nntpServer->QueryExtension("SEARCH", &searchable);
1400   if (NS_SUCCEEDED(rv) && searchable) {
1401     rv = SendData(NNTP_CMD_LIST_SEARCHES);
1402 
1403     m_nextState = NNTP_RESPONSE;
1404     m_nextStateAfterResponse = SEND_LIST_SEARCHES_RESPONSE;
1405     SetFlag(NNTP_PAUSE_FOR_READ);
1406   } else {
1407     /* since SEARCH isn't supported, move on to GET */
1408     m_nextState = NNTP_GET_PROPERTIES;
1409     ClearFlag(NNTP_PAUSE_FOR_READ);
1410   }
1411 
1412   return rv;
1413 }
1414 
SendListSearchesResponse(nsIInputStream * inputStream,uint32_t length)1415 nsresult nsNNTPProtocol::SendListSearchesResponse(nsIInputStream* inputStream,
1416                                                   uint32_t length) {
1417   uint32_t status = 0;
1418   nsresult rv = NS_OK;
1419 
1420   NS_ASSERTION(inputStream, "invalid input stream");
1421 
1422   bool pauseForMoreData = false;
1423   char* line = m_lineStreamBuffer->ReadNextLine(inputStream, status,
1424                                                 pauseForMoreData, &rv);
1425 
1426   NNTP_LOG_READ(line);
1427 
1428   if (pauseForMoreData) {
1429     SetFlag(NNTP_PAUSE_FOR_READ);
1430     return NS_OK;
1431   }
1432   if (!line) return rv; /* no line yet */
1433 
1434   if ('.' != line[0]) {
1435     nsAutoCString charset;
1436     nsAutoString lineUtf16;
1437     if (NS_FAILED(m_nntpServer->GetCharset(charset)) ||
1438         NS_FAILED(nsMsgI18NConvertToUnicode(charset, nsDependentCString(line),
1439                                             lineUtf16)))
1440       CopyUTF8toUTF16(nsDependentCString(line), lineUtf16);
1441 
1442     m_nntpServer->AddSearchableGroup(lineUtf16);
1443   } else {
1444     /* all searchable groups received */
1445     /* LIST SRCHFIELDS is legal if the server supports the SEARCH extension,
1446      * which */
1447     /* we already know it does */
1448     m_nextState = NNTP_LIST_SEARCH_HEADERS;
1449     ClearFlag(NNTP_PAUSE_FOR_READ);
1450   }
1451 
1452   PR_FREEIF(line);
1453   return rv;
1454 }
1455 
SendListSearchHeaders()1456 nsresult nsNNTPProtocol::SendListSearchHeaders() {
1457   nsresult rv = SendData(NNTP_CMD_LIST_SEARCH_FIELDS);
1458 
1459   m_nextState = NNTP_RESPONSE;
1460   m_nextStateAfterResponse = NNTP_LIST_SEARCH_HEADERS_RESPONSE;
1461   SetFlag(NNTP_PAUSE_FOR_READ);
1462 
1463   return rv;
1464 }
1465 
SendListSearchHeadersResponse(nsIInputStream * inputStream,uint32_t length)1466 nsresult nsNNTPProtocol::SendListSearchHeadersResponse(
1467     nsIInputStream* inputStream, uint32_t length) {
1468   uint32_t status = 0;
1469   nsresult rv;
1470 
1471   bool pauseForMoreData = false;
1472   char* line = m_lineStreamBuffer->ReadNextLine(inputStream, status,
1473                                                 pauseForMoreData, &rv);
1474 
1475   if (pauseForMoreData) {
1476     SetFlag(NNTP_PAUSE_FOR_READ);
1477     return NS_OK;
1478   }
1479   if (!line) return rv; /* no line yet */
1480 
1481   if ('.' != line[0])
1482     m_nntpServer->AddSearchableHeader(line);
1483   else {
1484     m_nextState = NNTP_GET_PROPERTIES;
1485     ClearFlag(NNTP_PAUSE_FOR_READ);
1486   }
1487 
1488   PR_FREEIF(line);
1489   return rv;
1490 }
1491 
GetProperties()1492 nsresult nsNNTPProtocol::GetProperties() {
1493   nsresult rv;
1494   bool setget = false;
1495 
1496   rv = m_nntpServer->QueryExtension("SETGET", &setget);
1497   if (NS_SUCCEEDED(rv) && setget) {
1498     rv = SendData(NNTP_CMD_GET_PROPERTIES);
1499     m_nextState = NNTP_RESPONSE;
1500     m_nextStateAfterResponse = NNTP_GET_PROPERTIES_RESPONSE;
1501     SetFlag(NNTP_PAUSE_FOR_READ);
1502   } else {
1503     /* since GET isn't supported, move on LIST SUBSCRIPTIONS */
1504     m_nextState = SEND_LIST_SUBSCRIPTIONS;
1505     ClearFlag(NNTP_PAUSE_FOR_READ);
1506   }
1507   return rv;
1508 }
1509 
GetPropertiesResponse(nsIInputStream * inputStream,uint32_t length)1510 nsresult nsNNTPProtocol::GetPropertiesResponse(nsIInputStream* inputStream,
1511                                                uint32_t length) {
1512   uint32_t status = 0;
1513   nsresult rv;
1514 
1515   bool pauseForMoreData = false;
1516   char* line = m_lineStreamBuffer->ReadNextLine(inputStream, status,
1517                                                 pauseForMoreData, &rv);
1518 
1519   if (pauseForMoreData) {
1520     SetFlag(NNTP_PAUSE_FOR_READ);
1521     return NS_OK;
1522   }
1523   if (!line) return rv; /* no line yet */
1524 
1525   if ('.' != line[0]) {
1526     char* propertyName = NS_xstrdup(line);
1527     if (propertyName) {
1528       char* space = PL_strchr(propertyName, ' ');
1529       if (space) {
1530         char* propertyValue = space + 1;
1531         *space = '\0';
1532         m_nntpServer->AddPropertyForGet(propertyName, propertyValue);
1533       }
1534       PR_Free(propertyName);
1535     }
1536   } else {
1537     /* all GET properties received, move on to LIST SUBSCRIPTIONS */
1538     m_nextState = SEND_LIST_SUBSCRIPTIONS;
1539     ClearFlag(NNTP_PAUSE_FOR_READ);
1540   }
1541 
1542   PR_FREEIF(line);
1543   return rv;
1544 }
1545 
SendListSubscriptions()1546 nsresult nsNNTPProtocol::SendListSubscriptions() {
1547   nsresult rv = NS_OK;
1548   /* TODO: is this needed for anything?
1549   #if 0
1550       bool searchable=false;
1551       rv = m_nntpServer->QueryExtension("LISTSUBSCR",&listsubscr);
1552       if (NS_SUCCEEDED(rv) && listsubscr)
1553   #else
1554     if (0)
1555   #endif
1556     {
1557       rv = SendData(NNTP_CMD_LIST_SUBSCRIPTIONS);
1558       m_nextState = NNTP_RESPONSE;
1559       m_nextStateAfterResponse = SEND_LIST_SUBSCRIPTIONS_RESPONSE;
1560       SetFlag(NNTP_PAUSE_FOR_READ);
1561     }
1562     else
1563   */
1564   {
1565     /* since LIST SUBSCRIPTIONS isn't supported, move on to real work */
1566     m_nextState = SEND_FIRST_NNTP_COMMAND;
1567     ClearFlag(NNTP_PAUSE_FOR_READ);
1568   }
1569 
1570   return rv;
1571 }
1572 
SendListSubscriptionsResponse(nsIInputStream * inputStream,uint32_t length)1573 nsresult nsNNTPProtocol::SendListSubscriptionsResponse(
1574     nsIInputStream* inputStream, uint32_t length) {
1575   uint32_t status = 0;
1576   nsresult rv;
1577 
1578   bool pauseForMoreData = false;
1579   char* line = m_lineStreamBuffer->ReadNextLine(inputStream, status,
1580                                                 pauseForMoreData, &rv);
1581 
1582   if (pauseForMoreData) {
1583     SetFlag(NNTP_PAUSE_FOR_READ);
1584     return NS_OK;
1585   }
1586   if (!line) return rv; /* no line yet */
1587 
1588   if ('.' != line[0]) {
1589     NS_ERROR("fix me");
1590 #if 0
1591     char *url = PR_smprintf ("%s//%s/%s", NEWS_SCHEME, m_hostName, line);
1592     if (url)
1593       MSG_AddSubscribedNewsgroup (cd->pane, url);
1594 #endif
1595   } else {
1596     /* all default subscriptions received */
1597     m_nextState = SEND_FIRST_NNTP_COMMAND;
1598     ClearFlag(NNTP_PAUSE_FOR_READ);
1599   }
1600 
1601   PR_FREEIF(line);
1602   return rv;
1603 }
1604 
1605 /* figure out what the first command is and send it
1606  *
1607  * returns the status from the NETWrite */
1608 
SendFirstNNTPCommand(nsIURI * url)1609 nsresult nsNNTPProtocol::SendFirstNNTPCommand(nsIURI* url) {
1610   char* command = 0;
1611 
1612   if (m_typeWanted == ARTICLE_WANTED) {
1613     if (m_key != nsMsgKey_None) {
1614       nsresult rv;
1615       nsCString newsgroupName;
1616       if (m_newsFolder) {
1617         rv = m_newsFolder->GetRawName(newsgroupName);
1618         NS_ENSURE_SUCCESS(rv, rv);
1619       }
1620       MOZ_LOG(NNTP, LogLevel::Info,
1621               ("(%p) current group = %s, desired group = %s", this,
1622                m_currentGroup.get(), newsgroupName.get()));
1623       // if the current group is the desired group, we can just issue the
1624       // ARTICLE command if not, we have to do a GROUP first
1625       if (newsgroupName.Equals(m_currentGroup))
1626         m_nextState = NNTP_SEND_ARTICLE_NUMBER;
1627       else
1628         m_nextState = NNTP_SEND_GROUP_FOR_ARTICLE;
1629 
1630       ClearFlag(NNTP_PAUSE_FOR_READ);
1631       return NS_OK;
1632     }
1633   }
1634 
1635   if (m_typeWanted == NEWS_POST) { /* posting to the news group */
1636     NS_MsgSACopy(&command, "POST");
1637   } else if (m_typeWanted == NEW_GROUPS) {
1638     uint32_t last_update;
1639     nsresult rv;
1640 
1641     if (!m_nntpServer) {
1642       NNTP_LOG_NOTE("m_nntpServer is null, panic!");
1643       return NS_ERROR_FAILURE;
1644     }
1645     rv = m_nntpServer->GetLastUpdatedTime(&last_update);
1646     NS_ENSURE_SUCCESS(rv, rv);
1647 
1648     if (!last_update) {
1649       NS_MsgSACopy(&command, "LIST");
1650     } else {
1651       char small_buf[64];
1652       PRExplodedTime expandedTime;
1653       PRTime t_usec = (PRTime)last_update * PR_USEC_PER_SEC;
1654       PR_ExplodeTime(t_usec, PR_LocalTimeParameters, &expandedTime);
1655       PR_FormatTimeUSEnglish(small_buf, sizeof(small_buf),
1656                              "NEWGROUPS %y%m%d %H%M%S", &expandedTime);
1657       NS_MsgSACopy(&command, small_buf);
1658     }
1659   } else if (m_typeWanted == LIST_WANTED) {
1660     nsresult rv;
1661 
1662     ClearFlag(NNTP_USE_FANCY_NEWSGROUP);
1663 
1664     NS_ASSERTION(m_nntpServer, "no m_nntpServer");
1665     if (!m_nntpServer) {
1666       NNTP_LOG_NOTE("m_nntpServer is null, panic!");
1667       return NS_ERROR_FAILURE;
1668     }
1669 
1670     bool xactive = false;
1671     rv = m_nntpServer->QueryExtension("XACTIVE", &xactive);
1672     if (NS_SUCCEEDED(rv) && xactive) {
1673       NS_MsgSACopy(&command, "LIST XACTIVE");
1674       SetFlag(NNTP_USE_FANCY_NEWSGROUP);
1675     } else {
1676       NS_MsgSACopy(&command, "LIST");
1677     }
1678   } else if (m_typeWanted == GROUP_WANTED) {
1679     nsresult rv = NS_ERROR_NULL_POINTER;
1680 
1681     NS_ASSERTION(m_newsFolder, "m_newsFolder is null, panic!");
1682     if (!m_newsFolder) return NS_ERROR_FAILURE;
1683 
1684     nsCString group_name;
1685     rv = m_newsFolder->GetRawName(group_name);
1686     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get newsgroup name");
1687     NS_ENSURE_SUCCESS(rv, rv);
1688 
1689     m_firstArticle = 0;
1690     m_lastArticle = 0;
1691 
1692     NS_MsgSACopy(&command, "GROUP ");
1693     NS_MsgSACat(&command, group_name.get());
1694   } else if (m_typeWanted == SEARCH_WANTED) {
1695     nsresult rv;
1696     MOZ_LOG(NNTP, LogLevel::Info, ("(%p) doing GROUP for XPAT", this));
1697     nsCString group_name;
1698 
1699     /* for XPAT, we have to GROUP into the group before searching */
1700     if (!m_newsFolder) {
1701       NNTP_LOG_NOTE("m_newsFolder is null, panic!");
1702       return NS_ERROR_FAILURE;
1703     }
1704     rv = m_newsFolder->GetRawName(group_name);
1705     NS_ENSURE_SUCCESS(rv, rv);
1706 
1707     NS_MsgSACopy(&command, "GROUP ");
1708     NS_MsgSACat(&command, group_name.get());
1709 
1710     // force a GROUP next time
1711     m_currentGroup.Truncate();
1712     m_nextState = NNTP_RESPONSE;
1713     m_nextStateAfterResponse = NNTP_XPAT_SEND;
1714   } else if (m_typeWanted == IDS_WANTED) {
1715     m_nextState = NNTP_LIST_GROUP;
1716     return NS_OK;
1717   } else /* article or cancel */
1718   {
1719     NS_ASSERTION(!m_messageID.IsEmpty(), "No message ID, bailing!");
1720     if (m_messageID.IsEmpty()) return NS_ERROR_FAILURE;
1721 
1722     if (m_typeWanted == CANCEL_WANTED)
1723       NS_MsgSACopy(&command, "HEAD ");
1724     else {
1725       NS_ASSERTION(m_typeWanted == ARTICLE_WANTED,
1726                    "not cancel, and not article");
1727       NS_MsgSACopy(&command, "ARTICLE ");
1728     }
1729 
1730     if (m_messageID[0] != '<') NS_MsgSACat(&command, "<");
1731 
1732     NS_MsgSACat(&command, m_messageID.get());
1733 
1734     if (PL_strchr(command + 8, '>') == 0) NS_MsgSACat(&command, ">");
1735   }
1736 
1737   NS_MsgSACat(&command, CRLF);
1738   nsresult rv = SendData(command);
1739   PR_Free(command);
1740 
1741   m_nextState = NNTP_RESPONSE;
1742   if (m_typeWanted != SEARCH_WANTED)
1743     m_nextStateAfterResponse = SEND_FIRST_NNTP_COMMAND_RESPONSE;
1744   SetFlag(NNTP_PAUSE_FOR_READ);
1745   return rv;
1746 } /* sent first command */
1747 
1748 /* interprets the server response from the first command sent
1749  *
1750  * returns negative if the server responds unexpectedly
1751  */
1752 
SendFirstNNTPCommandResponse()1753 nsresult nsNNTPProtocol::SendFirstNNTPCommandResponse() {
1754   int32_t major_opcode = MK_NNTP_RESPONSE_TYPE(m_responseCode);
1755 
1756   if ((major_opcode == MK_NNTP_RESPONSE_TYPE_CONT &&
1757        m_typeWanted == NEWS_POST) ||
1758       (major_opcode == MK_NNTP_RESPONSE_TYPE_OK && m_typeWanted != NEWS_POST)) {
1759     m_nextState = SETUP_NEWS_STREAM;
1760     SetFlag(NNTP_SOME_PROTOCOL_SUCCEEDED);
1761     return NS_OK;
1762   } else {
1763     nsresult rv = NS_OK;
1764 
1765     nsString group_name;
1766     NS_ASSERTION(m_newsFolder, "no newsFolder");
1767     if (m_newsFolder) rv = m_newsFolder->GetUnicodeName(group_name);
1768 
1769     if (m_responseCode == MK_NNTP_RESPONSE_GROUP_NO_GROUP &&
1770         m_typeWanted == GROUP_WANTED) {
1771       MOZ_LOG(NNTP, LogLevel::Info,
1772               ("(%p) group (%s) not found, so unset"
1773                " m_currentGroup",
1774                this, NS_ConvertUTF16toUTF8(group_name).get()));
1775       m_currentGroup.Truncate();
1776 
1777       m_nntpServer->GroupNotFound(m_msgWindow, group_name, true /* opening */);
1778     }
1779 
1780     /* if the server returned a 400 error then it is an expected
1781      * error.  the NEWS_ERROR state will not sever the connection
1782      */
1783     if (major_opcode == MK_NNTP_RESPONSE_TYPE_CANNOT)
1784       m_nextState = NEWS_ERROR;
1785     else
1786       m_nextState = NNTP_ERROR;
1787     // if we have no channel listener, then we're likely downloading
1788     // the message for offline use (or at least not displaying it)
1789     bool savingArticleOffline = (m_channelListener == nullptr);
1790 
1791     if (m_runningURL) FinishMemCacheEntry(false);  // cleanup mem cache entry
1792 
1793     if (NS_SUCCEEDED(rv) && !group_name.IsEmpty() && !savingArticleOffline) {
1794       nsCString uri("about:newserror?r="_ns);
1795       nsCString escapedResponse;
1796       MsgEscapeURL(nsDependentCString(m_responseText),
1797                    nsINetUtil::ESCAPE_URL_QUERY, escapedResponse);
1798       uri.Append(escapedResponse);
1799 
1800       if ((m_key != nsMsgKey_None) && m_newsFolder) {
1801         nsCString messageID;
1802         nsCString escapedMessageID;
1803         rv = m_newsFolder->GetMessageIdForKey(m_key, messageID);
1804         if (NS_SUCCEEDED(rv)) {
1805           uri.AppendLiteral("&m=");
1806           MsgEscapeURL(messageID, nsINetUtil::ESCAPE_URL_QUERY,
1807                        escapedMessageID);
1808           uri.Append(escapedMessageID);
1809           uri.AppendLiteral("&k=");
1810           uri.AppendInt(m_key);
1811         }
1812       }
1813 
1814       if (m_newsFolder) {
1815         nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
1816         if (NS_SUCCEEDED(rv) && folder) {
1817           nsCString folderURI;
1818           nsCString escapedFolderURI;
1819           rv = folder->GetURI(folderURI);
1820           if (NS_SUCCEEDED(rv)) {
1821             uri.AppendLiteral("&f=");
1822             MsgEscapeURL(folderURI, nsINetUtil::ESCAPE_URL_QUERY,
1823                          escapedFolderURI);
1824             uri.Append(escapedFolderURI);
1825           }
1826         }
1827       }
1828 
1829       if (!m_msgWindow) {
1830         nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl =
1831             do_QueryInterface(m_runningURL);
1832         if (mailnewsurl) {
1833           rv = mailnewsurl->GetMsgWindow(getter_AddRefs(m_msgWindow));
1834           NS_ENSURE_SUCCESS(rv, rv);
1835         }
1836       }
1837       if (!m_msgWindow) return NS_ERROR_FAILURE;
1838 
1839       // note, this will cause us to close the connection.
1840       // this will call nsDocShell::LoadURI(), which will
1841       // call nsDocShell::Stop(STOP_NETWORK), which will eventually
1842       // call nsNNTPProtocol::Cancel(), which will close the socket.
1843       // we need to fix this, since the connection is still valid.
1844       rv = m_msgWindow->DisplayURIInMessagePane(
1845           NS_ConvertASCIItoUTF16(uri), true,
1846           nsContentUtils::GetSystemPrincipal());
1847       NS_ENSURE_SUCCESS(rv, rv);
1848     }
1849     // let's take the opportunity of removing the hdr from the db so we don't
1850     // try to download it again.
1851     else if (savingArticleOffline) {
1852       if ((m_key != nsMsgKey_None) && (m_newsFolder)) {
1853         rv = m_newsFolder->RemoveMessage(m_key);
1854       }
1855     }
1856     return NS_ERROR_FAILURE;
1857   }
1858 }
1859 
SendGroupForArticle()1860 nsresult nsNNTPProtocol::SendGroupForArticle() {
1861   nsresult rv;
1862 
1863   nsCString groupname;
1864   rv = m_newsFolder->GetRawName(groupname);
1865   NS_ASSERTION(NS_SUCCEEDED(rv) && !groupname.IsEmpty(), "no group name");
1866 
1867   char outputBuffer[OUTPUT_BUFFER_SIZE];
1868 
1869   PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "GROUP %.512s" CRLF,
1870               groupname.get());
1871 
1872   rv = SendData(outputBuffer);
1873 
1874   m_nextState = NNTP_RESPONSE;
1875   m_nextStateAfterResponse = NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE;
1876   SetFlag(NNTP_PAUSE_FOR_READ);
1877   return rv;
1878 }
1879 
SetCurrentGroup()1880 nsresult nsNNTPProtocol::SetCurrentGroup() {
1881   nsCString groupname;
1882   NS_ASSERTION(m_newsFolder, "no news folder");
1883   if (!m_newsFolder) {
1884     m_currentGroup.Truncate();
1885     return NS_ERROR_UNEXPECTED;
1886   }
1887 
1888   mozilla::DebugOnly<nsresult> rv = m_newsFolder->GetRawName(groupname);
1889   NS_ASSERTION(NS_SUCCEEDED(rv) && !groupname.IsEmpty(), "no group name");
1890   MOZ_LOG(NNTP, LogLevel::Info,
1891           ("(%p) SetCurrentGroup to %s", this, groupname.get()));
1892   m_currentGroup = groupname;
1893   return NS_OK;
1894 }
1895 
SendGroupForArticleResponse()1896 nsresult nsNNTPProtocol::SendGroupForArticleResponse() {
1897   /* ignore the response code and continue
1898    */
1899   m_nextState = NNTP_SEND_ARTICLE_NUMBER;
1900 
1901   return SetCurrentGroup();
1902 }
1903 
SendArticleNumber()1904 nsresult nsNNTPProtocol::SendArticleNumber() {
1905   char outputBuffer[OUTPUT_BUFFER_SIZE];
1906   PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "ARTICLE %lu" CRLF, m_key);
1907 
1908   nsresult rv = SendData(outputBuffer);
1909 
1910   m_nextState = NNTP_RESPONSE;
1911   m_nextStateAfterResponse = SEND_FIRST_NNTP_COMMAND_RESPONSE;
1912   SetFlag(NNTP_PAUSE_FOR_READ);
1913 
1914   return rv;
1915 }
1916 
BeginArticle()1917 nsresult nsNNTPProtocol::BeginArticle() {
1918   if (m_typeWanted != ARTICLE_WANTED && m_typeWanted != CANCEL_WANTED)
1919     return NS_OK;
1920 
1921     /*  Set up the HTML stream
1922      */
1923 
1924 #ifdef NO_ARTICLE_CACHEING
1925   ce->format_out = CLEAR_CACHE_BIT(ce->format_out);
1926 #endif
1927 
1928   // if we have a channel listener,
1929   // create a pipe to pump the message into...the output will go to whoever
1930   // is consuming the message display
1931   //
1932   // the pipe must have an unlimited length since we are going to be filling
1933   // it on the main thread while reading it from the main thread.  iow, the
1934   // write must not block!! (see bug 190988)
1935   //
1936   if (m_channelListener) {
1937     nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
1938     nsresult rv = pipe->Init(false, false, 4096, PR_UINT32_MAX);
1939     NS_ENSURE_SUCCESS(rv, rv);
1940 
1941     // These always succeed because the pipe is initialized above.
1942     MOZ_ALWAYS_SUCCEEDS(
1943         pipe->GetInputStream(getter_AddRefs(mDisplayInputStream)));
1944     MOZ_ALWAYS_SUCCEEDS(
1945         pipe->GetOutputStream(getter_AddRefs(mDisplayOutputStream)));
1946 
1947     if (m_loadGroup) {
1948       m_loadGroup->AddRequest(this, nullptr);
1949     }
1950     // A bit sneaky, but we're pretending to be a newly-minted channel.
1951     // The corresponding OnStopRequest() call is in CleanupAfterRunningUrl().
1952     rv = m_channelListener->OnStartRequest(this);
1953     NS_ENSURE_SUCCESS(rv, rv);
1954   }
1955 
1956   m_nextState = NNTP_READ_ARTICLE;
1957 
1958   return NS_OK;
1959 }
1960 
1961 /**
1962  * Handler for NNTP_READ_ARTICLE state when being used as an nsIChannel
1963  * (e.g. for displaying an article in a nsDocShell). The nsIChannel
1964  * counterpart of ReadArticle().
1965  */
DisplayArticle(nsIInputStream * inputStream,uint32_t length)1966 nsresult nsNNTPProtocol::DisplayArticle(nsIInputStream* inputStream,
1967                                         uint32_t length) {
1968   uint32_t line_length = 0;
1969 
1970   bool pauseForMoreData = false;
1971   if (m_channelListener) {
1972     nsresult rv = NS_OK;
1973     char* line = m_lineStreamBuffer->ReadNextLine(inputStream, line_length,
1974                                                   pauseForMoreData, &rv, true);
1975 
1976     if (pauseForMoreData) {
1977       uint64_t inlength = 0;
1978       mDisplayInputStream->Available(&inlength);
1979       if (inlength > 0)  // broadcast our batched up ODA changes
1980         m_channelListener->OnDataAvailable(
1981             this, mDisplayInputStream, 0,
1982             std::min(inlength, uint64_t(PR_UINT32_MAX)));
1983       SetFlag(NNTP_PAUSE_FOR_READ);
1984       PR_Free(line);
1985       return rv;
1986     }
1987 
1988     if (!line) return NS_OK;
1989 
1990     // line only contains a single dot -> message end
1991     if (line_length == 1 + MSG_LINEBREAK_LEN && line[0] == '.') {
1992       if (m_newsFolder) m_newsFolder->NotifyDownloadedLine(line, m_key);
1993 
1994       m_nextState = NEWS_DONE;
1995 
1996       ClearFlag(NNTP_PAUSE_FOR_READ);
1997 
1998       uint64_t inlength = 0;
1999       mDisplayInputStream->Available(&inlength);
2000       if (inlength > 0)  // broadcast our batched up ODA changes
2001         m_channelListener->OnDataAvailable(
2002             this, mDisplayInputStream, 0,
2003             std::min(inlength, uint64_t(PR_UINT32_MAX)));
2004       PR_Free(line);
2005       return rv;
2006     } else  // we aren't finished with the message yet
2007     {
2008       uint32_t count = 0;
2009 
2010       // Skip the first of two '.' to reverse the dot-stuffing
2011       // see: https://tools.ietf.org/html/rfc3977#section-3.1.1
2012       const char* unescapedLine = line;
2013       if (line_length > 1 && line[0] == '.' && line[1] == '.') {
2014         unescapedLine++;
2015         line_length--;
2016       }
2017 
2018       if (m_newsFolder)
2019         m_newsFolder->NotifyDownloadedLine(unescapedLine, m_key);
2020 
2021       mDisplayOutputStream->Write(unescapedLine, line_length, &count);
2022     }
2023 
2024     PR_Free(line);
2025   }
2026 
2027   return NS_OK;
2028 }
2029 
2030 /**
2031  * Handler for NNTP_READ_ARTICLE state.
2032  * If we're running as an nsIChannel, defers handling to DisplayArticle()
2033  * instead.
2034  */
ReadArticle(nsIInputStream * inputStream,uint32_t length)2035 nsresult nsNNTPProtocol::ReadArticle(nsIInputStream* inputStream,
2036                                      uint32_t length) {
2037   uint32_t status = 0;
2038   nsresult rv;
2039   char* outputBuffer;
2040 
2041   bool pauseForMoreData = false;
2042 
2043   // if we have a channel listener, spool directly to it....
2044   // otherwise we must be doing something like save to disk or cancel
2045   // in which case we are doing the work.
2046   if (m_channelListener) return DisplayArticle(inputStream, length);
2047 
2048   char* line = m_lineStreamBuffer->ReadNextLine(inputStream, status,
2049                                                 pauseForMoreData, &rv, true);
2050   if (m_newsFolder && line) {
2051     const char* unescapedLine = line;
2052     // lines beginning with '.' are escaped by nntp server
2053     // or is it just '.' on a line by itself?
2054     if (line[0] == '.' && line[1] == '.') unescapedLine++;
2055     m_newsFolder->NotifyDownloadedLine(unescapedLine, m_key);
2056   }
2057 
2058   if (pauseForMoreData) {
2059     SetFlag(NNTP_PAUSE_FOR_READ);
2060     return NS_OK;
2061   }
2062   if (status > 1) {
2063     mBytesReceived += status;
2064     mBytesReceivedSinceLastStatusUpdate += status;
2065   }
2066 
2067   if (!line) return rv; /* no line yet or error */
2068 
2069   if (m_typeWanted == CANCEL_WANTED &&
2070       m_responseCode != MK_NNTP_RESPONSE_ARTICLE_HEAD) {
2071     /* HEAD command failed. */
2072     PR_FREEIF(line);
2073     return NS_ERROR_FAILURE;
2074   }
2075 
2076   if (line[0] == '.' && line[MSG_LINEBREAK_LEN + 1] == 0) {
2077     if (m_typeWanted == CANCEL_WANTED)
2078       m_nextState = NEWS_START_CANCEL;
2079     else
2080       m_nextState = NEWS_DONE;
2081 
2082     ClearFlag(NNTP_PAUSE_FOR_READ);
2083   } else {
2084     if (line[0] == '.')
2085       outputBuffer = line + 1;
2086     else
2087       outputBuffer = line;
2088 
2089     /* Don't send content-type to mime parser if we're doing a cancel
2090     because it confuses mime parser into not parsing.
2091     */
2092     if (m_typeWanted != CANCEL_WANTED ||
2093         strncmp(outputBuffer, "Content-Type:", 13)) {
2094       // if we are attempting to cancel, we want to snarf the headers and save
2095       // the aside, which is what ParseHeaderForCancel() does.
2096       if (m_typeWanted == CANCEL_WANTED) {
2097         ParseHeaderForCancel(outputBuffer);
2098       }
2099     }
2100   }
2101 
2102   PR_Free(line);
2103 
2104   return NS_OK;
2105 }
2106 
ParseHeaderForCancel(char * buf)2107 void nsNNTPProtocol::ParseHeaderForCancel(char* buf) {
2108   static int lastHeader = 0;
2109   nsAutoCString header(buf);
2110   if (header.First() == ' ' || header.First() == '\t') {
2111     header.StripWhitespace();
2112     // Add folded line to header if needed.
2113     switch (lastHeader) {
2114       case 1:
2115         m_cancelFromHdr += header;
2116         break;
2117       case 2:
2118         m_cancelID += header;
2119         break;
2120       case 3:
2121         m_cancelNewsgroups += header;
2122         break;
2123       case 4:
2124         m_cancelDistribution += header;
2125         break;
2126     }
2127     // Other folded lines are of no interest.
2128     return;
2129   }
2130 
2131   lastHeader = 0;
2132   int32_t colon = header.FindChar(':');
2133   if (!colon) return;
2134 
2135   nsCString value(Substring(header, colon + 1));
2136   value.StripWhitespace();
2137 
2138   switch (header.First()) {
2139     case 'F':
2140     case 'f':
2141       if (header.Find("From", /* ignoreCase = */ true) == 0) {
2142         m_cancelFromHdr = value;
2143         lastHeader = 1;
2144       }
2145       break;
2146     case 'M':
2147     case 'm':
2148       if (header.Find("Message-ID", /* ignoreCase = */ true) == 0) {
2149         m_cancelID = value;
2150         lastHeader = 2;
2151       }
2152       break;
2153     case 'N':
2154     case 'n':
2155       if (header.Find("Newsgroups", /* ignoreCase = */ true) == 0) {
2156         m_cancelNewsgroups = value;
2157         lastHeader = 3;
2158       }
2159       break;
2160     case 'D':
2161     case 'd':
2162       if (header.Find("Distributions", /* ignoreCase = */ true) == 0) {
2163         m_cancelDistribution = value;
2164         lastHeader = 4;
2165       }
2166       break;
2167   }
2168 
2169   return;
2170 }
2171 
BeginAuthorization()2172 nsresult nsNNTPProtocol::BeginAuthorization() {
2173   char* command = 0;
2174   nsresult rv = NS_OK;
2175 
2176   if (!m_newsFolder && m_nntpServer) {
2177     nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer);
2178     if (m_nntpServer) {
2179       nsCOMPtr<nsIMsgFolder> rootFolder;
2180       rv = server->GetRootFolder(getter_AddRefs(rootFolder));
2181       if (NS_SUCCEEDED(rv) && rootFolder) {
2182         m_newsFolder = do_QueryInterface(rootFolder);
2183       }
2184     }
2185   }
2186 
2187   NS_ASSERTION(m_newsFolder, "no m_newsFolder");
2188   if (!m_newsFolder) return NS_ERROR_FAILURE;
2189 
2190   // We want to get authentication credentials, but it is possible that the
2191   // master password prompt will end up being synchronous. In that case, check
2192   // to see if we already have the credentials stored.
2193   nsCString username, password;
2194   rv = m_newsFolder->GetGroupUsername(username);
2195   NS_ENSURE_SUCCESS(rv, rv);
2196   rv = m_newsFolder->GetGroupPassword(password);
2197   NS_ENSURE_SUCCESS(rv, rv);
2198 
2199   // If we don't have either a username or a password, queue an asynchronous
2200   // prompt.
2201   if (username.IsEmpty() || password.IsEmpty()) {
2202     nsCOMPtr<nsIMsgAsyncPrompter> asyncPrompter =
2203         do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID, &rv);
2204     NS_ENSURE_SUCCESS(rv, rv);
2205 
2206     // Get the key to coalesce auth prompts.
2207     bool singleSignon = false;
2208     m_nntpServer->GetSingleSignon(&singleSignon);
2209 
2210     nsCString queueKey;
2211     nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer);
2212     server->GetKey(queueKey);
2213     if (!singleSignon) {
2214       nsCString groupName;
2215       m_newsFolder->GetRawName(groupName);
2216       queueKey += groupName;
2217     }
2218 
2219     // If we were called back from HandleAuthenticationFailure, we must have
2220     // been handling the response of an authorization state. In that case,
2221     // let's hurry up on the auth request
2222     bool didAuthFail = m_nextStateAfterResponse == NNTP_AUTHORIZE_RESPONSE ||
2223                        m_nextStateAfterResponse == NNTP_PASSWORD_RESPONSE;
2224     rv = asyncPrompter->QueueAsyncAuthPrompt(queueKey, didAuthFail, this);
2225     NS_ENSURE_SUCCESS(rv, rv);
2226 
2227     m_nextState = NNTP_SUSPENDED;
2228     if (m_request) m_request->Suspend();
2229     return NS_OK;
2230   }
2231 
2232   NS_MsgSACopy(&command, "AUTHINFO user ");
2233   MOZ_LOG(NNTP, LogLevel::Info,
2234           ("(%p) use %s as the username", this, username.get()));
2235   NS_MsgSACat(&command, username.get());
2236   NS_MsgSACat(&command, CRLF);
2237 
2238   rv = SendData(command);
2239 
2240   PR_Free(command);
2241 
2242   m_nextState = NNTP_RESPONSE;
2243   m_nextStateAfterResponse = NNTP_AUTHORIZE_RESPONSE;
2244 
2245   SetFlag(NNTP_PAUSE_FOR_READ);
2246 
2247   return rv;
2248 }
2249 
AuthorizationResponse()2250 nsresult nsNNTPProtocol::AuthorizationResponse() {
2251   nsresult rv = NS_OK;
2252 
2253   if (MK_NNTP_RESPONSE_AUTHINFO_OK == m_responseCode ||
2254       MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_OK == m_responseCode) {
2255     /* successful login */
2256 #ifdef HAVE_NNTP_EXTENSIONS
2257     bool pushAuth;
2258     /* If we're here because the host demanded authentication before we
2259      * even sent a single command, then jump back to the beginning of everything
2260      */
2261     rv = m_nntpServer->GetPushAuth(&pushAuth);
2262 
2263     if (!TestFlag(NNTP_READER_PERFORMED)) m_nextState = NNTP_SEND_MODE_READER;
2264     /* If we're here because the host needs pushed authentication, then we
2265      * should jump back to SEND_LIST_EXTENSIONS
2266      */
2267     else if (NS_SUCCEEDED(rv) && pushAuth)
2268       m_nextState = SEND_LIST_EXTENSIONS;
2269     else
2270       /* Normal authentication */
2271       m_nextState = SEND_FIRST_NNTP_COMMAND;
2272 #else
2273     if (!TestFlag(NNTP_READER_PERFORMED))
2274       m_nextState = NNTP_SEND_MODE_READER;
2275     else
2276       m_nextState = SEND_FIRST_NNTP_COMMAND;
2277 #endif /* HAVE_NNTP_EXTENSIONS */
2278 
2279     return NS_OK;
2280   } else if (MK_NNTP_RESPONSE_AUTHINFO_CONT == m_responseCode) {
2281     char* command = 0;
2282 
2283     // Since we had to have called BeginAuthorization to get here, we've already
2284     // prompted for the authorization credentials. Just grab them without a
2285     // further prompt.
2286     nsCString password;
2287     rv = m_newsFolder->GetGroupPassword(password);
2288     if (NS_FAILED(rv) || password.IsEmpty()) return NS_ERROR_FAILURE;
2289 
2290     NS_MsgSACopy(&command, "AUTHINFO pass ");
2291     NS_MsgSACat(&command, password.get());
2292     NS_MsgSACat(&command, CRLF);
2293 
2294     rv = SendData(command, true);
2295 
2296     PR_FREEIF(command);
2297 
2298     m_nextState = NNTP_RESPONSE;
2299     m_nextStateAfterResponse = NNTP_PASSWORD_RESPONSE;
2300     SetFlag(NNTP_PAUSE_FOR_READ);
2301 
2302     return rv;
2303   } else {
2304     /* login failed */
2305     HandleAuthenticationFailure();
2306     return NS_OK;
2307   }
2308 
2309   NS_ERROR("should never get here");
2310   return NS_ERROR_FAILURE;
2311 }
2312 
PasswordResponse()2313 nsresult nsNNTPProtocol::PasswordResponse() {
2314   if (MK_NNTP_RESPONSE_AUTHINFO_OK == m_responseCode ||
2315       MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_OK == m_responseCode) {
2316     /* successful login */
2317 #ifdef HAVE_NNTP_EXTENSIONS
2318     bool pushAuth;
2319     /* If we're here because the host demanded authentication before we
2320      * even sent a single command, then jump back to the beginning of everything
2321      */
2322     nsresult rv = m_nntpServer->GetPushAuth(&pushAuth);
2323 
2324     if (!TestFlag(NNTP_READER_PERFORMED)) m_nextState = NNTP_SEND_MODE_READER;
2325     /* If we're here because the host needs pushed authentication, then we
2326      * should jump back to SEND_LIST_EXTENSIONS
2327      */
2328     else if (NS_SUCCEEDED(rv) && pushAuth)
2329       m_nextState = SEND_LIST_EXTENSIONS;
2330     else
2331       /* Normal authentication */
2332       m_nextState = SEND_FIRST_NNTP_COMMAND;
2333 #else
2334     if (!TestFlag(NNTP_READER_PERFORMED))
2335       m_nextState = NNTP_SEND_MODE_READER;
2336     else
2337       m_nextState = SEND_FIRST_NNTP_COMMAND;
2338 #endif /* HAVE_NNTP_EXTENSIONS */
2339     return NS_OK;
2340   } else {
2341     HandleAuthenticationFailure();
2342     return NS_OK;
2343   }
2344 
2345   NS_ERROR("should never get here");
2346   return NS_ERROR_FAILURE;
2347 }
2348 
OnPromptStartAsync(nsIMsgAsyncPromptCallback * aCallback)2349 NS_IMETHODIMP nsNNTPProtocol::OnPromptStartAsync(
2350     nsIMsgAsyncPromptCallback* aCallback) {
2351   bool result = false;
2352   OnPromptStart(&result);
2353   return aCallback->OnAuthResult(result);
2354 }
2355 
OnPromptStart(bool * authAvailable)2356 NS_IMETHODIMP nsNNTPProtocol::OnPromptStart(bool* authAvailable) {
2357   NS_ENSURE_ARG_POINTER(authAvailable);
2358   NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
2359 
2360   if (!m_newsFolder) {
2361     // If we don't have a news folder, we may have been closed already.
2362     NNTP_LOG_NOTE("Canceling queued authentication prompt");
2363     *authAvailable = false;
2364     return NS_OK;
2365   }
2366 
2367   bool didAuthFail = m_nextState == NNTP_AUTHORIZE_RESPONSE ||
2368                      m_nextState == NNTP_PASSWORD_RESPONSE;
2369   nsresult rv = m_newsFolder->GetAuthenticationCredentials(
2370       m_msgWindow, true, didAuthFail, authAvailable);
2371   NS_ENSURE_SUCCESS(rv, rv);
2372 
2373   // What we do depends on whether or not we have valid credentials
2374   return *authAvailable ? OnPromptAuthAvailable() : OnPromptCanceled();
2375 }
2376 
OnPromptAuthAvailable()2377 NS_IMETHODIMP nsNNTPProtocol::OnPromptAuthAvailable() {
2378   NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
2379 
2380   // We previously suspended the request; now resume it to read input
2381   if (m_request) m_request->Resume();
2382 
2383   // Now we have our password details accessible from the group, so just call
2384   // into the state machine to start the process going again.
2385   m_nextState = NNTP_BEGIN_AUTHORIZE;
2386   return ProcessProtocolState(nullptr, nullptr, 0, 0);
2387 }
2388 
OnPromptCanceled()2389 NS_IMETHODIMP nsNNTPProtocol::OnPromptCanceled() {
2390   NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
2391 
2392   // We previously suspended the request; now resume it to read input
2393   if (m_request) m_request->Resume();
2394 
2395   // Since the prompt was canceled, we can no longer continue the connection.
2396   // Thus, we need to go to the NNTP_ERROR state.
2397   m_nextState = NNTP_ERROR;
2398   return ProcessProtocolState(nullptr, nullptr, 0, 0);
2399 }
2400 
DisplayNewsgroups()2401 nsresult nsNNTPProtocol::DisplayNewsgroups() {
2402   m_nextState = NEWS_DONE;
2403   ClearFlag(NNTP_PAUSE_FOR_READ);
2404 
2405   MOZ_LOG(NNTP, LogLevel::Info, ("(%p) DisplayNewsgroups()", this));
2406 
2407   return NS_OK;
2408 }
2409 
BeginNewsgroups()2410 nsresult nsNNTPProtocol::BeginNewsgroups() {
2411   m_nextState = NNTP_NEWGROUPS;
2412   mBytesReceived = 0;
2413   mBytesReceivedSinceLastStatusUpdate = 0;
2414   m_startTime = PR_Now();
2415   return NS_OK;
2416 }
2417 
ProcessNewsgroups(nsIInputStream * inputStream,uint32_t length)2418 nsresult nsNNTPProtocol::ProcessNewsgroups(nsIInputStream* inputStream,
2419                                            uint32_t length) {
2420   char *line, *lineToFree, *s, *s1 = NULL, *s2 = NULL;
2421   uint32_t status = 0;
2422   nsresult rv = NS_OK;
2423 
2424   bool pauseForMoreData = false;
2425   line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status,
2426                                                        pauseForMoreData, &rv);
2427 
2428   if (pauseForMoreData) {
2429     SetFlag(NNTP_PAUSE_FOR_READ);
2430     return NS_OK;
2431   }
2432 
2433   if (!line) return rv; /* no line yet */
2434 
2435   /* End of list?
2436    */
2437   if (line[0] == '.' && line[1] == '\0') {
2438     ClearFlag(NNTP_PAUSE_FOR_READ);
2439     bool xactive = false;
2440     rv = m_nntpServer->QueryExtension("XACTIVE", &xactive);
2441     if (NS_SUCCEEDED(rv) && xactive) {
2442       nsAutoCString groupName;
2443       rv = m_nntpServer->GetFirstGroupNeedingExtraInfo(groupName);
2444       if (NS_SUCCEEDED(rv)) {
2445         rv = m_nntpServer->FindGroup(groupName, getter_AddRefs(m_newsFolder));
2446         NS_ASSERTION(NS_SUCCEEDED(rv), "FindGroup failed");
2447         m_nextState = NNTP_LIST_XACTIVE;
2448         MOZ_LOG(NNTP, LogLevel::Info,
2449                 ("(%p) listing xactive for %s", this, groupName.get()));
2450         PR_Free(lineToFree);
2451         return NS_OK;
2452       }
2453     }
2454     m_nextState = NEWS_DONE;
2455 
2456     PR_Free(lineToFree);
2457     if (status > 0)
2458       return NS_OK;
2459     else
2460       return rv;
2461   } else if (line[0] == '.' && line[1] == '.')
2462     /* The NNTP server quotes all lines beginning with "." by doubling it. */
2463     line++;
2464 
2465   /* almost correct
2466    */
2467   if (status > 1) {
2468     mBytesReceived += status;
2469     mBytesReceivedSinceLastStatusUpdate += status;
2470   }
2471 
2472   /* format is "rec.arts.movies.past-films 7302 7119 y"
2473    */
2474   s = PL_strchr(line, ' ');
2475   if (s) {
2476     *s = 0;
2477     s1 = s + 1;
2478     s = PL_strchr(s1, ' ');
2479     if (s) {
2480       *s = 0;
2481       s2 = s + 1;
2482       s = PL_strchr(s2, ' ');
2483       if (s) {
2484         *s = 0;
2485       }
2486     }
2487   }
2488 
2489   mBytesReceived += status;
2490   mBytesReceivedSinceLastStatusUpdate += status;
2491 
2492   NS_ASSERTION(m_nntpServer, "no nntp incoming server");
2493   if (m_nntpServer) {
2494     rv = m_nntpServer->AddNewsgroupToList(line);
2495     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to add to subscribe ds");
2496   }
2497 
2498   bool xactive = false;
2499   rv = m_nntpServer->QueryExtension("XACTIVE", &xactive);
2500   if (NS_SUCCEEDED(rv) && xactive) {
2501     nsAutoCString charset;
2502     nsAutoString lineUtf16;
2503     if (NS_SUCCEEDED(m_nntpServer->GetCharset(charset)) &&
2504         NS_SUCCEEDED(nsMsgI18NConvertToUnicode(
2505             charset, nsDependentCString(line), lineUtf16)))
2506       m_nntpServer->SetGroupNeedsExtraInfo(NS_ConvertUTF16toUTF8(lineUtf16),
2507                                            true);
2508     else
2509       m_nntpServer->SetGroupNeedsExtraInfo(nsDependentCString(line), true);
2510   }
2511 
2512   PR_Free(lineToFree);
2513   return rv;
2514 }
2515 
2516 /* Ahhh, this like print's out the headers and stuff
2517  *
2518  * always returns 0
2519  */
2520 
BeginReadNewsList()2521 nsresult nsNNTPProtocol::BeginReadNewsList() {
2522   m_readNewsListCount = 0;
2523   mNumGroupsListed = 0;
2524   m_nextState = NNTP_READ_LIST;
2525 
2526   mBytesReceived = 0;
2527   mBytesReceivedSinceLastStatusUpdate = 0;
2528   m_startTime = PR_Now();
2529 
2530   return NS_OK;
2531 }
2532 
2533 #define RATE_CONSTANT 976.5625 /* PR_USEC_PER_SEC / 1024 bytes */
2534 
ComputeRate(int32_t bytes,PRTime startTime,float * rate)2535 static void ComputeRate(int32_t bytes, PRTime startTime, float* rate) {
2536   // rate = (bytes / USECS since start) * RATE_CONSTANT
2537 
2538   // compute usecs since we started.
2539   int32_t delta = (int32_t)(PR_Now() - startTime);
2540 
2541   // compute rate
2542   if (delta > 0) {
2543     *rate = (float)((bytes * RATE_CONSTANT) / delta);
2544   } else {
2545     *rate = 0.0;
2546   }
2547 }
2548 
2549 /* display a list of all or part of the newsgroups list
2550  * from the news server
2551  */
ReadNewsList(nsIInputStream * inputStream,uint32_t length)2552 nsresult nsNNTPProtocol::ReadNewsList(nsIInputStream* inputStream,
2553                                       uint32_t length) {
2554   nsresult rv = NS_OK;
2555   int32_t i = 0;
2556   uint32_t status = 1;
2557 
2558   bool pauseForMoreData = false;
2559   char *line, *lineToFree;
2560   line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status,
2561                                                        pauseForMoreData, &rv);
2562 
2563   if (pauseForMoreData) {
2564     SetFlag(NNTP_PAUSE_FOR_READ);
2565     PR_Free(lineToFree);
2566     return NS_OK;
2567   }
2568 
2569   if (!line) return rv; /* no line yet */
2570 
2571   /* End of list? */
2572   if (line[0] == '.' && line[1] == '\0') {
2573     bool listpnames = false;
2574     NS_ASSERTION(m_nntpServer, "no nntp incoming server");
2575     if (m_nntpServer) {
2576       rv = m_nntpServer->QueryExtension("LISTPNAMES", &listpnames);
2577     }
2578     if (NS_SUCCEEDED(rv) && listpnames)
2579       m_nextState = NNTP_LIST_PRETTY_NAMES;
2580     else
2581       m_nextState = DISPLAY_NEWSGROUPS;
2582     ClearFlag(NNTP_PAUSE_FOR_READ);
2583     PR_Free(lineToFree);
2584     return NS_OK;
2585   } else if (line[0] == '.') {
2586     if ((line[1] == ' ') ||
2587         (line[1] == '.' && line[2] == '.' && line[3] == ' ')) {
2588       // some servers send "... 0000000001 0000000002 y"
2589       // and some servers send ". 0000000001 0000000002 y"
2590       // just skip that those lines
2591       // see bug #69231 and #123560
2592       PR_Free(lineToFree);
2593       return rv;
2594     }
2595     // The NNTP server quotes all lines beginning with "." by doubling it, so
2596     // unquote
2597     line++;
2598   }
2599 
2600   /* almost correct
2601    */
2602   if (status > 1) {
2603     mBytesReceived += status;
2604     mBytesReceivedSinceLastStatusUpdate += status;
2605 
2606     if ((mBytesReceivedSinceLastStatusUpdate > UPDATE_THRESHOLD) &&
2607         m_msgWindow) {
2608       mBytesReceivedSinceLastStatusUpdate = 0;
2609 
2610       nsCOMPtr<nsIMsgStatusFeedback> msgStatusFeedback;
2611 
2612       rv = m_msgWindow->GetStatusFeedback(getter_AddRefs(msgStatusFeedback));
2613       NS_ENSURE_SUCCESS(rv, rv);
2614 
2615       nsString statusString;
2616 
2617       nsCOMPtr<nsIStringBundleService> bundleService =
2618           mozilla::services::GetStringBundleService();
2619       NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
2620 
2621       nsCOMPtr<nsIStringBundle> bundle;
2622       rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
2623       NS_ENSURE_SUCCESS(rv, rv);
2624 
2625       nsAutoString bytesStr;
2626       bytesStr.AppendInt(mBytesReceived / 1024);
2627 
2628       // compute the rate, and then convert it have one
2629       // decimal precision.
2630       float rate = 0.0;
2631       ComputeRate(mBytesReceived, m_startTime, &rate);
2632       char rate_buf[RATE_STR_BUF_LEN];
2633       PR_snprintf(rate_buf, RATE_STR_BUF_LEN, "%.1f", rate);
2634 
2635       nsAutoString numGroupsStr;
2636       numGroupsStr.AppendInt(mNumGroupsListed);
2637 
2638       AutoTArray<nsString, 3> formatStrings = {numGroupsStr, bytesStr};
2639       CopyASCIItoUTF16(MakeStringSpan(rate_buf),
2640                        *formatStrings.AppendElement());
2641       rv = bundle->FormatStringFromName("bytesReceived", formatStrings,
2642                                         statusString);
2643 
2644       rv = msgStatusFeedback->ShowStatusString(statusString);
2645       if (NS_FAILED(rv)) {
2646         PR_Free(lineToFree);
2647         return rv;
2648       }
2649     }
2650   }
2651 
2652   /* find whitespace separator if it exits */
2653   for (i = 0; line[i] != '\0' && !NET_IS_SPACE(line[i]); i++)
2654     ; /* null body */
2655 
2656   line[i] = 0; /* terminate group name */
2657 
2658   /* store all the group names */
2659   NS_ASSERTION(m_nntpServer, "no nntp incoming server");
2660   if (m_nntpServer) {
2661     m_readNewsListCount++;
2662     mNumGroupsListed++;
2663     rv = m_nntpServer->AddNewsgroupToList(line);
2664     //    NS_ASSERTION(NS_SUCCEEDED(rv),"failed to add to subscribe ds");
2665     // since it's not fatal, don't let this error stop the LIST command.
2666     rv = NS_OK;
2667   } else
2668     rv = NS_ERROR_FAILURE;
2669 
2670   if (m_readNewsListCount == READ_NEWS_LIST_COUNT_MAX) {
2671     m_readNewsListCount = 0;
2672     if (mUpdateTimer) {
2673       mUpdateTimer->Cancel();
2674       mUpdateTimer = nullptr;
2675     }
2676     mUpdateTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
2677     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create timer");
2678     if (NS_FAILED(rv)) {
2679       PR_Free(lineToFree);
2680       return rv;
2681     }
2682 
2683     mInputStream = inputStream;
2684 
2685     const uint32_t kUpdateTimerDelay = READ_NEWS_LIST_TIMEOUT;
2686     rv = mUpdateTimer->InitWithCallback(static_cast<nsITimerCallback*>(this),
2687                                         kUpdateTimerDelay,
2688                                         nsITimer::TYPE_ONE_SHOT);
2689     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to init timer");
2690     if (NS_FAILED(rv)) {
2691       PR_Free(lineToFree);
2692       return rv;
2693     }
2694 
2695     m_nextState = NNTP_SUSPENDED;
2696 
2697     // suspend necko request until timeout
2698     // might not have a request if someone called CloseSocket()
2699     // see bug #195440
2700     if (m_request) m_request->Suspend();
2701   }
2702 
2703   PR_Free(lineToFree);
2704   return rv;
2705 }
2706 
2707 NS_IMETHODIMP
Notify(nsITimer * timer)2708 nsNNTPProtocol::Notify(nsITimer* timer) {
2709   NS_ASSERTION(timer == mUpdateTimer.get(), "Hey, this ain't my timer!");
2710   mUpdateTimer = nullptr;  // release my hold
2711   TimerCallback();
2712   return NS_OK;
2713 }
2714 
TimerCallback()2715 void nsNNTPProtocol::TimerCallback() {
2716   MOZ_LOG(NNTP, LogLevel::Info, ("nsNNTPProtocol::TimerCallback"));
2717   m_nextState = NNTP_READ_LIST;
2718 
2719   // process whatever is already in the buffer at least once.
2720   //
2721   // NOTE: while downloading, it would almost be enough to just
2722   // resume necko since it will call us again with data.  however,
2723   // if we are at the end of the data stream then we must call
2724   // ProcessProtocolState since necko will not call us again.
2725   //
2726   // NOTE: this function may Suspend necko.  Suspend is a reference
2727   // counted (i.e., two suspends requires two resumes before the
2728   // request will actually be resumed).
2729   //
2730   ProcessProtocolState(nullptr, mInputStream, 0, 0);
2731 
2732   // resume necko request
2733   // might not have a request if someone called CloseSocket()
2734   // see bug #195440
2735   if (m_request) m_request->Resume();
2736 
2737   return;
2738 }
2739 
HandleAuthenticationFailure()2740 void nsNNTPProtocol::HandleAuthenticationFailure() {
2741   nsCOMPtr<nsIMsgIncomingServer> server(do_QueryInterface(m_nntpServer));
2742   nsCString hostname;
2743   server->GetRealHostName(hostname);
2744   nsCString username;
2745   server->GetRealUsername(username);
2746   nsString accountname;
2747   server->GetPrettyName(accountname);
2748 
2749   int32_t choice = 1;
2750   MsgPromptLoginFailed(m_msgWindow, hostname, username, accountname, &choice);
2751 
2752   if (choice == 1)  // Cancel
2753   {
2754     // When the user requests to cancel the connection, we can't do anything
2755     // anymore.
2756     NNTP_LOG_NOTE("Password failed, user opted to cancel connection");
2757     m_nextState = NNTP_ERROR;
2758     return;
2759   }
2760 
2761   if (choice == 2)  // New password
2762   {
2763     NNTP_LOG_NOTE("Password failed, user opted to enter new password");
2764     NS_ASSERTION(m_newsFolder, "no newsFolder");
2765     m_newsFolder->ForgetAuthenticationCredentials();
2766   } else if (choice == 0)  // Retry
2767   {
2768     NNTP_LOG_NOTE("Password failed, user opted to retry");
2769   }
2770 
2771   // At this point, we've either forgotten the password or opted to retry. In
2772   // both cases, we need to try to auth with the password again, so return to
2773   // the authentication state.
2774   m_nextState = NNTP_BEGIN_AUTHORIZE;
2775 }
2776 
2777 ///////////////////////////////////////////////////////////////////////////////
2778 // XOVER, XHDR, and HEAD processing code
2779 // Used for filters
2780 // State machine explanation located in doxygen comments for nsNNTPProtocol
2781 ///////////////////////////////////////////////////////////////////////////////
2782 
BeginReadXover()2783 nsresult nsNNTPProtocol::BeginReadXover() {
2784   int32_t count; /* Response fields */
2785   nsresult rv = NS_OK;
2786 
2787   rv = SetCurrentGroup();
2788   NS_ENSURE_SUCCESS(rv, rv);
2789 
2790   /* Make sure we never close and automatically reopen the connection at this
2791   point; we'll confuse libmsg too much... */
2792 
2793   SetFlag(NNTP_SOME_PROTOCOL_SUCCEEDED);
2794 
2795   /* We have just issued a GROUP command and read the response.
2796   Now parse that response to help decide which articles to request
2797   xover data for.
2798    */
2799   PR_sscanf(m_responseText, "%d %d %d", &count, &m_firstPossibleArticle,
2800             &m_lastPossibleArticle);
2801 
2802   m_newsgroupList = do_CreateInstance(NS_NNTPNEWSGROUPLIST_CONTRACTID, &rv);
2803   NS_ENSURE_SUCCESS(rv, rv);
2804 
2805   rv = m_newsgroupList->Initialize(m_runningURL, m_newsFolder);
2806   NS_ENSURE_SUCCESS(rv, rv);
2807 
2808   rv = m_newsFolder->UpdateSummaryFromNNTPInfo(m_firstPossibleArticle,
2809                                                m_lastPossibleArticle, count);
2810   NS_ENSURE_SUCCESS(rv, rv);
2811 
2812   m_numArticlesLoaded = 0;
2813 
2814   // if the user sets max_articles to a bogus value, get them everything
2815   m_numArticlesWanted = m_maxArticles > 0 ? m_maxArticles : 1L << 30;
2816 
2817   m_nextState = NNTP_FIGURE_NEXT_CHUNK;
2818   ClearFlag(NNTP_PAUSE_FOR_READ);
2819   return NS_OK;
2820 }
2821 
FigureNextChunk()2822 nsresult nsNNTPProtocol::FigureNextChunk() {
2823   nsresult rv = NS_OK;
2824   int32_t status = 0;
2825 
2826   if (m_firstArticle > 0) {
2827     MOZ_LOG(NNTP, LogLevel::Info,
2828             ("(%p) add to known articles:  %d - %d", this, m_firstArticle,
2829              m_lastArticle));
2830 
2831     if (NS_SUCCEEDED(rv) && m_newsgroupList) {
2832       rv = m_newsgroupList->AddToKnownArticles(m_firstArticle, m_lastArticle);
2833     }
2834 
2835     NS_ENSURE_SUCCESS(rv, rv);
2836   }
2837 
2838   if (m_numArticlesLoaded >= m_numArticlesWanted) {
2839     m_nextState = NEWS_PROCESS_XOVER;
2840     ClearFlag(NNTP_PAUSE_FOR_READ);
2841     return NS_OK;
2842   }
2843 
2844   NS_ASSERTION(m_newsgroupList, "no newsgroupList");
2845   if (!m_newsgroupList) return NS_ERROR_FAILURE;
2846 
2847   bool getOldMessages = false;
2848   if (m_runningURL) {
2849     rv = m_runningURL->GetGetOldMessages(&getOldMessages);
2850     NS_ENSURE_SUCCESS(rv, rv);
2851   }
2852 
2853   rv = m_newsgroupList->SetGetOldMessages(getOldMessages);
2854   NS_ENSURE_SUCCESS(rv, rv);
2855 
2856   rv = m_newsgroupList->GetRangeOfArtsToDownload(
2857       m_msgWindow, m_firstPossibleArticle, m_lastPossibleArticle,
2858       m_numArticlesWanted - m_numArticlesLoaded, &(m_firstArticle),
2859       &(m_lastArticle), &status);
2860 
2861   NS_ENSURE_SUCCESS(rv, rv);
2862 
2863   if (m_firstArticle <= 0 || m_firstArticle > m_lastArticle) {
2864     /* Nothing more to get. */
2865     m_nextState = NEWS_PROCESS_XOVER;
2866     ClearFlag(NNTP_PAUSE_FOR_READ);
2867     return NS_OK;
2868   }
2869 
2870   MOZ_LOG(NNTP, LogLevel::Info,
2871           ("(%p) Chunk will be (%d-%d)", this, m_firstArticle, m_lastArticle));
2872 
2873   m_articleNumber = m_firstArticle;
2874 
2875   /* was MSG_InitXOVER() */
2876   if (m_newsgroupList) {
2877     rv = m_newsgroupList->InitXOVER(m_firstArticle, m_lastArticle);
2878   }
2879 
2880   NS_ENSURE_SUCCESS(rv, rv);
2881 
2882   ClearFlag(NNTP_PAUSE_FOR_READ);
2883   if (TestFlag(NNTP_NO_XOVER_SUPPORT))
2884     m_nextState = NNTP_READ_GROUP;
2885   else
2886     m_nextState = NNTP_XOVER_SEND;
2887 
2888   return NS_OK;
2889 }
2890 
XoverSend()2891 nsresult nsNNTPProtocol::XoverSend() {
2892   char outputBuffer[OUTPUT_BUFFER_SIZE];
2893 
2894   PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "XOVER %d-%d" CRLF,
2895               m_firstArticle, m_lastArticle);
2896 
2897   m_nextState = NNTP_RESPONSE;
2898   m_nextStateAfterResponse = NNTP_XOVER_RESPONSE;
2899   SetFlag(NNTP_PAUSE_FOR_READ);
2900 
2901   return SendData(outputBuffer);
2902 }
2903 
2904 /* see if the xover response is going to return us data
2905  * if the proper code isn't returned then assume xover
2906  * isn't supported and use
2907  * normal read_group
2908  */
2909 
ReadXoverResponse()2910 nsresult nsNNTPProtocol::ReadXoverResponse() {
2911 #ifdef TEST_NO_XOVER_SUPPORT
2912   m_responseCode =
2913       MK_NNTP_RESPONSE_CHECK_ERROR; /* pretend XOVER generated an error */
2914 #endif
2915 
2916   if (m_responseCode != MK_NNTP_RESPONSE_XOVER_OK) {
2917     /* If we didn't get back "224 data follows" from the XOVER request,
2918    then that must mean that this server doesn't support XOVER.  Or
2919    maybe the server's XOVER support is busted or something.  So,
2920    in that case, fall back to the very slow HEAD method.
2921 
2922    But, while debugging here at HQ, getting into this state means
2923    something went very wrong, since our servers do XOVER.  Thus
2924    the assert.
2925      */
2926     /*NS_ASSERTION (0,"something went very wrong");*/
2927     m_nextState = NNTP_READ_GROUP;
2928     SetFlag(NNTP_NO_XOVER_SUPPORT);
2929   } else {
2930     m_nextState = NNTP_XOVER;
2931   }
2932 
2933   return NS_OK; /* continue */
2934 }
2935 
2936 /* process the xover list as it comes from the server
2937  * and load it into the sort list.
2938  */
2939 
ReadXover(nsIInputStream * inputStream,uint32_t length)2940 nsresult nsNNTPProtocol::ReadXover(nsIInputStream* inputStream,
2941                                    uint32_t length) {
2942   char *line, *lineToFree;
2943   nsresult rv;
2944   uint32_t status = 1;
2945 
2946   bool pauseForMoreData = false;
2947   line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status,
2948                                                        pauseForMoreData, &rv);
2949 
2950   if (pauseForMoreData) {
2951     SetFlag(NNTP_PAUSE_FOR_READ);
2952     return NS_OK;
2953   }
2954 
2955   if (!line) return rv; /* no line yet or TCP error */
2956 
2957   if (line[0] == '.' && line[1] == '\0') {
2958     m_nextState = NNTP_XHDR_SEND;
2959     ClearFlag(NNTP_PAUSE_FOR_READ);
2960     PR_Free(lineToFree);
2961     return NS_OK;
2962   } else if (line[0] == '.' && line[1] == '.')
2963     /* The NNTP server quotes all lines beginning with "." by doubling it. */
2964     line++;
2965 
2966   /* almost correct
2967    */
2968   if (status > 1) {
2969     mBytesReceived += status;
2970     mBytesReceivedSinceLastStatusUpdate += status;
2971   }
2972 
2973   rv = m_newsgroupList->ProcessXOVERLINE(line, &status);
2974   NS_ASSERTION(NS_SUCCEEDED(rv), "failed to process the XOVERLINE");
2975 
2976   m_numArticlesLoaded++;
2977   PR_Free(lineToFree);
2978   return rv;
2979 }
2980 
2981 /* Finished processing all the XOVER data.
2982  */
2983 
ProcessXover()2984 nsresult nsNNTPProtocol::ProcessXover() {
2985   nsresult rv;
2986 
2987   /* xover_parse_state stored in MSG_Pane cd->pane */
2988   NS_ASSERTION(m_newsgroupList, "no newsgroupList");
2989   if (!m_newsgroupList) return NS_ERROR_FAILURE;
2990 
2991   // Some people may use the notifications in CallFilters to close the cached
2992   // connections, which will clear m_newsgroupList. So we keep a copy for
2993   // ourselves to ward off this threat.
2994   nsCOMPtr<nsINNTPNewsgroupList> list(m_newsgroupList);
2995   list->CallFilters();
2996   int32_t status = 0;
2997   rv = list->FinishXOVERLINE(0, &status);
2998   m_newsgroupList = nullptr;
2999   if (NS_SUCCEEDED(rv) && status < 0) return NS_ERROR_FAILURE;
3000 
3001   m_nextState = NEWS_DONE;
3002 
3003   return NS_OK;
3004 }
3005 
XhdrSend()3006 nsresult nsNNTPProtocol::XhdrSend() {
3007   nsCString header;
3008   m_newsgroupList->InitXHDR(header);
3009   if (header.IsEmpty()) {
3010     m_nextState = NNTP_FIGURE_NEXT_CHUNK;
3011     return NS_OK;
3012   }
3013 
3014   char outputBuffer[OUTPUT_BUFFER_SIZE];
3015   PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "XHDR %s %d-%d" CRLF,
3016               header.get(), m_firstArticle, m_lastArticle);
3017 
3018   m_nextState = NNTP_RESPONSE;
3019   m_nextStateAfterResponse = NNTP_XHDR_RESPONSE;
3020   SetFlag(NNTP_PAUSE_FOR_READ);
3021 
3022   return SendData(outputBuffer);
3023 }
3024 
XhdrResponse(nsIInputStream * inputStream)3025 nsresult nsNNTPProtocol::XhdrResponse(nsIInputStream* inputStream) {
3026   if (m_responseCode != MK_NNTP_RESPONSE_XHDR_OK) {
3027     m_nextState = NNTP_READ_GROUP;
3028     // The reasoning behind setting this flag and not an XHDR flag is that we
3029     // are going to have to use HEAD instead. At that point, using XOVER as
3030     // well is just wasting bandwidth.
3031     SetFlag(NNTP_NO_XOVER_SUPPORT);
3032     return NS_OK;
3033   }
3034 
3035   char *line, *lineToFree;
3036   nsresult rv;
3037   uint32_t status = 1;
3038 
3039   bool pauseForMoreData = false;
3040   line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status,
3041                                                        pauseForMoreData, &rv);
3042 
3043   if (pauseForMoreData) {
3044     SetFlag(NNTP_PAUSE_FOR_READ);
3045     return NS_OK;
3046   }
3047 
3048   if (!line) return rv; /* no line yet or TCP error */
3049 
3050   if (line[0] == '.' && line[1] == '\0') {
3051     m_nextState = NNTP_XHDR_SEND;
3052     ClearFlag(NNTP_PAUSE_FOR_READ);
3053     PR_Free(lineToFree);
3054     return NS_OK;
3055   }
3056 
3057   if (status > 1) {
3058     mBytesReceived += status;
3059     mBytesReceivedSinceLastStatusUpdate += status;
3060   }
3061 
3062   rv = m_newsgroupList->ProcessXHDRLine(nsDependentCString(line));
3063   NS_ASSERTION(NS_SUCCEEDED(rv), "failed to process the XHDRLINE");
3064 
3065   m_numArticlesLoaded++;
3066   PR_Free(lineToFree);
3067   return rv;
3068 }
3069 
ReadHeaders()3070 nsresult nsNNTPProtocol::ReadHeaders() {
3071   if (m_articleNumber > m_lastArticle) { /* end of groups */
3072 
3073     m_newsgroupList->InitHEAD(-1);
3074     m_nextState = NNTP_FIGURE_NEXT_CHUNK;
3075     ClearFlag(NNTP_PAUSE_FOR_READ);
3076     return NS_OK;
3077   } else {
3078     m_newsgroupList->InitHEAD(m_articleNumber);
3079 
3080     char outputBuffer[OUTPUT_BUFFER_SIZE];
3081     PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "HEAD %ld" CRLF,
3082                 m_articleNumber++);
3083     m_nextState = NNTP_RESPONSE;
3084     m_nextStateAfterResponse = NNTP_READ_GROUP_RESPONSE;
3085 
3086     SetFlag(NNTP_PAUSE_FOR_READ);
3087     return SendData(outputBuffer);
3088   }
3089 }
3090 
3091 /* See if the "HEAD" command was successful
3092  */
3093 
ReadNewsgroupResponse()3094 nsresult nsNNTPProtocol::ReadNewsgroupResponse() {
3095   if (m_responseCode ==
3096       MK_NNTP_RESPONSE_ARTICLE_HEAD) { /* Head follows - parse it:*/
3097     m_nextState = NNTP_READ_GROUP_BODY;
3098 
3099     return NS_OK;
3100   } else {
3101     m_newsgroupList->HEADFailed(m_articleNumber);
3102     m_nextState = NNTP_READ_GROUP;
3103     return NS_OK;
3104   }
3105 }
3106 
3107 /* read the body of the "HEAD" command
3108  */
ReadNewsgroupBody(nsIInputStream * inputStream,uint32_t length)3109 nsresult nsNNTPProtocol::ReadNewsgroupBody(nsIInputStream* inputStream,
3110                                            uint32_t length) {
3111   char *line, *lineToFree;
3112   nsresult rv;
3113   uint32_t status = 1;
3114 
3115   bool pauseForMoreData = false;
3116   line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status,
3117                                                        pauseForMoreData, &rv);
3118 
3119   if (pauseForMoreData) {
3120     SetFlag(NNTP_PAUSE_FOR_READ);
3121     return NS_OK;
3122   }
3123 
3124   /* if TCP error of if there is not a full line yet return
3125    */
3126   if (!line) return rv;
3127 
3128   MOZ_LOG(NNTP, LogLevel::Info,
3129           ("(%p) read_group_body: got line: %s|", this, line));
3130 
3131   /* End of body? */
3132   if (line[0] == '.' && line[1] == '\0') {
3133     m_nextState = NNTP_READ_GROUP;
3134     ClearFlag(NNTP_PAUSE_FOR_READ);
3135     return NS_OK;
3136   } else if (line[0] == '.' && line[1] == '.')
3137     /* The NNTP server quotes all lines beginning with "." by doubling it. */
3138     line++;
3139 
3140   nsCString safe_line(line);
3141   rv = m_newsgroupList->ProcessHEADLine(safe_line);
3142   PR_Free(lineToFree);
3143   return rv;
3144 }
3145 
GetNewsStringByID(int32_t stringID,char16_t ** aString)3146 nsresult nsNNTPProtocol::GetNewsStringByID(int32_t stringID,
3147                                            char16_t** aString) {
3148   nsresult rv;
3149   nsAutoString resultString(u"???"_ns);
3150 
3151   if (!m_stringBundle) {
3152     nsCOMPtr<nsIStringBundleService> bundleService =
3153         mozilla::services::GetStringBundleService();
3154     NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
3155 
3156     rv = bundleService->CreateBundle(NEWS_MSGS_URL,
3157                                      getter_AddRefs(m_stringBundle));
3158     NS_ENSURE_SUCCESS(rv, rv);
3159   }
3160 
3161   if (m_stringBundle) {
3162     nsAutoString str;
3163     rv = m_stringBundle->GetStringFromID(stringID, str);
3164 
3165     if (NS_FAILED(rv)) {
3166       resultString.AssignLiteral("[StringID");
3167       resultString.AppendInt(stringID);
3168       resultString.AppendLiteral("?]");
3169       *aString = ToNewUnicode(resultString);
3170     } else {
3171       *aString = ToNewUnicode(str);
3172     }
3173   } else {
3174     rv = NS_OK;
3175     *aString = ToNewUnicode(resultString);
3176   }
3177   return rv;
3178 }
3179 
GetNewsStringByName(const char * aName,char16_t ** aString)3180 nsresult nsNNTPProtocol::GetNewsStringByName(const char* aName,
3181                                              char16_t** aString) {
3182   nsresult rv;
3183   nsAutoString resultString(u"???"_ns);
3184   if (!m_stringBundle) {
3185     nsCOMPtr<nsIStringBundleService> bundleService =
3186         mozilla::services::GetStringBundleService();
3187     NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
3188 
3189     rv = bundleService->CreateBundle(NEWS_MSGS_URL,
3190                                      getter_AddRefs(m_stringBundle));
3191     NS_ENSURE_SUCCESS(rv, rv);
3192   }
3193 
3194   if (m_stringBundle) {
3195     nsAutoString str;
3196     rv = m_stringBundle->GetStringFromName(aName, str);
3197 
3198     if (NS_FAILED(rv)) {
3199       resultString.AssignLiteral("[StringName");
3200       resultString.Append(NS_ConvertASCIItoUTF16(aName));
3201       resultString.AppendLiteral("?]");
3202       *aString = ToNewUnicode(resultString);
3203     } else {
3204       *aString = ToNewUnicode(str);
3205     }
3206   } else {
3207     rv = NS_OK;
3208     *aString = ToNewUnicode(resultString);
3209   }
3210   return rv;
3211 }
3212 
3213 // sspitzer:  PostMessageInFile is derived from
3214 // nsSmtpProtocol::SendMessageInFile()
PostMessageInFile(nsIFile * postMessageFile)3215 nsresult nsNNTPProtocol::PostMessageInFile(nsIFile* postMessageFile) {
3216   nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningURL);
3217   if (url && postMessageFile) nsMsgProtocol::PostMessage(url, postMessageFile);
3218 
3219   SetFlag(NNTP_PAUSE_FOR_READ);
3220 
3221   // for now, we are always done at this point..we aren't making multiple
3222   // calls to post data...
3223 
3224   // always issue a '.' and CRLF when we are done...
3225   PL_strcpy(m_dataBuf, "." CRLF);
3226   SendData(m_dataBuf);
3227   m_nextState = NNTP_RESPONSE;
3228   m_nextStateAfterResponse = NNTP_SEND_POST_DATA_RESPONSE;
3229   return NS_OK;
3230 }
3231 
PostData()3232 nsresult nsNNTPProtocol::PostData() {
3233   /* returns 0 on done and negative on error
3234    * positive if it needs to continue.
3235    */
3236   NNTP_LOG_NOTE("nsNNTPProtocol::PostData()");
3237   nsresult rv = NS_OK;
3238 
3239   nsCOMPtr<nsINNTPNewsgroupPost> message;
3240   rv = m_runningURL->GetMessageToPost(getter_AddRefs(message));
3241   if (NS_SUCCEEDED(rv)) {
3242     nsCOMPtr<nsIFile> filePath;
3243     rv = message->GetPostMessageFile(getter_AddRefs(filePath));
3244     if (NS_SUCCEEDED(rv)) PostMessageInFile(filePath);
3245   }
3246 
3247   return NS_OK;
3248 }
3249 
3250 /* interpret the response code from the server
3251  * after the post is done
3252  */
PostDataResponse()3253 nsresult nsNNTPProtocol::PostDataResponse() {
3254   if (m_responseCode != MK_NNTP_RESPONSE_POST_OK) {
3255     AlertError(MK_NNTP_ERROR_MESSAGE, m_responseText);
3256     m_nextState = NEWS_ERROR;
3257     return NS_ERROR_FAILURE;
3258   }
3259   m_nextState = NEWS_POST_DONE;
3260   ClearFlag(NNTP_PAUSE_FOR_READ);
3261   return NS_OK;
3262 }
3263 
CheckForArticle()3264 nsresult nsNNTPProtocol::CheckForArticle() {
3265   m_nextState = NEWS_ERROR;
3266   if (m_responseCode >= 220 && m_responseCode <= 223) {
3267     /* Yes, this article is already there, we're all done. */
3268     return NS_OK;
3269   } else {
3270     /* The article isn't there, so the failure we had earlier wasn't due to
3271        a duplicate message-id.  Return the error from that previous
3272        posting attempt (which is already in ce->URL_s->error_msg). */
3273     return NS_ERROR_FAILURE;
3274   }
3275 }
3276 
StartCancel()3277 nsresult nsNNTPProtocol::StartCancel() {
3278   nsresult rv = SendData(NNTP_CMD_POST);
3279 
3280   m_nextState = NNTP_RESPONSE;
3281   m_nextStateAfterResponse = NEWS_DO_CANCEL;
3282   SetFlag(NNTP_PAUSE_FOR_READ);
3283   return rv;
3284 }
3285 
CheckIfAuthor(nsIMsgIdentity * aIdentity,const nsCString & aOldFrom,nsCString & aFrom)3286 void nsNNTPProtocol::CheckIfAuthor(nsIMsgIdentity* aIdentity,
3287                                    const nsCString& aOldFrom,
3288                                    nsCString& aFrom) {
3289   nsAutoCString from;
3290   nsresult rv = aIdentity->GetEmail(from);
3291   if (NS_FAILED(rv)) return;
3292   MOZ_LOG(NNTP, LogLevel::Info, ("from = %s", from.get()));
3293 
3294   nsCString us;
3295   nsCString them;
3296   ExtractEmail(EncodedHeader(from), us);
3297   ExtractEmail(EncodedHeader(aOldFrom), them);
3298 
3299   MOZ_LOG(NNTP, LogLevel::Info, ("us = %s, them = %s", us.get(), them.get()));
3300 
3301   if (us.Equals(them, nsCaseInsensitiveCStringComparator)) aFrom = from;
3302 }
3303 
DoCancel()3304 nsresult nsNNTPProtocol::DoCancel() {
3305   int32_t status = 0;
3306   bool failure = false;
3307   nsresult rv = NS_OK;
3308   bool requireConfirmationForCancel = true;
3309   bool showAlertAfterCancel = true;
3310 
3311   /* #### Should we do a more real check than this?  If the POST command
3312      didn't respond with "MK_NNTP_RESPONSE_POST_SEND_NOW Ok", then it's not
3313      ready for us to throw a message at it...   But the normal posting code
3314      doesn't do this check. Why?
3315    */
3316   NS_ASSERTION(m_responseCode == MK_NNTP_RESPONSE_POST_SEND_NOW,
3317                "code != POST_SEND_NOW");
3318 
3319   nsCOMPtr<nsIStringBundleService> bundleService =
3320       mozilla::services::GetStringBundleService();
3321   NS_ENSURE_TRUE(bundleService, NS_ERROR_OUT_OF_MEMORY);
3322 
3323   nsCOMPtr<nsIStringBundle> brandBundle;
3324   bundleService->CreateBundle("chrome://branding/locale/brand.properties",
3325                               getter_AddRefs(brandBundle));
3326   NS_ENSURE_TRUE(brandBundle, NS_ERROR_FAILURE);
3327 
3328   nsString brandFullName;
3329   rv = brandBundle->GetStringFromName("brandFullName", brandFullName);
3330   NS_ENSURE_SUCCESS(rv, rv);
3331   NS_ConvertUTF16toUTF8 appName(brandFullName);
3332 
3333   nsCString newsgroups(m_cancelNewsgroups);
3334   nsCString distribution(m_cancelDistribution);
3335   nsCString id(m_cancelID);
3336   nsCString oldFrom(m_cancelFromHdr);
3337 
3338   nsCOMPtr<nsIPrefBranch> prefBranch =
3339       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
3340   NS_ENSURE_SUCCESS(rv, rv);
3341 
3342   nsCOMPtr<nsIPrompt> dialog;
3343   if (m_runningURL) {
3344     nsCOMPtr<nsIMsgMailNewsUrl> msgUrl(do_QueryInterface(m_runningURL));
3345     rv = GetPromptDialogFromUrl(msgUrl, getter_AddRefs(dialog));
3346     NS_ENSURE_SUCCESS(rv, rv);
3347   }
3348 
3349   if (id.IsEmpty() || newsgroups.IsEmpty()) return NS_ERROR_FAILURE;
3350 
3351   m_cancelNewsgroups.Truncate();
3352   m_cancelDistribution.Truncate();
3353   m_cancelFromHdr.Truncate();
3354   m_cancelID.Truncate();
3355 
3356   nsString alertText;
3357   nsString confirmText;
3358   int32_t confirmCancelResult = 0;
3359 
3360   // A little early to declare, but the goto causes problems
3361   nsAutoCString otherHeaders;
3362 
3363   /* Make sure that this loser isn't cancelling someone else's posting.
3364      Yes, there are occasionally good reasons to do so.  Those people
3365      capable of making that decision (news admins) have other tools with
3366      which to cancel postings (like telnet.)
3367 
3368      Don't do this if server tells us it will validate user. DMB 3/19/97
3369    */
3370   bool cancelchk = false;
3371   rv = m_nntpServer->QueryExtension("CANCELCHK", &cancelchk);
3372   nsCString from;
3373   if (NS_SUCCEEDED(rv) && !cancelchk) {
3374     NNTP_LOG_NOTE("CANCELCHK not supported");
3375 
3376     // get the current identity from the news session....
3377     nsCOMPtr<nsIMsgAccountManager> accountManager =
3378         do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
3379     if (NS_SUCCEEDED(rv) && accountManager) {
3380       nsTArray<RefPtr<nsIMsgIdentity>> identities;
3381       rv = accountManager->GetAllIdentities(identities);
3382       NS_ENSURE_SUCCESS(rv, rv);
3383 
3384       for (auto identity : identities) {
3385         CheckIfAuthor(identity, oldFrom, from);
3386         if (!from.IsEmpty()) {
3387           break;
3388         }
3389       }
3390     }
3391 
3392     if (from.IsEmpty()) {
3393       GetNewsStringByName("cancelDisallowed", getter_Copies(alertText));
3394       rv = dialog->Alert(nullptr, alertText.get());
3395       // XXX:  todo, check rv?
3396 
3397       /* After the cancel is disallowed, Make the status update to be the same
3398          as though the cancel was allowed, otherwise, the newsgroup is not able
3399          to take further requests as reported here */
3400       status = MK_NNTP_CANCEL_DISALLOWED;
3401       m_nextState = NNTP_RESPONSE;
3402       m_nextStateAfterResponse = NNTP_SEND_POST_DATA_RESPONSE;
3403       SetFlag(NNTP_PAUSE_FOR_READ);
3404       failure = true;
3405       goto FAIL;
3406     } else {
3407       MOZ_LOG(NNTP, LogLevel::Info,
3408               ("(%p) CANCELCHK not supported, so post the cancel message as %s",
3409                this, from.get()));
3410     }
3411   } else
3412     NNTP_LOG_NOTE("CANCELCHK supported, don't do the us vs. them test");
3413 
3414   // QA needs to be able to disable this confirm dialog, for the automated
3415   // tests.  see bug #31057
3416   rv = prefBranch->GetBoolPref(PREF_NEWS_CANCEL_CONFIRM,
3417                                &requireConfirmationForCancel);
3418   if (NS_FAILED(rv) || requireConfirmationForCancel) {
3419     /* Last chance to cancel the cancel.*/
3420     GetNewsStringByName("cancelConfirm", getter_Copies(confirmText));
3421     bool dummyValue = false;
3422     rv = dialog->ConfirmEx(nullptr, confirmText.get(),
3423                            nsIPrompt::STD_YES_NO_BUTTONS, nullptr, nullptr,
3424                            nullptr, nullptr, &dummyValue, &confirmCancelResult);
3425     if (NS_FAILED(rv)) confirmCancelResult = 1;  // Default to No.
3426   } else
3427     confirmCancelResult = 0;  // Default to Yes.
3428 
3429   if (confirmCancelResult != 0) {
3430     // they cancelled the cancel
3431     status = MK_NNTP_NOT_CANCELLED;
3432     failure = true;
3433     goto FAIL;
3434   }
3435 
3436   otherHeaders.AppendLiteral("Control: cancel ");
3437   otherHeaders += id;
3438   otherHeaders.AppendLiteral(CRLF);
3439   if (!distribution.IsEmpty()) {
3440     otherHeaders.AppendLiteral("Distribution: ");
3441     otherHeaders += distribution;
3442     otherHeaders.AppendLiteral(CRLF);
3443   }
3444   otherHeaders.AppendLiteral("MIME-Version: 1.0");
3445   otherHeaders.AppendLiteral(CRLF);
3446   otherHeaders.AppendLiteral("Content-Type: text/plain");
3447   otherHeaders.AppendLiteral(CRLF);
3448 
3449   m_cancelStatus = 0;
3450 
3451   {
3452     /* NET_BlockingWrite() should go away soon? I think. */
3453     /* The following are what we really need to cancel a posted message */
3454     char* data;
3455     data = PR_smprintf(
3456         "From: %s" CRLF "Newsgroups: %s" CRLF "Subject: cancel %s" CRLF
3457         "References: %s" CRLF "%s" /* otherHeaders, already with CRLF */
3458         CRLF                       /* body separator */
3459         "This message was cancelled from within %s." CRLF /* body */
3460         "." CRLF, /* trailing message terminator "." */
3461         from.get(), newsgroups.get(), id.get(), id.get(), otherHeaders.get(),
3462         appName.get());
3463 
3464     rv = SendData(data);
3465     PR_Free(data);
3466     if (NS_FAILED(rv)) {
3467       nsAutoCString errorText;
3468       errorText.AppendInt(status);
3469       AlertError(MK_TCP_WRITE_ERROR, errorText.get());
3470       failure = true;
3471       goto FAIL;
3472     }
3473 
3474     SetFlag(NNTP_PAUSE_FOR_READ);
3475     m_nextState = NNTP_RESPONSE;
3476     m_nextStateAfterResponse = NNTP_SEND_POST_DATA_RESPONSE;
3477 
3478     // QA needs to be able to turn this alert off, for the automate tests.  see
3479     // bug #31057
3480     rv = prefBranch->GetBoolPref(PREF_NEWS_CANCEL_ALERT_ON_SUCCESS,
3481                                  &showAlertAfterCancel);
3482     if (NS_FAILED(rv) || showAlertAfterCancel) {
3483       GetNewsStringByName("messageCancelled", getter_Copies(alertText));
3484       rv = dialog->Alert(nullptr, alertText.get());
3485       // XXX:  todo, check rv?
3486     }
3487 
3488     if (!m_runningURL) return NS_ERROR_FAILURE;
3489 
3490     // delete the message from the db here.
3491     NS_ASSERTION(NS_SUCCEEDED(rv) && m_newsFolder && (m_key != nsMsgKey_None),
3492                  "need more to remove this message from the db");
3493     if ((m_key != nsMsgKey_None) && (m_newsFolder))
3494       rv = m_newsFolder->RemoveMessage(m_key);
3495   }
3496 
3497 FAIL:
3498   NS_ASSERTION(m_newsFolder, "no news folder");
3499   if (m_newsFolder)
3500     rv = (failure) ? m_newsFolder->CancelFailed()
3501                    : m_newsFolder->CancelComplete();
3502 
3503   return rv;
3504 }
3505 
XPATSend()3506 nsresult nsNNTPProtocol::XPATSend() {
3507   nsresult rv = NS_OK;
3508   int32_t slash = m_searchData.FindChar('/');
3509 
3510   if (slash >= 0) {
3511     /* extract the XPAT encoding for one query term */
3512     /* char *next_search = NULL; */
3513     char* command = NULL;
3514     char* unescapedCommand = NULL;
3515     char* endOfTerm = NULL;
3516     NS_MsgSACopy(&command, m_searchData.get() + slash + 1);
3517     endOfTerm = PL_strchr(command, '/');
3518     if (endOfTerm) *endOfTerm = '\0';
3519     NS_MsgSACat(&command, CRLF);
3520 
3521     unescapedCommand = MSG_UnEscapeSearchUrl(command);
3522 
3523     /* send one term off to the server */
3524     rv = SendData(unescapedCommand);
3525 
3526     m_nextState = NNTP_RESPONSE;
3527     m_nextStateAfterResponse = NNTP_XPAT_RESPONSE;
3528     SetFlag(NNTP_PAUSE_FOR_READ);
3529 
3530     PR_Free(command);
3531     PR_Free(unescapedCommand);
3532   } else {
3533     m_nextState = NEWS_DONE;
3534   }
3535   return rv;
3536 }
3537 
XPATResponse(nsIInputStream * inputStream,uint32_t length)3538 nsresult nsNNTPProtocol::XPATResponse(nsIInputStream* inputStream,
3539                                       uint32_t length) {
3540   uint32_t status = 1;
3541   nsresult rv;
3542 
3543   if (m_responseCode != MK_NNTP_RESPONSE_XPAT_OK) {
3544     AlertError(MK_NNTP_ERROR_MESSAGE, m_responseText);
3545     m_nextState = NNTP_ERROR;
3546     ClearFlag(NNTP_PAUSE_FOR_READ);
3547     return NS_ERROR_FAILURE;
3548   }
3549 
3550   bool pauseForMoreData = false;
3551   char* line = m_lineStreamBuffer->ReadNextLine(inputStream, status,
3552                                                 pauseForMoreData, &rv);
3553 
3554   NNTP_LOG_READ(line);
3555 
3556   if (pauseForMoreData) {
3557     SetFlag(NNTP_PAUSE_FOR_READ);
3558     return NS_OK;
3559   }
3560 
3561   if (line) {
3562     if (line[0] != '.') {
3563       long articleNumber;
3564       PR_sscanf(line, "%ld", &articleNumber);
3565       nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
3566       if (mailnewsurl) {
3567         nsCOMPtr<nsIMsgSearchSession> searchSession;
3568         nsCOMPtr<nsIMsgSearchAdapter> searchAdapter;
3569         mailnewsurl->GetSearchSession(getter_AddRefs(searchSession));
3570         if (searchSession) {
3571           searchSession->GetRunningAdapter(getter_AddRefs(searchAdapter));
3572           if (searchAdapter) searchAdapter->AddHit((uint32_t)articleNumber);
3573         }
3574       }
3575     } else {
3576       /* set up the next term for next time around */
3577       int32_t slash = m_searchData.FindChar('/');
3578 
3579       if (slash >= 0)
3580         m_searchData.Cut(0, slash + 1);
3581       else
3582         m_searchData.Truncate();
3583 
3584       m_nextState = NNTP_XPAT_SEND;
3585       ClearFlag(NNTP_PAUSE_FOR_READ);
3586       PR_FREEIF(line);
3587       return NS_OK;
3588     }
3589   }
3590   PR_FREEIF(line);
3591   return NS_OK;
3592 }
3593 
ListPrettyNames()3594 nsresult nsNNTPProtocol::ListPrettyNames() {
3595   nsCString group_name;
3596   char outputBuffer[OUTPUT_BUFFER_SIZE];
3597 
3598   m_newsFolder->GetRawName(group_name);
3599   PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "LIST PRETTYNAMES %.512s" CRLF,
3600               group_name.get());
3601 
3602   nsresult rv = SendData(outputBuffer);
3603   NNTP_LOG_NOTE(outputBuffer);
3604   m_nextState = NNTP_RESPONSE;
3605   m_nextStateAfterResponse = NNTP_LIST_PRETTY_NAMES_RESPONSE;
3606 
3607   return rv;
3608 }
3609 
ListPrettyNamesResponse(nsIInputStream * inputStream,uint32_t length)3610 nsresult nsNNTPProtocol::ListPrettyNamesResponse(nsIInputStream* inputStream,
3611                                                  uint32_t length) {
3612   uint32_t status = 0;
3613 
3614   if (m_responseCode != MK_NNTP_RESPONSE_LIST_OK) {
3615     m_nextState = DISPLAY_NEWSGROUPS;
3616     /*    m_nextState = NEWS_DONE; */
3617     ClearFlag(NNTP_PAUSE_FOR_READ);
3618     return NS_OK;
3619   }
3620 
3621   bool pauseForMoreData = false;
3622   char* line =
3623       m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
3624 
3625   NNTP_LOG_READ(line);
3626 
3627   if (pauseForMoreData) {
3628     SetFlag(NNTP_PAUSE_FOR_READ);
3629     return NS_OK;
3630   }
3631 
3632   if (line) {
3633     if (line[0] != '.') {
3634 #if 0  // SetPrettyName is not yet implemented. No reason to bother
3635       int i;
3636       /* find whitespace separator if it exits */
3637       for (i=0; line[i] != '\0' && !NET_IS_SPACE(line[i]); i++)
3638         ;  /* null body */
3639 
3640       char *prettyName;
3641       if(line[i] == '\0')
3642         prettyName = &line[i];
3643       else
3644         prettyName = &line[i+1];
3645 
3646       line[i] = 0; /* terminate group name */
3647       if (i > 0) {
3648         nsAutoCString charset;
3649         nsAutoString lineUtf16, prettyNameUtf16;
3650         if (NS_FAILED(m_nntpServer->GetCharset(charset) ||
3651             NS_FAILED(ConvertToUnicode(charset, line, lineUtf16)) ||
3652             NS_FAILED(ConvertToUnicode(charset, prettyName, prettyNameUtf16)))) {
3653           CopyUTF8toUTF16(line, lineUtf16);
3654           CopyUTF8toUTF16(prettyName, prettyNameUtf16);
3655         }
3656         m_nntpServer->SetPrettyNameForGroup(lineUtf16, prettyNameUtf16);
3657 
3658         MOZ_LOG(NNTP, LogLevel::Info,("(%p) adding pretty name %s", this,
3659                NS_ConvertUTF16toUTF8(prettyNameUtf16).get()));
3660       }
3661 #endif
3662     } else {
3663       m_nextState = DISPLAY_NEWSGROUPS; /* this assumes we were doing a list */
3664       /*      m_nextState = NEWS_DONE;   */ /* ### dmb - don't really know */
3665       ClearFlag(NNTP_PAUSE_FOR_READ);
3666       PR_FREEIF(line);
3667       return NS_OK;
3668     }
3669   }
3670   PR_FREEIF(line);
3671   return NS_OK;
3672 }
3673 
ListXActive()3674 nsresult nsNNTPProtocol::ListXActive() {
3675   nsCString group_name;
3676   nsresult rv;
3677   rv = m_newsFolder->GetRawName(group_name);
3678   NS_ENSURE_SUCCESS(rv, rv);
3679 
3680   char outputBuffer[OUTPUT_BUFFER_SIZE];
3681 
3682   PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "LIST XACTIVE %.512s" CRLF,
3683               group_name.get());
3684 
3685   rv = SendData(outputBuffer);
3686 
3687   m_nextState = NNTP_RESPONSE;
3688   m_nextStateAfterResponse = NNTP_LIST_XACTIVE_RESPONSE;
3689 
3690   return rv;
3691 }
3692 
ListXActiveResponse(nsIInputStream * inputStream,uint32_t length)3693 nsresult nsNNTPProtocol::ListXActiveResponse(nsIInputStream* inputStream,
3694                                              uint32_t length) {
3695   uint32_t status = 0;
3696   nsresult rv;
3697 
3698   NS_ASSERTION(m_responseCode == MK_NNTP_RESPONSE_LIST_OK, "code != LIST_OK");
3699   if (m_responseCode != MK_NNTP_RESPONSE_LIST_OK) {
3700     m_nextState = DISPLAY_NEWSGROUPS;
3701     /*    m_nextState = NEWS_DONE; */
3702     ClearFlag(NNTP_PAUSE_FOR_READ);
3703     return NS_OK;
3704   }
3705 
3706   bool pauseForMoreData = false;
3707   char* line =
3708       m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
3709 
3710   NNTP_LOG_READ(line);
3711 
3712   if (pauseForMoreData) {
3713     SetFlag(NNTP_PAUSE_FOR_READ);
3714     return NS_OK;
3715   }
3716 
3717   /* almost correct */
3718   if (status > 1) {
3719     mBytesReceived += status;
3720     mBytesReceivedSinceLastStatusUpdate += status;
3721   }
3722 
3723   if (line) {
3724     if (line[0] != '.') {
3725       char* s = line;
3726       /* format is "rec.arts.movies.past-films 7302 7119 csp"
3727        */
3728       while (*s && !NET_IS_SPACE(*s)) s++;
3729       if (*s) {
3730         char flags[32]; /* ought to be big enough */
3731         *s = 0;
3732         PR_sscanf(s + 1, "%d %d %31s", &m_firstPossibleArticle,
3733                   &m_lastPossibleArticle, flags);
3734 
3735         NS_ASSERTION(m_nntpServer, "no nntp incoming server");
3736         if (m_nntpServer) {
3737           rv = m_nntpServer->AddNewsgroupToList(line);
3738           NS_ASSERTION(NS_SUCCEEDED(rv), "failed to add to subscribe ds");
3739         }
3740 
3741         /* we're either going to list prettynames first, or list
3742         all prettynames every time, so we won't care so much
3743         if it gets interrupted. */
3744         MOZ_LOG(NNTP, LogLevel::Info,
3745                 ("(%p) got xactive for %s of %s", this, line, flags));
3746         /*  This isn't required, because the extra info is
3747         initialized to false for new groups. And it's
3748         an expensive call.
3749         */
3750         /* MSG_SetGroupNeedsExtraInfo(cd->host, line, false); */
3751       }
3752     } else {
3753       bool xactive = false;
3754       rv = m_nntpServer->QueryExtension("XACTIVE", &xactive);
3755       if (m_typeWanted == NEW_GROUPS && NS_SUCCEEDED(rv) && xactive) {
3756         nsCOMPtr<nsIMsgNewsFolder> old_newsFolder;
3757         old_newsFolder = m_newsFolder;
3758         nsCString groupName;
3759 
3760         rv = m_nntpServer->GetFirstGroupNeedingExtraInfo(groupName);
3761         NS_ENSURE_SUCCESS(rv, rv);
3762         rv = m_nntpServer->FindGroup(groupName, getter_AddRefs(m_newsFolder));
3763         NS_ENSURE_SUCCESS(rv, rv);
3764 
3765         // see if we got a different group
3766         if (old_newsFolder && m_newsFolder &&
3767             (old_newsFolder.get() != m_newsFolder.get()))
3768         /* make sure we're not stuck on the same group */
3769         {
3770           MOZ_LOG(NNTP, LogLevel::Info,
3771                   ("(%p) listing xactive for %s", this, groupName.get()));
3772           m_nextState = NNTP_LIST_XACTIVE;
3773           ClearFlag(NNTP_PAUSE_FOR_READ);
3774           PR_FREEIF(line);
3775           return NS_OK;
3776         } else {
3777           m_newsFolder = nullptr;
3778         }
3779       }
3780       bool listpname;
3781       rv = m_nntpServer->QueryExtension("LISTPNAME", &listpname);
3782       if (NS_SUCCEEDED(rv) && listpname)
3783         m_nextState = NNTP_LIST_PRETTY_NAMES;
3784       else
3785         m_nextState = DISPLAY_NEWSGROUPS; /* this assumes we were doing a list -
3786                                              who knows? */
3787       /*      m_nextState = NEWS_DONE;   */ /* ### dmb - don't really know */
3788       ClearFlag(NNTP_PAUSE_FOR_READ);
3789       PR_FREEIF(line);
3790       return NS_OK;
3791     }
3792   }
3793   PR_FREEIF(line);
3794   return NS_OK;
3795 }
3796 
SendListGroup()3797 nsresult nsNNTPProtocol::SendListGroup() {
3798   nsresult rv;
3799   char outputBuffer[OUTPUT_BUFFER_SIZE];
3800 
3801   NS_ASSERTION(m_newsFolder, "no newsFolder");
3802   if (!m_newsFolder) return NS_ERROR_FAILURE;
3803   nsCString newsgroupName;
3804 
3805   rv = m_newsFolder->GetRawName(newsgroupName);
3806   NS_ENSURE_SUCCESS(rv, rv);
3807 
3808   PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "listgroup %.512s" CRLF,
3809               newsgroupName.get());
3810 
3811   m_articleList = do_CreateInstance(NS_NNTPARTICLELIST_CONTRACTID, &rv);
3812   NS_ENSURE_SUCCESS(rv, rv);
3813 
3814   rv = m_articleList->Initialize(m_newsFolder);
3815   NS_ENSURE_SUCCESS(rv, rv);
3816 
3817   rv = SendData(outputBuffer);
3818 
3819   m_nextState = NNTP_RESPONSE;
3820   m_nextStateAfterResponse = NNTP_LIST_GROUP_RESPONSE;
3821   SetFlag(NNTP_PAUSE_FOR_READ);
3822 
3823   return rv;
3824 }
3825 
SendListGroupResponse(nsIInputStream * inputStream,uint32_t length)3826 nsresult nsNNTPProtocol::SendListGroupResponse(nsIInputStream* inputStream,
3827                                                uint32_t length) {
3828   uint32_t status = 0;
3829 
3830   NS_ASSERTION(m_responseCode == MK_NNTP_RESPONSE_GROUP_SELECTED,
3831                "code != GROUP_SELECTED");
3832   if (m_responseCode != MK_NNTP_RESPONSE_GROUP_SELECTED) {
3833     m_nextState = NEWS_DONE;
3834     ClearFlag(NNTP_PAUSE_FOR_READ);
3835     return NS_OK;
3836   }
3837 
3838   bool pauseForMoreData = false;
3839   char* line =
3840       m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
3841 
3842   if (pauseForMoreData) {
3843     SetFlag(NNTP_PAUSE_FOR_READ);
3844     return NS_OK;
3845   }
3846 
3847   if (line) {
3848     mozilla::DebugOnly<nsresult> rv;
3849     if (line[0] != '.') {
3850       nsMsgKey found_id = nsMsgKey_None;
3851       PR_sscanf(line, "%ld", &found_id);
3852       rv = m_articleList->AddArticleKey(found_id);
3853       NS_ASSERTION(NS_SUCCEEDED(rv), "add article key failed");
3854     } else {
3855       rv = m_articleList->FinishAddingArticleKeys();
3856       NS_ASSERTION(NS_SUCCEEDED(rv), "finish adding article key failed");
3857       m_articleList = nullptr;
3858       m_nextState = NEWS_DONE; /* ### dmb - don't really know */
3859       ClearFlag(NNTP_PAUSE_FOR_READ);
3860       PR_FREEIF(line);
3861       return NS_OK;
3862     }
3863   }
3864   PR_FREEIF(line);
3865   return NS_OK;
3866 }
3867 
Search()3868 nsresult nsNNTPProtocol::Search() {
3869   NS_ERROR("Search not implemented");
3870   return NS_ERROR_NOT_IMPLEMENTED;
3871 }
3872 
SearchResponse()3873 nsresult nsNNTPProtocol::SearchResponse() {
3874   if (MK_NNTP_RESPONSE_TYPE(m_responseCode) == MK_NNTP_RESPONSE_TYPE_OK)
3875     m_nextState = NNTP_SEARCH_RESULTS;
3876   else
3877     m_nextState = NEWS_DONE;
3878   ClearFlag(NNTP_PAUSE_FOR_READ);
3879   return NS_OK;
3880 }
3881 
SearchResults(nsIInputStream * inputStream,uint32_t length)3882 nsresult nsNNTPProtocol::SearchResults(nsIInputStream* inputStream,
3883                                        uint32_t length) {
3884   uint32_t status = 1;
3885   nsresult rv;
3886 
3887   bool pauseForMoreData = false;
3888   char* line = m_lineStreamBuffer->ReadNextLine(inputStream, status,
3889                                                 pauseForMoreData, &rv);
3890 
3891   if (pauseForMoreData) {
3892     SetFlag(NNTP_PAUSE_FOR_READ);
3893     return NS_OK;
3894   }
3895   if (!line) return rv; /* no line yet */
3896 
3897   if ('.' == line[0]) {
3898     /* all overview lines received */
3899     m_nextState = NEWS_DONE;
3900     ClearFlag(NNTP_PAUSE_FOR_READ);
3901   }
3902   PR_FREEIF(line);
3903   return rv;
3904 }
3905 
3906 /* Sets state for the transfer. This used to be known as net_setup_news_stream
3907  */
SetupForTransfer()3908 nsresult nsNNTPProtocol::SetupForTransfer() {
3909   if (m_typeWanted == NEWS_POST) {
3910     m_nextState = NNTP_SEND_POST_DATA;
3911   } else if (m_typeWanted == LIST_WANTED) {
3912     if (TestFlag(NNTP_USE_FANCY_NEWSGROUP))
3913       m_nextState = NNTP_LIST_XACTIVE_RESPONSE;
3914     else
3915       m_nextState = NNTP_READ_LIST_BEGIN;
3916   } else if (m_typeWanted == GROUP_WANTED)
3917     m_nextState = NNTP_XOVER_BEGIN;
3918   else if (m_typeWanted == NEW_GROUPS)
3919     m_nextState = NNTP_NEWGROUPS_BEGIN;
3920   else if (m_typeWanted == ARTICLE_WANTED || m_typeWanted == CANCEL_WANTED)
3921     m_nextState = NNTP_BEGIN_ARTICLE;
3922   else if (m_typeWanted == SEARCH_WANTED)
3923     m_nextState = NNTP_XPAT_SEND;
3924   else {
3925     NS_ERROR("unexpected");
3926     return NS_ERROR_FAILURE;
3927   }
3928 
3929   return NS_OK;
3930 }
3931 
3932 /////////////////////////////////////////////////////////////////////////////////////////////////////////
3933 // The following method is used for processing the news state machine.
3934 // It returns a negative number (mscott: we'll change this to be an enumerated
3935 // type which we'll coordinate with the netlib folks?) when we are done
3936 // processing.
3937 //////////////////////////////////////////////////////////////////////////////////////////////////////////
ProcessProtocolState(nsIURI * url,nsIInputStream * inputStream,uint64_t sourceOffset,uint32_t length)3938 nsresult nsNNTPProtocol::ProcessProtocolState(nsIURI* url,
3939                                               nsIInputStream* inputStream,
3940                                               uint64_t sourceOffset,
3941                                               uint32_t length) {
3942   nsresult status = NS_OK;
3943   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
3944   if (inputStream && (!mailnewsurl || !m_nntpServer)) {
3945     // In these cases, we are going to return since our data is effectively
3946     // invalid. However, nsInputStream would really rather that we at least read
3947     // some of our input data (even if not all of it). Therefore, we'll read a
3948     // little bit.
3949     char buffer[128];
3950     uint32_t readData = 0;
3951     inputStream->Read(buffer, 127, &readData);
3952     buffer[readData] = '\0';
3953     MOZ_LOG(NNTP, LogLevel::Debug, ("(%p) Ignoring data: %s", this, buffer));
3954   }
3955 
3956   if (!mailnewsurl) return NS_OK;  // probably no data available - it's OK.
3957 
3958   if (!m_nntpServer) {
3959     // Parsing must result in our m_nntpServer being set, so we should never
3960     // have a case where m_nntpServer being false is safe. Most likely, we have
3961     // already closed our socket and we are merely flushing out the socket
3962     // receive queue. Since the user told us to stop, don't process any more
3963     // input.
3964     return inputStream ? inputStream->Close() : NS_OK;
3965   }
3966 
3967   ClearFlag(NNTP_PAUSE_FOR_READ);
3968 
3969   while (!TestFlag(NNTP_PAUSE_FOR_READ)) {
3970     MOZ_LOG(NNTP, LogLevel::Info,
3971             ("(%p) Next state: %s", this, stateLabels[m_nextState]));
3972     // examine our current state and call an appropriate handler for that
3973     // state.....
3974     switch (m_nextState) {
3975       case NNTP_RESPONSE:
3976         if (inputStream == nullptr)
3977           SetFlag(NNTP_PAUSE_FOR_READ);
3978         else
3979           status = NewsResponse(inputStream, length);
3980         break;
3981 
3982         // mscott: I've removed the states involving connections on the
3983         // assumption that core netlib will now be managing that information.
3984 
3985       case NNTP_LOGIN_RESPONSE:
3986         if (inputStream == nullptr)
3987           SetFlag(NNTP_PAUSE_FOR_READ);
3988         else
3989           status = LoginResponse();
3990         break;
3991 
3992       case NNTP_SEND_MODE_READER:
3993         status = SendModeReader();
3994         break;
3995 
3996       case NNTP_SEND_MODE_READER_RESPONSE:
3997         if (inputStream == nullptr)
3998           SetFlag(NNTP_PAUSE_FOR_READ);
3999         else
4000           status = SendModeReaderResponse();
4001         break;
4002 
4003       case SEND_LIST_EXTENSIONS:
4004         status = SendListExtensions();
4005         break;
4006       case SEND_LIST_EXTENSIONS_RESPONSE:
4007         if (inputStream == nullptr)
4008           SetFlag(NNTP_PAUSE_FOR_READ);
4009         else
4010           status = SendListExtensionsResponse(inputStream, length);
4011         break;
4012       case SEND_LIST_SEARCHES:
4013         status = SendListSearches();
4014         break;
4015       case SEND_LIST_SEARCHES_RESPONSE:
4016         if (inputStream == nullptr)
4017           SetFlag(NNTP_PAUSE_FOR_READ);
4018         else
4019           status = SendListSearchesResponse(inputStream, length);
4020         break;
4021       case NNTP_LIST_SEARCH_HEADERS:
4022         status = SendListSearchHeaders();
4023         break;
4024       case NNTP_LIST_SEARCH_HEADERS_RESPONSE:
4025         if (inputStream == nullptr)
4026           SetFlag(NNTP_PAUSE_FOR_READ);
4027         else
4028           status = SendListSearchHeadersResponse(inputStream, length);
4029         break;
4030       case NNTP_GET_PROPERTIES:
4031         status = GetProperties();
4032         break;
4033       case NNTP_GET_PROPERTIES_RESPONSE:
4034         if (inputStream == nullptr)
4035           SetFlag(NNTP_PAUSE_FOR_READ);
4036         else
4037           status = GetPropertiesResponse(inputStream, length);
4038         break;
4039       case SEND_LIST_SUBSCRIPTIONS:
4040         status = SendListSubscriptions();
4041         break;
4042       case SEND_LIST_SUBSCRIPTIONS_RESPONSE:
4043         if (inputStream == nullptr)
4044           SetFlag(NNTP_PAUSE_FOR_READ);
4045         else
4046           status = SendListSubscriptionsResponse(inputStream, length);
4047         break;
4048 
4049       case SEND_FIRST_NNTP_COMMAND:
4050         status = SendFirstNNTPCommand(url);
4051         break;
4052       case SEND_FIRST_NNTP_COMMAND_RESPONSE:
4053         if (inputStream == nullptr)
4054           SetFlag(NNTP_PAUSE_FOR_READ);
4055         else
4056           status = SendFirstNNTPCommandResponse();
4057         break;
4058 
4059       case NNTP_SEND_GROUP_FOR_ARTICLE:
4060         status = SendGroupForArticle();
4061         break;
4062       case NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE:
4063         if (inputStream == nullptr)
4064           SetFlag(NNTP_PAUSE_FOR_READ);
4065         else
4066           status = SendGroupForArticleResponse();
4067         break;
4068       case NNTP_SEND_ARTICLE_NUMBER:
4069         status = SendArticleNumber();
4070         break;
4071 
4072       case SETUP_NEWS_STREAM:
4073         status = SetupForTransfer();
4074         break;
4075 
4076       case NNTP_BEGIN_AUTHORIZE:
4077         status = BeginAuthorization();
4078         break;
4079 
4080       case NNTP_AUTHORIZE_RESPONSE:
4081         if (inputStream == nullptr)
4082           SetFlag(NNTP_PAUSE_FOR_READ);
4083         else
4084           status = AuthorizationResponse();
4085         break;
4086 
4087       case NNTP_PASSWORD_RESPONSE:
4088         if (inputStream == nullptr)
4089           SetFlag(NNTP_PAUSE_FOR_READ);
4090         else
4091           status = PasswordResponse();
4092         break;
4093 
4094         // read list
4095       case NNTP_READ_LIST_BEGIN:
4096         status = BeginReadNewsList();
4097         break;
4098       case NNTP_READ_LIST:
4099         status = ReadNewsList(inputStream, length);
4100         break;
4101 
4102         // news group
4103       case DISPLAY_NEWSGROUPS:
4104         status = DisplayNewsgroups();
4105         break;
4106       case NNTP_NEWGROUPS_BEGIN:
4107         status = BeginNewsgroups();
4108         break;
4109       case NNTP_NEWGROUPS:
4110         status = ProcessNewsgroups(inputStream, length);
4111         break;
4112 
4113         // article specific
4114       case NNTP_BEGIN_ARTICLE:
4115         status = BeginArticle();
4116         break;
4117 
4118       case NNTP_READ_ARTICLE:
4119         status = ReadArticle(inputStream, length);
4120         break;
4121 
4122       case NNTP_XOVER_BEGIN:
4123         status = BeginReadXover();
4124         break;
4125 
4126       case NNTP_FIGURE_NEXT_CHUNK:
4127         status = FigureNextChunk();
4128         break;
4129 
4130       case NNTP_XOVER_SEND:
4131         status = XoverSend();
4132         break;
4133 
4134       case NNTP_XOVER:
4135         status = ReadXover(inputStream, length);
4136         break;
4137 
4138       case NNTP_XOVER_RESPONSE:
4139         if (inputStream == nullptr)
4140           SetFlag(NNTP_PAUSE_FOR_READ);
4141         else
4142           status = ReadXoverResponse();
4143         break;
4144 
4145       case NEWS_PROCESS_XOVER:
4146       case NEWS_PROCESS_BODIES:
4147         status = ProcessXover();
4148         break;
4149 
4150       case NNTP_XHDR_SEND:
4151         status = XhdrSend();
4152         break;
4153 
4154       case NNTP_XHDR_RESPONSE:
4155         status = XhdrResponse(inputStream);
4156         break;
4157 
4158       case NNTP_READ_GROUP:
4159         status = ReadHeaders();
4160         break;
4161 
4162       case NNTP_READ_GROUP_RESPONSE:
4163         if (inputStream == nullptr)
4164           SetFlag(NNTP_PAUSE_FOR_READ);
4165         else
4166           status = ReadNewsgroupResponse();
4167         break;
4168 
4169       case NNTP_READ_GROUP_BODY:
4170         status = ReadNewsgroupBody(inputStream, length);
4171         break;
4172 
4173       case NNTP_SEND_POST_DATA:
4174         status = PostData();
4175         break;
4176       case NNTP_SEND_POST_DATA_RESPONSE:
4177         if (inputStream == nullptr)
4178           SetFlag(NNTP_PAUSE_FOR_READ);
4179         else
4180           status = PostDataResponse();
4181         break;
4182 
4183       case NNTP_CHECK_FOR_MESSAGE:
4184         status = CheckForArticle();
4185         break;
4186 
4187         // cancel
4188       case NEWS_START_CANCEL:
4189         status = StartCancel();
4190         break;
4191 
4192       case NEWS_DO_CANCEL:
4193         status = DoCancel();
4194         break;
4195 
4196         // XPAT
4197       case NNTP_XPAT_SEND:
4198         status = XPATSend();
4199         break;
4200       case NNTP_XPAT_RESPONSE:
4201         if (inputStream == nullptr)
4202           SetFlag(NNTP_PAUSE_FOR_READ);
4203         else
4204           status = XPATResponse(inputStream, length);
4205         break;
4206 
4207         // search
4208       case NNTP_SEARCH:
4209         status = Search();
4210         break;
4211       case NNTP_SEARCH_RESPONSE:
4212         if (inputStream == nullptr)
4213           SetFlag(NNTP_PAUSE_FOR_READ);
4214         else
4215           status = SearchResponse();
4216         break;
4217       case NNTP_SEARCH_RESULTS:
4218         status = SearchResults(inputStream, length);
4219         break;
4220 
4221       case NNTP_LIST_PRETTY_NAMES:
4222         status = ListPrettyNames();
4223         break;
4224       case NNTP_LIST_PRETTY_NAMES_RESPONSE:
4225         if (inputStream == nullptr)
4226           SetFlag(NNTP_PAUSE_FOR_READ);
4227         else
4228           status = ListPrettyNamesResponse(inputStream, length);
4229         break;
4230       case NNTP_LIST_XACTIVE:
4231         status = ListXActive();
4232         break;
4233       case NNTP_LIST_XACTIVE_RESPONSE:
4234         if (inputStream == nullptr)
4235           SetFlag(NNTP_PAUSE_FOR_READ);
4236         else
4237           status = ListXActiveResponse(inputStream, length);
4238         break;
4239       case NNTP_LIST_GROUP:
4240         status = SendListGroup();
4241         break;
4242       case NNTP_LIST_GROUP_RESPONSE:
4243         if (inputStream == nullptr)
4244           SetFlag(NNTP_PAUSE_FOR_READ);
4245         else
4246           status = SendListGroupResponse(inputStream, length);
4247         break;
4248       case NEWS_DONE:
4249         m_nextState = NEWS_FREE;
4250         break;
4251       case NEWS_POST_DONE:
4252         NNTP_LOG_NOTE("NEWS_POST_DONE");
4253         mailnewsurl->SetUrlState(false, NS_OK);
4254         m_nextState = NEWS_FREE;
4255         break;
4256       case NEWS_ERROR:
4257         NNTP_LOG_NOTE("NEWS_ERROR");
4258         if (m_responseCode == MK_NNTP_RESPONSE_ARTICLE_NOTFOUND ||
4259             m_responseCode == MK_NNTP_RESPONSE_ARTICLE_NONEXIST)
4260           mailnewsurl->SetUrlState(false, NS_MSG_NEWS_ARTICLE_NOT_FOUND);
4261         else
4262           mailnewsurl->SetUrlState(false, NS_ERROR_FAILURE);
4263         m_nextState = NEWS_FREE;
4264         break;
4265       case NNTP_ERROR:
4266         // XXX do we really want to remove the connection from
4267         // the cache on error?
4268         /* check if this connection came from the cache or if it was
4269          * a new connection.  If it was not new lets start it over
4270          * again.  But only if we didn't have any successful protocol
4271          * dialog at all.
4272          */
4273         FinishMemCacheEntry(false);  // cleanup mem cache entry
4274         if (m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NOTFOUND &&
4275             m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NONEXIST)
4276           return CloseConnection();
4277         [[fallthrough]];
4278       case NEWS_FREE:
4279         // Remember when we last used this connection
4280         m_lastActiveTimeStamp = PR_Now();
4281         CleanupAfterRunningUrl();
4282         [[fallthrough]];
4283       case NNTP_SUSPENDED:
4284         return NS_OK;
4285         break;
4286       default:
4287         /* big error */
4288         return NS_ERROR_FAILURE;
4289 
4290     }  // end switch
4291 
4292     if (NS_FAILED(status) && m_nextState != NEWS_ERROR &&
4293         m_nextState != NNTP_ERROR && m_nextState != NEWS_FREE) {
4294       m_nextState = NNTP_ERROR;
4295       ClearFlag(NNTP_PAUSE_FOR_READ);
4296     }
4297 
4298   } /* end big while */
4299 
4300   return NS_OK; /* keep going */
4301 }
4302 
CloseConnection()4303 NS_IMETHODIMP nsNNTPProtocol::CloseConnection() {
4304   MOZ_LOG(NNTP, LogLevel::Info, ("(%p) ClosingConnection", this));
4305   SendData(NNTP_CMD_QUIT);  // this will cause OnStopRequest get called, which
4306                             // will call CloseSocket()
4307   // break some cycles
4308   CleanupNewsgroupList();
4309 
4310   if (m_nntpServer) {
4311     m_nntpServer->RemoveConnection(this);
4312     m_nntpServer = nullptr;
4313   }
4314   CloseSocket();
4315   m_newsFolder = nullptr;
4316 
4317   if (m_articleList) {
4318     m_articleList->FinishAddingArticleKeys();
4319     m_articleList = nullptr;
4320   }
4321 
4322   m_key = nsMsgKey_None;
4323   return NS_OK;
4324 }
4325 
CleanupNewsgroupList()4326 nsresult nsNNTPProtocol::CleanupNewsgroupList() {
4327   nsresult rv;
4328   if (!m_newsgroupList) return NS_OK;
4329   int32_t status = 0;
4330   rv = m_newsgroupList->FinishXOVERLINE(0, &status);
4331   m_newsgroupList = nullptr;
4332   NS_ASSERTION(NS_SUCCEEDED(rv), "FinishXOVERLINE failed");
4333   return rv;
4334 }
4335 
CleanupAfterRunningUrl()4336 nsresult nsNNTPProtocol::CleanupAfterRunningUrl() {
4337   /* do we need to know if we're parsing xover to call finish xover?  */
4338   /* yes, I think we do! Why did I think we should??? */
4339   /* If we've gotten to NEWS_FREE and there is still XOVER
4340   data, there was an error or we were interrupted or
4341   something.  So, tell libmsg there was an abnormal
4342   exit so that it can free its data. */
4343 
4344   MOZ_LOG(NNTP, LogLevel::Info, ("(%p) CleanupAfterRunningUrl()", this));
4345 
4346   // send StopRequest notification after we've cleaned up the protocol
4347   // because it can synchronously causes a new url to get run in the
4348   // protocol - truly evil, but we're stuck at the moment.
4349   if (m_channelListener) (void)m_channelListener->OnStopRequest(this, NS_OK);
4350 
4351   if (m_loadGroup)
4352     (void)m_loadGroup->RemoveRequest(static_cast<nsIRequest*>(this), nullptr,
4353                                      NS_OK);
4354   CleanupNewsgroupList();
4355 
4356   // clear out mem cache entry so we're not holding onto it.
4357   if (m_runningURL) {
4358     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
4359     if (mailnewsurl) {
4360       mailnewsurl->SetUrlState(false, NS_OK);
4361       mailnewsurl->SetMemCacheEntry(nullptr);
4362     }
4363   }
4364 
4365   Cleanup();
4366 
4367   mDisplayInputStream = nullptr;
4368   mDisplayOutputStream = nullptr;
4369   mProgressEventSink = nullptr;
4370   SetOwner(nullptr);
4371 
4372   m_isChannel = false;
4373   m_channelListener = nullptr;
4374   m_loadGroup = nullptr;
4375   mCallbacks = nullptr;
4376 
4377   // disable timeout before caching.
4378   nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
4379   if (strans)
4380     strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, PR_UINT32_MAX);
4381 
4382   // don't mark ourselves as not busy until we are done cleaning up the
4383   // connection. it should be the last thing we do.
4384   SetIsBusy(false);
4385 
4386   return NS_OK;
4387 }
4388 
CloseSocket()4389 nsresult nsNNTPProtocol::CloseSocket() {
4390   MOZ_LOG(NNTP, LogLevel::Info, ("(%p) ClosingSocket()", this));
4391 
4392   if (m_nntpServer) {
4393     m_nntpServer->RemoveConnection(this);
4394     m_nntpServer = nullptr;
4395   }
4396 
4397   CleanupAfterRunningUrl();  // is this needed?
4398   return nsMsgProtocol::CloseSocket();
4399 }
4400 
SetProgressBarPercent(uint32_t aProgress,uint32_t aProgressMax)4401 void nsNNTPProtocol::SetProgressBarPercent(uint32_t aProgress,
4402                                            uint32_t aProgressMax) {
4403   // XXX 64-bit
4404   if (mProgressEventSink)
4405     mProgressEventSink->OnProgress(this, uint64_t(aProgress),
4406                                    uint64_t(aProgressMax));
4407 }
4408 
SetProgressStatus(const char16_t * aMessage)4409 nsresult nsNNTPProtocol::SetProgressStatus(const char16_t* aMessage) {
4410   nsresult rv = NS_OK;
4411   if (mProgressEventSink)
4412     rv = mProgressEventSink->OnStatus(this, NS_OK, aMessage);
4413   return rv;
4414 }
4415 
GetContentType(nsACString & aContentType)4416 NS_IMETHODIMP nsNNTPProtocol::GetContentType(nsACString& aContentType) {
4417   // if we've been set with a content type, then return it....
4418   // this happens when we go through libmime now as it sets our new content type
4419   if (!mContentType.IsEmpty()) {
4420     aContentType = mContentType;
4421     return NS_OK;
4422   }
4423 
4424   // otherwise do what we did before...
4425 
4426   if (m_typeWanted == GROUP_WANTED)
4427     aContentType.AssignLiteral("x-application-newsgroup");
4428   else if (m_typeWanted == IDS_WANTED)
4429     aContentType.AssignLiteral("x-application-newsgroup-listids");
4430   else
4431     aContentType.AssignLiteral("message/rfc822");
4432   return NS_OK;
4433 }
4434 
AlertError(int32_t errorCode,const char * text)4435 nsresult nsNNTPProtocol::AlertError(int32_t errorCode, const char* text) {
4436   nsresult rv = NS_OK;
4437 
4438   // get the prompt from the running url....
4439   if (m_runningURL) {
4440     nsCOMPtr<nsIMsgMailNewsUrl> msgUrl(do_QueryInterface(m_runningURL));
4441     nsCOMPtr<nsIPrompt> dialog;
4442     rv = GetPromptDialogFromUrl(msgUrl, getter_AddRefs(dialog));
4443     NS_ENSURE_SUCCESS(rv, rv);
4444 
4445     nsString alertText;
4446     rv = GetNewsStringByID(MK_NNTP_ERROR_MESSAGE, getter_Copies(alertText));
4447     NS_ENSURE_SUCCESS(rv, rv);
4448     if (text) {
4449       alertText.Append(' ');
4450       alertText.Append(NS_ConvertASCIItoUTF16(text));
4451     }
4452     rv = dialog->Alert(nullptr, alertText.get());
4453     NS_ENSURE_SUCCESS(rv, rv);
4454   }
4455   return rv;
4456 }
4457 
GetCurrentFolder(nsIMsgFolder ** aFolder)4458 NS_IMETHODIMP nsNNTPProtocol::GetCurrentFolder(nsIMsgFolder** aFolder) {
4459   nsresult rv = NS_ERROR_NULL_POINTER;
4460   NS_ENSURE_ARG_POINTER(aFolder);
4461   if (m_newsFolder)
4462     rv =
4463         m_newsFolder->QueryInterface(NS_GET_IID(nsIMsgFolder), (void**)aFolder);
4464   return rv;
4465 }
4466