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