summaryrefslogtreecommitdiffstats
path: root/mailnews/news
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/news')
-rw-r--r--mailnews/news/content/downloadheaders.js85
-rw-r--r--mailnews/news/content/downloadheaders.xul49
-rw-r--r--mailnews/news/moz.build9
-rw-r--r--mailnews/news/public/moz.build24
-rw-r--r--mailnews/news/public/nsIMsgNewsFolder.idl132
-rw-r--r--mailnews/news/public/nsIMsgOfflineNewsState.idl23
-rw-r--r--mailnews/news/public/nsINNTPArticleList.idl17
-rw-r--r--mailnews/news/public/nsINNTPNewsgroupList.idl93
-rw-r--r--mailnews/news/public/nsINNTPNewsgroupPost.idl54
-rw-r--r--mailnews/news/public/nsINNTPProtocol.idl31
-rw-r--r--mailnews/news/public/nsINewsDownloadDialogArgs.idl19
-rw-r--r--mailnews/news/public/nsINntpIncomingServer.idl152
-rw-r--r--mailnews/news/public/nsINntpService.idl50
-rw-r--r--mailnews/news/public/nsINntpUrl.idl99
-rw-r--r--mailnews/news/public/nsMsgNewsCID.h117
-rw-r--r--mailnews/news/src/moz.build27
-rw-r--r--mailnews/news/src/nntpCore.h163
-rw-r--r--mailnews/news/src/nsNNTPArticleList.cpp104
-rw-r--r--mailnews/news/src/nsNNTPArticleList.h40
-rw-r--r--mailnews/news/src/nsNNTPNewsgroupList.cpp1332
-rw-r--r--mailnews/news/src/nsNNTPNewsgroupList.h124
-rw-r--r--mailnews/news/src/nsNNTPNewsgroupPost.cpp94
-rw-r--r--mailnews/news/src/nsNNTPNewsgroupPost.h61
-rw-r--r--mailnews/news/src/nsNNTPProtocol.cpp4777
-rw-r--r--mailnews/news/src/nsNNTPProtocol.h510
-rw-r--r--mailnews/news/src/nsNewsAutoCompleteSearch.js141
-rw-r--r--mailnews/news/src/nsNewsAutoCompleteSearch.manifest2
-rw-r--r--mailnews/news/src/nsNewsDownloadDialogArgs.cpp91
-rw-r--r--mailnews/news/src/nsNewsDownloadDialogArgs.h30
-rw-r--r--mailnews/news/src/nsNewsDownloader.cpp586
-rw-r--r--mailnews/news/src/nsNewsDownloader.h126
-rw-r--r--mailnews/news/src/nsNewsFolder.cpp1897
-rw-r--r--mailnews/news/src/nsNewsFolder.h147
-rw-r--r--mailnews/news/src/nsNewsUtils.cpp62
-rw-r--r--mailnews/news/src/nsNewsUtils.h33
-rw-r--r--mailnews/news/src/nsNntpIncomingServer.cpp2162
-rw-r--r--mailnews/news/src/nsNntpIncomingServer.h142
-rw-r--r--mailnews/news/src/nsNntpMockChannel.cpp353
-rw-r--r--mailnews/news/src/nsNntpMockChannel.h65
-rw-r--r--mailnews/news/src/nsNntpService.cpp1751
-rw-r--r--mailnews/news/src/nsNntpService.h76
-rw-r--r--mailnews/news/src/nsNntpUrl.cpp578
-rw-r--r--mailnews/news/src/nsNntpUrl.h64
43 files changed, 16492 insertions, 0 deletions
diff --git a/mailnews/news/content/downloadheaders.js b/mailnews/news/content/downloadheaders.js
new file mode 100644
index 000000000..bd856ae0d
--- /dev/null
+++ b/mailnews/news/content/downloadheaders.js
@@ -0,0 +1,85 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+var markreadElement = null;
+var numberElement = null;
+
+var nntpServer = null;
+var args = null;
+
+function OnLoad()
+{
+ let newsBundle = document.getElementById("bundle_news");
+
+ if ("arguments" in window && window.arguments[0]) {
+ args = window.arguments[0]
+ .QueryInterface(Components.interfaces.nsINewsDownloadDialogArgs);
+ /* by default, act like the user hit cancel */
+ args.hitOK = false;
+ /* by default, act like the user did not select download all */
+ args.downloadAll = false;
+
+
+ nntpServer = MailServices.accounts.getIncomingServer(args.serverKey)
+ .QueryInterface(Components.interfaces.nsINntpIncomingServer);
+
+ document.title = newsBundle.getString("downloadHeadersTitlePrefix");
+
+ let infotext = newsBundle.getFormattedString("downloadHeadersInfoText",
+ [args.articleCount]);
+ setText('info', infotext);
+ let okButtonText = newsBundle.getString("okButtonText");
+ let okbutton = document.documentElement.getButton("accept");
+ okbutton.setAttribute("label", okButtonText);
+ okbutton.focus();
+ setText("newsgroupLabel", args.groupName);
+ }
+
+ numberElement = document.getElementById("number");
+ numberElement.value = nntpServer.maxArticles;
+
+ markreadElement = document.getElementById("markread");
+ markreadElement.checked = nntpServer.markOldRead;
+
+ return true;
+}
+
+function setText(id, value) {
+ let element = document.getElementById(id);
+ if (!element)
+ return;
+
+ if (element.hasChildNodes())
+ element.firstChild.remove();
+ let textNode = document.createTextNode(value);
+ element.appendChild(textNode);
+}
+
+function OkButtonCallback() {
+ nntpServer.maxArticles = numberElement.value;
+ nntpServer.markOldRead = markreadElement.checked;
+
+ let radio = document.getElementById("all");
+ if (radio)
+ args.downloadAll = radio.selected;
+
+ args.hitOK = true;
+ return true;
+}
+
+function CancelButtonCallback() {
+ args.hitOK = false;
+ return true;
+}
+
+function setupDownloadUI(enable) {
+ let checkbox = document.getElementById("markread");
+ let numberFld = document.getElementById("number");
+
+ checkbox.disabled = !enable;
+ numberFld.disabled = !enable;
+}
diff --git a/mailnews/news/content/downloadheaders.xul b/mailnews/news/content/downloadheaders.xul
new file mode 100644
index 000000000..1adf7c819
--- /dev/null
+++ b/mailnews/news/content/downloadheaders.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/downloadheaders.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="max-width: 27em;"
+ buttonpack="center"
+ ondialogaccept="return OkButtonCallback();"
+ ondialogcancel="return CancelButtonCallback();"
+ onload="OnLoad();">
+
+ <stringbundle id="bundle_news" src="chrome://messenger/locale/news.properties"/>
+ <script type="application/javascript" src="chrome://messenger/content/downloadheaders.js"/>
+
+ <label class="header" style="width: 25em; max-width: 25em;" id="newsgroupLabel" control="downloadGroup"/>
+ <description style="width: 25em; max-width: 25em;" id="info" control="downloadGroup"/>
+ <separator class="thin"/>
+ <vbox class="indent">
+ <radiogroup id="downloadGroup">
+ <radio id="all" label="&all.label;" accesskey="&all.accesskey;"
+ oncommand="setupDownloadUI(false);"/>
+ <separator class="thin"/>
+ <hbox align="center" valign="middle">
+ <radio id="some" selected="true" label="&download.label;"
+ accesskey="&download.accesskey;"
+ oncommand="setupDownloadUI(true);"
+ aria-labelledby="some number headers"/>
+ <textbox id="number"
+ size="7"
+ type="number"
+ min="1"
+ increment="10"
+ aria-labelledby="some number headers"/>
+ <label value="&headers.label;" accesskey="&headers.accesskey;"
+ id="headers" control="number"/>
+ </hbox>
+ </radiogroup>
+
+ <hbox class="indent" align="start">
+ <checkbox id="markread" label="&mark.label;" accesskey="&mark.accesskey;"/>
+ </hbox>
+ </vbox>
+
+</dialog>
diff --git a/mailnews/news/moz.build b/mailnews/news/moz.build
new file mode 100644
index 000000000..27dcb8746
--- /dev/null
+++ b/mailnews/news/moz.build
@@ -0,0 +1,9 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'public',
+ 'src',
+]
diff --git a/mailnews/news/public/moz.build b/mailnews/news/public/moz.build
new file mode 100644
index 000000000..b1e02bd5b
--- /dev/null
+++ b/mailnews/news/public/moz.build
@@ -0,0 +1,24 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIMsgNewsFolder.idl',
+ 'nsIMsgOfflineNewsState.idl',
+ 'nsINewsDownloadDialogArgs.idl',
+ 'nsINNTPArticleList.idl',
+ 'nsINntpIncomingServer.idl',
+ 'nsINNTPNewsgroupList.idl',
+ 'nsINNTPNewsgroupPost.idl',
+ 'nsINNTPProtocol.idl',
+ 'nsINntpService.idl',
+ 'nsINntpUrl.idl',
+]
+
+XPIDL_MODULE = 'msgnews'
+
+EXPORTS += [
+ 'nsMsgNewsCID.h',
+]
+
diff --git a/mailnews/news/public/nsIMsgNewsFolder.idl b/mailnews/news/public/nsIMsgNewsFolder.idl
new file mode 100644
index 000000000..291fe3bfd
--- /dev/null
+++ b/mailnews/news/public/nsIMsgNewsFolder.idl
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgFolder.idl"
+
+%{C++
+#include "nsTArray.h"
+%}
+
+interface nsIMsgWindow;
+interface nsINntpIncomingServer;
+
+[ref] native nsMsgKeyArrayRef(nsTArray<nsMsgKey>);
+
+[scriptable, uuid(9a12c3a5-9de5-4c57-ace3-d51802b525a9)]
+interface nsIMsgNewsFolder : nsISupports {
+ readonly attribute AString unicodeName;
+ /**|rawName| is an 8-bit string to represent the name of a newsgroup used by
+ * a news server. It's offered for the convenience of callers so that they
+ * don't have to convert |unicodeName| to the server-side name when
+ * communicating with a news server. It's US-ASCII except for some
+ * 'stand-alone' Chinese news servers that use GB2312 for newsgroup names
+ * violating RFC 1036. For those servers, it's GB2312. However, it can be any
+ * other single and multibyte encoding in principle. The encoding of this
+ * string is stored in |nsINntpIncomingServer| because that's a server-wide
+ * property.
+ **/
+ [noscript] readonly attribute ACString rawName;
+ readonly attribute nsINntpIncomingServer nntpServer;
+ attribute boolean saveArticleOffline;
+
+ /**
+ * @name Authentication methods
+ * NNTP authentication is slightly wonky, due to edge cases that are not seen
+ * in other protocols. Authentication is not necessary; if authentication is
+ * used, it could be configured on a per-group basis or even require only a
+ * username and not a password.
+ *
+ * Since passwords could be per-group, it is necessary to refer to passwords
+ * using the methods on this interface and not nsIMsgIncomingServer. Passwords
+ * for the server as a whole are found via the root folder. If the server is
+ * configured to use single sign-on (the default), asking any group for its
+ * password will result in the server's password, otherwise, each group stores
+ * its password individually.
+ *
+ * Due to this setup, most of the password management functions on
+ * nsIMsgIncomingServer do not correctly work. The only one that would affect
+ * the passwords stored on folders correctly is forgetPassword; using any
+ * other on a news server would result in inconsistent state.
+ *
+ * Before requesting either the username or password for authentication, it is
+ * first necessary to call getAuthenticationCredentials. If the method returns
+ * true, then groupUsername and groupPassword are appropriately set up for
+ * necessary authentication; if not, then authentication must be stopped.
+ */
+ /// @{
+
+ /**
+ * Gets the authentication credentials, returning if the results are valid.
+ *
+ * If mustPrompt is true, then the user will always be asked for the
+ * credentials. Otherwise, if mayPrompt is true, then the user will be asked
+ * for credentials if there are no saved credentials. If mayPrompt is false,
+ * then no prompt will be shown, even if there are no saved credentials.
+ *
+ * If this method returns true, then groupUsername and groupPassword will
+ * contain non-empty results that could be used for authentication. If this
+ * method returns false, then the values of groupUsername and groupPassword
+ * will be cleared if they had previously been set. This could happen if
+ * mustPrompt were true and the user decided to cancel the authentication
+ * prompt.
+ *
+ * Note that this method will be executed synchronously; if an async prompt
+ * is wanted, it is the responsibility of the caller to manage it explicitly
+ * with nsIMsgAsyncPrompter.
+ */
+ bool getAuthenticationCredentials(in nsIMsgWindow aMsgWindow,
+ in bool mayPrompt, in bool mustPrompt);
+
+ /// The username that should be used for this group
+ attribute ACString groupUsername;
+
+ /// The password that should be used for this group
+ attribute ACString groupPassword;
+
+ /// Forgets saved authentication credentials permanently.
+ void forgetAuthenticationCredentials();
+ /// @}
+
+ void moveFolder(in nsIMsgFolder aNewsgroupToMove, in nsIMsgFolder aRefNewsgroup, in int32_t aOrientation);
+
+ nsIMsgFolder addNewsgroup(in AUTF8String newsgroupName, in ACString setStr);
+
+ void setReadSetFromStr(in ACString setStr);
+
+ readonly attribute ACString newsrcLine;
+ readonly attribute ACString optionLines;
+ readonly attribute ACString unsubscribedNewsgroupLines;
+ void SetNewsrcHasChanged(in boolean newsrcHasChanged);
+ void updateSummaryFromNNTPInfo(in long oldest, in long youngest, in long total);
+ void removeMessage(in nsMsgKey key);
+ [noscript] void removeMessages(in nsMsgKeyArrayRef aMsgKeys);
+ void cancelComplete();
+ void cancelFailed();
+
+ ACString getMessageIdForKey(in nsMsgKey key);
+
+ void getNextNMessages(in nsIMsgWindow aMsgWindow);
+ void notifyDownloadedLine(in string line, in nsMsgKey key);
+ void notifyFinishedDownloadinghdrs();
+
+ /**
+ * Retrieves the database, but does not cache it in mDatabase.
+ *
+ * This is useful for operations that shouldn't hold open the database.
+ */
+ nsIMsgDatabase getDatabaseWithoutCache();
+
+ /**
+ * Requests that a message be canceled.
+ *
+ * Note that, before sending the news cancel, this method will check to make
+ * sure that the user has proper permission to cancel the message.
+ *
+ * @param aMsgHdr The header of the message to be canceled.
+ * @param aMsgWindow The standard message window object, for error dialogs.
+ */
+ void cancelMessage(in nsIMsgDBHdr aMsgHdr, in nsIMsgWindow aMsgWindow);
+};
diff --git a/mailnews/news/public/nsIMsgOfflineNewsState.idl b/mailnews/news/public/nsIMsgOfflineNewsState.idl
new file mode 100644
index 000000000..7268ad6fd
--- /dev/null
+++ b/mailnews/news/public/nsIMsgOfflineNewsState.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * offline news message state. Interface for old MSG_OfflineNewsArtState
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(921AC210-96B5-11d2-B7EB-00805F05FFA5)]
+interface nsIMsgOfflineNewsState : nsISupports {
+
+ /* outputBuffer is actually
+ * a buffer to dump data into, but we normally pass it NET_Socket_Buffer,
+ * which is constant. The implementation should only allocate a new
+ * buffer if *outputBuffer is NULL.
+ */
+ long Process(out string outputBuffer, in long bufferSize);
+ long Interrupt();
+};
+
diff --git a/mailnews/news/public/nsINNTPArticleList.idl b/mailnews/news/public/nsINNTPArticleList.idl
new file mode 100644
index 000000000..a4ba9a967
--- /dev/null
+++ b/mailnews/news/public/nsINNTPArticleList.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgNewsFolder;
+
+[scriptable, uuid(043d9dd4-b133-4eb4-a1a8-71abff69b613)]
+interface nsINNTPArticleList : nsISupports {
+ void initialize(in nsIMsgNewsFolder newsFolder);
+ void addArticleKey(in nsMsgKey key);
+ void finishAddingArticleKeys();
+};
+
diff --git a/mailnews/news/public/nsINNTPNewsgroupList.idl b/mailnews/news/public/nsINNTPNewsgroupList.idl
new file mode 100644
index 000000000..537293c44
--- /dev/null
+++ b/mailnews/news/public/nsINNTPNewsgroupList.idl
@@ -0,0 +1,93 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgNewsFolder;
+interface nsINntpUrl;
+interface nsIMsgWindow;
+
+/**
+ * A utility class for nsINNTPProtocol that handles the list of new headers.
+ */
+[scriptable, uuid(579aa17b-4c77-465d-8eb6-feaa927cb19c)]
+interface nsINNTPNewsgroupList : nsISupports {
+
+ void initialize(in nsINntpUrl runningURL, in nsIMsgNewsFolder newsFolder);
+
+ long getRangeOfArtsToDownload(in nsIMsgWindow aMsgWindow, in long first_message,
+ in long last_message,
+ in long maxextra,
+ out long real_first_message,
+ out long real_last_message);
+
+ void addToKnownArticles(in long first_message, in long last_message);
+
+ /**
+ * Initializes the internal state to get the messages.
+ *
+ * This method should be called before sending the line
+ * <tt>XOVER @arg first_message-@arg last_message</tt> to the server.
+ *
+ * @param first_message The first message of the download range.
+ * @param last_message The last message of the download range.
+ */
+ void initXOVER(in long first_message, in long last_message);
+ void processXOVERLINE(in string line, out unsigned long status);
+ void resetXOVER();
+ void finishXOVERLINE(in long status, out long newstatus);
+
+ /**
+ * Initalizes the state in preparation for a call to XHDR.
+ *
+ * @return The next header to get, or an empty string if done.
+ */
+ ACString initXHDR();
+ /**
+ * Processes a line of the server's response to XHDR.
+ *
+ * It will calculate the message number and other information itself, so the
+ * unadulterated line itself should be sent.
+ *
+ * @param aLine The line as sent from the server.
+ */
+ void processXHDRLine(in ACString aLine);
+
+ /**
+ * Initalizes the internal state to process a HEAD command.
+ *
+ * This method should be called before sending the line
+ * <tt>HEAD @arg aMessage</tt> to the server.
+ *
+ * @param aMessage The message number that will be sent.
+ */
+ void initHEAD(in long aMessage);
+ /**
+ * Processes a line of the server's response to HEAD.
+ *
+ * This will not check for a quoted '.' at the beginning.
+ *
+ * @param aLine The line the server sent.
+ */
+ void processHEADLine(in ACString aLine);
+ /**
+ * Manages the internal state if the call to HEAD failed.
+ *
+ * @param aMessage The message key that caused the HEAD failure.
+ */
+ void HEADFailed(in long aMessage);
+
+ /**
+ * Calls the filters after all messages have been processed.
+ *
+ * This method also cleans out some internal state relating to the messages
+ * that have been processed, so it should always be called at the end of
+ * XOVER/XHDR/HEAD processing.
+ */
+ void callFilters();
+
+ attribute boolean getOldMessages;
+};
+
diff --git a/mailnews/news/public/nsINNTPNewsgroupPost.idl b/mailnews/news/public/nsINNTPNewsgroupPost.idl
new file mode 100644
index 000000000..382ad5d65
--- /dev/null
+++ b/mailnews/news/public/nsINNTPNewsgroupPost.idl
@@ -0,0 +1,54 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This object represents the stream of data which will be sent to an
+ NNTP server. You basically set up all the RFC850 required headers, etc,
+ then pass it to something that reads off the nsIInputStream interface.
+*/
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(9979a2cb-a4e6-45e6-bfeb-b08e704c5a2b)]
+interface nsINNTPNewsgroupPost : nsISupports {
+
+ /* from RFC850 */
+ /* section 2.1 - required headers */
+ attribute string relayVersion;
+ attribute string postingVersion;
+ attribute string from;
+ attribute string date;
+
+ void AddNewsgroup(in string newsgroupName);
+ readonly attribute string newsgroups;
+
+ attribute string subject;
+ attribute string path;
+
+ /* Secion 2.2 - optional headers */
+ attribute string replyTo;
+ attribute string sender;
+ attribute string followupTo;
+ attribute string dateReceived;
+ attribute string expires;
+
+ readonly attribute string references;
+
+ attribute string control;
+ attribute string distribution;
+ attribute string organization;
+
+ /* the message itself */
+ attribute string body;
+
+ /* is this a control message? */
+ readonly attribute boolean isControl;
+
+ attribute nsIFile postMessageFile;
+};
+
+
+
diff --git a/mailnews/news/public/nsINNTPProtocol.idl b/mailnews/news/public/nsINNTPProtocol.idl
new file mode 100644
index 000000000..f7c9105bb
--- /dev/null
+++ b/mailnews/news/public/nsINNTPProtocol.idl
@@ -0,0 +1,31 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+
+[scriptable, uuid(30106238-0991-11d4-a565-0060b0fc04b7)]
+interface nsINNTPProtocol : nsISupports {
+
+ /////////////////////////////////////////////////////////////////////////
+ // isBusy is true if the connection is currently processing a url
+ // and false otherwise.
+ /////////////////////////////////////////////////////////////////////////
+ attribute boolean isBusy;
+
+ void LoadNewsUrl(in nsIURI aUri, in nsISupports aConsumer);
+ void Initialize(in nsIURI aURL, in nsIMsgWindow aMsgWindow);
+
+ // Get last active time stamp
+ void GetLastActiveTimeStamp(out PRTime aTimeStamp);
+
+ attribute boolean isCachedConnection;
+ readonly attribute nsIMsgFolder currentFolder;
+
+ void CloseConnection();
+};
diff --git a/mailnews/news/public/nsINewsDownloadDialogArgs.idl b/mailnews/news/public/nsINewsDownloadDialogArgs.idl
new file mode 100644
index 000000000..9d728a9bd
--- /dev/null
+++ b/mailnews/news/public/nsINewsDownloadDialogArgs.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(3634327c-392b-4686-adf5-576e6cef9196)]
+interface nsINewsDownloadDialogArgs: nsISupports {
+ attribute AString groupName;
+ attribute long articleCount;
+ attribute string serverKey;
+ attribute boolean hitOK;
+ attribute boolean downloadAll;
+};
+
+%{ C++
+#define DOWNLOAD_HEADERS_URL "chrome://messenger/content/downloadheaders.xul"
+%}
diff --git a/mailnews/news/public/nsINntpIncomingServer.idl b/mailnews/news/public/nsINntpIncomingServer.idl
new file mode 100644
index 000000000..c2ac6e9d7
--- /dev/null
+++ b/mailnews/news/public/nsINntpIncomingServer.idl
@@ -0,0 +1,152 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIMsgNewsFolder;
+interface nsINNTPProtocol;
+interface nsNNTPProtocol;
+interface nsIChannel;
+interface nsIURI;
+interface nsIMsgWindow;
+
+[scriptable, uuid(077620ed-c6c4-4d4d-bed5-4d041f924002)]
+interface nsINntpIncomingServer : nsISupports {
+ /* the on-disk path to the newsrc file for this server */
+ attribute nsIFile newsrcFilePath;
+
+ /* the newsrc root path (the directories all the newsrc files live) */
+ attribute nsIFile newsrcRootPath;
+
+ /* ask the user before downloading more than maxArticles? */
+ attribute boolean notifyOn;
+
+ /* the max articles to download */
+ attribute long maxArticles;
+
+ /* when we don't download all, do we mark the rest read? */
+ attribute boolean markOldRead;
+
+ /* abbreviate the newsgroup names in the folder pane? */
+ attribute boolean abbreviate;
+
+ /* do we use a single login per server or do we login per group */
+ attribute boolean singleSignon;
+
+ /** the server charset and it may be needed to display newsgroup folder
+ * names correctly
+ **/
+ attribute ACString charset;
+
+ /* the server keeps track of all the newsgroups we are subscribed to */
+ void addNewsgroup(in AString name);
+ void removeNewsgroup(in AString name);
+
+ void writeNewsrcFile();
+
+ attribute boolean newsrcHasChanged;
+
+ /**
+ * The maximum number of connections to make to the server.
+ *
+ * This preference (internally max_cached_connections) controls how many
+ * connections we can make. A negative connection count is treated as only
+ * one connection, while 0 (the default) loads the default number of
+ * connections, presently 2.
+ */
+ attribute long maximumConnectionsNumber;
+
+ void displaySubscribedGroup(in nsIMsgNewsFolder msgFolder,
+ in long firstMessage, in long lastMessage,
+ in long totalMessages);
+
+
+ /**
+ * Get a new NNTP channel to run the URI.
+ *
+ * If the server has used up all of its connections, this will place the URI
+ * in the queue to be run when one is freed.
+ *
+ * @param uri The URI to run.
+ * @param window The standard message window object.
+ */
+ nsIChannel getNntpChannel(in nsIURI uri, in nsIMsgWindow window);
+ /**
+ * Enqueues a URI to be run when we have a free connection.
+ *
+ * If there is one already free, it will be immediately started.
+ *
+ * @param uri The URI to run.
+ * @param window The standard message window object.
+ * @param consumer An argument to be passed to nsINNTPProtocol:LoadNewUrl.
+ */
+ void loadNewsUrl(in nsIURI uri, in nsIMsgWindow window,
+ in nsISupports consumer);
+
+ /**
+ * Remove a connection from our connection cache.
+ *
+ * @param aNntpConnection The connection to be removed.
+ */
+ void removeConnection(in nsINNTPProtocol aNntpConnection);
+
+ /**
+ * Load the next URI in the queue to the given connection.
+ *
+ * @param aNntpConnection The newly-freed connection.
+ */
+ [noscript] void prepareForNextUrl(in nsNNTPProtocol aNntpConnection);
+
+ /**
+ * Returns whether or not the server has subscribed to the given newsgroup.
+ *
+ * Note that the name here is intended to be escaped; however, since `%' is
+ * not a legal newsgroup name, it is possibly safe to pass in an unescaped
+ * newsgroup name.
+ */
+ boolean containsNewsgroup(in AUTF8String escapedName);
+
+ void subscribeToNewsgroup(in AUTF8String name);
+
+ /* used for the subscribe dialog.
+ name is encoded in |charset| (attribute declared above) */
+ [noscript] void addNewsgroupToList(in string name);
+
+ attribute boolean supportsExtensions;
+ void addExtension(in string extension);
+ boolean queryExtension(in string extension);
+
+ attribute boolean postingAllowed;
+ attribute boolean pushAuth;
+ attribute unsigned long lastUpdatedTime;
+
+ void addPropertyForGet(in string name, in string value);
+ string queryPropertyForGet(in string name);
+
+ void addSearchableGroup(in AString name);
+ boolean querySearchableGroup(in AString name);
+
+ void addSearchableHeader(in string headerName);
+ boolean querySearchableHeader(in string headerName);
+
+ /**
+ * Returns the folder corresponding to the given group.
+ *
+ * Note that this name is expected to be unescaped.
+ * @note If the group does not exist, a bogus news folder will be returned.
+ * DO NOT call this method unless you are sure that the newsgroup
+ * is subscribed to (e.g., by containsNewsgroup)
+ */
+ nsIMsgNewsFolder findGroup(in AUTF8String name);
+
+ readonly attribute AUTF8String firstGroupNeedingExtraInfo;
+ void setGroupNeedsExtraInfo(in AUTF8String name, in boolean needsExtraInfo);
+
+ void groupNotFound(in nsIMsgWindow window, in AString group,
+ in boolean opening);
+
+ void setPrettyNameForGroup(in AString name, in AString prettyName);
+};
diff --git a/mailnews/news/public/nsINntpService.idl b/mailnews/news/public/nsINntpService.idl
new file mode 100644
index 000000000..0383bf864
--- /dev/null
+++ b/mailnews/news/public/nsINntpService.idl
@@ -0,0 +1,50 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIUrlListener.idl"
+#include "nsINntpIncomingServer.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIURI;
+interface nsIFile;
+interface nsIMsgWindow;
+interface nsIMsgFolder;
+interface nsICacheStorage;
+
+[scriptable, uuid(dc5cadb0-966c-4ef1-a4c8-cc1e48d1ac61)]
+interface nsINntpService : nsISupports {
+
+ /* newsgroupsList is a comma separated list of newsgroups, which may be
+ * in news://host/group or group form
+ * "news://host/group1,news://host/group2" or "group1,group2"
+ *
+ * newsgroupsHeaderVal is a comma separated list of groups in the group form
+ * "group1,group2"
+ *
+ * newshostHeaderVal is a single hostname.
+ * "host"
+ */
+ void generateNewsHeaderValsForPosting(in ACString newsgroupsList, out string newsgroupsHeaderVal, out string newshostHeaderVal);
+
+ nsIURI postMessage(in nsIFile aFileToPost, in string newsgroupNames, in string aAccountKey, in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow);
+
+ nsIURI getNewNews(in nsINntpIncomingServer nntpServer, in string uri, in boolean getOld, in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow);
+
+ nsIURI cancelMessage(in string cancelURL, in string messageURI, in nsISupports aConsumer, in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow);
+
+ void getListOfGroupsOnServer(in nsINntpIncomingServer nntpServer, in nsIMsgWindow aMsgWindow, in boolean getOnlyNew);
+
+ nsIURI fetchMessage(in nsIMsgFolder newsFolder, in nsMsgKey key, in nsIMsgWindow aMsgWindow, in nsISupports aConsumer, in nsIUrlListener aUrlListener);
+
+ void downloadNewsgroupsForOffline(in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
+ /**
+ * can handle news-message:// and news://
+ */
+ void decomposeNewsURI(in string uri, out nsIMsgFolder folder, out nsMsgKey key);
+
+ // handle to the cache session used by news....
+ readonly attribute nsICacheStorage cacheStorage;
+};
diff --git a/mailnews/news/public/nsINntpUrl.idl b/mailnews/news/public/nsINntpUrl.idl
new file mode 100644
index 000000000..d85b37e63
--- /dev/null
+++ b/mailnews/news/public/nsINntpUrl.idl
@@ -0,0 +1,99 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsINNTPNewsgroupPost;
+
+typedef long nsNewsAction;
+
+/**
+ * Represents specific attributes to a URL for news usage.
+ *
+ * Note that the urls represented by this interface can be one of five schemes:
+ * [s]news, nntp[s], or news-message. Any URI that is valid under RFC 5538 will
+ * be accepted. However, it is possible for some queries to be invalid. There
+ * are also a few important things to note:
+ *
+ * - Missing authorities in [s]news: URIs cause nsIMsgMailNewsUrl::server and
+ * nsIMsgMessageUrl::folder to be null.
+ * - nsIMsgMailNewsUrl::server and nsIMsgMessageUrl::folder will be null if the
+ * specified server does not actually exist. In addition, the folder is null
+ * if the group is not currently subscribed on that server.
+ * - Although news-message URIs are parsable, there is no protocol handler
+ * associated with this url. To run these, you should convert these to the
+ * corresponding [s]news or nntp URL, and set the original one in
+ * nsIMsgMessageUrl::uri and ::originalSpec.
+ * - A URI that results in an ActionUnknown will not be run.
+ * - Cancel URIs require the original spec to also be set, so it can find both
+ * the message ID and the group/key combination.
+ * * Some actions require either a group or a message id. Since actions can be
+ * set after the fact, these conditions are not verified.
+ */
+[scriptable, uuid(ef920ca3-9c46-48b8-9fa3-cb430d3681ea)]
+interface nsINntpUrl : nsISupports {
+ /// For ActionPostArticle URIs, the message to be posted.
+ attribute nsINNTPNewsgroupPost messageToPost;
+
+ /**
+ * The action that this URL will take when run.
+ *
+ * Most actions can be automatically determined from the URL spec as follows:
+ *
+ * 1. The query string is searched for the appropriate action.
+ *
+ * 2. A non-empty message ID or key is found (sets ActionFetchArticle).
+ *
+ * 3. A non-empty group is found (ActionGetNewNews or ActionListGroups).
+ */
+ attribute nsNewsAction newsAction;
+
+ /// For ActionGetNewNews URIs, whether or not to get older messages.
+ attribute boolean getOldMessages;
+
+ /**
+ * The group portion of the URI, if one is present.
+ *
+ * This group name is fully unescaped; if you need to construct news URLs with
+ * this value, be sure to escape it first.
+ */
+ readonly attribute ACString group;
+
+ /// The message ID portion of the URI, if one is present
+ readonly attribute ACString messageID;
+
+ /// The message key portion of the URI or nsMsgKey_None if not present
+ readonly attribute nsMsgKey key;
+
+ /// The action of this news URI could not be determined
+ const nsNewsAction ActionUnknown = 0;
+ /// Fetch the contents of an article
+ const nsNewsAction ActionFetchArticle = 1;
+ /// Fetch the part of an article (requires ?part=)
+ const nsNewsAction ActionFetchPart = 2;
+ /// Save the contents of an article to disk
+ const nsNewsAction ActionSaveMessageToDisk = 3;
+ /// Cancel the article (requires ?cancel)
+ const nsNewsAction ActionCancelArticle = 4;
+ /// Post an article
+ const nsNewsAction ActionPostArticle = 5;
+ /// List the non-expired ids in the newsgroup (requires ?list-ids)
+ const nsNewsAction ActionListIds = 6;
+ /// Do an online newsgroup search (requires ?search)
+ const nsNewsAction ActionSearch = 7;
+ /// Retrieve new messages from the server
+ const nsNewsAction ActionGetNewNews = 8;
+ /// List groups for subscribe
+ const nsNewsAction ActionListGroups = 9;
+ /// List new groups for subscribe (requires ?new-groups)
+ const nsNewsAction ActionListNewGroups = 10;
+
+ /// Constant for the default NNTP over ssl port number
+ const int32_t DEFAULT_NNTP_PORT = 119;
+
+ /// Constant for the default NNTP over ssl port number
+ const int32_t DEFAULT_NNTPS_PORT = 563;
+};
diff --git a/mailnews/news/public/nsMsgNewsCID.h b/mailnews/news/public/nsMsgNewsCID.h
new file mode 100644
index 000000000..e56a93bb7
--- /dev/null
+++ b/mailnews/news/public/nsMsgNewsCID.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMsgNewsCID_h__
+#define nsMsgNewsCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+#include "nsMsgBaseCID.h"
+
+//
+// nsMsgNewsFolder
+#define NS_NEWSFOLDERRESOURCE_CONTRACTID \
+ NS_RDF_RESOURCE_FACTORY_CONTRACTID_PREFIX "news"
+#define NS_NEWSFOLDERRESOURCE_CID \
+{ /* 4ace448a-f6d4-11d2-880d-004005263078 */ \
+ 0x4ace448a, 0xf6d4, 0x11d2, \
+ {0x88, 0x0d, 0x00, 0x40, 0x05, 0x26, 0x30, 0x78} \
+}
+
+//
+// nsNntpIncomingServer
+//
+#define NS_NNTPINCOMINGSERVER_CONTRACTID \
+ NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX "nntp"
+
+#define NS_NNTPINCOMINGSERVER_CID \
+{ /* 6ff28d0a-f776-11d2-87ca-004005263078 */ \
+ 0x6ff28d0a, 0xf776, 0x11d2, \
+ {0x87, 0xca, 0x00, 0x40, 0x05, 0x26, 0x30, 0x78} \
+}
+
+//
+// nsNntpService
+//
+
+#define NS_NNTPPROTOCOLINFO_CONTRACTID \
+ NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX "nntp"
+
+#define NS_NEWSPROTOCOLHANDLER_CONTRACTID \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "news"
+#define NS_SNEWSPROTOCOLHANDLER_CONTRACTID \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "snews"
+#define NS_NNTPPROTOCOLHANDLER_CONTRACTID \
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "nntp"
+#define NS_NEWSMESSAGESERVICE_CONTRACTID \
+ "@mozilla.org/messenger/messageservice;1?type=news-message"
+#define NS_NNTPMESSAGESERVICE_CONTRACTID \
+ "@mozilla.org/messenger/messageservice;1?type=news"
+#define NS_NNTPSERVICE_CONTRACTID \
+ "@mozilla.org/messenger/nntpservice;1"
+#define NS_NEWSSTARTUPHANDLER_CONTRACTID \
+ "@mozilla.org/commandlinehandler/general-startup;1?type=news"
+
+#define NS_NNTPSERVICE_CID \
+{ /* 4C9F90E1-E19B-11d2-806E-006008128C4E */ \
+ 0x4c9f90e1, 0xe19b, 0x11d2, \
+ {0x80, 0x6e, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e} \
+}
+
+//
+// nsNNTPNewsgroupPost
+//
+#define NS_NNTPNEWSGROUPPOST_CONTRACTID \
+ "@mozilla.org/messenger/nntpnewsgrouppost;1"
+#define NS_NNTPNEWSGROUPPOST_CID \
+{ /* 30c60228-187e-11d3-842f-004005263078 */ \
+ 0x30c60228, 0x187e, 0x11d3, \
+ {0x84, 0x2f, 0x00, 0x40, 0x05, 0x26, 0x30, 0x78} \
+}
+
+//
+// nsNNTPNewsgroupList
+//
+#define NS_NNTPNEWSGROUPLIST_CONTRACTID \
+ "@mozilla.org/messenger/nntpnewsgrouplist;1"
+#define NS_NNTPNEWSGROUPLIST_CID \
+{ /* 631e9054-1893-11d3-9916-004005263078 */ \
+ 0x631e9054, 0x1893, 0x11d3, \
+ {0x99, 0x16, 0x00, 0x40, 0x05, 0x26, 0x30, 0x78} \
+}
+
+//
+// nsNNTPArticleList
+//
+#define NS_NNTPARTICLELIST_CONTRACTID \
+ "@mozilla.org/messenger/nntparticlelist;1"
+#define NS_NNTPARTICLELIST_CID \
+{ /* 9f12bdf0-189f-11d3-973e-00805f916fd3 */ \
+ 0x9f12bdf0, 0x189f, 0x11d3, \
+ {0x97, 0x3e, 0x00, 0x80, 0x5f, 0x91, 0x6f, 0xd3} \
+}
+
+//
+// nsNntpUrl
+//
+#define NS_NNTPURL_CONTRACTID \
+ "@mozilla.org/messenger/nntpurl;1"
+#define NS_NNTPURL_CID \
+{ /* 196B4B30-E18C-11d2-806E-006008128C4E */ \
+ 0x196b4b30, 0xe18c, 0x11d2, \
+ { 0x80, 0x6e, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e } }
+
+//
+// nsNewsDownloadDialogArgs
+//
+#define NS_NEWSDOWNLOADDIALOGARGS_CONTRACTID \
+ "@mozilla.org/messenger/newsdownloaddialogargs;1"
+#define NS_NEWSDOWNLOADDIALOGARGS_CID \
+{ /* 1540689e-1dd2-11b2-933d-f0d1e460ef4a */ \
+ 0x1540689e, 0x1dd2, 0x11b2, \
+ { 0x93, 0x3d, 0xf0, 0xd1, 0xe4, 0x60, 0xef, 0x4a} }
+
+#endif // nsMsgNewsCID_h__
diff --git a/mailnews/news/src/moz.build b/mailnews/news/src/moz.build
new file mode 100644
index 000000000..c2f18c389
--- /dev/null
+++ b/mailnews/news/src/moz.build
@@ -0,0 +1,27 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsNewsDownloadDialogArgs.cpp',
+ 'nsNewsDownloader.cpp',
+ 'nsNewsFolder.cpp',
+ 'nsNewsUtils.cpp',
+ 'nsNNTPArticleList.cpp',
+ 'nsNntpIncomingServer.cpp',
+ 'nsNntpMockChannel.cpp',
+ 'nsNNTPNewsgroupList.cpp',
+ 'nsNNTPNewsgroupPost.cpp',
+ 'nsNNTPProtocol.cpp',
+ 'nsNntpService.cpp',
+ 'nsNntpUrl.cpp',
+]
+
+EXTRA_COMPONENTS += [
+ 'nsNewsAutoCompleteSearch.js',
+ 'nsNewsAutoCompleteSearch.manifest',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/news/src/nntpCore.h b/mailnews/news/src/nntpCore.h
new file mode 100644
index 000000000..e09f72f2c
--- /dev/null
+++ b/mailnews/news/src/nntpCore.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _NNTPCore_H__
+#define _NNTPCore_H__
+
+#define NEWS_MSGS_URL "chrome://messenger/locale/news.properties"
+
+// The following string constants are protocol strings. I'm defining them as macros here
+// so I don't have to sprinkle all of the strings throughout the protocol.
+#define NNTP_CMD_LIST_EXTENSIONS "LIST EXTENSIONS" CRLF
+#define NNTP_CMD_MODE_READER "MODE READER" CRLF
+#define NNTP_CMD_LIST_SEARCHES "LIST SEARCHES" CRLF
+#define NNTP_CMD_LIST_SEARCH_FIELDS "LIST SRCHFIELDS" CRLF
+#define NNTP_CMD_GET_PROPERTIES "GET" CRLF
+#define NNTP_CMD_LIST_SUBSCRIPTIONS "LIST SUBSCRIPTIONS" CRLF
+#define NNTP_CMD_POST "POST" CRLF
+#define NNTP_CMD_QUIT "QUIT" CRLF
+
+// end of protocol strings
+
+#define MK_NNTP_RESPONSE_HELP 100
+
+#define MK_NNTP_RESPONSE_POSTING_ALLOWED 200
+#define MK_NNTP_RESPONSE_POSTING_DENIED 201
+
+#define MK_NNTP_RESPONSE_DISCONTINUED 400
+
+#define MK_NNTP_RESPONSE_COMMAND_UNKNOWN 500
+#define MK_NNTP_RESPONSE_SYNTAX_ERROR 501
+#define MK_NNTP_RESPONSE_PERMISSION_DENIED 502
+#define MK_NNTP_RESPONSE_SERVER_ERROR 503
+
+#define MK_NNTP_RESPONSE_ARTICLE_BOTH 220
+#define MK_NNTP_RESPONSE_ARTICLE_HEAD 221
+#define MK_NNTP_RESPONSE_ARTICLE_BODY 222
+#define MK_NNTP_RESPONSE_ARTICLE_NONE 223
+#define MK_NNTP_RESPONSE_ARTICLE_NO_GROUP 412
+#define MK_NNTP_RESPONSE_ARTICLE_NO_CURRENT 420
+#define MK_NNTP_RESPONSE_ARTICLE_NONEXIST 423
+#define MK_NNTP_RESPONSE_ARTICLE_NOTFOUND 430
+
+#define MK_NNTP_RESPONSE_GROUP_SELECTED 211
+#define MK_NNTP_RESPONSE_GROUP_NO_GROUP 411
+
+#define MK_NNTP_RESPONSE_IHAVE_OK 235
+#define MK_NNTP_RESPONSE_IHAVE_ARTICLE 335
+#define MK_NNTP_RESPONSE_IHAVE_NOT_WANTED 435
+#define MK_NNTP_RESPONSE_IHAVE_FAILED 436
+#define MK_NNTP_RESPONSE_IHAVE_REJECTED 437
+
+#define MK_NNTP_RESPONSE_LAST_OK 223
+#define MK_NNTP_RESPONSE_LAST_NO_GROUP 412
+#define MK_NNTP_RESPONSE_LAST_NO_CURRENT 420
+#define MK_NNTP_RESPONSE_LAST_NO_ARTICLE 422
+
+#define MK_NNTP_RESPONSE_LIST_OK 215
+
+#define MK_NNTP_RESPONSE_NEWGROUPS_OK 231
+
+#define MK_NNTP_RESPONSE_NEWNEWS_OK 230
+
+#define MK_NNTP_RESPONSE_NEXT_OK 223
+#define MK_NNTP_RESPONSE_NEXT_NO_GROUP 412
+#define MK_NNTP_RESPONSE_NEXT_NO_CURRENT 420
+#define MK_NNTP_RESPONSE_NEXT_NO_ARTICLE 421
+
+#define MK_NNTP_RESPONSE_POST_OK 240
+#define MK_NNTP_RESPONSE_POST_SEND_NOW 340
+#define MK_NNTP_RESPONSE_POST_DENIED 440
+#define MK_NNTP_RESPONSE_POST_FAILED 441
+
+#define MK_NNTP_RESPONSE_QUIT_OK 205
+
+#define MK_NNTP_RESPONSE_SLAVE_OK 202
+
+#define MK_NNTP_RESPONSE_CHECK_NO_ARTICLE 238
+#define MK_NNTP_RESPONSE_CHECK_NO_ACCEPT 400
+#define MK_NNTP_RESPONSE_CHECK_LATER 431
+#define MK_NNTP_RESPONSE_CHECK_DONT_SEND 438
+#define MK_NNTP_RESPONSE_CHECK_DENIED 480
+#define MK_NNTP_RESPONSE_CHECK_ERROR 500
+
+#define MK_NNTP_RESPONSE_XHDR_OK 221
+#define MK_NNTP_RESPONSE_XHDR_NO_GROUP 412
+#define MK_NNTP_RESPONSE_XHDR_NO_CURRENT 420
+#define MK_NNTP_RESPONSE_XHDR_NO_ARTICLE 430
+#define MK_NNTP_RESPONSE_XHDR_DENIED 502
+
+#define MK_NNTP_RESPONSE_XOVER_OK 224
+#define MK_NNTP_RESPONSE_XOVER_NO_GROUP 412
+#define MK_NNTP_RESPONSE_XOVER_NO_CURRENT 420
+#define MK_NNTP_RESPONSE_XOVER_DENIED 502
+
+#define MK_NNTP_RESPONSE_XPAT_OK 221
+#define MK_NNTP_RESPONSE_XPAT_NO_ARTICLE 430
+#define MK_NNTP_RESPONSE_XPAT_DENIED 502
+
+#define MK_NNTP_RESPONSE_AUTHINFO_OK 281
+#define MK_NNTP_RESPONSE_AUTHINFO_CONT 381
+#define MK_NNTP_RESPONSE_AUTHINFO_REQUIRE 480
+#define MK_NNTP_RESPONSE_AUTHINFO_REJECT 482
+#define MK_NNTP_RESPONSE_AUTHINFO_DENIED 502
+
+#define MK_NNTP_RESPONSE_
+
+#define MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_OK 250
+#define MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_CONT 350
+#define MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_REQUIRE 450
+#define MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_REJECT 452
+
+#define MK_NNTP_RESPONSE_TYPE_INFO 1
+#define MK_NNTP_RESPONSE_TYPE_OK 2
+#define MK_NNTP_RESPONSE_TYPE_CONT 3
+#define MK_NNTP_RESPONSE_TYPE_CANNOT 4
+#define MK_NNTP_RESPONSE_TYPE_ERROR 5
+
+#define MK_NNTP_RESPONSE_TYPE(x) (x/100)
+
+// the following used to be defined in allxpstr.h. Until we find a new values for these,
+// I'm defining them here because I don't want to link against xplib.lib...(mscott)
+
+#define MK_DATA_LOADED 1
+#define MK_EMPTY_NEWS_LIST -227
+#define MK_INTERRUPTED -201
+#define MK_MALFORMED_URL_ERROR -209
+#define MK_NEWS_ERROR_FMT -430
+#define MK_NNTP_CANCEL_CONFIRM -426
+#define MK_NNTP_CANCEL_DISALLOWED -427
+#define MK_NNTP_NOT_CANCELLED -429
+#define MK_OUT_OF_MEMORY -207
+#define XP_CONFIRM_SAVE_NEWSGROUPS -1
+#define XP_HTML_ARTICLE_EXPIRED -1
+#define XP_HTML_NEWS_ERROR -1
+#define XP_PROGRESS_READ_NEWSGROUPINFO 1
+#define XP_PROGRESS_RECEIVE_ARTICLE 1
+#define XP_PROGRESS_RECEIVE_LISTARTICLES 1
+#define XP_PROGRESS_RECEIVE_NEWSGROUP 1
+#define XP_PROGRESS_SORT_ARTICLES 1
+#define XP_PROGRESS_READ_NEWSGROUP_COUNTS 1
+#define XP_THERMO_PERCENT_FORM 1
+#define XP_PROMPT_ENTER_USERNAME 1
+#define MK_NNTP_AUTH_FAILED -260
+#define MK_NNTP_ERROR_MESSAGE -304
+#define MK_NNTP_NEWSGROUP_SCAN_ERROR -305
+#define MK_NNTP_SERVER_ERROR -217
+#define MK_NNTP_SERVER_NOT_CONFIGURED -307
+#define MK_TCP_READ_ERROR -252
+#define MK_TCP_WRITE_ERROR -236
+#define MK_NNTP_CANCEL_ERROR -428
+#define XP_CONNECT_NEWS_HOST_CONTACTED_WAITING_FOR_REPLY 1
+#define XP_PLEASE_ENTER_A_PASSWORD_FOR_NEWS_SERVER_ACCESS 1
+#define XP_GARBAGE_COLLECTING 1
+#define XP_MESSAGE_SENT_WAITING_NEWS_REPLY 1
+#define MK_MSG_DELIV_NEWS 1
+#define MK_MSG_COLLABRA_DISABLED 1
+#define MK_MSG_EXPIRE_NEWS_ARTICLES 1
+#define MK_MSG_HTML_IMAP_NO_CACHED_BODY 1
+#define MK_MSG_CANT_MOVE_FOLDER 1
+
+#endif /* NNTPCore_H__ */
diff --git a/mailnews/news/src/nsNNTPArticleList.cpp b/mailnews/news/src/nsNNTPArticleList.cpp
new file mode 100644
index 000000000..106f5439f
--- /dev/null
+++ b/mailnews/news/src/nsNNTPArticleList.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+
+#include "nsCOMPtr.h"
+#include "nsNNTPArticleList.h"
+#include "nsIMsgFolder.h"
+#include "nsAutoPtr.h"
+#include "nsMsgKeyArray.h"
+
+NS_IMPL_ISUPPORTS(nsNNTPArticleList, nsINNTPArticleList)
+
+nsNNTPArticleList::nsNNTPArticleList()
+{
+}
+
+nsNNTPArticleList::~nsNNTPArticleList()
+{
+ if (m_newsDB) {
+ m_newsDB->Commit(nsMsgDBCommitType::kSessionCommit);
+ m_newsDB->Close(true);
+ m_newsDB = nullptr;
+ }
+
+ m_newsFolder = nullptr;
+}
+
+NS_IMETHODIMP
+nsNNTPArticleList::Initialize(nsIMsgNewsFolder *newsFolder)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(newsFolder);
+
+ m_dbIndex = 0;
+
+ m_newsFolder = newsFolder;
+
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = folder->GetMsgDatabase(getter_AddRefs(m_newsDB));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!m_newsDB) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ rv = m_newsDB->ListAllKeys(keys);
+ NS_ENSURE_SUCCESS(rv,rv);
+ keys->Sort();
+ m_idsInDB.AppendElements(keys->m_keys);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPArticleList::AddArticleKey(nsMsgKey key)
+{
+#ifdef DEBUG
+ m_idsOnServer.AppendElement(key);
+#endif
+
+ if (m_dbIndex < m_idsInDB.Length())
+ {
+ nsMsgKey idInDBToCheck = m_idsInDB[m_dbIndex];
+ // if there are keys in the database that aren't in the newsgroup
+ // on the server, remove them. We probably shouldn't do this if
+ // we have a copy of the article offline.
+ // We'll add to m_idsDeleted for now and remove the id later
+ while (idInDBToCheck < key)
+ {
+ m_idsDeleted.AppendElement(idInDBToCheck);
+ if (m_dbIndex >= m_idsInDB.Length())
+ break;
+ idInDBToCheck = m_idsInDB[++m_dbIndex];
+ }
+ if (idInDBToCheck == key)
+ m_dbIndex++;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPArticleList::FinishAddingArticleKeys()
+{
+ // if the last n messages in the group are cancelled, they won't have gotten removed
+ // so we have to go and remove them now.
+ if (m_dbIndex < m_idsInDB.Length())
+ m_idsDeleted.AppendElements(&m_idsInDB[m_dbIndex],
+ m_idsInDB.Length() - m_dbIndex);
+
+ if (m_idsDeleted.Length())
+ m_newsFolder->RemoveMessages(m_idsDeleted);
+
+#ifdef DEBUG
+ // make sure none of the deleted turned up on the idsOnServer list
+ for (uint32_t i = 0; i < m_idsDeleted.Length(); i++) {
+ NS_ASSERTION(!m_idsOnServer.Contains((nsMsgKey)m_idsDeleted[i]),
+ "a deleted turned up on the idsOnServer list");
+ }
+#endif
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNNTPArticleList.h b/mailnews/news/src/nsNNTPArticleList.h
new file mode 100644
index 000000000..5cbfc47df
--- /dev/null
+++ b/mailnews/news/src/nsNNTPArticleList.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNNTPArticleList_h___
+#define nsNNTPArticleList_h___
+
+#include "nsCOMPtr.h"
+#include "nsINNTPArticleList.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIMsgDatabase.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+
+class nsNNTPArticleList : public nsINNTPArticleList
+{
+public:
+ nsNNTPArticleList();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINNTPARTICLELIST
+
+protected:
+ virtual ~nsNNTPArticleList();
+
+ nsTArray<nsMsgKey> m_idsInDB;
+
+#ifdef DEBUG
+ nsTArray<nsMsgKey> m_idsOnServer;
+#endif
+ nsTArray<nsMsgKey> m_idsDeleted;
+
+ nsCOMPtr <nsIMsgNewsFolder> m_newsFolder;
+ nsCOMPtr <nsIMsgDatabase> m_newsDB;
+
+ uint32_t m_dbIndex;
+};
+
+#endif /* nsNNTPArticleList_h___ */
diff --git a/mailnews/news/src/nsNNTPNewsgroupList.cpp b/mailnews/news/src/nsNNTPNewsgroupList.cpp
new file mode 100644
index 000000000..3833390c7
--- /dev/null
+++ b/mailnews/news/src/nsNNTPNewsgroupList.cpp
@@ -0,0 +1,1332 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * formerly listngst.cpp
+ * This class should ultimately be part of a news group listing
+ * state machine - either by inheritance or delegation.
+ * Currently, a folder pane owns one and libnet news group listing
+ * related messages get passed to this object.
+ */
+
+#include "msgCore.h" // precompiled header...
+#include "MailNewsTypes.h"
+#include "nsCOMPtr.h"
+#include "nsIDBFolderInfo.h"
+#include "nsINewsDatabase.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsINntpIncomingServer.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgFilter.h"
+#include "nsNNTPNewsgroupList.h"
+
+#include "nsINNTPArticleList.h"
+#include "nsMsgKeySet.h"
+
+#include "nntpCore.h"
+#include "nsIStringBundle.h"
+
+#include "plstr.h"
+#include "prmem.h"
+#include "prprf.h"
+
+#include "nsMsgUtils.h"
+
+#include "nsMsgDatabase.h"
+
+#include "nsIDBFolderInfo.h"
+
+#include "nsNewsUtils.h"
+
+#include "nsMsgDBCID.h"
+
+#include "nsINewsDownloadDialogArgs.h"
+
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIMsgWindow.h"
+#include "nsIDocShell.h"
+#include "nsIMutableArray.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMsgFilterCustomAction.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+
+// update status on header download once per second
+#define MIN_STATUS_UPDATE_INTERVAL PRTime(PR_USEC_PER_SEC)
+
+
+nsNNTPNewsgroupList::nsNNTPNewsgroupList()
+ : m_finishingXover(false),
+ m_getOldMessages(false),
+ m_promptedAlready(false),
+ m_downloadAll(false),
+ m_maxArticles(0),
+ m_lastPercent(-1),
+ m_lastStatusUpdate(0),
+ m_lastProcessedNumber(0),
+ m_firstMsgNumber(0),
+ m_lastMsgNumber(0),
+ m_firstMsgToDownload(0),
+ m_lastMsgToDownload(0),
+ m_set(nullptr)
+{
+ memset(&m_knownArts, 0, sizeof(m_knownArts));
+}
+
+nsNNTPNewsgroupList::~nsNNTPNewsgroupList()
+{
+ CleanUp();
+}
+
+NS_IMPL_ISUPPORTS(nsNNTPNewsgroupList, nsINNTPNewsgroupList, nsIMsgFilterHitNotify)
+
+nsresult
+nsNNTPNewsgroupList::Initialize(nsINntpUrl *runningURL, nsIMsgNewsFolder *newsFolder)
+{
+ m_newsFolder = newsFolder;
+ m_runningURL = runningURL;
+ m_knownArts.set = nsMsgKeySet::Create();
+
+ nsresult rv = m_newsFolder->GetDatabaseWithoutCache(getter_AddRefs(m_newsDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = folder->GetFilterList(m_msgWindow, getter_AddRefs(m_filterList));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCString ngHeaders;
+ m_filterList->GetArbitraryHeaders(ngHeaders);
+ ParseString(ngHeaders, ' ', m_filterHeaders);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = server->GetFilterList(m_msgWindow, getter_AddRefs(m_serverFilterList));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsAutoCString servHeaders;
+ m_serverFilterList->GetArbitraryHeaders(servHeaders);
+
+ nsTArray<nsCString> servArray;
+ ParseString(servHeaders, ' ', servArray);
+
+ // servArray may have duplicates already in m_filterHeaders.
+ for (uint32_t i = 0; i < servArray.Length(); i++)
+ {
+ if (!m_filterHeaders.Contains(servArray[i]))
+ m_filterHeaders.AppendElement(servArray[i]);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::CleanUp()
+{
+ // here we make sure that there aren't missing articles in the unread set
+ // So if an article is the unread set, and the known arts set, but isn't in the
+ // db, then we should mark it read in the unread set.
+ if (m_newsDB)
+ {
+ if (m_knownArts.set && m_knownArts.set->getLength() && m_set->getLength())
+ {
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ m_newsDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ int32_t firstKnown = m_knownArts.set->GetFirstMember();
+ int32_t lastKnown = m_knownArts.set->GetLastMember();
+ if (folderInfo)
+ {
+ uint32_t lastMissingCheck;
+ folderInfo->GetUint32Property("lastMissingCheck", 0, &lastMissingCheck);
+ if (lastMissingCheck)
+ firstKnown = lastMissingCheck + 1;
+ }
+ bool foundMissingArticle = false;
+ while (firstKnown <= lastKnown)
+ {
+ int32_t firstUnreadStart, firstUnreadEnd;
+ if (firstKnown == 0)
+ firstKnown = 1;
+ m_set->FirstMissingRange(firstKnown, lastKnown, &firstUnreadStart, &firstUnreadEnd);
+ if (firstUnreadStart)
+ {
+ while (firstUnreadStart <= firstUnreadEnd)
+ {
+ bool containsKey;
+ m_newsDB->ContainsKey(firstUnreadStart, &containsKey);
+ if (!containsKey)
+ {
+ m_set->Add(firstUnreadStart);
+ foundMissingArticle = true;
+ }
+ firstUnreadStart++;
+ }
+ firstKnown = firstUnreadStart;
+ }
+ else
+ break;
+
+ }
+ if (folderInfo)
+ folderInfo->SetUint32Property("lastMissingCheck", lastKnown);
+
+ if (foundMissingArticle)
+ {
+ nsresult rv;
+ nsCOMPtr<nsINewsDatabase> db(do_QueryInterface(m_newsDB, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+ db->SetReadSet(m_set);
+ }
+ }
+ m_newsDB->Commit(nsMsgDBCommitType::kSessionCommit);
+ m_newsDB->Close(true);
+ m_newsDB = nullptr;
+ }
+
+ if (m_knownArts.set)
+ {
+ delete m_knownArts.set;
+ m_knownArts.set = nullptr;
+ }
+ if (m_newsFolder)
+ m_newsFolder->NotifyFinishedDownloadinghdrs();
+
+ m_newsFolder = nullptr;
+ m_runningURL = nullptr;
+
+ return NS_OK;
+}
+
+#ifdef HAVE_CHANGELISTENER
+void nsNNTPNewsgroupList::OnAnnouncerGoingAway (ChangeAnnouncer *instigator)
+{
+}
+#endif
+
+static nsresult
+openWindow(nsIMsgWindow *aMsgWindow, const char *chromeURL,
+ nsINewsDownloadDialogArgs *param)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+ nsCOMPtr<nsIDocShell> docShell;
+ rv = aMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow(do_GetInterface(docShell));
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow = nsPIDOMWindowOuter::From(domWindow);
+ parentWindow = parentWindow->GetOuterWindow();
+ NS_ENSURE_ARG_POINTER(parentWindow);
+
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ifptr->SetData(param);
+ ifptr->SetDataIID(&NS_GET_IID(nsINewsDownloadDialogArgs));
+
+ nsCOMPtr<nsPIDOMWindowOuter> dialogWindow;
+ rv = parentWindow->OpenDialog(NS_ConvertASCIItoUTF16(chromeURL),
+ NS_LITERAL_STRING("_blank"),
+ NS_LITERAL_STRING("centerscreen,chrome,modal,titlebar"),
+ ifptr, getter_AddRefs(dialogWindow));
+
+ return rv;
+}
+
+nsresult
+nsNNTPNewsgroupList::GetRangeOfArtsToDownload(nsIMsgWindow *aMsgWindow,
+ int32_t first_possible,
+ int32_t last_possible,
+ int32_t maxextra,
+ int32_t *first,
+ int32_t *last,
+ int32_t *status)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG_POINTER(first);
+ NS_ENSURE_ARG_POINTER(last);
+ NS_ENSURE_ARG_POINTER(status);
+ *first = 0;
+ *last = 0;
+
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_msgWindow = aMsgWindow;
+
+ nsCOMPtr<nsINewsDatabase> db(do_QueryInterface(m_newsDB, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = db->GetReadSet(&m_set);
+ if (NS_FAILED(rv) || !m_set)
+ return rv;
+
+ m_set->SetLastMember(last_possible); // make sure highwater mark is valid.
+
+ nsCOMPtr <nsIDBFolderInfo> newsGroupInfo;
+ rv = m_newsDB->GetDBFolderInfo(getter_AddRefs(newsGroupInfo));
+ if (NS_SUCCEEDED(rv) && newsGroupInfo) {
+ nsCString knownArtsString;
+ nsMsgKey mark;
+ newsGroupInfo->GetKnownArtsSet(getter_Copies(knownArtsString));
+
+ rv = newsGroupInfo->GetHighWater(&mark);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (last_possible < ((int32_t)mark))
+ newsGroupInfo->SetHighWater(last_possible);
+ if (m_knownArts.set)
+ delete m_knownArts.set;
+ m_knownArts.set = nsMsgKeySet::Create(knownArtsString.get());
+ }
+ else
+ {
+ if (m_knownArts.set)
+ delete m_knownArts.set;
+ m_knownArts.set = nsMsgKeySet::Create();
+ nsMsgKey low, high;
+ rv = m_newsDB->GetLowWaterArticleNum(&low);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = m_newsDB->GetHighWaterArticleNum(&high);
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_knownArts.set->AddRange(low,high);
+ }
+
+ if (m_knownArts.set->IsMember(last_possible)) {
+ nsString statusString;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = bundle->GetStringFromName(u"noNewMessages", getter_Copies(statusString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetProgressStatus(statusString.get());
+ }
+
+ if (maxextra <= 0 || last_possible < first_possible || last_possible < 1)
+ {
+ *status=0;
+ return NS_OK;
+ }
+
+ m_knownArts.first_possible = first_possible;
+ m_knownArts.last_possible = last_possible;
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ /* Determine if we only want to get just new articles or more messages.
+ If there are new articles at the end we haven't seen, we always want to get those first.
+ Otherwise, we get the newest articles we haven't gotten, if we're getting more.
+ My thought for now is that opening a newsgroup should only try to get new articles.
+ Selecting "More Messages" will first try to get unseen messages, then old messages. */
+
+ if (m_getOldMessages || !m_knownArts.set->IsMember(last_possible))
+ {
+ bool notifyMaxExceededOn = true;
+ rv = nntpServer->GetNotifyOn(&notifyMaxExceededOn);
+ if (NS_FAILED(rv)) notifyMaxExceededOn = true;
+
+ // if the preference to notify when downloading more than x headers is not on,
+ // and we're downloading new headers, set maxextra to a very large number.
+ if (!m_getOldMessages && !notifyMaxExceededOn)
+ maxextra = 0x7FFFFFFFL;
+ int result =
+ m_knownArts.set->LastMissingRange(first_possible, last_possible,
+ first, last);
+ if (result < 0) {
+ *status=result;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ if (*first > 0 && *last - *first >= maxextra)
+ {
+ if (!m_getOldMessages && !m_promptedAlready && notifyMaxExceededOn)
+ {
+ m_downloadAll = false;
+ nsCOMPtr<nsINewsDownloadDialogArgs> args = do_CreateInstance("@mozilla.org/messenger/newsdownloaddialogargs;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = args->SetArticleCount(*last - *first + 1);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString groupName;
+ rv = m_newsFolder->GetUnicodeName(groupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = args->SetGroupName(groupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // get the server key
+ nsCString serverKey;
+ rv = server->GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = args->SetServerKey(serverKey.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // we many not have a msgWindow if we are running an autosubscribe url from the browser
+ // and there isn't a 3 pane open.
+ //
+ // if we don't have one, bad things will happen when we fail to open up the "download headers dialog"
+ // (we will subscribe to the newsgroup, but it will appear like there are no messages!)
+ //
+ // for now, act like the "download headers dialog" came up, and the user hit cancel. (very safe)
+ //
+ // TODO, figure out why we aren't opening and using a 3 pane when the autosubscribe url is run.
+ // perhaps we can find an available 3 pane, and use it.
+
+ bool download = false;
+
+ if (aMsgWindow) {
+ rv = openWindow(aMsgWindow, DOWNLOAD_HEADERS_URL, args);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = args->GetHitOK(&download);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (download) {
+ rv = args->GetDownloadAll(&m_downloadAll);
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_maxArticles = 0;
+ rv = nntpServer->GetMaxArticles(&m_maxArticles);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ maxextra = m_maxArticles;
+ if (!m_downloadAll)
+ {
+ bool markOldRead = false;
+
+ rv = nntpServer->GetMarkOldRead(&markOldRead);
+ if (NS_FAILED(rv)) markOldRead = false;
+
+ if (markOldRead && m_set)
+ m_set->AddRange(*first, *last - maxextra);
+ *first = *last - maxextra + 1;
+ }
+ }
+ else
+ *first = *last = 0;
+ m_promptedAlready = true;
+ }
+ else if (m_promptedAlready && !m_downloadAll)
+ *first = *last - m_maxArticles + 1;
+ else if (!m_downloadAll)
+ *first = *last - maxextra + 1;
+ }
+ }
+
+ m_firstMsgToDownload = *first;
+ m_lastMsgToDownload = *last;
+ *status=0;
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::AddToKnownArticles(int32_t first, int32_t last)
+{
+ nsresult status;
+
+ if (!m_knownArts.set)
+ {
+ m_knownArts.set = nsMsgKeySet::Create();
+ if (!m_knownArts.set)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // XXX Casting int to nsresult
+ status = static_cast<nsresult>(m_knownArts.set->AddRange(first, last));
+
+ if (m_newsDB) {
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIDBFolderInfo> newsGroupInfo;
+ rv = m_newsDB->GetDBFolderInfo(getter_AddRefs(newsGroupInfo));
+ if (NS_SUCCEEDED(rv) && newsGroupInfo) {
+ nsCString output;
+ status = m_knownArts.set->Output(getter_Copies(output));
+ if (!output.IsEmpty())
+ newsGroupInfo->SetKnownArtsSet(output.get());
+ }
+ }
+ return status;
+}
+
+nsresult
+nsNNTPNewsgroupList::InitXOVER(int32_t first_msg, int32_t last_msg)
+{
+ /* Consistency checks, not that I know what to do if it fails (it will
+ probably handle it OK...) */
+ NS_ASSERTION(first_msg <= last_msg, "first > last");
+
+ /* If any XOVER lines from the last time failed to come in, mark those
+ messages as read. */
+ if (m_lastProcessedNumber < m_lastMsgNumber)
+ {
+ m_set->AddRange(m_lastProcessedNumber + 1, m_lastMsgNumber);
+ }
+ m_firstMsgNumber = first_msg;
+ m_lastMsgNumber = last_msg;
+ m_lastProcessedNumber = first_msg > 1 ? first_msg - 1 : 1;
+ m_currentXHDRIndex = -1;
+ return NS_OK;
+}
+
+// from RFC 822, don't translate
+#define FROM_HEADER "From: "
+#define SUBJECT_HEADER "Subject: "
+#define DATE_HEADER "Date: "
+
+nsresult
+nsNNTPNewsgroupList::ParseLine(char *line, uint32_t * message_number)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr <nsIMsgDBHdr> newMsgHdr;
+
+ if (!line || !message_number) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ char *next = line;
+
+#define GET_TOKEN() \
+ line = next; \
+ next = (line ? PL_strchr (line, '\t') : 0); \
+ if (next) *next++ = 0
+
+ GET_TOKEN (); /* message number */
+ *message_number = atol(line);
+
+ if (atol(line) == 0) /* bogus xover data */
+ return NS_ERROR_UNEXPECTED;
+
+ m_newsDB->CreateNewHdr(*message_number, getter_AddRefs(newMsgHdr));
+
+ NS_ASSERTION(newMsgHdr, "CreateNewHdr didn't fail, but it returned a null newMsgHdr");
+ if (!newMsgHdr)
+ return NS_ERROR_NULL_POINTER;
+
+ GET_TOKEN (); /* subject */
+ if (line) {
+ const char *subject = line; /* #### const evilness */
+
+ uint32_t flags = 0;
+ // ### should call IsHeaderRead here...
+ /* strip "Re: " */
+ nsCString modifiedSubject;
+ if (NS_MsgStripRE(nsDependentCString(subject), modifiedSubject))
+ (void) newMsgHdr->OrFlags(nsMsgMessageFlags::HasRe, &flags);
+
+ // this will make sure read flags agree with newsrc
+ if (! (flags & nsMsgMessageFlags::Read))
+ rv = newMsgHdr->OrFlags(nsMsgMessageFlags::New, &flags);
+
+ rv = newMsgHdr->SetSubject(modifiedSubject.IsEmpty() ? subject : modifiedSubject.get());
+
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ GET_TOKEN (); /* author */
+ if (line) {
+ rv = newMsgHdr->SetAuthor(line);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ GET_TOKEN ();
+ if (line) {
+ PRTime date;
+ PRStatus status = PR_ParseTimeString (line, false, &date);
+ if (PR_SUCCESS == status) {
+ rv = newMsgHdr->SetDate(date); /* date */
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ GET_TOKEN (); /* message id */
+ if (line) {
+ char *strippedId = line;
+ if (strippedId[0] == '<')
+ strippedId++;
+ char * lastChar = strippedId + PL_strlen(strippedId) -1;
+
+ if (*lastChar == '>')
+ *lastChar = '\0';
+
+ rv = newMsgHdr->SetMessageId(strippedId);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ GET_TOKEN (); /* references */
+ if (line) {
+ rv = newMsgHdr->SetReferences(line);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ GET_TOKEN (); /* bytes */
+ if (line) {
+ uint32_t msgSize = 0;
+ msgSize = (line) ? atol (line) : 0;
+
+ rv = newMsgHdr->SetMessageSize(msgSize);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ GET_TOKEN (); /* lines */
+ if (line) {
+ uint32_t numLines = 0;
+ numLines = line ? atol (line) : 0;
+ rv = newMsgHdr->SetLineCount(numLines);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ GET_TOKEN (); /* xref */
+
+ m_newHeaders.AppendObject(newMsgHdr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPNewsgroupList::ApplyFilterHit(nsIMsgFilter *aFilter, nsIMsgWindow *aMsgWindow, bool *aApplyMore)
+{
+ NS_ENSURE_ARG_POINTER(aFilter);
+ NS_ENSURE_ARG_POINTER(aApplyMore);
+ NS_ENSURE_TRUE(m_newMsgHdr, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(m_newsDB, NS_ERROR_UNEXPECTED);
+
+ // you can't move news messages, so applyMore is always true
+ *aApplyMore = true;
+
+ nsCOMPtr<nsIArray> filterActionList;
+
+ nsresult rv = aFilter->GetSortedActionList(getter_AddRefs(filterActionList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numActions;
+ rv = filterActionList->GetLength(&numActions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool loggingEnabled = false;
+ nsCOMPtr<nsIMsgFilterList> currentFilterList;
+ rv = aFilter->GetFilterList(getter_AddRefs(currentFilterList));
+ if (NS_SUCCEEDED(rv) && currentFilterList && numActions)
+ currentFilterList->GetLoggingEnabled(&loggingEnabled);
+
+ for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++)
+ {
+ nsCOMPtr<nsIMsgRuleAction> filterAction;
+ rv = filterActionList->QueryElementAt(actionIndex, NS_GET_IID(nsIMsgRuleAction),
+ getter_AddRefs(filterAction));
+ if (NS_FAILED(rv) || !filterAction)
+ continue;
+
+ nsMsgRuleActionType actionType;
+ if (NS_SUCCEEDED(filterAction->GetType(&actionType)))
+ {
+ if (loggingEnabled)
+ (void) aFilter->LogRuleHit(filterAction, m_newMsgHdr);
+
+ switch (actionType)
+ {
+ case nsMsgFilterAction::Delete:
+ m_addHdrToDB = false;
+ break;
+ case nsMsgFilterAction::MarkRead:
+ m_newsDB->MarkHdrRead(m_newMsgHdr, true, nullptr);
+ break;
+ case nsMsgFilterAction::MarkUnread:
+ m_newsDB->MarkHdrRead(m_newMsgHdr, false, nullptr);
+ break;
+ case nsMsgFilterAction::KillThread:
+ m_newMsgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored);
+ break;
+ case nsMsgFilterAction::KillSubthread:
+ {
+ uint32_t newFlags;
+ m_newMsgHdr->OrFlags(nsMsgMessageFlags::Ignored, &newFlags);
+ }
+ break;
+ case nsMsgFilterAction::WatchThread:
+ {
+ uint32_t newFlags;
+ m_newMsgHdr->OrFlags(nsMsgMessageFlags::Watched, &newFlags);
+ }
+ break;
+ case nsMsgFilterAction::MarkFlagged:
+ m_newMsgHdr->MarkFlagged(true);
+ break;
+ case nsMsgFilterAction::ChangePriority:
+ {
+ nsMsgPriorityValue filterPriority;
+ filterAction->GetPriority(&filterPriority);
+ m_newMsgHdr->SetPriority(filterPriority);
+ }
+ break;
+ case nsMsgFilterAction::AddTag:
+ {
+ nsCString keyword;
+ filterAction->GetStrValue(keyword);
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ messageArray->AppendElement(m_newMsgHdr, false);
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ if (folder)
+ folder->AddKeywordsToMessages(messageArray, keyword);
+ break;
+ }
+ case nsMsgFilterAction::Label:
+ {
+ nsMsgLabelValue filterLabel;
+ filterAction->GetLabel(&filterLabel);
+ nsMsgKey msgKey;
+ m_newMsgHdr->GetMessageKey(&msgKey);
+ m_newsDB->SetLabel(msgKey, filterLabel);
+ }
+ break;
+
+ case nsMsgFilterAction::StopExecution:
+ {
+ // don't apply any more filters
+ *aApplyMore = false;
+ }
+ break;
+
+ case nsMsgFilterAction::Custom:
+ {
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString value;
+ filterAction->GetStrValue(value);
+
+ nsCOMPtr<nsIMutableArray> messageArray(
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(messageArray, rv);
+ messageArray->AppendElement(m_newMsgHdr, false);
+
+ customAction->Apply(messageArray, value, nullptr,
+ nsMsgFilterType::NewsRule, aMsgWindow);
+ }
+ break;
+
+ default:
+ NS_ERROR("unexpected action");
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::ProcessXOVERLINE(const char *line, uint32_t *status)
+{
+ uint32_t message_number=0;
+ // int32_t lines;
+ nsresult rv = NS_OK;
+
+ NS_ASSERTION(line, "null ptr");
+ if (!line)
+ return NS_ERROR_NULL_POINTER;
+
+ if (m_newsDB)
+ {
+ char *xoverline = PL_strdup(line);
+ if (!xoverline)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = ParseLine(xoverline, &message_number);
+ PL_strfree(xoverline);
+ xoverline = nullptr;
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NS_ASSERTION(message_number > m_lastProcessedNumber ||
+ message_number == 1, "bad message_number");
+ if (m_set && message_number > m_lastProcessedNumber + 1)
+ {
+ /* There are some articles that XOVER skipped; they must no longer
+ exist. Mark them as read in the newsrc, so we don't include them
+ next time in our estimated number of unread messages. */
+ if (m_set->AddRange(m_lastProcessedNumber + 1, message_number - 1))
+ {
+ /* This isn't really an important enough change to warrant causing
+ the newsrc file to be saved; we haven't gathered any information
+ that won't also be gathered for free next time. */
+ }
+ }
+
+ m_lastProcessedNumber = message_number;
+ if (m_knownArts.set)
+ {
+ int result = m_knownArts.set->Add(message_number);
+ if (result < 0) {
+ if (status)
+ *status = result;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ }
+
+ if (message_number > m_lastMsgNumber)
+ m_lastMsgNumber = message_number;
+ else if (message_number < m_firstMsgNumber)
+ m_firstMsgNumber = message_number;
+
+ if (m_set) {
+ (void) m_set->IsMember(message_number);
+ }
+
+ /* Update the progress meter with a percentage of articles retrieved */
+ if (m_lastMsgNumber > m_firstMsgNumber)
+ {
+ int32_t totalToDownload = m_lastMsgToDownload - m_firstMsgToDownload + 1;
+ int32_t lastIndex = m_lastProcessedNumber - m_firstMsgNumber + 1;
+ int32_t numDownloaded = lastIndex;
+ int32_t totIndex = m_lastMsgNumber - m_firstMsgNumber + 1;
+
+ PRTime elapsedTime = PR_Now() - m_lastStatusUpdate;
+
+ if (elapsedTime > MIN_STATUS_UPDATE_INTERVAL || lastIndex == totIndex)
+ UpdateStatus(false, numDownloaded, totalToDownload);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::ResetXOVER()
+{
+ m_lastMsgNumber = m_firstMsgNumber;
+ m_lastProcessedNumber = m_lastMsgNumber;
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::FinishXOVERLINE(int status, int *newstatus)
+{
+ nsresult rv;
+ struct MSG_NewsKnown* k;
+
+ /* If any XOVER lines from the last time failed to come in, mark those
+ messages as read. */
+
+ if (status >= 0 && m_lastProcessedNumber < m_lastMsgNumber) {
+ m_set->AddRange(m_lastProcessedNumber + 1, m_lastMsgNumber);
+ }
+
+ if (m_lastProcessedNumber)
+ AddToKnownArticles(m_firstMsgNumber, m_lastProcessedNumber);
+
+ k = &m_knownArts;
+
+ if (k && k->set)
+ {
+ int32_t n = k->set->FirstNonMember();
+ if (n < k->first_possible || n > k->last_possible)
+ {
+ /* We know we've gotten all there is to know.
+ Take advantage of that to update our counts... */
+ // ### dmb
+ }
+ }
+
+ if (!m_finishingXover)
+ {
+ // turn on m_finishingXover - this is a horrible hack to avoid recursive
+ // calls which happen when the fe selects a message as a result of getting EndingUpdate,
+ // which interrupts this url right before it was going to finish and causes FinishXOver
+ // to get called again.
+ m_finishingXover = true;
+
+ // XXX is this correct?
+ m_runningURL = nullptr;
+
+ if (m_lastMsgNumber > 0) {
+ nsAutoString firstStr;
+ firstStr.AppendInt(m_lastProcessedNumber - m_firstMsgNumber + 1);
+
+ nsAutoString lastStr;
+ lastStr.AppendInt(m_lastMsgNumber - m_firstMsgNumber + 1);
+
+ nsString statusString;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char16_t *formatStrings[2] = { firstStr.get(), lastStr.get() };
+ rv = bundle->FormatStringFromName(u"downloadingArticles", formatStrings, 2, getter_Copies(statusString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetProgressStatus(statusString.get());
+ }
+ }
+
+ if (newstatus)
+ *newstatus=0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPNewsgroupList::InitXHDR(nsACString &header)
+{
+ if (++m_currentXHDRIndex >= m_filterHeaders.Length())
+ header.Truncate();
+ else
+ header.Assign(m_filterHeaders[m_currentXHDRIndex]);
+ // Don't include these in our XHDR bouts, as they are already provided through
+ // XOVER.
+ if (header.EqualsLiteral("message-id") ||
+ header.EqualsLiteral("references"))
+ return InitXHDR(header);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPNewsgroupList::ProcessXHDRLine(const nsACString &line)
+{
+ int32_t middle = line.FindChar(' ');
+ nsCString value, key = PromiseFlatCString(line);
+ if (middle == -1)
+ return NS_OK;
+ value = Substring(line, middle+1);
+ key.SetLength((uint32_t)middle);
+
+ // According to RFC 2980, some will send (none) instead.
+ // So we don't treat this is an error.
+ if (key.CharAt(0) < '0' || key.CharAt(0) > '9')
+ return NS_OK;
+
+ nsresult rv;
+ int32_t number = key.ToInteger(&rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ // RFC 2980 specifies one or more spaces.
+ value.Trim(" ");
+
+ nsCOMPtr<nsIMsgDBHdr> header;
+ rv = m_newsDB->GetMsgHdrForKey(number, getter_AddRefs(header));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = header->SetStringProperty(m_filterHeaders[m_currentXHDRIndex].get(), value.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t totalToDownload = m_lastMsgToDownload - m_firstMsgToDownload + 1;
+ int32_t numDownloaded = number - m_firstMsgNumber + 1;
+
+ PRTime elapsedTime = PR_Now() - m_lastStatusUpdate;
+
+ if (elapsedTime > MIN_STATUS_UPDATE_INTERVAL)
+ UpdateStatus(true, numDownloaded, totalToDownload);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNNTPNewsgroupList::InitHEAD(int32_t number)
+{
+ if (m_newMsgHdr)
+ {
+ // Finish processing for this header
+ // If HEAD didn't properly return, then the header won't be set
+ m_newHeaders.AppendObject(m_newMsgHdr);
+
+ int32_t totalToDownload = m_lastMsgToDownload - m_firstMsgToDownload + 1;
+ int32_t lastIndex = m_lastProcessedNumber - m_firstMsgNumber + 1;
+ int32_t numDownloaded = lastIndex;
+ int32_t totIndex = m_lastMsgNumber - m_firstMsgNumber + 1;
+
+ PRTime elapsedTime = PR_Now() - m_lastStatusUpdate;
+
+ if (elapsedTime > MIN_STATUS_UPDATE_INTERVAL || lastIndex == totIndex)
+ UpdateStatus(false, numDownloaded, totalToDownload);
+ }
+
+ if (number >= 0)
+ {
+ if (m_newHeaders.Count() > 0 && m_lastMsgNumber == m_lastProcessedNumber)
+ {
+ // We have done some processing of messages. This means that we have
+ // relics of headers from XOVER. Since we will get everything from HEAD
+ // anyways, just clear the array.
+ m_newHeaders.Clear();
+ }
+
+ nsresult rv = m_newsDB->CreateNewHdr(number, getter_AddRefs(m_newMsgHdr));
+ m_lastProcessedNumber = number;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ AddToKnownArticles(m_firstMsgNumber, m_lastProcessedNumber);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPNewsgroupList::HEADFailed(int32_t number)
+{
+ m_set->Add(number);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNNTPNewsgroupList::ProcessHEADLine(const nsACString &line)
+{
+ int32_t colon = line.FindChar(':');
+ nsCString header = PromiseFlatCString(line), value;
+ if (colon != -1)
+ {
+ value = Substring(line, colon+1);
+ header.SetLength((uint32_t)colon);
+ }
+ else if (line.CharAt(0) == ' ' || line.CharAt(0) == '\t') // We are continuing the header
+ {
+ m_thisLine += header; // Preserve whitespace (should we?)
+ return NS_OK;
+ }
+ else
+ {
+ return NS_OK; // We are malformed. Just ignore and hope for the best...
+ }
+
+ nsresult rv;
+ if (!m_lastHeader.IsEmpty())
+ {
+ rv = AddHeader(m_lastHeader.get(), m_thisLine.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ value.Trim(" ");
+
+ ToLowerCase(header, m_lastHeader);
+ m_thisLine.Assign(value);
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupList::AddHeader(const char *header, const char *value)
+{
+ nsresult rv = NS_OK;
+ // The From, Date, and Subject headers have special requirements.
+ if (PL_strcmp(header, "from") == 0)
+ {
+ rv = m_newMsgHdr->SetAuthor(value);
+ }
+ else if (PL_strcmp(header, "date") == 0)
+ {
+ PRTime date;
+ PRStatus status = PR_ParseTimeString (value, false, &date);
+ if (PR_SUCCESS == status)
+ rv = m_newMsgHdr->SetDate(date);
+ }
+ else if (PL_strcmp(header, "subject") == 0)
+ {
+ const char *subject = value;
+
+ uint32_t flags = 0;
+ // ### should call IsHeaderRead here...
+ /* strip "Re: " */
+ nsCString modifiedSubject;
+ if (NS_MsgStripRE(nsDependentCString(subject), modifiedSubject))
+ // this will make sure read flags agree with newsrc
+ (void) m_newMsgHdr->OrFlags(nsMsgMessageFlags::HasRe, &flags);
+
+ if (! (flags & nsMsgMessageFlags::Read))
+ rv = m_newMsgHdr->OrFlags(nsMsgMessageFlags::New, &flags);
+
+ rv = m_newMsgHdr->SetSubject(modifiedSubject.IsEmpty() ? subject :
+ modifiedSubject.get());
+ }
+ else if (PL_strcmp(header, "message-id") == 0)
+ {
+ rv = m_newMsgHdr->SetMessageId(value);
+ }
+ else if (PL_strcmp(header, "references") == 0)
+ {
+ rv = m_newMsgHdr->SetReferences(value);
+ }
+ else if (PL_strcmp(header, "bytes") == 0)
+ {
+ rv = m_newMsgHdr->SetMessageSize(atol(value));
+ }
+ else if (PL_strcmp(header, "lines") == 0)
+ {
+ rv = m_newMsgHdr->SetLineCount(atol(value));
+ }
+ else if (m_filterHeaders.Contains(nsDependentCString(header)))
+ {
+ rv = m_newMsgHdr->SetStringProperty(header, value);
+ }
+ return rv;
+}
+
+nsresult
+nsNNTPNewsgroupList::CallFilters()
+{
+ nsresult rv;
+ nsCString filterString;
+
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t filterCount = 0;
+ if (m_filterList)
+ {
+ rv = m_filterList->GetFilterCount(&filterCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ uint32_t serverFilterCount = 0;
+ if (m_serverFilterList)
+ {
+ rv = m_serverFilterList->GetFilterCount(&serverFilterCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ uint32_t count = m_newHeaders.Count();
+
+ // Notify MsgFolderListeners of message adds
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ m_newMsgHdr = m_newHeaders[i];
+ if (!filterCount && !serverFilterCount)
+ {
+ m_newsDB->AddNewHdrToDB(m_newMsgHdr, true);
+
+ if (notifier)
+ notifier->NotifyMsgAdded(m_newMsgHdr);
+ // mark the header as not yet reported classified
+ nsMsgKey msgKey;
+ m_newMsgHdr->GetMessageKey(&msgKey);
+ folder->OrProcessingFlags(msgKey,
+ nsMsgProcessingFlags::NotReportedClassified);
+
+ continue;
+ }
+ m_addHdrToDB = true;
+
+ // build up a "headers" for filter code
+ nsCString subject, author, date;
+ rv = m_newMsgHdr->GetSubject(getter_Copies(subject));
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = m_newMsgHdr->GetAuthor(getter_Copies(author));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString fullHeaders;
+ if (!(author.IsEmpty()))
+ {
+ fullHeaders.AppendLiteral(FROM_HEADER);
+ fullHeaders += author;
+ fullHeaders += '\0';
+ }
+
+ if (!(subject.IsEmpty()))
+ {
+ fullHeaders.AppendLiteral(SUBJECT_HEADER);
+ fullHeaders += subject;
+ fullHeaders += '\0';
+ }
+
+ for (uint32_t header = 0; header < m_filterHeaders.Length(); header++)
+ {
+ nsCString retValue;
+ m_newMsgHdr->GetStringProperty(m_filterHeaders[header].get(),
+ getter_Copies(retValue));
+ if (!retValue.IsEmpty())
+ {
+ fullHeaders += m_filterHeaders[header];
+ fullHeaders.AppendLiteral(": ");
+ fullHeaders += retValue;
+ fullHeaders += '\0';
+ }
+ }
+
+ // The per-newsgroup filters should go first. If something stops filter
+ // execution, then users should be able to override the global filters in
+ // the per-newsgroup filters.
+ if (filterCount)
+ {
+ rv = m_filterList->ApplyFiltersToHdr(nsMsgFilterType::NewsRule,
+ m_newMsgHdr, folder, m_newsDB, fullHeaders.get(),
+ fullHeaders.Length(), this, m_msgWindow);
+ }
+ if (serverFilterCount)
+ {
+ rv = m_serverFilterList->ApplyFiltersToHdr(nsMsgFilterType::NewsRule,
+ m_newMsgHdr, folder, m_newsDB, fullHeaders.get(),
+ fullHeaders.Length(), this, m_msgWindow);
+ }
+
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (m_addHdrToDB)
+ {
+ m_newsDB->AddNewHdrToDB(m_newMsgHdr, true);
+ if (notifier)
+ notifier->NotifyMsgAdded(m_newMsgHdr);
+ // mark the header as not yet reported classified
+ nsMsgKey msgKey;
+ m_newMsgHdr->GetMessageKey(&msgKey);
+ folder->OrProcessingFlags(msgKey,
+ nsMsgProcessingFlags::NotReportedClassified);
+ }
+ }
+ m_newHeaders.Clear();
+ return NS_OK;
+}
+
+void
+nsNNTPNewsgroupList::SetProgressBarPercent(int32_t percent)
+{
+ if (!m_runningURL)
+ return;
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
+ if (mailnewsUrl) {
+ nsCOMPtr <nsIMsgStatusFeedback> feedback;
+ mailnewsUrl->GetStatusFeedback(getter_AddRefs(feedback));
+
+ if (feedback) {
+ feedback->ShowProgress(percent);
+ }
+ }
+}
+
+void
+nsNNTPNewsgroupList::SetProgressStatus(const char16_t *aMessage)
+{
+ if (!m_runningURL)
+ return;
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
+ if (mailnewsUrl) {
+ nsCOMPtr <nsIMsgStatusFeedback> feedback;
+ mailnewsUrl->GetStatusFeedback(getter_AddRefs(feedback));
+
+ if (feedback) {
+ // prepending the account name to the status message.
+ nsresult rv;
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = mailnewsUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ nsString accountName;
+ server->GetPrettyName(accountName);
+ nsString statusMessage;
+ nsCOMPtr<nsIStringBundleService> sbs =
+ mozilla::services::GetStringBundleService();
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = sbs->CreateBundle(MSGS_URL,
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ const char16_t *params[] = { accountName.get(), aMessage };
+ bundle->FormatStringFromName(u"statusMessage",
+ params, 2, getter_Copies(statusMessage));
+
+ feedback->ShowStatusString(statusMessage);
+ }
+ }
+}
+
+void
+nsNNTPNewsgroupList::UpdateStatus(bool filtering, int32_t numDLed, int32_t totToDL)
+{
+ int32_t numerator = (filtering ? m_currentXHDRIndex + 1 : 1) * numDLed;
+ int32_t denominator = (m_filterHeaders.Length() + 1) * totToDL;
+ int32_t percent = numerator * 100 / denominator;
+
+ nsAutoString numDownloadedStr;
+ numDownloadedStr.AppendInt(numDLed);
+
+ nsAutoString totalToDownloadStr;
+ totalToDownloadStr.AppendInt(totToDL);
+
+ nsAutoString newsgroupName;
+ nsresult rv = m_newsFolder->GetUnicodeName(newsgroupName);
+ if (!NS_SUCCEEDED(rv))
+ return;
+
+ nsString statusString;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService)
+ return;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ if (!NS_SUCCEEDED(rv))
+ return;
+
+ if (filtering)
+ {
+ NS_ConvertUTF8toUTF16 header(m_filterHeaders[m_currentXHDRIndex]);
+ const char16_t *formatStrings[4] = { header.get(),
+ numDownloadedStr.get(), totalToDownloadStr.get(), newsgroupName.get() };
+ rv = bundle->FormatStringFromName(u"newNewsgroupFilteringHeaders",
+ formatStrings, 4, getter_Copies(statusString));
+ }
+ else
+ {
+ const char16_t *formatStrings[3] = { numDownloadedStr.get(),
+ totalToDownloadStr.get(), newsgroupName.get() };
+ rv = bundle->FormatStringFromName(u"newNewsgroupHeaders",
+ formatStrings, 3, getter_Copies(statusString));
+ }
+ if (!NS_SUCCEEDED(rv))
+ return;
+
+ SetProgressStatus(statusString.get());
+ m_lastStatusUpdate = PR_Now();
+
+ // only update the progress meter if it has changed
+ if (percent != m_lastPercent)
+ {
+ SetProgressBarPercent(percent);
+ m_lastPercent = percent;
+ }
+}
+
+NS_IMETHODIMP nsNNTPNewsgroupList::SetGetOldMessages(bool aGetOldMessages)
+{
+ m_getOldMessages = aGetOldMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPNewsgroupList::GetGetOldMessages(bool *aGetOldMessages)
+{
+ NS_ENSURE_ARG(aGetOldMessages);
+
+ *aGetOldMessages = m_getOldMessages;
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNNTPNewsgroupList.h b/mailnews/news/src/nsNNTPNewsgroupList.h
new file mode 100644
index 000000000..6b21be32b
--- /dev/null
+++ b/mailnews/news/src/nsNNTPNewsgroupList.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+ * formerly listngst.h
+ * This class should ultimately be part of a news group listing
+ * state machine - either by inheritance or delegation.
+ * Currently, a folder pane owns one and libnet news group listing
+ * related messages get passed to this object.
+ */
+#ifndef nsNNTPNewsgroupListState_h___
+#define nsNNTPNewsgroupListState_h___
+
+#include "nsINNTPNewsgroupList.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgKeySet.h"
+#include "nsINntpUrl.h"
+#include "nsIMsgFilterList.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgWindow.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+
+/* The below is all stuff that we remember for netlib about which
+ articles we've already seen in the current newsgroup. */
+
+typedef struct MSG_NewsKnown {
+ nsMsgKeySet* set; /* Set of articles we've already gotten
+ from the newsserver (if it's marked
+ "read", then we've already gotten it).
+ If an article is marked "read", it
+ doesn't mean we're actually displaying
+ it; it may be an article that no longer
+ exists, or it may be one that we've
+ marked read and we're only viewing
+ unread messages. */
+
+ int32_t first_possible; /* The oldest article in this group. */
+ int32_t last_possible; /* The newest article in this group. */
+
+ bool shouldGetOldest;
+} MSG_NewsKnown;
+
+// This class should ultimately be part of a news group listing
+// state machine - either by inheritance or delegation.
+// Currently, a folder pane owns one and libnet news group listing
+// related messages get passed to this object.
+class nsNNTPNewsgroupList : public nsINNTPNewsgroupList, public nsIMsgFilterHitNotify
+#ifdef HAVE_CHANGELISTENER
+/* ,public ChangeListener */
+#endif
+{
+public:
+ nsNNTPNewsgroupList();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINNTPNEWSGROUPLIST
+ NS_DECL_NSIMSGFILTERHITNOTIFY
+
+private:
+ virtual ~nsNNTPNewsgroupList();
+
+ NS_METHOD CleanUp();
+
+ bool m_finishingXover;
+
+#ifdef HAVE_CHANGELISTENER
+ virtual void OnAnnouncerGoingAway (ChangeAnnouncer *instigator);
+#endif
+ nsresult ParseLine(char *line, uint32_t *message_number);
+ nsresult GetDatabase(const char *uri, nsIMsgDatabase **db);
+ void SetProgressBarPercent(int32_t percent);
+ void SetProgressStatus(const char16_t *aMessage);
+
+ void UpdateStatus(bool filtering, int32_t numDled, int32_t totToDL);
+
+ nsresult AddHeader(const char * header, const char * value);
+protected:
+ bool m_getOldMessages;
+ bool m_promptedAlready;
+ bool m_downloadAll;
+ int32_t m_maxArticles;
+ int32_t m_lastPercent;
+ PRTime m_lastStatusUpdate;
+
+ nsCOMPtr <nsIMsgNewsFolder> m_newsFolder;
+ nsCOMPtr <nsIMsgDatabase> m_newsDB;
+ nsCOMPtr <nsINntpUrl> m_runningURL;
+
+ /**
+ * The last message that we have processed (XOVER or HEAD).
+ */
+ nsMsgKey m_lastProcessedNumber;
+ /**
+ * The endpoints of the message chunk we are actually downloading.
+ */
+ nsMsgKey m_firstMsgNumber, m_lastMsgNumber;
+ /**
+ * The endpoints of the message chunk we are capable of downloading.
+ */
+ int32_t m_firstMsgToDownload, m_lastMsgToDownload;
+
+ struct MSG_NewsKnown m_knownArts;
+ nsMsgKeySet *m_set;
+
+ nsTArray<nsCString> m_filterHeaders;
+ uint32_t m_currentXHDRIndex;
+ nsCString m_lastHeader;
+ nsCString m_thisLine;
+
+private:
+ nsCOMPtr <nsIMsgWindow> m_msgWindow;
+ nsCOMPtr <nsIMsgFilterList> m_filterList;
+ nsCOMPtr <nsIMsgFilterList> m_serverFilterList;
+ nsCOMPtr <nsIMsgDBHdr> m_newMsgHdr; // current message header we're building
+ nsCOMArray<nsIMsgDBHdr> m_newHeaders;
+
+ bool m_addHdrToDB;
+
+};
+
+#endif /* nsNNTPNewsgroupListState_h___ */
+
diff --git a/mailnews/news/src/nsNNTPNewsgroupPost.cpp b/mailnews/news/src/nsNNTPNewsgroupPost.cpp
new file mode 100644
index 000000000..03c67c455
--- /dev/null
+++ b/mailnews/news/src/nsNNTPNewsgroupPost.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nsNNTPNewsgroupPost.h"
+
+NS_IMPL_ISUPPORTS(nsNNTPNewsgroupPost, nsINNTPNewsgroupPost)
+
+nsNNTPNewsgroupPost::nsNNTPNewsgroupPost()
+{
+ m_isControl=false;
+}
+
+nsNNTPNewsgroupPost::~nsNNTPNewsgroupPost()
+{
+}
+
+#define IMPL_GETSET(attribute, member) \
+ NS_IMETHODIMP nsNNTPNewsgroupPost::Get##attribute(char **result) \
+ { \
+ NS_ENSURE_ARG_POINTER(result); \
+ *result = ToNewCString(member); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP nsNNTPNewsgroupPost::Set##attribute(const char *aValue) \
+ { \
+ member.Assign(aValue); \
+ return NS_OK; \
+ }
+
+IMPL_GETSET(RelayVersion, m_header[IDX_HEADER_RELAYVERSION])
+IMPL_GETSET(PostingVersion, m_header[IDX_HEADER_POSTINGVERSION])
+IMPL_GETSET(From, m_header[IDX_HEADER_FROM])
+IMPL_GETSET(Date, m_header[IDX_HEADER_DATE])
+IMPL_GETSET(Subject, m_header[IDX_HEADER_SUBJECT])
+IMPL_GETSET(Path, m_header[IDX_HEADER_PATH])
+IMPL_GETSET(ReplyTo, m_header[IDX_HEADER_REPLYTO])
+IMPL_GETSET(Sender, m_header[IDX_HEADER_SENDER])
+IMPL_GETSET(FollowupTo, m_header[IDX_HEADER_FOLLOWUPTO])
+IMPL_GETSET(DateReceived, m_header[IDX_HEADER_DATERECEIVED])
+IMPL_GETSET(Expires, m_header[IDX_HEADER_EXPIRES])
+IMPL_GETSET(Control, m_header[IDX_HEADER_CONTROL])
+IMPL_GETSET(Distribution, m_header[IDX_HEADER_DISTRIBUTION])
+IMPL_GETSET(Organization, m_header[IDX_HEADER_ORGANIZATION])
+IMPL_GETSET(Body, m_body)
+
+NS_IMETHODIMP nsNNTPNewsgroupPost::GetNewsgroups(char **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = ToNewCString(m_header[IDX_HEADER_NEWSGROUPS]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPNewsgroupPost::GetReferences(char **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = ToNewCString(m_header[IDX_HEADER_REFERENCES]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPNewsgroupPost::GetIsControl(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_isControl;
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupPost::AddNewsgroup(const char *newsgroup)
+{
+ m_header[IDX_HEADER_NEWSGROUPS].AppendLiteral(", ");
+ m_header[IDX_HEADER_NEWSGROUPS].Append(newsgroup);
+ return NS_OK;
+}
+
+
+// the message can be stored in a file....allow accessors for getting and setting
+// the file name to post...
+nsresult
+nsNNTPNewsgroupPost::SetPostMessageFile(nsIFile * aPostMessageFile)
+{
+ m_postMessageFile = aPostMessageFile;
+ return NS_OK;
+}
+
+nsresult
+nsNNTPNewsgroupPost::GetPostMessageFile(nsIFile ** aPostMessageFile)
+{
+ if (aPostMessageFile)
+ NS_IF_ADDREF(*aPostMessageFile = m_postMessageFile);
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNNTPNewsgroupPost.h b/mailnews/news/src/nsNNTPNewsgroupPost.h
new file mode 100644
index 000000000..721b4f1f5
--- /dev/null
+++ b/mailnews/news/src/nsNNTPNewsgroupPost.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsNNTPNewsgroupPost_h
+#define __nsNNTPNewsgroupPost_h
+
+#include "msgCore.h"
+#include "nsINNTPNewsgroupPost.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIFile.h"
+
+#define IDX_HEADER_FROM 0
+#define IDX_HEADER_NEWSGROUPS 1
+#define IDX_HEADER_SUBJECT 2
+
+// set this to the last required header
+#define IDX_HEADER_LAST_REQUIRED IDX_HEADER_SUBJECT
+
+#define IDX_HEADER_PATH 3
+#define IDX_HEADER_DATE 4
+
+#define IDX_HEADER_REPLYTO 5
+#define IDX_HEADER_SENDER 6
+#define IDX_HEADER_FOLLOWUPTO 7
+#define IDX_HEADER_DATERECEIVED 8
+#define IDX_HEADER_EXPIRES 9
+#define IDX_HEADER_CONTROL 10
+#define IDX_HEADER_DISTRIBUTION 11
+#define IDX_HEADER_ORGANIZATION 12
+#define IDX_HEADER_REFERENCES 13
+
+// stuff that's required to be in the message,
+// but probably generated on the server
+#define IDX_HEADER_RELAYVERSION 14
+#define IDX_HEADER_POSTINGVERSION 15
+#define IDX_HEADER_MESSAGEID 16
+
+// keep this in sync with the above
+#define HEADER_LAST IDX_HEADER_MESSAGEID
+
+class nsNNTPNewsgroupPost : public nsINNTPNewsgroupPost {
+
+public:
+ nsNNTPNewsgroupPost();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINNTPNEWSGROUPPOST
+
+private:
+ virtual ~nsNNTPNewsgroupPost();
+
+ nsCOMPtr<nsIFile> m_postMessageFile;
+ nsCString m_header[HEADER_LAST+1];
+ nsCString m_body;
+ bool m_isControl;
+};
+
+#endif /* __nsNNTPNewsgroupPost_h */
diff --git a/mailnews/news/src/nsNNTPProtocol.cpp b/mailnews/news/src/nsNNTPProtocol.cpp
new file mode 100644
index 000000000..c6b4c799b
--- /dev/null
+++ b/mailnews/news/src/nsNNTPProtocol.cpp
@@ -0,0 +1,4777 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "MailNewsTypes.h"
+#include "nntpCore.h"
+#include "nsNetUtil.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgHdr.h"
+#include "nsNNTPProtocol.h"
+#include "nsINNTPArticleList.h"
+#include "nsIOutputStream.h"
+#include "nsIMemory.h"
+#include "nsIPipe.h"
+#include "nsCOMPtr.h"
+#include "nsMsgI18N.h"
+#include "nsINNTPNewsgroupPost.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgNewsCID.h"
+
+#include "nsINntpUrl.h"
+#include "prmem.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "prerror.h"
+#include "nsStringGlue.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Services.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+#include "prprf.h"
+#include <algorithm>
+
+/* include event sink interfaces for news */
+
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIMsgStatusFeedback.h"
+
+#include "nsMsgKeySet.h"
+
+#include "nsNewsUtils.h"
+#include "nsMsgUtils.h"
+
+#include "nsIMsgIdentity.h"
+#include "nsIMsgAccountManager.h"
+
+#include "nsIPrompt.h"
+#include "nsIMsgStatusFeedback.h"
+
+#include "nsIMsgFolder.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIDocShell.h"
+
+#include "nsIMsgFilterList.h"
+
+// for the memory cache...
+#include "nsICacheEntry.h"
+#include "nsICacheStorage.h"
+#include "nsIApplicationCache.h"
+#include "nsIStreamListener.h"
+#include "nsNetCID.h"
+
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+
+#include "nsIMsgWindow.h"
+#include "nsIWindowWatcher.h"
+
+#include "nsINntpService.h"
+#include "nntpCore.h"
+#include "nsIStreamConverterService.h"
+#include "nsIStreamListenerTee.h"
+#include "nsISocketTransport.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+
+#include "nsIInputStreamPump.h"
+#include "nsIProxyInfo.h"
+#include "nsContentSecurityManager.h"
+
+#include <time.h>
+
+#undef GetPort // XXX Windows!
+#undef SetPort // XXX Windows!
+#undef PostMessage // avoid to collision with WinUser.h
+
+#define PREF_NEWS_CANCEL_CONFIRM "news.cancel.confirm"
+#define PREF_NEWS_CANCEL_ALERT_ON_SUCCESS "news.cancel.alert_on_success"
+#define READ_NEWS_LIST_COUNT_MAX 500 /* number of groups to process at a time when reading the list from the server */
+#define READ_NEWS_LIST_TIMEOUT 50 /* uSec to wait until doing more */
+#define RATE_STR_BUF_LEN 32
+#define UPDATE_THRESHHOLD 25600 /* only update every 25 KB */
+
+using namespace mozilla::mailnews;
+using namespace mozilla;
+
+// NNTP extensions are supported yet
+// until the extension code is ported,
+// we'll skip right to the first nntp command
+// after doing "mode reader"
+// and "pushed" authentication (if necessary),
+//#define HAVE_NNTP_EXTENSIONS
+
+// quiet compiler warnings by defining these function prototypes
+char *MSG_UnEscapeSearchUrl (const char *commandSpecificData);
+
+/* Logging stuff */
+
+PRLogModuleInfo* NNTP = NULL;
+#define out LogLevel::Info
+
+#define NNTP_LOG_READ(buf) \
+if (NNTP==NULL) \
+ NNTP = PR_NewLogModule("NNTP"); \
+MOZ_LOG(NNTP, out, ("(%p) Receiving: %s", this, buf)) ;
+
+#define NNTP_LOG_WRITE(buf) \
+if (NNTP==NULL) \
+ NNTP = PR_NewLogModule("NNTP"); \
+MOZ_LOG(NNTP, out, ("(%p) Sending: %s", this, buf)) ;
+
+#define NNTP_LOG_NOTE(buf) \
+if (NNTP==NULL) \
+ NNTP = PR_NewLogModule("NNTP"); \
+MOZ_LOG(NNTP, out, ("(%p) %s",this, buf)) ;
+
+const char *const stateLabels[] = {
+"NNTP_RESPONSE",
+#ifdef BLOCK_UNTIL_AVAILABLE_CONNECTION
+"NNTP_BLOCK_UNTIL_CONNECTIONS_ARE_AVAILABLE",
+"NNTP_CONNECTIONS_ARE_AVAILABLE",
+#endif
+"NNTP_CONNECT",
+"NNTP_CONNECT_WAIT",
+"NNTP_LOGIN_RESPONSE",
+"NNTP_SEND_MODE_READER",
+"NNTP_SEND_MODE_READER_RESPONSE",
+"SEND_LIST_EXTENSIONS",
+"SEND_LIST_EXTENSIONS_RESPONSE",
+"SEND_LIST_SEARCHES",
+"SEND_LIST_SEARCHES_RESPONSE",
+"NNTP_LIST_SEARCH_HEADERS",
+"NNTP_LIST_SEARCH_HEADERS_RESPONSE",
+"NNTP_GET_PROPERTIES",
+"NNTP_GET_PROPERTIES_RESPONSE",
+"SEND_LIST_SUBSCRIPTIONS",
+"SEND_LIST_SUBSCRIPTIONS_RESPONSE",
+"SEND_FIRST_NNTP_COMMAND",
+"SEND_FIRST_NNTP_COMMAND_RESPONSE",
+"SETUP_NEWS_STREAM",
+"NNTP_BEGIN_AUTHORIZE",
+"NNTP_AUTHORIZE_RESPONSE",
+"NNTP_PASSWORD_RESPONSE",
+"NNTP_READ_LIST_BEGIN",
+"NNTP_READ_LIST",
+"DISPLAY_NEWSGROUPS",
+"NNTP_NEWGROUPS_BEGIN",
+"NNTP_NEWGROUPS",
+"NNTP_BEGIN_ARTICLE",
+"NNTP_READ_ARTICLE",
+"NNTP_XOVER_BEGIN",
+"NNTP_FIGURE_NEXT_CHUNK",
+"NNTP_XOVER_SEND",
+"NNTP_XOVER_RESPONSE",
+"NNTP_XOVER",
+"NEWS_PROCESS_XOVER",
+"NNTP_XHDR_SEND",
+"NNTP_XHDR_RESPONSE",
+"NNTP_READ_GROUP",
+"NNTP_READ_GROUP_RESPONSE",
+"NNTP_READ_GROUP_BODY",
+"NNTP_SEND_GROUP_FOR_ARTICLE",
+"NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE",
+"NNTP_SEND_ARTICLE_NUMBER",
+"NEWS_PROCESS_BODIES",
+"NNTP_PRINT_ARTICLE_HEADERS",
+"NNTP_SEND_POST_DATA",
+"NNTP_SEND_POST_DATA_RESPONSE",
+"NNTP_CHECK_FOR_MESSAGE",
+"NEWS_START_CANCEL",
+"NEWS_DO_CANCEL",
+"NNTP_XPAT_SEND",
+"NNTP_XPAT_RESPONSE",
+"NNTP_SEARCH",
+"NNTP_SEARCH_RESPONSE",
+"NNTP_SEARCH_RESULTS",
+"NNTP_LIST_PRETTY_NAMES",
+"NNTP_LIST_PRETTY_NAMES_RESPONSE",
+"NNTP_LIST_XACTIVE_RESPONSE",
+"NNTP_LIST_XACTIVE",
+"NNTP_LIST_GROUP",
+"NNTP_LIST_GROUP_RESPONSE",
+"NEWS_DONE",
+"NEWS_POST_DONE",
+"NEWS_ERROR",
+"NNTP_ERROR",
+"NEWS_FREE",
+"NNTP_SUSPENDED"
+};
+
+
+/* end logging */
+
+/* Forward declarations */
+
+#define LIST_WANTED 0
+#define ARTICLE_WANTED 1
+#define CANCEL_WANTED 2
+#define GROUP_WANTED 3
+#define NEWS_POST 4
+#define NEW_GROUPS 5
+#define SEARCH_WANTED 6
+#define IDS_WANTED 7
+
+/* the output_buffer_size must be larger than the largest possible line
+ * 2000 seems good for news
+ *
+ * jwz: I increased this to 4k since it must be big enough to hold the
+ * entire button-bar HTML, and with the new "mailto" format, that can
+ * contain arbitrarily long header fields like "references".
+ *
+ * fortezza: proxy auth is huge, buffer increased to 8k (sigh).
+ */
+#define OUTPUT_BUFFER_SIZE (4096*2)
+
+/* the amount of time to subtract from the machine time
+ * for the newgroup command sent to the nntp server
+ */
+#define NEWGROUPS_TIME_OFFSET 60L*60L*12L /* 12 hours */
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// TEMPORARY HARD CODED FUNCTIONS
+///////////////////////////////////////////////////////////////////////////////////////////
+#define NET_IS_SPACE(x) ((x)==' ' || (x)=='\t')
+
+// turn "\xx" (with xx being hex numbers) in string into chars
+char *MSG_UnEscapeSearchUrl (const char *commandSpecificData)
+{
+ nsAutoCString result(commandSpecificData);
+ int32_t slashpos = 0;
+ while (slashpos = result.FindChar('\\', slashpos),
+ slashpos != kNotFound)
+ {
+ nsAutoCString hex;
+ hex.Assign(Substring(result, slashpos + 1, 2));
+ int32_t ch;
+ nsresult rv;
+ ch = hex.ToInteger(&rv, 16);
+ result.Replace(slashpos, 3, NS_SUCCEEDED(rv) && ch != 0 ? (char) ch : 'X');
+ slashpos++;
+ }
+ return ToNewCString(result);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// END OF TEMPORARY HARD CODED FUNCTIONS
+///////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNNTPProtocol, nsMsgProtocol, nsINNTPProtocol,
+ nsITimerCallback, nsICacheEntryOpenCallback, nsIMsgAsyncPromptListener)
+
+nsNNTPProtocol::nsNNTPProtocol(nsINntpIncomingServer *aServer, nsIURI *aURL,
+ nsIMsgWindow *aMsgWindow)
+: nsMsgProtocol(aURL),
+ m_connectionBusy(false),
+ m_nntpServer(aServer)
+{
+ if (!NNTP)
+ NNTP = PR_NewLogModule("NNTP");
+
+ m_ProxyServer = nullptr;
+ m_lineStreamBuffer = nullptr;
+ m_responseText = nullptr;
+ m_dataBuf = nullptr;
+
+ m_cancelFromHdr = nullptr;
+ m_cancelNewsgroups = nullptr;
+ m_cancelDistribution = nullptr;
+ m_cancelID = nullptr;
+
+ m_key = nsMsgKey_None;
+
+ mBytesReceived = 0;
+ mBytesReceivedSinceLastStatusUpdate = 0;
+ m_startTime = PR_Now();
+
+ if (aMsgWindow) {
+ m_msgWindow = aMsgWindow;
+ }
+
+ m_runningURL = nullptr;
+ m_fromCache = false;
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) creating",this));
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) initializing, so unset m_currentGroup",this));
+ m_currentGroup.Truncate();
+ m_lastActiveTimeStamp = 0;
+}
+
+nsNNTPProtocol::~nsNNTPProtocol()
+{
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) destroying",this));
+ if (m_nntpServer) {
+ m_nntpServer->WriteNewsrcFile();
+ m_nntpServer->RemoveConnection(this);
+ }
+ if (m_lineStreamBuffer) {
+ delete m_lineStreamBuffer;
+ }
+ if (mUpdateTimer) {
+ mUpdateTimer->Cancel();
+ mUpdateTimer = nullptr;
+ }
+ Cleanup();
+}
+
+void nsNNTPProtocol::Cleanup() //free char* member variables
+{
+ PR_FREEIF(m_responseText);
+ PR_FREEIF(m_dataBuf);
+ PR_FREEIF(m_cancelFromHdr);
+ PR_FREEIF(m_cancelNewsgroups);
+ PR_FREEIF(m_cancelDistribution);
+ PR_FREEIF(m_cancelID);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::Initialize(nsIURI *aURL, nsIMsgWindow *aMsgWindow)
+{
+ if (aMsgWindow) {
+ m_msgWindow = aMsgWindow;
+ }
+ nsMsgProtocol::InitFromURI(aURL);
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer);
+ NS_ASSERTION(m_nntpServer, "nsNNTPProtocol need an m_nntpServer.");
+ NS_ENSURE_TRUE(m_nntpServer, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = m_nntpServer->GetMaxArticles(&m_maxArticles);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port = 0;
+ rv = m_url->GetPort(&port);
+ if (NS_FAILED(rv) || (port<=0)) {
+ rv = server->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (port<=0) {
+ port = (socketType == nsMsgSocketType::SSL) ?
+ nsINntpUrl::DEFAULT_NNTPS_PORT : nsINntpUrl::DEFAULT_NNTP_PORT;
+ }
+
+ rv = m_url->SetPort(port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_PRECONDITION(m_url, "invalid URL passed into NNTP Protocol");
+
+ m_runningURL = do_QueryInterface(m_url, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetIsBusy(true);
+
+ nsCString group;
+
+ // Initialize m_newsAction before possible use in ParseURL method
+ m_runningURL->GetNewsAction(&m_newsAction);
+
+ // parse url to get the msg folder and check if the message is in the folder's
+ // local cache before opening a new socket and trying to download the message
+ rv = ParseURL(m_url, group, m_messageID);
+
+ if (NS_SUCCEEDED(rv) && m_runningURL)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
+ if (mailnewsUrl)
+ {
+ if (aMsgWindow)
+ mailnewsUrl->SetMsgWindow(aMsgWindow);
+
+ if (m_newsAction == nsINntpUrl::ActionFetchArticle || m_newsAction == nsINntpUrl::ActionFetchPart
+ || m_newsAction == nsINntpUrl::ActionSaveMessageToDisk)
+ {
+ // Look for the content length
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_runningURL));
+ if (msgUrl)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ // Note that for attachments, the messageSize is going to be the
+ // size of the entire message
+ uint32_t messageSize;
+ msgHdr->GetMessageSize(&messageSize);
+ SetContentLength(messageSize);
+ }
+ }
+
+ bool msgIsInLocalCache = false;
+ mailnewsUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
+ if (msgIsInLocalCache || WeAreOffline())
+ return NS_OK; // probably don't need to do anything else - definitely don't want
+ // to open the socket.
+ }
+ }
+ }
+ else {
+ return rv;
+ }
+
+ if (!m_socketIsOpen)
+ {
+ // When we are making a secure connection, we need to make sure that we
+ // pass an interface requestor down to the socket transport so that PSM can
+ // retrieve a nsIPrompt instance if needed.
+ nsCOMPtr<nsIInterfaceRequestor> ir;
+ if (socketType != nsMsgSocketType::plain && aMsgWindow)
+ {
+ nsCOMPtr<nsIDocShell> docShell;
+ aMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ ir = do_QueryInterface(docShell);
+ }
+
+ // call base class to set up the transport
+
+ int32_t port = 0;
+ nsCString hostName;
+ m_url->GetPort(&port);
+
+ rv = server->GetRealHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_LOG(NNTP, LogLevel::Info, ("(%p) opening connection to %s on port %d",
+ this, hostName.get(), port));
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ rv = MsgExamineForProxy(this, getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) proxyInfo = nullptr;
+
+ rv = OpenNetworkSocketWithInfo(hostName.get(), port,
+ (socketType == nsMsgSocketType::SSL) ? "ssl" : nullptr,
+ proxyInfo, ir);
+
+ NS_ENSURE_SUCCESS(rv,rv);
+ m_nextState = NNTP_LOGIN_RESPONSE;
+ }
+ else {
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+ }
+ m_dataBuf = (char *) PR_Malloc(sizeof(char) * OUTPUT_BUFFER_SIZE);
+ m_dataBufSize = OUTPUT_BUFFER_SIZE;
+
+ if (!m_lineStreamBuffer)
+ m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true /* create new lines */);
+
+ m_typeWanted = 0;
+ m_responseCode = 0;
+ m_previousResponseCode = 0;
+ m_responseText = nullptr;
+
+ m_firstArticle = 0;
+ m_lastArticle = 0;
+ m_firstPossibleArticle = 0;
+ m_lastPossibleArticle = 0;
+ m_numArticlesLoaded = 0;
+ m_numArticlesWanted = 0;
+
+ m_key = nsMsgKey_None;
+
+ m_articleNumber = 0;
+ m_originalContentLength = 0;
+ m_cancelID = nullptr;
+ m_cancelFromHdr = nullptr;
+ m_cancelNewsgroups = nullptr;
+ m_cancelDistribution = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::GetIsBusy(bool *aIsBusy)
+{
+ NS_ENSURE_ARG_POINTER(aIsBusy);
+ *aIsBusy = m_connectionBusy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::SetIsBusy(bool aIsBusy)
+{
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) setting busy to %d",this, aIsBusy));
+ m_connectionBusy = aIsBusy;
+
+ // Maybe we could load another URI.
+ if (!aIsBusy && m_nntpServer)
+ m_nntpServer->PrepareForNextUrl(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::GetIsCachedConnection(bool *aIsCachedConnection)
+{
+ NS_ENSURE_ARG_POINTER(aIsCachedConnection);
+ *aIsCachedConnection = m_fromCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::SetIsCachedConnection(bool aIsCachedConnection)
+{
+ m_fromCache = aIsCachedConnection;
+ return NS_OK;
+}
+
+/* void GetLastActiveTimeStamp (out PRTime aTimeStamp); */
+NS_IMETHODIMP nsNNTPProtocol::GetLastActiveTimeStamp(PRTime *aTimeStamp)
+{
+ NS_ENSURE_ARG_POINTER(aTimeStamp);
+ *aTimeStamp = m_lastActiveTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::LoadNewsUrl(nsIURI * aURL, nsISupports * aConsumer)
+{
+ // clear the previous channel listener and use the new one....
+ // don't reuse an existing channel listener
+ m_channelListener = nullptr;
+ m_channelListener = do_QueryInterface(aConsumer);
+ nsCOMPtr<nsINntpUrl> newsUrl (do_QueryInterface(aURL));
+ newsUrl->GetNewsAction(&m_newsAction);
+
+ SetupPartExtractorListener(m_channelListener);
+ return LoadUrl(aURL, aConsumer);
+}
+
+
+// WARNING: the cache stream listener is intended to be accessed from the UI thread!
+// it will NOT create another proxy for the stream listener that gets passed in...
+class nsNntpCacheStreamListener : public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsNntpCacheStreamListener ();
+
+ nsresult Init(nsIStreamListener * aStreamListener, nsIChannel* channel, nsIMsgMailNewsUrl *aRunningUrl);
+protected:
+ virtual ~nsNntpCacheStreamListener();
+ nsCOMPtr<nsIChannel> mChannelToUse;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIMsgMailNewsUrl> mRunningUrl;
+};
+
+NS_IMPL_ADDREF(nsNntpCacheStreamListener)
+NS_IMPL_RELEASE(nsNntpCacheStreamListener)
+
+NS_INTERFACE_MAP_BEGIN(nsNntpCacheStreamListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+NS_INTERFACE_MAP_END
+
+nsNntpCacheStreamListener::nsNntpCacheStreamListener()
+{
+}
+
+nsNntpCacheStreamListener::~nsNntpCacheStreamListener()
+{}
+
+nsresult nsNntpCacheStreamListener::Init(nsIStreamListener * aStreamListener, nsIChannel* channel,
+ nsIMsgMailNewsUrl *aRunningUrl)
+{
+ NS_ENSURE_ARG(aStreamListener);
+ NS_ENSURE_ARG(channel);
+
+ mChannelToUse = channel;
+
+ mListener = aStreamListener;
+ mRunningUrl = aRunningUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpCacheStreamListener::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
+{
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ nsCOMPtr <nsIRequest> ourRequest = do_QueryInterface(mChannelToUse);
+
+ NS_ASSERTION(mChannelToUse, "null channel in OnStartRequest");
+ if (mChannelToUse)
+ mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->AddRequest(ourRequest, nullptr /* context isupports */);
+ return (mListener) ? mListener->OnStartRequest(ourRequest, aCtxt) : NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpCacheStreamListener::OnStopRequest(nsIRequest *request, nsISupports * aCtxt, nsresult aStatus)
+{
+ nsCOMPtr <nsIRequest> ourRequest = do_QueryInterface(mChannelToUse);
+ nsresult rv = NS_OK;
+ NS_ASSERTION(mListener, "this assertion is for Bug 531794 comment 7");
+ if (mListener)
+ mListener->OnStopRequest(ourRequest, aCtxt, aStatus);
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ NS_ASSERTION(mChannelToUse, "null channel in OnStopRequest");
+ if (mChannelToUse)
+ mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->RemoveRequest(ourRequest, nullptr, aStatus);
+
+ // clear out mem cache entry so we're not holding onto it.
+ if (mRunningUrl)
+ mRunningUrl->SetMemCacheEntry(nullptr);
+
+ mListener = nullptr;
+ nsCOMPtr <nsINNTPProtocol> nntpProtocol = do_QueryInterface(mChannelToUse);
+ if (nntpProtocol) {
+ rv = nntpProtocol->SetIsBusy(false);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ mChannelToUse = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpCacheStreamListener::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, nsIInputStream * aInStream, uint64_t aSourceOffset, uint32_t aCount)
+{
+ NS_ENSURE_STATE(mListener);
+ nsCOMPtr <nsIRequest> ourRequest = do_QueryInterface(mChannelToUse);
+ return mListener->OnDataAvailable(ourRequest, aCtxt, aInStream, aSourceOffset, aCount);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::GetOriginalURI(nsIURI* *aURI)
+{
+ // News does not seem to have the notion of an original URI (See Bug #193317)
+ *aURI = m_url;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::SetOriginalURI(nsIURI* aURI)
+{
+ // News does not seem to have the notion of an original URI (See Bug #193317)
+ return NS_OK; // ignore
+}
+
+nsresult nsNNTPProtocol::SetupPartExtractorListener(nsIStreamListener * aConsumer)
+{
+ bool convertData = false;
+ nsresult rv = NS_OK;
+
+ if (m_newsAction == nsINntpUrl::ActionFetchArticle)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(m_runningURL, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString queryStr;
+ rv = msgUrl->GetQuery(queryStr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // check if this is a filter plugin requesting the message.
+ // in that case, set up a text converter
+ convertData = (queryStr.Find("header=filter") != kNotFound
+ || queryStr.Find("header=attach") != kNotFound);
+ }
+ else
+ {
+ convertData = (m_newsAction == nsINntpUrl::ActionFetchPart);
+ }
+ if (convertData)
+ {
+ nsCOMPtr<nsIStreamConverterService> converter = do_GetService("@mozilla.org/streamConverters;1");
+ if (converter && aConsumer)
+ {
+ nsCOMPtr<nsIStreamListener> newConsumer;
+ nsCOMPtr<nsIChannel> channel;
+ QueryInterface(NS_GET_IID(nsIChannel), getter_AddRefs(channel));
+ converter->AsyncConvertData("message/rfc822", "*/*",
+ aConsumer, channel, getter_AddRefs(newConsumer));
+ if (newConsumer)
+ m_channelListener = newConsumer;
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::ReadFromMemCache(nsICacheEntry *entry)
+{
+ NS_ENSURE_ARG(entry);
+
+ nsCOMPtr<nsIInputStream> cacheStream;
+ nsresult rv = entry->OpenInputStream(0, getter_AddRefs(cacheStream));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), cacheStream);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString group;
+ // do this to get m_key set, so that marking the message read will work.
+ rv = ParseURL(m_url, group, m_messageID);
+
+ RefPtr<nsNntpCacheStreamListener> cacheListener =
+ new nsNntpCacheStreamListener();
+
+ SetLoadGroup(m_loadGroup);
+ m_typeWanted = ARTICLE_WANTED;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
+ cacheListener->Init(m_channelListener, static_cast<nsIChannel *>(this), mailnewsUrl);
+
+ mContentType = ""; // reset the content type for the upcoming read....
+
+ rv = pump->AsyncRead(cacheListener, m_channelContext);
+
+ if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read should we return
+ {
+ // we're not calling nsMsgProtocol::AsyncRead(), which calls nsNNTPProtocol::LoadUrl, so we need to take care of some stuff it does.
+ m_channelListener = nullptr;
+ return rv;
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::ReadFromNewsConnection()
+{
+ // we might end up here if we thought we had a news message offline
+ // but it turned out not to be so. In which case, we need to
+ // recall Initialize().
+ if (!m_socketIsOpen || !m_dataBuf)
+ {
+ nsresult rv = Initialize(m_url, m_msgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return nsMsgProtocol::AsyncOpen(m_channelListener, m_channelContext);
+}
+
+// for messages stored in our offline cache, we have special code to handle that...
+// If it's in the local cache, we return true and we can abort the download because
+// this method does the rest of the work.
+bool nsNNTPProtocol::ReadFromLocalCache()
+{
+ bool msgIsInLocalCache = false;
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
+ mailnewsUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
+
+ if (msgIsInLocalCache)
+ {
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder);
+ if (folder && NS_SUCCEEDED(rv))
+ {
+ // we want to create a file channel and read the msg from there.
+ nsCOMPtr<nsIInputStream> fileStream;
+ int64_t offset=0;
+ uint32_t size=0;
+ rv = folder->GetOfflineFileStream(m_key, &offset, &size, getter_AddRefs(fileStream));
+
+ // get the file stream from the folder, somehow (through the message or
+ // folder sink?) We also need to set the transfer offset to the message offset
+ if (fileStream && NS_SUCCEEDED(rv))
+ {
+ // dougt - This may break the ablity to "cancel" a read from offline mail reading.
+ // fileChannel->SetLoadGroup(m_loadGroup);
+
+ m_typeWanted = ARTICLE_WANTED;
+
+ RefPtr<nsNntpCacheStreamListener> cacheListener =
+ new nsNntpCacheStreamListener();
+
+ cacheListener->Init(m_channelListener, static_cast<nsIChannel *>(this), mailnewsUrl);
+
+ // create a stream pump that will async read the specified amount of data.
+ // XXX make size 64-bit int
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump),
+ fileStream, offset, (int64_t) size);
+ if (NS_SUCCEEDED(rv))
+ rv = pump->AsyncRead(cacheListener, m_channelContext);
+
+ if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read should we return
+ {
+ mContentType.Truncate();
+ m_channelListener = nullptr;
+ NNTP_LOG_NOTE("Loading message from offline storage");
+ return true;
+ }
+ }
+ else
+ mailnewsUrl->SetMsgIsInLocalCache(false);
+ }
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsNNTPProtocol::OnCacheEntryAvailable(nsICacheEntry *entry, bool aNew, nsIApplicationCache* aAppCache, nsresult status)
+{
+ nsresult rv = NS_OK;
+
+ if (NS_SUCCEEDED(status))
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL, &rv);
+ mailnewsUrl->SetMemCacheEntry(entry);
+
+ // Insert a "stream T" into the flow so data gets written to both.
+ if (aNew)
+ {
+ // use a stream listener Tee to force data into the cache and to our current channel listener...
+ nsCOMPtr<nsIStreamListener> newListener;
+ nsCOMPtr<nsIStreamListenerTee> tee = do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> outStream;
+ rv = entry->OpenOutputStream(0, getter_AddRefs(outStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = tee->Init(m_channelListener, outStream, nullptr);
+ m_channelListener = do_QueryInterface(tee);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ rv = ReadFromMemCache(entry);
+ if (NS_SUCCEEDED(rv)) {
+ entry->MarkValid();
+ return NS_OK; // kick out if reading from the cache succeeded...
+ }
+ }
+ } // if we got a valid entry back from the cache...
+
+ // if reading from the cache failed or if we are writing into the cache, default to ReadFromNewsConnection.
+ return ReadFromNewsConnection();
+}
+
+NS_IMETHODIMP
+nsNNTPProtocol::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache,
+ uint32_t* aResult)
+{
+ *aResult = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::OpenCacheEntry()
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL, &rv);
+ // get the cache session from our nntp service...
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ rv = nntpService->GetCacheStorage(getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Open a cache entry with key = url, no extension.
+ nsCOMPtr<nsIURI> uri;
+ rv = mailnewsUrl->GetBaseURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Truncate of the query part so we don't duplicate urls in the cache for
+ // various message parts.
+ nsCOMPtr<nsIURI> newUri;
+ uri->Clone(getter_AddRefs(newUri));
+ nsAutoCString path;
+ newUri->GetPath(path);
+ int32_t pos = path.FindChar('?');
+ if (pos != kNotFound) {
+ path.SetLength(pos);
+ newUri->SetPath(path);
+ }
+ return cacheStorage->AsyncOpenURI(newUri, EmptyCString(), nsICacheStorage::OPEN_NORMALLY, this);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ int32_t port;
+ rv = mailnewsUrl->GetPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = NS_CheckPortSafety(port, "news");
+ if (NS_FAILED(rv))
+ return rv;
+
+ m_channelContext = ctxt;
+ m_channelListener = listener;
+ m_runningURL->GetNewsAction(&m_newsAction);
+
+ // Before running through the connection, try to see if we can grab the data
+ // from the offline storage or the memory cache. Only actions retrieving
+ // messages can be cached.
+ if (mailnewsUrl && (m_newsAction == nsINntpUrl::ActionFetchArticle ||
+ m_newsAction == nsINntpUrl::ActionFetchPart ||
+ m_newsAction == nsINntpUrl::ActionSaveMessageToDisk))
+ {
+ SetupPartExtractorListener(m_channelListener);
+
+ // Attempt to get the message from the offline storage cache. If this
+ // succeeds, we don't need to use our connection, so tell the server that we
+ // are ready for the next URL.
+ if (ReadFromLocalCache())
+ {
+ if (m_nntpServer)
+ m_nntpServer->PrepareForNextUrl(this);
+ return NS_OK;
+ }
+
+ // If it wasn't offline, try to get the cache from memory. If this call
+ // succeeds, we probably won't need the connection, but the cache might fail
+ // later on. The code there will determine if we need to fallback and will
+ // handle informing the server of our readiness.
+ if (NS_SUCCEEDED(OpenCacheEntry()))
+ return NS_OK;
+ }
+ return nsMsgProtocol::AsyncOpen(listener, ctxt);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+nsresult nsNNTPProtocol::LoadUrl(nsIURI * aURL, nsISupports * aConsumer)
+{
+ NS_ENSURE_ARG_POINTER(aURL);
+
+ nsCString group;
+ mContentType.Truncate();
+ nsresult rv = NS_OK;
+
+ m_runningURL = do_QueryInterface(aURL, &rv);
+ if (NS_FAILED(rv)) return rv;
+ m_runningURL->GetNewsAction(&m_newsAction);
+
+ SetIsBusy(true);
+
+ rv = ParseURL(aURL, group, m_messageID);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to parse news url");
+ //if (NS_FAILED(rv)) return rv;
+ // XXX group returned from ParseURL is assumed to be in UTF-8
+ NS_ASSERTION(MsgIsUTF8(group), "newsgroup name is not in UTF-8");
+ NS_ASSERTION(m_nntpServer, "Parsing must result in an m_nntpServer");
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) m_messageID = %s", this, m_messageID.get()));
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) group = %s", this, group.get()));
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) m_key = %d",this,m_key));
+
+ if (m_newsAction == nsINntpUrl::ActionFetchArticle ||
+ m_newsAction == nsINntpUrl::ActionFetchPart ||
+ m_newsAction == nsINntpUrl::ActionSaveMessageToDisk)
+ m_typeWanted = ARTICLE_WANTED;
+ else if (m_newsAction == nsINntpUrl::ActionCancelArticle)
+ m_typeWanted = CANCEL_WANTED;
+ else if (m_newsAction == nsINntpUrl::ActionPostArticle)
+ {
+ m_typeWanted = NEWS_POST;
+ m_messageID = "";
+ }
+ else if (m_newsAction == nsINntpUrl::ActionListIds)
+ {
+ m_typeWanted = IDS_WANTED;
+ rv = m_nntpServer->FindGroup(group, getter_AddRefs(m_newsFolder));
+ }
+ else if (m_newsAction == nsINntpUrl::ActionSearch)
+ {
+ m_typeWanted = SEARCH_WANTED;
+
+ // Get the search data
+ nsCString commandSpecificData;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(m_runningURL);
+ rv = url->GetQuery(commandSpecificData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MsgUnescapeString(commandSpecificData, 0, m_searchData);
+
+ rv = m_nntpServer->FindGroup(group, getter_AddRefs(m_newsFolder));
+ if (!m_newsFolder)
+ goto FAIL;
+ }
+ else if (m_newsAction == nsINntpUrl::ActionGetNewNews)
+ {
+ bool containsGroup = true;
+ rv = m_nntpServer->ContainsNewsgroup(group, &containsGroup);
+ if (NS_FAILED(rv))
+ goto FAIL;
+
+ if (!containsGroup)
+ {
+ // We have the name of a newsgroup which we're not subscribed to,
+ // the next step is to ask the user whether we should subscribe to it.
+ nsCOMPtr<nsIPrompt> dialog;
+
+ if (m_msgWindow)
+ m_msgWindow->GetPromptDialog(getter_AddRefs(dialog));
+
+ if (!dialog)
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ wwatch->GetNewPrompter(nullptr, getter_AddRefs(dialog));
+ }
+
+ nsString statusString, confirmText;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+
+ // to handle non-ASCII newsgroup names, we store them internally
+ // as escaped. decode and unescape the newsgroup name so we'll
+ // display a proper name.
+
+ nsAutoString unescapedName;
+ rv = NS_MsgDecodeUnescapeURLPath(group, unescapedName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ const char16_t *formatStrings[1] = { unescapedName.get() };
+
+ rv = bundle->FormatStringFromName(
+ u"autoSubscribeText", formatStrings, 1,
+ getter_Copies(confirmText));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool confirmResult = false;
+ rv = dialog->Confirm(nullptr, confirmText.get(), &confirmResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (confirmResult)
+ {
+ rv = m_nntpServer->SubscribeToNewsgroup(group);
+ containsGroup = true;
+ }
+ else
+ {
+ // XXX FIX ME
+ // the way news is current written, we've already opened the socket
+ // and initialized the connection.
+ //
+ // until that is fixed, when the user cancels an autosubscribe, we've got to close it and clean up after ourselves
+ //
+ // see bug http://bugzilla.mozilla.org/show_bug.cgi?id=108293
+ // another problem, autosubscribe urls are ending up as cache entries
+ // because the default action on nntp urls is ActionFetchArticle
+ //
+ // see bug http://bugzilla.mozilla.org/show_bug.cgi?id=108294
+ if (m_runningURL)
+ FinishMemCacheEntry(false); // cleanup mem cache entry
+
+ return CloseConnection();
+ }
+ }
+
+ // If we have a group (since before, or just subscribed), set the m_newsFolder.
+ if (containsGroup)
+ {
+ rv = m_nntpServer->FindGroup(group, getter_AddRefs(m_newsFolder));
+ if (!m_newsFolder)
+ goto FAIL;
+ }
+ m_typeWanted = GROUP_WANTED;
+ }
+ else if (m_newsAction == nsINntpUrl::ActionListGroups)
+ m_typeWanted = LIST_WANTED;
+ else if (m_newsAction == nsINntpUrl::ActionListNewGroups)
+ m_typeWanted = NEW_GROUPS;
+ else if (!m_messageID.IsEmpty() || m_key != nsMsgKey_None)
+ m_typeWanted = ARTICLE_WANTED;
+ else
+ {
+ NS_NOTREACHED("Unknown news action");
+ rv = NS_ERROR_FAILURE;
+ }
+
+ // if this connection comes from the cache, we need to initialize the
+ // load group here, by generating the start request notification. nsMsgProtocol::OnStartRequest
+ // ignores the first parameter (which is supposed to be the channel) so we'll pass in null.
+ if (m_fromCache)
+ nsMsgProtocol::OnStartRequest(nullptr, aURL);
+
+ /* At this point, we're all done parsing the URL, and know exactly
+ what we want to do with it.
+ */
+
+FAIL:
+ if (NS_FAILED(rv))
+ {
+ AlertError(0, nullptr);
+ return rv;
+ }
+ else
+ {
+ if (!m_socketIsOpen)
+ {
+ m_nextStateAfterResponse = m_nextState;
+ m_nextState = NNTP_RESPONSE;
+ }
+ rv = nsMsgProtocol::LoadUrl(aURL, aConsumer);
+ }
+
+ // Make sure that we have the information we need to be able to run the
+ // URLs
+ NS_ASSERTION(m_nntpServer, "Parsing must result in an m_nntpServer");
+ if (m_typeWanted == ARTICLE_WANTED)
+ {
+ if (m_key != nsMsgKey_None)
+ NS_ASSERTION(m_newsFolder, "ARTICLE_WANTED needs m_newsFolder w/ key");
+ else
+ NS_ASSERTION(!m_messageID.IsEmpty(), "ARTICLE_WANTED needs m_messageID w/o key");
+ }
+ else if (m_typeWanted == CANCEL_WANTED)
+ {
+ NS_ASSERTION(!m_messageID.IsEmpty(), "CANCEL_WANTED needs m_messageID");
+ NS_ASSERTION(m_newsFolder, "CANCEL_WANTED needs m_newsFolder");
+ NS_ASSERTION(m_key != nsMsgKey_None, "CANCEL_WANTED needs m_key");
+ }
+ else if (m_typeWanted == GROUP_WANTED)
+ NS_ASSERTION(m_newsFolder, "GROUP_WANTED needs m_newsFolder");
+ else if (m_typeWanted == SEARCH_WANTED)
+ NS_ASSERTION(!m_searchData.IsEmpty(), "SEARCH_WANTED needs m_searchData");
+ else if (m_typeWanted == IDS_WANTED)
+ NS_ASSERTION(m_newsFolder, "IDS_WANTED needs m_newsFolder");
+
+ return rv;
+}
+
+void nsNNTPProtocol::FinishMemCacheEntry(bool valid)
+{
+ nsCOMPtr <nsICacheEntry> memCacheEntry;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (mailnewsurl)
+ mailnewsurl->GetMemCacheEntry(getter_AddRefs(memCacheEntry));
+ if (memCacheEntry)
+ {
+ if (valid)
+ memCacheEntry->MarkValid();
+ else
+ memCacheEntry->AsyncDoom(nullptr);
+ }
+}
+
+// stop binding is a "notification" informing us that the stream associated with aURL is going away.
+NS_IMETHODIMP nsNNTPProtocol::OnStopRequest(nsIRequest *request, nsISupports * aContext, nsresult aStatus)
+{
+ // either remove mem cache entry, or mark it valid if url successful and
+ // command succeeded
+ FinishMemCacheEntry(NS_SUCCEEDED(aStatus)
+ && MK_NNTP_RESPONSE_TYPE(m_responseCode) == MK_NNTP_RESPONSE_TYPE_OK);
+
+ nsMsgProtocol::OnStopRequest(request, aContext, aStatus);
+
+ // nsMsgProtocol::OnStopRequest() has called m_channelListener. There is
+ // no need to be called again in CloseSocket(). Let's clear it here.
+ if (m_channelListener) {
+ m_channelListener = nullptr;
+ }
+
+ // okay, we've been told that the send is done and the connection is going away. So
+ // we need to release all of our state
+ return CloseSocket();
+}
+
+NS_IMETHODIMP nsNNTPProtocol::Cancel(nsresult status) // handle stop button
+{
+ m_nextState = NNTP_ERROR;
+ return nsMsgProtocol::Cancel(NS_BINDING_ABORTED);
+}
+
+nsresult
+nsNNTPProtocol::ParseURL(nsIURI *aURL, nsCString &aGroup, nsCString &aMessageID)
+{
+ NS_ENSURE_ARG_POINTER(aURL);
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) ParseURL",this));
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningURL, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(msgUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString spec;
+ rv = msgUrl->GetOriginalSpec(getter_Copies(spec));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if the original spec is non empty, use it to determine m_newsFolder and m_key
+ if (!spec.IsEmpty()) {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) original message spec = %s",this,spec.get()));
+
+ rv = nntpService->DecomposeNewsURI(spec.get(), getter_AddRefs(folder), &m_key);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // since we are reading a message in this folder, we can set m_newsFolder
+ m_newsFolder = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if we are cancelling, we aren't done. we still need to parse out the messageID from the url
+ // later, we'll use m_newsFolder and m_key to delete the message from the DB, if the cancel is successful.
+ if (m_newsAction != nsINntpUrl::ActionCancelArticle) {
+ return NS_OK;
+ }
+ }
+ else {
+ // clear this, we'll set it later.
+ m_newsFolder = nullptr;
+ m_currentGroup.Truncate();
+ }
+
+ // Load the values from the URL for parsing.
+ rv = m_runningURL->GetGroup(aGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = m_runningURL->GetMessageID(aMessageID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(aMessageID.IsEmpty() || aMessageID != aGroup, "something not null");
+
+ // If we are cancelling, we've got our message id, m_key, and m_newsFolder.
+ // Bail out now to prevent messing those up.
+ if (m_newsAction == nsINntpUrl::ActionCancelArticle)
+ return NS_OK;
+
+ rv = m_runningURL->GetKey(&m_key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if the key is in the local cache.
+ // It's possible that we're have a server/group/key combo that doesn't exist
+ // (think nntp://server/group/key), so not having the folder isn't a bad
+ // thing.
+ if (m_key != nsMsgKey_None)
+ {
+ rv = mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ m_newsFolder = do_QueryInterface(folder);
+
+ if (NS_SUCCEEDED(rv) && m_newsFolder)
+ {
+ bool useLocalCache = false;
+ rv = folder->HasMsgOffline(m_key, &useLocalCache);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // set message is in local cache
+ rv = mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+
+ return NS_OK;
+}
+/*
+ * Writes the data contained in dataBuffer into the current output stream. It also informs
+ * the transport layer that this data is now available for transmission.
+ * Returns a positive number for success, 0 for failure (not all the bytes were written to the
+ * stream, etc). We need to make another pass through this file to install an error system (mscott)
+ */
+
+nsresult nsNNTPProtocol::SendData(const char * dataBuffer, bool aSuppressLogging)
+{
+ if (!aSuppressLogging) {
+ NNTP_LOG_WRITE(dataBuffer);
+ }
+ else {
+ MOZ_LOG(NNTP, out, ("(%p) Logging suppressed for this command (it probably contained authentication information)", this));
+ }
+
+ return nsMsgProtocol::SendData(dataBuffer); // base class actually transmits the data
+}
+
+/* gets the response code from the nntp server and the
+ * response line
+ *
+ * returns the TCP return code from the read
+ */
+nsresult nsNNTPProtocol::NewsResponse(nsIInputStream *inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+
+ NS_PRECONDITION(nullptr != inputStream, "invalid input stream");
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
+
+ NNTP_LOG_READ(line);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if(!line)
+ return NS_ERROR_FAILURE;
+
+ ClearFlag(NNTP_PAUSE_FOR_READ); /* don't pause if we got a line */
+
+ /* almost correct */
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ m_previousResponseCode = m_responseCode;
+
+ PR_sscanf(line, "%d", &m_responseCode);
+
+ if (m_responseCode && PL_strlen(line) > 3)
+ NS_MsgSACopy(&m_responseText, line + 4);
+ else
+ NS_MsgSACopy(&m_responseText, line);
+
+ /* authentication required can come at any time
+ */
+ if (MK_NNTP_RESPONSE_AUTHINFO_REQUIRE == m_responseCode ||
+ MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_REQUIRE == m_responseCode)
+ {
+ m_nextState = NNTP_BEGIN_AUTHORIZE;
+ }
+ else {
+ m_nextState = m_nextStateAfterResponse;
+ }
+
+ PR_FREEIF(line);
+ return NS_OK;
+}
+
+/* interpret the server response after the connect
+ *
+ * returns negative if the server responds unexpectedly
+ */
+
+nsresult nsNNTPProtocol::LoginResponse()
+{
+ bool postingAllowed = m_responseCode == MK_NNTP_RESPONSE_POSTING_ALLOWED;
+
+ if(MK_NNTP_RESPONSE_TYPE(m_responseCode)!=MK_NNTP_RESPONSE_TYPE_OK)
+ {
+ AlertError(MK_NNTP_ERROR_MESSAGE, m_responseText);
+
+ m_nextState = NNTP_ERROR;
+ return NS_ERROR_FAILURE;
+ }
+
+ m_nntpServer->SetPostingAllowed(postingAllowed);
+ m_nextState = NNTP_SEND_MODE_READER;
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SendModeReader()
+{
+ nsresult rv = NS_OK;
+
+ rv = SendData(NNTP_CMD_MODE_READER);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_SEND_MODE_READER_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendModeReaderResponse()
+{
+ SetFlag(NNTP_READER_PERFORMED);
+
+ /* ignore the response code and continue
+ */
+ bool pushAuth = false;
+ nsresult rv = NS_OK;
+
+ NS_ASSERTION(m_nntpServer, "no server, see bug #107797");
+ if (m_nntpServer) {
+ rv = m_nntpServer->GetPushAuth(&pushAuth);
+ }
+ if (NS_SUCCEEDED(rv) && pushAuth) {
+ /* if the news host is set up to require volunteered (pushed) authentication,
+ * do that before we do anything else
+ */
+ m_nextState = NNTP_BEGIN_AUTHORIZE;
+ }
+ else {
+#ifdef HAVE_NNTP_EXTENSIONS
+ m_nextState = SEND_LIST_EXTENSIONS;
+#else
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+#endif /* HAVE_NNTP_EXTENSIONS */
+ }
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SendListExtensions()
+{
+ nsresult rv = SendData(NNTP_CMD_LIST_EXTENSIONS);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = SEND_LIST_EXTENSIONS_RESPONSE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListExtensionsResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ nsresult rv = NS_OK;
+
+ if (MK_NNTP_RESPONSE_TYPE(m_responseCode) == MK_NNTP_RESPONSE_TYPE_OK)
+ {
+ uint32_t status = 0;
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' != line[0]) {
+ m_nntpServer->AddExtension(line);
+ }
+ else
+ {
+ /* tell libmsg that it's ok to ask this news host for extensions */
+ m_nntpServer->SetSupportsExtensions(true);
+ /* all extensions received */
+ m_nextState = SEND_LIST_SEARCHES;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+ }
+ else
+ {
+ /* LIST EXTENSIONS not recognized
+ * tell libmsg not to ask for any more extensions and move on to
+ * the real NNTP command we were trying to do. */
+
+ m_nntpServer->SetSupportsExtensions(false);
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SendListSearches()
+{
+ nsresult rv;
+ bool searchable=false;
+
+ rv = m_nntpServer->QueryExtension("SEARCH",&searchable);
+ if (NS_SUCCEEDED(rv) && searchable)
+ {
+ rv = SendData(NNTP_CMD_LIST_SEARCHES);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = SEND_LIST_SEARCHES_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ }
+ else
+ {
+ /* since SEARCH isn't supported, move on to GET */
+ m_nextState = NNTP_GET_PROPERTIES;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListSearchesResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv = NS_OK;
+
+ NS_PRECONDITION(inputStream, "invalid input stream");
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ NNTP_LOG_READ(line);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' != line[0])
+ {
+ nsAutoCString charset;
+ nsAutoString lineUtf16;
+ if (NS_FAILED(m_nntpServer->GetCharset(charset)) ||
+ NS_FAILED(nsMsgI18NConvertToUnicode(charset.get(),
+ nsDependentCString(line),
+ lineUtf16, true)))
+ CopyUTF8toUTF16(nsDependentCString(line), lineUtf16);
+
+ m_nntpServer->AddSearchableGroup(lineUtf16);
+ }
+ else
+ {
+ /* all searchable groups received */
+ /* LIST SRCHFIELDS is legal if the server supports the SEARCH extension, which */
+ /* we already know it does */
+ m_nextState = NNTP_LIST_SEARCH_HEADERS;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ PR_FREEIF(line);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListSearchHeaders()
+{
+ nsresult rv = SendData(NNTP_CMD_LIST_SEARCH_FIELDS);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_LIST_SEARCH_HEADERS_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListSearchHeadersResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv;
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' != line[0])
+ m_nntpServer->AddSearchableHeader(line);
+ else
+ {
+ m_nextState = NNTP_GET_PROPERTIES;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ PR_FREEIF(line);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::GetProperties()
+{
+ nsresult rv;
+ bool setget=false;
+
+ rv = m_nntpServer->QueryExtension("SETGET",&setget);
+ if (NS_SUCCEEDED(rv) && setget)
+ {
+ rv = SendData(NNTP_CMD_GET_PROPERTIES);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_GET_PROPERTIES_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ }
+ else
+ {
+ /* since GET isn't supported, move on LIST SUBSCRIPTIONS */
+ m_nextState = SEND_LIST_SUBSCRIPTIONS;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+ return rv;
+}
+
+nsresult nsNNTPProtocol::GetPropertiesResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv;
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' != line[0])
+ {
+ char *propertyName = NS_strdup(line);
+ if (propertyName)
+ {
+ char *space = PL_strchr(propertyName, ' ');
+ if (space)
+ {
+ char *propertyValue = space + 1;
+ *space = '\0';
+ m_nntpServer->AddPropertyForGet(propertyName, propertyValue);
+ }
+ PR_Free(propertyName);
+ }
+ }
+ else
+ {
+ /* all GET properties received, move on to LIST SUBSCRIPTIONS */
+ m_nextState = SEND_LIST_SUBSCRIPTIONS;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ PR_FREEIF(line);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListSubscriptions()
+{
+ nsresult rv = NS_OK;
+/* TODO: is this needed for anything?
+#if 0
+ bool searchable=false;
+ rv = m_nntpServer->QueryExtension("LISTSUBSCR",&listsubscr);
+ if (NS_SUCCEEDED(rv) && listsubscr)
+#else
+ if (0)
+#endif
+ {
+ rv = SendData(NNTP_CMD_LIST_SUBSCRIPTIONS);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = SEND_LIST_SUBSCRIPTIONS_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ }
+ else
+*/
+ {
+ /* since LIST SUBSCRIPTIONS isn't supported, move on to real work */
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListSubscriptionsResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv;
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' != line[0])
+ {
+ NS_ERROR("fix me");
+#if 0
+ char *url = PR_smprintf ("%s//%s/%s", NEWS_SCHEME, m_hostName, line);
+ if (url)
+ MSG_AddSubscribedNewsgroup (cd->pane, url);
+#endif
+ }
+ else
+ {
+ /* all default subscriptions received */
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ PR_FREEIF(line);
+ return rv;
+}
+
+/* figure out what the first command is and send it
+ *
+ * returns the status from the NETWrite */
+
+nsresult nsNNTPProtocol::SendFirstNNTPCommand(nsIURI * url)
+{
+ char *command=0;
+
+ if (m_typeWanted == ARTICLE_WANTED) {
+ if (m_key != nsMsgKey_None) {
+ nsresult rv;
+ nsCString newsgroupName;
+ if (m_newsFolder) {
+ rv = m_newsFolder->GetRawName(newsgroupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ MOZ_LOG(NNTP, LogLevel::Info,
+ ("(%p) current group = %s, desired group = %s", this,
+ m_currentGroup.get(), newsgroupName.get()));
+ // if the current group is the desired group, we can just issue the ARTICLE command
+ // if not, we have to do a GROUP first
+ if (newsgroupName.Equals(m_currentGroup))
+ m_nextState = NNTP_SEND_ARTICLE_NUMBER;
+ else
+ m_nextState = NNTP_SEND_GROUP_FOR_ARTICLE;
+
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ }
+
+ if(m_typeWanted == NEWS_POST)
+ { /* posting to the news group */
+ NS_MsgSACopy(&command, "POST");
+ }
+ else if (m_typeWanted == NEW_GROUPS)
+ {
+ uint32_t last_update;
+ nsresult rv;
+
+ if (!m_nntpServer)
+ {
+ NNTP_LOG_NOTE("m_nntpServer is null, panic!");
+ return NS_ERROR_FAILURE;
+ }
+ rv = m_nntpServer->GetLastUpdatedTime(&last_update);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!last_update)
+ {
+ NS_MsgSACopy(&command, "LIST");
+ }
+ else
+ {
+ char small_buf[64];
+ PRExplodedTime expandedTime;
+ PRTime t_usec = (PRTime)last_update * PR_USEC_PER_SEC;
+ PR_ExplodeTime(t_usec, PR_LocalTimeParameters, &expandedTime);
+ PR_FormatTimeUSEnglish(small_buf, sizeof(small_buf),
+ "NEWGROUPS %y%m%d %H%M%S", &expandedTime);
+ NS_MsgSACopy(&command, small_buf);
+ }
+ }
+ else if(m_typeWanted == LIST_WANTED)
+ {
+ nsresult rv;
+
+ ClearFlag(NNTP_USE_FANCY_NEWSGROUP);
+
+ NS_ASSERTION(m_nntpServer, "no m_nntpServer");
+ if (!m_nntpServer) {
+ NNTP_LOG_NOTE("m_nntpServer is null, panic!");
+ return NS_ERROR_FAILURE;
+ }
+
+ bool xactive=false;
+ rv = m_nntpServer->QueryExtension("XACTIVE",&xactive);
+ if (NS_SUCCEEDED(rv) && xactive)
+ {
+ NS_MsgSACopy(&command, "LIST XACTIVE");
+ SetFlag(NNTP_USE_FANCY_NEWSGROUP);
+ }
+ else
+ {
+ NS_MsgSACopy(&command, "LIST");
+ }
+ }
+ else if(m_typeWanted == GROUP_WANTED)
+ {
+ nsresult rv = NS_ERROR_NULL_POINTER;
+
+ NS_ASSERTION(m_newsFolder, "m_newsFolder is null, panic!");
+ if (!m_newsFolder) return NS_ERROR_FAILURE;
+
+ nsCString group_name;
+ rv = m_newsFolder->GetRawName(group_name);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get newsgroup name");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_firstArticle = 0;
+ m_lastArticle = 0;
+
+ NS_MsgSACopy(&command, "GROUP ");
+ NS_MsgSACat(&command, group_name.get());
+ }
+ else if (m_typeWanted == SEARCH_WANTED)
+ {
+ nsresult rv;
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) doing GROUP for XPAT", this));
+ nsCString group_name;
+
+ /* for XPAT, we have to GROUP into the group before searching */
+ if (!m_newsFolder) {
+ NNTP_LOG_NOTE("m_newsFolder is null, panic!");
+ return NS_ERROR_FAILURE;
+ }
+ rv = m_newsFolder->GetRawName(group_name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_MsgSACopy(&command, "GROUP ");
+ NS_MsgSACat (&command, group_name.get());
+
+ // force a GROUP next time
+ m_currentGroup.Truncate();
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_XPAT_SEND;
+ }
+ else if (m_typeWanted == IDS_WANTED)
+ {
+ m_nextState = NNTP_LIST_GROUP;
+ return NS_OK;
+ }
+ else /* article or cancel */
+ {
+ NS_ASSERTION(!m_messageID.IsEmpty(), "No message ID, bailing!");
+ if (m_messageID.IsEmpty()) return NS_ERROR_FAILURE;
+
+ if (m_typeWanted == CANCEL_WANTED)
+ NS_MsgSACopy(&command, "HEAD ");
+ else {
+ NS_ASSERTION(m_typeWanted == ARTICLE_WANTED, "not cancel, and not article");
+ NS_MsgSACopy(&command, "ARTICLE ");
+ }
+
+ if (m_messageID[0] != '<')
+ NS_MsgSACat(&command,"<");
+
+ NS_MsgSACat(&command, m_messageID.get());
+
+ if (PL_strchr(command+8, '>')==0)
+ NS_MsgSACat(&command,">");
+ }
+
+ NS_MsgSACat(&command, CRLF);
+ nsresult rv = SendData(command);
+ PR_Free(command);
+
+ m_nextState = NNTP_RESPONSE;
+ if (m_typeWanted != SEARCH_WANTED)
+ m_nextStateAfterResponse = SEND_FIRST_NNTP_COMMAND_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return rv;
+} /* sent first command */
+
+
+/* interprets the server response from the first command sent
+ *
+ * returns negative if the server responds unexpectedly
+ */
+
+nsresult nsNNTPProtocol::SendFirstNNTPCommandResponse()
+{
+ int32_t major_opcode = MK_NNTP_RESPONSE_TYPE(m_responseCode);
+
+ if((major_opcode == MK_NNTP_RESPONSE_TYPE_CONT &&
+ m_typeWanted == NEWS_POST)
+ || (major_opcode == MK_NNTP_RESPONSE_TYPE_OK &&
+ m_typeWanted != NEWS_POST) )
+ {
+
+ m_nextState = SETUP_NEWS_STREAM;
+ SetFlag(NNTP_SOME_PROTOCOL_SUCCEEDED);
+ return NS_OK;
+ }
+ else
+ {
+ nsresult rv = NS_OK;
+
+ nsString group_name;
+ NS_ASSERTION(m_newsFolder, "no newsFolder");
+ if (m_newsFolder)
+ rv = m_newsFolder->GetUnicodeName(group_name);
+
+ if (m_responseCode == MK_NNTP_RESPONSE_GROUP_NO_GROUP &&
+ m_typeWanted == GROUP_WANTED) {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) group (%s) not found, so unset"
+ " m_currentGroup", this,
+ NS_ConvertUTF16toUTF8(group_name).get()));
+ m_currentGroup.Truncate();
+
+ m_nntpServer->GroupNotFound(m_msgWindow, group_name, true /* opening */);
+ }
+
+ /* if the server returned a 400 error then it is an expected
+ * error. the NEWS_ERROR state will not sever the connection
+ */
+ if(major_opcode == MK_NNTP_RESPONSE_TYPE_CANNOT)
+ m_nextState = NEWS_ERROR;
+ else
+ m_nextState = NNTP_ERROR;
+ // if we have no channel listener, then we're likely downloading
+ // the message for offline use (or at least not displaying it)
+ bool savingArticleOffline = (m_channelListener == nullptr);
+
+ if (m_runningURL)
+ FinishMemCacheEntry(false); // cleanup mem cache entry
+
+ if (NS_SUCCEEDED(rv) && !group_name.IsEmpty() && !savingArticleOffline) {
+ nsString titleStr;
+ rv = GetNewsStringByName("htmlNewsErrorTitle", getter_Copies(titleStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString newsErrorStr;
+ rv = GetNewsStringByName("htmlNewsError", getter_Copies(newsErrorStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsAutoString errorHtml;
+ errorHtml.Append(newsErrorStr);
+
+ errorHtml.AppendLiteral("<b>");
+ errorHtml.Append(NS_ConvertASCIItoUTF16(m_responseText));
+ errorHtml.AppendLiteral("</b><p>");
+
+ rv = GetNewsStringByName("articleExpired", getter_Copies(newsErrorStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ errorHtml.Append(newsErrorStr);
+
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ if ((m_key != nsMsgKey_None) && m_newsFolder) {
+ nsCString messageID;
+ rv = m_newsFolder->GetMessageIdForKey(m_key, messageID);
+ if (NS_SUCCEEDED(rv)) {
+ PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE,"<P>&lt;%.512s&gt; (%lu)", messageID.get(), m_key);
+ errorHtml.Append(NS_ConvertASCIItoUTF16(outputBuffer));
+ }
+ }
+
+ if (m_newsFolder) {
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
+ if (NS_SUCCEEDED(rv) && folder) {
+ nsCString folderURI;
+ rv = folder->GetURI(folderURI);
+ if (NS_SUCCEEDED(rv)) {
+ PR_snprintf(outputBuffer,OUTPUT_BUFFER_SIZE,"<P> <A HREF=\"%s?list-ids\">", folderURI.get());
+ }
+ }
+ }
+
+ errorHtml.Append(NS_ConvertASCIItoUTF16(outputBuffer));
+
+ rv = GetNewsStringByName("removeExpiredArtLinkText", getter_Copies(newsErrorStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ errorHtml.Append(newsErrorStr);
+ errorHtml.AppendLiteral("</A> </P>");
+
+ if (!m_msgWindow) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (mailnewsurl) {
+ rv = mailnewsurl->GetMsgWindow(getter_AddRefs(m_msgWindow));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ if (!m_msgWindow) return NS_ERROR_FAILURE;
+
+ // note, this will cause us to close the connection.
+ // this will call nsDocShell::LoadURI(), which will
+ // call nsDocShell::Stop(STOP_NETWORK), which will eventually
+ // call nsNNTPProtocol::Cancel(), which will close the socket.
+ // we need to fix this, since the connection is still valid.
+ rv = m_msgWindow->DisplayHTMLInMessagePane(titleStr, errorHtml, true);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ // let's take the opportunity of removing the hdr from the db so we don't try to download
+ // it again.
+ else if (savingArticleOffline)
+ {
+ if ((m_key != nsMsgKey_None) && (m_newsFolder)) {
+ rv = m_newsFolder->RemoveMessage(m_key);
+ }
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+}
+
+nsresult nsNNTPProtocol::SendGroupForArticle()
+{
+ nsresult rv;
+
+ nsCString groupname;
+ rv = m_newsFolder->GetRawName(groupname);
+ NS_ASSERTION(NS_SUCCEEDED(rv) && !groupname.IsEmpty(), "no group name");
+
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "GROUP %.512s" CRLF,
+ groupname.get());
+
+ rv = SendData(outputBuffer);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return rv;
+}
+
+nsresult
+nsNNTPProtocol::SetCurrentGroup()
+{
+ nsCString groupname;
+ NS_ASSERTION(m_newsFolder, "no news folder");
+ if (!m_newsFolder) {
+ m_currentGroup.Truncate();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mozilla::DebugOnly<nsresult> rv = m_newsFolder->GetRawName(groupname);
+ NS_ASSERTION(NS_SUCCEEDED(rv) && !groupname.IsEmpty(), "no group name");
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) SetCurrentGroup to %s",this, groupname.get()));
+ m_currentGroup = groupname;
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SendGroupForArticleResponse()
+{
+ /* ignore the response code and continue
+ */
+ m_nextState = NNTP_SEND_ARTICLE_NUMBER;
+
+ return SetCurrentGroup();
+}
+
+
+nsresult nsNNTPProtocol::SendArticleNumber()
+{
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+ PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "ARTICLE %lu" CRLF, m_key);
+
+ nsresult rv = SendData(outputBuffer);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = SEND_FIRST_NNTP_COMMAND_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::BeginArticle()
+{
+ if (m_typeWanted != ARTICLE_WANTED && m_typeWanted != CANCEL_WANTED)
+ return NS_OK;
+
+ /* Set up the HTML stream
+ */
+
+#ifdef NO_ARTICLE_CACHEING
+ ce->format_out = CLEAR_CACHE_BIT (ce->format_out);
+#endif
+
+ // if we have a channel listener,
+ // create a pipe to pump the message into...the output will go to whoever
+ // is consuming the message display
+ //
+ // the pipe must have an unlimited length since we are going to be filling
+ // it on the main thread while reading it from the main thread. iow, the
+ // write must not block!! (see bug 190988)
+ //
+ if (m_channelListener) {
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+ nsresult rv = pipe->Init(false, false, 4096, PR_UINT32_MAX);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // These always succeed because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(mDisplayInputStream)));
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(mDisplayOutputStream)));
+ }
+
+ m_nextState = NNTP_READ_ARTICLE;
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::DisplayArticle(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t line_length = 0;
+
+ bool pauseForMoreData = false;
+ if (m_channelListener)
+ {
+ nsresult rv = NS_OK;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, line_length, pauseForMoreData, &rv, true);
+ if (pauseForMoreData)
+ {
+ uint64_t inlength = 0;
+ mDisplayInputStream->Available(&inlength);
+ if (inlength > 0) // broadcast our batched up ODA changes
+ m_channelListener->OnDataAvailable(this, m_channelContext, mDisplayInputStream, 0, std::min(inlength, uint64_t(PR_UINT32_MAX)));
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ PR_Free(line);
+ return rv;
+ }
+
+ if (m_newsFolder)
+ m_newsFolder->NotifyDownloadedLine(line, m_key);
+
+ // line only contains a single dot -> message end
+ if (line_length == 1 + MSG_LINEBREAK_LEN && line[0] == '.')
+ {
+ m_nextState = NEWS_DONE;
+
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+
+ uint64_t inlength = 0;
+ mDisplayInputStream->Available(&inlength);
+ if (inlength > 0) // broadcast our batched up ODA changes
+ m_channelListener->OnDataAvailable(this, m_channelContext, mDisplayInputStream, 0, std::min(inlength, uint64_t(PR_UINT32_MAX)));
+ PR_Free(line);
+ return rv;
+ }
+ else // we aren't finished with the message yet
+ {
+ uint32_t count = 0;
+
+ // skip over the quoted '.'
+ if (line_length > 1 && line[0] == '.' && line[1] == '.')
+ mDisplayOutputStream->Write(line+1, line_length-1, &count);
+ else
+ mDisplayOutputStream->Write(line, line_length, &count);
+ }
+
+ PR_Free(line);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::ReadArticle(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv;
+ char *outputBuffer;
+
+ bool pauseForMoreData = false;
+
+ // if we have a channel listener, spool directly to it....
+ // otherwise we must be doing something like save to disk or cancel
+ // in which case we are doing the work.
+ if (m_channelListener)
+ return DisplayArticle(inputStream, length);
+
+
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv, true);
+ if (m_newsFolder && line)
+ {
+ const char *unescapedLine = line;
+ // lines beginning with '.' are escaped by nntp server
+ // or is it just '.' on a line by itself?
+ if (line[0] == '.' && line[1] == '.')
+ unescapedLine++;
+ m_newsFolder->NotifyDownloadedLine(unescapedLine, m_key);
+
+ }
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ if(!line)
+ return rv; /* no line yet or error */
+
+ nsCOMPtr<nsISupports> ctxt = do_QueryInterface(m_runningURL);
+
+ if (m_typeWanted == CANCEL_WANTED && m_responseCode != MK_NNTP_RESPONSE_ARTICLE_HEAD)
+ {
+ /* HEAD command failed. */
+ PR_FREEIF(line);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (line[0] == '.' && line[MSG_LINEBREAK_LEN + 1] == 0)
+ {
+ if (m_typeWanted == CANCEL_WANTED)
+ m_nextState = NEWS_START_CANCEL;
+ else
+ m_nextState = NEWS_DONE;
+
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+ else
+ {
+ if (line[0] == '.')
+ outputBuffer = line + 1;
+ else
+ outputBuffer = line;
+
+ /* Don't send content-type to mime parser if we're doing a cancel
+ because it confuses mime parser into not parsing.
+ */
+ if (m_typeWanted != CANCEL_WANTED || strncmp(outputBuffer, "Content-Type:", 13))
+ {
+ // if we are attempting to cancel, we want to snarf the headers and save the aside, which is what
+ // ParseHeaderForCancel() does.
+ if (m_typeWanted == CANCEL_WANTED) {
+ ParseHeaderForCancel(outputBuffer);
+ }
+
+ }
+ }
+
+ PR_Free(line);
+
+ return NS_OK;
+}
+
+void nsNNTPProtocol::ParseHeaderForCancel(char *buf)
+{
+ nsAutoCString header(buf);
+ int32_t colon = header.FindChar(':');
+ if (!colon)
+ return;
+
+ nsCString value(Substring(header, colon + 1));
+ value.StripWhitespace();
+
+ switch (header.First()) {
+ case 'F': case 'f':
+ if (header.Find("From", CaseInsensitiveCompare) == 0) {
+ PR_FREEIF(m_cancelFromHdr);
+ m_cancelFromHdr = ToNewCString(value);
+ }
+ break;
+ case 'M': case 'm':
+ if (header.Find("Message-ID", CaseInsensitiveCompare) == 0) {
+ PR_FREEIF(m_cancelID);
+ m_cancelID = ToNewCString(value);
+ }
+ break;
+ case 'N': case 'n':
+ if (header.Find("Newsgroups", CaseInsensitiveCompare) == 0) {
+ PR_FREEIF(m_cancelNewsgroups);
+ m_cancelNewsgroups = ToNewCString(value);
+ }
+ break;
+ case 'D': case 'd':
+ if (header.Find("Distributions", CaseInsensitiveCompare) == 0) {
+ PR_FREEIF(m_cancelDistribution);
+ m_cancelDistribution = ToNewCString(value);
+ }
+ break;
+ }
+
+ return;
+}
+
+nsresult nsNNTPProtocol::BeginAuthorization()
+{
+ char * command = 0;
+ nsresult rv = NS_OK;
+
+ if (!m_newsFolder && m_nntpServer) {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer);
+ if (m_nntpServer) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ m_newsFolder = do_QueryInterface(rootFolder);
+ }
+ }
+ }
+
+ NS_ASSERTION(m_newsFolder, "no m_newsFolder");
+ if (!m_newsFolder)
+ return NS_ERROR_FAILURE;
+
+ // We want to get authentication credentials, but it is possible that the
+ // master password prompt will end up being synchronous. In that case, check
+ // to see if we already have the credentials stored.
+ nsCString username, password;
+ rv = m_newsFolder->GetGroupUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = m_newsFolder->GetGroupPassword(password);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we don't have either a username or a password, queue an asynchronous
+ // prompt.
+ if (username.IsEmpty() || password.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgAsyncPrompter> asyncPrompter =
+ do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the key to coalesce auth prompts.
+ bool singleSignon = false;
+ m_nntpServer->GetSingleSignon(&singleSignon);
+
+ nsCString queueKey;
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_nntpServer);
+ server->GetKey(queueKey);
+ if (!singleSignon)
+ {
+ nsCString groupName;
+ m_newsFolder->GetRawName(groupName);
+ queueKey += groupName;
+ }
+
+ // If we were called back from HandleAuthenticationFailure, we must have
+ // been handling the response of an authorization state. In that case,
+ // let's hurry up on the auth request
+ bool didAuthFail = m_nextStateAfterResponse == NNTP_AUTHORIZE_RESPONSE ||
+ m_nextStateAfterResponse == NNTP_PASSWORD_RESPONSE;
+ rv = asyncPrompter->QueueAsyncAuthPrompt(queueKey, didAuthFail, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_nextState = NNTP_SUSPENDED;
+ if (m_request)
+ m_request->Suspend();
+ return NS_OK;
+ }
+
+ NS_MsgSACopy(&command, "AUTHINFO user ");
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) use %s as the username", this, username.get()));
+ NS_MsgSACat(&command, username.get());
+ NS_MsgSACat(&command, CRLF);
+
+ rv = SendData(command);
+
+ PR_Free(command);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_AUTHORIZE_RESPONSE;
+
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::AuthorizationResponse()
+{
+ nsresult rv = NS_OK;
+
+ if (MK_NNTP_RESPONSE_AUTHINFO_OK == m_responseCode ||
+ MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_OK == m_responseCode)
+ {
+ /* successful login */
+#ifdef HAVE_NNTP_EXTENSIONS
+ bool pushAuth;
+ /* If we're here because the host demanded authentication before we
+ * even sent a single command, then jump back to the beginning of everything
+ */
+ rv = m_nntpServer->GetPushAuth(&pushAuth);
+
+ if (!TestFlag(NNTP_READER_PERFORMED))
+ m_nextState = NNTP_SEND_MODE_READER;
+ /* If we're here because the host needs pushed authentication, then we
+ * should jump back to SEND_LIST_EXTENSIONS
+ */
+ else if (NS_SUCCEEDED(rv) && pushAuth)
+ m_nextState = SEND_LIST_EXTENSIONS;
+ else
+ /* Normal authentication */
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+#else
+ if (!TestFlag(NNTP_READER_PERFORMED))
+ m_nextState = NNTP_SEND_MODE_READER;
+ else
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+#endif /* HAVE_NNTP_EXTENSIONS */
+
+ return NS_OK;
+ }
+ else if (MK_NNTP_RESPONSE_AUTHINFO_CONT == m_responseCode)
+ {
+ char * command = 0;
+
+ // Since we had to have called BeginAuthorization to get here, we've already
+ // prompted for the authorization credentials. Just grab them without a
+ // further prompt.
+ nsCString password;
+ rv = m_newsFolder->GetGroupPassword(password);
+ if (NS_FAILED(rv) || password.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ NS_MsgSACopy(&command, "AUTHINFO pass ");
+ NS_MsgSACat(&command, password.get());
+ NS_MsgSACat(&command, CRLF);
+
+ rv = SendData(command, true);
+
+ PR_FREEIF(command);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_PASSWORD_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return rv;
+ }
+ else
+ {
+ /* login failed */
+ HandleAuthenticationFailure();
+ return NS_OK;
+ }
+
+ NS_ERROR("should never get here");
+ return NS_ERROR_FAILURE;
+
+}
+
+nsresult nsNNTPProtocol::PasswordResponse()
+{
+ if (MK_NNTP_RESPONSE_AUTHINFO_OK == m_responseCode ||
+ MK_NNTP_RESPONSE_AUTHINFO_SIMPLE_OK == m_responseCode)
+ {
+ /* successful login */
+#ifdef HAVE_NNTP_EXTENSIONS
+ bool pushAuth;
+ /* If we're here because the host demanded authentication before we
+ * even sent a single command, then jump back to the beginning of everything
+ */
+ nsresult rv = m_nntpServer->GetPushAuth(&pushAuth);
+
+ if (!TestFlag(NNTP_READER_PERFORMED))
+ m_nextState = NNTP_SEND_MODE_READER;
+ /* If we're here because the host needs pushed authentication, then we
+ * should jump back to SEND_LIST_EXTENSIONS
+ */
+ else if (NS_SUCCEEDED(rv) && pushAuth)
+ m_nextState = SEND_LIST_EXTENSIONS;
+ else
+ /* Normal authentication */
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+#else
+ if (!TestFlag(NNTP_READER_PERFORMED))
+ m_nextState = NNTP_SEND_MODE_READER;
+ else
+ m_nextState = SEND_FIRST_NNTP_COMMAND;
+#endif /* HAVE_NNTP_EXTENSIONS */
+ return NS_OK;
+ }
+ else
+ {
+ HandleAuthenticationFailure();
+ return NS_OK;
+ }
+
+ NS_ERROR("should never get here");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::OnPromptStart(bool *authAvailable)
+{
+ NS_ENSURE_ARG_POINTER(authAvailable);
+ NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
+
+ if (!m_newsFolder)
+ {
+ // If we don't have a news folder, we may have been closed already.
+ NNTP_LOG_NOTE("Canceling queued authentication prompt");
+ *authAvailable = false;
+ return NS_OK;
+ }
+
+ bool didAuthFail = m_nextState == NNTP_AUTHORIZE_RESPONSE ||
+ m_nextState == NNTP_PASSWORD_RESPONSE;
+ nsresult rv = m_newsFolder->GetAuthenticationCredentials(m_msgWindow,
+ true, didAuthFail, authAvailable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // What we do depends on whether or not we have valid credentials
+ return *authAvailable ? OnPromptAuthAvailable() : OnPromptCanceled();
+}
+
+NS_IMETHODIMP nsNNTPProtocol::OnPromptAuthAvailable()
+{
+ NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
+
+ // We previously suspended the request; now resume it to read input
+ if (m_request)
+ m_request->Resume();
+
+ // Now we have our password details accessible from the group, so just call
+ // into the state machine to start the process going again.
+ m_nextState = NNTP_BEGIN_AUTHORIZE;
+ return ProcessProtocolState(nullptr, nullptr, 0, 0);
+}
+
+NS_IMETHODIMP nsNNTPProtocol::OnPromptCanceled()
+{
+ NS_ENSURE_STATE(m_nextState == NNTP_SUSPENDED);
+
+ // We previously suspended the request; now resume it to read input
+ if (m_request)
+ m_request->Resume();
+
+ // Since the prompt was canceled, we can no longer continue the connection.
+ // Thus, we need to go to the NNTP_ERROR state.
+ m_nextState = NNTP_ERROR;
+ return ProcessProtocolState(nullptr, nullptr, 0, 0);
+}
+
+nsresult nsNNTPProtocol::DisplayNewsgroups()
+{
+ m_nextState = NEWS_DONE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) DisplayNewsgroups()",this));
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::BeginNewsgroups()
+{
+ m_nextState = NNTP_NEWGROUPS;
+ mBytesReceived = 0;
+ mBytesReceivedSinceLastStatusUpdate = 0;
+ m_startTime = PR_Now();
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::ProcessNewsgroups(nsIInputStream * inputStream, uint32_t length)
+{
+ char *line, *lineToFree, *s, *s1=NULL, *s2=NULL;
+ uint32_t status = 0;
+ nsresult rv = NS_OK;
+
+ bool pauseForMoreData = false;
+ line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if(!line)
+ return rv; /* no line yet */
+
+ /* End of list?
+ */
+ if (line[0]=='.' && line[1]=='\0')
+ {
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ bool xactive=false;
+ rv = m_nntpServer->QueryExtension("XACTIVE",&xactive);
+ if (NS_SUCCEEDED(rv) && xactive)
+ {
+ nsAutoCString groupName;
+ rv = m_nntpServer->GetFirstGroupNeedingExtraInfo(groupName);
+ if (NS_SUCCEEDED(rv)) {
+ rv = m_nntpServer->FindGroup(groupName, getter_AddRefs(m_newsFolder));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "FindGroup failed");
+ m_nextState = NNTP_LIST_XACTIVE;
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) listing xactive for %s", this,
+ groupName.get()));
+ PR_Free(lineToFree);
+ return NS_OK;
+ }
+ }
+ m_nextState = NEWS_DONE;
+
+ PR_Free(lineToFree);
+ if(status > 0)
+ return NS_OK;
+ else
+ return rv;
+ }
+ else if (line [0] == '.' && line [1] == '.')
+ /* The NNTP server quotes all lines beginning with "." by doubling it. */
+ line++;
+
+ /* almost correct
+ */
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ /* format is "rec.arts.movies.past-films 7302 7119 y"
+ */
+ s = PL_strchr (line, ' ');
+ if (s)
+ {
+ *s = 0;
+ s1 = s+1;
+ s = PL_strchr (s1, ' ');
+ if (s)
+ {
+ *s = 0;
+ s2 = s+1;
+ s = PL_strchr (s2, ' ');
+ if (s)
+ {
+ *s = 0;
+ }
+ }
+ }
+
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+
+ NS_ASSERTION(m_nntpServer, "no nntp incoming server");
+ if (m_nntpServer) {
+ rv = m_nntpServer->AddNewsgroupToList(line);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to add to subscribe ds");
+ }
+
+ bool xactive=false;
+ rv = m_nntpServer->QueryExtension("XACTIVE",&xactive);
+ if (NS_SUCCEEDED(rv) && xactive)
+ {
+ nsAutoCString charset;
+ nsAutoString lineUtf16;
+ if (NS_SUCCEEDED(m_nntpServer->GetCharset(charset)) &&
+ NS_SUCCEEDED(nsMsgI18NConvertToUnicode(charset.get(),
+ nsDependentCString(line),
+ lineUtf16, true)))
+ m_nntpServer->SetGroupNeedsExtraInfo(NS_ConvertUTF16toUTF8(lineUtf16),
+ true);
+ else
+ m_nntpServer->SetGroupNeedsExtraInfo(nsDependentCString(line), true);
+ }
+
+ PR_Free(lineToFree);
+ return rv;
+}
+
+/* Ahhh, this like print's out the headers and stuff
+ *
+ * always returns 0
+ */
+
+nsresult nsNNTPProtocol::BeginReadNewsList()
+{
+ m_readNewsListCount = 0;
+ mNumGroupsListed = 0;
+ m_nextState = NNTP_READ_LIST;
+
+ mBytesReceived = 0;
+ mBytesReceivedSinceLastStatusUpdate = 0;
+ m_startTime = PR_Now();
+
+ return NS_OK;
+}
+
+#define RATE_CONSTANT 976.5625 /* PR_USEC_PER_SEC / 1024 bytes */
+
+static void ComputeRate(int32_t bytes, PRTime startTime, float *rate)
+{
+ // rate = (bytes / USECS since start) * RATE_CONSTANT
+
+ // compute usecs since we started.
+ int32_t delta = (int32_t)(PR_Now() - startTime);
+
+ // compute rate
+ if (delta > 0) {
+ *rate = (float) ((bytes * RATE_CONSTANT) / delta);
+ }
+ else {
+ *rate = 0.0;
+ }
+}
+
+/* display a list of all or part of the newsgroups list
+ * from the news server
+ */
+nsresult nsNNTPProtocol::ReadNewsList(nsIInputStream * inputStream, uint32_t length)
+{
+ nsresult rv = NS_OK;
+ int32_t i=0;
+ uint32_t status = 1;
+
+ bool pauseForMoreData = false;
+ char *line, *lineToFree;
+ line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if (pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ PR_Free(lineToFree);
+ return NS_OK;
+ }
+
+ if (!line)
+ return rv; /* no line yet */
+
+ /* End of list? */
+ if (line[0]=='.' && line[1]=='\0')
+ {
+ bool listpnames=false;
+ NS_ASSERTION(m_nntpServer, "no nntp incoming server");
+ if (m_nntpServer) {
+ rv = m_nntpServer->QueryExtension("LISTPNAMES",&listpnames);
+ }
+ if (NS_SUCCEEDED(rv) && listpnames)
+ m_nextState = NNTP_LIST_PRETTY_NAMES;
+ else
+ m_nextState = DISPLAY_NEWSGROUPS;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_Free(lineToFree);
+ return NS_OK;
+ }
+ else if (line[0] == '.')
+ {
+ if ((line[1] == ' ') || (line[1] == '.' && line [2] == '.' && line[3] == ' '))
+ {
+ // some servers send "... 0000000001 0000000002 y"
+ // and some servers send ". 0000000001 0000000002 y"
+ // just skip that those lines
+ // see bug #69231 and #123560
+ PR_Free(lineToFree);
+ return rv;
+ }
+ // The NNTP server quotes all lines beginning with "." by doubling it, so unquote
+ line++;
+ }
+
+ /* almost correct
+ */
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+
+ if ((mBytesReceivedSinceLastStatusUpdate > UPDATE_THRESHHOLD) && m_msgWindow) {
+ mBytesReceivedSinceLastStatusUpdate = 0;
+
+ nsCOMPtr <nsIMsgStatusFeedback> msgStatusFeedback;
+
+ rv = m_msgWindow->GetStatusFeedback(getter_AddRefs(msgStatusFeedback));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString statusString;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString bytesStr;
+ bytesStr.AppendInt(mBytesReceived / 1024);
+
+ // compute the rate, and then convert it have one
+ // decimal precision.
+ float rate = 0.0;
+ ComputeRate(mBytesReceived, m_startTime, &rate);
+ char rate_buf[RATE_STR_BUF_LEN];
+ PR_snprintf(rate_buf,RATE_STR_BUF_LEN,"%.1f", rate);
+
+ nsAutoString numGroupsStr;
+ numGroupsStr.AppendInt(mNumGroupsListed);
+ NS_ConvertASCIItoUTF16 rateStr(rate_buf);
+
+ const char16_t *formatStrings[3] = { numGroupsStr.get(), bytesStr.get(), rateStr.get()};
+ rv = bundle->FormatStringFromName(u"bytesReceived",
+ formatStrings, 3,
+ getter_Copies(statusString));
+
+ rv = msgStatusFeedback->ShowStatusString(statusString);
+ if (NS_FAILED(rv)) {
+ PR_Free(lineToFree);
+ return rv;
+ }
+ }
+ }
+
+ /* find whitespace separator if it exits */
+ for(i=0; line[i] != '\0' && !NET_IS_SPACE(line[i]); i++)
+ ; /* null body */
+
+ line[i] = 0; /* terminate group name */
+
+ /* store all the group names */
+ NS_ASSERTION(m_nntpServer, "no nntp incoming server");
+ if (m_nntpServer) {
+ m_readNewsListCount++;
+ mNumGroupsListed++;
+ rv = m_nntpServer->AddNewsgroupToList(line);
+// NS_ASSERTION(NS_SUCCEEDED(rv),"failed to add to subscribe ds");
+ // since it's not fatal, don't let this error stop the LIST command.
+ rv = NS_OK;
+ }
+ else
+ rv = NS_ERROR_FAILURE;
+
+ if (m_readNewsListCount == READ_NEWS_LIST_COUNT_MAX) {
+ m_readNewsListCount = 0;
+ if (mUpdateTimer) {
+ mUpdateTimer->Cancel();
+ mUpdateTimer = nullptr;
+ }
+ mUpdateTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create timer");
+ if (NS_FAILED(rv)) {
+ PR_Free(lineToFree);
+ return rv;
+ }
+
+ mInputStream = inputStream;
+
+ const uint32_t kUpdateTimerDelay = READ_NEWS_LIST_TIMEOUT;
+ rv = mUpdateTimer->InitWithCallback(static_cast<nsITimerCallback*>(this), kUpdateTimerDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to init timer");
+ if (NS_FAILED(rv)) {
+ PR_Free(lineToFree);
+ return rv;
+ }
+
+ m_nextState = NNTP_SUSPENDED;
+
+ // suspend necko request until timeout
+ // might not have a request if someone called CloseSocket()
+ // see bug #195440
+ if (m_request)
+ m_request->Suspend();
+ }
+
+ PR_Free(lineToFree);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNNTPProtocol::Notify(nsITimer *timer)
+{
+ NS_ASSERTION(timer == mUpdateTimer.get(), "Hey, this ain't my timer!");
+ mUpdateTimer = nullptr; // release my hold
+ TimerCallback();
+ return NS_OK;
+}
+
+void nsNNTPProtocol::TimerCallback()
+{
+ MOZ_LOG(NNTP, LogLevel::Info,("nsNNTPProtocol::TimerCallback\n"));
+ m_nextState = NNTP_READ_LIST;
+
+ // process whatever is already in the buffer at least once.
+ //
+ // NOTE: while downloading, it would almost be enough to just
+ // resume necko since it will call us again with data. however,
+ // if we are at the end of the data stream then we must call
+ // ProcessProtocolState since necko will not call us again.
+ //
+ // NOTE: this function may Suspend necko. Suspend is a reference
+ // counted (i.e., two suspends requires two resumes before the
+ // request will actually be resumed).
+ //
+ ProcessProtocolState(nullptr, mInputStream, 0,0);
+
+ // resume necko request
+ // might not have a request if someone called CloseSocket()
+ // see bug #195440
+ if (m_request)
+ m_request->Resume();
+
+ return;
+}
+
+void nsNNTPProtocol::HandleAuthenticationFailure()
+{
+ nsCOMPtr<nsIMsgIncomingServer> server(do_QueryInterface(m_nntpServer));
+ nsCString hostname;
+ server->GetRealHostName(hostname);
+ int32_t choice = 1;
+ MsgPromptLoginFailed(m_msgWindow, hostname, &choice);
+
+ if (choice == 1) // Cancel
+ {
+ // When the user requests to cancel the connection, we can't do anything
+ // anymore.
+ NNTP_LOG_NOTE("Password failed, user opted to cancel connection");
+ m_nextState = NNTP_ERROR;
+ return;
+ }
+
+ if (choice == 2) // New password
+ {
+ NNTP_LOG_NOTE("Password failed, user opted to enter new password");
+ NS_ASSERTION(m_newsFolder, "no newsFolder");
+ m_newsFolder->ForgetAuthenticationCredentials();
+ }
+ else if (choice == 0) // Retry
+ {
+ NNTP_LOG_NOTE("Password failed, user opted to retry");
+ }
+
+ // At this point, we've either forgotten the password or opted to retry. In
+ // both cases, we need to try to auth with the password again, so return to
+ // the authentication state.
+ m_nextState = NNTP_BEGIN_AUTHORIZE;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// XOVER, XHDR, and HEAD processing code
+// Used for filters
+// State machine explanation located in doxygen comments for nsNNTPProtocol
+///////////////////////////////////////////////////////////////////////////////
+
+nsresult nsNNTPProtocol::BeginReadXover()
+{
+ int32_t count; /* Response fields */
+ nsresult rv = NS_OK;
+
+ rv = SetCurrentGroup();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* Make sure we never close and automatically reopen the connection at this
+ point; we'll confuse libmsg too much... */
+
+ SetFlag(NNTP_SOME_PROTOCOL_SUCCEEDED);
+
+ /* We have just issued a GROUP command and read the response.
+ Now parse that response to help decide which articles to request
+ xover data for.
+ */
+ PR_sscanf(m_responseText,
+ "%d %d %d",
+ &count,
+ &m_firstPossibleArticle,
+ &m_lastPossibleArticle);
+
+ m_newsgroupList = do_CreateInstance(NS_NNTPNEWSGROUPLIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_newsgroupList->Initialize(m_runningURL, m_newsFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_newsFolder->UpdateSummaryFromNNTPInfo(m_firstPossibleArticle, m_lastPossibleArticle, count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_numArticlesLoaded = 0;
+
+ // if the user sets max_articles to a bogus value, get them everything
+ m_numArticlesWanted = m_maxArticles > 0 ? m_maxArticles : 1L << 30;
+
+ m_nextState = NNTP_FIGURE_NEXT_CHUNK;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::FigureNextChunk()
+{
+ nsresult rv = NS_OK;
+ int32_t status = 0;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (m_firstArticle > 0)
+ {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) add to known articles: %d - %d", this, m_firstArticle, m_lastArticle));
+
+ if (NS_SUCCEEDED(rv) && m_newsgroupList) {
+ rv = m_newsgroupList->AddToKnownArticles(m_firstArticle,
+ m_lastArticle);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (m_numArticlesLoaded >= m_numArticlesWanted)
+ {
+ m_nextState = NEWS_PROCESS_XOVER;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ NS_ASSERTION(m_newsgroupList, "no newsgroupList");
+ if (!m_newsgroupList) return NS_ERROR_FAILURE;
+
+ bool getOldMessages = false;
+ if (m_runningURL) {
+ rv = m_runningURL->GetGetOldMessages(&getOldMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = m_newsgroupList->SetGetOldMessages(getOldMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_newsgroupList->GetRangeOfArtsToDownload(m_msgWindow,
+ m_firstPossibleArticle,
+ m_lastPossibleArticle,
+ m_numArticlesWanted - m_numArticlesLoaded,
+ &(m_firstArticle),
+ &(m_lastArticle),
+ &status);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_firstArticle <= 0 || m_firstArticle > m_lastArticle)
+ {
+ /* Nothing more to get. */
+ m_nextState = NEWS_PROCESS_XOVER;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) Chunk will be (%d-%d)", this, m_firstArticle, m_lastArticle));
+
+ m_articleNumber = m_firstArticle;
+
+ /* was MSG_InitXOVER() */
+ if (m_newsgroupList) {
+ rv = m_newsgroupList->InitXOVER(m_firstArticle, m_lastArticle);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ if (TestFlag(NNTP_NO_XOVER_SUPPORT))
+ m_nextState = NNTP_READ_GROUP;
+ else
+ m_nextState = NNTP_XOVER_SEND;
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::XoverSend()
+{
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "XOVER %d-%d" CRLF,
+ m_firstArticle,
+ m_lastArticle);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_XOVER_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return SendData(outputBuffer);
+}
+
+/* see if the xover response is going to return us data
+ * if the proper code isn't returned then assume xover
+ * isn't supported and use
+ * normal read_group
+ */
+
+nsresult nsNNTPProtocol::ReadXoverResponse()
+{
+#ifdef TEST_NO_XOVER_SUPPORT
+ m_responseCode = MK_NNTP_RESPONSE_CHECK_ERROR; /* pretend XOVER generated an error */
+#endif
+
+ if(m_responseCode != MK_NNTP_RESPONSE_XOVER_OK)
+ {
+ /* If we didn't get back "224 data follows" from the XOVER request,
+ then that must mean that this server doesn't support XOVER. Or
+ maybe the server's XOVER support is busted or something. So,
+ in that case, fall back to the very slow HEAD method.
+
+ But, while debugging here at HQ, getting into this state means
+ something went very wrong, since our servers do XOVER. Thus
+ the assert.
+ */
+ /*NS_ASSERTION (0,"something went very wrong");*/
+ m_nextState = NNTP_READ_GROUP;
+ SetFlag(NNTP_NO_XOVER_SUPPORT);
+ }
+ else
+ {
+ m_nextState = NNTP_XOVER;
+ }
+
+ return NS_OK; /* continue */
+}
+
+/* process the xover list as it comes from the server
+ * and load it into the sort list.
+ */
+
+nsresult nsNNTPProtocol::ReadXover(nsIInputStream * inputStream, uint32_t length)
+{
+ char *line, *lineToFree;
+ nsresult rv;
+ uint32_t status = 1;
+
+ bool pauseForMoreData = false;
+ line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if(!line)
+ return rv; /* no line yet or TCP error */
+
+ if(line[0] == '.' && line[1] == '\0')
+ {
+ m_nextState = NNTP_XHDR_SEND;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_Free(lineToFree);
+ return NS_OK;
+ }
+ else if (line [0] == '.' && line [1] == '.')
+ /* The NNTP server quotes all lines beginning with "." by doubling it. */
+ line++;
+
+ /* almost correct
+ */
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ rv = m_newsgroupList->ProcessXOVERLINE(line, &status);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to process the XOVERLINE");
+
+ m_numArticlesLoaded++;
+ PR_Free(lineToFree);
+ return rv;
+}
+
+/* Finished processing all the XOVER data.
+*/
+
+nsresult nsNNTPProtocol::ProcessXover()
+{
+ nsresult rv;
+
+ /* xover_parse_state stored in MSG_Pane cd->pane */
+ NS_ASSERTION(m_newsgroupList, "no newsgroupList");
+ if (!m_newsgroupList) return NS_ERROR_FAILURE;
+
+ // Some people may use the notifications in CallFilters to close the cached
+ // connections, which will clear m_newsgroupList. So we keep a copy for
+ // ourselves to ward off this threat.
+ nsCOMPtr<nsINNTPNewsgroupList> list(m_newsgroupList);
+ list->CallFilters();
+ int32_t status = 0;
+ rv = list->FinishXOVERLINE(0, &status);
+ m_newsgroupList = nullptr;
+ if (NS_SUCCEEDED(rv) && status < 0) return NS_ERROR_FAILURE;
+
+ m_nextState = NEWS_DONE;
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::XhdrSend()
+{
+ nsCString header;
+ m_newsgroupList->InitXHDR(header);
+ if (header.IsEmpty())
+ {
+ m_nextState = NNTP_FIGURE_NEXT_CHUNK;
+ return NS_OK;
+ }
+
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+ PR_snprintf(outputBuffer, OUTPUT_BUFFER_SIZE, "XHDR %s %d-%d" CRLF,
+ header.get(), m_firstArticle, m_lastArticle);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_XHDR_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return SendData(outputBuffer);
+}
+
+nsresult nsNNTPProtocol::XhdrResponse(nsIInputStream *inputStream)
+{
+ if (m_responseCode != MK_NNTP_RESPONSE_XHDR_OK)
+ {
+ m_nextState = NNTP_READ_GROUP;
+ // The reasoning behind setting this flag and not an XHDR flag is that we
+ // are going to have to use HEAD instead. At that point, using XOVER as
+ // well is just wasting bandwidth.
+ SetFlag(NNTP_NO_XOVER_SUPPORT);
+ return NS_OK;
+ }
+
+ char *line, *lineToFree;
+ nsresult rv;
+ uint32_t status = 1;
+
+ bool pauseForMoreData = false;
+ line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if (pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if (!line)
+ return rv; /* no line yet or TCP error */
+
+ if (line[0] == '.' && line[1] == '\0')
+ {
+ m_nextState = NNTP_XHDR_SEND;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_Free(lineToFree);
+ return NS_OK;
+ }
+
+ if (status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ rv = m_newsgroupList->ProcessXHDRLine(nsDependentCString(line));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to process the XHDRLINE");
+
+ m_numArticlesLoaded++;
+ PR_Free(lineToFree);
+ return rv;
+}
+
+nsresult nsNNTPProtocol::ReadHeaders()
+{
+ if(m_articleNumber > m_lastArticle)
+ { /* end of groups */
+
+ m_newsgroupList->InitHEAD(-1);
+ m_nextState = NNTP_FIGURE_NEXT_CHUNK;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ else
+ {
+ m_newsgroupList->InitHEAD(m_articleNumber);
+
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "HEAD %ld" CRLF,
+ m_articleNumber++);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_READ_GROUP_RESPONSE;
+
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return SendData(outputBuffer);
+ }
+}
+
+/* See if the "HEAD" command was successful
+*/
+
+nsresult nsNNTPProtocol::ReadNewsgroupResponse()
+{
+ if (m_responseCode == MK_NNTP_RESPONSE_ARTICLE_HEAD)
+ { /* Head follows - parse it:*/
+ m_nextState = NNTP_READ_GROUP_BODY;
+
+ return NS_OK;
+ }
+ else
+ {
+ m_newsgroupList->HEADFailed(m_articleNumber);
+ m_nextState = NNTP_READ_GROUP;
+ return NS_OK;
+ }
+}
+
+/* read the body of the "HEAD" command
+*/
+nsresult nsNNTPProtocol::ReadNewsgroupBody(nsIInputStream * inputStream, uint32_t length)
+{
+ char *line, *lineToFree;
+ nsresult rv;
+ uint32_t status = 1;
+
+ bool pauseForMoreData = false;
+ line = lineToFree = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ /* if TCP error of if there is not a full line yet return
+ */
+ if(!line)
+ return rv;
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) read_group_body: got line: %s|",this,line));
+
+ /* End of body? */
+ if (line[0]=='.' && line[1]=='\0')
+ {
+ m_nextState = NNTP_READ_GROUP;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ else if (line [0] == '.' && line [1] == '.')
+ /* The NNTP server quotes all lines beginning with "." by doubling it. */
+ line++;
+
+ nsCString safe_line(line);
+ rv = m_newsgroupList->ProcessHEADLine(safe_line);
+ PR_Free(lineToFree);
+ return rv;
+}
+
+
+nsresult nsNNTPProtocol::GetNewsStringByID(int32_t stringID, char16_t **aString)
+{
+ nsresult rv;
+ nsAutoString resultString(NS_LITERAL_STRING("???"));
+
+ if (!m_stringBundle)
+ {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(m_stringBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (m_stringBundle) {
+ char16_t *ptrv = nullptr;
+ rv = m_stringBundle->GetStringFromID(stringID, &ptrv);
+
+ if (NS_FAILED(rv)) {
+ resultString.AssignLiteral("[StringID");
+ resultString.AppendInt(stringID);
+ resultString.AppendLiteral("?]");
+ *aString = ToNewUnicode(resultString);
+ }
+ else {
+ *aString = ptrv;
+ }
+ }
+ else {
+ rv = NS_OK;
+ *aString = ToNewUnicode(resultString);
+ }
+ return rv;
+}
+
+nsresult nsNNTPProtocol::GetNewsStringByName(const char *aName, char16_t **aString)
+{
+ nsresult rv;
+ nsAutoString resultString(NS_LITERAL_STRING("???"));
+ if (!m_stringBundle)
+ {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(m_stringBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (m_stringBundle)
+ {
+ nsAutoString unicodeName;
+ CopyASCIItoUTF16(nsDependentCString(aName), unicodeName);
+
+ char16_t *ptrv = nullptr;
+ rv = m_stringBundle->GetStringFromName(unicodeName.get(), &ptrv);
+
+ if (NS_FAILED(rv))
+ {
+ resultString.AssignLiteral("[StringName");
+ resultString.Append(NS_ConvertASCIItoUTF16(aName));
+ resultString.AppendLiteral("?]");
+ *aString = ToNewUnicode(resultString);
+ }
+ else
+ {
+ *aString = ptrv;
+ }
+ }
+ else
+ {
+ rv = NS_OK;
+ *aString = ToNewUnicode(resultString);
+ }
+ return rv;
+}
+
+// sspitzer: PostMessageInFile is derived from nsSmtpProtocol::SendMessageInFile()
+nsresult nsNNTPProtocol::PostMessageInFile(nsIFile *postMessageFile)
+{
+ nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningURL);
+ if (url && postMessageFile)
+ nsMsgProtocol::PostMessage(url, postMessageFile);
+
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ // for now, we are always done at this point..we aren't making multiple
+ // calls to post data...
+
+ // always issue a '.' and CRLF when we are done...
+ PL_strcpy(m_dataBuf, "." CRLF);
+ SendData(m_dataBuf);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_SEND_POST_DATA_RESPONSE;
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::PostData()
+{
+ /* returns 0 on done and negative on error
+ * positive if it needs to continue.
+ */
+ NNTP_LOG_NOTE("nsNNTPProtocol::PostData()");
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsINNTPNewsgroupPost> message;
+ rv = m_runningURL->GetMessageToPost(getter_AddRefs(message));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIFile> filePath;
+ rv = message->GetPostMessageFile(getter_AddRefs(filePath));
+ if (NS_SUCCEEDED(rv))
+ PostMessageInFile(filePath);
+ }
+
+ return NS_OK;
+}
+
+
+/* interpret the response code from the server
+ * after the post is done
+ */
+nsresult nsNNTPProtocol::PostDataResponse()
+{
+ if (m_responseCode != MK_NNTP_RESPONSE_POST_OK)
+ {
+ AlertError(MK_NNTP_ERROR_MESSAGE,m_responseText);
+ m_nextState = NEWS_ERROR;
+ return NS_ERROR_FAILURE;
+ }
+ m_nextState = NEWS_POST_DONE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::CheckForArticle()
+{
+ m_nextState = NEWS_ERROR;
+ if (m_responseCode >= 220 && m_responseCode <= 223) {
+ /* Yes, this article is already there, we're all done. */
+ return NS_OK;
+ }
+ else
+ {
+ /* The article isn't there, so the failure we had earlier wasn't due to
+ a duplicate message-id. Return the error from that previous
+ posting attempt (which is already in ce->URL_s->error_msg). */
+ return NS_ERROR_FAILURE;
+ }
+}
+
+nsresult nsNNTPProtocol::StartCancel()
+{
+ nsresult rv = SendData(NNTP_CMD_POST);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NEWS_DO_CANCEL;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return rv;
+}
+
+void nsNNTPProtocol::CheckIfAuthor(nsIMsgIdentity *aIdentity, const nsCString &aOldFrom, nsCString &aFrom)
+{
+ nsAutoCString from;
+ nsresult rv = aIdentity->GetEmail(from);
+ if (NS_FAILED(rv))
+ return;
+ MOZ_LOG(NNTP, LogLevel::Info,("from = %s", from.get()));
+
+ nsCString us;
+ nsCString them;
+ ExtractEmail(EncodedHeader(from), us);
+ ExtractEmail(EncodedHeader(aOldFrom), them);
+
+ MOZ_LOG(NNTP, LogLevel::Info,("us = %s, them = %s", us.get(), them.get()));
+
+ if (us.Equals(them, nsCaseInsensitiveCStringComparator()))
+ aFrom = from;
+}
+
+nsresult nsNNTPProtocol::DoCancel()
+{
+ int32_t status = 0;
+ bool failure = false;
+ nsresult rv = NS_OK;
+ char *id = nullptr;
+ char *subject = nullptr;
+ char *newsgroups = nullptr;
+ char *distribution = nullptr;
+ char *body = nullptr;
+ bool requireConfirmationForCancel = true;
+ bool showAlertAfterCancel = true;
+
+ int L;
+
+ /* #### Should we do a more real check than this? If the POST command
+ didn't respond with "MK_NNTP_RESPONSE_POST_SEND_NOW Ok", then it's not ready for us to throw a
+ message at it... But the normal posting code doesn't do this check.
+ Why?
+ */
+ NS_ASSERTION (m_responseCode == MK_NNTP_RESPONSE_POST_SEND_NOW, "code != POST_SEND_NOW");
+
+ // These shouldn't be set yet, since the headers haven't been "flushed"
+ // "Distribution: " doesn't appear to be required, so
+ // don't assert on m_cancelDistribution
+ NS_ASSERTION (m_cancelID &&
+ m_cancelFromHdr &&
+ m_cancelNewsgroups, "null ptr");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(brandBundle));
+ NS_ENSURE_TRUE(brandBundle, NS_ERROR_FAILURE);
+
+ nsString brandFullName;
+ rv = brandBundle->GetStringFromName(u"brandFullName",
+ getter_Copies(brandFullName));
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ConvertUTF16toUTF8 appName(brandFullName);
+
+ newsgroups = m_cancelNewsgroups;
+ distribution = m_cancelDistribution;
+ id = m_cancelID;
+ nsCString oldFrom(m_cancelFromHdr);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIPrompt> dialog;
+ if (m_runningURL)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(m_runningURL));
+ rv = GetPromptDialogFromUrl(msgUrl, getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ASSERTION (id && newsgroups, "null ptr");
+ if (!id || !newsgroups) return NS_ERROR_FAILURE;
+
+ m_cancelNewsgroups = nullptr;
+ m_cancelDistribution = nullptr;
+ m_cancelFromHdr = nullptr;
+ m_cancelID = nullptr;
+
+ L = PL_strlen (id);
+
+ subject = (char *) PR_Malloc (L + 20);
+ body = (char *) PR_Malloc (PL_strlen (appName.get()) + 100);
+
+ nsString alertText;
+ nsString confirmText;
+ int32_t confirmCancelResult = 0;
+
+ // A little early to declare, but the goto causes problems
+ nsAutoCString otherHeaders;
+
+ /* Make sure that this loser isn't cancelling someone else's posting.
+ Yes, there are occasionally good reasons to do so. Those people
+ capable of making that decision (news admins) have other tools with
+ which to cancel postings (like telnet.)
+
+ Don't do this if server tells us it will validate user. DMB 3/19/97
+ */
+ bool cancelchk=false;
+ rv = m_nntpServer->QueryExtension("CANCELCHK",&cancelchk);
+ nsCString from;
+ if (NS_SUCCEEDED(rv) && !cancelchk)
+ {
+ NNTP_LOG_NOTE("CANCELCHK not supported");
+
+ // get the current identity from the news session....
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && accountManager) {
+ nsCOMPtr<nsIArray> identities;
+ rv = accountManager->GetAllIdentities(getter_AddRefs(identities));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t length;
+ rv = identities->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < length && from.IsEmpty(); ++i)
+ {
+ nsCOMPtr<nsIMsgIdentity> identity(do_QueryElementAt(identities, i, &rv));
+ if (NS_SUCCEEDED(rv))
+ CheckIfAuthor(identity, oldFrom, from);
+ }
+ }
+
+ if (from.IsEmpty())
+ {
+ GetNewsStringByName("cancelDisallowed", getter_Copies(alertText));
+ rv = dialog->Alert(nullptr, alertText.get());
+ // XXX: todo, check rv?
+
+ /* After the cancel is disallowed, Make the status update to be the same as though the
+ cancel was allowed, otherwise, the newsgroup is not able to take further requests as
+ reported here */
+ status = MK_NNTP_CANCEL_DISALLOWED;
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_SEND_POST_DATA_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ failure = true;
+ goto FAIL;
+ }
+ else
+ {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) CANCELCHK not supported, so post the cancel message as %s", this, from.get()));
+ }
+ }
+ else
+ NNTP_LOG_NOTE("CANCELCHK supported, don't do the us vs. them test");
+
+ // QA needs to be able to disable this confirm dialog, for the automated tests. see bug #31057
+ rv = prefBranch->GetBoolPref(PREF_NEWS_CANCEL_CONFIRM, &requireConfirmationForCancel);
+ if (NS_FAILED(rv) || requireConfirmationForCancel) {
+ /* Last chance to cancel the cancel.*/
+ GetNewsStringByName("cancelConfirm", getter_Copies(confirmText));
+ bool dummyValue = false;
+ rv = dialog->ConfirmEx(nullptr, confirmText.get(), nsIPrompt::STD_YES_NO_BUTTONS,
+ nullptr, nullptr, nullptr, nullptr, &dummyValue, &confirmCancelResult);
+ if (NS_FAILED(rv))
+ confirmCancelResult = 1; // Default to No.
+ }
+ else
+ confirmCancelResult = 0; // Default to Yes.
+
+ if (confirmCancelResult != 0) {
+ // they cancelled the cancel
+ status = MK_NNTP_NOT_CANCELLED;
+ failure = true;
+ goto FAIL;
+ }
+
+ if (!subject || !body)
+ {
+ status = MK_OUT_OF_MEMORY;
+ failure = true;
+ goto FAIL;
+ }
+
+ PL_strcpy (subject, "cancel ");
+ PL_strcat (subject, id);
+
+ otherHeaders.AppendLiteral("Control: cancel ");
+ otherHeaders += id;
+ otherHeaders.AppendLiteral(CRLF);
+ if (distribution) {
+ otherHeaders.AppendLiteral("Distribution: ");
+ otherHeaders += distribution;
+ otherHeaders.AppendLiteral(CRLF);
+ }
+
+ PL_strcpy (body, "This message was cancelled from within ");
+ PL_strcat (body, appName.get());
+ PL_strcat (body, "." CRLF);
+
+ m_cancelStatus = 0;
+
+ {
+ /* NET_BlockingWrite() should go away soon? I think. */
+ /* The following are what we really need to cancel a posted message */
+ char *data;
+ data = PR_smprintf("From: %s" CRLF
+ "Newsgroups: %s" CRLF
+ "Subject: %s" CRLF
+ "References: %s" CRLF
+ "%s" /* otherHeaders, already with CRLF */
+ CRLF /* body separator */
+ "%s" /* body, already with CRLF */
+ "." CRLF, /* trailing message terminator "." */
+ from.get(), newsgroups, subject, id,
+ otherHeaders.get(), body);
+
+ rv = SendData(data);
+ PR_Free (data);
+ if (NS_FAILED(rv)) {
+ nsAutoCString errorText;
+ errorText.AppendInt(status);
+ AlertError(MK_TCP_WRITE_ERROR, errorText.get());
+ failure = true;
+ goto FAIL;
+ }
+
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_SEND_POST_DATA_RESPONSE;
+
+ // QA needs to be able to turn this alert off, for the automate tests. see bug #31057
+ rv = prefBranch->GetBoolPref(PREF_NEWS_CANCEL_ALERT_ON_SUCCESS, &showAlertAfterCancel);
+ if (NS_FAILED(rv) || showAlertAfterCancel) {
+ GetNewsStringByName("messageCancelled", getter_Copies(alertText));
+ rv = dialog->Alert(nullptr, alertText.get());
+ // XXX: todo, check rv?
+ }
+
+ if (!m_runningURL) return NS_ERROR_FAILURE;
+
+ // delete the message from the db here.
+ NS_ASSERTION(NS_SUCCEEDED(rv) && m_newsFolder && (m_key != nsMsgKey_None), "need more to remove this message from the db");
+ if ((m_key != nsMsgKey_None) && (m_newsFolder))
+ rv = m_newsFolder->RemoveMessage(m_key);
+
+ }
+
+FAIL:
+ NS_ASSERTION(m_newsFolder,"no news folder");
+ if (m_newsFolder)
+ rv = ( failure ) ? m_newsFolder->CancelFailed()
+ : m_newsFolder->CancelComplete();
+
+ PR_Free (id);
+ PR_Free (subject);
+ PR_Free (newsgroups);
+ PR_Free (distribution);
+ PR_Free (body);
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::XPATSend()
+{
+ nsresult rv = NS_OK;
+ int32_t slash = m_searchData.FindChar('/');
+
+ if (slash >= 0)
+ {
+ /* extract the XPAT encoding for one query term */
+ /* char *next_search = NULL; */
+ char *command = NULL;
+ char *unescapedCommand = NULL;
+ char *endOfTerm = NULL;
+ NS_MsgSACopy (&command, m_searchData.get() + slash + 1);
+ endOfTerm = PL_strchr(command, '/');
+ if (endOfTerm)
+ *endOfTerm = '\0';
+ NS_MsgSACat(&command, CRLF);
+
+ unescapedCommand = MSG_UnEscapeSearchUrl(command);
+
+ /* send one term off to the server */
+ rv = SendData(unescapedCommand);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_XPAT_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ PR_Free(command);
+ PR_Free(unescapedCommand);
+ }
+ else
+ {
+ m_nextState = NEWS_DONE;
+ }
+ return rv;
+}
+
+nsresult nsNNTPProtocol::XPATResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 1;
+ nsresult rv;
+
+ if (m_responseCode != MK_NNTP_RESPONSE_XPAT_OK)
+ {
+ AlertError(MK_NNTP_ERROR_MESSAGE,m_responseText);
+ m_nextState = NNTP_ERROR;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_ERROR_FAILURE;
+ }
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ NNTP_LOG_READ(line);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if (line)
+ {
+ if (line[0] != '.')
+ {
+ long articleNumber;
+ PR_sscanf(line, "%ld", &articleNumber);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (mailnewsurl)
+ {
+ nsCOMPtr <nsIMsgSearchSession> searchSession;
+ nsCOMPtr <nsIMsgSearchAdapter> searchAdapter;
+ mailnewsurl->GetSearchSession(getter_AddRefs(searchSession));
+ if (searchSession)
+ {
+ searchSession->GetRunningAdapter(getter_AddRefs(searchAdapter));
+ if (searchAdapter)
+ searchAdapter->AddHit((uint32_t) articleNumber);
+ }
+ }
+ }
+ else
+ {
+ /* set up the next term for next time around */
+ int32_t slash = m_searchData.FindChar('/');
+
+ if (slash >= 0)
+ m_searchData.Cut(0, slash + 1);
+ else
+ m_searchData.Truncate();
+
+ m_nextState = NNTP_XPAT_SEND;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_FREEIF(line);
+ return NS_OK;
+ }
+ }
+ PR_FREEIF(line);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::ListPrettyNames()
+{
+
+ nsCString group_name;
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ m_newsFolder->GetRawName(group_name);
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "LIST PRETTYNAMES %.512s" CRLF,
+ group_name.get());
+
+ nsresult rv = SendData(outputBuffer);
+ NNTP_LOG_NOTE(outputBuffer);
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_LIST_PRETTY_NAMES_RESPONSE;
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::ListPrettyNamesResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+
+ if (m_responseCode != MK_NNTP_RESPONSE_LIST_OK)
+ {
+ m_nextState = DISPLAY_NEWSGROUPS;
+ /* m_nextState = NEWS_DONE; */
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
+
+ NNTP_LOG_READ(line);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if (line)
+ {
+ if (line[0] != '.')
+ {
+#if 0 // SetPrettyName is not yet implemented. No reason to bother
+ int i;
+ /* find whitespace separator if it exits */
+ for (i=0; line[i] != '\0' && !NET_IS_SPACE(line[i]); i++)
+ ; /* null body */
+
+ char *prettyName;
+ if(line[i] == '\0')
+ prettyName = &line[i];
+ else
+ prettyName = &line[i+1];
+
+ line[i] = 0; /* terminate group name */
+ if (i > 0) {
+ nsAutoCString charset;
+ nsAutoString lineUtf16, prettyNameUtf16;
+ if (NS_FAILED(m_nntpServer->GetCharset(charset) ||
+ NS_FAILED(ConvertToUnicode(charset, line, lineUtf16)) ||
+ NS_FAILED(ConvertToUnicode(charset, prettyName, prettyNameUtf16)))) {
+ CopyUTF8toUTF16(line, lineUtf16);
+ CopyUTF8toUTF16(prettyName, prettyNameUtf16);
+ }
+ m_nntpServer->SetPrettyNameForGroup(lineUtf16, prettyNameUtf16);
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) adding pretty name %s", this,
+ NS_ConvertUTF16toUTF8(prettyNameUtf16).get()));
+ }
+#endif
+ }
+ else
+ {
+ m_nextState = DISPLAY_NEWSGROUPS; /* this assumes we were doing a list */
+ /* m_nextState = NEWS_DONE; */ /* ### dmb - don't really know */
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_FREEIF(line);
+ return NS_OK;
+ }
+ }
+ PR_FREEIF(line);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::ListXActive()
+{
+ nsCString group_name;
+ nsresult rv;
+ rv = m_newsFolder->GetRawName(group_name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "LIST XACTIVE %.512s" CRLF,
+ group_name.get());
+
+ rv = SendData(outputBuffer);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_LIST_XACTIVE_RESPONSE;
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::ListXActiveResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+ nsresult rv;
+
+ NS_ASSERTION(m_responseCode == MK_NNTP_RESPONSE_LIST_OK, "code != LIST_OK");
+ if (m_responseCode != MK_NNTP_RESPONSE_LIST_OK)
+ {
+ m_nextState = DISPLAY_NEWSGROUPS;
+ /* m_nextState = NEWS_DONE; */
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
+
+ NNTP_LOG_READ(line);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ /* almost correct */
+ if(status > 1)
+ {
+ mBytesReceived += status;
+ mBytesReceivedSinceLastStatusUpdate += status;
+ }
+
+ if (line)
+ {
+ if (line[0] != '.')
+ {
+ char *s = line;
+ /* format is "rec.arts.movies.past-films 7302 7119 csp"
+ */
+ while (*s && !NET_IS_SPACE(*s))
+ s++;
+ if (*s)
+ {
+ char flags[32]; /* ought to be big enough */
+ *s = 0;
+ PR_sscanf(s + 1,
+ "%d %d %31s",
+ &m_firstPossibleArticle,
+ &m_lastPossibleArticle,
+ flags);
+
+
+ NS_ASSERTION(m_nntpServer, "no nntp incoming server");
+ if (m_nntpServer) {
+ rv = m_nntpServer->AddNewsgroupToList(line);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to add to subscribe ds");
+ }
+
+ /* we're either going to list prettynames first, or list
+ all prettynames every time, so we won't care so much
+ if it gets interrupted. */
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) got xactive for %s of %s", this, line, flags));
+ /* This isn't required, because the extra info is
+ initialized to false for new groups. And it's
+ an expensive call.
+ */
+ /* MSG_SetGroupNeedsExtraInfo(cd->host, line, false); */
+ }
+ }
+ else
+ {
+ bool xactive=false;
+ rv = m_nntpServer->QueryExtension("XACTIVE",&xactive);
+ if (m_typeWanted == NEW_GROUPS &&
+ NS_SUCCEEDED(rv) && xactive)
+ {
+ nsCOMPtr <nsIMsgNewsFolder> old_newsFolder;
+ old_newsFolder = m_newsFolder;
+ nsCString groupName;
+
+ rv = m_nntpServer->GetFirstGroupNeedingExtraInfo(groupName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = m_nntpServer->FindGroup(groupName,
+ getter_AddRefs(m_newsFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // see if we got a different group
+ if (old_newsFolder && m_newsFolder &&
+ (old_newsFolder.get() != m_newsFolder.get()))
+ /* make sure we're not stuck on the same group */
+ {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) listing xactive for %s", this, groupName.get()));
+ m_nextState = NNTP_LIST_XACTIVE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_FREEIF(line);
+ return NS_OK;
+ }
+ else
+ {
+ m_newsFolder = nullptr;
+ }
+ }
+ bool listpname;
+ rv = m_nntpServer->QueryExtension("LISTPNAME",&listpname);
+ if (NS_SUCCEEDED(rv) && listpname)
+ m_nextState = NNTP_LIST_PRETTY_NAMES;
+ else
+ m_nextState = DISPLAY_NEWSGROUPS; /* this assumes we were doing a list - who knows? */
+ /* m_nextState = NEWS_DONE; */ /* ### dmb - don't really know */
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_FREEIF(line);
+ return NS_OK;
+ }
+ }
+ PR_FREEIF(line);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SendListGroup()
+{
+ nsresult rv;
+ char outputBuffer[OUTPUT_BUFFER_SIZE];
+
+ NS_ASSERTION(m_newsFolder,"no newsFolder");
+ if (!m_newsFolder) return NS_ERROR_FAILURE;
+ nsCString newsgroupName;
+
+ rv = m_newsFolder->GetRawName(newsgroupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ PR_snprintf(outputBuffer,
+ OUTPUT_BUFFER_SIZE,
+ "listgroup %.512s" CRLF,
+ newsgroupName.get());
+
+ m_articleList = do_CreateInstance(NS_NNTPARTICLELIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = m_articleList->Initialize(m_newsFolder);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = SendData(outputBuffer);
+
+ m_nextState = NNTP_RESPONSE;
+ m_nextStateAfterResponse = NNTP_LIST_GROUP_RESPONSE;
+ SetFlag(NNTP_PAUSE_FOR_READ);
+
+ return rv;
+}
+
+nsresult nsNNTPProtocol::SendListGroupResponse(nsIInputStream * inputStream, uint32_t length)
+{
+ uint32_t status = 0;
+
+ NS_ASSERTION(m_responseCode == MK_NNTP_RESPONSE_GROUP_SELECTED, "code != GROUP_SELECTED");
+ if (m_responseCode != MK_NNTP_RESPONSE_GROUP_SELECTED)
+ {
+ m_nextState = NEWS_DONE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+
+ if (line)
+ {
+ mozilla::DebugOnly<nsresult> rv;
+ if (line[0] != '.')
+ {
+ nsMsgKey found_id = nsMsgKey_None;
+ PR_sscanf(line, "%ld", &found_id);
+ rv = m_articleList->AddArticleKey(found_id);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "add article key failed");
+ }
+ else
+ {
+ rv = m_articleList->FinishAddingArticleKeys();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "finish adding article key failed");
+ m_articleList = nullptr;
+ m_nextState = NEWS_DONE; /* ### dmb - don't really know */
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ PR_FREEIF(line);
+ return NS_OK;
+ }
+ }
+ PR_FREEIF(line);
+ return NS_OK;
+}
+
+
+nsresult nsNNTPProtocol::Search()
+{
+ NS_ERROR("Search not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsNNTPProtocol::SearchResponse()
+{
+ if (MK_NNTP_RESPONSE_TYPE(m_responseCode) == MK_NNTP_RESPONSE_TYPE_OK)
+ m_nextState = NNTP_SEARCH_RESULTS;
+ else
+ m_nextState = NEWS_DONE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::SearchResults(nsIInputStream *inputStream, uint32_t length)
+{
+ uint32_t status = 1;
+ nsresult rv;
+
+ bool pauseForMoreData = false;
+ char *line = m_lineStreamBuffer->ReadNextLine(inputStream, status, pauseForMoreData, &rv);
+
+ if(pauseForMoreData)
+ {
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ return NS_OK;
+ }
+ if (!line)
+ return rv; /* no line yet */
+
+ if ('.' == line[0])
+ {
+ /* all overview lines received */
+ m_nextState = NEWS_DONE;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+ PR_FREEIF(line);
+ return rv;
+}
+
+/* Sets state for the transfer. This used to be known as net_setup_news_stream */
+nsresult nsNNTPProtocol::SetupForTransfer()
+{
+ if (m_typeWanted == NEWS_POST)
+ {
+ m_nextState = NNTP_SEND_POST_DATA;
+ }
+ else if(m_typeWanted == LIST_WANTED)
+ {
+ if (TestFlag(NNTP_USE_FANCY_NEWSGROUP))
+ m_nextState = NNTP_LIST_XACTIVE_RESPONSE;
+ else
+ m_nextState = NNTP_READ_LIST_BEGIN;
+ }
+ else if(m_typeWanted == GROUP_WANTED)
+ m_nextState = NNTP_XOVER_BEGIN;
+ else if(m_typeWanted == NEW_GROUPS)
+ m_nextState = NNTP_NEWGROUPS_BEGIN;
+ else if(m_typeWanted == ARTICLE_WANTED ||
+ m_typeWanted== CANCEL_WANTED)
+ m_nextState = NNTP_BEGIN_ARTICLE;
+ else if (m_typeWanted== SEARCH_WANTED)
+ m_nextState = NNTP_XPAT_SEND;
+ else
+ {
+ NS_ERROR("unexpected");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+// The following method is used for processing the news state machine.
+// It returns a negative number (mscott: we'll change this to be an enumerated type which we'll coordinate
+// with the netlib folks?) when we are done processing.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+nsresult nsNNTPProtocol::ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length)
+{
+ nsresult status = NS_OK;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (inputStream && (!mailnewsurl || !m_nntpServer))
+ {
+ // In these cases, we are going to return since our data is effectively
+ // invalid. However, nsInputStream would really rather that we at least read
+ // some of our input data (even if not all of it). Therefore, we'll read a
+ // little bit.
+ char buffer[128];
+ uint32_t readData = 0;
+ inputStream->Read(buffer, 127, &readData);
+ buffer[readData] = '\0';
+ MOZ_LOG(NNTP, LogLevel::Debug, ("(%p) Ignoring data: %s", this, buffer));
+ }
+
+ if (!mailnewsurl)
+ return NS_OK; // probably no data available - it's OK.
+
+ if (!m_nntpServer)
+ {
+ // Parsing must result in our m_nntpServer being set, so we should never
+ // have a case where m_nntpServer being false is safe. Most likely, we have
+ // already closed our socket and we are merely flushing out the socket
+ // receive queue. Since the user told us to stop, don't process any more
+ // input.
+ return inputStream ? inputStream->Close() : NS_OK;
+ }
+
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+
+ while(!TestFlag(NNTP_PAUSE_FOR_READ))
+ {
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) Next state: %s",this, stateLabels[m_nextState]));
+ // examine our current state and call an appropriate handler for that state.....
+ switch(m_nextState)
+ {
+ case NNTP_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = NewsResponse(inputStream, length);
+ break;
+
+ // mscott: I've removed the states involving connections on the assumption
+ // that core netlib will now be managing that information.
+
+ case NNTP_LOGIN_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = LoginResponse();
+ break;
+
+ case NNTP_SEND_MODE_READER:
+ status = SendModeReader();
+ break;
+
+ case NNTP_SEND_MODE_READER_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendModeReaderResponse();
+ break;
+
+ case SEND_LIST_EXTENSIONS:
+ status = SendListExtensions();
+ break;
+ case SEND_LIST_EXTENSIONS_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendListExtensionsResponse(inputStream, length);
+ break;
+ case SEND_LIST_SEARCHES:
+ status = SendListSearches();
+ break;
+ case SEND_LIST_SEARCHES_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendListSearchesResponse(inputStream, length);
+ break;
+ case NNTP_LIST_SEARCH_HEADERS:
+ status = SendListSearchHeaders();
+ break;
+ case NNTP_LIST_SEARCH_HEADERS_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendListSearchHeadersResponse(inputStream, length);
+ break;
+ case NNTP_GET_PROPERTIES:
+ status = GetProperties();
+ break;
+ case NNTP_GET_PROPERTIES_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = GetPropertiesResponse(inputStream, length);
+ break;
+ case SEND_LIST_SUBSCRIPTIONS:
+ status = SendListSubscriptions();
+ break;
+ case SEND_LIST_SUBSCRIPTIONS_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendListSubscriptionsResponse(inputStream, length);
+ break;
+
+ case SEND_FIRST_NNTP_COMMAND:
+ status = SendFirstNNTPCommand(url);
+ break;
+ case SEND_FIRST_NNTP_COMMAND_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendFirstNNTPCommandResponse();
+ break;
+
+ case NNTP_SEND_GROUP_FOR_ARTICLE:
+ status = SendGroupForArticle();
+ break;
+ case NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendGroupForArticleResponse();
+ break;
+ case NNTP_SEND_ARTICLE_NUMBER:
+ status = SendArticleNumber();
+ break;
+
+ case SETUP_NEWS_STREAM:
+ status = SetupForTransfer();
+ break;
+
+ case NNTP_BEGIN_AUTHORIZE:
+ status = BeginAuthorization();
+ break;
+
+ case NNTP_AUTHORIZE_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = AuthorizationResponse();
+ break;
+
+ case NNTP_PASSWORD_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = PasswordResponse();
+ break;
+
+ // read list
+ case NNTP_READ_LIST_BEGIN:
+ status = BeginReadNewsList();
+ break;
+ case NNTP_READ_LIST:
+ status = ReadNewsList(inputStream, length);
+ break;
+
+ // news group
+ case DISPLAY_NEWSGROUPS:
+ status = DisplayNewsgroups();
+ break;
+ case NNTP_NEWGROUPS_BEGIN:
+ status = BeginNewsgroups();
+ break;
+ case NNTP_NEWGROUPS:
+ status = ProcessNewsgroups(inputStream, length);
+ break;
+
+ // article specific
+ case NNTP_BEGIN_ARTICLE:
+ status = BeginArticle();
+ break;
+
+ case NNTP_READ_ARTICLE:
+ status = ReadArticle(inputStream, length);
+ break;
+
+ case NNTP_XOVER_BEGIN:
+ status = BeginReadXover();
+ break;
+
+ case NNTP_FIGURE_NEXT_CHUNK:
+ status = FigureNextChunk();
+ break;
+
+ case NNTP_XOVER_SEND:
+ status = XoverSend();
+ break;
+
+ case NNTP_XOVER:
+ status = ReadXover(inputStream, length);
+ break;
+
+ case NNTP_XOVER_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = ReadXoverResponse();
+ break;
+
+ case NEWS_PROCESS_XOVER:
+ case NEWS_PROCESS_BODIES:
+ status = ProcessXover();
+ break;
+
+ case NNTP_XHDR_SEND:
+ status = XhdrSend();
+ break;
+
+ case NNTP_XHDR_RESPONSE:
+ status = XhdrResponse(inputStream);
+ break;
+
+ case NNTP_READ_GROUP:
+ status = ReadHeaders();
+ break;
+
+ case NNTP_READ_GROUP_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = ReadNewsgroupResponse();
+ break;
+
+ case NNTP_READ_GROUP_BODY:
+ status = ReadNewsgroupBody(inputStream, length);
+ break;
+
+ case NNTP_SEND_POST_DATA:
+ status = PostData();
+ break;
+ case NNTP_SEND_POST_DATA_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = PostDataResponse();
+ break;
+
+ case NNTP_CHECK_FOR_MESSAGE:
+ status = CheckForArticle();
+ break;
+
+ // cancel
+ case NEWS_START_CANCEL:
+ status = StartCancel();
+ break;
+
+ case NEWS_DO_CANCEL:
+ status = DoCancel();
+ break;
+
+ // XPAT
+ case NNTP_XPAT_SEND:
+ status = XPATSend();
+ break;
+ case NNTP_XPAT_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = XPATResponse(inputStream, length);
+ break;
+
+ // search
+ case NNTP_SEARCH:
+ status = Search();
+ break;
+ case NNTP_SEARCH_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SearchResponse();
+ break;
+ case NNTP_SEARCH_RESULTS:
+ status = SearchResults(inputStream, length);
+ break;
+
+
+ case NNTP_LIST_PRETTY_NAMES:
+ status = ListPrettyNames();
+ break;
+ case NNTP_LIST_PRETTY_NAMES_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = ListPrettyNamesResponse(inputStream, length);
+ break;
+ case NNTP_LIST_XACTIVE:
+ status = ListXActive();
+ break;
+ case NNTP_LIST_XACTIVE_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = ListXActiveResponse(inputStream, length);
+ break;
+ case NNTP_LIST_GROUP:
+ status = SendListGroup();
+ break;
+ case NNTP_LIST_GROUP_RESPONSE:
+ if (inputStream == nullptr)
+ SetFlag(NNTP_PAUSE_FOR_READ);
+ else
+ status = SendListGroupResponse(inputStream, length);
+ break;
+ case NEWS_DONE:
+ m_nextState = NEWS_FREE;
+ break;
+ case NEWS_POST_DONE:
+ NNTP_LOG_NOTE("NEWS_POST_DONE");
+ mailnewsurl->SetUrlState(false, NS_OK);
+ m_nextState = NEWS_FREE;
+ break;
+ case NEWS_ERROR:
+ NNTP_LOG_NOTE("NEWS_ERROR");
+ if (m_responseCode == MK_NNTP_RESPONSE_ARTICLE_NOTFOUND || m_responseCode == MK_NNTP_RESPONSE_ARTICLE_NONEXIST)
+ mailnewsurl->SetUrlState(false, NS_MSG_NEWS_ARTICLE_NOT_FOUND);
+ else
+ mailnewsurl->SetUrlState(false, NS_ERROR_FAILURE);
+ m_nextState = NEWS_FREE;
+ break;
+ case NNTP_ERROR:
+ // XXX do we really want to remove the connection from
+ // the cache on error?
+ /* check if this connection came from the cache or if it was
+ * a new connection. If it was not new lets start it over
+ * again. But only if we didn't have any successful protocol
+ * dialog at all.
+ */
+ FinishMemCacheEntry(false); // cleanup mem cache entry
+ if (m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NOTFOUND && m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NONEXIST)
+ return CloseConnection();
+ MOZ_FALLTHROUGH;
+ case NEWS_FREE:
+ // Remember when we last used this connection
+ m_lastActiveTimeStamp = PR_Now();
+ CleanupAfterRunningUrl();
+ MOZ_FALLTHROUGH;
+ case NNTP_SUSPENDED:
+ return NS_OK;
+ break;
+ default:
+ /* big error */
+ return NS_ERROR_FAILURE;
+
+ } // end switch
+
+ if (NS_FAILED(status) && m_nextState != NEWS_ERROR &&
+ m_nextState != NNTP_ERROR && m_nextState != NEWS_FREE)
+ {
+ m_nextState = NNTP_ERROR;
+ ClearFlag(NNTP_PAUSE_FOR_READ);
+ }
+
+ } /* end big while */
+
+ return NS_OK; /* keep going */
+}
+
+NS_IMETHODIMP nsNNTPProtocol::CloseConnection()
+{
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) ClosingConnection",this));
+ SendData(NNTP_CMD_QUIT); // this will cause OnStopRequest get called, which will call CloseSocket()
+ // break some cycles
+ CleanupNewsgroupList();
+
+ if (m_nntpServer) {
+ m_nntpServer->RemoveConnection(this);
+ m_nntpServer = nullptr;
+ }
+ CloseSocket();
+ m_newsFolder = nullptr;
+
+ if (m_articleList) {
+ m_articleList->FinishAddingArticleKeys();
+ m_articleList = nullptr;
+ }
+
+ m_key = nsMsgKey_None;
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::CleanupNewsgroupList()
+{
+ nsresult rv;
+ if (!m_newsgroupList) return NS_OK;
+ int32_t status = 0;
+ rv = m_newsgroupList->FinishXOVERLINE(0,&status);
+ m_newsgroupList = nullptr;
+ NS_ASSERTION(NS_SUCCEEDED(rv), "FinishXOVERLINE failed");
+ return rv;
+}
+
+nsresult nsNNTPProtocol::CleanupAfterRunningUrl()
+{
+ /* do we need to know if we're parsing xover to call finish xover? */
+ /* yes, I think we do! Why did I think we should??? */
+ /* If we've gotten to NEWS_FREE and there is still XOVER
+ data, there was an error or we were interrupted or
+ something. So, tell libmsg there was an abnormal
+ exit so that it can free its data. */
+
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) CleanupAfterRunningUrl()", this));
+
+ // send StopRequest notification after we've cleaned up the protocol
+ // because it can synchronously causes a new url to get run in the
+ // protocol - truly evil, but we're stuck at the moment.
+ if (m_channelListener)
+ (void) m_channelListener->OnStopRequest(this, m_channelContext, NS_OK);
+
+ if (m_loadGroup)
+ (void) m_loadGroup->RemoveRequest(static_cast<nsIRequest *>(this), nullptr, NS_OK);
+ CleanupNewsgroupList();
+
+ // clear out mem cache entry so we're not holding onto it.
+ if (m_runningURL)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningURL);
+ if (mailnewsurl)
+ {
+ mailnewsurl->SetUrlState(false, NS_OK);
+ mailnewsurl->SetMemCacheEntry(nullptr);
+ }
+ }
+
+ Cleanup();
+
+ mDisplayInputStream = nullptr;
+ mDisplayOutputStream = nullptr;
+ mProgressEventSink = nullptr;
+ SetOwner(nullptr);
+
+ m_channelContext = nullptr;
+ m_channelListener = nullptr;
+ m_loadGroup = nullptr;
+ mCallbacks = nullptr;
+
+ // disable timeout before caching.
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
+ if (strans)
+ strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, PR_UINT32_MAX);
+
+ // don't mark ourselves as not busy until we are done cleaning up the connection. it should be the
+ // last thing we do.
+ SetIsBusy(false);
+
+ return NS_OK;
+}
+
+nsresult nsNNTPProtocol::CloseSocket()
+{
+ MOZ_LOG(NNTP, LogLevel::Info,("(%p) ClosingSocket()",this));
+
+ if (m_nntpServer) {
+ m_nntpServer->RemoveConnection(this);
+ m_nntpServer = nullptr;
+ }
+
+ CleanupAfterRunningUrl(); // is this needed?
+ return nsMsgProtocol::CloseSocket();
+}
+
+void nsNNTPProtocol::SetProgressBarPercent(uint32_t aProgress, uint32_t aProgressMax)
+{
+ // XXX 64-bit
+ if (mProgressEventSink)
+ mProgressEventSink->OnProgress(this, m_channelContext, uint64_t(aProgress),
+ uint64_t(aProgressMax));
+}
+
+nsresult
+nsNNTPProtocol::SetProgressStatus(const char16_t *aMessage)
+{
+ nsresult rv = NS_OK;
+ if (mProgressEventSink)
+ rv = mProgressEventSink->OnStatus(this, m_channelContext, NS_OK, aMessage);
+ return rv;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::GetContentType(nsACString &aContentType)
+{
+
+ // if we've been set with a content type, then return it....
+ // this happens when we go through libmime now as it sets our new content type
+ if (!mContentType.IsEmpty())
+ {
+ aContentType = mContentType;
+ return NS_OK;
+ }
+
+ // otherwise do what we did before...
+
+ if (m_typeWanted == GROUP_WANTED)
+ aContentType.AssignLiteral("x-application-newsgroup");
+ else if (m_typeWanted == IDS_WANTED)
+ aContentType.AssignLiteral("x-application-newsgroup-listids");
+ else
+ aContentType.AssignLiteral("message/rfc822");
+ return NS_OK;
+}
+
+nsresult
+nsNNTPProtocol::AlertError(int32_t errorCode, const char *text)
+{
+ nsresult rv = NS_OK;
+
+ // get the prompt from the running url....
+ if (m_runningURL) {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(m_runningURL));
+ nsCOMPtr<nsIPrompt> dialog;
+ rv = GetPromptDialogFromUrl(msgUrl, getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString alertText;
+ rv = GetNewsStringByID(MK_NNTP_ERROR_MESSAGE, getter_Copies(alertText));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (text) {
+ alertText.Append(' ');
+ alertText.Append(NS_ConvertASCIItoUTF16(text));
+ }
+ rv = dialog->Alert(nullptr, alertText.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsNNTPProtocol::GetCurrentFolder(nsIMsgFolder **aFolder)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ NS_ENSURE_ARG_POINTER(aFolder);
+ if (m_newsFolder)
+ rv = m_newsFolder->QueryInterface(NS_GET_IID(nsIMsgFolder), (void **) aFolder);
+ return rv;
+}
+
diff --git a/mailnews/news/src/nsNNTPProtocol.h b/mailnews/news/src/nsNNTPProtocol.h
new file mode 100644
index 000000000..08db18ee8
--- /dev/null
+++ b/mailnews/news/src/nsNNTPProtocol.h
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNNTPProtocol_h___
+#define nsNNTPProtocol_h___
+
+#include "nsMsgProtocol.h"
+
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsINntpUrl.h"
+#include "nsINntpIncomingServer.h"
+#include "nsINNTPProtocol.h"
+
+#include "nsINNTPNewsgroupList.h"
+#include "nsINNTPArticleList.h"
+#include "nsIMsgAsyncPrompter.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIMsgWindow.h"
+
+#include "nsMsgLineBuffer.h"
+#include "nsIStringBundle.h"
+#include "nsITimer.h"
+#include "nsICacheEntryOpenCallback.h"
+
+// this is only needed as long as our libmime hack is in place
+#include "prio.h"
+
+// State Flags (Note, I use the word state in terms of storing
+// state information about the connection (authentication, have we sent
+// commands, etc. I do not intend it to refer to protocol state)
+
+#define NNTP_PAUSE_FOR_READ 0x00000001 /* should we pause for the next read */
+#define NNTP_PROXY_AUTH_REQUIRED 0x00000002 /* is auth required */
+#define NNTP_SENT_PROXY_AUTH 0x00000004 /* have we sent a proxy auth? */
+#define NNTP_READER_PERFORMED 0x00000010 /* have we sent any cmds to the server yet? */
+#define NNTP_USE_FANCY_NEWSGROUP 0x00000020 /* use LIST XACTIVE or LIST */
+#define NNTP_DESTROY_PROGRESS_GRAPH 0x00000040 /* do we need to destroy graph progress */
+#define NNTP_SOME_PROTOCOL_SUCCEEDED 0x0000080 /* some protocol has suceeded so don't kill the connection */
+#define NNTP_NO_XOVER_SUPPORT 0x00000100 /* xover command is not supported here */
+
+/* states of the machine
+ */
+typedef enum _StatesEnum {
+NNTP_RESPONSE,
+#ifdef BLOCK_UNTIL_AVAILABLE_CONNECTION
+NNTP_BLOCK_UNTIL_CONNECTIONS_ARE_AVAILABLE,
+NNTP_CONNECTIONS_ARE_AVAILABLE,
+#endif
+NNTP_CONNECT,
+NNTP_CONNECT_WAIT,
+NNTP_LOGIN_RESPONSE,
+NNTP_SEND_MODE_READER,
+NNTP_SEND_MODE_READER_RESPONSE,
+SEND_LIST_EXTENSIONS,
+SEND_LIST_EXTENSIONS_RESPONSE,
+SEND_LIST_SEARCHES,
+SEND_LIST_SEARCHES_RESPONSE,
+NNTP_LIST_SEARCH_HEADERS,
+NNTP_LIST_SEARCH_HEADERS_RESPONSE,
+NNTP_GET_PROPERTIES,
+NNTP_GET_PROPERTIES_RESPONSE,
+SEND_LIST_SUBSCRIPTIONS,
+SEND_LIST_SUBSCRIPTIONS_RESPONSE,
+SEND_FIRST_NNTP_COMMAND,
+SEND_FIRST_NNTP_COMMAND_RESPONSE,
+SETUP_NEWS_STREAM,
+NNTP_BEGIN_AUTHORIZE,
+NNTP_AUTHORIZE_RESPONSE,
+NNTP_PASSWORD_RESPONSE,
+NNTP_READ_LIST_BEGIN,
+NNTP_READ_LIST,
+DISPLAY_NEWSGROUPS,
+NNTP_NEWGROUPS_BEGIN,
+NNTP_NEWGROUPS,
+NNTP_BEGIN_ARTICLE,
+NNTP_READ_ARTICLE,
+NNTP_XOVER_BEGIN,
+NNTP_FIGURE_NEXT_CHUNK,
+NNTP_XOVER_SEND,
+NNTP_XOVER_RESPONSE,
+NNTP_XOVER,
+NEWS_PROCESS_XOVER,
+NNTP_XHDR_SEND,
+NNTP_XHDR_RESPONSE,
+NNTP_READ_GROUP,
+NNTP_READ_GROUP_RESPONSE,
+NNTP_READ_GROUP_BODY,
+NNTP_SEND_GROUP_FOR_ARTICLE,
+NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE,
+NNTP_SEND_ARTICLE_NUMBER,
+NEWS_PROCESS_BODIES,
+NNTP_PRINT_ARTICLE_HEADERS,
+NNTP_SEND_POST_DATA,
+NNTP_SEND_POST_DATA_RESPONSE,
+NNTP_CHECK_FOR_MESSAGE,
+NEWS_START_CANCEL,
+NEWS_DO_CANCEL,
+NNTP_XPAT_SEND,
+NNTP_XPAT_RESPONSE,
+NNTP_SEARCH,
+NNTP_SEARCH_RESPONSE,
+NNTP_SEARCH_RESULTS,
+NNTP_LIST_PRETTY_NAMES,
+NNTP_LIST_PRETTY_NAMES_RESPONSE,
+NNTP_LIST_XACTIVE,
+NNTP_LIST_XACTIVE_RESPONSE,
+NNTP_LIST_GROUP,
+NNTP_LIST_GROUP_RESPONSE,
+NEWS_DONE,
+NEWS_POST_DONE,
+NEWS_ERROR,
+NNTP_ERROR,
+NEWS_FREE,
+NNTP_SUSPENDED
+} StatesEnum;
+
+class nsICacheEntry;
+
+class nsNNTPProtocol : public nsMsgProtocol,
+ public nsINNTPProtocol,
+ public nsITimerCallback,
+ public nsICacheEntryOpenCallback,
+ public nsIMsgAsyncPromptListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINNTPPROTOCOL
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIMSGASYNCPROMPTLISTENER
+
+ // Creating a protocol instance requires the URL
+ // need to call Initialize after we do a new of nsNNTPProtocol
+ nsNNTPProtocol(nsINntpIncomingServer *aServer, nsIURI *aURL,
+ nsIMsgWindow *aMsgWindow);
+
+ // stop binding is a "notification" informing us that the stream associated with aURL is going away.
+ NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports * aCtxt, nsresult aStatus) override;
+
+ char * m_ProxyServer; /* proxy server hostname */
+
+ NS_IMETHOD Cancel(nsresult status) override; // handle stop button
+ NS_IMETHOD GetContentType(nsACString &aContentType) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) override;
+ NS_IMETHOD AsyncOpen2(nsIStreamListener *listener) override;
+ NS_IMETHOD GetOriginalURI(nsIURI* *aURI) override;
+ NS_IMETHOD SetOriginalURI(nsIURI* aURI) override;
+
+ nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer) override;
+
+private:
+ virtual ~nsNNTPProtocol();
+ /**
+ * Triggers the protocol state machine.
+ * Most of the time, this machine will read as much input as it can before
+ * closing.
+ *
+ * This method additionally handles some states not covered by other methods:
+ * NEWS_DONE: Alias for NEWS_FREE
+ * NEWS_POST_DONE: Alias for NEWS_FREE that cleans up the URL state
+ * NEWS_ERROR: An error which permits further use of the connection
+ * NNTP_ERROR: An error which does not permit further use of the connection
+ * NEWS_FREE: Cleans up from the current URL and prepares for the next one
+ * NNTP_SUSPENDED: A state where the state machine does not read input until
+ * reenabled by a non-network related callback
+ *
+ * @note Use of NNTP_SUSPENDED is dangerous: if input comes along the socket,
+ * the code will not read the input stream at all. Therefore, it is strongly
+ * advised to suspend the request before using this state.
+ */
+ virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length) override;
+ virtual nsresult CloseSocket() override;
+
+ // we have our own implementation of SendData which writes to the nntp log
+ // and then calls the base class to transmit the data
+ nsresult SendData(const char * dataBuffer, bool aSuppressLogging = false) override;
+
+ nsresult CleanupAfterRunningUrl();
+ void Cleanup(); //free char* member variables
+
+ void ParseHeaderForCancel(char *buf);
+
+ virtual const char* GetType() override { return "nntp"; }
+
+ static void CheckIfAuthor(nsIMsgIdentity *aIdentity, const nsCString &aOldFrom, nsCString &aFrom);
+
+ nsCOMPtr <nsINNTPNewsgroupList> m_newsgroupList;
+ nsCOMPtr <nsINNTPArticleList> m_articleList;
+
+ nsCOMPtr <nsIMsgNewsFolder> m_newsFolder;
+ nsCOMPtr <nsIMsgWindow> m_msgWindow;
+
+ nsCOMPtr<nsIAsyncInputStream> mDisplayInputStream;
+ nsCOMPtr<nsIAsyncOutputStream> mDisplayOutputStream;
+ nsMsgLineStreamBuffer * m_lineStreamBuffer; // used to efficiently extract lines from the incoming data stream
+ // the nsINntpURL that is currently running
+ nsCOMPtr<nsINntpUrl> m_runningURL;
+ bool m_connectionBusy;
+ bool m_fromCache; // is this connection from the cache?
+ PRTime m_lastActiveTimeStamp;
+ nsNewsAction m_newsAction;
+
+ // Generic state information -- What state are we in? What state do we want to go to
+ // after the next response? What was the last response code? etc.
+ StatesEnum m_nextState;
+ StatesEnum m_nextStateAfterResponse;
+ int32_t m_typeWanted; /* Article, List, or Group */
+ int32_t m_responseCode; /* code returned from NNTP server */
+ int32_t m_previousResponseCode;
+ char *m_responseText; /* text returned from NNTP server */
+
+ char *m_dataBuf;
+ uint32_t m_dataBufSize;
+
+ /* for group command */
+ nsCString m_currentGroup; /* current group */
+
+ int32_t m_firstArticle;
+ int32_t m_lastArticle;
+ int32_t m_firstPossibleArticle;
+ int32_t m_lastPossibleArticle;
+
+ int32_t m_numArticlesLoaded; /* How many articles we got XOVER lines for. */
+ int32_t m_numArticlesWanted; /* How many articles we wanted to get XOVER lines for. */
+ int32_t m_maxArticles; /* max articles to get during an XOVER */
+
+ // Cancelation specific state. In particular, the headers that should be
+ // used for the cancelation message.
+ // mscott: we can probably replace this stuff with nsString
+ char *m_cancelFromHdr;
+ char *m_cancelNewsgroups;
+ char *m_cancelDistribution;
+ char *m_cancelID;
+ int32_t m_cancelStatus;
+
+ // variable for ReadNewsList
+ int32_t m_readNewsListCount;
+
+ // Per news article state information. (article number, author, subject, id, etc
+ nsCString m_messageID;
+ int32_t m_articleNumber; /* current article number */
+ nsCString m_searchData;
+
+ int32_t m_originalContentLength; /* the content length at the time of calling graph progress */
+
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+
+ nsCOMPtr<nsINntpIncomingServer> m_nntpServer;
+
+ nsresult GetNewsStringByName(const char *aName, char16_t **aString);
+ nsresult GetNewsStringByID(int32_t stringID, char16_t **aString);
+
+ nsresult PostMessageInFile(nsIFile * filePath);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Communication methods --> Reading and writing protocol
+ //////////////////////////////////////////////////////////////////////////////
+
+ int32_t ReadLine(nsIInputStream * inputStream, uint32_t length, char ** line);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Protocol Methods --> This protocol is state driven so each protocol method
+ // is designed to re-act to the current "state". I've attempted to
+ // group them together based on functionality.
+ //////////////////////////////////////////////////////////////////////////////
+
+ // gets the response code from the nntp server and the response line. Returns the TCP return code
+ // from the read.
+ nsresult NewsResponse(nsIInputStream *inputStream, uint32_t length);
+
+ // Interpret the server response after the connect.
+ // Returns negative if the server responds unexpectedly
+ nsresult LoginResponse();
+ nsresult SendModeReader();
+ nsresult SendModeReaderResponse();
+
+ nsresult SendListExtensions();
+ nsresult SendListExtensionsResponse(nsIInputStream *inputStream, uint32_t length);
+
+ nsresult SendListSearches();
+ nsresult SendListSearchesResponse(nsIInputStream *inputStream, uint32_t length);
+
+ nsresult SendListSearchHeaders();
+ nsresult SendListSearchHeadersResponse(nsIInputStream *inputStream, uint32_t length);
+
+ nsresult GetProperties();
+ nsresult GetPropertiesResponse(nsIInputStream *inputStream, uint32_t length);
+
+ nsresult SendListSubscriptions();
+ nsresult SendListSubscriptionsResponse(nsIInputStream *inputStream, uint32_t length);
+
+ // Figure out what the first command is and send it.
+ // Returns the status from the NETWrite.
+ nsresult SendFirstNNTPCommand(nsIURI *url);
+
+ // Interprets the server response from the first command sent.
+ // returns negative if the server responds unexpectedly.
+ nsresult SendFirstNNTPCommandResponse();
+
+ nsresult SetupForTransfer();
+
+ nsresult SendGroupForArticle();
+ nsresult SendGroupForArticleResponse();
+
+ nsresult SendArticleNumber();
+ nsresult BeginArticle();
+ nsresult ReadArticle(nsIInputStream *inputStream, uint32_t length);
+ nsresult DisplayArticle(nsIInputStream *inputStream, uint32_t length);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // News authentication code
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Sends the username via AUTHINFO USER, NNTP_BEGIN_AUTHORIZE.
+ * This also handles the step of getting authentication credentials; if this
+ * requires a prompt to run, the connection will be suspended and we will
+ * come back to this point.
+ * Followed by: NNTP_AUTHORIZE_RESPONSE if the username was sent
+ * NNTP_SUSPENDED if we need to wait for the password
+ */
+ nsresult BeginAuthorization();
+ /**
+ * Sends the password if necessary, the state NNTP_AUTHORIZE_RESPONSE.
+ * This also reads the result of the username.
+ * Followed by: NNTP_PASSWORD_RESPONSE if a password is sent
+ * NNTP_SEND_MODE_READER if MODE READER needed auth
+ * SEND_FIRST_NNTP_COMMAND if any other command needed auth
+ * NNTP_ERROR if the username was rejected
+ */
+ nsresult AuthorizationResponse();
+ /**
+ * This state, NNTP_PASSWORD_RESPONSE, reads the password.
+ * Followed by: NNTP_SEND_MODE_READER if MODE READER needed auth
+ * SEND_FIRST_NNTP_COMMAND if any other command needed auth
+ * NNTP_ERROR if the password was rejected
+ */
+ nsresult PasswordResponse();
+
+ nsresult BeginReadNewsList();
+ nsresult ReadNewsList(nsIInputStream *inputStream, uint32_t length);
+
+ // Newsgroup specific protocol handlers
+ nsresult DisplayNewsgroups();
+ nsresult BeginNewsgroups();
+ nsresult ProcessNewsgroups(nsIInputStream *inputStream, uint32_t length);
+
+ // Protocol handlers used for posting data
+ nsresult PostData();
+ nsresult PostDataResponse();
+
+ nsresult CheckForArticle();
+
+ /////////////////////////////////////////////////////////////////////////////
+ // XHDR, XOVER, HEAD filtering process handlers
+ // These are ordered by the rough order of usage
+ /////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * The first step in the filtering process, the state NNTP_XOVER_BEGIN.
+ * This method sets up m_newsgroupList.
+ * Followed by: NNTP_FIGURE_NEXT_CHUNK
+ */
+ nsresult BeginReadXover();
+ /**
+ * The loop control for filtering, the state NNTP_FIGURE_NEXT_CHUNK.
+ * This method contacts the newsgroupList to figure out which articles to
+ * download and then prepares it for XOVER support.
+ * Followed by: NEWS_PROCESS_XOVER if everything is finished
+ * NNTP_READ_GROUP if XOVER doesn't work
+ * NNTP_XOVER_SEND if XOVER does work
+ */
+ nsresult FigureNextChunk();
+
+ // The XOVER process core
+ /**
+ * The state NNTP_XOVER_SEND, which actually sends the message.
+ * Followed by: NNTP_XOVER_RESPONSE
+ */
+ nsresult XoverSend();
+ /**
+ * This state, NNTP_XOVER_RESPONSE, actually checks the XOVER capabiliity.
+ * Followed by: NNTP_XOVER if XOVER is supported
+ * NNTP_READ_GROUP if it isn't
+ */
+ nsresult ReadXoverResponse();
+ /**
+ * This state, NNTP_XOVER, processes the results from the XOVER command.
+ * It asks nsNNTPNewsgroupList to process the line using ProcessXOVERLINE.
+ * Followed by: NNTP_XHDR_SEND
+ */
+ nsresult ReadXover(nsIInputStream *inputStream, uint32_t length);
+
+ // The XHDR process core
+ /**
+ * This state, NNTP_XHDR_SEND, sends the XHDR command.
+ * The headers are all managed by nsNNTPNewsgroupList, and this picks them up
+ * one by one as they are needed.
+ * Followed by: NNTP_XHDR_RESPONSE if there is a header to be sent
+ * NNTP_FIGURE_NEXT_CHUNK if all headers have been sent
+ */
+ nsresult XhdrSend();
+ /**
+ * This state, NNTP_XHDR_RESPONSE, processes the XHDR response.
+ * It mostly passes the information off to nsNNTPNewsgroupList, and only does
+ * response code checking and a bit of preprocessing. Note that if XHDR
+ * doesn't work properly, HEAD fallback is switched on and all subsequent
+ * chunks will NOT use XOVER.
+ * Followed by: NNTP_READ_GROUP if XHDR doesn't work properly
+ * NNTP_XHDR_SEND when finished processing XHR.
+ */
+ nsresult XhdrResponse(nsIInputStream *inputStream);
+
+ // HEAD processing core
+ /**
+ * This state, NNTP_READ_GROUP, is the control for the HEAD processor.
+ * It sends the HEAD command and increments the article number until it is
+ * finished. WARNING: HEAD is REALLY SLOW.
+ * Followed by: NNTP_FIGURE_NEXT_CHUNK when it is finished
+ * NNTP_READ_GROUP_RESPONSE when it is not
+ */
+ nsresult ReadHeaders();
+ /**
+ * This state, NNTP_READ_GROUP_RESPONSE, checks if the article exists.
+ * Because it is required by NNTP, if it doesn't work, the only problem would
+ * be that the article doesn't exist. Passes off article number data to
+ * nsNNTPNewsgroupList.
+ * Followed by: NNTP_READ_GROUP_BODY if the article exists
+ * NNTP_READ_GROUP if it doesn't.
+ */
+ nsresult ReadNewsgroupResponse();
+ /**
+ * This state, NNTP_READ_GROUP_BODY, reads the body of the HEAD command.
+ * Once again, it passes information off to nsNNTPNewsgroupList.
+ * Followed by: NNTP_READ_GROUP
+ */
+ nsresult ReadNewsgroupBody(nsIInputStream *inputStream, uint32_t length);
+
+ /**
+ * This state, NNTP_PROCESS_XOVER, is the final step of the filter-processing
+ * code. Currently, all it does is cleans up the unread count and calls the
+ * filters, both via nsNNTPNewsgroupList.
+ * Followed by: NEWS_DONE
+ */
+ nsresult ProcessXover();
+
+
+
+ // Canceling
+ nsresult StartCancel();
+ nsresult DoCancel();
+
+ // XPAT
+ nsresult XPATSend();
+ nsresult XPATResponse(nsIInputStream *inputStream, uint32_t length);
+ nsresult ListPrettyNames();
+ nsresult ListPrettyNamesResponse(nsIInputStream *inputStream, uint32_t length);
+
+ nsresult ListXActive();
+ nsresult ListXActiveResponse(nsIInputStream *inputStream, uint32_t length);
+
+ // for "?list-ids"
+ nsresult SendListGroup();
+ nsresult SendListGroupResponse(nsIInputStream *inputStream, uint32_t length);
+
+ // Searching Protocol....
+ nsresult Search();
+ nsresult SearchResponse();
+ nsresult SearchResults(nsIInputStream *inputStream, uint32_t length);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // End of Protocol Methods
+ //////////////////////////////////////////////////////////////////////////////
+
+ nsresult ParseURL(nsIURI *aURL, nsCString &aGroup, nsCString &aMessageID);
+
+ void SetProgressBarPercent(uint32_t aProgress, uint32_t aProgressMax);
+ nsresult SetProgressStatus(const char16_t *aMessage);
+ nsresult InitializeNewsFolderFromUri(const char *uri);
+ void TimerCallback();
+
+ void HandleAuthenticationFailure();
+ nsCOMPtr <nsIInputStream> mInputStream;
+ nsCOMPtr <nsITimer> mUpdateTimer;
+ nsresult AlertError(int32_t errorCode, const char *text);
+ int32_t mBytesReceived;
+ int32_t mBytesReceivedSinceLastStatusUpdate;
+ PRTime m_startTime;
+ int32_t mNumGroupsListed;
+ nsMsgKey m_key;
+
+ nsresult SetCurrentGroup(); /* sets m_currentGroup. should be called after doing a successful GROUP command */
+ nsresult CleanupNewsgroupList(); /* cleans up m_newsgroupList, and set it to null */
+
+ // cache related helper methods
+ void FinishMemCacheEntry(bool valid); // either mark it valid, or doom it
+ nsresult OpenCacheEntry(); // makes a request to the cache service for a cache entry for a url
+ bool ReadFromLocalCache(); // attempts to read the url out of our local (offline) cache....
+ nsresult ReadFromNewsConnection(); // creates a new news connection to read the url
+ nsresult ReadFromMemCache(nsICacheEntry *entry); // attempts to read the url out of our memory cache
+ nsresult SetupPartExtractorListener(nsIStreamListener * aConsumer);
+};
+
+
+#endif // nsNNTPProtocol_h___
diff --git a/mailnews/news/src/nsNewsAutoCompleteSearch.js b/mailnews/news/src/nsNewsAutoCompleteSearch.js
new file mode 100644
index 000000000..335c32cba
--- /dev/null
+++ b/mailnews/news/src/nsNewsAutoCompleteSearch.js
@@ -0,0 +1,141 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var kACR = Ci.nsIAutoCompleteResult;
+var kSupportedTypes = new Set(["addr_newsgroups", "addr_followup"]);
+
+function nsNewsAutoCompleteResult(aSearchString) {
+ // Can't create this in the prototype as we'd get the same array for
+ // all instances
+ this._searchResults = [];
+ this.searchString = aSearchString;
+}
+
+nsNewsAutoCompleteResult.prototype = {
+ _searchResults: null,
+
+ // nsIAutoCompleteResult
+
+ searchString: null,
+ searchResult: kACR.RESULT_NOMATCH,
+ defaultIndex: -1,
+ errorDescription: null,
+
+ get matchCount() {
+ return this._searchResults.length;
+ },
+
+ getValueAt: function getValueAt(aIndex) {
+ return this._searchResults[aIndex].value;
+ },
+
+ getLabelAt: function getLabelAt(aIndex) {
+ return this._searchResults[aIndex].value;
+ },
+
+ getCommentAt: function getCommentAt(aIndex) {
+ return this._searchResults[aIndex].comment;
+ },
+
+ getStyleAt: function getStyleAt(aIndex) {
+ return "subscribed-news";
+ },
+
+ getImageAt: function getImageAt(aIndex) {
+ return "";
+ },
+
+ getFinalCompleteValueAt: function(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ removeValueAt: function removeValueAt(aRowIndex, aRemoveFromDB) {
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([kACR])
+}
+
+function nsNewsAutoCompleteSearch() {}
+
+nsNewsAutoCompleteSearch.prototype = {
+ // For component registration
+ classDescription: "Newsgroup Autocomplete",
+ classID: Components.ID("e9bb3330-ac7e-11de-8a39-0800200c9a66"),
+
+ cachedAccountKey: "",
+ cachedServer: null,
+
+ /**
+ * Find the newsgroup server associated with the given accountKey.
+ *
+ * @param accountKey The key of the account.
+ * @return The incoming news server (or null if one does not exist).
+ */
+ _findServer: function _findServer(accountKey) {
+ let account = MailServices.accounts.getAccount(accountKey);
+
+ if (account.incomingServer.type == 'nntp')
+ return account.incomingServer;
+ else
+ return null;
+ },
+
+ // nsIAutoCompleteSearch
+ startSearch: function startSearch(aSearchString, aSearchParam,
+ aPreviousResult, aListener) {
+ let params = aSearchParam ? JSON.parse(aSearchParam) : {};
+ let result = new nsNewsAutoCompleteResult(aSearchString);
+ if (!("type" in params) || !("accountKey" in params) ||
+ !kSupportedTypes.has(params.type)) {
+ result.searchResult = kACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, result);
+ return;
+ }
+
+ if (("accountKey" in params) && (params.accountKey != this.cachedAccountKey)) {
+ this.cachedAccountKey = params.accountKey;
+ this.cachedServer = this._findServer(params.accountKey);
+ }
+
+ if (this.cachedServer) {
+ let groups = this.cachedServer.rootFolder.subFolders;
+ while (groups.hasMoreElements()) {
+ let curr = groups.getNext().QueryInterface(Ci.nsIMsgFolder);
+ if (curr.prettiestName.includes(aSearchString)) {
+ result._searchResults.push({
+ value: curr.prettiestName,
+ comment: this.cachedServer.prettyName
+ });
+ }
+ }
+ }
+
+ if (result.matchCount) {
+ result.searchResult = kACR.RESULT_SUCCESS;
+ // If the user does not select anything, use the first entry:
+ result.defaultIndex = 0;
+ }
+ aListener.onSearchResult(this, result);
+ },
+
+ stopSearch: function stopSearch() {
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch])
+};
+
+// Module
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsNewsAutoCompleteSearch]);
diff --git a/mailnews/news/src/nsNewsAutoCompleteSearch.manifest b/mailnews/news/src/nsNewsAutoCompleteSearch.manifest
new file mode 100644
index 000000000..7a90b8074
--- /dev/null
+++ b/mailnews/news/src/nsNewsAutoCompleteSearch.manifest
@@ -0,0 +1,2 @@
+component {e9bb3330-ac7e-11de-8a39-0800200c9a66} nsNewsAutoCompleteSearch.js
+contract @mozilla.org/autocomplete/search;1?name=news {e9bb3330-ac7e-11de-8a39-0800200c9a66}
diff --git a/mailnews/news/src/nsNewsDownloadDialogArgs.cpp b/mailnews/news/src/nsNewsDownloadDialogArgs.cpp
new file mode 100644
index 000000000..277f635b0
--- /dev/null
+++ b/mailnews/news/src/nsNewsDownloadDialogArgs.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNewsDownloadDialogArgs.h"
+
+nsNewsDownloadDialogArgs::nsNewsDownloadDialogArgs()
+{
+ mArticleCount = 0;
+ mServerKey = "";
+ mHitOK = false;
+ mDownloadAll = false;
+}
+
+nsNewsDownloadDialogArgs::~nsNewsDownloadDialogArgs()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsNewsDownloadDialogArgs, nsINewsDownloadDialogArgs)
+
+NS_IMETHODIMP nsNewsDownloadDialogArgs::GetGroupName(nsAString & aGroupName)
+ {
+ aGroupName = mGroupName;
+
+ return NS_OK;
+ }
+NS_IMETHODIMP nsNewsDownloadDialogArgs::SetGroupName(const nsAString & aGroupName)
+ {
+
+ mGroupName = aGroupName;
+
+ return NS_OK;
+ }
+NS_IMETHODIMP nsNewsDownloadDialogArgs::GetArticleCount(int32_t *aArticleCount)
+{
+ NS_ENSURE_ARG_POINTER(aArticleCount);
+
+ *aArticleCount = mArticleCount;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::SetArticleCount(int32_t aArticleCount)
+{
+ mArticleCount = aArticleCount;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::GetServerKey(char * *aServerKey)
+{
+ NS_ENSURE_ARG_POINTER(aServerKey);
+
+ *aServerKey = ToNewCString(mServerKey);
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::SetServerKey(const char * aServerKey)
+{
+ NS_ENSURE_ARG_POINTER(aServerKey);
+
+ mServerKey = aServerKey;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::GetHitOK(bool *aHitOK)
+{
+ NS_ENSURE_ARG_POINTER(aHitOK);
+
+ *aHitOK = mHitOK;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::SetHitOK(bool aHitOK)
+{
+ mHitOK = aHitOK;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::GetDownloadAll(bool *aDownloadAll)
+{
+ NS_ENSURE_ARG_POINTER(aDownloadAll);
+
+ *aDownloadAll = mDownloadAll;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsNewsDownloadDialogArgs::SetDownloadAll(bool aDownloadAll)
+{
+ mDownloadAll = aDownloadAll;
+
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNewsDownloadDialogArgs.h b/mailnews/news/src/nsNewsDownloadDialogArgs.h
new file mode 100644
index 000000000..5ec8e0df6
--- /dev/null
+++ b/mailnews/news/src/nsNewsDownloadDialogArgs.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNewsDownloadDialogArgs_h__
+#define nsNewsDownloadDialogArgs_h__
+
+#include "nsINewsDownloadDialogArgs.h"
+#include "nsStringGlue.h"
+
+class nsNewsDownloadDialogArgs : public nsINewsDownloadDialogArgs
+{
+public:
+ nsNewsDownloadDialogArgs();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINEWSDOWNLOADDIALOGARGS
+
+private:
+ virtual ~nsNewsDownloadDialogArgs();
+
+ nsString mGroupName;
+ int32_t mArticleCount;
+ nsCString mServerKey;
+ bool mHitOK;
+ bool mDownloadAll;
+};
+
+#endif // nsNewsDownloadDialogArgs_h__
diff --git a/mailnews/news/src/nsNewsDownloader.cpp b/mailnews/news/src/nsNewsDownloader.cpp
new file mode 100644
index 000000000..0794e30e7
--- /dev/null
+++ b/mailnews/news/src/nsNewsDownloader.cpp
@@ -0,0 +1,586 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nntpCore.h"
+#include "netCore.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIStringBundle.h"
+#include "nsNewsDownloader.h"
+#include "nsINntpService.h"
+#include "nsMsgNewsCID.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgSearchValidityManager.h"
+#include "nsRDFCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIRequestObserver.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+
+// This file contains the news article download state machine.
+
+// if pIds is not null, download the articles whose id's are passed in. Otherwise,
+// which articles to download is determined by nsNewsDownloader object,
+// or subclasses thereof. News can download marked objects, for example.
+nsresult nsNewsDownloader::DownloadArticles(nsIMsgWindow *window, nsIMsgFolder *folder, nsTArray<nsMsgKey> *pIds)
+{
+ if (pIds != nullptr)
+ m_keysToDownload.InsertElementsAt(0, pIds->Elements(), pIds->Length());
+
+ if (!m_keysToDownload.IsEmpty())
+ m_downloadFromKeys = true;
+
+ m_folder = folder;
+ m_window = window;
+ m_numwrote = 0;
+
+ bool headersToDownload = GetNextHdrToRetrieve();
+ // should we have a special error code for failure here?
+ return (headersToDownload) ? DownloadNext(true) : NS_ERROR_FAILURE;
+}
+
+/* Saving news messages
+ */
+
+NS_IMPL_ISUPPORTS(nsNewsDownloader, nsIUrlListener, nsIMsgSearchNotify)
+
+nsNewsDownloader::nsNewsDownloader(nsIMsgWindow *window, nsIMsgDatabase *msgDB, nsIUrlListener *listener)
+{
+ m_numwrote = 0;
+ m_downloadFromKeys = false;
+ m_newsDB = msgDB;
+ m_abort = false;
+ m_listener = listener;
+ m_window = window;
+ m_lastPercent = -1;
+ m_lastProgressTime = 0;
+ // not the perfect place for this, but I think it will work.
+ if (m_window)
+ m_window->SetStopped(false);
+}
+
+nsNewsDownloader::~nsNewsDownloader()
+{
+ if (m_listener)
+ m_listener->OnStopRunningUrl(/* don't have a url */nullptr, m_status);
+ if (m_newsDB)
+ {
+ m_newsDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ m_newsDB = nullptr;
+ }
+}
+
+NS_IMETHODIMP nsNewsDownloader::OnStartRunningUrl(nsIURI* url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNewsDownloader::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
+{
+ bool stopped = false;
+ if (m_window)
+ m_window->GetStopped(&stopped);
+ if (stopped)
+ exitCode = NS_BINDING_ABORTED;
+
+ nsresult rv = exitCode;
+ if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_NEWS_ARTICLE_NOT_FOUND)
+ rv = DownloadNext(false);
+
+ return rv;
+}
+
+nsresult nsNewsDownloader::DownloadNext(bool firstTimeP)
+{
+ nsresult rv;
+ if (!firstTimeP)
+ {
+ bool moreHeaders = GetNextHdrToRetrieve();
+ if (!moreHeaders)
+ {
+ if (m_listener)
+ m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ return NS_OK;
+ }
+ }
+ StartDownload();
+ m_wroteAnyP = false;
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nntpService->FetchMessage(m_folder, m_keyToDownload, m_window, nullptr, this, nullptr);
+}
+
+bool DownloadNewsArticlesToOfflineStore::GetNextHdrToRetrieve()
+{
+ nsresult rv;
+
+ if (m_downloadFromKeys)
+ return nsNewsDownloader::GetNextHdrToRetrieve();
+
+ if (m_headerEnumerator == nullptr)
+ rv = m_newsDB->EnumerateMessages(getter_AddRefs(m_headerEnumerator));
+
+ bool hasMore = false;
+
+ while (NS_SUCCEEDED(rv = m_headerEnumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = m_headerEnumerator->GetNext(getter_AddRefs(supports));
+ m_newsHeader = do_QueryInterface(supports);
+ NS_ENSURE_SUCCESS(rv, false);
+ uint32_t hdrFlags;
+ m_newsHeader->GetFlags(&hdrFlags);
+ if (hdrFlags & nsMsgMessageFlags::Marked)
+ {
+ m_newsHeader->GetMessageKey(&m_keyToDownload);
+ break;
+ }
+ else
+ {
+ m_newsHeader = nullptr;
+ }
+ }
+ return hasMore;
+}
+
+void nsNewsDownloader::Abort() {}
+void nsNewsDownloader::Complete() {}
+
+bool nsNewsDownloader::GetNextHdrToRetrieve()
+{
+ nsresult rv;
+ if (m_downloadFromKeys)
+ {
+ if (m_numwrote >= (int32_t) m_keysToDownload.Length())
+ return false;
+
+ m_keyToDownload = m_keysToDownload[m_numwrote++];
+ int32_t percent;
+ percent = (100 * m_numwrote) / (int32_t) m_keysToDownload.Length();
+
+ int64_t nowMS = 0;
+ if (percent < 100) // always need to do 100%
+ {
+ nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
+ if (nowMS - m_lastProgressTime < 750)
+ return true;
+ }
+
+ m_lastProgressTime = nowMS;
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_folder);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, false);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoString firstStr;
+ firstStr.AppendInt(m_numwrote);
+ nsAutoString totalStr;
+ totalStr.AppendInt(int(m_keysToDownload.Length()));
+ nsString prettiestName;
+ nsString statusString;
+
+ m_folder->GetPrettiestName(prettiestName);
+
+ const char16_t *formatStrings[3] = { firstStr.get(), totalStr.get(), prettiestName.get() };
+ rv = bundle->FormatStringFromName(u"downloadingArticlesForOffline",
+ formatStrings, 3, getter_Copies(statusString));
+ NS_ENSURE_SUCCESS(rv, false);
+ ShowProgress(statusString.get(), percent);
+ return true;
+ }
+ NS_ASSERTION(false, "shouldn't get here if we're not downloading from keys.");
+ return false; // shouldn't get here if we're not downloading from keys.
+}
+
+nsresult nsNewsDownloader::ShowProgress(const char16_t *progressString, int32_t percent)
+{
+ if (!m_statusFeedback)
+ {
+ if (m_window)
+ m_window->GetStatusFeedback(getter_AddRefs(m_statusFeedback));
+ }
+ if (m_statusFeedback)
+ {
+ m_statusFeedback->ShowStatusString(nsDependentString(progressString));
+ if (percent != m_lastPercent)
+ {
+ m_statusFeedback->ShowProgress(percent);
+ m_lastPercent = percent;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP DownloadNewsArticlesToOfflineStore::OnStartRunningUrl(nsIURI* url)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP DownloadNewsArticlesToOfflineStore::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
+{
+ m_status = exitCode;
+ if (m_newsHeader != nullptr)
+ {
+#ifdef DEBUG_bienvenu
+ // XP_Trace("finished retrieving %ld\n", m_newsHeader->GetMessageKey());
+#endif
+ if (m_newsDB)
+ {
+ nsMsgKey msgKey;
+ m_newsHeader->GetMessageKey(&msgKey);
+ m_newsDB->MarkMarked(msgKey, false, nullptr);
+ }
+ }
+ m_newsHeader = nullptr;
+ return nsNewsDownloader::OnStopRunningUrl(url, exitCode);
+}
+
+int DownloadNewsArticlesToOfflineStore::FinishDownload()
+{
+ return 0;
+}
+
+
+NS_IMETHODIMP nsNewsDownloader::OnSearchHit(nsIMsgDBHdr *header, nsIMsgFolder *folder)
+{
+ NS_ENSURE_ARG(header);
+
+
+ uint32_t msgFlags;
+ header->GetFlags(&msgFlags);
+ // only need to download articles we don't already have...
+ if (! (msgFlags & nsMsgMessageFlags::Offline))
+ {
+ nsMsgKey key;
+ header->GetMessageKey(&key);
+ m_keysToDownload.AppendElement(key);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNewsDownloader::OnSearchDone(nsresult status)
+{
+ if (m_keysToDownload.IsEmpty())
+ {
+ if (m_listener)
+ return m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ nsresult rv = DownloadArticles(m_window, m_folder,
+ /* we've already set m_keysToDownload, so don't pass it in */ nullptr);
+ if (NS_FAILED(rv))
+ if (m_listener)
+ m_listener->OnStopRunningUrl(nullptr, rv);
+
+ return rv;
+}
+NS_IMETHODIMP nsNewsDownloader::OnNewSearch()
+{
+ return NS_OK;
+}
+
+int DownloadNewsArticlesToOfflineStore::StartDownload()
+{
+ m_newsDB->GetMsgHdrForKey(m_keyToDownload, getter_AddRefs(m_newsHeader));
+ return 0;
+}
+
+DownloadNewsArticlesToOfflineStore::DownloadNewsArticlesToOfflineStore(nsIMsgWindow *window, nsIMsgDatabase *db, nsIUrlListener *listener)
+ : nsNewsDownloader(window, db, listener)
+{
+ m_newsDB = db;
+}
+
+DownloadNewsArticlesToOfflineStore::~DownloadNewsArticlesToOfflineStore()
+{
+}
+
+DownloadMatchingNewsArticlesToNewsDB::DownloadMatchingNewsArticlesToNewsDB
+ (nsIMsgWindow *window, nsIMsgFolder *folder, nsIMsgDatabase *newsDB,
+ nsIUrlListener *listener) :
+ DownloadNewsArticlesToOfflineStore(window, newsDB, listener)
+{
+ m_window = window;
+ m_folder = folder;
+ m_newsDB = newsDB;
+ m_downloadFromKeys = true; // search term matching means downloadFromKeys.
+}
+
+DownloadMatchingNewsArticlesToNewsDB::~DownloadMatchingNewsArticlesToNewsDB()
+{
+}
+
+
+NS_IMPL_ISUPPORTS(nsMsgDownloadAllNewsgroups, nsIUrlListener)
+
+
+nsMsgDownloadAllNewsgroups::nsMsgDownloadAllNewsgroups(nsIMsgWindow *window, nsIUrlListener *listener)
+{
+ m_window = window;
+ m_listener = listener;
+ m_downloaderForGroup = new DownloadMatchingNewsArticlesToNewsDB(window, nullptr, nullptr, this);
+ NS_IF_ADDREF(m_downloaderForGroup);
+ m_downloadedHdrsForCurGroup = false;
+}
+
+nsMsgDownloadAllNewsgroups::~nsMsgDownloadAllNewsgroups()
+{
+ NS_IF_RELEASE(m_downloaderForGroup);
+}
+
+NS_IMETHODIMP nsMsgDownloadAllNewsgroups::OnStartRunningUrl(nsIURI* url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDownloadAllNewsgroups::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
+{
+ nsresult rv = exitCode;
+ if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_NEWS_ARTICLE_NOT_FOUND)
+ {
+ if (m_downloadedHdrsForCurGroup)
+ {
+ bool savingArticlesOffline = false;
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
+ if (newsFolder)
+ newsFolder->GetSaveArticleOffline(&savingArticlesOffline);
+
+ m_downloadedHdrsForCurGroup = false;
+ if (savingArticlesOffline) // skip this group - we're saving to it already
+ rv = ProcessNextGroup();
+ else
+ rv = DownloadMsgsForCurrentGroup();
+ }
+ else
+ {
+ rv = ProcessNextGroup();
+ }
+ }
+ else if (m_listener) // notify main observer.
+ m_listener->OnStopRunningUrl(url, exitCode);
+
+ return rv;
+}
+
+/**
+ * Leaves m_currentServer at the next nntp "server" that
+ * might have folders to download for offline use. If no more servers,
+ * m_currentServer will be left at nullptr and the function returns false.
+ * Also, sets up m_serverEnumerator to enumerate over the server.
+ * If no servers found, m_serverEnumerator will be left at null.
+ */
+bool nsMsgDownloadAllNewsgroups::AdvanceToNextServer()
+{
+ nsresult rv;
+
+ if (!m_allServers)
+ {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ASSERTION(accountManager && NS_SUCCEEDED(rv), "couldn't get account mgr");
+ if (!accountManager || NS_FAILED(rv))
+ return false;
+
+ rv = accountManager->GetAllServers(getter_AddRefs(m_allServers));
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+ uint32_t serverIndex = 0;
+ if (m_currentServer)
+ {
+ rv = m_allServers->IndexOf(0, m_currentServer, &serverIndex);
+ if (NS_FAILED(rv))
+ serverIndex = -1;
+
+ ++serverIndex;
+ }
+ m_currentServer = nullptr;
+ uint32_t numServers;
+ m_allServers->GetLength(&numServers);
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+
+ while (serverIndex < numServers)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(m_allServers, serverIndex);
+ serverIndex++;
+
+ nsCOMPtr <nsINntpIncomingServer> newsServer = do_QueryInterface(server);
+ if (!newsServer) // we're only looking for news servers
+ continue;
+
+ if (server)
+ {
+ m_currentServer = server;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ {
+ rv = rootFolder->GetDescendants(getter_AddRefs(m_allFolders));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = m_allFolders->Enumerate(getter_AddRefs(m_serverEnumerator));
+ if (NS_SUCCEEDED(rv) && m_serverEnumerator)
+ {
+ bool hasMore = false;
+ rv = m_serverEnumerator->HasMoreElements(&hasMore);
+ if (NS_SUCCEEDED(rv) && hasMore)
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Sets m_currentFolder to the next usable folder.
+ *
+ * @return False if no more folders found, otherwise true.
+ */
+bool nsMsgDownloadAllNewsgroups::AdvanceToNextGroup()
+{
+ nsresult rv = NS_OK;
+
+ if (m_currentFolder)
+ {
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
+ if (newsFolder)
+ newsFolder->SetSaveArticleOffline(false);
+
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && session)
+ {
+ bool folderOpen;
+ uint32_t folderFlags;
+ m_currentFolder->GetFlags(&folderFlags);
+ session->IsFolderOpenInWindow(m_currentFolder, &folderOpen);
+ if (!folderOpen && ! (folderFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
+ m_currentFolder->SetMsgDatabase(nullptr);
+ }
+ m_currentFolder = nullptr;
+ }
+
+ bool hasMore = false;
+ if (m_currentServer)
+ m_serverEnumerator->HasMoreElements(&hasMore);
+ if (!hasMore)
+ hasMore = AdvanceToNextServer();
+
+ if (hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ rv = m_serverEnumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv))
+ m_currentFolder = do_QueryInterface(supports);
+ }
+ return m_currentFolder;
+}
+
+nsresult DownloadMatchingNewsArticlesToNewsDB::RunSearch(nsIMsgFolder *folder, nsIMsgDatabase *newsDB, nsIMsgSearchSession *searchSession)
+{
+ m_folder = folder;
+ m_newsDB = newsDB;
+ m_searchSession = searchSession;
+
+ m_keysToDownload.Clear();
+
+ NS_ENSURE_ARG(searchSession);
+ NS_ENSURE_ARG(folder);
+
+ searchSession->RegisterListener(this,
+ nsIMsgSearchSession::allNotifications);
+ nsresult rv = searchSession->AddScopeTerm(nsMsgSearchScope::localNews, folder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return searchSession->Search(m_window);
+}
+
+nsresult nsMsgDownloadAllNewsgroups::ProcessNextGroup()
+{
+ bool done = false;
+
+ while (!done)
+ {
+ done = !AdvanceToNextGroup();
+ if (!done && m_currentFolder)
+ {
+ uint32_t folderFlags;
+ m_currentFolder->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Offline)
+ break;
+ }
+ }
+ if (done)
+ {
+ if (m_listener)
+ return m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ m_downloadedHdrsForCurGroup = true;
+ return m_currentFolder ? m_currentFolder->GetNewMessages(m_window, this) : NS_ERROR_NOT_INITIALIZED;
+}
+
+nsresult nsMsgDownloadAllNewsgroups::DownloadMsgsForCurrentGroup()
+{
+ NS_ENSURE_TRUE(m_downloaderForGroup, NS_ERROR_OUT_OF_MEMORY);
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsCOMPtr <nsIMsgDownloadSettings> downloadSettings;
+ m_currentFolder->GetMsgDatabase(getter_AddRefs(db));
+ nsresult rv = m_currentFolder->GetDownloadSettings(getter_AddRefs(downloadSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
+ if (newsFolder)
+ newsFolder->SetSaveArticleOffline(true);
+
+ nsCOMPtr <nsIMsgSearchSession> searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool downloadByDate, downloadUnreadOnly;
+ uint32_t ageLimitOfMsgsToDownload;
+
+ downloadSettings->GetDownloadByDate(&downloadByDate);
+ downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly);
+ downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload);
+
+ nsCOMPtr <nsIMsgSearchTerm> term;
+ nsCOMPtr <nsIMsgSearchValue> value;
+
+ rv = searchSession->CreateTerm(getter_AddRefs(term));
+ NS_ENSURE_SUCCESS(rv, rv);
+ term->GetValue(getter_AddRefs(value));
+
+ if (downloadUnreadOnly)
+ {
+ value->SetAttrib(nsMsgSearchAttrib::MsgStatus);
+ value->SetStatus(nsMsgMessageFlags::Read);
+ searchSession->AddSearchTerm(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, value, true, nullptr);
+ }
+ if (downloadByDate)
+ {
+ value->SetAttrib(nsMsgSearchAttrib::AgeInDays);
+ value->SetAge(ageLimitOfMsgsToDownload);
+ searchSession->AddSearchTerm(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, value, nsMsgSearchBooleanOp::BooleanAND, nullptr);
+ }
+ value->SetAttrib(nsMsgSearchAttrib::MsgStatus);
+ value->SetStatus(nsMsgMessageFlags::Offline);
+ searchSession->AddSearchTerm(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, value, nsMsgSearchBooleanOp::BooleanAND, nullptr);
+
+ m_downloaderForGroup->RunSearch(m_currentFolder, db, searchSession);
+ return rv;
+}
diff --git a/mailnews/news/src/nsNewsDownloader.h b/mailnews/news/src/nsNewsDownloader.h
new file mode 100644
index 000000000..b9a018032
--- /dev/null
+++ b/mailnews/news/src/nsNewsDownloader.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsNewsDownloader_H_
+#define _nsNewsDownloader_H_
+
+
+#include "nsIMsgDatabase.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsIMsgSearchSession.h"
+
+// base class for downloading articles in a single newsgroup. Keys to download are passed in
+// to DownloadArticles method.
+class nsNewsDownloader : public nsIUrlListener, public nsIMsgSearchNotify
+{
+public:
+ nsNewsDownloader(nsIMsgWindow *window, nsIMsgDatabase *db, nsIUrlListener *listener);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGSEARCHNOTIFY
+
+ virtual nsresult DownloadArticles(nsIMsgWindow *window, nsIMsgFolder *folder, nsTArray<nsMsgKey> *pKeyArray);
+
+ bool ShouldAbort() const { return m_abort; }
+
+protected:
+ virtual ~nsNewsDownloader();
+
+ virtual int32_t Write(const char * /*block*/, int32_t length) {return length;}
+ virtual void Abort();
+ virtual void Complete();
+ virtual bool GetNextHdrToRetrieve();
+ virtual nsresult DownloadNext(bool firstTimeP);
+ virtual int32_t FinishDownload() {return 0;}
+ virtual int32_t StartDownload() {return 0;}
+ virtual nsresult ShowProgress(const char16_t *progressString, int32_t percent);
+
+ nsTArray<nsMsgKey> m_keysToDownload;
+ nsCOMPtr <nsIMsgFolder> m_folder;
+ nsCOMPtr <nsIMsgDatabase> m_newsDB;
+ nsCOMPtr <nsIUrlListener> m_listener;
+ bool m_downloadFromKeys;
+ bool m_existedP;
+ bool m_wroteAnyP;
+ bool m_summaryValidP;
+ bool m_abort;
+ int32_t m_numwrote;
+ nsMsgKey m_keyToDownload;
+ nsCOMPtr <nsIMsgWindow> m_window;
+ nsCOMPtr <nsIMsgStatusFeedback> m_statusFeedback;
+ nsCOMPtr <nsIMsgSearchSession> m_searchSession;
+ int32_t m_lastPercent;
+ int64_t m_lastProgressTime;
+ nsresult m_status;
+};
+
+
+// class for downloading articles in a single newsgroup to the offline store.
+class DownloadNewsArticlesToOfflineStore : public nsNewsDownloader
+{
+public:
+ DownloadNewsArticlesToOfflineStore(nsIMsgWindow *window, nsIMsgDatabase *db, nsIUrlListener *listener);
+ virtual ~DownloadNewsArticlesToOfflineStore();
+
+ NS_IMETHOD OnStartRunningUrl(nsIURI* url);
+ NS_IMETHOD OnStopRunningUrl(nsIURI* url, nsresult exitCode);
+protected:
+ virtual int32_t StartDownload();
+ virtual int32_t FinishDownload();
+ virtual bool GetNextHdrToRetrieve();
+
+ nsCOMPtr <nsISimpleEnumerator> m_headerEnumerator;
+ nsCOMPtr <nsIMsgDBHdr> m_newsHeader;
+};
+
+// class for downloading all the articles that match the passed in search criteria
+// for a single newsgroup.
+class DownloadMatchingNewsArticlesToNewsDB : public DownloadNewsArticlesToOfflineStore
+{
+public:
+ DownloadMatchingNewsArticlesToNewsDB(nsIMsgWindow *window, nsIMsgFolder *folder, nsIMsgDatabase *newsDB, nsIUrlListener *listener);
+ virtual ~DownloadMatchingNewsArticlesToNewsDB();
+ nsresult RunSearch(nsIMsgFolder *folder, nsIMsgDatabase *newsDB, nsIMsgSearchSession *searchSession);
+protected:
+};
+
+// this class iterates all the news servers for each group on the server that's configured for
+// offline use, downloads the messages that meet the download criteria for that newsgroup/server
+class nsMsgDownloadAllNewsgroups : public nsIUrlListener
+{
+public:
+ nsMsgDownloadAllNewsgroups(nsIMsgWindow *window, nsIUrlListener *listener);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+
+ nsresult ProcessNextGroup();
+
+protected:
+ virtual ~nsMsgDownloadAllNewsgroups();
+
+ bool AdvanceToNextServer();
+ bool AdvanceToNextGroup();
+ nsresult DownloadMsgsForCurrentGroup();
+
+ DownloadMatchingNewsArticlesToNewsDB *m_downloaderForGroup;
+
+ nsCOMPtr <nsIMsgFolder> m_currentFolder;
+ nsCOMPtr <nsIMsgWindow> m_window;
+ nsCOMPtr <nsIArray> m_allServers;
+ nsCOMPtr <nsIArray> m_allFolders;
+ nsCOMPtr <nsIMsgIncomingServer> m_currentServer;
+ nsCOMPtr <nsISimpleEnumerator> m_serverEnumerator;
+ nsCOMPtr <nsIUrlListener> m_listener;
+
+ bool m_downloadedHdrsForCurGroup;
+};
+
+#endif
diff --git a/mailnews/news/src/nsNewsFolder.cpp b/mailnews/news/src/nsNewsFolder.cpp
new file mode 100644
index 000000000..3af5ae43f
--- /dev/null
+++ b/mailnews/news/src/nsNewsFolder.cpp
@@ -0,0 +1,1897 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "prlog.h"
+
+#include "msgCore.h" // precompiled header...
+#include "nntpCore.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsNewsFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "MailNewsTypes.h"
+#include "prprf.h"
+#include "prsystem.h"
+#include "nsIArray.h"
+#include "nsIServiceManager.h"
+#include "nsINntpService.h"
+#include "nsIFolderListener.h"
+#include "nsCOMPtr.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgNewsCID.h"
+#include "nsMsgUtils.h"
+#include "nsNewsUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsINntpIncomingServer.h"
+#include "nsINewsDatabase.h"
+#include "nsMsgBaseCID.h"
+#include "nsILineInputStream.h"
+
+#include "nsIMsgWindow.h"
+#include "nsIDocShell.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+
+#include "nsNetUtil.h"
+#include "nsIAuthPrompt.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsINntpUrl.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsArrayEnumerator.h"
+#include "nsNewsDownloader.h"
+#include "nsIStringBundle.h"
+#include "nsMsgI18N.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIMsgAccountManager.h"
+#include "nsArrayUtils.h"
+#include "nsIMsgAsyncPrompter.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMutableArray.h"
+#include "nsILoginInfo.h"
+#include "nsILoginManager.h"
+#include "nsIPromptService.h"
+#include "nsEmbedCID.h"
+#include "nsIDOMWindow.h"
+#include "mozilla/Services.h"
+#include "nsAutoPtr.h"
+#include "nsIInputStream.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+// ###tw This really ought to be the most
+// efficient file reading size for the current
+// operating system.
+#define NEWSRC_FILE_BUFFER_SIZE 1024
+
+#define kNewsSortOffset 9000
+
+#define NEWS_SCHEME "news:"
+#define SNEWS_SCHEME "snews:"
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsMsgNewsFolder::nsMsgNewsFolder(void) :
+ mExpungedBytes(0), mGettingNews(false),
+ mInitialized(false),
+ m_downloadMessageForOfflineUse(false), m_downloadingMultipleMessages(false),
+ mReadSet(nullptr), mSortOrder(kNewsSortOffset)
+{
+ MOZ_COUNT_CTOR(nsMsgNewsFolder); // double count these for now.
+ mFolderSize = kSizeUnknown;
+}
+
+nsMsgNewsFolder::~nsMsgNewsFolder(void)
+{
+ MOZ_COUNT_DTOR(nsMsgNewsFolder);
+ delete mReadSet;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsMsgNewsFolder, nsMsgDBFolder)
+NS_IMPL_RELEASE_INHERITED(nsMsgNewsFolder, nsMsgDBFolder)
+
+NS_IMETHODIMP nsMsgNewsFolder::QueryInterface(REFNSIID aIID, void** aInstancePtr)
+{
+ if (!aInstancePtr)
+ return NS_ERROR_NULL_POINTER;
+ *aInstancePtr = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsIMsgNewsFolder)))
+ *aInstancePtr = static_cast<nsIMsgNewsFolder*>(this);
+ if(*aInstancePtr)
+ {
+ AddRef();
+ return NS_OK;
+ }
+
+ return nsMsgDBFolder::QueryInterface(aIID, aInstancePtr);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsMsgNewsFolder::CreateSubFolders(nsIFile *path)
+{
+ nsresult rv;
+ bool isNewsServer = false;
+ rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isNewsServer)
+ {
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nntpServer->GetNewsrcFilePath(getter_AddRefs(mNewsrcFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = LoadNewsrcFileAndCreateNewsgroups();
+ }
+ else // is not a host, so it has no newsgroups. (what about categories??)
+ rv = NS_OK;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::AddNewsgroup(const nsACString &name, const nsACString& setStr,
+ nsIMsgFolder **child)
+{
+ NS_ENSURE_ARG_POINTER(child);
+ nsresult rv;
+ nsCOMPtr <nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+ // URI should use UTF-8
+ // (see RFC2396 Uniform Resource Identifiers (URI): Generic Syntax)
+
+ // we are handling newsgroup names in UTF-8
+ NS_ConvertUTF8toUTF16 nameUtf16(name);
+
+ nsAutoCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(nameUtf16, escapedName);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = nntpServer->AddNewsgroup(nameUtf16);
+ if (NS_FAILED(rv)) return rv;
+
+ uri.Append(escapedName);
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgNewsFolder> newsFolder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ // cache this for when we open the db
+ rv = newsFolder->SetReadSetFromStr(setStr);
+
+ rv = folder->SetParent(this);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // this what shows up in the UI
+ rv = folder->SetName(nameUtf16);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = folder->SetFlag(nsMsgFolderFlags::Newsgroup);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t numExistingGroups = mSubFolders.Count();
+
+ // add kNewsSortOffset (9000) to prevent this problem: 1,10,11,2,3,4,5
+ // We use 9000 instead of 1000 so newsgroups will sort to bottom of flat folder views
+ rv = folder->SetSortOrder(numExistingGroups + kNewsSortOffset);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ mSubFolders.AppendObject(folder);
+ folder->SetParent(this);
+ folder.swap(*child);
+ return rv;
+}
+
+nsresult nsMsgNewsFolder::ParseFolder(nsIFile *path)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsMsgNewsFolder::AddDirectorySeparator(nsIFile *path)
+{
+ // don't concat the full separator with .sbd
+ return (mURI.Equals(kNewsRootURI)) ?
+ NS_OK :
+ nsMsgDBFolder::AddDirectorySeparator(path);
+}
+
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetSubFolders(nsISimpleEnumerator **aResult)
+{
+ if (!mInitialized)
+ {
+ // do this first, so we make sure to do it, even on failure.
+ // see bug #70494
+ mInitialized = true;
+
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CreateSubFolders(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // force ourselves to get initialized from cache
+ // Don't care if it fails. this will fail the first time after
+ // migration, but we continue on. see #66018
+ (void)UpdateSummaryTotals(false);
+ }
+
+ return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER;
+}
+
+//Makes sure the database is open and exists. If the database is valid then
+//returns NS_OK. Otherwise returns a failure error value.
+nsresult nsMsgNewsFolder::GetDatabase()
+{
+ nsresult rv;
+ if (!mDatabase)
+ {
+ nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Get the database, blowing it away if it's out of date.
+ rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase));
+ if (NS_FAILED(rv))
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if(mAddListener)
+ rv = mDatabase->AddListener(this);
+
+ nsCOMPtr<nsINewsDatabase> db = do_QueryInterface(mDatabase, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = db->SetReadSet(mReadSet);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = UpdateSummaryTotals(true);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetDatabaseWithoutCache(nsIMsgDatabase **db)
+{
+ NS_ENSURE_ARG_POINTER(db);
+
+ // The simplest way to perform this operation is to get the database normally
+ // and then clear our information about it if we didn't already hold it open.
+ bool wasCached = !!mDatabase;
+ nsresult rv = GetDatabase();
+ NS_IF_ADDREF(*db = mDatabase);
+
+ // If the DB was not open before, close our reference to it now.
+ if (!wasCached && mDatabase)
+ {
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::UpdateFolder(nsIMsgWindow *aWindow)
+{
+ // Get news.get_messages_on_select pref
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool getMessagesOnSelect = true;
+ prefBranch->GetBoolPref("news.get_messages_on_select", &getMessagesOnSelect);
+
+ // Only if news.get_messages_on_select is true do we get new messages automatically
+ if (getMessagesOnSelect)
+ {
+ rv = GetDatabase(); // want this cached...
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mDatabase)
+ {
+ nsCOMPtr<nsIMsgRetentionSettings> retentionSettings;
+ nsresult rv = GetRetentionSettings(getter_AddRefs(retentionSettings));
+ if (NS_SUCCEEDED(rv))
+ rv = mDatabase->ApplyRetentionSettings(retentionSettings, false);
+ }
+ rv = AutoCompact(aWindow);
+ NS_ENSURE_SUCCESS(rv,rv);
+ // GetNewMessages has to be the last rv set before we get to the next check, so
+ // that we'll have rv set to NS_MSG_ERROR_OFFLINE when offline and send
+ // a folder loaded notification to the front end.
+ rv = GetNewMessages(aWindow, nullptr);
+ }
+ if (rv != NS_MSG_ERROR_OFFLINE)
+ return rv;
+ }
+ // We're not getting messages because either get_messages_on_select is
+ // false or we're offline. Send an immediate folder loaded notification.
+ NotifyFolderEvent(mFolderLoadedAtom);
+ (void) RefreshSizeOnDisk();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanSubscribe(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ bool isNewsServer = false;
+ nsresult rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ // you can only subscribe to news servers, not news groups
+ *aResult = isNewsServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanFileMessages(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // you can't file messages into a news server or news group
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanCreateSubfolders(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // you can't create subfolders on a news server or a news group
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanRename(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // you can't rename a news server or a news group
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetCanCompact(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // you can't compact a news server or a news group
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetMessages(nsISimpleEnumerator **result)
+{
+ nsresult rv = GetDatabase();
+ *result = nullptr;
+
+ if(NS_SUCCEEDED(rv))
+ rv = mDatabase->EnumerateMessages(result);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetFolderURL(nsACString& aUrl)
+{
+ nsCString hostName;
+ nsresult rv = GetHostname(hostName);
+ nsString groupName;
+ rv = GetName(groupName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port;
+ rv = server->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ const char *newsScheme = (socketType == nsMsgSocketType::SSL) ?
+ SNEWS_SCHEME : NEWS_SCHEME;
+ nsCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(groupName, escapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString tmpStr;
+ tmpStr.Adopt(PR_smprintf("%s//%s:%ld/%s", newsScheme, hostName.get(), port,
+ escapedName.get()));
+ aUrl.Assign(tmpStr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetNewsrcHasChanged(bool newsrcHasChanged)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+ return nntpServer->SetNewsrcHasChanged(newsrcHasChanged);
+}
+
+nsresult nsMsgNewsFolder::CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder)
+{
+ nsMsgNewsFolder *newFolder = new nsMsgNewsFolder;
+ if (!newFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*folder = newFolder);
+ newFolder->Init(uri.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CreateSubfolder(const nsAString& newsgroupName,
+ nsIMsgWindow *msgWindow)
+{
+ nsresult rv = NS_OK;
+ if (newsgroupName.IsEmpty())
+ return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ nsCOMPtr<nsIMsgFolder> child;
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDatabase> newsDBFactory;
+ nsCOMPtr <nsIMsgDatabase> newsDB;
+
+ //Now let's create the actual new folder
+ rv = AddNewsgroup(NS_ConvertUTF16toUTF8(newsgroupName), EmptyCString(), getter_AddRefs(child));
+
+ if (NS_SUCCEEDED(rv))
+ SetNewsrcHasChanged(true); // subscribe UI does this - but maybe we got here through auto-subscribe
+
+ if(NS_SUCCEEDED(rv) && child){
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString dataCharset;
+ rv = nntpServer->GetCharset(dataCharset);
+ if (NS_FAILED(rv)) return rv;
+
+ child->SetCharset(dataCharset);
+ NotifyItemAdded(child);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderAdded(child);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Delete()
+{
+ nsresult rv = GetDatabase();
+
+ if(NS_SUCCEEDED(rv))
+ {
+ mDatabase->ForceClosed();
+ mDatabase = nullptr;
+ }
+
+ nsCOMPtr<nsIFile> folderPath;
+ rv = GetFilePath(getter_AddRefs(folderPath));
+
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIFile> summaryPath;
+ rv = GetSummaryFileLocation(folderPath, getter_AddRefs(summaryPath));
+ if (NS_SUCCEEDED(rv))
+ {
+ bool exists = false;
+ rv = folderPath->Exists(&exists);
+
+ if (NS_SUCCEEDED(rv) && exists)
+ rv = folderPath->Remove(false);
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed to remove News Folder");
+
+ rv = summaryPath->Exists(&exists);
+
+ if (NS_SUCCEEDED(rv) && exists)
+ rv = summaryPath->Remove(false);
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed to remove News Folder Summary File");
+ }
+ }
+
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString name;
+ rv = GetUnicodeName(name);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nntpServer->RemoveNewsgroup(name);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ (void) RefreshSizeOnDisk();
+
+ return SetNewsrcHasChanged(true);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Rename(const nsAString& newName, nsIMsgWindow *msgWindow)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetAbbreviatedName(nsAString& aAbbreviatedName)
+{
+ nsresult rv;
+
+ rv = nsMsgDBFolder::GetPrettyName(aAbbreviatedName);
+ if(NS_FAILED(rv)) return rv;
+
+ // only do this for newsgroup names, not for newsgroup hosts.
+ bool isNewsServer = false;
+ rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!isNewsServer) {
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ bool abbreviate = true;
+ rv = nntpServer->GetAbbreviate(&abbreviate);
+ if (NS_FAILED(rv)) return rv;
+
+ if (abbreviate)
+ rv = AbbreviatePrettyName(aAbbreviatedName, 1 /* hardcoded for now */);
+ }
+ return rv;
+}
+
+// original code from Oleg Rekutin
+// rekusha@asan.com
+// Public domain, created by Oleg Rekutin
+//
+// takes a newsgroup name, number of words from the end to leave unabberviated
+// the newsgroup name, will get reset to the following format:
+// x.x.x, where x is the first letter of each word and with the
+// exception of last 'fullwords' words, which are left intact.
+// If a word has a dash in it, it is abbreviated as a-b, where
+// 'a' is the first letter of the part of the word before the
+// dash and 'b' is the first letter of the part of the word after
+// the dash
+nsresult nsMsgNewsFolder::AbbreviatePrettyName(nsAString& prettyName, int32_t fullwords)
+{
+ nsAutoString name(prettyName);
+ int32_t totalwords = 0; // total no. of words
+
+ // get the total no. of words
+ int32_t pos = 0;
+ while(1)
+ {
+ pos = name.FindChar('.', pos);
+ if(pos == -1)
+ {
+ totalwords++;
+ break;
+ }
+ else
+ {
+ totalwords++;
+ pos++;
+ }
+ }
+
+ // get the no. of words to abbreviate
+ int32_t abbrevnum = totalwords - fullwords;
+ if (abbrevnum < 1)
+ return NS_OK; // nothing to abbreviate
+
+ // build the ellipsis
+ nsAutoString out;
+ out += name[0];
+
+ int32_t length = name.Length();
+ int32_t newword = 0; // == 2 if done with all abbreviated words
+
+ fullwords = 0;
+ char16_t currentChar;
+ for (int32_t i = 1; i < length; i++)
+ {
+ // this temporary assignment is needed to fix an intel mac compiler bug.
+ // See Bug #327037 for details.
+ currentChar = name[i];
+ if (newword < 2) {
+ switch (currentChar) {
+ case '.':
+ fullwords++;
+ // check if done with all abbreviated words...
+ if (fullwords == abbrevnum)
+ newword = 2;
+ else
+ newword = 1;
+ break;
+ case '-':
+ newword = 1;
+ break;
+ default:
+ if (newword)
+ newword = 0;
+ else
+ continue;
+ }
+ }
+ out.Append(currentChar);
+ }
+ prettyName = out;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db)
+{
+ NS_ENSURE_ARG_POINTER(folderInfo);
+ NS_ENSURE_ARG_POINTER(db);
+ nsresult openErr;
+ openErr = GetDatabase();
+ *db = mDatabase;
+ if (mDatabase) {
+ NS_ADDREF(*db);
+ if (NS_SUCCEEDED(openErr))
+ openErr = (*db)->GetDBFolderInfo(folderInfo);
+ }
+ return openErr;
+}
+
+/* this used to be MSG_FolderInfoNews::UpdateSummaryFromNNTPInfo() */
+NS_IMETHODIMP
+nsMsgNewsFolder::UpdateSummaryFromNNTPInfo(int32_t oldest, int32_t youngest, int32_t total)
+{
+ /* First, mark all of the articles now known to be expired as read. */
+ if (oldest > 1)
+ {
+ nsCString oldSet;
+ nsCString newSet;
+ mReadSet->Output(getter_Copies(oldSet));
+ mReadSet->AddRange(1, oldest - 1);
+ mReadSet->Output(getter_Copies(newSet));
+ }
+
+ /* Now search the newsrc line and figure out how many of these messages are marked as unread. */
+
+ /* make sure youngest is a least 1. MSNews seems to return a youngest of 0. */
+ if (youngest == 0)
+ youngest = 1;
+
+ int32_t unread = mReadSet->CountMissingInRange(oldest, youngest);
+ NS_ASSERTION(unread >= 0,"CountMissingInRange reported unread < 0");
+ if (unread < 0)
+ // servers can send us stuff like "211 0 41 40 nz.netstatus"
+ // we should handle it gracefully.
+ unread = 0;
+
+ if (unread > total)
+ {
+ /* This can happen when the newsrc file shows more unread than exist in the group (total is not necessarily `end - start'.) */
+ unread = total;
+ int32_t deltaInDB = mNumTotalMessages - mNumUnreadMessages;
+ //int32_t deltaInDB = m_totalInDB - m_unreadInDB;
+ /* if we know there are read messages in the db, subtract that from the unread total */
+ if (deltaInDB > 0)
+ unread -= deltaInDB;
+ }
+
+ bool dbWasOpen = mDatabase != nullptr;
+ int32_t pendingUnreadDelta = unread - mNumUnreadMessages - mNumPendingUnreadMessages;
+ int32_t pendingTotalDelta = total - mNumTotalMessages - mNumPendingTotalMessages;
+ ChangeNumPendingUnread(pendingUnreadDelta);
+ ChangeNumPendingTotalMessages(pendingTotalDelta);
+ if (!dbWasOpen && mDatabase)
+ {
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetExpungedBytesCount(int64_t *count)
+{
+ NS_ENSURE_ARG_POINTER(count);
+ *count = mExpungedBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetDeletable(bool *deletable)
+{
+ NS_ENSURE_ARG_POINTER(deletable);
+
+ *deletable = false;
+ // For legacy reasons, there can be Saved search folders under news accounts.
+ // Allow deleting those.
+ GetFlag(nsMsgFolderFlags::Virtual, deletable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::RefreshSizeOnDisk()
+{
+ uint64_t oldFolderSize = mFolderSize;
+ // We set size to unknown to force it to get recalculated from disk.
+ mFolderSize = kSizeUnknown;
+ if (NS_SUCCEEDED(GetSizeOnDisk(&mFolderSize)))
+ NotifyIntPropertyChanged(kFolderSizeAtom, oldFolderSize, mFolderSize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetSizeOnDisk(int64_t *size)
+{
+ NS_ENSURE_ARG_POINTER(size);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ // If this is the rootFolder, return 0 as a safe value.
+ if (NS_FAILED(rv) || isServer)
+ mFolderSize = 0;
+
+ // 0 is a valid folder size (meaning empty file with no offline messages),
+ // but 1 is not. So use -1 as a special value meaning no file size was fetched
+ // from disk yet.
+ if (mFolderSize == kSizeUnknown)
+ {
+ nsCOMPtr<nsIFile> diskFile;
+ nsresult rv = GetFilePath(getter_AddRefs(diskFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there were no news messages downloaded for offline use, the folder file
+ // may not exist yet. In that case size is 0.
+ bool exists = false;
+ rv = diskFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ {
+ mFolderSize = 0;
+ }
+ else
+ {
+ int64_t fileSize;
+ rv = diskFile->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mFolderSize = fileSize;
+ }
+ }
+
+ *size = mFolderSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::DeleteMessages(nsIArray *messages, nsIMsgWindow *aMsgWindow,
+ bool deleteStorage, bool isMove,
+ nsIMsgCopyServiceListener* listener,
+ bool allowUndo)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG_POINTER(messages);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ if (!isMove)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsDeleted(messages);
+ }
+
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = EnableNotifications(allMessageCountNotifications, false, true);
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t count = 0;
+ rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < count && NS_SUCCEEDED(rv); i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i, &rv);
+ if (msgHdr)
+ rv = mDatabase->DeleteHeader(msgHdr, nullptr, true, true);
+ }
+ EnableNotifications(allMessageCountNotifications, true, true);
+ }
+
+ if (!isMove)
+ NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom :
+ mDeleteOrMoveMsgFailedAtom);
+
+ (void) RefreshSizeOnDisk();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CancelMessage(nsIMsgDBHdr *msgHdr,
+ nsIMsgWindow *aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ nsresult rv;
+
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // for cancel, we need to
+ // turn "newsmessage://sspitzer@news.mozilla.org/netscape.test#5428"
+ // into "news://sspitzer@news.mozilla.org/23423@netscape.com"
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString serverURI;
+ rv = server->GetServerURI(serverURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageID;
+ rv = msgHdr->GetMessageId(getter_Copies(messageID));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // we need to escape the message ID,
+ // it might contain characters which will mess us up later, like #
+ // see bug #120502
+ nsCString escapedMessageID;
+ MsgEscapeString(messageID, nsINetUtil::ESCAPE_URL_PATH, escapedMessageID);
+
+ nsAutoCString cancelURL(serverURI.get());
+ cancelURL += '/';
+ cancelURL += escapedMessageID;
+ cancelURL += "?cancel";
+
+ nsCString messageURI;
+ rv = GetUriForMsg(msgHdr, messageURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return nntpService->CancelMessage(cancelURL.get(), messageURI.get(), nullptr /* consumer */, nullptr,
+ aMsgWindow, nullptr);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetNewMessages(nsIMsgWindow *aMsgWindow, nsIUrlListener *aListener)
+{
+ return GetNewsMessages(aMsgWindow, false, aListener);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetNextNMessages(nsIMsgWindow *aMsgWindow)
+{
+ return GetNewsMessages(aMsgWindow, true, nullptr);
+}
+
+nsresult nsMsgNewsFolder::GetNewsMessages(nsIMsgWindow *aMsgWindow, bool aGetOld, nsIUrlListener *aUrlListener)
+{
+ nsresult rv = NS_OK;
+
+ bool isNewsServer = false;
+ rv = GetIsServer(&isNewsServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isNewsServer)
+ // get new messages only works on a newsgroup, not a news server
+ return NS_OK;
+
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIURI> resultUri;
+ rv = nntpService->GetNewNews(nntpServer, mURI.get(), aGetOld, this,
+ aMsgWindow, getter_AddRefs(resultUri));
+ if (aUrlListener && NS_SUCCEEDED(rv) && resultUri)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(resultUri));
+ if (msgUrl)
+ msgUrl->RegisterListener(aUrlListener);
+ }
+ return rv;
+}
+
+nsresult
+nsMsgNewsFolder::LoadNewsrcFileAndCreateNewsgroups()
+{
+ nsresult rv = NS_OK;
+ if (!mNewsrcFilePath) return NS_ERROR_FAILURE;
+
+ bool exists;
+ rv = mNewsrcFilePath->Exists(&exists);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!exists)
+ // it is ok for the newsrc file to not exist yet
+ return NS_OK;
+
+ nsCOMPtr<nsIInputStream> fileStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), mNewsrcFilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsCString line;
+
+ while (more && NS_SUCCEEDED(rv))
+ {
+ rv = lineInputStream->ReadLine(line, &more);
+ if (line.IsEmpty())
+ continue;
+ HandleNewsrcLine(line.get(), line.Length());
+ }
+
+ fileStream->Close();
+ return rv;
+}
+
+int32_t
+nsMsgNewsFolder::HandleNewsrcLine(const char * line, uint32_t line_size)
+{
+ nsresult rv;
+
+ /* guard against blank line lossage */
+ if (line[0] == '#' || line[0] == '\r' || line[0] == '\n') return 0;
+
+ if ((line[0] == 'o' || line[0] == 'O') &&
+ !PL_strncasecmp (line, "options", 7))
+ return RememberLine(nsDependentCString(line));
+
+ const char *s = nullptr;
+ const char *setStr = nullptr;
+ const char *end = line + line_size;
+
+ for (s = line; s < end; s++)
+ if ((*s == ':') || (*s == '!'))
+ break;
+
+ if (*s == 0)
+ /* What is this?? Well, don't just throw it away... */
+ return RememberLine(nsDependentCString(line));
+
+ bool subscribed = (*s == ':');
+ setStr = s+1;
+
+ if (*line == '\0')
+ return 0;
+
+ // previous versions of Communicator poluted the
+ // newsrc files with articles
+ // (this would happen when you clicked on a link like
+ // news://news.mozilla.org/3746EF3F.6080309@netscape.com)
+ //
+ // legal newsgroup names can't contain @ or %
+ //
+ // News group names are structured into parts separated by dots,
+ // for example "netscape.public.mozilla.mail-news".
+ // Each part may be up to 14 characters long, and should consist
+ // only of letters, digits, "+" and "-", with at least one letter
+ //
+ // @ indicates an article and %40 is @ escaped.
+ // previous versions of Communicator also dumped
+ // the escaped version into the newsrc file
+ //
+ // So lines like this in a newsrc file should be ignored:
+ // 3746EF3F.6080309@netscape.com:
+ // 3746EF3F.6080309%40netscape.com:
+ if (PL_strchr(line, '@') || PL_strstr(line, "%40"))
+ // skipping, it contains @ or %40
+ subscribed = false;
+
+ if (subscribed)
+ {
+ // we're subscribed, so add it
+ nsCOMPtr <nsIMsgFolder> child;
+
+ rv = AddNewsgroup(Substring(line, s), nsDependentCString(setStr), getter_AddRefs(child));
+ if (NS_FAILED(rv)) return -1;
+ }
+ else {
+ rv = RememberUnsubscribedGroup(nsDependentCString(line), nsDependentCString(setStr));
+ if (NS_FAILED(rv)) return -1;
+ }
+
+ return 0;
+}
+
+
+nsresult
+nsMsgNewsFolder::RememberUnsubscribedGroup(const nsACString& newsgroup, const nsACString& setStr)
+{
+ mUnsubscribedNewsgroupLines.Append(newsgroup);
+ mUnsubscribedNewsgroupLines.AppendLiteral("! ");
+ if (!setStr.IsEmpty())
+ mUnsubscribedNewsgroupLines.Append(setStr);
+ else
+ mUnsubscribedNewsgroupLines.Append(MSG_LINEBREAK);
+ return NS_OK;
+}
+
+int32_t
+nsMsgNewsFolder::RememberLine(const nsACString& line)
+{
+ mOptionLines = line;
+ mOptionLines.Append(MSG_LINEBREAK);
+ return 0;
+}
+
+nsresult nsMsgNewsFolder::ForgetLine()
+{
+ mOptionLines.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetGroupUsername(nsACString& aGroupUsername)
+{
+ aGroupUsername = mGroupUsername;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetGroupUsername(const nsACString& aGroupUsername)
+{
+ mGroupUsername = aGroupUsername;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetGroupPassword(nsACString& aGroupPassword)
+{
+ aGroupPassword = mGroupPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetGroupPassword(const nsACString& aGroupPassword)
+{
+ mGroupPassword = aGroupPassword;
+ return NS_OK;
+}
+
+nsresult nsMsgNewsFolder::CreateNewsgroupUrlForSignon(const char *ref,
+ nsAString &result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv)) return rv;
+
+ bool singleSignon = true;
+ rv = nntpServer->GetSingleSignon(&singleSignon);
+
+ if (singleSignon)
+ {
+ nsCString serverURI;
+ rv = server->GetServerURI(serverURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->SetSpec(serverURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ rv = url->SetSpec(mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ int32_t port = 0;
+ rv = url->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (port <= 0)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ nsresult rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only set this for ssl newsgroups as for non-ssl connections, we don't
+ // need to specify the port as it is the default for the protocol and
+ // password manager "blanks" those out.
+ if (socketType == nsMsgSocketType::SSL)
+ {
+ rv = url->SetPort(nsINntpUrl::DEFAULT_NNTPS_PORT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsCString rawResult;
+ if (ref)
+ {
+ rv = url->SetRef(nsDependentCString(ref));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->GetSpec(rawResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ // If the url doesn't have a path, make sure we don't get a '/' on the end
+ // as that will confuse searching in password manager.
+ nsCString spec;
+ rv = url->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!spec.IsEmpty() && spec[spec.Length() - 1] == '/')
+ rawResult = StringHead(spec, spec.Length() - 1);
+ else
+ rawResult = spec;
+ }
+ result = NS_ConvertASCIItoUTF16(rawResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetAuthenticationCredentials(nsIMsgWindow *aMsgWindow,
+ bool mayPrompt, bool mustPrompt, bool *validCredentials)
+{
+ // Not strictly necessary, but it would help consumers to realize that this is
+ // a rather nonsensical combination.
+ NS_ENSURE_FALSE(mustPrompt && !mayPrompt, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_ARG_POINTER(validCredentials);
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString signonUrl;
+ rv = CreateNewsgroupUrlForSignon(nullptr, signonUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we don't have a username or password, try to load it via the login mgr.
+ // Do this even if mustPrompt is true, to prefill the dialog.
+ if (mGroupUsername.IsEmpty() || mGroupPassword.IsEmpty())
+ {
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numLogins = 0;
+ nsILoginInfo **logins = nullptr;
+ rv = loginMgr->FindLogins(&numLogins, signonUrl, EmptyString(), signonUrl,
+ &logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (numLogins > 0)
+ {
+ nsString uniUsername, uniPassword;
+ logins[0]->GetUsername(uniUsername);
+ logins[0]->GetPassword(uniPassword);
+ mGroupUsername = NS_LossyConvertUTF16toASCII(uniUsername);
+ mGroupPassword = NS_LossyConvertUTF16toASCII(uniPassword);
+
+ *validCredentials = true;
+ }
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numLogins, logins);
+ }
+
+ // Show the prompt if we need to
+ if (mustPrompt ||
+ (mayPrompt && (mGroupUsername.IsEmpty() || mGroupPassword.IsEmpty())))
+ {
+ nsCOMPtr<nsIAuthPrompt> dialog;
+ if (aMsgWindow)
+ {
+ rv = aMsgWindow->GetAuthPrompt(getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch)
+ wwatch->GetNewAuthPrompter(0, getter_AddRefs(dialog));
+ if (!dialog) return NS_ERROR_FAILURE;
+ }
+
+ NS_ASSERTION(dialog, "We didn't get a net prompt");
+ if (dialog)
+ {
+ // Format the prompt text strings
+ nsString promptTitle, promptText;
+ bundle->GetStringFromName(u"enterUserPassTitle",
+ getter_Copies(promptTitle));
+
+ nsString serverName;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ server->GetPrettyName(serverName);
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool singleSignon = true;
+ nntpServer->GetSingleSignon(&singleSignon);
+
+ const char16_t *params[2];
+ params[0] = mName.get();
+ params[1] = serverName.get();
+ if (singleSignon)
+ bundle->FormatStringFromName(
+ u"enterUserPassServer",
+ &params[1], 1, getter_Copies(promptText));
+ else
+ bundle->FormatStringFromName(
+ u"enterUserPassGroup",
+ params, 2, getter_Copies(promptText));
+
+ // Fill the signon url for the dialog
+ nsString signonURL;
+ rv = CreateNewsgroupUrlForSignon(nullptr, signonURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Prefill saved username/password
+ char16_t *uniGroupUsername = ToNewUnicode(
+ NS_ConvertASCIItoUTF16(mGroupUsername));
+ char16_t *uniGroupPassword = ToNewUnicode(
+ NS_ConvertASCIItoUTF16(mGroupPassword));
+
+ // Prompt for the dialog
+ rv = dialog->PromptUsernameAndPassword(promptTitle.get(),
+ promptText.get(), signonURL.get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ &uniGroupUsername, &uniGroupPassword, validCredentials);
+
+ nsAutoString uniPasswordAdopted, uniUsernameAdopted;
+ uniPasswordAdopted.Adopt(uniGroupPassword);
+ uniUsernameAdopted.Adopt(uniGroupUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only use the username/password if the user didn't cancel.
+ if (*validCredentials)
+ {
+ SetGroupUsername(NS_LossyConvertUTF16toASCII(uniUsernameAdopted));
+ SetGroupPassword(NS_LossyConvertUTF16toASCII(uniPasswordAdopted));
+ }
+ else
+ {
+ mGroupUsername.Truncate();
+ mGroupPassword.Truncate();
+ }
+ }
+ }
+
+ *validCredentials = !(mGroupUsername.IsEmpty() || mGroupPassword.IsEmpty());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::ForgetAuthenticationCredentials()
+{
+ nsString signonUrl;
+ nsresult rv = CreateNewsgroupUrlForSignon(nullptr, signonUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ nsILoginInfo** logins;
+
+ rv = loginMgr->FindLogins(&count, signonUrl, EmptyString(), signonUrl,
+ &logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // There should only be one-login stored for this url, however just in case
+ // there isn't.
+ for (uint32_t i = 0; i < count; ++i)
+ loginMgr->RemoveLogin(logins[i]);
+ NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins);
+
+ // Clear out the saved passwords for anyone else who tries to call.
+ mGroupUsername.Truncate();
+ mGroupPassword.Truncate();
+
+ return NS_OK;
+}
+
+// change order of subfolders (newsgroups)
+// aOrientation = -1 ... aNewsgroupToMove aRefNewsgroup ...
+// aOrientation = 1 ... aRefNewsgroup aNewsgroupToMove ...
+NS_IMETHODIMP nsMsgNewsFolder::MoveFolder(nsIMsgFolder *aNewsgroupToMove, nsIMsgFolder *aRefNewsgroup, int32_t aOrientation)
+{
+ // if folders are identical do nothing
+ if (aNewsgroupToMove == aRefNewsgroup)
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+
+ // get index for aNewsgroupToMove
+ int32_t indexNewsgroupToMove = mSubFolders.IndexOf(aNewsgroupToMove);
+ if (indexNewsgroupToMove == -1)
+ // aNewsgroupToMove is no subfolder of this folder
+ return NS_ERROR_INVALID_ARG;
+
+ // get index for aRefNewsgroup
+ int32_t indexRefNewsgroup = mSubFolders.IndexOf(aRefNewsgroup);
+ if (indexRefNewsgroup == -1)
+ // aRefNewsgroup is no subfolder of this folder
+ return NS_ERROR_INVALID_ARG;
+
+ // set new index for NewsgroupToMove
+ uint32_t indexMin, indexMax;
+ if (indexNewsgroupToMove < indexRefNewsgroup)
+ {
+ if (aOrientation < 0)
+ indexRefNewsgroup--;
+ indexMin = indexNewsgroupToMove;
+ indexMax = indexRefNewsgroup;
+ }
+ else
+ {
+ if (aOrientation > 0)
+ indexRefNewsgroup++;
+ indexMin = indexRefNewsgroup;
+ indexMax = indexNewsgroupToMove;
+ }
+
+ // move NewsgroupToMove to new index and set new sort order
+ NotifyItemRemoved(aNewsgroupToMove);
+
+ if (indexNewsgroupToMove != indexRefNewsgroup)
+ {
+ nsCOMPtr<nsIMsgFolder> newsgroup = mSubFolders[indexNewsgroupToMove];
+
+ mSubFolders.RemoveObjectAt(indexNewsgroupToMove);
+
+ // indexRefNewsgroup is already set up correctly.
+ mSubFolders.InsertObjectAt(newsgroup, indexRefNewsgroup);
+ }
+
+ for (uint32_t i = indexMin; i <= indexMax; i++)
+ mSubFolders[i]->SetSortOrder(kNewsSortOffset + i);
+
+ NotifyItemAdded(aNewsgroupToMove);
+
+ // write changes back to file
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nntpServer->SetNewsrcHasChanged(true);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nntpServer->WriteNewsrcFile();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return rv;
+}
+
+nsresult nsMsgNewsFolder::CreateBaseMessageURI(const nsACString& aURI)
+{
+ return nsCreateNewsBaseMessageURI(nsCString(aURI).get(), mBaseMessageURI);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetNewsrcLine(nsACString& newsrcLine)
+{
+ nsresult rv;
+ nsString newsgroupNameUtf16;
+ rv = GetName(newsgroupNameUtf16);
+ if (NS_FAILED(rv)) return rv;
+ NS_ConvertUTF16toUTF8 newsgroupName(newsgroupNameUtf16);
+
+ newsrcLine = newsgroupName;
+ newsrcLine.Append(':');
+
+ if (mReadSet) {
+ nsCString setStr;
+ mReadSet->Output(getter_Copies(setStr));
+ if (NS_SUCCEEDED(rv))
+ {
+ newsrcLine.Append(' ');
+ newsrcLine.Append(setStr);
+ newsrcLine.AppendLiteral(MSG_LINEBREAK);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetReadSetFromStr(const nsACString& newsrcLine)
+{
+ delete mReadSet;
+ mReadSet = nsMsgKeySet::Create(nsCString(newsrcLine).get());
+ NS_ENSURE_TRUE(mReadSet, NS_ERROR_OUT_OF_MEMORY);
+
+ // Now that mReadSet is recreated, make sure it's stored in the db as well.
+ nsCOMPtr<nsINewsDatabase> db = do_QueryInterface(mDatabase);
+ if (db) // it's ok not to have a db here.
+ db->SetReadSet(mReadSet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetUnsubscribedNewsgroupLines(nsACString& aUnsubscribedNewsgroupLines)
+{
+ aUnsubscribedNewsgroupLines = mUnsubscribedNewsgroupLines;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetOptionLines(nsACString& optionLines)
+{
+ optionLines = mOptionLines;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::OnReadChanged(nsIDBChangeListener * aInstigator)
+{
+ return SetNewsrcHasChanged(true);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetUnicodeName(nsAString& aName)
+{
+ return GetName(aName);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetRawName(nsACString & aRawName)
+{
+ nsresult rv;
+ if (mRawName.IsEmpty())
+ {
+ nsString name;
+ rv = GetName(name);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // convert to the server-side encoding
+ nsCOMPtr <nsINntpIncomingServer> nntpServer;
+ rv = GetNntpServer(getter_AddRefs(nntpServer));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString dataCharset;
+ rv = nntpServer->GetCharset(dataCharset);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = nsMsgI18NConvertFromUnicode(dataCharset.get(), name, mRawName);
+
+ if (NS_FAILED(rv))
+ LossyCopyUTF16toASCII(name, mRawName);
+ }
+ aRawName = mRawName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetNntpServer(nsINntpIncomingServer **result)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ rv = server->QueryInterface(NS_GET_IID(nsINntpIncomingServer),
+ getter_AddRefs(nntpServer));
+ if (NS_FAILED(rv))
+ return rv;
+ nntpServer.swap(*result);
+ return NS_OK;
+}
+
+// this gets called after the message actually gets cancelled
+// it removes the cancelled message from the db
+NS_IMETHODIMP nsMsgNewsFolder::RemoveMessage(nsMsgKey key)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv); // if GetDatabase succeeds, mDatabase will be non-null
+
+ // Notify listeners of a delete for a single message
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> msgHdrs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ msgHdrs->AppendElement(msgHdr, false);
+
+ notifier->NotifyMsgsDeleted(msgHdrs);
+ }
+ return mDatabase->DeleteMessage(key, nullptr, false);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::RemoveMessages(nsTArray<nsMsgKey> &aMsgKeys)
+{
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv); // if GetDatabase succeeds, mDatabase will be non-null
+
+ // Notify listeners of a multiple message delete
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+
+ if (notifier)
+ {
+ nsCOMPtr<nsIMutableArray> msgHdrs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ rv = MsgGetHeadersFromKeys(mDatabase, aMsgKeys, msgHdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ notifier->NotifyMsgsDeleted(msgHdrs);
+ }
+
+ return mDatabase->DeleteMessages(aMsgKeys.Length(), aMsgKeys.Elements(), nullptr);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CancelComplete()
+{
+ NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CancelFailed()
+{
+ NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetSaveArticleOffline(bool *aBool)
+{
+ NS_ENSURE_ARG(aBool);
+ *aBool = m_downloadMessageForOfflineUse;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetSaveArticleOffline(bool aBool)
+{
+ m_downloadMessageForOfflineUse = aBool;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow)
+{
+ nsTArray<nsMsgKey> srcKeyArray;
+ SetSaveArticleOffline(true);
+ nsresult rv = NS_OK;
+
+ // build up message keys.
+ if (mDatabase)
+ {
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ bool hasMore;
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports);
+ if (pHeader && NS_SUCCEEDED(rv))
+ {
+ bool shouldStoreMsgOffline = false;
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline);
+ if (shouldStoreMsgOffline)
+ srcKeyArray.AppendElement(msgKey);
+ }
+ }
+ }
+ }
+ RefPtr<DownloadNewsArticlesToOfflineStore> downloadState =
+ new DownloadNewsArticlesToOfflineStore(msgWindow, mDatabase, this);
+ m_downloadingMultipleMessages = true;
+ rv = downloadState->DownloadArticles(msgWindow, this, &srcKeyArray);
+ (void) RefreshSizeOnDisk();
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::DownloadMessagesForOffline(nsIArray *messages, nsIMsgWindow *window)
+{
+ nsTArray<nsMsgKey> srcKeyArray;
+ SetSaveArticleOffline(true); // ### TODO need to clear this when we've finished
+ uint32_t count = 0;
+ uint32_t i;
+ nsresult rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // build up message keys.
+ for (i = 0; i < count; i++)
+ {
+ nsMsgKey key;
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr = do_QueryElementAt(messages, i, &rv);
+ if (msgDBHdr)
+ rv = msgDBHdr->GetMessageKey(&key);
+ if (NS_SUCCEEDED(rv))
+ srcKeyArray.AppendElement(key);
+ }
+ RefPtr<DownloadNewsArticlesToOfflineStore> downloadState =
+ new DownloadNewsArticlesToOfflineStore(window, mDatabase, this);
+ m_downloadingMultipleMessages = true;
+
+ rv = downloadState->DownloadArticles(window, this, &srcKeyArray);
+ (void) RefreshSizeOnDisk();
+ return rv;
+}
+
+// line does not have a line terminator (e.g., CR or CRLF)
+NS_IMETHODIMP nsMsgNewsFolder::NotifyDownloadedLine(const char *line, nsMsgKey keyOfArticle)
+{
+ nsresult rv = NS_OK;
+ if (m_downloadMessageForOfflineUse)
+ {
+ if (!m_offlineHeader)
+ {
+ GetMessageHeader(keyOfArticle, getter_AddRefs(m_offlineHeader));
+ rv = StartNewOfflineMessage();
+ }
+ m_numOfflineMsgLines++;
+ }
+
+ if (m_tempMessageStream)
+ {
+ // line now contains the linebreak.
+ if (line[0] == '.' && line[MSG_LINEBREAK_LEN + 1] == 0)
+ {
+ // end of article.
+ if (m_offlineHeader)
+ EndNewOfflineMessage();
+
+ if (m_tempMessageStream && !m_downloadingMultipleMessages)
+ {
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+ }
+ }
+ else
+ {
+ uint32_t count = 0;
+ rv = m_tempMessageStream->Write(line, strlen(line), &count);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::NotifyFinishedDownloadinghdrs()
+{
+ bool wasCached = !!mDatabase;
+ ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
+ ChangeNumPendingUnread(-mNumPendingUnreadMessages);
+ bool filtersRun;
+ // run the bayesian spam filters, if enabled.
+ CallFilterPlugins(nullptr, &filtersRun);
+
+ // If the DB was not open before, close our reference to it now.
+ if (!wasCached && mDatabase)
+ {
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ mDatabase->RemoveListener(this);
+ // This also clears all of the cached headers that may have been added while
+ // we were downloading messages (and those clearing refcount cycles in the
+ // database).
+ mDatabase->ClearCachedHdrs();
+ mDatabase = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ rv = GetDatabase();
+ if (mDatabase)
+ ApplyRetentionSettings();
+ (void) RefreshSizeOnDisk();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::ApplyRetentionSettings()
+{
+ return nsMsgDBFolder::ApplyRetentionSettings(false);
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetMessageIdForKey(nsMsgKey key, nsACString& result)
+{
+ nsresult rv = GetDatabase();
+ if (!mDatabase)
+ return rv;
+ nsCOMPtr <nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCString id;
+ rv = hdr->GetMessageId(getter_Copies(id));
+ result.Assign(id);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::SetSortOrder(int32_t order)
+{
+ int32_t oldOrder = mSortOrder;
+
+ mSortOrder = order;
+ nsCOMPtr<nsIAtom> sortOrderAtom = MsgGetAtom("SortOrder");
+ // What to do if the atom can't be allocated?
+ NotifyIntPropertyChanged(sortOrderAtom, oldOrder, order);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::GetSortOrder(int32_t *order)
+{
+ NS_ENSURE_ARG_POINTER(order);
+ *order = mSortOrder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::Shutdown(bool shutdownChildren)
+{
+ if (mFilterList)
+ {
+ // close the filter log stream
+ nsresult rv = mFilterList->SetLogStream(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mFilterList = nullptr;
+ }
+
+ mInitialized = false;
+ if (mReadSet) {
+ // the nsINewsDatabase holds a weak ref to the readset,
+ // and we outlive the db, so it's safe to delete it here.
+ nsCOMPtr<nsINewsDatabase> db = do_QueryInterface(mDatabase);
+ if (db)
+ db->SetReadSet(nullptr);
+ delete mReadSet;
+ mReadSet = nullptr;
+ }
+ return nsMsgDBFolder::Shutdown(shutdownChildren);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::SetFilterList(nsIMsgFilterList *aFilterList)
+{
+ if (mIsServer)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return server->SetFilterList(aFilterList);
+ }
+
+ mFilterList = aFilterList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ if (mIsServer)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ return server->GetFilterList(aMsgWindow, aResult);
+ }
+
+ if (!mFilterList)
+ {
+ nsCOMPtr<nsIFile> thisFolder;
+ nsresult rv = GetFilePath(getter_AddRefs(thisFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIFile> filterFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);;
+ rv = filterFile->InitWithFile(thisFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // in 4.x, the news filter file was
+ // C:\Program Files\Netscape\Users\meer\News\host-news.mcom.com\mcom.test.dat
+ // where the summary file was
+ // C:\Program Files\Netscape\Users\meer\News\host-news.mcom.com\mcom.test.snm
+ // we make the rules file ".dat" in mozilla, so that migration works.
+
+ // NOTE:
+ // we don't we need to call NS_MsgHashIfNecessary()
+ // it's already been hashed, if necessary
+ nsCString filterFileName;
+ rv = filterFile->GetNativeLeafName(filterFileName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ filterFileName.AppendLiteral(".dat");
+
+ rv = filterFile->SetNativeLeafName(filterFileName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filterService->OpenFilterList(filterFile, this, aMsgWindow, getter_AddRefs(mFilterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*aResult = mFilterList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetEditableFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ // We don't support pluggable filter list types for news.
+ return GetFilterList(aMsgWindow, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::SetEditableFilterList(nsIMsgFilterList *aFilterList)
+{
+ return SetFilterList(aFilterList);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ if (m_tempMessageStream)
+ {
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+ }
+ m_downloadingMultipleMessages = false;
+ return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
+}
+
+NS_IMETHODIMP
+nsMsgNewsFolder::GetIncomingServerType(nsACString& serverType)
+{
+ serverType.AssignLiteral("nntp");
+ return NS_OK;
+}
+
diff --git a/mailnews/news/src/nsNewsFolder.h b/mailnews/news/src/nsNewsFolder.h
new file mode 100644
index 000000000..b5b085b0b
--- /dev/null
+++ b/mailnews/news/src/nsNewsFolder.h
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ Interface for representing News folders.
+*/
+
+#ifndef nsMsgNewsFolder_h__
+#define nsMsgNewsFolder_h__
+
+#include "mozilla/Attributes.h"
+#include "nsMsgDBFolder.h"
+#include "nsIFile.h"
+#include "nsINntpIncomingServer.h" // need this for the IID
+#include "nsNewsUtils.h"
+#include "nsMsgKeySet.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFilterService.h"
+#include "nsIArray.h"
+
+class nsMsgNewsFolder : public nsMsgDBFolder, public nsIMsgNewsFolder
+{
+public:
+ nsMsgNewsFolder(void);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMSGNEWSFOLDER
+
+ // nsIUrlListener method
+ NS_IMETHOD OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode) override;
+ // nsIMsgFolder methods:
+ NS_IMETHOD GetSubFolders(nsISimpleEnumerator **aResult) override;
+
+ NS_IMETHOD GetMessages(nsISimpleEnumerator **result) override;
+ NS_IMETHOD UpdateFolder(nsIMsgWindow *aWindow) override;
+
+ NS_IMETHOD CreateSubfolder(const nsAString& folderName,
+ nsIMsgWindow *msgWindow) override;
+
+ NS_IMETHOD Delete() override;
+ NS_IMETHOD Rename(const nsAString& newName,
+ nsIMsgWindow *msgWindow) override;
+
+ NS_IMETHOD GetAbbreviatedName(nsAString& aAbbreviatedName) override;
+
+ NS_IMETHOD GetFolderURL(nsACString& url) override;
+
+ NS_IMETHOD GetExpungedBytesCount(int64_t *count);
+ NS_IMETHOD GetDeletable(bool *deletable) override;
+
+ NS_IMETHOD RefreshSizeOnDisk();
+
+ NS_IMETHOD GetSizeOnDisk(int64_t *size) override;
+
+ NS_IMETHOD GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo,
+ nsIMsgDatabase **db) override;
+
+ NS_IMETHOD DeleteMessages(nsIArray *messages,
+ nsIMsgWindow *msgWindow, bool deleteStorage,
+ bool isMove, nsIMsgCopyServiceListener* listener,
+ bool allowUndo) override;
+ NS_IMETHOD GetNewMessages(nsIMsgWindow *aWindow,
+ nsIUrlListener *aListener) override;
+
+ NS_IMETHOD GetCanSubscribe(bool *aResult) override;
+ NS_IMETHOD GetCanFileMessages(bool *aResult) override;
+ NS_IMETHOD GetCanCreateSubfolders(bool *aResult) override;
+ NS_IMETHOD GetCanRename(bool *aResult) override;
+ NS_IMETHOD GetCanCompact(bool *aResult) override;
+ NS_IMETHOD OnReadChanged(nsIDBChangeListener * aInstigator) override;
+
+ NS_IMETHOD DownloadMessagesForOffline(nsIArray *messages,
+ nsIMsgWindow *window) override;
+ NS_IMETHOD Compact(nsIUrlListener *aListener,
+ nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD DownloadAllForOffline(nsIUrlListener *listener,
+ nsIMsgWindow *msgWindow) override;
+ NS_IMETHOD GetSortOrder(int32_t *order) override;
+ NS_IMETHOD SetSortOrder(int32_t order) override;
+
+ NS_IMETHOD Shutdown(bool shutdownChildren) override;
+
+ NS_IMETHOD GetFilterList(nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList **aFilterList) override;
+ NS_IMETHOD GetEditableFilterList(nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList **aFilterList) override;
+ NS_IMETHOD SetFilterList(nsIMsgFilterList *aFilterList) override;
+ NS_IMETHOD SetEditableFilterList(nsIMsgFilterList *aFilterList) override;
+ NS_IMETHOD ApplyRetentionSettings() override;
+ NS_IMETHOD GetIncomingServerType(nsACString& serverType) override;
+
+protected:
+ virtual ~nsMsgNewsFolder();
+ // helper routine to parse the URI and update member variables
+ nsresult AbbreviatePrettyName(nsAString& prettyName, int32_t fullwords);
+ nsresult ParseFolder(nsIFile *path);
+ nsresult CreateSubFolders(nsIFile *path);
+ nsresult AddDirectorySeparator(nsIFile *path);
+ nsresult GetDatabase() override;
+ virtual nsresult CreateChildFromURI(const nsCString &uri,
+ nsIMsgFolder **folder) override;
+
+ nsresult LoadNewsrcFileAndCreateNewsgroups();
+ int32_t RememberLine(const nsACString& line);
+ nsresult RememberUnsubscribedGroup(const nsACString& newsgroup, const nsACString& setStr);
+ nsresult ForgetLine(void);
+ nsresult GetNewsMessages(nsIMsgWindow *aMsgWindow, bool getOld, nsIUrlListener *aListener);
+
+ int32_t HandleNewsrcLine(const char * line, uint32_t line_size);
+ virtual nsresult CreateBaseMessageURI(const nsACString& aURI) override;
+
+protected:
+ int64_t mExpungedBytes;
+ bool mGettingNews;
+ bool mInitialized;
+ bool m_downloadMessageForOfflineUse;
+ bool m_downloadingMultipleMessages;
+
+ nsCString mOptionLines;
+ nsCString mUnsubscribedNewsgroupLines;
+ nsMsgKeySet *mReadSet;
+
+ nsCOMPtr<nsIFile> mNewsrcFilePath;
+
+ // used for auth news
+ nsCString mGroupUsername;
+ nsCString mGroupPassword;
+
+ // the name of the newsgroup.
+ nsCString mRawName;
+ int32_t mSortOrder;
+
+private:
+ /**
+ * Constructs a signon url for use in login manager.
+ *
+ * @param ref The URI ref (should be null unless working with legacy).
+ * @param result The result of the string
+ */
+ nsresult CreateNewsgroupUrlForSignon(const char *ref, nsAString &result);
+ nsCOMPtr <nsIMsgFilterList> mFilterList;
+};
+
+#endif // nsMsgNewsFolder_h__
diff --git a/mailnews/news/src/nsNewsUtils.cpp b/mailnews/news/src/nsNewsUtils.cpp
new file mode 100644
index 000000000..b7fc5e70c
--- /dev/null
+++ b/mailnews/news/src/nsNewsUtils.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "nntpCore.h"
+#include "nsNewsUtils.h"
+#include "nsMsgUtils.h"
+
+
+/* parses NewsMessageURI */
+nsresult
+nsParseNewsMessageURI(const char* uri, nsCString& group, nsMsgKey *key)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(key);
+
+ nsAutoCString uriStr(uri);
+ int32_t keySeparator = uriStr.FindChar('#');
+ if(keySeparator != -1)
+ {
+ int32_t keyEndSeparator = MsgFindCharInSet(uriStr, "?&", keySeparator);
+
+ // Grab between the last '/' and the '#' for the key
+ group = StringHead(uriStr, keySeparator);
+ int32_t groupSeparator = group.RFind("/");
+ if (groupSeparator == -1)
+ return NS_ERROR_FAILURE;
+
+ // Our string APIs don't let us unescape into the same buffer from earlier,
+ // so escape into a temporary
+ nsAutoCString unescapedGroup;
+ MsgUnescapeString(Substring(group, groupSeparator + 1), 0, unescapedGroup);
+ group = unescapedGroup;
+
+ nsAutoCString keyStr;
+ if (keyEndSeparator != -1)
+ keyStr = Substring(uriStr, keySeparator + 1, keyEndSeparator - (keySeparator + 1));
+ else
+ keyStr = Substring(uriStr, keySeparator + 1);
+ nsresult errorCode;
+ *key = keyStr.ToInteger(&errorCode);
+
+ return errorCode;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsCreateNewsBaseMessageURI(const char *baseURI, nsCString &baseMessageURI)
+{
+ nsAutoCString tailURI(baseURI);
+
+ // chop off news:/
+ if (tailURI.Find(kNewsRootURI) == 0)
+ tailURI.Cut(0, PL_strlen(kNewsRootURI));
+
+ baseMessageURI = kNewsMessageRootURI;
+ baseMessageURI += tailURI;
+
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNewsUtils.h b/mailnews/news/src/nsNewsUtils.h
new file mode 100644
index 000000000..17170d6c2
--- /dev/null
+++ b/mailnews/news/src/nsNewsUtils.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_NEWSUTILS_H
+#define NS_NEWSUTILS_H
+
+#include "nsStringGlue.h"
+#include "MailNewsTypes2.h"
+
+class nsIMsgNewsFolder;
+
+#define kNewsRootURI "news:/"
+#define kNntpRootURI "nntp:/"
+#define kNewsMessageRootURI "news-message:/"
+#define kNewsURIGroupQuery "?group="
+#define kNewsURIKeyQuery "&key="
+
+#define kNewsRootURILen 6
+#define kNntpRootURILen 6
+#define kNewsMessageRootURILen 14
+#define kNewsURIGroupQueryLen 7
+#define kNewsURIKeyQueryLen 5
+
+extern nsresult
+nsParseNewsMessageURI(const char* uri, nsCString& group, nsMsgKey *key);
+
+extern nsresult
+nsCreateNewsBaseMessageURI(const char *baseURI, nsCString &baseMessageURI);
+
+#endif //NS_NEWSUTILS_H
+
diff --git a/mailnews/news/src/nsNntpIncomingServer.cpp b/mailnews/news/src/nsNntpIncomingServer.cpp
new file mode 100644
index 000000000..7c3fcad4b
--- /dev/null
+++ b/mailnews/news/src/nsNntpIncomingServer.cpp
@@ -0,0 +1,2162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNntpIncomingServer.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsNewsFolder.h"
+#include "nsIMsgFolder.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsINntpService.h"
+#include "nsINNTPProtocol.h"
+#include "nsMsgNewsCID.h"
+#include "nsNNTPProtocol.h"
+#include "nsIDirectoryService.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsMsgUtils.h"
+#include "nsIPrompt.h"
+#include "nsIStringBundle.h"
+#include "nntpCore.h"
+#include "nsIWindowWatcher.h"
+#include "nsITreeColumns.h"
+#include "nsIDOMElement.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgI18N.h"
+#include "nsUnicharUtils.h"
+#include "nsILineInputStream.h"
+#include "nsNetUtil.h"
+#include "nsISimpleEnumerator.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+#include "nsITreeBoxObject.h"
+
+#define INVALID_VERSION 0
+#define VALID_VERSION 2
+#define NEW_NEWS_DIR_NAME "News"
+#define PREF_MAIL_NEWSRC_ROOT "mail.newsrc_root"
+#define PREF_MAIL_NEWSRC_ROOT_REL "mail.newsrc_root-rel"
+#define PREF_MAILNEWS_VIEW_DEFAULT_CHARSET "mailnews.view_default_charset"
+#define HOSTINFO_FILE_NAME "hostinfo.dat"
+
+#define NEWS_DELIMITER '.'
+
+// this platform specific junk is so the newsrc filenames we create
+// will resemble the migrated newsrc filenames.
+#if defined(XP_UNIX)
+#define NEWSRC_FILE_PREFIX "newsrc-"
+#define NEWSRC_FILE_SUFFIX ""
+#else
+#define NEWSRC_FILE_PREFIX ""
+#define NEWSRC_FILE_SUFFIX ".rc"
+#endif /* XP_UNIX */
+
+// ###tw This really ought to be the most
+// efficient file reading size for the current
+// operating system.
+#define HOSTINFO_FILE_BUFFER_SIZE 1024
+
+#include "nsMsgUtils.h"
+
+/**
+ * A comparator class to do cases insensitive comparisons for nsTArray.Sort()
+ */
+class nsCStringLowerCaseComparator
+{
+public:
+ bool Equals(const nsCString &a, const nsCString &b) const
+ {
+ return a.Equals(b, nsCaseInsensitiveCStringComparator());
+ }
+
+ bool LessThan(const nsCString &a, const nsCString &b) const
+ {
+ return Compare(a, b, nsCaseInsensitiveCStringComparator()) < 0;
+ }
+};
+
+static NS_DEFINE_CID(kSubscribableServerCID, NS_SUBSCRIBABLESERVER_CID);
+
+NS_IMPL_ADDREF_INHERITED(nsNntpIncomingServer, nsMsgIncomingServer)
+NS_IMPL_RELEASE_INHERITED(nsNntpIncomingServer, nsMsgIncomingServer)
+
+NS_INTERFACE_MAP_BEGIN(nsNntpIncomingServer)
+ NS_INTERFACE_MAP_ENTRY(nsINntpIncomingServer)
+ NS_INTERFACE_MAP_ENTRY(nsIUrlListener)
+ NS_INTERFACE_MAP_ENTRY(nsISubscribableServer)
+ NS_INTERFACE_MAP_ENTRY(nsITreeView)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgIncomingServer)
+
+nsNntpIncomingServer::nsNntpIncomingServer()
+{
+ mNewsrcHasChanged = false;
+
+ mGetOnlyNew = true;
+
+ mHostInfoLoaded = false;
+ mHostInfoHasChanged = false;
+ mVersion = INVALID_VERSION;
+
+ mLastGroupDate = 0;
+ mUniqueId = 0;
+ mHasSeenBeginGroups = false;
+ mPostingAllowed = false;
+ mLastUpdatedTime = 0;
+
+ // these atoms are used for subscribe search
+ mSubscribedAtom = MsgGetAtom("subscribed");
+ mNntpAtom = MsgGetAtom("nntp");
+
+ // we have server wide and per group filters
+ m_canHaveFilters = true;
+
+ SetupNewsrcSaveTimer();
+}
+
+nsNntpIncomingServer::~nsNntpIncomingServer()
+{
+ mozilla::DebugOnly<nsresult> rv;
+
+ if (mNewsrcSaveTimer) {
+ mNewsrcSaveTimer->Cancel();
+ mNewsrcSaveTimer = nullptr;
+ }
+ rv = ClearInner();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "ClearInner failed");
+
+ rv = CloseCachedConnections();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CloseCachedConnections failed");
+}
+
+NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, NotifyOn, "notify.on")
+NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, MarkOldRead, "mark_old_read")
+NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, Abbreviate, "abbreviate")
+NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, PushAuth, "always_authenticate")
+NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, SingleSignon, "singleSignon")
+NS_IMPL_SERVERPREF_INT(nsNntpIncomingServer, MaxArticles, "max_articles")
+
+nsresult
+nsNntpIncomingServer::CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder)
+{
+ nsMsgNewsFolder *newRootFolder = new nsMsgNewsFolder;
+ if (!newRootFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*rootFolder = newRootFolder);
+ newRootFolder->Init(serverUri.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetNewsrcFilePath(nsIFile **aNewsrcFilePath)
+{
+ nsresult rv;
+ if (mNewsrcFilePath)
+ {
+ *aNewsrcFilePath = mNewsrcFilePath;
+ NS_IF_ADDREF(*aNewsrcFilePath);
+ return NS_OK;
+ }
+
+ rv = GetFileValue("newsrc.file-rel", "newsrc.file", aNewsrcFilePath);
+ if (NS_SUCCEEDED(rv) && *aNewsrcFilePath)
+ {
+ mNewsrcFilePath = *aNewsrcFilePath;
+ return rv;
+ }
+
+ rv = GetNewsrcRootPath(getter_AddRefs(mNewsrcFilePath));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString hostname;
+ rv = GetHostName(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newsrcFileName(NEWSRC_FILE_PREFIX);
+ newsrcFileName.Append(hostname);
+ newsrcFileName.Append(NEWSRC_FILE_SUFFIX);
+ rv = mNewsrcFilePath->AppendNative(newsrcFileName);
+ rv = mNewsrcFilePath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetNewsrcFilePath(mNewsrcFilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aNewsrcFilePath = mNewsrcFilePath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetNewsrcFilePath(nsIFile *aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ bool exists;
+ nsresult rv = aFile->Exists(&exists);
+ if (!exists)
+ {
+ rv = aFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0664);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return SetFileValue("newsrc.file-rel", "newsrc.file", aFile);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetLocalStoreType(nsACString& type)
+{
+ type.AssignLiteral("news");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetLocalDatabaseType(nsACString& type)
+{
+ type.AssignLiteral("news");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetNewsrcRootPath(nsIFile *aNewsrcRootPath)
+{
+ NS_ENSURE_ARG(aNewsrcRootPath);
+ return NS_SetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT, aNewsrcRootPath);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetNewsrcRootPath(nsIFile **aNewsrcRootPath)
+{
+ NS_ENSURE_ARG_POINTER(aNewsrcRootPath);
+ *aNewsrcRootPath = nullptr;
+
+ bool havePref;
+ nsresult rv = NS_GetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL,
+ PREF_MAIL_NEWSRC_ROOT,
+ NS_APP_NEWS_50_DIR,
+ havePref,
+ aNewsrcRootPath);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = (*aNewsrcRootPath)->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = (*aNewsrcRootPath)->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!havePref || !exists)
+ {
+ rv = NS_SetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT, *aNewsrcRootPath);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
+ }
+ return rv;
+}
+
+/* static */ void nsNntpIncomingServer::OnNewsrcSaveTimer(nsITimer *timer, void *voidIncomingServer)
+{
+ nsNntpIncomingServer *incomingServer = (nsNntpIncomingServer*)voidIncomingServer;
+ incomingServer->WriteNewsrcFile();
+}
+
+nsresult nsNntpIncomingServer::SetupNewsrcSaveTimer()
+{
+ int64_t ms(300000); // hard code, 5 minutes.
+ //Convert biffDelay into milliseconds
+ uint32_t timeInMSUint32 = (uint32_t)ms;
+ //Can't currently reset a timer when it's in the process of
+ //calling Notify. So, just release the timer here and create a new one.
+ if(mNewsrcSaveTimer)
+ mNewsrcSaveTimer->Cancel();
+ mNewsrcSaveTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mNewsrcSaveTimer->InitWithFuncCallback(OnNewsrcSaveTimer, (void*)this, timeInMSUint32,
+ nsITimer::TYPE_REPEATING_SLACK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetCharset(const nsACString & aCharset)
+{
+ return SetCharValue("charset", aCharset);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCharset(nsACString & aCharset)
+{
+ //first we get the per-server settings mail.server.<serverkey>.charset
+ nsresult rv = GetCharValue("charset", aCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ //if the per-server setting is empty,we get the default charset from
+ //mailnews.view_default_charset setting and set it as per-server preference.
+ if (aCharset.IsEmpty()) {
+ nsString defaultCharset;
+ rv = NS_GetLocalizedUnicharPreferenceWithDefault(nullptr,
+ PREF_MAILNEWS_VIEW_DEFAULT_CHARSET,
+ NS_LITERAL_STRING("ISO-8859-1"), defaultCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LossyCopyUTF16toASCII(defaultCharset, aCharset);
+ SetCharset(aCharset);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::WriteNewsrcFile()
+{
+ nsresult rv;
+
+ bool newsrcHasChanged;
+ rv = GetNewsrcHasChanged(&newsrcHasChanged);
+ if (NS_FAILED(rv)) return rv;
+
+#ifdef DEBUG_NEWS
+ nsCString hostname;
+ rv = GetHostName(hostname);
+ if (NS_FAILED(rv)) return rv;
+#endif /* DEBUG_NEWS */
+
+ if (newsrcHasChanged) {
+#ifdef DEBUG_NEWS
+ printf("write newsrc file for %s\n", hostname.get());
+#endif
+ nsCOMPtr <nsIFile> newsrcFile;
+ rv = GetNewsrcFilePath(getter_AddRefs(newsrcFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> newsrcStream;
+ nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(newsrcStream), newsrcFile, -1, 00600);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(rootFolder, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t bytesWritten;
+ nsCString optionLines;
+ rv = newsFolder->GetOptionLines(optionLines);
+ if (NS_SUCCEEDED(rv) && !optionLines.IsEmpty()) {
+ newsrcStream->Write(optionLines.get(), optionLines.Length(), &bytesWritten);
+#ifdef DEBUG_NEWS
+ printf("option lines:\n%s", optionLines.get());
+#endif /* DEBUG_NEWS */
+ }
+#ifdef DEBUG_NEWS
+ else {
+ printf("no option lines to write out\n");
+ }
+#endif /* DEBUG_NEWS */
+
+ nsCString unsubscribedLines;
+ rv = newsFolder->GetUnsubscribedNewsgroupLines(unsubscribedLines);
+ if (NS_SUCCEEDED(rv) && !unsubscribedLines.IsEmpty()) {
+ newsrcStream->Write(unsubscribedLines.get(), unsubscribedLines.Length(), &bytesWritten);
+#ifdef DEBUG_NEWS
+ printf("unsubscribedLines:\n%s", unsubscribedLines.get());
+#endif /* DEBUG_NEWS */
+ }
+#ifdef DEBUG_NEWS
+ else {
+ printf("no unsubscribed lines to write out\n");
+ }
+#endif /* DEBUG_NEWS */
+
+ rv = rootFolder->GetSubFolders(getter_AddRefs(subFolders));
+ if (NS_FAILED(rv)) return rv;
+
+ bool moreFolders;
+
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) &&
+ moreFolders) {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ newsFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && newsFolder) {
+ nsCString newsrcLine;
+ rv = newsFolder->GetNewsrcLine(newsrcLine);
+ if (NS_SUCCEEDED(rv) && !newsrcLine.IsEmpty()) {
+ // write the line to the newsrc file
+ newsrcStream->Write(newsrcLine.get(), newsrcLine.Length(), &bytesWritten);
+ }
+ }
+ }
+ }
+
+ newsrcStream->Close();
+
+ rv = SetNewsrcHasChanged(false);
+ if (NS_FAILED(rv)) return rv;
+ }
+#ifdef DEBUG_NEWS
+ else {
+ printf("no need to write newsrc file for %s, it was not dirty\n", (hostname.get()));
+ }
+#endif /* DEBUG_NEWS */
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetNewsrcHasChanged(bool aNewsrcHasChanged)
+{
+ mNewsrcHasChanged = aNewsrcHasChanged;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetNewsrcHasChanged(bool *aNewsrcHasChanged)
+{
+ if (!aNewsrcHasChanged) return NS_ERROR_NULL_POINTER;
+
+ *aNewsrcHasChanged = mNewsrcHasChanged;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::CloseCachedConnections()
+{
+ nsresult rv;
+ nsCOMPtr<nsINNTPProtocol> connection;
+
+ // iterate through the connection cache and close the connections.
+ int32_t cnt = mConnectionCache.Count();
+
+ for (int32_t i = 0; i < cnt; ++i)
+ {
+ connection = mConnectionCache[0];
+ if (connection)
+ {
+ rv = connection->CloseConnection();
+ // We need to do this instead of RemoveObjectAt(0) because the
+ // above call will likely cause the object to be removed from the
+ // array anyway
+ mConnectionCache.RemoveObject(connection);
+ }
+ }
+
+ rv = WriteNewsrcFile();
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mGetOnlyNew && !mHostInfoLoaded)
+ {
+ rv = WriteHostInfoFile();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetMaximumConnectionsNumber(int32_t *aMaxConnections)
+{
+ NS_ENSURE_ARG_POINTER(aMaxConnections);
+
+ nsresult rv = GetIntValue("max_cached_connections", aMaxConnections);
+ // Get our maximum connection count. We need at least 1. If the value is 0,
+ // we use the default. If it's negative, we treat that as 1.
+ if (NS_SUCCEEDED(rv) && *aMaxConnections > 0)
+ return NS_OK;
+
+ *aMaxConnections = (NS_FAILED(rv) || (*aMaxConnections == 0)) ? 2 : 1;
+ (void)SetMaximumConnectionsNumber(*aMaxConnections);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetMaximumConnectionsNumber(int32_t aMaxConnections)
+{
+ return SetIntValue("max_cached_connections", aMaxConnections);
+}
+
+bool
+nsNntpIncomingServer::ConnectionTimeOut(nsINNTPProtocol* aConnection)
+{
+ bool retVal = false;
+ if (!aConnection)
+ return retVal;
+
+ PRTime lastActiveTimeStamp;
+ if (NS_FAILED(aConnection->GetLastActiveTimeStamp(&lastActiveTimeStamp)))
+ return retVal;
+
+ if (PR_Now() - lastActiveTimeStamp >= PRTime(170) * PR_USEC_PER_SEC)
+ {
+#ifdef DEBUG_seth
+ printf("XXX connection timed out, close it, and remove it from the connection cache\n");
+#endif
+ aConnection->CloseConnection();
+ mConnectionCache.RemoveObject(aConnection);
+ retVal = true;
+ }
+ return retVal;
+}
+
+
+nsresult
+nsNntpIncomingServer::CreateProtocolInstance(nsINNTPProtocol ** aNntpConnection, nsIURI *url,
+ nsIMsgWindow *aMsgWindow)
+{
+ // create a new connection and add it to the connection cache
+ // we may need to flag the protocol connection as busy so we don't get
+ // a race
+ // condition where someone else goes through this code
+ nsNNTPProtocol *protocolInstance = new nsNNTPProtocol(this, url, aMsgWindow);
+ if (!protocolInstance)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = protocolInstance->QueryInterface(NS_GET_IID(nsINNTPProtocol), (void **) aNntpConnection);
+ // take the protocol instance and add it to the connectionCache
+ if (NS_SUCCEEDED(rv) && *aNntpConnection)
+ mConnectionCache.AppendObject(*aNntpConnection);
+ return rv;
+}
+
+
+nsresult
+nsNntpIncomingServer::GetNntpConnection(nsIURI * aUri, nsIMsgWindow *aMsgWindow,
+ nsINNTPProtocol ** aNntpConnection)
+{
+ int32_t maxConnections;
+ (void)GetMaximumConnectionsNumber(&maxConnections);
+
+ // Find a non-busy connection
+ nsCOMPtr<nsINNTPProtocol> connection;
+ int32_t cnt = mConnectionCache.Count();
+ for (int32_t i = 0; i < cnt; i++)
+ {
+ connection = mConnectionCache[i];
+ if (connection)
+ {
+ bool isBusy;
+ connection->GetIsBusy(&isBusy);
+ if (!isBusy)
+ break;
+ connection = nullptr;
+ }
+ }
+
+ if (ConnectionTimeOut(connection))
+ {
+ connection = nullptr;
+ // We have one less connection, since we closed this one.
+ --cnt;
+ }
+
+ if (connection)
+ {
+ NS_IF_ADDREF(*aNntpConnection = connection);
+ connection->SetIsCachedConnection(true);
+ }
+ else if (cnt < maxConnections)
+ {
+ // We have room for another connection. Create this connection and return
+ // it to the caller.
+ nsresult rv = CreateProtocolInstance(aNntpConnection, aUri, aMsgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ // We maxed out our connection count. The caller must therefore enqueue the
+ // call.
+ *aNntpConnection = nullptr;
+ return NS_OK;
+ }
+
+ // Initialize the URI here and now.
+ return (*aNntpConnection)->Initialize(aUri, aMsgWindow);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetNntpChannel(nsIURI *aURI, nsIMsgWindow *aMsgWindow,
+ nsIChannel **aChannel)
+{
+ NS_ENSURE_ARG_POINTER(aChannel);
+
+ nsCOMPtr<nsINNTPProtocol> protocol;
+ nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (protocol)
+ return CallQueryInterface(protocol, aChannel);
+
+ // No protocol? We need our mock channel.
+ nsNntpMockChannel *channel = new nsNntpMockChannel(aURI, aMsgWindow);
+ if (!channel)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aChannel = channel);
+
+ m_queuedChannels.AppendElement(channel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::LoadNewsUrl(nsIURI *aURI, nsIMsgWindow *aMsgWindow,
+ nsISupports *aConsumer)
+{
+ nsCOMPtr<nsINNTPProtocol> protocol;
+ nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (protocol)
+ return protocol->LoadNewsUrl(aURI, aConsumer);
+
+ // No protocol? We need our mock channel.
+ nsNntpMockChannel *channel = new nsNntpMockChannel(aURI, aMsgWindow,
+ aConsumer);
+ if (!channel)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ m_queuedChannels.AppendElement(channel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PrepareForNextUrl(nsNNTPProtocol *aConnection)
+{
+ NS_ENSURE_ARG(aConnection);
+
+ // Start the connection on the next URL in the queue. If it can't get a URL to
+ // work, drop that URL (the channel will handle failure notification) and move
+ // on.
+ while (m_queuedChannels.Length() > 0)
+ {
+ RefPtr<nsNntpMockChannel> channel = m_queuedChannels[0];
+ m_queuedChannels.RemoveElementAt(0);
+ nsresult rv = channel->AttachNNTPConnection(*aConnection);
+ // If this succeeded, the connection is now running the URL.
+ if (NS_SUCCEEDED(rv))
+ return NS_OK;
+ }
+
+ // No queued uris.
+ return NS_OK;
+}
+
+/* void RemoveConnection (in nsINNTPProtocol aNntpConnection); */
+NS_IMETHODIMP nsNntpIncomingServer::RemoveConnection(nsINNTPProtocol *aNntpConnection)
+{
+ if (aNntpConnection)
+ mConnectionCache.RemoveObject(aNntpConnection);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PerformExpand(nsIMsgWindow *aMsgWindow)
+{
+ // Get news.update_unread_on_expand pref
+ nsresult rv;
+ bool updateUnreadOnExpand = true;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ prefBranch->GetBoolPref("news.update_unread_on_expand", &updateUnreadOnExpand);
+
+ // Only if news.update_unread_on_expand is true do we update the unread counts
+ if (updateUnreadOnExpand)
+ return DownloadMail(aMsgWindow);
+ return NS_OK;
+}
+
+nsresult
+nsNntpIncomingServer::DownloadMail(nsIMsgWindow *aMsgWindow)
+{
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> groups;
+ rv = rootFolder->GetSubFolders(getter_AddRefs(groups));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasNext;
+ while (NS_SUCCEEDED(rv = groups->HasMoreElements(&hasNext)) && hasNext)
+ {
+ nsCOMPtr<nsISupports> nextGroup;
+ rv = groups->GetNext(getter_AddRefs(nextGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> group(do_QueryInterface(nextGroup));
+ rv = group->GetNewMessages(aMsgWindow, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::DisplaySubscribedGroup(nsIMsgNewsFolder *aMsgFolder, int32_t aFirstMessage, int32_t aLastMessage, int32_t aTotalMessages)
+{
+ nsresult rv;
+
+ if (!aMsgFolder) return NS_ERROR_NULL_POINTER;
+#ifdef DEBUG_NEWS
+ printf("DisplaySubscribedGroup(...,%ld,%ld,%ld)\n",aFirstMessage,aLastMessage,aTotalMessages);
+#endif
+ rv = aMsgFolder->UpdateSummaryFromNNTPInfo(aFirstMessage,aLastMessage,aTotalMessages);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PerformBiff(nsIMsgWindow *aMsgWindow)
+{
+ // Biff will force a download of the messages. If the user doesn't want this
+ // (e.g., there is a lot of high-traffic newsgroups), the better option is to
+ // just ignore biff.
+ return PerformExpand(aMsgWindow);
+}
+
+NS_IMETHODIMP nsNntpIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff)
+{
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ *aServerRequiresPasswordForBiff = false; // for news, biff is getting the unread counts
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::OnStartRunningUrl(nsIURI *url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::OnStopRunningUrl(nsIURI *url, nsresult exitCode)
+{
+ nsresult rv;
+ rv = UpdateSubscribed();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = StopPopulating(mMsgWindow);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::ContainsNewsgroup(const nsACString &aName,
+ bool *containsGroup)
+{
+ NS_ENSURE_ARG_POINTER(containsGroup);
+ NS_ENSURE_FALSE(aName.IsEmpty(), NS_ERROR_FAILURE);
+
+ if (mSubscribedNewsgroups.Length() == 0)
+ {
+ // If this is empty, we may need to discover folders
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ {
+ nsCOMPtr<nsISimpleEnumerator> subfolders;
+ rootFolder->GetSubFolders(getter_AddRefs(subfolders));
+ }
+ }
+ nsAutoCString unescapedName;
+ MsgUnescapeString(aName, 0, unescapedName);
+ *containsGroup = mSubscribedNewsgroups.Contains(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SubscribeToNewsgroup(const nsACString &aName)
+{
+ NS_ASSERTION(!aName.IsEmpty(), "no name");
+ NS_ENSURE_FALSE(aName.IsEmpty(), NS_ERROR_FAILURE);
+
+ // If we already have this newsgroup, do nothing and report success.
+ bool containsGroup = false;
+ nsresult rv = ContainsNewsgroup(aName, &containsGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (containsGroup)
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> msgfolder;
+ rv = GetRootMsgFolder(getter_AddRefs(msgfolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(msgfolder, NS_ERROR_FAILURE);
+
+ return msgfolder->CreateSubfolder(NS_ConvertUTF8toUTF16(aName), nullptr);
+}
+
+bool
+writeGroupToHostInfoFile(nsCString &aElement, void *aData)
+{
+ nsIOutputStream *stream;
+ stream = (nsIOutputStream *)aData;
+ NS_ASSERTION(stream, "no stream");
+ if (!stream) {
+ // stop, something is bad.
+ return false;
+ }
+ return true;
+}
+
+void nsNntpIncomingServer::WriteLine(nsIOutputStream *stream, nsCString &str)
+{
+ uint32_t bytesWritten;
+ str.Append(MSG_LINEBREAK);
+ stream->Write(str.get(), str.Length(), &bytesWritten);
+}
+nsresult
+nsNntpIncomingServer::WriteHostInfoFile()
+{
+ if (!mHostInfoHasChanged)
+ return NS_OK;
+
+ mLastUpdatedTime = uint32_t(PR_Now() / PR_USEC_PER_SEC);
+
+ nsCString hostname;
+ nsresult rv = GetHostName(hostname);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!mHostInfoFile)
+ return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIOutputStream> hostInfoStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(hostInfoStream), mHostInfoFile, -1, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX TODO: missing some formatting, see the 4.x code
+ nsAutoCString header("# News host information file.");
+ WriteLine(hostInfoStream, header);
+ header.Assign("# This is a generated file! Do not edit.");
+ WriteLine(hostInfoStream, header);
+ header.Truncate();
+ WriteLine(hostInfoStream, header);
+ nsAutoCString version("version=");
+ version.AppendInt(VALID_VERSION);
+ WriteLine(hostInfoStream, version);
+ nsAutoCString newsrcname("newsrcname=");
+ newsrcname.Append(hostname);
+ WriteLine(hostInfoStream, hostname);
+ nsAutoCString dateStr("lastgroupdate=");
+ dateStr.AppendInt(mLastUpdatedTime);
+ WriteLine(hostInfoStream, dateStr);
+ dateStr = "uniqueid=";
+ dateStr.AppendInt(mUniqueId);
+ WriteLine(hostInfoStream, dateStr);
+ header.Assign(MSG_LINEBREAK"begingroups");
+ WriteLine(hostInfoStream, header);
+
+ // XXX TODO: sort groups first?
+ uint32_t length = mGroupsOnServer.Length();
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ uint32_t bytesWritten;
+ hostInfoStream->Write(mGroupsOnServer[i].get(), mGroupsOnServer[i].Length(),
+ &bytesWritten);
+ hostInfoStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten);
+ }
+
+ hostInfoStream->Close();
+ mHostInfoHasChanged = false;
+ return NS_OK;
+}
+
+nsresult
+nsNntpIncomingServer::LoadHostInfoFile()
+{
+ nsresult rv;
+ // we haven't loaded it yet
+ mHostInfoLoaded = false;
+
+ rv = GetLocalPath(getter_AddRefs(mHostInfoFile));
+ if (NS_FAILED(rv)) return rv;
+ if (!mHostInfoFile) return NS_ERROR_FAILURE;
+
+ rv = mHostInfoFile->AppendNative(NS_LITERAL_CSTRING(HOSTINFO_FILE_NAME));
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ rv = mHostInfoFile->Exists(&exists);
+ if (NS_FAILED(rv)) return rv;
+
+ // it is ok if the hostinfo.dat file does not exist.
+ if (!exists) return NS_OK;
+
+ nsCOMPtr<nsIInputStream> fileStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), mHostInfoFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsCString line;
+
+ while (more && NS_SUCCEEDED(rv))
+ {
+ rv = lineInputStream->ReadLine(line, &more);
+ if (line.IsEmpty())
+ continue;
+ HandleLine(line.get(), line.Length());
+ }
+ mHasSeenBeginGroups = false;
+ fileStream->Close();
+
+ return UpdateSubscribed();
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::StartPopulatingWithUri(nsIMsgWindow *aMsgWindow, bool aForceToServer, const char *uri)
+{
+#ifdef DEBUG_seth
+ printf("StartPopulatingWithUri(%s)\n",uri);
+#endif
+
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mInner->StartPopulatingWithUri(aMsgWindow, aForceToServer, uri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = StopPopulating(mMsgWindow);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SubscribeCleanup()
+{
+ nsresult rv = NS_OK;
+ rv = ClearInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::StartPopulating(nsIMsgWindow *aMsgWindow, bool aForceToServer, bool aGetOnlyNew)
+{
+ mMsgWindow = aMsgWindow;
+
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mInner->StartPopulating(aMsgWindow, aForceToServer, aGetOnlyNew);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = SetDelimiter(NEWS_DELIMITER);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = SetShowFullName(true);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ mHostInfoLoaded = false;
+ mVersion = INVALID_VERSION;
+ mGroupsOnServer.Clear();
+ mGetOnlyNew = aGetOnlyNew;
+
+ if (!aForceToServer) {
+ rv = LoadHostInfoFile();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // mHostInfoLoaded can be false if we failed to load anything
+ if (aForceToServer || !mHostInfoLoaded || (mVersion != VALID_VERSION)) {
+ // set these to true, so when we are done and we call WriteHostInfoFile()
+ // we'll write out to hostinfo.dat
+ mHostInfoHasChanged = true;
+ mVersion = VALID_VERSION;
+
+ mGroupsOnServer.Clear();
+ rv = nntpService->GetListOfGroupsOnServer(this, aMsgWindow, aGetOnlyNew);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ rv = StopPopulating(aMsgWindow);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * This method is the entry point for |nsNNTPProtocol| class. |aName| is now
+ * encoded in the serverside character encoding, but we need to handle
+ * newsgroup names in UTF-8 internally, So we convert |aName| to
+ * UTF-8 here for later use.
+ **/
+NS_IMETHODIMP
+nsNntpIncomingServer::AddNewsgroupToList(const char *aName)
+{
+ nsresult rv;
+
+ nsAutoString newsgroupName;
+ nsAutoCString dataCharset;
+ rv = GetCharset(dataCharset);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nsMsgI18NConvertToUnicode(dataCharset.get(),
+ nsDependentCString(aName),
+ newsgroupName);
+#ifdef DEBUG_jungshik
+ NS_ASSERTION(NS_SUCCEEDED(rv), "newsgroup name conversion failed");
+#endif
+ if (NS_FAILED(rv)) {
+ CopyASCIItoUTF16(nsDependentCString(aName), newsgroupName);
+ }
+
+ rv = AddTo(NS_ConvertUTF16toUTF8(newsgroupName),
+ false, true, true);
+ if (NS_FAILED(rv)) return rv;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetIncomingServer(nsIMsgIncomingServer *aServer)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetIncomingServer(aServer);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetShowFullName(bool showFullName)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetShowFullName(showFullName);
+}
+
+nsresult
+nsNntpIncomingServer::ClearInner()
+{
+ nsresult rv = NS_OK;
+
+ if (mInner) {
+ rv = mInner->SetSubscribeListener(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mInner->SetIncomingServer(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ mInner = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsNntpIncomingServer::EnsureInner()
+{
+ nsresult rv = NS_OK;
+
+ if (mInner)
+ return NS_OK;
+
+ mInner = do_CreateInstance(kSubscribableServerCID,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!mInner)
+ return NS_ERROR_FAILURE;
+
+ rv = SetIncomingServer(this);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetDelimiter(char *aDelimiter)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetDelimiter(aDelimiter);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetDelimiter(char aDelimiter)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetDelimiter(aDelimiter);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetAsSubscribed(const nsACString &path)
+{
+ mTempSubscribed.AppendElement(path);
+ if (mGetOnlyNew && (!mGroupsOnServer.Contains(path)))
+ return NS_OK;
+
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetAsSubscribed(path);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::UpdateSubscribed()
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ mTempSubscribed.Clear();
+ uint32_t length = mSubscribedNewsgroups.Length();
+ for (uint32_t i = 0; i < length; ++i)
+ SetAsSubscribed(mSubscribedNewsgroups[i]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddTo(const nsACString &aName, bool addAsSubscribed,
+ bool aSubscribable, bool changeIfExists)
+{
+ NS_ASSERTION(MsgIsUTF8(aName), "Non-UTF-8 newsgroup name");
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = AddGroupOnServer(aName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mInner->AddTo(aName, addAsSubscribed, aSubscribable, changeIfExists);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::StopPopulating(nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsISubscribeListener> listener;
+ rv = GetSubscribeListener(getter_AddRefs(listener));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!listener)
+ return NS_ERROR_FAILURE;
+
+ rv = listener->OnDonePopulating();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = mInner->StopPopulating(aMsgWindow);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!mGetOnlyNew && !mHostInfoLoaded)
+ {
+ rv = WriteHostInfoFile();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // XXX TODO: when do I set this to null?
+ // rv = ClearInner();
+ // NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetSubscribeListener(nsISubscribeListener *aListener)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->SetSubscribeListener(aListener);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSubscribeListener(nsISubscribeListener **aListener)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetSubscribeListener(aListener);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::Subscribe(const char16_t *aUnicharName)
+{
+ return SubscribeToNewsgroup(NS_ConvertUTF16toUTF8(aUnicharName));
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::Unsubscribe(const char16_t *aUnicharName)
+{
+ NS_ENSURE_ARG_POINTER(aUnicharName);
+
+ nsresult rv;
+
+ nsCOMPtr <nsIMsgFolder> serverFolder;
+ rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!serverFolder)
+ return NS_ERROR_FAILURE;
+
+ // to handle non-ASCII newsgroup names, we store them internally as escaped.
+ // so we need to escape and encode the name, in order to find it.
+ nsAutoCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(nsDependentString(aUnicharName), escapedName);
+
+ nsCOMPtr <nsIMsgFolder> newsgroupFolder;
+ rv = serverFolder->FindSubFolder(escapedName,
+ getter_AddRefs(newsgroupFolder));
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!newsgroupFolder)
+ return NS_ERROR_FAILURE;
+
+ rv = serverFolder->PropagateDelete(newsgroupFolder, true /* delete storage */, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // since we've unsubscribed to a newsgroup, the newsrc needs to be written out
+ rv = SetNewsrcHasChanged(true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return NS_OK;
+}
+
+nsresult
+nsNntpIncomingServer::HandleLine(const char* line, uint32_t line_size)
+{
+ NS_ASSERTION(line, "line is null");
+ if (!line)
+ return NS_OK;
+
+ // skip blank lines and comments
+ if (line[0] == '#' || line[0] == '\0')
+ return NS_OK;
+ // XXX TODO: make this truly const, maybe pass in an nsCString &
+
+ if (mHasSeenBeginGroups) {
+ // v1 hostinfo files had additional data fields delimited by commas.
+ // with v2 hostinfo files, the additional data fields are removed.
+ char *commaPos = (char *) PL_strchr(line,',');
+ if (commaPos) *commaPos = 0;
+
+ // newsrc entries are all in UTF-8
+#ifdef DEBUG_jungshik
+ NS_ASSERTION(MsgIsUTF8(nsDependentCString(line)), "newsrc line is not utf-8");
+#endif
+ nsresult rv = AddTo(nsDependentCString(line), false, true, true);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to add line");
+ if (NS_SUCCEEDED(rv)) {
+ // since we've seen one group, we can claim we've loaded the
+ // hostinfo file
+ mHostInfoLoaded = true;
+ }
+ }
+ else {
+ if (PL_strncmp(line,"begingroups", 11) == 0) {
+ mHasSeenBeginGroups = true;
+ }
+ char*equalPos = (char *) PL_strchr(line, '=');
+ if (equalPos) {
+ *equalPos++ = '\0';
+ if (PL_strcmp(line, "lastgroupdate") == 0) {
+ mLastUpdatedTime = strtoul(equalPos, nullptr, 10);
+ } else if (PL_strcmp(line, "uniqueid") == 0) {
+ mUniqueId = strtol(equalPos, nullptr, 16);
+ } else if (PL_strcmp(line, "version") == 0) {
+ mVersion = strtol(equalPos, nullptr, 16);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNntpIncomingServer::AddGroupOnServer(const nsACString &aName)
+{
+ mGroupsOnServer.AppendElement(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddNewsgroup(const nsAString &aName)
+{
+ // handle duplicates?
+ mSubscribedNewsgroups.AppendElement(NS_ConvertUTF16toUTF8(aName));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::RemoveNewsgroup(const nsAString &aName)
+{
+ // handle duplicates?
+ mSubscribedNewsgroups.RemoveElement(NS_ConvertUTF16toUTF8(aName));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetState(const nsACString &path, bool state,
+ bool *stateChanged)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = mInner->SetState(path, state, stateChanged);
+ if (*stateChanged) {
+ if (state)
+ mTempSubscribed.AppendElement(path);
+ else
+ mTempSubscribed.RemoveElement(path);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::HasChildren(const nsACString &path, bool *aHasChildren)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->HasChildren(path, aHasChildren);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsSubscribed(const nsACString &path,
+ bool *aIsSubscribed)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->IsSubscribed(path, aIsSubscribed);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsSubscribable(const nsACString &path,
+ bool *aIsSubscribable)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->IsSubscribable(path, aIsSubscribable);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetLeafName(const nsACString &path, nsAString &aLeafName)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetLeafName(path, aLeafName);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetFirstChildURI(const nsACString &path, nsACString &aResult)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetFirstChildURI(path, aResult);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetChildren(const nsACString &aPath,
+ nsISimpleEnumerator **aResult)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetChildren(aPath, aResult);
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::CommitSubscribeChanges()
+{
+ // we force the newrc to be dirty, so it will get written out when
+ // we call WriteNewsrcFile()
+ nsresult rv = SetNewsrcHasChanged(true);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return WriteNewsrcFile();
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::ForgetPassword()
+{
+ // clear password of root folder (for the news account)
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!rootFolder) return NS_ERROR_FAILURE;
+
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(rootFolder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!newsFolder) return NS_ERROR_FAILURE;
+
+ rv = newsFolder->ForgetAuthenticationCredentials();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // clear password of all child folders
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+
+ rv = rootFolder->GetSubFolders(getter_AddRefs(subFolders));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool moreFolders = false;
+
+ nsresult return_rv = NS_OK;
+
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) &&
+ moreFolders) {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ newsFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && newsFolder) {
+ rv = newsFolder->ForgetAuthenticationCredentials();
+ if (NS_FAILED(rv))
+ return_rv = rv;
+ }
+ else {
+ return_rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ return return_rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSupportsExtensions(bool *aSupportsExtensions)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetSupportsExtensions(bool aSupportsExtensions)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddExtension(const char *extension)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::QueryExtension(const char *extension, bool *result)
+{
+#ifdef DEBUG_seth
+ printf("no extension support yet\n");
+#endif
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetPostingAllowed(bool *aPostingAllowed)
+{
+ *aPostingAllowed = mPostingAllowed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetPostingAllowed(bool aPostingAllowed)
+{
+ mPostingAllowed = aPostingAllowed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetLastUpdatedTime(uint32_t *aLastUpdatedTime)
+{
+ *aLastUpdatedTime = mLastUpdatedTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetLastUpdatedTime(uint32_t aLastUpdatedTime)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddPropertyForGet(const char *name, const char *value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::QueryPropertyForGet(const char *name, char **value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddSearchableGroup(const nsAString &name)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::QuerySearchableGroup(const nsAString &name, bool *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::AddSearchableHeader(const char *name)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::QuerySearchableHeader(const char *name, bool *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::FindGroup(const nsACString &name, nsIMsgNewsFolder **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> serverFolder;
+ rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!serverFolder) return NS_ERROR_FAILURE;
+
+ // Escape the name for using FindSubFolder
+ nsAutoCString escapedName;
+ rv = MsgEscapeString(name, nsINetUtil::ESCAPE_URL_PATH, escapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgFolder> subFolder;
+ rv = serverFolder->FindSubFolder(escapedName, getter_AddRefs(subFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!subFolder) return NS_ERROR_FAILURE;
+
+ rv = subFolder->QueryInterface(NS_GET_IID(nsIMsgNewsFolder), (void**)result);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!*result) return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetFirstGroupNeedingExtraInfo(nsACString &result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetGroupNeedsExtraInfo(const nsACString &name,
+ bool needsExtraInfo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GroupNotFound(nsIMsgWindow *aMsgWindow,
+ const nsAString &aName, bool aOpening)
+{
+ nsresult rv;
+ nsCOMPtr <nsIPrompt> prompt;
+
+ if (aMsgWindow) {
+ rv = aMsgWindow->GetPromptDialog(getter_AddRefs(prompt));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "no prompt from the msg window");
+ }
+
+ if (!prompt) {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ rv = wwatch->GetNewPrompter(nullptr, getter_AddRefs(prompt));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ nsCOMPtr <nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr <nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString hostname;
+ rv = GetRealHostName(hostname);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ConvertUTF8toUTF16 hostStr(hostname);
+
+ nsString groupName(aName);
+ const char16_t *formatStrings[2] = { groupName.get(), hostStr.get() };
+ nsString confirmText;
+ rv = bundle->FormatStringFromName(
+ u"autoUnsubscribeText",
+ formatStrings, 2,
+ getter_Copies(confirmText));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ bool confirmResult = false;
+ rv = prompt->Confirm(nullptr, confirmText.get(), &confirmResult);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (confirmResult) {
+ rv = Unsubscribe(groupName.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetPrettyNameForGroup(const nsAString &name,
+ const nsAString &prettyName)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCanSearchMessages(bool *canSearchMessages)
+{
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ *canSearchMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetOfflineSupportLevel(int32_t *aSupportLevel)
+{
+ NS_ENSURE_ARG_POINTER(aSupportLevel);
+ nsresult rv;
+
+ rv = GetIntValue("offline_support_level", aSupportLevel);
+ if (*aSupportLevel != OFFLINE_SUPPORT_LEVEL_UNDEFINED) return rv;
+
+ // set default value
+ *aSupportLevel = OFFLINE_SUPPORT_LEVEL_EXTENDED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetDefaultCopiesAndFoldersPrefsToServer(bool *aCopiesAndFoldersOnServer)
+{
+ NS_ENSURE_ARG_POINTER(aCopiesAndFoldersOnServer);
+
+ /**
+ * When a news account is created, the copies and folder prefs for the
+ * associated identity don't point to folders on the server.
+ * This makes sense, since there is no "Drafts" folder on a news server.
+ * They'll point to the ones on "Local Folders"
+ */
+
+ *aCopiesAndFoldersOnServer = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer)
+{
+ NS_ENSURE_ARG_POINTER(aCanCreateFoldersOnServer);
+
+ // No folder creation on news servers. Return false.
+ *aCanCreateFoldersOnServer = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetSearchValue(const nsAString &aSearchValue)
+{
+ nsCString searchValue = NS_ConvertUTF16toUTF8(aSearchValue);
+ MsgCompressWhitespace(searchValue);
+
+ if (mTree) {
+ mTree->BeginUpdateBatch();
+ mTree->RowCountChanged(0, -static_cast<int32_t>(mSubscribeSearchResult.Length()));
+ }
+
+ nsTArray<nsCString> searchStringParts;
+ if (!searchValue.IsEmpty())
+ ParseString(searchValue, ' ', searchStringParts);
+
+ mSubscribeSearchResult.Clear();
+ uint32_t length = mGroupsOnServer.Length();
+ for (uint32_t i = 0; i < length; i++)
+ {
+ // check that all parts of the search string occur
+ bool found = true;
+ for (uint32_t j = 0; j < searchStringParts.Length(); ++j) {
+ if (MsgFind(mGroupsOnServer[i], searchStringParts[j], true, 0) == kNotFound) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found)
+ mSubscribeSearchResult.AppendElement(mGroupsOnServer[i]);
+ }
+
+ nsCStringLowerCaseComparator comparator;
+ mSubscribeSearchResult.Sort(comparator);
+
+ if (mTree)
+ {
+ mTree->RowCountChanged(0, mSubscribeSearchResult.Length());
+ mTree->EndUpdateBatch();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSupportsSubscribeSearch(bool *retVal)
+{
+ *retVal = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetRowCount(int32_t *aRowCount)
+{
+ *aRowCount = mSubscribeSearchResult.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSelection(nsITreeSelection * *aSelection)
+{
+ *aSelection = mTreeSelection;
+ NS_IF_ADDREF(*aSelection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetSelection(nsITreeSelection * aSelection)
+{
+ mTreeSelection = aSelection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetRowProperties(int32_t index, nsAString& properties)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCellProperties(int32_t row, nsITreeColumn* col, nsAString& properties)
+{
+ if (!IsValidRow(row))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ENSURE_ARG_POINTER(col);
+
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+ if (colID[0] == 's') {
+ // if <name> is in our temporary list of subscribed groups
+ // add the "subscribed" property so the check mark shows up
+ // in the "subscribedCol"
+ if (mSearchResultSortDescending)
+ row = mSubscribeSearchResult.Length() - 1 - row;
+ if (mTempSubscribed.Contains(mSubscribeSearchResult.ElementAt(row))) {
+ properties.AssignLiteral("subscribed");
+ }
+ }
+ else if (colID[0] == 'n') {
+ // add the "nntp" property to the "nameCol"
+ // so we get the news folder icon in the search view
+ properties.AssignLiteral("nntp");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetColumnProperties(nsITreeColumn* col, nsAString& properties)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsContainer(int32_t index, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsContainerOpen(int32_t index, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsContainerEmpty(int32_t index, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsSeparator(int32_t index, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsSorted(bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::CanDrop(int32_t index,
+ int32_t orientation,
+ nsIDOMDataTransfer *dataTransfer,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::Drop(int32_t row,
+ int32_t orientation,
+ nsIDOMDataTransfer *dataTransfer)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetParentIndex(int32_t rowIndex, int32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetLevel(int32_t index, int32_t *_retval)
+{
+ *_retval = 0;
+ return NS_OK;
+}
+
+bool
+nsNntpIncomingServer::IsValidRow(int32_t row)
+{
+ return ((row >= 0) && (row < (int32_t)mSubscribeSearchResult.Length()));
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ if (!IsValidRow(row))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ENSURE_ARG_POINTER(col);
+
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+
+ nsresult rv = NS_OK;
+ if (colID[0] == 'n') {
+ nsAutoCString str;
+ if (mSearchResultSortDescending)
+ row = mSubscribeSearchResult.Length() - 1 - row;
+ // some servers have newsgroup names that are non ASCII. we store
+ // those as escaped. unescape here so the UI is consistent
+ rv = NS_MsgDecodeUnescapeURLPath(mSubscribeSearchResult.ElementAt(row), _retval);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetTree(nsITreeBoxObject *tree)
+{
+ mTree = tree;
+ if (!tree)
+ return NS_OK;
+
+ nsCOMPtr<nsITreeColumns> cols;
+ tree->GetColumns(getter_AddRefs(cols));
+ if (!cols)
+ return NS_OK;
+
+ nsCOMPtr<nsITreeColumn> col;
+ cols->GetKeyColumn(getter_AddRefs(col));
+ if (!col)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMElement> element;
+ col->GetElement(getter_AddRefs(element));
+ if (!element)
+ return NS_OK;
+
+ nsAutoString dir;
+ element->GetAttribute(NS_LITERAL_STRING("sortDirection"), dir);
+ mSearchResultSortDescending = dir.EqualsLiteral("descending");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::ToggleOpenState(int32_t index)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::CycleHeader(nsITreeColumn* col)
+{
+ NS_ENSURE_ARG_POINTER(col);
+
+ bool cycler;
+ col->GetCycler(&cycler);
+ if (!cycler) {
+ NS_NAMED_LITERAL_STRING(dir, "sortDirection");
+ nsCOMPtr<nsIDOMElement> element;
+ col->GetElement(getter_AddRefs(element));
+ mSearchResultSortDescending = !mSearchResultSortDescending;
+ element->SetAttribute(dir, mSearchResultSortDescending ?
+ NS_LITERAL_STRING("descending") : NS_LITERAL_STRING("ascending"));
+ mTree->Invalidate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SelectionChanged()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::CycleCell(int32_t row, nsITreeColumn* col)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsEditable(int32_t row, nsITreeColumn* col, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::IsSelectable(int32_t row, nsITreeColumn* col, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PerformAction(const char16_t *action)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PerformActionOnRow(const char16_t *action, int32_t row)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PerformActionOnCell(const char16_t *action, int32_t row, nsITreeColumn* col)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer)
+{
+ NS_ENSURE_ARG_POINTER(aCanFileMessagesOnServer);
+
+ // No folder creation on news servers. Return false.
+ *aCanFileMessagesOnServer = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetFilterScope(nsMsgSearchScopeValue *filterScope)
+{
+ NS_ENSURE_ARG_POINTER(filterScope);
+
+ *filterScope = nsMsgSearchScope::newsFilter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSearchScope(nsMsgSearchScopeValue *searchScope)
+{
+ NS_ENSURE_ARG_POINTER(searchScope);
+
+ if (WeAreOffline()) {
+ // This value is set to the localNewsBody scope to be compatible with
+ // the legacy default value.
+ *searchScope = nsMsgSearchScope::localNewsBody;
+ }
+ else {
+ *searchScope = nsMsgSearchScope::news;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSocketType(int32_t *aSocketType)
+{
+ NS_ENSURE_ARG_POINTER(aSocketType);
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mPrefBranch->GetIntPref("socketType", aSocketType);
+ if (NS_FAILED(rv))
+ {
+ if (!mDefPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+ rv = mDefPrefBranch->GetIntPref("socketType", aSocketType);
+ if (NS_FAILED(rv))
+ *aSocketType = nsMsgSocketType::plain;
+ }
+
+ // nsMsgIncomingServer::GetSocketType migrates old isSecure to socketType
+ // style for mail. Unfortunately, a bug caused news socketType 0 to be stored
+ // in the prefs even for isSecure true, so the migration wouldn't happen :(
+
+ // Now that we know the socket, make sure isSecure true + socketType 0
+ // doesn't mix. Migrate if that's the case here.
+ if (*aSocketType == nsMsgSocketType::plain)
+ {
+ bool isSecure = false;
+ nsresult rv2 = mPrefBranch->GetBoolPref("isSecure", &isSecure);
+ if (NS_SUCCEEDED(rv2) && isSecure)
+ {
+ *aSocketType = nsMsgSocketType::SSL;
+ // Don't call virtual method in case overrides call GetSocketType.
+ nsMsgIncomingServer::SetSocketType(*aSocketType);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::SetSocketType(int32_t aSocketType)
+{
+ if (!mPrefBranch)
+ return NS_ERROR_NOT_INITIALIZED;
+ nsresult rv = nsMsgIncomingServer::SetSocketType(aSocketType);
+ if (NS_SUCCEEDED(rv))
+ {
+ bool isSecure = false;
+ if (NS_SUCCEEDED(mPrefBranch->GetBoolPref("isSecure", &isSecure)))
+ {
+ // Must keep isSecure in sync since we migrate based on it... if it's set.
+ rv = mPrefBranch->SetBoolPref("isSecure",
+ aSocketType == nsMsgSocketType::SSL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged)
+{
+ nsresult rv;
+ // 1. Do common things in the base class.
+ rv = nsMsgIncomingServer::OnUserOrHostNameChanged(oldName, newName, hostnameChanged);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // 2. Remove file hostinfo.dat so that the new subscribe
+ // list will be reloaded from the new server.
+ nsCOMPtr <nsIFile> hostInfoFile;
+ rv = GetLocalPath(getter_AddRefs(hostInfoFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = hostInfoFile->AppendNative(NS_LITERAL_CSTRING(HOSTINFO_FILE_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+ hostInfoFile->Remove(false);
+
+ // 3.Unsubscribe and then subscribe the existing groups to clean up the article numbers
+ // in the rc file (this is because the old and new servers may maintain different
+ // numbers for the same articles if both servers handle the same groups).
+ nsCOMPtr <nsIMsgFolder> serverFolder;
+ rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ rv = serverFolder->GetSubFolders(getter_AddRefs(subFolders));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsTArray<nsString> groupList;
+ nsString folderName;
+
+ // Prepare the group list
+ bool hasMore;
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ subFolders->GetNext(getter_AddRefs(item));
+ nsCOMPtr<nsIMsgFolder> newsgroupFolder(do_QueryInterface(item));
+ if (!newsgroupFolder)
+ continue;
+
+ rv = newsgroupFolder->GetName(folderName);
+ NS_ENSURE_SUCCESS(rv,rv);
+ groupList.AppendElement(folderName);
+ }
+
+ // If nothing subscribed then we're done.
+ if (groupList.Length() == 0)
+ return NS_OK;
+
+ // Now unsubscribe & subscribe.
+ uint32_t i;
+ uint32_t cnt = groupList.Length();
+ nsAutoCString cname;
+ for (i = 0; i < cnt; i++)
+ {
+ // unsubscribe.
+ rv = Unsubscribe(groupList[i].get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ for (i = 0; i < cnt; i++)
+ {
+ // subscribe.
+ rv = SubscribeToNewsgroup(NS_ConvertUTF16toUTF8(groupList[i]));
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // Force updating the rc file.
+ return CommitSubscribeChanges();
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::GetSortOrder(int32_t* aSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = 500000000;
+ return NS_OK;
+}
diff --git a/mailnews/news/src/nsNntpIncomingServer.h b/mailnews/news/src/nsNntpIncomingServer.h
new file mode 100644
index 000000000..b2196f05d
--- /dev/null
+++ b/mailnews/news/src/nsNntpIncomingServer.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsNntpIncomingServer_h
+#define __nsNntpIncomingServer_h
+
+#include "nsINntpIncomingServer.h"
+#include "nsIUrlListener.h"
+#include "nscore.h"
+
+#include "nsMsgIncomingServer.h"
+
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+
+#include "nsIMsgWindow.h"
+#include "nsISubscribableServer.h"
+#include "nsITimer.h"
+#include "nsIFile.h"
+#include "nsITreeView.h"
+#include "nsITreeSelection.h"
+#include "nsIAtom.h"
+#include "nsCOMArray.h"
+
+#include "nsNntpMockChannel.h"
+#include "nsAutoPtr.h"
+
+class nsINntpUrl;
+class nsIMsgMailNewsUrl;
+
+/* get some implementation from nsMsgIncomingServer */
+class nsNntpIncomingServer : public nsMsgIncomingServer,
+ public nsINntpIncomingServer,
+ public nsIUrlListener,
+ public nsISubscribableServer,
+ public nsITreeView
+
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINNTPINCOMINGSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSISUBSCRIBABLESERVER
+ NS_DECL_NSITREEVIEW
+
+ nsNntpIncomingServer();
+
+ NS_IMETHOD GetLocalStoreType(nsACString& type) override;
+ NS_IMETHOD GetLocalDatabaseType(nsACString& type) override;
+ NS_IMETHOD CloseCachedConnections() override;
+ NS_IMETHOD PerformBiff(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD PerformExpand(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged) override;
+
+ // for nsMsgLineBuffer
+ virtual nsresult HandleLine(const char *line, uint32_t line_size);
+
+ // override to clear all passwords associated with server
+ NS_IMETHODIMP ForgetPassword() override;
+ NS_IMETHOD GetCanSearchMessages(bool *canSearchMessages) override;
+ NS_IMETHOD GetOfflineSupportLevel(int32_t *aSupportLevel) override;
+ NS_IMETHOD GetDefaultCopiesAndFoldersPrefsToServer(bool *aCopiesAndFoldersOnServer) override;
+ NS_IMETHOD GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer) override;
+ NS_IMETHOD GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer) override;
+ NS_IMETHOD GetFilterScope(nsMsgSearchScopeValue *filterScope) override;
+ NS_IMETHOD GetSearchScope(nsMsgSearchScopeValue *searchScope) override;
+
+ NS_IMETHOD GetSocketType(int32_t *aSocketType) override; // override nsMsgIncomingServer impl
+ NS_IMETHOD SetSocketType(int32_t aSocketType) override; // override nsMsgIncomingServer impl
+ NS_IMETHOD GetSortOrder(int32_t* aSortOrder) override;
+
+protected:
+ virtual ~nsNntpIncomingServer();
+ virtual nsresult CreateRootFolderFromUri(const nsCString &serverUri,
+ nsIMsgFolder **rootFolder) override;
+ nsresult GetNntpConnection(nsIURI *url, nsIMsgWindow *window,
+ nsINNTPProtocol **aNntpConnection);
+ nsresult CreateProtocolInstance(nsINNTPProtocol **aNntpConnection,
+ nsIURI *url, nsIMsgWindow *window);
+ bool ConnectionTimeOut(nsINNTPProtocol* aNntpConnection);
+ nsCOMArray<nsINNTPProtocol> mConnectionCache;
+ nsTArray<RefPtr<nsNntpMockChannel> > m_queuedChannels;
+
+ /**
+ * Downloads the newsgroup headers.
+ */
+ nsresult DownloadMail(nsIMsgWindow *aMsgWindow);
+
+ NS_IMETHOD GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) override;
+ nsresult SetupNewsrcSaveTimer();
+ static void OnNewsrcSaveTimer(nsITimer *timer, void *voidIncomingServer);
+ void WriteLine(nsIOutputStream *stream, nsCString &str);
+
+private:
+ nsTArray<nsCString> mSubscribedNewsgroups;
+ nsTArray<nsCString> mGroupsOnServer;
+ nsTArray<nsCString> mSubscribeSearchResult;
+ bool mSearchResultSortDescending;
+ // the list of of subscribed newsgroups within a given
+ // subscribed dialog session.
+ // we need to keep track of them so we know what to show as "checked"
+ // in the search view
+ nsTArray<nsCString> mTempSubscribed;
+ nsCOMPtr<nsIAtom> mSubscribedAtom;
+ nsCOMPtr<nsIAtom> mNntpAtom;
+
+ nsCOMPtr<nsITreeBoxObject> mTree;
+ nsCOMPtr<nsITreeSelection> mTreeSelection;
+
+ bool mHasSeenBeginGroups;
+ bool mGetOnlyNew;
+ nsresult WriteHostInfoFile();
+ nsresult LoadHostInfoFile();
+ nsresult AddGroupOnServer(const nsACString &name);
+
+ bool mNewsrcHasChanged;
+ bool mHostInfoLoaded;
+ bool mHostInfoHasChanged;
+ nsCOMPtr <nsIFile> mHostInfoFile;
+
+ uint32_t mLastGroupDate;
+ int32_t mUniqueId;
+ uint32_t mLastUpdatedTime;
+ int32_t mVersion;
+ bool mPostingAllowed;
+
+ nsCOMPtr<nsITimer> mNewsrcSaveTimer;
+ nsCOMPtr <nsIMsgWindow> mMsgWindow;
+
+ nsCOMPtr <nsISubscribableServer> mInner;
+ nsresult EnsureInner();
+ nsresult ClearInner();
+ bool IsValidRow(int32_t row);
+ nsCOMPtr<nsIFile> mNewsrcFilePath;
+};
+
+#endif
diff --git a/mailnews/news/src/nsNntpMockChannel.cpp b/mailnews/news/src/nsNntpMockChannel.cpp
new file mode 100644
index 000000000..1dfd462ff
--- /dev/null
+++ b/mailnews/news/src/nsNntpMockChannel.cpp
@@ -0,0 +1,353 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNntpMockChannel.h"
+
+#include "msgCore.h"
+#include "nsILoadInfo.h"
+#include "nsNNTPProtocol.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsContentSecurityManager.h"
+
+NS_IMPL_ISUPPORTS(nsNntpMockChannel, nsIChannel, nsIRequest)
+
+nsNntpMockChannel::nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow)
+: m_url(aUri),
+ m_msgWindow(aMsgWindow),
+ m_channelState(CHANNEL_UNOPENED),
+ m_protocol(nullptr),
+ m_cancelStatus(NS_OK),
+ m_loadFlags(0),
+ m_contentLength(-1)
+{
+}
+
+nsNntpMockChannel::nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow,
+ nsISupports *aConsumer)
+: m_url(aUri),
+ m_context(aConsumer),
+ m_msgWindow(aMsgWindow),
+ m_channelState(CHANNEL_OPEN_WITH_LOAD),
+ m_protocol(nullptr),
+ m_cancelStatus(NS_OK),
+ m_loadFlags(0),
+ m_contentLength(-1)
+{
+}
+
+nsNntpMockChannel::~nsNntpMockChannel()
+{
+}
+
+#define FORWARD_CALL(function, argument) \
+ if (m_protocol) \
+ return m_protocol->function(argument);
+
+////////////////////////
+// nsIRequest methods //
+////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::GetName(nsACString &result)
+{
+ FORWARD_CALL(GetName, result)
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::IsPending(bool *result)
+{
+ FORWARD_CALL(IsPending, result)
+ // We haven't been loaded yet, so we're still pending.
+ *result = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetStatus(nsresult *status)
+{
+ FORWARD_CALL(GetStatus, status)
+ *status = m_cancelStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Cancel(nsresult status)
+{
+ m_cancelStatus = status;
+ m_channelState = CHANNEL_CLOSED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Suspend()
+{
+ NS_NOTREACHED("nsNntpMockChannel::Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Resume()
+{
+ NS_NOTREACHED("nsNntpMockChannel::Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ FORWARD_CALL(SetLoadGroup, aLoadGroup)
+ m_loadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ FORWARD_CALL(GetLoadGroup, aLoadGroup)
+ NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ FORWARD_CALL(GetLoadFlags, aLoadFlags)
+ *aLoadFlags = m_loadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ FORWARD_CALL(SetLoadFlags, aLoadFlags)
+ m_loadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ FORWARD_CALL(GetLoadInfo, aLoadInfo)
+ NS_IF_ADDREF(*aLoadInfo = m_loadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetLoadInfo(nsILoadInfo *aLoadInfo)
+{
+ FORWARD_CALL(SetLoadInfo, aLoadInfo)
+ m_loadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+////////////////////////
+// nsIChannel methods //
+////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::GetOriginalURI(nsIURI **aURI)
+{
+ FORWARD_CALL(GetOriginalURI, aURI)
+ NS_IF_ADDREF(*aURI = m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetOriginalURI(nsIURI *aURI)
+{
+ FORWARD_CALL(SetOriginalURI, aURI)
+ // News does not seem to have the notion of an original URI.
+ // (See bug 193317 and bug 1312314.)
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetURI(nsIURI **aURI)
+{
+ NS_IF_ADDREF(*aURI = m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetOwner(nsISupports **owner)
+{
+ FORWARD_CALL(GetOwner, owner)
+ NS_IF_ADDREF(*owner = m_owner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetOwner(nsISupports *aOwner)
+{
+ FORWARD_CALL(SetOwner, aOwner)
+ m_owner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::GetNotificationCallbacks(nsIInterfaceRequestor **callbacks)
+{
+ FORWARD_CALL(GetNotificationCallbacks, callbacks)
+ NS_IF_ADDREF(*callbacks = m_notificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ FORWARD_CALL(SetNotificationCallbacks, aCallbacks)
+ m_notificationCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetSecurityInfo(nsISupports **securityInfo)
+{
+ FORWARD_CALL(GetSecurityInfo, securityInfo)
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentType(nsACString &aContentType)
+{
+ FORWARD_CALL(GetContentType, aContentType)
+ aContentType = m_contentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetContentType(const nsACString &aContentType)
+{
+ FORWARD_CALL(SetContentType, aContentType)
+ return NS_ParseResponseContentType(aContentType, m_contentType, m_contentCharset);
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentCharset(nsACString &aCharset)
+{
+ FORWARD_CALL(GetContentCharset, aCharset)
+ aCharset = m_contentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetContentCharset(const nsACString &aCharset)
+{
+ FORWARD_CALL(SetContentCharset, aCharset)
+ m_contentCharset = aCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentLength(int64_t *length)
+{
+ FORWARD_CALL(GetContentLength, length)
+ *length = m_contentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetContentLength(int64_t aLength)
+{
+ FORWARD_CALL(SetContentLength, aLength)
+ m_contentLength = aLength;
+ return NS_OK;
+}
+
+////////////////////////////////////////
+// nsIChannel and nsNNTPProtocol glue //
+////////////////////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::Open(nsIInputStream **_retval)
+{
+ return NS_ImplementChannelOpen(this, _retval);
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Open2(nsIInputStream **_retval)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(_retval);
+}
+
+NS_IMETHODIMP nsNntpMockChannel::AsyncOpen(nsIStreamListener *listener,
+ nsISupports *ctxt)
+{
+ m_channelState = CHANNEL_OPEN_WITH_ASYNC;
+ m_channelListener = listener;
+ m_context = ctxt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+nsresult
+nsNntpMockChannel::AttachNNTPConnection(nsNNTPProtocol &protocol)
+{
+ // First things first. Were we canceled? If so, tell the protocol.
+ if (m_channelState == CHANNEL_CLOSED || m_channelState == CHANNEL_UNOPENED)
+ return NS_ERROR_FAILURE;
+
+
+ // We're going to active the protocol now. Note that if the user has
+ // interacted with us through the nsIChannel API, we need to pass it to the
+ // protocol instance. We also need to initialize it. For best results, we're
+ // going to initialize the code and then set the values.
+ nsresult rv = protocol.Initialize(m_url, m_msgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Variable fun
+ protocol.SetLoadGroup(m_loadGroup);
+ protocol.SetLoadFlags(m_loadFlags);
+ protocol.SetOwner(m_owner);
+ protocol.SetNotificationCallbacks(m_notificationCallbacks);
+ protocol.SetContentType(m_contentType);
+
+ // Now that we've set up the protocol, attach it to ourselves so that we can
+ // forward all future calls to the protocol instance. We do not refcount this
+ // instance, since the server will be owning all of them: once the server
+ // releases its reference, the protocol instance is no longer usable anyways.
+ m_protocol = &protocol;
+
+ switch (m_channelState)
+ {
+ case CHANNEL_OPEN_WITH_LOAD:
+ rv = protocol.LoadNewsUrl(m_url, m_context);
+ break;
+ case CHANNEL_OPEN_WITH_ASYNC:
+ rv = protocol.AsyncOpen(m_channelListener, m_context);
+ break;
+ default:
+ NS_NOTREACHED("Unknown channel state got us here.");
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we fail, that means that loading the NNTP protocol failed. Since we
+ // essentially promised that we would load (by virtue of returning NS_OK to
+ // AsyncOpen), we must now tell our listener the bad news.
+ if (NS_FAILED(rv) && m_channelListener)
+ m_channelListener->OnStopRequest(this, m_context, rv);
+
+ // Returning a failure code is our way of telling the server that this URL
+ // isn't going to run, so it should give the connection the next URL in the
+ // queue.
+ return rv;
+}
diff --git a/mailnews/news/src/nsNntpMockChannel.h b/mailnews/news/src/nsNntpMockChannel.h
new file mode 100644
index 000000000..dc20185ec
--- /dev/null
+++ b/mailnews/news/src/nsNntpMockChannel.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNntpMockChannel_h___
+#define nsNntpMockChannel_h___
+
+#include "nsIChannel.h"
+#include "nsIMsgWindow.h"
+
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+
+class nsNNTPProtocol;
+
+class nsNntpMockChannel : public nsIChannel
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUEST
+
+ nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow);
+ nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow,
+ nsISupports *aConsumer);
+
+ nsresult AttachNNTPConnection(nsNNTPProtocol &protocol);
+protected:
+ virtual ~nsNntpMockChannel();
+
+ // The URL we will be running
+ nsCOMPtr<nsIURI> m_url;
+
+ // Variables for arguments to pass into the opening phase.
+ nsCOMPtr<nsIStreamListener> m_channelListener;
+ nsCOMPtr<nsISupports> m_context;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+
+ // The state we're in
+ enum
+ {
+ CHANNEL_UNOPENED, //!< No one bothered to open this yet
+ CHANNEL_OPEN_WITH_LOAD, //!< We should open with LoadNewsUrl
+ CHANNEL_OPEN_WITH_ASYNC, //!< We should open with AsyncOpen
+ CHANNEL_CLOSED //!< We were closed and should not open
+ } m_channelState;
+
+ // The protocol instance
+ nsNNTPProtocol *m_protocol;
+
+ // Temporary variables for accessors before we get to the actual instance.
+ nsresult m_cancelStatus;
+ nsCOMPtr<nsILoadGroup> m_loadGroup;
+ nsCOMPtr<nsILoadInfo> m_loadInfo;
+ nsLoadFlags m_loadFlags;
+
+ nsCOMPtr<nsISupports> m_owner;
+ nsCOMPtr<nsIInterfaceRequestor> m_notificationCallbacks;
+ nsCString m_contentType;
+ nsCString m_contentCharset;
+ int64_t m_contentLength;
+};
+
+#endif // nsNntpMockChannel_h___
diff --git a/mailnews/news/src/nsNntpService.cpp b/mailnews/news/src/nsNntpService.cpp
new file mode 100644
index 000000000..8cb3cb2ec
--- /dev/null
+++ b/mailnews/news/src/nsNntpService.cpp
@@ -0,0 +1,1751 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+#include "nntpCore.h"
+#include "nsMsgNewsCID.h"
+#include "nsINntpUrl.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsNNTPNewsgroupPost.h"
+#include "nsIMsgIdentity.h"
+#include "nsStringGlue.h"
+#include "nsNewsUtils.h"
+#include "nsNewsDatabase.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgBaseCID.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsNntpService.h"
+#include "nsIChannel.h"
+#include "nsILoadGroup.h"
+#include "nsCOMPtr.h"
+#include "nsIDirectoryService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMessengerMigrator.h"
+#include "nsINntpIncomingServer.h"
+#include "nsICategoryManager.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIMessengerWindowService.h"
+#include "nsIWindowMediator.h"
+#include "mozIDOMWindow.h"
+#include "nsIMsgSearchSession.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsIWebNavigation.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsIPrompt.h"
+#include "nsNewsDownloader.h"
+#include "prprf.h"
+#include "nsICacheStorage.h"
+#include "nsICacheStorageService.h"
+#include "nsILoadContextInfo.h"
+#include "nsICacheEntry.h"
+#include "nsMsgUtils.h"
+#include "nsNetUtil.h"
+#include "nsIWindowWatcher.h"
+#include "nsICommandLine.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgMailSession.h"
+#include "nsISupportsPrimitives.h"
+#include "nsArrayUtils.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "../../base/src/MailnewsLoadContextInfo.h"
+
+#undef GetPort // XXX Windows!
+#undef SetPort // XXX Windows!
+
+#define PREF_MAIL_ROOT_NNTP "mail.root.nntp" // old - for backward compatibility only
+#define PREF_MAIL_ROOT_NNTP_REL "mail.root.nntp-rel"
+
+nsNntpService::nsNntpService()
+{
+ mPrintingOperation = false;
+ mOpenAttachmentOperation = false;
+}
+
+nsNntpService::~nsNntpService()
+{
+ // do nothing
+}
+
+NS_IMPL_ISUPPORTS(nsNntpService, nsINntpService, nsIMsgMessageService,
+ nsIProtocolHandler, nsIMsgProtocolInfo, nsICommandLineHandler,
+ nsIMsgMessageFetchPartService, nsIContentHandler)
+
+////////////////////////////////////////////////////////////////////////////////////////
+// nsIMsgMessageService support
+////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsNntpService::SaveMessageToDisk(const char *aMessageURI,
+ nsIFile *aFile,
+ bool aAddDummyEnvelope,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ bool canonicalLineEnding,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+
+ // double check it is a news-message:/ uri
+ if (PL_strncmp(aMessageURI, kNewsMessageRootURI, kNewsMessageRootURILen))
+ {
+ rv = NS_ERROR_UNEXPECTED;
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsMsgKey key = nsMsgKey_None;
+ rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageIdURL;
+ rv = CreateMessageIDURL(folder, key, getter_Copies(messageIdURL));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(messageIdURL.get(), aUrlListener, aMsgWindow, aMessageURI, nsINntpUrl::ActionSaveMessageToDisk, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(url);
+ if (msgUrl) {
+// msgUrl->SetMessageFile(aFile);
+ msgUrl->SetAddDummyEnvelope(aAddDummyEnvelope);
+ msgUrl->SetCanonicalLineEnding(canonicalLineEnding);
+ }
+
+ bool hasMsgOffline = false;
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(url);
+ if (folder)
+ {
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(folder);
+ if (newsFolder)
+ {
+ if (mailNewsUrl)
+ {
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ mailNewsUrl->SetMsgIsInLocalCache(hasMsgOffline);
+ }
+ }
+ }
+
+ if (mailNewsUrl)
+ {
+ nsCOMPtr <nsIStreamListener> saveAsListener;
+ mailNewsUrl->GetSaveAsListener(aAddDummyEnvelope, aFile, getter_AddRefs(saveAsListener));
+
+ rv = DisplayMessage(aMessageURI, saveAsListener, /* nsIMsgWindow *aMsgWindow */nullptr, aUrlListener, nullptr /*aCharsetOverride */, aURL);
+ }
+ return rv;
+}
+
+
+nsresult
+nsNntpService::CreateMessageIDURL(nsIMsgFolder *folder, nsMsgKey key, char **url)
+{
+ NS_ENSURE_ARG_POINTER(folder);
+ NS_ENSURE_ARG_POINTER(url);
+ if (key == nsMsgKey_None) return NS_ERROR_INVALID_ARG;
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageID;
+ rv = newsFolder->GetMessageIdForKey(key, messageID);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // we need to escape the message ID,
+ // it might contain characters which will mess us up later, like #
+ // see bug #120502
+ nsCString escapedMessageID;
+ MsgEscapeString(messageID, nsINetUtil::ESCAPE_URL_PATH, escapedMessageID);
+
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ rv = folder->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString rootFolderURI;
+ rv = rootFolder->GetURI(rootFolderURI);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsString groupName;
+ rv = folder->GetName(groupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString uri;
+ uri = rootFolderURI.get();
+ uri += '/';
+ uri += escapedMessageID;
+ uri += kNewsURIGroupQuery; // ?group=
+ AppendUTF16toUTF8(groupName, uri);
+ uri += kNewsURIKeyQuery; // &key=
+ uri.AppendInt(key);
+ *url = ToNewCString(uri);
+
+ if (!*url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::DisplayMessage(const char* aMessageURI, nsISupports * aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow, nsIUrlListener * aUrlListener, const char * aCharsetOverride, nsIURI ** aURL)
+{
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsMsgKey key = nsMsgKey_None;
+ rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString urlStr;
+ // if we are displaying (or printing), we want the news://host/message-id url
+ // we keep the original uri around, for cancelling and so we can get to the
+ // articles by doing GROUP and then ARTICLE <n>.
+ //
+ // using news://host/message-id has an extra benefit.
+ // we'll use that to look up in the cache, so if
+ // you are reading a message that you've already read, you
+ // (from a cross post) it would be in your cache.
+ rv = CreateMessageIDURL(folder, key, getter_Copies(urlStr));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // rhp: If we are displaying this message for the purposes of printing, append
+ // the magic operand.
+ if (mPrintingOperation)
+ urlStr.Append("?header=print");
+
+ nsNewsAction action = nsINntpUrl::ActionFetchArticle;
+ if (mOpenAttachmentOperation)
+ action = nsINntpUrl::ActionFetchPart;
+
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(urlStr.get(), aUrlListener, aMsgWindow, aMessageURI, action, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(url,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgI18NUrl> i18nurl = do_QueryInterface(msgUrl,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ i18nurl->SetCharsetOverRide(aCharsetOverride);
+
+ bool shouldStoreMsgOffline = false;
+
+ if (folder)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // We need to set the port on the url, just like
+ // nsNNTPProtocol::Initialize does, so the specs will be the same.
+ // we can ignore errors here - worst case, we'll display the
+ // "message not available" message.
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port = 0;
+ rv = url->GetPort(&port);
+ if (NS_FAILED(rv) || (port <= 0))
+ {
+ rv = server->GetPort(&port);
+ if (NS_FAILED(rv) || (port <= 0))
+ {
+ int32_t socketType;
+ rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ port = (socketType == nsMsgSocketType::SSL) ?
+ nsINntpUrl::DEFAULT_NNTPS_PORT : nsINntpUrl::DEFAULT_NNTP_PORT;
+ }
+
+ rv = url->SetPort(port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ folder->ShouldStoreMsgOffline(key, &shouldStoreMsgOffline);
+
+ // Look for the message in the offline cache
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+
+ // Now look in the memory cache
+ if (!hasMsgOffline)
+ {
+ rv = IsMsgInMemCache(url, folder, &hasMsgOffline);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If the message is not found in either, then we might need to return
+ if (!hasMsgOffline && WeAreOffline())
+ return server->DisplayOfflineMsg(aMsgWindow);
+
+ msgUrl->SetMsgIsInLocalCache(hasMsgOffline);
+
+ nsCOMPtr<nsIMsgNewsFolder> newsFolder(do_QueryInterface(folder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ newsFolder->SetSaveArticleOffline(shouldStoreMsgOffline);
+ }
+
+ if (aURL)
+ NS_IF_ADDREF(*aURL = url);
+
+ return GetMessageFromUrl(url, aMsgWindow, aDisplayConsumer);
+}
+
+nsresult nsNntpService::GetMessageFromUrl(nsIURI *aUrl,
+ nsIMsgWindow *aMsgWindow,
+ nsISupports *aDisplayConsumer)
+{
+ nsresult rv;
+ // if the consumer is the docshell then we want to run the url in the webshell
+ // in order to display it. If it isn't a docshell then just run the news url
+ // like we would any other news url.
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ // DIRTY LITTLE HACK --> if we are opening an attachment we want the docshell to
+ // treat this load as if it were a user click event. Then the dispatching stuff will be much
+ // happier.
+ if (mOpenAttachmentOperation)
+ {
+ docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadLink);
+ }
+
+ rv = docShell->LoadURI(aUrl, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false);
+ }
+ else
+ {
+ nsCOMPtr<nsIStreamListener> aStreamListener(do_QueryInterface(aDisplayConsumer, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIChannel> aChannel;
+ nsCOMPtr<nsILoadGroup> aLoadGroup;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aUrl, &rv);
+ if (NS_SUCCEEDED(rv) && mailnewsUrl)
+ {
+ if (aMsgWindow)
+ mailnewsUrl->SetMsgWindow(aMsgWindow);
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(aLoadGroup));
+ }
+ rv = NewChannel(aUrl, getter_AddRefs(aChannel));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aChannel->SetLoadGroup(aLoadGroup);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISupports> aCtxt = do_QueryInterface(aUrl);
+ // now try to open the channel passing in our display consumer as the listener
+ rv = aChannel->AsyncOpen(aStreamListener, aCtxt);
+ }
+ else
+ rv = RunNewsUrl(aUrl, aMsgWindow, aDisplayConsumer);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::FetchMessage(nsIMsgFolder *folder, nsMsgKey key, nsIMsgWindow *aMsgWindow, nsISupports * aConsumer, nsIUrlListener * aUrlListener, nsIURI ** aURL)
+{
+ NS_ENSURE_ARG_POINTER(folder);
+ nsresult rv;
+ nsCOMPtr<nsIMsgNewsFolder> msgNewsFolder = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgDBHdr> hdr;
+ rv = folder->GetMessageHeader(key, getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString originalMessageUri;
+ rv = folder->GetUriForMsg(hdr, originalMessageUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageIdURL;
+ rv = CreateMessageIDURL(folder, key, getter_Copies(messageIdURL));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(messageIdURL.get(), aUrlListener, aMsgWindow, originalMessageUri.get(),
+ nsINntpUrl::ActionFetchArticle, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = RunNewsUrl(url, aMsgWindow, aConsumer);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aURL)
+ url.swap(*aURL);
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpService::FetchMimePart(nsIURI *aURI, const char *aMessageURI, nsISupports *aDisplayConsumer, nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener, nsIURI **aURL)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(aURI, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgUrl->SetMsgWindow(aMsgWindow);
+
+ // set up the url listener
+ if (aUrlListener)
+ msgUrl->RegisterListener(aUrlListener);
+
+// this code isn't ready yet, but it helps getting opening attachments
+// while offline working
+// nsCOMPtr<nsIMsgMessageUrl> msgMessageUrl = do_QueryInterface(aURI);
+// if (msgMessageUrl)
+// {
+// nsAutoCString spec;
+// rv = aURI->GetSpec(spec);
+// NS_ENSURE_SUCCESS(rv, rv);
+// msgMessageUrl->SetOriginalSpec(spec.get());
+// }
+ return RunNewsUrl(msgUrl, aMsgWindow, aDisplayConsumer);
+}
+
+NS_IMETHODIMP nsNntpService::OpenAttachment(const char *aContentType,
+ const char *aFileName,
+ const char *aUrl,
+ const char *aMessageUri,
+ nsISupports *aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ NS_ENSURE_ARG_POINTER(aFileName);
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = NS_OK;
+ nsAutoCString newsUrl;
+ newsUrl = aUrl;
+ newsUrl += "&type=";
+ newsUrl += aContentType;
+ newsUrl += "&filename=";
+ newsUrl += aFileName;
+
+ NewURI(newsUrl, nullptr, nullptr, getter_AddRefs(url));
+
+ if (NS_SUCCEEDED(rv) && url)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(url, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgUrl->SetMsgWindow(aMsgWindow);
+ msgUrl->SetFileName(nsDependentCString(aFileName));
+// this code isn't ready yet, but it helps getting opening attachments
+// while offline working
+// nsCOMPtr<nsIMsgMessageUrl> msgMessageUrl = do_QueryInterface(url);
+// if (msgMessageUrl)
+// msgMessageUrl->SetOriginalSpec(newsUrl.get());
+ // set up the url listener
+ if (aUrlListener)
+ msgUrl->RegisterListener(aUrlListener);
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ if (NS_SUCCEEDED(rv) && docShell)
+ {
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadLink);
+ return docShell->LoadURI(url, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false);
+ }
+ else
+ return RunNewsUrl(url, aMsgWindow, aDisplayConsumer);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::GetUrlForUri(const char *aMessageURI, nsIURI **aURL, nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+
+ // double check that it is a news-message:/ uri
+ if (PL_strncmp(aMessageURI, kNewsMessageRootURI, kNewsMessageRootURILen))
+ {
+ rv = NS_ERROR_UNEXPECTED;
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsMsgKey key = nsMsgKey_None;
+ rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString messageIdURL;
+ rv = CreateMessageIDURL(folder, key, getter_Copies(messageIdURL));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // this is only called by view message source
+ rv = ConstructNntpUrl(messageIdURL.get(), nullptr, aMsgWindow, aMessageURI, nsINntpUrl::ActionFetchArticle, aURL);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (folder && *aURL)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(*aURL);
+ if (mailnewsUrl)
+ {
+ bool useLocalCache = false;
+ folder->HasMsgOffline(key, &useLocalCache);
+ mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+ }
+ }
+ return rv;
+
+}
+
+NS_IMETHODIMP
+nsNntpService::DecomposeNewsURI(const char *uri, nsIMsgFolder **folder, nsMsgKey *aMsgKey)
+{
+ nsresult rv;
+
+ rv = DecomposeNewsMessageURI(uri, folder, aMsgKey);
+
+ return rv;
+}
+
+nsresult
+nsNntpService::DecomposeNewsMessageURI(const char * aMessageURI, nsIMsgFolder ** aFolder, nsMsgKey *aMsgKey)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aMsgKey);
+
+ nsresult rv = NS_OK;
+
+ // Construct the news URL
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_CreateInstance(NS_NNTPURL_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsINntpUrl> nntpUrl = do_QueryInterface(mailnewsurl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mailnewsurl->SetSpec(nsDependentCString(aMessageURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the group name and key from the url
+ nsAutoCString groupName;
+ rv = nntpUrl->GetGroup(groupName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nntpUrl->GetKey(aMsgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there is no group, try the harder way.
+ if (groupName.IsEmpty())
+ {
+ *aMsgKey = nsMsgKey_None;
+ return GetFolderFromUri(aMessageURI, aFolder);
+ }
+
+ return mailnewsurl->GetFolder(aFolder);
+}
+
+nsresult
+nsNntpService::GetFolderFromUri(const char *aUri, nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aUri);
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(aUri));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString path;
+ rv = uri->GetPath(path);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = accountManager->FindServerByURI(uri, false, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // check if path is "/"
+ // if so, use the root folder
+ if (path.Length() == 1)
+ {
+ NS_ADDREF(*aFolder = rootFolder);
+ return NS_OK;
+ }
+
+ // the URI is news://host/(escaped group)
+ // but the *name* of the newsgroup (we are calling ::GetChildNamed())
+ // is unescaped. see http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c17
+ // for more about this
+ nsCString unescapedPath;
+ MsgUnescapeString(Substring(path, 1), 0, unescapedPath); /* skip the leading slash */
+
+ nsCOMPtr<nsIMsgFolder> subFolder;
+ rv = rootFolder->GetChildNamed(NS_ConvertUTF8toUTF16(unescapedPath),
+ getter_AddRefs(subFolder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ subFolder.swap(*aFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::CopyMessage(const char * aSrcMessageURI, nsIStreamListener * aMailboxCopyHandler, bool moveMessage,
+ nsIUrlListener * aUrlListener, nsIMsgWindow *aMsgWindow, nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aSrcMessageURI);
+ NS_ENSURE_ARG_POINTER(aMailboxCopyHandler);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> streamSupport = do_QueryInterface(aMailboxCopyHandler, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = DisplayMessage(aSrcMessageURI, streamSupport, aMsgWindow, aUrlListener, nullptr, aURL);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::CopyMessages(uint32_t aNumKeys, nsMsgKey *akeys,
+ nsIMsgFolder *srcFolder,
+ nsIStreamListener * aMailboxCopyHandler,
+ bool moveMessage,
+ nsIUrlListener * aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsNntpService::FindServerWithNewsgroup(nsCString &host, nsCString &groupName)
+{
+ nsresult rv;
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIArray> servers;
+ rv = accountManager->GetAllServers(getter_AddRefs(servers));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ASSERTION(MsgIsUTF8(groupName),
+ "newsgroup is not in UTF-8");
+
+ // XXX TODO
+ // this only looks at the list of subscribed newsgroups.
+ // fix to use the hostinfo.dat information
+
+ uint32_t length;
+ rv = servers->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < length; ++i)
+ {
+ nsCOMPtr<nsINntpIncomingServer> newsserver(do_QueryElementAt(servers, i, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ bool containsGroup = false;
+ rv = newsserver->ContainsNewsgroup(groupName,
+ &containsGroup);
+ if (containsGroup)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server(do_QueryInterface(newsserver, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return server->GetHostName(host);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsNntpService::FindHostFromGroup(nsCString &host, nsCString &groupName)
+{
+ nsresult rv = NS_OK;
+ // host always comes in as ""
+ NS_ASSERTION(host.IsEmpty(), "host is not empty");
+ if (!host.IsEmpty()) return NS_ERROR_FAILURE;
+
+ rv = FindServerWithNewsgroup(host, groupName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // host can be empty
+ return NS_OK;
+}
+
+nsresult
+nsNntpService::SetUpNntpUrlForPosting(const char *aAccountKey, char **newsUrlSpec)
+{
+ nsresult rv = NS_OK;
+
+ nsCString host;
+ int32_t port = -1;
+
+ nsCOMPtr<nsIMsgIncomingServer> nntpServer;
+ rv = GetNntpServerByAccount(aAccountKey, getter_AddRefs(nntpServer));
+ if (NS_SUCCEEDED(rv) && nntpServer)
+ {
+ nntpServer->GetHostName(host);
+ nntpServer->GetPort(&port);
+ }
+ else
+ {
+ NS_WARNING("Failure to obtain host and port");
+ }
+
+ *newsUrlSpec = PR_smprintf("%s/%s:%d",kNewsRootURI, host.IsEmpty() ? "news" : host.get(), port);
+ if (!*newsUrlSpec) return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+////////////////////////////////////////////////////////////////////////////////
+// nsINntpService support
+////////////////////////////////////////////////////////////////////////////////
+// XXX : may not work with non-ASCII newsgroup names and IDN hostnames
+NS_IMETHODIMP
+nsNntpService::GenerateNewsHeaderValsForPosting(const nsACString& newsgroupsList, char **newsgroupsHeaderVal, char **newshostHeaderVal)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG_POINTER(newsgroupsHeaderVal);
+ NS_ENSURE_ARG_POINTER(newshostHeaderVal);
+
+ // newsgroupsList can be a comma separated list of these:
+ // news://host/group
+ // news://group
+ // host/group
+ // group
+ //
+ // we are not going to allow the user to cross post to multiple hosts.
+ // if we detect that, we stop and return error.
+
+ nsAutoCString host;
+ nsAutoCString newsgroups;
+
+ nsTArray<nsCString> list;
+ ParseString(newsgroupsList, ',', list);
+ for (uint32_t index = 0; index < list.Length(); index++)
+ {
+ list[index].StripWhitespace();
+ if (!list[index].IsEmpty())
+ {
+ nsAutoCString currentHost;
+ nsAutoCString theRest;
+ // does list[index] start with "news:/"?
+ if (StringBeginsWith(list[index], NS_LITERAL_CSTRING(kNewsRootURI)))
+ {
+ // we have news://group or news://host/group
+ // set theRest to what's after news://
+ theRest = Substring(list[index], kNewsRootURILen /* for news:/ */ + 1 /* for the slash */);
+ }
+ else if (list[index].Find(":/") != -1)
+ {
+ // we have x:/y where x != news. this is bad, return failure
+ return NS_ERROR_FAILURE;
+ }
+ else
+ theRest = list[index];
+
+ // theRest is "group" or "host/group"
+ int32_t slashpos = theRest.FindChar('/');
+ if (slashpos > 0 )
+ {
+ nsAutoCString currentGroup;
+
+ // theRest is "host/group"
+ currentHost = StringHead(theRest, slashpos);
+
+ // from "host/group", put "group" into currentGroup;
+ currentGroup = Substring(theRest, slashpos + 1);
+
+ NS_ASSERTION(!currentGroup.IsEmpty(), "currentGroup is empty");
+ if (currentGroup.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ // build up the newsgroups
+ if (!newsgroups.IsEmpty())
+ newsgroups += ",";
+ newsgroups += currentGroup;
+ }
+ else
+ {
+ // theRest is "group"
+ rv = FindHostFromGroup(currentHost, theRest);
+ if (NS_FAILED(rv))
+ return rv;
+ // build up the newsgroups
+ if (!newsgroups.IsEmpty())
+ newsgroups += ",";
+ newsgroups += theRest;
+ }
+
+ if (!currentHost.IsEmpty())
+ {
+ if (host.IsEmpty())
+ host = currentHost;
+ else
+ {
+ if (!host.Equals(currentHost))
+ return NS_ERROR_NNTP_NO_CROSS_POSTING;
+ }
+ }
+ currentHost = "";
+ }
+ }
+
+ *newshostHeaderVal = ToNewCString(host);
+ if (!*newshostHeaderVal) return NS_ERROR_OUT_OF_MEMORY;
+
+ *newsgroupsHeaderVal = ToNewCString(newsgroups);
+ if (!*newsgroupsHeaderVal) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+nsresult
+nsNntpService::GetNntpServerByAccount(const char *aAccountKey, nsIMsgIncomingServer **aNntpServer)
+{
+ NS_ENSURE_ARG_POINTER(aNntpServer);
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (aAccountKey)
+ {
+ nsCOMPtr <nsIMsgAccount> account;
+ rv = accountManager->GetAccount(nsDependentCString(aAccountKey), getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account)
+ rv = account->GetIncomingServer(aNntpServer);
+ }
+
+ // if we don't have a news host, find the first news server and use it
+ if (NS_FAILED(rv) || !*aNntpServer)
+ rv = accountManager->FindServer(EmptyCString(), EmptyCString(), NS_LITERAL_CSTRING("nntp"), aNntpServer);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::PostMessage(nsIFile *aFileToPost, const char *newsgroupsNames, const char *aAccountKey, nsIUrlListener * aUrlListener, nsIMsgWindow *aMsgWindow, nsIURI **_retval)
+{
+ // aMsgWindow might be null
+ NS_ENSURE_ARG_POINTER(newsgroupsNames);
+
+ NS_ENSURE_ARG(*newsgroupsNames);
+
+ nsresult rv;
+
+ nsCOMPtr <nsINntpUrl> nntpUrl = do_CreateInstance(NS_NNTPURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nntpUrl->SetNewsAction(nsINntpUrl::ActionPostArticle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString newsUrlSpec;
+ rv = SetUpNntpUrlForPosting(aAccountKey, getter_Copies(newsUrlSpec));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(nntpUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailnewsurl->SetSpec(newsUrlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aUrlListener) // register listener if there is one...
+ mailnewsurl->RegisterListener(aUrlListener);
+
+ nsCOMPtr<nsINNTPNewsgroupPost> post = do_CreateInstance(NS_NNTPNEWSGROUPPOST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = post->SetPostMessageFile(aFileToPost);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nntpUrl->SetMessageToPost(post);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> url = do_QueryInterface(nntpUrl);
+ rv = RunNewsUrl(url, aMsgWindow, nullptr /* consumer */);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (_retval)
+ rv = CallQueryInterface(nntpUrl, _retval);
+
+ return rv;
+}
+
+nsresult
+nsNntpService::ConstructNntpUrl(const char *urlString, nsIUrlListener *aUrlListener, nsIMsgWindow *aMsgWindow, const char *originalMessageUri, int32_t action, nsIURI ** aUrl)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsINntpUrl> nntpUrl = do_CreateInstance(NS_NNTPURL_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(nntpUrl);
+ mailnewsurl->SetMsgWindow(aMsgWindow);
+ nsCOMPtr <nsIMsgMessageUrl> msgUrl = do_QueryInterface(nntpUrl);
+ msgUrl->SetUri(originalMessageUri);
+ rv = mailnewsurl->SetSpec(nsDependentCString(urlString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nntpUrl->SetNewsAction(action);
+
+ if (originalMessageUri)
+ {
+ // we'll use this later in nsNNTPProtocol::ParseURL()
+ rv = msgUrl->SetOriginalSpec(originalMessageUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (aUrlListener) // register listener if there is one...
+ mailnewsurl->RegisterListener(aUrlListener);
+
+ (*aUrl) = mailnewsurl;
+ NS_IF_ADDREF(*aUrl);
+ return rv;
+}
+
+nsresult
+nsNntpService::CreateNewsAccount(const char *aHostname, bool aUseSSL,
+ int32_t aPort, nsIMsgIncomingServer **aServer)
+{
+ NS_ENSURE_ARG_POINTER(aHostname);
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgAccount> account;
+ rv = accountManager->CreateAccount(getter_AddRefs(account));
+ if (NS_FAILED(rv)) return rv;
+
+ // for news, username is always null
+ rv = accountManager->CreateIncomingServer(EmptyCString(), nsDependentCString(aHostname), NS_LITERAL_CSTRING("nntp"), aServer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (aUseSSL)
+ {
+ rv = (*aServer)->SetSocketType(nsMsgSocketType::SSL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = (*aServer)->SetPort(aPort);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIMsgIdentity> identity;
+ rv = accountManager->CreateIdentity(getter_AddRefs(identity));
+ if (NS_FAILED(rv)) return rv;
+ if (!identity) return NS_ERROR_FAILURE;
+
+ // by default, news accounts should be composing in plain text
+ rv = identity->SetComposeHtml(false);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // the identity isn't filled in, so it is not valid.
+ rv = (*aServer)->SetValid(false);
+ if (NS_FAILED(rv)) return rv;
+
+ // hook them together
+ rv = account->SetIncomingServer(*aServer);
+ if (NS_FAILED(rv)) return rv;
+ rv = account->AddIdentity(identity);
+ if (NS_FAILED(rv)) return rv;
+
+ // Now save the new acct info to pref file.
+ rv = accountManager->SaveAccountInfo();
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult
+nsNntpService::GetServerForUri(nsIURI *aUri, nsINntpIncomingServer **aServer)
+{
+ nsAutoCString hostName;
+ nsAutoCString scheme;
+ nsAutoCString path;
+ int32_t port = 0;
+ nsresult rv;
+
+ rv = aUri->GetAsciiHost(hostName);
+ rv = aUri->GetScheme(scheme);
+ rv = aUri->GetPort(&port);
+ rv = aUri->GetPath(path);
+
+ nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // find the incoming server, it if exists.
+ // migrate if necessary, before searching for it.
+ // if it doesn't exist, create it.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ // Grab all servers for if this is a no-authority URL. This also loads
+ // accounts if they haven't been loaded, i.e., we're running this straight
+ // from the command line
+ nsCOMPtr <nsIArray> servers;
+ rv = accountManager->GetAllServers(getter_AddRefs(servers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!server && !hostName.IsEmpty())
+ {
+ // If we don't have this server but it isn't no-auth, add it.
+ // Ideally, we should remove this account quickly (see bug 41133)
+ bool useSSL = false;
+ if (scheme.EqualsLiteral("snews") || scheme.EqualsLiteral("nntps"))
+ {
+ useSSL = true;
+ if ((port == 0) || (port == -1))
+ port = nsINntpUrl::DEFAULT_NNTPS_PORT;
+ }
+ rv = CreateNewsAccount(hostName.get(), useSSL, port, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!server && hostName.IsEmpty())
+ // XXX: Until we support no-auth uris, bail
+ return NS_ERROR_FAILURE;
+
+ if (!server) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsINntpIncomingServer> nntpServer;
+ nntpServer = do_QueryInterface(server, &rv);
+
+ if (!nntpServer || NS_FAILED(rv))
+ return rv;
+
+ NS_IF_ADDREF(*aServer = nntpServer);
+
+ nsAutoCString spec;
+ rv = aUri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+#if 0 // this not ready yet.
+ nsNewsAction action = nsINntpUrl::ActionUnknown;
+ nsCOMPtr <nsINntpUrl> nntpUrl = do_QueryInterface(aUri);
+ if (nntpUrl) {
+ rv = nntpUrl->GetNewsAction(&action);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ // if this is a news-message:/ uri, decompose it and set hasMsgOffline on the uri
+ // Or, if it's of this form, we need to do the same.
+ // "news://news.mozilla.org:119/3D612B96.1050301%40netscape.com?part=1.2&type=image/gif&filename=hp_icon_logo.gif"
+
+ // XXX todo, or do we want to check if it is a news-message:// uri or
+ // a news:// uri (but action is not a fetch related action?)
+ if (!PL_strncmp(spec.get(), kNewsMessageRootURI, kNewsMessageRootURILen) ||
+ (action == nsINntpUrl::ActionFetchPart || action == nsINntpUrl::ActionFetchArticle))
+ {
+#else
+ // if this is a news-message:/ uri, decompose it and set hasMsgOffline on the uri
+ if (!PL_strncmp(spec.get(), kNewsMessageRootURI, kNewsMessageRootURILen))
+ {
+#endif
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsMsgKey key = nsMsgKey_None;
+ rv = DecomposeNewsMessageURI(spec.get(), getter_AddRefs(folder), &key);
+ if (NS_SUCCEEDED(rv) && folder)
+ {
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(aUri));
+ if (msgUrl)
+ msgUrl->SetMsgIsInLocalCache(hasMsgOffline);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNntpService::RunNewsUrl(nsIURI * aUri, nsIMsgWindow *aMsgWindow, nsISupports * aConsumer)
+{
+ nsresult rv;
+
+ if (WeAreOffline())
+ return NS_MSG_ERROR_OFFLINE;
+
+ // almost there...now create a nntp protocol instance to run the url in...
+ nsCOMPtr<nsINntpIncomingServer> server;
+ rv = GetServerForUri(aUri, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return server->LoadNewsUrl(aUri, aMsgWindow, aConsumer);
+}
+
+NS_IMETHODIMP nsNntpService::GetNewNews(nsINntpIncomingServer *nntpServer, const char *uri, bool aGetOld, nsIUrlListener * aUrlListener, nsIMsgWindow *aMsgWindow, nsIURI **_retval)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ server = do_QueryInterface(nntpServer);
+
+ /* double check that it is a "news:/" url */
+ if (strncmp(uri, kNewsRootURI, kNewsRootURILen) == 0)
+ {
+ nsCOMPtr<nsIURI> aUrl;
+ rv = ConstructNntpUrl(uri, aUrlListener, aMsgWindow, nullptr, nsINntpUrl::ActionGetNewNews, getter_AddRefs(aUrl));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsINntpUrl> nntpUrl = do_QueryInterface(aUrl);
+ if (nntpUrl)
+ {
+ rv = nntpUrl->SetGetOldMessages(aGetOld);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(aUrl);
+ if (mailNewsUrl)
+ mailNewsUrl->SetUpdatingFolder(true);
+
+ rv = RunNewsUrl(aUrl, aMsgWindow, nullptr);
+
+ if (_retval)
+ NS_IF_ADDREF(*_retval = aUrl);
+ }
+ else
+ {
+ NS_ERROR("not a news:/ url");
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::CancelMessage(const char *cancelURL, const char *messageURI, nsISupports * aConsumer, nsIUrlListener * aUrlListener, nsIMsgWindow *aMsgWindow, nsIURI ** aURL)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(cancelURL);
+ NS_ENSURE_ARG_POINTER(messageURI);
+
+ nsCOMPtr<nsIURI> url;
+ // the url should be "news://host/message-id?cancel"
+ rv = ConstructNntpUrl(cancelURL, aUrlListener, aMsgWindow, messageURI, nsINntpUrl::ActionCancelArticle, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = RunNewsUrl(url, aMsgWindow, aConsumer);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aURL)
+ {
+ *aURL = url;
+ NS_IF_ADDREF(*aURL);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpService::GetScheme(nsACString &aScheme)
+{
+ aScheme = "news";
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::GetDefaultDoBiff(bool *aDoBiff)
+{
+ NS_ENSURE_ARG_POINTER(aDoBiff);
+ // by default, don't do biff for NNTP servers
+ *aDoBiff = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::GetDefaultPort(int32_t *aDefaultPort)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultPort);
+ *aDefaultPort = nsINntpUrl::DEFAULT_NNTP_PORT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ *_retval = true; // allow news on any port
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetDefaultServerPort(bool aUseSSL, int32_t *aDefaultPort)
+{
+ nsresult rv = NS_OK;
+
+ // Return Secure NNTP Port if secure option chosen i.e., if useSSL is TRUE.
+ if (aUseSSL)
+ *aDefaultPort = nsINntpUrl::DEFAULT_NNTPS_PORT;
+ else
+ rv = GetDefaultPort(aDefaultPort);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpService::GetProtocolFlags(uint32_t *aUritype)
+{
+ NS_ENSURE_ARG_POINTER(aUritype);
+ *aUritype = URI_NORELATIVE | URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ URI_LOADABLE_BY_ANYONE | ALLOWS_PROXY | URI_FORBIDS_COOKIE_ACCESS
+#ifdef IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ | ORIGIN_IS_FULL_SPEC
+#endif
+ ;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::NewURI(const nsACString &aSpec,
+ const char *aCharset, // ignored
+ nsIURI *aBaseURI,
+ nsIURI **_retval)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> nntpUri = do_CreateInstance(NS_NNTPURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (aBaseURI)
+ {
+ nsAutoCString newSpec;
+ aBaseURI->Resolve(aSpec, newSpec);
+ rv = nntpUri->SetSpec(newSpec);
+ }
+ else
+ {
+ rv = nntpUri->SetSpec(aSpec);
+ }
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_ADDREF(*_retval = nntpUri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+NS_IMETHODIMP nsNntpService::NewChannel2(nsIURI *aURI,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel **_retval)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINntpIncomingServer> server;
+ rv = GetServerForUri(aURI, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = server->GetNntpChannel(aURI, nullptr, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = channel->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::SetDefaultLocalPath(nsIFile *aPath)
+{
+ NS_ENSURE_ARG(aPath);
+ return NS_SetPersistentFile(PREF_MAIL_ROOT_NNTP_REL, PREF_MAIL_ROOT_NNTP, aPath);
+}
+
+NS_IMETHODIMP
+nsNntpService::GetDefaultLocalPath(nsIFile ** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ bool havePref;
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_GetPersistentFile(PREF_MAIL_ROOT_NNTP_REL,
+ PREF_MAIL_ROOT_NNTP,
+ NS_APP_NEWS_50_DIR,
+ havePref,
+ getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ rv = localFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!havePref || !exists)
+ {
+ rv = NS_SetPersistentFile(PREF_MAIL_ROOT_NNTP_REL, PREF_MAIL_ROOT_NNTP, localFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
+ }
+
+ NS_IF_ADDREF(*aResult = localFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetServerIID(nsIID* *aServerIID)
+{
+ *aServerIID = new nsIID(NS_GET_IID(nsINntpIncomingServer));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetRequiresUsername(bool *aRequiresUsername)
+{
+ NS_ENSURE_ARG_POINTER(aRequiresUsername);
+ *aRequiresUsername = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetPreflightPrettyNameWithEmailAddress(bool *aPreflightPrettyNameWithEmailAddress)
+{
+ NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress);
+ *aPreflightPrettyNameWithEmailAddress = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetCanLoginAtStartUp(bool *aCanLoginAtStartUp)
+{
+ NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp);
+ *aCanLoginAtStartUp = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetCanDelete(bool *aCanDelete)
+{
+ NS_ENSURE_ARG_POINTER(aCanDelete);
+ *aCanDelete = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetCanDuplicate(bool *aCanDuplicate)
+{
+ NS_ENSURE_ARG_POINTER(aCanDuplicate);
+ *aCanDuplicate = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetCanGetMessages(bool *aCanGetMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetMessages);
+ *aCanGetMessages = false; // poorly named, this just means we don't have an inbox.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetCanGetIncomingMessages(bool *aCanGetIncomingMessages)
+{
+ NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages);
+ // temporarily returns false because we don't yet support spam
+ // filtering in news. this will change.
+ *aCanGetIncomingMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetShowComposeMsgLink(bool *showComposeMsgLink)
+{
+ NS_ENSURE_ARG_POINTER(showComposeMsgLink);
+ *showComposeMsgLink = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetFoldersCreatedAsync(bool *aAsyncCreation)
+{
+ NS_ENSURE_ARG_POINTER(aAsyncCreation);
+ *aAsyncCreation = false;
+ return NS_OK;
+}
+
+//
+// rhp: Right now, this is the same as simple DisplayMessage, but it will change
+// to support print rendering.
+//
+NS_IMETHODIMP nsNntpService::DisplayMessageForPrinting(const char* aMessageURI, nsISupports * aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow, nsIUrlListener * aUrlListener, nsIURI ** aURL)
+{
+ mPrintingOperation = true;
+ nsresult rv = DisplayMessage(aMessageURI, aDisplayConsumer, aMsgWindow, aUrlListener, nullptr, aURL);
+ mPrintingOperation = false;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::StreamMessage(const char *aMessageURI, nsISupports *aConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ bool /* convertData */,
+ const nsACString &aAdditionalHeader,
+ bool aLocalOnly,
+ nsIURI **aURL)
+{
+ // The nntp protocol object will look for "header=filter" to decide if it wants to convert
+ // the data instead of using aConvertData. It turns out to be way too hard to pass aConvertData
+ // all the way over to the nntp protocol object.
+ nsAutoCString aURIString(aMessageURI);
+
+ if (!aAdditionalHeader.IsEmpty())
+ {
+ aURIString.FindChar('?') == kNotFound ? aURIString += "?" : aURIString += "&";
+ aURIString += "header=";
+ aURIString += aAdditionalHeader;
+ }
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey key;
+ nsresult rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString urlStr;
+ rv = CreateMessageIDURL(folder, key, getter_Copies(urlStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsNewsAction action = nsINntpUrl::ActionFetchArticle;
+ if (mOpenAttachmentOperation)
+ action = nsINntpUrl::ActionFetchPart;
+
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(urlStr.get(), aUrlListener, aMsgWindow, aURIString.get(),
+ action, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLocalOnly || WeAreOffline())
+ {
+ // Check in the offline cache, then in the mem cache
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl(do_QueryInterface(url, &rv));
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ if (!hasMsgOffline)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = server->GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ url->SetPort((socketType == nsMsgSocketType::SSL) ?
+ nsINntpUrl::DEFAULT_NNTPS_PORT : nsINntpUrl::DEFAULT_NNTP_PORT);
+
+ rv = IsMsgInMemCache(url, folder, &hasMsgOffline);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Return with an error if we didn't find it in the memory cache either
+ if (!hasMsgOffline)
+ return NS_ERROR_FAILURE;
+
+ msgUrl->SetMsgIsInLocalCache(true);
+ }
+
+ if (aURL)
+ NS_IF_ADDREF(*aURL = url);
+
+ return GetMessageFromUrl(url, aMsgWindow, aConsumer);
+}
+
+NS_IMETHODIMP nsNntpService::StreamHeaders(const char *aMessageURI,
+ nsIStreamListener *aConsumer,
+ nsIUrlListener *aUrlListener,
+ bool aLocalOnly,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+ NS_ENSURE_ARG_POINTER(aConsumer);
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (key == nsMsgKey_None)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ if (hasMsgOffline)
+ {
+ int64_t messageOffset;
+ uint32_t messageSize;
+ folder->GetOfflineFileStream(key, &messageOffset, &messageSize, getter_AddRefs(inputStream));
+ if (inputStream)
+ return MsgStreamMsgHeaders(inputStream, aConsumer);
+ }
+ nsAutoCString urlStr;
+ rv = CreateMessageIDURL(folder, key, getter_Copies(urlStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLocalOnly)
+ return NS_ERROR_FAILURE;
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpService::IsMsgInMemCache(nsIURI *aUrl,
+ nsIMsgFolder *aFolder,
+ bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ *aResult = false;
+ nsresult rv;
+
+ if (mCacheStorage)
+ {
+ // NNTP urls are truncated at the query part when used as cache keys.
+ nsCOMPtr <nsIURI> newUri;
+ aUrl->Clone(getter_AddRefs(newUri));
+ nsAutoCString path;
+ newUri->GetPath(path);
+ int32_t pos = path.FindChar('?');
+ if (pos != kNotFound) {
+ path.SetLength(pos);
+ newUri->SetPath(path);
+ }
+ bool exists;
+ rv = mCacheStorage->Exists(newUri, EmptyCString(), &exists);
+ if (NS_SUCCEEDED(rv) && exists) {
+ *aResult = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpService::Search(nsIMsgSearchSession *aSearchSession, nsIMsgWindow *aMsgWindow, nsIMsgFolder *aMsgFolder, const char *aSearchUri)
+{
+ NS_ENSURE_ARG(aMsgFolder);
+ NS_ENSURE_ARG(aSearchUri);
+
+ nsresult rv;
+
+ nsCString searchUrl;
+ rv = aMsgFolder->GetURI(searchUrl);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ searchUrl.Append(aSearchUri);
+
+ nsCOMPtr <nsIUrlListener> urlListener = do_QueryInterface(aSearchSession);
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(searchUrl.get(), urlListener, aMsgWindow, nullptr, nsINntpUrl::ActionSearch, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(url));
+ if (msgurl)
+ msgurl->SetSearchSession(aSearchSession);
+
+ // run the url to update the counts
+ return RunNewsUrl(url, nullptr, nullptr);
+}
+
+NS_IMETHODIMP
+nsNntpService::GetListOfGroupsOnServer(nsINntpIncomingServer *aNntpServer, nsIMsgWindow *aMsgWindow, bool aGetOnlyNew)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aNntpServer);
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(aNntpServer, &rv);
+ if (NS_FAILED(rv)) return rv;
+ if (!server) return NS_ERROR_FAILURE;
+
+ nsCString serverUri;
+ rv = server->GetServerURI(serverUri);
+ nsNewsAction newsAction;
+ if (aGetOnlyNew)
+ {
+ serverUri.AppendLiteral("/?newgroups");
+ newsAction = nsINntpUrl::ActionListNewGroups;
+ }
+ else
+ {
+ serverUri.AppendLiteral("/*");
+ newsAction = nsINntpUrl::ActionListGroups;
+ }
+
+ nsCOMPtr <nsIUrlListener> listener = do_QueryInterface(aNntpServer, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> url;
+ rv = ConstructNntpUrl(serverUri.get(), listener, aMsgWindow, nullptr, newsAction, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now run the url to add the rest of the groups
+ return RunNewsUrl(url, aMsgWindow, nullptr);
+}
+
+
+NS_IMETHODIMP
+nsNntpService::Handle(nsICommandLine* aCmdLine)
+{
+ NS_ENSURE_ARG_POINTER(aCmdLine);
+
+ nsresult rv;
+ bool found;
+
+ rv = aCmdLine->HandleFlag(NS_LITERAL_STRING("news"), false, &found);
+ if (NS_SUCCEEDED(rv) && found) {
+ nsCOMPtr<nsIWindowWatcher> wwatch (do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE);
+
+ nsCOMPtr<mozIDOMWindowProxy> opened;
+ wwatch->OpenWindow(nullptr, "chrome://messenger/content/", "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar",
+ nullptr, getter_AddRefs(opened));
+ aCmdLine->SetPreventDefault(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::GetHelpInfo(nsACString& aResult)
+{
+ aResult.Assign(NS_LITERAL_CSTRING(" -news Open the news client.\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::HandleContent(const char * aContentType, nsIInterfaceRequestor* aWindowContext, nsIRequest *request)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(request);
+
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check for x-application-newsgroup or x-application-newsgroup-listids
+ if (PL_strncasecmp(aContentType, "x-application-newsgroup", 23) == 0)
+ {
+ nsCOMPtr<nsIURI> uri;
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(uri);
+ if (mailUrl)
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = mailUrl->GetFolder(getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // No folder means we can't handle this
+ if (!msgFolder)
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+
+ nsCString folderURL;
+ rv = msgFolder->GetURI(folderURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // this is all we need for listing newsgroup ids.
+ if (!PL_strcasecmp(aContentType, "x-application-newsgroup-listids"))
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ mailUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (!msgWindow)
+ {
+ // This came from a docshell that didn't set msgWindow, so find one
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+
+ if (!msgWindow)
+ {
+ // We need to create a 3-pane window, then
+ nsCOMPtr<nsIWindowWatcher> wwatcher =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsCString> arg =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
+ arg->SetData(folderURL);
+
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ rv = wwatcher->OpenWindow(nullptr, "chrome://messenger/content/",
+ "_blank", "chome,all,dialog=no", arg, getter_AddRefs(newWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ msgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectFolder(folderURL);
+ }
+ request->Cancel(NS_BINDING_ABORTED);
+ }
+ } else // The content-type was not x-application-newsgroup.
+ rv = NS_ERROR_WONT_HANDLE_CONTENT;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNntpService::MessageURIToMsgHdr(const char *uri, nsIMsgDBHdr **_retval)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv = NS_OK;
+
+ nsCOMPtr <nsIMsgFolder> folder;
+ nsMsgKey msgKey;
+
+ rv = DecomposeNewsMessageURI(uri, getter_AddRefs(folder), &msgKey);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!folder)
+ return NS_ERROR_NULL_POINTER;
+
+ rv = folder->GetMessageHeader(msgKey, _retval);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpService::DownloadNewsgroupsForOffline(nsIMsgWindow *aMsgWindow, nsIUrlListener *aListener)
+{
+ RefPtr<nsMsgDownloadAllNewsgroups> newsgroupDownloader =
+ new nsMsgDownloadAllNewsgroups(aMsgWindow, aListener);
+ return newsgroupDownloader->ProcessNextGroup();
+}
+
+NS_IMETHODIMP nsNntpService::GetCacheStorage(nsICacheStorage **result)
+{
+ nsresult rv = NS_OK;
+ if (!mCacheStorage)
+ {
+ nsCOMPtr<nsICacheStorageService> cacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<MailnewsLoadContextInfo> lci =
+ new MailnewsLoadContextInfo(false, false, mozilla::NeckoOriginAttributes());
+
+ rv = cacheStorageService->MemoryCacheStorage(lci, getter_AddRefs(mCacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*result = mCacheStorage);
+ return rv;
+}
diff --git a/mailnews/news/src/nsNntpService.h b/mailnews/news/src/nsNntpService.h
new file mode 100644
index 000000000..58c2699f2
--- /dev/null
+++ b/mailnews/news/src/nsNntpService.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNntpService_h___
+#define nsNntpService_h___
+
+#include "nsINntpService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIMsgMessageService.h"
+#include "nsINntpIncomingServer.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIFile.h"
+#include "MailNewsTypes.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIMsgWindow.h"
+#include "nsINntpUrl.h"
+#include "nsCOMPtr.h"
+#include "nsIContentHandler.h"
+#include "nsICacheStorage.h"
+
+#include "nsICommandLineHandler.h"
+
+class nsIURI;
+class nsIUrlListener;
+
+class nsNntpService : public nsINntpService,
+ public nsIMsgMessageService,
+ public nsIMsgMessageFetchPartService,
+ public nsIProtocolHandler,
+ public nsIMsgProtocolInfo,
+ public nsICommandLineHandler,
+ public nsIContentHandler
+{
+public:
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINNTPSERVICE
+ NS_DECL_NSIMSGMESSAGESERVICE
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIMSGPROTOCOLINFO
+ NS_DECL_NSICONTENTHANDLER
+ NS_DECL_NSIMSGMESSAGEFETCHPARTSERVICE
+ NS_DECL_NSICOMMANDLINEHANDLER
+
+ // nsNntpService
+ nsNntpService();
+
+protected:
+ virtual ~nsNntpService();
+
+ nsresult GetNntpServerByAccount(const char *aAccountKey, nsIMsgIncomingServer **aNntpServer);
+ nsresult SetUpNntpUrlForPosting(const char *aAccountKey, char **newsUrlSpec);
+ nsresult FindHostFromGroup(nsCString &host, nsCString &groupName);
+ nsresult FindServerWithNewsgroup(nsCString &host, nsCString &groupName);
+
+ nsresult CreateMessageIDURL(nsIMsgFolder *folder, nsMsgKey key, char **url);
+ nsresult GetMessageFromUrl(nsIURI *aUrl, nsIMsgWindow *aMsgWindow, nsISupports *aDisplayConsumer);
+ // a convience routine used to put together news urls
+ nsresult ConstructNntpUrl(const char * urlString, nsIUrlListener *aUrlListener, nsIMsgWindow * aMsgWindow, const char *originalMessageUri, int32_t action, nsIURI ** aUrl);
+ nsresult CreateNewsAccount(const char *aHostname, bool aIsSecure, int32_t aPort, nsIMsgIncomingServer **aServer);
+ nsresult GetServerForUri(nsIURI *aUri, nsINntpIncomingServer **aProtocol);
+ // a convience routine to run news urls
+ nsresult RunNewsUrl (nsIURI * aUrl, nsIMsgWindow *aMsgWindow, nsISupports * aConsumer);
+ // a convience routine to go from folder uri to msg folder
+ nsresult GetFolderFromUri(const char *uri, nsIMsgFolder **folder);
+ nsresult DecomposeNewsMessageURI(const char * aMessageURI, nsIMsgFolder ** aFolder, nsMsgKey *aMsgKey);
+
+ bool mPrintingOperation; // Flag for printing operations
+ bool mOpenAttachmentOperation; // Flag for opening attachments
+
+ nsCOMPtr<nsICacheStorage> mCacheStorage; // the cache storage used by news
+};
+
+#endif /* nsNntpService_h___ */
diff --git a/mailnews/news/src/nsNntpUrl.cpp b/mailnews/news/src/nsNntpUrl.cpp
new file mode 100644
index 000000000..5cdc3ba8d
--- /dev/null
+++ b/mailnews/news/src/nsNntpUrl.cpp
@@ -0,0 +1,578 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h" // precompiled header...
+
+#include "nsIURL.h"
+#include "nsNntpUrl.h"
+
+#include "nsStringGlue.h"
+#include "nsNewsUtils.h"
+#include "nsMsgUtils.h"
+
+#include "nntpCore.h"
+
+#include "nsCOMPtr.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgNewsCID.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsINntpService.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsServiceManagerUtils.h"
+
+
+nsNntpUrl::nsNntpUrl()
+{
+ m_newsgroupPost = nullptr;
+ m_newsAction = nsINntpUrl::ActionUnknown;
+ m_addDummyEnvelope = false;
+ m_canonicalLineEnding = false;
+ m_filePath = nullptr;
+ m_getOldMessages = false;
+ m_key = nsMsgKey_None;
+}
+
+nsNntpUrl::~nsNntpUrl()
+{
+}
+
+NS_IMPL_ADDREF_INHERITED(nsNntpUrl, nsMsgMailNewsUrl)
+NS_IMPL_RELEASE_INHERITED(nsNntpUrl, nsMsgMailNewsUrl)
+
+NS_INTERFACE_MAP_BEGIN(nsNntpUrl)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINntpUrl)
+ NS_INTERFACE_MAP_ENTRY(nsINntpUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgMessageUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgI18NUrl)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgMailNewsUrl)
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin nsINntpUrl specific support
+////////////////////////////////////////////////////////////////////////////////
+
+/* News URI parsing explanation:
+ * We support 3 different news URI schemes, essentially boiling down to 8
+ * different formats:
+ * news://host/group
+ * news://host/message
+ * news://host/
+ * news:group
+ * news:message
+ * nntp://host/group
+ * nntp://host/group/key
+ * news-message://host/group#key
+ *
+ * In addition, we use queries on the news URIs with authorities for internal
+ * NNTP processing. The most important one is ?group=group&key=key, for cache
+ * canonicalization.
+ */
+
+NS_IMETHODIMP nsNntpUrl::SetSpec(const nsACString &aSpec)
+{
+ // For [s]news: URIs, we need to munge the spec if it is no authority, because
+ // the URI parser guesses the wrong thing otherwise
+ nsCString parseSpec(aSpec);
+ int32_t colon = parseSpec.Find(":");
+
+ // Our smallest scheme is 4 characters long, so colon must be at least 4
+ if (colon < 4 || colon + 1 == (int32_t) parseSpec.Length())
+ return NS_ERROR_MALFORMED_URI;
+
+ if (Substring(parseSpec, colon - 4, 4).EqualsLiteral("news") &&
+ parseSpec[colon + 1] != '/')
+ {
+ // To make this parse properly, we add in three slashes, which convinces the
+ // parser that the authority component is empty.
+ parseSpec = Substring(aSpec, 0, colon + 1);
+ parseSpec.AppendLiteral("///");
+ parseSpec += Substring(aSpec, colon + 1);
+ }
+
+ nsresult rv = nsMsgMailNewsUrl::SetSpec(parseSpec);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString scheme;
+ rv = GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (scheme.EqualsLiteral("news") || scheme.EqualsLiteral("snews"))
+ rv = ParseNewsURL();
+ else if (scheme.EqualsLiteral("nntp") || scheme.EqualsLiteral("nntps"))
+ rv = ParseNntpURL();
+ else if (scheme.EqualsLiteral("news-message"))
+ {
+ nsAutoCString spec;
+ rv = GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsParseNewsMessageURI(spec.get(), m_group, &m_key);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+ }
+ else
+ return NS_ERROR_MALFORMED_URI;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = DetermineNewsAction();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+nsresult nsNntpUrl::ParseNewsURL()
+{
+ // The path here is the group/msgid portion
+ nsAutoCString path;
+ nsresult rv = GetFilePath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Drop the potential beginning from the path
+ if (path.Length() && path[0] == '/')
+ path = Substring(path, 1);
+
+ // The presence of an `@' is a sign we have a msgid
+ if (path.Find("@") != -1 || path.Find("%40") != -1)
+ {
+ MsgUnescapeString(path, 0, m_messageID);
+
+ // Set group, key for ?group=foo&key=123 uris
+ nsAutoCString spec;
+ rv = GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t groupPos = spec.Find(kNewsURIGroupQuery); // find ?group=
+ int32_t keyPos = spec.Find(kNewsURIKeyQuery); // find &key=
+ if (groupPos != kNotFound && keyPos != kNotFound)
+ {
+ // get group name and message key
+ m_group = Substring(spec, groupPos + kNewsURIGroupQueryLen,
+ keyPos - groupPos - kNewsURIGroupQueryLen);
+ nsCString keyStr(Substring(spec, keyPos + kNewsURIKeyQueryLen));
+ m_key = keyStr.ToInteger(&rv, 10);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+ }
+ }
+ else
+ MsgUnescapeString(path, 0, m_group);
+
+ return NS_OK;
+}
+
+nsresult nsNntpUrl::ParseNntpURL()
+{
+ nsAutoCString path;
+ nsresult rv = GetFilePath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (path.Length() > 0 && path[0] == '/')
+ path = Substring(path, 1);
+
+ if (path.IsEmpty())
+ return NS_ERROR_MALFORMED_URI;
+
+ int32_t slash = path.FindChar('/');
+ if (slash == -1)
+ {
+ m_group = path;
+ m_key = nsMsgKey_None;
+ }
+ else
+ {
+ m_group = Substring(path, 0, slash);
+ nsAutoCString keyStr;
+ keyStr = Substring(path, slash + 1);
+ m_key = keyStr.ToInteger(&rv, 10);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+
+ // Keys must be at least one
+ if (m_key == 0)
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsNntpUrl::DetermineNewsAction()
+{
+ nsAutoCString path;
+ nsresult rv = nsMsgMailNewsUrl::GetPath(path);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString query;
+ rv = GetQuery(query);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (query.EqualsLiteral("cancel"))
+ {
+ m_newsAction = nsINntpUrl::ActionCancelArticle;
+ return NS_OK;
+ }
+ if (query.EqualsLiteral("list-ids"))
+ {
+ m_newsAction = nsINntpUrl::ActionListIds;
+ return NS_OK;
+ }
+ if (query.EqualsLiteral("newgroups"))
+ {
+ m_newsAction = nsINntpUrl::ActionListNewGroups;
+ return NS_OK;
+ }
+ if (StringBeginsWith(query, NS_LITERAL_CSTRING("search")))
+ {
+ m_newsAction = nsINntpUrl::ActionSearch;
+ return NS_OK;
+ }
+ if (StringBeginsWith(query, NS_LITERAL_CSTRING("part=")) ||
+ query.Find("&part=") > 0)
+ {
+ // news://news.mozilla.org:119/3B98D201.3020100%40cs.com?part=1
+ // news://news.mozilla.org:119/b58dme%24aia2%40ripley.netscape.com?header=print&part=1.2&type=image/jpeg&filename=Pole.jpg
+ m_newsAction = nsINntpUrl::ActionFetchPart;
+ return NS_OK;
+ }
+
+ if (!m_messageID.IsEmpty() || m_key != nsMsgKey_None)
+ {
+ m_newsAction = nsINntpUrl::ActionFetchArticle;
+ return NS_OK;
+ }
+
+ if (m_group.Find("*") >= 0)
+ {
+ // If the group is a wildmat, list groups instead of grabbing a group.
+ m_newsAction = nsINntpUrl::ActionListGroups;
+ return NS_OK;
+ }
+ if (!m_group.IsEmpty())
+ {
+ m_newsAction = nsINntpUrl::ActionGetNewNews;
+ return NS_OK;
+ }
+
+ // At this point, we have a URI that contains neither a query, a group, nor a
+ // message ID. Ergo, we don't know what it is.
+ m_newsAction = nsINntpUrl::ActionUnknown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::SetGetOldMessages(bool aGetOldMessages)
+{
+ m_getOldMessages = aGetOldMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetGetOldMessages(bool * aGetOldMessages)
+{
+ NS_ENSURE_ARG(aGetOldMessages);
+ *aGetOldMessages = m_getOldMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetNewsAction(nsNewsAction *aNewsAction)
+{
+ if (aNewsAction)
+ *aNewsAction = m_newsAction;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsNntpUrl::SetNewsAction(nsNewsAction aNewsAction)
+{
+ m_newsAction = aNewsAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetGroup(nsACString &group)
+{
+ group = m_group;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetMessageID(nsACString &messageID)
+{
+ messageID = m_messageID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetKey(nsMsgKey *key)
+{
+ NS_ENSURE_ARG_POINTER(key);
+ *key = m_key;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetPrincipalSpec(nsACString& aPrincipalSpec)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpUrl::SetUri(const char * aURI)
+{
+ mURI = aURI;
+ return NS_OK;
+}
+
+// from nsIMsgMessageUrl
+NS_IMETHODIMP nsNntpUrl::GetUri(char ** aURI)
+{
+ nsresult rv = NS_OK;
+
+ // if we have been given a uri to associate with this url, then use it
+ // otherwise try to reconstruct a URI on the fly....
+ if (mURI.IsEmpty()) {
+ nsAutoCString spec;
+ rv = GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv,rv);
+ mURI = spec;
+ }
+
+ *aURI = ToNewCString(mURI);
+ if (!*aURI) return NS_ERROR_OUT_OF_MEMORY;
+ return rv;
+}
+
+
+NS_IMPL_GETSET(nsNntpUrl, AddDummyEnvelope, bool, m_addDummyEnvelope)
+NS_IMPL_GETSET(nsNntpUrl, CanonicalLineEnding, bool, m_canonicalLineEnding)
+
+NS_IMETHODIMP nsNntpUrl::SetMessageFile(nsIFile * aFile)
+{
+ m_messageFile = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetMessageFile(nsIFile ** aFile)
+{
+ if (aFile)
+ NS_IF_ADDREF(*aFile = m_messageFile);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// End nsINntpUrl specific support
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsNntpUrl::SetMessageToPost(nsINNTPNewsgroupPost *post)
+{
+ m_newsgroupPost = post;
+ if (post)
+ SetNewsAction(nsINntpUrl::ActionPostArticle);
+ return NS_OK;
+}
+
+nsresult nsNntpUrl::GetMessageToPost(nsINNTPNewsgroupPost **aPost)
+{
+ NS_ENSURE_ARG_POINTER(aPost);
+ NS_IF_ADDREF(*aPost = m_newsgroupPost);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::SetMessageHeader(nsIMsgDBHdr *aMsgHdr)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetMessageHeader(nsIMsgDBHdr ** aMsgHdr)
+{
+ nsresult rv;
+
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgMessageService> msgService = do_QueryInterface(nntpService, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString spec(mOriginalSpec);
+ if (spec.IsEmpty()) {
+ // Handle the case where necko directly runs an internal news:// URL,
+ // one that looks like news://host/message-id?group=mozilla.announce&key=15
+ // Other sorts of URLs -- e.g. news://host/message-id -- will not succeed.
+ rv = GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return msgService->MessageURIToMsgHdr(spec.get(), aMsgHdr);
+}
+
+NS_IMETHODIMP nsNntpUrl::IsUrlType(uint32_t type, bool *isType)
+{
+ NS_ENSURE_ARG(isType);
+
+ switch(type)
+ {
+ case nsIMsgMailNewsUrl::eDisplay:
+ *isType = (m_newsAction == nsINntpUrl::ActionFetchArticle);
+ break;
+ default:
+ *isType = false;
+ };
+
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP
+nsNntpUrl::GetOriginalSpec(char **aSpec)
+{
+ NS_ENSURE_ARG_POINTER(aSpec);
+ *aSpec = ToNewCString(mOriginalSpec);
+ if (!*aSpec) return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpUrl::SetOriginalSpec(const char *aSpec)
+{
+ mOriginalSpec = aSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpUrl::GetServer(nsIMsgIncomingServer **aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ nsresult rv;
+ nsAutoCString scheme, user, host;
+
+ GetScheme(scheme);
+ GetUsername(user);
+ GetHost(host);
+
+ // No authority -> no server
+ if (host.IsEmpty())
+ {
+ *aServer = nullptr;
+ return NS_OK;
+ }
+
+ // Looking up the server...
+ // news-message is used purely internally, so it can never refer to the real
+ // attribute. nntp is never used internally, so it probably refers to the real
+ // one. news is used both internally and externally, so it could refer to
+ // either one. We'll assume it's an internal one first, though.
+ bool isNews = scheme.EqualsLiteral("news") || scheme.EqualsLiteral("snews");
+ bool isNntp = scheme.EqualsLiteral("nntp") || scheme.EqualsLiteral("nntps");
+
+ bool tryReal = isNntp;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ignoring return results: it is perfectly acceptable for the server to not
+ // exist, but FindServer (and not FindRealServer) throws NS_ERROR_UNEXPECTED
+ // in this case.
+ *aServer = nullptr;
+ if (tryReal)
+ accountManager->FindRealServer(user, host, NS_LITERAL_CSTRING("nntp"), 0,
+ aServer);
+ else
+ accountManager->FindServer(user, host, NS_LITERAL_CSTRING("nntp"), aServer);
+ if (!*aServer && (isNews || isNntp))
+ {
+ // Didn't find it, try the other option
+ if (tryReal)
+ accountManager->FindServer(user, host, NS_LITERAL_CSTRING("nntp"),
+ aServer);
+ else
+ accountManager->FindRealServer(user, host, NS_LITERAL_CSTRING("nntp"), 0,
+ aServer);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetFolder(nsIMsgFolder **msgFolder)
+{
+ NS_ENSURE_ARG_POINTER(msgFolder);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Need a server and a group to get the folder
+ if (!server || m_group.IsEmpty())
+ {
+ *msgFolder = nullptr;
+ return NS_OK;
+ }
+
+ // Find the group on the server
+ nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasGroup = false;
+ rv = nntpServer->ContainsNewsgroup(m_group, &hasGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasGroup)
+ {
+ *msgFolder = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgNewsFolder> newsFolder;
+ rv = nntpServer->FindGroup(m_group, getter_AddRefs(newsFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return newsFolder->QueryInterface(NS_GET_IID(nsIMsgFolder), (void**)msgFolder);
+}
+
+NS_IMETHODIMP
+nsNntpUrl::GetFolderCharset(char **aCharacterSet)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetFolder(getter_AddRefs(folder));
+ // don't assert here. this can happen if there is no message folder
+ // like when we display a news://host/message-id url
+ if (NS_FAILED(rv) || !folder)
+ return rv;
+ nsCString tmpStr;
+ rv = folder->GetCharset(tmpStr);
+ *aCharacterSet = ToNewCString(tmpStr);
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetFolderCharsetOverride(bool * aCharacterSetOverride)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ENSURE_TRUE(folder, NS_ERROR_FAILURE);
+ rv = folder->GetCharsetOverride(aCharacterSetOverride);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsNntpUrl::GetCharsetOverRide(char ** aCharacterSet)
+{
+ if (!mCharsetOverride.IsEmpty())
+ *aCharacterSet = ToNewCString(mCharsetOverride);
+ else
+ *aCharacterSet = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::SetCharsetOverRide(const char * aCharacterSet)
+{
+ mCharsetOverride = aCharacterSet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpUrl::CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef,
+ nsIURI **_retval)
+{
+ nsresult rv;
+ rv = nsMsgMailNewsUrl::CloneInternal(aRefHandlingMode, newRef, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMessageUrl> newsurl = do_QueryInterface(*_retval, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return newsurl->SetUri(mURI.get());
+}
+
diff --git a/mailnews/news/src/nsNntpUrl.h b/mailnews/news/src/nsNntpUrl.h
new file mode 100644
index 000000000..db040a395
--- /dev/null
+++ b/mailnews/news/src/nsNntpUrl.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNntpUrl_h__
+#define nsNntpUrl_h__
+
+#include "nsINntpUrl.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsINNTPNewsgroupPost.h"
+#include "nsIFile.h"
+
+class nsNntpUrl : public nsINntpUrl, public nsMsgMailNewsUrl, public nsIMsgMessageUrl, public nsIMsgI18NUrl
+{
+public:
+ NS_DECL_NSINNTPURL
+ NS_DECL_NSIMSGMESSAGEURL
+ NS_DECL_NSIMSGI18NURL
+
+ // nsIURI over-ride...
+ NS_IMETHOD SetSpec(const nsACString &aSpec) override;
+
+ NS_IMETHOD IsUrlType(uint32_t type, bool *isType) override;
+
+ // nsIMsgMailNewsUrl overrides
+ NS_IMETHOD GetServer(nsIMsgIncomingServer **server) override;
+ NS_IMETHOD GetFolder(nsIMsgFolder **msgFolder) override;
+ NS_IMETHOD CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef,nsIURI **_retval) override;
+
+ // nsNntpUrl
+ nsNntpUrl();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ virtual ~nsNntpUrl();
+ nsresult DetermineNewsAction();
+ nsresult ParseNewsURL();
+ nsresult ParseNntpURL();
+
+ nsCOMPtr<nsINNTPNewsgroupPost> m_newsgroupPost;
+ nsNewsAction m_newsAction; // the action this url represents...parse mailbox, display messages, etc.
+
+ nsCString mURI; // the RDF URI associated with this url.
+ nsCString mCharsetOverride; // used by nsIMsgI18NUrl...
+
+ nsCString mOriginalSpec;
+ nsCOMPtr <nsIFile> m_filePath;
+
+ // used by save message to disk
+ nsCOMPtr<nsIFile> m_messageFile;
+
+ bool m_addDummyEnvelope;
+ bool m_canonicalLineEnding;
+ bool m_getOldMessages;
+
+ nsCString m_group;
+ nsCString m_messageID;
+ nsMsgKey m_key;
+};
+
+#endif // nsNntpUrl_h__