diff options
Diffstat (limited to 'mailnews/news')
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(¬ifyMaxExceededOn); + 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><%.512s> (%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", + ¶ms[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__ |