summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/local
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/local
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/local')
-rw-r--r--comm/mailnews/local/public/moz.build26
-rw-r--r--comm/mailnews/local/public/nsILocalMailIncomingServer.idl23
-rw-r--r--comm/mailnews/local/public/nsIMailboxService.idl34
-rw-r--r--comm/mailnews/local/public/nsIMailboxUrl.idl58
-rw-r--r--comm/mailnews/local/public/nsIMsgLocalMailFolder.idl134
-rw-r--r--comm/mailnews/local/public/nsIMsgParseMailMsgState.idl46
-rw-r--r--comm/mailnews/local/public/nsINewsBlogFeedDownloader.idl32
-rw-r--r--comm/mailnews/local/public/nsINoIncomingServer.idl16
-rw-r--r--comm/mailnews/local/public/nsINoneService.idl11
-rw-r--r--comm/mailnews/local/public/nsIPop3IncomingServer.idl33
-rw-r--r--comm/mailnews/local/public/nsIPop3Protocol.idl23
-rw-r--r--comm/mailnews/local/public/nsIPop3Service.idl128
-rw-r--r--comm/mailnews/local/public/nsIPop3Sink.idl42
-rw-r--r--comm/mailnews/local/public/nsIPop3URL.idl19
-rw-r--r--comm/mailnews/local/public/nsIRssIncomingServer.idl16
-rw-r--r--comm/mailnews/local/public/nsIRssService.idl10
-rw-r--r--comm/mailnews/local/src/Pop3Channel.jsm105
-rw-r--r--comm/mailnews/local/src/Pop3Client.jsm1570
-rw-r--r--comm/mailnews/local/src/Pop3IncomingServer.jsm308
-rw-r--r--comm/mailnews/local/src/Pop3ProtocolHandler.jsm40
-rw-r--r--comm/mailnews/local/src/Pop3ProtocolInfo.jsm44
-rw-r--r--comm/mailnews/local/src/Pop3Service.jsm77
-rw-r--r--comm/mailnews/local/src/components.conf150
-rw-r--r--comm/mailnews/local/src/moz.build40
-rw-r--r--comm/mailnews/local/src/nsLocalMailFolder.cpp3468
-rw-r--r--comm/mailnews/local/src/nsLocalMailFolder.h285
-rw-r--r--comm/mailnews/local/src/nsLocalUndoTxn.cpp495
-rw-r--r--comm/mailnews/local/src/nsLocalUndoTxn.h79
-rw-r--r--comm/mailnews/local/src/nsLocalUtils.cpp208
-rw-r--r--comm/mailnews/local/src/nsLocalUtils.h29
-rw-r--r--comm/mailnews/local/src/nsMailboxProtocol.cpp657
-rw-r--r--comm/mailnews/local/src/nsMailboxProtocol.h113
-rw-r--r--comm/mailnews/local/src/nsMailboxServer.cpp28
-rw-r--r--comm/mailnews/local/src/nsMailboxServer.h22
-rw-r--r--comm/mailnews/local/src/nsMailboxService.cpp559
-rw-r--r--comm/mailnews/local/src/nsMailboxService.h59
-rw-r--r--comm/mailnews/local/src/nsMailboxUrl.cpp474
-rw-r--r--comm/mailnews/local/src/nsMailboxUrl.h106
-rw-r--r--comm/mailnews/local/src/nsMsgBrkMBoxStore.cpp1033
-rw-r--r--comm/mailnews/local/src/nsMsgBrkMBoxStore.h57
-rw-r--r--comm/mailnews/local/src/nsMsgFileHdr.cpp389
-rw-r--r--comm/mailnews/local/src/nsMsgFileHdr.h41
-rw-r--r--comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp380
-rw-r--r--comm/mailnews/local/src/nsMsgLocalStoreUtils.h39
-rw-r--r--comm/mailnews/local/src/nsMsgMaildirStore.cpp1380
-rw-r--r--comm/mailnews/local/src/nsMsgMaildirStore.h39
-rw-r--r--comm/mailnews/local/src/nsNoIncomingServer.cpp189
-rw-r--r--comm/mailnews/local/src/nsNoIncomingServer.h41
-rw-r--r--comm/mailnews/local/src/nsNoneService.cpp147
-rw-r--r--comm/mailnews/local/src/nsNoneService.h26
-rw-r--r--comm/mailnews/local/src/nsParseMailbox.cpp2354
-rw-r--r--comm/mailnews/local/src/nsParseMailbox.h256
-rw-r--r--comm/mailnews/local/src/nsPop3Sink.cpp740
-rw-r--r--comm/mailnews/local/src/nsPop3Sink.h70
-rw-r--r--comm/mailnews/local/src/nsPop3URL.cpp202
-rw-r--r--comm/mailnews/local/src/nsPop3URL.h36
-rw-r--r--comm/mailnews/local/src/nsRssIncomingServer.cpp248
-rw-r--r--comm/mailnews/local/src/nsRssIncomingServer.h47
-rw-r--r--comm/mailnews/local/src/nsRssService.cpp113
-rw-r--r--comm/mailnews/local/src/nsRssService.h23
-rw-r--r--comm/mailnews/local/test/moz.build6
-rw-r--r--comm/mailnews/local/test/unit/data/dot10
-rw-r--r--comm/mailnews/local/test/unit/data/invalid_mozilla_keys.eml5
-rw-r--r--comm/mailnews/local/test/unit/data/mailformed_recipients.eml66
-rw-r--r--comm/mailnews/local/test/unit/data/mailformed_subject.eml1934
-rw-r--r--comm/mailnews/local/test/unit/data/message1.eml7
-rw-r--r--comm/mailnews/local/test/unit/data/message2.eml9
-rw-r--r--comm/mailnews/local/test/unit/data/message3.eml7
-rw-r--r--comm/mailnews/local/test/unit/data/movemailspool169
-rw-r--r--comm/mailnews/local/test/unit/head_maillocal.js214
-rw-r--r--comm/mailnews/local/test/unit/test_Pop3Channel.js75
-rw-r--r--comm/mailnews/local/test/unit/test_bug457168.js165
-rw-r--r--comm/mailnews/local/test/unit/test_duplicateKey.js81
-rw-r--r--comm/mailnews/local/test/unit/test_fileName.js112
-rw-r--r--comm/mailnews/local/test/unit/test_folderLoaded.js89
-rw-r--r--comm/mailnews/local/test/unit/test_localFolder.js164
-rw-r--r--comm/mailnews/local/test/unit/test_mailboxContentLength.js62
-rw-r--r--comm/mailnews/local/test/unit/test_mailboxProtocol.js52
-rw-r--r--comm/mailnews/local/test/unit/test_mailboxURL.js82
-rw-r--r--comm/mailnews/local/test/unit/test_msgCopy.js27
-rw-r--r--comm/mailnews/local/test/unit/test_msgIDParsing.js24
-rw-r--r--comm/mailnews/local/test/unit/test_noTop.js64
-rw-r--r--comm/mailnews/local/test/unit/test_noUidl.js89
-rw-r--r--comm/mailnews/local/test/unit/test_nsIMsgLocalMailFolder.js321
-rw-r--r--comm/mailnews/local/test/unit/test_nsIMsgParseMailMsgState.js42
-rw-r--r--comm/mailnews/local/test/unit/test_nsIMsgPluggableStore.js52
-rw-r--r--comm/mailnews/local/test/unit/test_over2GBMailboxes.js129
-rw-r--r--comm/mailnews/local/test/unit/test_over4GBMailboxes.js640
-rw-r--r--comm/mailnews/local/test/unit/test_pop3AuthMethods.js201
-rw-r--r--comm/mailnews/local/test/unit/test_pop3Client.js145
-rw-r--r--comm/mailnews/local/test/unit/test_pop3Download.js81
-rw-r--r--comm/mailnews/local/test/unit/test_pop3DownloadTempFileHandling.js62
-rw-r--r--comm/mailnews/local/test/unit/test_pop3Duplicates.js40
-rw-r--r--comm/mailnews/local/test/unit/test_pop3FilterActions.js143
-rw-r--r--comm/mailnews/local/test/unit/test_pop3Filters.js114
-rw-r--r--comm/mailnews/local/test/unit/test_pop3GSSAPIFail.js222
-rw-r--r--comm/mailnews/local/test/unit/test_pop3GetNewMail.js147
-rw-r--r--comm/mailnews/local/test/unit/test_pop3MoveFilter.js137
-rw-r--r--comm/mailnews/local/test/unit/test_pop3MoveFilter2.js108
-rw-r--r--comm/mailnews/local/test/unit/test_pop3MultiCopy.js97
-rw-r--r--comm/mailnews/local/test/unit/test_pop3MultiCopy2.js179
-rw-r--r--comm/mailnews/local/test/unit/test_pop3Password.js162
-rw-r--r--comm/mailnews/local/test/unit/test_pop3Password2.js213
-rw-r--r--comm/mailnews/local/test/unit/test_pop3Password3.js75
-rw-r--r--comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc1939.js216
-rw-r--r--comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc2449.js215
-rw-r--r--comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc5034.js217
-rw-r--r--comm/mailnews/local/test/unit/test_pop3Proxy.js60
-rw-r--r--comm/mailnews/local/test/unit/test_pop3Pump.js32
-rw-r--r--comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMDisconnect.js127
-rw-r--r--comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMFail.js124
-rw-r--r--comm/mailnews/local/test/unit/test_preview.js40
-rw-r--r--comm/mailnews/local/test/unit/test_saveMessage.js69
-rw-r--r--comm/mailnews/local/test/unit/test_streamHeaders.js90
-rw-r--r--comm/mailnews/local/test/unit/test_undoDelete.js80
-rw-r--r--comm/mailnews/local/test/unit/test_verifyLogon.js93
-rw-r--r--comm/mailnews/local/test/unit/xpcshell.ini57
117 files changed, 25660 insertions, 0 deletions
diff --git a/comm/mailnews/local/public/moz.build b/comm/mailnews/local/public/moz.build
new file mode 100644
index 0000000000..962078a7af
--- /dev/null
+++ b/comm/mailnews/local/public/moz.build
@@ -0,0 +1,26 @@
+# 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 += [
+ "nsILocalMailIncomingServer.idl",
+ "nsIMailboxService.idl",
+ "nsIMailboxUrl.idl",
+ "nsIMsgLocalMailFolder.idl",
+ "nsIMsgParseMailMsgState.idl",
+ "nsINewsBlogFeedDownloader.idl",
+ "nsINoIncomingServer.idl",
+ "nsINoneService.idl",
+ "nsIPop3IncomingServer.idl",
+ "nsIPop3Protocol.idl",
+ "nsIPop3Service.idl",
+ "nsIPop3Sink.idl",
+ "nsIPop3URL.idl",
+ "nsIRssIncomingServer.idl",
+ "nsIRssService.idl",
+]
+
+XPIDL_MODULE = "msglocal"
+
+EXPORTS += []
diff --git a/comm/mailnews/local/public/nsILocalMailIncomingServer.idl b/comm/mailnews/local/public/nsILocalMailIncomingServer.idl
new file mode 100644
index 0000000000..11bd5543b6
--- /dev/null
+++ b/comm/mailnews/local/public/nsILocalMailIncomingServer.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/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIMsgWindow;
+interface nsIUrlListener;
+interface nsIMsgFolder;
+
+[scriptable, uuid(f465a3ee-5b29-4da6-8b2e-d764bcba468e)]
+interface nsILocalMailIncomingServer : nsISupports
+{
+ /// Create the necessary default folders that must always exist in an account (e.g. Inbox/Trash).
+ void createDefaultMailboxes();
+
+ /// Set special folder flags on the default folders.
+ void setFlagsOnDefaultMailboxes();
+
+ nsIURI getNewMail(in nsIMsgWindow aMsgWindow, in nsIUrlListener aUrlListener, in nsIMsgFolder aInbox);
+};
diff --git a/comm/mailnews/local/public/nsIMailboxService.idl b/comm/mailnews/local/public/nsIMailboxService.idl
new file mode 100644
index 0000000000..13d998844d
--- /dev/null
+++ b/comm/mailnews/local/public/nsIMailboxService.idl
@@ -0,0 +1,34 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIUrlListener.idl"
+
+interface nsIURI;
+interface nsIStreamListener;
+interface nsIMsgWindow;
+interface nsIFile;
+
+[scriptable, uuid(809FCD02-B9EA-4DC0-84F0-3FBC55AE11F1)]
+interface nsIMailboxService : nsISupports {
+
+ /*
+ * All of these functions build mailbox urls and run them. If you want a
+ * handle on the running task, pass in a valid nsIURI ptr. You can later
+ * interrupt this action by asking the netlib service manager to interrupt
+ * the url you are given back. Remember to release aURL when you are done
+ * with it. Pass nullptr in for aURL if you don't care about the returned URL.
+ */
+
+ /*
+ * Pass in a file path for the mailbox you wish to parse. You also need to
+ * pass in a mailbox parser (the consumer). The url listener can be null
+ * if you have no interest in tracking the url.
+ */
+ nsIURI ParseMailbox(in nsIMsgWindow aMsgWindow, in nsIFile aMailboxPath,
+ in nsIStreamListener aMailboxParser,
+ in nsIUrlListener aUrlListener);
+
+};
diff --git a/comm/mailnews/local/public/nsIMailboxUrl.idl b/comm/mailnews/local/public/nsIMailboxUrl.idl
new file mode 100644
index 0000000000..85aefd7343
--- /dev/null
+++ b/comm/mailnews/local/public/nsIMailboxUrl.idl
@@ -0,0 +1,58 @@
+/* -*- 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 "MailNewsTypes2.idl"
+
+interface nsIStreamListener;
+interface nsIMsgDBHdr;
+
+typedef long nsMailboxAction;
+
+[scriptable, uuid(2ac72280-90f4-4d80-8af1-5e7a1997e2a8)]
+interface nsIMailboxUrl : nsISupports {
+
+ // Mailbox urls which parse a mailbox folder require a consumer of the
+ // stream which will represent the mailbox. This consumer is the mailbox
+ // parser. As data from the mailbox folder is read in, the data will be
+ // written to a stream and the consumer will be notified through
+ // nsIStreamListenter::OnDataAvailable that the stream has data
+ // available...
+ // mscott: I wonder if the caller should be allowed to create and set
+ // the stream they want the data written to as well? Hmm....
+
+ attribute nsIStreamListener mailboxParser;
+
+ /////////////////////////////////////////////////////////////////////////
+ // Copy/Move mailbox urls require a mailbox copy handler which actually
+ // performs the copy.
+ /////////////////////////////////////////////////////////////////////////
+ attribute nsIStreamListener mailboxCopyHandler;
+
+ // Some mailbox urls include a message key for the message in question.
+ readonly attribute nsMsgKey messageKey;
+
+ // this is to support multiple msg move/copy in one url
+ void setMoveCopyMsgKeys(in Array<nsMsgKey> keysToFlag);
+ void getMoveCopyMsgHdrForIndex(in unsigned long msgIndex, out nsIMsgDBHdr msgHdr);
+ readonly attribute unsigned long numMoveCopyMsgs;
+ attribute unsigned long curMoveCopyMsgIndex;
+ // mailbox urls to fetch a mail message can specify the size of
+ // the message...
+ // this saves us the trouble of having to open up the msg db and ask
+ // ourselves...
+ attribute unsigned long messageSize;
+
+ attribute nsMailboxAction mailboxAction;
+
+ /* these are nsMailboxActions */
+ const long ActionParseMailbox = 0;
+ const long ActionFetchMessage = 1;
+ const long ActionCopyMessage = 2;
+ const long ActionMoveMessage = 3;
+ const long ActionSaveMessageToDisk = 4;
+ const long ActionAppendMessageToDisk = 5;
+ const long ActionFetchPart = 6;
+};
diff --git a/comm/mailnews/local/public/nsIMsgLocalMailFolder.idl b/comm/mailnews/local/public/nsIMsgLocalMailFolder.idl
new file mode 100644
index 0000000000..80c7da02f7
--- /dev/null
+++ b/comm/mailnews/local/public/nsIMsgLocalMailFolder.idl
@@ -0,0 +1,134 @@
+/* -*- 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"
+interface nsIMsgWindow;
+interface nsIUrlListener;
+interface nsIMsgDatabase;
+interface nsIMsgDBHdr;
+interface nsIMsgFolder;
+interface nsIMsgCopyServiceListener;
+
+[ptr] native nsLocalFolderScanState(nsLocalFolderScanState);
+
+%{C++
+/* flags for markMsgsOnPop3Server */
+#define POP3_NONE 0
+#define POP3_DELETE 1
+#define POP3_FETCH_BODY 2
+#define POP3_FORCE_DEL 3
+
+struct nsLocalFolderScanState;
+%}
+
+[scriptable, uuid(ebf7576c-e15f-4aba-b021-cc6e9266e90c)]
+interface nsIMsgLocalMailFolder : nsISupports {
+ /**
+ * Set the default flags on the subfolders of this folder, such as
+ * Drafts, Templates, etc.
+ * @param flags bitwise OR matching the type of mailboxes you want to flag.
+ * This function will be smart and find the right names.
+ * E.g. nsMsgFolderFlags::Inbox | nsMsgFolderFlags::Drafts
+ */
+ void setFlagsOnDefaultMailboxes(in unsigned long flags);
+
+ /*
+ * This will return null if the db is out of date
+ */
+ nsIMsgDatabase getDatabaseWOReparse();
+
+ /*
+ * If the db is out of date, this will return NS_ERROR_NOT_INITIALIZED
+ * and kick off an async url to reparse the messages.
+ * If aReparseUrlListener is null, folder will use itself as the listener.
+ */
+ nsIMsgDatabase getDatabaseWithReparse(in nsIUrlListener aReparseUrlListener, in nsIMsgWindow aMsgWindow);
+ void parseFolder(in nsIMsgWindow aMsgWindow, in nsIUrlListener listener);
+ void copyFolderLocal(in nsIMsgFolder srcFolder, in boolean isMove, in nsIMsgWindow msgWindow, in nsIMsgCopyServiceListener listener);
+
+ /**
+ * Does copy of same level subfolders of the srcFolder to the destination
+ * (this) local folder. If isMove is true, the messages in the subfolders are
+ * deleted (or marked deleted if source is imap) after the copy completes; so
+ * effectively, the folders are copied and only the messages are moved.
+ *
+ * @param srcFolder The folder one level above subfolders being copied
+ * @param msgWindow Window for notification callbacks, can be null
+ * @param listener Listener which receive operation notifications, can be null
+ * @param isMove If true, after copy completes, delete the source messages
+ */
+ void copyAllSubFolders(in nsIMsgFolder srcFolder, in nsIMsgWindow msgWindow,
+ in nsIMsgCopyServiceListener listener, in boolean isMove);
+
+ void onCopyCompleted(in nsISupports aSrcSupport, in boolean aMoveCopySucceeded);
+ attribute boolean checkForNewMessagesAfterParsing;
+ void markMsgsOnPop3Server(in Array<nsIMsgDBHdr> aMessages, in int32_t aMark);
+
+ /**
+ * File size on disk has possibly changed - update and notify.
+ */
+ void refreshSizeOnDisk();
+
+ /**
+ * Creates a subfolder to the current folder with the passed in folder name.
+ * @param aFolderName name of the folder to create.
+ * @return newly created folder.
+ */
+ nsIMsgFolder createLocalSubfolder(in AString aFolderName);
+
+ /**
+ * Adds a message to the end of the folder, parsing it as it goes, and
+ * applying filters, if applicable.
+ * @param aMessage string containing the entire body of the message to add
+ * @return the nsIMsgDBHdr of the added message
+ */
+ nsIMsgDBHdr addMessage(in string aMessage);
+
+ /**
+ * Add one or more messages to the end of the folder in a single batch. Each
+ * batch requires an fsync() on the mailbox file so it is a good idea to
+ * try and minimize the number of calls you make to this method or addMessage.
+ *
+ * Filters are applied, if applicable.
+ *
+ * @param aMessageCount The number of messages.
+ * @param aMessages An array of pointers to strings containing entire message
+ * bodies.
+ * @return an array of nsIMsgDBHdr of the added messages
+ */
+ Array<nsIMsgDBHdr> addMessageBatch(in Array<ACString> aMessages);
+
+ /**
+ * Functions for updating the UI while running downloadMessagesForOffline:
+ * delete the old message before adding its newly downloaded body, and
+ * select the new message after it has replaced the old one
+ */
+ void deleteDownloadMsg(in nsIMsgDBHdr aMsgHdr);
+ void notifyDelete();
+
+ /**
+ * Functions for grubbing through a folder to find the Uidl for a
+ * given msgDBHdr.
+ */
+ [noscript] void getFolderScanState(in nsLocalFolderScanState aState);
+ [noscript] void getUidlFromFolder(in nsLocalFolderScanState aState, in nsIMsgDBHdr aMsgHdr);
+
+
+ /**
+ * Shows warning if there is not enough space in the message store
+ * for a message of the given size.
+ */
+ boolean warnIfLocalFileTooBig(in nsIMsgWindow aWindow,
+ [optional] in long long aSpaceRequested);
+
+ /**
+ * Update properties on a new header from an old header, for cases where
+ * a partial message will be replaced with a full message.
+ *
+ * @param aOldHdr message header used as properties source
+ * @param aNewHdr message header used as properties destination
+ */
+ void updateNewMsgHdr(in nsIMsgDBHdr aOldHdr, in nsIMsgDBHdr aNewHdr);
+};
diff --git a/comm/mailnews/local/public/nsIMsgParseMailMsgState.idl b/comm/mailnews/local/public/nsIMsgParseMailMsgState.idl
new file mode 100644
index 0000000000..77c92b72df
--- /dev/null
+++ b/comm/mailnews/local/public/nsIMsgParseMailMsgState.idl
@@ -0,0 +1,46 @@
+/* -*- 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 "nsISupports.idl"
+#include "MailNewsTypes2.idl" // for nsMsgKey typedef
+
+interface nsIMsgDatabase;
+interface nsIMsgDBHdr;
+
+typedef long nsMailboxParseState;
+
+[scriptable, uuid(0d44646c-0759-43a2-954d-dc2a9a9660ec)]
+interface nsIMsgParseMailMsgState : nsISupports {
+ void SetMailDB(in nsIMsgDatabase aDatabase);
+ /*
+ * Set a backup mail database, whose data will be read during parsing to
+ * attempt to recover message metadata
+ *
+ * @param aDatabase the backup database
+ */
+ void SetBackupMailDB(in nsIMsgDatabase aDatabase);
+ void Clear();
+
+ void ParseAFolderLine(in string line, in unsigned long lineLength);
+ /// db header for message we're currently parsing
+ attribute nsIMsgDBHdr newMsgHdr;
+ void FinishHeader();
+
+ long GetAllHeaders(out string headers);
+ readonly attribute string headers;
+ attribute nsMailboxParseState state;
+ /* these are nsMailboxParseState */
+ const long ParseEnvelopeState=0;
+ const long ParseHeadersState=1;
+ const long ParseBodyState=2;
+
+ /**
+ * Set the key to be used for the new message header.
+ *
+ * @param aNewKey the new db key
+ *
+ */
+ void setNewKey(in nsMsgKey aKey);
+};
diff --git a/comm/mailnews/local/public/nsINewsBlogFeedDownloader.idl b/comm/mailnews/local/public/nsINewsBlogFeedDownloader.idl
new file mode 100644
index 0000000000..c87320fb47
--- /dev/null
+++ b/comm/mailnews/local/public/nsINewsBlogFeedDownloader.idl
@@ -0,0 +1,32 @@
+/* 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 nsIMsgFolder;
+interface nsIUrlListener;
+interface nsIMsgWindow;
+
+[scriptable, uuid(86e5bd0e-c324-11e3-923a-00269e4fddc1)]
+interface nsINewsBlogFeedDownloader : nsISupports
+{
+ void downloadFeed(in nsIMsgFolder aFolder,
+ in nsIUrlListener aUrlListener,
+ in bool aIsBiff,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Called when the RSS Incoming Server detects a change to an RSS folder name,
+ * such as delete (move to trash), move/copy, or rename. We then need to update
+ * the feeds.rdf subscriptions data source.
+ *
+ * @param nsIMsgFolder aFolder - the folder, new if rename or target of
+ * move/copy folder (new parent)
+ * @param nsIMsgFolder aOrigFolder - original folder
+ * @param string aAction - "move" or "copy" or "rename"
+ */
+ void updateSubscriptionsDS(in nsIMsgFolder aFolder,
+ in nsIMsgFolder aOrigFolder,
+ in string aAction);
+};
diff --git a/comm/mailnews/local/public/nsINoIncomingServer.idl b/comm/mailnews/local/public/nsINoIncomingServer.idl
new file mode 100644
index 0000000000..717689f58b
--- /dev/null
+++ b/comm/mailnews/local/public/nsINoIncomingServer.idl
@@ -0,0 +1,16 @@
+/* -*- 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"
+
+[scriptable, uuid(dacd4917-ddbe-47bb-8a49-6a8d91c49a86)]
+interface nsINoIncomingServer : nsISupports {
+ /**
+ * Copy the default messages (e.g. Templates) from
+ * bin/defaults/messenger/<folderNameOnDisk> to <rootFolder>/<folderNameOnDisk>.
+ * This is useful when first creating the standard folders (like Templates).
+ */
+ void copyDefaultMessages(in string folderNameOnDisk);
+};
diff --git a/comm/mailnews/local/public/nsINoneService.idl b/comm/mailnews/local/public/nsINoneService.idl
new file mode 100644
index 0000000000..045a018fff
--- /dev/null
+++ b/comm/mailnews/local/public/nsINoneService.idl
@@ -0,0 +1,11 @@
+/* -*- 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 "nsISupports.idl"
+
+[scriptable, uuid(14714890-1dd2-11b2-87de-d265839520d6)]
+interface nsINoneService : nsISupports {
+ /* nothing yet, but soon. */
+};
diff --git a/comm/mailnews/local/public/nsIPop3IncomingServer.idl b/comm/mailnews/local/public/nsIPop3IncomingServer.idl
new file mode 100644
index 0000000000..b1433f5676
--- /dev/null
+++ b/comm/mailnews/local/public/nsIPop3IncomingServer.idl
@@ -0,0 +1,33 @@
+/* -*- 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 nsIPop3Protocol;
+interface nsIMsgFolder;
+interface nsIUrlListener;
+interface nsIMsgWindow;
+
+[scriptable, uuid(8494584a-49b7-49df-9001-80ccdd0b50aa)]
+interface nsIPop3IncomingServer : nsISupports {
+ attribute boolean leaveMessagesOnServer;
+ attribute boolean headersOnly;
+ attribute boolean deleteMailLeftOnServer;
+ attribute unsigned long pop3CapabilityFlags;
+ attribute boolean deleteByAgeFromServer;
+ attribute long numDaysToLeaveOnServer;
+ // client adds uidls to mark one by one, then calls markMessages
+ void addUidlToMark(in string aUidl, in int32_t newStatus);
+ // TODO: make this async.
+ void markMessages();
+ /* account to which this server defers storage, for global inbox */
+ attribute ACString deferredToAccount;
+ // whether get new mail in deferredToAccount gets
+ // new mail with this server.
+ attribute boolean deferGetNewMail;
+ void downloadMailFromServers(
+ in Array<nsIPop3IncomingServer> aServers, in nsIMsgWindow aMsgWindow,
+ in nsIMsgFolder aFolder, in nsIUrlListener aListener);
+};
diff --git a/comm/mailnews/local/public/nsIPop3Protocol.idl b/comm/mailnews/local/public/nsIPop3Protocol.idl
new file mode 100644
index 0000000000..8263f94a55
--- /dev/null
+++ b/comm/mailnews/local/public/nsIPop3Protocol.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/. */
+
+#include "nsISupports.idl"
+
+[ptr] native Pop3UidlEntryArrayRef(nsTArray<Pop3UidlEntry*>);
+
+%{C++
+#include "nsTArray.h"
+struct Pop3UidlEntry;
+%}
+
+[scriptable, uuid(3aff0550-87de-4337-9bc1-c84eb5462afe)]
+interface nsIPop3Protocol : nsISupports {
+ /* aUidl is an array of pointers to Pop3UidlEntry's. That structure is
+ * currently defined in nsPop3Protocol.h, perhaps it should be here
+ * instead...
+ */
+ [noscript] void markMessages(in Pop3UidlEntryArrayRef aUidl);
+ boolean checkMessage(in string aUidl);
+};
diff --git a/comm/mailnews/local/public/nsIPop3Service.idl b/comm/mailnews/local/public/nsIPop3Service.idl
new file mode 100644
index 0000000000..4c3e9a28ee
--- /dev/null
+++ b/comm/mailnews/local/public/nsIPop3Service.idl
@@ -0,0 +1,128 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIUrlListener.idl"
+#include "nsIPop3IncomingServer.idl"
+#include "nsIMsgFolder.idl"
+
+interface nsIURI;
+interface nsIMsgWindow;
+interface nsIMsgFolder;
+
+[scriptable, uuid(7302fd8e-946f-4ae3-9468-0fb3a7706c51)]
+interface nsIPop3ServiceListener : nsISupports {
+ /**
+ * Notification that a pop3 download has started.
+ *
+ * @param aFolder folder in which the download is started.
+ */
+ void onDownloadStarted(in nsIMsgFolder aFolder);
+
+ /**
+ * Notification about download progress.
+ *
+ * @param aFolder folder in which the download is happening.
+ * @param aNumDownloaded number of the messages that have been downloaded.
+ * @param aTotalToDownload total number of messages to download.
+ */
+ void onDownloadProgress(in nsIMsgFolder aFolder,
+ in unsigned long aNumDownloaded,
+ in unsigned long aTotalToDownload);
+
+ /**
+ * Notification that a download has completed.
+ *
+ * @param aFolder folder to which the download has completed.
+ * @param aNumberOfMessages number of the messages that were downloaded.
+ */
+ void onDownloadCompleted(in nsIMsgFolder aFolder,
+ in unsigned long aNumberOfMessages);
+};
+
+/*
+ * The Pop3 Service is an interface designed to make building and running
+ * pop3 urls easier.
+ */
+[scriptable, uuid(96d3cc14-a842-4cdf-98f8-a4cc695f8b3b)]
+interface nsIPop3Service : nsISupports {
+ /*
+ * All of these functions build pop3 urls and run them. If you want
+ * a handle on the running task, pass in a valid nsIURI ptr. You can later
+ * interrupt this action by asking the netlib service manager to interrupt
+ * the url you are given back. Remember to release aURL when you are
+ * done with it. Pass nullptr in for aURL if you
+ * don't care about the returned URL.
+ */
+
+ /*
+ * right now getting new mail doesn't require any user specific data.
+ * We use the default current identity for this information. I suspect that
+ * we'll eventually pass in an identity to this call so you can get
+ * mail on different pop3 accounts....
+ */
+
+ nsIURI GetNewMail(in nsIMsgWindow aMsgWindow, in nsIUrlListener aUrlListener,
+ in nsIMsgFolder aInbox, in nsIPop3IncomingServer popServer);
+
+ nsIURI CheckForNewMail(in nsIMsgWindow aMsgWindow, in nsIUrlListener aUrlListener,
+ in nsIMsgFolder inbox, in nsIPop3IncomingServer popServer);
+
+ /**
+ * Verify that we can logon
+ *
+ * @param aServer - pop3 server we're logging on to.
+ * @param aUrlListener - gets called back with success or failure.
+ * @param aMsgWindow - nsIMsgWindow to use for notification callbacks.
+ * @return - the url that we run.
+ *
+ */
+ nsIURI verifyLogon(in nsIMsgIncomingServer aServer,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Add a listener for pop3 events like message download. This is
+ * used by the activity manager.
+ *
+ * @param aListener listener that gets notified of pop3 events.
+ */
+ void addListener(in nsIPop3ServiceListener aListener);
+
+ /**
+ * Remove a listener for pop3 events like message download.
+ *
+ * @param aListener listener to remove.
+ */
+ void removeListener(in nsIPop3ServiceListener aListener);
+
+ /**
+ * Send the notification that a pop3 download has started.
+ * This is called from the nsIPop3Sink code.
+ *
+ * @param aFolder folder in which the download is started.
+ */
+ void notifyDownloadStarted(in nsIMsgFolder aFolder);
+
+ /**
+ * Send notification about download progress.
+ *
+ * @param aFolder folder in which the download is happening.
+ * @param aNumDownloaded number of the messages that have been downloaded.
+ * @param aTotalToDownload total number of messages to download.
+ */
+ void notifyDownloadProgress(in nsIMsgFolder aFolder,
+ in unsigned long aNumDownloaded,
+ in unsigned long aTotalToDownload);
+ /**
+ * Send the notification that a download has completed.
+ * This is called from the nsIPop3Sink code.
+ *
+ * @param aFolder folder to which the download has completed.
+ * @param aNumberOfMessages number of the messages that were downloaded.
+ */
+ void notifyDownloadCompleted(in nsIMsgFolder aFolder,
+ in unsigned long aNumberOfMessages);
+};
diff --git a/comm/mailnews/local/public/nsIPop3Sink.idl b/comm/mailnews/local/public/nsIPop3Sink.idl
new file mode 100644
index 0000000000..0291329a2c
--- /dev/null
+++ b/comm/mailnews/local/public/nsIPop3Sink.idl
@@ -0,0 +1,42 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIPop3IncomingServer.idl"
+#include "nsIMsgFolder.idl"
+
+interface nsIURI;
+
+[scriptable, uuid(ceabfc6b-f139-4c25-890f-efb7c3069d40)]
+interface nsIPop3Sink : nsISupports {
+
+ attribute boolean buildMessageUri;
+ attribute AUTF8String messageUri;
+ attribute AUTF8String baseMessageUri;
+
+ /// message uri for header-only message version
+ attribute AUTF8String origMessageUri;
+
+ boolean beginMailDelivery(in boolean uidlDownload, in nsIMsgWindow msgWindow);
+ void endMailDelivery(in nsIPop3Protocol protocol);
+ void abortMailDelivery(in nsIPop3Protocol protocol);
+
+ void incorporateBegin(in string uidlString, in unsigned long flags);
+ void incorporateWrite(in string block, in long length);
+ void incorporateComplete(in nsIMsgWindow aMsgWindow, in int32_t aSize);
+ void incorporateAbort(in boolean uidlDownload);
+
+ /**
+ * Tell the pop3sink how many messages we're going to download.
+ *
+ * @param aNumMessages how many messages we're going to download.
+ */
+ void setMsgsToDownload(in unsigned long aNumMessages);
+
+ void setBiffStateAndUpdateFE(in unsigned long biffState, in long numNewMessages, in boolean notify);
+
+ attribute nsIPop3IncomingServer popServer;
+ attribute nsIMsgFolder folder;
+};
diff --git a/comm/mailnews/local/public/nsIPop3URL.idl b/comm/mailnews/local/public/nsIPop3URL.idl
new file mode 100644
index 0000000000..3fcd66e1a3
--- /dev/null
+++ b/comm/mailnews/local/public/nsIPop3URL.idl
@@ -0,0 +1,19 @@
+/* -*- 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 "nsIPop3Sink.idl"
+
+[scriptable, uuid(5fb87ae7-a3a0-440a-8b49-6bca42fb7ff2)]
+interface nsIPop3URL : nsISupports {
+ attribute nsIPop3Sink pop3Sink;
+ attribute AUTF8String messageUri;
+
+ /// Constant for the default POP3 port number
+ const int32_t DEFAULT_POP3_PORT = 110;
+
+ /// Constant for the default POP3 over ssl port number
+ const int32_t DEFAULT_POP3S_PORT = 995;
+};
diff --git a/comm/mailnews/local/public/nsIRssIncomingServer.idl b/comm/mailnews/local/public/nsIRssIncomingServer.idl
new file mode 100644
index 0000000000..aa5373126b
--- /dev/null
+++ b/comm/mailnews/local/public/nsIRssIncomingServer.idl
@@ -0,0 +1,16 @@
+/* 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;
+
+[scriptable, uuid(6d744e7f-2218-45c6-8734-998a56cb3c6d)]
+interface nsIRssIncomingServer : nsISupports {
+ // Path to the subscriptions file for this RSS server.
+ readonly attribute nsIFile subscriptionsPath;
+
+ // Path to the feed items file for this RSS server.
+ readonly attribute nsIFile feedItemsPath;
+};
diff --git a/comm/mailnews/local/public/nsIRssService.idl b/comm/mailnews/local/public/nsIRssService.idl
new file mode 100644
index 0000000000..8f5e94af55
--- /dev/null
+++ b/comm/mailnews/local/public/nsIRssService.idl
@@ -0,0 +1,10 @@
+/* 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(4b31bdd9-6f4c-46d4-a766-a94f11b599bc)]
+interface nsIRssService : nsISupports
+{
+};
diff --git a/comm/mailnews/local/src/Pop3Channel.jsm b/comm/mailnews/local/src/Pop3Channel.jsm
new file mode 100644
index 0000000000..4fae72894b
--- /dev/null
+++ b/comm/mailnews/local/src/Pop3Channel.jsm
@@ -0,0 +1,105 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["Pop3Channel"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ Pop3Client: "resource:///modules/Pop3Client.jsm",
+});
+
+/**
+ * A channel to interact with POP3 server.
+ *
+ * @implements {nsIChannel}
+ * @implements {nsIRequest}
+ */
+class Pop3Channel {
+ QueryInterface = ChromeUtils.generateQI(["nsIChannel", "nsIRequest"]);
+
+ _logger = console.createInstance({
+ prefix: "mailnews.pop3",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mailnews.pop3.loglevel",
+ });
+
+ /**
+ * @param {nsIURI} uri - The uri to construct the channel from.
+ * @param {nsILoadInfo} loadInfo - The loadInfo associated with the channel.
+ */
+ constructor(uri, loadInfo) {
+ this._server = MailServices.accounts
+ .findServerByURI(uri)
+ .QueryInterface(Ci.nsIPop3IncomingServer);
+
+ // nsIChannel attributes.
+ this.originalURI = uri;
+ this.URI = uri;
+ this.loadInfo = loadInfo;
+ this.contentLength = 0;
+ }
+
+ /**
+ * @see nsIRequest
+ */
+ get status() {
+ return Cr.NS_OK;
+ }
+
+ /**
+ * @see nsIChannel
+ */
+ get contentType() {
+ return this._contentType || "message/rfc822";
+ }
+
+ set contentType(value) {
+ this._contentType = value;
+ }
+
+ get isDocument() {
+ return true;
+ }
+
+ open() {
+ throw Components.Exception(
+ "Pop3Channel.open() not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ asyncOpen(listener) {
+ this._logger.debug(`asyncOpen ${this.URI.spec}`);
+ let match = this.URI.spec.match(/pop3?:\/\/.+\/(?:\?|&)uidl=([^&]+)/);
+ let uidl = decodeURIComponent(match?.[1] || "");
+ if (!uidl) {
+ throw Components.Exception(
+ `Unrecognized url=${this.URI.spec}`,
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+
+ let client = new lazy.Pop3Client(this._server);
+ client.runningUri = this.URI;
+ client.connect();
+ client.onOpen = () => {
+ listener.onStartRequest(this);
+ client.fetchBodyForUidl(
+ this.URI.QueryInterface(Ci.nsIPop3URL).pop3Sink,
+ uidl
+ );
+ };
+ client.onDone = status => {
+ listener.onStopRequest(this, status);
+ };
+ }
+}
diff --git a/comm/mailnews/local/src/Pop3Client.jsm b/comm/mailnews/local/src/Pop3Client.jsm
new file mode 100644
index 0000000000..59e21a66f7
--- /dev/null
+++ b/comm/mailnews/local/src/Pop3Client.jsm
@@ -0,0 +1,1570 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["Pop3Client"];
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var { CommonUtils } = ChromeUtils.importESModule(
+ "resource://services-common/utils.sys.mjs"
+);
+var { CryptoUtils } = ChromeUtils.importESModule(
+ "resource://services-crypto/utils.sys.mjs"
+);
+var { LineReader } = ChromeUtils.import("resource:///modules/LineReader.jsm");
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailStringUtils } = ChromeUtils.import(
+ "resource:///modules/MailStringUtils.jsm"
+);
+var { Pop3Authenticator } = ChromeUtils.import(
+ "resource:///modules/MailAuthenticator.jsm"
+);
+
+/**
+ * A structure to represent a response received from the server. A response can
+ * be a single status line of a multi-line data block.
+ *
+ * @typedef {object} Pop3Response
+ * @property {boolean} success - True for a positive status indicator ("+OK","+").
+ * @property {string} status - The status indicator, can be "+OK", "-ERR" or "+".
+ * @property {string} statusText - The status line of the response excluding the
+ * status indicator.
+ * @property {string} data - The part of a multi-line data block excluding the
+ * status line.
+ *
+ * A single char to represent a uidl status, possible values are:
+ * - 'k'=KEEP,
+ * - 'd'=DELETE
+ * - 'b'=TOO_BIG
+ * - 'f'=FETCH_BODY
+ * @typedef {string} UidlStatus
+ */
+
+const UIDL_KEEP = "k";
+const UIDL_DELETE = "d";
+const UIDL_TOO_BIG = "b";
+const UIDL_FETCH_BODY = "f";
+
+// There can be multiple Pop3Client running concurrently, assign each logger a
+// unique prefix.
+let loggerId = 0;
+
+function getLoggerId() {
+ return loggerId++ % 1000;
+}
+
+/**
+ * A class to interact with POP3 server.
+ */
+class Pop3Client {
+ /**
+ * @param {nsIPop3IncomingServer} server - The associated server instance.
+ */
+ constructor(server) {
+ this._server = server.QueryInterface(Ci.nsIMsgIncomingServer);
+ this._server.wrappedJSObject.runningClient = this;
+ this._authenticator = new Pop3Authenticator(server);
+ this._lineReader = new LineReader();
+
+ // Somehow, Services.io.newURI("pop3://localhost") doesn't work, what we
+ // need is just a valid nsIMsgMailNewsUrl to propagate OnStopRunningUrl and
+ // secInfo.
+ this.runningUri = Services.io
+ .newURI(`smtp://${this._server.hostName}:${this._server.port}`)
+ .mutate()
+ .setScheme("pop3")
+ .finalize()
+ .QueryInterface(Ci.nsIMsgMailNewsUrl);
+
+ // A list of auth methods detected from the EHLO response.
+ this._supportedAuthMethods = [];
+ // A list of auth methods that worth a try.
+ this._possibleAuthMethods = [];
+ // Auth method set by user preference.
+ this._preferredAuthMethods =
+ {
+ [Ci.nsMsgAuthMethod.passwordCleartext]: ["USERPASS", "PLAIN", "LOGIN"],
+ [Ci.nsMsgAuthMethod.passwordEncrypted]: ["CRAM-MD5"],
+ [Ci.nsMsgAuthMethod.GSSAPI]: ["GSSAPI"],
+ [Ci.nsMsgAuthMethod.NTLM]: ["NTLM"],
+ [Ci.nsMsgAuthMethod.OAuth2]: ["XOAUTH2"],
+ [Ci.nsMsgAuthMethod.secure]: ["CRAM-MD5", "GSSAPI"],
+ }[server.authMethod] || [];
+ // The next auth method to try if the current failed.
+ this._nextAuthMethod = null;
+
+ this._sink = Cc["@mozilla.org/messenger/pop3-sink;1"].createInstance(
+ Ci.nsIPop3Sink
+ );
+ this._sink.popServer = server;
+
+ this._logger = console.createInstance({
+ prefix: `mailnews.pop3.${getLoggerId()}`,
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mailnews.pop3.loglevel",
+ });
+
+ this.onReady = () => {};
+
+ this._cutOffTimestamp = -1;
+ if (
+ this._server.deleteByAgeFromServer &&
+ this._server.numDaysToLeaveOnServer
+ ) {
+ // We will send DELE request for messages received before this timestamp.
+ this._cutOffTimestamp =
+ Date.now() / 1000 - this._server.numDaysToLeaveOnServer * 24 * 60 * 60;
+ }
+
+ this._maxMessageSize = Infinity;
+ if (this._server.limitOfflineMessageSize) {
+ this._maxMessageSize = this._server.maxMessageSize
+ ? this._server.maxMessageSize * 1024
+ : 50 * 1024;
+ }
+
+ this._messagesToHandle = [];
+ }
+
+ /**
+ * Initiate a connection to the server
+ */
+ connect() {
+ let hostname = this._server.hostName.toLowerCase();
+ this._logger.debug(`Connecting to pop://${hostname}:${this._server.port}`);
+ this.runningUri
+ .QueryInterface(Ci.nsIMsgMailNewsUrl)
+ .SetUrlState(true, Cr.NS_OK);
+ this._server.serverBusy = true;
+ this._secureTransport = this._server.socketType == Ci.nsMsgSocketType.SSL;
+ this._socket = new TCPSocket(hostname, this._server.port, {
+ binaryType: "arraybuffer",
+ useSecureTransport: this._secureTransport,
+ });
+ this._socket.onopen = this._onOpen;
+ this._socket.onerror = this._onError;
+
+ this._authenticating = false;
+ // Indicates if the connection has been closed and can't be used anymore.
+ this._destroyed = false;
+ // Save the incomplete server payload, start parsing after seeing \r\n.
+ this._pendingPayload = "";
+ }
+
+ /**
+ * Check and fetch new mails.
+ *
+ * @param {boolean} downloadMail - Whether to download mails using TOP/RETR.
+ * @param {nsIMsgWindow} msgWindow - The associated msg window.
+ * @param {nsIMsgFolder} folder - The folder to save the messages to.
+ */
+ async getMail(downloadMail, msgWindow, folder) {
+ this._downloadMail = downloadMail;
+ this._msgWindow = msgWindow;
+ this._sink.folder = folder;
+ this._actionAfterAuth = this._actionStat;
+ this.urlListener.OnStartRunningUrl(this.runningUri, Cr.NS_OK);
+
+ await this._loadUidlState();
+ this._actionCapa();
+ }
+
+ /**
+ * Verify that we can logon to the server. Exit after auth success/failure.
+ *
+ * @param {nsIMsgWindow} msgWindow - The associated msg window.
+ */
+ verifyLogon(msgWindow) {
+ this._msgWindow = msgWindow;
+ this._verifyLogon = true;
+ this._actionAfterAuth = this._actionDone;
+ this._actionCapa();
+ }
+
+ /**
+ * Fetch the full message of a uidl.
+ *
+ * @param {nsIPop3Sink} sink - The sink to use for this request.
+ * @param {string} uidl - The uidl of the message to fetch.
+ */
+ async fetchBodyForUidl(sink, uidl) {
+ this._logger.debug(`Fetching body for uidl=${uidl}`);
+
+ this._downloadMail = true;
+ this._sink = sink;
+ this._sink.buildMessageUri = true;
+ this.urlListener = sink.folder.QueryInterface(Ci.nsIUrlListener);
+ this.urlListener.OnStartRunningUrl(this.runningUri, Cr.NS_OK);
+
+ await this._loadUidlState();
+
+ let uidlState = this._uidlMap.get(uidl);
+ if (!uidlState) {
+ // This uidl is no longer on the server, use this._sink to delete the
+ // msgHdr.
+ try {
+ this._sink.beginMailDelivery(true, null);
+ this._folderLocked = true;
+ this._logger.debug(
+ `Folder lock acquired uri=${this._sink.folder.URI}.`
+ );
+ this._sink.incorporateBegin(uidl, 0);
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ } catch (e) {
+ this._actionError("pop3MessageWriteError");
+ }
+ return;
+ }
+ if (uidlState.status != UIDL_TOO_BIG) {
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+
+ this._singleUidlToDownload = uidl;
+ this._uidlMap.set(uidl, {
+ ...uidlState,
+ status: UIDL_FETCH_BODY,
+ });
+ this._actionAfterAuth = this._actionStat;
+ this._actionCapa();
+ }
+
+ /**
+ * Mark uidl status by a passed in Map, then write to popstate.dat.
+ *
+ * @param {Map<string, UidlStatus>} uidlsToMark - A Map from uidl to status.
+ */
+ async markMessages(uidlsToMark) {
+ this._logger.debug("markMessages", uidlsToMark);
+ if (!this._uidlMap) {
+ this._loadUidlState();
+ }
+ // Callers of nsIPop3IncomingServer.markMessages (e.g. filters) expect it to
+ // act as a sync function, otherwise, the flags set by filters may not take
+ // effect.
+ Services.tm.spinEventLoopUntil(
+ "nsIPop3IncomingServer.markMessages is a synchronous function",
+ () => {
+ return this._uidlMap;
+ }
+ );
+ for (let [uidl, status] of uidlsToMark) {
+ let uidlState = this._uidlMap.get(uidl);
+ this._uidlMap.set(uidl, {
+ ...uidlState,
+ status,
+ });
+ this._uidlMapChanged = true;
+ }
+ await this._writeUidlState(true);
+ }
+
+ /**
+ * Send `QUIT` request to the server.
+ * @param {Function} nextAction - Callback function after QUIT response.
+ */
+ quit(nextAction) {
+ this._onData = () => {};
+ this._onError = () => {};
+ if (this._socket?.readyState == "open") {
+ this._send("QUIT");
+ this._nextAction = nextAction || this.close;
+ } else if (nextAction) {
+ nextAction();
+ }
+ }
+
+ /**
+ * Close the socket.
+ */
+ close = () => {
+ this._socket.close();
+ };
+
+ /**
+ * The open event handler.
+ */
+ _onOpen = () => {
+ this._logger.debug("Connected");
+ this._socket.ondata = this._onData;
+ this._socket.onclose = this._onClose;
+ this._nextAction = res => {
+ // See if there is an APOP timestamp.
+ // eslint-disable-next-line no-control-regex
+ let matches = res.statusText.match(/<[\x00-\x7F]+@[\x00-\x7F]+>/);
+ if (matches?.[0]) {
+ this._apopTimestamp = matches[0];
+ }
+ this.onOpen();
+ };
+ this._socket.transport.setTimeout(
+ Ci.nsISocketTransport.TIMEOUT_READ_WRITE,
+ Services.prefs.getIntPref("mailnews.tcptimeout")
+ );
+ };
+
+ /**
+ * Parse the server response.
+ *
+ * @param {string} str - Response received from the server.
+ * @returns {Pop3Response}
+ */
+ _parse(str) {
+ if (this._lineReader.processingMultiLineResponse) {
+ // When processing multi-line response, no parsing should happen. If
+ // `+something` is treated as status line, _actionRetrResponse will treat
+ // it as a new message.
+ return { data: str };
+ }
+ let matches = /^(\+OK|-ERR|\+) ?(.*)\r\n([^]*)/.exec(str);
+ if (matches) {
+ let [, status, statusText, data] = matches;
+ return { success: status != "-ERR", status, statusText, data };
+ }
+ return { data: str };
+ }
+
+ /**
+ * The data event handler.
+ *
+ * @param {TCPSocketEvent} event - The data event.
+ */
+ _onData = async event => {
+ // Some servers close the socket on invalid username/password, this line
+ // guarantees onclose is handled before we try another AUTH method. See the
+ // same handling in SmtpClient.jsm.
+ await new Promise(resolve => setTimeout(resolve));
+
+ let stringPayload = CommonUtils.arrayBufferToByteString(
+ new Uint8Array(event.data)
+ );
+ this._logger.debug(`S: ${stringPayload}`);
+ if (this._pendingPayload) {
+ stringPayload = this._pendingPayload + stringPayload;
+ }
+ if (stringPayload.includes("\r\n")) {
+ // Start parsing if the payload contains at least one line break.
+ this._pendingPayload = "";
+ let res = this._parse(stringPayload);
+ this._nextAction?.(res);
+ } else {
+ // Save the incomplete payload for the next ondata event.
+ this._pendingPayload = stringPayload;
+ }
+ };
+
+ /**
+ * The error event handler.
+ *
+ * @param {TCPSocketErrorEvent} event - The error event.
+ */
+ _onError = async event => {
+ this._logger.error(`${event.name}: a ${event.message} error occurred`);
+ this._server.serverBusy = false;
+ this.quit();
+ let secInfo =
+ await event.target.transport?.tlsSocketControl?.asyncGetSecurityInfo();
+ if (secInfo) {
+ this._logger.error(`SecurityError info: ${secInfo.errorCodeString}`);
+ if (secInfo.failedCertChain.length) {
+ let chain = secInfo.failedCertChain.map(c => {
+ return c.commonName + "; serial# " + c.serialNumber;
+ });
+ this._logger.error(`SecurityError cert chain: ${chain.join(" <- ")}`);
+ }
+ this.runningUri.failedSecInfo = secInfo;
+ // Notify about the error directly. Due to the await above, the _onClose
+ // event is likely to complete before we get here, which means _actionDone
+ // ran and won't run again.
+ this.urlListener.OnStopRunningUrl(this.runningUri, event.errorCode);
+ }
+ this._actionDone(event.errorCode);
+ };
+
+ /**
+ * The close event handler.
+ */
+ _onClose = () => {
+ this._logger.debug("Connection closed.");
+ this._server.serverBusy = false;
+ this._destroyed = true;
+ if (this._authenticating) {
+ // In some cases, socket is closed for invalid username/password.
+ this._actionAuthResponse({ success: false });
+ } else {
+ this._actionDone();
+ }
+ };
+
+ _lineSeparator = AppConstants.platform == "win" ? "\r\n" : "\n";
+
+ /**
+ * Read popstate.dat into this._uidlMap.
+ */
+ async _loadUidlState() {
+ let stateFile = this._server.localPath;
+ stateFile.append("popstate.dat");
+ if (!(await IOUtils.exists(stateFile.path))) {
+ this._uidlMap = new Map();
+ return;
+ }
+
+ let content = await IOUtils.readUTF8(stateFile.path);
+ this._uidlMap = new Map();
+ let uidlLine = false;
+ for (let line of content.split(this._lineSeparator)) {
+ if (!line) {
+ continue;
+ }
+ if (uidlLine) {
+ let [status, uidl, receivedAt] = line.split(" ");
+ this._uidlMap.set(uidl, {
+ status, // @type {UidlStatus}
+ uidl,
+ receivedAt,
+ });
+ }
+ if (line.startsWith("#")) {
+ // A comment line.
+ continue;
+ }
+ if (line.startsWith("*")) {
+ // The host & user line.
+ uidlLine = true;
+ }
+ }
+ }
+
+ /**
+ * Write this._uidlMap into popstate.dat.
+ *
+ * @param {boolean} [resetFlag] - If true, reset _uidlMapChanged to false.
+ */
+ async _writeUidlState(resetFlag) {
+ if (!this._uidlMapChanged) {
+ return;
+ }
+
+ let stateFile = this._server.localPath;
+ stateFile.append("popstate.dat");
+ let content = [
+ "# POP3 State File",
+ "# This is a generated file! Do not edit.",
+ "",
+ `*${this._server.hostName} ${this._server.username}`,
+ ];
+ for (let msg of this._messagesToHandle) {
+ // _messagesToHandle is not empty means an error happened, put them back
+ // to _uidlMap to prevent loss of popstate.
+ this._uidlMap.set(msg.uidl, msg);
+ }
+ for (let { status, uidl, receivedAt } of this._uidlMap.values()) {
+ if (receivedAt) {
+ content.push(`${status} ${uidl} ${receivedAt}`);
+ }
+ }
+ this._writeUidlPromise = IOUtils.writeUTF8(
+ stateFile.path,
+ content.join(this._lineSeparator)
+ );
+ await this._writeUidlPromise;
+ this._writeUidlPromise = null;
+
+ if (resetFlag) {
+ this._uidlMapChanged = false;
+ }
+ }
+
+ /**
+ * Send a command to the server.
+ *
+ * @param {string} str - The command string to send.
+ * @param {boolean} [suppressLogging=false] - Whether to suppress logging the str.
+ */
+ _send(str, suppressLogging) {
+ if (this._socket?.readyState != "open") {
+ if (str != "QUIT") {
+ this._logger.warn(
+ `Socket state is ${this._socket?.readyState} - won't send command.`
+ );
+ }
+ return;
+ }
+
+ if (suppressLogging && AppConstants.MOZ_UPDATE_CHANNEL != "default") {
+ this._logger.debug(
+ "C: Logging suppressed (it probably contained auth information)"
+ );
+ } else {
+ // Do not suppress for non-release builds, so that debugging auth problems
+ // is easier.
+ this._logger.debug(`C: ${str}`);
+ }
+
+ this._socket.send(CommonUtils.byteStringToArrayBuffer(str + "\r\n").buffer);
+ }
+
+ /**
+ * Send `CAPA` request to the server.
+ */
+ _actionCapa = () => {
+ this._nextAction = this._actionCapaResponse;
+ this._capabilities = [];
+ this._newMessageDownloaded = 0;
+ this._newMessageTotal = 0;
+ this._send("CAPA");
+ };
+
+ /**
+ * Handle `CAPA` response.
+ *
+ * @param {Pop3Response} res - CAPA response received from the server.
+ */
+ _actionCapaResponse = res => {
+ if (res.status && !res.success) {
+ this._actionChooseFirstAuthMethod();
+ return;
+ }
+ this._lineReader.read(
+ res.data,
+ line => {
+ line = line.trim().toUpperCase();
+ if (line == "USER") {
+ this._supportedAuthMethods.push("USERPASS");
+ } else if (line.startsWith("SASL ")) {
+ this._supportedAuthMethods.push(...line.slice(5).split(" "));
+ } else {
+ this._capabilities.push(line.split(" ")[0]);
+ }
+ },
+ () => this._actionChooseFirstAuthMethod()
+ );
+ };
+
+ /**
+ * Decide the first auth method to try.
+ */
+ _actionChooseFirstAuthMethod = () => {
+ if (
+ [
+ Ci.nsMsgSocketType.trySTARTTLS,
+ Ci.nsMsgSocketType.alwaysSTARTTLS,
+ ].includes(this._server.socketType) &&
+ !this._secureTransport
+ ) {
+ if (this._capabilities.includes("STLS")) {
+ // Init STARTTLS negotiation if required by user pref and supported.
+ this._nextAction = this._actionStlsResponse;
+ // STLS is the POP3 command to init STARTTLS.
+ this._send("STLS");
+ } else {
+ // Abort if not supported.
+ this._logger.error("Server doesn't support STLS. Aborting.");
+ this._actionError("nsErrorCouldNotConnectViaTls");
+ }
+ return;
+ }
+
+ // If a preferred method is not supported by the server, no need to try it.
+ this._possibleAuthMethods = this._preferredAuthMethods.filter(x =>
+ this._supportedAuthMethods.includes(x)
+ );
+ if (!this._possibleAuthMethods.length) {
+ if (this._server.authMethod == Ci.nsMsgAuthMethod.passwordCleartext) {
+ this._possibleAuthMethods.unshift("USERPASS");
+ } else if (
+ this._server.authMethod == Ci.nsMsgAuthMethod.passwordEncrypted
+ ) {
+ this._possibleAuthMethods.unshift(
+ this._apopTimestamp ? "APOP" : "CRAM-MD5"
+ );
+ } else if (this._server.authMethod == Ci.nsMsgAuthMethod.GSSAPI) {
+ this._possibleAuthMethods.unshift("GSSAPI");
+ } else if (this._server.authMethod == Ci.nsMsgAuthMethod.NTLM) {
+ this._possibleAuthMethods.unshift("NTLM");
+ } else if (this._server.authMethod == Ci.nsMsgAuthMethod.OAuth2) {
+ // Some servers don't return XOAUTH2 in CAPA correctly.
+ this._possibleAuthMethods.unshift("XOAUTH2");
+ }
+ }
+ this._logger.debug(`Possible auth methods: ${this._possibleAuthMethods}`);
+ this._nextAuthMethod = this._nextAuthMethod || this._possibleAuthMethods[0];
+
+ if (this._nextAuthMethod) {
+ this._updateStatus("hostContact");
+ this._actionAuth();
+ return;
+ }
+
+ // Preferred auth methods don't match any supported auth methods. Give user
+ // some hints to change the config.
+ if (
+ this._server.authMethod == Ci.nsMsgAuthMethod.passwordCleartext &&
+ this._supportedAuthMethods.includes("CRAM-MD5")
+ ) {
+ // Suggest changing from plain password to encrypted password.
+ this._actionError("pop3AuthChangePlainToEncrypt");
+ } else if (
+ this._server.authMethod == Ci.nsMsgAuthMethod.passwordEncrypted &&
+ (this._supportedAuthMethods.includes("PLAIN") ||
+ this._supportedAuthMethods.includes("LOGIN"))
+ ) {
+ // Suggest changing from encrypted password to plain password.
+ this._actionError(
+ this._secureTransport
+ ? "pop3AuthChangeEncryptToPlainSSL"
+ : "pop3AuthChangeEncryptToPlainNoSSL"
+ );
+ } else {
+ // General suggestion about changing auth method.
+ this._actionError("pop3AuthMechNotSupported");
+ }
+ };
+
+ /**
+ * Handle STLS response. STLS is the POP3 command to init STARTTLS.
+ *
+ * @param {Pop3Response} res - STLS response received from the server.
+ */
+ _actionStlsResponse = res => {
+ if (!res.success) {
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ this._socket.upgradeToSecure();
+ this._secureTransport = true;
+ this._actionCapa();
+ };
+
+ /**
+ * Init authentication depending on server capabilities and user prefs.
+ */
+ _actionAuth = async () => {
+ if (!this._nextAuthMethod) {
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (this._destroyed) {
+ // If connection is lost, reconnect.
+ this.connect();
+ return;
+ }
+
+ this._authenticating = true;
+
+ this._currentAuthMethod = this._nextAuthMethod;
+ this._nextAuthMethod =
+ this._possibleAuthMethods[
+ this._possibleAuthMethods.indexOf(this._currentAuthMethod) + 1
+ ];
+ this._logger.debug(`Current auth method: ${this._currentAuthMethod}`);
+ this._nextAction = this._actionAuthResponse;
+
+ switch (this._currentAuthMethod) {
+ case "USERPASS":
+ this._nextAction = this._actionAuthUserPass;
+ this._send(`USER ${this._authenticator.username}`);
+ break;
+ case "PLAIN":
+ this._nextAction = this._actionAuthPlain;
+ this._send("AUTH PLAIN");
+ break;
+ case "LOGIN":
+ this._nextAction = this._actionAuthLoginUser;
+ this._send("AUTH LOGIN");
+ break;
+ case "CRAM-MD5":
+ this._nextAction = this._actionAuthCramMd5;
+ this._send("AUTH CRAM-MD5");
+ break;
+ case "APOP": {
+ let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
+ Ci.nsICryptoHash
+ );
+ hasher.init(hasher.MD5);
+ let data =
+ this._apopTimestamp +
+ (await this._authenticator.getByteStringPassword());
+ let digest = CommonUtils.bytesAsHex(
+ CryptoUtils.digestBytes(data, hasher)
+ );
+ this._send(`APOP ${this._authenticator.username} ${digest}`, true);
+ break;
+ }
+ case "GSSAPI": {
+ this._authenticator.initGssapiAuth("pop");
+ try {
+ let token = this._authenticator.getNextGssapiToken("");
+ this._nextAction = res => this._actionAuthGssapi(res, token);
+ } catch (e) {
+ this._logger.error(e);
+ this._actionError("pop3GssapiFailure");
+ return;
+ }
+ this._send("AUTH GSSAPI");
+ break;
+ }
+ case "NTLM": {
+ this._authenticator.initNtlmAuth("pop");
+ try {
+ let token = this._authenticator.getNextNtlmToken("");
+ this._nextAction = res => this._actionAuthNtlm(res, token);
+ } catch (e) {
+ this._logger.error(e);
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ this._send("AUTH NTLM");
+ break;
+ }
+ case "XOAUTH2":
+ this._nextAction = this._actionAuthXoauth;
+ this._send("AUTH XOAUTH2");
+ break;
+ default:
+ this._actionDone();
+ }
+ };
+
+ /**
+ * Handle authentication response.
+ *
+ * @param {Pop3Response} res - Authentication response received from the server.
+ */
+ _actionAuthResponse = res => {
+ this._authenticating = false;
+ if (res.success) {
+ this._actionAfterAuth();
+ return;
+ }
+
+ if (this._nextAuthMethod) {
+ // Try the next auth method.
+ this._actionAuth();
+ return;
+ }
+
+ if (this._verifyLogon) {
+ this.runningUri.errorCode = "pop3PasswordFailed";
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (
+ ["USERPASS", "PLAIN", "LOGIN", "CRAM-MD5"].includes(
+ this._currentAuthMethod
+ )
+ ) {
+ this._actionError(
+ "pop3PasswordFailed",
+ [this._server.username],
+ res.statusText
+ );
+
+ // Ask user what to do.
+ let action = this._authenticator.promptAuthFailed();
+ if (action == 1) {
+ // Cancel button pressed.
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ if (action == 2) {
+ // 'New password' button pressed.
+ this._authenticator.forgetPassword();
+ }
+
+ // Retry.
+ this._nextAuthMethod = this._possibleAuthMethods[0];
+ this._actionAuth();
+ } else if (this._currentAuthMethod == "GSSAPI") {
+ this._actionError("pop3GssapiFailure", [], res.statusText);
+ }
+ };
+
+ /**
+ * The second step of USER/PASS auth, send the password to the server.
+ */
+ _actionAuthUserPass = async res => {
+ if (!res.success) {
+ this._actionError("pop3UsernameFailure", [], res.statusText);
+ return;
+ }
+ this._nextAction = this._actionAuthResponse;
+ this._send(
+ `PASS ${await this._authenticator.getByteStringPassword()}`,
+ true
+ );
+ };
+
+ /**
+ * The second step of PLAIN auth, send the auth token to the server.
+ */
+ _actionAuthPlain = async res => {
+ if (!res.success) {
+ this._actionError("pop3UsernameFailure", [], res.statusText);
+ return;
+ }
+ this._nextAction = this._actionAuthResponse;
+ this._send(await this._authenticator.getPlainToken(), true);
+ };
+
+ /**
+ * The second step of LOGIN auth, send the username to the server.
+ */
+ _actionAuthLoginUser = () => {
+ this._nextAction = this._actionAuthLoginPass;
+ this._logger.debug("AUTH LOGIN USER");
+ this._send(btoa(this._authenticator.username), true);
+ };
+
+ /**
+ * The third step of LOGIN auth, send the password to the server.
+ */
+ _actionAuthLoginPass = async res => {
+ if (!res.success) {
+ this._actionError("pop3UsernameFailure", [], res.statusText);
+ return;
+ }
+ this._nextAction = this._actionAuthResponse;
+ this._logger.debug("AUTH LOGIN PASS");
+ let password = await this._authenticator.getPassword();
+ if (
+ !Services.prefs.getBoolPref(
+ "mail.smtp_login_pop3_user_pass_auth_is_latin1",
+ true
+ ) ||
+ !/^[\x00-\xFF]+$/.test(password) // eslint-disable-line no-control-regex
+ ) {
+ // Unlike PLAIN auth, the payload of LOGIN auth is not standardized. When
+ // `mail.smtp_login_pop3_user_pass_auth_is_latin1` is true, we apply
+ // base64 encoding directly. Otherwise, we convert it to UTF-8
+ // BinaryString first, to make it work with btoa().
+ password = MailStringUtils.stringToByteString(password);
+ }
+ this._send(btoa(password), true);
+ };
+
+ /**
+ * The second step of CRAM-MD5 auth, send a HMAC-MD5 signature to the server.
+ *
+ * @param {Pop3Response} res - AUTH response received from the server.
+ */
+ _actionAuthCramMd5 = async res => {
+ if (!res.success) {
+ this._actionError("pop3UsernameFailure", [], res.statusText);
+ return;
+ }
+ this._nextAction = this._actionAuthResponse;
+ this._send(
+ this._authenticator.getCramMd5Token(
+ await this._authenticator.getPassword(),
+ res.statusText
+ ),
+ true
+ );
+ };
+
+ /**
+ * The second and next step of GSSAPI auth.
+ *
+ * @param {Pop3Response} res - AUTH response received from the server.
+ * @param {string} firstToken - The first GSSAPI token to send.
+ */
+ _actionAuthGssapi = (res, firstToken) => {
+ if (res.status != "+") {
+ this._actionAuthResponse(res);
+ return;
+ }
+
+ if (firstToken) {
+ this._nextAction = this._actionAuthGssapi;
+ this._send(firstToken, true);
+ return;
+ }
+
+ // Server returns a challenge, we send a new token. Can happen multiple times.
+ let token;
+ try {
+ token = this._authenticator.getNextGssapiToken(res.statusText);
+ } catch (e) {
+ this._logger.error(e);
+ this._actionAuthResponse({ success: false, data: "AUTH GSSAPI" });
+ return;
+ }
+ this._send(token, true);
+ };
+
+ /**
+ * The second and next step of NTLM auth.
+ *
+ * @param {Pop3Response} res - AUTH response received from the server.
+ * @param {string} firstToken - The first NTLM token to send.
+ */
+ _actionAuthNtlm = (res, firstToken) => {
+ if (res.status != "+") {
+ this._actionAuthResponse(res);
+ return;
+ }
+
+ if (firstToken) {
+ this._nextAction = this._actionAuthNtlm;
+ this._send(firstToken, true);
+ return;
+ }
+
+ // Server returns a challenge, we send a new token. Can happen multiple times.
+ let token;
+ try {
+ token = this._authenticator.getNextNtlmToken(res.statusText);
+ } catch (e) {
+ this._logger.error(e);
+ this._actionAuthResponse({ success: false, data: "AUTH NTLM" });
+ return;
+ }
+ this._send(token, true);
+ };
+
+ /**
+ * The second step of XOAUTH2 auth.
+ *
+ * @param {Pop3Response} res - AUTH response received from the server.
+ */
+ _actionAuthXoauth = async res => {
+ if (res.status != "+") {
+ this._actionAuthResponse(res);
+ return;
+ }
+ this._nextAction = this._actionAuthResponse;
+ let token = await this._authenticator.getOAuthToken();
+ this._send(token, true);
+ };
+
+ /**
+ * Send `STAT` request to the server.
+ */
+ _actionStat = () => {
+ this._nextAction = this._actionStatResponse;
+ this._send("STAT");
+ };
+
+ /**
+ * Handle `STAT` response.
+ *
+ * @param {Pop3Response} res - STAT response received from the server.
+ */
+ _actionStatResponse = res => {
+ if (!res.success) {
+ this._actionError("pop3StatFail", [], res.statusText);
+ return;
+ }
+
+ let numberOfMessages = Number.parseInt(res.statusText);
+ if (!numberOfMessages) {
+ if (this._uidlMap.size) {
+ this._uidlMap.clear();
+ this._uidlMapChanged = true;
+ }
+ // Finish if there is no message.
+ MailServices.pop3.notifyDownloadCompleted(this._sink.folder, 0);
+ this._actionDone();
+ return;
+ }
+ if (!this._downloadMail && !this._server.leaveMessagesOnServer) {
+ // We are not downloading new mails, so finish now.
+ this._sink.setBiffStateAndUpdateFE(
+ Ci.nsIMsgFolder.nsMsgBiffState_NewMail,
+ numberOfMessages,
+ true
+ );
+ this._actionDone();
+ return;
+ }
+
+ if (this._downloadMail) {
+ try {
+ this._sink.beginMailDelivery(
+ this._singleUidlToDownload,
+ this._msgWindow
+ );
+ this._folderLocked = true;
+ this._logger.debug(
+ `Folder lock acquired uri=${this._sink.folder.URI}.`
+ );
+ } catch (e) {
+ const NS_MSG_FOLDER_BUSY = 2153054218;
+ if (e.result == NS_MSG_FOLDER_BUSY) {
+ this._actionError("pop3ServerBusy", [this._server.prettyName]);
+ } else {
+ this._actionError("pop3MessageWriteError");
+ }
+ return;
+ }
+ }
+ this._actionList();
+ };
+
+ /**
+ * Send `LIST` request to the server.
+ */
+ _actionList = () => {
+ this._messageSizeMap = new Map();
+ this._nextAction = this._actionListResponse;
+ this._send("LIST");
+ };
+
+ /**
+ * Handle `LIST` response.
+ *
+ * @param {Pop3Response} res - LIST response received from the server.
+ */
+ _actionListResponse = res => {
+ if (res.status && !res.success) {
+ this._actionError("pop3ListFailure", [], res.statusText);
+ return;
+ }
+ this._lineReader.read(
+ res.data,
+ line => {
+ let [messageNumber, messageSize] = line.split(" ");
+ this._messageSizeMap.set(messageNumber, Number(messageSize));
+ },
+ () => {
+ this._actionUidl();
+ }
+ );
+ };
+
+ /**
+ * Send `UIDL` request to the server.
+ */
+ _actionUidl = () => {
+ this._messagesToHandle = [];
+ this._newUidlMap = new Map();
+ this._nextAction = this._actionUidlResponse;
+ this._send("UIDL");
+ };
+
+ /**
+ * Handle `UIDL` response.
+ *
+ * @param {Pop3Response} res - UIDL response received from the server.
+ */
+ _actionUidlResponse = ({ status, success, data }) => {
+ if (status && !success) {
+ this._actionNoUidl();
+ return;
+ }
+ this._lineReader.read(
+ data,
+ line => {
+ let [messageNumber, uidl] = line.split(" ");
+ uidl = uidl.trim();
+ let uidlState = this._uidlMap.get(uidl);
+ if (uidlState) {
+ if (
+ uidlState.status == UIDL_KEEP &&
+ (!this._server.leaveMessagesOnServer ||
+ uidlState.receivedAt < this._cutOffTimestamp)
+ ) {
+ // Delete this message.
+ this._messagesToHandle.push({
+ ...uidlState,
+ messageNumber,
+ status: UIDL_DELETE,
+ });
+ } else if (
+ [UIDL_FETCH_BODY, UIDL_DELETE].includes(uidlState.status)
+ ) {
+ // Fetch the full message.
+ this._messagesToHandle.push({
+ ...uidlState,
+ messageNumber,
+ status: uidlState.status,
+ });
+ } else {
+ // Do nothing to this message.
+ this._newUidlMap.set(uidl, uidlState);
+ }
+ } else {
+ this._newMessageTotal++;
+ // Fetch the full message or only headers depending on server settings
+ // and message size.
+ let status =
+ this._server.headersOnly ||
+ this._messageSizeMap.get(messageNumber) > this._maxMessageSize
+ ? UIDL_TOO_BIG
+ : UIDL_FETCH_BODY;
+ this._messagesToHandle.push({
+ messageNumber,
+ uidl,
+ status,
+ });
+ }
+ },
+ () => {
+ if (!this._downloadMail) {
+ let numberOfMessages = this._messagesToHandle.filter(
+ // No receivedAt means we're seeing it for the first time.
+ msg => !msg.receivedAt
+ ).length;
+ if (numberOfMessages) {
+ this._sink.setBiffStateAndUpdateFE(
+ Ci.nsIMsgFolder.nsMsgBiffState_NewMail,
+ numberOfMessages,
+ true
+ );
+ }
+ this._actionDone();
+ return;
+ }
+
+ if (this._singleUidlToDownload) {
+ this._messagesToHandle = this._messagesToHandle.filter(
+ msg => msg.uidl == this._singleUidlToDownload
+ );
+ this._newUidlMap = this._uidlMap;
+ }
+
+ this._messagesToDownload = this._messagesToHandle.filter(msg =>
+ [UIDL_FETCH_BODY, UIDL_TOO_BIG].includes(msg.status)
+ );
+ this._totalDownloadSize = this._messagesToDownload.reduce(
+ (acc, msg) => acc + this._messageSizeMap.get(msg.messageNumber),
+ 0
+ );
+ this._totalReceivedSize = 0;
+ try {
+ let localFolder = this._sink.folder.QueryInterface(
+ Ci.nsIMsgLocalMailFolder
+ );
+ if (
+ localFolder.warnIfLocalFileTooBig(
+ this._msgWindow,
+ this._totalDownloadSize
+ )
+ ) {
+ throw new Error("Not enough disk space");
+ }
+ } catch (e) {
+ this._logger.error(e);
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+
+ this._uidlMapChanged =
+ this._uidlMap.size != this._newUidlMap.size ||
+ this._messagesToHandle.length;
+ // This discards staled uidls that are no longer on the server.
+ this._uidlMap = this._newUidlMap;
+
+ this._sink.setMsgsToDownload(this._messagesToDownload.length);
+ this._actionHandleMessage();
+ this._updateProgress();
+ }
+ );
+ };
+
+ /**
+ * If the server doesn't support UIDL, leaveMessagesOnServer and headersOnly
+ * feature can't be used.
+ */
+ _actionNoUidl = () => {
+ if (
+ this._server.leaveMessagesOnServer ||
+ this._server.headersOnly ||
+ this._server.limitOfflineMessageSize ||
+ this._singleUidlToDownload
+ ) {
+ this._actionError("pop3ServerDoesNotSupportUidlEtc", [
+ this._server.hostName,
+ ]);
+ return;
+ }
+ for (let [messageNumber] of this._messageSizeMap) {
+ // Send RETR for each message.
+ this._messagesToHandle.push({
+ status: UIDL_FETCH_BODY,
+ messageNumber,
+ });
+ }
+ this._actionHandleMessage();
+ };
+
+ /**
+ * Consume a message from this._messagesToHandle, decide to send TOP, RETR or
+ * DELE request.
+ */
+ _actionHandleMessage = () => {
+ this._currentMessage = this._messagesToHandle.shift();
+ if (
+ this._messagesToHandle.length > 0 &&
+ this._messagesToHandle.length % 20 == 0 &&
+ !this._writeUidlPromise
+ ) {
+ // Update popstate.dat every 20 messages, so that even if an error
+ // happens, no need to re-download all messages.
+ this._writeUidlState();
+ }
+ if (this._currentMessage) {
+ switch (this._currentMessage.status) {
+ case UIDL_TOO_BIG:
+ if (this._topFailed) {
+ this._actionRetr();
+ } else {
+ this._actionTop();
+ }
+ break;
+ case UIDL_FETCH_BODY:
+ this._actionRetr();
+ break;
+ case UIDL_DELETE:
+ this._actionDelete();
+ break;
+ default:
+ break;
+ }
+ } else {
+ this._sink.setBiffStateAndUpdateFE(
+ Ci.nsIMsgFolder.nsMsgBiffState_NewMail,
+ this._messagesToDownload
+ ? this._messagesToDownload.length
+ : // No UIDL support, every message is new.
+ this._messageSizeMap.size,
+ false
+ );
+ try {
+ this._sink.endMailDelivery(this);
+ this._folderLocked = false;
+ this._logger.debug("Folder lock released.");
+ } catch (e) {
+ this._logger.error("endMailDelivery failed", e);
+ this._actionDone(e.result || Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ this._actionDone();
+ }
+ };
+
+ /**
+ * Send `TOP` request to the server.
+ */
+ _actionTop = () => {
+ this._nextAction = this._actionTopResponse;
+ let lineNumber = this._server.headersOnly ? 0 : 20;
+ this._send(`TOP ${this._currentMessage.messageNumber} ${lineNumber}`);
+ this._updateStatus("receivingMessages", [
+ ++this._newMessageDownloaded,
+ this._newMessageTotal,
+ ]);
+ };
+
+ /**
+ * Handle `TOP` response.
+ *
+ * @param {Pop3Response} res - TOP response received from the server.
+ */
+ _actionTopResponse = res => {
+ if (res.status) {
+ if (res.success) {
+ try {
+ // Call incorporateBegin only once for each message.
+ this._sink.incorporateBegin(
+ this._currentMessage.uidl,
+ Ci.nsMsgMessageFlags.Partial
+ );
+ } catch (e) {
+ this._actionError("pop3MessageWriteError");
+ return;
+ }
+ } else {
+ // TOP is not supported.
+ this._topFailed = true;
+ this._actionRetr();
+ return;
+ }
+ }
+ this._lineReader.read(
+ res.data,
+ line => {
+ // Remove \r\n and use the OS native line ending.
+ line = line.slice(0, -2) + this._lineSeparator;
+ try {
+ this._sink.incorporateWrite(line, line.length);
+ } catch (e) {
+ this._actionError("pop3MessageWriteError");
+ throw e; // Stop reading.
+ }
+ },
+ () => {
+ try {
+ this._sink.incorporateComplete(
+ this._msgWindow,
+ // Set size because it's a partial message.
+ this._messageSizeMap.get(this._currentMessage.messageNumber)
+ );
+ } catch (e) {
+ this._actionError("pop3MessageWriteError");
+ return;
+ }
+
+ let state = this._uidlMap.get(this._currentMessage.uidl);
+ if (state?.status == UIDL_FETCH_BODY) {
+ this._actionRetr();
+ return;
+ }
+ if (state?.status == UIDL_DELETE) {
+ this._actionDelete();
+ return;
+ }
+ this._uidlMap.set(this._currentMessage.uidl, {
+ status: UIDL_TOO_BIG,
+ uidl: this._currentMessage.uidl,
+ receivedAt: Math.floor(Date.now() / 1000),
+ });
+ this._uidlMapChanged = true;
+ this._actionHandleMessage();
+ }
+ );
+ };
+
+ /**
+ * Send `RETR` request to the server.
+ */
+ _actionRetr = () => {
+ this._nextAction = this._actionRetrResponse;
+ this._send(`RETR ${this._currentMessage.messageNumber}`);
+ this._updateStatus("receivingMessages", [
+ ++this._newMessageDownloaded,
+ this._newMessageTotal,
+ ]);
+ };
+
+ /**
+ * Handle `RETR` response.
+ *
+ * @param {Pop3Response} res - RETR response received from the server.
+ */
+ _actionRetrResponse = res => {
+ if (res.status) {
+ if (!res.success) {
+ this._actionError("pop3RetrFailure", [], res.statusText);
+ return;
+ }
+ try {
+ // Call incorporateBegin only once for each message.
+ this._sink.incorporateBegin(this._currentMessage.uidl, 0);
+ } catch (e) {
+ this._actionError("pop3MessageWriteError");
+ return;
+ }
+ }
+ this._lineReader.read(
+ res.data,
+ line => {
+ line = line.slice(0, -2) + this._lineSeparator;
+ try {
+ this._sink.incorporateWrite(line, line.length);
+ } catch (e) {
+ this._actionError("pop3MessageWriteError");
+ throw e; // Stop reading.
+ }
+ },
+ () => {
+ // Don't count the ending indicator.
+ this._totalReceivedSize -= ".\r\n".length;
+ try {
+ this._sink.incorporateComplete(
+ this._msgWindow,
+ 0 // Set size only when it's a partial message.
+ );
+ } catch (e) {
+ this._actionError("pop3MessageWriteError");
+ return;
+ }
+ if (this._server.leaveMessagesOnServer) {
+ let state = this._uidlMap.get(this._currentMessage.uidl);
+ if (state?.status == UIDL_DELETE) {
+ this._actionDelete();
+ } else {
+ this._uidlMap.set(this._currentMessage.uidl, {
+ status: UIDL_KEEP,
+ uidl: this._currentMessage.uidl,
+ receivedAt: Math.floor(Date.now() / 1000),
+ });
+ this._uidlMapChanged = true;
+ this._actionHandleMessage();
+ }
+ } else {
+ this._actionDelete();
+ }
+ }
+ );
+
+ this._totalReceivedSize += res.data.length;
+ this._updateProgress();
+ };
+
+ /**
+ * Send `DELE` request to the server.
+ */
+ _actionDelete = () => {
+ this._nextAction = this._actionDeleteResponse;
+ this._send(`DELE ${this._currentMessage.messageNumber}`);
+ };
+
+ /**
+ * Handle `DELE` response.
+ *
+ * @param {Pop3Response} res - DELE response received from the server.
+ */
+ _actionDeleteResponse = res => {
+ if (!res.success) {
+ this._actionError("pop3DeleFailure", [], res.statusText);
+ return;
+ }
+ this._actionHandleMessage();
+ };
+
+ /**
+ * Show an error prompt.
+ *
+ * @param {string} errorName - An error name corresponds to an entry of
+ * localMsgs.properties.
+ * @param {string[]} errorParams - Params to construct the error message.
+ * @param {string} serverErrorMsg - Error message returned by the server.
+ */
+ _actionError(errorName, errorParams, serverErrorMsg) {
+ this._logger.error(`Got an error name=${errorName}`);
+ if (errorName != "pop3PasswordFailed") {
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ }
+
+ if (!this._msgWindow) {
+ return;
+ }
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/localMsgs.properties"
+ );
+ let errorMsg;
+ if (errorParams) {
+ errorMsg = bundle.formatStringFromName(errorName, errorParams);
+ } else {
+ errorMsg = bundle.GetStringFromName(errorName);
+ }
+ if (serverErrorMsg) {
+ let serverSaidPrefix = bundle.formatStringFromName("pop3ServerSaid", [
+ this._server.hostName,
+ ]);
+ errorMsg += ` ${serverSaidPrefix} ${serverErrorMsg}`;
+ }
+
+ let errorTitle = bundle.formatStringFromName("pop3ErrorDialogTitle", [
+ this._server.prettyName,
+ ]);
+ Services.prompt.alert(this._msgWindow.domWindow, errorTitle, errorMsg);
+ }
+
+ /**
+ * Save popstate.dat when necessary, send QUIT.
+ * @param {nsresult} status - Indicate if the last action succeeded.
+ */
+ _actionDone = (status = Cr.NS_OK) => {
+ if (this._done) {
+ return;
+ }
+ this._done = true;
+ this._logger.debug(`Done with status=${status}`);
+ this._authenticating = false;
+ if (status == Cr.NS_OK) {
+ if (this._newMessageTotal) {
+ this._updateStatus("receivedMsgs", [
+ this._newMessageTotal,
+ this._newMessageTotal,
+ ]);
+ } else {
+ this._updateStatus("noNewMessages");
+ }
+ } else if (this._currentMessage) {
+ // Put _currentMessage back to the queue to prevent loss of popstate.
+ this._messagesToHandle.unshift(this._currentMessage);
+ }
+ this._writeUidlState(true);
+ // Normally we clean up after QUIT response.
+ this.quit(() => this._cleanUp(status));
+ // If we didn't receive QUIT response after 3 seconds, clean up anyway.
+ setTimeout(() => {
+ if (!this._cleanedUp) {
+ this._cleanUp(status);
+ }
+ }, 3000);
+ };
+
+ /**
+ * Notify listeners, close the socket and rest states.
+ * @param {nsresult} status - Indicate if the last action succeeded.
+ */
+ _cleanUp = status => {
+ this._cleanedUp = true;
+ this.close();
+ this.urlListener.OnStopRunningUrl(this.runningUri, status);
+ this.runningUri.SetUrlState(false, Cr.NS_OK);
+ this.onDone?.(status);
+ if (this._folderLocked) {
+ this._sink.abortMailDelivery(this);
+ this._folderLocked = false;
+ this._logger.debug("Folder lock released.");
+ }
+ this._server.wrappedJSObject.runningClient = null;
+ };
+
+ /**
+ * Show a status message in the status bar.
+ *
+ * @param {string} statusName - A string name in localMsgs.properties.
+ * @param {string[]} [params] - Params to format the string.
+ */
+ _updateStatus(statusName, params) {
+ if (!this._msgWindow?.statusFeedback) {
+ return;
+ }
+ if (!this._localBundle) {
+ this._localBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/localMsgs.properties"
+ );
+ this._messengerBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messenger.properties"
+ );
+ }
+ let status = params
+ ? this._localBundle.formatStringFromName(statusName, params)
+ : this._localBundle.GetStringFromName(statusName);
+ this._msgWindow.statusFeedback.showStatusString(
+ this._messengerBundle.formatStringFromName("statusMessage", [
+ this._server.prettyName,
+ status,
+ ])
+ );
+ }
+
+ /**
+ * Show a progress bar in the status bar.
+ */
+ _updateProgress() {
+ this._msgWindow?.statusFeedback?.showProgress(
+ Math.floor((this._totalReceivedSize * 100) / this._totalDownloadSize)
+ );
+ }
+
+ /** @see nsIPop3Protocol */
+ checkMessage(uidl) {
+ return this._uidlMap.has(uidl);
+ }
+}
diff --git a/comm/mailnews/local/src/Pop3IncomingServer.jsm b/comm/mailnews/local/src/Pop3IncomingServer.jsm
new file mode 100644
index 0000000000..b8c147a022
--- /dev/null
+++ b/comm/mailnews/local/src/Pop3IncomingServer.jsm
@@ -0,0 +1,308 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["Pop3IncomingServer"];
+
+var { MsgIncomingServer } = ChromeUtils.import(
+ "resource:///modules/MsgIncomingServer.jsm"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ Pop3Client: "resource:///modules/Pop3Client.jsm",
+});
+
+/**
+ * @implements {nsIPop3IncomingServer}
+ * @implements {nsILocalMailIncomingServer}
+ * @implements {nsIMsgIncomingServer}
+ * @implements {nsISupportsWeakReference}
+ */
+class Pop3IncomingServer extends MsgIncomingServer {
+ QueryInterface = ChromeUtils.generateQI([
+ "nsIPop3IncomingServer",
+ "nsILocalMailIncomingServer",
+ "nsIMsgIncomingServer",
+ "nsISupportsWeakReference",
+ ]);
+
+ constructor() {
+ super();
+
+ // nsIMsgIncomingServer attributes.
+ this.localStoreType = "mailbox";
+ this.localDatabaseType = "mailbox";
+ this.downloadMessagesAtStartup = true;
+ this.canBeDefaultServer = true;
+
+ Object.defineProperty(this, "canCreateFoldersOnServer", {
+ get: () => !this.deferredToAccount,
+ });
+ Object.defineProperty(this, "canFileMessagesOnServer", {
+ get: () => !this.deferredToAccount,
+ });
+
+ // nsIPop3IncomingServer attributes that map directly to pref values.
+ this._mapAttrsToPrefs([
+ ["Bool", "leaveMessagesOnServer", "leave_on_server"],
+ ["Bool", "headersOnly", "headers_only"],
+ ["Bool", "deleteMailLeftOnServer", "delete_mail_left_on_server"],
+ ["Bool", "deleteByAgeFromServer", "delete_by_age_from_server"],
+ ["Bool", "deferGetNewMail", "defer_get_new_mail"],
+ ["Int", "numDaysToLeaveOnServer", "num_days_to_leave_on_server"],
+ ]);
+
+ // @type {Map<string,string>} - A map from uidl to status.
+ this._uidlsToMark = new Map();
+ }
+
+ /** @see nsIMsgIncomingServer */
+ get rootMsgFolder() {
+ if (this._rootMsgFolder) {
+ return this._rootMsgFolder;
+ }
+
+ if (!this.deferredToAccount) {
+ this._rootMsgFolder = this.rootFolder;
+ return this._rootMsgFolder;
+ }
+
+ let incomingServer = MailServices.accounts.getAccount(
+ this.deferredToAccount
+ ).incomingServer;
+ if (incomingServer.equals(this)) {
+ // Make sure we're not deferred to ourself.
+ throw Components.Exception(
+ `${incomingServer.prettyName} cannot be deferred to itself`,
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ this._rootMsgFolder = incomingServer.rootMsgFolder;
+ return this._rootMsgFolder;
+ }
+
+ get canSearchMessages() {
+ return this.canFileMessagesOnServer;
+ }
+
+ getNewMessages(folder, msgWindow, urlListener) {
+ let inbox = this.rootMsgFolder.getFolderWithFlags(
+ Ci.nsMsgFolderFlags.Inbox
+ );
+ if (!this.deferredToAccount) {
+ let deferredServers = this._getDeferedServers(folder.server);
+ if (deferredServers.length) {
+ // If other servers are deferred to this server, get new mail for them
+ // as well.
+ return this.downloadMailFromServers(
+ [...deferredServers, this],
+ msgWindow,
+ inbox,
+ urlListener
+ );
+ }
+ }
+ return MailServices.pop3.GetNewMail(msgWindow, urlListener, inbox, this);
+ }
+
+ verifyLogon(urlListener, msgWindow) {
+ return MailServices.pop3.verifyLogon(this, urlListener, msgWindow);
+ }
+
+ performBiff(msgWindow) {
+ this.performingBiff = true;
+ let inbox = this.rootMsgFolder.getFolderWithFlags(
+ Ci.nsMsgFolderFlags.Inbox
+ );
+ let urlListener = inbox.QueryInterface(Ci.nsIUrlListener);
+ if (this.downloadOnBiff) {
+ MailServices.pop3.GetNewMail(msgWindow, urlListener, inbox, this);
+ } else {
+ MailServices.pop3.CheckForNewMail(msgWindow, urlListener, inbox, this);
+ }
+ }
+
+ /** @see nsILocalMailIncomingServer */
+ createDefaultMailboxes() {
+ for (let name of ["Inbox", "Trash"]) {
+ let folderUri = this.rootFolder.URI + "/" + name;
+ // Check by URI instead of by name, because folder name can be localized.
+ if (!this.rootFolder.getChildWithURI(folderUri, false, false)) {
+ this.msgStore.createFolder(this.rootFolder, name);
+ }
+ }
+ }
+
+ setFlagsOnDefaultMailboxes() {
+ this.rootFolder
+ .QueryInterface(Ci.nsIMsgLocalMailFolder)
+ // POP3 account gets an inbox, but no queue (unsent messages).
+ .setFlagsOnDefaultMailboxes(
+ Ci.nsMsgFolderFlags.SpecialUse & ~Ci.nsMsgFolderFlags.Queue
+ );
+ }
+
+ getNewMail(msgWindow, urlListener, inbox) {
+ return MailServices.pop3.GetNewMail(msgWindow, urlListener, inbox, this);
+ }
+
+ /** @see nsIPop3IncomingServer */
+ get deferredToAccount() {
+ let accountKey = this.getCharValue("deferred_to_account");
+ if (!accountKey) {
+ return "";
+ }
+
+ let account = MailServices.accounts.getAccount(accountKey);
+ // If currently deferred to an invalid or hidden server, change to defer to
+ // the local folders inbox.
+ if (!account || !account.incomingServer || account.incomingServer.hidden) {
+ let localAccount;
+ try {
+ localAccount = MailServices.accounts.FindAccountForServer(
+ MailServices.accounts.localFoldersServer
+ );
+ } catch (e) {
+ // MailServices.accounts.localFoldersServer throws if no Local Folders.
+ return "";
+ }
+ accountKey = localAccount.key;
+ this.setCharValue("deferred_to_account", accountKey);
+ }
+
+ return accountKey;
+ }
+
+ set deferredToAccount(accountKey) {
+ this._rootMsgFolder = null;
+
+ let wasDeferred = Boolean(this.deferredToAccount);
+ this.setCharValue("deferred_to_account", accountKey);
+
+ // If isDeferred state has changed, send notification.
+ if (Boolean(accountKey) != wasDeferred) {
+ let folderListenerManager = MailServices.mailSession.QueryInterface(
+ Ci.nsIFolderListener
+ );
+ folderListenerManager.onFolderBoolPropertyChanged(
+ this.rootFolder,
+ "isDeferred",
+ wasDeferred,
+ !wasDeferred
+ );
+ folderListenerManager.onFolderBoolPropertyChanged(
+ this.rootFolder,
+ "CanFileMessages",
+ !wasDeferred,
+ wasDeferred
+ );
+ }
+
+ if (!accountKey) {
+ return;
+ }
+ // Check if we are deferred to the local folders, and create INBOX if needed.
+ let server = MailServices.accounts.getAccount(accountKey).incomingServer;
+ if (server instanceof Ci.nsILocalMailIncomingServer) {
+ // Check by URI instead of by name, because folder name can be localized.
+ if (
+ !this.rootFolder.getChildWithURI(
+ `${this.rootFolder.URI}/Inbox`,
+ false,
+ false
+ )
+ ) {
+ server.rootFolder.createSubfolder("Inbox", null);
+ }
+ }
+ }
+
+ addUidlToMark(uidl, mark) {
+ // @see nsIMsgLocalMailFolder
+ const POP3_DELETE = 1;
+ const POP3_FETCH_BODY = 2;
+ let status = "k";
+ if (mark == POP3_DELETE) {
+ status = "d";
+ } else if (mark == POP3_FETCH_BODY) {
+ status = "f";
+ }
+ this._uidlsToMark.set(uidl, status);
+ }
+
+ markMessages() {
+ if (!this._uidlsToMark.size) {
+ return;
+ }
+
+ let client = this.runningClient || new lazy.Pop3Client(this);
+ // Pass a clone of this._uidlsToMark to client.markMessages, because
+ // this._uidlsToMark may be changed before markMessages finishes.
+ client.markMessages(new Map(this._uidlsToMark));
+ this._uidlsToMark = new Map();
+ }
+
+ downloadMailFromServers(servers, msgWindow, folder, urlListener) {
+ let server = servers.shift();
+ if (!server) {
+ urlListener?.OnStopRunningUrl(null, Cr.NS_OK);
+ return;
+ }
+
+ // If server != folder.server, it means server is deferred to folder.server,
+ // so if server.deferGetNewMail is false, no need to call GetNewMail.
+ if (server == folder.server || server.deferGetNewMail) {
+ MailServices.pop3.GetNewMail(
+ msgWindow,
+ {
+ OnStartRunningUrl() {},
+ OnStopRunningUrl: () => {
+ // Call GetNewMail for the next server only after it is finished for
+ // the current server.
+ this.downloadMailFromServers(
+ servers,
+ msgWindow,
+ folder,
+ urlListener
+ );
+ },
+ },
+ folder,
+ server
+ );
+ return;
+ }
+ this.downloadMailFromServers(servers, msgWindow, folder, urlListener);
+ }
+
+ /**
+ * Get all the servers that defer to the passed in server.
+ *
+ * @param {nsIMsgIncomingServer} dstServer - The server that others servers
+ * are deferred to.
+ */
+ _getDeferedServers(dstServer) {
+ let dstAccount = MailServices.accounts.FindAccountForServer(dstServer);
+ if (!dstAccount) {
+ return [];
+ }
+ return MailServices.accounts.allServers.filter(
+ server =>
+ server instanceof Ci.nsIPop3IncomingServer &&
+ server.deferredToAccount == dstAccount.key
+ );
+ }
+}
+
+Pop3IncomingServer.prototype.classID = Components.ID(
+ "{f99fdbf7-2e79-4ce3-9d94-7af3763b82fc}"
+);
diff --git a/comm/mailnews/local/src/Pop3ProtocolHandler.jsm b/comm/mailnews/local/src/Pop3ProtocolHandler.jsm
new file mode 100644
index 0000000000..51b1d5bf22
--- /dev/null
+++ b/comm/mailnews/local/src/Pop3ProtocolHandler.jsm
@@ -0,0 +1,40 @@
+/* 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 EXPORTED_SYMBOLS = ["Pop3ProtocolHandler"];
+
+var { Pop3Channel } = ChromeUtils.import("resource:///modules/Pop3Channel.jsm");
+
+/**
+ * @implements {nsIProtocolHandler}
+ */
+class Pop3ProtocolHandler {
+ QueryInterface = ChromeUtils.generateQI(["nsIProtocolHandler"]);
+
+ scheme = "pop3";
+
+ newChannel(uri, loadInfo) {
+ let channel = new Pop3Channel(uri, loadInfo);
+ let spec = uri.spec;
+ if (
+ spec.includes("part=") &&
+ !spec.includes("type=message/rfc822") &&
+ !spec.includes("type=application/x-message-display") &&
+ !spec.includes("type=application/pdf")
+ ) {
+ channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT;
+ } else {
+ channel.contentDisposition = Ci.nsIChannel.DISPOSITION_INLINE;
+ }
+ return channel;
+ }
+
+ allowPort(port, scheme) {
+ return true;
+ }
+}
+
+Pop3ProtocolHandler.prototype.classID = Components.ID(
+ "{eed38573-d01b-4c13-9f9d-f69963095a4d}"
+);
diff --git a/comm/mailnews/local/src/Pop3ProtocolInfo.jsm b/comm/mailnews/local/src/Pop3ProtocolInfo.jsm
new file mode 100644
index 0000000000..57fc9b07d9
--- /dev/null
+++ b/comm/mailnews/local/src/Pop3ProtocolInfo.jsm
@@ -0,0 +1,44 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["Pop3ProtocolInfo"];
+
+var { MsgProtocolInfo } = ChromeUtils.importESModule(
+ "resource:///modules/MsgProtocolInfo.sys.mjs"
+);
+
+/**
+ * @implements {nsIMsgProtocolInfo}
+ */
+class Pop3ProtocolInfo extends MsgProtocolInfo {
+ QueryInterface = ChromeUtils.generateQI(["nsIMsgProtocolInfo"]);
+
+ serverIID = Components.ID("{f99fdbf7-2e79-4ce3-9d94-7af3763b82fc}");
+
+ requiresUsername = true;
+ preflightPrettyNameWithEmailAddress = true;
+ canDelete = true;
+ canLoginAtStartUp = true;
+ canDuplicate = true;
+ canGetMessages = true;
+ canGetIncomingMessages = true;
+ defaultDoBiff = true;
+ showComposeMsgLink = true;
+ foldersCreatedAsync = false;
+
+ getDefaultServerPort(isSecure) {
+ return isSecure
+ ? Ci.nsIPop3URL.DEFAULT_POP3S_PORT
+ : Ci.nsIPop3URL.DEFAULT_POP3_PORT;
+ }
+
+ // @see MsgProtocolInfo.sys.mjs
+ RELATIVE_PREF = "mail.root.pop3-rel";
+ ABSOLUTE_PREF = "mail.root.pop3";
+ DIR_SERVICE_PROP = "MailD";
+}
+
+Pop3ProtocolInfo.prototype.classID = Components.ID(
+ "{7689942f-cbd1-42ad-87b9-44128354f55d}"
+);
diff --git a/comm/mailnews/local/src/Pop3Service.jsm b/comm/mailnews/local/src/Pop3Service.jsm
new file mode 100644
index 0000000000..0270489fcd
--- /dev/null
+++ b/comm/mailnews/local/src/Pop3Service.jsm
@@ -0,0 +1,77 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["Pop3Service"];
+
+var { Pop3Client } = ChromeUtils.import("resource:///modules/Pop3Client.jsm");
+
+/**
+ * @implements {nsIPop3Service}
+ */
+class Pop3Service {
+ QueryInterface = ChromeUtils.generateQI(["nsIPop3Service"]);
+
+ constructor() {
+ this._listeners = new Set();
+ }
+
+ GetNewMail(msgWindow, urlListener, inbox, server) {
+ return this._getMail(true, msgWindow, urlListener, inbox, server);
+ }
+
+ CheckForNewMail(msgWindow, urlListener, inbox, server) {
+ return this._getMail(false, msgWindow, urlListener, inbox, server);
+ }
+
+ verifyLogon(server, urlListener, msgWindow) {
+ let client = new Pop3Client(server);
+ client.urlListener = urlListener;
+ client.connect();
+ client.onOpen = () => {
+ client.verifyLogon(msgWindow);
+ };
+ return client.runningUri;
+ }
+
+ addListener(listener) {
+ this._listeners.add(listener);
+ }
+
+ removeListener(listener) {
+ this._listeners.remove(listener);
+ }
+
+ notifyDownloadStarted(folder) {
+ for (let listener of this._listeners) {
+ listener.onDownloadStarted(folder);
+ }
+ }
+
+ notifyDownloadProgress(folder, numMessages, numTotalMessages) {
+ for (let listener of this._listeners) {
+ listener.onDownloadProgress(folder, numMessages, numTotalMessages);
+ }
+ }
+
+ notifyDownloadCompleted(folder, numMessages) {
+ for (let listener of this._listeners) {
+ listener.onDownloadCompleted(folder, numMessages);
+ }
+ }
+
+ _getMail(downloadNewMail, msgWindow, urlListener, inbox, server) {
+ let client = new Pop3Client(server);
+ client.runningUri.msgWindow = msgWindow;
+ client.urlListener = urlListener;
+ client.connect();
+ client.onOpen = () => {
+ client.getMail(downloadNewMail, msgWindow, inbox);
+ };
+ return client.runningUri;
+ }
+}
+
+Pop3Service.prototype.classID = Components.ID(
+ "{1e8f21c3-32c3-4114-9ea4-3d74006fb351}"
+);
diff --git a/comm/mailnews/local/src/components.conf b/comm/mailnews/local/src/components.conf
new file mode 100644
index 0000000000..00f814f5df
--- /dev/null
+++ b/comm/mailnews/local/src/components.conf
@@ -0,0 +1,150 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+Classes = [
+ {
+ "cid": "{060e684a-0acd-41f1-b36b-12de686f201e}",
+ "contract_ids": ["@mozilla.org/messenger/pop3-sink;1"],
+ "type": "nsPop3Sink",
+ "headers": ["/comm/mailnews/local/src/nsPop3Sink.h"],
+ },
+ {
+ "cid": "{f99fdbf7-2e79-4ce3-9d94-7af3763b82fc}",
+ "contract_ids": ["@mozilla.org/messenger/server;1?type=pop3"],
+ "jsm": "resource:///modules/Pop3IncomingServer.jsm",
+ "constructor": "Pop3IncomingServer",
+ },
+ {
+ "cid": "{1e8f21c3-32c3-4114-9ea4-3d74006fb351}",
+ "contract_ids": ["@mozilla.org/messenger/popservice;1"],
+ "jsm": "resource:///modules/Pop3Service.jsm",
+ "constructor": "Pop3Service",
+ },
+ {
+ "cid": "{7689942f-cbd1-42ad-87b9-44128354f55d}",
+ "contract_ids": ["@mozilla.org/messenger/protocol/info;1?type=pop3"],
+ "jsm": "resource:///modules/Pop3ProtocolInfo.jsm",
+ "constructor": "Pop3ProtocolInfo",
+ },
+ {
+ "cid": "{eed38573-d01b-4c13-9f9d-f69963095a4d}",
+ "contract_ids": ["@mozilla.org/network/protocol;1?name=pop"],
+ "jsm": "resource:///modules/Pop3ProtocolHandler.jsm",
+ "constructor": "Pop3ProtocolHandler",
+ "protocol_config": {
+ "scheme": "pop3",
+ "flags": [
+ "URI_NORELATIVE",
+ "URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT",
+ "URI_DANGEROUS_TO_LOAD",
+ "ALLOWS_PROXY",
+ "URI_FORBIDS_COOKIE_ACCESS",
+ ],
+ "default_port": 110,
+ },
+ },
+ {
+ "cid": "{46efcb10-cb6d-11d2-8065-006008128c4e}",
+ "contract_ids": ["@mozilla.org/messenger/mailboxurl;1"],
+ "type": "nsMailboxUrl",
+ "headers": ["/comm/mailnews/local/src/nsMailboxUrl.h"],
+ },
+ {
+ "cid": "{3fdae3ab-4ac1-4ad4-b28a-28d0fa363929}",
+ "contract_ids": ["@mozilla.org/messenger/msgmailnewsurl;1"],
+ "type": "nsMsgMailNewsUrl",
+ "headers": ["/comm/mailnews/base/src/nsMsgMailNewsUrl.h"],
+ },
+ {
+ "cid": "{eef82462-cb69-11d2-8065-006008128c4e}",
+ "contract_ids": [
+ "@mozilla.org/messenger/mailboxservice;1",
+ "@mozilla.org/messenger/messageservice;1?type=mailbox",
+ "@mozilla.org/messenger/messageservice;1?type=mailbox-message",
+ "@mozilla.org/network/protocol;1?name=mailbox",
+ ],
+ "type": "nsMailboxService",
+ "headers": ["/comm/mailnews/local/src/nsMailboxService.h"],
+ "protocol_config": {
+ "scheme": "mailbox",
+ "flags": [
+ "URI_NORELATIVE",
+ "URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT",
+ "URI_DANGEROUS_TO_LOAD",
+ "URI_FORBIDS_COOKIE_ACCESS",
+ "ORIGIN_IS_FULL_SPEC",
+ ],
+ },
+ },
+ {
+ "cid": "{d17e0e22-285b-4239-863c-2eebb008b5cd}",
+ "contract_ids": ["@mozilla.org/messenger/mailboxparser;1"],
+ "type": "nsMsgMailboxParser",
+ "headers": ["/comm/mailnews/local/src/nsParseMailbox.h"],
+ },
+ {
+ "cid": "{ea1b0a11-e6f4-11d2-8070-006008128c4e}",
+ "contract_ids": ["@mozilla.org/messenger/popurl;1"],
+ "type": "nsPop3URL",
+ "headers": ["/comm/mailnews/local/src/nsPop3URL.h"],
+ },
+ {
+ "cid": "{75b63b46-1dd2-11b2-9873-bb375e1550fa}",
+ "contract_ids": [
+ "@mozilla.org/messenger/noneservice;1",
+ "@mozilla.org/messenger/protocol/info;1?type=none",
+ ],
+ "type": "nsNoneService",
+ "headers": ["/comm/mailnews/local/src/nsNoneService.h"],
+ },
+ {
+ "cid": "{e490d22c-cd67-11d2-8cca-0060b0fc14a3}",
+ "contract_ids": ["@mozilla.org/mail/folder-factory;1?name=mailbox"],
+ "type": "nsMsgLocalMailFolder",
+ "headers": ["/comm/mailnews/local/src/nsLocalMailFolder.h"],
+ },
+ {
+ "cid": "{36358199-a0e4-4b68-929f-77c01de34c67}",
+ "contract_ids": ["@mozilla.org/msgstore/berkeleystore;1"],
+ "type": "nsMsgBrkMBoxStore",
+ "headers": ["/comm/mailnews/local/src/nsMsgBrkMBoxStore.h"],
+ },
+ {
+ "cid": "{1f993eda-7dd9-11df-819a-6257dfd72085}",
+ "contract_ids": ["@mozilla.org/msgstore/maildirstore;1"],
+ "type": "nsMsgMaildirStore",
+ "headers": ["/comm/mailnews/local/src/nsMsgMaildirStore.h"],
+ },
+ {
+ "cid": "{ca5ffe7e-5f47-11d3-9a51-004005263078}",
+ "contract_ids": ["@mozilla.org/messenger/server;1?type=none"],
+ "type": "nsNoIncomingServer",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/local/src/nsNoIncomingServer.h"],
+ },
+ {
+ "cid": "{2b79ac51-1459-11d3-8097-006008128c4e}",
+ "contract_ids": ["@mozilla.org/messenger/messagestateparser;1"],
+ "type": "nsParseMailMessageState",
+ "headers": ["/comm/mailnews/local/src/nsParseMailbox.h"],
+ },
+ {
+ "cid": "{44aef4ce-475b-42e3-bc42-7730d5ce7365}",
+ "contract_ids": [
+ "@mozilla.org/messenger/rssservice;1",
+ "@mozilla.org/messenger/protocol/info;1?type=rss",
+ ],
+ "type": "nsRssService",
+ "headers": ["/comm/mailnews/local/src/nsRssService.h"],
+ },
+ {
+ "cid": "{3a874285-5520-41a0-bcda-a3dee3dbf4f3}",
+ "contract_ids": ["@mozilla.org/messenger/server;1?type=rss"],
+ "type": "nsRssIncomingServer",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/local/src/nsRssIncomingServer.h"],
+ },
+]
diff --git a/comm/mailnews/local/src/moz.build b/comm/mailnews/local/src/moz.build
new file mode 100644
index 0000000000..f530740b80
--- /dev/null
+++ b/comm/mailnews/local/src/moz.build
@@ -0,0 +1,40 @@
+# 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 += [
+ "nsLocalMailFolder.cpp",
+ "nsLocalUndoTxn.cpp",
+ "nsLocalUtils.cpp",
+ "nsMailboxProtocol.cpp",
+ "nsMailboxServer.cpp",
+ "nsMailboxService.cpp",
+ "nsMailboxUrl.cpp",
+ "nsMsgBrkMBoxStore.cpp",
+ "nsMsgFileHdr.cpp",
+ "nsMsgLocalStoreUtils.cpp",
+ "nsMsgMaildirStore.cpp",
+ "nsNoIncomingServer.cpp",
+ "nsNoneService.cpp",
+ "nsParseMailbox.cpp",
+ "nsPop3Sink.cpp",
+ "nsPop3URL.cpp",
+ "nsRssIncomingServer.cpp",
+ "nsRssService.cpp",
+]
+
+FINAL_LIBRARY = "mail"
+
+EXTRA_JS_MODULES += [
+ "Pop3Channel.jsm",
+ "Pop3Client.jsm",
+ "Pop3IncomingServer.jsm",
+ "Pop3ProtocolHandler.jsm",
+ "Pop3ProtocolInfo.jsm",
+ "Pop3Service.jsm",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
diff --git a/comm/mailnews/local/src/nsLocalMailFolder.cpp b/comm/mailnews/local/src/nsLocalMailFolder.cpp
new file mode 100644
index 0000000000..2602c40046
--- /dev/null
+++ b/comm/mailnews/local/src/nsLocalMailFolder.cpp
@@ -0,0 +1,3468 @@
+/* -*- 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 "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "prlog.h"
+
+#include "msgCore.h" // precompiled header...
+#include "nsLocalMailFolder.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsITransactionManager.h"
+#include "nsParseMailbox.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgWindow.h"
+#include "nsCOMPtr.h"
+#include "nsMsgUtils.h"
+#include "nsLocalUtils.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsILocalMailIncomingServer.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsString.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsUnicharUtils.h"
+#include "nsICopyMessageStreamListener.h"
+#include "nsIMsgCopyService.h"
+#include "nsMsgTxn.h"
+#include "nsIMessenger.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIDocShell.h"
+#include "nsIPrompt.h"
+#include "nsIPop3URL.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsNetCID.h"
+#include "nsISpamSettings.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsMailHeaders.h"
+#include "nsCOMArray.h"
+#include "nsIRssIncomingServer.h"
+#include "nsNetUtil.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsReadLine.h"
+#include "nsIStringEnumerator.h"
+#include "nsIURIMutator.h"
+#include "mozilla/Components.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/SlicedInputStream.h"
+
+//////////////////////////////////////////////////////////////////////////////
+// nsLocal
+/////////////////////////////////////////////////////////////////////////////
+
+nsLocalMailCopyState::nsLocalMailCopyState()
+ : m_flags(0),
+ m_lastProgressTime(PR_IntervalToMilliseconds(PR_IntervalNow())),
+ m_curDstKey(nsMsgKey_None),
+ m_curCopyIndex(0),
+ m_totalMsgCount(0),
+ m_dataBufferSize(0),
+ m_leftOver(0),
+ m_isMove(false),
+ m_dummyEnvelopeNeeded(false),
+ m_fromLineSeen(false),
+ m_writeFailed(false),
+ m_notifyFolderLoaded(false) {}
+
+nsLocalMailCopyState::~nsLocalMailCopyState() {
+ PR_Free(m_dataBuffer);
+ if (m_fileStream) m_fileStream->Close();
+ if (m_messageService) {
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(m_srcSupport);
+ if (srcFolder && m_message) {
+ nsCString uri;
+ srcFolder->GetUriForMsg(m_message, uri);
+ }
+ }
+}
+
+nsLocalFolderScanState::nsLocalFolderScanState() : m_uidl(nullptr) {}
+
+nsLocalFolderScanState::~nsLocalFolderScanState() {}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsMsgLocalMailFolder interface
+///////////////////////////////////////////////////////////////////////////////
+
+nsMsgLocalMailFolder::nsMsgLocalMailFolder(void)
+ : mCopyState(nullptr),
+ mHaveReadNameFromDB(false),
+ mInitialized(false),
+ mCheckForNewMessagesAfterParsing(false),
+ m_parsingFolder(false),
+ mDownloadState(DOWNLOAD_STATE_NONE) {}
+
+nsMsgLocalMailFolder::~nsMsgLocalMailFolder(void) {}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgLocalMailFolder, nsMsgDBFolder,
+ nsICopyMessageListener, nsIMsgLocalMailFolder)
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsMsgLocalMailFolder::CreateChildFromURI(const nsACString& uri,
+ nsIMsgFolder** folder) {
+ nsMsgLocalMailFolder* newFolder = new nsMsgLocalMailFolder;
+ if (!newFolder) return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*folder = newFolder);
+ newFolder->Init(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::CreateLocalSubfolder(
+ const nsAString& aFolderName, nsIMsgFolder** aChild) {
+ NS_ENSURE_ARG_POINTER(aChild);
+ nsresult rv = CreateSubfolderInternal(aFolderName, nullptr, aChild);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyFolderAdded(*aChild);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetManyHeadersToDownload(bool* retval) {
+ bool isLocked;
+ // if the folder is locked, we're probably reparsing - let's build the
+ // view when we've finished reparsing.
+ GetLocked(&isLocked);
+ if (isLocked) {
+ *retval = true;
+ return NS_OK;
+ }
+
+ return nsMsgDBFolder::GetManyHeadersToDownload(retval);
+}
+
+// run the url to parse the mailbox
+NS_IMETHODIMP nsMsgLocalMailFolder::ParseFolder(nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aListener) {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aListener != this) mReparseListener = aListener;
+ // if parsing is synchronous, we need to set m_parsingFolder to
+ // true before starting. And we need to open the db before
+ // setting m_parsingFolder to true.
+ // OpenDatabase();
+ rv = msgStore->RebuildIndex(this, mDatabase, aMsgWindow, this);
+ if (NS_SUCCEEDED(rv)) m_parsingFolder = true;
+
+ return rv;
+}
+
+// this won't force a reparse of the folder if the db is invalid.
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase) {
+ return GetDatabaseWOReparse(aMsgDatabase);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetSubFolders(nsTArray<RefPtr<nsIMsgFolder>>& folders) {
+ if (!mInitialized) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ // need to set this flag here to avoid infinite recursion
+ mInitialized = true;
+ rv = server->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // This should add all existing folders as sub-folders of this folder.
+ rv = msgStore->DiscoverSubFolders(this, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> path;
+ rv = GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv)) return rv;
+
+ bool directory;
+ path->IsDirectory(&directory);
+ if (directory) {
+ SetFlag(nsMsgFolderFlags::Mail | nsMsgFolderFlags::Elided |
+ nsMsgFolderFlags::Directory);
+
+ bool isServer;
+ GetIsServer(&isServer);
+ if (isServer) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ nsCOMPtr<nsILocalMailIncomingServer> localMailServer;
+ localMailServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ // first create the folders on disk (as empty files)
+ rv = localMailServer->CreateDefaultMailboxes();
+ if (NS_FAILED(rv) && rv != NS_MSG_FOLDER_EXISTS) return rv;
+
+ // must happen after CreateSubFolders, or the folders won't exist.
+ rv = localMailServer->SetFlagsOnDefaultMailboxes();
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ UpdateSummaryTotals(false);
+ }
+
+ return nsMsgDBFolder::GetSubFolders(folders);
+}
+
+nsresult nsMsgLocalMailFolder::GetDatabase() {
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ return GetDatabaseWOReparse(getter_AddRefs(msgDB));
+}
+
+// we treat failure as null db returned
+NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWOReparse(
+ nsIMsgDatabase** aDatabase) {
+ NS_ENSURE_ARG(aDatabase);
+ if (m_parsingFolder) return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+
+ nsresult rv = NS_OK;
+ if (!mDatabase) {
+ rv = OpenDatabase();
+ if (mDatabase) {
+ mDatabase->AddListener(this);
+ UpdateNewMessages();
+ }
+ }
+ NS_IF_ADDREF(*aDatabase = mDatabase);
+ if (mDatabase) mDatabase->SetLastUseTime(PR_Now());
+ return rv;
+}
+
+// Makes sure the database is open and exists. If the database is out of date,
+// then this call will return NS_ERROR_NOT_INITIALIZED and run an async url
+// to reparse the folder. The passed in url listener will get called when the
+// url is done.
+NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWithReparse(
+ nsIUrlListener* aReparseUrlListener, nsIMsgWindow* aMsgWindow,
+ nsIMsgDatabase** aMsgDatabase) {
+ nsresult rv = NS_OK;
+ // if we're already reparsing, just remember the listener so we can notify it
+ // when we've finished.
+ if (m_parsingFolder) {
+ NS_ASSERTION(!mReparseListener, "can't have an existing listener");
+ mReparseListener = aReparseUrlListener;
+ return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+
+ if (!mDatabase) {
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ rv = pathFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_NULL_POINTER; // mDatabase will be null at this point.
+
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult folderOpen =
+ msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase));
+ if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr<nsIDBFolderInfo> transferInfo;
+ if (mDatabase) {
+ mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo) {
+ dbFolderInfo->SetNumMessages(0);
+ dbFolderInfo->SetNumUnreadMessages(0);
+ dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo));
+ }
+ dbFolderInfo = nullptr;
+
+ // A backup message database might have been created earlier, for
+ // example if the user requested a reindex. We'll use the earlier one if
+ // we can, otherwise we'll try to backup at this point.
+ if (NS_FAILED(OpenBackupMsgDatabase())) {
+ CloseAndBackupFolderDB(EmptyCString());
+ if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase) {
+ mBackupDatabase->RemoveListener(this);
+ mBackupDatabase = nullptr;
+ }
+ } else
+ mDatabase->ForceClosed();
+
+ mDatabase = nullptr;
+ }
+ nsCOMPtr<nsIFile> summaryFile;
+ rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Remove summary file.
+ summaryFile->Remove(false);
+
+ // if it's out of date then reopen with upgrade.
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (transferInfo && mDatabase) {
+ SetDBTransferInfo(transferInfo);
+ mDatabase->SetSummaryValid(false);
+ }
+ } else if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) {
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+ }
+
+ if (mDatabase) {
+ if (mAddListener) mDatabase->AddListener(this);
+
+ // if we have to regenerate the folder, run the parser url.
+ if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
+ folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) {
+ if (NS_FAILED(rv = ParseFolder(aMsgWindow, aReparseUrlListener))) {
+ if (rv == NS_MSG_FOLDER_BUSY) {
+ // we need to null out the db so that parsing gets kicked off again.
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ ThrowAlertMsg("parsingFolderFailed", aMsgWindow);
+ }
+ return rv;
+ }
+
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // We have a valid database so lets extract necessary info.
+ UpdateSummaryTotals(true);
+ }
+ }
+ NS_IF_ADDREF(*aMsgDatabase = mDatabase);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::UpdateFolder(nsIMsgWindow* aWindow) {
+ (void)RefreshSizeOnDisk();
+ nsresult rv;
+
+ if (!PromptForMasterPasswordIfNecessary()) return NS_ERROR_FAILURE;
+
+ // If we don't currently have a database, get it. Otherwise, the folder has
+ // been updated (presumably this changes when we download headers when opening
+ // inbox). If it's updated, send NotifyFolderLoaded.
+ if (!mDatabase) {
+ // return of NS_ERROR_NOT_INITIALIZED means running parsing URL
+ // We don't need the return value, and assigning it to mDatabase which
+ // is already set internally leaks.
+ nsCOMPtr<nsIMsgDatabase> returnedDb;
+ rv = GetDatabaseWithReparse(this, aWindow, getter_AddRefs(returnedDb));
+ if (NS_SUCCEEDED(rv)) NotifyFolderEvent(kFolderLoaded);
+ } else {
+ bool valid;
+ rv = mDatabase->GetSummaryValid(&valid);
+ // don't notify folder loaded or try compaction if db isn't valid
+ // (we're probably reparsing or copying msgs to it)
+ if (NS_SUCCEEDED(rv) && valid)
+ NotifyFolderEvent(kFolderLoaded);
+ else if (mCopyState)
+ mCopyState->m_notifyFolderLoaded =
+ true; // defer folder loaded notification
+ else if (!m_parsingFolder) // if the db was already open, it's probably OK
+ // to load it if not parsing
+ NotifyFolderEvent(kFolderLoaded);
+ }
+ bool filtersRun;
+ bool hasNewMessages;
+ GetHasNewMessages(&hasNewMessages);
+ if (mDatabase) ApplyRetentionSettings();
+ // if we have new messages, try the filter plugins.
+ if (NS_SUCCEEDED(rv) && hasNewMessages)
+ (void)CallFilterPlugins(aWindow, &filtersRun);
+ // Callers should rely on folder loaded event to ensure completion of loading.
+ // So we'll return NS_OK even if parsing is still in progress
+ if (rv == NS_ERROR_NOT_INITIALIZED) rv = NS_OK;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetFolderURL(nsACString& aUrl) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> path;
+ rv = GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_GetURLSpecFromFile(path, aUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aUrl.Replace(0, strlen("file:"), "mailbox:");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::CreateStorageIfMissing(
+ nsIUrlListener* aUrlListener) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ GetParent(getter_AddRefs(msgParent));
+
+ // parent is probably not set because *this* was probably created by rdf
+ // and not by folder discovery. So, we have to compute the parent.
+ if (!msgParent) {
+ nsAutoCString folderName(mURI);
+ nsAutoCString uri;
+ int32_t leafPos = folderName.RFindChar('/');
+ nsAutoCString parentName(folderName);
+ if (leafPos > 0) {
+ // If there is a hierarchy, there is a parent.
+ // Don't strip off slash if it's the first character
+ parentName.SetLength(leafPos);
+ rv = GetOrCreateFolder(parentName, getter_AddRefs(msgParent));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ if (msgParent) {
+ nsString folderName;
+ GetName(folderName);
+ rv = msgParent->CreateSubfolder(folderName, nullptr);
+ // by definition, this is OK.
+ if (rv == NS_MSG_FOLDER_EXISTS) return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::CreateSubfolder(const nsAString& folderName,
+ nsIMsgWindow* msgWindow) {
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ nsresult rv =
+ CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyFolderAdded(newFolder);
+
+ return NS_OK;
+}
+
+nsresult nsMsgLocalMailFolder::CreateSubfolderInternal(
+ const nsAString& folderName, nsIMsgWindow* msgWindow,
+ nsIMsgFolder** aNewFolder) {
+ nsresult rv = CheckIfFolderExists(folderName, this, msgWindow);
+ // No need for an assertion: we already throw an alert.
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgStore->CreateFolder(this, folderName, aNewFolder);
+ if (rv == NS_MSG_ERROR_INVALID_FOLDER_NAME) {
+ ThrowAlertMsg("folderCreationFailed", msgWindow);
+ } else if (rv == NS_MSG_FOLDER_EXISTS) {
+ ThrowAlertMsg("folderExists", msgWindow);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // we need to notify explicitly the flag change because it failed when we
+ // did AddSubfolder
+ (*aNewFolder)->OnFlagChange(mFlags);
+ (*aNewFolder)
+ ->SetPrettyName(
+ folderName); // because empty trash will create a new trash folder
+ NotifyFolderAdded(*aNewFolder);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::CompactAll(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool storeSupportsCompaction;
+ msgStore->GetSupportsCompaction(&storeSupportsCompaction);
+ nsTArray<RefPtr<nsIMsgFolder>> folderArray;
+ if (storeSupportsCompaction) {
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ rv = rootFolder->GetDescendants(allDescendants);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t expungedBytes = 0;
+ for (auto folder : allDescendants) {
+ expungedBytes = 0;
+ if (folder) rv = folder->GetExpungedBytes(&expungedBytes);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (expungedBytes > 0) folderArray.AppendElement(folder);
+ }
+ }
+
+ if (folderArray.IsEmpty()) {
+ // Nothing to do - early out.
+ if (aListener) {
+ aListener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv);
+ return folderCompactor->CompactFolders(folderArray, aListener, aMsgWindow);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::Compact(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t expungedBytes = 0;
+ GetExpungedBytes(&expungedBytes);
+ bool supportsCompaction;
+ msgStore->GetSupportsCompaction(&supportsCompaction);
+ if (!supportsCompaction || expungedBytes == 0) {
+ // Nothing to do. Early out.
+ if (aListener) {
+ aListener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderCompactor->CompactFolders({this}, aListener, aMsgWindow);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::EmptyTrash(nsIUrlListener* aListener) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t flags;
+ trashFolder->GetFlags(&flags);
+ int32_t totalMessages = 0;
+ rv = trashFolder->GetTotalMessages(true, &totalMessages);
+ if (totalMessages <= 0) {
+ // Any folders to deal with?
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = trashFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (subFolders.IsEmpty()) {
+ return NS_OK;
+ }
+ }
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ rv = trashFolder->GetParent(getter_AddRefs(parentFolder));
+ if (NS_SUCCEEDED(rv) && parentFolder) {
+ nsCOMPtr<nsIDBFolderInfo> transferInfo;
+ trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo));
+ trashFolder->SetParent(nullptr);
+ parentFolder->PropagateDelete(trashFolder, true);
+ parentFolder->CreateSubfolder(u"Trash"_ns, nullptr);
+ nsCOMPtr<nsIMsgFolder> newTrashFolder;
+ rv = GetTrashFolder(getter_AddRefs(newTrashFolder));
+ if (NS_SUCCEEDED(rv) && newTrashFolder) {
+ nsCOMPtr<nsIMsgLocalMailFolder> localTrash =
+ do_QueryInterface(newTrashFolder);
+ if (transferInfo) newTrashFolder->SetDBTransferInfo(transferInfo);
+ if (localTrash) localTrash->RefreshSizeOnDisk();
+ // update the summary totals so the front end will
+ // show the right thing for the new trash folder
+ // see bug #161999
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ newTrashFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db));
+ if (dbFolderInfo) {
+ dbFolderInfo->SetNumUnreadMessages(0);
+ dbFolderInfo->SetNumMessages(0);
+ }
+ newTrashFolder->UpdateSummaryTotals(true);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::IsChildOfTrash(bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ uint32_t parentFlags = 0;
+ *result = false;
+ bool isServer;
+ nsresult rv = GetIsServer(&isServer);
+ if (NS_FAILED(rv) || isServer) return NS_OK;
+
+ rv = GetFlags(&parentFlags); // this is the parent folder
+ if (parentFlags & nsMsgFolderFlags::Trash) {
+ *result = true;
+ return rv;
+ }
+
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ nsCOMPtr<nsIMsgFolder> thisFolder;
+ rv = QueryInterface(NS_GET_IID(nsIMsgFolder), getter_AddRefs(thisFolder));
+
+ while (!isServer) {
+ thisFolder->GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder) return NS_OK;
+
+ rv = parentFolder->GetIsServer(&isServer);
+ if (NS_FAILED(rv) || isServer) return NS_OK;
+
+ rv = parentFolder->GetFlags(&parentFlags);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ if (parentFlags & nsMsgFolderFlags::Trash) {
+ *result = true;
+ return rv;
+ }
+
+ thisFolder = parentFolder;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::DeleteSelf(nsIMsgWindow* msgWindow) {
+ nsresult rv;
+ bool isChildOfTrash;
+ IsChildOfTrash(&isChildOfTrash);
+
+ uint32_t folderFlags = 0;
+ GetFlags(&folderFlags);
+ // when deleting from trash, or virtual folder, just delete it.
+ if (isChildOfTrash || folderFlags & nsMsgFolderFlags::Virtual)
+ return nsMsgDBFolder::DeleteSelf(msgWindow);
+
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgCopyService> copyService(
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyFolder(this, trashFolder, true, nullptr, msgWindow);
+ }
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::ConfirmFolderDeletion(nsIMsgWindow* aMsgWindow,
+ nsIMsgFolder* aFolder,
+ bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ NS_ENSURE_ARG(aMsgWindow);
+ NS_ENSURE_ARG(aFolder);
+ nsCOMPtr<nsIDocShell> docShell;
+ aMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (docShell) {
+ bool confirmDeletion = true;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pPrefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash",
+ &confirmDeletion);
+ if (confirmDeletion) {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/localMsgs.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = aFolder->GetName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AutoTArray<nsString, 1> formatStrings = {folderName};
+
+ nsAutoString deleteFolderDialogTitle;
+ rv = bundle->GetStringFromName("pop3DeleteFolderDialogTitle",
+ deleteFolderDialogTitle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString deleteFolderButtonLabel;
+ rv = bundle->GetStringFromName("pop3DeleteFolderButtonLabel",
+ deleteFolderButtonLabel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString confirmationStr;
+ rv = bundle->FormatStringFromName("pop3MoveFolderToTrash", formatStrings,
+ confirmationStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ if (dialog) {
+ int32_t buttonPressed = 0;
+ // Default the dialog to "cancel".
+ const uint32_t buttonFlags =
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);
+ bool dummyValue = false;
+ rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(),
+ confirmationStr.get(), buttonFlags,
+ deleteFolderButtonLabel.get(), nullptr, nullptr,
+ nullptr, &dummyValue, &buttonPressed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aResult = !buttonPressed; // "ok" is in position 0
+ }
+ } else
+ *aResult = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::Rename(const nsAString& aNewName,
+ nsIMsgWindow* msgWindow) {
+ // Renaming to the same name is easy
+ if (mName.Equals(aNewName)) return NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ nsresult rv = GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder) return NS_ERROR_NULL_POINTER;
+
+ rv = CheckIfFolderExists(aNewName, parentFolder, msgWindow);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgStore->RenameFolder(this, aNewName, getter_AddRefs(newFolder));
+ if (NS_FAILED(rv)) {
+ if (msgWindow)
+ (void)ThrowAlertMsg(
+ (rv == NS_MSG_FOLDER_EXISTS) ? "folderExists" : "folderRenameFailed",
+ msgWindow);
+ return rv;
+ }
+
+ int32_t count = mSubFolders.Count();
+ if (newFolder) {
+ // Because we just renamed the db, w/o setting the pretty name in it,
+ // we need to force the pretty name to be correct.
+ // SetPrettyName won't write the name to the db if it doesn't think the
+ // name has changed. This hack forces the pretty name to get set in the db.
+ // We could set the new pretty name on the db before renaming the .msf file,
+ // but if the rename failed, it would be out of sync.
+ newFolder->SetPrettyName(EmptyString());
+ newFolder->SetPrettyName(aNewName);
+ bool changed = false;
+ MatchOrChangeFilterDestination(newFolder, true /*case-insensitive*/,
+ &changed);
+ if (changed) AlertFilterChanged(msgWindow);
+
+ if (count > 0) newFolder->RenameSubFolders(msgWindow, this);
+
+ // Discover the subfolders inside this folder (this is recursive)
+ nsTArray<RefPtr<nsIMsgFolder>> dummy;
+ newFolder->GetSubFolders(dummy);
+
+ // the newFolder should have the same flags
+ newFolder->SetFlags(mFlags);
+ if (parentFolder) {
+ SetParent(nullptr);
+ parentFolder->PropagateDelete(this, false);
+ parentFolder->NotifyFolderAdded(newFolder);
+ }
+ // Forget our path, since this folder object renamed itself.
+ SetFilePath(nullptr);
+ newFolder->NotifyFolderEvent(kRenameCompleted);
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyFolderRenamed(this, newFolder);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::RenameSubFolders(nsIMsgWindow* msgWindow,
+ nsIMsgFolder* oldFolder) {
+ nsresult rv = NS_OK;
+ mInitialized = true;
+
+ uint32_t flags;
+ oldFolder->GetFlags(&flags);
+ SetFlags(flags);
+
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = oldFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* msgFolder : subFolders) {
+ nsString folderName;
+ rv = msgFolder->GetName(folderName);
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ AddSubfolder(folderName, getter_AddRefs(newFolder));
+ if (newFolder) {
+ newFolder->SetPrettyName(folderName);
+ bool changed = false;
+ msgFolder->MatchOrChangeFilterDestination(
+ newFolder, true /*case-insensitive*/, &changed);
+ if (changed) msgFolder->AlertFilterChanged(msgWindow);
+ newFolder->RenameSubFolders(msgWindow, msgFolder);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetPrettyName(nsAString& prettyName) {
+ return nsMsgDBFolder::GetPrettyName(prettyName);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::SetPrettyName(const nsAString& aName) {
+ nsresult rv = nsMsgDBFolder::SetPrettyName(aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString folderName;
+ rv = GetStringProperty("folderName", folderName);
+ NS_ConvertUTF16toUTF8 utf8FolderName(mName);
+ return NS_FAILED(rv) || !folderName.Equals(utf8FolderName)
+ ? SetStringProperty("folderName", utf8FolderName)
+ : rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetName(nsAString& aName) {
+ ReadDBFolderInfo(false);
+ return nsMsgDBFolder::GetName(aName);
+}
+
+nsresult nsMsgLocalMailFolder::OpenDatabase() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetFilePath(getter_AddRefs(file));
+
+ rv = msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) {
+ // check if we're a real folder by looking at the parent folder.
+ nsCOMPtr<nsIMsgFolder> parent;
+ GetParent(getter_AddRefs(parent));
+ if (parent) {
+ // This little dance creates an empty .msf file and then checks
+ // if the db is valid - this works if the folder is empty, which
+ // we don't have a direct way of checking.
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(db));
+ if (db) {
+ UpdateSummaryTotals(true);
+ db->Close(true);
+ mDatabase = nullptr;
+ db = nullptr;
+ rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase));
+ if (NS_FAILED(rv)) mDatabase = nullptr;
+ }
+ }
+ } else if (NS_FAILED(rv))
+ mDatabase = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo** folderInfo,
+ nsIMsgDatabase** db) {
+ if (!db || !folderInfo || !mPath || mIsServer)
+ return NS_ERROR_NULL_POINTER; // ducarroz: should we use
+ // NS_ERROR_INVALID_ARG?
+
+ nsresult rv;
+ if (mDatabase)
+ rv = NS_OK;
+ else {
+ rv = OpenDatabase();
+
+ if (mAddListener && mDatabase) mDatabase->AddListener(this);
+ }
+
+ NS_IF_ADDREF(*db = mDatabase);
+ if (NS_SUCCEEDED(rv) && *db) rv = (*db)->GetDBFolderInfo(folderInfo);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::ReadFromFolderCacheElem(
+ nsIMsgFolderCacheElement* element) {
+ NS_ENSURE_ARG_POINTER(element);
+ nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString utf8Name;
+ rv = element->GetCachedString("folderName", utf8Name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(utf8Name, mName);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::WriteToFolderCacheElem(
+ nsIMsgFolderCacheElement* element) {
+ NS_ENSURE_ARG_POINTER(element);
+ nsMsgDBFolder::WriteToFolderCacheElem(element);
+ return element->SetCachedString("folderName", NS_ConvertUTF16toUTF8(mName));
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetDeletable(bool* deletable) {
+ NS_ENSURE_ARG_POINTER(deletable);
+
+ bool isServer;
+ GetIsServer(&isServer);
+ *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::RefreshSizeOnDisk() {
+ int64_t oldFolderSize = mFolderSize;
+ // we set this to unknown to force it to get recalculated from disk
+ mFolderSize = kSizeUnknown;
+ if (NS_SUCCEEDED(GetSizeOnDisk(&mFolderSize)))
+ NotifyIntPropertyChanged(kFolderSize, oldFolderSize, mFolderSize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetSizeOnDisk(int64_t* aSize) {
+ NS_ENSURE_ARG_POINTER(aSize);
+
+ 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;
+
+ if (mFolderSize == kSizeUnknown) {
+ nsCOMPtr<nsIFile> file;
+ rv = GetFilePath(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Use a temporary variable so that we keep mFolderSize on kSizeUnknown
+ // if GetFileSize() fails.
+ int64_t folderSize;
+ rv = file->GetFileSize(&folderSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mFolderSize = folderSize;
+ }
+ *aSize = mFolderSize;
+ return NS_OK;
+}
+
+nsresult nsMsgLocalMailFolder::GetTrashFolder(nsIMsgFolder** result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv)) {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, result);
+ if (!*result) rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::DeleteMessages(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& msgHeaders, nsIMsgWindow* msgWindow,
+ bool deleteStorage, bool isMove, nsIMsgCopyServiceListener* listener,
+ bool allowUndo) {
+ nsresult rv;
+
+ // shift delete case - (delete to trash is handled in EndMove)
+ // this is also the case when applying retention settings.
+ if (deleteStorage && !isMove) {
+ nsTArray<RefPtr<nsIMsgDBHdr>> hdrsToDelete;
+ for (auto msgHdr : msgHeaders) {
+ uint32_t attachmentDetached = 0;
+ msgHdr->GetUint32Property("attachmentDetached", &attachmentDetached);
+ if (!attachmentDetached) {
+ hdrsToDelete.AppendElement(msgHdr);
+ }
+ }
+ MarkMsgsOnPop3Server(hdrsToDelete, POP3_DELETE);
+ }
+
+ bool isTrashFolder = mFlags & nsMsgFolderFlags::Trash;
+
+ // notify on delete from trash and shift-delete
+ if (!isMove && (deleteStorage || isTrashFolder)) {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ if (listener) {
+ listener->OnStartCopy();
+ listener->OnStopCopy(NS_OK);
+ }
+ notifier->NotifyMsgsDeleted(msgHeaders);
+ }
+ }
+
+ if (!deleteStorage && !isTrashFolder) {
+ // We're moving the messages to trash folder. Start by kicking off a copy.
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // When the copy completes, DeleteMessages() will be called again to
+ // perform the actual delete.
+ return copyService->CopyMessages(this, msgHeaders, trashFolder, true,
+ listener, msgWindow, allowUndo);
+ }
+ } else {
+ // Performing an _actual_ delete. There are two ways we got here:
+ // 1) We're deleting messages without moving to trash.
+ // 2) We're in the second phase of a Move (to trash or elsewhere). The
+ // copy succeeded, and now we need to delete the source messages.
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ rv = GetDatabaseWOReparse(getter_AddRefs(msgDB));
+ if (NS_SUCCEEDED(rv)) {
+ if (deleteStorage && isMove && GetDeleteFromServerOnMove())
+ MarkMsgsOnPop3Server(msgHeaders, POP3_DELETE);
+
+ nsCOMPtr<nsISupports> msgSupport;
+ rv = EnableNotifications(allMessageCountNotifications, false);
+ if (NS_SUCCEEDED(rv)) {
+ // First, delete the actual messages in the store.
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_SUCCEEDED(rv)) {
+ // Second, remove the message entries from the DB.
+ rv = msgStore->DeleteMessages(msgHeaders);
+ for (auto hdr : msgHeaders) {
+ rv = msgDB->DeleteHeader(hdr, nullptr, false, true);
+ }
+ }
+ } else if (rv == NS_MSG_FOLDER_BUSY) {
+ ThrowAlertMsg("deletingMsgsFailed", msgWindow);
+ }
+
+ // Let everyone know the operation has finished.
+ NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted
+ : kDeleteOrMoveMsgFailed);
+ // NOTE: This reenabling also forces immediate recount + notification.
+ EnableNotifications(allMessageCountNotifications, true);
+ if (msgWindow) {
+ AutoCompact(msgWindow);
+ }
+ }
+ }
+
+ if (msgWindow && !isMove && (deleteStorage || isTrashFolder)) {
+ // Clear undo and redo stack.
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr) {
+ txnMgr->Clear();
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::AddMessageDispositionState(
+ nsIMsgDBHdr* aMessage, nsMsgDispositionState aDispositionFlag) {
+ nsMsgMessageFlagType msgFlag = 0;
+ switch (aDispositionFlag) {
+ case nsIMsgFolder::nsMsgDispositionState_Replied:
+ msgFlag = nsMsgMessageFlags::Replied;
+ break;
+ case nsIMsgFolder::nsMsgDispositionState_Forwarded:
+ msgFlag = nsMsgMessageFlags::Forwarded;
+ break;
+ case nsIMsgFolder::nsMsgDispositionState_Redirected:
+ msgFlag = nsMsgMessageFlags::Redirected;
+ break;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv =
+ nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->ChangeFlags({aMessage}, msgFlag, true);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::MarkMessagesRead(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, bool aMarkRead) {
+ nsresult rv = nsMsgDBFolder::MarkMessagesRead(aMessages, aMarkRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Read, aMarkRead);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::MarkMessagesFlagged(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, bool aMarkFlagged) {
+ nsresult rv = nsMsgDBFolder::MarkMessagesFlagged(aMessages, aMarkFlagged);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Marked,
+ aMarkFlagged);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::MarkAllMessagesRead(nsIMsgWindow* aMsgWindow) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsMsgKey> thoseMarked;
+ EnableNotifications(allMessageCountNotifications, false);
+ rv = mDatabase->MarkAllRead(thoseMarked);
+ EnableNotifications(allMessageCountNotifications, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (thoseMarked.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ rv = MsgGetHeadersFromKeys(mDatabase, thoseMarked, messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+
+ // Setup a undo-state
+ if (aMsgWindow)
+ rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked.Elements(),
+ thoseMarked.Length());
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::MarkThreadRead(nsIMsgThread* thread) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsMsgKey> thoseMarked;
+ rv = mDatabase->MarkThreadRead(thread, nullptr, thoseMarked);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (thoseMarked.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ rv = MsgGetHeadersFromKeys(mDatabase, thoseMarked, messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::InitCopyState(
+ nsISupports* aSupport, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, nsIMsgCopyServiceListener* listener, nsIMsgWindow* msgWindow,
+ bool isFolder, bool allowUndo) {
+ nsCOMPtr<nsIFile> path;
+
+ NS_ASSERTION(!mCopyState, "already copying a msg into this folder");
+ if (mCopyState) return NS_ERROR_FAILURE; // already has a copy in progress
+
+ // get mDatabase set, so we can use it to add new hdrs to this db.
+ // calling GetDatabase will set mDatabase - we use the comptr
+ // here to avoid doubling the refcnt on mDatabase. We don't care if this
+ // fails - we just want to give it a chance. It will definitely fail in
+ // nsLocalMailFolder::EndCopy because we will have written data to the folder
+ // and changed its size.
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ GetDatabaseWOReparse(getter_AddRefs(msgDB));
+ bool isLocked;
+
+ GetLocked(&isLocked);
+ if (isLocked) return NS_MSG_FOLDER_BUSY;
+
+ AcquireSemaphore(static_cast<nsIMsgLocalMailFolder*>(this));
+
+ mCopyState = new nsLocalMailCopyState();
+ NS_ENSURE_TRUE(mCopyState, NS_ERROR_OUT_OF_MEMORY);
+
+ mCopyState->m_dataBuffer = (char*)PR_CALLOC(COPY_BUFFER_SIZE + 1);
+ NS_ENSURE_TRUE(mCopyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ mCopyState->m_dataBufferSize = COPY_BUFFER_SIZE;
+ mCopyState->m_destDB = msgDB;
+
+ mCopyState->m_srcSupport = aSupport;
+ mCopyState->m_messages = messages.Clone();
+ mCopyState->m_curCopyIndex = 0;
+ mCopyState->m_isMove = isMove;
+ mCopyState->m_isFolder = isFolder;
+ mCopyState->m_allowUndo = allowUndo;
+ mCopyState->m_msgWindow = msgWindow;
+ mCopyState->m_totalMsgCount = messages.Length();
+ if (listener) mCopyState->m_listener = listener;
+ mCopyState->m_copyingMultipleMessages = false;
+ mCopyState->m_wholeMsgInStream = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::OnAnnouncerGoingAway(
+ nsIDBChangeAnnouncer* instigator) {
+ if (mCopyState) mCopyState->m_destDB = nullptr;
+ return nsMsgDBFolder::OnAnnouncerGoingAway(instigator);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::OnCopyCompleted(nsISupports* srcSupport,
+ bool moveCopySucceeded) {
+ if (mCopyState && mCopyState->m_notifyFolderLoaded)
+ NotifyFolderEvent(kFolderLoaded);
+
+ (void)RefreshSizeOnDisk();
+ // we are the destination folder for a move/copy
+ bool haveSemaphore;
+ nsresult rv =
+ TestSemaphore(static_cast<nsIMsgLocalMailFolder*>(this), &haveSemaphore);
+ if (NS_SUCCEEDED(rv) && haveSemaphore)
+ ReleaseSemaphore(static_cast<nsIMsgLocalMailFolder*>(this));
+
+ if (mCopyState && !mCopyState->m_newMsgKeywords.IsEmpty() &&
+ mCopyState->m_newHdr) {
+ AddKeywordsToMessages({&*mCopyState->m_newHdr},
+ mCopyState->m_newMsgKeywords);
+ }
+ if (moveCopySucceeded && mDatabase) {
+ mDatabase->SetSummaryValid(true);
+ (void)CloseDBIfFolderNotOpen(false);
+ }
+
+ delete mCopyState;
+ mCopyState = nullptr;
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return copyService->NotifyCompletion(
+ srcSupport, this, moveCopySucceeded ? NS_OK : NS_ERROR_FAILURE);
+}
+
+bool nsMsgLocalMailFolder::CheckIfSpaceForCopy(nsIMsgWindow* msgWindow,
+ nsIMsgFolder* srcFolder,
+ nsISupports* srcSupports,
+ bool isMove,
+ int64_t totalMsgSize) {
+ bool spaceNotAvailable = true;
+ nsresult rv =
+ WarnIfLocalFileTooBig(msgWindow, totalMsgSize, &spaceNotAvailable);
+ if (NS_FAILED(rv) || spaceNotAvailable) {
+ if (isMove && srcFolder)
+ srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed);
+ OnCopyCompleted(srcSupports, false);
+ return false;
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::CopyMessages(nsIMsgFolder* srcFolder,
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& srcHdrs,
+ bool isMove, nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener,
+ bool isFolder, bool allowUndo) {
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ bool isServer;
+ nsresult rv = GetIsServer(&isServer);
+ if (NS_SUCCEEDED(rv) && isServer) {
+ NS_ERROR("Destination is the root folder. Cannot move/copy here");
+ if (isMove) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed);
+ return OnCopyCompleted(srcSupport, false);
+ }
+
+ UpdateTimestamps(allowUndo);
+ nsCString protocolType;
+ rv = srcFolder->GetURI(protocolType);
+ protocolType.SetLength(protocolType.FindChar(':'));
+
+ // If we're offline and the source folder is imap or news, to do the
+ // copy the message bodies MUST reside in offline storage.
+ bool needOfflineBodies =
+ (WeAreOffline() && (protocolType.LowerCaseEqualsLiteral("imap") ||
+ protocolType.LowerCaseEqualsLiteral("news")));
+ int64_t totalMsgSize = 0;
+ bool allMsgsHaveOfflineStore = true;
+ for (auto message : srcHdrs) {
+ nsMsgKey key;
+ uint32_t msgSize;
+ message->GetMessageSize(&msgSize);
+
+ /* 200 is a per-message overhead to account for any extra data added
+ to the message.
+ */
+ totalMsgSize += msgSize + 200;
+
+ // Check if each source folder message has offline storage regardless
+ // of whether we're online or offline.
+ message->GetMessageKey(&key);
+ bool hasMsgOffline = false;
+ srcFolder->HasMsgOffline(key, &hasMsgOffline);
+ allMsgsHaveOfflineStore = allMsgsHaveOfflineStore && hasMsgOffline;
+
+ // If we're offline and not all messages are in offline storage, the copy
+ // or move can't occur and a notification for the user to download the
+ // messages is posted.
+ if (needOfflineBodies && !hasMsgOffline) {
+ if (isMove) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed);
+ ThrowAlertMsg("cantMoveMsgWOBodyOffline", msgWindow);
+ return OnCopyCompleted(srcSupport, false);
+ }
+ }
+
+ if (!CheckIfSpaceForCopy(msgWindow, srcFolder, srcSupport, isMove,
+ totalMsgSize))
+ return NS_OK;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool storeDidCopy = false;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsITransaction> undoTxn;
+ nsTArray<RefPtr<nsIMsgDBHdr>> dstHdrs;
+ rv = msgStore->CopyMessages(isMove, srcHdrs, this, dstHdrs,
+ getter_AddRefs(undoTxn), &storeDidCopy);
+ if (storeDidCopy) {
+ NS_ASSERTION(undoTxn, "if store does copy, it needs to add undo action");
+ if (msgWindow && undoTxn) {
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr) txnMgr->DoTransaction(undoTxn);
+ }
+ if (isMove) {
+ srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted
+ : kDeleteOrMoveMsgFailed);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // If the store did the copy, like maildir, we need to mark messages on
+ // the server. Otherwise that's done in EndMove().
+ nsCOMPtr<nsIMsgLocalMailFolder> localDstFolder;
+ QueryInterface(NS_GET_IID(nsIMsgLocalMailFolder),
+ getter_AddRefs(localDstFolder));
+ if (localDstFolder) {
+ // If we are the trash and a local msg is being moved to us, mark the
+ // source for delete from server, if so configured.
+ if (mFlags & nsMsgFolderFlags::Trash) {
+ // If we're deleting on all moves, we'll mark this message for
+ // deletion when we call DeleteMessages on the source folder. So don't
+ // mark it for deletion here, in that case.
+ if (!GetDeleteFromServerOnMove()) {
+ localDstFolder->MarkMsgsOnPop3Server(dstHdrs, POP3_DELETE);
+ }
+ }
+ }
+ }
+
+ OnCopyCompleted(srcSupport, NS_SUCCEEDED(rv));
+ return rv;
+ }
+ // If the store doesn't do the copy, we'll stream the source messages into
+ // the target folder, using getMsgInputStream and getNewMsgOutputStream.
+
+ // don't update the counts in the dest folder until it is all over
+ EnableNotifications(allMessageCountNotifications, false);
+
+ // sort the message array by key
+ nsTArray<nsMsgKey> keyArray(srcHdrs.Length());
+ nsTArray<RefPtr<nsIMsgDBHdr>> sortedMsgs(srcHdrs.Length());
+ for (nsIMsgDBHdr* aMessage : srcHdrs) {
+ nsMsgKey key;
+ aMessage->GetMessageKey(&key);
+ keyArray.AppendElement(key);
+ }
+ keyArray.Sort();
+ rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitCopyState(srcSupport, sortedMsgs, isMove, listener, msgWindow,
+ isFolder, allowUndo);
+
+ if (NS_FAILED(rv)) {
+ ThrowAlertMsg("operationFailedFolderBusy", msgWindow);
+ (void)OnCopyCompleted(srcSupport, false);
+ return rv;
+ }
+
+ if (!protocolType.LowerCaseEqualsLiteral("mailbox")) {
+ // Copying from a non-mbox source, so we will be synthesising
+ // a "From " line and "X-Mozilla-*" headers before copying the message
+ // proper.
+ mCopyState->m_dummyEnvelopeNeeded = true;
+ nsParseMailMessageState* parseMsgState = new nsParseMailMessageState();
+ if (parseMsgState) {
+ nsCOMPtr<nsIMsgDatabase> msgDb;
+ mCopyState->m_parseMsgState = parseMsgState;
+ GetDatabaseWOReparse(getter_AddRefs(msgDb));
+ if (msgDb) parseMsgState->SetMailDB(msgDb);
+ }
+ }
+
+ // undo stuff
+ if (allowUndo) // no undo for folder move/copy or or move/copy from search
+ // window
+ {
+ RefPtr<nsLocalMoveCopyMsgTxn> msgTxn = new nsLocalMoveCopyMsgTxn;
+ if (msgTxn && NS_SUCCEEDED(msgTxn->Init(srcFolder, this, isMove))) {
+ msgTxn->SetMsgWindow(msgWindow);
+ if (isMove) {
+ if (mFlags & nsMsgFolderFlags::Trash)
+ msgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ else
+ msgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ } else
+ msgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ msgTxn.swap(mCopyState->m_undoMsgTxn);
+ }
+ }
+
+ if (srcHdrs.Length() > 1 &&
+ ((protocolType.LowerCaseEqualsLiteral("imap") &&
+ !allMsgsHaveOfflineStore) ||
+ protocolType.LowerCaseEqualsLiteral("mailbox"))) {
+ // For an imap source folder with more than one message to be copied that
+ // are not all in offline storage, this fetches all the messages from the
+ // imap server to do the copy. When source folder is "mailbox", this is not
+ // a concern since source messages are in local storage.
+ mCopyState->m_copyingMultipleMessages = true;
+ rv = CopyMessagesTo(keyArray, msgWindow, isMove);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("copy message failed");
+ (void)OnCopyCompleted(srcSupport, false);
+ }
+ } else {
+ // This obtains the source messages from local/offline storage to do the
+ // copy. Note: CopyMessageTo() actually handles one or more messages.
+ nsIMsgDBHdr* msgSupport = mCopyState->m_messages[0];
+ if (msgSupport) {
+ rv = CopyMessageTo(msgSupport, msgWindow, isMove);
+ if (NS_FAILED(rv)) {
+ NS_ASSERTION(false, "copy message failed");
+ (void)OnCopyCompleted(srcSupport, false);
+ }
+ }
+ }
+ // if this failed immediately, need to turn back on notifications and inform
+ // FE.
+ if (NS_FAILED(rv)) {
+ if (isMove) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed);
+ EnableNotifications(allMessageCountNotifications, true);
+ }
+ return rv;
+}
+
+// for srcFolder that are on different server than the dstFolder.
+// "this" is the parent of the new dest folder.
+nsresult nsMsgLocalMailFolder::CopyFolderAcrossServer(
+ nsIMsgFolder* srcFolder, nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener, bool moveMsgs) {
+ mInitialized = true;
+
+ nsString folderName;
+ srcFolder->GetName(folderName);
+
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ nsresult rv = CreateSubfolderInternal(folderName, msgWindow,
+ getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgEnumerator> messages;
+ rv = srcFolder->GetMessages(getter_AddRefs(messages));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> msgArray;
+ bool hasMoreElements = false;
+
+ if (messages) rv = messages->HasMoreElements(&hasMoreElements);
+
+ while (NS_SUCCEEDED(rv) && hasMoreElements) {
+ nsCOMPtr<nsIMsgDBHdr> msg;
+ rv = messages->GetNext(getter_AddRefs(msg));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgArray.AppendElement(msg);
+ rv = messages->HasMoreElements(&hasMoreElements);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (msgArray.Length() > 0) // if only srcFolder has messages..
+ // Allow move of copied messages but keep source folder in place.
+ newMsgFolder->CopyMessages(srcFolder, msgArray, moveMsgs, msgWindow,
+ listener, true /* is folder*/,
+ false /* allowUndo */);
+ else {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
+ do_QueryInterface(newMsgFolder);
+ if (localFolder) {
+ // normally these would get called from ::EndCopy when the last message
+ // was finished copying. But since there are no messages, we have to call
+ // them explicitly.
+ nsCOMPtr<nsISupports> srcSupports = do_QueryInterface(srcFolder);
+ localFolder->CopyAllSubFolders(srcFolder, msgWindow, listener, moveMsgs);
+ return localFolder->OnCopyCompleted(srcSupports, true);
+ }
+ }
+ return NS_OK; // otherwise the front-end will say Exception::CopyFolder
+}
+
+nsresult // copy the sub folders
+nsMsgLocalMailFolder::CopyAllSubFolders(nsIMsgFolder* srcFolder,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener,
+ bool isMove) {
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ nsresult rv = srcFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* folder : subFolders) {
+ CopyFolderAcrossServer(folder, msgWindow, listener, isMove);
+ }
+ return NS_OK;
+}
+
+// "this" is the destination (parent) folder that srcFolder is copied to.
+NS_IMETHODIMP
+nsMsgLocalMailFolder::CopyFolder(nsIMsgFolder* srcFolder, bool isMoveFolder,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener) {
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ nsresult rv;
+ bool sameServer;
+ rv = IsOnSameServer(this, srcFolder, &sameServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (sameServer && isMoveFolder) {
+ // Do a pure folder move within the same Local Folder account/server. where
+ // "pure" means the folder AND messages are copied to the Local Folders
+ // destination and then both are removed from source account.
+ rv = CopyFolderLocal(srcFolder, isMoveFolder, msgWindow, listener);
+ } else {
+ // !sameServer OR it's a copy. Unit tests expect a successful folder
+ // copy within Local Folders account/server even though the UI forbids copy
+ // and only allows moves inside the same account. CopyFolderAcrossServer(),
+ // called below, handles the folder copy within Local Folders (needed by
+ // unit tests) and it handles the folder move or copy from another account
+ // or server into Local Folders. The move from another account is "impure"
+ // since just the messages are moved but the source folder remains in place.
+ rv = CopyFolderAcrossServer(srcFolder, msgWindow, listener, isMoveFolder);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::CopyFolderLocal(nsIMsgFolder* srcFolder,
+ bool isMoveFolder,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* aListener) {
+ mInitialized = true;
+ bool isChildOfTrash;
+ nsresult rv = IsChildOfTrash(&isChildOfTrash);
+ if (NS_SUCCEEDED(rv) && isChildOfTrash) {
+ // do it just for the parent folder (isMoveFolder is true for parent only)
+ // if we are deleting/moving a folder tree don't confirm for rss folders.
+ if (isMoveFolder) {
+ // if there's a msgWindow, confirm the deletion
+ if (msgWindow) {
+ bool okToDelete = false;
+ ConfirmFolderDeletion(msgWindow, srcFolder, &okToDelete);
+ if (!okToDelete) return NS_MSG_ERROR_COPY_FOLDER_ABORTED;
+ }
+ // if we are moving a favorite folder to trash, we should clear the
+ // favorites flag so it gets removed from the view.
+ srcFolder->ClearFlag(nsMsgFolderFlags::Favorite);
+ }
+
+ bool match = false;
+ srcFolder->MatchOrChangeFilterDestination(nullptr, false, &match);
+ if (match && msgWindow) {
+ bool confirmed = false;
+ srcFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirmed);
+ if (!confirmed) return NS_MSG_ERROR_COPY_FOLDER_ABORTED;
+ }
+ }
+
+ nsAutoString newFolderName;
+ nsAutoString folderName;
+ rv = srcFolder->GetName(folderName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isMoveFolder) {
+ rv = CheckIfFolderExists(folderName, this, msgWindow);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ // If folder name already exists in destination, generate a new unique name.
+ bool containsChild = true;
+ uint32_t i;
+ for (i = 1; containsChild; i++) {
+ newFolderName.Assign(folderName);
+ if (i > 1) {
+ // This could be localizable but Toolkit is fine without it, see
+ // mozilla/toolkit/content/contentAreaUtils.js::uniqueFile()
+ newFolderName.Append('(');
+ newFolderName.AppendInt(i);
+ newFolderName.Append(')');
+ }
+ rv = ContainsChildNamed(newFolderName, &containsChild);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // 'i' is one more than the number of iterations done
+ // and the number tacked onto the name of the folder.
+ if (i > 2 && !isChildOfTrash) {
+ // Folder name already exists, ask if rename is OK.
+ // If moving to Trash, don't ask and do it.
+ if (!ConfirmAutoFolderRename(msgWindow, folderName, newFolderName))
+ return NS_MSG_ERROR_COPY_FOLDER_ABORTED;
+ }
+ }
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return msgStore->CopyFolder(srcFolder, this, isMoveFolder, msgWindow,
+ aListener, newFolderName);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::CopyFileMessage(nsIFile* aFile, nsIMsgDBHdr* msgToReplace,
+ bool isDraftOrTemplate,
+ uint32_t newMsgFlags,
+ const nsACString& aNewMsgKeywords,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ nsParseMailMessageState* parseMsgState = nullptr;
+ int64_t fileSize = 0;
+
+ nsCOMPtr<nsISupports> fileSupport(aFile);
+
+ aFile->GetFileSize(&fileSize);
+ if (!CheckIfSpaceForCopy(msgWindow, nullptr, fileSupport, false, fileSize))
+ return NS_OK;
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ if (msgToReplace) messages.AppendElement(msgToReplace);
+
+ rv = InitCopyState(fileSupport, messages, msgToReplace ? true : false,
+ listener, msgWindow, false, false);
+ if (NS_SUCCEEDED(rv)) {
+ if (mCopyState) {
+ mCopyState->m_newMsgKeywords = aNewMsgKeywords;
+ mCopyState->m_flags = newMsgFlags;
+ }
+
+ parseMsgState = new nsParseMailMessageState();
+ NS_ENSURE_TRUE(parseMsgState, NS_ERROR_OUT_OF_MEMORY);
+ nsCOMPtr<nsIMsgDatabase> msgDb;
+ mCopyState->m_parseMsgState = parseMsgState;
+ GetDatabaseWOReparse(getter_AddRefs(msgDb));
+ if (msgDb) parseMsgState->SetMailDB(msgDb);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+
+ // All or none for adding a message file to the store
+ if (NS_SUCCEEDED(rv) && fileSize > PR_INT32_MAX)
+ rv = NS_ERROR_ILLEGAL_VALUE; // may need error code for max msg size
+
+ if (NS_SUCCEEDED(rv) && inputStream) {
+ char buffer[5];
+ uint32_t readCount;
+ rv = inputStream->Read(buffer, 5, &readCount);
+ if (NS_SUCCEEDED(rv)) {
+ if (strncmp(buffer, "From ", 5))
+ mCopyState->m_dummyEnvelopeNeeded = true;
+ nsCOMPtr<nsISeekableStream> seekableStream =
+ do_QueryInterface(inputStream, &rv);
+ if (NS_SUCCEEDED(rv))
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+ }
+
+ mCopyState->m_wholeMsgInStream = true;
+ if (NS_SUCCEEDED(rv)) rv = BeginCopy();
+
+ if (NS_SUCCEEDED(rv)) rv = CopyData(inputStream, (int32_t)fileSize);
+
+ if (NS_SUCCEEDED(rv)) rv = EndCopy(true);
+
+ // mDatabase should have been initialized above - if we got msgDb
+ // If we were going to delete, here is where we would do it. But because
+ // existing code already supports doing those deletes, we are just going
+ // to end the copy.
+ if (NS_SUCCEEDED(rv) && msgToReplace && mDatabase)
+ rv = OnCopyCompleted(fileSupport, true);
+
+ if (inputStream) inputStream->Close();
+ }
+
+ if (NS_FAILED(rv)) (void)OnCopyCompleted(fileSupport, false);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetNewMessages(nsIMsgWindow* aWindow,
+ nsIUrlListener* aListener) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ nsCOMPtr<nsILocalMailIncomingServer> localMailServer =
+ do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ // XXX todo, move all this into nsILocalMailIncomingServer's GetNewMail
+ // so that we don't have to have RSS foo here.
+ nsCOMPtr<nsIRssIncomingServer> rssServer = do_QueryInterface(server, &rv);
+ mozilla::Unused << rssServer;
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIURI> resultURI;
+ return localMailServer->GetNewMail(aWindow, aListener, this,
+ getter_AddRefs(resultURI));
+ }
+
+ nsCOMPtr<nsIMsgFolder> inbox;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(inbox));
+ }
+ nsCOMPtr<nsIMsgLocalMailFolder> localInbox = do_QueryInterface(inbox, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ bool valid = false;
+ nsCOMPtr<nsIMsgDatabase> db;
+ // this will kick off a reparse if the db is out of date.
+ rv = localInbox->GetDatabaseWithReparse(nullptr, aWindow,
+ getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIURI> resultURI;
+ db->GetSummaryValid(&valid);
+ rv = valid ? localMailServer->GetNewMail(aWindow, aListener, inbox,
+ getter_AddRefs(resultURI))
+ : localInbox->SetCheckForNewMessagesAfterParsing(true);
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::WriteStartOfNewMessage() {
+ // If moving, delete the message in source folder that was just copied.
+ // It will have index one less than the current index.
+ // But only do this if source folder is imap.
+ // Could be optimized (DeleteMessages() operate on non-array)?
+ nsresult rv;
+ uint32_t idx = mCopyState->m_curCopyIndex;
+ if (mCopyState->m_isMove && idx) {
+ nsCOMPtr<nsIMsgFolder> srcFolder =
+ do_QueryInterface(mCopyState->m_srcSupport, &rv);
+ if (NS_SUCCEEDED(rv) && srcFolder) {
+ // Delete source messages as we go only if they come from
+ // an imap folder.
+ nsCString protocolType;
+ if (NS_SUCCEEDED(srcFolder->GetURI(protocolType))) {
+ if (StringHead(protocolType, 5).LowerCaseEqualsLiteral("imap:")) {
+ // Create "array" of one message header to delete
+ idx--;
+ if (idx < mCopyState->m_messages.Length()) {
+ // Above check avoids a possible MOZ_CRASH after error recovery.
+ RefPtr<nsIMsgDBHdr> msg = mCopyState->m_messages[idx];
+ srcFolder->DeleteMessages({msg}, mCopyState->m_msgWindow, true,
+ true, nullptr, mCopyState->m_allowUndo);
+ }
+ }
+ }
+ }
+ }
+
+ // CopyFileMessage() and CopyMessages() from servers other than pop3
+ if (mCopyState->m_parseMsgState) {
+ // Make sure the parser knows where the "From " separator is.
+ // A hack for Bug 1734847.
+ // If we were using nsMsgMailboxParser, that would handle it automatically.
+ // But we're using the base class (nsParseMailMessageState) which doesn't.
+ mCopyState->m_parseMsgState->m_envelope_pos =
+ mCopyState->m_parseMsgState->m_position;
+
+ if (mCopyState->m_parseMsgState->m_newMsgHdr) {
+ mCopyState->m_parseMsgState->m_newMsgHdr->GetMessageKey(
+ &mCopyState->m_curDstKey);
+ }
+ mCopyState->m_parseMsgState->SetState(
+ nsIMsgParseMailMsgState::ParseHeadersState);
+ }
+ if (mCopyState->m_dummyEnvelopeNeeded) {
+ nsCString result;
+ nsAutoCString nowStr;
+ MsgGenerateNowStr(nowStr);
+ result.AppendLiteral("From - ");
+ result.Append(nowStr);
+ result.Append(MSG_LINEBREAK);
+
+ uint32_t bytesWritten;
+ mCopyState->m_fileStream->Write(result.get(), result.Length(),
+ &bytesWritten);
+ if (mCopyState->m_parseMsgState) {
+ mCopyState->m_parseMsgState->ParseAFolderLine(result.get(),
+ result.Length());
+ // Make sure the parser knows where the header block begins.
+ // Another hack for Bug 1734847.
+ mCopyState->m_parseMsgState->m_headerstartpos =
+ mCopyState->m_parseMsgState->m_position;
+ }
+
+ // *** jt - hard code status line for now; come back later
+ char statusStrBuf[50];
+ if (mCopyState->m_curCopyIndex < mCopyState->m_messages.Length()) {
+ uint32_t dbFlags = 0;
+ mCopyState->m_messages[mCopyState->m_curCopyIndex]->GetFlags(&dbFlags);
+
+ // write out x-mozilla-status, but make sure we don't write out
+ // nsMsgMessageFlags::Offline
+ PR_snprintf(
+ statusStrBuf, sizeof(statusStrBuf),
+ X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK,
+ dbFlags &
+ ~(nsMsgMessageFlags::RuntimeOnly | nsMsgMessageFlags::Offline) &
+ 0x0000FFFF);
+ } else
+ strcpy(statusStrBuf, "X-Mozilla-Status: 0001" MSG_LINEBREAK);
+ mCopyState->m_fileStream->Write(statusStrBuf, strlen(statusStrBuf),
+ &bytesWritten);
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->ParseAFolderLine(statusStrBuf,
+ strlen(statusStrBuf));
+ result = "X-Mozilla-Status2: 00000000" MSG_LINEBREAK;
+ mCopyState->m_fileStream->Write(result.get(), result.Length(),
+ &bytesWritten);
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->ParseAFolderLine(result.get(),
+ result.Length());
+ result = X_MOZILLA_KEYWORDS;
+ mCopyState->m_fileStream->Write(result.get(), result.Length(),
+ &bytesWritten);
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->ParseAFolderLine(result.get(),
+ result.Length());
+ mCopyState->m_fromLineSeen = true;
+ } else
+ mCopyState->m_fromLineSeen = false;
+
+ mCopyState->m_curCopyIndex++;
+ return NS_OK;
+}
+
+nsresult nsMsgLocalMailFolder::InitCopyMsgHdrAndFileStream() {
+ nsresult rv = GetMsgStore(getter_AddRefs(mCopyState->m_msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mCopyState->m_msgStore->GetNewMsgOutputStream(
+ this, getter_AddRefs(mCopyState->m_newHdr),
+ getter_AddRefs(mCopyState->m_fileStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->SetNewMsgHdr(mCopyState->m_newHdr);
+ return rv;
+}
+
+// nsICopyMessageListener
+NS_IMETHODIMP nsMsgLocalMailFolder::BeginCopy() {
+ if (!mCopyState) return NS_ERROR_NULL_POINTER;
+
+ if (!mCopyState->m_copyingMultipleMessages) {
+ nsresult rv = InitCopyMsgHdrAndFileStream();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // The output stream may or may not be set already, depending upon all kinds
+ // of inscrutable conditions. This needs cleaning up (see Bug 1731177).
+ if (!mCopyState->m_fileStream) {
+ return NS_OK;
+ }
+
+ int32_t messageIndex = (mCopyState->m_copyingMultipleMessages)
+ ? mCopyState->m_curCopyIndex - 1
+ : mCopyState->m_curCopyIndex;
+ NS_ASSERTION(!mCopyState->m_copyingMultipleMessages || messageIndex >= 0,
+ "messageIndex invalid");
+ // by the time we get here, m_curCopyIndex is 1 relative because
+ // WriteStartOfNewMessage increments it
+ if (messageIndex < (int32_t)mCopyState->m_messages.Length()) {
+ mCopyState->m_message = mCopyState->m_messages[messageIndex];
+ } else {
+ mCopyState->m_message = nullptr;
+ }
+ // The flags of the source message can get changed when it is deleted, so
+ // save them here.
+ if (mCopyState->m_message)
+ mCopyState->m_message->GetFlags(&(mCopyState->m_flags));
+ DisplayMoveCopyStatusMsg();
+ if (mCopyState->m_listener)
+ mCopyState->m_listener->OnProgress(mCopyState->m_curCopyIndex,
+ mCopyState->m_totalMsgCount);
+ // if we're copying more than one message, StartMessage will handle this.
+ return !mCopyState->m_copyingMultipleMessages ? WriteStartOfNewMessage()
+ : NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::CopyData(nsIInputStream* aIStream,
+ int32_t aLength) {
+ // check to make sure we have control of the write.
+ bool haveSemaphore;
+ nsresult rv = NS_OK;
+
+ rv = TestSemaphore(static_cast<nsIMsgLocalMailFolder*>(this), &haveSemaphore);
+ if (NS_FAILED(rv)) return rv;
+ if (!haveSemaphore) return NS_MSG_FOLDER_BUSY;
+
+ if (!mCopyState) return NS_ERROR_OUT_OF_MEMORY;
+
+ uint32_t readCount;
+ // allocate one extra byte for '\0' at the end and another extra byte at the
+ // front to insert a '>' if we have a "From" line
+ // allocate 2 more for crlf that may be needed for those without crlf at end
+ // of file
+ if (aLength + mCopyState->m_leftOver + 4 > mCopyState->m_dataBufferSize) {
+ char* newBuffer = (char*)PR_REALLOC(mCopyState->m_dataBuffer,
+ aLength + mCopyState->m_leftOver + 4);
+ if (!newBuffer) return NS_ERROR_OUT_OF_MEMORY;
+
+ mCopyState->m_dataBuffer = newBuffer;
+ mCopyState->m_dataBufferSize = aLength + mCopyState->m_leftOver + 3;
+ }
+
+ rv = aIStream->Read(mCopyState->m_dataBuffer + mCopyState->m_leftOver + 1,
+ aLength, &readCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCopyState->m_leftOver += readCount;
+ mCopyState->m_dataBuffer[mCopyState->m_leftOver + 1] = '\0';
+ char* start = mCopyState->m_dataBuffer + 1;
+ char* endBuffer = mCopyState->m_dataBuffer + mCopyState->m_leftOver + 1;
+
+ uint32_t lineLength;
+ uint32_t bytesWritten;
+
+ while (1) {
+ char* end = PL_strnpbrk(start, "\r\n", endBuffer - start);
+ if (!end) {
+ mCopyState->m_leftOver -= (start - mCopyState->m_dataBuffer - 1);
+ // In CopyFileMessage, a complete message is being copied in a single
+ // call to CopyData, and if it does not have a LINEBREAK at the EOF,
+ // then end will be null after reading the last line, and we need
+ // to append the LINEBREAK to the buffer to enable transfer of the last
+ // line.
+ if (mCopyState->m_wholeMsgInStream) {
+ end = start + mCopyState->m_leftOver;
+ memcpy(end, MSG_LINEBREAK "\0", MSG_LINEBREAK_LEN + 1);
+ } else {
+ memmove(mCopyState->m_dataBuffer + 1, start, mCopyState->m_leftOver);
+ break;
+ }
+ }
+
+ // need to set the linebreak_len each time
+ uint32_t linebreak_len = 1; // assume CR or LF
+ if (*end == '\r' && *(end + 1) == '\n') linebreak_len = 2; // CRLF
+
+ if (!mCopyState->m_fromLineSeen) {
+ mCopyState->m_fromLineSeen = true;
+ NS_ASSERTION(strncmp(start, "From ", 5) == 0,
+ "Fatal ... bad message format\n");
+ } else if (strncmp(start, "From ", 5) == 0) {
+ // if we're at the beginning of the buffer, we've reserved a byte to
+ // insert a '>'. If we're in the middle, we're overwriting the previous
+ // line ending, but we've already written it to m_fileStream, so it's OK.
+ *--start = '>';
+ }
+
+ if (!mCopyState->m_fileStream) {
+ ThrowAlertMsg("copyMsgWriteFailed", mCopyState->m_msgWindow);
+ mCopyState->m_writeFailed = true;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ lineLength = end - start + linebreak_len;
+ rv = mCopyState->m_fileStream->Write(start, lineLength, &bytesWritten);
+ if (bytesWritten != lineLength || NS_FAILED(rv)) {
+ ThrowAlertMsg("copyMsgWriteFailed", mCopyState->m_msgWindow);
+ mCopyState->m_writeFailed = true;
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+
+ if (mCopyState->m_parseMsgState)
+ mCopyState->m_parseMsgState->ParseAFolderLine(start, lineLength);
+
+ start = end + linebreak_len;
+ if (start >= endBuffer) {
+ mCopyState->m_leftOver = 0;
+ break;
+ }
+ }
+ return rv;
+}
+
+void nsMsgLocalMailFolder::CopyPropertiesToMsgHdr(nsIMsgDBHdr* destHdr,
+ nsIMsgDBHdr* srcHdr,
+ bool aIsMove) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCString dontPreserve;
+
+ // These preferences exist so that extensions can control which properties
+ // are preserved in the database when a message is moved or copied. All
+ // properties are preserved except those listed in these preferences
+ if (aIsMove)
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnMove",
+ dontPreserve);
+ else
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy",
+ dontPreserve);
+
+ CopyHdrPropertiesWithSkipList(destHdr, srcHdr, dontPreserve);
+}
+
+void nsMsgLocalMailFolder::CopyHdrPropertiesWithSkipList(
+ nsIMsgDBHdr* destHdr, nsIMsgDBHdr* srcHdr, const nsCString& skipList) {
+ nsTArray<nsCString> properties;
+ nsresult rv = srcHdr->GetProperties(properties);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // We'll add spaces at beginning and end so we can search for space-name-space
+ nsCString dontPreserveEx(" "_ns);
+ dontPreserveEx.Append(skipList);
+ dontPreserveEx.Append(' ');
+
+ nsCString sourceString;
+ for (auto property : properties) {
+ nsAutoCString propertyEx(" "_ns);
+ propertyEx.Append(property);
+ propertyEx.Append(' ');
+ if (dontPreserveEx.Find(propertyEx) != -1) // -1 is not found
+ continue;
+
+ srcHdr->GetStringProperty(property.get(), sourceString);
+ destHdr->SetStringProperty(property.get(), sourceString);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsMsgLocalMailFolder::EndCopy(bool aCopySucceeded) {
+ if (!mCopyState) return NS_OK;
+
+ // we are the destination folder for a move/copy
+ nsresult rv = aCopySucceeded ? NS_OK : NS_ERROR_FAILURE;
+
+ if (!aCopySucceeded || mCopyState->m_writeFailed) {
+ if (mCopyState->m_fileStream) {
+ if (mCopyState->m_curDstKey != nsMsgKey_None)
+ mCopyState->m_msgStore->DiscardNewMessage(mCopyState->m_fileStream,
+ mCopyState->m_newHdr);
+ mCopyState->m_fileStream->Close();
+ }
+
+ if (!mCopyState->m_isMove) {
+ // passing true because the messages that have been successfully
+ // copied have their corresponding hdrs in place. The message that has
+ // failed has been truncated so the msf file and berkeley mailbox
+ // are in sync.
+ (void)OnCopyCompleted(mCopyState->m_srcSupport, true);
+ // enable the dest folder
+ EnableNotifications(allMessageCountNotifications, true);
+ }
+ return NS_OK;
+ }
+
+ bool multipleCopiesFinished =
+ (mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount);
+
+ RefPtr<nsLocalMoveCopyMsgTxn> localUndoTxn = mCopyState->m_undoMsgTxn;
+
+ NS_ASSERTION(mCopyState->m_leftOver == 0,
+ "whoops, something wrong with previous copy");
+ mCopyState->m_leftOver = 0; // reset to 0.
+ // need to reset this in case we're move/copying multiple msgs.
+ mCopyState->m_fromLineSeen = false;
+
+ // flush the copied message. We need a close at the end to get the
+ // file size and time updated correctly.
+ //
+ // These filestream closes are handled inconsistently in the code. In some
+ // cases, this is done in EndMessage, while in others it is done here in
+ // EndCopy. When we do the close in EndMessage, we'll set
+ // mCopyState->m_fileStream to null since it is no longer needed, and detect
+ // here the null stream so we know that we don't have to close it here.
+ //
+ // Similarly, m_parseMsgState->GetNewMsgHdr() returns a null hdr if the hdr
+ // has already been processed by EndMessage so it is not doubly added here.
+
+ if (mCopyState->m_fileStream) {
+ rv = FinishNewLocalMessage(mCopyState->m_fileStream, mCopyState->m_newHdr,
+ mCopyState->m_msgStore,
+ mCopyState->m_parseMsgState);
+ if (NS_SUCCEEDED(rv) && mCopyState->m_newHdr)
+ mCopyState->m_newHdr->GetMessageKey(&mCopyState->m_curDstKey);
+ if (multipleCopiesFinished)
+ mCopyState->m_fileStream->Close();
+ else
+ mCopyState->m_fileStream->Flush();
+ }
+ // Copy the header to the new database
+ if (mCopyState->m_message) {
+ // CopyMessages() goes here, and CopyFileMessages() with metadata to save;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ if (!mCopyState->m_parseMsgState) {
+ if (mCopyState->m_destDB) {
+ if (mCopyState->m_newHdr) {
+ newHdr = mCopyState->m_newHdr;
+ CopyHdrPropertiesWithSkipList(newHdr, mCopyState->m_message,
+ "storeToken msgOffset"_ns);
+ // We need to copy more than just what UpdateNewMsgHdr does. In fact,
+ // I think we want to copy almost every property other than
+ // storeToken and msgOffset.
+ mCopyState->m_destDB->AddNewHdrToDB(newHdr, true);
+ } else {
+ rv = mCopyState->m_destDB->CopyHdrFromExistingHdr(
+ mCopyState->m_curDstKey, mCopyState->m_message, true,
+ getter_AddRefs(newHdr));
+ }
+ uint32_t newHdrFlags;
+ if (newHdr) {
+ // turn off offline flag - it's not valid for local mail folders.
+ newHdr->AndFlags(~nsMsgMessageFlags::Offline, &newHdrFlags);
+ mCopyState->m_destMessages.AppendElement(newHdr);
+ }
+ }
+ // we can do undo with the dest folder db, see bug #198909
+ // else
+ // mCopyState->m_undoMsgTxn = nullptr; // null out the transaction
+ // // because we can't undo w/o
+ // // the msg db
+ }
+
+ // if we plan on allowing undo, (if we have a mCopyState->m_parseMsgState or
+ // not) we need to save the source and dest keys on the undo txn. see bug
+ // #179856 for details
+ bool isImap;
+ if (NS_SUCCEEDED(rv) && localUndoTxn) {
+ localUndoTxn->GetSrcIsImap(&isImap);
+ if (!isImap || !mCopyState->m_copyingMultipleMessages) {
+ nsMsgKey aKey;
+ mCopyState->m_message->GetMessageKey(&aKey);
+ localUndoTxn->AddSrcKey(aKey);
+ localUndoTxn->AddDstKey(mCopyState->m_curDstKey);
+ }
+ }
+ }
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ // CopyFileMessage() and CopyMessages() from servers other than mailbox
+ if (mCopyState->m_parseMsgState) {
+ nsCOMPtr<nsIMsgDatabase> msgDb;
+ mCopyState->m_parseMsgState->FinishHeader();
+ GetDatabaseWOReparse(getter_AddRefs(msgDb));
+ if (msgDb) {
+ nsresult result =
+ mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr));
+ // we need to copy newHdr because mCopyState will get cleared
+ // in OnCopyCompleted, but we need OnCopyCompleted to know about
+ // the newHdr, via mCopyState. And we send a notification about newHdr
+ // after OnCopyCompleted.
+ mCopyState->m_newHdr = newHdr;
+ if (NS_SUCCEEDED(result) && newHdr) {
+ // Copy message metadata.
+ uint32_t newFlags;
+ newHdr->GetFlags(&newFlags);
+ if (mCopyState->m_message) {
+ // Propagate the new flag on an imap to local folder filter action
+ // Flags may get changed when deleting the original source message in
+ // IMAP. We have a copy of the original flags, but parseMsgState has
+ // already tried to decide what those flags should be. Who to believe?
+ // Let's deal here with the flags that might get changed, Read and
+ // New, and trust upstream code for everything else. However,
+ // we need to carry over HasRe since the subject is copied over
+ // from the original.
+ uint32_t carryOver = nsMsgMessageFlags::New |
+ nsMsgMessageFlags::Read |
+ nsMsgMessageFlags::HasRe;
+ newHdr->SetFlags((newFlags & ~carryOver) |
+ ((mCopyState->m_flags) & carryOver));
+
+ // Copy other message properties.
+ CopyPropertiesToMsgHdr(newHdr, mCopyState->m_message,
+ mCopyState->m_isMove);
+ } else {
+ // Carry over some of the enforced flags, but do not clear any of the
+ // already set flags (for example nsMsgMessageFlags::Queued or
+ // nsMsgMessageFlags::MDNReportSent).
+ uint32_t carryOver = nsMsgMessageFlags::New |
+ nsMsgMessageFlags::Read |
+ nsMsgMessageFlags::Marked;
+ newHdr->SetFlags((newFlags & ~carryOver) |
+ ((mCopyState->m_flags) & carryOver));
+ }
+ msgDb->AddNewHdrToDB(newHdr, true);
+ if (localUndoTxn) {
+ // ** jt - recording the message size for possible undo use; the
+ // message size is different for pop3 and imap4 messages
+ uint32_t msgSize;
+ newHdr->GetMessageSize(&msgSize);
+ localUndoTxn->AddDstMsgSize(msgSize);
+ }
+
+ mCopyState->m_destMessages.AppendElement(newHdr);
+ }
+ // msgDb->SetSummaryValid(true);
+ // msgDb->Commit(nsMsgDBCommitType::kLargeCommit);
+ } else
+ mCopyState->m_undoMsgTxn = nullptr; // null out the transaction because
+ // we can't undo w/o the msg db
+
+ mCopyState->m_parseMsgState->Clear();
+ if (mCopyState->m_listener) // CopyFileMessage() only
+ mCopyState->m_listener->SetMessageKey(mCopyState->m_curDstKey);
+ }
+
+ if (!multipleCopiesFinished && !mCopyState->m_copyingMultipleMessages) {
+ // CopyMessages() goes here; CopyFileMessage() never gets in here because
+ // curCopyIndex will always be less than the mCopyState->m_totalMsgCount
+ nsIMsgDBHdr* aSupport = mCopyState->m_messages[mCopyState->m_curCopyIndex];
+ rv = CopyMessageTo(aSupport, mCopyState->m_msgWindow, mCopyState->m_isMove);
+ } else {
+ // If we have some headers, then there is a source, so notify
+ // itemMoveCopyCompleted. If we don't have any headers already, (eg save as
+ // draft, send) then notify itemAdded. This notification is done after the
+ // messages are deleted, so that saving a new draft of a message works
+ // correctly -- first an itemDeleted is sent for the old draft, then an
+ // itemAdded for the new draft.
+ uint32_t numHdrs = mCopyState->m_messages.Length();
+
+ if (multipleCopiesFinished && numHdrs && !mCopyState->m_isFolder) {
+ // we need to send this notification before we delete the source messages,
+ // because deleting the source messages clears out the src msg db hdr.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ notifier->NotifyMsgsMoveCopyCompleted(mCopyState->m_isMove,
+ mCopyState->m_messages, this,
+ mCopyState->m_destMessages);
+ }
+ }
+
+ // Now allow folder or nested folders move of their msgs from Local Folders.
+ // The original source folder(s) remain, just the msgs are moved (after
+ // copy they are deleted).
+ if (multipleCopiesFinished) {
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ srcFolder = do_QueryInterface(mCopyState->m_srcSupport);
+ if (mCopyState->m_isFolder) {
+ // Copy or move all subfolders then notify completion
+ CopyAllSubFolders(srcFolder, nullptr, nullptr, mCopyState->m_isMove);
+ }
+
+ // If this is done on move of selected messages between "mailbox" folders,
+ // the source messages are never deleted. So do this only on msg copy.
+ if (!mCopyState->m_isMove) {
+ if (mCopyState->m_msgWindow && mCopyState->m_undoMsgTxn) {
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ mCopyState->m_msgWindow->GetTransactionManager(
+ getter_AddRefs(txnMgr));
+ if (txnMgr) {
+ RefPtr<nsLocalMoveCopyMsgTxn> txn = mCopyState->m_undoMsgTxn;
+ txnMgr->DoTransaction(txn);
+ }
+ }
+
+ // enable the dest folder
+ EnableNotifications(allMessageCountNotifications, true);
+ if (srcFolder && !mCopyState->m_isFolder) {
+ // I'm not too sure of the proper location of this event. It seems to
+ // need to be after the EnableNotifications, or the folder counts can
+ // be incorrect during the kDeleteOrMoveMsgCompleted call.
+ srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
+ }
+ (void)OnCopyCompleted(mCopyState->m_srcSupport, true);
+ }
+ }
+ // Send the itemAdded notification in case we didn't send the
+ // itemMoveCopyCompleted notification earlier. Posting news messages
+ // involves this, yet doesn't have the newHdr initialized, so don't send any
+ // notifications in that case.
+ if (!numHdrs && newHdr) {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ notifier->NotifyMsgAdded(newHdr);
+ // We do not appear to trigger classification in this case, so let's
+ // paper over the abyss by just sending the classification notification.
+ notifier->NotifyMsgsClassified({&*newHdr}, false, false);
+ // (We do not add the NotReportedClassified processing flag since we
+ // just reported it!)
+ }
+ }
+ }
+ return rv;
+}
+
+static bool gGotGlobalPrefs;
+static bool gDeleteFromServerOnMove;
+
+bool nsMsgLocalMailFolder::GetDeleteFromServerOnMove() {
+ if (!gGotGlobalPrefs) {
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (pPrefBranch) {
+ pPrefBranch->GetBoolPref("mail.pop3.deleteFromServerOnMove",
+ &gDeleteFromServerOnMove);
+ gGotGlobalPrefs = true;
+ }
+ }
+ return gDeleteFromServerOnMove;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsMsgLocalMailFolder::EndMove(bool moveSucceeded) {
+ nsresult rv;
+ if (!mCopyState) return NS_OK;
+
+ if (!moveSucceeded || mCopyState->m_writeFailed) {
+ // Notify that a completion finished.
+ nsCOMPtr<nsIMsgFolder> srcFolder =
+ do_QueryInterface(mCopyState->m_srcSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed);
+
+ /* passing true because the messages that have been successfully copied have
+ their corresponding hdrs in place. The message that has failed has been
+ truncated so the msf file and berkeley mailbox are in sync*/
+
+ (void)OnCopyCompleted(mCopyState->m_srcSupport, true);
+ // enable the dest folder
+ EnableNotifications(allMessageCountNotifications, true);
+ return NS_OK;
+ }
+
+ if (mCopyState && mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount) {
+ // Notify that a completion finished.
+ nsCOMPtr<nsIMsgFolder> srcFolder =
+ do_QueryInterface(mCopyState->m_srcSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgLocalMailFolder> localSrcFolder =
+ do_QueryInterface(srcFolder);
+ if (localSrcFolder) {
+ // if we are the trash and a local msg is being moved to us, mark the
+ // source for delete from server, if so configured.
+ if (mFlags & nsMsgFolderFlags::Trash) {
+ // if we're deleting on all moves, we'll mark this message for deletion
+ // when we call DeleteMessages on the source folder. So don't mark it
+ // for deletion here, in that case.
+ if (!GetDeleteFromServerOnMove()) {
+ localSrcFolder->MarkMsgsOnPop3Server(mCopyState->m_messages,
+ POP3_DELETE);
+ }
+ }
+ }
+ // lets delete these all at once - much faster that way
+ rv = srcFolder->DeleteMessages(mCopyState->m_messages,
+ mCopyState->m_msgWindow, true, true, nullptr,
+ mCopyState->m_allowUndo);
+ AutoCompact(mCopyState->m_msgWindow);
+
+ // enable the dest folder
+ EnableNotifications(allMessageCountNotifications, true);
+ // I'm not too sure of the proper location of this event. It seems to need
+ // to be after the EnableNotifications, or the folder counts can be
+ // incorrect during the kDeleteOrMoveMsgCompleted call.
+ srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted
+ : kDeleteOrMoveMsgFailed);
+
+ if (NS_SUCCEEDED(rv) && mCopyState->m_msgWindow &&
+ mCopyState->m_undoMsgTxn) {
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ mCopyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr) {
+ RefPtr<nsLocalMoveCopyMsgTxn> txn = mCopyState->m_undoMsgTxn;
+ txnMgr->DoTransaction(txn);
+ }
+ }
+ (void)OnCopyCompleted(
+ mCopyState->m_srcSupport,
+ NS_SUCCEEDED(rv)
+ ? true
+ : false); // clear the copy state so that the next message from a
+ // different folder can be move
+ }
+
+ return NS_OK;
+}
+
+// this is the beginning of the next message copied
+NS_IMETHODIMP nsMsgLocalMailFolder::StartMessage() {
+ // We get crashes that we don't understand (bug 284876), so stupidly prevent
+ // that.
+ NS_ENSURE_ARG_POINTER(mCopyState);
+ nsresult rv = InitCopyMsgHdrAndFileStream();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return WriteStartOfNewMessage();
+}
+
+// just finished the current message.
+NS_IMETHODIMP nsMsgLocalMailFolder::EndMessage(nsMsgKey key) {
+ NS_ENSURE_ARG_POINTER(mCopyState);
+
+ RefPtr<nsLocalMoveCopyMsgTxn> localUndoTxn = mCopyState->m_undoMsgTxn;
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsresult rv;
+
+ if (localUndoTxn) {
+ localUndoTxn->GetMsgWindow(getter_AddRefs(msgWindow));
+ localUndoTxn->AddSrcKey(key);
+ localUndoTxn->AddDstKey(mCopyState->m_curDstKey);
+ }
+
+ // I think this is always true for online to offline copy
+ mCopyState->m_dummyEnvelopeNeeded = true;
+ if (mCopyState->m_fileStream) {
+ rv = FinishNewLocalMessage(mCopyState->m_fileStream, mCopyState->m_newHdr,
+ mCopyState->m_msgStore,
+ mCopyState->m_parseMsgState);
+ mCopyState->m_fileStream->Close();
+ mCopyState->m_fileStream = nullptr; // all done with the file stream
+ }
+
+ // CopyFileMessage() and CopyMessages() from servers other than mailbox
+ if (mCopyState->m_parseMsgState) {
+ nsCOMPtr<nsIMsgDatabase> msgDb;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ mCopyState->m_parseMsgState->FinishHeader();
+
+ rv = mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr));
+ if (NS_SUCCEEDED(rv) && newHdr) {
+ nsCOMPtr<nsIMsgFolder> srcFolder =
+ do_QueryInterface(mCopyState->m_srcSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (srcDB) {
+ nsCOMPtr<nsIMsgDBHdr> srcMsgHdr;
+ srcDB->GetMsgHdrForKey(key, getter_AddRefs(srcMsgHdr));
+ if (srcMsgHdr)
+ CopyPropertiesToMsgHdr(newHdr, srcMsgHdr, mCopyState->m_isMove);
+ }
+ rv = GetDatabaseWOReparse(getter_AddRefs(msgDb));
+ if (NS_SUCCEEDED(rv) && msgDb) {
+ msgDb->AddNewHdrToDB(newHdr, true);
+ if (localUndoTxn) {
+ // ** jt - recording the message size for possible undo use; the
+ // message size is different for pop3 and imap4 messages
+ uint32_t msgSize;
+ newHdr->GetMessageSize(&msgSize);
+ localUndoTxn->AddDstMsgSize(msgSize);
+ }
+ } else
+ mCopyState->m_undoMsgTxn = nullptr; // null out the transaction because
+ // we can't undo w/o the msg db
+ }
+ mCopyState->m_parseMsgState->Clear();
+
+ if (mCopyState->m_listener) // CopyFileMessage() only
+ mCopyState->m_listener->SetMessageKey(mCopyState->m_curDstKey);
+ }
+
+ if (mCopyState->m_fileStream) mCopyState->m_fileStream->Flush();
+ return NS_OK;
+}
+
+nsresult nsMsgLocalMailFolder::CopyMessagesTo(nsTArray<nsMsgKey>& keyArray,
+ nsIMsgWindow* aMsgWindow,
+ bool isMove) {
+ if (!mCopyState) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+
+ nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance(
+ "@mozilla.org/messenger/copymessagestreamlistener;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> srcFolder(
+ do_QueryInterface(mCopyState->m_srcSupport, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+
+ rv = copyStreamListener->Init(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mCopyState->m_messageService) {
+ nsCString uri;
+ srcFolder->GetURI(uri);
+ rv = GetMessageServiceFromURI(uri,
+ getter_AddRefs(mCopyState->m_messageService));
+ }
+
+ if (NS_SUCCEEDED(rv) && mCopyState->m_messageService) {
+ nsCOMPtr<nsIStreamListener> streamListener(
+ do_QueryInterface(copyStreamListener, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+
+ mCopyState->m_curCopyIndex = 0;
+ // we need to kick off the first message - subsequent messages
+ // are kicked off by nsMailboxProtocol when it finishes a message
+ // before starting the next message. Only do this if the source folder
+ // is a local folder, however. IMAP will handle calling StartMessage for
+ // each message that gets downloaded, and news doesn't go through here
+ // because news only downloads one message at a time, and this routine
+ // is for multiple message copy.
+ nsCOMPtr<nsIMsgLocalMailFolder> srcLocalFolder =
+ do_QueryInterface(srcFolder);
+ if (srcLocalFolder) {
+ StartMessage();
+ }
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = mCopyState->m_messageService->CopyMessages(
+ keyArray, srcFolder, streamListener, isMove, nullptr, aMsgWindow,
+ getter_AddRefs(dummyNull));
+ }
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::CopyMessageTo(nsISupports* message,
+ nsIMsgWindow* aMsgWindow,
+ bool isMove) {
+ if (!mCopyState) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryInterface(message, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+
+ mCopyState->m_message = msgHdr;
+
+ nsCOMPtr<nsIMsgFolder> srcFolder(
+ do_QueryInterface(mCopyState->m_srcSupport, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+ nsCString uri;
+ srcFolder->GetUriForMsg(msgHdr, uri);
+
+ nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance(
+ "@mozilla.org/messenger/copymessagestreamlistener;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = copyStreamListener->Init(this);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mCopyState->m_messageService)
+ rv = GetMessageServiceFromURI(uri,
+ getter_AddRefs(mCopyState->m_messageService));
+
+ if (NS_SUCCEEDED(rv) && mCopyState->m_messageService) {
+ nsCOMPtr<nsIStreamListener> streamListener(
+ do_QueryInterface(copyStreamListener, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
+ rv = mCopyState->m_messageService->CopyMessage(uri, streamListener, isMove,
+ nullptr, aMsgWindow);
+ }
+ return rv;
+}
+
+// A message is being deleted from a POP3 mail file, so check and see if we have
+// the message being deleted in the server. If so, then we need to remove the
+// message from the server as well. We have saved the UIDL of the message in the
+// popstate.dat file and we must match this uidl, so read the message headers
+// and see if we have it, then mark the message for deletion from the server.
+// The next time we look at mail the message will be deleted from the server.
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::MarkMsgsOnPop3Server(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, int32_t aMark) {
+ nsLocalFolderScanState folderScanState;
+ nsCOMPtr<nsIPop3IncomingServer> curFolderPop3MailServer;
+ nsCOMArray<nsIPop3IncomingServer>
+ pop3Servers; // servers with msgs deleted...
+
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ nsresult rv = GetServer(getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // I wonder if we should run through the pop3 accounts and see if any of them
+ // have leave on server set. If not, we could short-circuit some of this.
+
+ curFolderPop3MailServer = do_QueryInterface(incomingServer, &rv);
+ rv = GetFolderScanState(&folderScanState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Filter delete requests are always honored, others are subject
+ // to the deleteMailLeftOnServer preference.
+ int32_t mark;
+ mark = (aMark == POP3_FORCE_DEL) ? POP3_DELETE : aMark;
+
+ for (auto msgDBHdr : aMessages) {
+ uint32_t flags = 0;
+ if (msgDBHdr) {
+ msgDBHdr->GetFlags(&flags);
+ nsCOMPtr<nsIPop3IncomingServer> msgPop3Server = curFolderPop3MailServer;
+ bool leaveOnServer = false;
+ bool deleteMailLeftOnServer = false;
+ // set up defaults, in case there's no x-mozilla-account header
+ if (curFolderPop3MailServer) {
+ curFolderPop3MailServer->GetDeleteMailLeftOnServer(
+ &deleteMailLeftOnServer);
+ curFolderPop3MailServer->GetLeaveMessagesOnServer(&leaveOnServer);
+ }
+
+ rv = GetUidlFromFolder(&folderScanState, msgDBHdr);
+ if (!NS_SUCCEEDED(rv)) continue;
+
+ if (folderScanState.m_uidl) {
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = accountManager->GetAccount(folderScanState.m_accountKey,
+ getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ account->GetIncomingServer(getter_AddRefs(incomingServer));
+ nsCOMPtr<nsIPop3IncomingServer> curMsgPop3MailServer =
+ do_QueryInterface(incomingServer);
+ if (curMsgPop3MailServer) {
+ msgPop3Server = curMsgPop3MailServer;
+ msgPop3Server->GetDeleteMailLeftOnServer(&deleteMailLeftOnServer);
+ msgPop3Server->GetLeaveMessagesOnServer(&leaveOnServer);
+ }
+ }
+ }
+ // ignore this header if not partial and leaveOnServer not set...
+ // or if we can't find the pop3 server.
+ if (!msgPop3Server ||
+ (!(flags & nsMsgMessageFlags::Partial) && !leaveOnServer))
+ continue;
+ // if marking deleted, ignore header if we're not deleting from
+ // server when deleting locally.
+ if (aMark == POP3_DELETE && leaveOnServer && !deleteMailLeftOnServer)
+ continue;
+ if (folderScanState.m_uidl) {
+ msgPop3Server->AddUidlToMark(folderScanState.m_uidl, mark);
+ // remember this pop server in list of servers with msgs deleted
+ if (pop3Servers.IndexOfObject(msgPop3Server) == -1)
+ pop3Servers.AppendObject(msgPop3Server);
+ }
+ }
+ }
+ if (folderScanState.m_inputStream) folderScanState.m_inputStream->Close();
+ // need to do this for all pop3 mail servers that had messages deleted.
+ uint32_t serverCount = pop3Servers.Count();
+ for (uint32_t index = 0; index < serverCount; index++)
+ pop3Servers[index]->MarkMessages();
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::DeleteDownloadMsg(nsIMsgDBHdr* aMsgHdr) {
+ uint32_t numMsgs;
+ char* newMsgId;
+
+ // This method is only invoked through DownloadMessagesForOffline()
+ if (mDownloadState != DOWNLOAD_STATE_NONE) {
+ // We only remember the first key, no matter how many
+ // messages were originally selected.
+ if (mDownloadState == DOWNLOAD_STATE_INITED) {
+ aMsgHdr->GetMessageKey(&mDownloadSelectKey);
+ mDownloadState = DOWNLOAD_STATE_GOTMSG;
+ }
+
+ aMsgHdr->GetMessageId(&newMsgId);
+
+ // Walk through all the selected headers, looking for a matching
+ // Message-ID.
+ numMsgs = mDownloadMessages.Length();
+ for (uint32_t i = 0; i < numMsgs; i++) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr = mDownloadMessages[i];
+ char* oldMsgId = nullptr;
+ msgDBHdr->GetMessageId(&oldMsgId);
+
+ // Delete the first match and remove it from the array
+ if (!PL_strcmp(newMsgId, oldMsgId)) {
+ rv = GetDatabase();
+ if (!mDatabase) return rv;
+
+ UpdateNewMsgHdr(msgDBHdr, aMsgHdr);
+
+ mDatabase->DeleteHeader(msgDBHdr, nullptr, false, true);
+ mDownloadMessages.RemoveElementAt(i);
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::DownloadMessagesForOffline(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& aMessages, nsIMsgWindow* aWindow) {
+ if (mDownloadState != DOWNLOAD_STATE_NONE)
+ return NS_ERROR_FAILURE; // already has a download in progress
+
+ // We're starting a download...
+ mDownloadState = DOWNLOAD_STATE_INITED;
+
+ MarkMsgsOnPop3Server(aMessages, POP3_FETCH_BODY);
+
+ // Pull out all the PARTIAL messages into a new array
+ nsresult rv;
+ for (nsIMsgDBHdr* hdr : aMessages) {
+ uint32_t flags = 0;
+ hdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial) {
+ mDownloadMessages.AppendElement(hdr);
+ }
+ }
+ mDownloadWindow = aWindow;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ nsCOMPtr<nsILocalMailIncomingServer> localMailServer =
+ do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+ nsCOMPtr<nsIURI> resultURI;
+ return localMailServer->GetNewMail(aWindow, this, this,
+ getter_AddRefs(resultURI));
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::HasMsgOffline(nsMsgKey msgKey,
+ bool* result) {
+ NS_ENSURE_ARG(result);
+ *result = false;
+ GetDatabase();
+ if (!mDatabase) return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if (NS_FAILED(rv)) return rv;
+
+ if (hdr) {
+ uint32_t flags = 0;
+ hdr->GetFlags(&flags);
+ // Would be nice to check nsMsgMessageFlags::Offline... but local
+ // folders don't set it.
+ // Don't want partial messages.
+ if (!(flags & nsMsgMessageFlags::Partial)) {
+ *result = true;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::GetLocalMsgStream(nsIMsgDBHdr* hdr,
+ nsIInputStream** stream) {
+ uint64_t offset = 0;
+ hdr->GetMessageOffset(&offset);
+ // It's a local folder - .messageSize holds the size.
+ uint32_t size = 0;
+ hdr->GetMessageSize(&size);
+ nsCOMPtr<nsIInputStream> fileStream;
+ nsresult rv = GetMsgInputStream(hdr, getter_AddRefs(fileStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<mozilla::SlicedInputStream> slicedStream =
+ new mozilla::SlicedInputStream(fileStream.forget(), offset,
+ uint64_t(size));
+ slicedStream.forget(stream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::NotifyDelete() {
+ NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
+ return NS_OK;
+}
+
+// TODO: once we move certain code into the IncomingServer (search for TODO)
+// this method will go away.
+// sometimes this gets called when we don't have the server yet, so
+// that's why we're not calling GetServer()
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetIncomingServerType(nsACString& aServerType) {
+ nsresult rv;
+ if (mType.IsEmpty()) {
+ nsCOMPtr<nsIURL> url;
+ rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(mURI)
+ .Finalize(url);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // try "none" first
+ rv = NS_MutateURI(url).SetScheme("none"_ns).Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accountManager->FindServerByURI(url, getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ mType.AssignLiteral("none");
+ else {
+ // next try "pop3"
+ rv = NS_MutateURI(url).SetScheme("pop3"_ns).Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accountManager->FindServerByURI(url, getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ mType.AssignLiteral("pop3");
+ else {
+ // next try "rss"
+ rv = NS_MutateURI(url).SetScheme("rss"_ns).Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accountManager->FindServerByURI(url, getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ mType.AssignLiteral("rss");
+ else {
+ }
+ }
+ }
+ }
+ aServerType = mType;
+ return NS_OK;
+}
+
+nsresult nsMsgLocalMailFolder::CreateBaseMessageURI(const nsACString& aURI) {
+ return nsCreateLocalBaseMessageURI(aURI, mBaseMessageURI);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::OnStartRunningUrl(nsIURI* aUrl) {
+ nsresult rv;
+ nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(aUrl, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString aSpec;
+ rv = aUrl->GetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (strstr(aSpec.get(), "uidl=")) {
+ nsCOMPtr<nsIPop3Sink> popsink;
+ rv = popurl->GetPop3Sink(getter_AddRefs(popsink));
+ if (NS_SUCCEEDED(rv)) {
+ popsink->SetBaseMessageUri(mBaseMessageURI);
+ nsCString messageuri;
+ popurl->GetMessageUri(messageuri);
+ popsink->SetOrigMessageUri(messageuri);
+ }
+ }
+ }
+ return nsMsgDBFolder::OnStartRunningUrl(aUrl);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
+ // If we just finished a DownloadMessages call, reset...
+ if (mDownloadState != DOWNLOAD_STATE_NONE) {
+ mDownloadState = DOWNLOAD_STATE_NONE;
+ mDownloadMessages.Clear();
+ mDownloadWindow = nullptr;
+ return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
+ }
+
+ nsresult rv;
+ if (NS_SUCCEEDED(aExitCode)) {
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ nsAutoCString aSpec;
+ if (aUrl) {
+ rv = aUrl->GetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mFlags & nsMsgFolderFlags::Inbox) {
+ if (mDatabase && mCheckForNewMessagesAfterParsing) {
+ bool valid =
+ false; // GetSummaryValid may return without setting valid.
+ mDatabase->GetSummaryValid(&valid);
+ if (valid && msgWindow) rv = GetNewMessages(msgWindow, nullptr);
+ mCheckForNewMessagesAfterParsing = false;
+ }
+ }
+ }
+
+ if (m_parsingFolder) {
+ // Clear this before calling OnStopRunningUrl, in case the url listener
+ // tries to get the database.
+ m_parsingFolder = false;
+
+ // TODO: Updating the size should be pushed down into the msg store backend
+ // so that the size is recalculated as part of parsing the folder data
+ // (important for maildir), once GetSizeOnDisk is pushed into the msgStores
+ // (bug 1032360).
+ (void)RefreshSizeOnDisk();
+
+ // Update the summary totals so the front end will
+ // show the right thing.
+ UpdateSummaryTotals(true);
+
+ if (mReparseListener) {
+ nsCOMPtr<nsIUrlListener> saveReparseListener = mReparseListener;
+ mReparseListener = nullptr;
+ saveReparseListener->OnStopRunningUrl(aUrl, aExitCode);
+ }
+ }
+ if (mFlags & nsMsgFolderFlags::Inbox) {
+ // if we are the inbox and running pop url
+ nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(aUrl, &rv);
+ mozilla::Unused << popurl;
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ GetServer(getter_AddRefs(server));
+ // this is the deferred to account, in the global inbox case
+ if (server) server->SetPerformingBiff(false); // biff is over
+ }
+ }
+ return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
+}
+
+nsresult nsMsgLocalMailFolder::DisplayMoveCopyStatusMsg() {
+ nsresult rv = NS_OK;
+ if (mCopyState) {
+ if (!mCopyState->m_statusFeedback) {
+ // get msgWindow from undo txn
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ if (mCopyState->m_undoMsgTxn)
+ mCopyState->m_undoMsgTxn->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (!msgWindow) return NS_OK; // not a fatal error.
+
+ msgWindow->GetStatusFeedback(
+ getter_AddRefs(mCopyState->m_statusFeedback));
+ }
+
+ if (!mCopyState->m_stringBundle) {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/localMsgs.properties",
+ getter_AddRefs(mCopyState->m_stringBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (mCopyState->m_statusFeedback && mCopyState->m_stringBundle) {
+ nsString folderName;
+ GetName(folderName);
+ nsAutoString numMsgSoFarString;
+ numMsgSoFarString.AppendInt((mCopyState->m_copyingMultipleMessages)
+ ? mCopyState->m_curCopyIndex
+ : 1);
+
+ nsAutoString totalMessagesString;
+ totalMessagesString.AppendInt(mCopyState->m_totalMsgCount);
+ nsString finalString;
+ AutoTArray<nsString, 3> stringArray = {numMsgSoFarString,
+ totalMessagesString, folderName};
+ rv = mCopyState->m_stringBundle->FormatStringFromName(
+ (mCopyState->m_isMove) ? "movingMessagesStatus"
+ : "copyingMessagesStatus",
+ stringArray, finalString);
+ int64_t nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
+
+ // only update status/progress every half second
+ if (nowMS - mCopyState->m_lastProgressTime < 500 &&
+ mCopyState->m_curCopyIndex < mCopyState->m_totalMsgCount)
+ return NS_OK;
+
+ mCopyState->m_lastProgressTime = nowMS;
+ mCopyState->m_statusFeedback->ShowStatusString(finalString);
+ mCopyState->m_statusFeedback->ShowProgress(
+ mCopyState->m_curCopyIndex * 100 / mCopyState->m_totalMsgCount);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::SetFlagsOnDefaultMailboxes(uint32_t flags) {
+ if (flags & nsMsgFolderFlags::Inbox)
+ setSubfolderFlag(u"Inbox"_ns, nsMsgFolderFlags::Inbox);
+
+ if (flags & nsMsgFolderFlags::SentMail)
+ setSubfolderFlag(u"Sent"_ns, nsMsgFolderFlags::SentMail);
+
+ if (flags & nsMsgFolderFlags::Drafts)
+ setSubfolderFlag(u"Drafts"_ns, nsMsgFolderFlags::Drafts);
+
+ if (flags & nsMsgFolderFlags::Templates)
+ setSubfolderFlag(u"Templates"_ns, nsMsgFolderFlags::Templates);
+
+ if (flags & nsMsgFolderFlags::Trash)
+ setSubfolderFlag(u"Trash"_ns, nsMsgFolderFlags::Trash);
+
+ if (flags & nsMsgFolderFlags::Queue)
+ setSubfolderFlag(u"Unsent Messages"_ns, nsMsgFolderFlags::Queue);
+
+ if (flags & nsMsgFolderFlags::Junk)
+ setSubfolderFlag(u"Junk"_ns, nsMsgFolderFlags::Junk);
+
+ if (flags & nsMsgFolderFlags::Archive)
+ setSubfolderFlag(u"Archives"_ns, nsMsgFolderFlags::Archive);
+
+ return NS_OK;
+}
+
+nsresult nsMsgLocalMailFolder::setSubfolderFlag(const nsAString& aFolderName,
+ uint32_t flags) {
+ // FindSubFolder() expects the folder name to be escaped
+ // see bug #192043
+ nsAutoCString escapedFolderName;
+ nsresult rv = NS_MsgEscapeEncodeURLPath(aFolderName, escapedFolderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = FindSubFolder(escapedFolderName, getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we only want to do this if the folder *really* exists,
+ // so check if it has a parent. Otherwise, we'll create the
+ // .msf file when we don't want to.
+ nsCOMPtr<nsIMsgFolder> parent;
+ msgFolder->GetParent(getter_AddRefs(parent));
+ if (!parent) return NS_ERROR_FAILURE;
+
+ rv = msgFolder->SetFlag(flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgFolder->SetPrettyName(aFolderName);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetCheckForNewMessagesAfterParsing(
+ bool* aCheckForNewMessagesAfterParsing) {
+ NS_ENSURE_ARG_POINTER(aCheckForNewMessagesAfterParsing);
+ *aCheckForNewMessagesAfterParsing = mCheckForNewMessagesAfterParsing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::SetCheckForNewMessagesAfterParsing(
+ bool aCheckForNewMessagesAfterParsing) {
+ mCheckForNewMessagesAfterParsing = aCheckForNewMessagesAfterParsing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::NotifyCompactCompleted() {
+ mExpungedBytes = 0;
+ m_newMsgs.Clear(); // if compacted, m_newMsgs probably aren't valid.
+ // if compacted, processing flags probably also aren't valid.
+ ClearProcessingFlags();
+ (void)RefreshSizeOnDisk();
+ (void)CloseDBIfFolderNotOpen(false);
+ NotifyFolderEvent(kCompactCompleted);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::Shutdown(bool shutdownChildren) {
+ mInitialized = false;
+ return nsMsgDBFolder::Shutdown(shutdownChildren);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::OnMessageClassified(const nsACString& aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent)
+
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString spamFolderURI;
+ rv = spamSettings->GetSpamFolderURI(spamFolderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aMsgURI.IsEmpty()) // not end of batch
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ rv = msgHdr->GetMessageKey(&msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if this message needs junk classification
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+
+ if (processingFlags & nsMsgProcessingFlags::ClassifyJunk) {
+ nsMsgDBFolder::OnMessageClassified(aMsgURI, aClassification,
+ aJunkPercent);
+
+ if (aClassification == nsIJunkMailPlugin::JUNK) {
+ bool willMoveMessage = false;
+
+ // don't do the move when we are opening up
+ // the junk mail folder or the trash folder
+ // or when manually classifying messages in those folders
+ if (!(mFlags & nsMsgFolderFlags::Junk ||
+ mFlags & nsMsgFolderFlags::Trash)) {
+ bool moveOnSpam = false;
+ rv = spamSettings->GetMoveOnSpam(&moveOnSpam);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (moveOnSpam) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = FindFolder(spamFolderURI, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (folder) {
+ rv = folder->SetFlag(nsMsgFolderFlags::Junk);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSpamKeysToMove.AppendElement(msgKey);
+ willMoveMessage = true;
+ } else {
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // the listener should do
+ // rv = folder->SetFlag(nsMsgFolderFlags::Junk);
+ // NS_ENSURE_SUCCESS(rv,rv);
+ // mSpamKeysToMove.AppendElement(msgKey);
+ // willMoveMessage = true;
+ rv =
+ GetOrCreateJunkFolder(spamFolderURI, nullptr /* aListener */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateJunkFolder failed");
+ }
+ }
+ }
+ rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ else // end of batch
+ {
+ // Parent will apply post bayes filters.
+ nsMsgDBFolder::OnMessageClassified(EmptyCString(),
+ nsIJunkMailPlugin::UNCLASSIFIED, 0);
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ if (!mSpamKeysToMove.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ if (!spamFolderURI.IsEmpty()) {
+ rv = FindFolder(spamFolderURI, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ for (uint32_t keyIndex = 0; keyIndex < mSpamKeysToMove.Length();
+ keyIndex++) {
+ // If an upstream filter moved this message, don't move it here.
+ nsMsgKey msgKey = mSpamKeysToMove.ElementAt(keyIndex);
+ nsMsgProcessingFlagType processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+ if (folder && !(processingFlags & nsMsgProcessingFlags::FilterToMove)) {
+ nsCOMPtr<nsIMsgDBHdr> mailHdr;
+ rv = GetMessageHeader(msgKey, getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr) messages.AppendElement(mailHdr);
+ } else {
+ // We don't need the processing flag any more.
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::FilterToMove);
+ }
+ }
+
+ if (folder) {
+ nsCOMPtr<nsIMsgCopyService> copySvc =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = copySvc->CopyMessages(
+ this, messages, folder, true,
+ /*nsIMsgCopyServiceListener* listener*/ nullptr, nullptr,
+ false /*allowUndo*/);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "CopyMessages failed");
+ if (NS_FAILED(rv)) {
+ nsAutoCString logMsg(
+ "failed to copy junk messages to junk folder rv = ");
+ logMsg.AppendInt(static_cast<uint32_t>(rv), 16);
+ spamSettings->LogJunkString(logMsg.get());
+ }
+ }
+ }
+ int32_t numNewMessages;
+ GetNumNewMessages(false, &numNewMessages);
+ SetNumNewMessages(numNewMessages - messages.Length());
+ mSpamKeysToMove.Clear();
+ // check if this is the inbox first...
+ if (mFlags & nsMsgFolderFlags::Inbox) PerformBiffNotifications();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetFolderScanState(nsLocalFolderScanState* aState) {
+ NS_ENSURE_ARG_POINTER(aState);
+
+ nsresult rv = GetMsgStore(getter_AddRefs(aState->m_msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ aState->m_uidl = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::GetUidlFromFolder(nsLocalFolderScanState* aState,
+ nsIMsgDBHdr* aMsgDBHdr) {
+ bool more = false;
+ uint32_t size = 0, len = 0;
+ const char* accountKey = nullptr;
+ nsresult rv =
+ GetMsgInputStream(aMsgDBHdr, getter_AddRefs(aState->m_inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::UniquePtr<nsLineBuffer<char>> lineBuffer(new nsLineBuffer<char>);
+
+ aState->m_uidl = nullptr;
+
+ aMsgDBHdr->GetMessageSize(&len);
+ while (len > 0) {
+ rv = NS_ReadLine(aState->m_inputStream.get(), lineBuffer.get(),
+ aState->m_header, &more);
+ if (NS_SUCCEEDED(rv)) {
+ size = aState->m_header.Length();
+ if (!size) break;
+ // this isn't quite right - need to account for line endings
+ len -= size;
+ // account key header will always be before X_UIDL header
+ if (!accountKey) {
+ accountKey =
+ strstr(aState->m_header.get(), HEADER_X_MOZILLA_ACCOUNT_KEY);
+ if (accountKey) {
+ accountKey += strlen(HEADER_X_MOZILLA_ACCOUNT_KEY) + 2;
+ aState->m_accountKey = accountKey;
+ }
+ } else {
+ aState->m_uidl = strstr(aState->m_header.get(), X_UIDL);
+ if (aState->m_uidl) {
+ aState->m_uidl += X_UIDL_LEN + 2; // skip UIDL: header
+ break;
+ }
+ }
+ }
+ }
+ aState->m_inputStream->Close();
+ aState->m_inputStream = nullptr;
+ return rv;
+}
+
+/**
+ * Adds a message to the end of the folder, parsing it as it goes, and
+ * applying filters, if applicable.
+ */
+NS_IMETHODIMP
+nsMsgLocalMailFolder::AddMessage(const char* aMessage, nsIMsgDBHdr** aHdr) {
+ NS_ENSURE_ARG_POINTER(aHdr);
+ AutoTArray<nsCString, 1> aMessages = {nsDependentCString(aMessage)};
+ nsTArray<RefPtr<nsIMsgDBHdr>> hdrs;
+ nsresult rv = AddMessageBatch(aMessages, hdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ADDREF(*aHdr = hdrs[0]);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::AddMessageBatch(
+ const nsTArray<nsCString>& aMessages,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray) {
+ aHdrArray.ClearAndRetainStorage();
+ aHdrArray.SetCapacity(aMessages.Length());
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsCOMPtr<nsIOutputStream> outFileStream;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ rv = server->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isLocked;
+
+ GetLocked(&isLocked);
+ if (isLocked) return NS_MSG_FOLDER_BUSY;
+
+ AcquireSemaphore(static_cast<nsIMsgLocalMailFolder*>(this));
+
+ if (NS_SUCCEEDED(rv)) {
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < aMessages.Length(); i++) {
+ RefPtr<nsParseNewMailState> newMailParser = new nsParseNewMailState;
+ NS_ENSURE_TRUE(newMailParser, NS_ERROR_OUT_OF_MEMORY);
+ if (!mGettingNewMessages) newMailParser->DisableFilters();
+ rv = msgStore->GetNewMsgOutputStream(this, getter_AddRefs(newHdr),
+ getter_AddRefs(outFileStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get a msgWindow. Proceed without one, but filter actions to imap
+ // folders will silently fail if not signed in and no window for a prompt.
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ if (NS_SUCCEEDED(rv))
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+
+ rv = newMailParser->Init(rootFolder, this, msgWindow, newHdr,
+ outFileStream);
+
+ uint32_t bytesWritten;
+ uint32_t messageLen = aMessages[i].Length();
+ outFileStream->Write(aMessages[i].get(), messageLen, &bytesWritten);
+ rv = newMailParser->BufferInput(aMessages[i].get(), messageLen);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = newMailParser->Flush();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ FinishNewLocalMessage(outFileStream, newHdr, msgStore, newMailParser);
+ outFileStream->Close();
+ outFileStream = nullptr;
+ newMailParser->OnStopRequest(nullptr, NS_OK);
+ newMailParser->EndMsgDownload();
+ aHdrArray.AppendElement(newHdr);
+ }
+ }
+ ReleaseSemaphore(static_cast<nsIMsgLocalMailFolder*>(this));
+ return rv;
+}
+
+nsresult nsMsgLocalMailFolder::FinishNewLocalMessage(
+ nsIOutputStream* aOutputStream, nsIMsgDBHdr* aNewHdr,
+ nsIMsgPluggableStore* aMsgStore, nsParseMailMessageState* aParseMsgState) {
+ uint32_t bytesWritten;
+ aOutputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten);
+ if (aParseMsgState)
+ aParseMsgState->ParseAFolderLine(MSG_LINEBREAK, MSG_LINEBREAK_LEN);
+ return aMsgStore->FinishNewMessage(aOutputStream, aNewHdr);
+}
+
+NS_IMETHODIMP
+nsMsgLocalMailFolder::WarnIfLocalFileTooBig(nsIMsgWindow* aWindow,
+ int64_t aSpaceRequested,
+ bool* aTooBig) {
+ NS_ENSURE_ARG_POINTER(aTooBig);
+
+ *aTooBig = true;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool spaceAvailable = false;
+ // check if we have a reasonable amount of space left
+ rv = msgStore->HasSpaceAvailable(this, aSpaceRequested, &spaceAvailable);
+ if (NS_SUCCEEDED(rv) && spaceAvailable) {
+ *aTooBig = false;
+ } else if (rv == NS_ERROR_FILE_TOO_BIG) {
+ ThrowAlertMsg("mailboxTooLarge", aWindow);
+ } else {
+ ThrowAlertMsg("outOfDiskSpace", aWindow);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::FetchMsgPreviewText(
+ nsTArray<nsMsgKey> const& aKeysToFetch, nsIUrlListener* aUrlListener,
+ bool* aAsyncResults) {
+ NS_ENSURE_ARG_POINTER(aAsyncResults);
+
+ *aAsyncResults = false;
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ for (uint32_t i = 0; i < aKeysToFetch.Length(); i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCString prevBody;
+ nsresult rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ignore messages that already have a preview body.
+ msgHdr->GetStringProperty("preview", prevBody);
+ if (!prevBody.IsEmpty()) continue;
+
+ rv = GetMsgInputStream(msgHdr, getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetMsgPreviewTextFromStream(msgHdr, inputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::AddKeywordsToMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) {
+ nsresult rv = nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->ChangeKeywords(aMessages, aKeywords, true /* add */);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::RemoveKeywordsFromMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) {
+ nsresult rv = nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->ChangeKeywords(aMessages, aKeywords, false /* remove */);
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::UpdateNewMsgHdr(nsIMsgDBHdr* aOldHdr,
+ nsIMsgDBHdr* aNewHdr) {
+ NS_ENSURE_ARG_POINTER(aOldHdr);
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+ // Preserve any properties set on the message.
+ CopyPropertiesToMsgHdr(aNewHdr, aOldHdr, true);
+
+ // Preserve keywords manually, since they are set as don't preserve.
+ nsCString keywordString;
+ aOldHdr->GetStringProperty("keywords", keywordString);
+ aNewHdr->SetStringProperty("keywords", keywordString);
+
+ // If the junk score was set by the plugin, remove junkscore to force a new
+ // junk analysis, this time using the body.
+ nsCString junkScoreOrigin;
+ aOldHdr->GetStringProperty("junkscoreorigin", junkScoreOrigin);
+ if (junkScoreOrigin.EqualsLiteral("plugin"))
+ aNewHdr->SetStringProperty("junkscore", ""_ns);
+
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsLocalMailFolder.h b/comm/mailnews/local/src/nsLocalMailFolder.h
new file mode 100644
index 0000000000..247a7d459c
--- /dev/null
+++ b/comm/mailnews/local/src/nsLocalMailFolder.h
@@ -0,0 +1,285 @@
+/* -*- 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 Local Mail folders.
+*/
+
+#ifndef nsMsgLocalMailFolder_h__
+#define nsMsgLocalMailFolder_h__
+
+#include "mozilla/Attributes.h"
+#include "nsMsgDBFolder.h" /* include the interface we are going to support */
+#include "nsICopyMessageListener.h"
+#include "nsMsgTxn.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIStringBundle.h"
+#include "nsLocalUndoTxn.h"
+
+#define COPY_BUFFER_SIZE 16384
+
+class nsParseMailMessageState;
+
+struct nsLocalMailCopyState {
+ nsLocalMailCopyState();
+ virtual ~nsLocalMailCopyState();
+
+ nsCOMPtr<nsIOutputStream> m_fileStream;
+ nsCOMPtr<nsIMsgPluggableStore> m_msgStore;
+ nsCOMPtr<nsISupports> m_srcSupport;
+ /// Source nsIMsgDBHdr instances.
+ nsTArray<RefPtr<nsIMsgDBHdr>> m_messages;
+ /// Destination nsIMsgDBHdr instances.
+ nsTArray<RefPtr<nsIMsgDBHdr>> m_destMessages;
+ RefPtr<nsLocalMoveCopyMsgTxn> m_undoMsgTxn;
+ nsCOMPtr<nsIMsgDBHdr> m_message; // current copy message
+ nsMsgMessageFlagType m_flags; // current copy message flags
+ RefPtr<nsParseMailMessageState> m_parseMsgState;
+ nsCOMPtr<nsIMsgCopyServiceListener> m_listener;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ nsCOMPtr<nsIMsgDatabase> m_destDB;
+
+ // for displaying status;
+ nsCOMPtr<nsIMsgStatusFeedback> m_statusFeedback;
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+ int64_t m_lastProgressTime;
+
+ nsMsgKey m_curDstKey;
+ uint32_t m_curCopyIndex;
+ nsCOMPtr<nsIMsgMessageService> m_messageService;
+ /// The number of messages in m_messages.
+ uint32_t m_totalMsgCount;
+ char* m_dataBuffer;
+ uint32_t m_dataBufferSize;
+ uint32_t m_leftOver;
+ bool m_isMove;
+ bool m_isFolder; // isFolder move/copy
+ bool m_dummyEnvelopeNeeded;
+ bool m_copyingMultipleMessages;
+ bool m_fromLineSeen;
+ bool m_allowUndo;
+ bool m_writeFailed;
+ bool m_notifyFolderLoaded;
+ bool m_wholeMsgInStream;
+ nsCString m_newMsgKeywords;
+ nsCOMPtr<nsIMsgDBHdr> m_newHdr;
+};
+
+struct nsLocalFolderScanState {
+ nsLocalFolderScanState();
+ ~nsLocalFolderScanState();
+
+ nsCOMPtr<nsIInputStream> m_inputStream;
+ nsCOMPtr<nsIMsgPluggableStore> m_msgStore;
+ nsCString m_header;
+ nsCString m_accountKey;
+ const char* m_uidl; // memory is owned by m_header
+};
+
+class nsMsgLocalMailFolder : public nsMsgDBFolder,
+ public nsIMsgLocalMailFolder,
+ public nsICopyMessageListener {
+ public:
+ nsMsgLocalMailFolder(void);
+ NS_DECL_NSICOPYMESSAGELISTENER
+ NS_DECL_NSIMSGLOCALMAILFOLDER
+ NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIUrlListener methods
+ NS_IMETHOD OnStartRunningUrl(nsIURI* aUrl) override;
+ NS_IMETHOD OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) override;
+
+ // nsIMsgFolder methods:
+ NS_IMETHOD GetSubFolders(nsTArray<RefPtr<nsIMsgFolder>>& folders) override;
+ NS_IMETHOD GetMsgDatabase(nsIMsgDatabase** aMsgDatabase) override;
+
+ NS_IMETHOD OnAnnouncerGoingAway(nsIDBChangeAnnouncer* instigator) override;
+ NS_IMETHOD UpdateFolder(nsIMsgWindow* aWindow) override;
+
+ NS_IMETHOD CreateSubfolder(const nsAString& folderName,
+ nsIMsgWindow* msgWindow) override;
+
+ NS_IMETHOD Compact(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) override;
+ NS_IMETHOD CompactAll(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) override;
+ NS_IMETHOD EmptyTrash(nsIUrlListener* aListener) override;
+ NS_IMETHOD DeleteSelf(nsIMsgWindow* msgWindow) override;
+ NS_IMETHOD CreateStorageIfMissing(nsIUrlListener* urlListener) override;
+ NS_IMETHOD Rename(const nsAString& aNewName,
+ nsIMsgWindow* msgWindow) override;
+ NS_IMETHOD RenameSubFolders(nsIMsgWindow* msgWindow,
+ nsIMsgFolder* oldFolder) override;
+
+ NS_IMETHOD GetPrettyName(nsAString& prettyName)
+ override; // Override of the base, for top-level mail folder
+ NS_IMETHOD SetPrettyName(const nsAString& aName) override;
+
+ NS_IMETHOD GetFolderURL(nsACString& url) override;
+
+ NS_IMETHOD GetManyHeadersToDownload(bool* retval) override;
+
+ NS_IMETHOD GetDeletable(bool* deletable) override;
+ NS_IMETHOD GetSizeOnDisk(int64_t* size) override;
+
+ NS_IMETHOD GetDBFolderInfoAndDB(nsIDBFolderInfo** folderInfo,
+ nsIMsgDatabase** db) override;
+
+ NS_IMETHOD DeleteMessages(nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ nsIMsgWindow* msgWindow, bool deleteStorage,
+ bool isMove, nsIMsgCopyServiceListener* listener,
+ bool allowUndo) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD CopyMessages(
+ nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener,
+ bool isFolder, bool allowUndo) override;
+ NS_IMETHOD CopyFolder(nsIMsgFolder* srcFolder, bool isMoveFolder,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener) override;
+ NS_IMETHOD CopyFileMessage(nsIFile* aFile, nsIMsgDBHdr* msgToReplace,
+ bool isDraftOrTemplate, uint32_t newMsgFlags,
+ const nsACString& aNewMsgKeywords,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener) override;
+
+ NS_IMETHOD AddMessageDispositionState(
+ nsIMsgDBHdr* aMessage, nsMsgDispositionState aDispositionFlag) override;
+ NS_IMETHOD MarkMessagesRead(const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ bool aMarkRead) override;
+ NS_IMETHOD MarkMessagesFlagged(const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ bool aMarkFlagged) override;
+ NS_IMETHOD MarkAllMessagesRead(nsIMsgWindow* aMsgWindow) override;
+ NS_IMETHOD MarkThreadRead(nsIMsgThread* thread) override;
+ NS_IMETHOD GetNewMessages(nsIMsgWindow* aWindow,
+ nsIUrlListener* aListener) override;
+ NS_IMETHOD NotifyCompactCompleted() override;
+ NS_IMETHOD Shutdown(bool shutdownChildren) override;
+
+ NS_IMETHOD WriteToFolderCacheElem(nsIMsgFolderCacheElement* element) override;
+ NS_IMETHOD ReadFromFolderCacheElem(
+ nsIMsgFolderCacheElement* element) override;
+
+ NS_IMETHOD GetName(nsAString& aName) override;
+
+ // Used when headers_only is TRUE
+ NS_IMETHOD DownloadMessagesForOffline(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& aMessages,
+ nsIMsgWindow* aWindow) override;
+ NS_IMETHOD HasMsgOffline(nsMsgKey msgKey, bool* result) override;
+ NS_IMETHOD GetLocalMsgStream(nsIMsgDBHdr* hdr,
+ nsIInputStream** stream) override;
+ NS_IMETHOD FetchMsgPreviewText(nsTArray<nsMsgKey> const& aKeysToFetch,
+ nsIUrlListener* aUrlListener,
+ bool* aAsyncResults) override;
+ NS_IMETHOD AddKeywordsToMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) override;
+ NS_IMETHOD RemoveKeywordsFromMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) override;
+ NS_IMETHOD GetIncomingServerType(nsACString& serverType) override;
+
+ protected:
+ virtual ~nsMsgLocalMailFolder();
+ nsresult CreateChildFromURI(const nsACString& uri,
+ nsIMsgFolder** folder) override;
+ nsresult CopyFolderAcrossServer(nsIMsgFolder* srcFolder,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener,
+ bool moveMsgs);
+
+ nsresult CreateSubFolders(nsIFile* path);
+ nsresult GetTrashFolder(nsIMsgFolder** trashFolder);
+ nsresult WriteStartOfNewMessage();
+
+ // CreateSubfolder, but without the nsIMsgFolderListener notification
+ nsresult CreateSubfolderInternal(const nsAString& folderName,
+ nsIMsgWindow* msgWindow,
+ nsIMsgFolder** aNewFolder);
+
+ nsresult IsChildOfTrash(bool* result);
+ nsresult RecursiveSetDeleteIsMoveTrash(bool bVal);
+ nsresult ConfirmFolderDeletion(nsIMsgWindow* aMsgWindow,
+ nsIMsgFolder* aFolder, bool* aResult);
+
+ nsresult GetDatabase() override;
+ // this will set mDatabase, if successful. It will also create a .msf file
+ // for an empty local mail folder. It will leave invalid DBs in place, and
+ // return an error.
+ nsresult OpenDatabase();
+
+ // copy message helper
+ nsresult DisplayMoveCopyStatusMsg();
+
+ nsresult CopyMessageTo(nsISupports* message, nsIMsgWindow* msgWindow,
+ bool isMove);
+
+ /**
+ * Checks if there's room in the target folder to copy message(s) into.
+ * If not, handles alerting the user, and sending the copy notifications.
+ */
+ bool CheckIfSpaceForCopy(nsIMsgWindow* msgWindow, nsIMsgFolder* srcFolder,
+ nsISupports* srcSupports, bool isMove,
+ int64_t totalMsgSize);
+
+ // copy multiple messages at a time from this folder
+ nsresult CopyMessagesTo(nsTArray<nsMsgKey>& keyArray,
+ nsIMsgWindow* aMsgWindow, bool isMove);
+ nsresult InitCopyState(nsISupports* aSupport,
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* msgWindow, bool isMoveFolder,
+ bool allowUndo);
+ nsresult InitCopyMsgHdrAndFileStream();
+ // preserve message metadata when moving or copying messages
+ void CopyPropertiesToMsgHdr(nsIMsgDBHdr* destHdr, nsIMsgDBHdr* srcHdr,
+ bool isMove);
+ virtual nsresult CreateBaseMessageURI(const nsACString& aURI) override;
+ nsresult ChangeKeywordForMessages(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& aMessages,
+ const nsACString& aKeyword, bool add);
+ bool GetDeleteFromServerOnMove();
+ void CopyHdrPropertiesWithSkipList(nsIMsgDBHdr* destHdr, nsIMsgDBHdr* srcHdr,
+ const nsCString& skipList);
+ nsresult FinishNewLocalMessage(nsIOutputStream* outputStream,
+ nsIMsgDBHdr* newHdr,
+ nsIMsgPluggableStore* msgStore,
+ nsParseMailMessageState* parseMsgState);
+
+ protected:
+ nsLocalMailCopyState* mCopyState; // We only allow one of these at a time
+ nsCString mType;
+ bool mHaveReadNameFromDB;
+ bool mInitialized;
+ bool mCheckForNewMessagesAfterParsing;
+ bool m_parsingFolder;
+ nsCOMPtr<nsIUrlListener> mReparseListener;
+ nsTArray<nsMsgKey> mSpamKeysToMove;
+ nsresult setSubfolderFlag(const nsAString& aFolderName, uint32_t flags);
+
+ // state variables for DownloadMessagesForOffline
+
+ nsCOMArray<nsIMsgDBHdr> mDownloadMessages;
+ nsCOMPtr<nsIMsgWindow> mDownloadWindow;
+ nsMsgKey mDownloadSelectKey;
+ uint32_t mDownloadState;
+#define DOWNLOAD_STATE_NONE 0
+#define DOWNLOAD_STATE_INITED 1
+#define DOWNLOAD_STATE_GOTMSG 2
+#define DOWNLOAD_STATE_DIDSEL 3
+};
+
+#endif // nsMsgLocalMailFolder_h__
diff --git a/comm/mailnews/local/src/nsLocalUndoTxn.cpp b/comm/mailnews/local/src/nsLocalUndoTxn.cpp
new file mode 100644
index 0000000000..73bda9dcec
--- /dev/null
+++ b/comm/mailnews/local/src/nsLocalUndoTxn.cpp
@@ -0,0 +1,495 @@
+/* -*- 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 "nsIMsgHdr.h"
+#include "nsLocalUndoTxn.h"
+#include "nsImapCore.h"
+#include "nsIImapService.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsThreadUtils.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "nsMsgDBFolder.h"
+
+NS_IMPL_ISUPPORTS_INHERITED(nsLocalMoveCopyMsgTxn, nsMsgTxn, nsIFolderListener)
+
+nsLocalMoveCopyMsgTxn::nsLocalMoveCopyMsgTxn()
+ : m_srcIsImap4(false), m_canUndelete(false) {}
+
+nsLocalMoveCopyMsgTxn::~nsLocalMoveCopyMsgTxn() {}
+
+nsresult nsLocalMoveCopyMsgTxn::Init(nsIMsgFolder* srcFolder,
+ nsIMsgFolder* dstFolder, bool isMove) {
+ nsresult rv;
+ rv = SetSrcFolder(srcFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetDstFolder(dstFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_isMove = isMove;
+
+ mUndoFolderListener = nullptr;
+
+ nsCString protocolType;
+ rv = srcFolder->GetURI(protocolType);
+ protocolType.SetLength(protocolType.FindChar(':'));
+ if (protocolType.LowerCaseEqualsLiteral("imap")) m_srcIsImap4 = true;
+ return nsMsgTxn::Init();
+}
+nsresult nsLocalMoveCopyMsgTxn::GetSrcIsImap(bool* isImap) {
+ *isImap = m_srcIsImap4;
+ return NS_OK;
+}
+nsresult nsLocalMoveCopyMsgTxn::SetSrcFolder(nsIMsgFolder* srcFolder) {
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ if (srcFolder) m_srcFolder = do_GetWeakReference(srcFolder, &rv);
+ return rv;
+}
+
+nsresult nsLocalMoveCopyMsgTxn::SetDstFolder(nsIMsgFolder* dstFolder) {
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ if (dstFolder) m_dstFolder = do_GetWeakReference(dstFolder, &rv);
+ return rv;
+}
+
+nsresult nsLocalMoveCopyMsgTxn::AddSrcKey(nsMsgKey aKey) {
+ m_srcKeyArray.AppendElement(aKey);
+ return NS_OK;
+}
+
+nsresult nsLocalMoveCopyMsgTxn::AddDstKey(nsMsgKey aKey) {
+ m_dstKeyArray.AppendElement(aKey);
+ return NS_OK;
+}
+
+nsresult nsLocalMoveCopyMsgTxn::AddDstMsgSize(uint32_t msgSize) {
+ m_dstSizeArray.AppendElement(msgSize);
+ return NS_OK;
+}
+
+nsresult nsLocalMoveCopyMsgTxn::UndoImapDeleteFlag(nsIMsgFolder* folder,
+ nsTArray<nsMsgKey>& keyArray,
+ bool deleteFlag) {
+ nsresult rv = NS_ERROR_FAILURE;
+ if (m_srcIsImap4) {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIUrlListener> urlListener;
+ nsCString msgIds;
+ uint32_t i, count = keyArray.Length();
+ urlListener = do_QueryInterface(folder, &rv);
+ for (i = 0; i < count; i++) {
+ if (!msgIds.IsEmpty()) msgIds.Append(',');
+ msgIds.AppendInt((int32_t)keyArray[i]);
+ }
+ // This is to make sure that we are in the selected state
+ // when executing the imap url; we don't want to load the
+ // folder so use lite select to do the trick
+ rv = imapService->LiteSelectFolder(folder, urlListener, nullptr, nullptr);
+ if (!deleteFlag)
+ rv = imapService->AddMessageFlags(folder, urlListener, msgIds,
+ kImapMsgDeletedFlag, true);
+ else
+ rv = imapService->SubtractMessageFlags(folder, urlListener, msgIds,
+ kImapMsgDeletedFlag, true);
+ if (NS_SUCCEEDED(rv) && m_msgWindow) folder->UpdateFolder(m_msgWindow);
+ rv = NS_OK; // always return NS_OK to indicate that the src is imap
+ } else
+ rv = NS_ERROR_FAILURE;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalMoveCopyMsgTxn::UndoTransaction() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgDatabase> dstDB;
+
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgLocalMailFolder> dstlocalMailFolder =
+ do_QueryReferent(m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dstlocalMailFolder->GetDatabaseWOReparse(getter_AddRefs(dstDB));
+
+ if (!dstDB) {
+ // This will listen for the db reparse finishing, and the corresponding
+ // FolderLoadedNotification. When it gets that, it will then call
+ // UndoTransactionInternal.
+ mUndoFolderListener = new nsLocalUndoFolderListener(this, dstFolder);
+ if (!mUndoFolderListener) return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(mUndoFolderListener);
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailSession->AddFolderListener(mUndoFolderListener,
+ nsIFolderListener::event);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else
+ rv = UndoTransactionInternal();
+ return rv;
+}
+
+nsresult nsLocalMoveCopyMsgTxn::UndoTransactionInternal() {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (mUndoFolderListener) {
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailSession->RemoveFolderListener(mUndoFolderListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_RELEASE(mUndoFolderListener);
+ mUndoFolderListener = nullptr;
+ }
+
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIMsgDatabase> dstDB;
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t count = m_srcKeyArray.Length();
+ uint32_t i;
+
+ // protect against a bogus undo txn without any source keys
+ // see bug #179856 for details
+ NS_ASSERTION(count, "no source keys");
+ if (!count) return NS_ERROR_UNEXPECTED;
+
+ if (m_isMove) {
+ if (m_srcIsImap4) {
+ bool deleteFlag =
+ true; // message has been deleted -we are trying to undo it
+ CheckForToggleDelete(srcFolder, m_srcKeyArray[0],
+ &deleteFlag); // there could have been a toggle.
+ rv = UndoImapDeleteFlag(srcFolder, m_srcKeyArray, deleteFlag);
+ } else if (m_canUndelete) {
+ nsTArray<RefPtr<nsIMsgDBHdr>> srcMessages(count);
+ nsTArray<RefPtr<nsIMsgDBHdr>> destMessages(count);
+
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsIMsgDBHdr> oldHdr;
+ rv = dstDB->GetMsgHdrForKey(m_dstKeyArray[i], getter_AddRefs(oldHdr));
+ NS_ASSERTION(oldHdr, "fatal ... cannot get old msg header");
+ if (NS_SUCCEEDED(rv) && oldHdr) {
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ rv = srcDB->CopyHdrFromExistingHdr(m_srcKeyArray[i], oldHdr, true,
+ getter_AddRefs(newHdr));
+ NS_ASSERTION(newHdr, "fatal ... cannot create new msg header");
+ if (NS_SUCCEEDED(rv) && newHdr) {
+ srcDB->UndoDelete(newHdr);
+ srcMessages.AppendElement(newHdr);
+ // (we want to keep these two lists in sync)
+ destMessages.AppendElement(oldHdr);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ // Remember that we're actually moving things back from the destination
+ // to the source!
+ notifier->NotifyMsgsMoveCopyCompleted(true, destMessages, srcFolder,
+ srcMessages);
+ }
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
+ do_QueryInterface(srcFolder);
+ if (localFolder) {
+ localFolder->MarkMsgsOnPop3Server(srcMessages,
+ POP3_NONE /*deleteMsgs*/);
+ }
+ } else // undoing a move means moving the messages back.
+ {
+ nsTArray<RefPtr<nsIMsgDBHdr>> dstMessages(m_dstKeyArray.Length());
+ m_numHdrsCopied = 0;
+ m_srcKeyArray.Clear();
+ for (i = 0; i < count; i++) {
+ // GetMsgHdrForKey is not a test for whether the key exists, so check.
+ bool hasKey = false;
+ dstDB->ContainsKey(m_dstKeyArray[i], &hasKey);
+ nsCOMPtr<nsIMsgDBHdr> dstHdr;
+ if (hasKey)
+ dstDB->GetMsgHdrForKey(m_dstKeyArray[i], getter_AddRefs(dstHdr));
+ if (dstHdr) {
+ nsCString messageId;
+ dstHdr->GetMessageId(getter_Copies(messageId));
+ dstMessages.AppendElement(dstHdr);
+ m_copiedMsgIds.AppendElement(messageId);
+ } else {
+ NS_WARNING("Cannot get old msg header");
+ }
+ }
+ if (m_copiedMsgIds.Length()) {
+ srcFolder->AddFolderListener(this);
+ m_undoing = true;
+ return srcFolder->CopyMessages(dstFolder, dstMessages, true, nullptr,
+ nullptr, false, false);
+ } else {
+ // Nothing to do, probably because original messages were deleted.
+ NS_WARNING("Undo did not find any messages to move");
+ }
+ }
+ srcDB->SetSummaryValid(true);
+ }
+
+ dstDB->DeleteMessages(m_dstKeyArray, nullptr);
+ dstDB->SetSummaryValid(true);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalMoveCopyMsgTxn::RedoTransaction() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIMsgDatabase> dstDB;
+
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (NS_FAILED(rv)) return rv;
+ rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t count = m_srcKeyArray.Length();
+ uint32_t i;
+ nsCOMPtr<nsIMsgDBHdr> oldHdr;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> srcMessages(m_srcKeyArray.Length());
+ for (i = 0; i < count; i++) {
+ rv = srcDB->GetMsgHdrForKey(m_srcKeyArray[i], getter_AddRefs(oldHdr));
+ NS_ASSERTION(oldHdr, "fatal ... cannot get old msg header");
+
+ if (NS_SUCCEEDED(rv) && oldHdr) {
+ srcMessages.AppendElement(oldHdr);
+
+ if (m_canUndelete) {
+ rv = dstDB->CopyHdrFromExistingHdr(m_dstKeyArray[i], oldHdr, true,
+ getter_AddRefs(newHdr));
+ NS_ASSERTION(newHdr, "fatal ... cannot get new msg header");
+ if (NS_SUCCEEDED(rv) && newHdr) {
+ if (i < m_dstSizeArray.Length())
+ rv = newHdr->SetMessageSize(m_dstSizeArray[i]);
+ dstDB->UndoDelete(newHdr);
+ }
+ }
+ }
+ }
+ dstDB->SetSummaryValid(true);
+
+ if (m_isMove) {
+ if (m_srcIsImap4) {
+ // protect against a bogus undo txn without any source keys
+ // see bug #179856 for details
+ NS_ASSERTION(!m_srcKeyArray.IsEmpty(), "no source keys");
+ if (m_srcKeyArray.IsEmpty()) return NS_ERROR_UNEXPECTED;
+
+ bool deleteFlag = false; // message is un-deleted- we are trying to redo
+ CheckForToggleDelete(srcFolder, m_srcKeyArray[0],
+ &deleteFlag); // there could have been a toggle
+ rv = UndoImapDeleteFlag(srcFolder, m_srcKeyArray, deleteFlag);
+ } else if (m_canUndelete) {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
+ do_QueryInterface(srcFolder);
+ if (localFolder) {
+ localFolder->MarkMsgsOnPop3Server(srcMessages,
+ POP3_DELETE /*deleteMsgs*/);
+ }
+
+ rv = srcDB->DeleteMessages(m_srcKeyArray, nullptr);
+ srcDB->SetSummaryValid(true);
+ } else {
+ nsCOMPtr<nsIMsgDBHdr> srcHdr;
+ m_numHdrsCopied = 0;
+ m_dstKeyArray.Clear();
+ for (i = 0; i < count; i++) {
+ srcDB->GetMsgHdrForKey(m_srcKeyArray[i], getter_AddRefs(srcHdr));
+ NS_ASSERTION(srcHdr, "fatal ... cannot get old msg header");
+ if (srcHdr) {
+ nsCString messageId;
+ srcHdr->GetMessageId(getter_Copies(messageId));
+ m_copiedMsgIds.AppendElement(messageId);
+ }
+ }
+ dstFolder->AddFolderListener(this);
+ m_undoing = false;
+ return dstFolder->CopyMessages(srcFolder, srcMessages, true, nullptr,
+ nullptr, false, false);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnFolderAdded(nsIMsgFolder* parent,
+ nsIMsgFolder* child) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnMessageAdded(nsIMsgFolder* parent,
+ nsIMsgDBHdr* msgHdr) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder =
+ do_QueryReferent(m_undoing ? m_srcFolder : m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ if (m_copiedMsgIds.Contains(messageId)) {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (m_undoing)
+ m_srcKeyArray.AppendElement(msgKey);
+ else
+ m_dstKeyArray.AppendElement(msgKey);
+ if (++m_numHdrsCopied == m_copiedMsgIds.Length()) {
+ folder->RemoveFolderListener(this);
+ m_copiedMsgIds.Clear();
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnFolderRemoved(nsIMsgFolder* parent,
+ nsIMsgFolder* child) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnMessageRemoved(nsIMsgFolder* parent,
+ nsIMsgDBHdr* msg) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnFolderPropertyChanged(
+ nsIMsgFolder* item, const nsACString& property, const nsACString& oldValue,
+ const nsACString& newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnFolderIntPropertyChanged(
+ nsIMsgFolder* item, const nsACString& property, int64_t oldValue,
+ int64_t newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnFolderBoolPropertyChanged(
+ nsIMsgFolder* item, const nsACString& property, bool oldValue,
+ bool newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnFolderUnicharPropertyChanged(
+ nsIMsgFolder* item, const nsACString& property, const nsAString& oldValue,
+ const nsAString& newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnFolderPropertyFlagChanged(
+ nsIMsgDBHdr* item, const nsACString& property, uint32_t oldFlag,
+ uint32_t newFlag) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalMoveCopyMsgTxn::OnFolderEvent(nsIMsgFolder* aItem,
+ const nsACString& aEvent) {
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsLocalUndoFolderListener, nsIFolderListener)
+
+nsLocalUndoFolderListener::nsLocalUndoFolderListener(
+ nsLocalMoveCopyMsgTxn* aTxn, nsIMsgFolder* aFolder) {
+ mTxn = aTxn;
+ mFolder = aFolder;
+}
+
+nsLocalUndoFolderListener::~nsLocalUndoFolderListener() {}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnFolderAdded(nsIMsgFolder* parent,
+ nsIMsgFolder* child) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnMessageAdded(nsIMsgFolder* parent,
+ nsIMsgDBHdr* msg) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnFolderRemoved(nsIMsgFolder* parent,
+ nsIMsgFolder* child) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnMessageRemoved(nsIMsgFolder* parent,
+ nsIMsgDBHdr* msg) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnFolderPropertyChanged(
+ nsIMsgFolder* item, const nsACString& property, const nsACString& oldValue,
+ const nsACString& newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnFolderIntPropertyChanged(
+ nsIMsgFolder* item, const nsACString& property, int64_t oldValue,
+ int64_t newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnFolderBoolPropertyChanged(
+ nsIMsgFolder* item, const nsACString& property, bool oldValue,
+ bool newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnFolderUnicharPropertyChanged(
+ nsIMsgFolder* item, const nsACString& property, const nsAString& oldValue,
+ const nsAString& newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnFolderPropertyFlagChanged(
+ nsIMsgDBHdr* item, const nsACString& property, uint32_t oldFlag,
+ uint32_t newFlag) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalUndoFolderListener::OnFolderEvent(
+ nsIMsgFolder* aItem, const nsACString& aEvent) {
+ if (mTxn && mFolder && aItem == mFolder) {
+ if (aEvent.Equals(kFolderLoaded)) return mTxn->UndoTransactionInternal();
+ }
+
+ return NS_ERROR_FAILURE;
+}
diff --git a/comm/mailnews/local/src/nsLocalUndoTxn.h b/comm/mailnews/local/src/nsLocalUndoTxn.h
new file mode 100644
index 0000000000..f9f8244193
--- /dev/null
+++ b/comm/mailnews/local/src/nsLocalUndoTxn.h
@@ -0,0 +1,79 @@
+/* -*- 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 nsLocalUndoTxn_h__
+#define nsLocalUndoTxn_h__
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsIMsgFolder.h"
+#include "nsMailboxService.h"
+#include "nsMsgTxn.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsIFolderListener.h"
+#include "nsIWeakReferenceUtils.h"
+
+class nsLocalUndoFolderListener;
+
+class nsLocalMoveCopyMsgTxn : public nsIFolderListener, public nsMsgTxn {
+ public:
+ nsLocalMoveCopyMsgTxn();
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFOLDERLISTENER
+
+ // overloading nsITransaction methods
+ NS_IMETHOD UndoTransaction(void) override;
+ NS_IMETHOD RedoTransaction(void) override;
+
+ // helper
+ nsresult AddSrcKey(nsMsgKey aKey);
+ nsresult AddDstKey(nsMsgKey aKey);
+ nsresult AddDstMsgSize(uint32_t msgSize);
+ nsresult SetSrcFolder(nsIMsgFolder* srcFolder);
+ nsresult GetSrcIsImap(bool* isImap);
+ nsresult SetDstFolder(nsIMsgFolder* dstFolder);
+ nsresult Init(nsIMsgFolder* srcFolder, nsIMsgFolder* dstFolder, bool isMove);
+ nsresult UndoImapDeleteFlag(nsIMsgFolder* aFolder,
+ nsTArray<nsMsgKey>& aKeyArray, bool deleteFlag);
+ nsresult UndoTransactionInternal();
+ // If the store using this undo transaction can "undelete" a message,
+ // it will call this function on the transaction; This makes undo/redo
+ // easy because message keys don't change after undo/redo. Otherwise,
+ // we need to adjust the src or dst keys after every undo/redo action
+ // to note the new keys.
+ void SetCanUndelete(bool canUndelete) { m_canUndelete = canUndelete; }
+
+ private:
+ virtual ~nsLocalMoveCopyMsgTxn();
+ nsWeakPtr m_srcFolder;
+ nsTArray<nsMsgKey> m_srcKeyArray; // used when src is local or imap
+ nsWeakPtr m_dstFolder;
+ nsTArray<nsMsgKey> m_dstKeyArray;
+ bool m_isMove;
+ bool m_srcIsImap4;
+ bool m_canUndelete;
+ nsTArray<uint32_t> m_dstSizeArray;
+ bool m_undoing; // if false, re-doing
+ uint32_t m_numHdrsCopied;
+ nsTArray<nsCString> m_copiedMsgIds;
+ nsLocalUndoFolderListener* mUndoFolderListener;
+};
+
+class nsLocalUndoFolderListener : public nsIFolderListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFOLDERLISTENER
+
+ nsLocalUndoFolderListener(nsLocalMoveCopyMsgTxn* aTxn, nsIMsgFolder* aFolder);
+
+ private:
+ virtual ~nsLocalUndoFolderListener();
+ nsLocalMoveCopyMsgTxn* mTxn;
+ nsIMsgFolder* mFolder;
+};
+
+#endif
diff --git a/comm/mailnews/local/src/nsLocalUtils.cpp b/comm/mailnews/local/src/nsLocalUtils.cpp
new file mode 100644
index 0000000000..2dc96635c0
--- /dev/null
+++ b/comm/mailnews/local/src/nsLocalUtils.cpp
@@ -0,0 +1,208 @@
+/* -*- 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 "nsLocalUtils.h"
+#include "prsystem.h"
+#include "nsCOMPtr.h"
+#include "prmem.h"
+// stuff for temporary root folder hack
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsNativeCharsetUtils.h"
+
+#include "nsMsgUtils.h"
+#include "nsNetCID.h"
+#include "nsIURIMutator.h"
+
+// it would be really cool to:
+// - cache the last hostname->path match
+// - if no such server exists, behave like an old-style mailbox URL
+// (i.e. return the mail.directory preference or something)
+static nsresult nsGetMailboxServer(const char* uriStr,
+ nsIMsgIncomingServer** aResult) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIURL> url;
+ rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(nsDependentCString(uriStr))
+ .Finalize(url);
+ if (NS_FAILED(rv)) return rv;
+
+ // retrieve the AccountManager
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // find all local mail "no servers" matching the given hostname
+ nsCOMPtr<nsIMsgIncomingServer> none_server;
+ rv = NS_MutateURI(url).SetScheme("none"_ns).Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // No unescaping of username or hostname done here.
+ // The unescaping is done inside of FindServerByURI
+ rv = accountManager->FindServerByURI(url, getter_AddRefs(none_server));
+ if (NS_SUCCEEDED(rv)) {
+ none_server.forget(aResult);
+ return rv;
+ }
+
+ // if that fails, look for the rss hosts matching the given hostname
+ nsCOMPtr<nsIMsgIncomingServer> rss_server;
+ rv = NS_MutateURI(url).SetScheme("rss"_ns).Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accountManager->FindServerByURI(url, getter_AddRefs(rss_server));
+ if (NS_SUCCEEDED(rv)) {
+ rss_server.forget(aResult);
+ return rv;
+ }
+
+ // if that fails, look for the pop hosts matching the given hostname
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_FAILED(rv)) {
+ rv = NS_MutateURI(url).SetScheme("pop3"_ns).Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accountManager->FindServerByURI(url, getter_AddRefs(server));
+
+ // if we can't find a pop server, maybe it's a local message
+ // in an imap hierarchy. look for an imap server.
+ if (NS_FAILED(rv)) {
+ rv = NS_MutateURI(url).SetScheme("imap"_ns).Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accountManager->FindServerByURI(url, getter_AddRefs(server));
+ }
+ }
+ if (NS_SUCCEEDED(rv)) {
+ server.forget(aResult);
+ return rv;
+ }
+
+ // If you fail after looking at all "pop3", "none" servers, you fail.
+ return rv;
+}
+
+static nsresult nsLocalURI2Server(const char* uriStr,
+ nsIMsgIncomingServer** aResult) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = nsGetMailboxServer(uriStr, getter_AddRefs(server));
+ server.forget(aResult);
+ return rv;
+}
+
+// given rootURI and rootURI##folder, return on-disk path of folder
+nsresult nsLocalURI2Path(const char* rootURI, const char* uriStr,
+ nsCString& pathResult) {
+ nsresult rv;
+
+ // verify that rootURI starts with "mailbox:/" or "mailbox-message:/"
+ if ((PL_strcmp(rootURI, kMailboxRootURI) != 0) &&
+ (PL_strcmp(rootURI, kMailboxMessageRootURI) != 0)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // verify that uristr starts with rooturi
+ nsAutoCString uri(uriStr);
+ if (uri.Find(rootURI) != 0) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = nsLocalURI2Server(uriStr, getter_AddRefs(server));
+
+ if (NS_FAILED(rv)) return rv;
+
+ // now ask the server what it's root is
+ // and begin pathResult with the mailbox root
+ nsCOMPtr<nsIFile> localPath;
+ rv = server->GetLocalPath(getter_AddRefs(localPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef XP_WIN
+ nsString path = localPath->NativePath();
+ nsCString localNativePath;
+ NS_CopyUnicodeToNative(path, localNativePath);
+#else
+ nsCString localNativePath = localPath->NativePath();
+#endif
+ nsEscapeNativePath(localNativePath);
+ pathResult = localNativePath.get();
+ const char* curPos = uriStr + PL_strlen(rootURI);
+ if (curPos) {
+ // advance past hostname
+ while ((*curPos) == '/') curPos++;
+ while (*curPos && (*curPos) != '/') curPos++;
+
+ nsAutoCString newPath("");
+
+ // Unescape folder name
+ nsCString unescapedStr;
+ MsgUnescapeString(nsDependentCString(curPos), 0, unescapedStr);
+ NS_MsgCreatePathStringFromFolderURI(unescapedStr.get(), newPath, "none"_ns);
+
+ pathResult.Append('/');
+ pathResult.Append(newPath);
+ }
+
+ return NS_OK;
+}
+
+/* parses LocalMessageURI
+ * mailbox-message://folder1/folder2#123?header=none or
+ * mailbox-message://folder1/folder2#1234&part=1.2
+ *
+ * puts folder URI in folderURI (mailbox://folder1/folder2)
+ * message key number in key
+ */
+nsresult nsParseLocalMessageURI(const nsACString& uri, nsCString& folderURI,
+ nsMsgKey* key) {
+ if (!key) return NS_ERROR_NULL_POINTER;
+
+ const nsPromiseFlatCString& uriStr = PromiseFlatCString(uri);
+ int32_t keySeparator = uriStr.FindChar('#');
+ if (keySeparator != -1) {
+ int32_t keyEndSeparator = MsgFindCharInSet(uriStr, "?&", keySeparator);
+ folderURI = StringHead(uriStr, keySeparator);
+ folderURI.Cut(7, 8); // cut out the -message part of mailbox-message:
+
+ nsAutoCString keyStr;
+ if (keyEndSeparator != -1)
+ keyStr = Substring(uriStr, keySeparator + 1,
+ keyEndSeparator - (keySeparator + 1));
+ else
+ keyStr = StringTail(uriStr, uriStr.Length() - (keySeparator + 1));
+
+ *key = msgKeyFromInt(ParseUint64Str(keyStr.get()));
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsBuildLocalMessageURI(const nsACString& baseURI, nsMsgKey key,
+ nsACString& uri) {
+ // need to convert mailbox://hostname/.. to mailbox-message://hostname/..
+ uri.Append(baseURI);
+ uri.Append('#');
+ uri.AppendInt(key);
+ return NS_OK;
+}
+
+nsresult nsCreateLocalBaseMessageURI(const nsACString& baseURI,
+ nsCString& baseMessageURI) {
+ nsAutoCString tailURI(baseURI);
+
+ // chop off mailbox:/
+ if (tailURI.Find(kMailboxRootURI) == 0)
+ tailURI.Cut(0, PL_strlen(kMailboxRootURI));
+
+ baseMessageURI = kMailboxMessageRootURI;
+ baseMessageURI += tailURI;
+
+ return NS_OK;
+}
+
+void nsEscapeNativePath(nsCString& nativePath) {
+#if defined(XP_WIN)
+ nativePath.Insert('/', 0);
+ nativePath.ReplaceChar('\\', '/');
+#endif
+}
diff --git a/comm/mailnews/local/src/nsLocalUtils.h b/comm/mailnews/local/src/nsLocalUtils.h
new file mode 100644
index 0000000000..0208be8495
--- /dev/null
+++ b/comm/mailnews/local/src/nsLocalUtils.h
@@ -0,0 +1,29 @@
+/* -*- 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_LOCALUTILS_H
+#define NS_LOCALUTILS_H
+
+#include "nsString.h"
+#include "MailNewsTypes2.h"
+
+static const char kMailboxRootURI[] = "mailbox:/";
+static const char kMailboxMessageRootURI[] = "mailbox-message:/";
+
+nsresult nsLocalURI2Path(const char* rootURI, const char* uriStr,
+ nsCString& pathResult);
+
+nsresult nsParseLocalMessageURI(const nsACString& uri, nsCString& folderURI,
+ nsMsgKey* key);
+
+nsresult nsBuildLocalMessageURI(const nsACString& baseURI, nsMsgKey key,
+ nsACString& uri);
+
+nsresult nsCreateLocalBaseMessageURI(const nsACString& baseURI,
+ nsCString& baseMessageURI);
+
+void nsEscapeNativePath(nsCString& nativePath);
+
+#endif // NS_LOCALUTILS_H
diff --git a/comm/mailnews/local/src/nsMailboxProtocol.cpp b/comm/mailnews/local/src/nsMailboxProtocol.cpp
new file mode 100644
index 0000000000..389de0a93e
--- /dev/null
+++ b/comm/mailnews/local/src/nsMailboxProtocol.cpp
@@ -0,0 +1,657 @@
+/* -*- 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 "nsMailboxProtocol.h"
+#include "nscore.h"
+#include "nsIInputStreamPump.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgLineBuffer.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgFolder.h"
+#include "nsICopyMessageStreamListener.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "mozilla/SlicedInputStream.h"
+#include "prerror.h"
+#include "prprf.h"
+#include "nspr.h"
+#include "nsIStreamTransportService.h"
+#include "nsIStreamConverterService.h"
+#include "nsNetUtil.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgWindow.h"
+#include "nsISeekableStream.h"
+#include "nsStreamUtils.h"
+
+using namespace mozilla;
+
+static LazyLogModule MAILBOX("Mailbox");
+
+/* 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)
+
+nsMailboxProtocol::nsMailboxProtocol(nsIURI* aURI)
+ : nsMsgProtocol(aURI),
+ m_mailboxAction(nsIMailboxUrl::ActionParseMailbox),
+ m_nextState(MAILBOX_UNINITIALIZED),
+ m_initialState(MAILBOX_UNINITIALIZED),
+ mCurrentProgress(0) {}
+
+nsMailboxProtocol::~nsMailboxProtocol() {}
+
+nsresult nsMailboxProtocol::OpenMultipleMsgTransport(uint64_t offset,
+ int64_t size) {
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamTransportService> serv =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsCOMPtr<nsIInputStream> replacementStream;
+ rv = NS_CloneInputStream(m_multipleMsgMoveCopyStream,
+ getter_AddRefs(clonedStream),
+ getter_AddRefs(replacementStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (replacementStream) {
+ // If m_multipleMsgMoveCopyStream is not clonable, NS_CloneInputStream
+ // will clone it using a pipe. In order to keep the copy alive and working,
+ // we have to replace the original stream with the replacement.
+ m_multipleMsgMoveCopyStream = replacementStream.forget();
+ }
+ // XXX 64-bit
+ // This can be called with size == -1 which means "read as much as we can".
+ // We pass this on as UINT64_MAX, which is in fact uint64_t(-1).
+ RefPtr<SlicedInputStream> slicedStream = new SlicedInputStream(
+ clonedStream.forget(), offset, size == -1 ? UINT64_MAX : uint64_t(size));
+ // Always close the sliced stream when done, we still have the original.
+ rv = serv->CreateInputTransport(slicedStream, true,
+ getter_AddRefs(m_transport));
+
+ return rv;
+}
+
+nsresult nsMailboxProtocol::Initialize(nsIURI* aURL) {
+ NS_ASSERTION(aURL, "invalid URL passed into MAILBOX Protocol");
+ nsresult rv = NS_OK;
+ if (aURL) {
+ m_runningUrl = do_QueryInterface(aURL, &rv);
+ if (NS_SUCCEEDED(rv) && m_runningUrl) {
+ nsCOMPtr<nsIMsgWindow> window;
+ rv = m_runningUrl->GetMailboxAction(&m_mailboxAction);
+ // clear stopped flag on msg window, because we care.
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl) {
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(window));
+ if (window) window->SetStopped(false);
+ }
+
+ if (m_mailboxAction == nsIMailboxUrl::ActionParseMailbox) {
+ // Set the length of the file equal to the max progress
+ nsCOMPtr<nsIFile> file;
+ GetFileFromURL(aURL, getter_AddRefs(file));
+ if (file) {
+ int64_t fileSize = 0;
+ file->GetFileSize(&fileSize);
+ mailnewsUrl->SetMaxProgress(fileSize);
+ }
+
+ rv =
+ OpenFileSocket(aURL, 0, -1 /* read in all the bytes in the file */);
+ } else {
+ if (RunningMultipleMsgUrl()) {
+ // if we're running multiple msg url, we clear the event sink because
+ // the multiple msg urls will handle setting the progress.
+ mProgressEventSink = nullptr;
+ }
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl =
+ do_QueryInterface(m_runningUrl, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr) {
+ uint32_t msgSize = 0;
+ msgHdr->GetMessageSize(&msgSize);
+ m_runningUrl->SetMessageSize(msgSize);
+
+ SetContentLength(msgSize);
+ mailnewsUrl->SetMaxProgress(msgSize);
+
+ rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder) {
+ nsCOMPtr<nsIInputStream> stream;
+ int64_t offset = 0;
+ rv = folder->GetMsgInputStream(msgHdr, getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISeekableStream> seekableStream(
+ do_QueryInterface(stream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ seekableStream->Tell(&offset);
+ // create input stream transport
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ m_readCount = msgSize;
+
+ RefPtr<SlicedInputStream> slicedStream = new SlicedInputStream(
+ stream.forget(), offset, uint64_t(msgSize));
+ // Always close the sliced stream when done, we still have the
+ // original.
+ rv = sts->CreateInputTransport(slicedStream, true,
+ getter_AddRefs(m_transport));
+
+ m_socketIsOpen = false;
+ }
+ }
+ if (!folder) { // must be a .eml file
+ rv = OpenFileSocket(aURL, 0, -1);
+ }
+ }
+ NS_ASSERTION(NS_SUCCEEDED(rv), "oops....i messed something up");
+ }
+ }
+ }
+
+ m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true);
+
+ m_nextState = MAILBOX_READ_FOLDER;
+ m_initialState = MAILBOX_READ_FOLDER;
+ mCurrentProgress = 0;
+
+ // do we really need both?
+ m_tempMessageFile = m_tempMsgFile;
+ return rv;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// we support the nsIStreamListener interface
+////////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMailboxProtocol::OnStartRequest(nsIRequest* request) {
+ // extract the appropriate event sinks from the url and initialize them in our
+ // protocol data the URL should be queried for a nsINewsURL. If it doesn't
+ // support a news URL interface then we have an error.
+ if (m_nextState == MAILBOX_READ_FOLDER && m_mailboxParser) {
+ // we need to inform our mailbox parser that it's time to start...
+ // NOTE: `request` here will be an nsInputStreamPump, but our callbacks
+ // are expecting to be able to QI to a `nsIChannel` to get the URI.
+ // So we pass `this`. See Bug 1528662.
+ m_mailboxParser->OnStartRequest(this);
+ }
+ return nsMsgProtocol::OnStartRequest(request);
+}
+
+bool nsMailboxProtocol::RunningMultipleMsgUrl() {
+ if (m_mailboxAction == nsIMailboxUrl::ActionCopyMessage ||
+ m_mailboxAction == nsIMailboxUrl::ActionMoveMessage) {
+ uint32_t numMoveCopyMsgs;
+ nsresult rv = m_runningUrl->GetNumMoveCopyMsgs(&numMoveCopyMsgs);
+ if (NS_SUCCEEDED(rv) && numMoveCopyMsgs > 1) return true;
+ }
+ return false;
+}
+
+// stop binding is a "notification" informing us that the stream associated with
+// aURL is going away.
+NS_IMETHODIMP nsMailboxProtocol::OnStopRequest(nsIRequest* request,
+ nsresult aStatus) {
+ nsresult rv;
+ if (m_nextState == MAILBOX_READ_FOLDER && m_mailboxParser) {
+ // we need to inform our mailbox parser that there is no more incoming
+ // data... NOTE: `request` here will be an nsInputStreamPump, but our
+ // callbacks are expecting to be able to QI to a `nsIChannel` to get the
+ // URI. So we pass `this`. See Bug 1528662.
+ m_mailboxParser->OnStopRequest(this, aStatus);
+ } else if (m_nextState == MAILBOX_READ_MESSAGE) {
+ DoneReadingMessage();
+ }
+ // I'm not getting cancel status - maybe the load group still has the status.
+ bool stopped = false;
+ if (m_runningUrl) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl) {
+ nsCOMPtr<nsIMsgWindow> window;
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(window));
+ if (window) window->GetStopped(&stopped);
+ }
+
+ if (!stopped && NS_SUCCEEDED(aStatus) &&
+ (m_mailboxAction == nsIMailboxUrl::ActionCopyMessage ||
+ m_mailboxAction == nsIMailboxUrl::ActionMoveMessage)) {
+ uint32_t numMoveCopyMsgs;
+ uint32_t curMoveCopyMsgIndex;
+ rv = m_runningUrl->GetNumMoveCopyMsgs(&numMoveCopyMsgs);
+ if (NS_SUCCEEDED(rv) && numMoveCopyMsgs > 0) {
+ m_runningUrl->GetCurMoveCopyMsgIndex(&curMoveCopyMsgIndex);
+ if (++curMoveCopyMsgIndex < numMoveCopyMsgs) {
+ if (!mSuppressListenerNotifications && m_channelListener) {
+ nsCOMPtr<nsICopyMessageStreamListener> listener =
+ do_QueryInterface(m_channelListener, &rv);
+ if (listener) {
+ listener->EndCopy(mailnewsUrl, aStatus);
+ listener->StartMessage(); // start next message.
+ }
+ }
+ m_runningUrl->SetCurMoveCopyMsgIndex(curMoveCopyMsgIndex);
+ nsCOMPtr<nsIMsgDBHdr> nextMsg;
+ rv = m_runningUrl->GetMoveCopyMsgHdrForIndex(curMoveCopyMsgIndex,
+ getter_AddRefs(nextMsg));
+ if (NS_SUCCEEDED(rv) && nextMsg) {
+ uint32_t msgSize = 0;
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nextMsg->GetFolder(getter_AddRefs(msgFolder));
+ NS_ASSERTION(
+ msgFolder,
+ "couldn't get folder for next msg in multiple msg local copy");
+ if (msgFolder) {
+ nsCString uri;
+ msgFolder->GetUriForMsg(nextMsg, uri);
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl =
+ do_QueryInterface(m_runningUrl);
+ if (msgUrl) {
+ msgUrl->SetOriginalSpec(uri);
+ msgUrl->SetUri(uri);
+
+ uint64_t msgOffset;
+ nextMsg->GetMessageOffset(&msgOffset);
+ nextMsg->GetMessageSize(&msgSize);
+ // now we have to seek to the right position in the file and
+ // basically re-initialize the transport with the correct
+ // message size. then, we have to make sure the url keeps
+ // running somehow.
+ //
+ // put us in a state where we are always notified of incoming
+ // data
+ //
+ m_transport = nullptr; // open new stream transport
+ m_outputStream = nullptr;
+
+ if (m_multipleMsgMoveCopyStream) {
+ rv = OpenMultipleMsgTransport(msgOffset, msgSize);
+ } else {
+ nsCOMPtr<nsIInputStream> stream;
+ rv = msgFolder->GetMsgInputStream(nextMsg,
+ getter_AddRefs(stream));
+ if (NS_SUCCEEDED(rv)) {
+ // create input stream transport
+ nsCOMPtr<nsIStreamTransportService> sts = do_GetService(
+ NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ m_readCount = msgSize;
+ RefPtr<SlicedInputStream> slicedStream =
+ new SlicedInputStream(stream.forget(), msgOffset,
+ uint64_t(msgSize));
+ rv = sts->CreateInputTransport(
+ slicedStream, true, getter_AddRefs(m_transport));
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIInputStream> stream;
+ rv = m_transport->OpenInputStream(0, 0, 0,
+ getter_AddRefs(stream));
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump),
+ stream.forget());
+ if (NS_SUCCEEDED(rv)) {
+ rv = pump->AsyncRead(this);
+ if (NS_SUCCEEDED(rv)) m_request = pump;
+ }
+ }
+ }
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncRead failed");
+ if (m_loadGroup)
+ m_loadGroup->RemoveRequest(static_cast<nsIRequest*>(this),
+ nullptr, aStatus);
+ m_socketIsOpen = true; // mark the channel as open
+ return aStatus;
+ }
+ }
+ }
+ } else {
+ }
+ }
+ }
+ }
+ // and we want to mark ourselves for deletion or some how inform our protocol
+ // manager that we are available for another url if there is one.
+
+ // mscott --> maybe we should set our state to done because we don't run
+ // multiple urls in a mailbox protocol connection....
+ m_nextState = MAILBOX_DONE;
+
+ // the following is for smoke test purposes. QA is looking at this "Mailbox
+ // Done" string which is printed out to the console and determining if the
+ // mail app loaded up correctly...obviously this solution is not very good so
+ // we should look at something better, but don't remove this line before
+ // talking to me (mscott) and mailnews QA....
+
+ MOZ_LOG(MAILBOX, LogLevel::Info, ("Mailbox Done"));
+
+ // when on stop binding is called, we as the protocol are done...let's close
+ // down the connection releasing all of our interfaces. It's important to
+ // remember that this on stop binding call is coming from netlib so they are
+ // never going to ping us again with on data available. This means we'll never
+ // be going through the Process loop...
+
+ if (m_multipleMsgMoveCopyStream) {
+ m_multipleMsgMoveCopyStream->Close();
+ m_multipleMsgMoveCopyStream = nullptr;
+ }
+ nsMsgProtocol::OnStopRequest(request, aStatus);
+ return CloseSocket();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// End of nsIStreamListenerSupport
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsMailboxProtocol::DoneReadingMessage() {
+ nsresult rv = NS_OK;
+ // and close the article file if it was open....
+
+ if (m_mailboxAction == nsIMailboxUrl::ActionSaveMessageToDisk &&
+ m_msgFileOutputStream)
+ rv = m_msgFileOutputStream->Close();
+
+ return rv;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Begin protocol state machine functions...
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsMailboxProtocol::LoadUrl(nsIURI* aURL, nsISupports* aConsumer) {
+ nsresult rv = NS_OK;
+ // if we were already initialized with a consumer, use it...
+ nsCOMPtr<nsIStreamListener> consumer = do_QueryInterface(aConsumer);
+ if (consumer) m_channelListener = consumer;
+
+ if (aURL) {
+ m_runningUrl = do_QueryInterface(aURL);
+ if (m_runningUrl) {
+ // find out from the url what action we are supposed to perform...
+ rv = m_runningUrl->GetMailboxAction(&m_mailboxAction);
+
+ bool convertData = false;
+
+ // need to check if we're fetching an rfc822 part in order to
+ // quote a message.
+ if (m_mailboxAction == nsIMailboxUrl::ActionFetchMessage) {
+ 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") != -1 ||
+ queryStr.Find("header=attach") != -1);
+ } else if (m_mailboxAction == nsIMailboxUrl::ActionFetchPart) {
+ // when fetching a part, we need to insert a converter into the listener
+ // chain order to force just the part out of the message. Our channel
+ // listener is the consumer we'll pass in to AsyncConvertData.
+ convertData = true;
+ consumer = m_channelListener;
+ }
+ if (convertData) {
+ nsCOMPtr<nsIStreamConverterService> streamConverter =
+ do_GetService("@mozilla.org/streamConverters;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIStreamListener> conversionListener;
+ nsCOMPtr<nsIChannel> channel;
+ QueryInterface(NS_GET_IID(nsIChannel), getter_AddRefs(channel));
+
+ rv = streamConverter->AsyncConvertData(
+ "message/rfc822", "*/*", consumer, channel,
+ getter_AddRefs(m_channelListener));
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ switch (m_mailboxAction) {
+ case nsIMailboxUrl::ActionParseMailbox:
+ // extract the mailbox parser..
+ rv =
+ m_runningUrl->GetMailboxParser(getter_AddRefs(m_mailboxParser));
+ m_nextState = MAILBOX_READ_FOLDER;
+ break;
+ case nsIMailboxUrl::ActionSaveMessageToDisk:
+ // ohhh, display message already writes a msg to disk (as part of a
+ // hack) so we can piggy back off of that!! We just need to change
+ // m_tempMessageFile to be the name of our save message to disk
+ // file. Since save message to disk urls are run without a docshell
+ // to display the msg into, we won't be trying to display the
+ // message after we write it to disk...
+ {
+ nsCOMPtr<nsIMsgMessageUrl> messageUrl =
+ do_QueryInterface(m_runningUrl, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ messageUrl->GetMessageFile(getter_AddRefs(m_tempMessageFile));
+ rv = MsgNewBufferedFileOutputStream(
+ getter_AddRefs(m_msgFileOutputStream), m_tempMessageFile,
+ -1, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool addDummyEnvelope = false;
+ messageUrl->GetAddDummyEnvelope(&addDummyEnvelope);
+ if (addDummyEnvelope)
+ SetFlag(MAILBOX_MSG_PARSE_FIRST_LINE);
+ else
+ ClearFlag(MAILBOX_MSG_PARSE_FIRST_LINE);
+ }
+ }
+ m_nextState = MAILBOX_READ_MESSAGE;
+ break;
+ case nsIMailboxUrl::ActionCopyMessage:
+ case nsIMailboxUrl::ActionMoveMessage:
+ case nsIMailboxUrl::ActionFetchMessage:
+ ClearFlag(MAILBOX_MSG_PARSE_FIRST_LINE);
+ m_nextState = MAILBOX_READ_MESSAGE;
+ break;
+ case nsIMailboxUrl::ActionFetchPart:
+ m_nextState = MAILBOX_READ_MESSAGE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ rv = nsMsgProtocol::LoadUrl(aURL, m_channelListener);
+
+ } // if we received an MAILBOX url...
+ } // if we received a url!
+
+ return rv;
+}
+
+int32_t nsMailboxProtocol::ReadFolderResponse(nsIInputStream* inputStream,
+ uint64_t sourceOffset,
+ uint32_t length) {
+ // okay we are doing a folder read in 8K chunks of a mail folder....
+ // this is almost too easy....we can just forward the data in this stream on
+ // to our folder parser object!!!
+
+ nsresult rv = NS_OK;
+ mCurrentProgress += length;
+
+ if (m_mailboxParser) {
+ rv = m_mailboxParser->OnDataAvailable(
+ nullptr, inputStream, sourceOffset,
+ length); // let the parser deal with it...
+ }
+ if (NS_FAILED(rv)) {
+ m_nextState = MAILBOX_ERROR_DONE; // drop out of the loop....
+ return -1;
+ }
+
+ // now wait for the next 8K chunk to come in.....
+ SetFlag(MAILBOX_PAUSE_FOR_READ);
+
+ // leave our state alone so when the next chunk of the mailbox comes in we
+ // jump to this state and repeat....how does this process end? Well when the
+ // file is done being read in, core net lib will issue an ::OnStopRequest to
+ // us...we'll use that as our sign to drop out of this state and to close the
+ // protocol instance...
+
+ return 0;
+}
+
+int32_t nsMailboxProtocol::ReadMessageResponse(nsIInputStream* inputStream,
+ uint64_t sourceOffset,
+ uint32_t length) {
+ char* line = nullptr;
+ uint32_t status = 0;
+ nsresult rv = NS_OK;
+ mCurrentProgress += length;
+
+ // if we are doing a move or a copy, forward the data onto the copy handler...
+ // if we want to display the message then parse the incoming data...
+
+ if (m_channelListener) {
+ // just forward the data we read in to the listener...
+ rv = m_channelListener->OnDataAvailable(this, inputStream, sourceOffset,
+ length);
+ } else {
+ bool pauseForMoreData = false;
+ bool canonicalLineEnding = false;
+ nsCOMPtr<nsIMsgMessageUrl> msgurl = do_QueryInterface(m_runningUrl);
+
+ if (msgurl) msgurl->GetCanonicalLineEnding(&canonicalLineEnding);
+
+ while ((line = m_lineStreamBuffer->ReadNextLine(inputStream, status,
+ pauseForMoreData)) &&
+ !pauseForMoreData) {
+ /* When we're sending this line to a converter (ie,
+ it's a message/rfc822) use the local line termination
+ convention, not CRLF. This makes text articles get
+ saved with the local line terminators. Since SMTP
+ and NNTP mandate the use of CRLF, it is expected that
+ the local system will convert that to the local line
+ terminator as it is read.
+ */
+ // mscott - the firstline hack is aimed at making sure we don't write
+ // out the dummy header when we are trying to display the message.
+ // The dummy header is the From line with the date tag on it.
+ if (m_msgFileOutputStream && TestFlag(MAILBOX_MSG_PARSE_FIRST_LINE)) {
+ uint32_t count = 0;
+ rv = m_msgFileOutputStream->Write(line, PL_strlen(line), &count);
+ if (NS_FAILED(rv)) break;
+
+ if (canonicalLineEnding)
+ rv = m_msgFileOutputStream->Write(CRLF, 2, &count);
+ else
+ rv = m_msgFileOutputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN,
+ &count);
+
+ if (NS_FAILED(rv)) break;
+ } else
+ SetFlag(MAILBOX_MSG_PARSE_FIRST_LINE);
+ PR_Free(line);
+ }
+ PR_Free(line);
+ }
+
+ SetFlag(MAILBOX_PAUSE_FOR_READ); // wait for more data to become available...
+ if (mProgressEventSink && m_runningUrl) {
+ int64_t maxProgress;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
+ mailnewsUrl->GetMaxProgress(&maxProgress);
+ mProgressEventSink->OnProgress(this, mCurrentProgress, maxProgress);
+ }
+
+ if (NS_FAILED(rv)) return -1;
+
+ return 0;
+}
+
+/*
+ * returns negative if the transfer is finished or error'd out
+ *
+ * returns zero or more if the transfer needs to be continued.
+ */
+nsresult nsMailboxProtocol::ProcessProtocolState(nsIURI* url,
+ nsIInputStream* inputStream,
+ uint64_t offset,
+ uint32_t length) {
+ nsresult rv = NS_OK;
+ int32_t status = 0;
+ ClearFlag(MAILBOX_PAUSE_FOR_READ); /* already paused; reset */
+
+ while (!TestFlag(MAILBOX_PAUSE_FOR_READ)) {
+ switch (m_nextState) {
+ case MAILBOX_READ_MESSAGE:
+ if (inputStream == nullptr)
+ SetFlag(MAILBOX_PAUSE_FOR_READ);
+ else
+ status = ReadMessageResponse(inputStream, offset, length);
+ break;
+ case MAILBOX_READ_FOLDER:
+ if (inputStream == nullptr)
+ SetFlag(MAILBOX_PAUSE_FOR_READ); // wait for file socket to read in
+ // the next chunk...
+ else
+ status = ReadFolderResponse(inputStream, offset, length);
+ break;
+ case MAILBOX_DONE:
+ case MAILBOX_ERROR_DONE: {
+ nsCOMPtr<nsIMsgMailNewsUrl> anotherUrl =
+ do_QueryInterface(m_runningUrl);
+ rv = m_nextState == MAILBOX_DONE ? NS_OK : NS_ERROR_FAILURE;
+ anotherUrl->SetUrlState(false, rv);
+ m_nextState = MAILBOX_FREE;
+ } break;
+
+ case MAILBOX_FREE:
+ // MAILBOX is a one time use connection so kill it if we get here...
+ CloseSocket();
+ return rv; /* final end */
+
+ default: /* should never happen !!! */
+ m_nextState = MAILBOX_ERROR_DONE;
+ break;
+ }
+
+ /* check for errors during load and call error
+ * state if found
+ */
+ if (status < 0 && m_nextState != MAILBOX_FREE) {
+ m_nextState = MAILBOX_ERROR_DONE;
+ /* don't exit! loop around again and do the free case */
+ ClearFlag(MAILBOX_PAUSE_FOR_READ);
+ }
+ } /* while(!MAILBOX_PAUSE_FOR_READ) */
+
+ return rv;
+}
+
+nsresult nsMailboxProtocol::CloseSocket() {
+ // how do you force a release when closing the connection??
+ nsMsgProtocol::CloseSocket();
+ m_runningUrl = nullptr;
+ m_mailboxParser = nullptr;
+ return NS_OK;
+}
+
+// vim: ts=2 sw=2
diff --git a/comm/mailnews/local/src/nsMailboxProtocol.h b/comm/mailnews/local/src/nsMailboxProtocol.h
new file mode 100644
index 0000000000..287729cb44
--- /dev/null
+++ b/comm/mailnews/local/src/nsMailboxProtocol.h
@@ -0,0 +1,113 @@
+/* -*- 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 nsMailboxProtocol_h___
+#define nsMailboxProtocol_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMsgProtocol.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIOutputStream.h"
+#include "nsIMailboxUrl.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 MAILBOX_PAUSE_FOR_READ \
+ 0x00000001 /* should we pause for the next read */
+#define MAILBOX_MSG_PARSE_FIRST_LINE \
+ 0x00000002 /* have we read in the first line of the msg */
+
+/* states of the machine
+ */
+typedef enum _MailboxStatesEnum {
+ MAILBOX_UNINITIALIZED,
+ MAILBOX_READ_FOLDER,
+ MAILBOX_READ_MESSAGE,
+ MAILBOX_DONE,
+ MAILBOX_ERROR_DONE,
+ MAILBOX_FREE,
+} MailboxStatesEnum;
+
+class nsMsgLineStreamBuffer;
+
+class nsMailboxProtocol : public nsMsgProtocol {
+ public:
+ // Creating a protocol instance requires the URL which needs to be run AND it
+ // requires a transport layer.
+ explicit nsMailboxProtocol(nsIURI* aURL);
+ virtual ~nsMailboxProtocol();
+
+ // initialization function given a new url and transport layer
+ nsresult Initialize(nsIURI* aURL);
+
+ // the consumer of the url might be something like an nsIDocShell....
+ virtual nsresult LoadUrl(nsIURI* aURL, nsISupports* aConsumer) override;
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we support the nsIStreamListener interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ NS_IMETHOD OnStartRequest(nsIRequest* request) override;
+ NS_IMETHOD OnStopRequest(nsIRequest* request, nsresult aStatus) override;
+
+ private:
+ nsCOMPtr<nsIMailboxUrl>
+ m_runningUrl; // the nsIMailboxURL that is currently running
+ nsMailboxAction m_mailboxAction; // current mailbox action associated with
+ // this connection...
+ // Event sink handles
+ nsCOMPtr<nsIStreamListener> m_mailboxParser;
+
+ // Local state for the current operation
+ RefPtr<nsMsgLineStreamBuffer>
+ m_lineStreamBuffer; // used to efficiently extract lines from the
+ // incoming data stream
+
+ // 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.
+ MailboxStatesEnum m_nextState;
+ MailboxStatesEnum m_initialState;
+
+ int64_t mCurrentProgress;
+
+ // can we just use the base class m_tempMsgFile?
+ nsCOMPtr<nsIFile> m_tempMessageFile;
+ nsCOMPtr<nsIOutputStream> m_msgFileOutputStream;
+
+ // this is used to hold the source mailbox file open when move/copying
+ // multiple messages.
+ nsCOMPtr<nsIInputStream> m_multipleMsgMoveCopyStream;
+
+ virtual nsresult ProcessProtocolState(nsIURI* url,
+ nsIInputStream* inputStream,
+ uint64_t sourceOffset,
+ uint32_t length) override;
+ virtual nsresult CloseSocket() override;
+
+ nsresult OpenMultipleMsgTransport(uint64_t offset, int64_t size);
+ bool RunningMultipleMsgUrl();
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // 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.
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ // When parsing a mailbox folder in chunks, this protocol state reads in the
+ // current chunk and forwards it to the mailbox parser.
+ int32_t ReadFolderResponse(nsIInputStream* inputStream, uint64_t sourceOffset,
+ uint32_t length);
+ int32_t ReadMessageResponse(nsIInputStream* inputStream,
+ uint64_t sourceOffset, uint32_t length);
+ nsresult DoneReadingMessage();
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // End of Protocol Methods
+ ////////////////////////////////////////////////////////////////////////////////////////
+};
+
+#endif // nsMailboxProtocol_h___
diff --git a/comm/mailnews/local/src/nsMailboxServer.cpp b/comm/mailnews/local/src/nsMailboxServer.cpp
new file mode 100644
index 0000000000..e0ddc26388
--- /dev/null
+++ b/comm/mailnews/local/src/nsMailboxServer.cpp
@@ -0,0 +1,28 @@
+/**
+ * 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 "nsMailboxServer.h"
+#include "nsLocalMailFolder.h"
+
+NS_IMETHODIMP
+nsMailboxServer::GetLocalStoreType(nsACString& type) {
+ type.AssignLiteral("mailbox");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailboxServer::GetLocalDatabaseType(nsACString& type) {
+ type.AssignLiteral("mailbox");
+ return NS_OK;
+}
+
+nsresult nsMailboxServer::CreateRootFolderFromUri(const nsACString& serverUri,
+ nsIMsgFolder** rootFolder) {
+ nsMsgLocalMailFolder* newRootFolder = new nsMsgLocalMailFolder;
+ if (!newRootFolder) return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*rootFolder = newRootFolder);
+ newRootFolder->Init(serverUri);
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsMailboxServer.h b/comm/mailnews/local/src/nsMailboxServer.h
new file mode 100644
index 0000000000..07bf628aae
--- /dev/null
+++ b/comm/mailnews/local/src/nsMailboxServer.h
@@ -0,0 +1,22 @@
+/**
+ * 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 nsMailboxServer_h__
+#define nsMailboxServer_h__
+
+#include "mozilla/Attributes.h"
+#include "nsMsgIncomingServer.h"
+
+class nsMailboxServer : public nsMsgIncomingServer {
+ public:
+ NS_IMETHOD GetLocalStoreType(nsACString& type) override;
+ NS_IMETHOD GetLocalDatabaseType(nsACString& type) override;
+
+ protected:
+ virtual nsresult CreateRootFolderFromUri(const nsACString& serverUri,
+ nsIMsgFolder** rootFolder) override;
+};
+
+#endif
diff --git a/comm/mailnews/local/src/nsMailboxService.cpp b/comm/mailnews/local/src/nsMailboxService.cpp
new file mode 100644
index 0000000000..8a6a9766a8
--- /dev/null
+++ b/comm/mailnews/local/src/nsMailboxService.cpp
@@ -0,0 +1,559 @@
+/* -*- 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 "nsCOMPtr.h"
+
+#include "nsMailboxService.h"
+#include "nsMailboxUrl.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMailboxProtocol.h"
+#include "nsIMsgDatabase.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsLocalUtils.h"
+#include "nsIDocShell.h"
+#include "nsMsgUtils.h"
+#include "nsPop3URL.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsNetUtil.h"
+#include "nsIWebNavigation.h"
+#include "prprf.h"
+#include "nsIMsgHdr.h"
+#include "nsIFileURL.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/LoadInfo.h"
+#include "nsDocShellLoadState.h"
+#include "nsContentUtils.h"
+#include "nsMsgFileHdr.h"
+
+nsMailboxService::nsMailboxService() {}
+
+nsMailboxService::~nsMailboxService() {}
+
+NS_IMPL_ISUPPORTS(nsMailboxService, nsIMailboxService, nsIMsgMessageService,
+ nsIProtocolHandler, nsIMsgMessageFetchPartService)
+
+nsresult nsMailboxService::ParseMailbox(nsIMsgWindow* aMsgWindow,
+ nsIFile* aMailboxPath,
+ nsIStreamListener* aMailboxParser,
+ nsIUrlListener* aUrlListener,
+ nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aMailboxPath);
+
+ nsresult rv;
+ nsCOMPtr<nsIMailboxUrl> mailboxurl =
+ do_CreateInstance("@mozilla.org/messenger/mailboxurl;1", &rv);
+ if (NS_SUCCEEDED(rv) && mailboxurl) {
+ nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(mailboxurl);
+ // okay now generate the url string
+#ifdef XP_WIN
+ nsString path = aMailboxPath->NativePath();
+ nsCString mailboxPath;
+ NS_CopyUnicodeToNative(path, mailboxPath);
+#else
+ nsCString mailboxPath = aMailboxPath->NativePath();
+#endif
+ nsAutoCString buf;
+ MsgEscapeURL(mailboxPath,
+ nsINetUtil::ESCAPE_URL_MINIMAL | nsINetUtil::ESCAPE_URL_FORCED,
+ buf);
+ nsEscapeNativePath(buf);
+ url->SetUpdatingFolder(true);
+ url->SetMsgWindow(aMsgWindow);
+ nsAutoCString uriSpec("mailbox://");
+ uriSpec.Append(buf);
+ rv = url->SetSpecInternal(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mailboxurl->SetMailboxParser(aMailboxParser);
+ if (aUrlListener) url->RegisterListener(aUrlListener);
+
+ rv = RunMailboxUrl(url, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aURL) {
+ url.forget(aURL);
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsMailboxService::CopyMessage(const nsACString& aSrcMailboxURI,
+ nsIStreamListener* aMailboxCopyHandler,
+ bool moveMessage,
+ nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow) {
+ nsMailboxAction mailboxAction = nsIMailboxUrl::ActionMoveMessage;
+ nsCOMPtr<nsIURI> aURL; // unused...
+ if (!moveMessage) mailboxAction = nsIMailboxUrl::ActionCopyMessage;
+ return FetchMessage(aSrcMailboxURI, aMailboxCopyHandler, aMsgWindow,
+ aUrlListener, nullptr, mailboxAction, false,
+ getter_AddRefs(aURL));
+}
+
+nsresult nsMailboxService::CopyMessages(
+ const nsTArray<nsMsgKey>& aMsgKeys, nsIMsgFolder* srcFolder,
+ nsIStreamListener* aMailboxCopyHandler, bool moveMessage,
+ nsIUrlListener* aUrlListener, nsIMsgWindow* aMsgWindow, nsIURI** aURL) {
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG(srcFolder);
+ NS_ENSURE_TRUE(!aMsgKeys.IsEmpty(), NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsIMailboxUrl> mailboxurl;
+
+ nsMailboxAction actionToUse = nsIMailboxUrl::ActionMoveMessage;
+ if (!moveMessage) actionToUse = nsIMailboxUrl::ActionCopyMessage;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgDatabase> db;
+ srcFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (db) {
+ db->GetMsgHdrForKey(aMsgKeys[0], getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ nsCString uri;
+ srcFolder->GetUriForMsg(msgHdr, uri);
+ rv = PrepareMessageUrl(uri, aUrlListener, actionToUse,
+ getter_AddRefs(mailboxurl), aMsgWindow);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIURI> url = do_QueryInterface(mailboxurl);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl(do_QueryInterface(url));
+ nsCOMPtr<nsIMailboxUrl> mailboxUrl(do_QueryInterface(url));
+ msgUrl->SetMsgWindow(aMsgWindow);
+
+ mailboxUrl->SetMoveCopyMsgKeys(aMsgKeys);
+ rv = RunMailboxUrl(url, aMailboxCopyHandler);
+ }
+ }
+ }
+ if (aURL && mailboxurl) CallQueryInterface(mailboxurl, aURL);
+
+ return rv;
+}
+
+nsresult nsMailboxService::FetchMessage(
+ const nsACString& aMessageURI, nsISupports* aDisplayConsumer,
+ nsIMsgWindow* aMsgWindow, nsIUrlListener* aUrlListener,
+ const char* aFileName, /* only used by open attachment... */
+ nsMailboxAction mailboxAction, bool aAutodetectCharset, nsIURI** aURL) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMailboxUrl> mailboxurl;
+ nsMailboxAction actionToUse = mailboxAction;
+ nsCOMPtr<nsIURI> url;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
+ nsAutoCString uriString(aMessageURI);
+
+ if (StringBeginsWith(aMessageURI, "file:"_ns)) {
+ int64_t fileSize;
+ nsCOMPtr<nsIURI> fileUri;
+ rv = NS_NewURI(getter_AddRefs(fileUri), aMessageURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(fileUri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ file->GetFileSize(&fileSize);
+ uriString.Replace(0, 5, "mailbox:"_ns);
+ uriString.AppendLiteral("&number=0");
+ rv = NS_NewURI(getter_AddRefs(url), uriString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgUrl = do_QueryInterface(url);
+ if (msgUrl) {
+ msgUrl->SetMsgWindow(aMsgWindow);
+ nsCOMPtr<nsIMailboxUrl> mailboxUrl = do_QueryInterface(msgUrl, &rv);
+ mailboxUrl->SetMessageSize((uint32_t)fileSize);
+ }
+ } else {
+ // this happens with forward inline of message/rfc822 attachment
+ // opened in a stand-alone msg window.
+ int32_t typeIndex = uriString.Find("&type=application/x-message-display");
+ if (typeIndex != -1) {
+ uriString.Cut(typeIndex,
+ sizeof("&type=application/x-message-display") - 1);
+ rv = NS_NewURI(getter_AddRefs(url), uriString.get());
+ mailboxurl = do_QueryInterface(url);
+ } else
+ rv = PrepareMessageUrl(aMessageURI, aUrlListener, actionToUse,
+ getter_AddRefs(mailboxurl), aMsgWindow);
+
+ if (NS_SUCCEEDED(rv)) {
+ url = do_QueryInterface(mailboxurl);
+ msgUrl = do_QueryInterface(url);
+ msgUrl->SetMsgWindow(aMsgWindow);
+ if (aFileName) msgUrl->SetFileNameInternal(nsDependentCString(aFileName));
+ }
+ }
+
+ nsCOMPtr<nsIMsgI18NUrl> i18nurl(do_QueryInterface(msgUrl));
+ if (i18nurl) i18nurl->SetAutodetectCharset(aAutodetectCharset);
+
+ // instead of running the mailbox url like we used to, let's try to run the
+ // url in the docshell...
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ // if we were given a docShell, run the url in the docshell..otherwise just
+ // run it normally.
+ if (NS_SUCCEEDED(rv) && docShell && url) {
+ // 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.
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(url);
+ loadState->SetLoadFlags(mailboxAction == nsIMailboxUrl::ActionFetchPart
+ ? nsIWebNavigation::LOAD_FLAGS_IS_LINK
+ : nsIWebNavigation::LOAD_FLAGS_NONE);
+ if (mailboxAction == nsIMailboxUrl::ActionFetchPart)
+ loadState->SetLoadType(LOAD_LINK);
+ loadState->SetFirstParty(false);
+ loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
+ rv = docShell->LoadURI(loadState, false);
+ } else
+ rv = RunMailboxUrl(url, aDisplayConsumer);
+
+ if (aURL && mailboxurl) CallQueryInterface(mailboxurl, aURL);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxService::FetchMimePart(
+ nsIURI* aURI, const nsACString& 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);
+
+ return RunMailboxUrl(msgUrl, aDisplayConsumer);
+}
+
+NS_IMETHODIMP nsMailboxService::LoadMessage(const nsACString& aMessageURI,
+ nsISupports* aDisplayConsumer,
+ nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aUrlListener,
+ bool aOverideCharset) {
+ nsCOMPtr<nsIURI> aURL; // unused...
+ return FetchMessage(aMessageURI, aDisplayConsumer, aMsgWindow, aUrlListener,
+ nullptr, nsIMailboxUrl::ActionFetchMessage,
+ aOverideCharset, getter_AddRefs(aURL));
+}
+
+NS_IMETHODIMP
+nsMailboxService::StreamMessage(const nsACString& aMessageURI,
+ nsISupports* aConsumer,
+ nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aUrlListener,
+ bool /* aConvertData */,
+ const nsACString& aAdditionalHeader,
+ bool aLocalOnly, nsIURI** aURL) {
+ // The mailbox protocol object will look for "header=filter" or
+ // "header=attach" 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 mailbox protocol object.
+ nsAutoCString aURIString(aMessageURI);
+ if (!aAdditionalHeader.IsEmpty()) {
+ aURIString.FindChar('?') == -1 ? aURIString += "?" : aURIString += "&";
+ aURIString += "header=";
+ aURIString += aAdditionalHeader;
+ }
+
+ return FetchMessage(aURIString, aConsumer, aMsgWindow, aUrlListener, nullptr,
+ nsIMailboxUrl::ActionFetchMessage, false, aURL);
+}
+
+NS_IMETHODIMP nsMailboxService::StreamHeaders(const nsACString& aMessageURI,
+ nsIStreamListener* aConsumer,
+ nsIUrlListener* aUrlListener,
+ bool aLocalOnly, nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aConsumer);
+ nsAutoCString folderURI;
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv =
+ DecomposeMailboxURI(aMessageURI, getter_AddRefs(folder), &msgKey);
+ if (msgKey == nsMsgKey_None) return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = folder->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = db->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = folder->GetLocalMsgStream(msgHdr, getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return MsgStreamMsgHeaders(inputStream, aConsumer);
+}
+
+NS_IMETHODIMP nsMailboxService::IsMsgInMemCache(nsIURI* aUrl,
+ nsIMsgFolder* aFolder,
+ bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMailboxService::SaveMessageToDisk(const nsACString& aMessageURI,
+ nsIFile* aFile, bool aAddDummyEnvelope,
+ nsIUrlListener* aUrlListener, nsIURI** aURL,
+ bool canonicalLineEnding,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMailboxUrl> mailboxurl;
+
+ rv = PrepareMessageUrl(aMessageURI, aUrlListener,
+ nsIMailboxUrl::ActionSaveMessageToDisk,
+ getter_AddRefs(mailboxurl), aMsgWindow);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(mailboxurl);
+ if (msgUrl) {
+ msgUrl->SetMessageFile(aFile);
+ msgUrl->SetAddDummyEnvelope(aAddDummyEnvelope);
+ msgUrl->SetCanonicalLineEnding(canonicalLineEnding);
+ }
+
+ nsCOMPtr<nsIURI> url = do_QueryInterface(mailboxurl);
+ rv = RunMailboxUrl(url);
+ }
+
+ if (aURL && mailboxurl) CallQueryInterface(mailboxurl, aURL);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxService::GetUrlForUri(const nsACString& aMessageURI,
+ nsIMsgWindow* aMsgWindow,
+ nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aURL);
+ if (StringBeginsWith(aMessageURI, "file:"_ns) ||
+ PL_strstr(PromiseFlatCString(aMessageURI).get(),
+ "type=application/x-message-display") ||
+ StringBeginsWith(aMessageURI, "mailbox:"_ns))
+ return NS_NewURI(aURL, aMessageURI);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMailboxUrl> mailboxurl;
+ rv =
+ PrepareMessageUrl(aMessageURI, nullptr, nsIMailboxUrl::ActionFetchMessage,
+ getter_AddRefs(mailboxurl), aMsgWindow);
+ if (NS_SUCCEEDED(rv) && mailboxurl) rv = CallQueryInterface(mailboxurl, aURL);
+ return rv;
+}
+
+// Takes a mailbox url, this method creates a protocol instance and loads the
+// url into the protocol instance.
+nsresult nsMailboxService::RunMailboxUrl(nsIURI* aMailboxUrl,
+ nsISupports* aDisplayConsumer) {
+ // create a protocol instance to run the url..
+ RefPtr<nsMailboxProtocol> protocol = new nsMailboxProtocol(aMailboxUrl);
+ // It implements nsIChannel, and all channels require loadInfo.
+ protocol->SetLoadInfo(new mozilla::net::LoadInfo(
+ nsContentUtils::GetSystemPrincipal(), nullptr, nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER));
+ nsresult rv = protocol->Initialize(aMailboxUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return protocol->LoadUrl(aMailboxUrl, aDisplayConsumer);
+}
+
+// This function takes a message uri, converts it into a file path & msgKey
+// pair. It then turns that into a mailbox url object. It also registers a url
+// listener if appropriate. AND it can take in a mailbox action and set that
+// field on the returned url as well.
+nsresult nsMailboxService::PrepareMessageUrl(
+ const nsACString& aSrcMsgMailboxURI, nsIUrlListener* aUrlListener,
+ nsMailboxAction aMailboxAction, nsIMailboxUrl** aMailboxUrl,
+ nsIMsgWindow* msgWindow) {
+ nsresult rv =
+ CallCreateInstance("@mozilla.org/messenger/mailboxurl;1", aMailboxUrl);
+ if (NS_SUCCEEDED(rv) && aMailboxUrl && *aMailboxUrl) {
+ // okay now generate the url string
+ char* urlSpec;
+ nsAutoCString folderURI;
+ nsMsgKey msgKey;
+ nsCString folderPath;
+ const nsPromiseFlatCString& flat = PromiseFlatCString(aSrcMsgMailboxURI);
+ const char* part = PL_strstr(flat.get(), "part=");
+ const char* header = PL_strstr(flat.get(), "header=");
+ rv = nsParseLocalMessageURI(aSrcMsgMailboxURI, folderURI, &msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsLocalURI2Path(kMailboxRootURI, folderURI.get(), folderPath);
+
+ if (NS_SUCCEEDED(rv)) {
+ // set up the url spec and initialize the url with it.
+ nsAutoCString buf;
+ MsgEscapeURL(
+ folderPath,
+ nsINetUtil::ESCAPE_URL_DIRECTORY | nsINetUtil::ESCAPE_URL_FORCED,
+ buf);
+ if (part)
+ urlSpec =
+ PR_smprintf("mailbox://%s?number=%lu&%s", buf.get(), msgKey, part);
+ else if (header)
+ urlSpec = PR_smprintf("mailbox://%s?number=%lu&%s", buf.get(), msgKey,
+ header);
+ else
+ urlSpec = PR_smprintf("mailbox://%s?number=%lu", buf.get(), msgKey);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(*aMailboxUrl);
+ rv = url->SetSpecInternal(nsDependentCString(urlSpec));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PR_smprintf_free(urlSpec);
+
+ (*aMailboxUrl)->SetMailboxAction(aMailboxAction);
+
+ // set up the url listener
+ if (aUrlListener) rv = url->RegisterListener(aUrlListener);
+
+ url->SetMsgWindow(msgWindow);
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(url);
+ if (msgUrl) {
+ msgUrl->SetOriginalSpec(aSrcMsgMailboxURI);
+ msgUrl->SetUri(aSrcMsgMailboxURI);
+ }
+
+ } // if we got a url
+ } // if we got a url
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxService::GetScheme(nsACString& aScheme) {
+ aScheme = "mailbox";
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxService::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+nsresult nsMailboxService::NewURI(const nsACString& aSpec,
+ const char* aOriginCharset, nsIURI* aBaseURI,
+ nsIURI** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = 0;
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> aMsgUri =
+ do_CreateInstance("@mozilla.org/messenger/mailboxurl;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // SetSpecInternal must not fail, or else the URL won't have a base URL and
+ // we'll crash later.
+ if (aBaseURI) {
+ nsAutoCString newSpec;
+ rv = aBaseURI->Resolve(aSpec, newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aMsgUri->SetSpecInternal(newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ rv = aMsgUri->SetSpecInternal(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ aMsgUri.forget(_retval);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxService::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** _retval) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(_retval);
+ MOZ_ASSERT(aLoadInfo);
+ nsresult rv = NS_OK;
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (spec.Find("?uidl=") >= 0 || spec.Find("&uidl=") >= 0) {
+ nsCOMPtr<nsIProtocolHandler> handler =
+ do_GetService("@mozilla.org/network/protocol;1?name=pop", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIURI> pop3Uri;
+
+ rv = nsPop3URL::NewURI(spec, aURI, getter_AddRefs(pop3Uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return handler->NewChannel(pop3Uri, aLoadInfo, _retval);
+ }
+ }
+
+ RefPtr<nsMailboxProtocol> protocol = new nsMailboxProtocol(aURI);
+ if (!protocol) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = protocol->Initialize(aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = protocol->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add the attachment disposition. This forces docShell to open the
+ // attachment instead of displaying it. Content types we have special
+ // handlers for are white-listed. This white list also exists in
+ // nsImapService::NewChannel and nsNntpService::NewChannel, so if you're
+ // changing this, update those too.
+ if (spec.Find("part=") >= 0 && spec.Find("type=message/rfc822") < 0 &&
+ spec.Find("type=application/x-message-display") < 0 &&
+ spec.Find("type=application/pdf") < 0) {
+ rv = protocol->SetContentDisposition(nsIChannel::DISPOSITION_ATTACHMENT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ protocol.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxService::Search(nsIMsgSearchSession* aSearchSession,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgFolder* aMsgFolder,
+ const nsACString& aMessageUri) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsMailboxService::DecomposeMailboxURI(const nsACString& aMessageURI,
+ nsIMsgFolder** aFolder,
+ nsMsgKey* aMsgKey) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aMsgKey);
+
+ nsresult rv = NS_OK;
+ nsAutoCString folderURI;
+ rv = nsParseLocalMessageURI(aMessageURI, folderURI, aMsgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetOrCreateFolder(folderURI, aFolder);
+}
+
+NS_IMETHODIMP
+nsMailboxService::MessageURIToMsgHdr(const nsACString& uri,
+ nsIMsgDBHdr** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (StringBeginsWith(uri, "file:"_ns)) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = new nsMsgFileHdr(uri);
+ msgHdr.forget(_retval);
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey msgKey;
+
+ rv = DecomposeMailboxURI(uri, getter_AddRefs(folder), &msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = folder->GetMessageHeader(msgKey, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsMailboxService.h b/comm/mailnews/local/src/nsMailboxService.h
new file mode 100644
index 0000000000..1ab9be1b67
--- /dev/null
+++ b/comm/mailnews/local/src/nsMailboxService.h
@@ -0,0 +1,59 @@
+/* -*- 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 nsMailboxService_h___
+#define nsMailboxService_h___
+
+#include "nscore.h"
+#include "nsISupports.h"
+
+#include "nsIMailboxService.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgWindow.h"
+#include "nsIMailboxUrl.h"
+#include "nsIURI.h"
+#include "nsIUrlListener.h"
+#include "nsIProtocolHandler.h"
+
+class nsMailboxService : public nsIMailboxService,
+ public nsIMsgMessageService,
+ public nsIMsgMessageFetchPartService,
+ public nsIProtocolHandler {
+ public:
+ nsMailboxService();
+ static nsresult NewURI(const nsACString& aSpec, const char* aOriginCharset,
+ nsIURI* aBaseURI, nsIURI** _retval);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMAILBOXSERVICE
+ NS_DECL_NSIMSGMESSAGESERVICE
+ NS_DECL_NSIMSGMESSAGEFETCHPARTSERVICE
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ protected:
+ virtual ~nsMailboxService();
+
+ // helper functions used by the service
+ nsresult PrepareMessageUrl(const nsACString& aSrcMsgMailboxURI,
+ nsIUrlListener* aUrlListener,
+ nsMailboxAction aMailboxAction,
+ nsIMailboxUrl** aMailboxUrl,
+ nsIMsgWindow* msgWindow);
+
+ nsresult RunMailboxUrl(nsIURI* aMailboxUrl,
+ nsISupports* aDisplayConsumer = nullptr);
+
+ nsresult FetchMessage(
+ const nsACString& aMessageURI, nsISupports* aDisplayConsumer,
+ nsIMsgWindow* aMsgWindow, nsIUrlListener* aUrlListener,
+ const char* aFileName, /* only used by open attachment */
+ nsMailboxAction mailboxAction, bool aAutodetectCharset, nsIURI** aURL);
+
+ nsresult DecomposeMailboxURI(const nsACString& aMessageURI,
+ nsIMsgFolder** aFolder, nsMsgKey* aMsgKey);
+};
+
+#endif /* nsMailboxService_h___ */
diff --git a/comm/mailnews/local/src/nsMailboxUrl.cpp b/comm/mailnews/local/src/nsMailboxUrl.cpp
new file mode 100644
index 0000000000..841b6d8922
--- /dev/null
+++ b/comm/mailnews/local/src/nsMailboxUrl.cpp
@@ -0,0 +1,474 @@
+/* -*- 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 "nsIURI.h"
+#include "nsIMailboxUrl.h"
+#include "nsMailboxUrl.h"
+
+#include "nsString.h"
+#include "nsLocalUtils.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+
+#include "nsIMsgFolder.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsIMsgMailSession.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsIStandardURL.h"
+
+// this is totally lame and MUST be removed by M6
+// the real fix is to attach the URI to the URL as it runs through netlib
+// then grab it and use it on the other side
+#include "nsCOMPtr.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Components.h"
+
+// helper function for parsing the search field of a url
+char* extractAttributeValue(const char* searchString,
+ const char* attributeName);
+
+nsMailboxUrl::nsMailboxUrl() {
+ m_mailboxAction = nsIMailboxUrl::ActionParseMailbox;
+ m_filePath = nullptr;
+ m_messageID = nullptr;
+ m_messageKey = nsMsgKey_None;
+ m_messageSize = 0;
+ m_messageFile = nullptr;
+ m_addDummyEnvelope = false;
+ m_canonicalLineEnding = false;
+ m_curMsgIndex = 0;
+ mAutodetectCharset = false;
+}
+
+nsMailboxUrl::~nsMailboxUrl() { PR_Free(m_messageID); }
+
+NS_IMPL_ADDREF_INHERITED(nsMailboxUrl, nsMsgMailNewsUrl)
+NS_IMPL_RELEASE_INHERITED(nsMailboxUrl, nsMsgMailNewsUrl)
+
+NS_INTERFACE_MAP_BEGIN(nsMailboxUrl)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMailboxUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMailboxUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgMessageUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgI18NUrl)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgMailNewsUrl)
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIMailboxUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+nsresult nsMailboxUrl::SetMailboxParser(nsIStreamListener* aMailboxParser) {
+ if (aMailboxParser) m_mailboxParser = aMailboxParser;
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::GetMailboxParser(nsIStreamListener** aConsumer) {
+ NS_ENSURE_ARG_POINTER(aConsumer);
+
+ NS_IF_ADDREF(*aConsumer = m_mailboxParser);
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::SetMailboxCopyHandler(
+ nsIStreamListener* aMailboxCopyHandler) {
+ if (aMailboxCopyHandler) m_mailboxCopyHandler = aMailboxCopyHandler;
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::GetMailboxCopyHandler(
+ nsIStreamListener** aMailboxCopyHandler) {
+ NS_ENSURE_ARG_POINTER(aMailboxCopyHandler);
+
+ if (aMailboxCopyHandler) {
+ NS_IF_ADDREF(*aMailboxCopyHandler = m_mailboxCopyHandler);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::GetMessageKey(nsMsgKey* aMessageKey) {
+ *aMessageKey = m_messageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetMessageSize(uint32_t* aMessageSize) {
+ if (aMessageSize) {
+ *aMessageSize = m_messageSize;
+ return NS_OK;
+ } else
+ return NS_ERROR_NULL_POINTER;
+}
+
+nsresult nsMailboxUrl::SetMessageSize(uint32_t aMessageSize) {
+ m_messageSize = aMessageSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetNormalizedSpec(nsACString& aPrincipalSpec) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsURL;
+ QueryInterface(NS_GET_IID(nsIMsgMailNewsUrl), getter_AddRefs(mailnewsURL));
+
+ nsAutoCString spec;
+ mailnewsURL->GetSpecIgnoringRef(spec);
+
+ // mailbox: URLs contain a lot of query parts. We want need a normalized form:
+ // mailbox:///path/to/folder?number=nn.
+ // We also need to translate the second form
+ // mailbox://user@domain@server/folder?number=nn.
+
+ char* messageKey = extractAttributeValue(spec.get(), "number=");
+
+ // Strip any query part beginning with ? or /;
+ MsgRemoveQueryPart(spec);
+
+ // Check for format lacking absolute path.
+ if (spec.Find("///") == kNotFound) {
+ nsCString folderPath;
+ nsresult rv = nsLocalURI2Path(kMailboxRootURI, spec.get(), folderPath);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString buf;
+ MsgEscapeURL(
+ folderPath,
+ nsINetUtil::ESCAPE_URL_DIRECTORY | nsINetUtil::ESCAPE_URL_FORCED,
+ buf);
+ spec = "mailbox://"_ns + buf;
+ }
+ }
+
+ if (messageKey) {
+ spec += "?number="_ns;
+ spec.Append(messageKey);
+ PR_Free(messageKey);
+ }
+
+ aPrincipalSpec.Assign(spec);
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::CreateURL(const nsACString& aSpec, nsIURL** aURL) {
+ nsresult rv;
+ nsCOMPtr<nsIURL> url;
+ // Check whether the URL is of the form
+ // mailbox://user@domain@server/folder?number=nn and contains a hostname.
+ // Check for format lacking absolute path.
+ if (PromiseFlatCString(aSpec).Find("///") == kNotFound) {
+ rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(aSpec)
+ .Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // The URL is more like a file URL without a hostname.
+ rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .Apply(&nsIStandardURLMutator::Init,
+ nsIStandardURL::URLTYPE_NO_AUTHORITY, -1,
+ PromiseFlatCString(aSpec), nullptr, nullptr, nullptr)
+ .Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ url.forget(aURL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::SetUri(const nsACString& aURI) {
+ mURI = aURI;
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::Clone(nsIURI** _retval) {
+ nsresult rv = nsMsgMailNewsUrl::Clone(_retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // also clone the mURI member, because GetUri below won't work if
+ // mURI isn't set due to nsIFile fun.
+ nsCOMPtr<nsIMsgMessageUrl> clonedUrl = do_QueryInterface(*_retval);
+ if (clonedUrl) clonedUrl->SetUri(mURI);
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetUri(nsACString& aURI) {
+ // 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())
+ aURI = mURI;
+ else {
+ if (m_filePath) {
+ nsAutoCString baseUri;
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we blow off errors here so that we can open attachments
+ // in .eml files.
+ (void)accountManager->FolderUriForPath(m_filePath, baseUri);
+ if (baseUri.IsEmpty()) {
+ rv = m_baseURL->GetSpec(baseUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCString baseMessageURI;
+ nsCreateLocalBaseMessageURI(baseUri, baseMessageURI);
+ nsBuildLocalMessageURI(baseMessageURI, m_messageKey, aURI);
+ } else
+ aURI = "";
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::GetMsgHdrForKey(nsMsgKey msgKey, nsIMsgDBHdr** aMsgHdr) {
+ nsresult rv = NS_OK;
+ if (aMsgHdr && m_filePath) {
+ nsCOMPtr<nsIMsgDatabase> mailDBFactory;
+ nsCOMPtr<nsIMsgDatabase> mailDB;
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+
+ if (msgDBService) {
+ rv = msgDBService->OpenMailDBFromFile(m_filePath, nullptr, false, false,
+ getter_AddRefs(mailDB));
+ }
+ if (NS_SUCCEEDED(rv) && mailDB) {
+ // Did we get a db back?
+ rv = mailDB->GetMsgHdrForKey(msgKey, aMsgHdr);
+ } else {
+ rv = NS_OK;
+ }
+ } else {
+ rv = NS_ERROR_NULL_POINTER;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetMessageHeader(nsIMsgDBHdr** aMsgHdr) {
+ return GetMsgHdrForKey(m_messageKey, aMsgHdr);
+}
+
+NS_IMPL_GETSET(nsMailboxUrl, AddDummyEnvelope, bool, m_addDummyEnvelope)
+NS_IMPL_GETSET(nsMailboxUrl, CanonicalLineEnding, bool, m_canonicalLineEnding)
+
+NS_IMETHODIMP
+nsMailboxUrl::GetOriginalSpec(nsACString& aSpec) {
+ if (m_originalSpec.IsEmpty()) return NS_ERROR_NULL_POINTER;
+ aSpec = m_originalSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailboxUrl::SetOriginalSpec(const nsACString& aSpec) {
+ m_originalSpec = aSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::SetMessageFile(nsIFile* aFile) {
+ m_messageFile = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetMessageFile(nsIFile** aFile) {
+ // why don't we return an error for null aFile?
+ if (aFile) NS_IF_ADDREF(*aFile = m_messageFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::IsUrlType(uint32_t type, bool* isType) {
+ NS_ENSURE_ARG(isType);
+
+ switch (type) {
+ case nsIMsgMailNewsUrl::eCopy:
+ *isType = (m_mailboxAction == nsIMailboxUrl::ActionCopyMessage);
+ break;
+ case nsIMsgMailNewsUrl::eMove:
+ *isType = (m_mailboxAction == nsIMailboxUrl::ActionMoveMessage);
+ break;
+ case nsIMsgMailNewsUrl::eDisplay:
+ *isType = (m_mailboxAction == nsIMailboxUrl::ActionFetchMessage ||
+ m_mailboxAction == nsIMailboxUrl::ActionFetchPart);
+ break;
+ default:
+ *isType = false;
+ };
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// End nsIMailboxUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+// possible search part phrases include: MessageID=id&number=MessageKey
+
+nsresult nsMailboxUrl::ParseSearchPart() {
+ nsAutoCString searchPart;
+ nsresult rv = GetQuery(searchPart);
+ // add code to this function to decompose everything past the '?'.....
+ if (NS_SUCCEEDED(rv) && !searchPart.IsEmpty()) {
+ // the action for this mailbox must be a display message...
+ char* msgPart = extractAttributeValue(searchPart.get(), "part=");
+ if (msgPart) // if we have a part in the url then we must be fetching just
+ // the part.
+ m_mailboxAction = nsIMailboxUrl::ActionFetchPart;
+ else
+ m_mailboxAction = nsIMailboxUrl::ActionFetchMessage;
+
+ char* messageKey = extractAttributeValue(searchPart.get(), "number=");
+ m_messageID = extractAttributeValue(searchPart.get(), "messageid=");
+ if (messageKey)
+ m_messageKey =
+ (nsMsgKey)ParseUint64Str(messageKey); // convert to a uint32_t...
+
+ PR_Free(msgPart);
+ PR_Free(messageKey);
+ } else
+ m_mailboxAction = nsIMailboxUrl::ActionParseMailbox;
+
+ return rv;
+}
+
+// warning: don't assume when parsing the url that the protocol part is
+// "news"...
+nsresult nsMailboxUrl::ParseUrl() {
+ GetFilePath(m_file);
+
+ ParseSearchPart();
+ // ### fix me.
+ // this hack is to avoid asserting on every local message loaded because the
+ // security manager is creating an empty "mailbox://" uri for every message.
+ if (m_file.Length() < 2)
+ m_filePath = nullptr;
+ else {
+ nsCString fileUri("file://");
+ fileUri.Append(m_file);
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService = mozilla::components::IO::Service();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIURI> uri;
+ rv = ioService->NewURI(fileUri, nullptr, nullptr, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> fileURLFile;
+ fileURL->GetFile(getter_AddRefs(fileURLFile));
+ NS_ENSURE_TRUE(fileURLFile, NS_ERROR_NULL_POINTER);
+ m_filePath = fileURLFile;
+ }
+
+ GetPathQueryRef(m_file);
+ return NS_OK;
+}
+
+nsresult nsMailboxUrl::SetSpecInternal(const nsACString& aSpec) {
+ nsresult rv = nsMsgMailNewsUrl::SetSpecInternal(aSpec);
+ if (NS_SUCCEEDED(rv)) {
+ // Do not try to parse URLs of the form
+ // mailbox://user@domain@server/folder?number=nn since this will fail.
+ // Check for format lacking absolute path.
+ if (PromiseFlatCString(aSpec).Find("///") != kNotFound) {
+ rv = ParseUrl();
+ } else {
+ // We still need to parse the search part to populate
+ // m_messageKey etc.
+ ParseSearchPart();
+ }
+ }
+ return rv;
+}
+
+nsresult nsMailboxUrl::SetQuery(const nsACString& aQuery) {
+ nsresult rv = nsMsgMailNewsUrl::SetQuery(aQuery);
+ if (NS_SUCCEEDED(rv)) rv = ParseUrl();
+ return rv;
+}
+
+// takes a string like ?messageID=fooo&number=MsgKey and returns a new string
+// containing just the attribute value. i.e you could pass in this string with
+// an attribute name of messageID and I'll return fooo. Use PR_Free to delete
+// this string...
+
+// Assumption: attribute pairs in the string are separated by '&'.
+char* extractAttributeValue(const char* searchString,
+ const char* attributeName) {
+ char* attributeValue = nullptr;
+
+ if (searchString && attributeName) {
+ // search the string for attributeName
+ uint32_t attributeNameSize = PL_strlen(attributeName);
+ char* startOfAttribute = PL_strcasestr(searchString, attributeName);
+ if (startOfAttribute) {
+ startOfAttribute += attributeNameSize; // skip over the attributeName
+ if (startOfAttribute) // is there something after the attribute name
+ {
+ char* endOfAttribute = PL_strchr(startOfAttribute, '&');
+ nsDependentCString attributeValueStr;
+ if (startOfAttribute &&
+ endOfAttribute) // is there text after attribute value
+ attributeValueStr.Assign(startOfAttribute,
+ endOfAttribute - startOfAttribute);
+ else // there is nothing left so eat up rest of line.
+ attributeValueStr.Assign(startOfAttribute);
+
+ // now unescape the string...
+ nsCString unescapedValue;
+ MsgUnescapeString(attributeValueStr, 0, unescapedValue);
+ attributeValue = PL_strdup(unescapedValue.get());
+ } // if we have a attribute value
+
+ } // if we have a attribute name
+ } // if we got non-null search string and attribute name values
+
+ return attributeValue;
+}
+
+// nsIMsgI18NUrl support
+
+nsresult nsMailboxUrl::GetFolder(nsIMsgFolder** msgFolder) {
+ // if we have a RDF URI, then try to get the folder for that URI and then ask
+ // the folder for it's charset....
+ nsCString uri;
+ GetUri(uri);
+ NS_ENSURE_TRUE(!uri.IsEmpty(), NS_ERROR_FAILURE);
+ nsCOMPtr<nsIMsgDBHdr> msg;
+ GetMsgDBHdrFromURI(uri, getter_AddRefs(msg));
+ if (!msg) return NS_ERROR_FAILURE;
+ return msg->GetFolder(msgFolder);
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetAutodetectCharset(bool* aAutodetectCharset) {
+ *aAutodetectCharset = mAutodetectCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::SetAutodetectCharset(bool aAutodetectCharset) {
+ mAutodetectCharset = aAutodetectCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::SetMoveCopyMsgKeys(
+ const nsTArray<nsMsgKey>& keysToFlag) {
+ m_keys = keysToFlag.Clone();
+ if (!m_keys.IsEmpty() && m_messageKey == nsMsgKey_None)
+ m_messageKey = m_keys[0];
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetMoveCopyMsgHdrForIndex(uint32_t msgIndex,
+ nsIMsgDBHdr** msgHdr) {
+ NS_ENSURE_ARG(msgHdr);
+ if (msgIndex < m_keys.Length()) {
+ nsMsgKey nextKey = m_keys[msgIndex];
+ return GetMsgHdrForKey(nextKey, msgHdr);
+ }
+ return NS_MSG_MESSAGE_NOT_FOUND;
+}
+
+NS_IMETHODIMP nsMailboxUrl::GetNumMoveCopyMsgs(uint32_t* numMsgs) {
+ NS_ENSURE_ARG(numMsgs);
+ *numMsgs = m_keys.Length();
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsMailboxUrl.h b/comm/mailnews/local/src/nsMailboxUrl.h
new file mode 100644
index 0000000000..29b932e38c
--- /dev/null
+++ b/comm/mailnews/local/src/nsMailboxUrl.h
@@ -0,0 +1,106 @@
+/* -*- 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 nsMailboxUrl_h__
+#define nsMailboxUrl_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIMailboxUrl.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+
+class nsMailboxUrl : public nsIMailboxUrl,
+ public nsMsgMailNewsUrl,
+ public nsIMsgMessageUrl,
+ public nsIMsgI18NUrl {
+ public:
+ // nsIMsgMailNewsUrl override
+ nsresult SetSpecInternal(const nsACString& aSpec) override;
+ nsresult SetQuery(const nsACString& aQuery) override;
+ nsresult CreateURL(const nsACString& aSpec, nsIURL** aURL) override;
+
+ // from nsIMailboxUrl:
+ NS_IMETHOD SetMailboxParser(nsIStreamListener* aConsumer) override;
+ NS_IMETHOD GetMailboxParser(nsIStreamListener** aConsumer) override;
+ NS_IMETHOD SetMailboxCopyHandler(nsIStreamListener* aConsumer) override;
+ NS_IMETHOD GetMailboxCopyHandler(nsIStreamListener** aConsumer) override;
+
+ NS_IMETHOD GetMessageKey(nsMsgKey* aMessageKey) override;
+ NS_IMETHOD GetMessageSize(uint32_t* aMessageSize) override;
+ NS_IMETHOD SetMessageSize(uint32_t aMessageSize) override;
+ NS_IMETHOD GetMailboxAction(nsMailboxAction* result) override {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_mailboxAction;
+ return NS_OK;
+ }
+ NS_IMETHOD SetMailboxAction(nsMailboxAction aAction) override {
+ m_mailboxAction = aAction;
+ return NS_OK;
+ }
+ NS_IMETHOD IsUrlType(uint32_t type, bool* isType) override;
+ NS_IMETHOD SetMoveCopyMsgKeys(const nsTArray<nsMsgKey>& keysToFlag) override;
+ NS_IMETHOD GetMoveCopyMsgHdrForIndex(uint32_t msgIndex,
+ nsIMsgDBHdr** msgHdr) override;
+ NS_IMETHOD GetNumMoveCopyMsgs(uint32_t* numMsgs) override;
+ NS_IMETHOD GetCurMoveCopyMsgIndex(uint32_t* result) override {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_curMsgIndex;
+ return NS_OK;
+ }
+ NS_IMETHOD SetCurMoveCopyMsgIndex(uint32_t aIndex) override {
+ m_curMsgIndex = aIndex;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetFolder(nsIMsgFolder** msgFolder) override;
+
+ // nsMsgMailNewsUrl override
+ nsresult Clone(nsIURI** _retval) override;
+
+ // nsMailboxUrl
+ nsMailboxUrl();
+ NS_DECL_NSIMSGMESSAGEURL
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMSGI18NURL
+
+ protected:
+ virtual ~nsMailboxUrl();
+ // protocol specific code to parse a url...
+ virtual nsresult ParseUrl();
+ nsresult GetMsgHdrForKey(nsMsgKey msgKey, nsIMsgDBHdr** aMsgHdr);
+
+ // mailboxurl specific state
+ nsCOMPtr<nsIStreamListener> m_mailboxParser;
+ nsCOMPtr<nsIStreamListener> m_mailboxCopyHandler;
+
+ nsMailboxAction m_mailboxAction; // the action this url represents...parse
+ // mailbox, display messages, etc.
+ nsCOMPtr<nsIFile> m_filePath;
+ char* m_messageID;
+ uint32_t m_messageSize;
+ nsMsgKey m_messageKey;
+ nsCString m_file;
+
+ // used by save message to disk
+ nsCOMPtr<nsIFile> m_messageFile;
+ bool m_addDummyEnvelope;
+ bool m_canonicalLineEnding;
+ nsresult ParseSearchPart();
+
+ // for multiple msg move/copy
+ nsTArray<nsMsgKey> m_keys;
+ int32_t m_curMsgIndex;
+
+ // truncated message support
+ nsCString m_originalSpec;
+ nsCString mURI; // the RDF URI associated with this url.
+ bool mAutodetectCharset; // used by nsIMsgI18NUrl...
+};
+
+#endif // nsMailboxUrl_h__
diff --git a/comm/mailnews/local/src/nsMsgBrkMBoxStore.cpp b/comm/mailnews/local/src/nsMsgBrkMBoxStore.cpp
new file mode 100644
index 0000000000..1f48b7d278
--- /dev/null
+++ b/comm/mailnews/local/src/nsMsgBrkMBoxStore.cpp
@@ -0,0 +1,1033 @@
+/* -*- 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/. */
+
+/**
+ Class for handling Berkeley Mailbox stores.
+*/
+
+#include "prlog.h"
+#include "msgCore.h"
+#include "nsMsgBrkMBoxStore.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIInputStream.h"
+#include "nsCOMArray.h"
+#include "nsIFile.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsIMsgHdr.h"
+#include "nsNetUtil.h"
+#include "nsIMsgDatabase.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsMsgUtils.h"
+#include "nsIDBFolderInfo.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMailHeaders.h"
+#include "nsParseMailbox.h"
+#include "nsIMailboxService.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsPrintfCString.h"
+#include "nsQuarantinedOutputStream.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SlicedInputStream.h"
+#include "prprf.h"
+#include <cstdlib> // for std::abs(int/long)
+#include <cmath> // for std::abs(float/double)
+
+nsMsgBrkMBoxStore::nsMsgBrkMBoxStore() {}
+
+nsMsgBrkMBoxStore::~nsMsgBrkMBoxStore() {}
+
+NS_IMPL_ISUPPORTS(nsMsgBrkMBoxStore, nsIMsgPluggableStore)
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::DiscoverSubFolders(nsIMsgFolder* aParentFolder,
+ bool aDeep) {
+ NS_ENSURE_ARG_POINTER(aParentFolder);
+
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aParentFolder->GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ path->Exists(&exists);
+ if (!exists) {
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return AddSubFolders(aParentFolder, path, aDeep);
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::CreateFolder(nsIMsgFolder* aParent,
+ const nsAString& aFolderName,
+ nsIMsgFolder** aResult) {
+ NS_ENSURE_ARG_POINTER(aParent);
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (aFolderName.IsEmpty()) return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ nsCOMPtr<nsIFile> path;
+ nsCOMPtr<nsIMsgFolder> child;
+ nsresult rv = aParent->GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv)) return rv;
+ // Get a directory based on our current path.
+ rv = CreateDirectoryForFolder(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // Now we have a valid directory or we have returned.
+ // Make sure the new folder name is valid
+ nsAutoString safeFolderName(aFolderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+
+ path->Append(safeFolderName);
+ bool exists;
+ path->Exists(&exists);
+ if (exists) // check this because localized names are different from disk
+ // names
+ return NS_MSG_FOLDER_EXISTS;
+
+ rv = path->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // GetFlags and SetFlags in AddSubfolder will fail because we have no db at
+ // this point but mFlags is set.
+ rv = aParent->AddSubfolder(safeFolderName, getter_AddRefs(child));
+ if (!child || NS_FAILED(rv)) {
+ path->Remove(false);
+ return rv;
+ }
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ if (msgDBService) {
+ nsCOMPtr<nsIMsgDatabase> unusedDB;
+ rv = msgDBService->OpenFolderDB(child, true, getter_AddRefs(unusedDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ rv = msgDBService->CreateNewDB(child, getter_AddRefs(unusedDB));
+
+ if ((NS_SUCCEEDED(rv) || rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) &&
+ unusedDB) {
+ // need to set the folder name
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (NS_SUCCEEDED(rv)) folderInfo->SetMailboxName(safeFolderName);
+
+ unusedDB->SetSummaryValid(true);
+ unusedDB->Close(true);
+ aParent->UpdateSummaryTotals(true);
+ } else {
+ path->Remove(false);
+ rv = NS_MSG_CANT_CREATE_FOLDER;
+ }
+ }
+ child.forget(aResult);
+ return rv;
+}
+
+// Get the current attributes of the mbox file, corrected for caching
+void nsMsgBrkMBoxStore::GetMailboxModProperties(nsIMsgFolder* aFolder,
+ int64_t* aSize,
+ uint32_t* aDate) {
+ // We'll simply return 0 on errors.
+ *aDate = 0;
+ *aSize = 0;
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = pathFile->GetFileSize(aSize);
+ if (NS_FAILED(rv)) return; // expected result for virtual folders
+
+ PRTime lastModTime;
+ rv = pathFile->GetLastModifiedTime(&lastModTime);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ *aDate = (uint32_t)(lastModTime / PR_MSEC_PER_SEC);
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::HasSpaceAvailable(nsIMsgFolder* aFolder,
+ int64_t aSpaceRequested,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool allow4GBfolders =
+ mozilla::Preferences::GetBool("mailnews.allowMboxOver4GB", true);
+
+ if (!allow4GBfolders) {
+ // Allow the mbox to only reach 0xFFC00000 = 4 GiB - 4 MiB.
+ int64_t fileSize;
+ rv = pathFile->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = ((fileSize + aSpaceRequested) < 0xFFC00000LL);
+ if (!*aResult) return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ *aResult = DiskSpaceAvailableInStore(pathFile, aSpaceRequested);
+ if (!*aResult) return NS_ERROR_FILE_NO_DEVICE_SPACE;
+
+ return NS_OK;
+}
+
+static bool gGotGlobalPrefs = false;
+static int32_t gTimeStampLeeway = 60;
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::IsSummaryFileValid(nsIMsgFolder* aFolder,
+ nsIMsgDatabase* aDB,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aDB);
+ NS_ENSURE_ARG_POINTER(aResult);
+ // We only check local folders for db validity.
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder(do_QueryInterface(aFolder));
+ if (!localFolder) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = aDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t folderSize;
+ uint32_t folderDate;
+ int32_t numUnreadMessages;
+
+ *aResult = false;
+
+ folderInfo->GetNumUnreadMessages(&numUnreadMessages);
+ folderInfo->GetFolderSize(&folderSize);
+ folderInfo->GetFolderDate(&folderDate);
+
+ int64_t fileSize = 0;
+ uint32_t actualFolderTimeStamp = 0;
+ GetMailboxModProperties(aFolder, &fileSize, &actualFolderTimeStamp);
+
+ if (folderSize == fileSize && numUnreadMessages >= 0) {
+ if (!folderSize) {
+ *aResult = true;
+ return NS_OK;
+ }
+ if (!gGotGlobalPrefs) {
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (pPrefBranch) {
+ rv = pPrefBranch->GetIntPref("mail.db_timestamp_leeway",
+ &gTimeStampLeeway);
+ gGotGlobalPrefs = true;
+ }
+ }
+ // if those values are ok, check time stamp
+ if (gTimeStampLeeway == 0)
+ *aResult = folderDate == actualFolderTimeStamp;
+ else
+ *aResult = std::abs((int32_t)(actualFolderTimeStamp - folderDate)) <=
+ gTimeStampLeeway;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::SetSummaryFileValid(nsIMsgFolder* aFolder,
+ nsIMsgDatabase* aDB,
+ bool aValid) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aDB);
+ // We only need to do this for local folders.
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder(do_QueryInterface(aFolder));
+ if (!localFolder) return NS_OK;
+
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = aDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists;
+ pathFile->Exists(&exists);
+ if (!exists) return NS_MSG_ERROR_FOLDER_MISSING;
+
+ if (aValid) {
+ uint32_t actualFolderTimeStamp;
+ int64_t fileSize;
+ GetMailboxModProperties(aFolder, &fileSize, &actualFolderTimeStamp);
+ folderInfo->SetFolderSize(fileSize);
+ folderInfo->SetFolderDate(actualFolderTimeStamp);
+ } else {
+ folderInfo->SetVersion(0); // that ought to do the trick.
+ }
+ aDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::DeleteFolder(nsIMsgFolder* aFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ bool exists;
+
+ // Delete mbox file.
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ exists = false;
+ pathFile->Exists(&exists);
+ if (exists) {
+ rv = pathFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Delete any subfolders (.sbd-suffixed directories).
+ AddDirectorySeparator(pathFile);
+ exists = false;
+ pathFile->Exists(&exists);
+ if (exists) {
+ rv = pathFile->Remove(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::RenameFolder(nsIMsgFolder* aFolder,
+ const nsAString& aNewName,
+ nsIMsgFolder** aNewFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aNewFolder);
+
+ uint32_t numChildren;
+ aFolder->GetNumSubFolders(&numChildren);
+ nsString existingName;
+ aFolder->GetName(existingName);
+
+ nsCOMPtr<nsIFile> oldPathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(oldPathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ rv = aFolder->GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder) return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsIFile> oldSummaryFile;
+ rv = aFolder->GetSummaryFile(getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dirFile;
+ oldPathFile->Clone(getter_AddRefs(dirFile));
+
+ if (numChildren > 0) {
+ rv = CreateDirectoryForFolder(dirFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoString safeName(aNewName);
+ NS_MsgHashIfNecessary(safeName);
+
+ nsAutoCString oldLeafName;
+ oldPathFile->GetNativeLeafName(oldLeafName);
+
+ nsCOMPtr<nsIFile> parentPathFile;
+ parentFolder->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory = false;
+ parentPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory) {
+ nsAutoString leafName;
+ parentPathFile->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ rv = parentPathFile->SetLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aFolder->ForceDBClosed();
+ // save off dir name before appending .msf
+ rv = oldPathFile->MoveTo(nullptr, safeName);
+ if (NS_FAILED(rv)) return rv;
+
+ nsString dbName(safeName);
+ dbName.AppendLiteral(SUMMARY_SUFFIX);
+ oldSummaryFile->MoveTo(nullptr, dbName);
+
+ if (numChildren > 0) {
+ // rename "*.sbd" directory
+ nsAutoString newNameDirStr(safeName);
+ newNameDirStr.AppendLiteral(FOLDER_SUFFIX);
+ dirFile->MoveTo(nullptr, newNameDirStr);
+ }
+
+ return parentFolder->AddSubfolder(safeName, aNewFolder);
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::CopyFolder(
+ nsIMsgFolder* aSrcFolder, nsIMsgFolder* aDstFolder, bool aIsMoveFolder,
+ nsIMsgWindow* aMsgWindow, nsIMsgCopyServiceListener* aListener,
+ const nsAString& aNewName) {
+ NS_ENSURE_ARG_POINTER(aSrcFolder);
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+
+ nsAutoString folderName;
+ if (aNewName.IsEmpty())
+ aSrcFolder->GetName(folderName);
+ else
+ folderName.Assign(aNewName);
+
+ nsAutoString safeFolderName(folderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+ nsCOMPtr<nsIMsgLocalMailFolder> localSrcFolder(do_QueryInterface(aSrcFolder));
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ if (localSrcFolder)
+ localSrcFolder->GetDatabaseWOReparse(getter_AddRefs(srcDB));
+ bool summaryValid = !!srcDB;
+ srcDB = nullptr;
+ aSrcFolder->ForceDBClosed();
+
+ nsCOMPtr<nsIFile> oldPath;
+ nsresult rv = aSrcFolder->GetFilePath(getter_AddRefs(oldPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> summaryFile;
+ GetSummaryFileLocation(oldPath, getter_AddRefs(summaryFile));
+
+ nsCOMPtr<nsIFile> newPath;
+ rv = aDstFolder->GetFilePath(getter_AddRefs(newPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool newPathIsDirectory = false;
+ newPath->IsDirectory(&newPathIsDirectory);
+ if (!newPathIsDirectory) {
+ AddDirectorySeparator(newPath);
+ rv = newPath->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv == NS_ERROR_FILE_ALREADY_EXISTS) rv = NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIFile> origPath;
+ oldPath->Clone(getter_AddRefs(origPath));
+
+ // copying necessary for aborting.... if failure return
+ rv = oldPath->CopyTo(newPath, safeFolderName);
+ NS_ENSURE_SUCCESS(rv, rv); // Will fail if a file by that name exists
+
+ // Copy to dir can fail if filespec does not exist. If copy fails, we test
+ // if the filespec exist or not, if it does not that's ok, we continue
+ // without copying it. If it fails and filespec exist and is not zero sized
+ // there is real problem
+ // Copy the file to the new dir
+ nsAutoString dbName(safeFolderName);
+ dbName.AppendLiteral(SUMMARY_SUFFIX);
+ rv = summaryFile->CopyTo(newPath, dbName);
+ if (NS_FAILED(rv)) // Test if the copy is successful
+ {
+ // Test if the filespec has data
+ bool exists;
+ int64_t fileSize;
+ summaryFile->Exists(&exists);
+ summaryFile->GetFileSize(&fileSize);
+ if (exists && fileSize > 0)
+ NS_ENSURE_SUCCESS(rv, rv); // Yes, it should have worked !
+ // else case is filespec is zero sized, no need to copy it,
+ // not an error
+ }
+
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ rv = aDstFolder->AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // linux and mac are not good about maintaining the file stamp when copying
+ // folders around. So if the source folder db is good, set the dest db as
+ // good too.
+ nsCOMPtr<nsIMsgDatabase> destDB;
+ if (summaryValid) {
+ nsAutoString folderLeafName;
+ origPath->GetLeafName(folderLeafName);
+ newPath->Append(folderLeafName);
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenMailDBFromFile(newPath, newMsgFolder, false, true,
+ getter_AddRefs(destDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE && destDB)
+ destDB->SetSummaryValid(true);
+ }
+ newMsgFolder->SetPrettyName(folderName);
+ uint32_t flags;
+ aSrcFolder->GetFlags(&flags);
+ newMsgFolder->SetFlags(flags);
+ bool changed = false;
+ rv = aSrcFolder->MatchOrChangeFilterDestination(newMsgFolder, true, &changed);
+ if (changed) aSrcFolder->AlertFilterChanged(aMsgWindow);
+
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = aSrcFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy subfolders to the new location
+ nsresult copyStatus = NS_OK;
+ nsCOMPtr<nsIMsgLocalMailFolder> localNewFolder(
+ do_QueryInterface(newMsgFolder, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ for (nsIMsgFolder* folder : subFolders) {
+ copyStatus =
+ localNewFolder->CopyFolderLocal(folder, false, aMsgWindow, aListener);
+ // Test if the call succeeded, if not we have to stop recursive call
+ if (NS_FAILED(copyStatus)) {
+ // Copy failed we have to notify caller to handle the error and stop
+ // moving the folders. In case this happens to the topmost level of
+ // recursive call, then we just need to break from the while loop and
+ // go to error handling code.
+ if (!aIsMoveFolder) return copyStatus;
+ break;
+ }
+ }
+ }
+
+ if (aIsMoveFolder && NS_SUCCEEDED(copyStatus)) {
+ if (localNewFolder) {
+ nsCOMPtr<nsISupports> srcSupport(do_QueryInterface(aSrcFolder));
+ localNewFolder->OnCopyCompleted(srcSupport, true);
+ }
+
+ // Notify the "folder" that was dragged and dropped has been created. No
+ // need to do this for its subfolders. isMoveFolder will be true for folder.
+ aDstFolder->NotifyFolderAdded(newMsgFolder);
+
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ aSrcFolder->GetParent(getter_AddRefs(msgParent));
+ aSrcFolder->SetParent(nullptr);
+ if (msgParent) {
+ // The files have already been moved, so delete storage false
+ msgParent->PropagateDelete(aSrcFolder, false);
+ oldPath->Remove(false); // berkeley mailbox
+ aSrcFolder->DeleteStorage();
+
+ nsCOMPtr<nsIFile> parentPath;
+ rv = msgParent->GetFilePath(getter_AddRefs(parentPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddDirectorySeparator(parentPath);
+ nsCOMPtr<nsIDirectoryEnumerator> children;
+ parentPath->GetDirectoryEntries(getter_AddRefs(children));
+ bool more;
+ // checks if the directory is empty or not
+ if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more)
+ parentPath->Remove(true);
+ }
+ } else {
+ // This is the case where the copy of a subfolder failed.
+ // We have to delete the newDirectory tree to make a "rollback".
+ // Someone should add a popup to warn the user that the move was not
+ // possible.
+ if (aIsMoveFolder && NS_FAILED(copyStatus)) {
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ newMsgFolder->ForceDBClosed();
+ newMsgFolder->GetParent(getter_AddRefs(msgParent));
+ newMsgFolder->SetParent(nullptr);
+ if (msgParent) {
+ msgParent->PropagateDelete(newMsgFolder, false);
+ newMsgFolder->DeleteStorage();
+ AddDirectorySeparator(newPath);
+ newPath->Remove(true); // berkeley mailbox
+ }
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::GetNewMsgOutputStream(nsIMsgFolder* aFolder,
+ nsIMsgDBHdr** aNewMsgHdr,
+ nsIOutputStream** aResult) {
+ bool quarantining = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch) {
+ prefBranch->GetBoolPref("mailnews.downloadToTempFile", &quarantining);
+ }
+
+ if (!quarantining) {
+ // Caller will write directly to mbox.
+ return InternalGetNewMsgOutputStream(aFolder, aNewMsgHdr, aResult);
+ }
+
+ // Quarantining is on, so we want to write the new message to a temp file
+ // and let the virus checker have at it before we append it to the mbox.
+ // We'll wrap the mboxStream with an nsQuarantinedOutputStream and return
+ // that.
+ nsCOMPtr<nsIOutputStream> mboxStream;
+ nsresult rv = InternalGetNewMsgOutputStream(aFolder, aNewMsgHdr,
+ getter_AddRefs(mboxStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsQuarantinedOutputStream> qStream =
+ new nsQuarantinedOutputStream(mboxStream);
+ qStream.forget(aResult);
+ return NS_OK;
+}
+
+nsresult nsMsgBrkMBoxStore::InternalGetNewMsgOutputStream(
+ nsIMsgFolder* aFolder, nsIMsgDBHdr** aNewMsgHdr,
+ nsIOutputStream** aResult) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aNewMsgHdr);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+#ifdef _DEBUG
+ NS_ASSERTION(m_streamOutstandingFolder != aFolder, "didn't finish prev msg");
+ m_streamOutstandingFolder = aFolder;
+#endif
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> mboxFile;
+ rv = aFolder->GetFilePath(getter_AddRefs(mboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDatabase> db;
+ aFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (!db && !*aNewMsgHdr) NS_WARNING("no db, and no message header");
+ bool exists = false;
+ mboxFile->Exists(&exists);
+ if (!exists) {
+ rv = mboxFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString URI;
+ aFolder->GetURI(URI);
+ nsCOMPtr<nsISeekableStream> seekable;
+ if (m_outputStreams.Get(URI, aResult)) {
+ seekable = do_QueryInterface(*aResult, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_END, 0);
+ if (NS_FAILED(rv)) {
+ m_outputStreams.Remove(URI);
+ NS_RELEASE(*aResult);
+ }
+ }
+ if (!*aResult) {
+ rv = MsgGetFileStream(mboxFile, aResult);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed opening offline store for output");
+ if (NS_FAILED(rv))
+ printf("failed opening offline store for %s\n", URI.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ seekable = do_QueryInterface(*aResult, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_END, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_outputStreams.InsertOrUpdate(URI, *aResult);
+ }
+ int64_t filePos;
+ seekable->Tell(&filePos);
+
+ if (db && !*aNewMsgHdr) {
+ db->CreateNewHdr(nsMsgKey_None, aNewMsgHdr);
+ }
+
+ if (*aNewMsgHdr) {
+ nsCString storeToken = nsPrintfCString("%" PRId64, filePos);
+ (*aNewMsgHdr)->SetStringProperty("storeToken", storeToken);
+ (*aNewMsgHdr)->SetMessageOffset(filePos);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::DiscardNewMessage(nsIOutputStream* aOutputStream,
+ nsIMsgDBHdr* aNewHdr) {
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+#ifdef _DEBUG
+ m_streamOutstandingFolder = nullptr;
+#endif
+ nsCOMPtr<nsISafeOutputStream> safe = do_QueryInterface(aOutputStream);
+ if (safe) {
+ // nsISafeOutputStream only writes upon finish(), so no cleanup required.
+ return aOutputStream->Close();
+ }
+ // Truncate the mbox back to where we started writing.
+ uint64_t hdrOffset;
+ aNewHdr->GetMessageOffset(&hdrOffset);
+ aOutputStream->Close();
+ nsCOMPtr<nsIFile> mboxFile;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetFilePath(getter_AddRefs(mboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mboxFile->SetFileSize(hdrOffset);
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::FinishNewMessage(nsIOutputStream* aOutputStream,
+ nsIMsgDBHdr* aNewHdr) {
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+#ifdef _DEBUG
+ m_streamOutstandingFolder = nullptr;
+#endif
+ // Quarantining is implemented using a nsISafeOutputStream.
+ // It requires an explicit commit, or the data will be discarded.
+ nsCOMPtr<nsISafeOutputStream> safe = do_QueryInterface(aOutputStream);
+ if (safe) {
+ return safe->Finish();
+ }
+ aOutputStream->Close();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::MoveNewlyDownloadedMessage(nsIMsgDBHdr* aNewHdr,
+ nsIMsgFolder* aDestFolder,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+ NS_ENSURE_ARG_POINTER(aDestFolder);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::GetMsgInputStream(nsIMsgFolder* aMsgFolder,
+ const nsACString& aMsgToken,
+ nsIInputStream** aResult) {
+ MOZ_ASSERT(aMsgFolder);
+ MOZ_ASSERT(aResult);
+ MOZ_ASSERT(!aMsgToken.IsEmpty());
+
+ uint64_t offset = ParseUint64Str(PromiseFlatCString(aMsgToken).get());
+ nsCOMPtr<nsIFile> mboxFile;
+ nsresult rv = aMsgFolder->GetFilePath(getter_AddRefs(mboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStream> msgStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(msgStream), mboxFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISeekableStream> seekable(do_QueryInterface(msgStream));
+ rv = seekable->Seek(PR_SEEK_SET, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgStream.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::DeleteMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray) {
+ return ChangeFlags(aHdrArray, nsMsgMessageFlags::Expunged, true);
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::CopyMessages(bool isMove,
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray,
+ nsIMsgFolder* aDstFolder,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& aDstHdrs,
+ nsITransaction** aUndoAction, bool* aCopyDone) {
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+ NS_ENSURE_ARG_POINTER(aCopyDone);
+ aDstHdrs.Clear();
+ *aUndoAction = nullptr;
+ // We return false to indicate there's no shortcut. The calling code will
+ // just have to perform the copy the hard way.
+ *aCopyDone = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::GetSupportsCompaction(bool* aSupportsCompaction) {
+ NS_ENSURE_ARG_POINTER(aSupportsCompaction);
+ *aSupportsCompaction = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::CompactFolder(nsIMsgFolder* aFolder,
+ nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ // Eventually, folder compaction should be managed by nsMsgBrkMBoxStore, but
+ // for now it's separate (see nsMsgFolderCompactor) and invoked via the
+ // folder methods Compact() and CompactAll().
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::RebuildIndex(nsIMsgFolder* aFolder,
+ nsIMsgDatabase* aMsgDB,
+ nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool isLocked;
+ aFolder->GetLocked(&isLocked);
+ if (isLocked) {
+ NS_ASSERTION(false, "Could not get folder lock");
+ return NS_MSG_FOLDER_BUSY;
+ }
+
+ nsCOMPtr<nsIMailboxService> mailboxService =
+ do_GetService("@mozilla.org/messenger/mailboxservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsMsgMailboxParser> parser = new nsMsgMailboxParser(aFolder);
+ NS_ENSURE_TRUE(parser, NS_ERROR_OUT_OF_MEMORY);
+ rv = parser->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailboxService->ParseMailbox(aMsgWindow, pathFile, parser, aListener,
+ nullptr);
+ if (NS_SUCCEEDED(rv)) ResetForceReparse(aMsgDB);
+ return rv;
+}
+
+nsresult nsMsgBrkMBoxStore::GetOutputStream(
+ nsIMsgDBHdr* aHdr, nsCOMPtr<nsIOutputStream>& outputStream,
+ nsCOMPtr<nsISeekableStream>& seekableStream, int64_t& restorePos) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString URI;
+ folder->GetURI(URI);
+ restorePos = -1;
+ if (m_outputStreams.Get(URI, getter_AddRefs(outputStream))) {
+ seekableStream = do_QueryInterface(outputStream);
+ rv = seekableStream->Tell(&restorePos);
+ if (NS_FAILED(rv)) {
+ outputStream = nullptr;
+ m_outputStreams.Remove(URI);
+ }
+ }
+ if (!outputStream) {
+ nsCOMPtr<nsIFile> mboxFile;
+ rv = folder->GetFilePath(getter_AddRefs(mboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = MsgGetFileStream(mboxFile, getter_AddRefs(outputStream));
+ seekableStream = do_QueryInterface(outputStream);
+ if (NS_SUCCEEDED(rv)) m_outputStreams.InsertOrUpdate(URI, outputStream);
+ }
+ return rv;
+}
+
+void nsMsgBrkMBoxStore::SetDBValid(nsIMsgDBHdr* aHdr) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ aHdr->GetFolder(getter_AddRefs(folder));
+ if (folder) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ folder->GetMsgDatabase(getter_AddRefs(db));
+ if (db) SetSummaryFileValid(folder, db, true);
+ }
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::ChangeFlags(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray, uint32_t aFlags,
+ bool aSet) {
+ if (aHdrArray.IsEmpty()) return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsCOMPtr<nsISeekableStream> seekableStream;
+ int64_t restoreStreamPos;
+ nsresult rv = GetOutputStream(aHdrArray[0], outputStream, seekableStream,
+ restoreStreamPos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ for (auto msgHdr : aHdrArray) {
+ // Work out the flags we want to write.
+ uint32_t flags = 0;
+ (void)msgHdr->GetFlags(&flags);
+ flags &= ~(nsMsgMessageFlags::RuntimeOnly | nsMsgMessageFlags::Offline);
+ if (aSet) {
+ flags |= aFlags;
+ } else {
+ flags &= ~aFlags;
+ }
+
+ // Rewrite flags into X-Mozilla-Status headers.
+ nsCOMPtr<nsISeekableStream> seekable(do_QueryInterface(outputStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint64_t msgOffset;
+ rv = msgHdr->GetMessageOffset(&msgOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, msgOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = RewriteMsgFlags(seekable, flags);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+ if (restoreStreamPos != -1)
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, restoreStreamPos);
+ else if (outputStream)
+ outputStream->Close();
+ SetDBValid(aHdrArray[0]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::ChangeKeywords(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray, const nsACString& aKeywords,
+ bool aAdd) {
+ if (aHdrArray.IsEmpty()) return NS_ERROR_INVALID_ARG;
+
+ nsTArray<nsCString> keywordsToAdd;
+ nsTArray<nsCString> keywordsToRemove;
+ if (aAdd) {
+ ParseString(aKeywords, ' ', keywordsToAdd);
+ } else {
+ ParseString(aKeywords, ' ', keywordsToRemove);
+ }
+
+ // Get the (possibly-cached) seekable & writable stream for this mbox.
+ nsCOMPtr<nsIOutputStream> output;
+ nsCOMPtr<nsISeekableStream> seekable;
+ int64_t restoreStreamPos;
+ nsresult rv =
+ GetOutputStream(aHdrArray[0], output, seekable, restoreStreamPos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto msgHdr : aHdrArray) {
+ uint64_t msgStart;
+ msgHdr->GetMessageOffset(&msgStart);
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, msgStart);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool notEnoughRoom;
+ rv = ChangeKeywordsHelper(seekable, keywordsToAdd, keywordsToRemove,
+ notEnoughRoom);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (notEnoughRoom) {
+ // The growKeywords property indicates that the X-Mozilla-Keys header
+ // doesn't have enough space, and should be rebuilt during the next
+ // folder compaction.
+ msgHdr->SetUint32Property("growKeywords", 1);
+ }
+ }
+
+ if (restoreStreamPos != -1) {
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, restoreStreamPos);
+ } else if (output) {
+ output->Close();
+ }
+ SetDBValid(aHdrArray[0]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBrkMBoxStore::GetStoreType(nsACString& aType) {
+ aType.AssignLiteral("mbox");
+ return NS_OK;
+}
+
+// Iterates over the files in the "path" directory, and adds subfolders to
+// parent for each mailbox file found.
+nsresult nsMsgBrkMBoxStore::AddSubFolders(nsIMsgFolder* parent,
+ nsCOMPtr<nsIFile>& path, bool deep) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> tmp; // at top level so we can safely assign to path
+ bool isDirectory;
+ path->IsDirectory(&isDirectory);
+ if (!isDirectory) {
+ rv = path->Clone(getter_AddRefs(tmp));
+ path = tmp;
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ path->SetLeafName(leafName);
+ path->IsDirectory(&isDirectory);
+ }
+ if (!isDirectory) return NS_OK;
+ // first find out all the current subfolders and files, before using them
+ // while creating new subfolders; we don't want to modify and iterate the same
+ // directory at once.
+ nsCOMArray<nsIFile> currentDirEntries;
+ nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
+ rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ nsCOMPtr<nsIFile> currentFile;
+ rv = directoryEnumerator->GetNextFile(getter_AddRefs(currentFile));
+ if (NS_SUCCEEDED(rv) && currentFile) {
+ currentDirEntries.AppendObject(currentFile);
+ }
+ }
+
+ // add the folders
+ int32_t count = currentDirEntries.Count();
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIFile> currentFile(currentDirEntries[i]);
+
+ nsAutoString leafName;
+ currentFile->GetLeafName(leafName);
+ // here we should handle the case where the current file is a .sbd directory
+ // w/o a matching folder file, or a directory w/o the name .sbd
+ if (nsShouldIgnoreFile(leafName, currentFile)) continue;
+
+ nsCOMPtr<nsIMsgFolder> child;
+ rv = parent->AddSubfolder(leafName, getter_AddRefs(child));
+ if (NS_FAILED(rv) && rv != NS_MSG_FOLDER_EXISTS) {
+ return rv;
+ }
+ if (child) {
+ nsString folderName;
+ child->GetName(folderName); // try to get it from cache/db
+ if (folderName.IsEmpty()) child->SetPrettyName(leafName);
+ if (deep) {
+ nsCOMPtr<nsIFile> path;
+ rv = child->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = AddSubFolders(child, path, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ return rv == NS_MSG_FOLDER_EXISTS ? NS_OK : rv;
+}
+
+/* Finds the directory associated with this folder. That is if the path is
+ c:\Inbox, it will return c:\Inbox.sbd if it succeeds. If that path doesn't
+ currently exist then it will create it. Path is strictly an out parameter.
+ */
+nsresult nsMsgBrkMBoxStore::CreateDirectoryForFolder(nsIFile* path) {
+ nsresult rv = NS_OK;
+
+ bool pathIsDirectory = false;
+ path->IsDirectory(&pathIsDirectory);
+ if (!pathIsDirectory) {
+ // If the current path isn't a directory, add directory separator
+ // and test it out.
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ rv = path->SetLeafName(leafName);
+ if (NS_FAILED(rv)) return rv;
+
+ // If that doesn't exist, then we have to create this directory
+ pathIsDirectory = false;
+ path->IsDirectory(&pathIsDirectory);
+ if (!pathIsDirectory) {
+ bool pathExists;
+ path->Exists(&pathExists);
+ // If for some reason there's a file with the directory separator
+ // then we are going to fail.
+ rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY
+ : path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgBrkMBoxStore::SliceStream(nsIInputStream* inStream, uint64_t start,
+ uint32_t length, nsIInputStream** result) {
+ nsCOMPtr<nsIInputStream> in(inStream);
+ RefPtr<mozilla::SlicedInputStream> slicedStream =
+ new mozilla::SlicedInputStream(in.forget(), start, uint64_t(length));
+ slicedStream.forget(result);
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsMsgBrkMBoxStore.h b/comm/mailnews/local/src/nsMsgBrkMBoxStore.h
new file mode 100644
index 0000000000..a943cafb60
--- /dev/null
+++ b/comm/mailnews/local/src/nsMsgBrkMBoxStore.h
@@ -0,0 +1,57 @@
+/* -*- 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/. */
+
+/**
+ Class for handling Berkeley Mailbox stores.
+*/
+
+#ifndef nsMsgBrkMboxStore_h__
+#define nsMsgBrkMboxStore_h__
+
+#include "nsMsgLocalStoreUtils.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIFile.h"
+#include "nsInterfaceHashtable.h"
+#include "nsISeekableStream.h"
+#include "nsIOutputStream.h"
+
+class nsMsgBrkMBoxStore final : public nsMsgLocalStoreUtils,
+ nsIMsgPluggableStore {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPLUGGABLESTORE
+
+ nsMsgBrkMBoxStore();
+
+ private:
+ ~nsMsgBrkMBoxStore();
+
+ protected:
+ nsresult InternalGetNewMsgOutputStream(nsIMsgFolder* aFolder,
+ nsIMsgDBHdr** aNewMsgHdr,
+ nsIOutputStream** aResult);
+ nsresult AddSubFolders(nsIMsgFolder* parent, nsCOMPtr<nsIFile>& path,
+ bool deep);
+ nsresult CreateDirectoryForFolder(nsIFile* path);
+ nsresult GetOutputStream(nsIMsgDBHdr* aHdr,
+ nsCOMPtr<nsIOutputStream>& outputStream,
+ nsCOMPtr<nsISeekableStream>& seekableStream,
+ int64_t& restorePos);
+ void GetMailboxModProperties(nsIMsgFolder* aFolder, int64_t* aSize,
+ uint32_t* aDate);
+ void SetDBValid(nsIMsgDBHdr* aHdr);
+
+ // We don't want to keep re-opening an output stream when downloading
+ // multiple pop3 messages, or adjusting x-mozilla-status headers, so
+ // we cache output streams based on folder uri's. If the caller has closed
+ // the stream, we'll get a new one.
+ nsInterfaceHashtable<nsCStringHashKey, nsIOutputStream> m_outputStreams;
+
+#ifdef _DEBUG
+ nsCOMPtr<nsIMsgFolder> m_streamOutstandingFolder;
+#endif
+};
+
+#endif
diff --git a/comm/mailnews/local/src/nsMsgFileHdr.cpp b/comm/mailnews/local/src/nsMsgFileHdr.cpp
new file mode 100644
index 0000000000..57cac43886
--- /dev/null
+++ b/comm/mailnews/local/src/nsMsgFileHdr.cpp
@@ -0,0 +1,389 @@
+/* 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 "nsCOMPtr.h"
+#include "nsMsgFileHdr.h"
+#include "nsMsgMessageFlags.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "HeaderReader.h"
+#include "nsIFileStreams.h"
+#include "nsIMimeConverter.h"
+
+static inline uint32_t PRTimeToSeconds(PRTime aTimeUsec) {
+ return uint32_t(aTimeUsec / PR_USEC_PER_SEC);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgFileHdr, nsIMsgDBHdr)
+
+nsMsgFileHdr::nsMsgFileHdr(const nsACString& aUri) {
+ mUri = nsCString(aUri);
+ mDate = 0;
+ mFlags = 0;
+}
+
+nsMsgFileHdr::~nsMsgFileHdr() {}
+
+nsresult nsMsgFileHdr::ReadFile() {
+ if (mFile) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), mUri);
+ nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(uri);
+ rv = fileUrl->GetFile(getter_AddRefs(mFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFileInputStream> fileStream =
+ do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID);
+ rv = fileStream->Init(mFile, PR_RDONLY, 0664, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ char buffer[8192];
+ rv = fileStream->Read(&buffer[0], 8192, &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto cb = [&](HeaderReader::Hdr const& hdr) {
+ auto name = hdr.Name(buffer);
+ if (name.EqualsLiteral("Subject") && mSubject.IsEmpty()) {
+ mSubject = hdr.Value(buffer);
+ }
+ if (name.EqualsLiteral("From") && mAuthor.IsEmpty()) {
+ mAuthor = hdr.Value(buffer);
+ }
+ if (name.EqualsLiteral("To") && mRecipients.IsEmpty()) {
+ mRecipients = hdr.Value(buffer);
+ }
+ if (name.EqualsLiteral("Cc") && mCcList.IsEmpty()) {
+ mCcList = hdr.Value(buffer);
+ }
+ if (name.EqualsLiteral("Bcc") && mBccList.IsEmpty()) {
+ mBccList = hdr.Value(buffer);
+ }
+ if (name.EqualsLiteral("Date") && mDate == 0) {
+ PR_ParseTimeString(hdr.Value(buffer).get(), false, &mDate);
+ }
+ if (name.EqualsLiteral("Message-ID") && mMessageID.IsEmpty()) {
+ mMessageID = hdr.Value(buffer);
+ mMessageID.Trim("<>");
+ }
+ return true;
+ };
+ HeaderReader rdr;
+ rdr.Parse(buffer, cb);
+
+ nsCOMPtr<nsIMimeConverter> mimeConverter =
+ do_GetService("@mozilla.org/messenger/mimeconverter;1");
+ mimeConverter->DecodeMimeHeader(mSubject.get(), "UTF-8", false, true,
+ mDecodedSubject);
+ mimeConverter->DecodeMimeHeader(mAuthor.get(), "UTF-8", false, true,
+ mDecodedAuthor);
+ mimeConverter->DecodeMimeHeader(mRecipients.get(), "UTF-8", false, true,
+ mDecodedRecipients);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetStringProperty(const char* propertyName,
+ const nsACString& propertyValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetStringProperty(const char* propertyName,
+ nsACString& _retval) {
+ if (!strcmp(propertyName, "dummyMsgUrl")) {
+ _retval = mUri;
+ return NS_OK;
+ }
+ _retval.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetUint32Property(const char* propertyName,
+ uint32_t* _retval) {
+ if (!strcmp(propertyName, "dummyMsgLastModifiedTime")) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRTime modifiedTime;
+ mFile->GetLastModifiedTime(&modifiedTime);
+ *_retval = PRTimeToSeconds(modifiedTime);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetUint32Property(const char* propertyName,
+ uint32_t propertyVal) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetIsRead(bool* aIsRead) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::GetIsFlagged(bool* aIsFlagged) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::GetIsKilled(bool* aIsKilled) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::MarkRead(bool read) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::MarkFlagged(bool flagged) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::MarkHasAttachments(bool hasAttachments) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetPriority(nsMsgPriorityValue* aPriority) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetPriority(nsMsgPriorityValue aPriority) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetFlags(uint32_t* aFlags) {
+ *aFlags = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetFlags(uint32_t aFlags) {
+ mFlags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::OrFlags(uint32_t flags, uint32_t* _retval) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::AndFlags(uint32_t flags, uint32_t* _retval) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetThreadId(nsMsgKey* aThreadId) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::SetThreadId(nsMsgKey aThreadId) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::GetMessageKey(nsMsgKey* aMessageKey) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetMessageKey(nsMsgKey aMessageKey) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetThreadParent(nsMsgKey* aThreadParent) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetThreadParent(nsMsgKey aThreadParent) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetMessageSize(uint32_t* aMessageSize) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t fileSize;
+ mFile->GetFileSize(&fileSize);
+
+ *aMessageSize = uint32_t(fileSize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetMessageSize(uint32_t aMessageSize) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetLineCount(uint32_t* aLineCount) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::SetLineCount(uint32_t aLineCount) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::GetMessageOffset(uint64_t* aMessageOffset) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetMessageOffset(uint64_t aMessageOffset) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetOfflineMessageSize(
+ uint32_t* aOfflineMessageSize) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetOfflineMessageSize(
+ uint32_t aOfflineMessageSize) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetDate(PRTime* aDate) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aDate = mDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetDate(PRTime aDate) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::GetDateInSeconds(uint32_t* aDateInSeconds) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetMessageId(char** aMessageId) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aMessageId = strdup(mMessageID.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetMessageId(const char* aMessageId) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetCcList(char** aCcList) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aCcList = strdup(mCcList.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetCcList(const char* aCcList) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::GetBccList(char** aBccList) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aBccList = strdup(mBccList.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetBccList(const char* aBccList) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::GetAuthor(char** aAuthor) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aAuthor = strdup(mAuthor.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetAuthor(const char* aAuthor) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::GetSubject(nsACString& aSubject) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aSubject = mSubject;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetSubject(const nsACString& aSubject) {
+ mSubject = aSubject;
+ bool strippedRE = NS_MsgStripRE(mSubject, mSubject);
+ nsCOMPtr<nsIMimeConverter> mimeConverter =
+ do_GetService("@mozilla.org/messenger/mimeconverter;1");
+ mimeConverter->DecodeMimeHeader(mSubject.get(), "UTF-8", false, true,
+ mDecodedSubject);
+ if (strippedRE) {
+ mFlags |= nsMsgMessageFlags::HasRe;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetRecipients(char** aRecipients) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aRecipients = strdup(mRecipients.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetRecipients(const char* aRecipients) {
+ // FIXME: should do assignment (maybe not used but if used, a trap!)
+ // Same for all the other unimplemented setters here.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::SetReferences(const nsACString& references) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetNumReferences(uint16_t* aNumReferences) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetStringReference(int32_t refNum,
+ nsACString& _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetMime2DecodedAuthor(
+ nsAString& aMime2DecodedAuthor) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMime2DecodedAuthor.Truncate();
+ aMime2DecodedAuthor.Assign(mDecodedAuthor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetMime2DecodedSubject(
+ nsAString& aMime2DecodedSubject) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMime2DecodedSubject.Truncate();
+ aMime2DecodedSubject.Assign(mDecodedSubject);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetMime2DecodedRecipients(
+ nsAString& aMime2DecodedRecipients) {
+ nsresult rv = ReadFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMime2DecodedRecipients.Truncate();
+ aMime2DecodedRecipients.Assign(mDecodedRecipients);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetAuthorCollationKey(nsTArray<uint8_t>& _retval) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetSubjectCollationKey(nsTArray<uint8_t>& _retval) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetRecipientsCollationKey(
+ nsTArray<uint8_t>& _retval) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetCharset(char** aCharset) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::SetCharset(const char* aCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetEffectiveCharset(nsACString& aEffectiveCharset) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetAccountKey(char** aAccountKey) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::SetAccountKey(const char* aAccountKey) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgFileHdr::GetFolder(nsIMsgFolder** aFolder) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgFileHdr::GetProperties(nsTArray<nsCString>& headers) {
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsMsgFileHdr.h b/comm/mailnews/local/src/nsMsgFileHdr.h
new file mode 100644
index 0000000000..d63b099e04
--- /dev/null
+++ b/comm/mailnews/local/src/nsMsgFileHdr.h
@@ -0,0 +1,41 @@
+/* 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 _nsMsgFileHdr_H
+#define _nsMsgFileHdr_H
+
+#include "nsIMsgHdr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+/* This mail-related class is a stub. You can help mailnews by expanding it. */
+
+class nsMsgFileHdr : public nsIMsgDBHdr {
+ public:
+ explicit nsMsgFileHdr(const nsACString& aUri);
+
+ NS_DECL_NSIMSGDBHDR
+ NS_DECL_ISUPPORTS
+
+ private:
+ virtual ~nsMsgFileHdr();
+
+ nsresult ReadFile();
+
+ nsCString mUri;
+ nsCOMPtr<nsIFile> mFile;
+ nsCString mAuthor;
+ nsString mDecodedAuthor;
+ nsCString mSubject;
+ nsString mDecodedSubject;
+ nsCString mRecipients;
+ nsString mDecodedRecipients;
+ nsCString mCcList;
+ nsCString mBccList;
+ PRTime mDate;
+ nsCString mMessageID;
+ uint32_t mFlags;
+};
+
+#endif
diff --git a/comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp b/comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp
new file mode 100644
index 0000000000..eef6694a67
--- /dev/null
+++ b/comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp
@@ -0,0 +1,380 @@
+/* -*- 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 "nsMsgLocalStoreUtils.h"
+#include "nsIFile.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgDatabase.h"
+#include "HeaderReader.h"
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "mozilla/Buffer.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "prprf.h"
+
+#define EXTRA_SAFETY_SPACE 0x400000 // (4MiB)
+
+nsMsgLocalStoreUtils::nsMsgLocalStoreUtils() {}
+
+nsresult nsMsgLocalStoreUtils::AddDirectorySeparator(nsIFile* path) {
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ return path->SetLeafName(leafName);
+}
+
+bool nsMsgLocalStoreUtils::nsShouldIgnoreFile(nsAString& name, nsIFile* path) {
+ if (name.IsEmpty()) return true;
+
+ char16_t firstChar = name.First();
+ if (firstChar == '.' || firstChar == '#' ||
+ name.CharAt(name.Length() - 1) == '~')
+ return true;
+
+ if (name.LowerCaseEqualsLiteral("msgfilterrules.dat") ||
+ name.LowerCaseEqualsLiteral("rules.dat") ||
+ name.LowerCaseEqualsLiteral("filterlog.html") ||
+ name.LowerCaseEqualsLiteral("junklog.html") ||
+ name.LowerCaseEqualsLiteral("rulesbackup.dat"))
+ return true;
+
+ // don't add summary files to the list of folders;
+ // don't add popstate files to the list either, or rules (sort.dat).
+ if (StringEndsWith(name, u".snm"_ns) ||
+ name.LowerCaseEqualsLiteral("popstate.dat") ||
+ name.LowerCaseEqualsLiteral("sort.dat") ||
+ name.LowerCaseEqualsLiteral("mailfilt.log") ||
+ name.LowerCaseEqualsLiteral("filters.js") ||
+ StringEndsWith(name, u".toc"_ns))
+ return true;
+
+ // ignore RSS data source files (see FeedUtils.jsm)
+ if (name.LowerCaseEqualsLiteral("feeds.json") ||
+ name.LowerCaseEqualsLiteral("feeds.json.tmp") ||
+ name.LowerCaseEqualsLiteral("feeds.json.backup") ||
+ name.LowerCaseEqualsLiteral("feeds.json.corrupt") ||
+ name.LowerCaseEqualsLiteral("feeditems.json") ||
+ name.LowerCaseEqualsLiteral("feeditems.json.tmp") ||
+ name.LowerCaseEqualsLiteral("feeditems.json.backup") ||
+ name.LowerCaseEqualsLiteral("feeditems.json.corrupt") ||
+ name.LowerCaseEqualsLiteral("feeds.rdf") ||
+ name.LowerCaseEqualsLiteral("feeditems.rdf") ||
+ StringBeginsWith(name, u"feeditems_error"_ns))
+ return true;
+
+ // Ignore hidden and other special system files.
+ bool specialFile = false;
+ path->IsHidden(&specialFile);
+ if (specialFile) return true;
+ specialFile = false;
+ path->IsSpecial(&specialFile);
+ if (specialFile) return true;
+
+ // The .mozmsgs dir is for spotlight support
+ return (StringEndsWith(name, u".mozmsgs"_ns) ||
+ StringEndsWith(name, NS_LITERAL_STRING_FROM_CSTRING(FOLDER_SUFFIX)) ||
+ StringEndsWith(name, NS_LITERAL_STRING_FROM_CSTRING(SUMMARY_SUFFIX)));
+}
+
+// Attempts to fill a buffer. Returns a span holding the data read.
+// Might be less than buffer size, if EOF was encountered.
+// Upon error, an empty span is returned.
+static mozilla::Span<char> readBuf(nsIInputStream* readable,
+ mozilla::Buffer<char>& buf) {
+ uint32_t total = 0;
+ while (total < buf.Length()) {
+ uint32_t n;
+ nsresult rv =
+ readable->Read(buf.Elements() + total, buf.Length() - total, &n);
+ if (NS_FAILED(rv)) {
+ total = 0;
+ break;
+ }
+ if (n == 0) {
+ break; // EOF
+ }
+ total += n;
+ }
+ return mozilla::Span<char>(buf.Elements(), total);
+}
+
+// Write data to outputstream, until complete or error.
+static nsresult writeBuf(nsIOutputStream* writeable, const char* data,
+ size_t dataSize) {
+ uint32_t written = 0;
+ while (written < dataSize) {
+ uint32_t n;
+ nsresult rv = writeable->Write(data + written, dataSize - written, &n);
+ NS_ENSURE_SUCCESS(rv, rv);
+ written += n;
+ }
+ return NS_OK;
+}
+
+/**
+ * Attempt to update X-Mozilla-Status and X-Mozilla-Status2 headers with
+ * new message flags by rewriting them in place.
+ */
+nsresult nsMsgLocalStoreUtils::RewriteMsgFlags(nsISeekableStream* seekable,
+ uint32_t msgFlags) {
+ nsresult rv;
+
+ // Remember where we started.
+ int64_t msgStart;
+ rv = seekable->Tell(&msgStart);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We edit the file in-place, so need to be able to read and write too.
+ nsCOMPtr<nsIInputStream> readable(do_QueryInterface(seekable, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIOutputStream> writable = do_QueryInterface(seekable, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Read in the first chunk of the header and search for the X-Mozilla-Status
+ // headers. We know that those headers always appear at the beginning, so
+ // don't need to look too far in.
+ mozilla::Buffer<char> buf(512);
+ mozilla::Span<const char> data = readBuf(readable, buf);
+
+ // If there's a "From " line, consume it.
+ mozilla::Span<const char> fromLine;
+ if (data.Length() >= 5 &&
+ nsDependentCSubstring(data.First(5)).EqualsLiteral("From ")) {
+ fromLine = FirstLine(data);
+ data = data.From(fromLine.Length());
+ }
+
+ HeaderReader::Hdr statusHdr;
+ HeaderReader::Hdr status2Hdr;
+ auto findHeadersFn = [&](auto const& hdr) {
+ if (hdr.Name(data).EqualsLiteral(X_MOZILLA_STATUS)) {
+ statusHdr = hdr;
+ } else if (hdr.Name(data).EqualsLiteral(X_MOZILLA_STATUS2)) {
+ status2Hdr = hdr;
+ } else {
+ return true; // Keep looking.
+ }
+ // Keep looking until we find both.
+ return statusHdr.IsEmpty() || status2Hdr.IsEmpty();
+ };
+ HeaderReader rdr;
+ rdr.Parse(data, findHeadersFn);
+
+ // Update X-Mozilla-Status (holds the lower 16bits worth of flags).
+ if (!statusHdr.IsEmpty()) {
+ uint32_t oldFlags = statusHdr.Value(data).ToInteger(&rv, 16);
+ if (NS_SUCCEEDED(rv)) {
+ // Preserve the Queued flag from existing X-Mozilla-Status header.
+ // (Note: not sure why we do this, but keeping it in for now. - BenC)
+ msgFlags |= oldFlags & nsMsgMessageFlags::Queued;
+
+ if ((msgFlags & 0xFFFF) != oldFlags) {
+ auto out = nsPrintfCString("%4.4x", msgFlags & 0xFFFF);
+ if (out.Length() <= statusHdr.rawValLen) {
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ msgStart + fromLine.Length() + statusHdr.pos +
+ statusHdr.rawValOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Should be an exact fit already, but just in case...
+ while (out.Length() < statusHdr.rawValLen) {
+ out.Append(' ');
+ }
+ rv = writeBuf(writable, out.BeginReading(), out.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ }
+
+ // Update X-Mozilla-Status2 (holds the upper 16bit flags only(!)).
+ if (!status2Hdr.IsEmpty()) {
+ uint32_t oldFlags = status2Hdr.Value(data).ToInteger(&rv, 16);
+ if (NS_SUCCEEDED(rv)) {
+ if ((msgFlags & 0xFFFF0000) != oldFlags) {
+ auto out = nsPrintfCString("%8.8x", msgFlags & 0xFFFF0000);
+ if (out.Length() <= status2Hdr.rawValLen) {
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ msgStart + fromLine.Length() + status2Hdr.pos +
+ status2Hdr.rawValOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (out.Length() < status2Hdr.rawValLen) {
+ out.Append(' ');
+ }
+ rv = writeBuf(writable, out.BeginReading(), out.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Returns true if there is enough space on disk.
+ *
+ * @param aFile Any file in the message store that is on a logical
+ * disk volume so that it can be queried for disk space.
+ * @param aSpaceRequested The size of free space there must be on the disk
+ * to return true.
+ */
+bool nsMsgLocalStoreUtils::DiskSpaceAvailableInStore(nsIFile* aFile,
+ uint64_t aSpaceRequested) {
+ int64_t diskFree;
+ nsresult rv = aFile->GetDiskSpaceAvailable(&diskFree);
+ if (NS_SUCCEEDED(rv)) {
+#ifdef DEBUG
+ printf("GetDiskSpaceAvailable returned: %lld bytes\n", (long long)diskFree);
+#endif
+ // When checking for disk space available, take into consideration
+ // possible database changes, therefore ask for a little more
+ // (EXTRA_SAFETY_SPACE) than what the requested size is. Also, due to disk
+ // sector sizes, allocation blocks, etc. The space "available" may be
+ // greater than the actual space usable.
+ return ((aSpaceRequested + EXTRA_SAFETY_SPACE) < (uint64_t)diskFree);
+ } else if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ // The call to GetDiskSpaceAvailable is not implemented!
+ // This will happen on certain platforms where GetDiskSpaceAvailable
+ // is not implemented. Since people on those platforms still need
+ // to download mail, we will simply bypass the disk-space check.
+ //
+ // We'll leave a debug message to warn people.
+#ifdef DEBUG
+ printf(
+ "Call to GetDiskSpaceAvailable FAILED because it is not "
+ "implemented!\n");
+#endif
+ return true;
+ } else {
+ printf("Call to GetDiskSpaceAvailable FAILED!\n");
+ return false;
+ }
+}
+
+/**
+ * Resets forceReparse in the database.
+ *
+ * @param aMsgDb The database to reset.
+ */
+void nsMsgLocalStoreUtils::ResetForceReparse(nsIMsgDatabase* aMsgDB) {
+ if (aMsgDB) {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ aMsgDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (folderInfo) folderInfo->SetBooleanProperty("forceReparse", false);
+ }
+}
+
+/**
+ * Update the value of an X-Mozilla-Keys header in place.
+ *
+ * @param seekable The stream containing the message, positioned at the
+ * beginning of the message (must also be readable and
+ * writable).
+ * @param keywordsToAdd The list of keywords to add.
+ * @param keywordsToRemove The list of keywords to remove.
+ * @param notEnoughRoom Upon return, this will be set if the header is missing
+ * or too small to contain the new keywords.
+ *
+ */
+nsresult nsMsgLocalStoreUtils::ChangeKeywordsHelper(
+ nsISeekableStream* seekable, nsTArray<nsCString> const& keywordsToAdd,
+ nsTArray<nsCString> const& keywordsToRemove, bool& notEnoughRoom) {
+ notEnoughRoom = false;
+ nsresult rv;
+
+ // Remember where we started.
+ int64_t msgStart;
+ rv = seekable->Tell(&msgStart);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We edit the file in-place, so need to be able to read and write too.
+ nsCOMPtr<nsIInputStream> readable(do_QueryInterface(seekable, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIOutputStream> writable = do_QueryInterface(seekable, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Read in the first chunk of the header and search for X-Mozilla-Keys.
+ // We know that it always appears near the beginning, so don't need to look
+ // too far in.
+ mozilla::Buffer<char> buf(512);
+ mozilla::Span<const char> data = readBuf(readable, buf);
+
+ // If there's a "From " line, consume it.
+ mozilla::Span<const char> fromLine;
+ if (data.Length() >= 5 &&
+ nsDependentCSubstring(data.First(5)).EqualsLiteral("From ")) {
+ fromLine = FirstLine(data);
+ data = data.From(fromLine.Length());
+ }
+
+ HeaderReader::Hdr kwHdr;
+ auto findHeaderFn = [&](auto const& hdr) {
+ if (hdr.Name(data).EqualsLiteral(HEADER_X_MOZILLA_KEYWORDS)) {
+ kwHdr = hdr;
+ return false;
+ }
+ return true; // Keep looking.
+ };
+ HeaderReader rdr;
+ rdr.Parse(data, findHeaderFn);
+
+ if (kwHdr.IsEmpty()) {
+ NS_WARNING("X-Mozilla-Keys header not found.");
+ notEnoughRoom = true;
+ return NS_OK;
+ }
+
+ // Get existing keywords.
+ nsTArray<nsCString> keywords;
+ nsAutoCString old(kwHdr.Value(data));
+ old.CompressWhitespace();
+ for (nsACString const& kw : old.Split(' ')) {
+ keywords.AppendElement(kw);
+ }
+
+ bool altered = false;
+ // Add missing keywords.
+ for (auto const& add : keywordsToAdd) {
+ if (!keywords.Contains(add)) {
+ keywords.AppendElement(add);
+ altered = true;
+ }
+ }
+
+ // Remove any keywords we want gone.
+ for (auto const& remove : keywordsToRemove) {
+ auto idx = keywords.IndexOf(remove);
+ if (idx != keywords.NoIndex) {
+ keywords.RemoveElementAt(idx);
+ altered = true;
+ }
+ }
+
+ if (!altered) {
+ return NS_OK;
+ }
+
+ // Write updated keywords over existing value.
+ auto out = StringJoin(" "_ns, keywords);
+ if (out.Length() > kwHdr.rawValLen) {
+ NS_WARNING("X-Mozilla-Keys too small for new value.");
+ notEnoughRoom = true;
+ return NS_OK;
+ }
+ while (out.Length() < kwHdr.rawValLen) {
+ out.Append(' ');
+ }
+
+ rv = seekable->Seek(
+ nsISeekableStream::NS_SEEK_SET,
+ msgStart + fromLine.Length() + kwHdr.pos + kwHdr.rawValOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = writeBuf(writable, out.BeginReading(), out.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsMsgLocalStoreUtils.h b/comm/mailnews/local/src/nsMsgLocalStoreUtils.h
new file mode 100644
index 0000000000..968ee8d436
--- /dev/null
+++ b/comm/mailnews/local/src/nsMsgLocalStoreUtils.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsMsgLocalStoreUtils_h__
+#define nsMsgLocalStoreUtils_h__
+
+#include "msgCore.h"
+#include "nsString.h"
+#include "nsISeekableStream.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMailHeaders.h"
+#include "nsMsgUtils.h"
+#include "nsMsgMessageFlags.h"
+#include "nsArray.h"
+
+/**
+ * Utility Class for handling local mail stores. Berkeley Mailbox
+ * and MailDir stores inherit from this class to share some code.
+ */
+
+class nsMsgLocalStoreUtils {
+ public:
+ nsMsgLocalStoreUtils();
+
+ static nsresult AddDirectorySeparator(nsIFile* path);
+ static bool nsShouldIgnoreFile(nsAString& name, nsIFile* path);
+ static nsresult ChangeKeywordsHelper(
+ nsISeekableStream* seekable, nsTArray<nsCString> const& keywordsToAdd,
+ nsTArray<nsCString> const& keywordsToRemove, bool& notEnoughSpace);
+
+ static void ResetForceReparse(nsIMsgDatabase* aMsgDB);
+
+ nsresult RewriteMsgFlags(nsISeekableStream* seekable, uint32_t flags);
+ bool DiskSpaceAvailableInStore(nsIFile* aFile, uint64_t aSpaceRequested);
+};
+
+#endif
diff --git a/comm/mailnews/local/src/nsMsgMaildirStore.cpp b/comm/mailnews/local/src/nsMsgMaildirStore.cpp
new file mode 100644
index 0000000000..2f597cd464
--- /dev/null
+++ b/comm/mailnews/local/src/nsMsgMaildirStore.cpp
@@ -0,0 +1,1380 @@
+/* -*- 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/. */
+
+/**
+ Class for handling Maildir stores.
+*/
+
+#include "prprf.h"
+#include "msgCore.h"
+#include "nsMsgMaildirStore.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsIInputStream.h"
+#include "nsMsgFolderFlags.h"
+#include "nsCOMArray.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsIMsgDatabase.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsMsgUtils.h"
+#include "nsIDBFolderInfo.h"
+#include "nsMailHeaders.h"
+#include "nsParseMailbox.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsITimer.h"
+#include "nsIMailboxUrl.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsLocalUndoTxn.h"
+#include "nsIMessenger.h"
+#include "mozilla/Logging.h"
+#include "mozilla/SlicedInputStream.h"
+#include "mozilla/UniquePtr.h"
+
+static mozilla::LazyLogModule MailDirLog("MailDirStore");
+
+// Helper function to produce a safe filename from a Message-ID value.
+// We'll percent-encode anything not in this set: [-+.%=@_0-9a-zA-Z]
+// This is an overly-picky set, but should:
+// - leave most sane Message-IDs unchanged
+// - be safe on windows (the pickiest case)
+// - avoid chars that can trip up shell scripts (spaces, semicolons etc)
+// If input contains malicious binary (or multibyte chars) it'll be
+// safely encoded as individual bytes.
+static void percentEncode(nsACString const& in, nsACString& out) {
+ const char* end = in.EndReading();
+ const char* cur;
+ // We know the output will be at least as long as the input.
+ out.SetLength(0);
+ out.SetCapacity(in.Length());
+ for (cur = in.BeginReading(); cur < end; ++cur) {
+ const char c = *cur;
+ bool whitelisted = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') || c == '-' || c == '+' ||
+ c == '.' || c == '%' || c == '=' || c == '@' || c == '_';
+ if (whitelisted) {
+ out.Append(c);
+ } else {
+ out.AppendPrintf("%%%02x", (unsigned char)c);
+ }
+ }
+}
+
+nsMsgMaildirStore::nsMsgMaildirStore() {}
+
+nsMsgMaildirStore::~nsMsgMaildirStore() {}
+
+NS_IMPL_ISUPPORTS(nsMsgMaildirStore, nsIMsgPluggableStore)
+
+// Iterates over the folders in the "path" directory, and adds subfolders to
+// parent for each Maildir folder found.
+nsresult nsMsgMaildirStore::AddSubFolders(nsIMsgFolder* parent, nsIFile* path,
+ bool deep) {
+ nsCOMArray<nsIFile> currentDirEntries;
+
+ nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
+ nsresult rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ nsCOMPtr<nsIFile> currentFile;
+ rv = directoryEnumerator->GetNextFile(getter_AddRefs(currentFile));
+ if (NS_SUCCEEDED(rv) && currentFile) {
+ nsAutoString leafName;
+ currentFile->GetLeafName(leafName);
+ bool isDirectory = false;
+ currentFile->IsDirectory(&isDirectory);
+ // Make sure this really is a mail folder dir (i.e., a directory that
+ // contains cur and tmp sub-dirs, and not a .sbd or .mozmsgs dir).
+ if (isDirectory && !nsShouldIgnoreFile(leafName, currentFile))
+ currentDirEntries.AppendObject(currentFile);
+ }
+ }
+
+ // add the folders
+ int32_t count = currentDirEntries.Count();
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIFile> currentFile(currentDirEntries[i]);
+
+ nsAutoString leafName;
+ currentFile->GetLeafName(leafName);
+
+ nsCOMPtr<nsIMsgFolder> child;
+ rv = parent->AddSubfolder(leafName, getter_AddRefs(child));
+ if (child) {
+ nsString folderName;
+ child->GetName(folderName); // try to get it from cache/db
+ if (folderName.IsEmpty()) child->SetPrettyName(leafName);
+ if (deep) {
+ nsCOMPtr<nsIFile> path;
+ rv = child->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Construct the .sbd directory path for the possible children of the
+ // folder.
+ GetDirectoryForFolder(path);
+ bool directory = false;
+ // Check that <folder>.sbd really is a directory.
+ path->IsDirectory(&directory);
+ if (directory) AddSubFolders(child, path, true);
+ }
+ }
+ }
+ return rv == NS_MSG_FOLDER_EXISTS ? NS_OK : rv;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::DiscoverSubFolders(nsIMsgFolder* aParentFolder,
+ bool aDeep) {
+ NS_ENSURE_ARG_POINTER(aParentFolder);
+
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aParentFolder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isServer, directory = false;
+ aParentFolder->GetIsServer(&isServer);
+ if (!isServer) GetDirectoryForFolder(path);
+
+ path->IsDirectory(&directory);
+ if (directory) rv = AddSubFolders(aParentFolder, path, aDeep);
+
+ return (rv == NS_MSG_FOLDER_EXISTS) ? NS_OK : rv;
+}
+
+/**
+ * Create if missing a Maildir-style folder with "tmp" and "cur" subfolders
+ * but no "new" subfolder, because it doesn't make sense in the mail client
+ * context. ("new" directory is for messages on the server that haven't been
+ * seen by a mail client).
+ * aFolderName is already "safe" - it has been through NS_MsgHashIfNecessary.
+ */
+nsresult nsMsgMaildirStore::CreateMaildir(nsIFile* path) {
+ nsresult rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ NS_WARNING("Could not create root directory for message folder");
+ return rv;
+ }
+
+ // Create tmp, cur leaves
+ nsCOMPtr<nsIFile> leaf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ leaf->InitWithFile(path);
+
+ leaf->AppendNative("tmp"_ns);
+ rv = leaf->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ NS_WARNING("Could not create tmp directory for message folder");
+ return rv;
+ }
+
+ leaf->SetNativeLeafName("cur"_ns);
+ rv = leaf->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ NS_WARNING("Could not create cur directory for message folder");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::CreateFolder(nsIMsgFolder* aParent,
+ const nsAString& aFolderName,
+ nsIMsgFolder** aResult) {
+ NS_ENSURE_ARG_POINTER(aParent);
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (aFolderName.IsEmpty()) return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aParent->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get a directory based on our current path
+ bool isServer;
+ aParent->GetIsServer(&isServer);
+ rv = CreateDirectoryForFolder(path, isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure the new folder name is valid
+ nsAutoString safeFolderName(aFolderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+
+ path->Append(safeFolderName);
+ bool exists;
+ path->Exists(&exists);
+ if (exists) // check this because localized names are different from disk
+ // names
+ return NS_MSG_FOLDER_EXISTS;
+
+ rv = CreateMaildir(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> child;
+ // GetFlags and SetFlags in AddSubfolder will fail because we have no db at
+ // this point but mFlags is set.
+ rv = aParent->AddSubfolder(safeFolderName, getter_AddRefs(child));
+ if (!child || NS_FAILED(rv)) {
+ path->Remove(true); // recursive
+ return rv;
+ }
+
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ if (msgDBService) {
+ nsCOMPtr<nsIMsgDatabase> unusedDB;
+ rv = msgDBService->OpenFolderDB(child, true, getter_AddRefs(unusedDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ rv = msgDBService->CreateNewDB(child, getter_AddRefs(unusedDB));
+
+ if ((NS_SUCCEEDED(rv) || rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) &&
+ unusedDB) {
+ // need to set the folder name
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (NS_SUCCEEDED(rv)) folderInfo->SetMailboxName(safeFolderName);
+
+ unusedDB->SetSummaryValid(true);
+ unusedDB->Close(true);
+ aParent->UpdateSummaryTotals(true);
+ } else {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("CreateFolder - failed creating db for new folder"));
+ path->Remove(true); // recursive
+ rv = NS_MSG_CANT_CREATE_FOLDER;
+ }
+ }
+ child.forget(aResult);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::HasSpaceAvailable(nsIMsgFolder* aFolder,
+ int64_t aSpaceRequested,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = DiskSpaceAvailableInStore(pathFile, aSpaceRequested);
+ if (!*aResult) return NS_ERROR_FILE_NO_DEVICE_SPACE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::IsSummaryFileValid(nsIMsgFolder* aFolder,
+ nsIMsgDatabase* aDB,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aDB);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ nsresult rv =
+ dbFolderInfo->GetBooleanProperty("maildirValid", false, aResult);
+ if (!*aResult) {
+ nsCOMPtr<nsIFile> newFile;
+ rv = aFolder->GetFilePath(getter_AddRefs(newFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ newFile->Append(u"cur"_ns);
+
+ // If the "cur" sub-dir doesn't exist, and there are no messages
+ // in the db, then the folder is probably new and the db is valid.
+ bool exists;
+ newFile->Exists(&exists);
+ if (!exists) {
+ int32_t numMessages;
+ dbFolderInfo->GetNumMessages(&numMessages);
+ if (!numMessages) *aResult = true;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::SetSummaryFileValid(nsIMsgFolder* aFolder,
+ nsIMsgDatabase* aDB,
+ bool aValid) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aDB);
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ NS_ENSURE_STATE(dbFolderInfo);
+ return dbFolderInfo->SetBooleanProperty("maildirValid", aValid);
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::DeleteFolder(nsIMsgFolder* aFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ bool exists;
+
+ // Delete the Maildir itself.
+ nsCOMPtr<nsIFile> pathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ exists = false;
+ pathFile->Exists(&exists);
+ if (exists) {
+ rv = pathFile->Remove(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Delete any subfolders (.sbd-suffixed directories).
+ AddDirectorySeparator(pathFile);
+ exists = false;
+ pathFile->Exists(&exists);
+ if (exists) {
+ rv = pathFile->Remove(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::RenameFolder(nsIMsgFolder* aFolder,
+ const nsAString& aNewName,
+ nsIMsgFolder** aNewFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aNewFolder);
+
+ // old path
+ nsCOMPtr<nsIFile> oldPathFile;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(oldPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // old sbd directory
+ nsCOMPtr<nsIFile> sbdPathFile;
+ uint32_t numChildren;
+ aFolder->GetNumSubFolders(&numChildren);
+ if (numChildren > 0) {
+ sbdPathFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sbdPathFile->InitWithFile(oldPathFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ GetDirectoryForFolder(sbdPathFile);
+ }
+
+ // old summary
+ nsCOMPtr<nsIFile> oldSummaryFile;
+ rv = aFolder->GetSummaryFile(getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Validate new name
+ nsAutoString safeName(aNewName);
+ NS_MsgHashIfNecessary(safeName);
+
+ aFolder->ForceDBClosed();
+
+ // rename folder
+ rv = oldPathFile->MoveTo(nullptr, safeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (numChildren > 0) {
+ // rename "*.sbd" directory
+ nsAutoString sbdName = safeName;
+ sbdName.AppendLiteral(FOLDER_SUFFIX);
+ sbdPathFile->MoveTo(nullptr, sbdName);
+ }
+
+ // rename summary
+ nsAutoString summaryName(safeName);
+ summaryName.AppendLiteral(SUMMARY_SUFFIX);
+ oldSummaryFile->MoveTo(nullptr, summaryName);
+
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ rv = aFolder->GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder) return NS_ERROR_NULL_POINTER;
+
+ return parentFolder->AddSubfolder(safeName, aNewFolder);
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::CopyFolder(
+ nsIMsgFolder* aSrcFolder, nsIMsgFolder* aDstFolder, bool aIsMoveFolder,
+ nsIMsgWindow* aMsgWindow, nsIMsgCopyServiceListener* aListener,
+ const nsAString& aNewName) {
+ NS_ENSURE_ARG_POINTER(aSrcFolder);
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+
+ nsAutoString folderName;
+ if (aNewName.IsEmpty())
+ aSrcFolder->GetName(folderName);
+ else
+ folderName.Assign(aNewName);
+
+ nsAutoString safeFolderName(folderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+ aSrcFolder->ForceDBClosed();
+
+ nsCOMPtr<nsIFile> oldPath;
+ nsresult rv = aSrcFolder->GetFilePath(getter_AddRefs(oldPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> summaryFile;
+ GetSummaryFileLocation(oldPath, getter_AddRefs(summaryFile));
+
+ nsCOMPtr<nsIFile> newPath;
+ rv = aDstFolder->GetFilePath(getter_AddRefs(newPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create target directory based on our current path
+ bool isServer;
+ aDstFolder->GetIsServer(&isServer);
+ rv = CreateDirectoryForFolder(newPath, isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> origPath;
+ oldPath->Clone(getter_AddRefs(origPath));
+
+ rv = oldPath->CopyTo(newPath, safeFolderName);
+ NS_ENSURE_SUCCESS(rv, rv); // will fail if a file by that name exists
+
+ // Copy to dir can fail if file does not exist. If copy fails, we test
+ // if the file exists or not, if it does not that's ok, we continue
+ // without copying it. If it fails and file exist and is not zero sized
+ // there is real problem.
+ nsAutoString dbName(safeFolderName);
+ dbName.AppendLiteral(SUMMARY_SUFFIX);
+ rv = summaryFile->CopyTo(newPath, dbName);
+ if (!NS_SUCCEEDED(rv)) {
+ // Test if the file is not empty
+ bool exists;
+ int64_t fileSize;
+ summaryFile->Exists(&exists);
+ summaryFile->GetFileSize(&fileSize);
+ if (exists && fileSize > 0)
+ NS_ENSURE_SUCCESS(rv, rv); // Yes, it should have worked!
+ // else case is file is zero sized, no need to copy it,
+ // not an error
+ // else case is file does not exist - not an error
+ }
+
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ rv = aDstFolder->AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newMsgFolder->SetPrettyName(folderName);
+ uint32_t flags;
+ aSrcFolder->GetFlags(&flags);
+ newMsgFolder->SetFlags(flags);
+ bool changed = false;
+ rv = aSrcFolder->MatchOrChangeFilterDestination(newMsgFolder, true, &changed);
+ if (changed) aSrcFolder->AlertFilterChanged(aMsgWindow);
+
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = aSrcFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy subfolders to the new location
+ nsresult copyStatus = NS_OK;
+ nsCOMPtr<nsIMsgLocalMailFolder> localNewFolder(
+ do_QueryInterface(newMsgFolder, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ for (nsIMsgFolder* folder : subFolders) {
+ copyStatus =
+ localNewFolder->CopyFolderLocal(folder, false, aMsgWindow, aListener);
+ // Test if the call succeeded, if not we have to stop recursive call
+ if (NS_FAILED(copyStatus)) {
+ // Copy failed we have to notify caller to handle the error and stop
+ // moving the folders. In case this happens to the topmost level of
+ // recursive call, then we just need to break from the while loop and
+ // go to error handling code.
+ if (!aIsMoveFolder) return copyStatus;
+ break;
+ }
+ }
+ }
+
+ if (aIsMoveFolder && NS_SUCCEEDED(copyStatus)) {
+ if (localNewFolder) {
+ nsCOMPtr<nsISupports> srcSupport(do_QueryInterface(aSrcFolder));
+ localNewFolder->OnCopyCompleted(srcSupport, true);
+ }
+
+ // Notify that the folder that was dragged and dropped has been created.
+ // No need to do this for its subfolders - isMoveFolder will be true for
+ // folder.
+ aDstFolder->NotifyFolderAdded(newMsgFolder);
+
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ aSrcFolder->GetParent(getter_AddRefs(msgParent));
+ aSrcFolder->SetParent(nullptr);
+ if (msgParent) {
+ // The files have already been moved, so delete storage false
+ msgParent->PropagateDelete(aSrcFolder, false);
+ oldPath->Remove(true);
+ aSrcFolder->DeleteStorage();
+
+ nsCOMPtr<nsIFile> parentPath;
+ rv = msgParent->GetFilePath(getter_AddRefs(parentPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddDirectorySeparator(parentPath);
+ nsCOMPtr<nsIDirectoryEnumerator> children;
+ parentPath->GetDirectoryEntries(getter_AddRefs(children));
+ bool more;
+ // checks if the directory is empty or not
+ if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more)
+ parentPath->Remove(true);
+ }
+ } else {
+ // This is the case where the copy of a subfolder failed.
+ // We have to delete the newDirectory tree to make a "rollback".
+ // Someone should add a popup to warn the user that the move was not
+ // possible.
+ if (aIsMoveFolder && NS_FAILED(copyStatus)) {
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ newMsgFolder->ForceDBClosed();
+ newMsgFolder->GetParent(getter_AddRefs(msgParent));
+ newMsgFolder->SetParent(nullptr);
+ if (msgParent) {
+ msgParent->PropagateDelete(newMsgFolder, false);
+ newMsgFolder->DeleteStorage();
+ AddDirectorySeparator(newPath);
+ newPath->Remove(true); // berkeley mailbox
+ }
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::GetNewMsgOutputStream(nsIMsgFolder* aFolder,
+ nsIMsgDBHdr** aNewMsgHdr,
+ nsIOutputStream** aResult) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aNewMsgHdr);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = aFolder->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!*aNewMsgHdr) {
+ rv = db->CreateNewHdr(nsMsgKey_None, aNewMsgHdr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // With maildir, messages have whole file to themselves.
+ (*aNewMsgHdr)->SetMessageOffset(0);
+
+ // We're going to save the new message into the maildir 'tmp' folder.
+ // When the message is completed, it can be moved to 'cur'.
+ nsCOMPtr<nsIFile> newFile;
+ rv = aFolder->GetFilePath(getter_AddRefs(newFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ newFile->Append(u"tmp"_ns);
+
+ // let's check if the folder exists
+ // XXX TODO: kill this and make sure maildir creation includes cur/tmp
+ bool exists;
+ newFile->Exists(&exists);
+ if (!exists) {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("GetNewMsgOutputStream - tmp subfolder does not exist!!"));
+ rv = newFile->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Generate the 'tmp' file name based on timestamp.
+ // (We'll use the Message-ID as the basis for the final filename,
+ // but we don't have headers at this point).
+ nsAutoCString newName;
+ newName.AppendInt(static_cast<int64_t>(PR_Now()));
+ newFile->AppendNative(newName);
+
+ // CreateUnique, in case we get more than one message per millisecond :-)
+ rv = newFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newFile->GetNativeLeafName(newName);
+ // save the file name in the message header - otherwise no way to retrieve it
+ (*aNewMsgHdr)->SetStringProperty("storeToken", newName);
+ return MsgNewBufferedFileOutputStream(aResult, newFile,
+ PR_WRONLY | PR_CREATE_FILE, 00600);
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::DiscardNewMessage(nsIOutputStream* aOutputStream,
+ nsIMsgDBHdr* aNewHdr) {
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+
+ aOutputStream->Close();
+ // file path is stored in message header property "storeToken"
+ nsAutoCString fileName;
+ aNewHdr->GetStringProperty("storeToken", fileName);
+ if (fileName.IsEmpty()) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> path;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // path to the message download folder
+ path->Append(u"tmp"_ns);
+ path->AppendNative(fileName);
+
+ return path->Remove(false);
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::FinishNewMessage(nsIOutputStream* aOutputStream,
+ nsIMsgDBHdr* aNewHdr) {
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+
+ aOutputStream->Close();
+
+ nsCOMPtr<nsIFile> folderPath;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // tmp filename is stored in "storeToken".
+ // By now we'll have the Message-ID, which we'll base the final filename on.
+ nsAutoCString tmpName;
+ aNewHdr->GetStringProperty("storeToken", tmpName);
+ if (tmpName.IsEmpty()) {
+ NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // path to the new destination
+ nsCOMPtr<nsIFile> curPath;
+ folderPath->Clone(getter_AddRefs(curPath));
+ curPath->Append(u"cur"_ns);
+
+ // let's check if the folder exists
+ // XXX TODO: kill this and make sure maildir creation includes cur/tmp
+ bool exists;
+ curPath->Exists(&exists);
+ if (!exists) {
+ rv = curPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // path to the downloaded message
+ nsCOMPtr<nsIFile> fromPath;
+ folderPath->Clone(getter_AddRefs(fromPath));
+ fromPath->Append(u"tmp"_ns);
+ fromPath->AppendNative(tmpName);
+
+ // Check that the message is still in tmp.
+ // XXX TODO: revisit this. I think it's needed because the
+ // pairing rules for:
+ // GetNewMsgOutputStream(), FinishNewMessage(),
+ // MoveNewlyDownloadedMessage() and DiscardNewMessage()
+ // are not well defined.
+ // If they are sorted out, this code can be removed.
+ fromPath->Exists(&exists);
+ if (!exists) {
+ // Perhaps the message has already moved. See bug 1028372 to fix this.
+ nsCOMPtr<nsIFile> existingPath;
+ curPath->Clone(getter_AddRefs(existingPath));
+ existingPath->AppendNative(tmpName);
+ existingPath->Exists(&exists);
+ if (exists) // then there is nothing to do
+ return NS_OK;
+
+ NS_ERROR("FinishNewMessage - oops! file does not exist!");
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ nsCString msgID;
+ aNewHdr->GetMessageId(getter_Copies(msgID));
+
+ nsCString baseName;
+ // For missing or suspiciously-short Message-IDs, use a timestamp
+ // instead.
+ // This also avoids some special filenames we can't use in windows (CON,
+ // AUX, NUL, LPT1 etc...). With an extension (eg "LPT4.txt") they're all
+ // below 9 chars.
+ if (msgID.Length() < 9) {
+ baseName.AppendInt(static_cast<int64_t>(PR_Now()));
+ } else {
+ percentEncode(msgID, baseName);
+ // No length limit on Message-Id header, but lets clip our filenames
+ // well below any MAX_PATH limits.
+ if (baseName.Length() > (128 - 4)) {
+ baseName.SetLength(128 - 4); // (4 for ".eml")
+ }
+ }
+
+ nsCOMPtr<nsIFile> toPath;
+ curPath->Clone(getter_AddRefs(toPath));
+ nsCString toName(baseName);
+ toName.Append(".eml");
+ toPath->AppendNative(toName);
+
+ // Using CreateUnique in case we have duplicate Message-Ids
+ rv = toPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_FAILED(rv)) {
+ // NS_ERROR_FILE_TOO_BIG means CreateUnique() bailed out at 10000 attempts.
+ if (rv != NS_ERROR_FILE_TOO_BIG) {
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // As a last resort, fall back to using timestamp as filename.
+ toName.SetLength(0);
+ toName.AppendInt(static_cast<int64_t>(PR_Now()));
+ toName.Append(".eml");
+ toPath->SetNativeLeafName(toName);
+ rv = toPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Move into place (using whatever name CreateUnique() settled upon).
+ toPath->GetNativeLeafName(toName);
+ rv = fromPath->MoveToNative(curPath, toName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Update the db to reflect the final filename.
+ aNewHdr->SetStringProperty("storeToken", toName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::MoveNewlyDownloadedMessage(nsIMsgDBHdr* aHdr,
+ nsIMsgFolder* aDestFolder,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aHdr);
+ NS_ENSURE_ARG_POINTER(aDestFolder);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsIFile> folderPath;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // file path is stored in message header property
+ nsAutoCString fileName;
+ aHdr->GetStringProperty("storeToken", fileName);
+ if (fileName.IsEmpty()) {
+ NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // path to the downloaded message
+ nsCOMPtr<nsIFile> fromPath;
+ folderPath->Clone(getter_AddRefs(fromPath));
+ fromPath->Append(u"cur"_ns);
+ fromPath->AppendNative(fileName);
+
+ // let's check if the tmp file exists
+ bool exists;
+ fromPath->Exists(&exists);
+ if (!exists) {
+ NS_ERROR("FinishNewMessage - oops! file does not exist!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // move to the "cur" subfolder
+ nsCOMPtr<nsIFile> toPath;
+ aDestFolder->GetFilePath(getter_AddRefs(folderPath));
+ folderPath->Clone(getter_AddRefs(toPath));
+ toPath->Append(u"cur"_ns);
+
+ // let's check if the folder exists
+ // XXX TODO: kill this and make sure maildir creation includes cur/tmp
+ toPath->Exists(&exists);
+ if (!exists) {
+ rv = toPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIMsgDatabase> destMailDB;
+ rv = aDestFolder->GetMsgDatabase(getter_AddRefs(destMailDB));
+ NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv),
+ "failed to open mail db moving message");
+
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ if (destMailDB)
+ rv = destMailDB->CopyHdrFromExistingHdr(nsMsgKey_None, aHdr, true,
+ getter_AddRefs(newHdr));
+ if (NS_SUCCEEDED(rv) && !newHdr) rv = NS_ERROR_UNEXPECTED;
+
+ if (NS_FAILED(rv)) {
+ aDestFolder->ThrowAlertMsg("filterFolderHdrAddFailed", nullptr);
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> existingPath;
+ toPath->Clone(getter_AddRefs(existingPath));
+ existingPath->AppendNative(fileName);
+ existingPath->Exists(&exists);
+
+ if (exists) {
+ rv = existingPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ existingPath->GetNativeLeafName(fileName);
+ newHdr->SetStringProperty("storeToken", fileName);
+ }
+
+ rv = fromPath->MoveToNative(toPath, fileName);
+ *aResult = NS_SUCCEEDED(rv);
+ if (NS_FAILED(rv))
+ aDestFolder->ThrowAlertMsg("filterFolderWriteFailed", nullptr);
+
+ if (NS_FAILED(rv)) {
+ if (destMailDB) destMailDB->Close(true);
+
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+
+ bool movedMsgIsNew = false;
+ // if we have made it this far then the message has successfully been
+ // written to the new folder now add the header to the destMailDB.
+
+ uint32_t newFlags;
+ newHdr->GetFlags(&newFlags);
+ nsMsgKey msgKey;
+ newHdr->GetMessageKey(&msgKey);
+ if (!(newFlags & nsMsgMessageFlags::Read)) {
+ nsCString junkScoreStr;
+ (void)newHdr->GetStringProperty("junkscore", junkScoreStr);
+ if (atoi(junkScoreStr.get()) != nsIJunkMailPlugin::IS_SPAM_SCORE) {
+ newHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
+ destMailDB->AddToNewList(msgKey);
+ movedMsgIsNew = true;
+ }
+ }
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyMsgAdded(newHdr);
+
+ if (movedMsgIsNew) {
+ aDestFolder->SetHasNewMessages(true);
+
+ // Notify the message was moved.
+ if (notifier) {
+ notifier->NotifyMsgUnincorporatedMoved(folder, newHdr);
+ }
+ }
+
+ nsCOMPtr<nsIMsgDatabase> sourceDB;
+ rv = folder->GetMsgDatabase(getter_AddRefs(sourceDB));
+
+ if (NS_SUCCEEDED(rv) && sourceDB) sourceDB->RemoveHeaderMdbRow(aHdr);
+
+ destMailDB->SetSummaryValid(true);
+ aDestFolder->UpdateSummaryTotals(true);
+ destMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::GetMsgInputStream(nsIMsgFolder* aMsgFolder,
+ const nsACString& aMsgToken,
+ nsIInputStream** aResult) {
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // construct path to file
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aMsgFolder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aMsgToken.IsEmpty()) {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("GetMsgInputStream - empty storeToken!!"));
+ return NS_ERROR_FAILURE;
+ }
+
+ path->Append(u"cur"_ns);
+
+ // let's check if the folder exists
+ // XXX TODO: kill this and make sure maildir creation includes cur/tmp
+ bool exists;
+ path->Exists(&exists);
+ if (!exists) {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("GetMsgInputStream - oops! cur subfolder does not exist!"));
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ path->AppendNative(aMsgToken);
+ return NS_NewLocalFileInputStream(aResult, path);
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::DeleteMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray) {
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ for (auto msgHdr : aHdrArray) {
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = folder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString fileName;
+ msgHdr->GetStringProperty("storeToken", fileName);
+
+ if (fileName.IsEmpty()) {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("DeleteMessages - empty storeToken!!"));
+ // Perhaps an offline store has not downloaded this particular message.
+ continue;
+ }
+
+ path->Append(u"cur"_ns);
+ path->AppendNative(fileName);
+
+ // Let's check if the message exists.
+ bool exists;
+ path->Exists(&exists);
+ if (!exists) {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("DeleteMessages - file does not exist !!"));
+ // Perhaps an offline store has not downloaded this particular message.
+ continue;
+ }
+ path->Remove(false);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::CopyMessages(bool aIsMove,
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray,
+ nsIMsgFolder* aDstFolder,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& aDstHdrs,
+ nsITransaction** aUndoAction, bool* aCopyDone) {
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+ NS_ENSURE_ARG_POINTER(aCopyDone);
+ NS_ENSURE_ARG_POINTER(aUndoAction);
+
+ *aCopyDone = false;
+ if (aHdrArray.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ nsresult rv;
+ nsIMsgDBHdr* msgHdr = aHdrArray[0];
+ rv = msgHdr->GetFolder(getter_AddRefs(srcFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Both source and destination folders must use maildir type store.
+ nsCOMPtr<nsIMsgPluggableStore> srcStore;
+ nsAutoCString srcType;
+ srcFolder->GetMsgStore(getter_AddRefs(srcStore));
+ if (srcStore) srcStore->GetStoreType(srcType);
+ nsCOMPtr<nsIMsgPluggableStore> dstStore;
+ nsAutoCString dstType;
+ aDstFolder->GetMsgStore(getter_AddRefs(dstStore));
+ if (dstStore) dstStore->GetStoreType(dstType);
+ if (!srcType.EqualsLiteral("maildir") || !dstType.EqualsLiteral("maildir"))
+ return NS_OK;
+
+ // Both source and destination must be local folders. In theory we could
+ // do efficient copies of the offline store of IMAP, but this is not
+ // supported yet. For that, we need to deal with both correct handling
+ // of deletes from the src server, and msgKey = UIDL in the dst folder.
+ nsCOMPtr<nsIMsgLocalMailFolder> destLocalFolder(
+ do_QueryInterface(aDstFolder));
+ if (!destLocalFolder) return NS_OK;
+ nsCOMPtr<nsIMsgLocalMailFolder> srcLocalFolder(do_QueryInterface(srcFolder));
+ if (!srcLocalFolder) return NS_OK;
+
+ // We should be able to use a file move for an efficient copy.
+
+ nsCOMPtr<nsIFile> destFolderPath;
+ nsCOMPtr<nsIMsgDatabase> destDB;
+ aDstFolder->GetMsgDatabase(getter_AddRefs(destDB));
+ rv = aDstFolder->GetFilePath(getter_AddRefs(destFolderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+ destFolderPath->Append(u"cur"_ns);
+
+ nsCOMPtr<nsIFile> srcFolderPath;
+ rv = srcFolder->GetFilePath(getter_AddRefs(srcFolderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+ srcFolderPath->Append(u"cur"_ns);
+
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ RefPtr<nsLocalMoveCopyMsgTxn> msgTxn = new nsLocalMoveCopyMsgTxn;
+ NS_ENSURE_TRUE(msgTxn, NS_ERROR_OUT_OF_MEMORY);
+ if (NS_SUCCEEDED(msgTxn->Init(srcFolder, aDstFolder, aIsMove))) {
+ if (aIsMove)
+ msgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ else
+ msgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ }
+
+ aDstHdrs.Clear();
+ aDstHdrs.SetCapacity(aHdrArray.Length());
+
+ for (auto srcHdr : aHdrArray) {
+ nsMsgKey srcKey;
+ srcHdr->GetMessageKey(&srcKey);
+ msgTxn->AddSrcKey(srcKey);
+ nsAutoCString fileName;
+ srcHdr->GetStringProperty("storeToken", fileName);
+ if (fileName.IsEmpty()) {
+ MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
+ ("GetMsgInputStream - empty storeToken!!"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> srcFile;
+ rv = srcFolderPath->Clone(getter_AddRefs(srcFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ srcFile->AppendNative(fileName);
+
+ nsCOMPtr<nsIFile> destFile;
+ destFolderPath->Clone(getter_AddRefs(destFile));
+ destFile->AppendNative(fileName);
+ bool exists;
+ destFile->Exists(&exists);
+ if (exists) {
+ rv = destFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ destFile->GetNativeLeafName(fileName);
+ }
+ if (aIsMove)
+ rv = srcFile->MoveToNative(destFolderPath, fileName);
+ else
+ rv = srcFile->CopyToNative(destFolderPath, fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> destHdr;
+ if (destDB) {
+ rv = destDB->CopyHdrFromExistingHdr(nsMsgKey_None, srcHdr, true,
+ getter_AddRefs(destHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ destHdr->SetStringProperty("storeToken", fileName);
+ aDstHdrs.AppendElement(destHdr);
+ nsMsgKey dstKey;
+ destHdr->GetMessageKey(&dstKey);
+ msgTxn->AddDstKey(dstKey);
+ }
+ }
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ notifier->NotifyMsgsMoveCopyCompleted(aIsMove, aHdrArray, aDstFolder,
+ aDstHdrs);
+ }
+
+ // For now, we only support local dest folders, and for those we are done and
+ // can delete the messages. Perhaps this should be moved into the folder
+ // when we try to support other folder types.
+ if (aIsMove) {
+ for (auto msgDBHdr : aHdrArray) {
+ srcDB->DeleteHeader(msgDBHdr, nullptr, false, true);
+ }
+ }
+
+ *aCopyDone = true;
+ msgTxn.forget(aUndoAction);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::GetSupportsCompaction(bool* aSupportsCompaction) {
+ NS_ENSURE_ARG_POINTER(aSupportsCompaction);
+ *aSupportsCompaction = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::CompactFolder(nsIMsgFolder* aFolder,
+ nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ return NS_OK;
+}
+
+class MaildirStoreParser {
+ public:
+ MaildirStoreParser(nsIMsgFolder* aFolder, nsIMsgDatabase* aMsgDB,
+ nsIDirectoryEnumerator* aDirectoryEnumerator,
+ nsIUrlListener* aUrlListener);
+ virtual ~MaildirStoreParser();
+
+ nsresult ParseNextMessage(nsIFile* aFile);
+ static void TimerCallback(nsITimer* aTimer, void* aClosure);
+ nsresult StartTimer();
+
+ nsCOMPtr<nsIDirectoryEnumerator> m_directoryEnumerator;
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsCOMPtr<nsIMsgDatabase> m_db;
+ nsCOMPtr<nsITimer> m_timer;
+ nsCOMPtr<nsIUrlListener> m_listener;
+};
+
+MaildirStoreParser::MaildirStoreParser(nsIMsgFolder* aFolder,
+ nsIMsgDatabase* aMsgDB,
+ nsIDirectoryEnumerator* aDirEnum,
+ nsIUrlListener* aUrlListener) {
+ m_folder = aFolder;
+ m_db = aMsgDB;
+ m_directoryEnumerator = aDirEnum;
+ m_listener = aUrlListener;
+}
+
+MaildirStoreParser::~MaildirStoreParser() {}
+
+nsresult MaildirStoreParser::ParseNextMessage(nsIFile* aFile) {
+ nsresult rv;
+ NS_ENSURE_TRUE(m_db, NS_ERROR_NULL_POINTER);
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIMsgParseMailMsgState> msgParser =
+ do_CreateInstance("@mozilla.org/messenger/messagestateparser;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgParser->SetMailDB(m_db);
+ nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
+ rv = m_db->CreateNewHdr(nsMsgKey_None, getter_AddRefs(newMsgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newMsgHdr->SetMessageOffset(0);
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+ if (NS_SUCCEEDED(rv) && inputStream) {
+ RefPtr<nsMsgLineStreamBuffer> inputStreamBuffer =
+ new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE, true, false);
+ int64_t fileSize;
+ aFile->GetFileSize(&fileSize);
+ msgParser->SetNewMsgHdr(newMsgHdr);
+ msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
+ bool needMoreData = false;
+ char* newLine = nullptr;
+ uint32_t numBytesInLine = 0;
+ // we only have to read the headers, because we know the message size
+ // from the file size. So we can do this in one time slice.
+ do {
+ newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine,
+ needMoreData);
+ if (newLine) {
+ msgParser->ParseAFolderLine(newLine, numBytesInLine);
+ free(newLine);
+ }
+ } while (newLine && numBytesInLine > 0);
+
+ msgParser->FinishHeader();
+ // A single message needs to be less than 4GB
+ newMsgHdr->SetMessageSize((uint32_t)fileSize);
+ m_db->AddNewHdrToDB(newMsgHdr, true);
+ nsAutoCString storeToken;
+ aFile->GetNativeLeafName(storeToken);
+ newMsgHdr->SetStringProperty("storeToken", storeToken);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+void MaildirStoreParser::TimerCallback(nsITimer* aTimer, void* aClosure) {
+ MaildirStoreParser* parser = (MaildirStoreParser*)aClosure;
+ bool hasMore;
+ parser->m_directoryEnumerator->HasMoreElements(&hasMore);
+ if (!hasMore) {
+ nsCOMPtr<nsIMsgPluggableStore> store;
+ parser->m_folder->GetMsgStore(getter_AddRefs(store));
+ parser->m_timer->Cancel();
+ if (parser->m_db) parser->m_db->SetSummaryValid(true);
+ // store->SetSummaryFileValid(parser->m_folder, parser->m_db, true);
+ if (parser->m_listener) {
+ nsresult rv;
+ nsCOMPtr<nsIMailboxUrl> mailboxurl =
+ do_CreateInstance("@mozilla.org/messenger/mailboxurl;1", &rv);
+ if (NS_SUCCEEDED(rv) && mailboxurl) {
+ nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(mailboxurl);
+ url->SetUpdatingFolder(true);
+ nsAutoCString uriSpec("mailbox://");
+ // ### TODO - what if SetSpec fails?
+ (void)url->SetSpecInternal(uriSpec);
+ parser->m_listener->OnStopRunningUrl(url, NS_OK);
+ }
+ }
+ // Parsing complete and timer cancelled, so we release the parser object.
+ delete parser;
+ return;
+ }
+ nsCOMPtr<nsIFile> currentFile;
+ nsresult rv =
+ parser->m_directoryEnumerator->GetNextFile(getter_AddRefs(currentFile));
+ if (NS_SUCCEEDED(rv)) rv = parser->ParseNextMessage(currentFile);
+ if (NS_FAILED(rv) && parser->m_listener)
+ parser->m_listener->OnStopRunningUrl(nullptr, NS_ERROR_FAILURE);
+}
+
+nsresult MaildirStoreParser::StartTimer() {
+ nsresult rv;
+ m_timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_timer->InitWithNamedFuncCallback(TimerCallback, (void*)this, 0,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "MaildirStoreParser::TimerCallback");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::RebuildIndex(nsIMsgFolder* aFolder,
+ nsIMsgDatabase* aMsgDB,
+ nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ // This code needs to iterate over the maildir files, and parse each
+ // file and add a msg hdr to the db for the file.
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aFolder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+ path->Append(u"cur"_ns);
+
+ nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
+ rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MaildirStoreParser* fileParser =
+ new MaildirStoreParser(aFolder, aMsgDB, directoryEnumerator, aListener);
+ NS_ENSURE_TRUE(fileParser, NS_ERROR_OUT_OF_MEMORY);
+ fileParser->StartTimer();
+ ResetForceReparse(aMsgDB);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::ChangeFlags(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray, uint32_t aFlags,
+ bool aSet) {
+ for (auto msgHdr : aHdrArray) {
+ // get output stream for header
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsresult rv = GetOutputStream(msgHdr, outputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Work out the flags we want to write.
+ uint32_t flags = 0;
+ (void)msgHdr->GetFlags(&flags);
+ flags &= ~(nsMsgMessageFlags::RuntimeOnly | nsMsgMessageFlags::Offline);
+ if (aSet) {
+ flags |= aFlags;
+ } else {
+ flags &= ~aFlags;
+ }
+
+ // Rewrite X-Mozilla-Status headers.
+ nsCOMPtr<nsISeekableStream> seekable(do_QueryInterface(outputStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = RewriteMsgFlags(seekable, flags);
+ if (NS_FAILED(rv)) NS_WARNING("updateFolderFlag failed");
+ }
+ return NS_OK;
+}
+
+// get output stream from header
+nsresult nsMsgMaildirStore::GetOutputStream(
+ nsIMsgDBHdr* aHdr, nsCOMPtr<nsIOutputStream>& aOutputStream) {
+ // file name is stored in message header property "storeToken"
+ nsAutoCString fileName;
+ aHdr->GetStringProperty("storeToken", fileName);
+ if (fileName.IsEmpty()) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> folderPath;
+ rv = folder->GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> maildirFile;
+ folderPath->Clone(getter_AddRefs(maildirFile));
+ maildirFile->Append(u"cur"_ns);
+ maildirFile->AppendNative(fileName);
+
+ return MsgGetFileStream(maildirFile, getter_AddRefs(aOutputStream));
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::ChangeKeywords(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray, const nsACString& aKeywords,
+ bool aAdd) {
+ if (aHdrArray.IsEmpty()) return NS_ERROR_INVALID_ARG;
+
+ nsTArray<nsCString> keywordsToAdd;
+ nsTArray<nsCString> keywordsToRemove;
+ if (aAdd) {
+ ParseString(aKeywords, ' ', keywordsToAdd);
+ } else {
+ ParseString(aKeywords, ' ', keywordsToRemove);
+ }
+
+ for (auto msgHdr : aHdrArray) {
+ // Open the message file.
+ nsCOMPtr<nsIOutputStream> output;
+ nsresult rv = GetOutputStream(msgHdr, output);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISeekableStream> seekable(do_QueryInterface(output, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool notEnoughRoom;
+ rv = ChangeKeywordsHelper(seekable, keywordsToAdd, keywordsToRemove,
+ notEnoughRoom);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (notEnoughRoom) {
+ // The growKeywords property indicates that the X-Mozilla-Keys header
+ // doesn't have enough space, and should be rebuilt during the next
+ // folder compaction.
+ // TODO: For maildir there is no compaction, so this'll have no effect!
+ msgHdr->SetUint32Property("growKeywords", 1);
+ }
+ output->Close();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMaildirStore::GetStoreType(nsACString& aType) {
+ aType.AssignLiteral("maildir");
+ return NS_OK;
+}
+
+/**
+ * Finds the directory associated with this folder. That is if the path is
+ * c:\Inbox, it will return c:\Inbox.sbd if it succeeds. Path is strictly
+ * an out parameter.
+ */
+nsresult nsMsgMaildirStore::GetDirectoryForFolder(nsIFile* path) {
+ // add directory separator to the path
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ return path->SetLeafName(leafName);
+}
+
+nsresult nsMsgMaildirStore::CreateDirectoryForFolder(nsIFile* path,
+ bool aIsServer) {
+ nsresult rv = NS_OK;
+ if (!aIsServer) {
+ rv = GetDirectoryForFolder(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ bool pathIsDirectory = false;
+ path->IsDirectory(&pathIsDirectory);
+ if (!pathIsDirectory) {
+ bool pathExists;
+ path->Exists(&pathExists);
+ // If for some reason there's a file with the directory separator
+ // then we are going to fail.
+ rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY
+ : path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgMaildirStore::SliceStream(nsIInputStream* inStream, uint64_t start,
+ uint32_t length, nsIInputStream** result) {
+ nsCOMPtr<nsIInputStream> in(inStream);
+ RefPtr<mozilla::SlicedInputStream> slicedStream =
+ new mozilla::SlicedInputStream(in.forget(), start, uint64_t(length));
+ slicedStream.forget(result);
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsMsgMaildirStore.h b/comm/mailnews/local/src/nsMsgMaildirStore.h
new file mode 100644
index 0000000000..0ab95be6c0
--- /dev/null
+++ b/comm/mailnews/local/src/nsMsgMaildirStore.h
@@ -0,0 +1,39 @@
+/* -*- 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/. */
+
+/**
+ Class for handling Maildir stores.
+*/
+
+#ifndef nsMsgMaildirStore_h__
+#define nsMsgMaildirStore_h__
+
+#include "nsMsgLocalStoreUtils.h"
+#include "nsIOutputStream.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIFile.h"
+#include "nsMsgMessageFlags.h"
+
+class nsMsgMaildirStore final : public nsMsgLocalStoreUtils,
+ nsIMsgPluggableStore {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPLUGGABLESTORE
+
+ nsMsgMaildirStore();
+
+ private:
+ ~nsMsgMaildirStore();
+
+ protected:
+ nsresult GetDirectoryForFolder(nsIFile* path);
+ nsresult CreateDirectoryForFolder(nsIFile* path, bool aIsServer);
+
+ nsresult CreateMaildir(nsIFile* path);
+ nsresult AddSubFolders(nsIMsgFolder* parent, nsIFile* path, bool deep);
+ nsresult GetOutputStream(nsIMsgDBHdr* aHdr,
+ nsCOMPtr<nsIOutputStream>& aOutputStream);
+};
+#endif
diff --git a/comm/mailnews/local/src/nsNoIncomingServer.cpp b/comm/mailnews/local/src/nsNoIncomingServer.cpp
new file mode 100644
index 0000000000..b3a351eda6
--- /dev/null
+++ b/comm/mailnews/local/src/nsNoIncomingServer.cpp
@@ -0,0 +1,189 @@
+/* -*- 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" // pre-compiled headers
+
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+#include "nsNoIncomingServer.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgMailSession.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNoIncomingServer, nsMsgIncomingServer,
+ nsINoIncomingServer, nsILocalMailIncomingServer)
+
+nsNoIncomingServer::nsNoIncomingServer() {}
+
+nsNoIncomingServer::~nsNoIncomingServer() {}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetLocalStoreType(nsACString& type) {
+ type.AssignLiteral("mailbox");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetLocalDatabaseType(nsACString& type) {
+ type.AssignLiteral("mailbox");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetAccountManagerChrome(nsAString& aResult) {
+ aResult.AssignLiteral("am-serverwithnoidentities.xhtml");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::SetFlagsOnDefaultMailboxes() {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
+ do_QueryInterface(rootFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // None server may have an inbox if it's deferred to,
+ // or if it's the smart mailboxes account.
+ localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::SpecialUse);
+
+ return NS_OK;
+}
+
+// TODO: make this work with maildir message store, bug 890742.
+NS_IMETHODIMP nsNoIncomingServer::CopyDefaultMessages(
+ const char* folderNameOnDisk) {
+ NS_ENSURE_ARG(folderNameOnDisk);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get defaults directory for messenger files. MailSession service appends
+ // 'messenger' to the the app defaults folder and returns it. Locale will be
+ // added to the path, if there is one.
+ nsCOMPtr<nsIFile> defaultMessagesFile;
+ rv = mailSession->GetDataFilesDir("messenger",
+ getter_AddRefs(defaultMessagesFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if bin/defaults/messenger/<folderNameOnDisk>
+ // (or bin/defaults/messenger/<locale>/<folderNameOnDisk> if we had a locale
+ // provide) exists. it doesn't have to exist. if it doesn't, return
+ rv = defaultMessagesFile->AppendNative(nsDependentCString(folderNameOnDisk));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = defaultMessagesFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) return NS_OK;
+
+ nsCOMPtr<nsIFile> parentDir;
+ rv = GetLocalPath(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if parentDir/<folderNameOnDisk> exists
+ {
+ nsCOMPtr<nsIFile> testDir;
+ rv = parentDir->Clone(getter_AddRefs(testDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = testDir->AppendNative(nsDependentCString(folderNameOnDisk));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = testDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // if it exists add to the end, else copy
+ if (exists) {
+#ifdef DEBUG
+ printf("append default %s (unimplemented)\n", folderNameOnDisk);
+#endif
+ // todo for bug #1181 (the bug ID seems wrong...)
+ // open folderFile, seek to end
+ // read defaultMessagesFile, write to folderFile
+ } else {
+#ifdef DEBUG
+ printf("copy default %s\n", folderNameOnDisk);
+#endif
+ rv = defaultMessagesFile->CopyTo(parentDir, EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNoIncomingServer::CreateDefaultMailboxes() {
+ nsresult rv;
+ bool isHidden = false;
+ GetHidden(&isHidden);
+ if (isHidden) return NS_OK;
+
+ // notice, no Inbox, unless we're deferred to...
+ bool isDeferredTo;
+ if (NS_SUCCEEDED(GetIsDeferredTo(&isDeferredTo)) && isDeferredTo) {
+ rv = CreateLocalFolder(u"Inbox"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = CreateLocalFolder(u"Trash"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // copy the default templates into the Templates folder
+ rv = CopyDefaultMessages("Templates");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CreateLocalFolder(u"Unsent Messages"_ns);
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetNewMail(nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aUrlListener,
+ nsIMsgFolder* aInbox, nsIURI** aResult) {
+ if (aResult) {
+ *aResult = nullptr;
+ }
+ nsTArray<RefPtr<nsIPop3IncomingServer>> deferredServers;
+ nsresult rv = GetDeferredServers(this, deferredServers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!deferredServers.IsEmpty()) {
+ rv = deferredServers[0]->DownloadMailFromServers(
+ deferredServers, aMsgWindow, aInbox, aUrlListener);
+ }
+ // listener might be counting on us to send a notification.
+ else if (aUrlListener)
+ aUrlListener->OnStopRunningUrl(nullptr, NS_OK);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetCanSearchMessages(bool* canSearchMessages) {
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ *canSearchMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetServerRequiresPasswordForBiff(
+ bool* aServerRequiresPasswordForBiff) {
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ *aServerRequiresPasswordForBiff =
+ false; // for local folders, we don't require a password
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoIncomingServer::GetSortOrder(int32_t* aSortOrder) {
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = 200000000;
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsNoIncomingServer.h b/comm/mailnews/local/src/nsNoIncomingServer.h
new file mode 100644
index 0000000000..79d09baa3a
--- /dev/null
+++ b/comm/mailnews/local/src/nsNoIncomingServer.h
@@ -0,0 +1,41 @@
+/* -*- 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 __nsNoIncomingServer_h
+#define __nsNoIncomingServer_h
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsINoIncomingServer.h"
+#include "nsILocalMailIncomingServer.h"
+#include "nsMsgIncomingServer.h"
+#include "nsMailboxServer.h"
+
+/* get some implementation from nsMsgIncomingServer */
+class nsNoIncomingServer : public nsMailboxServer,
+ public nsINoIncomingServer,
+ public nsILocalMailIncomingServer
+
+{
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINOINCOMINGSERVER
+ NS_DECL_NSILOCALMAILINCOMINGSERVER
+
+ nsNoIncomingServer();
+
+ NS_IMETHOD GetLocalStoreType(nsACString& type) override;
+ NS_IMETHOD GetLocalDatabaseType(nsACString& type) override;
+ NS_IMETHOD GetCanSearchMessages(bool* canSearchMessages) override;
+ NS_IMETHOD GetServerRequiresPasswordForBiff(
+ bool* aServerRequiresPasswordForBiff) override;
+ NS_IMETHOD GetAccountManagerChrome(nsAString& aResult) override;
+ NS_IMETHOD GetSortOrder(int32_t* aSortOrder) override;
+
+ private:
+ virtual ~nsNoIncomingServer();
+};
+
+#endif
diff --git a/comm/mailnews/local/src/nsNoneService.cpp b/comm/mailnews/local/src/nsNoneService.cpp
new file mode 100644
index 0000000000..74b03dc93e
--- /dev/null
+++ b/comm/mailnews/local/src/nsNoneService.cpp
@@ -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/. */
+
+#include "msgCore.h" // precompiled header...
+
+#include "nsNoneService.h"
+#include "nsINoIncomingServer.h"
+#include "nsINoneService.h"
+#include "nsIMsgProtocolInfo.h"
+
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsMsgUtils.h"
+
+#include "nsMailDirServiceDefs.h"
+
+#define PREF_MAIL_ROOT_NONE_REL "mail.root.none-rel"
+// old - for backward compatibility only
+#define PREF_MAIL_ROOT_NONE "mail.root.none"
+
+nsNoneService::nsNoneService() {}
+
+nsNoneService::~nsNoneService() {}
+
+NS_IMPL_ISUPPORTS(nsNoneService, nsINoneService, nsIMsgProtocolInfo)
+
+NS_IMETHODIMP
+nsNoneService::SetDefaultLocalPath(nsIFile* aPath) {
+ NS_ENSURE_ARG(aPath);
+ return NS_SetPersistentFile(PREF_MAIL_ROOT_NONE_REL, PREF_MAIL_ROOT_NONE,
+ aPath);
+}
+
+NS_IMETHODIMP
+nsNoneService::GetDefaultLocalPath(nsIFile** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ bool havePref;
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_GetPersistentFile(PREF_MAIL_ROOT_NONE_REL,
+ PREF_MAIL_ROOT_NONE, NS_APP_MAIL_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);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!havePref || !exists) {
+ rv = NS_SetPersistentFile(PREF_MAIL_ROOT_NONE_REL, PREF_MAIL_ROOT_NONE,
+ localFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
+ }
+
+ localFile.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetServerIID(nsIID** aServerIID) {
+ *aServerIID = new nsIID(NS_GET_IID(nsINoIncomingServer));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetRequiresUsername(bool* aRequiresUsername) {
+ NS_ENSURE_ARG_POINTER(aRequiresUsername);
+ *aRequiresUsername = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetPreflightPrettyNameWithEmailAddress(
+ bool* aPreflightPrettyNameWithEmailAddress) {
+ NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress);
+ *aPreflightPrettyNameWithEmailAddress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetCanLoginAtStartUp(bool* aCanLoginAtStartUp) {
+ NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp);
+ *aCanLoginAtStartUp = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetCanDelete(bool* aCanDelete) {
+ NS_ENSURE_ARG_POINTER(aCanDelete);
+ *aCanDelete = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetCanDuplicate(bool* aCanDuplicate) {
+ NS_ENSURE_ARG_POINTER(aCanDuplicate);
+ *aCanDuplicate = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetCanGetMessages(bool* aCanGetMessages) {
+ NS_ENSURE_ARG_POINTER(aCanGetMessages);
+ *aCanGetMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetCanGetIncomingMessages(bool* aCanGetIncomingMessages) {
+ NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages);
+ *aCanGetIncomingMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetDefaultDoBiff(bool* aDoBiff) {
+ NS_ENSURE_ARG_POINTER(aDoBiff);
+ // by default, don't do biff for "none" servers
+ *aDoBiff = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetDefaultServerPort(bool isSecure, int32_t* aDefaultPort) {
+ NS_ENSURE_ARG_POINTER(aDefaultPort);
+ *aDefaultPort = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetShowComposeMsgLink(bool* showComposeMsgLink) {
+ NS_ENSURE_ARG_POINTER(showComposeMsgLink);
+ *showComposeMsgLink = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoneService::GetFoldersCreatedAsync(bool* aAsyncCreation) {
+ NS_ENSURE_ARG_POINTER(aAsyncCreation);
+ *aAsyncCreation = false;
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsNoneService.h b/comm/mailnews/local/src/nsNoneService.h
new file mode 100644
index 0000000000..f87b25b846
--- /dev/null
+++ b/comm/mailnews/local/src/nsNoneService.h
@@ -0,0 +1,26 @@
+/* -*- 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 nsNoneService_h___
+#define nsNoneService_h___
+
+#include "nscore.h"
+
+#include "nsIMsgProtocolInfo.h"
+#include "nsINoneService.h"
+
+class nsNoneService : public nsIMsgProtocolInfo, public nsINoneService {
+ public:
+ nsNoneService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPROTOCOLINFO
+ NS_DECL_NSINONESERVICE
+
+ private:
+ virtual ~nsNoneService();
+};
+
+#endif /* nsNoneService_h___ */
diff --git a/comm/mailnews/local/src/nsParseMailbox.cpp b/comm/mailnews/local/src/nsParseMailbox.cpp
new file mode 100644
index 0000000000..7dc3c4d5b7
--- /dev/null
+++ b/comm/mailnews/local/src/nsParseMailbox.cpp
@@ -0,0 +1,2354 @@
+/* -*- 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 "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsParseMailbox.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIInputStream.h"
+#include "nsIFile.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsIMailboxUrl.h"
+#include "nsNetUtil.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIMsgFilterHitNotify.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsMsgI18N.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsMsgUtils.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsMsgSearchCore.h"
+#include "nsMailHeaders.h"
+#include "nsIMsgMailSession.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIMsgComposeService.h"
+#include "nsIMsgCopyService.h"
+#include "nsICryptoHash.h"
+#include "nsIStringBundle.h"
+#include "nsPrintfCString.h"
+#include "nsIMsgFilterCustomAction.h"
+#include <ctype.h>
+#include "nsIMsgPluggableStore.h"
+#include "mozilla/Components.h"
+#include "nsQueryObject.h"
+#include "nsIOutputStream.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+extern LazyLogModule FILTERLOGMODULE;
+
+/* the following macros actually implement addref, release and query interface
+ * for our component. */
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgMailboxParser, nsParseMailMessageState,
+ nsIStreamListener, nsIRequestObserver)
+
+// Whenever data arrives from the connection, core netlib notifices the protocol
+// by calling OnDataAvailable. We then read and process the incoming data from
+// the input stream.
+NS_IMETHODIMP nsMsgMailboxParser::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* aIStream,
+ uint64_t sourceOffset,
+ uint32_t aLength) {
+ return ProcessMailboxInputStream(aIStream, aLength);
+}
+
+NS_IMETHODIMP nsMsgMailboxParser::OnStartRequest(nsIRequest* request) {
+ // extract the appropriate event sinks from the url and initialize them in our
+ // protocol data the URL should be queried for a nsIMailboxURL. If it doesn't
+ // support a mailbox URL interface then we have an error.
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIIOService> ioServ = mozilla::components::IO::Service();
+ NS_ENSURE_TRUE(ioServ, NS_ERROR_UNEXPECTED);
+
+ // We know the request is an nsIChannel we can get a URI from, but this is
+ // probably bad form. See Bug 1528662.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "error QI nsIRequest to nsIChannel failed");
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMailboxUrl> runningUrl = do_QueryInterface(uri, &rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(uri);
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
+
+ if (NS_SUCCEEDED(rv) && runningUrl && folder) {
+ url->GetStatusFeedback(getter_AddRefs(m_statusFeedback));
+
+ // okay, now fill in our event sinks...Note that each getter ref counts
+ // before it returns the interface to us...we'll release when we are done
+
+ folder->GetName(m_folderName);
+
+ nsCOMPtr<nsIFile> path;
+ folder->GetFilePath(getter_AddRefs(path));
+
+ if (path) {
+ int64_t fileSize;
+ path->GetFileSize(&fileSize);
+ // the size of the mailbox file is our total base line for measuring
+ // progress
+ m_graph_progress_total = fileSize;
+ UpdateStatusText("buildingSummary");
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ if (msgDBService) {
+ // Use OpenFolderDB to always open the db so that db's m_folder
+ // is set correctly.
+ rv = msgDBService->OpenFolderDB(folder, true, getter_AddRefs(m_mailDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ rv = msgDBService->CreateNewDB(folder, getter_AddRefs(m_mailDB));
+
+ if (m_mailDB) m_mailDB->AddListener(this);
+ }
+ NS_ASSERTION(m_mailDB, "failed to open mail db parsing folder");
+
+ // try to get a backup message database
+ nsresult rvignore =
+ folder->GetBackupMsgDatabase(getter_AddRefs(m_backupMailDB));
+
+ // We'll accept failures and move on, as we're dealing with some
+ // sort of unknown problem to begin with.
+ if (NS_FAILED(rvignore)) {
+ if (m_backupMailDB) m_backupMailDB->RemoveListener(this);
+ m_backupMailDB = nullptr;
+ } else if (m_backupMailDB) {
+ m_backupMailDB->AddListener(this);
+ }
+ }
+ }
+
+ // need to get the mailbox name out of the url and call SetMailboxName with
+ // it. then, we need to open the mail db for this parser.
+ return rv;
+}
+
+// stop binding is a "notification" informing us that the stream associated with
+// aURL is going away.
+NS_IMETHODIMP nsMsgMailboxParser::OnStopRequest(nsIRequest* request,
+ nsresult aStatus) {
+ DoneParsingFolder(aStatus);
+ // what can we do? we can close the stream?
+
+ if (m_mailDB) m_mailDB->RemoveListener(this);
+ // and we want to mark ourselves for deletion or some how inform our protocol
+ // manager that we are available for another url if there is one....
+
+ ReleaseFolderLock();
+ // be sure to clear any status text and progress info..
+ m_graph_progress_received = 0;
+ UpdateProgressPercent();
+ UpdateStatusText("localStatusDocumentDone");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParseMailMessageState::OnHdrPropertyChanged(
+ nsIMsgDBHdr* aHdrToChange, const nsACString& property, bool aPreChange,
+ uint32_t* aStatus, nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParseMailMessageState::OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged,
+ uint32_t aOldFlags,
+ uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParseMailMessageState::OnHdrDeleted(nsIMsgDBHdr* aHdrChanged,
+ nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParseMailMessageState::OnHdrAdded(nsIMsgDBHdr* aHdrAdded, nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+/* void OnParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in
+ * nsMsgKey newParent, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP
+nsParseMailMessageState::OnParentChanged(nsMsgKey aKeyChanged,
+ nsMsgKey oldParent, nsMsgKey newParent,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+/* void OnAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */
+NS_IMETHODIMP
+nsParseMailMessageState::OnAnnouncerGoingAway(
+ nsIDBChangeAnnouncer* instigator) {
+ if (m_backupMailDB && m_backupMailDB == instigator) {
+ m_backupMailDB->RemoveListener(this);
+ m_backupMailDB = nullptr;
+ } else if (m_mailDB) {
+ m_mailDB->RemoveListener(this);
+ m_mailDB = nullptr;
+ m_newMsgHdr = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::OnEvent(nsIMsgDatabase* aDB,
+ const char* aEvent) {
+ return NS_OK;
+}
+
+/* void OnReadChanged (in nsIDBChangeListener instigator); */
+NS_IMETHODIMP
+nsParseMailMessageState::OnReadChanged(nsIDBChangeListener* instigator) {
+ return NS_OK;
+}
+
+/* void OnJunkScoreChanged (in nsIDBChangeListener instigator); */
+NS_IMETHODIMP
+nsParseMailMessageState::OnJunkScoreChanged(nsIDBChangeListener* instigator) {
+ return NS_OK;
+}
+
+nsMsgMailboxParser::nsMsgMailboxParser() : nsMsgLineBuffer() { Init(); }
+
+nsMsgMailboxParser::nsMsgMailboxParser(nsIMsgFolder* aFolder)
+ : nsMsgLineBuffer() {
+ m_folder = do_GetWeakReference(aFolder);
+}
+
+nsMsgMailboxParser::~nsMsgMailboxParser() { ReleaseFolderLock(); }
+
+nsresult nsMsgMailboxParser::Init() {
+ m_graph_progress_total = 0;
+ m_graph_progress_received = 0;
+ return AcquireFolderLock();
+}
+
+void nsMsgMailboxParser::UpdateStatusText(const char* stringName) {
+ if (m_statusFeedback) {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ if (!bundleService) return;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/localMsgs.properties",
+ getter_AddRefs(bundle));
+ if (NS_FAILED(rv)) return;
+ nsString finalString;
+ AutoTArray<nsString, 1> stringArray = {m_folderName};
+ rv = bundle->FormatStringFromName(stringName, stringArray, finalString);
+ m_statusFeedback->ShowStatusString(finalString);
+ }
+}
+
+void nsMsgMailboxParser::UpdateProgressPercent() {
+ if (m_statusFeedback && m_graph_progress_total != 0) {
+ // prevent overflow by dividing both by 100
+ int64_t progressTotal = m_graph_progress_total / 100;
+ int64_t progressReceived = m_graph_progress_received / 100;
+ if (progressTotal > 0)
+ m_statusFeedback->ShowProgress((100 * (progressReceived)) /
+ progressTotal);
+ }
+}
+
+nsresult nsMsgMailboxParser::ProcessMailboxInputStream(nsIInputStream* aIStream,
+ uint32_t aLength) {
+ nsresult ret = NS_OK;
+
+ uint32_t bytesRead = 0;
+
+ if (NS_SUCCEEDED(m_inputStream.GrowBuffer(aLength))) {
+ // OK, this sucks, but we're going to have to copy into our
+ // own byte buffer, and then pass that to the line buffering code,
+ // which means a couple buffer copies.
+ ret = aIStream->Read(m_inputStream.GetBuffer(), aLength, &bytesRead);
+ if (NS_SUCCEEDED(ret))
+ ret = BufferInput(m_inputStream.GetBuffer(), bytesRead);
+ }
+ if (m_graph_progress_total > 0) {
+ if (NS_SUCCEEDED(ret)) m_graph_progress_received += bytesRead;
+ }
+ return (ret);
+}
+
+void nsMsgMailboxParser::DoneParsingFolder(nsresult status) {
+ // End of file. Flush out any data remaining in the buffer.
+ Flush();
+ PublishMsgHeader(nullptr);
+
+ // only mark the db valid if we've succeeded.
+ if (NS_SUCCEEDED(status) &&
+ m_mailDB) // finished parsing, so flush db folder info
+ UpdateDBFolderInfo();
+ else if (m_mailDB)
+ m_mailDB->SetSummaryValid(false);
+
+ // remove the backup database
+ if (m_backupMailDB) {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
+ if (folder) folder->RemoveBackupMsgDatabase();
+ m_backupMailDB = nullptr;
+ }
+}
+
+void nsMsgMailboxParser::UpdateDBFolderInfo() { UpdateDBFolderInfo(m_mailDB); }
+
+// update folder info in db so we know not to reparse.
+void nsMsgMailboxParser::UpdateDBFolderInfo(nsIMsgDatabase* mailDB) {
+ mailDB->SetSummaryValid(true);
+}
+
+// Tell the world about the message header (add to db, and view, if any)
+int32_t nsMsgMailboxParser::PublishMsgHeader(nsIMsgWindow* msgWindow) {
+ FinishHeader();
+ if (m_newMsgHdr) {
+ nsCString storeToken = nsPrintfCString("%" PRIu64, m_envelope_pos);
+ m_newMsgHdr->SetStringProperty("storeToken", storeToken);
+ m_newMsgHdr->SetMessageOffset(m_envelope_pos);
+
+ uint32_t flags;
+ (void)m_newMsgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Expunged) {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ m_mailDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ uint32_t size;
+ (void)m_newMsgHdr->GetMessageSize(&size);
+ folderInfo->ChangeExpungedBytes(size);
+ m_newMsgHdr = nullptr;
+ } else if (m_mailDB) {
+ // add hdr but don't notify - shouldn't be requiring notifications
+ // during summary file rebuilding
+ m_mailDB->AddNewHdrToDB(m_newMsgHdr, false);
+ m_newMsgHdr = nullptr;
+ } else
+ NS_ASSERTION(
+ false,
+ "no database while parsing local folder"); // should have a DB, no?
+ } else if (m_mailDB) {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ m_mailDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (folderInfo)
+ folderInfo->ChangeExpungedBytes(m_position - m_envelope_pos);
+ }
+ return 0;
+}
+
+void nsMsgMailboxParser::AbortNewHeader() {
+ if (m_newMsgHdr && m_mailDB) m_newMsgHdr = nullptr;
+}
+
+void nsMsgMailboxParser::OnNewMessage(nsIMsgWindow* msgWindow) {
+ PublishMsgHeader(msgWindow);
+ Clear();
+}
+
+nsresult nsMsgMailboxParser::HandleLine(const char* line, uint32_t lineLength) {
+ /* If this is the very first line of a non-empty folder, make sure it's an
+ * envelope */
+ if (m_graph_progress_received == 0) {
+ /* This is the first block from the file. Check to see if this
+ looks like a mail file. */
+ const char* s = line;
+ const char* end = s + lineLength;
+ while (s < end && IS_SPACE(*s)) s++;
+ if ((end - s) < 20 || !IsEnvelopeLine(s, end - s)) {
+ // char buf[500];
+ // PR_snprintf (buf, sizeof(buf),
+ // XP_GetString(MK_MSG_NON_MAIL_FILE_READ_QUESTION),
+ // folder_name);
+ // else if (!FE_Confirm (m_context, buf))
+ // return NS_MSG_NOT_A_MAIL_FOLDER; /* #### NOT_A_MAIL_FILE */
+ }
+ }
+ // m_graph_progress_received += lineLength;
+
+ // mailbox parser needs to do special stuff when it finds an envelope
+ // after parsing a message body. So do that.
+ if (line[0] == 'F' && IsEnvelopeLine(line, lineLength)) {
+ // **** This used to be
+ // PR_ASSERT (m_parseMsgState->m_state == nsMailboxParseBodyState);
+ // **** I am not sure this is a right thing to do. This happens when
+ // going online, downloading a message while playing back append
+ // draft/template offline operation. We are mixing
+ // nsMailboxParseBodyState &&
+ // nsMailboxParseHeadersState. David I need your help here too. **** jt
+
+ NS_ASSERTION(m_state == nsIMsgParseMailMsgState::ParseBodyState ||
+ m_state == nsIMsgParseMailMsgState::ParseHeadersState,
+ "invalid parse state"); /* else folder corrupted */
+ OnNewMessage(nullptr);
+ nsresult rv = StartNewEnvelope(line, lineLength);
+ NS_ASSERTION(NS_SUCCEEDED(rv), " error starting envelope parsing mailbox");
+ // at the start of each new message, update the progress bar
+ UpdateProgressPercent();
+ return rv;
+ }
+
+ // otherwise, the message parser can handle it completely.
+ if (m_mailDB != nullptr) // if no DB, do we need to parse at all?
+ return ParseFolderLine(line, lineLength);
+
+ return NS_ERROR_NULL_POINTER; // need to error out if we don't have a db.
+}
+
+void nsMsgMailboxParser::ReleaseFolderLock() {
+ nsresult result;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
+ if (!folder) return;
+ bool haveSemaphore;
+ nsCOMPtr<nsISupports> supports =
+ do_QueryInterface(static_cast<nsIMsgParseMailMsgState*>(this));
+ result = folder->TestSemaphore(supports, &haveSemaphore);
+ if (NS_SUCCEEDED(result) && haveSemaphore)
+ (void)folder->ReleaseSemaphore(supports);
+}
+
+nsresult nsMsgMailboxParser::AcquireFolderLock() {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
+ if (!folder) return NS_ERROR_NULL_POINTER;
+ nsCOMPtr<nsISupports> supports = do_QueryObject(this);
+ return folder->AcquireSemaphore(supports);
+}
+
+NS_IMPL_ISUPPORTS(nsParseMailMessageState, nsIMsgParseMailMsgState,
+ nsIDBChangeListener)
+
+nsParseMailMessageState::nsParseMailMessageState() {
+ m_position = 0;
+ m_new_key = nsMsgKey_None;
+ m_state = nsIMsgParseMailMsgState::ParseBodyState;
+
+ // setup handling of custom db headers, headers that are added to .msf files
+ // as properties of the nsMsgHdr objects, controlled by the
+ // pref mailnews.customDBHeaders, a space-delimited list of headers.
+ // E.g., if mailnews.customDBHeaders is "X-Spam-Score", and we're parsing
+ // a mail message with the X-Spam-Score header, we'll set the
+ // "x-spam-score" property of nsMsgHdr to the value of the header.
+ m_customDBHeaderValues = nullptr;
+ nsCString customDBHeaders; // not shown in search UI
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (!pPrefBranch) {
+ return;
+ }
+ pPrefBranch->GetCharPref("mailnews.customDBHeaders", customDBHeaders);
+ ToLowerCase(customDBHeaders);
+ if (customDBHeaders.Find("content-base") == -1)
+ customDBHeaders.InsertLiteral("content-base ", 0);
+ ParseString(customDBHeaders, ' ', m_customDBHeaders);
+
+ // now add customHeaders
+ nsCString customHeadersString; // shown in search UI
+ nsTArray<nsCString> customHeadersArray;
+ pPrefBranch->GetCharPref("mailnews.customHeaders", customHeadersString);
+ ToLowerCase(customHeadersString);
+ customHeadersString.StripWhitespace();
+ ParseString(customHeadersString, ':', customHeadersArray);
+ for (uint32_t i = 0; i < customHeadersArray.Length(); i++) {
+ if (!m_customDBHeaders.Contains(customHeadersArray[i]))
+ m_customDBHeaders.AppendElement(customHeadersArray[i]);
+ }
+
+ if (m_customDBHeaders.Length()) {
+ m_customDBHeaderValues =
+ new struct message_header[m_customDBHeaders.Length()];
+ }
+ Clear();
+}
+
+nsParseMailMessageState::~nsParseMailMessageState() {
+ ClearAggregateHeader(m_toList);
+ ClearAggregateHeader(m_ccList);
+ delete[] m_customDBHeaderValues;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::Clear() {
+ m_message_id.length = 0;
+ m_references.length = 0;
+ m_date.length = 0;
+ m_delivery_date.length = 0;
+ m_from.length = 0;
+ m_sender.length = 0;
+ m_newsgroups.length = 0;
+ m_subject.length = 0;
+ m_status.length = 0;
+ m_mozstatus.length = 0;
+ m_mozstatus2.length = 0;
+ m_envelope_from.length = 0;
+ m_envelope_date.length = 0;
+ m_priority.length = 0;
+ m_keywords.length = 0;
+ m_mdn_dnt.length = 0;
+ m_return_path.length = 0;
+ m_account_key.length = 0;
+ m_in_reply_to.length = 0;
+ m_replyTo.length = 0;
+ m_content_type.length = 0;
+ m_mdn_original_recipient.length = 0;
+ m_bccList.length = 0;
+ m_body_lines = 0;
+ m_lastLineBlank = 0;
+ m_newMsgHdr = nullptr;
+ m_envelope_pos = 0;
+ m_new_key = nsMsgKey_None;
+ ClearAggregateHeader(m_toList);
+ ClearAggregateHeader(m_ccList);
+ m_headers.ResetWritePos();
+ m_envelope.ResetWritePos();
+ m_receivedTime = 0;
+ m_receivedValue.Truncate();
+ for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++) {
+ m_customDBHeaderValues[i].length = 0;
+ }
+ m_headerstartpos = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetState(nsMailboxParseState aState) {
+ m_state = aState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::GetState(nsMailboxParseState* aState) {
+ if (!aState) return NS_ERROR_NULL_POINTER;
+
+ *aState = m_state;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::GetNewMsgHdr(nsIMsgDBHdr** aMsgHeader) {
+ NS_ENSURE_ARG_POINTER(aMsgHeader);
+ NS_IF_ADDREF(*aMsgHeader = m_newMsgHdr);
+ return m_newMsgHdr ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetNewMsgHdr(nsIMsgDBHdr* aMsgHeader) {
+ m_newMsgHdr = aMsgHeader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::ParseAFolderLine(const char* line,
+ uint32_t lineLength) {
+ ParseFolderLine(line, lineLength);
+ return NS_OK;
+}
+
+nsresult nsParseMailMessageState::ParseFolderLine(const char* line,
+ uint32_t lineLength) {
+ nsresult rv;
+
+ if (m_state == nsIMsgParseMailMsgState::ParseHeadersState) {
+ if (EMPTY_MESSAGE_LINE(line)) {
+ /* End of headers. Now parse them. */
+ rv = ParseHeaders();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error parsing headers parsing mailbox");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = FinalizeHeaders();
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "error finalizing headers parsing mailbox");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_state = nsIMsgParseMailMsgState::ParseBodyState;
+ } else {
+ /* Otherwise, this line belongs to a header. So append it to the
+ header data, and stay in MBOX `MIME_PARSE_HEADERS' state.
+ */
+ m_headers.AppendBuffer(line, lineLength);
+ }
+ } else if (m_state == nsIMsgParseMailMsgState::ParseBodyState) {
+ m_body_lines++;
+ // See comment in msgCore.h for why we use `IS_MSG_LINEBREAK` rather than
+ // just comparing `line` to `MSG_LINEBREAK`.
+ m_lastLineBlank = IS_MSG_LINEBREAK(line);
+ }
+
+ m_position += lineLength;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetMailDB(nsIMsgDatabase* mailDB) {
+ m_mailDB = mailDB;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetBackupMailDB(
+ nsIMsgDatabase* aBackupMailDB) {
+ m_backupMailDB = aBackupMailDB;
+ if (m_backupMailDB) m_backupMailDB->AddListener(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::SetNewKey(nsMsgKey aKey) {
+ m_new_key = aKey;
+ return NS_OK;
+}
+
+/* #define STRICT_ENVELOPE */
+
+bool nsParseMailMessageState::IsEnvelopeLine(const char* buf,
+ int32_t buf_size) {
+#ifdef STRICT_ENVELOPE
+ /* The required format is
+ From jwz Fri Jul 1 09:13:09 1994
+ But we should also allow at least:
+ From jwz Fri, Jul 01 09:13:09 1994
+ From jwz Fri Jul 1 09:13:09 1994 PST
+ From jwz Fri Jul 1 09:13:09 1994 (+0700)
+
+ We can't easily call XP_ParseTimeString() because the string is not
+ null terminated (ok, we could copy it after a quick check...) but
+ XP_ParseTimeString() may be too lenient for our purposes.
+
+ DANGER!! The released version of 2.0b1 was (on some systems,
+ some Unix, some NT, possibly others) writing out envelope lines
+ like "From - 10/13/95 11:22:33" which STRICT_ENVELOPE will reject!
+ */
+ const char *date, *end;
+
+ if (buf_size < 29) return false;
+ if (*buf != 'F') return false;
+ if (strncmp(buf, "From ", 5)) return false;
+
+ end = buf + buf_size;
+ date = buf + 5;
+
+ /* Skip horizontal whitespace between "From " and user name. */
+ while ((*date == ' ' || *date == '\t') && date < end) date++;
+
+ /* If at the end, it doesn't match. */
+ if (IS_SPACE(*date) || date == end) return false;
+
+ /* Skip over user name. */
+ while (!IS_SPACE(*date) && date < end) date++;
+
+ /* Skip horizontal whitespace between user name and date. */
+ while ((*date == ' ' || *date == '\t') && date < end) date++;
+
+ /* Don't want this to be localized. */
+# define TMP_ISALPHA(x) \
+ (((x) >= 'A' && (x) <= 'Z') || ((x) >= 'a' && (x) <= 'z'))
+
+ /* take off day-of-the-week. */
+ if (date >= end - 3) return false;
+ if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2]))
+ return false;
+ date += 3;
+ /* Skip horizontal whitespace (and commas) between dotw and month. */
+ if (*date != ' ' && *date != '\t' && *date != ',') return false;
+ while ((*date == ' ' || *date == '\t' || *date == ',') && date < end) date++;
+
+ /* take off month. */
+ if (date >= end - 3) return false;
+ if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2]))
+ return false;
+ date += 3;
+ /* Skip horizontal whitespace between month and dotm. */
+ if (date == end || (*date != ' ' && *date != '\t')) return false;
+ while ((*date == ' ' || *date == '\t') && date < end) date++;
+
+ /* Skip over digits and whitespace. */
+ while (((*date >= '0' && *date <= '9') || *date == ' ' || *date == '\t') &&
+ date < end)
+ date++;
+ /* Next character should be a colon. */
+ if (date >= end || *date != ':') return false;
+
+ /* Ok, that ought to be enough... */
+
+# undef TMP_ISALPHA
+
+#else /* !STRICT_ENVELOPE */
+
+ if (buf_size < 5) return false;
+ if (*buf != 'F') return false;
+ if (strncmp(buf, "From ", 5)) return false;
+
+#endif /* !STRICT_ENVELOPE */
+
+ return true;
+}
+
+// We've found the start of the next message, so finish this one off.
+NS_IMETHODIMP nsParseMailMessageState::FinishHeader() {
+ if (m_newMsgHdr) {
+ if (m_lastLineBlank) m_body_lines--;
+ m_newMsgHdr->SetMessageSize(m_position - m_envelope_pos - m_lastLineBlank);
+ m_newMsgHdr->SetLineCount(m_body_lines);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsParseMailMessageState::GetAllHeaders(char** pHeaders,
+ int32_t* pHeadersSize) {
+ if (!pHeaders || !pHeadersSize) return NS_ERROR_NULL_POINTER;
+ *pHeaders = m_headers.GetBuffer();
+ *pHeadersSize = m_headers.GetBufferPos();
+ return NS_OK;
+}
+
+// generate headers as a string, with CRLF between the headers
+NS_IMETHODIMP nsParseMailMessageState::GetHeaders(char** pHeaders) {
+ NS_ENSURE_ARG_POINTER(pHeaders);
+ nsCString crlfHeaders;
+ char* curHeader = m_headers.GetBuffer();
+ for (uint32_t headerPos = 0; headerPos < m_headers.GetBufferPos();) {
+ crlfHeaders.Append(curHeader);
+ crlfHeaders.Append(CRLF);
+ int32_t headerLen = strlen(curHeader);
+ curHeader += headerLen + 1;
+ headerPos += headerLen + 1;
+ }
+ *pHeaders = ToNewCString(crlfHeaders);
+ return NS_OK;
+}
+
+struct message_header* nsParseMailMessageState::GetNextHeaderInAggregate(
+ nsTArray<struct message_header*>& list) {
+ // When parsing a message with multiple To or CC header lines, we're storing
+ // each line in a list, where the list represents the "aggregate" total of all
+ // the header. Here we get a new line for the list
+
+ struct message_header* header =
+ (struct message_header*)PR_Calloc(1, sizeof(struct message_header));
+ list.AppendElement(header);
+ return header;
+}
+
+void nsParseMailMessageState::GetAggregateHeader(
+ nsTArray<struct message_header*>& list, struct message_header* outHeader) {
+ // When parsing a message with multiple To or CC header lines, we're storing
+ // each line in a list, where the list represents the "aggregate" total of all
+ // the header. Here we combine all the lines together, as though they were
+ // really all found on the same line
+
+ struct message_header* header = nullptr;
+ int length = 0;
+ size_t i;
+
+ // Count up the bytes required to allocate the aggregated header
+ for (i = 0; i < list.Length(); i++) {
+ header = list.ElementAt(i);
+ length += (header->length + 1); //+ for ","
+ }
+
+ if (length > 0) {
+ char* value = (char*)PR_CALLOC(length + 1); //+1 for null term
+ if (value) {
+ // Catenate all the To lines together, separated by commas
+ value[0] = '\0';
+ size_t size = list.Length();
+ for (i = 0; i < size; i++) {
+ header = list.ElementAt(i);
+ PL_strncat(value, header->value, header->length);
+ if (i + 1 < size) PL_strcat(value, ",");
+ }
+ outHeader->length = length;
+ outHeader->value = value;
+ }
+ } else {
+ outHeader->length = 0;
+ outHeader->value = nullptr;
+ }
+}
+
+void nsParseMailMessageState::ClearAggregateHeader(
+ nsTArray<struct message_header*>& list) {
+ // Reset the aggregate headers. Free only the message_header struct since
+ // we don't own the value pointer
+
+ for (size_t i = 0; i < list.Length(); i++) PR_Free(list.ElementAt(i));
+ list.Clear();
+}
+
+// We've found a new envelope to parse.
+nsresult nsParseMailMessageState::StartNewEnvelope(const char* line,
+ uint32_t lineLength) {
+ m_envelope_pos = m_position;
+ m_state = nsIMsgParseMailMsgState::ParseHeadersState;
+ m_position += lineLength;
+ m_headerstartpos = m_position;
+ return ParseEnvelope(line, lineLength);
+}
+
+/* largely lifted from mimehtml.c, which does similar parsing, sigh...
+ */
+nsresult nsParseMailMessageState::ParseHeaders() {
+ char* buf = m_headers.GetBuffer();
+ uint32_t buf_length = m_headers.GetBufferPos();
+ if (buf_length == 0) {
+ // No header of an expected type is present. Consider this a successful
+ // parse so email still shows on summary and can be accessed and deleted.
+ return NS_OK;
+ }
+ char* buf_end = buf + buf_length;
+ if (!(buf_length > 1 &&
+ (buf[buf_length - 1] == '\r' || buf[buf_length - 1] == '\n'))) {
+ NS_WARNING("Header text should always end in a newline");
+ return NS_ERROR_UNEXPECTED;
+ }
+ while (buf < buf_end) {
+ char* colon = PL_strnchr(buf, ':', buf_end - buf);
+ char* value = 0;
+ struct message_header* header = 0;
+ struct message_header receivedBy;
+
+ if (!colon) break;
+
+ nsDependentCSubstring headerStr(buf, colon);
+ ToLowerCase(headerStr);
+
+ // Obtain firstChar in headerStr. But if headerStr is empty, just set it to
+ // the colon. This is needed because First() asserts on an empty string.
+ char firstChar = !headerStr.IsEmpty() ? headerStr.First() : *colon;
+
+ // See RFC 5322 section 3.6 for min-max number for given header.
+ // If multiple headers exist we need to make sure to use the first one.
+
+ switch (firstChar) {
+ case 'b':
+ if (headerStr.EqualsLiteral("bcc") && !m_bccList.length)
+ header = &m_bccList;
+ break;
+ case 'c':
+ if (headerStr.EqualsLiteral("cc")) // XXX: RFC 5322 says it's 0 or 1.
+ header = GetNextHeaderInAggregate(m_ccList);
+ else if (headerStr.EqualsLiteral("content-type"))
+ header = &m_content_type;
+ break;
+ case 'd':
+ if (headerStr.EqualsLiteral("date") && !m_date.length)
+ header = &m_date;
+ else if (headerStr.EqualsLiteral("disposition-notification-to"))
+ header = &m_mdn_dnt;
+ else if (headerStr.EqualsLiteral("delivery-date"))
+ header = &m_delivery_date;
+ break;
+ case 'f':
+ if (headerStr.EqualsLiteral("from") && !m_from.length) {
+ header = &m_from;
+ }
+ break;
+ case 'i':
+ if (headerStr.EqualsLiteral("in-reply-to") && !m_in_reply_to.length)
+ header = &m_in_reply_to;
+ break;
+ case 'm':
+ if (headerStr.EqualsLiteral("message-id") && !m_message_id.length)
+ header = &m_message_id;
+ break;
+ case 'n':
+ if (headerStr.EqualsLiteral("newsgroups")) header = &m_newsgroups;
+ break;
+ case 'o':
+ if (headerStr.EqualsLiteral("original-recipient"))
+ header = &m_mdn_original_recipient;
+ break;
+ case 'p':
+ // we could very well care what the priority header was when we
+ // remember its value. If so, need to remember it here. Also,
+ // different priority headers can appear in the same message,
+ // but we only remember the last one that we see. Applies also to
+ // x-priority checked below.
+ if (headerStr.EqualsLiteral("priority")) header = &m_priority;
+ break;
+ case 'r':
+ if (headerStr.EqualsLiteral("references") && !m_references.length)
+ header = &m_references;
+ else if (headerStr.EqualsLiteral("return-path"))
+ header = &m_return_path;
+ // treat conventional Return-Receipt-To as MDN
+ // Disposition-Notification-To
+ else if (headerStr.EqualsLiteral("return-receipt-to"))
+ header = &m_mdn_dnt;
+ else if (headerStr.EqualsLiteral("reply-to") && !m_replyTo.length)
+ header = &m_replyTo;
+ else if (headerStr.EqualsLiteral("received")) {
+ header = &receivedBy;
+ header->length = 0;
+ }
+ break;
+ case 's':
+ if (headerStr.EqualsLiteral("subject") && !m_subject.length)
+ header = &m_subject;
+ else if (headerStr.EqualsLiteral("sender") && !m_sender.length)
+ header = &m_sender;
+ else if (headerStr.EqualsLiteral("status"))
+ header = &m_status;
+ break;
+ case 't':
+ if (headerStr.EqualsLiteral("to")) // XXX: RFC 5322 says it's 0 or 1.
+ header = GetNextHeaderInAggregate(m_toList);
+ break;
+ case 'x':
+ if (headerStr.EqualsIgnoreCase(X_MOZILLA_STATUS2) &&
+ !m_mozstatus2.length)
+ header = &m_mozstatus2;
+ else if (headerStr.EqualsIgnoreCase(X_MOZILLA_STATUS) &&
+ !m_mozstatus.length)
+ header = &m_mozstatus;
+ else if (headerStr.EqualsIgnoreCase(HEADER_X_MOZILLA_ACCOUNT_KEY) &&
+ !m_account_key.length)
+ header = &m_account_key;
+ else if (headerStr.EqualsLiteral("x-priority")) // See case 'p' above.
+ header = &m_priority;
+ else if (headerStr.EqualsIgnoreCase(HEADER_X_MOZILLA_KEYWORDS) &&
+ !m_keywords.length)
+ header = &m_keywords;
+ break;
+ }
+
+ if (!header && m_customDBHeaders.Length()) {
+ size_t customHeaderIndex = m_customDBHeaders.IndexOf(headerStr);
+ if (customHeaderIndex != m_customDBHeaders.NoIndex)
+ header = &m_customDBHeaderValues[customHeaderIndex];
+ }
+
+ buf = colon + 1;
+ // We will be shuffling downwards, so this is our insertion point.
+ char* bufWrite = buf;
+
+ SEARCH_NEWLINE:
+ // move past any non terminating characters, rewriting them if folding white
+ // space exists
+ while (buf < buf_end && *buf != '\r' && *buf != '\n') {
+ if (buf != bufWrite) *bufWrite = *buf;
+ buf++;
+ bufWrite++;
+ }
+
+ // Look for folding, so CRLF, CR or LF followed by space or tab.
+ if ((buf + 2 < buf_end && (buf[0] == '\r' && buf[1] == '\n') &&
+ (buf[2] == ' ' || buf[2] == '\t')) ||
+ (buf + 1 < buf_end && (buf[0] == '\r' || buf[0] == '\n') &&
+ (buf[1] == ' ' || buf[1] == '\t'))) {
+ // Remove trailing spaces at the "write position" and add a single
+ // folding space.
+ while (*(bufWrite - 1) == ' ' || *(bufWrite - 1) == '\t') bufWrite--;
+ *(bufWrite++) = ' ';
+
+ // Skip CRLF, CR+space or LF+space ...
+ buf += 2;
+
+ // ... and skip leading spaces in that line.
+ while (buf < buf_end && (*buf == ' ' || *buf == '\t')) buf++;
+
+ // If we get here, the message headers ended in an empty line, like:
+ // To: blah blah blah<CR><LF> <CR><LF>[end of buffer]. The code below
+ // requires buf to land on a newline to properly null-terminate the
+ // string, so back up a tad so that it is pointing to one.
+ if (buf == buf_end) {
+ --buf;
+ MOZ_ASSERT(*buf == '\n' || *buf == '\r',
+ "Header text should always end in a newline.");
+ }
+ goto SEARCH_NEWLINE;
+ }
+
+ if (header) {
+ value = colon + 1;
+ // eliminate trailing blanks after the colon
+ while (value < bufWrite && (*value == ' ' || *value == '\t')) value++;
+
+ header->value = value;
+ header->length = bufWrite - value;
+ if (header->length < 0) header->length = 0;
+ }
+ if (*buf == '\r' || *buf == '\n') {
+ char* last = bufWrite;
+ char* saveBuf = buf;
+ if (*buf == '\r' && buf + 1 < buf_end && buf[1] == '\n') buf++;
+ buf++;
+ // null terminate the left-over slop so we don't confuse msg filters.
+ *saveBuf = 0;
+ *last = 0; /* short-circuit const, and null-terminate header. */
+ }
+
+ if (header) {
+ /* More const short-circuitry... */
+ /* strip trailing whitespace */
+ while (header->length > 0 && IS_SPACE(header->value[header->length - 1]))
+ ((char*)header->value)[--header->length] = 0;
+ if (header == &receivedBy) {
+ if (m_receivedTime == 0) {
+ // parse Received: header for date.
+ // We trust the first header as that is closest to recipient,
+ // and less likely to be spoofed.
+ nsAutoCString receivedHdr(header->value, header->length);
+ int32_t lastSemicolon = receivedHdr.RFindChar(';');
+ if (lastSemicolon != -1) {
+ nsAutoCString receivedDate;
+ receivedDate = Substring(receivedHdr, lastSemicolon + 1);
+ receivedDate.Trim(" \t\b\r\n");
+ PRTime resultTime;
+ if (PR_ParseTimeString(receivedDate.get(), false, &resultTime) ==
+ PR_SUCCESS)
+ m_receivedTime = resultTime;
+ else
+ NS_WARNING("PR_ParseTimeString failed in ParseHeaders().");
+ }
+ }
+ // Someone might want the received header saved.
+ if (m_customDBHeaders.Length()) {
+ if (m_customDBHeaders.Contains("received"_ns)) {
+ if (!m_receivedValue.IsEmpty()) m_receivedValue.Append(' ');
+ m_receivedValue.Append(header->value, header->length);
+ }
+ }
+ }
+
+ MOZ_ASSERT(header->value[header->length] == 0,
+ "Non-null-terminated strings cause very, very bad problems");
+ }
+ }
+ return NS_OK;
+}
+
+// Try and glean a sender and/or timestamp from the "From " line, to use
+// as last-ditch fallbacks if the message is missing "From"/"Sender" or
+// "Date" headers.
+nsresult nsParseMailMessageState::ParseEnvelope(const char* line,
+ uint32_t line_size) {
+ const char* end;
+ char* s;
+
+ m_envelope.AppendBuffer(line, line_size);
+ end = m_envelope.GetBuffer() + line_size;
+ s = m_envelope.GetBuffer() + 5;
+
+ while (s < end && IS_SPACE(*s)) s++;
+ m_envelope_from.value = s;
+ while (s < end && !IS_SPACE(*s)) s++;
+ m_envelope_from.length = s - m_envelope_from.value;
+
+ while (s < end && IS_SPACE(*s)) s++;
+ m_envelope_date.value = s;
+ m_envelope_date.length = (uint16_t)(line_size - (s - m_envelope.GetBuffer()));
+
+ while (m_envelope_date.length > 0 &&
+ IS_SPACE(m_envelope_date.value[m_envelope_date.length - 1]))
+ m_envelope_date.length--;
+
+ /* #### short-circuit const */
+ ((char*)m_envelope_from.value)[m_envelope_from.length] = 0;
+ ((char*)m_envelope_date.value)[m_envelope_date.length] = 0;
+
+ return NS_OK;
+}
+
+nsresult nsParseMailMessageState::InternSubject(struct message_header* header) {
+ if (!header || header->length == 0) {
+ m_newMsgHdr->SetSubject(""_ns);
+ return NS_OK;
+ }
+
+ nsDependentCString key(header->value);
+
+ uint32_t flags;
+ (void)m_newMsgHdr->GetFlags(&flags);
+ /* strip "Re: " */
+ /**
+ We trust the X-Mozilla-Status line to be the smartest in almost
+ all things. One exception, however, is the HAS_RE flag. Since
+ we just parsed the subject header anyway, we expect that parsing
+ to be smartest. (After all, what if someone just went in and
+ edited the subject line by hand?)
+ */
+ nsCString modifiedSubject;
+ bool strippedRE = NS_MsgStripRE(key, modifiedSubject);
+ if (strippedRE)
+ flags |= nsMsgMessageFlags::HasRe;
+ else
+ flags &= ~nsMsgMessageFlags::HasRe;
+ m_newMsgHdr->SetFlags(flags); // this *does not* update the mozilla-status
+ // header in the local folder
+
+ m_newMsgHdr->SetSubject(strippedRE ? modifiedSubject : key);
+
+ return NS_OK;
+}
+
+// we've reached the end of the envelope, and need to turn all our accumulated
+// message_headers into a single nsIMsgDBHdr to store in a database.
+nsresult nsParseMailMessageState::FinalizeHeaders() {
+ nsresult rv;
+ struct message_header* sender;
+ struct message_header* recipient;
+ struct message_header* subject;
+ struct message_header* id;
+ struct message_header* inReplyTo;
+ struct message_header* replyTo;
+ struct message_header* references;
+ struct message_header* date;
+ struct message_header* deliveryDate;
+ struct message_header* statush;
+ struct message_header* mozstatus;
+ struct message_header* mozstatus2;
+ struct message_header* priority;
+ struct message_header* keywords;
+ struct message_header* account_key;
+ struct message_header* ccList;
+ struct message_header* bccList;
+ struct message_header* mdn_dnt;
+ struct message_header md5_header;
+ struct message_header* content_type;
+ char md5_data[50];
+
+ uint32_t flags = 0;
+ nsMsgPriorityValue priorityFlags = nsMsgPriority::notSet;
+
+ if (!m_mailDB) // if we don't have a valid db, skip the header.
+ return NS_OK;
+
+ struct message_header to;
+ GetAggregateHeader(m_toList, &to);
+ struct message_header cc;
+ GetAggregateHeader(m_ccList, &cc);
+ // we don't aggregate bcc, as we only generate it locally,
+ // and we don't use multiple lines
+
+ // clang-format off
+ sender = (m_from.length ? &m_from :
+ m_sender.length ? &m_sender :
+ m_envelope_from.length ? &m_envelope_from : 0);
+ recipient = (to.length ? &to :
+ cc.length ? &cc :
+ m_newsgroups.length ? &m_newsgroups : 0);
+ ccList = (cc.length ? &cc : 0);
+ bccList = (m_bccList.length ? &m_bccList : 0);
+ subject = (m_subject.length ? &m_subject : 0);
+ id = (m_message_id.length ? &m_message_id : 0);
+ references = (m_references.length ? &m_references : 0);
+ statush = (m_status.length ? &m_status : 0);
+ mozstatus = (m_mozstatus.length ? &m_mozstatus : 0);
+ mozstatus2 = (m_mozstatus2.length ? &m_mozstatus2 : 0);
+ date = (m_date.length ? &m_date :
+ m_envelope_date.length ? &m_envelope_date : 0);
+ deliveryDate = (m_delivery_date.length ? &m_delivery_date : 0);
+ priority = (m_priority.length ? &m_priority : 0);
+ keywords = (m_keywords.length ? &m_keywords : 0);
+ mdn_dnt = (m_mdn_dnt.length ? &m_mdn_dnt : 0);
+ inReplyTo = (m_in_reply_to.length ? &m_in_reply_to : 0);
+ replyTo = (m_replyTo.length ? &m_replyTo : 0);
+ content_type = (m_content_type.length ? &m_content_type : 0);
+ account_key = (m_account_key.length ? &m_account_key : 0);
+ // clang-format on
+
+ if (mozstatus) {
+ if (mozstatus->length == 4) {
+ NS_ASSERTION(MsgIsHex(mozstatus->value, 4),
+ "Expected 4 hex digits for flags.");
+ flags = MsgUnhex(mozstatus->value, 4);
+ // strip off and remember priority bits.
+ flags &= ~nsMsgMessageFlags::RuntimeOnly;
+ priorityFlags =
+ (nsMsgPriorityValue)((flags & nsMsgMessageFlags::Priorities) >> 13);
+ flags &= ~nsMsgMessageFlags::Priorities;
+ }
+ }
+
+ if (mozstatus2) {
+ uint32_t flags2 = 0;
+ sscanf(mozstatus2->value, " %x ", &flags2);
+ flags |= flags2;
+ }
+
+ if (!(flags & nsMsgMessageFlags::Expunged)) // message was deleted, don't
+ // bother creating a hdr.
+ {
+ // We'll need the message id first to recover data from the backup database
+ nsAutoCString rawMsgId;
+ /* Take off <> around message ID. */
+ if (id) {
+ if (id->length > 0 && id->value[0] == '<') {
+ id->length--;
+ id->value++;
+ }
+
+ NS_WARNING_ASSERTION(id->length > 0,
+ "id->length failure in FinalizeHeaders().");
+
+ if (id->length > 0 && id->value[id->length - 1] == '>')
+ /* generate a new null-terminated string without the final > */
+ rawMsgId.Assign(id->value, id->length - 1);
+ else
+ rawMsgId.Assign(id->value);
+ }
+
+ /*
+ * Try to copy the data from the backup database, referencing the MessageID
+ * If that fails, just create a new header
+ */
+ nsCOMPtr<nsIMsgDBHdr> oldHeader;
+ nsresult ret = NS_OK;
+
+ if (m_backupMailDB && !rawMsgId.IsEmpty())
+ ret = m_backupMailDB->GetMsgHdrForMessageID(rawMsgId.get(),
+ getter_AddRefs(oldHeader));
+
+ // m_new_key is set in nsImapMailFolder::ParseAdoptedHeaderLine to be
+ // the UID of the message, so that the key can get created as UID. That of
+ // course is extremely confusing, and we really need to clean that up. We
+ // really should not conflate the meaning of envelope position, key, and
+ // UID.
+ if (NS_SUCCEEDED(ret) && oldHeader)
+ ret = m_mailDB->CopyHdrFromExistingHdr(m_new_key, oldHeader, false,
+ getter_AddRefs(m_newMsgHdr));
+ else if (!m_newMsgHdr) {
+ // Should assert that this is not a local message
+ ret = m_mailDB->CreateNewHdr(m_new_key, getter_AddRefs(m_newMsgHdr));
+ }
+
+ if (NS_SUCCEEDED(ret) && m_newMsgHdr) {
+ uint32_t origFlags;
+ (void)m_newMsgHdr->GetFlags(&origFlags);
+ if (origFlags & nsMsgMessageFlags::HasRe)
+ flags |= nsMsgMessageFlags::HasRe;
+ else
+ flags &= ~nsMsgMessageFlags::HasRe;
+
+ flags &=
+ ~nsMsgMessageFlags::Offline; // don't keep nsMsgMessageFlags::Offline
+ // for local msgs
+ if (mdn_dnt && !(origFlags & nsMsgMessageFlags::Read) &&
+ !(origFlags & nsMsgMessageFlags::MDNReportSent) &&
+ !(flags & nsMsgMessageFlags::MDNReportSent))
+ flags |= nsMsgMessageFlags::MDNReportNeeded;
+
+ m_newMsgHdr->SetFlags(flags);
+ if (priorityFlags != nsMsgPriority::notSet)
+ m_newMsgHdr->SetPriority(priorityFlags);
+
+ // if we have a reply to header, and it's different from the from: header,
+ // set the "replyTo" attribute on the msg hdr.
+ if (replyTo && (!sender || replyTo->length != sender->length ||
+ strncmp(replyTo->value, sender->value, sender->length)))
+ m_newMsgHdr->SetStringProperty("replyTo",
+ nsDependentCString(replyTo->value));
+ if (sender) m_newMsgHdr->SetAuthor(sender->value);
+ if (recipient == &m_newsgroups) {
+ /* In the case where the recipient is a newsgroup, truncate the string
+ at the first comma. This is used only for presenting the thread
+ list, and newsgroup lines tend to be long and non-shared, and tend to
+ bloat the string table. So, by only showing the first newsgroup, we
+ can reduce memory and file usage at the expense of only showing the
+ one group in the summary list, and only being able to sort on the
+ first group rather than the whole list. It's worth it. */
+ char* ch;
+ ch = PL_strchr(recipient->value, ',');
+ if (ch) {
+ /* generate a new string that terminates before the , */
+ nsAutoCString firstGroup;
+ firstGroup.Assign(recipient->value, ch - recipient->value);
+ m_newMsgHdr->SetRecipients(firstGroup.get());
+ }
+ m_newMsgHdr->SetRecipients(recipient->value);
+ } else if (recipient) {
+ m_newMsgHdr->SetRecipients(recipient->value);
+ }
+ if (ccList) {
+ m_newMsgHdr->SetCcList(ccList->value);
+ }
+
+ if (bccList) {
+ m_newMsgHdr->SetBccList(bccList->value);
+ }
+
+ rv = InternSubject(subject);
+ if (NS_SUCCEEDED(rv)) {
+ if (!id) {
+ // what to do about this? we used to do a hash of all the headers...
+ nsAutoCString hash;
+ const char* md5_b64 = "dummy.message.id";
+ nsresult rv;
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ if (NS_SUCCEEDED(hasher->Init(nsICryptoHash::MD5)) &&
+ NS_SUCCEEDED(
+ hasher->Update((const uint8_t*)m_headers.GetBuffer(),
+ m_headers.GetBufferPos())) &&
+ NS_SUCCEEDED(hasher->Finish(true, hash)))
+ md5_b64 = hash.get();
+ }
+ PR_snprintf(md5_data, sizeof(md5_data), "<md5:%s>", md5_b64);
+ md5_header.value = md5_data;
+ md5_header.length = strlen(md5_data);
+ id = &md5_header;
+ }
+
+ if (!rawMsgId.IsEmpty())
+ m_newMsgHdr->SetMessageId(rawMsgId.get());
+ else
+ m_newMsgHdr->SetMessageId(id->value);
+ m_mailDB->UpdatePendingAttributes(m_newMsgHdr);
+
+ if (!mozstatus && statush) {
+ /* Parse a little bit of the Berkeley Mail status header. */
+ for (const char* s = statush->value; *s; s++) {
+ uint32_t msgFlags = 0;
+ (void)m_newMsgHdr->GetFlags(&msgFlags);
+ switch (*s) {
+ case 'R':
+ case 'r':
+ m_newMsgHdr->SetFlags(msgFlags | nsMsgMessageFlags::Read);
+ break;
+ case 'D':
+ case 'd':
+ /* msg->flags |= nsMsgMessageFlags::Expunged; ### Is this
+ * reasonable? */
+ break;
+ case 'N':
+ case 'n':
+ case 'U':
+ case 'u':
+ m_newMsgHdr->SetFlags(msgFlags & ~nsMsgMessageFlags::Read);
+ break;
+ default: // Should check for corrupt file.
+ NS_ERROR("Corrupt file. Should not happen.");
+ break;
+ }
+ }
+ }
+
+ if (account_key != nullptr)
+ m_newMsgHdr->SetAccountKey(account_key->value);
+ // use in-reply-to header as references, if there's no references header
+ if (references != nullptr) {
+ m_newMsgHdr->SetReferences(nsDependentCString(references->value));
+ } else if (inReplyTo != nullptr)
+ m_newMsgHdr->SetReferences(nsDependentCString(inReplyTo->value));
+
+ // 'Received' should be as reliable an indicator of the receipt
+ // date+time as possible, whilst always giving something *from
+ // the message*. It won't use PR_Now() under any circumstance.
+ // Therefore, the fall-thru order for 'Received' is:
+ // Received: -> Delivery-date: -> date
+ // 'Date' uses:
+ // date -> 'Received' -> PR_Now()
+ //
+ // date is:
+ // Date: -> m_envelope_date
+
+ uint32_t rcvTimeSecs = 0;
+ PRTime datePRTime = 0;
+ if (date) {
+ // Date:
+ if (PR_ParseTimeString(date->value, false, &datePRTime) ==
+ PR_SUCCESS) {
+ // Convert to seconds as default value for 'Received'.
+ PRTime2Seconds(datePRTime, &rcvTimeSecs);
+ } else {
+ NS_WARNING(
+ "PR_ParseTimeString of date failed in FinalizeHeader().");
+ }
+ }
+ if (m_receivedTime) {
+ // Upgrade 'Received' to Received: ?
+ PRTime2Seconds(m_receivedTime, &rcvTimeSecs);
+ if (datePRTime == 0) datePRTime = m_receivedTime;
+ } else if (deliveryDate) {
+ // Upgrade 'Received' to Delivery-date: ?
+ PRTime resultTime;
+ if (PR_ParseTimeString(deliveryDate->value, false, &resultTime) ==
+ PR_SUCCESS) {
+ PRTime2Seconds(resultTime, &rcvTimeSecs);
+ if (datePRTime == 0) datePRTime = resultTime;
+ } else {
+ // TODO/FIXME: We need to figure out what to do in this case!
+ NS_WARNING(
+ "PR_ParseTimeString of delivery date failed in "
+ "FinalizeHeader().");
+ }
+ }
+ m_newMsgHdr->SetUint32Property("dateReceived", rcvTimeSecs);
+
+ if (datePRTime == 0) {
+ // If there was some problem parsing the Date header *AND* we
+ // couldn't get a valid envelope date *AND* we couldn't get a valid
+ // Received: header date, use now as the time.
+ // This doesn't affect local (POP3) messages, because we use the
+ // envelope date if there's no Date: header, but it will affect IMAP
+ // msgs w/o a Date: header or Received: headers.
+ datePRTime = PR_Now();
+ }
+ m_newMsgHdr->SetDate(datePRTime);
+
+ if (priority) {
+ nsMsgPriorityValue priorityVal = nsMsgPriority::Default;
+
+ // We can ignore |NS_MsgGetPriorityFromString()| return value,
+ // since we set a default value for |priorityVal|.
+ NS_MsgGetPriorityFromString(priority->value, priorityVal);
+ m_newMsgHdr->SetPriority(priorityVal);
+ } else if (priorityFlags == nsMsgPriority::notSet)
+ m_newMsgHdr->SetPriority(nsMsgPriority::none);
+ if (keywords) {
+ // When there are many keywords, some may not have been written
+ // to the message file, so add extra keywords from the backup
+ nsAutoCString oldKeywords;
+ m_newMsgHdr->GetStringProperty("keywords", oldKeywords);
+ nsTArray<nsCString> newKeywordArray, oldKeywordArray;
+ ParseString(
+ Substring(keywords->value, keywords->value + keywords->length),
+ ' ', newKeywordArray);
+ ParseString(oldKeywords, ' ', oldKeywordArray);
+ for (uint32_t i = 0; i < oldKeywordArray.Length(); i++)
+ if (!newKeywordArray.Contains(oldKeywordArray[i]))
+ newKeywordArray.AppendElement(oldKeywordArray[i]);
+ nsAutoCString newKeywords;
+ for (uint32_t i = 0; i < newKeywordArray.Length(); i++) {
+ if (i) newKeywords.Append(' ');
+ newKeywords.Append(newKeywordArray[i]);
+ }
+ m_newMsgHdr->SetStringProperty("keywords", newKeywords);
+ }
+ for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++) {
+ if (m_customDBHeaderValues[i].length)
+ m_newMsgHdr->SetStringProperty(
+ m_customDBHeaders[i].get(),
+ nsDependentCString(m_customDBHeaderValues[i].value));
+ // The received header is accumulated separately
+ if (m_customDBHeaders[i].EqualsLiteral("received") &&
+ !m_receivedValue.IsEmpty())
+ m_newMsgHdr->SetStringProperty("received", m_receivedValue);
+ }
+ if (content_type) {
+ char* substring = PL_strstr(content_type->value, "charset");
+ if (substring) {
+ char* charset = PL_strchr(substring, '=');
+ if (charset) {
+ charset++;
+ /* strip leading whitespace and double-quote */
+ while (*charset && (IS_SPACE(*charset) || '\"' == *charset))
+ charset++;
+ /* strip trailing whitespace and double-quote */
+ char* end = charset;
+ while (*end && !IS_SPACE(*end) && '\"' != *end && ';' != *end)
+ end++;
+ if (*charset) {
+ if (*end != '\0') {
+ // if we're not at the very end of the line, we need
+ // to generate a new string without the trailing crud
+ nsAutoCString rawCharSet;
+ rawCharSet.Assign(charset, end - charset);
+ m_newMsgHdr->SetCharset(rawCharSet.get());
+ } else {
+ m_newMsgHdr->SetCharset(charset);
+ }
+ }
+ }
+ }
+ substring = PL_strcasestr(content_type->value, "multipart/mixed");
+ if (substring) {
+ uint32_t newFlags;
+ m_newMsgHdr->OrFlags(nsMsgMessageFlags::Attachment, &newFlags);
+ }
+ }
+ }
+ } else {
+ NS_ASSERTION(false, "error creating message header");
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else
+ rv = NS_OK;
+
+ // ### why is this stuff const?
+ char* tmp = (char*)to.value;
+ PR_Free(tmp);
+ tmp = (char*)cc.value;
+ PR_Free(tmp);
+
+ return rv;
+}
+
+nsParseNewMailState::nsParseNewMailState() : m_disableFilters(false) {
+ m_numNotNewMessages = 0;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsParseNewMailState, nsMsgMailboxParser,
+ nsIMsgFilterHitNotify)
+
+nsresult nsParseNewMailState::Init(nsIMsgFolder* serverFolder,
+ nsIMsgFolder* downloadFolder,
+ nsIMsgWindow* aMsgWindow, nsIMsgDBHdr* aHdr,
+ nsIOutputStream* aOutputStream) {
+ NS_ENSURE_ARG_POINTER(serverFolder);
+ nsresult rv;
+ Clear();
+ m_rootFolder = serverFolder;
+ m_msgWindow = aMsgWindow;
+ m_downloadFolder = downloadFolder;
+
+ m_newMsgHdr = aHdr;
+ m_outputStream = aOutputStream;
+ // the new mail parser isn't going to get the stream input, it seems, so we
+ // can't use the OnStartRequest mechanism the mailbox parser uses. So, let's
+ // open the db right now.
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ if (msgDBService && !m_mailDB)
+ rv = msgDBService->OpenFolderDB(downloadFolder, false,
+ getter_AddRefs(m_mailDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = serverFolder->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv)) {
+ nsString serverName;
+ server->GetPrettyName(serverName);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Detected new local messages on account '%s'",
+ NS_ConvertUTF16toUTF8(serverName).get()));
+ rv = server->GetFilterList(aMsgWindow, getter_AddRefs(m_filterList));
+
+ if (m_filterList) rv = server->ConfigureTemporaryFilters(m_filterList);
+ // check if this server defers to another server, in which case
+ // we'll use that server's filters as well.
+ nsCOMPtr<nsIMsgFolder> deferredToRootFolder;
+ server->GetRootMsgFolder(getter_AddRefs(deferredToRootFolder));
+ if (serverFolder != deferredToRootFolder) {
+ nsCOMPtr<nsIMsgIncomingServer> deferredToServer;
+ deferredToRootFolder->GetServer(getter_AddRefs(deferredToServer));
+ if (deferredToServer)
+ deferredToServer->GetFilterList(
+ aMsgWindow, getter_AddRefs(m_deferredToServerFilterList));
+ }
+ }
+ m_disableFilters = false;
+ return NS_OK;
+}
+
+nsParseNewMailState::~nsParseNewMailState() {
+ if (m_mailDB) m_mailDB->Close(true);
+ if (m_backupMailDB) m_backupMailDB->ForceClosed();
+#ifdef DOING_JSFILTERS
+ JSFilter_cleanup();
+#endif
+}
+
+// not an IMETHOD so we don't need to do error checking or return an error.
+// We only have one caller.
+void nsParseNewMailState::GetMsgWindow(nsIMsgWindow** aMsgWindow) {
+ NS_IF_ADDREF(*aMsgWindow = m_msgWindow);
+}
+
+// This gets called for every message because libnet calls IncorporateBegin,
+// IncorporateWrite (once or more), and IncorporateComplete for every message.
+void nsParseNewMailState::DoneParsingFolder(nsresult status) {
+ PublishMsgHeader(nullptr);
+ if (m_mailDB) // finished parsing, so flush db folder info
+ UpdateDBFolderInfo();
+}
+
+void nsParseNewMailState::OnNewMessage(nsIMsgWindow* msgWindow) {}
+
+int32_t nsParseNewMailState::PublishMsgHeader(nsIMsgWindow* msgWindow) {
+ bool moved = false;
+ FinishHeader();
+
+ if (m_newMsgHdr) {
+ uint32_t newFlags, oldFlags;
+ m_newMsgHdr->GetFlags(&oldFlags);
+ if (!(oldFlags &
+ nsMsgMessageFlags::Read)) // don't mark read messages as new.
+ m_newMsgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
+
+ if (!m_disableFilters) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_rootFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, 0);
+ int32_t duplicateAction;
+ server->GetIncomingDuplicateAction(&duplicateAction);
+ if (duplicateAction != nsIMsgIncomingServer::keepDups) {
+ bool isDup;
+ server->IsNewHdrDuplicate(m_newMsgHdr, &isDup);
+ if (isDup) {
+ // we want to do something similar to applying filter hits.
+ // if a dup is marked read, it shouldn't trigger biff.
+ // Same for deleting it or moving it to trash.
+ switch (duplicateAction) {
+ case nsIMsgIncomingServer::deleteDups: {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv =
+ m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_SUCCEEDED(rv)) {
+ rv = msgStore->DiscardNewMessage(m_outputStream, m_newMsgHdr);
+ if (NS_FAILED(rv))
+ m_rootFolder->ThrowAlertMsg("dupDeleteFolderTruncateFailed",
+ msgWindow);
+ }
+ m_mailDB->RemoveHeaderMdbRow(m_newMsgHdr);
+ } break;
+
+ case nsIMsgIncomingServer::moveDupsToTrash: {
+ nsCOMPtr<nsIMsgFolder> trash;
+ GetTrashFolder(getter_AddRefs(trash));
+ if (trash) {
+ uint32_t newFlags;
+ bool msgMoved;
+ m_newMsgHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_SUCCEEDED(rv))
+ rv = msgStore->MoveNewlyDownloadedMessage(m_newMsgHdr, trash,
+ &msgMoved);
+ if (NS_SUCCEEDED(rv) && !msgMoved) {
+ rv = MoveIncorporatedMessage(m_newMsgHdr, m_mailDB, trash,
+ nullptr, msgWindow);
+ if (NS_SUCCEEDED(rv))
+ rv = m_mailDB->RemoveHeaderMdbRow(m_newMsgHdr);
+ }
+ if (NS_FAILED(rv))
+ NS_WARNING("moveDupsToTrash failed for some reason.");
+ }
+ } break;
+ case nsIMsgIncomingServer::markDupsRead:
+ MarkFilteredMessageRead(m_newMsgHdr);
+ break;
+ }
+ int32_t numNewMessages;
+ m_downloadFolder->GetNumNewMessages(false, &numNewMessages);
+ m_downloadFolder->SetNumNewMessages(numNewMessages - 1);
+
+ m_newMsgHdr = nullptr;
+ return 0;
+ }
+ }
+
+ ApplyFilters(&moved, msgWindow);
+ }
+ if (!moved) {
+ if (m_mailDB) {
+ m_mailDB->AddNewHdrToDB(m_newMsgHdr, true);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyMsgAdded(m_newMsgHdr);
+ // mark the header as not yet reported classified
+ nsMsgKey msgKey;
+ m_newMsgHdr->GetMessageKey(&msgKey);
+ m_downloadFolder->OrProcessingFlags(
+ msgKey, nsMsgProcessingFlags::NotReportedClassified);
+ }
+ } // if it was moved by imap filter, m_parseMsgState->m_newMsgHdr ==
+ // nullptr
+ m_newMsgHdr = nullptr;
+ }
+ return 0;
+}
+
+nsresult nsParseNewMailState::GetTrashFolder(nsIMsgFolder** pTrashFolder) {
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (!pTrashFolder) return NS_ERROR_NULL_POINTER;
+
+ if (m_downloadFolder) {
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ m_downloadFolder->GetServer(getter_AddRefs(incomingServer));
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ incomingServer->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ if (rootMsgFolder) {
+ rv = rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
+ pTrashFolder);
+ if (!*pTrashFolder) rv = NS_ERROR_FAILURE;
+ }
+ }
+ return rv;
+}
+
+void nsParseNewMailState::ApplyFilters(bool* pMoved, nsIMsgWindow* msgWindow) {
+ m_msgMovedByFilter = m_msgCopiedByFilter = false;
+
+ if (!m_disableFilters) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr;
+ nsCOMPtr<nsIMsgFolder> downloadFolder = m_downloadFolder;
+ if (m_rootFolder) {
+ if (!downloadFolder)
+ m_rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(downloadFolder));
+ if (downloadFolder) downloadFolder->GetURI(m_inboxUri);
+ char* headers = m_headers.GetBuffer();
+ uint32_t headersSize = m_headers.GetBufferPos();
+ nsAutoCString tok;
+ msgHdr->GetStringProperty("storeToken", tok);
+ if (m_filterList) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Running filters on 1 message (%s)", tok.get()));
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Using filters from the original account"));
+ (void)m_filterList->ApplyFiltersToHdr(
+ nsMsgFilterType::InboxRule, msgHdr, downloadFolder, m_mailDB,
+ nsDependentCSubstring(headers, headersSize), this, msgWindow);
+ }
+ if (!m_msgMovedByFilter && m_deferredToServerFilterList) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Running filters on 1 message (%s)", tok.get()));
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Using filters from the deferred to account"));
+ (void)m_deferredToServerFilterList->ApplyFiltersToHdr(
+ nsMsgFilterType::InboxRule, msgHdr, downloadFolder, m_mailDB,
+ nsDependentCSubstring(headers, headersSize), this, msgWindow);
+ }
+ }
+ }
+ if (pMoved) *pMoved = m_msgMovedByFilter;
+}
+
+NS_IMETHODIMP nsParseNewMailState::ApplyFilterHit(nsIMsgFilter* filter,
+ nsIMsgWindow* msgWindow,
+ bool* applyMore) {
+ NS_ENSURE_ARG_POINTER(filter);
+ NS_ENSURE_ARG_POINTER(applyMore);
+
+ uint32_t newFlags;
+ nsresult rv = NS_OK;
+
+ *applyMore = true;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr;
+
+ nsTArray<RefPtr<nsIMsgRuleAction>> filterActionList;
+ rv = filter->GetSortedActionList(filterActionList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numActions = filterActionList.Length();
+
+ nsCString msgId;
+ msgHdr->GetMessageId(getter_Copies(msgId));
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Applying %" PRIu32
+ " filter actions on message with key %" PRIu32,
+ numActions, msgKeyToInt(msgKey)));
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
+ ("(Local) Message ID: %s", msgId.get()));
+
+ bool loggingEnabled = false;
+ if (m_filterList && numActions)
+ m_filterList->GetLoggingEnabled(&loggingEnabled);
+
+ bool msgIsNew = true;
+ nsresult finalResult = NS_OK; // result of all actions
+ for (uint32_t actionIndex = 0; actionIndex < numActions && *applyMore;
+ actionIndex++) {
+ nsCOMPtr<nsIMsgRuleAction> filterAction(filterActionList[actionIndex]);
+ if (!filterAction) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
+ ("(Local) Filter action at index %" PRIu32 " invalid, skipping",
+ actionIndex));
+ continue;
+ }
+
+ nsMsgRuleActionType actionType;
+ if (NS_SUCCEEDED(filterAction->GetType(&actionType))) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Running filter action at index %" PRIu32
+ ", action type = %i",
+ actionIndex, actionType));
+ if (loggingEnabled) (void)filter->LogRuleHit(filterAction, msgHdr);
+
+ nsCString actionTargetFolderUri;
+ if (actionType == nsMsgFilterAction::MoveToFolder ||
+ actionType == nsMsgFilterAction::CopyToFolder) {
+ rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
+ if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty()) {
+ // clang-format off
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
+ ("(Local) Target URI for Copy/Move action is empty, skipping"));
+ // clang-format on
+ NS_ASSERTION(false, "actionTargetFolderUri is empty");
+ continue;
+ }
+ }
+
+ rv = NS_OK; // result of the current action
+ switch (actionType) {
+ case nsMsgFilterAction::Delete: {
+ nsCOMPtr<nsIMsgFolder> trash;
+ // set value to trash folder
+ rv = GetTrashFolder(getter_AddRefs(trash));
+ if (NS_SUCCEEDED(rv) && trash) {
+ rv = trash->GetURI(actionTargetFolderUri);
+ if (NS_FAILED(rv)) break;
+ }
+
+ rv = msgHdr->OrFlags(nsMsgMessageFlags::Read,
+ &newFlags); // mark read in trash.
+ msgIsNew = false;
+ }
+ // FALLTHROUGH
+ [[fallthrough]];
+ case nsMsgFilterAction::MoveToFolder: {
+ // if moving to a different file, do it.
+ if (!actionTargetFolderUri.IsEmpty() &&
+ !m_inboxUri.Equals(actionTargetFolderUri,
+ nsCaseInsensitiveCStringComparator)) {
+ nsCOMPtr<nsIMsgFolder> destIFolder;
+ // XXX TODO: why do we create the folder here, while we do not in
+ // the Copy action?
+ rv = GetOrCreateFolder(actionTargetFolderUri,
+ getter_AddRefs(destIFolder));
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Local) Target Folder for Move action does not exist"));
+ break;
+ }
+ bool msgMoved = false;
+ // If we're moving to an imap folder, or this message has already
+ // has a pending copy action, use the imap coalescer so that
+ // we won't truncate the inbox before the copy fires.
+ if (m_msgCopiedByFilter ||
+ StringBeginsWith(actionTargetFolderUri, "imap:"_ns)) {
+ if (!m_moveCoalescer)
+ m_moveCoalescer =
+ new nsImapMoveCoalescer(m_downloadFolder, m_msgWindow);
+ NS_ENSURE_TRUE(m_moveCoalescer, NS_ERROR_OUT_OF_MEMORY);
+ rv = m_moveCoalescer->AddMove(destIFolder, msgKey);
+ msgIsNew = false;
+ if (NS_FAILED(rv)) break;
+ } else {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_SUCCEEDED(rv))
+ rv = msgStore->MoveNewlyDownloadedMessage(msgHdr, destIFolder,
+ &msgMoved);
+ if (NS_SUCCEEDED(rv) && !msgMoved)
+ rv = MoveIncorporatedMessage(msgHdr, m_mailDB, destIFolder,
+ filter, msgWindow);
+ m_msgMovedByFilter = NS_SUCCEEDED(rv);
+ if (!m_msgMovedByFilter /* == NS_FAILED(err) */) {
+ // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands.
+ if (loggingEnabled) {
+ (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
+ "filterFailureMoveFailed"_ns);
+ }
+ }
+ }
+ } else {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Target folder is the same as source folder, "
+ "skipping"));
+ rv = NS_OK;
+ }
+ *applyMore = false;
+ } break;
+ case nsMsgFilterAction::CopyToFolder: {
+ nsCString uri;
+ rv = m_rootFolder->GetURI(uri);
+
+ if (!actionTargetFolderUri.IsEmpty() &&
+ !actionTargetFolderUri.Equals(uri)) {
+ nsCOMPtr<nsIMsgFolder> dstFolder;
+ nsCOMPtr<nsIMsgCopyService> copyService;
+ rv = GetExistingFolder(actionTargetFolderUri,
+ getter_AddRefs(dstFolder));
+ if (NS_FAILED(rv)) {
+ // Let's show a more specific warning.
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Local) Target Folder for Copy action does not exist"));
+ NS_WARNING("Target Folder does not exist.");
+ break;
+ }
+
+ copyService = do_GetService(
+ "@mozilla.org/messenger/messagecopyservice;1", &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = copyService->CopyMessages(m_downloadFolder, {&*msgHdr},
+ dstFolder, false, nullptr,
+ msgWindow, false);
+
+ if (NS_FAILED(rv)) {
+ // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands.
+ if (loggingEnabled) {
+ (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
+ "filterFailureCopyFailed"_ns);
+ }
+ } else
+ m_msgCopiedByFilter = true;
+ } else {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Target folder is the same as source folder, "
+ "skipping"));
+ break;
+ }
+ } break;
+ case nsMsgFilterAction::MarkRead:
+ msgIsNew = false;
+ MarkFilteredMessageRead(msgHdr);
+ rv = NS_OK;
+ break;
+ case nsMsgFilterAction::MarkUnread:
+ msgIsNew = true;
+ MarkFilteredMessageUnread(msgHdr);
+ rv = NS_OK;
+ break;
+ case nsMsgFilterAction::KillThread:
+ rv = msgHdr->SetUint32Property("ProtoThreadFlags",
+ nsMsgMessageFlags::Ignored);
+ break;
+ case nsMsgFilterAction::KillSubthread:
+ rv = msgHdr->OrFlags(nsMsgMessageFlags::Ignored, &newFlags);
+ break;
+ case nsMsgFilterAction::WatchThread:
+ rv = msgHdr->OrFlags(nsMsgMessageFlags::Watched, &newFlags);
+ break;
+ case nsMsgFilterAction::MarkFlagged: {
+ rv = m_downloadFolder->MarkMessagesFlagged({&*msgHdr}, true);
+ } break;
+ case nsMsgFilterAction::ChangePriority: {
+ nsMsgPriorityValue filterPriority;
+ filterAction->GetPriority(&filterPriority);
+ rv = msgHdr->SetPriority(filterPriority);
+ } break;
+ case nsMsgFilterAction::AddTag: {
+ nsCString keyword;
+ filterAction->GetStrValue(keyword);
+ rv = m_downloadFolder->AddKeywordsToMessages({&*msgHdr}, keyword);
+ break;
+ }
+ case nsMsgFilterAction::JunkScore: {
+ nsAutoCString junkScoreStr;
+ int32_t junkScore;
+ filterAction->GetJunkScore(&junkScore);
+ junkScoreStr.AppendInt(junkScore);
+ if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) msgIsNew = false;
+ rv = msgHdr->SetStringProperty("junkscore", junkScoreStr);
+ msgHdr->SetStringProperty("junkscoreorigin", "filter"_ns);
+ } break;
+ case nsMsgFilterAction::Forward: {
+ nsCString forwardTo;
+ filterAction->GetStrValue(forwardTo);
+ m_forwardTo.AppendElement(forwardTo);
+ m_msgToForwardOrReply = msgHdr;
+ rv = NS_OK;
+ } break;
+ case nsMsgFilterAction::Reply: {
+ nsCString replyTemplateUri;
+ filterAction->GetStrValue(replyTemplateUri);
+ m_replyTemplateUri.AppendElement(replyTemplateUri);
+ m_msgToForwardOrReply = msgHdr;
+ m_ruleAction = filterAction;
+ m_filter = filter;
+ rv = NS_OK;
+ } break;
+ case nsMsgFilterAction::DeleteFromPop3Server: {
+ nsCOMPtr<nsIMsgFolder> downloadFolder;
+ msgHdr->GetFolder(getter_AddRefs(downloadFolder));
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
+ do_QueryInterface(downloadFolder, &rv);
+ if (NS_FAILED(rv) || !localFolder) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Local) Couldn't find local mail folder"));
+ break;
+ }
+ // This action ignores the deleteMailLeftOnServer preference
+ rv = localFolder->MarkMsgsOnPop3Server({&*msgHdr}, POP3_FORCE_DEL);
+
+ // If this is just a header, throw it away. It's useless now
+ // that the server copy is being deleted.
+ uint32_t flags = 0;
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial) {
+ m_msgMovedByFilter = true;
+ msgIsNew = false;
+ }
+ } break;
+ case nsMsgFilterAction::FetchBodyFromPop3Server: {
+ nsCOMPtr<nsIMsgFolder> downloadFolder;
+ msgHdr->GetFolder(getter_AddRefs(downloadFolder));
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
+ do_QueryInterface(downloadFolder, &rv);
+ if (NS_FAILED(rv) || !localFolder) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Local) Couldn't find local mail folder"));
+ break;
+ }
+ uint32_t flags = 0;
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial) {
+ rv = localFolder->MarkMsgsOnPop3Server({&*msgHdr}, POP3_FETCH_BODY);
+ // Don't add this header to the DB, we're going to replace it
+ // with the full message.
+ m_msgMovedByFilter = true;
+ msgIsNew = false;
+ // Don't do anything else in this filter, wait until we
+ // have the full message.
+ *applyMore = false;
+ }
+ } break;
+
+ case nsMsgFilterAction::StopExecution: {
+ // don't apply any more filters
+ *applyMore = false;
+ rv = NS_OK;
+ } break;
+
+ case nsMsgFilterAction::Custom: {
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
+ if (NS_FAILED(rv)) break;
+
+ nsAutoCString value;
+ rv = filterAction->GetStrValue(value);
+ if (NS_FAILED(rv)) break;
+
+ rv = customAction->ApplyAction({&*msgHdr}, value, nullptr,
+ nsMsgFilterType::InboxRule, msgWindow);
+ } break;
+
+ default:
+ // XXX should not be reached. Check in debug build.
+ NS_ERROR("unexpected filter action");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ }
+ if (NS_FAILED(rv)) {
+ finalResult = rv;
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Local) Action execution failed with error: %" PRIx32,
+ static_cast<uint32_t>(rv)));
+ if (loggingEnabled) {
+ (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
+ "filterFailureAction"_ns);
+ }
+ } else {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Action execution succeeded"));
+ }
+ }
+ if (!msgIsNew) {
+ int32_t numNewMessages;
+ m_downloadFolder->GetNumNewMessages(false, &numNewMessages);
+ if (numNewMessages > 0)
+ m_downloadFolder->SetNumNewMessages(numNewMessages - 1);
+ m_numNotNewMessages++;
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Message will not be marked new"));
+ }
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Finished executing actions"));
+ return finalResult;
+}
+
+// this gets run in a second pass, after apply filters to a header.
+nsresult nsParseNewMailState::ApplyForwardAndReplyFilter(
+ nsIMsgWindow* msgWindow) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ uint32_t i;
+ uint32_t count = m_forwardTo.Length();
+ nsMsgKey msgKey;
+ if (count > 0 && m_msgToForwardOrReply) {
+ m_msgToForwardOrReply->GetMessageKey(&msgKey);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Forwarding message with key %" PRIu32 " to %" PRIu32
+ " addresses",
+ msgKeyToInt(msgKey), count));
+ }
+
+ for (i = 0; i < count; i++) {
+ if (!m_forwardTo[i].IsEmpty()) {
+ nsAutoString forwardStr;
+ CopyASCIItoUTF16(m_forwardTo[i], forwardStr);
+ rv = m_rootFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ {
+ nsCOMPtr<nsIMsgComposeService> compService =
+ do_GetService("@mozilla.org/messengercompose;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = compService->ForwardMessage(
+ forwardStr, m_msgToForwardOrReply, msgWindow, server,
+ nsIMsgComposeService::kForwardAsDefault);
+ if (NS_FAILED(rv))
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Local) Forwarding failed"));
+ }
+ }
+ }
+ m_forwardTo.Clear();
+
+ count = m_replyTemplateUri.Length();
+ if (count > 0 && m_msgToForwardOrReply) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Local) Replying message with key %" PRIu32 " to %" PRIu32
+ " addresses",
+ msgKeyToInt(msgKey), count));
+ }
+
+ for (i = 0; i < count; i++) {
+ if (!m_replyTemplateUri[i].IsEmpty()) {
+ // copy this and truncate the original, so we don't accidentally re-use it
+ // on the next hdr.
+ rv = m_rootFolder->GetServer(getter_AddRefs(server));
+ if (server) {
+ nsCOMPtr<nsIMsgComposeService> compService =
+ do_GetService("@mozilla.org/messengercompose;1");
+ if (compService) {
+ rv = compService->ReplyWithTemplate(
+ m_msgToForwardOrReply, m_replyTemplateUri[i], msgWindow, server);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("ReplyWithTemplate failed");
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Local) Replying failed"));
+ if (rv == NS_ERROR_ABORT) {
+ (void)m_filter->LogRuleHitFail(
+ m_ruleAction, m_msgToForwardOrReply, rv,
+ "filterFailureSendingReplyAborted"_ns);
+ } else {
+ (void)m_filter->LogRuleHitFail(
+ m_ruleAction, m_msgToForwardOrReply, rv,
+ "filterFailureSendingReplyError"_ns);
+ }
+ }
+ }
+ }
+ }
+ }
+ m_replyTemplateUri.Clear();
+ m_msgToForwardOrReply = nullptr;
+ return rv;
+}
+
+void nsParseNewMailState::MarkFilteredMessageRead(nsIMsgDBHdr* msgHdr) {
+ m_downloadFolder->MarkMessagesRead({msgHdr}, true);
+}
+
+void nsParseNewMailState::MarkFilteredMessageUnread(nsIMsgDBHdr* msgHdr) {
+ uint32_t newFlags;
+ if (m_mailDB) {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ m_mailDB->AddToNewList(msgKey);
+ } else {
+ msgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
+ }
+ m_downloadFolder->MarkMessagesRead({msgHdr}, false);
+}
+
+nsresult nsParseNewMailState::EndMsgDownload() {
+ if (m_moveCoalescer) m_moveCoalescer->PlaybackMoves();
+
+ // need to do this for all folders that had messages filtered into them
+ uint32_t serverCount = m_filterTargetFolders.Count();
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ if (NS_SUCCEEDED(rv) && session) // don't use NS_ENSURE_SUCCESS here - we
+ // need to release semaphore below
+ {
+ for (uint32_t index = 0; index < serverCount; index++) {
+ bool folderOpen;
+ session->IsFolderOpenInWindow(m_filterTargetFolders[index], &folderOpen);
+ if (!folderOpen) {
+ uint32_t folderFlags;
+ m_filterTargetFolders[index]->GetFlags(&folderFlags);
+ if (!(folderFlags &
+ (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox))) {
+ bool filtersRun;
+ m_filterTargetFolders[index]->CallFilterPlugins(nullptr, &filtersRun);
+ if (!filtersRun)
+ m_filterTargetFolders[index]->SetMsgDatabase(nullptr);
+ }
+ }
+ }
+ }
+ m_filterTargetFolders.Clear();
+ return rv;
+}
+
+nsresult nsParseNewMailState::AppendMsgFromStream(nsIInputStream* fileStream,
+ nsIMsgDBHdr* aHdr,
+ nsIMsgFolder* destFolder) {
+ nsCOMPtr<nsIMsgPluggableStore> store;
+ nsCOMPtr<nsIOutputStream> destOutputStream;
+ nsresult rv = destFolder->GetMsgStore(getter_AddRefs(store));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = store->GetNewMsgOutputStream(destFolder, &aHdr,
+ getter_AddRefs(destOutputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t bytesCopied;
+ rv = SyncCopyStream(fileStream, destOutputStream, bytesCopied);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = store->FinishNewMessage(destOutputStream, aHdr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+/*
+ * Moves message pointed to by mailHdr into folder destIFolder.
+ * After successful move mailHdr is no longer usable by the caller.
+ */
+nsresult nsParseNewMailState::MoveIncorporatedMessage(nsIMsgDBHdr* mailHdr,
+ nsIMsgDatabase* sourceDB,
+ nsIMsgFolder* destIFolder,
+ nsIMsgFilter* filter,
+ nsIMsgWindow* msgWindow) {
+ NS_ENSURE_ARG_POINTER(destIFolder);
+ nsresult rv = NS_OK;
+
+ // check if the destination is a real folder (by checking for null parent)
+ // and if it can file messages (e.g., servers or news folders can't file
+ // messages). Or read only imap folders...
+ bool canFileMessages = true;
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ destIFolder->GetParent(getter_AddRefs(parentFolder));
+ if (parentFolder) destIFolder->GetCanFileMessages(&canFileMessages);
+ if (!parentFolder || !canFileMessages) {
+ if (filter) {
+ filter->SetEnabled(false);
+ // we need to explicitly save the filter file.
+ if (m_filterList) m_filterList->SaveToDefaultFile();
+ destIFolder->ThrowAlertMsg("filterDisabled", msgWindow);
+ }
+ return NS_MSG_NOT_A_MAIL_FOLDER;
+ }
+
+ uint32_t messageLength;
+ mailHdr->GetMessageSize(&messageLength);
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(destIFolder);
+ if (localFolder) {
+ bool destFolderTooBig = true;
+ rv = localFolder->WarnIfLocalFileTooBig(msgWindow, messageLength,
+ &destFolderTooBig);
+ if (NS_FAILED(rv) || destFolderTooBig)
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+
+ nsCOMPtr<nsISupports> myISupports =
+ do_QueryInterface(static_cast<nsIMsgParseMailMsgState*>(this));
+
+ // Make sure no one else is writing into this folder
+ if (NS_FAILED(rv = destIFolder->AcquireSemaphore(myISupports))) {
+ destIFolder->ThrowAlertMsg("filterFolderDeniedLocked", msgWindow);
+ return rv;
+ }
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv =
+ m_downloadFolder->GetLocalMsgStream(mailHdr, getter_AddRefs(inputStream));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("couldn't get source msg input stream in move filter");
+ destIFolder->ReleaseSemaphore(myISupports);
+ return NS_MSG_FOLDER_UNREADABLE; // ### dmb
+ }
+
+ nsCOMPtr<nsIMsgDatabase> destMailDB;
+
+ if (!localFolder) return NS_MSG_POP_FILTER_TARGET_ERROR;
+
+ // don't force upgrade in place - open the db here before we start writing to
+ // the destination file because XP_Stat can return file size including bytes
+ // written...
+ rv = localFolder->GetDatabaseWOReparse(getter_AddRefs(destMailDB));
+ NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv),
+ "failed to open mail db parsing folder");
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ if (destMailDB)
+ rv = destMailDB->CopyHdrFromExistingHdr(m_new_key, mailHdr, true,
+ getter_AddRefs(newHdr));
+ if (NS_SUCCEEDED(rv) && !newHdr) rv = NS_ERROR_UNEXPECTED;
+
+ if (NS_FAILED(rv)) {
+ destIFolder->ThrowAlertMsg("filterFolderHdrAddFailed", msgWindow);
+ } else {
+ rv = AppendMsgFromStream(inputStream, newHdr, destIFolder);
+ if (NS_FAILED(rv))
+ destIFolder->ThrowAlertMsg("filterFolderWriteFailed", msgWindow);
+ }
+
+ if (NS_FAILED(rv)) {
+ if (destMailDB) destMailDB->Close(true);
+
+ destIFolder->ReleaseSemaphore(myISupports);
+
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+
+ bool movedMsgIsNew = false;
+ // if we have made it this far then the message has successfully been written
+ // to the new folder now add the header to the destMailDB.
+
+ uint32_t newFlags;
+ newHdr->GetFlags(&newFlags);
+ nsMsgKey msgKey;
+ newHdr->GetMessageKey(&msgKey);
+ if (!(newFlags & nsMsgMessageFlags::Read)) {
+ nsCString junkScoreStr;
+ (void)newHdr->GetStringProperty("junkscore", junkScoreStr);
+ if (atoi(junkScoreStr.get()) == nsIJunkMailPlugin::IS_HAM_SCORE) {
+ newHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
+ destMailDB->AddToNewList(msgKey);
+ movedMsgIsNew = true;
+ }
+ }
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyMsgAdded(newHdr);
+ // mark the header as not yet reported classified
+ destIFolder->OrProcessingFlags(msgKey,
+ nsMsgProcessingFlags::NotReportedClassified);
+ m_msgToForwardOrReply = newHdr;
+
+ if (movedMsgIsNew) destIFolder->SetHasNewMessages(true);
+ if (!m_filterTargetFolders.Contains(destIFolder))
+ m_filterTargetFolders.AppendObject(destIFolder);
+
+ destIFolder->ReleaseSemaphore(myISupports);
+
+ (void)localFolder->RefreshSizeOnDisk();
+
+ // Notify the message was moved.
+ if (notifier) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = mailHdr->GetFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv)) {
+ notifier->NotifyMsgUnincorporatedMoved(folder, newHdr);
+ } else {
+ NS_WARNING("Can't get folder for message that was moved.");
+ }
+ }
+
+ nsCOMPtr<nsIMsgPluggableStore> store;
+ rv = m_downloadFolder->GetMsgStore(getter_AddRefs(store));
+ if (store) store->DiscardNewMessage(m_outputStream, mailHdr);
+ if (sourceDB) sourceDB->RemoveHeaderMdbRow(mailHdr);
+
+ // update the folder size so we won't reparse.
+ UpdateDBFolderInfo(destMailDB);
+ destIFolder->UpdateSummaryTotals(true);
+
+ destMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ return rv;
+}
diff --git a/comm/mailnews/local/src/nsParseMailbox.h b/comm/mailnews/local/src/nsParseMailbox.h
new file mode 100644
index 0000000000..1e272cd8c0
--- /dev/null
+++ b/comm/mailnews/local/src/nsParseMailbox.h
@@ -0,0 +1,256 @@
+/* -*- 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 nsParseMailbox_H
+#define nsParseMailbox_H
+
+#include "mozilla/Attributes.h"
+#include "nsIURI.h"
+#include "nsIMsgParseMailMsgState.h"
+#include "nsIStreamListener.h"
+#include "nsMsgLineBuffer.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIDBChangeListener.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIMsgWindow.h"
+#include "nsImapMoveCoalescer.h"
+#include "nsString.h"
+#include "nsIMsgFilterList.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgFilterHitNotify.h"
+#include "nsTArray.h"
+
+class nsOutputFileStream;
+class nsIMsgFolder;
+
+/* Used for the various things that parse RFC822 headers...
+ */
+typedef struct message_header {
+ const char* value; /* The contents of a header (after ": ") */
+ int32_t length; /* The length of the data (it is not NULL-terminated.) */
+} message_header;
+
+// This object maintains the parse state for a single mail message.
+class nsParseMailMessageState : public nsIMsgParseMailMsgState,
+ public nsIDBChangeListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPARSEMAILMSGSTATE
+ NS_DECL_NSIDBCHANGELISTENER
+
+ nsParseMailMessageState();
+
+ nsresult ParseFolderLine(const char* line, uint32_t lineLength);
+ nsresult StartNewEnvelope(const char* line, uint32_t lineLength);
+ nsresult ParseHeaders();
+ nsresult FinalizeHeaders();
+ nsresult ParseEnvelope(const char* line, uint32_t line_size);
+ nsresult InternSubject(struct message_header* header);
+
+ // Returns true if line looks like an mbox "From " line.
+ static bool IsEnvelopeLine(const char* buf, int32_t buf_size);
+
+ // Helpers for dealing with multi-value headers.
+ struct message_header* GetNextHeaderInAggregate(
+ nsTArray<struct message_header*>& list);
+ void GetAggregateHeader(nsTArray<struct message_header*>& list,
+ struct message_header*);
+ void ClearAggregateHeader(nsTArray<struct message_header*>& list);
+
+ nsCOMPtr<nsIMsgDBHdr> m_newMsgHdr; /* current message header we're building */
+ nsCOMPtr<nsIMsgDatabase> m_mailDB;
+ nsCOMPtr<nsIMsgDatabase> m_backupMailDB;
+
+ nsMailboxParseState m_state;
+ int64_t m_position;
+ // The start of the "From " line (the line before the start of the message).
+ uint64_t m_envelope_pos;
+ // The start of the message headers (immediately follows "From " line).
+ uint64_t m_headerstartpos;
+ nsMsgKey m_new_key; // DB key for the new header.
+
+ // The "From " line, if any.
+ ::nsByteArray m_envelope;
+
+ // These two point into the m_envelope buffer.
+ struct message_header m_envelope_from;
+ struct message_header m_envelope_date;
+
+ // The raw header data.
+ ::nsByteArray m_headers;
+
+ // These all point into the m_headers buffer.
+ struct message_header m_message_id;
+ struct message_header m_references;
+ struct message_header m_date;
+ struct message_header m_delivery_date;
+ struct message_header m_from;
+ struct message_header m_sender;
+ struct message_header m_newsgroups;
+ struct message_header m_subject;
+ struct message_header m_status;
+ struct message_header m_mozstatus;
+ struct message_header m_mozstatus2;
+ struct message_header m_in_reply_to;
+ struct message_header m_replyTo;
+ struct message_header m_content_type;
+ struct message_header m_bccList;
+
+ // Support for having multiple To or Cc header lines in a message
+ nsTArray<struct message_header*> m_toList;
+ nsTArray<struct message_header*> m_ccList;
+
+ struct message_header m_priority;
+ struct message_header m_account_key;
+ struct message_header m_keywords;
+
+ // Mdn support
+ struct message_header m_mdn_original_recipient;
+ struct message_header m_return_path;
+ struct message_header m_mdn_dnt; /* MDN Disposition-Notification-To: header */
+
+ PRTime m_receivedTime;
+ uint16_t m_body_lines;
+ uint16_t m_lastLineBlank;
+
+ // this enables extensions to add the values of particular headers to
+ // the .msf file as properties of nsIMsgHdr. It is initialized from a
+ // pref, mailnews.customDBHeaders
+ nsTArray<nsCString> m_customDBHeaders;
+ struct message_header* m_customDBHeaderValues;
+ nsCString m_receivedValue; // accumulated received header
+ protected:
+ virtual ~nsParseMailMessageState();
+};
+
+// This class is part of the mailbox parsing state machine
+class nsMsgMailboxParser : public nsIStreamListener,
+ public nsParseMailMessageState,
+ public nsMsgLineBuffer {
+ public:
+ explicit nsMsgMailboxParser(nsIMsgFolder*);
+ nsMsgMailboxParser();
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we support the nsIStreamListener interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ void SetDB(nsIMsgDatabase* mailDB) { m_mailDB = mailDB; }
+
+ // message socket libnet callbacks, which come through folder pane
+ nsresult ProcessMailboxInputStream(nsIInputStream* aIStream,
+ uint32_t aLength);
+
+ virtual void DoneParsingFolder(nsresult status);
+ virtual void AbortNewHeader();
+
+ // for nsMsgLineBuffer
+ virtual nsresult HandleLine(const char* line, uint32_t line_length) override;
+
+ void UpdateDBFolderInfo();
+ void UpdateDBFolderInfo(nsIMsgDatabase* mailDB);
+ void UpdateStatusText(const char* stringName);
+
+ // Update the progress bar based on what we know.
+ virtual void UpdateProgressPercent();
+ virtual void OnNewMessage(nsIMsgWindow* msgWindow);
+
+ protected:
+ virtual ~nsMsgMailboxParser();
+ nsCOMPtr<nsIMsgStatusFeedback> m_statusFeedback;
+
+ virtual int32_t PublishMsgHeader(nsIMsgWindow* msgWindow);
+
+ // data
+ nsString m_folderName;
+ nsCString m_inboxUri;
+ ::nsByteArray m_inputStream;
+ uint64_t m_graph_progress_total;
+ uint64_t m_graph_progress_received;
+
+ private:
+ nsWeakPtr m_folder;
+ void ReleaseFolderLock();
+ nsresult AcquireFolderLock();
+};
+
+class nsParseNewMailState : public nsMsgMailboxParser,
+ public nsIMsgFilterHitNotify {
+ public:
+ nsParseNewMailState();
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsresult Init(nsIMsgFolder* rootFolder, nsIMsgFolder* downloadFolder,
+ nsIMsgWindow* aMsgWindow, nsIMsgDBHdr* aHdr,
+ nsIOutputStream* aOutputStream);
+
+ virtual void DoneParsingFolder(nsresult status) override;
+
+ void DisableFilters() { m_disableFilters = true; }
+
+ NS_DECL_NSIMSGFILTERHITNOTIFY
+
+ nsOutputFileStream* GetLogFile();
+ virtual int32_t PublishMsgHeader(nsIMsgWindow* msgWindow) override;
+ void GetMsgWindow(nsIMsgWindow** aMsgWindow);
+ nsresult EndMsgDownload();
+
+ nsresult AppendMsgFromStream(nsIInputStream* fileStream, nsIMsgDBHdr* aHdr,
+ nsIMsgFolder* destFolder);
+
+ void ApplyFilters(bool* pMoved, nsIMsgWindow* msgWindow);
+ nsresult ApplyForwardAndReplyFilter(nsIMsgWindow* msgWindow);
+ virtual void OnNewMessage(nsIMsgWindow* msgWindow) override;
+
+ // this keeps track of how many messages we downloaded that
+ // aren't new - e.g., marked read, or moved to an other server.
+ int32_t m_numNotNewMessages;
+
+ protected:
+ virtual ~nsParseNewMailState();
+ virtual nsresult GetTrashFolder(nsIMsgFolder** pTrashFolder);
+ virtual nsresult MoveIncorporatedMessage(nsIMsgDBHdr* mailHdr,
+ nsIMsgDatabase* sourceDB,
+ nsIMsgFolder* destIFolder,
+ nsIMsgFilter* filter,
+ nsIMsgWindow* msgWindow);
+ virtual void MarkFilteredMessageRead(nsIMsgDBHdr* msgHdr);
+ virtual void MarkFilteredMessageUnread(nsIMsgDBHdr* msgHdr);
+
+ nsCOMPtr<nsIMsgFilterList> m_filterList;
+ nsCOMPtr<nsIMsgFilterList> m_deferredToServerFilterList;
+ nsCOMPtr<nsIMsgFolder> m_rootFolder;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ nsCOMPtr<nsIMsgFolder> m_downloadFolder;
+ nsCOMPtr<nsIOutputStream> m_outputStream;
+ nsCOMArray<nsIMsgFolder> m_filterTargetFolders;
+
+ RefPtr<nsImapMoveCoalescer> m_moveCoalescer;
+
+ bool m_msgMovedByFilter;
+ bool m_msgCopiedByFilter;
+ bool m_disableFilters;
+
+ // we have to apply the reply/forward filters in a second pass, after
+ // msg quarantining and moving to other local folders, so we remember the
+ // info we'll need to apply them with these vars.
+ // these need to be arrays in case we have multiple reply/forward filters.
+ nsTArray<nsCString> m_forwardTo;
+ nsTArray<nsCString> m_replyTemplateUri;
+ nsCOMPtr<nsIMsgDBHdr> m_msgToForwardOrReply;
+ nsCOMPtr<nsIMsgFilter> m_filter;
+ nsCOMPtr<nsIMsgRuleAction> m_ruleAction;
+};
+
+#endif
diff --git a/comm/mailnews/local/src/nsPop3Sink.cpp b/comm/mailnews/local/src/nsPop3Sink.cpp
new file mode 100644
index 0000000000..2d4b7472c9
--- /dev/null
+++ b/comm/mailnews/local/src/nsPop3Sink.cpp
@@ -0,0 +1,740 @@
+/* -*- 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 "nsPop3Sink.h"
+#include "prprf.h"
+#include "prlog.h"
+#include "nscore.h"
+#include <stdio.h>
+#include <time.h>
+#include "nsParseMailbox.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsLocalUtils.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsMsgMessageFlags.h"
+#include "nsMailHeaders.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIPop3Protocol.h"
+#include "nsLocalMailFolder.h"
+#include "nsIInputStream.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIPromptService.h"
+#include "nsIDocShell.h"
+#include "mozIDOMWindow.h"
+#include "nsEmbedCID.h"
+#include "nsMsgUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIObserverService.h"
+#include "nsIPop3Service.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Services.h"
+
+/* for logging to Error Console */
+#include "nsIScriptError.h"
+
+mozilla::LazyLogModule POP3LOGMODULE("POP3");
+#define POP3LOG(str) "sink: [this=%p] " str, this
+
+NS_IMPL_ISUPPORTS(nsPop3Sink, nsIPop3Sink)
+
+nsPop3Sink::nsPop3Sink() {
+ m_biffState = 0;
+ m_numNewMessages = 0;
+ m_numNewMessagesInFolder = 0;
+ m_numMsgsDownloaded = 0;
+ m_senderAuthed = false;
+ m_outFileStream = nullptr;
+ m_uidlDownload = false;
+ m_buildMessageUri = false;
+}
+
+nsPop3Sink::~nsPop3Sink() {
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("Calling ReleaseFolderLock from ~nsPop3Sink")));
+ ReleaseFolderLock();
+}
+
+partialRecord::partialRecord() : m_msgDBHdr(nullptr) {}
+
+partialRecord::~partialRecord() {}
+
+// Walk through all the messages in this folder and look for any
+// PARTIAL messages. For each of those, dig through the mailbox and
+// find the Account that the message belongs to. If that Account
+// matches the current Account, then look for the Uidl and save
+// this message for later processing.
+nsresult nsPop3Sink::FindPartialMessages() {
+ nsCOMPtr<nsIMsgEnumerator> messages;
+ bool hasMore = false;
+ bool isOpen = false;
+ nsLocalFolderScanState folderScanState;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+ m_folder->GetMsgDatabase(getter_AddRefs(db));
+ if (!localFolder || !db)
+ return NS_ERROR_FAILURE; // we need it to grub through the folder
+
+ nsresult rv = db->EnumerateMessages(getter_AddRefs(messages));
+ if (messages) messages->HasMoreElements(&hasMore);
+ while (hasMore && NS_SUCCEEDED(rv)) {
+ uint32_t flags = 0;
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
+ rv = messages->GetNext(getter_AddRefs(msgDBHdr));
+ if (!NS_SUCCEEDED(rv)) break;
+ msgDBHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial) {
+ // Open the various streams we need to seek and read from the mailbox
+ if (!isOpen) {
+ rv = localFolder->GetFolderScanState(&folderScanState);
+ if (NS_SUCCEEDED(rv))
+ isOpen = true;
+ else
+ break;
+ }
+ rv = localFolder->GetUidlFromFolder(&folderScanState, msgDBHdr);
+ if (!NS_SUCCEEDED(rv)) break;
+
+ // If we got the uidl, see if this partial message belongs to this
+ // account. Add it to the array if so...
+ if (folderScanState.m_uidl &&
+ m_accountKey.Equals(folderScanState.m_accountKey,
+ nsCaseInsensitiveCStringComparator)) {
+ partialRecord* partialMsg = new partialRecord();
+ if (partialMsg) {
+ partialMsg->m_uidl = folderScanState.m_uidl;
+ partialMsg->m_msgDBHdr = msgDBHdr;
+ m_partialMsgsArray.AppendElement(partialMsg);
+ }
+ }
+ }
+ messages->HasMoreElements(&hasMore);
+ }
+ if (isOpen && folderScanState.m_inputStream)
+ folderScanState.m_inputStream->Close();
+ return rv;
+}
+
+// For all the partial messages saved by FindPartialMessages,
+// ask the protocol handler if they still exist on the server.
+// Any messages that don't exist any more are deleted from the
+// msgDB.
+void nsPop3Sink::CheckPartialMessages(nsIPop3Protocol* protocol) {
+ uint32_t count = m_partialMsgsArray.Length();
+ bool deleted = false;
+
+ for (uint32_t i = 0; i < count; i++) {
+ partialRecord* partialMsg;
+ bool found = true;
+ partialMsg = m_partialMsgsArray.ElementAt(i);
+ protocol->CheckMessage(partialMsg->m_uidl.get(), &found);
+ if (!found && partialMsg->m_msgDBHdr) {
+ if (m_newMailParser)
+ m_newMailParser->m_mailDB->DeleteHeader(partialMsg->m_msgDBHdr, nullptr,
+ false, true);
+ deleted = true;
+ }
+ delete partialMsg;
+ }
+ m_partialMsgsArray.Clear();
+ if (deleted) {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+ if (localFolder) localFolder->NotifyDelete();
+ }
+}
+
+nsresult nsPop3Sink::BeginMailDelivery(bool uidlDownload,
+ nsIMsgWindow* aMsgWindow, bool* aBool) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer);
+ if (!server) return NS_ERROR_UNEXPECTED;
+
+ m_window = aMsgWindow;
+
+ nsCOMPtr<nsIMsgAccountManager> acctMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ nsCOMPtr<nsIMsgAccount> account;
+ NS_ENSURE_SUCCESS(rv, rv);
+ acctMgr->FindAccountForServer(server, getter_AddRefs(account));
+ if (account) account->GetKey(m_accountKey);
+
+ bool isLocked;
+ nsCOMPtr<nsISupports> supports =
+ do_QueryInterface(static_cast<nsIPop3Sink*>(this));
+
+ NS_ENSURE_STATE(m_folder);
+ m_folder->GetLocked(&isLocked);
+ if (!isLocked) {
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("BeginMailDelivery acquiring semaphore")));
+ m_folder->AcquireSemaphore(supports);
+ } else {
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("BeginMailDelivery folder locked")));
+ return NS_MSG_FOLDER_BUSY;
+ }
+ m_uidlDownload = uidlDownload;
+ if (!uidlDownload) FindPartialMessages();
+
+ m_folder->GetNumNewMessages(false, &m_numNewMessagesInFolder);
+
+#ifdef DEBUG
+ printf("Begin mail message delivery.\n");
+#endif
+ nsCOMPtr<nsIPop3Service> pop3Service(
+ do_GetService("@mozilla.org/messenger/popservice;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pop3Service->NotifyDownloadStarted(m_folder);
+ if (aBool) *aBool = true;
+ return NS_OK;
+}
+
+nsresult nsPop3Sink::EndMailDelivery(nsIPop3Protocol* protocol) {
+ CheckPartialMessages(protocol);
+
+ if (m_newMailParser) {
+ if (m_outFileStream) m_outFileStream->Flush(); // try this.
+ m_newMailParser->OnStopRequest(nullptr, NS_OK);
+ m_newMailParser->EndMsgDownload();
+ }
+ if (m_outFileStream) {
+ m_outFileStream->Close();
+ m_outFileStream = nullptr;
+ }
+
+ // tell the parser to mark the db valid *after* closing the mailbox.
+ if (m_newMailParser) m_newMailParser->UpdateDBFolderInfo();
+
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("Calling ReleaseFolderLock from EndMailDelivery")));
+ nsresult rv = ReleaseFolderLock();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "folder lock not released successfully");
+
+ bool filtersRun;
+ m_folder->CallFilterPlugins(nullptr,
+ &filtersRun); // ??? do we need msgWindow?
+ int32_t numNewMessagesInFolder;
+ // if filters have marked msgs read or deleted, the num new messages count
+ // will go negative by the number of messages marked read or deleted,
+ // so if we add that number to the number of msgs downloaded, that will give
+ // us the number of actual new messages.
+ m_folder->GetNumNewMessages(false, &numNewMessagesInFolder);
+ m_numNewMessages -= (m_numNewMessagesInFolder - numNewMessagesInFolder);
+ m_folder->SetNumNewMessages(
+ m_numNewMessages); // we'll adjust this for spam later
+ if (!filtersRun && m_numNewMessages > 0) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ m_folder->GetServer(getter_AddRefs(server));
+ if (server) {
+ server->SetPerformingBiff(true);
+ m_folder->SetBiffState(m_biffState);
+ server->SetPerformingBiff(false);
+ }
+ }
+ // note that size on disk has possibly changed.
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+ if (localFolder) (void)localFolder->RefreshSizeOnDisk();
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer);
+ if (server) {
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ rv = server->GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (filterList) (void)filterList->FlushLogIfNecessary();
+ }
+
+ // fix for bug #161999
+ // we should update the summary totals for the folder (inbox)
+ // in case it's not the open folder
+ m_folder->UpdateSummaryTotals(true);
+
+ // check if the folder open in this window is not the current folder, and if
+ // it has new message, in which case we need to try to run the filter plugin.
+ if (m_newMailParser) {
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ m_newMailParser->GetMsgWindow(getter_AddRefs(msgWindow));
+ // this breaks down if it's biff downloading new mail because
+ // there's no msgWindow...
+ if (msgWindow) {
+ nsCOMPtr<nsIMsgFolder> openFolder;
+ (void)msgWindow->GetOpenFolder(getter_AddRefs(openFolder));
+ if (openFolder && openFolder != m_folder) {
+ // only call filter plugins if folder is a local folder, because only
+ // local folders get messages filtered into them synchronously by pop3.
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
+ do_QueryInterface(openFolder);
+ if (localFolder) {
+ bool hasNew, isLocked;
+ (void)openFolder->GetHasNewMessages(&hasNew);
+ if (hasNew) {
+ // if the open folder is locked, we shouldn't run the spam filters
+ // on it because someone is using the folder. see 218433.
+ // Ideally, the filter plugin code would try to grab the folder lock
+ // and hold onto it until done, but that's more difficult and I
+ // think this will actually fix the problem.
+ openFolder->GetLocked(&isLocked);
+ if (!isLocked) openFolder->CallFilterPlugins(nullptr, &filtersRun);
+ }
+ }
+ }
+ }
+ }
+#ifdef DEBUG
+ printf("End mail message delivery.\n");
+#endif
+ nsCOMPtr<nsIPop3Service> pop3Service(
+ do_GetService("@mozilla.org/messenger/popservice;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pop3Service->NotifyDownloadCompleted(m_folder, m_numNewMessages);
+ return NS_OK;
+}
+
+nsresult nsPop3Sink::ReleaseFolderLock() {
+ nsresult result = NS_OK;
+ if (!m_folder) return result;
+ bool haveSemaphore;
+ nsCOMPtr<nsISupports> supports =
+ do_QueryInterface(static_cast<nsIPop3Sink*>(this));
+ result = m_folder->TestSemaphore(supports, &haveSemaphore);
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("ReleaseFolderLock haveSemaphore = %s"),
+ haveSemaphore ? "TRUE" : "FALSE"));
+
+ if (NS_SUCCEEDED(result) && haveSemaphore)
+ result = m_folder->ReleaseSemaphore(supports);
+ return result;
+}
+
+nsresult nsPop3Sink::AbortMailDelivery(nsIPop3Protocol* protocol) {
+ CheckPartialMessages(protocol);
+
+ // ### PS TODO - discard any new message?
+
+ if (m_outFileStream) {
+ m_outFileStream->Close();
+ m_outFileStream = nullptr;
+ }
+
+ /* tell the parser to mark the db valid *after* closing the mailbox.
+ we have truncated the inbox, so berkeley mailbox and msf file are in sync*/
+ if (m_newMailParser) m_newMailParser->UpdateDBFolderInfo();
+ MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+ (POP3LOG("Calling ReleaseFolderLock from AbortMailDelivery")));
+
+ nsresult rv = ReleaseFolderLock();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "folder lock not released successfully");
+
+#ifdef DEBUG
+ printf("Abort mail message delivery.\n");
+#endif
+ nsCOMPtr<nsIPop3Service> pop3Service(
+ do_GetService("@mozilla.org/messenger/popservice;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pop3Service->NotifyDownloadCompleted(m_folder, 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::IncorporateBegin(const char* uidlString, uint32_t flags) {
+#ifdef DEBUG
+ printf("Incorporate message begin:\n");
+ if (uidlString) printf("uidl string: %s\n", uidlString);
+#endif
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer);
+ if (!server) return NS_ERROR_UNEXPECTED;
+
+ rv = server->GetMsgStore(getter_AddRefs(m_msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = m_msgStore->GetNewMsgOutputStream(m_folder, getter_AddRefs(newHdr),
+ getter_AddRefs(m_outFileStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create a new mail parser
+ if (!m_newMailParser) m_newMailParser = new nsParseNewMailState;
+ NS_ENSURE_TRUE(m_newMailParser, NS_ERROR_OUT_OF_MEMORY);
+ if (m_uidlDownload) m_newMailParser->DisableFilters();
+
+ nsCOMPtr<nsIMsgFolder> serverFolder;
+ rv = GetServerFolder(getter_AddRefs(serverFolder));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = m_newMailParser->Init(serverFolder, m_folder, m_window, newHdr,
+ m_outFileStream);
+ // If we failed to initialize the parser, then just don't use it!!!
+ // We can still continue without one.
+
+ if (NS_FAILED(rv)) {
+ m_newMailParser = nullptr;
+ rv = NS_OK;
+ }
+
+ nsCString outputString(GetDummyEnvelope());
+ rv = WriteLineToMailbox(outputString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Write out account-key before UIDL so the code that looks for
+ // UIDL will find the account first and know it can stop looking
+ // once it finds the UIDL line.
+ if (!m_accountKey.IsEmpty()) {
+ outputString.AssignLiteral(HEADER_X_MOZILLA_ACCOUNT_KEY ": ");
+ outputString.Append(m_accountKey);
+ outputString.AppendLiteral(MSG_LINEBREAK);
+ rv = WriteLineToMailbox(outputString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (uidlString) {
+ outputString.AssignLiteral("X-UIDL: ");
+ outputString.Append(uidlString);
+ outputString.AppendLiteral(MSG_LINEBREAK);
+ rv = WriteLineToMailbox(outputString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // WriteLineToMailbox("X-Mozilla-Status: 8000" MSG_LINEBREAK);
+ char* statusLine = PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, flags);
+ outputString.Assign(statusLine);
+ rv = WriteLineToMailbox(outputString);
+ PR_smprintf_free(statusLine);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = WriteLineToMailbox("X-Mozilla-Status2: 00000000"_ns MSG_LINEBREAK);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // leave space for 60 bytes worth of keys/tags
+ rv = WriteLineToMailbox(nsLiteralCString(X_MOZILLA_KEYWORDS));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::SetPopServer(nsIPop3IncomingServer* server) {
+ m_popServer = server;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::GetPopServer(nsIPop3IncomingServer** aServer) {
+ NS_ENSURE_ARG_POINTER(aServer);
+ NS_IF_ADDREF(*aServer = m_popServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Sink::GetFolder(nsIMsgFolder** aFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_IF_ADDREF(*aFolder = m_folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPop3Sink::SetFolder(nsIMsgFolder* aFolder) {
+ m_folder = aFolder;
+ return NS_OK;
+}
+
+nsresult nsPop3Sink::GetServerFolder(nsIMsgFolder** aFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ if (m_popServer) {
+ // not sure what this is used for - might be wrong if we have a deferred
+ // account.
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer =
+ do_QueryInterface(m_popServer);
+ if (incomingServer) return incomingServer->GetRootFolder(aFolder);
+ }
+ *aFolder = nullptr;
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsPop3Sink::SetMsgsToDownload(uint32_t aNumMessages) {
+ m_numNewMessages = aNumMessages;
+ return NS_OK;
+}
+
+char* nsPop3Sink::GetDummyEnvelope(void) {
+ static char result[75];
+ char* ct;
+ time_t now = time((time_t*)0);
+#if defined(XP_WIN)
+ if (now < 0 || now > 0x7FFFFFFF) now = 0x7FFFFFFF;
+#endif
+ ct = ctime(&now);
+ PR_ASSERT(ct[24] == '\r' || ct[24] == '\n');
+ ct[24] = 0;
+ /* This value must be in ctime() format, with English abbreviations.
+ strftime("... %c ...") is no good, because it is localized. */
+ PL_strcpy(result, "From - ");
+ PL_strcpy(result + 7, ct);
+ PL_strcpy(result + 7 + 24, MSG_LINEBREAK);
+ return result;
+}
+
+nsresult nsPop3Sink::IncorporateWrite(const char* block, int32_t length) {
+ m_outputBuffer.Truncate();
+ if (!strncmp(block, "From ", 5)) m_outputBuffer.Assign('>');
+
+ m_outputBuffer.Append(block);
+
+ return WriteLineToMailbox(m_outputBuffer);
+}
+
+nsresult nsPop3Sink::WriteLineToMailbox(const nsACString& buffer) {
+ if (!buffer.IsEmpty()) {
+ uint32_t bufferLen = buffer.Length();
+ if (m_newMailParser)
+ m_newMailParser->HandleLine(buffer.BeginReading(), bufferLen);
+ // The following (!m_outFileStream etc) was added to make sure that we don't
+ // write somewhere where for some reason or another we can't write to and
+ // lose the messages See bug 62480
+ NS_ENSURE_TRUE(m_outFileStream, NS_ERROR_OUT_OF_MEMORY);
+
+ // To remove seeking to the end for each line to be written, remove the
+ // following line. See bug 1116055 for details.
+#define SEEK_TO_END
+#ifdef SEEK_TO_END
+ // seek to the end in case someone else has sought elsewhere in our stream.
+ nsCOMPtr<nsISeekableStream> seekableOutStream =
+ do_QueryInterface(m_outFileStream);
+
+ if (seekableOutStream) {
+ int64_t before_seek_pos;
+ nsresult rv2 = seekableOutStream->Tell(&before_seek_pos);
+ MOZ_ASSERT(NS_SUCCEEDED(rv2),
+ "seekableOutStream->Tell(&before_seek_pos) failed");
+
+ // XXX Handle error such as network error for remote file system.
+ seekableOutStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+
+ int64_t after_seek_pos;
+ nsresult rv3 = seekableOutStream->Tell(&after_seek_pos);
+ MOZ_ASSERT(NS_SUCCEEDED(rv3),
+ "seekableOutStream->Tell(&after_seek_pos) failed");
+
+ if (NS_SUCCEEDED(rv2) && NS_SUCCEEDED(rv3)) {
+ if (before_seek_pos != after_seek_pos) {
+ nsString folderName;
+ if (m_folder) m_folder->GetPrettyName(folderName);
+ // This merits a console message, it's poor man's telemetry.
+ MsgLogToConsole4(
+ u"Unexpected file position change detected"_ns +
+ (folderName.IsEmpty() ? EmptyString() : u" in folder "_ns) +
+ (folderName.IsEmpty() ? EmptyString() : folderName) +
+ u". "
+ "If you can reliably reproduce this, please report the "
+ "steps you used to dev-apps-thunderbird@lists.mozilla.org "
+ "or to bug 1308335 at bugzilla.mozilla.org. "
+ "Resolving this problem will allow speeding up message "
+ "downloads."_ns,
+ NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
+ nsIScriptError::errorFlag);
+# ifdef DEBUG
+ // Debugging, see bug 1116055.
+ if (!folderName.IsEmpty()) {
+ fprintf(stderr,
+ "(seekdebug) WriteLineToMailbox() detected an unexpected "
+ "file position change in folder %s.\n",
+ NS_ConvertUTF16toUTF8(folderName).get());
+ } else {
+ fprintf(stderr,
+ "(seekdebug) WriteLineToMailbox() detected an unexpected "
+ "file position change.\n");
+ }
+ fprintf(stderr,
+ "(seekdebug) before_seek_pos=0x%016llx, "
+ "after_seek_pos=0x%016llx\n",
+ (long long unsigned int)before_seek_pos,
+ (long long unsigned int)after_seek_pos);
+# endif
+ }
+ }
+ }
+#endif
+
+ uint32_t bytesWritten;
+ nsresult rv =
+ m_outFileStream->Write(buffer.BeginReading(), bufferLen, &bytesWritten);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(bytesWritten == bufferLen, NS_ERROR_FAILURE);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::IncorporateComplete(nsIMsgWindow* aMsgWindow, int32_t aSize) {
+ if (m_buildMessageUri && !m_baseMessageUri.IsEmpty() && m_newMailParser &&
+ m_newMailParser->m_newMsgHdr) {
+ nsMsgKey msgKey;
+ m_newMailParser->m_newMsgHdr->GetMessageKey(&msgKey);
+ m_messageUri.Truncate();
+ nsBuildLocalMessageURI(m_baseMessageUri, msgKey, m_messageUri);
+ }
+
+ nsresult rv = WriteLineToMailbox(nsLiteralCString(MSG_LINEBREAK));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool leaveOnServer = false;
+ m_popServer->GetLeaveMessagesOnServer(&leaveOnServer);
+ // We need to flush the output stream, in case mail filters move
+ // the new message, which relies on all the data being flushed.
+ rv =
+ m_outFileStream->Flush(); // Make sure the message is written to the disk
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(m_newMailParser, "could not get m_newMailParser");
+ if (m_newMailParser) {
+ // PublishMsgHdr clears m_newMsgHdr, so we need a comptr to
+ // hold onto it.
+ nsCOMPtr<nsIMsgDBHdr> hdr = m_newMailParser->m_newMsgHdr;
+ NS_ASSERTION(hdr, "m_newMailParser->m_newMsgHdr wasn't set");
+ if (!hdr) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+
+ // If a header already exists for this message (for example, when
+ // getting a complete message when a partial exists), then update the new
+ // header from the old.
+ nsCOMPtr<nsIMsgDBHdr> oldMsgHdr;
+ if (!m_origMessageUri.IsEmpty() && localFolder) {
+ rv = GetMsgDBHdrFromURI(m_origMessageUri, getter_AddRefs(oldMsgHdr));
+ if (NS_SUCCEEDED(rv) && oldMsgHdr) {
+ localFolder->UpdateNewMsgHdr(oldMsgHdr, hdr);
+ }
+ }
+ m_msgStore->FinishNewMessage(m_outFileStream, hdr);
+ m_newMailParser->PublishMsgHeader(aMsgWindow);
+ m_newMailParser->ApplyForwardAndReplyFilter(aMsgWindow);
+ if (aSize) hdr->SetUint32Property("onlineSize", aSize);
+
+ if (oldMsgHdr) {
+ // We had the partial message, but got the full now.
+ nsCOMPtr<nsIMsgFolder> oldMsgFolder;
+ rv = oldMsgHdr->GetFolder(getter_AddRefs(oldMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString oldURI;
+ rv = oldMsgFolder->GetUriForMsg(oldMsgHdr, oldURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ rv = hdr->GetFolder(getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString newURI;
+ rv = newMsgFolder->GetUriForMsg(hdr, newURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Delete old header before notifying.
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = m_folder->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = db->DeleteHeader(oldMsgHdr, nullptr, false, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ nsCOMPtr<nsISupportsString> origUri =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ origUri->SetData(NS_ConvertUTF8toUTF16(oldURI));
+ obsServ->NotifyObservers(origUri, "message-content-updated",
+ NS_ConvertUTF8toUTF16(newURI).get());
+ }
+ }
+ }
+
+#ifdef DEBUG
+ printf("Incorporate message complete.\n");
+#endif
+ nsCOMPtr<nsIPop3Service> pop3Service(
+ do_GetService("@mozilla.org/messenger/popservice;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pop3Service->NotifyDownloadProgress(m_folder, ++m_numMsgsDownloaded,
+ m_numNewMessages);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::IncorporateAbort(bool uidlDownload) {
+ NS_ENSURE_STATE(m_outFileStream);
+ nsresult rv = m_outFileStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (m_msgStore && m_newMailParser && m_newMailParser->m_newMsgHdr) {
+ m_msgStore->DiscardNewMessage(m_outFileStream,
+ m_newMailParser->m_newMsgHdr);
+ }
+#ifdef DEBUG
+ printf("Incorporate message abort.\n");
+#endif
+ return rv;
+}
+
+nsresult nsPop3Sink::SetBiffStateAndUpdateFE(uint32_t aBiffState,
+ int32_t numNewMessages,
+ bool notify) {
+ m_biffState = aBiffState;
+ if (m_newMailParser) numNewMessages -= m_newMailParser->m_numNotNewMessages;
+
+ if (notify && m_folder && numNewMessages > 0 &&
+ numNewMessages != m_numNewMessages &&
+ aBiffState == nsIMsgFolder::nsMsgBiffState_NewMail) {
+ m_folder->SetNumNewMessages(numNewMessages);
+ m_folder->SetBiffState(aBiffState);
+ }
+ m_numNewMessages = numNewMessages;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::GetBuildMessageUri(bool* bVal) {
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_buildMessageUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::SetBuildMessageUri(bool bVal) {
+ m_buildMessageUri = bVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::GetMessageUri(nsACString& messageUri) {
+ NS_ENSURE_TRUE(!m_messageUri.IsEmpty(), NS_ERROR_FAILURE);
+ messageUri = m_messageUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::SetMessageUri(const nsACString& messageUri) {
+ m_messageUri = messageUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::GetBaseMessageUri(nsACString& baseMessageUri) {
+ NS_ENSURE_TRUE(!m_baseMessageUri.IsEmpty(), NS_ERROR_FAILURE);
+ baseMessageUri = m_baseMessageUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::SetBaseMessageUri(const nsACString& baseMessageUri) {
+ m_baseMessageUri = baseMessageUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::GetOrigMessageUri(nsACString& aOrigMessageUri) {
+ aOrigMessageUri.Assign(m_origMessageUri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3Sink::SetOrigMessageUri(const nsACString& aOrigMessageUri) {
+ m_origMessageUri.Assign(aOrigMessageUri);
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsPop3Sink.h b/comm/mailnews/local/src/nsPop3Sink.h
new file mode 100644
index 0000000000..9ea4e6b2ce
--- /dev/null
+++ b/comm/mailnews/local/src/nsPop3Sink.h
@@ -0,0 +1,70 @@
+/* -*- 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 nsPop3Sink_h__
+#define nsPop3Sink_h__
+
+#include "nscore.h"
+#include "nsIPop3Sink.h"
+#include "nsIOutputStream.h"
+#include "prmem.h"
+#include "prio.h"
+#include "plstr.h"
+#include "prenv.h"
+#include "nsIMsgFolder.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+class nsParseNewMailState;
+class nsIMsgFolder;
+
+struct partialRecord {
+ partialRecord();
+ ~partialRecord();
+
+ nsCOMPtr<nsIMsgDBHdr> m_msgDBHdr;
+ nsCString m_uidl;
+};
+
+class nsPop3Sink : public nsIPop3Sink {
+ public:
+ nsPop3Sink();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPOP3SINK
+ nsresult GetServerFolder(nsIMsgFolder** aFolder);
+ nsresult FindPartialMessages();
+ void CheckPartialMessages(nsIPop3Protocol* protocol);
+
+ static char* GetDummyEnvelope(void);
+
+ protected:
+ virtual ~nsPop3Sink();
+ nsresult WriteLineToMailbox(const nsACString& buffer);
+ nsresult ReleaseFolderLock();
+
+ uint32_t m_biffState;
+ int32_t m_numNewMessages;
+ int32_t m_numNewMessagesInFolder;
+ int32_t m_numMsgsDownloaded;
+ bool m_senderAuthed;
+ nsCString m_outputBuffer;
+ nsCOMPtr<nsIPop3IncomingServer> m_popServer;
+ // Currently the folder we want to update about biff info
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ RefPtr<nsParseNewMailState> m_newMailParser;
+ nsCOMPtr<nsIOutputStream>
+ m_outFileStream; // the file we write to, which may be temporary
+ nsCOMPtr<nsIMsgPluggableStore> m_msgStore;
+ bool m_uidlDownload;
+ bool m_buildMessageUri;
+ nsCOMPtr<nsIMsgWindow> m_window;
+ nsCString m_messageUri;
+ nsCString m_baseMessageUri;
+ nsCString m_origMessageUri;
+ nsCString m_accountKey;
+ nsTArray<partialRecord*> m_partialMsgsArray;
+};
+
+#endif
diff --git a/comm/mailnews/local/src/nsPop3URL.cpp b/comm/mailnews/local/src/nsPop3URL.cpp
new file mode 100644
index 0000000000..138a4d9930
--- /dev/null
+++ b/comm/mailnews/local/src/nsPop3URL.cpp
@@ -0,0 +1,202 @@
+/* -*- 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 "nsPop3URL.h"
+#include "nsString.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgAccountManager.h"
+#include "nsLocalMailFolder.h"
+#include "nsPop3Sink.h"
+
+#define NS_POP3URL_CID \
+ { \
+ 0xea1b0a11, 0xe6f4, 0x11d2, { \
+ 0x80, 0x70, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e \
+ } \
+ }
+static NS_DEFINE_CID(kPop3UrlCID, NS_POP3URL_CID);
+
+nsPop3URL::nsPop3URL() : nsMsgMailNewsUrl() {}
+
+nsPop3URL::~nsPop3URL() {}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPop3URL, nsMsgMailNewsUrl, nsIPop3URL)
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIPop3URL specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsPop3URL::SetPop3Sink(nsIPop3Sink* aPop3Sink) {
+ if (aPop3Sink) m_pop3Sink = aPop3Sink;
+ return NS_OK;
+}
+
+nsresult nsPop3URL::GetPop3Sink(nsIPop3Sink** aPop3Sink) {
+ if (aPop3Sink) {
+ *aPop3Sink = m_pop3Sink;
+ NS_IF_ADDREF(*aPop3Sink);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3URL::GetMessageUri(nsACString& aMessageUri) {
+ if (m_messageUri.IsEmpty()) return NS_ERROR_NULL_POINTER;
+ aMessageUri = m_messageUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPop3URL::SetMessageUri(const nsACString& aMessageUri) {
+ m_messageUri = aMessageUri;
+ return NS_OK;
+}
+
+nsresult nsPop3URL::BuildPop3Url(const char* urlSpec, nsIMsgFolder* inbox,
+ nsIPop3IncomingServer* server,
+ nsIUrlListener* aUrlListener, nsIURI** aUrl,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+
+ nsPop3Sink* pop3Sink = new nsPop3Sink();
+
+ pop3Sink->SetPopServer(server);
+ pop3Sink->SetFolder(inbox);
+
+ // now create a pop3 url and a protocol instance to run the url....
+ nsCOMPtr<nsIPop3URL> pop3Url = do_CreateInstance(kPop3UrlCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pop3Url->SetPop3Sink(pop3Sink);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl;
+ rv = pop3Url->QueryInterface(NS_GET_IID(nsIMsgMailNewsUrl),
+ getter_AddRefs(mailnewsurl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailnewsurl->SetSpecInternal(nsDependentCString(urlSpec));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aUrlListener) mailnewsurl->RegisterListener(aUrlListener);
+ if (aMsgWindow) mailnewsurl->SetMsgWindow(aMsgWindow);
+
+ mailnewsurl.forget(aUrl);
+
+ return rv;
+}
+
+nsresult nsPop3URL::NewURI(const nsACString& aSpec, nsIURI* aBaseURI,
+ nsIURI** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsAutoCString folderUri(aSpec);
+ int32_t offset = folderUri.FindChar('?');
+ if (offset != kNotFound) folderUri.SetLength(offset);
+
+ // Hold onto the string until it goes out of scope.
+ const nsPromiseFlatCString& flat = PromiseFlatCString(aSpec);
+ const char* uidl = PL_strstr(flat.get(), "uidl=");
+ NS_ENSURE_TRUE(uidl, NS_ERROR_FAILURE);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(folderUri, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ nsLocalFolderScanState folderScanState;
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder);
+ nsCOMPtr<nsIMailboxUrl> mailboxUrl = do_QueryInterface(aBaseURI);
+
+ if (mailboxUrl && localFolder) {
+ rv = localFolder->GetFolderScanState(&folderScanState);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsMsgKey msgKey;
+ mailboxUrl->GetMessageKey(&msgKey);
+ folder->GetMessageHeader(msgKey, getter_AddRefs(msgHdr));
+ // we do this to get the account key
+ if (msgHdr) localFolder->GetUidlFromFolder(&folderScanState, msgHdr);
+ if (!folderScanState.m_accountKey.IsEmpty()) {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (accountManager) {
+ nsCOMPtr<nsIMsgAccount> account;
+ accountManager->GetAccount(folderScanState.m_accountKey,
+ getter_AddRefs(account));
+ if (account) account->GetIncomingServer(getter_AddRefs(server));
+ }
+ }
+ }
+
+ if (!server) rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPop3IncomingServer> popServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString hostname;
+ nsCString username;
+ server->GetHostName(hostname);
+ server->GetUsername(username);
+
+ int32_t port;
+ server->GetPort(&port);
+ if (port == -1) port = nsIPop3URL::DEFAULT_POP3_PORT;
+
+ // We need to escape the username before calling SetUsername() because it may
+ // contain characters like / % or @. GetUsername() will unescape the username.
+ nsCString escapedUsername;
+ MsgEscapeString(username, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+
+ nsAutoCString popSpec("pop://");
+ popSpec += escapedUsername;
+ popSpec += "@";
+ popSpec += hostname;
+ popSpec += ":";
+ popSpec.AppendInt(port);
+ popSpec += "?";
+ popSpec += uidl;
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> newUri;
+ rv = BuildPop3Url(popSpec.get(), folder, popServer, urlListener,
+ getter_AddRefs(newUri), nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(newUri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailnewsurl->SetUsernameInternal(escapedUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(newUri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString messageUri(aSpec);
+ if (!strncmp(messageUri.get(), "mailbox:", 8))
+ messageUri.Replace(0, 8, "mailbox-message:");
+ offset = messageUri.Find("?number=");
+ if (offset != kNotFound) messageUri.Replace(offset, 8, "#");
+ offset = messageUri.FindChar('&');
+ if (offset != kNotFound) messageUri.SetLength(offset);
+ popurl->SetMessageUri(messageUri);
+ nsCOMPtr<nsIPop3Sink> pop3Sink;
+ rv = popurl->GetPop3Sink(getter_AddRefs(pop3Sink));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pop3Sink->SetBuildMessageUri(true);
+
+ newUri.forget(_retval);
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsPop3URL.h b/comm/mailnews/local/src/nsPop3URL.h
new file mode 100644
index 0000000000..44e2ce6dc9
--- /dev/null
+++ b/comm/mailnews/local/src/nsPop3URL.h
@@ -0,0 +1,36 @@
+/* -*- 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 nsPop3URL_h__
+#define nsPop3URL_h__
+
+#include "nsIPop3URL.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsCOMPtr.h"
+
+class nsPop3URL : public nsIPop3URL, public nsMsgMailNewsUrl {
+ public:
+ NS_DECL_NSIPOP3URL
+ nsPop3URL();
+ static nsresult NewURI(const nsACString& aSpec, nsIURI* aBaseURI,
+ nsIURI** _retval);
+ NS_DECL_ISUPPORTS_INHERITED
+
+ protected:
+ virtual ~nsPop3URL();
+
+ nsCString m_messageUri;
+
+ /* Pop3 specific event sinks */
+ nsCOMPtr<nsIPop3Sink> m_pop3Sink;
+
+ // convenience function to make constructing of the pop3 url easier...
+ static nsresult BuildPop3Url(const char* urlSpec, nsIMsgFolder* inbox,
+ nsIPop3IncomingServer*,
+ nsIUrlListener* aUrlListener, nsIURI** aUrl,
+ nsIMsgWindow* aMsgWindow);
+};
+
+#endif // nsPop3URL_h__
diff --git a/comm/mailnews/local/src/nsRssIncomingServer.cpp b/comm/mailnews/local/src/nsRssIncomingServer.cpp
new file mode 100644
index 0000000000..005e202c14
--- /dev/null
+++ b/comm/mailnews/local/src/nsRssIncomingServer.cpp
@@ -0,0 +1,248 @@
+/* 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 "nsRssIncomingServer.h"
+#include "nsMsgFolderFlags.h"
+#include "nsINewsBlogFeedDownloader.h"
+#include "nsIFile.h"
+#include "nsIMsgFolderNotificationService.h"
+
+#include "nsIMsgLocalMailFolder.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+
+nsrefcnt nsRssIncomingServer::gInstanceCount = 0;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsRssIncomingServer, nsMsgIncomingServer,
+ nsIRssIncomingServer, nsIMsgFolderListener,
+ nsILocalMailIncomingServer)
+
+nsRssIncomingServer::nsRssIncomingServer() {
+ m_canHaveFilters = true;
+
+ if (gInstanceCount == 0) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolderNotificationService> notifyService =
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1", &rv);
+ if (NS_SUCCEEDED(rv))
+ notifyService->AddListener(
+ this, nsIMsgFolderNotificationService::folderAdded |
+ nsIMsgFolderNotificationService::folderDeleted |
+ nsIMsgFolderNotificationService::folderMoveCopyCompleted |
+ nsIMsgFolderNotificationService::folderRenamed);
+ }
+
+ gInstanceCount++;
+}
+
+nsRssIncomingServer::~nsRssIncomingServer() {
+ gInstanceCount--;
+
+ if (gInstanceCount == 0) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolderNotificationService> notifyService =
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1", &rv);
+ if (NS_SUCCEEDED(rv)) notifyService->RemoveListener(this);
+ }
+}
+
+nsresult nsRssIncomingServer::FillInDataSourcePath(
+ const nsAString& aDataSourceName, nsIFile** aLocation) {
+ nsresult rv;
+ // Get the local path for this server.
+ nsCOMPtr<nsIFile> localFile;
+ rv = GetLocalPath(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Append the name of the subscriptions data source.
+ rv = localFile->Append(aDataSourceName);
+ localFile.forget(aLocation);
+ return rv;
+}
+
+// nsIRSSIncomingServer methods
+NS_IMETHODIMP nsRssIncomingServer::GetSubscriptionsPath(nsIFile** aLocation) {
+ return FillInDataSourcePath(u"feeds.json"_ns, aLocation);
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetFeedItemsPath(nsIFile** aLocation) {
+ return FillInDataSourcePath(u"feeditems.json"_ns, aLocation);
+}
+
+NS_IMETHODIMP nsRssIncomingServer::CreateDefaultMailboxes() {
+ // For Feeds, all we have is Trash.
+ return CreateLocalFolder(u"Trash"_ns);
+}
+
+NS_IMETHODIMP nsRssIncomingServer::SetFlagsOnDefaultMailboxes() {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
+ do_QueryInterface(rootFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::Trash);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::PerformBiff(nsIMsgWindow* aMsgWindow) {
+ // Get the account root (server) folder and pass it on.
+ nsCOMPtr<nsIMsgFolder> rootRSSFolder;
+ GetRootMsgFolder(getter_AddRefs(rootRSSFolder));
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(rootRSSFolder);
+ nsresult rv;
+ bool isBiff = true;
+ nsCOMPtr<nsINewsBlogFeedDownloader> rssDownloader =
+ do_GetService("@mozilla.org/newsblog-feed-downloader;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rssDownloader->DownloadFeed(rootRSSFolder, urlListener, isBiff, aMsgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetNewMail(nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aUrlListener,
+ nsIMsgFolder* aFolder,
+ nsIURI** _retval) {
+ // Pass the selected folder on to the downloader.
+ if (_retval) {
+ *_retval = nullptr;
+ }
+ NS_ENSURE_ARG_POINTER(aFolder);
+ nsresult rv;
+ bool isBiff = false;
+ nsCOMPtr<nsINewsBlogFeedDownloader> rssDownloader =
+ do_GetService("@mozilla.org/newsblog-feed-downloader;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rssDownloader->DownloadFeed(aFolder, aUrlListener, isBiff, aMsgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetAccountManagerChrome(nsAString& aResult) {
+ aResult.AssignLiteral("am-newsblog.xhtml");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetOfflineSupportLevel(
+ int32_t* aSupportLevel) {
+ NS_ENSURE_ARG_POINTER(aSupportLevel);
+ *aSupportLevel = OFFLINE_SUPPORT_LEVEL_NONE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetSupportsDiskSpace(
+ bool* aSupportsDiskSpace) {
+ NS_ENSURE_ARG_POINTER(aSupportsDiskSpace);
+ *aSupportsDiskSpace = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetServerRequiresPasswordForBiff(
+ bool* aServerRequiresPasswordForBiff) {
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ // For Feed folders, we don't require a password.
+ *aServerRequiresPasswordForBiff = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::GetCanSearchMessages(
+ bool* canSearchMessages) {
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ *canSearchMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgAdded(nsIMsgDBHdr* aMsg) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgsClassified(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMsgs, bool aJunkProcessed,
+ bool aTraitProcessed) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgsJunkStatusChanged(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& messages) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgsDeleted(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMsgs) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgsMoveCopyCompleted(
+ bool aMove, const nsTArray<RefPtr<nsIMsgDBHdr>>& aSrcMsgs,
+ nsIMsgFolder* aDestFolder, const nsTArray<RefPtr<nsIMsgDBHdr>>& aDestMsgs) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgKeyChanged(nsMsgKey aOldKey,
+ nsIMsgDBHdr* aNewHdr) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderAdded(nsIMsgFolder* aFolder) {
+ // Nothing to do. Not necessary for new folder adds, as a new folder never
+ // has a subscription.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderDeleted(nsIMsgFolder* aFolder) {
+ // Not necessary for folder deletes, which are move to Trash and handled by
+ // movecopy. Virtual folder or trash folder deletes send a folderdeleted,
+ // but these should have no subscriptions already.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderMoveCopyCompleted(
+ bool aMove, nsIMsgFolder* aSrcFolder, nsIMsgFolder* aDestFolder) {
+ return FolderChanged(aDestFolder, aSrcFolder, (aMove ? "move" : "copy"));
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderRenamed(nsIMsgFolder* aOrigFolder,
+ nsIMsgFolder* aNewFolder) {
+ return FolderChanged(aNewFolder, aOrigFolder, "rename");
+}
+
+nsresult nsRssIncomingServer::FolderChanged(nsIMsgFolder* aFolder,
+ nsIMsgFolder* aOrigFolder,
+ const char* aAction) {
+ if (!aFolder) return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsINewsBlogFeedDownloader> rssDownloader =
+ do_GetService("@mozilla.org/newsblog-feed-downloader;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rssDownloader->UpdateSubscriptionsDS(aFolder, aOrigFolder, aAction);
+ return rv;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::MsgUnincorporatedMoved(
+ nsIMsgFolder* srcFolder, nsIMsgDBHdr* msg) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderCompactStart(nsIMsgFolder* folder) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderCompactFinish(nsIMsgFolder* folder) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssIncomingServer::FolderReindexTriggered(
+ nsIMsgFolder* folder) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsRssIncomingServer::GetSortOrder(int32_t* aSortOrder) {
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = 400000000;
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsRssIncomingServer.h b/comm/mailnews/local/src/nsRssIncomingServer.h
new file mode 100644
index 0000000000..337f1ce95d
--- /dev/null
+++ b/comm/mailnews/local/src/nsRssIncomingServer.h
@@ -0,0 +1,47 @@
+/* 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 __nsRssIncomingServer_h
+#define __nsRssIncomingServer_h
+
+#include "mozilla/Attributes.h"
+#include "nsIRssIncomingServer.h"
+#include "nsILocalMailIncomingServer.h"
+#include "nsMsgIncomingServer.h"
+#include "nsIMsgFolderListener.h"
+#include "nsMailboxServer.h"
+
+class nsRssIncomingServer : public nsMailboxServer,
+ public nsIRssIncomingServer,
+ public nsILocalMailIncomingServer,
+ public nsIMsgFolderListener
+
+{
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRSSINCOMINGSERVER
+ NS_DECL_NSILOCALMAILINCOMINGSERVER
+ NS_DECL_NSIMSGFOLDERLISTENER
+
+ NS_IMETHOD GetOfflineSupportLevel(int32_t* aSupportLevel) override;
+ NS_IMETHOD GetSupportsDiskSpace(bool* aSupportsDiskSpace) override;
+ NS_IMETHOD GetAccountManagerChrome(nsAString& aResult) override;
+ NS_IMETHOD PerformBiff(nsIMsgWindow* aMsgWindow) override;
+ NS_IMETHOD GetServerRequiresPasswordForBiff(
+ bool* aServerRequiresPasswordForBiff) override;
+ NS_IMETHOD GetCanSearchMessages(bool* canSearchMessages) override;
+ NS_IMETHOD GetSortOrder(int32_t* aSortOrder) override;
+
+ nsRssIncomingServer();
+
+ protected:
+ virtual ~nsRssIncomingServer();
+ nsresult FolderChanged(nsIMsgFolder* aFolder, nsIMsgFolder* aOrigFolder,
+ const char* aAction);
+ nsresult FillInDataSourcePath(const nsAString& aDataSourceName,
+ nsIFile** aLocation);
+ static nsrefcnt gInstanceCount;
+};
+
+#endif /* __nsRssIncomingServer_h */
diff --git a/comm/mailnews/local/src/nsRssService.cpp b/comm/mailnews/local/src/nsRssService.cpp
new file mode 100644
index 0000000000..77cf04365d
--- /dev/null
+++ b/comm/mailnews/local/src/nsRssService.cpp
@@ -0,0 +1,113 @@
+/* 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 "nsRssService.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsIProperties.h"
+#include "nsServiceManagerUtils.h"
+
+nsRssService::nsRssService() {}
+
+nsRssService::~nsRssService() {}
+
+NS_IMPL_ISUPPORTS(nsRssService, nsIRssService, nsIMsgProtocolInfo)
+
+NS_IMETHODIMP nsRssService::GetDefaultLocalPath(nsIFile** aDefaultLocalPath) {
+ NS_ENSURE_ARG_POINTER(aDefaultLocalPath);
+ *aDefaultLocalPath = nullptr;
+
+ nsCOMPtr<nsIFile> localFile;
+ nsCOMPtr<nsIProperties> dirService(
+ do_GetService("@mozilla.org/file/directory_service;1"));
+ if (!dirService) return NS_ERROR_FAILURE;
+ dirService->Get(NS_APP_MAIL_50_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(localFile));
+ if (!localFile) return NS_ERROR_FAILURE;
+
+ bool exists;
+ nsresult rv = localFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(aDefaultLocalPath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::SetDefaultLocalPath(nsIFile* aDefaultLocalPath) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssService::GetServerIID(nsIID** aServerIID) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssService::GetRequiresUsername(bool* aRequiresUsername) {
+ NS_ENSURE_ARG_POINTER(aRequiresUsername);
+ *aRequiresUsername = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetPreflightPrettyNameWithEmailAddress(
+ bool* aPreflightPrettyNameWithEmailAddress) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsRssService::GetCanDelete(bool* aCanDelete) {
+ NS_ENSURE_ARG_POINTER(aCanDelete);
+ *aCanDelete = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetCanLoginAtStartUp(bool* aCanLoginAtStartUp) {
+ NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp);
+ *aCanLoginAtStartUp = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetCanDuplicate(bool* aCanDuplicate) {
+ NS_ENSURE_ARG_POINTER(aCanDuplicate);
+ *aCanDuplicate = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetDefaultServerPort(bool isSecure,
+ int32_t* _retval) {
+ *_retval = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetCanGetMessages(bool* aCanGetMessages) {
+ NS_ENSURE_ARG_POINTER(aCanGetMessages);
+ *aCanGetMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetCanGetIncomingMessages(
+ bool* aCanGetIncomingMessages) {
+ NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages);
+ *aCanGetIncomingMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetDefaultDoBiff(bool* aDefaultDoBiff) {
+ NS_ENSURE_ARG_POINTER(aDefaultDoBiff);
+ // by default, do biff for RSS feeds
+ *aDefaultDoBiff = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetShowComposeMsgLink(bool* aShowComposeMsgLink) {
+ NS_ENSURE_ARG_POINTER(aShowComposeMsgLink);
+ *aShowComposeMsgLink = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRssService::GetFoldersCreatedAsync(bool* aAsyncCreation) {
+ NS_ENSURE_ARG_POINTER(aAsyncCreation);
+ *aAsyncCreation = false;
+ return NS_OK;
+}
diff --git a/comm/mailnews/local/src/nsRssService.h b/comm/mailnews/local/src/nsRssService.h
new file mode 100644
index 0000000000..490351d61e
--- /dev/null
+++ b/comm/mailnews/local/src/nsRssService.h
@@ -0,0 +1,23 @@
+/* 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 nsRssService_h___
+#define nsRssService_h___
+
+#include "nsIRssService.h"
+#include "nsIMsgProtocolInfo.h"
+
+class nsRssService : public nsIMsgProtocolInfo, public nsIRssService {
+ public:
+ nsRssService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRSSSERVICE
+ NS_DECL_NSIMSGPROTOCOLINFO
+
+ private:
+ virtual ~nsRssService();
+};
+
+#endif /* nsRssService_h___ */
diff --git a/comm/mailnews/local/test/moz.build b/comm/mailnews/local/test/moz.build
new file mode 100644
index 0000000000..6b37fdbe09
--- /dev/null
+++ b/comm/mailnews/local/test/moz.build
@@ -0,0 +1,6 @@
+# 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/.
+
+XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"]
diff --git a/comm/mailnews/local/test/unit/data/dot b/comm/mailnews/local/test/unit/data/dot
new file mode 100644
index 0000000000..adab8ff515
--- /dev/null
+++ b/comm/mailnews/local/test/unit/data/dot
@@ -0,0 +1,10 @@
+From - Tue Jan 31 11:44:20 2012
+Subject: Dot at the line head
+Date: Tue, 21 Jan 2012 11:44:20 +0000
+Mime-Version: 1.0
+Content-Type: text/html; charset=iso-8859-1; format=flowed
+
+.
+. This is a line starting with a dot.
+.
+
diff --git a/comm/mailnews/local/test/unit/data/invalid_mozilla_keys.eml b/comm/mailnews/local/test/unit/data/invalid_mozilla_keys.eml
new file mode 100644
index 0000000000..611029bb7d
--- /dev/null
+++ b/comm/mailnews/local/test/unit/data/invalid_mozilla_keys.eml
@@ -0,0 +1,5 @@
+Date: Tue, 21 Jan 2012 11:44:20 +0000
+X-Mozilla-Keys:
+
+
+This mail has invalid X-Mozilla-Keys header.
diff --git a/comm/mailnews/local/test/unit/data/mailformed_recipients.eml b/comm/mailnews/local/test/unit/data/mailformed_recipients.eml
new file mode 100644
index 0000000000..3a3b9a874a
--- /dev/null
+++ b/comm/mailnews/local/test/unit/data/mailformed_recipients.eml
@@ -0,0 +1,66 @@
+Return-Path: <notifier.mars@krw.rzd>
+Received: from [10.95.185.198] (HELO mars)
+ by cgbe1.sf.icc.krsk.krw.rzd (CommuniGate Pro SMTP 5.4.2)
+ with ESMTP id 197561; Sat, 03 Dec 2011 17:07:41 +0400
+Subject: CS-MARS Incident Notification (red, Rule Name: System Rule: DoS: Network - Success Likely)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 7bit
+Date: Sat, 3 Dec 2011 17:07:41 +0400
+From: notifier.mars@krw.rzd
+Message-Id: <1322917661.14@mars>
+To: LyukshinRA@krw.rzd,
+ biakus@krw.rzd,
+
+
+The following incident occurred on "mars"
+
+Start time: Sat Dec 3 16:52:33 2011
+End time: Sat Dec 3 17:07:35 2011
+Fired Rule Id: 3354883
+Fired Rule: System Rule: DoS: Network - Success Likely
+Incident Id: 24500896822
+Incident Severity:red
+
+Top 3 src-dest address pairs sorted by severity and count (showing 3 of 319):
+1. N/A -> 10.88.21.45 Severity: red Count: 16
+2. 10.89.234.223 -> N/A Severity: red Count: 16
+3. 10.144.58.124 -> 10.92.23.37 Severity: green Count: 1
+
+Top 3 src ip's address sorted by severity and count (showing 3 of 10):
+1. N/A -> Severity: red Count: 16
+2. 10.89.234.223 -> Severity: red Count: 16
+3. 10.132.51.53 -> Severity: green Count: 48
+
+Top 3 dest ip's address sorted by severity and count (showing 3 of 319):
+1. 10.88.21.45 -> Severity: red Count: 16
+2. N/A -> Severity: red Count: 16
+3. 10.92.23.37 -> Severity: green Count: 1
+
+Top 3 dest TCP/UDP ports sorted by severity and count (showing 0 of 0):
+
+Top 3 event types sorted by severity and count (showing 2 of 2):
+1. Sudden increase of traffic to a port Severity: red Count: 32
+2. Deny packet due to security policy Severity: green Count: 317
+
+Top 3 reporting devices sorted by count (showing 3 of 11):
+1. KRW-EXP3 Count: 152
+2. kzi-spd-asa.secadm.m.krw.rzd Count: 151
+3. mars Count: 32
+
+
+
+For more details about this incident please go to:
+ https://mars/Incidents/IncidentDetails.jsp?Incident_Id=24500896822
+ https://mars.secadm.m.krw.rzd/Incidents/IncidentDetails.jsp?Incident_Id=24500896822
+ https://mars.krw.rzd/Incidents/IncidentDetails.jsp?Incident_Id=24500896822
+ https://10.95.185.198/Incidents/IncidentDetails.jsp?Incident_Id=24500896822
+ https://10.95.185.130/Incidents/IncidentDetails.jsp?Incident_Id=24500896822
+
+For all incidents occurred recently please go to:
+ https://mars/Incidents/
+ https://mars.secadm.m.krw.rzd/Incidents/
+ https://mars.krw.rzd/Incidents/
+ https://10.95.185.198/Incidents/
+ https://10.95.185.130/Incidents/
+
diff --git a/comm/mailnews/local/test/unit/data/mailformed_subject.eml b/comm/mailnews/local/test/unit/data/mailformed_subject.eml
new file mode 100644
index 0000000000..b4cae27827
--- /dev/null
+++ b/comm/mailnews/local/test/unit/data/mailformed_subject.eml
@@ -0,0 +1,1934 @@
+From - Wed Nov 09 20:53:04 2011
+X-Account-Key: account6
+X-UIDL: AK9oUtQAAQncTrq1IQTAI2gZvkw
+X-Mozilla-Status: 1003
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:
+X-Apparently-To: milonguero66@yahoo.de via 212.82.104.175; Wed, 09 Nov 2011 17:15:13 +0000
+Received-SPF: none (domain of yahoo.de does not designate permitted sender hosts)
+X-YMailISG: ki6Cgm0WLDt5_yRciWKCGRrm_vnjeHjNnC0KO465U1eaOZyt
+ pivGwlOFQRjp2s2Ygsm5JQ9e3mXBbof5vgzmR3tyuYMCf7EIK7zbRrnlOVCi
+ KfWabfqU2cTV2h7Ic5RTSuENb5nJQIXwVJKAsZspB63KFOJ4tESpGHVHOXLl
+ DcjkJJPSCgA23jDgVh_2XoL__I9xRfVC93IJkNBx_k1iWhz7faHweH.AS26L
+ E2Z0o97aS7U.UR3JJPtTZYjhPyw5ncRZefyDpP6XXTHGjbXp7TkaopFfZ1t5
+ MO0QFlOSCAXq30VBg.ViDBIz6nfSetzflBHtgCRA2k6ovl.KLOPN4ZOvV3Io
+ YikmLnc3EMQKKwuGytzvr0qvB2W_dARwmdVAkWdpAFqoGQIRsuuK.vEPbQgt
+ o.4_7krO5H.E5yTSKs3AxRpSgu7Tpqo1USzeN6VfSp4XSsqoEXf6jPwo9COl
+ cRiDNC1ofVzcPDp1FP2A6ihTDsi64ZKhrtfRXBPSAWs0DbNsjrbO2qAG8iZi
+ cmO1Hxg_8F5BxOolKIkzc3ykR7Ou.M0ebP9OusURSxfgLLmUo4Mw2asmWCTZ
+ umthtzDW1Zf76E.flIaVKlP0btDZqJpvTVyg7KN0cWRMtFdC4ybvQLyVxSK2
+ KWQ7f232DAlH.JUbIuJKVnyuTVAlZBPoZue8AQi3P53J2ZwC7XB6VXahgqgn
+ xKlZiD.zUREwUeUCJQBMSZk7re7TrCoPwXfE.n3tc8NjtmIzeQRPxAPdl1Ka
+ 8fwMfibbBAAOS9SmeqayScRVPjvNyidH1t5BNk0YWc6EzyxgLPvru65hvXnZ
+ W3CayiMl7XKxeIDctxiRKlbUhJ_QzgVGMunN_jynWts_1vIIBvGUbaP9EFjZ
+ PAcNoNwSiTnSlQ3cc4aLCaGBUG.Gq8e5u3zUu_L0HRvAhIaZcBvi_xiWjDjp
+ kRXylog9074fulAzY_7Zlbq2.xa6AdUR7PrOzlS3QMblu5yc4_hlpq8KPYbM
+ voqzmi6JIZ8dAKTp4BAVFr7Q63UlHJWUjsdlJ1uIds.dl1OxJiTgGfEh.O1g
+ tyh3zY_hE3z7zRW7LFzEqoNclh8WYNie0j7kDHYfXbR1klg49y2mK_8jJRFA
+ nAOeUm71bWXPP65HaHm2r8ti.czc2g_CxzuReSyWyhdJNvLe5YTwpVZqCcbS
+ rhx7A7jtQiNf
+X-Originating-IP: [217.146.183.238]
+Authentication-Results: mta1087.mail.ird.yahoo.com from=yahoo.de; domainkeys=neutral (no sig); from=yahoo.de; dkim=pass (ok)
+Received: from 127.0.0.1 (HELO nm8-vm0.bullet.mail.ukl.yahoo.com) (217.146.183.238)
+ by mta1087.mail.ird.yahoo.com with SMTP; Wed, 09 Nov 2011 17:15:13 +0000
+Received: from [217.146.183.212] by nm8.bullet.mail.ukl.yahoo.com with NNFMP; 09 Nov 2011 17:15:12 -0000
+Received: from [77.238.184.80] by tm5.bullet.mail.ukl.yahoo.com with NNFMP; 09 Nov 2011 17:15:12 -0000
+Received: from [127.0.0.1] by smtp149.mail.ukl.yahoo.com with NNFMP; 09 Nov 2011 17:15:12 -0000
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.de; s=s1024; t=1320858912; bh=HOGFslJAcWp5sxJ2b+wkpi2WeYP/qYljF1212byJva8=; h=X-Yahoo-Newman-Id:X-Yahoo-Newman-Property:X-YMail-OSG:X-Yahoo-SMTP:Received:MIME-Version:Message-Id:Date:Content-Type:X-Mailer:From:X-FID:X-Priority:To:Subject; b=lb3j0vTYp/7M5Mgxy6xy1mn0rkgwxme0XA3iGfZrWEiQ3i3VNFsspvEXyWjerEKVcggZZyvH3Wjgc5U8HxxG73FrdEHjunpYEMy7Vp0sA4L+s+pndQdqI710P3SaDP21+VSA8HhKO03X3k4CVy5od2iSUGqdDa/hcJwYPgPBhZM=
+X-Yahoo-Newman-Id: 289474.41256.bm@smtp149.mail.ukl.yahoo.com
+X-YMail-OSG: QnILw7kVM1ljfDVZBNFZdSZEHkFwvGT1RdUyd.LgwTYAaBt
+ YMNKhbRWIEnXuXKkEHGuj_Sfc8hluz9X9kIlgz9EsdAdrB_gcA85hCzCU3_v
+ gpoxkri1I9D_2rR79yzbCR7Pi6ltYwTnticz1f40ZsonLoDAsGloPD26p_sp
+ xjH2379iUBy5m2tdBG4LP7C64twSNJVSivUYEQakBGKdvA7lNOsTZl.Gseh.
+ Qr8bS65cSwyKFKsC8jQGsowP7r_flK.gD.YJf7BKKCxal.SIOjAjIwZiDF2g
+ TufXZcJKOguH.kUeolYYe1DfS1qJ1WHYCkUKXZ8eloDp5N4id8XsNsl.IYdp
+ dXjQ71ZvRf6x0vMHEO1ykyWT45MpmKClZzsa_up1GmYM.mJ0fYPawsVDKLPj
+ vLa67ErmwS71fflMajhNBi9KZaCxLLBrXNvL0dprlyLG6rIpm4h9EvihcZOq
+ dARgemVEvLAtwAM50Et4NRKktpDhYBUr97kp.7dN0xTOzvqKqU3.2rfTFJ42
+ Kil6kccIv3e3njoOaQ1y.GQt6e_WjvtaDSVHRB3wJDN_8b7SE3nKkhkfMx.R
+ .ZEmZ4ywN8zixGYBOJrzH
+X-Yahoo-SMTP: 6uAm6CCswBCasj3_KVt4BWWJsLuOj9UNkP4D
+Received: from milkyway (pink_amaryllis@89.204.137.94 with login)
+ by smtp149.mail.ukl.yahoo.com with SMTP; 09 Nov 2011 17:15:06 +0000 GMT
+MIME-Version: 1.0
+Message-Id: <4EBAB50C.00000E.03032@MILKYWAY>
+Date: Wed, 9 Nov 2011 18:14:52 +0100 (Westeuropäische Normalzeit)
+Content-Type: Multipart/related;
+ charset="iso-8859-1";
+ type="multipart/alternative";
+ boundary="------------Boundary-00=_S8LEXFP0000000000000"
+X-Mailer: IncrediMail (6274918)
+From: "Eva C. Hammel" <pink_amaryllis@yahoo.de>
+X-FID: 1D3F6781-6356-4D2B-9E2A-C2FC357A0AB3
+X-Priority: 3
+To: <milonguero66@yahoo.de>
+Subject: =?iso-8859-1?B?UmVjaG51bmcgQW535Gx0aW4=?=
+
+
+
+--------------Boundary-00=_S8LEXFP0000000000000
+Content-Type: Multipart/Alternative;
+ boundary="------------Boundary-00=_S8LESPT1VA4000000000"
+
+
+--------------Boundary-00=_S8LESPT1VA4000000000
+Content-Type: Text/Plain;
+ charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+ist bearbeitet;=0D
+Ku=DF!
+--------------Boundary-00=_S8LESPT1VA4000000000
+Content-Type: Text/HTML;
+ charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+<HTML><HEAD>
+<META content=3D"text/html; charset=3Diso-8859-1" http-equiv=3DContent-Ty=
+pe>
+<META name=3DGENERATOR content=3D"IncrediMail 1.0">
+<STYLE>=0Av\:* {behavior:url (#default#vml);}=0A</STYLE>
+
+<!--IncrdiXMLRemarkStart>
+<IncrdiX-Info>
+<X-FID>1D3F6781-6356-4D2B-9E2A-C2FC357A0AB3</X-FID>
+<X-FVER>4,000000</X-FVER>
+<X-FIT>Letter</X-FIT>
+<X-FILE>Letter\jack-o-lantern.imf</X-FILE>
+<X-FCOL>Halloween</X-FCOL>
+<X-FCAT>Holidays</X-FCAT>
+<X-FDIS>Jack-O-Lantern</X-FDIS>
+<X-Extensions>SU1CTDEsNDYsgUmBSYUolTiZncGFTZmVjZlNiSiRME3FLJEkTTSRODSVjZ0=
+kgSQwlUmBSYFJgSxJTUJMMiwwLCxJTUJMMywwLCw=3D</X-Extensions>
+<X-BG>cid:05946161-B6A4-43DD-9BA5-19BC9B252161</X-BG>
+<X-BGT>no-repeat</X-BGT>
+<X-BGC>#f35901</X-BGC>
+<X-BGPX>right</X-BGPX>
+<X-BGPY>bottom</X-BGPY>
+<X-ASN>BCEB29C0-42D3-11D4-BA3E-0050DAC68030</X-ASN>
+<X-ASNF>0</X-ASNF>
+<X-ASH>BCEB29C0-42D3-11D4-BA3E-0050DAC68030</X-ASH>
+<X-ASHF>1</X-ASHF>
+<X-AN>BFF138F0-3EFC-11D4-BA3D-0050DAC68030</X-AN>
+<X-ANF>0</X-ANF>
+<X-AP>BFF138F0-3EFC-11D4-BA3D-0050DAC68030</X-AP>
+<X-APF>1</X-APF>
+<X-AD>E3F15280-2BF7-11D4-BA28-0050DAC68030</X-AD>
+<X-ADF>0</X-ADF>
+<X-AUTO>X-ASN,X-ASH,X-AN,X-AP,X-AD</X-AUTO>
+<X-CNT>;</X-CNT>
+</IncrdiX-Info>
+<IncrdiXMLRemarkEnd-->
+</HEAD>
+<BODY style=3D"MARGIN: 0px 200px 0px 10px; BACKGROUND-REPEAT: no-repeat; =
+FONT-FAMILY: Verdana; BACKGROUND-POSITION: right bottom; COLOR: #f8fdb5; =
+FONT-SIZE: 12pt" background=3Dcid:05946161-B6A4-43DD-9BA5-19BC9B252161 aL=
+ink=3D#00ff00 scroll=3Dyes link=3D#00ff00 bgProperties=3Dfixed bgColor=3D=
+#f35901 text=3D#f8fdb5 vLink=3D#00ff00 INCREDIFIXEDFORIMOL=3D"true" SIGCO=
+LOR=3D"16777215">
+<TABLE id=3DINCREDIMAINTABLE border=3D0 cellSpacing=3D0 cellPadding=3D2 w=
+idth=3D"100%">
+<TBODY>
+<TR>
+<TD style=3D"POSITION: relative; DIRECTION: ltr; FONT-SIZE: 12pt" id=3DIN=
+CREDITEXTREGION vAlign=3Dtop width=3D"100%">
+<DIV style=3D"PADDING-LEFT: 2px; FONT-FAMILY: DejaVu Serif; FONT-SIZE: 12=
+pt" id=3DINCREDI_TEXT_AREA>
+<DIV>ist bearbeitet;</DIV>
+<DIV>Ku=DF!</DIV><BR><BR><BR></DIV></TD></TR>
+<TR>
+<TD id=3DINCREDIFOOTER width=3D"100%">
+<TABLE cellSpacing=3D0 cellPadding=3D0 width=3D"100%">
+<TBODY>
+<TR>
+<TD width=3D"100%"></TD>
+<TD id=3DINCREDISOUND vAlign=3Dbottom align=3Dmiddle></TD>
+<TD id=3DINCREDIANIM vAlign=3Dbottom align=3Dmiddle></TD></TR></TBODY></T=
+ABLE></TD></TR></TBODY></TABLE><SPAN id=3DIncrediStamp><A href=3D"http://=
+www.incredimail.com/?id=3D619278&amp;did=3D10500&amp;ppd=3D2690,201107041=
+701,7,[TypeID],[IM_UPN2]&amp;rui=3D116597769&amp;sd=3D20111109"><SPAN nam=
+e=3D"imgCache" border=3D"0"><IMG border=3D0 alt=3D"Tierisch gut! KOSTENLO=
+SE E-Mail-Animationen =96 von IncrediMail! Hier Klicken!" src=3D"cid:F0E3=
+0D5E-C01C-4CC4-BCED-F05ADA8739AC"></SPAN></A></SPAN></BODY></HTML>
+--------------Boundary-00=_S8LESPT1VA4000000000--
+
+--------------Boundary-00=_S8LEXFP0000000000000
+Content-Type: image/jpeg;
+ name="pum_final.jpg"
+Content-Transfer-Encoding: base64
+Content-ID: <05946161-B6A4-43DD-9BA5-19BC9B252161>
+
+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
+bl25ds77eOV9qbzZe19/Pde3aOWfcdfd5dwy1MtfLHGdbKrrhjjiseHKZky7vHJz4xrnaY1xjXEx
+1xPDr8fucPPrjm4s8W2fy5xPD+LNPeH3S3XujcbLcZ7TT2O72uGelq7nb5TqTuNPKHjGcTjFfpz+
+bGbTznxOHk4t+fGP1ZtnGPH0zbP/ADZufVmdPDHTxsw6T272jj7PXbXG2dtds4zM/wBPxn1+f1YY
+Vre5tDVziNbWmMsMsqZ/UnjM9Iq2zk17LbGOmPwdx6MYTDvs6Ov9bPVjPUxjhiphwnGUTPOH0Ge1
+9WsxhlnW4bGt7n7nnhr47WI+pNpidWcZwnNPGfGYiecI5Ney48Zx6k1118219r/de49i993ffO57
+KO9dz3k33O51teNPLBclljjMRhj0xjE2Pde117rTTTTPo108NZmZ/HGb9rj9y7bXvOD9HG+ePH1X
++Dv/AHv9z9x707vp941NphsNGNrhscdppbidxp4/T1c841vqfTw45RnOMxOPAme1sxMdJ4YnTGJJ
+nbb6/Fr+2e38fYcH6Wu2d/zZ29WcTxxjElz0xPi1/br7l9n9ke6e69375huNbt/ddno7T6O1wxy1
+NLPb6mpnGc4zMOJjUmFzNbu/bdufh101xc6+r+qfzY1+WfCf49Gv7x7dv3vDrrptjXOu162eHXri
+/Jjfv73V7W7p7n3Pe/bO5+ts97hjlq9v19LU225y1o4fUweMYzljHqbHtvY8/FwY4+XE9Oem1xti
+fDLtewztp2mvDzZ/1NLM464zr8GI59/0nTCY1a8JzrGGXzrPQ7jHa5+ozs4WPf5x3GOf5cdK05Wz
+crqo8n/M589r+X5sM7uJuu9ZakWwwx0tKZnjh+WJjxyiWjn4+2nj1ywzyTq6ye55aeUY55Xwy4zl
+jMuMekTBtfoXwX9Xq6/vO40s9N45/picsc48Z4rh5Gz22mcZY7crzP3VhE7rS3GMKNTGYmfGcZ/t
+PV9jn8ucfB4X+4tMevTfGPHE+n4uhiTsXlcNTIyamuRFRvmFOAFcIDURUfoAYB/OQKwJ8AD6AGAY
+BgGAYBgGAYBgGAYBoA4APiAYBwAfoAfoAiQDAMAAiQDAP5SAmeKAPzATPyAMA+ICJAjKLMkBgGBJ
+nwKLEkEifkUV/wAQQHwAPxKg+oUZAfgBGUGBXHyIDAPiERlFcEEZQYB+IUc8+gB+PLwCDAMKRPiE
+GAcgH4AH/QAwHEA2AiQDAMCOQKwDAMB8wDAjAszIBgInwAjArAAH/EgGAmQD8/kAcgT+YAAwEAAK
+/MCAADAAIlAAUfUAwK+gEAoD+QEAMAwUAMIBaAAgAAAGAAMACgAFAD6FKMhRyAAFB+pAAAowDkFA
+UAMFGChSgKAoAAMhQFABQAgKAUAyFQqVJkrHOWnnMRHOeBWL6Q+0O00dDsWnERGGpr/l1L8q8H64
+zJ8t/uDkztzZ+T7Z7Zx44+z49cY8v8X0j7f7rjs+27vY4a30sO4brR09LOMLY546ccIfTj5M+Xd3
+wZ33xtL6dc5y6ru+D18mu+cX0Yz+LIPfXuGM+3zqbSInDR0sNvt89T+5lqTOm48Z6mn7f2v58Y28
+fHP2dXTe29tnTbr45zcvGvem7nZfU7NoZzGpH09tmqxMTwieU8Jn4nsvbeP1zkz88vVcO3qx6suj
+7l3THZ4x9GcsNDQwjSwmZi0yuULj8ZOx4eD1+PjnqudmLanfc8Yy1fqY4TjLicpmax48Op2+va48
+IwzvPNuaPuLKMIbiIiMonHpPPjE+PMw27Tqv6jlT37Pcx9HCcctDJTqTk18HjxZw/wC1xr1z4md6
+4257pttvrTuN3qrb6eM21csYmccYjhjEREc+RzacG22Jrjrlnr8cu8jsfvTa+z495b7t049k19TC
+Yxzictba6GvK0dSf+TOeEvlPxND/AHHa79znt9Nvz64+zbOPHH14beebtv8A6vV/rYxZ8Z46/wDN
+rjr9/wAGObnuOtnjjuM9OMc8/wAmWWEY1icevDjGR2WnDjHStXbPxcPPX188omM8p08pdn4f8XwN
+jGuuGNbun3GdOJxvjGMxN9SFFZnqYZ4amN3Gy7rqafH6t8MeWMp18YficmODGfJP1Jmurz7lE62W
+aytPHGZmXEwbmOHpHDnlznNaNTuGWWUTEPKP1TKcz/sMscUceeSuHrbiIiYmbTPGMH1ObXRx7bxw
+I7hlln9OcpxxiZtipyUx0Nn9LpWvjk6zybervbvLGeGEvGHxnxmDLXijL9TGfDycfX3meeCy4TMK
+0RyienzOTXjxjLDbkzhinfFntccv/wArU4T/ANX5V+J3Pa9Nvrw837vj1dvjP+Xb/B0cSdg8ljLU
+Ys8ZVhlQAwAFIyOfGQDAAVgQFALwAP5ARgHxAAGAYF+QE5AGBfgBGABTiAc/MIAAtGEoFGEGFAgw
+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
+lYw5nlAVzsu1952m6x0MtnuNLduJww+nnGc+E4qOPyMMb6bYtxHJtx767TOM4y+oPsl9we9732nr
+dt7pnlnuO3av0NHPVhZZaXD8uUzEzM4zKPmH9ye16a82OTTw28fr+L6F/b3NnuNPRydc6+GXrG13
+nc88f3GplEzg5+nOUZwso5Qjxm3b6Z6PT8nDpjo3O8+3Nn7r7Dnh3X/yNHS/72jGMRq4uOeL4xMM
+vbc2/ac116fsaO2Zv6JPV4Z8nhmP2U32v7a18dtEaXedhra37eYc6e72traUan/BqRxiJjgk/E9z
+v/cfFjn1xn+XbGLn/Lt5/Xhp69tyceM4z8emL5fTo8sw1M9LKdPWxnT1tPJamnk4yxyxmYmJjyk9
+LnW9ceDDHJXH3Gt/5enqOVMzz8JOTTX8ucNLm5Jy6bfTq0Y7rLTzrM/lj1UGWdMZwy155tMuRhvm
+9LNZ6Ocfnwnjy5fGYOPPF5+bb17jGczyeo/br/UH9wvtzp6Gw2O+/wAz9u4ZKO3b6J1p0Yng9POZ
+vGMf8LPMe5f212nebZ3n6fJ/m16X68eF+Zydv2vdbY/3GPVjPjtj+fGPj/xT/i6zwywPuvc/827l
+3DueeeOpnvt3rbrPKIrFtabTw6cZO74OD9Lj10x/TrjH3Oz7nbjzy7ejN0s1+rGI+h/9Iuxx1+6+
+6e45w8YnYbLTmePGM51co+Cx4nzr++d5pwa/8237nRe6bzinzz+Gv/8AJ9X7/uEYxqThnOepMzEY
+8ofLnJ8uteU4eCy4mHAy0Nr3jtmt2vuu20u5baco1MtrvNLHW074S8ZrnExKnkZa8m/HnG2mc65+
+OMzP4NnO23DyY5OPOdM+F1zOn2Oq0vbXtvQ0dTU0e07Hbdyc/T1Y2WjMcPGKo5s91zZ6Z32zj/my
+3v8Ae9xcY/U3zp8PXl1+tobnaaGO5wUbe9c8NLSx08dHLLksdOEp8V8S4zjfPz/a3tN9d9vTnxnn
+nOb97lbH3Z27sO21e9993ulseyaUTff7nOMdNx004555eWLMf9pyc2+OPj1ztvnyx4/4fa1O67Hb
+l/0+PF3+GPLHx2z/AE4+t4v73+/3uv7kb3P2x9rNtuO2e3dWctHW9w6mGWnuNZ8/oOJ+nC/vKcuv
+A+hdl/bPB2XHjn77OM7Y644/HXH/ADf5s/Lwdh7b7Vwdv+fk/wBTfH/Zr9/8315/L8MbdMvIvuj2
+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
+m52z/wBuJ+99c/b725sPZfac+0bDb7XZbfTikY7bSjDVyo/z6mUzOWeU84mT5Z3/AH2/dcmdt9s+
+Px/d5OXk4+O49GM/a7vfa2Hccdx2vXiM9tGETqy/zZTl+jGJiPDjl8og0c4zx7+rT+a/lz+9nrrO
+v0+586bn7O+zfbnd977i3upqd07l+5nc447rKKY6mplOcflxX6T2/J733XNx40xNcSdPH6Zd12ft
+/HvnGZnN/Bj+z7R717P3Tune5z1u5eytxucv22eEzuNXR1pjH/DyjH8+ETjPCZxjHkb3Pv2/c8Gm
+J6eXXE8pludrx78PdbaZ319O12men3Xpn54rJOyb7b7zU3G00NXCdxqV1NXaYfl11pzE5uJiJi0P
+p4dZOi5uLfXGM5+/ydxz8uJc9Z5ux3eh3TZ6OjtdnnjH6vo6sKM8lMy11iYJwcmNrnPROPn022zn
+OKx3uO87xExlqa+jpYzEPPQwzxeMypcwo5fqZ2fHx6bZ+Lc/T4t9M69evxz4fU6z3Dj27um2jaRv
+s9LumzWpo6O4lY5Y6uPDnVtc+h3PDvjXW+WXmccfNwcnXW4+X4PJt/37Q0tTV0dSjxyyx1YzzjDG
+MsZ8cZzyyhx/djid1x9rtnGM4+n7GHN7j6sZ1xcXx+nX72Zdg2nt/f8AYs88Pc+1ju2enluMthj2
+vffR0+HPPXxjG0+M0kw5eL05xnOM9Pnho7dvtnHXbPXw8c9fq/a6PX9h977vtdTuew7h2zuex01p
+av7DUynU0uMT/iYaqzw87Ywc+ebTix+bGcfX/g4OHh5c7ZmvT8en4uJ3v27tO390nZ/W1dxo5Yaf
+5JrE41xiMcXx4REI1eHvc8mvqxjzen7fsP1OPHq6X6fb9Pm4/ddnq6uhpaWGp+6x0Jx0NPSyi9HH
+5azEdKxyyNvt+6zi3o0u89uz6tca5uu2et8sfHHwad/7g93bv2po+0M9xlt+06OvlutPPHHGZnUy
+iYmzUqPzJ8pmTDh4e307jPNnF2ziHe9nzcvH/pbenMnl18/s+ldn233R7l7b2fT7ZhuoyxxlRvJx
+xjdac8OMZTDxj4KevM4efsO35OX9TOs+Xk0OHvO500xptv6vL4ff8vnl0HcdPW3+pnnvtxq7zXc4
+5a24znWy5zP6sp81zNjTONOmuMa4+XR3PBx+vi69c/f9Pg63T2n0NS2nGOWlaYzjPkvPylHPnkrD
+k7bXbXOM46Z8WQ/V9sftrfttP6yp9P8Abadv0urXh/ebtx5cDSnN6vHM+vP0/wAHH/t+3np/Tx/2
+4+Hx8ZOvx9XXw6PHn5nrnzhWEHHiBPOeQUcAX+YE+IBlQaIHUKFQZA+HABADoVRkQfUocOpAZVGE
+ORAclB/ABzAMijKgwD+RAfAoP+oAgFBkDnBQmfQAwDkAwDARIBgADnqA+YB/MBzAMAwHmAjkAAnE
+CsAAAATmBeYEYF+AEArAgFYEAAX4gRgX+QEYF4gGBAgFUJU+AUAoEAAAHwCEhT4gAgFAH4hD4hRg
+AgBQoEQKBAAAfkFAgAAAAH8MAA+AAACgAACgAAAAAPIAAAAAAAAAAAAAAACgAJQAFAAAIAAUCgAF
+WImZUc/ACBAACgAABu6G519rq46+21c9HWx/Tnp5TjlHzgx21xtiZxWenJtpm65mfkzb259zu7dq
+1Pp90e/2mUK35cdbDzjKYU/CTqe49s4+TH5fy5/B6723+6u67XM5P9XX4Z8fsy9W9s+6dr7j0stz
+sNxhjGCx1NHJYbjTiedseU/GDzPddntw9NsfwfR/b/7i7bu9bjOddvhnxZho62w08M8txvPyYw8c
+VNpfDl5HT7a8l6au3x3/ABZxNc9W/O+22Orjp6OcxpZYROnnKico5xPHlE8oOD07Z65bOuc5xfwb
+213V9SfqakRpL/uyomseE8uBhyZxjHTDDlmMdWn/ADXR2ncMc9j3CNjOpnMZ5amnOpoTGUcbxhjK
+fXLGCen9TWb638Muo3300xPTnPn0bG6y1t9uNfbb/CM9acZyznHK2nljEcIeEquXTKJNjXTXXXGd
+fBvY07fn0xjHXH4/b5sGjt+ns9x9DZY02ueWWpGjlxnK0xCxmfBHdY5M7a/m8XX8XYY7bkz6M59O
+3llyNbtEamWEaf8AgznlaVlaIcdIjy5F15M+fU5tMZzcdM1rnYaunjTQznDQz4T9WIrnnjMRz4Su
+POZLnfHnj7mtrx5znOMbZx9fn9PrcXue1jVwyy166VcHCl4ZVl/KY8xxbTPRhz7a63O+cYxPp9Xy
+cXZbbW2+E/XwmZy/N9SOOE24xxj/AHnNyb4znoy7e+nr5/c7DSy1NDUjcxqTpaWi9TPcTNcYxxj8
+zmOcRHM456umPFy8/Jx8emdt89MfFzPaX3K9o7Lebnt/ce47XX225/L/AORhuNHa5ZRMJ5/S1V/1
+I3+X2/n21xdbPn1eR193498zGfTnyz4Ox1e8dsnumX+W57XX0taZzxjt25y3mGnhEq2cVxnGIacw
+dX3Pab6aXP5fr8/qeg7b3TTXOPXt6r9V+vo19x9ydt7ft9SN3uccNXTxnONDC31dR/piIlQp8fma
+vD2vJyZ/Ljp8fLDY7r3ntuLTOfV1+Hnn5L7Q3Pfe4rf+4dDPDSxzwjtGlm8NHT0s4+q1jDnhFrTx
+4HH3+vDw/l4uuf6s+fw+mHn+w5+fuc7b8txjMmJjwdfvu0bPe6e73G908tbcZTnMa2OWWLyymPzR
+HKeMDt+75OLOMau5732/h7v0+rOcYxjyz93T6R2X2v3HuH2tsN5p9s3ez2+ptdzfTw1tLLKcscol
+OYj82Mzbh0lrmZ+68nF3Xpzm+HV4rPtu/bYzpvj1a58Jn9z2v2z37u/eu2Z9y77noZ5bfLKPrbbH
+LS08p01OWUxnlPKJ5uOTR8+77g49OTGvHfDz6sduLGm3pxjxbu+7pG72U57bWx1NtjOOrq447jLb
+5Z4xx4ZYY5TjyibVObtu13tz4Y+ng2ePjzjaZx+FYz7c9xdh7/3DU0+6Z222llMztp1JnLKcVEZR
+nnjjnOHCfP4co7LuOPbimcY+5t8/63Dx4xp/N8f8GZf+7e2+xaE9v7Hp7fT22OUzOEfV1pnOeczM
+Y5ekyaf6e+/XOHRbdtzc23r5LnP2MT99Z7T3r2v/ADDs+3z7b7v2GUbnZb/baWeP1Yxj82nnKxme
+EOG+MR0mYOy7Tfbi29O/5tM9M4+DPixt2+0zm658vh9Pk8v3H3A7r27t2c952Wj3PueP+HttbRjL
+bYzmph6tZnl/+nEOenU7f/x3HycmPRtnXXz8/u/xd72XqznOPCfjj6ebpfr/AHM7hp6m83G40NGc
+4xyx2Wphp2zif0xlhMZzEypTyiTYzjsuLpjGc/O/vdl69PB0PeMu7d009DV19nGy3Ns9DV08cZrE
+4Z8YxnLj18fmb/Fnj0z0zceOHNx7eu4uMZv3NOOt2T25pT3HvPtfZbjdxM5aW83GtnOnjHRaET+f
+JxwiZ4vjyN7tuXPLjOmu+c/KfvdT3vH+nyY2x6dMdc7b56yfDXzznyw7DtfuT3P7n1NTuffNDHS2
+GrttTS7Vt4jT0cMdGfyxEYxHCJXDpw84OP3HOnHrji0z1uM7fT5NX2n/AP6eX9X07Z0xn8uc+efp
+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
+qNqvM47tfA/Vx6bM/VP3eL//2Q==
+
+--------------Boundary-00=_S8LEXFP0000000000000
+Content-Type: image/gif;
+ name="stampa_girl_line_de.gif"
+Content-Transfer-Encoding: base64
+Content-ID: <F0E30D5E-C01C-4CC4-BCED-F05ADA8739AC>
+
+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
+BkEAADs=
+
+--------------Boundary-00=_S8LEXFP0000000000000--
+
+
diff --git a/comm/mailnews/local/test/unit/data/message1.eml b/comm/mailnews/local/test/unit/data/message1.eml
new file mode 100644
index 0000000000..e82610ebc7
--- /dev/null
+++ b/comm/mailnews/local/test/unit/data/message1.eml
@@ -0,0 +1,7 @@
+From: from@invalid.com
+To: to@invalid.com
+Subject: test mail
+
+this email is in dos format because that is what the interface requires
+
+test message
diff --git a/comm/mailnews/local/test/unit/data/message2.eml b/comm/mailnews/local/test/unit/data/message2.eml
new file mode 100644
index 0000000000..9574d946aa
--- /dev/null
+++ b/comm/mailnews/local/test/unit/data/message2.eml
@@ -0,0 +1,9 @@
+From: from@invalid.com
+To: to@invalid.com
+Message-ID: <abc@def.org>
+cc: dupemail@test.com
+Subject: test mail
+
+this email is in dos format because that is what the interface requires
+
+test message
diff --git a/comm/mailnews/local/test/unit/data/message3.eml b/comm/mailnews/local/test/unit/data/message3.eml
new file mode 100644
index 0000000000..92722046f9
--- /dev/null
+++ b/comm/mailnews/local/test/unit/data/message3.eml
@@ -0,0 +1,7 @@
+From: from@invalid.com
+To: to@invalid.com
+Subject: test mail 2
+
+this email is in dos format because that is what the interface requires
+
+test message 2
diff --git a/comm/mailnews/local/test/unit/data/movemailspool b/comm/mailnews/local/test/unit/data/movemailspool
new file mode 100644
index 0000000000..1fb0587c49
--- /dev/null
+++ b/comm/mailnews/local/test/unit/data/movemailspool
@@ -0,0 +1,169 @@
+From - Tue Oct 02 00:26:47 2007
+X-Account-Key: account2
+X-UIDL: UID18345-1161858178
+X-Mozilla-Status: 0001
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys: $label4
+Received: from caspiaco by host29.example.com with local-bsmtp (Exim 4.68)
+ (envelope-from <bugzilla-daemon@mozilla.org>)
+ id 1JtrbR-0001Kc-Nf
+ for kent@example.com; Wed, 07 May 2008 15:55:21 -0600
+X-Spam-Checker-Version: SpamAssassin 3.2.3 (2007-08-08) on
+ host29.example.com
+X-Spam-Level:
+X-Spam-Status: No, score=-2.4 required=5.0 tests=AWL,BAYES_00 autolearn=ham
+ version=3.2.3
+Received: from webapp01.sj.mozilla.com ([63.245.208.146] helo=webapp-out.mozilla.org)
+ by host29.example.com with esmtps (TLSv1:AES256-SHA:256)
+ (Exim 4.68)
+ (envelope-from <bugzilla-daemon@mozilla.org>)
+ id 1JtrbR-0001KT-FP
+ for kent@example.com; Wed, 07 May 2008 15:55:09 -0600
+Received: from mrapp51.mozilla.org (mrapp51.mozilla.org [127.0.0.1])
+ by webapp-out.mozilla.org (8.13.8/8.13.8) with ESMTP id m47LtAFJ007547
+ for <kent@example.com>; Wed, 7 May 2008 14:55:10 -0700
+Received: (from root@localhost)
+ by mrapp51.mozilla.org (8.13.8/8.13.8/Submit) id m47LtAEf007542;
+ Wed, 7 May 2008 14:55:10 -0700
+Date: Wed, 7 May 2008 14:55:10 -0700
+Message-Id: <200805072155.m47LtAEf007542@mrapp51.mozilla.org>
+To: invalid@example.com
+From: PrimaryEmail1@test.invalid
+Cc: invalid@example.com
+Subject: [Bug 397009] A filter will let me tag, but not untag
+X-Bugzilla-Reason: None
+X-Bugzilla-Type: newchanged
+X-Bugzilla-Watch-Reason: QAcontact filters@mail.bugs
+X-Bugzilla-Product: Core
+X-Bugzilla-Component: MailNews: Filters
+X-Bugzilla-Keywords:
+X-Bugzilla-Severity: enhancement
+X-Bugzilla-Who: bugmail@example.org
+X-Bugzilla-Status: NEW
+X-Bugzilla-Priority: --
+X-Bugzilla-Assigned-To: nobody@mozilla.org
+X-Bugzilla-Target-Milestone: ---
+X-Bugzilla-Changed-Fields: Blocks
+In-Reply-To: <bug-397009-254728@https.bugzilla.mozilla.org/>
+References: <bug-397009-254728@https.bugzilla.mozilla.org/>
+Content-Type: text/plain; charset="UTF-8"
+MIME-Version: 1.0
+X-user: ::::63.245.208.146:host29.hostmonster.com::::::
+DomainKey-Status: no signature
+
+Do not reply to this email. You can add comments to this bug at
+https://bugzilla.mozilla.org/show_bug.cgi?id=397009
+
+
+Some User <bugmail@example.org> changed:
+
+ What |Removed |Added
+----------------------------------------------------------------------------
+ Blocks| |432710
+
+
+
+
+--
+Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email
+------- You are receiving this mail because: -------
+You are watching the QA contact of the bug.
+
+
+From - Sat Jun 07 04:23:42 2008
+X-Mozilla-Status: 0000
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:
+FCC: mailbox://nobody@Local%20Folders/Sent
+BCC: Some User <u1@example.com>, Another Person <u2@example.com>
+X-Identity-Key: id2
+Message-ID: <4849BF7B.2030800@example.com>
+Date: Sat, 07 Jun 2008 04:23:42 +0530
+From: Some User <example@example.com>
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0
+User-Agent: Thunderbird 3.0a2pre (Windows/2008060416)
+MIME-Version: 1.0
+To: bugmail@example.org
+Subject: Hello, did you receive my bugmail?
+Content-Type: text/html; charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="content-type"
+ content="text/html; charset=ISO-8859-1">
+</head>
+<body text="#000000" bgcolor="#ffffff">
+This is an HTML message. Did you receive my bugmail?<br>
+<br>
+Thanks, and goodbye.<br>
+<br>
+--<br>
+Some User<br>
+<br>
+</body>
+</html>
+
+
+From - Tue Oct 02 00:26:47 2007
+X-UIDL: UID18345-1161858178
+X-Mozilla-Status: 0001
+X-Mozilla-Status2: 00000000
+Received: (from root@localhost)
+ by mrapp51.mozilla.org (8.13.8/8.13.8/Submit) id m47LtAEf007542;
+ Wed, 7 May 2008 14:55:10 -0700
+Date: Wed, 7 May 2008 14:55:10 -0700
+Message-Id: <200805072155.m47LtAEf007542@mrapp51.mozilla.org>
+To: invalid@example.com
+From: PrimaryEmail1@test.invalid
+Cc: invalid@example.com
+Subject: [Bug 655578] list-id filter broken
+X-Bugzilla-Reason: None
+X-Bugzilla-Type: newchanged
+X-Bugzilla-Watch-Reason: QAcontact filters@mail.bugs
+X-Bugzilla-Product: Core
+X-Bugzilla-Component: MailNews: Filters
+X-Bugzilla-Keywords:
+X-Bugzilla-Severity: enhancement
+X-Bugzilla-Who: bugmail@example.org
+X-Bugzilla-Status: NEW
+X-Bugzilla-Priority: --
+X-Bugzilla-Assigned-To: nobody@mozilla.org
+X-Bugzilla-Target-Milestone: ---
+X-Bugzilla-Changed-Fields: Blocks
+In-Reply-To: <bug-397009-254728@https.bugzilla.mozilla.org/>
+References: <bug-397009-254728@https.bugzilla.mozilla.org/>
+ <4DC2493C.4060403@gmx.invalid>
+ <BANLkTin2w8LJxYGHV3_5NpFbsiBhrP96XA@mail.gmail.invalid>
+ <175221688.20110506234025@my_localhost>
+ <201105072315.25120@thufir.ingo-kloecker.invalid>
+ <BANLkTinacQCd+mZ7fL1THLK55X2+u9g5-w@mail.gmail.invalid>
+ <05433510.20110507224940@my_localhost>
+ <4DC5C015.7050800@sixdemonbag.invalid>
+ <BANLkTinv7NvPG9gE1Fha+X+6ZkHzdXdRdg@mail.gmail.invalid>
+ <BANLkTi=6zDTsYymc+bUTwPOM2AohJD2wfA@mail.gmail.invalid>
+Reply-To: FOO <bar@foo.invalid>
+List-Id: Help and discussion among users of GnuPG <gnupg-users.gnupg.org>
+Content-Type: text/plain; charset="UTF-8"
+MIME-Version: 1.0
+
+Do not reply to this email. You can add comments to this bug at
+https://bugzilla.mozilla.org/show_bug.cgi?id=397009
+
+
+Some User <bugmail@example.org> changed:
+
+ What |Removed |Added
+----------------------------------------------------------------------------
+ Blocks| |432710
+
+
+
+
+--
+Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email
+------- You are receiving this mail because: -------
+You are watching the QA contact of the bug.
+
+
diff --git a/comm/mailnews/local/test/unit/head_maillocal.js b/comm/mailnews/local/test/unit/head_maillocal.js
new file mode 100644
index 0000000000..dff897b36e
--- /dev/null
+++ b/comm/mailnews/local/test/unit/head_maillocal.js
@@ -0,0 +1,214 @@
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { mailTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MailTestUtils.jsm"
+);
+var { localAccountUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/LocalAccountUtils.jsm"
+);
+
+var test = null;
+
+// WebApps.jsm called by ProxyAutoConfig (PAC) requires a valid nsIXULAppInfo.
+var { getAppInfo, newAppInfo, updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+updateAppInfo();
+
+// Ensure the profile directory is set up
+do_get_profile();
+
+var gDEPTH = "../../../../";
+
+// Import the pop3 server scripts
+/* import-globals-from ../../../test/fakeserver/Maild.jsm */
+/* import-globals-from ../../../test/fakeserver/Auth.jsm */
+/* import-globals-from ../../../test/fakeserver/Pop3d.jsm */
+var {
+ nsMailServer,
+ gThreadManager,
+ fsDebugNone,
+ fsDebugAll,
+ fsDebugRecv,
+ fsDebugRecvSend,
+} = ChromeUtils.import("resource://testing-common/mailnews/Maild.jsm");
+var { AuthPLAIN, AuthLOGIN, AuthCRAM } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Auth.jsm"
+);
+var {
+ Pop3Daemon,
+ POP3_RFC1939_handler,
+ POP3_RFC2449_handler,
+ POP3_RFC5034_handler,
+} = ChromeUtils.import("resource://testing-common/mailnews/Pop3d.jsm");
+
+// Setup the daemon and server
+// If the debugOption is set, then it will be applied to the server.
+function setupServerDaemon(debugOption) {
+ var daemon = new Pop3Daemon();
+ var extraProps = {};
+ function createHandler(d) {
+ var handler = new POP3_RFC5034_handler(d);
+ for (var prop in extraProps) {
+ handler[prop] = extraProps[prop];
+ }
+ return handler;
+ }
+ var server = new nsMailServer(createHandler, daemon);
+ if (debugOption) {
+ server.setDebugLevel(debugOption);
+ }
+ return [daemon, server, extraProps];
+}
+
+function createPop3ServerAndLocalFolders(port, hostname = "localhost") {
+ localAccountUtils.loadLocalMailAccount();
+ let server = localAccountUtils.create_incoming_server(
+ "pop3",
+ port,
+ "fred",
+ "wilma",
+ hostname
+ );
+ return server;
+}
+
+var gCopyListener = {
+ callbackFunction: null,
+ copiedMessageHeaderKeys: [],
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {
+ try {
+ this.copiedMessageHeaderKeys.push(aKey);
+ } catch (ex) {
+ dump(ex);
+ }
+ },
+ GetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ if (this.callbackFunction) {
+ mailTestUtils.do_timeout_function(0, this.callbackFunction, null, [
+ this.copiedMessageHeaderKeys,
+ aStatus,
+ ]);
+ }
+ },
+};
+
+/**
+ * copyFileMessageInLocalFolder
+ * A utility wrapper of nsIMsgCopyService.copyFileMessage to copy a message
+ * into local inbox folder.
+ *
+ * @param aMessageFile An instance of nsIFile to copy.
+ * @param aMessageFlags Message flags which will be set after message is
+ * copied
+ * @param aMessageKeyword Keywords which will be set for newly copied
+ * message
+ * @param aMessageWindow Window for notification callbacks, can be null
+ * @param aCallback Callback function which will be invoked after
+ * message is copied
+ */
+function copyFileMessageInLocalFolder(
+ aMessageFile,
+ aMessageFlags,
+ aMessageKeywords,
+ aMessageWindow,
+ aCallback
+) {
+ // Set up local folders
+ localAccountUtils.loadLocalMailAccount();
+
+ gCopyListener.callbackFunction = aCallback;
+ // Copy a message into the local folder
+ MailServices.copy.copyFileMessage(
+ aMessageFile,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ aMessageFlags,
+ aMessageKeywords,
+ gCopyListener,
+ aMessageWindow
+ );
+}
+
+function do_check_transaction(real, expected) {
+ // If we don't spin the event loop before starting the next test, the readers
+ // aren't expired. In this case, the "real" real transaction is the last one.
+ if (Array.isArray(real)) {
+ real = real[real.length - 1];
+ }
+
+ // real.them may have an extra QUIT on the end, where the stream is only
+ // closed after we have a chance to process it and not them. We therefore
+ // excise this from the list
+ if (real.them[real.them.length - 1] == "QUIT") {
+ real.them.pop();
+ }
+
+ if (expected[0] == "AUTH") {
+ // We don't send initial AUTH command now.
+ expected = expected.slice(1);
+ }
+
+ Assert.equal(real.them.join(","), expected.join(","));
+ dump("Passed test " + test + "\n");
+}
+
+function create_temporary_directory() {
+ let directory = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ directory.append("mailFolder");
+ directory.createUnique(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0700", 8));
+ return directory;
+}
+
+function create_sub_folders(parent, subFolders) {
+ parent.leafName = parent.leafName + ".sbd";
+ parent.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0700", 8));
+
+ for (let folder in subFolders) {
+ let subFolder = parent.clone();
+ subFolder.append(subFolders[folder].name);
+ subFolder.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0600", 8));
+ if (subFolders[folder].subFolders) {
+ create_sub_folders(subFolder, subFolders[folder].subFolders);
+ }
+ }
+}
+
+function create_mail_directory(subFolders) {
+ let root = create_temporary_directory();
+
+ for (let folder in subFolders) {
+ if (!subFolders[folder].subFolders) {
+ continue;
+ }
+ let directory = root.clone();
+ directory.append(subFolders[folder].name);
+ create_sub_folders(directory, subFolders[folder].subFolders);
+ }
+
+ return root;
+}
+
+function setup_mailbox(type, mailboxPath) {
+ let user = Services.uuid.generateUUID().toString();
+ let incomingServer = MailServices.accounts.createIncomingServer(
+ user,
+ "Local Folder",
+ type
+ );
+ incomingServer.localPath = mailboxPath;
+
+ return incomingServer.rootFolder;
+}
+
+registerCleanupFunction(function () {
+ load(gDEPTH + "mailnews/resources/mailShutdown.js");
+});
diff --git a/comm/mailnews/local/test/unit/test_Pop3Channel.js b/comm/mailnews/local/test/unit/test_Pop3Channel.js
new file mode 100644
index 0000000000..b518e5b19f
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_Pop3Channel.js
@@ -0,0 +1,75 @@
+/* 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 { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+let [daemon, server] = setupServerDaemon();
+server.start();
+registerCleanupFunction(() => {
+ server.stop();
+});
+
+/**
+ * Test Pop3Channel can download a partial message correctly.
+ */
+add_task(async function test_fetchPartialMessage() {
+ // Set up a test message.
+ daemon.setMessages(["message1.eml"]);
+
+ // Set up the incoming server to fetch headers only.
+ let incomingServer = createPop3ServerAndLocalFolders(server.port);
+ incomingServer
+ .QueryInterface(Ci.nsILocalMailIncomingServer)
+ .createDefaultMailboxes();
+ incomingServer.headersOnly = true;
+
+ // Use GetNewMail to fetch the headers.
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ incomingServer.rootFolder.getChildNamed("Inbox"),
+ incomingServer
+ );
+ await urlListener.promise;
+
+ // Check TOP is correctly sent.
+ let transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "TOP 1 0",
+ ]);
+
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ // A nsIPop3URL instance is needed to construct a Pop3Channel, but we can't
+ // create a nsIPop3URL instance in JS directly. A workaround is constructing a
+ // mailbox: url with a uidl query, then newChannel will return a Pop3Channel.
+ let channel = NetUtil.newChannel({
+ uri: `${incomingServer.serverURI}/Inbox?uidl=UIDL1`,
+ loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+ channel.asyncOpen(streamListener);
+ await streamListener.promise;
+
+ // Check RETR is correctly sent.
+ transaction = server.playTransaction();
+ do_check_transaction(transaction, [
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "RETR 1",
+ "DELE 1",
+ ]);
+});
diff --git a/comm/mailnews/local/test/unit/test_bug457168.js b/comm/mailnews/local/test/unit/test_bug457168.js
new file mode 100644
index 0000000000..6b2ec41b88
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_bug457168.js
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Protocol tests for POP3.
+ */
+
+var server;
+var daemon;
+var incomingServer;
+var thisTest;
+
+var tests = [
+ {
+ title: "Get New Mail, One Message",
+ messages: ["message2.eml", "message2.eml", "message3.eml"],
+ transaction: [
+ "AUTH",
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "RETR 1",
+ "DELE 1",
+ "RETR 2",
+ "DELE 2",
+ "RETR 3",
+ "DELE 3",
+ ],
+ },
+];
+
+var urlListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, result) {
+ try {
+ var transaction = server.playTransaction();
+
+ do_check_transaction(transaction, thisTest.transaction);
+
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 2);
+ Assert.equal(localAccountUtils.inboxFolder.getNumUnread(false), 1);
+
+ Assert.equal(result, 0);
+ } catch (e) {
+ // If we have an error, clean up nicely before we throw it.
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_throw(e);
+ }
+
+ // Let OnStopRunningUrl return cleanly before doing anything else.
+ do_timeout(0, checkBusy);
+ },
+};
+
+function checkBusy() {
+ if (tests.length == 0) {
+ incomingServer.closeCachedConnections();
+
+ // No more tests, let everything finish
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+ return;
+ }
+
+ // If the server hasn't quite finished, just delay a little longer.
+ if (incomingServer.serverBusy) {
+ do_timeout(20, checkBusy);
+ return;
+ }
+
+ testNext();
+}
+
+function testNext() {
+ thisTest = tests.shift();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ server.resetTest();
+
+ // Set up the test
+ test = thisTest.title;
+
+ daemon.setMessages(thisTest.messages);
+
+ // Now get the mail
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+
+ server.performTest();
+ } catch (e) {
+ server.stop();
+
+ do_throw(e);
+ } finally {
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
+
+function run_test() {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+ Services.prefs.setIntPref("mail.server.default.dup_action", 2);
+
+ server = setupServerDaemon();
+ daemon = server[0];
+ server = server[1];
+ server.start();
+
+ // Set up the basic accounts and folders
+ incomingServer = createPop3ServerAndLocalFolders(server.port);
+
+ // Create a cc filter
+ var filters = incomingServer.getFilterList(null);
+
+ var filter = filters.createFilter("cc dup test");
+ filter.filterType = Ci.nsMsgFilterType.Incoming;
+ var searchTerm = filter.createTerm();
+ searchTerm.attrib = Ci.nsMsgSearchAttrib.CC;
+ searchTerm.op = Ci.nsMsgSearchOp.Contains;
+ var oldValue = searchTerm.value;
+ oldValue.attrib = Ci.nsMsgSearchAttrib.CC;
+ oldValue.str = "dupemail";
+ searchTerm.value = oldValue;
+ filter.appendTerm(searchTerm);
+ var filterAction = filter.createAction();
+ filterAction.type = Ci.nsMsgFilterAction.MarkRead;
+ filter.appendAction(filterAction);
+ filter.enabled = true;
+
+ filters.insertFilterAt(0, filter);
+
+ incomingServer.setFilterList(filters);
+
+ // Check that we haven't got any messages in the folder, if we have its a test
+ // setup issue.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+
+ do_test_pending();
+
+ testNext();
+}
diff --git a/comm/mailnews/local/test/unit/test_duplicateKey.js b/comm/mailnews/local/test/unit/test_duplicateKey.js
new file mode 100644
index 0000000000..845a25cc1a
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_duplicateKey.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test deletes intermediate messages, then compacts, then adds more
+ * messages, testing for duplicated keys in bug 1202105.
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_task(async function runPump() {
+ gPOP3Pump.files = [
+ "../../../data/bugmail1",
+ "../../../data/bugmail1",
+ "../../../data/bugmail1",
+ "../../../data/bugmail1",
+ "../../../data/bugmail1",
+ ];
+ await gPOP3Pump.run();
+
+ // get message headers for the inbox folder
+ var hdrs = showMessages(localAccountUtils.inboxFolder);
+ Assert.equal(hdrs.length, 5, "Check initial db count");
+
+ // Deletes 2 middle messages.
+ let deletes = [hdrs[1], hdrs[2]];
+
+ // Note the listener won't work because this is a sync delete,
+ // but it should!
+ localAccountUtils.inboxFolder.deleteMessages(
+ deletes,
+ null, // in nsIMsgWindow msgWindow,
+ true, // in boolean deleteStorage,
+ true, // in boolean isMove,
+ null, // in nsIMsgCopyServiceListener,
+ false
+ ); // in boolean allowUndo
+
+ dump("Messages after delete\n");
+ hdrs = showMessages(localAccountUtils.inboxFolder);
+ Assert.equal(hdrs.length, 3, "Check db length after deleting two messages");
+
+ // compact
+ var listener = new PromiseTestUtils.PromiseUrlListener();
+ localAccountUtils.inboxFolder.compact(listener, null);
+ await listener.promise;
+
+ dump("Messages after compact\n");
+ hdrs = showMessages(localAccountUtils.inboxFolder);
+ Assert.equal(hdrs.length, 3, "Check db length after compact");
+
+ // Add some more messages. This fails in nsMsgDatabase::AddNewHdrToDB with
+ // NS_ERROR("adding hdr that already exists") before bug 1202105.
+ gPOP3Pump.files = ["../../../data/draft1"];
+ await gPOP3Pump.run();
+
+ dump("Messages after new message\n");
+ hdrs = showMessages(localAccountUtils.inboxFolder);
+ Assert.equal(hdrs.length, 4, "Check db length after adding one message");
+
+ gPOP3Pump = null;
+});
+
+function showMessages(folder) {
+ var hdrs = [];
+ for (let hdr of folder.msgDatabase.enumerateMessages()) {
+ hdrs.push(hdr);
+ dump(
+ "key " +
+ (hdrs.length - 1) +
+ " is " +
+ hdrs[hdrs.length - 1].messageKey +
+ "\n"
+ );
+ }
+ return hdrs;
+}
diff --git a/comm/mailnews/local/test/unit/test_fileName.js b/comm/mailnews/local/test/unit/test_fileName.js
new file mode 100644
index 0000000000..e69b1b45a4
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_fileName.js
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Test handling of special chars in folder names
+ */
+
+function run_test() {
+ let testFolderName = "";
+ let OSname = Services.sysinfo.getProperty("name");
+ if (OSname == "Windows_NT") {
+ // On Windows test file with ' ' in the name.
+ testFolderName = "bugmail 1";
+ } else if (OSname == "Linux") {
+ // On Linux test file with '`' in the name.
+ testFolderName = "bugmail`1";
+ } else if (OSname == "Darwin") {
+ // On Mac test file with ':' in the name (generated from Mozilla 1.8 branch).
+ testFolderName = "bugmail:1";
+ } else {
+ // Not sure what this OS is so just use a safe name.
+ testFolderName = "bugmail1";
+ }
+
+ let bugmail = do_get_file("../../../data/bugmail-1");
+ let bugmailmsf = do_get_file("../../../data/bugmail-1.msf");
+ let localMailDir = do_get_profile().clone();
+ localMailDir.append("Mail");
+ localMailDir.append("Local Folders");
+ let pop3dir = do_get_profile().clone();
+ pop3dir.append("Mail");
+ pop3dir.append("poptest");
+ // Copy the file to the local mail directory
+ bugmail.copyTo(localMailDir, testFolderName);
+ bugmailmsf.copyTo(localMailDir, testFolderName + ".msf");
+
+ // Copy the file to the pop3 server mail directory
+ bugmail.copyTo(pop3dir, testFolderName);
+ bugmailmsf.copyTo(pop3dir, testFolderName + ".msf");
+
+ // These preferences set up a local folders account so we'll use the
+ // contents of the Local Folders dir we've already pre-populated.
+ Services.prefs.setCharPref("mail.account.account1.server", "server1");
+ Services.prefs.setCharPref("mail.account.account2.server", "server2");
+ Services.prefs.setCharPref(
+ "mail.accountmanager.accounts",
+ "account1,account2"
+ );
+ Services.prefs.setCharPref(
+ "mail.accountmanager.localfoldersserver",
+ "server1"
+ );
+ Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account1");
+ Services.prefs.setCharPref(
+ "mail.server.server1.directory-rel",
+ "[ProfD]Mail/Local Folders"
+ );
+ Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server1.name", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server1.type", "none");
+ Services.prefs.setCharPref("mail.server.server1.userName", "nobody");
+ Services.prefs.setCharPref(
+ "mail.server.server2.directory-rel",
+ "[ProfD]Mail/poptest"
+ );
+ Services.prefs.setCharPref("mail.server.server2.hostname", "poptest");
+ Services.prefs.setCharPref("mail.server.server2.name", "poptest");
+ Services.prefs.setCharPref("mail.server.server2.type", "pop3");
+ Services.prefs.setCharPref("mail.server.server2.userName", "user");
+ // This basically says to ignore the time stamp in the .msf file
+ Services.prefs.setIntPref("mail.db_timestamp_leeway", 0x7fffffff);
+
+ localAccountUtils.incomingServer = MailServices.accounts.localFoldersServer;
+ // force load of accounts.
+ MailServices.accounts.defaultAccount;
+
+ let pop3Server = MailServices.accounts.findServer("user", "poptest", "pop3");
+ let rootFolder =
+ localAccountUtils.incomingServer.rootMsgFolder.QueryInterface(
+ Ci.nsIMsgLocalMailFolder
+ );
+ let pop3Root = pop3Server.rootMsgFolder;
+
+ // Note: Inbox is not created automatically when there is no deferred server,
+ // so we need to create it.
+ localAccountUtils.inboxFolder = rootFolder.createLocalSubfolder("Inbox");
+ // a local inbox should have a Mail flag!
+ localAccountUtils.inboxFolder.setFlag(Ci.nsMsgFolderFlags.Mail);
+
+ rootFolder = localAccountUtils.incomingServer.rootMsgFolder;
+ bugmail = rootFolder.getChildNamed(testFolderName);
+ Assert.equal(bugmail.getTotalMessages(false), 1);
+ bugmail = pop3Root.getChildNamed(testFolderName);
+ Assert.equal(bugmail.getTotalMessages(false), 1);
+
+ // Check if creating an empty folder returns a proper error
+ // instead of crash (bug 831190).
+ try {
+ rootFolder.createSubfolder("", null);
+ do_throw("createSubfolder() should have failed on empty folder name.");
+ } catch (e) {
+ // NS_MSG_ERROR_INVALID_FOLDER_NAME
+ Assert.equal(e.result, 2153054242);
+ }
+
+ // And try to create an existing folder again.
+ try {
+ rootFolder.createSubfolder(testFolderName, null);
+ do_throw("createSubfolder() should have failed on existing folder.");
+ } catch (e) {
+ // NS_MSG_FOLDER_EXISTS
+ Assert.equal(e.result, 2153054227);
+ }
+}
diff --git a/comm/mailnews/local/test/unit/test_folderLoaded.js b/comm/mailnews/local/test/unit/test_folderLoaded.js
new file mode 100644
index 0000000000..398aa6bff4
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_folderLoaded.js
@@ -0,0 +1,89 @@
+/* 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/. */
+
+/**
+ * The intent of this file is to show a folder loaded event after a load
+ * with a null database.
+ */
+
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var testSubjects = [
+ "[Bug 397009] A filter will let me tag, but not untag",
+ "Hello, did you receive my bugmail?",
+];
+var gMsgFile1 = do_get_file("../../../data/bugmail1");
+var gMsgFile2 = do_get_file("../../../data/draft1");
+
+var gTargetFolder = null;
+
+add_setup(async function () {
+ if (typeof localAccountUtils.inboxFolder == "undefined") {
+ localAccountUtils.loadLocalMailAccount();
+ }
+ localAccountUtils.rootFolder.createSubfolder("target", null);
+ gTargetFolder = localAccountUtils.rootFolder.getChildNamed("target");
+
+ let copyListenerFile1 = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ gMsgFile1,
+ gTargetFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListenerFile1,
+ null
+ );
+ await copyListenerFile1.promise;
+
+ let copyListenerFile2 = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ gMsgFile2,
+ gTargetFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListenerFile2,
+ null
+ );
+ await copyListenerFile2.promise;
+});
+
+add_task(async function firstUpdate() {
+ // Get message headers for the target folder.
+ var msgCount = 0;
+ for (let hdr of gTargetFolder.msgDatabase.enumerateMessages()) {
+ msgCount++;
+ Assert.equal(hdr.subject, testSubjects[msgCount - 1]);
+ }
+ Assert.equal(msgCount, 2);
+
+ let folderAddedListener = PromiseTestUtils.promiseFolderEvent(
+ gTargetFolder,
+ "FolderLoaded"
+ );
+ gTargetFolder.updateFolder(null);
+ await folderAddedListener;
+});
+
+add_task(async function secondUpdate() {
+ // If the following executes, the test hangs in bug 787557.
+ gTargetFolder.msgDatabase = null;
+ let folderAddedListener = PromiseTestUtils.promiseFolderEvent(
+ gTargetFolder,
+ "FolderLoaded"
+ );
+ gTargetFolder.updateFolder(null);
+ await folderAddedListener;
+});
diff --git a/comm/mailnews/local/test/unit/test_localFolder.js b/comm/mailnews/local/test/unit/test_localFolder.js
new file mode 100644
index 0000000000..de7bb7619f
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_localFolder.js
@@ -0,0 +1,164 @@
+/* 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/. */
+
+/**
+ * nsIMsgFolder.subFolders tests
+ * These tests intend to test pluggableStore.discoverSubFolders
+ * and nsIMsgFolder.hasSubFolders.
+ */
+
+// Currently we have two mailbox storage formats.
+var gPluggableStores = [
+ "@mozilla.org/msgstore/berkeleystore;1",
+ "@mozilla.org/msgstore/maildirstore;1",
+];
+
+/**
+ * Check whether the expected folder structure
+ * exists in the root folder "mailFolder".
+ *
+ * @param expected array of folders and subfolders
+ * we expect
+ * @param actual actual subfolders enumerator
+ */
+function check_sub_folders(expected, actual) {
+ for (let actualFolder of actual) {
+ let index;
+ for (index = 0; index < expected.length; index++) {
+ if (expected[index].name == actualFolder.name) {
+ break;
+ }
+ }
+ // If index goes out of bounds, probably we didn't find the name.
+ Assert.ok(index < expected.length);
+
+ let pluggableStore = actualFolder.msgStore;
+ pluggableStore.discoverSubFolders(actualFolder, true);
+ Assert.equal(!!expected[index].subFolders, actualFolder.hasSubFolders);
+ if (actualFolder.hasSubFolders) {
+ Assert.equal(
+ expected[index].subFolders.length,
+ actualFolder.numSubFolders
+ );
+ check_sub_folders(expected[index].subFolders, actualFolder.subFolders);
+ }
+ }
+}
+
+/**
+ * Test default mailbox without creating any subfolders.
+ */
+function test_default_mailbox(expected, type) {
+ let mailbox = setup_mailbox(type, create_temporary_directory());
+
+ check_sub_folders(expected, mailbox.subFolders);
+}
+
+/**
+ * A helper method to add the folders in aFolderArray
+ * to the aParentFolder as subfolders.
+ *
+ * @param aFolderArray array of folders and subfolders
+ * (js objects).
+ * @param aParentFolder folder (nsIMsgFolder) to which
+ * the folders and subfolders from
+ * aFolderArray are to be added.
+ */
+function add_sub_folders(aFolderArray, aParentFolder) {
+ for (let msgFolder of aFolderArray) {
+ if (!aParentFolder.containsChildNamed(msgFolder.name)) {
+ aParentFolder.createSubfolder(msgFolder.name, null);
+ }
+ if (msgFolder.subFolders) {
+ add_sub_folders(
+ msgFolder.subFolders,
+ aParentFolder.getChildNamed(msgFolder.name)
+ );
+ }
+ }
+}
+
+/**
+ * Create a server with folders and subfolders from the
+ * "expected" structure, then create a new server with
+ * the same filePath, and test that we can discover these
+ * folders based on that filePath.
+ */
+function test_mailbox(expected, type) {
+ let mailboxRootFolder = setup_mailbox(type, create_temporary_directory());
+ add_sub_folders(expected, mailboxRootFolder);
+
+ let actualFolder = setup_mailbox(type, mailboxRootFolder.filePath);
+ check_sub_folders(expected, actualFolder.subFolders);
+}
+
+function run_all_tests() {
+ test_default_mailbox([{ name: "Trash" }, { name: "Outbox" }], "none");
+ test_default_mailbox([{ name: "Inbox" }, { name: "Trash" }], "pop3");
+
+ // Assuming that the order of the folders returned from the actual folder
+ // discovery is independent and un-important for this test.
+ test_mailbox(
+ [
+ {
+ name: "Inbox",
+ subFolders: [
+ {
+ name: "sub4",
+ },
+ ],
+ },
+ {
+ name: "Trash",
+ },
+ ],
+ "pop3"
+ );
+
+ test_mailbox(
+ [
+ {
+ name: "Inbox",
+ subFolders: [
+ {
+ name: "inbox-sub1",
+ subFolders: [
+ {
+ name: "inbox-sub-sub1",
+ },
+ {
+ name: "inbox-sub-sub2",
+ },
+ ],
+ },
+ {
+ name: "inbox-sub2",
+ },
+ ],
+ },
+ {
+ name: "Trash",
+ },
+ {
+ name: "Outbox",
+ subFolders: [
+ {
+ name: "outbox-sub1",
+ },
+ ],
+ },
+ ],
+ "pop3"
+ );
+}
+
+function run_test() {
+ for (let store in gPluggableStores) {
+ Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ gPluggableStores[store]
+ );
+ run_all_tests();
+ }
+}
diff --git a/comm/mailnews/local/test/unit/test_mailboxContentLength.js b/comm/mailnews/local/test/unit/test_mailboxContentLength.js
new file mode 100644
index 0000000000..f97a787946
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_mailboxContentLength.js
@@ -0,0 +1,62 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test content length for the mailbox protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+// Take a multipart message as we're testing attachment URLs as well
+var gFile = do_get_file("../../../data/multipart-complex2");
+
+function run_test() {
+ do_test_pending();
+ copyFileMessageInLocalFolder(gFile, 0, "", null, verifyContentLength);
+}
+
+function verifyContentLength(aMessageHeaderKeys, aStatus) {
+ Assert.notEqual(aMessageHeaderKeys, null);
+ // First get the message URI
+ let msgHdr = localAccountUtils.inboxFolder.GetMessageHeader(
+ aMessageHeaderKeys[0]
+ );
+ let messageUri = localAccountUtils.inboxFolder.getUriForMsg(msgHdr);
+ // Convert this to a URI that necko can run
+ let messageService = MailServices.messageServiceFromURI(messageUri);
+ let neckoURL = messageService.getUrlForUri(messageUri);
+ // Don't use the necko URL directly. Instead, get the spec and create a new
+ // URL using the IO service
+ let urlToRun = Services.io.newURI(neckoURL.spec);
+
+ // Get a channel from this URI, and check its content length
+ let channel = Services.io.newChannelFromURI(
+ urlToRun,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ Assert.equal(channel.contentLength, gFile.fileSize);
+
+ // Now try an attachment. &part=1.2
+ let attachmentURL = Services.io.newURI(neckoURL.spec + "&part=1.2");
+ Services.io.newChannelFromURI(
+ attachmentURL,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ // Currently attachments have their content length set to the length of the
+ // entire message
+ Assert.equal(channel.contentLength, gFile.fileSize);
+
+ do_test_finished();
+}
diff --git a/comm/mailnews/local/test/unit/test_mailboxProtocol.js b/comm/mailnews/local/test/unit/test_mailboxProtocol.js
new file mode 100644
index 0000000000..f7662e8cfb
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_mailboxProtocol.js
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for getting mailbox urls via the protocol handler.
+ */
+
+var defaultProtocolFlags =
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+ Ci.nsIProtocolHandler.URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ Ci.nsIProtocolHandler.URI_FORBIDS_COOKIE_ACCESS |
+ Ci.nsIProtocolHandler.ORIGIN_IS_FULL_SPEC;
+
+var protocols = [
+ {
+ protocol: "mailbox",
+ urlSpec: "mailbox://user@localhost/",
+ // mailbox protocol doesn't use a port
+ defaultPort: -1,
+ },
+];
+
+function run_test() {
+ for (var part = 0; part < protocols.length; ++part) {
+ print("protocol: " + protocols[part].protocol);
+
+ var pH = Cc[
+ "@mozilla.org/network/protocol;1?name=" + protocols[part].protocol
+ ].createInstance(Ci.nsIProtocolHandler);
+
+ Assert.equal(pH.scheme, protocols[part].protocol);
+ Assert.equal(
+ Services.io.getDefaultPort(pH.scheme),
+ protocols[part].defaultPort
+ );
+ Assert.equal(Services.io.getProtocolFlags(pH.scheme), defaultProtocolFlags);
+
+ // Whip through some of the ports to check we get the right results.
+ for (let i = 0; i < 1024; ++i) {
+ Assert.equal(pH.allowPort(i, ""), false);
+ }
+
+ // Check we get a URI when we ask for one
+ var uri = Services.io.newURI(protocols[part].urlSpec);
+
+ uri.QueryInterface(Ci.nsIMailboxUrl);
+
+ Assert.equal(uri.spec, protocols[part].urlSpec);
+
+ // XXX This fails on Windows
+ // do_check_neq(pH.newChannel(uri), null);
+ }
+}
diff --git a/comm/mailnews/local/test/unit/test_mailboxURL.js b/comm/mailnews/local/test/unit/test_mailboxURL.js
new file mode 100644
index 0000000000..f2330cf8ca
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_mailboxURL.js
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Tests for mailbox: URLs.
+ */
+
+var mailboxFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+mailboxFile.append("mailFolder");
+mailboxFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+var mailboxFileName = Services.io.newFileURI(mailboxFile).pathQueryRef;
+
+var mailboxURLs = [
+ {
+ url: "mailbox://user@domain@example.com/folder?number=1",
+ spec: "mailbox://user%40domain@example.com/folder?number=1",
+ host: "example.com",
+ port: -1,
+ scheme: "mailbox",
+ pathQueryRef: "/folder?number=1",
+ prePath: "mailbox://user%40domain@example.com",
+ },
+ {
+ url: "mailbox://nobody@Local%20Folders/folder?number=2",
+ spec: "mailbox://nobody@local%20folders/folder?number=2",
+ host: "local%20folders",
+ port: -1,
+ scheme: "mailbox",
+ pathQueryRef: "/folder?number=2",
+ prePath: "mailbox://nobody@local%20folders",
+ },
+ {
+ url: "mailbox://" + mailboxFileName + "?number=3",
+ spec: "mailbox://" + mailboxFileName + "?number=3",
+ host: "",
+ port: -1,
+ scheme: "mailbox",
+ pathQueryRef: mailboxFileName + "?number=3",
+ prePath: "mailbox://",
+ },
+];
+
+function run_test() {
+ registerCleanupFunction(teardown);
+ var url;
+
+ // Test - get and check urls.
+ var part = 0;
+ for (part = 0; part < mailboxURLs.length; part++) {
+ dump(`url: ${mailboxURLs[part].url}\n`);
+ url = Services.io.newURI(mailboxURLs[part].url);
+
+ Assert.equal(url.spec, mailboxURLs[part].spec);
+ Assert.equal(url.scheme, mailboxURLs[part].scheme);
+ Assert.equal(url.host, mailboxURLs[part].host);
+ Assert.equal(url.port, mailboxURLs[part].port);
+ Assert.equal(url.pathQueryRef, mailboxURLs[part].pathQueryRef);
+ Assert.equal(url.prePath, mailboxURLs[part].prePath);
+ }
+
+ // Test - Check changing values.
+ dump("Other Tests\n");
+
+ // We can set the username on the URLs with a host.
+ url = Services.io.newURI("mailbox://user@domain@example.com/folder?number=1");
+ url.mutate().setUsername("john").finalize();
+ url = Services.io.newURI("mailbox://nobody@Local%20Folders/folder?number=2");
+ url.mutate().setUsername("jane").finalize();
+
+ // It should throw on our file-style URLs.
+ url = Services.io.newURI("mailbox://" + mailboxFileName + "?number=3");
+ try {
+ url.mutate().setUsername("noway").finalize();
+ do_throw("Should not be able to set username on file-style mailbox: URL");
+ } catch (ex) {
+ Assert.equal(ex.result, Cr.NS_ERROR_UNEXPECTED);
+ }
+}
+
+function teardown() {
+ if (mailboxFile.exists()) {
+ mailboxFile.remove(false);
+ }
+}
diff --git a/comm/mailnews/local/test/unit/test_msgCopy.js b/comm/mailnews/local/test/unit/test_msgCopy.js
new file mode 100644
index 0000000000..c49bff465b
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_msgCopy.js
@@ -0,0 +1,27 @@
+/* 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/. */
+
+// Test of setting keywords with copyFileMessage
+
+var bugmail11 = do_get_file("../../../data/bugmail11");
+
+// main test
+
+// tag used with test messages
+var tag1 = "istag";
+
+function run_test() {
+ do_test_pending();
+ copyFileMessageInLocalFolder(bugmail11, 0, tag1, null, test_keywords);
+}
+
+function test_keywords(aMessageHeaderKeys, aStatus) {
+ let headerKeys = aMessageHeaderKeys;
+ Assert.notEqual(headerKeys, null);
+ let copiedMessage = localAccountUtils.inboxFolder.GetMessageHeader(
+ headerKeys[0]
+ );
+ Assert.equal(copiedMessage.getStringProperty("keywords"), tag1);
+ do_test_finished();
+}
diff --git a/comm/mailnews/local/test/unit/test_msgIDParsing.js b/comm/mailnews/local/test/unit/test_msgIDParsing.js
new file mode 100644
index 0000000000..39d435cb1f
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_msgIDParsing.js
@@ -0,0 +1,24 @@
+/*
+ * Test bug 676916 - nsParseMailbox parses multi-line message-id header incorrectly
+ */
+
+var headers =
+ "from: alice@t1.example.com\r\n" +
+ "to: bob@t2.example.net\r\n" +
+ "message-id: \r\n <abcmessageid>\r\n";
+
+function testMsgID() {
+ localAccountUtils.inboxFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ localAccountUtils.inboxFolder.addMessage(
+ "From \r\n" + headers + "\r\nhello\r\n"
+ );
+ let msgHdr = localAccountUtils.inboxFolder.firstNewMessage;
+ Assert.equal(msgHdr.messageId, "abcmessageid");
+}
+
+function run_test() {
+ for (let storeID of localAccountUtils.pluggableStores) {
+ localAccountUtils.loadLocalMailAccount(storeID);
+ testMsgID();
+ }
+}
diff --git a/comm/mailnews/local/test/unit/test_noTop.js b/comm/mailnews/local/test/unit/test_noTop.js
new file mode 100644
index 0000000000..a30a03c060
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_noTop.js
@@ -0,0 +1,64 @@
+/* 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 { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/**
+ * A handler with no TOP support.
+ */
+class NoTopHandler extends POP3_RFC1939_handler {
+ TOP() {
+ return this.onError("TOP");
+ }
+}
+
+let daemon = new Pop3Daemon();
+let server = new nsMailServer(d => {
+ let handler = new NoTopHandler(d);
+ return handler;
+}, daemon);
+server.start();
+registerCleanupFunction(() => {
+ server.stop();
+});
+
+/**
+ * Inject a message to the server and do a GetNewMail for the incomingServer.
+ *
+ * @param {nsIPop3IncomingServer} incomingServer
+ */
+async function getNewMail(incomingServer) {
+ daemon.setMessages(["message1.eml"]);
+
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ return urlListener.promise;
+}
+
+/**
+ * Test TOP is sent even if not advertised, and fallback to RETR after failed.
+ */
+add_task(async function testNoTop() {
+ let incomingServer = createPop3ServerAndLocalFolders(server.port);
+ incomingServer.headersOnly = true;
+ await getNewMail(incomingServer);
+ do_check_transaction(server.playTransaction(), [
+ "CAPA",
+ "USER fred",
+ "PASS wilma",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "TOP 1 0",
+ "RETR 1",
+ "DELE 1",
+ ]);
+});
diff --git a/comm/mailnews/local/test/unit/test_noUidl.js b/comm/mailnews/local/test/unit/test_noUidl.js
new file mode 100644
index 0000000000..a3f8de4725
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_noUidl.js
@@ -0,0 +1,89 @@
+/* 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 { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/**
+ * A handler with no UIDL support.
+ */
+class NoUidlHandler extends POP3_RFC1939_handler {
+ UIDL() {
+ return this.onError("UIDL");
+ }
+}
+
+let daemon = new Pop3Daemon();
+let server = new nsMailServer(d => {
+ let handler = new NoUidlHandler(d);
+ return handler;
+}, daemon);
+server.start();
+registerCleanupFunction(() => {
+ server.stop();
+});
+
+/**
+ * Inject a message to the server and do a GetNewMail for the incomingServer.
+ *
+ * @param {nsIPop3IncomingServer} incomingServer
+ */
+async function getNewMail(incomingServer) {
+ daemon.setMessages(["message1.eml"]);
+
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ // Now get the mail.
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ return urlListener.promise;
+}
+
+/**
+ * Test that RETR and DELE are correctly sent even if UIDL is not supported.
+ */
+add_task(async function testNoUidl() {
+ let incomingServer = createPop3ServerAndLocalFolders(server.port);
+ await getNewMail(incomingServer);
+ do_check_transaction(server.playTransaction(), [
+ "CAPA",
+ "USER fred",
+ "PASS wilma",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "RETR 1",
+ "DELE 1",
+ ]);
+});
+
+/**
+ * Test that connection is aborted if trying to use headersOnly when UIDL is
+ * unsupported.
+ */
+add_task(async function testNoUidlHeadersOnly() {
+ let incomingServer = createPop3ServerAndLocalFolders(server.port);
+ incomingServer.headersOnly = true;
+ await Assert.rejects(
+ getNewMail(incomingServer),
+ e => e == Cr.NS_ERROR_FAILURE
+ );
+});
+
+/**
+ * Test that connection is aborted if trying to use leaveMessagesOnServer when
+ * UIDL is unsupported.
+ */
+add_task(async function testNoUidlLeaveMessagesOnServer() {
+ let incomingServer = createPop3ServerAndLocalFolders(server.port);
+ incomingServer.leaveMessagesOnServer = true;
+ await Assert.rejects(
+ getNewMail(incomingServer),
+ e => e == Cr.NS_ERROR_FAILURE
+ );
+});
diff --git a/comm/mailnews/local/test/unit/test_nsIMsgLocalMailFolder.js b/comm/mailnews/local/test/unit/test_nsIMsgLocalMailFolder.js
new file mode 100644
index 0000000000..b0b14ecf31
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_nsIMsgLocalMailFolder.js
@@ -0,0 +1,321 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for local folder functions.
+ */
+
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/MessageGenerator.jsm");
+
+/**
+ * Bug 66763
+ * Test deletion of a folder with a name already existing in Trash.
+ */
+function subtest_folder_deletion(root) {
+ // Now we only have <root> and some default subfolders, like Trash.
+ let trash = root.getChildNamed("Trash");
+ Assert.ok(!trash.hasSubFolders);
+
+ // Create new "folder" in root.
+ let folder = root.createLocalSubfolder("folder");
+ let path = folder.filePath;
+ Assert.ok(path.exists());
+
+ // Delete "folder" into Trash.
+ folder.deleteSelf(null);
+ Assert.ok(!path.exists());
+ Assert.equal(trash.numSubFolders, 1);
+ trash.getChildNamed("folder");
+
+ // Create another "folder" in root.
+ folder = root.createLocalSubfolder("folder");
+ // Delete "folder" into Trash again.
+ folder.deleteSelf(null);
+ Assert.equal(trash.numSubFolders, 2);
+ // The folder should be automatically renamed as the same name already is in Trash.
+ trash.getChildNamed("folder(2)");
+
+ // Create yet another "folder" in root.
+ folder = root.createLocalSubfolder("folder");
+
+ // But now with another subfolder
+ folder
+ .QueryInterface(Ci.nsIMsgLocalMailFolder)
+ .createLocalSubfolder("subfolder");
+
+ // Delete folder into Trash again
+ folder.deleteSelf(null);
+ Assert.equal(trash.numSubFolders, 3);
+ // The folder should be automatically renamed as the same name already is in Trash
+ // but the subfolder should be untouched.
+ let folderDeleted3 = trash.getChildNamed("folder(3)");
+ Assert.notEqual(folderDeleted3, null);
+ Assert.ok(folderDeleted3.containsChildNamed("subfolder"));
+ // Now we have <root>
+ // +--Trash
+ // +--folder
+ // +--folder(2)
+ // +--folder(3)
+ // +--subfolder
+
+ // Create another "folder(3)" in root.
+ Assert.ok(!root.containsChildNamed("folder(3)"));
+ folder = root.createLocalSubfolder("folder(3)");
+ Assert.ok(root.containsChildNamed("folder(3)"));
+ // Now try to move "folder(3)" from Trash back to root.
+ // That should fail, because the user gets a prompt about it and that does
+ // not work in xpcshell.
+ try {
+ root.copyFolderLocal(folderDeleted3, true, null, null);
+ do_throw("copyFolderLocal() should have failed here due to user prompt!");
+ } catch (e) {
+ // Catch only the expected error NS_MSG_ERROR_COPY_FOLDER_ABORTED,
+ // otherwise fail the test.
+ if (e.result != 0x8055001a) {
+ throw e;
+ }
+ }
+}
+
+/**
+ * Test proper creation/rename/removal of folders under a Local mail account
+ */
+function subtest_folder_operations(root) {
+ // Test - num/hasSubFolders
+
+ // Get the current number of folders
+ var numSubFolders = root.numSubFolders;
+
+ var folder = root
+ .createLocalSubfolder("folder1")
+ .QueryInterface(Ci.nsIMsgLocalMailFolder);
+
+ Assert.ok(root.hasSubFolders);
+ Assert.equal(root.numSubFolders, numSubFolders + 1);
+
+ Assert.ok(!folder.hasSubFolders);
+ Assert.equal(folder.numSubFolders, 0);
+
+ var folder2 = folder.createLocalSubfolder("folder2");
+
+ Assert.ok(folder.hasSubFolders);
+ Assert.equal(folder.numSubFolders, 1);
+
+ // Now we have <root>
+ // +--folder1
+ // +--folder2
+
+ // Test - getChildNamed
+
+ Assert.equal(root.getChildNamed("folder1"), folder);
+
+ // Check for non match, this should throw
+ var thrown = false;
+ try {
+ root.getChildNamed("folder2");
+ } catch (e) {
+ thrown = true;
+ }
+
+ Assert.ok(thrown);
+
+ // folder2 is a child of folder however.
+ folder2 = folder.getChildNamed("folder2");
+
+ // Test - isAncestorOf
+
+ Assert.ok(folder.isAncestorOf(folder2));
+ Assert.ok(root.isAncestorOf(folder2));
+ Assert.ok(!folder.isAncestorOf(root));
+
+ // Test - FoldersWithFlag
+
+ folder.setFlag(Ci.nsMsgFolderFlags.CheckNew);
+ Assert.ok(folder.getFlag(Ci.nsMsgFolderFlags.CheckNew));
+ Assert.ok(!folder.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ folder.setFlag(Ci.nsMsgFolderFlags.Offline);
+ Assert.ok(folder.getFlag(Ci.nsMsgFolderFlags.CheckNew));
+ Assert.ok(folder.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ folder.toggleFlag(Ci.nsMsgFolderFlags.CheckNew);
+ Assert.ok(!folder.getFlag(Ci.nsMsgFolderFlags.CheckNew));
+ Assert.ok(folder.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ folder.clearFlag(Ci.nsMsgFolderFlags.Offline);
+ Assert.ok(!folder.getFlag(Ci.nsMsgFolderFlags.CheckNew));
+ Assert.ok(!folder.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ folder.setFlag(Ci.nsMsgFolderFlags.Favorite);
+ folder2.setFlag(Ci.nsMsgFolderFlags.Favorite);
+ folder.setFlag(Ci.nsMsgFolderFlags.CheckNew);
+ folder2.setFlag(Ci.nsMsgFolderFlags.Offline);
+
+ Assert.equal(root.getFolderWithFlags(Ci.nsMsgFolderFlags.CheckNew), folder);
+
+ // Test - Move folders around
+
+ var folder3 = root.createLocalSubfolder("folder3");
+ var folder3Local = folder3.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ var folder1Local = folder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+
+ // put a single message in folder1.
+ let messageGenerator = new MessageGenerator();
+ let message = messageGenerator.makeMessage();
+ let hdr = folder1Local.addMessage(message.toMboxString());
+ Assert.equal(message.messageId, hdr.messageId);
+
+ folder3Local.copyFolderLocal(folder, true, null, null);
+
+ // Test - Get the new folders, make sure the old ones don't exist
+
+ var folder1Moved = folder3.getChildNamed("folder1");
+ folder1Moved.getChildNamed("folder2");
+
+ thrown = false;
+ try {
+ root.getChildNamed("folder1");
+ } catch (e) {
+ thrown = true;
+ }
+
+ Assert.ok(thrown);
+
+ if (folder.filePath.exists()) {
+ dump("shouldn't exist - folder file path " + folder.URI + "\n");
+ }
+ Assert.ok(!folder.filePath.exists());
+ if (folder2.filePath.exists()) {
+ dump("shouldn't exist - folder2 file path " + folder2.URI + "\n");
+ }
+ Assert.ok(!folder2.filePath.exists());
+
+ // make sure getting the db doesn't throw an exception
+ let db = folder1Moved.msgDatabase;
+ Assert.ok(db.summaryValid);
+
+ // Move folders back, get them
+ var rootLocal = root.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ rootLocal.copyFolderLocal(folder1Moved, true, null, null);
+ folder = root.getChildNamed("folder1");
+ folder2 = folder.getChildNamed("folder2");
+
+ // Test - Rename (test that .msf file is renamed as well)
+ folder.rename("folder1-newname", null);
+ // make sure getting the db doesn't throw an exception, and is valid
+ folder = rootLocal.getChildNamed("folder1-newname");
+ db = folder.msgDatabase;
+ Assert.ok(db.summaryValid);
+
+ folder.rename("folder1", null);
+ folder = rootLocal.getChildNamed("folder1");
+
+ // Test - propagateDelete (this tests recursiveDelete as well)
+ // The folders will be removed from disk completely, not merely to Trash.
+
+ var path1 = folder.filePath;
+ var path2 = folder2.filePath;
+ var path3 = folder3.filePath;
+
+ Assert.ok(path1.exists());
+ Assert.ok(path2.exists());
+ Assert.ok(path3.exists());
+
+ // First try deleting folder3 -- folder1 and folder2 paths should still exist
+ root.propagateDelete(folder3, true);
+
+ Assert.ok(path1.exists());
+ Assert.ok(path2.exists());
+ Assert.ok(!path3.exists());
+
+ root.propagateDelete(folder, true);
+
+ Assert.ok(!path1.exists());
+ Assert.ok(!path2.exists());
+}
+
+function test_store_rename(root) {
+ let folder1 = root
+ .createLocalSubfolder("newfolder1")
+ .QueryInterface(Ci.nsIMsgLocalMailFolder);
+ Assert.ok(root.hasSubFolders);
+ Assert.ok(!folder1.hasSubFolders);
+ let folder2 = folder1.createLocalSubfolder("newfolder1-sub");
+ let folder3 = root
+ .createLocalSubfolder("newfolder3")
+ .QueryInterface(Ci.nsIMsgLocalMailFolder);
+ folder3.createLocalSubfolder("newfolder3-sub");
+
+ Assert.ok(folder1.hasSubFolders);
+ Assert.ok(!folder2.hasSubFolders);
+ Assert.ok(folder3.hasSubFolders);
+
+ folder1.rename("folder1", null);
+ Assert.ok(root.containsChildNamed("folder1"));
+ folder1 = root.getChildNamed("folder1");
+
+ folder1.rename("newfolder1", null);
+ Assert.ok(root.containsChildNamed("newfolder1"));
+ folder1 = root.getChildNamed("newfolder1");
+ folder2 = folder1.getChildNamed("newfolder1-sub");
+
+ Assert.ok(folder1.containsChildNamed(folder2.name));
+ Assert.ok(folder2.filePath.exists());
+
+ folder3 = root.getChildNamed("newfolder3");
+ root.propagateDelete(folder3, true);
+ Assert.ok(!root.containsChildNamed("newfolder3"));
+ folder3 = root
+ .createLocalSubfolder("newfolder3")
+ .QueryInterface(Ci.nsIMsgLocalMailFolder);
+ folder3.createLocalSubfolder("newfolder3-sub");
+ folder3.rename("folder3", null);
+
+ Assert.ok(root.containsChildNamed("folder3"));
+ Assert.ok(!root.containsChildNamed("newfolder3"));
+}
+
+var gPluggableStores = [
+ "@mozilla.org/msgstore/berkeleystore;1",
+ "@mozilla.org/msgstore/maildirstore;1",
+];
+
+function run_all_tests(aHostName) {
+ let server = MailServices.accounts.createIncomingServer(
+ "nobody",
+ aHostName,
+ "none"
+ );
+ let account = MailServices.accounts.createAccount();
+ account.incomingServer = server;
+
+ let root = server.rootMsgFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ subtest_folder_operations(root);
+ subtest_folder_deletion(root);
+ test_store_rename(root);
+}
+
+function run_test() {
+ let hostName = "Local Folders";
+ let index = 0;
+ while (index < gPluggableStores.length) {
+ Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ gPluggableStores[index]
+ );
+ run_all_tests(hostName);
+ hostName += "-" + ++index;
+ }
+
+ // At this point,
+ // we should have <root>
+ // +--newfolder1
+ // +--newfolder1-subfolder
+ // +--newfolder3-anotherName
+ // +--newfolder3-sub
+ // +--folder(3)
+ // +--Trash
+ // +--folder
+ // +--folder(2)
+ // +--folder(3)
+ // +--subfolder
+}
diff --git a/comm/mailnews/local/test/unit/test_nsIMsgParseMailMsgState.js b/comm/mailnews/local/test/unit/test_nsIMsgParseMailMsgState.js
new file mode 100644
index 0000000000..a64c88f8d6
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_nsIMsgParseMailMsgState.js
@@ -0,0 +1,42 @@
+/* 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 { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var MSG_LINEBREAK = "\r\n";
+
+add_task(async function run_the_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ await test_parse_headers_without_crash("./data/mailformed_recipients.eml");
+ await test_parse_headers_without_crash("./data/mailformed_subject.eml");
+ await test_parse_headers_without_crash("./data/invalid_mozilla_keys.eml");
+});
+
+async function test_parse_headers_without_crash(eml) {
+ let file = do_get_file(eml);
+
+ let parser = Cc["@mozilla.org/messenger/messagestateparser;1"].createInstance(
+ Ci.nsIMsgParseMailMsgState
+ );
+
+ parser.SetMailDB(localAccountUtils.inboxFolder.getDatabaseWOReparse());
+ parser.state = Ci.nsIMsgParseMailMsgState.ParseHeadersState;
+
+ let bytes = await IOUtils.read(file.path);
+ let mailData = new TextDecoder().decode(bytes);
+ let lines = mailData.split(MSG_LINEBREAK);
+
+ for (let line = 0; line < lines.length; line++) {
+ parser.ParseAFolderLine(
+ lines[line] + MSG_LINEBREAK,
+ lines[line].length + 2
+ );
+ }
+ // Apparently getDatabaseWOReparse doesn't like being called too often
+ // in a row.
+ await PromiseTestUtils.promiseDelay(200);
+}
diff --git a/comm/mailnews/local/test/unit/test_nsIMsgPluggableStore.js b/comm/mailnews/local/test/unit/test_nsIMsgPluggableStore.js
new file mode 100644
index 0000000000..c9e7d63864
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_nsIMsgPluggableStore.js
@@ -0,0 +1,52 @@
+/* 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/. */
+
+/**
+ * nsIMsgPluggableStore interface tests
+ */
+
+function test_discoverSubFolders() {
+ let mailbox = setup_mailbox("none", create_temporary_directory());
+ mailbox.msgStore.discoverSubFolders(mailbox, true);
+}
+
+function test_sliceStream() {
+ let mailbox = setup_mailbox("none", create_temporary_directory());
+
+ let str = "Just a test string.";
+ let strStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ strStream.setData(str, str.length);
+
+ let sliced = mailbox.msgStore.sliceStream(strStream, 7, 4);
+
+ let s = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ s.init(sliced);
+
+ let chunk = s.read(1024);
+ Assert.equal(chunk, "test", "Check we got the expected subset.");
+ Assert.equal(s.available(), 0, "Check no more bytes available.");
+ Assert.equal(s.read(1024), "", "Check read() returns EOF.");
+}
+
+// Return a wrapper which sets the store type before running fn().
+function withStore(store, fn) {
+ return () => {
+ Services.prefs.setCharPref("mail.serverDefaultStoreContractID", store);
+ fn();
+ };
+}
+
+const pluggableStores = [
+ "@mozilla.org/msgstore/berkeleystore;1",
+ "@mozilla.org/msgstore/maildirstore;1",
+];
+
+for (let store of pluggableStores) {
+ add_task(withStore(store, test_discoverSubFolders));
+ add_task(withStore(store, test_sliceStream));
+}
diff --git a/comm/mailnews/local/test/unit/test_over2GBMailboxes.js b/comm/mailnews/local/test/unit/test_over2GBMailboxes.js
new file mode 100644
index 0000000000..fe7976b0fe
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_over2GBMailboxes.js
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* Test of accessing over 2 GiB local folder. */
+
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Find hdr for message whose offset is over 2 GiB.
+function findHugeMessageHdr(folder) {
+ //getMessageHdr() {
+ for (let header of folder.msgDatabase.enumerateMessages()) {
+ if (header.messageOffset >= 0x80000000) {
+ return header;
+ }
+ }
+
+ do_throw("Over 2 GiB msgkey was not found!");
+ return null; // This won't ever happen, but we're keeping the linter happy.
+}
+
+let gInboxFile;
+let gInbox;
+let gSmallMsgFile = do_get_file("../../../data/bugmail10");
+
+add_setup(async function () {
+ // Make sure we're using mbox.
+ Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+ );
+
+ localAccountUtils.loadLocalMailAccount();
+
+ gInbox = localAccountUtils.inboxFolder;
+ gInboxFile = gInbox.filePath;
+
+ let neededFreeSpace = 0x100000000;
+ let freeDiskSpace = gInboxFile.diskSpaceAvailable;
+ info("Free disk space = " + mailTestUtils.toMiBString(freeDiskSpace));
+ if (freeDiskSpace < neededFreeSpace) {
+ throw new Error(
+ "This test needs " +
+ mailTestUtils.toMiBString(neededFreeSpace) +
+ " free space to run. Aborting."
+ );
+ }
+});
+
+// Extend mbox file to over 2 GiB.
+add_task(async function extendPast2GiB() {
+ let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream)
+ .QueryInterface(Ci.nsISeekableStream);
+ // Open in write-only mode, no truncate.
+ outputStream.init(gInboxFile, 0x02, -1, 0);
+ // seek past 2GB.
+ outputStream.seek(0, 0x80000010);
+ // Write a "space" character.
+ outputStream.write(" ", 1);
+ outputStream.close();
+});
+
+// Copy another (normal sized) message into the local folder.
+// This message should be past the 2GiB position.
+add_task(async function appendSmallMessage() {
+ // Remember initial mbox file size.
+ let initialInboxSize = gInbox.filePath.fileSize;
+ info(`Local inbox size (before copyFileMessage()) = ${initialInboxSize}`);
+
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ gSmallMsgFile,
+ gInbox,
+ null /* msgToReplace*/,
+ false /* isDraftOrTemplate */,
+ 0 /* message flags */,
+ "" /* keywords */,
+ copyListener,
+ null /* window */
+ );
+ await copyListener.promise;
+
+ // Make sure inbox file grew (i.e., we were not writing over data).
+ let localInboxSize = gInbox.filePath.fileSize;
+ info(
+ "Local inbox size (after copyFileMessageInLocalFolder()) = " +
+ localInboxSize
+ );
+ Assert.greater(localInboxSize, initialInboxSize);
+});
+
+// Copy the huge message into a subfolder.
+add_task(async function copyHugeMessage() {
+ let trash =
+ localAccountUtils.incomingServer.rootMsgFolder.getChildNamed("Trash");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ gInbox,
+ [findHugeMessageHdr(gInbox)],
+ trash /* destFolder */,
+ false,
+ copyListener,
+ null,
+ false
+ );
+ await copyListener.promise;
+});
+
+// Read out the smaller message beyond the 2 GiB offset and make sure
+// it matches what we expect.
+add_task(async function verifySmallMessage() {
+ let msghdr = findHugeMessageHdr(gInbox);
+ let msgURI = msghdr.folder.getUriForMsg(msghdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamMessage(msgURI, streamListener, null, null, false, "", true);
+ let got = await streamListener.promise;
+
+ let expected = await IOUtils.readUTF8(gSmallMsgFile.path);
+ Assert.equal(got, expected);
+});
+
+add_task(async function cleanup() {
+ // Free up disk space - if you want to look at the file after running
+ // this test, comment out this line.
+ gInbox.filePath.remove(false);
+});
diff --git a/comm/mailnews/local/test/unit/test_over4GBMailboxes.js b/comm/mailnews/local/test/unit/test_over4GBMailboxes.js
new file mode 100644
index 0000000000..befd72fca9
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_over4GBMailboxes.js
@@ -0,0 +1,640 @@
+/* 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/. */
+
+/*
+ * Test to ensure that operations around the 4GiB folder size boundary work correctly.
+ * This test only works for mbox format mail folders.
+ * Some of the tests will be removed when support for over 4GiB folders is enabled by default.
+ * The test functions are executed in this order:
+ * - run_test
+ * - ParseListener_run_test
+ * - downloadUnder4GiB
+ * - downloadOver4GiB_fail
+ * - downloadOver4GiB_success
+ * - downloadOver4GiB_success_check
+ * - copyIntoOver4GiB_fail
+ * - copyIntoOver4GiB_fail_check
+ * - copyIntoOver4GiB_success
+ * - copyIntoOver4GiB_success_check1
+ * - copyIntoOver4GiB_success_check2
+ * - compactOver4GiB
+ * - CompactListener_compactOver4GiB
+ * - compactUnder4GiB
+ * - CompactListener_compactUnder4GiB
+ */
+
+// Need to do this before loading POP3Pump.js
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/alertTestUtils.js");
+load("../../../resources/POP3pump.js");
+
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// If we're running out of memory parsing the folder, lowering the
+// block size might help, though it will slow the test down and consume
+// more disk space.
+var kSparseBlockSize = 102400000;
+var kSizeLimit = 0x100000000; // 4GiB
+var kNearLimit = kSizeLimit - 0x1000000; // -16MiB
+
+var gInboxFile = null; // The mbox file storing the Inbox folder.
+var gInboxSize = 0; // The size of the Inbox folder.
+var gInbox; // The nsIMsgFolder object of the Inbox folder in Local Folders.
+var gExpectedNewMessages = 0; // The number of messages pushed manually into the mbox file.
+
+var alertIsPending = true;
+var alertResolve;
+var alertPromise = new Promise(resolve => {
+ alertResolve = resolve;
+}).finally(() => {
+ alertIsPending = false;
+});
+function resetAlertPromise() {
+ alertIsPending = true;
+ alertPromise = new Promise(resolve => {
+ alertResolve = resolve;
+ }).finally(() => {
+ alertIsPending = false;
+ });
+}
+
+add_setup(async function () {
+ registerAlertTestUtils();
+
+ localAccountUtils.loadLocalMailAccount();
+
+ allow4GBFolders(false);
+
+ gInbox = localAccountUtils.inboxFolder;
+ gInboxFile = gInbox.filePath;
+
+ let neededFreeSpace = kSizeLimit + 0x10000000; // +256MiB
+ // On Windows, check whether the drive is NTFS. If it is, mark the file as
+ // sparse. If it isn't, then bail out now, because in all probability it is
+ // FAT32, which doesn't support file sizes greater than 4 GiB.
+ if (
+ "@mozilla.org/windows-registry-key;1" in Cc &&
+ mailTestUtils.get_file_system(gInboxFile) != "NTFS"
+ ) {
+ throw new Error("On Windows, this test only works on NTFS volumes.\n");
+ }
+
+ let freeDiskSpace = gInboxFile.diskSpaceAvailable;
+ info("Free disk space = " + mailTestUtils.toMiBString(freeDiskSpace));
+ if (freeDiskSpace < neededFreeSpace) {
+ throw new Error(
+ "This test needs " +
+ mailTestUtils.toMiBString(neededFreeSpace) +
+ " free space to run. Aborting."
+ );
+ }
+
+ MailServices.mailSession.AddFolderListener(
+ FListener,
+ Ci.nsIFolderListener.all
+ );
+
+ // Grow inbox to a size near the max limit.
+ gExpectedNewMessages = growInbox(kNearLimit);
+
+ // Force the db closed, so that getDatabaseWithReparse will notice
+ // that it's out of date.
+ gInbox.msgDatabase.forceClosed();
+ gInbox.msgDatabase = null;
+ let parseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ try {
+ gInbox.getDatabaseWithReparse(parseUrlListener, gDummyMsgWindow);
+ } catch (ex) {
+ Assert.equal(ex.result, Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+ await parseUrlListener.promise;
+ // Check: reparse successful.
+ Assert.notEqual(gInbox.msgDatabase, null);
+ Assert.ok(gInbox.msgDatabase.summaryValid);
+ // Bug 813459
+ // Check if the onFolderIntPropertyChanged folder listener hook can return
+ // values below 2^32 for properties which are not 64 bits long.
+ Assert.equal(FListener.msgsHistory(0), gExpectedNewMessages);
+ Assert.equal(FListener.msgsHistory(0), gInbox.getTotalMessages(false));
+ Assert.equal(FListener.sizeHistory(0), gInbox.sizeOnDisk);
+});
+
+/**
+ * Check we can download new mail when we are near 4GiB limit but do not cross it.
+ */
+add_task(async function downloadUnder4GiB() {
+ // Check fake POP3 server is ready.
+ Assert.notEqual(gPOP3Pump.fakeServer, null);
+
+ // Download a file that still fits into the limit.
+ let bigFile = do_get_file("../../../data/mime-torture");
+ Assert.ok(bigFile.fileSize >= 1024 * 1024);
+ Assert.ok(bigFile.fileSize <= 1024 * 1024 * 2);
+
+ gPOP3Pump.files = ["../../../data/mime-torture"];
+ let pop3Resolve;
+ let pop3OnDonePromise = new Promise(resolve => {
+ pop3Resolve = resolve;
+ });
+ gPOP3Pump.onDone = pop3Resolve;
+ // It must succeed.
+ gPOP3Pump.run(Cr.NS_OK);
+ await pop3OnDonePromise;
+});
+
+/**
+ * Bug 640371
+ * Check we will not cross the 4GiB limit when downloading new mail.
+ */
+add_task(async function downloadOver4GiB_fail() {
+ let localInboxSize = gInboxFile.clone().fileSize;
+ Assert.ok(localInboxSize >= kNearLimit);
+ Assert.ok(localInboxSize < kSizeLimit);
+ Assert.equal(gInbox.sizeOnDisk, localInboxSize);
+ Assert.ok(gInbox.msgDatabase.summaryValid);
+ // The big file is between 1 and 2 MiB. Append it 16 times to attempt to cross the 4GiB limit.
+ gPOP3Pump.files = [
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ ];
+ let pop3Resolve;
+ let pop3OnDonePromise = new Promise(resolve => {
+ pop3Resolve = resolve;
+ });
+ gPOP3Pump.onDone = pop3Resolve;
+ // The download must fail.
+ gPOP3Pump.run(Cr.NS_ERROR_FAILURE);
+ await pop3OnDonePromise;
+});
+
+/**
+ * Bug 789679
+ * Check we can cross the 4GiB limit when downloading new mail.
+ */
+add_task(async function downloadOver4GiB_success_check() {
+ allow4GBFolders(true);
+ // Grow inbox to size greater than the max limit (+16 MiB).
+ gExpectedNewMessages = 16;
+ // We are in the .onDone() callback of the previous run of gPOP3Pump
+ // so we need a new POP3Pump so that internal variables of the previous
+ // one don't get confused.
+ gPOP3Pump = new POP3Pump();
+ gPOP3Pump._incomingServer = gPOP3Pump._createPop3ServerAndLocalFolders();
+ gPOP3Pump.files = [
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ "../../../data/mime-torture",
+ ];
+ let pop3Resolve;
+ let pop3OnDonePromise = new Promise(resolve => {
+ pop3Resolve = resolve;
+ });
+ gPOP3Pump.onDone = pop3Resolve;
+ // The download must not fail.
+ gPOP3Pump.run(Cr.NS_OK);
+ await pop3OnDonePromise;
+
+ /**
+ * Bug 608449
+ * Check we can parse a folder if it is above 4GiB.
+ */
+ let localInboxSize = gInboxFile.clone().fileSize;
+ info(
+ "Local inbox size (after downloadOver4GiB_success) = " +
+ localInboxSize +
+ "\n"
+ );
+ Assert.ok(localInboxSize > kSizeLimit);
+ Assert.ok(gInbox.msgDatabase.summaryValid);
+
+ // Bug 789679
+ // Check if the public SizeOnDisk method can return sizes above 4GB.
+ Assert.equal(gInbox.sizeOnDisk, localInboxSize);
+
+ // Bug 813459
+ // Check if the onFolderIntPropertyChanged folder listener hook can return
+ // values above 2^32 for properties where it is relevant.
+ Assert.equal(FListener.sizeHistory(0), gInbox.sizeOnDisk);
+ Assert.ok(FListener.sizeHistory(1) < FListener.sizeHistory(0));
+ Assert.equal(
+ FListener.msgsHistory(0),
+ FListener.msgsHistory(16) + gExpectedNewMessages
+ );
+ Assert.equal(gInbox.expungedBytes, 0);
+
+ // Bug 1183490
+ // Check that the message keys are below 4GB (thus no offset),
+ // actually just incrementing by 1 for each message.
+ let key = 0;
+ for (let hdr of gInbox.messages) {
+ key++;
+ Assert.equal(hdr.messageKey, key);
+ }
+});
+
+/**
+ * Bug 598104
+ * Check that copy operation does not allow to grow a local folder above 4 GiB.
+ */
+add_task(async function copyIntoOver4GiB_fail_check() {
+ allow4GBFolders(false);
+ // Save initial file size.
+ let localInboxSize = gInboxFile.clone().fileSize;
+ info("Local inbox size (before copyFileMessage) = " + localInboxSize);
+
+ // Use copyFileMessage to (try to) append another message
+ // to local inbox.
+ let file = do_get_file("../../../data/mime-torture");
+
+ // Set up local folders
+ localAccountUtils.loadLocalMailAccount();
+
+ let copiedMessageHeaderKeys = []; // Accumulated MsgHdrKeys for listener.
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ copiedMessageHeaderKeys.push(aKey);
+ },
+ });
+ // Copy a message into the local folder
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ gDummyMsgWindow
+ );
+ await Assert.rejects(
+ copyListener.promise,
+ reason => {
+ return reason === Cr.NS_ERROR_FAILURE;
+ },
+ "The local folder is not above 4GiB"
+ );
+
+ Assert.equal(copiedMessageHeaderKeys.length, 0);
+ let alertText = await alertPromise;
+ Assert.ok(
+ alertText.startsWith(
+ "The folder Inbox on Local Folders is full, and can't hold any more messages."
+ )
+ );
+
+ // Make sure inbox file did not grow (i.e., no data were appended).
+ let newLocalInboxSize = gInboxFile.clone().fileSize;
+ info("Local inbox size (after copyFileMessage()) = " + newLocalInboxSize);
+});
+
+/**
+ * Bug 789679
+ * Check that copy operation does allow to grow a local folder above 4 GiB.
+ */
+add_task(async function copyIntoOver4GiB_success_check1() {
+ allow4GBFolders(true);
+ // Append 2 new 2MB messages to the folder.
+ gExpectedNewMessages = 2;
+
+ // Reset the Promise for alertTestUtils.js.
+ // This message will be preserved in CompactUnder4GB.
+ resetAlertPromise();
+ let file = do_get_file("../../../data/mime-torture");
+ let copiedMessageHeaderKeys = []; // Accumulated MsgHdrKeys for listener.
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ copiedMessageHeaderKeys.push(aKey);
+ },
+ });
+ // Copy a message into the local folder
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ gDummyMsgWindow
+ );
+
+ await copyListener.promise;
+ Assert.equal(copiedMessageHeaderKeys[0], 60);
+ // An alert shouldn't be triggered after our reset.
+ Assert.ok(alertIsPending);
+});
+
+add_task(async function copyIntoOver4GiB_success_check2() {
+ // This message will be removed in compactOver4GB.
+ let file = do_get_file("../../../data/mime-torture");
+ let copiedMessageHeaderKeys = []; // Accumulated MsgHdrKeys for listener.
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ copiedMessageHeaderKeys.push(aKey);
+ },
+ });
+ // Copy a message into the local folder.
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ gDummyMsgWindow
+ );
+
+ await copyListener.promise;
+ Assert.equal(copiedMessageHeaderKeys[0], 61);
+ // An alert shouldn't be triggered so far.
+ Assert.ok(alertIsPending);
+
+ Assert.equal(
+ FListener.msgsHistory(0),
+ FListener.msgsHistory(2) + gExpectedNewMessages
+ );
+});
+
+/**
+ * Bug 794303
+ * Check we can compact a folder that stays above 4 GiB after compact.
+ */
+add_task(async function compactOver4GiB() {
+ gInboxSize = gInboxFile.clone().fileSize;
+ Assert.ok(gInboxSize > kSizeLimit);
+ Assert.equal(gInbox.expungedBytes, 0);
+ // Delete the last small message at folder end.
+ let doomed = [...gInbox.messages].slice(-1);
+ let sizeToExpunge = 0;
+ for (let header of doomed) {
+ sizeToExpunge = header.messageSize;
+ }
+ let deleteListener = new PromiseTestUtils.PromiseCopyListener();
+ gInbox.deleteMessages(doomed, null, true, false, deleteListener, false);
+ await deleteListener.promise;
+ Assert.equal(gInbox.expungedBytes, sizeToExpunge);
+
+ /* Unfortunately, the compaction now would kill the sparse markings in the file
+ * so it will really take 4GiB of space in the filesystem and may be slow. */
+ // Note: compact() will also add 'X-Mozilla-Status' and 'X-Mozilla-Status2'
+ // lines to message(s).
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ gInbox.compact(urlListener, null);
+ await urlListener.promise;
+ Assert.ok(gInbox.msgDatabase.summaryValid);
+ // Check that folder size is still above max limit ...
+ let localInboxSize = gInbox.filePath.clone().fileSize;
+ info("Local inbox size (after compact 1) = " + localInboxSize);
+ Assert.ok(localInboxSize > kSizeLimit);
+ // ... but it got smaller by removing 1 message.
+ Assert.ok(gInboxSize > localInboxSize);
+ Assert.equal(gInbox.sizeOnDisk, localInboxSize);
+});
+
+/**
+ * Bug 608449
+ * Check we can compact a folder to get it under 4 GiB.
+ */
+add_task(async function compactUnder4GiB() {
+ // The folder is still above 4GB.
+ Assert.ok(gInboxFile.clone().fileSize > kSizeLimit);
+ let folderSize = gInbox.sizeOnDisk;
+ let totalMsgs = gInbox.getTotalMessages(false);
+ // Let's close the database and re-open the folder (hopefully dumping memory caches)
+ // and re-reading the values from disk (msg database). That is to test if
+ // the values were properly serialized to the database.
+ gInbox.ForceDBClosed();
+ gInbox.msgDatabase = null;
+ gInbox.getDatabaseWOReparse();
+
+ Assert.equal(gInbox.sizeOnDisk, folderSize);
+ Assert.equal(gInbox.getTotalMessages(false), totalMsgs);
+
+ // Very last header in folder is retained,
+ // but all other preceding headers are marked as deleted.
+ let doomed = [...gInbox.messages].slice(0, -1);
+ let sizeToExpunge = gInbox.expungedBytes; // If compact in compactOver4GB was skipped, this is not 0.
+ for (let header of doomed) {
+ sizeToExpunge += header.messageSize;
+ }
+ let deleteListener = new PromiseTestUtils.PromiseCopyListener();
+ gInbox.deleteMessages(doomed, null, true, false, deleteListener, false);
+ await deleteListener.promise;
+
+ // Bug 894012: size of messages to expunge is now higher than 4GB.
+ // Only the small 1MiB message remains.
+ Assert.equal(gInbox.expungedBytes, sizeToExpunge);
+ Assert.ok(sizeToExpunge > kSizeLimit);
+
+ // Note: compact() will also add 'X-Mozilla-Status' and 'X-Mozilla-Status2'
+ // lines to message(s).
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ gInbox.compact(urlListener, null);
+ await urlListener.promise;
+ // Check: message successfully copied.
+ Assert.ok(gInbox.msgDatabase.summaryValid);
+
+ // Check that folder size isn't much bigger than our sparse block size, ...
+ let localInboxSize = gInbox.filePath.clone().fileSize;
+ info("Local inbox size (after compact 2) = " + localInboxSize);
+ Assert.equal(gInbox.sizeOnDisk, localInboxSize);
+ Assert.ok(localInboxSize < kSparseBlockSize + 1000);
+ // ... i.e., that we just have one message.
+ Assert.equal(gInbox.getTotalMessages(false), 1);
+ Assert.equal(FListener.sizeHistory(0), gInbox.sizeOnDisk);
+ Assert.equal(FListener.msgsHistory(0), 1);
+
+ // The message has its key preserved in compact.
+ Assert.equal([...gInbox.messages][0].messageKey, 60);
+});
+
+add_task(function endTest() {
+ MailServices.mailSession.RemoveFolderListener(FListener);
+ // Free up disk space - if you want to look at the file after running
+ // this test, comment out this line.
+ gInbox.filePath.remove(false);
+ Services.prefs.clearUserPref("mailnews.allowMboxOver4GB");
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
+
+// This alert() is triggered when file size becomes close (enough) to or
+// exceeds 4 GiB.
+// See hardcoded value in nsMsgBrkMBoxStore::HasSpaceAvailable().
+function alertPS(parent, aDialogTitle, aText) {
+ // See "/*/locales/en-US/chrome/*/messenger.properties > mailboxTooLarge".
+ alertResolve(aText);
+}
+
+// A stub nsIMsgFolderListener that only listens to changes on Inbox and stores
+// the seen values for interesting folder properties so we can later test them.
+var FListener = {
+ folderSize: [-1], // an array of seen values of "FolderSize"
+ totalMsgs: [-1], // an array of seen values of "TotalMessages"
+
+ // Returns the value that is stored 'aBack' entries from the last one in the history.
+ sizeHistory(aBack) {
+ return this.folderSize[this.folderSize.length - 1 - aBack];
+ },
+ msgsHistory(aBack) {
+ return this.totalMsgs[this.totalMsgs.length - 1 - aBack];
+ },
+
+ onFolderAdded: function act_add(parentFolder, child) {},
+ onMessageAdded: function act_add(parentFolder, msg) {},
+ onFolderRemoved: function act_remove(parentFolder, child) {},
+ onMessageRemoved: function act_remove(parentFolder, msg) {},
+
+ onFolderPropertyChanged(aItem, aProperty, aOld, aNew) {},
+ onFolderIntPropertyChanged(aItem, aProperty, aOld, aNew) {
+ if (aItem === gInbox) {
+ info(
+ "Property change on folder Inbox:" +
+ aProperty +
+ "=" +
+ aOld +
+ "->" +
+ aNew +
+ "\n"
+ );
+ if (aProperty == "FolderSize") {
+ this.folderSize.push(aNew);
+ } else if (aProperty == "TotalMessages") {
+ this.totalMsgs.push(aNew);
+ }
+ }
+ },
+ onFolderBoolPropertyChanged(aItem, aProperty, aOld, aNew) {},
+ onFolderUnicharPropertyChanged(aItem, aProperty, aOld, aNew) {},
+ onFolderPropertyFlagChanged(aItem, aProperty, aOld, aNew) {},
+ onFolderEvent(aFolder, aEvent) {},
+};
+
+/**
+ * Allow folders to grow over 4GB.
+ */
+function allow4GBFolders(aOn) {
+ Services.prefs.setBoolPref("mailnews.allowMboxOver4GB", aOn);
+}
+
+/**
+ * Grow local inbox folder to the wanted size using direct appending
+ * to the underlying file. The folder is filled with copies of a dummy
+ * message with kSparseBlockSize bytes in size.
+ * The file must be reparsed (getDatabaseWithReparse) after it is artificially
+ * enlarged here.
+ * The file is marked as sparse in the filesystem so that it does not
+ * really take 4GiB and working with it is faster.
+ *
+ * @returns The number of messages created in the folder file.
+ */
+function growInbox(aWantedSize) {
+ let msgsAdded = 0;
+ // Put a single message in the Inbox.
+ let messageGenerator = new MessageGenerator();
+ let message = messageGenerator.makeMessage();
+
+ // Refresh 'gInboxFile'.
+ gInboxFile = gInbox.filePath;
+ let localSize = 0;
+
+ let mboxString = message.toMboxString();
+ let plugStore = gInbox.msgStore;
+ // Grow local inbox to our wished size that is below the max limit.
+ do {
+ let sparseStart = gInboxFile.clone().fileSize + mboxString.length;
+ let nextOffset = Math.min(sparseStart + kSparseBlockSize, aWantedSize - 2);
+ if (aWantedSize - (nextOffset + 2) < mboxString.length + 2) {
+ nextOffset = aWantedSize - 2;
+ }
+
+ // Get stream to write a new message.
+ let reusable = {};
+ let newMsgHdr = {};
+ let outputStream = plugStore
+ .getNewMsgOutputStream(gInbox, newMsgHdr, reusable)
+ .QueryInterface(Ci.nsISeekableStream);
+ // Write message header.
+ outputStream.write(mboxString, mboxString.length);
+ outputStream.close();
+
+ // "Add" a new (empty) sparse block at the end of the file.
+ if (nextOffset - sparseStart == kSparseBlockSize) {
+ mailTestUtils.mark_file_region_sparse(
+ gInboxFile,
+ sparseStart,
+ kSparseBlockSize
+ );
+ }
+
+ // Append message terminator.
+ outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream)
+ .QueryInterface(Ci.nsISeekableStream);
+ // Open in write-only mode, no truncate.
+ outputStream.init(gInboxFile, 0x02, 0o600, 0);
+
+ // Skip to the wished end of the message.
+ outputStream.seek(0, nextOffset);
+ // Add a CR+LF to terminate the message.
+ outputStream.write("\r\n", 2);
+ outputStream.close();
+ msgsAdded++;
+
+ // Refresh 'gInboxFile'.
+ gInboxFile = gInbox.filePath;
+ localSize = gInboxFile.clone().fileSize;
+ } while (localSize < aWantedSize);
+
+ Assert.equal(gInboxFile.clone().fileSize, aWantedSize);
+ info(
+ "Local inbox size = " +
+ localSize +
+ "bytes = " +
+ mailTestUtils.toMiBString(localSize)
+ );
+ Assert.equal(localSize, aWantedSize);
+ return msgsAdded;
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3AuthMethods.js b/comm/mailnews/local/test/unit/test_pop3AuthMethods.js
new file mode 100644
index 0000000000..3b45287f23
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3AuthMethods.js
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Login tests for POP3
+ *
+ * Test code <copied from="test_pop3GetNewMail.js">
+ */
+
+var server;
+var handler;
+var incomingServer;
+var thisTest;
+
+var tests = [
+ {
+ title: "Cleartext password, with server only supporting USER/PASS",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: [],
+ expectSuccess: true,
+ transaction: ["AUTH", "CAPA", "USER fred", "PASS wilma", "STAT"],
+ },
+ {
+ // Just to make sure we clear the auth flags and re-issue "AUTH"
+ title:
+ "Second time Cleartext password, with server only supporting USER/PASS",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: [],
+ expectSuccess: true,
+ transaction: ["AUTH", "CAPA", "USER fred", "PASS wilma", "STAT"],
+ },
+ {
+ title:
+ "Cleartext password, with server supporting AUTH PLAIN, LOGIN and CRAM",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: ["AUTH", "CAPA", "AUTH PLAIN", "STAT"],
+ },
+ {
+ title: "Cleartext password, with server supporting only AUTH LOGIN",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: ["LOGIN"],
+ expectSuccess: true,
+ transaction: ["AUTH", "CAPA", "AUTH LOGIN", "STAT"],
+ },
+ {
+ title: "Encrypted password, with server supporting PLAIN and CRAM",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted,
+ serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: ["AUTH", "CAPA", "AUTH CRAM-MD5", "STAT"],
+ },
+ {
+ title: "Encrypted password, try CRAM even if if not advertised",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted,
+ serverAuthMethods: ["PLAIN", "LOGIN"],
+ expectSuccess: false,
+ transaction: ["AUTH", "CAPA", "AUTH CRAM-MD5"],
+ },
+ {
+ title: "Any secure method, with server supporting AUTH PLAIN and CRAM",
+ clientAuthMethod: Ci.nsMsgAuthMethod.secure,
+ serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: ["AUTH", "CAPA", "AUTH CRAM-MD5", "STAT"],
+ },
+ {
+ title:
+ "Any secure method, with server only supporting AUTH PLAIN and LOGIN (must fail)",
+ clientAuthMethod: Ci.nsMsgAuthMethod.secure,
+ serverAuthMethods: ["PLAIN"],
+ expectSuccess: false,
+ transaction: ["AUTH", "CAPA"],
+ },
+];
+
+var urlListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, result) {
+ try {
+ if (thisTest.expectSuccess) {
+ Assert.equal(result, 0);
+ } else {
+ Assert.notEqual(result, 0);
+ }
+
+ var transaction = server.playTransaction();
+ do_check_transaction(transaction, thisTest.transaction);
+
+ do_timeout(0, checkBusy);
+ } catch (e) {
+ server.stop();
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_throw(e);
+ }
+ },
+};
+
+function checkBusy() {
+ if (tests.length == 0) {
+ incomingServer.closeCachedConnections();
+
+ // No more tests, let everything finish
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+ return;
+ }
+
+ // If the server hasn't quite finished, just delay a little longer.
+ if (incomingServer.serverBusy) {
+ do_timeout(20, checkBusy);
+ return;
+ }
+
+ testNext();
+}
+
+function testNext() {
+ thisTest = tests.shift();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ server.resetTest();
+
+ test = thisTest.title;
+ dump("NEXT test: " + thisTest.title + "\n");
+
+ handler.kAuthSchemes = thisTest.serverAuthMethods;
+
+ // Mailnews caches server capabilities, so try to reset it
+ // (alternative would be .pop3CapabilityFlags = 0, but this is safer)
+ deletePop3Server();
+ incomingServer = createPop3Server();
+
+ let msgServer = incomingServer;
+ msgServer.QueryInterface(Ci.nsIMsgIncomingServer);
+ msgServer.authMethod = thisTest.clientAuthMethod;
+
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ server.performTest();
+ } catch (e) {
+ server.stop();
+ do_throw(e);
+ }
+}
+
+// <copied from="head_maillocal.js::createPop3ServerAndLocalFolders()">
+function createPop3Server() {
+ let incoming = MailServices.accounts.createIncomingServer(
+ "fred",
+ "localhost",
+ "pop3"
+ );
+ incoming.port = server.port;
+ incoming.password = "wilma";
+ return incoming;
+}
+// </copied>
+
+function deletePop3Server() {
+ if (!incomingServer) {
+ return;
+ }
+ MailServices.accounts.removeIncomingServer(incomingServer, true);
+ incomingServer = null;
+}
+
+function run_test() {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ let ssd = setupServerDaemon();
+ server = ssd[1];
+ handler = ssd[2];
+ server.start();
+
+ // incomingServer = createPop3ServerAndLocalFolders();
+ localAccountUtils.loadLocalMailAccount();
+
+ do_test_pending();
+
+ testNext();
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3Client.js b/comm/mailnews/local/test/unit/test_pop3Client.js
new file mode 100644
index 0000000000..19e71a3e78
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3Client.js
@@ -0,0 +1,145 @@
+/* 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/. */
+
+let [daemon, server, handler] = setupServerDaemon();
+handler.kCapabilities = ["uidl", "top"]; // CAPA response is case-insensitive.
+server.start();
+registerCleanupFunction(() => {
+ server.stop();
+});
+
+/**
+ * Test when alwaysSTARTTLS is set, but the server doesn't support STARTTLS,
+ * should abort after CAPA response.
+ */
+add_task(async function testSTARTTLS() {
+ server.resetTest();
+
+ let incomingServer = createPop3ServerAndLocalFolders(server.port);
+ // Set to always use STARTTLS.
+ incomingServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+
+ let urlListener = {
+ OnStartRunningUrl() {},
+ OnStopRunningUrl(url, result) {
+ try {
+ let transaction = server.playTransaction();
+ do_check_transaction(transaction, ["AUTH", "CAPA"]);
+ Assert.equal(result, Cr.NS_ERROR_FAILURE);
+ } catch (e) {
+ } finally {
+ MailServices.accounts.removeIncomingServer(incomingServer, false);
+ do_test_finished();
+ }
+ },
+ };
+
+ // Now get the mail.
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+
+ server.performTest();
+
+ do_test_pending();
+});
+
+/**
+ * Test that depending on user prefs and message size, TOP or RETR should be used.
+ *
+ * @param {nsIMsgIncomingServer} incomingServer - A server instance.
+ * @param {string[]} transaction - The commands sent to the server.
+ */
+async function testTopOrRetr(incomingServer, transaction) {
+ server.resetTest();
+ // Any message file larger than 50KB is good for this test.
+ daemon.setMessages(["mailformed_subject.eml"]);
+
+ let urlListener = {
+ OnStartRunningUrl() {},
+ OnStopRunningUrl(url, result) {
+ try {
+ do_check_transaction(server.playTransaction(), transaction);
+ Assert.equal(result, 0);
+ } catch (e) {
+ } finally {
+ MailServices.accounts.removeIncomingServer(incomingServer, false);
+ do_test_finished();
+ }
+ },
+ };
+
+ // Now get the mail.
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+
+ server.performTest();
+
+ do_test_pending();
+}
+
+/**
+ * Turn off server.limitOfflineMessageSize, test RETR is used.
+ */
+add_task(async function testNoOfflineMessageSizeLimit() {
+ let incomingServer = createPop3ServerAndLocalFolders(server.port);
+ incomingServer.limitOfflineMessageSize = false;
+ incomingServer.maxMessageSize = 1;
+
+ testTopOrRetr(incomingServer, [
+ "AUTH",
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "RETR 1",
+ "DELE 1",
+ ]);
+});
+
+/**
+ * Turn on server.limitOfflineMessageSize and set maxMessageSize to 1KB, test
+ * TOP is used.
+ */
+add_task(async function testMaxMessageSize() {
+ let incomingServer = createPop3ServerAndLocalFolders(server.port);
+ incomingServer.limitOfflineMessageSize = true;
+ incomingServer.maxMessageSize = 1;
+
+ testTopOrRetr(incomingServer, [
+ "AUTH",
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "TOP 1 20",
+ ]);
+});
+
+/**
+ * Turn on server.headersOnly, test TOP is used.
+ */
+add_task(async function testHeadersOnly() {
+ let incomingServer = createPop3ServerAndLocalFolders(server.port);
+ incomingServer.headersOnly = true;
+
+ testTopOrRetr(incomingServer, [
+ "AUTH",
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "TOP 1 0",
+ ]);
+});
diff --git a/comm/mailnews/local/test/unit/test_pop3Download.js b/comm/mailnews/local/test/unit/test_pop3Download.js
new file mode 100644
index 0000000000..0268170f30
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3Download.js
@@ -0,0 +1,81 @@
+/**
+ * The intent of this file is to test that pop3 download code message storage
+ * works correctly.
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var testSubjects = [
+ "[Bug 397009] A filter will let me tag, but not untag",
+ "Hello, did you receive my bugmail?",
+ "[Bug 655578] list-id filter broken",
+];
+
+var gMsgHdrs = [];
+var gHdrIndex = 0;
+var gFiles = [
+ "../../../data/bugmail1",
+ "../../../data/draft1",
+ "../../../data/bugmail19",
+];
+
+// This combination of prefs is required to reproduce bug 713611, which
+// is what this test is about.
+Services.prefs.setBoolPref("mailnews.downloadToTempFile", false);
+Services.prefs.setBoolPref("mail.server.default.leave_on_server", true);
+
+function run_test() {
+ // add 3 messages
+ gPOP3Pump.files = gFiles;
+ gPOP3Pump.onDone = continueTest;
+ do_test_pending();
+ gPOP3Pump.run();
+}
+
+function continueTest() {
+ // get message headers for the inbox folder
+ var msgCount = 0;
+ for (let hdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) {
+ gMsgHdrs.push(hdr);
+ Assert.equal(hdr.subject, testSubjects[msgCount++]);
+ }
+ Assert.equal(msgCount, 3);
+ gPOP3Pump = null;
+ streamNextMessage();
+}
+
+function streamNextMessage() {
+ let msghdr = gMsgHdrs[gHdrIndex];
+ let msgURI = msghdr.folder.getUriForMsg(msghdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ msgServ.streamMessage(msgURI, gStreamListener, null, null, false, "", true);
+}
+
+var gStreamListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
+ _stream: null,
+ _data: null,
+ onStartRequest(aRequest) {
+ this._stream = null;
+ this._data = "";
+ },
+ onStopRequest(aRequest, aStatusCode) {
+ // check that the streamed message starts with "From "
+ Assert.ok(this._data.startsWith("From "));
+ if (++gHdrIndex == gFiles.length) {
+ do_test_finished();
+ } else {
+ streamNextMessage();
+ }
+ },
+ onDataAvailable(aRequest, aInputStream, aOff, aCount) {
+ if (this._stream == null) {
+ this._stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ this._stream.init(aInputStream);
+ }
+ this._data += this._stream.read(aCount);
+ },
+};
diff --git a/comm/mailnews/local/test/unit/test_pop3DownloadTempFileHandling.js b/comm/mailnews/local/test/unit/test_pop3DownloadTempFileHandling.js
new file mode 100644
index 0000000000..2524c489df
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3DownloadTempFileHandling.js
@@ -0,0 +1,62 @@
+/**
+ * The intent of this file is to test temp file handling when
+ * downloading multiple pop3 messages with quarantining turned on.
+ *
+ * Original author: David Bienvenu <dbienvenu@mozilla.com>
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var testSubjects = [
+ "[Bug 397009] A filter will let me tag, but not untag",
+ "Hello, did you receive my bugmail?",
+];
+var gExpectedFiles;
+
+function run_test() {
+ Services.prefs.setBoolPref("mailnews.downloadToTempFile", true);
+ gExpectedFiles = createExpectedTemporaryFiles(2);
+ // add 2 messages
+ gPOP3Pump.files = ["../../../data/bugmail1", "../../../data/draft1"];
+ gPOP3Pump.onDone = continueTest;
+ do_test_pending();
+ gPOP3Pump.run();
+}
+
+function continueTest() {
+ dump("temp file path = " + gExpectedFiles[0].path + "\n");
+ dump("temp file path = " + gExpectedFiles[1].path + "\n");
+ for (let expectedFile of gExpectedFiles) {
+ Assert.ok(!expectedFile.exists());
+ }
+
+ // get message headers for the inbox folder
+ var msgCount = 0;
+ for (let hdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) {
+ Assert.equal(hdr.subject, testSubjects[msgCount++]);
+ }
+ Assert.equal(msgCount, 2);
+ gPOP3Pump = null;
+ do_test_finished();
+}
+
+function createExpectedTemporaryFiles(numFiles) {
+ function createTemporaryFile() {
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append("newmsg");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ return file;
+ }
+
+ let expectedFiles = [];
+ for (let i = 0; i < numFiles; i++) {
+ expectedFiles.push(createTemporaryFile());
+ }
+
+ for (let expectedFile of expectedFiles) {
+ expectedFile.remove(false);
+ }
+
+ return expectedFiles;
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3Duplicates.js b/comm/mailnews/local/test/unit/test_pop3Duplicates.js
new file mode 100644
index 0000000000..a863cbb1ed
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3Duplicates.js
@@ -0,0 +1,40 @@
+/**
+ * The intent of this file is to test duplicate handling options
+ * in the pop3 download code.
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var testSubjects = [
+ "[Bug 397009] A filter will let me tag, but not untag",
+ "Hello, did you receive my bugmail?",
+];
+
+function run_test() {
+ // Set duplicate action to be delete duplicates.
+ Services.prefs.setIntPref(
+ "mail.server.default.dup_action",
+ Ci.nsIMsgIncomingServer.deleteDups
+ );
+ // add 3 messages, 2 of which are duplicates.
+ gPOP3Pump.files = [
+ "../../../data/bugmail1",
+ "../../../data/draft1",
+ "../../../data/bugmail1",
+ ];
+ gPOP3Pump.onDone = continueTest;
+ do_test_pending();
+ gPOP3Pump.run();
+}
+
+function continueTest() {
+ // get message headers for the inbox folder
+ var msgCount = 0;
+ for (let hdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) {
+ Assert.equal(hdr.subject, testSubjects[msgCount++]);
+ }
+ Assert.equal(msgCount, 2);
+ gPOP3Pump = null;
+ do_test_finished();
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3FilterActions.js b/comm/mailnews/local/test/unit/test_pop3FilterActions.js
new file mode 100644
index 0000000000..4c35e46735
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3FilterActions.js
@@ -0,0 +1,143 @@
+/*
+ * This file tests that a pop3 add tag filter writes the new tag
+ * into the message keywords header. It also tests marking read,
+ * and flagging messages.
+ *
+ * Original author: David Bienvenu <dbienvenu@mozilla.com>
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gFiles = ["../../../data/bugmail10", "../../../data/bugmail11"];
+
+Services.prefs.setBoolPref("mail.server.default.leave_on_server", true);
+
+// Currently we have two mailbox storage formats.
+var gPluggableStores = [
+ "@mozilla.org/msgstore/berkeleystore;1",
+ "@mozilla.org/msgstore/maildirstore;1",
+];
+
+// Map subject to previews using subject as the key.
+var previews = {
+ "[Bug 436880] IMAP itemDeleted and itemMoveCopyCompleted notifications quite broken":
+ "Do not reply to this email. You can add comments to this bug at https://bugzilla.mozilla.org/show_bug.cgi?id=436880 -- Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email ------- You are receiving this mail because: -----",
+ "Bugzilla: confirm account creation":
+ "Bugzilla has received a request to create a user account using your email address (example@example.org). To confirm that you want to create an account using that email address, visit the following link: https://bugzilla.mozilla.org/token.cgi?t=xxx",
+};
+
+var gFilter; // the test filter
+var gFilterList;
+var gTestArray = [
+ function createFilters() {
+ gFilterList = gPOP3Pump.fakeServer.getFilterList(null);
+ gFilter = gFilterList.createFilter("AddKeyword");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.matchAll = true;
+ gFilter.appendTerm(searchTerm);
+ let tagAction = gFilter.createAction();
+ tagAction.type = Ci.nsMsgFilterAction.AddTag;
+ tagAction.strValue = "TheTag";
+ gFilter.appendAction(tagAction);
+ tagAction = gFilter.createAction();
+ tagAction.type = Ci.nsMsgFilterAction.MarkRead;
+ gFilter.appendAction(tagAction);
+ tagAction = gFilter.createAction();
+ tagAction.type = Ci.nsMsgFilterAction.MarkFlagged;
+ gFilter.appendAction(tagAction);
+ gFilter.enabled = true;
+ gFilter.filterType = Ci.nsMsgFilterType.InboxRule;
+ gFilterList.insertFilterAt(0, gFilter);
+ },
+ // just get a message into the local folder
+ async function getLocalMessages1() {
+ gPOP3Pump.files = gFiles;
+ await gPOP3Pump.run();
+ },
+ async function verifyFolders2() {
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 2);
+
+ // invalidate the inbox summary file, to be sure that we wrote the keywords
+ // into the mailbox.
+ localAccountUtils.inboxFolder.msgDatabase.summaryValid = false;
+ localAccountUtils.inboxFolder.msgDatabase = null;
+ localAccountUtils.inboxFolder.ForceDBClosed();
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ try {
+ localAccountUtils.inboxFolder.getDatabaseWithReparse(
+ promiseUrlListener,
+ null
+ );
+ } catch (ex) {
+ await promiseUrlListener.promise;
+ Assert.ok(ex.result == Cr.NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+
+ // This statement is never reached.
+ Assert.ok(false);
+ },
+ function verifyMessages() {
+ let hdrs = [];
+ let keys = [];
+ for (let hdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) {
+ keys.push(hdr.messageKey);
+ hdrs.push(hdr);
+ }
+ Assert.ok(!localAccountUtils.inboxFolder.fetchMsgPreviewText(keys, null));
+ let preview1 = hdrs[0].getStringProperty("preview");
+ let preview2 = hdrs[1].getStringProperty("preview");
+ Assert.equal(preview1, previews[hdrs[0].subject]);
+ Assert.equal(preview2, previews[hdrs[1].subject]);
+ Assert.equal(hdrs[0].getStringProperty("keywords"), "TheTag");
+ Assert.equal(hdrs[1].getStringProperty("keywords"), "TheTag");
+ Assert.equal(
+ hdrs[0].flags,
+ Ci.nsMsgMessageFlags.Read | Ci.nsMsgMessageFlags.Marked
+ );
+ Assert.equal(
+ hdrs[1].flags,
+ Ci.nsMsgMessageFlags.Read | Ci.nsMsgMessageFlags.Marked
+ );
+ },
+];
+
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
+
+function setup_store(storeID) {
+ return function _setup_store() {
+ // Initialize pop3Pump with correct mailbox format.
+ gPOP3Pump.resetPluggableStore(storeID);
+
+ // Set the default mailbox store.
+ Services.prefs.setCharPref("mail.serverDefaultStoreContractID", storeID);
+
+ // Make sure we're not quarantining messages
+ Services.prefs.setBoolPref("mailnews.downloadToTempFile", false);
+
+ if (!localAccountUtils.inboxFolder) {
+ localAccountUtils.loadLocalMailAccount();
+ }
+ };
+}
+
+function run_test() {
+ for (let store of gPluggableStores) {
+ add_task(setup_store(store));
+ gTestArray.forEach(x => add_task(x));
+ }
+
+ add_task(exitTest);
+ run_next_test();
+}
+
+function exitTest() {
+ info("Exiting mail tests\n");
+ gPOP3Pump = null;
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3Filters.js b/comm/mailnews/local/test/unit/test_pop3Filters.js
new file mode 100644
index 0000000000..53ef182a6d
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3Filters.js
@@ -0,0 +1,114 @@
+/* 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 { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+let [daemon, server, handler] = setupServerDaemon();
+server.start();
+registerCleanupFunction(() => {
+ server.stop();
+});
+
+let incomingServer = createPop3ServerAndLocalFolders(server.port);
+
+/**
+ * Inject a message to the server and do a GetNewMail for the incomingServer.
+ */
+async function getNewMail() {
+ daemon.setMessages(["message1.eml", "message3.eml"]);
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ return urlListener.promise;
+}
+
+/**
+ * Test DeleteFromPop3Server filter should send DELE for matched message.
+ */
+add_task(async function testDeleteFromPop3Server() {
+ // Turn on leaveMessagesOnServer, so that DELE would not be sent normally.
+ incomingServer.leaveMessagesOnServer = true;
+
+ // Create a DeleteFromPop3Server filter.
+ let filterList = incomingServer.getFilterList(null);
+ let filter = filterList.createFilter("deleteFromServer");
+
+ let searchTerm = filter.createTerm();
+ searchTerm.attrib = Ci.nsMsgSearchAttrib.Subject;
+ searchTerm.op = Ci.nsMsgSearchOp.Contains;
+ let value = searchTerm.value;
+ value.str = "mail 2";
+ searchTerm.value = value;
+ filter.appendTerm(searchTerm);
+
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.DeleteFromPop3Server;
+ filter.appendAction(action);
+
+ filter.enabled = true;
+ filterList.insertFilterAt(0, filter);
+
+ await getNewMail();
+ do_check_transaction(server.playTransaction(), [
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "RETR 1", // message1.eml doesn't match the filter, no DELE.
+ "RETR 2",
+ "DELE 2", // message3.eml matches the filter, DELE was sent.
+ ]);
+
+ // MailServices.accounts.removeIncomingServer(incomingServer, false);
+ filterList.removeFilterAt(0);
+});
+
+/**
+ * Test FetchBodyFromPop3Server filter should send RETR for matched message.
+ */
+add_task(async function testFetchBodyFromPop3Server() {
+ incomingServer.leaveMessagesOnServer = true;
+ // Turn on leaveMessagesOnServer, so that RETR would not be sent normally.
+ incomingServer.headersOnly = true;
+
+ // Create a FetchBodyFromPop3Server filter.
+ let filterList = incomingServer.getFilterList(null);
+ let filter = filterList.createFilter("fetchBodyFromServer");
+
+ let searchTerm = filter.createTerm();
+ searchTerm.attrib = Ci.nsMsgSearchAttrib.Subject;
+ searchTerm.op = Ci.nsMsgSearchOp.Contains;
+ let value = searchTerm.value;
+ value.str = "mail 2";
+ searchTerm.value = value;
+ filter.appendTerm(searchTerm);
+
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.FetchBodyFromPop3Server;
+ filter.appendAction(action);
+
+ filter.enabled = true;
+ filterList.insertFilterAt(0, filter);
+
+ await getNewMail();
+ do_check_transaction(server.playTransaction(), [
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "TOP 1 0", // message1.eml doesn't match the filter, no RETR.
+ "TOP 2 0",
+ "RETR 2", // message3.eml matches the filter, RETR was sent.
+ ]);
+
+ filterList.removeFilterAt(0);
+});
diff --git a/comm/mailnews/local/test/unit/test_pop3GSSAPIFail.js b/comm/mailnews/local/test/unit/test_pop3GSSAPIFail.js
new file mode 100644
index 0000000000..2ff133a14e
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3GSSAPIFail.js
@@ -0,0 +1,222 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * A server offers GSSAPI (Kerberos), but auth fails, due to client or server.
+ *
+ * This mainly tests whether we use the correct login mode.
+ *
+ * Whether it fails due to
+ * - client not set up
+ * - client ticket expired / not logged in
+ * - server not being set up properly
+ * makes no difference to Thunderbird, as that's all hidden in the gssapi-Library
+ * from the OS. So, the server here just returning err is a good approximation
+ * of reality of the above cases.
+ *
+ * Actually, we (more precisely the OS GSSAPI lib) fail out of band
+ * in the Kerberos protocol, before the AUTH GSSAPI command is even issued.
+ *
+ * @author Ben Bucksch
+ */
+
+var server;
+var daemon;
+var authSchemes;
+var incomingServer;
+var thisTest;
+
+var tests = [
+ {
+ title: "GSSAPI auth, server with GSSAPI only",
+ clientAuthMethod: Ci.nsMsgAuthMethod.GSSAPI,
+ serverAuthMethods: ["GSSAPI"],
+ expectSuccess: false,
+ transaction: ["AUTH", "CAPA"],
+ },
+ {
+ // First GSSAPI step happens and fails out of band, thus no "AUTH GSSAPI"
+ title: "GSSAPI auth, server with GSSAPI and CRAM-MD5",
+ clientAuthMethod: Ci.nsMsgAuthMethod.GSSAPI,
+ serverAuthMethods: ["GSSAPI", "CRAM-MD5"],
+ expectSuccess: false,
+ transaction: ["AUTH", "CAPA"],
+ },
+ {
+ title: "Any secure auth, server with GSSAPI only",
+ clientAuthMethod: Ci.nsMsgAuthMethod.secure,
+ serverAuthMethods: ["GSSAPI"],
+ expectSuccess: false,
+ transaction: ["AUTH", "CAPA"],
+ },
+ {
+ title: "Any secure auth, server with GSSAPI and CRAM-MD5",
+ clientAuthMethod: Ci.nsMsgAuthMethod.secure,
+ serverAuthMethods: ["GSSAPI", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: ["AUTH", "CAPA", "AUTH CRAM-MD5", "STAT"],
+ },
+ {
+ title: "Encrypted password, server with GSSAPI and CRAM-MD5",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted,
+ serverAuthMethods: ["GSSAPI", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: ["AUTH", "CAPA", "AUTH CRAM-MD5", "STAT"],
+ },
+];
+
+var urlListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, result) {
+ try {
+ if (thisTest.expectSuccess) {
+ Assert.equal(result, 0);
+ } else {
+ Assert.notEqual(result, 0);
+ }
+
+ var transaction = server.playTransaction();
+ do_check_transaction(transaction, thisTest.transaction);
+
+ do_timeout(0, checkBusy);
+ } catch (e) {
+ server.stop();
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_throw(e);
+ }
+ },
+};
+
+function checkBusy() {
+ if (tests.length == 0) {
+ incomingServer.closeCachedConnections();
+
+ // No more tests, let everything finish
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+ return;
+ }
+
+ // If the server hasn't quite finished, just delay a little longer.
+ if (incomingServer.serverBusy) {
+ do_timeout(20, checkBusy);
+ return;
+ }
+
+ testNext();
+}
+
+function testNext() {
+ thisTest = tests.shift();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ server.resetTest();
+
+ test = thisTest.title;
+ dump("NEXT test is: " + thisTest.title + "\n");
+
+ authSchemes = thisTest.serverAuthMethods;
+
+ // Mailnews caches server capabilities, so try to reset it
+ deletePop3Server();
+ incomingServer = createPop3Server();
+
+ let msgServer = incomingServer;
+ msgServer.QueryInterface(Ci.nsIMsgIncomingServer);
+ msgServer.authMethod = thisTest.clientAuthMethod;
+
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ server.performTest();
+ } catch (e) {
+ server.stop();
+ do_throw(e);
+ }
+}
+
+// <copied from="head_maillocal.js::createPop3ServerAndLocalFolders()">
+function createPop3Server() {
+ let incoming = MailServices.accounts.createIncomingServer(
+ "fred",
+ "localhost",
+ "pop3"
+ );
+ incoming.port = server.port;
+ incoming.password = "wilma";
+ return incoming;
+}
+// </copied>
+
+function deletePop3Server() {
+ if (!incomingServer) {
+ return;
+ }
+ MailServices.accounts.removeIncomingServer(incomingServer, true);
+ incomingServer = null;
+}
+
+class GSSAPIFail_handler extends POP3_RFC5034_handler {
+ _needGSSAPI = false;
+ // kAuthSchemes will be set by test
+
+ AUTH(restLine) {
+ var scheme = restLine.split(" ")[0];
+ if (scheme == "GSSAPI") {
+ this._multiline = true;
+ this._needGSSAPI = true;
+ return "+";
+ }
+ return super.AUTH(restLine); // call parent
+ }
+ onMultiline(line) {
+ if (this._needGSSAPI) {
+ this._multiline = false;
+ this._needGSSAPI = false;
+ return "-ERR hm.... shall I allow you? hm... NO.";
+ }
+
+ if (super.onMultiline) {
+ // Call parent.
+ return super.onMultiline(line);
+ }
+ return undefined;
+ }
+}
+
+function run_test() {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ daemon = new Pop3Daemon();
+ function createHandler(d) {
+ var handler = new GSSAPIFail_handler(d);
+ handler.kAuthSchemes = authSchemes;
+ return handler;
+ }
+ server = new nsMailServer(createHandler, daemon);
+ server.start();
+
+ // incomingServer = createPop3ServerAndLocalFolders();
+ localAccountUtils.loadLocalMailAccount();
+
+ do_test_pending();
+
+ testNext();
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3GetNewMail.js b/comm/mailnews/local/test/unit/test_pop3GetNewMail.js
new file mode 100644
index 0000000000..f0ecf9eb69
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3GetNewMail.js
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Protocol tests for POP3.
+ */
+var server;
+var daemon;
+var incomingServer;
+var thisTest;
+
+var tests = [
+ {
+ title: "Get New Mail, No Messages",
+ messages: [],
+ transaction: ["AUTH", "CAPA", "AUTH PLAIN", "STAT"],
+ },
+ {
+ title: "Get New Mail, No Messages 2",
+ messages: [],
+ transaction: ["CAPA", "AUTH PLAIN", "STAT"],
+ },
+ {
+ title: "Get New Mail, One Message",
+ messages: ["message1.eml"],
+ transaction: [
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "RETR 1",
+ "DELE 1",
+ ],
+ },
+];
+
+var urlListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, result) {
+ try {
+ var transaction = server.playTransaction();
+
+ do_check_transaction(transaction, thisTest.transaction);
+
+ Assert.equal(
+ localAccountUtils.inboxFolder.getTotalMessages(false),
+ thisTest.messages.length
+ );
+
+ Assert.equal(result, 0);
+ } catch (e) {
+ // If we have an error, clean up nicely before we throw it.
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_throw(e);
+ }
+
+ // Let OnStopRunningUrl return cleanly before doing anything else.
+ do_timeout(0, checkBusy);
+ },
+};
+
+function checkBusy() {
+ if (tests.length == 0) {
+ incomingServer.closeCachedConnections();
+
+ // No more tests, let everything finish
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+ return;
+ }
+
+ // If the server hasn't quite finished, just delay a little longer.
+ if (incomingServer.serverBusy) {
+ do_timeout(20, checkBusy);
+ return;
+ }
+
+ testNext();
+}
+
+function testNext() {
+ thisTest = tests.shift();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ server.resetTest();
+
+ // Set up the test
+ test = thisTest.title;
+ daemon.setMessages(thisTest.messages);
+
+ // Now get the mail
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+
+ server.performTest();
+ } catch (e) {
+ server.stop();
+
+ do_throw(e);
+ } finally {
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
+
+function run_test() {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ server = setupServerDaemon();
+ daemon = server[0];
+ server = server[1];
+ server.start();
+
+ // Set up the basic accounts and folders
+ incomingServer = createPop3ServerAndLocalFolders(server.port);
+
+ // Check that we haven't got any messages in the folder, if we have its a test
+ // setup issue.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+
+ do_test_pending();
+
+ testNext();
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3MoveFilter.js b/comm/mailnews/local/test/unit/test_pop3MoveFilter.js
new file mode 100644
index 0000000000..eec79bb63a
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3MoveFilter.js
@@ -0,0 +1,137 @@
+/*
+ * This file tests that a pop3 move filter doesn't leave the
+ * original message in the inbox.
+ *
+ * Original author: David Bienvenu <dbienvenu@mozilla.com>
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gFiles = ["../../../data/bugmail10", "../../../data/bugmail11"];
+
+// make sure limiting download size doesn't causes issues with move filters.
+Services.prefs.setBoolPref(
+ "mail.server.default.limit_offline_message_size",
+ true
+);
+Services.prefs.setBoolPref("mail.server.default.leave_on_server", true);
+
+// Currently we have two mailbox storage formats.
+var gPluggableStores = [
+ "@mozilla.org/msgstore/berkeleystore;1",
+ "@mozilla.org/msgstore/maildirstore;1",
+];
+
+var previews = {
+ "[Bug 436880] IMAP itemDeleted and itemMoveCopyCompleted notifications quite broken":
+ "Do not reply to this email. You can add comments to this bug at https://bugzilla.mozilla.org/show_bug.cgi?id=436880 -- Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email ------- You are receiving this mail because: -----",
+ "Bugzilla: confirm account creation":
+ "Bugzilla has received a request to create a user account using your email address (example@example.org). To confirm that you want to create an account using that email address, visit the following link: https://bugzilla.mozilla.org/token.cgi?t=xxx",
+};
+
+var gMoveFolder;
+var gFilter; // the test filter
+var gFilterList;
+var gTestArray = [
+ function createFilters() {
+ gFilterList = gPOP3Pump.fakeServer.getFilterList(null);
+ gFilter = gFilterList.createFilter("MoveAll");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.matchAll = true;
+ gFilter.appendTerm(searchTerm);
+ let moveAction = gFilter.createAction();
+ moveAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ moveAction.targetFolderUri = gMoveFolder.URI;
+ gFilter.appendAction(moveAction);
+ gFilter.enabled = true;
+ gFilter.filterType = Ci.nsMsgFilterType.InboxRule;
+ gFilterList.insertFilterAt(0, gFilter);
+ },
+ // just get a message into the local folder
+ async function getLocalMessages1() {
+ gPOP3Pump.files = gFiles;
+ await gPOP3Pump.run();
+ },
+ async function verifyFolders2() {
+ Assert.equal(folderCount(gMoveFolder), 2);
+ // the local inbox folder should now be empty, since we moved incoming mail.
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 0);
+
+ // invalidate the inbox summary file, to be sure that we really moved
+ // the mail.
+ localAccountUtils.inboxFolder.msgDatabase.summaryValid = false;
+ localAccountUtils.inboxFolder.msgDatabase = null;
+ localAccountUtils.inboxFolder.ForceDBClosed();
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ try {
+ localAccountUtils.inboxFolder.getDatabaseWithReparse(
+ promiseUrlListener,
+ null
+ );
+ } catch (ex) {
+ await promiseUrlListener.promise;
+ Assert.ok(ex.result == Cr.NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+ // This statement isn't reached since the error is thrown.
+ Assert.ok(false);
+ },
+ function verifyMessages() {
+ let hdrs = [];
+ let keys = [];
+ for (let hdr of gMoveFolder.msgDatabase.enumerateMessages()) {
+ keys.push(hdr.messageKey);
+ hdrs.push(hdr);
+ }
+ Assert.ok(!gMoveFolder.fetchMsgPreviewText(keys, null));
+ Assert.equal(
+ hdrs[0].getStringProperty("preview"),
+ previews[hdrs[0].subject]
+ );
+ Assert.equal(
+ hdrs[1].getStringProperty("preview"),
+ previews[hdrs[1].subject]
+ );
+ },
+];
+
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
+
+function setup_store(storeID) {
+ return function _setup_store() {
+ // Reset pop3Pump with correct mailbox format.
+ gPOP3Pump.resetPluggableStore(storeID);
+
+ // Make sure we're not quarantining messages
+ Services.prefs.setBoolPref("mailnews.downloadToTempFile", false);
+
+ if (!localAccountUtils.inboxFolder) {
+ localAccountUtils.loadLocalMailAccount();
+ }
+
+ gMoveFolder =
+ localAccountUtils.rootFolder.createLocalSubfolder("MoveFolder");
+ };
+}
+
+function run_test() {
+ for (let store of gPluggableStores) {
+ add_task(setup_store(store));
+ gTestArray.forEach(x => add_task(x));
+ }
+
+ add_task(exitTest);
+ run_next_test();
+}
+
+function exitTest() {
+ // Cleanup and exit the test.
+ info("Exiting mail tests\n");
+ gPOP3Pump = null;
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3MoveFilter2.js b/comm/mailnews/local/test/unit/test_pop3MoveFilter2.js
new file mode 100644
index 0000000000..71a7578310
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3MoveFilter2.js
@@ -0,0 +1,108 @@
+/*
+ * This file tests that a pop3 move filter doesn't reuse msg hdr
+ * info from previous moves.
+ *
+ * Original author: David Bienvenu <dbienvenu@mozilla.com>
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+var gFiles = ["../../../data/bugmail10", "../../../data/basic1"];
+
+Services.prefs.setBoolPref("mail.server.default.leave_on_server", true);
+
+// Currently we have two mailbox storage formats.
+var gPluggableStores = [
+ "@mozilla.org/msgstore/berkeleystore;1",
+ "@mozilla.org/msgstore/maildirstore;1",
+];
+var basic1_preview = "Hello, world!";
+var bugmail10_preview =
+ "Do not reply to this email. You can add comments to this bug at https://bugzilla.mozilla.org/show_bug.cgi?id=436880 -- Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email ------- You are receiving this mail because: -----";
+
+var gMoveFolder;
+var gFilter; // the test filter
+var gFilterList;
+var gTestArray = [
+ function createFilters() {
+ gFilterList = gPOP3Pump.fakeServer.getFilterList(null);
+ // create a cc filter which will match the first message but not the second.
+ gFilter = gFilterList.createFilter("MoveCc");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.attrib = Ci.nsMsgSearchAttrib.CC;
+ searchTerm.op = Ci.nsMsgSearchOp.Contains;
+ var oldValue = searchTerm.value;
+ oldValue.attrib = Ci.nsMsgSearchAttrib.CC;
+ oldValue.str = "invalid@example.com";
+ searchTerm.value = oldValue;
+ gFilter.appendTerm(searchTerm);
+ let moveAction = gFilter.createAction();
+ moveAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ moveAction.targetFolderUri = gMoveFolder.URI;
+ gFilter.appendAction(moveAction);
+ gFilter.enabled = true;
+ gFilter.filterType = Ci.nsMsgFilterType.InboxRule;
+ gFilterList.insertFilterAt(0, gFilter);
+ },
+ // just get a message into the local folder
+ async function getLocalMessages1() {
+ gPOP3Pump.files = gFiles;
+ await gPOP3Pump.run();
+ },
+ function verifyFolders2() {
+ Assert.equal(folderCount(gMoveFolder), 1);
+ // the local inbox folder should have one message.
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 1);
+ },
+ function verifyMessages() {
+ // check MoveFolder message
+ let hdr = [...gMoveFolder.msgDatabase.enumerateMessages()][0];
+ Assert.ok(!gMoveFolder.fetchMsgPreviewText([hdr.messageKey], null));
+ Assert.equal(hdr.getStringProperty("preview"), bugmail10_preview);
+ // check inbox message
+ hdr = [...localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()][0];
+ Assert.ok(
+ !localAccountUtils.inboxFolder.fetchMsgPreviewText([hdr.messageKey], null)
+ );
+ Assert.equal(hdr.getStringProperty("preview"), basic1_preview);
+ },
+];
+
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
+
+function setup_store(storeID) {
+ return function _setup_store() {
+ // Initialize pop3Pump with correct mailbox format.
+ gPOP3Pump.resetPluggableStore(storeID);
+
+ // Set the default mailbox store.
+ Services.prefs.setCharPref("mail.serverDefaultStoreContractID", storeID);
+
+ // Make sure we're not quarantining messages
+ Services.prefs.setBoolPref("mailnews.downloadToTempFile", false);
+ if (!localAccountUtils.inboxFolder) {
+ localAccountUtils.loadLocalMailAccount();
+ }
+
+ gMoveFolder =
+ localAccountUtils.rootFolder.createLocalSubfolder("MoveFolder");
+ };
+}
+
+function run_test() {
+ for (let store of gPluggableStores) {
+ add_task(setup_store(store));
+ gTestArray.forEach(x => add_task(x));
+ }
+
+ add_task(exitTest);
+ run_next_test();
+}
+
+function exitTest() {
+ // Cleanup and exit the test.
+ info("Exiting mail tests\n");
+ gPOP3Pump = null;
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3MultiCopy.js b/comm/mailnews/local/test/unit/test_pop3MultiCopy.js
new file mode 100644
index 0000000000..42a0f67e23
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3MultiCopy.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests that copied multiple messages in maildir are correct.
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var testSubjects = [
+ "[Bug 397009] A filter will let me tag, but not untag",
+ "Hello, did you receive my bugmail?",
+];
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/maildirstore;1"
+);
+
+add_task(async function runPump() {
+ // Test for multiple message copy for maildir.
+ let storeID = "@mozilla.org/msgstore/maildirstore;1";
+ gPOP3Pump.resetPluggableStore(storeID);
+ // Set the default mailbox store.
+ Services.prefs.setCharPref("mail.serverDefaultStoreContractID", storeID);
+
+ // We want to test cross-server copy, so don't defer.
+ gPOP3Pump.fakeServer.deferredToAccount = "";
+
+ gPOP3Pump.files = ["../../../data/bugmail1", "../../../data/draft1"];
+ await gPOP3Pump.run();
+
+ // get message headers for the inbox folder
+ let inbox = gPOP3Pump.fakeServer.rootMsgFolder.getFolderWithFlags(
+ Ci.nsMsgFolderFlags.Inbox
+ );
+ dump("inbox is at " + inbox.filePath.path + "\n");
+
+ // Accumulate messages to copy.
+ let messages = [];
+ let msgCount = 0;
+ for (let hdr of inbox.msgDatabase.enumerateMessages()) {
+ msgCount++;
+ messages.push(hdr);
+ Assert.equal(hdr.subject, testSubjects[msgCount - 1]);
+ }
+ Assert.equal(messages.length, 2);
+
+ // Create a test folder on the Local Folders account.
+ let testFolder = localAccountUtils.rootFolder
+ .QueryInterface(Ci.nsIMsgLocalMailFolder)
+ .createLocalSubfolder("test");
+ dump("testFolder is at " + testFolder.filePath.path + "\n");
+
+ // Copy messages to that folder.
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ inbox,
+ messages,
+ testFolder,
+ false,
+ promiseCopyListener,
+ null,
+ false
+ );
+ await promiseCopyListener.promise;
+
+ // Check the destination headers.
+ messages = [];
+ msgCount = 0;
+ let subjects = [];
+ for (let hdr of testFolder.msgDatabase.enumerateMessages()) {
+ msgCount++;
+ messages.push(hdr);
+ dump("Subject: " + hdr.subject + "\n");
+ subjects.push(hdr.subject);
+ }
+ Assert.equal(messages.length, 2);
+
+ // Check for subjects. maildir order for messages may not match
+ // order for creation, hence the array.includes.
+ for (let subject of testSubjects) {
+ Assert.ok(subjects.includes(subject));
+ }
+
+ // Make sure the body matches the message.
+ for (let hdr of testFolder.msgDatabase.enumerateMessages()) {
+ let body = mailTestUtils.loadMessageToString(testFolder, hdr);
+ Assert.ok(body.includes(hdr.subject));
+ }
+
+ gPOP3Pump = null;
+});
diff --git a/comm/mailnews/local/test/unit/test_pop3MultiCopy2.js b/comm/mailnews/local/test/unit/test_pop3MultiCopy2.js
new file mode 100644
index 0000000000..007a0fd99b
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3MultiCopy2.js
@@ -0,0 +1,179 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests that moved multiple messages from maildir->mbox and
+ mbox->maildir are correct.
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/maildirstore;1"
+);
+
+var gInboxFolder, gTestFolder;
+
+gPOP3Pump.files = ["../../../data/bugmail1", "../../../data/draft1"];
+var gTestSubjects = [
+ "[Bug 397009] A filter will let me tag, but not untag",
+ "Hello, did you receive my bugmail?",
+];
+
+add_setup(async function () {
+ let storeID = "@mozilla.org/msgstore/maildirstore;1";
+ resetPluggableStoreLocal(storeID);
+
+ // We want to test cross-server copy, so don't defer.
+ gPOP3Pump.fakeServer.deferredToAccount = "";
+
+ // Create a test folder on the Local Folders account.
+ gTestFolder = localAccountUtils.rootFolder
+ .QueryInterface(Ci.nsIMsgLocalMailFolder)
+ .createLocalSubfolder("test");
+ dump("testFolder is at " + gTestFolder.filePath.path + "\n");
+ await gPOP3Pump.run();
+});
+
+add_task(async function maildirToMbox() {
+ // Test for multiple message copy for maildir->mbox.
+
+ // get message headers for the inbox folder
+ gInboxFolder = gPOP3Pump.fakeServer.rootMsgFolder.getFolderWithFlags(
+ Ci.nsMsgFolderFlags.Inbox
+ );
+ dump("inbox is at " + gInboxFolder.filePath.path + "\n");
+
+ // Accumulate messages to copy.
+ let messages = [];
+ for (let hdr of gInboxFolder.msgDatabase.enumerateMessages()) {
+ messages.push(hdr);
+ }
+ Assert.equal(messages.length, 2);
+
+ // Move messages to mbox test folder.
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ gInboxFolder,
+ messages,
+ gTestFolder,
+ true, // isMove
+ promiseCopyListener,
+ null, // window
+ false // allowUndo
+ );
+ await promiseCopyListener.promise;
+
+ // Check the destination headers.
+ messages = [];
+ let subjects = [];
+ for (let hdr of gTestFolder.msgDatabase.enumerateMessages()) {
+ messages.push(hdr);
+ dump("Subject: " + hdr.subject + "\n");
+ subjects.push(hdr.subject);
+ }
+ Assert.equal(messages.length, 2);
+
+ // messages should be missing from source
+ Assert.equal(gInboxFolder.getTotalMessages(false), 0);
+
+ // Check for subjects. maildir order for messages may not match
+ // order for creation, hence the array.includes.
+ for (let subject of gTestSubjects) {
+ Assert.ok(subjects.includes(subject));
+ }
+
+ // Make sure the body matches the message.
+ for (let hdr of gTestFolder.msgDatabase.enumerateMessages()) {
+ let body = mailTestUtils.loadMessageToString(gTestFolder, hdr);
+ Assert.ok(body.includes(hdr.subject));
+ }
+});
+
+add_task(async function mboxToMaildir() {
+ // Test for multiple message copy for mbox->maildir.
+
+ // Accumulate messages to copy.
+ let messages = [];
+ for (let hdr of gTestFolder.msgDatabase.enumerateMessages()) {
+ messages.push(hdr);
+ }
+ Assert.equal(messages.length, 2);
+
+ // Move messages to inbox folder.
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ gTestFolder,
+ messages,
+ gInboxFolder,
+ true,
+ promiseCopyListener,
+ null,
+ false
+ );
+ await promiseCopyListener.promise;
+
+ // Check the destination headers.
+ messages = [];
+ let subjects = [];
+ for (let hdr of gInboxFolder.msgDatabase.enumerateMessages()) {
+ messages.push(hdr);
+ dump("Subject: " + hdr.subject + "\n");
+ subjects.push(hdr.subject);
+ }
+ Assert.equal(messages.length, 2);
+
+ // messages should be missing from source
+ Assert.equal(gTestFolder.getTotalMessages(false), 0);
+
+ // Check for subjects. maildir order for messages may not match
+ // order for creation, hence the array.includes.
+ for (let subject of gTestSubjects) {
+ Assert.ok(subjects.includes(subject));
+ }
+
+ // Make sure the body matches the message.
+ for (let hdr of gInboxFolder.msgDatabase.enumerateMessages()) {
+ let body = mailTestUtils.loadMessageToString(gInboxFolder, hdr);
+ Assert.ok(body.includes(hdr.subject));
+ }
+});
+
+add_task(function testCleanup() {
+ gPOP3Pump = null;
+});
+
+// Clone of POP3pump resetPluggableStore that does not reset local folders.
+function resetPluggableStoreLocal(aStoreContractID) {
+ Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ aStoreContractID
+ );
+
+ // Cleanup existing files, server and account instances, if any.
+ if (gPOP3Pump._server) {
+ gPOP3Pump._server.stop();
+ }
+
+ if (gPOP3Pump.fakeServer && gPOP3Pump.fakeServer.valid) {
+ gPOP3Pump.fakeServer.closeCachedConnections();
+ MailServices.accounts.removeIncomingServer(gPOP3Pump.fakeServer, false);
+ }
+
+ gPOP3Pump.fakeServer = localAccountUtils.create_incoming_server(
+ "pop3",
+ gPOP3Pump.kPOP3_PORT,
+ "fred",
+ "wilma"
+ );
+
+ // localAccountUtils.clearAll();
+
+ gPOP3Pump._incomingServer = gPOP3Pump.fakeServer;
+ gPOP3Pump._mailboxStoreContractID = aStoreContractID;
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3Password.js b/comm/mailnews/local/test/unit/test_pop3Password.js
new file mode 100644
index 0000000000..8cfd0de79d
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3Password.js
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Authentication tests for POP3.
+ */
+
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/passwordStorage.js");
+
+var server;
+var daemon;
+var incomingServer;
+var thisTest;
+
+var tests = [
+ {
+ title: "Get New Mail, One Message",
+ messages: ["message1.eml"],
+ transaction: [
+ "AUTH",
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "RETR 1",
+ "DELE 1",
+ ],
+ },
+];
+
+var urlListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, result) {
+ try {
+ var transaction = server.playTransaction();
+
+ do_check_transaction(transaction, thisTest.transaction);
+
+ Assert.equal(
+ localAccountUtils.inboxFolder.getTotalMessages(false),
+ thisTest.messages.length
+ );
+
+ Assert.equal(result, 0);
+ } catch (e) {
+ // If we have an error, clean up nicely before we throw it.
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_throw(e);
+ }
+
+ // Let OnStopRunningUrl return cleanly before doing anything else.
+ do_timeout(0, checkBusy);
+ },
+};
+
+function checkBusy() {
+ if (tests.length == 0) {
+ incomingServer.closeCachedConnections();
+
+ // No more tests, let everything finish
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+ return;
+ }
+
+ // If the server hasn't quite finished, just delay a little longer.
+ if (incomingServer.serverBusy) {
+ do_timeout(20, checkBusy);
+ return;
+ }
+
+ testNext();
+}
+
+function testNext() {
+ thisTest = tests.shift();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ server.resetTest();
+
+ // Set up the test
+ test = thisTest.title;
+ daemon.setMessages(thisTest.messages);
+
+ // Now get the mail
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+
+ server.performTest();
+ } catch (e) {
+ server.stop();
+
+ do_throw(e);
+ } finally {
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
+
+add_task(async function () {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8.json");
+
+ // Set up the Server
+ var serverArray = setupServerDaemon();
+ daemon = serverArray[0];
+ server = serverArray[1];
+ var handler = serverArray[2];
+ server.start();
+
+ // Login information needs to match the one stored in the signons json file.
+ handler.kUsername = "testpop3";
+ handler.kPassword = "pop3test";
+
+ // Set up the basic accounts and folders.
+ // We would use createPop3ServerAndLocalFolders() however we want to have
+ // a different username and NO password for this test (as we expect to load
+ // it from the signons json file in which the login information is stored).
+ localAccountUtils.loadLocalMailAccount();
+
+ incomingServer = MailServices.accounts.createIncomingServer(
+ "testpop3",
+ "localhost",
+ "pop3"
+ );
+
+ incomingServer.port = server.port;
+
+ // Check that we haven't got any messages in the folder, if we have its a test
+ // setup issue.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+
+ do_test_pending();
+
+ testNext();
+});
diff --git a/comm/mailnews/local/test/unit/test_pop3Password2.js b/comm/mailnews/local/test/unit/test_pop3Password2.js
new file mode 100644
index 0000000000..9bc32e471b
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3Password2.js
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Authentication tests for POP3 - checks for servers whose details have
+ * changed (e.g. realusername and realhostname are different from username and
+ * hostname).
+ */
+
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/passwordStorage.js");
+
+var server;
+var daemon;
+var incomingServer;
+var thisTest;
+
+var tests = [
+ {
+ title: "Get New Mail, One Message",
+ messages: ["message1.eml"],
+ transaction: [
+ "AUTH",
+ "CAPA",
+ "AUTH PLAIN",
+ "STAT",
+ "LIST",
+ "UIDL",
+ "RETR 1",
+ "DELE 1",
+ ],
+ },
+];
+
+var urlListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, result) {
+ try {
+ var transaction = server.playTransaction();
+
+ do_check_transaction(transaction, thisTest.transaction);
+
+ Assert.equal(
+ localAccountUtils.inboxFolder.getTotalMessages(false),
+ thisTest.messages.length
+ );
+
+ Assert.equal(result, 0);
+ } catch (e) {
+ // If we have an error, clean up nicely before we throw it.
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_throw(e);
+ }
+
+ // Let OnStopRunningUrl return cleanly before doing anything else.
+ do_timeout(0, checkBusy);
+ },
+};
+
+function checkBusy() {
+ if (tests.length == 0) {
+ incomingServer.closeCachedConnections();
+
+ // No more tests, let everything finish
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+ return;
+ }
+
+ // If the server hasn't quite finished, just delay a little longer.
+ if (incomingServer.serverBusy) {
+ do_timeout(20, checkBusy);
+ return;
+ }
+
+ testNext();
+}
+
+function testNext() {
+ thisTest = tests.shift();
+
+ // Handle the server in a try/catch/finally loop so that we always will stop
+ // the server if something fails.
+ try {
+ server.resetTest();
+
+ // Set up the test
+ test = thisTest.title;
+ daemon.setMessages(thisTest.messages);
+
+ // Now get the mail
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+
+ server.performTest();
+ } catch (e) {
+ server.stop();
+
+ do_throw(e);
+ } finally {
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
+
+add_task(async function () {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ // These preferences set up a local pop server that has had its hostname
+ // and username changed from the original settings. We can't do this by
+ // function calls for this test as they would cause the password to be
+ // forgotten when changing the hostname/username and this breaks the test.
+ Services.prefs.setCharPref("mail.account.account1.server", "server1");
+ Services.prefs.setCharPref("mail.account.account2.server", "server2");
+ Services.prefs.setCharPref("mail.account.account2.identities", "id1");
+ Services.prefs.setCharPref(
+ "mail.accountmanager.accounts",
+ "account1,account2"
+ );
+ Services.prefs.setCharPref(
+ "mail.accountmanager.localfoldersserver",
+ "server1"
+ );
+ Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account2");
+ Services.prefs.setCharPref("mail.identity.id1.fullName", "testpop3");
+ Services.prefs.setCharPref(
+ "mail.identity.id1.useremail",
+ "testpop3@localhost"
+ );
+ Services.prefs.setBoolPref("mail.identity.id1.valid", true);
+ Services.prefs.setCharPref(
+ "mail.server.server1.directory-rel",
+ "[ProfD]Mail/Local Folders"
+ );
+ Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server1.name", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server1.type", "none");
+ Services.prefs.setCharPref("mail.server.server1.userName", "nobody");
+ Services.prefs.setCharPref(
+ "mail.server.server2.directory-rel",
+ "[ProfD]Mail/invalid"
+ );
+ Services.prefs.setCharPref("mail.server.server2.hostname", "invalid");
+ Services.prefs.setCharPref(
+ "mail.server.server2.name",
+ "testpop3 on localhost"
+ );
+ Services.prefs.setCharPref("mail.server.server2.realhostname", "localhost");
+ Services.prefs.setCharPref("mail.server.server2.realuserName", "testpop3");
+ Services.prefs.setCharPref("mail.server.server2.type", "pop3");
+ Services.prefs.setCharPref("mail.server.server2.userName", "othername");
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8-alt.json");
+
+ // Set up the Server
+ var serverArray = setupServerDaemon();
+ daemon = serverArray[0];
+ server = serverArray[1];
+ var handler = serverArray[2];
+ server.start();
+ Services.prefs.setIntPref("mail.server.server2.port", server.port);
+
+ // Login information needs to match the one stored in the signons json file.
+ handler.kUsername = "testpop3";
+ handler.kPassword = "pop3test";
+
+ MailServices.accounts.loadAccounts();
+
+ localAccountUtils.incomingServer = MailServices.accounts.localFoldersServer;
+
+ var rootFolder =
+ localAccountUtils.incomingServer.rootMsgFolder.QueryInterface(
+ Ci.nsIMsgLocalMailFolder
+ );
+
+ // Note: Inbox is not created automatically when there is no deferred server,
+ // so we need to create it.
+ localAccountUtils.inboxFolder = rootFolder.createLocalSubfolder("Inbox");
+ // a local inbox should have a Mail flag!
+ localAccountUtils.inboxFolder.setFlag(Ci.nsMsgFolderFlags.Mail);
+
+ // Create the incoming server with "original" details.
+ incomingServer = MailServices.accounts.getIncomingServer("server2");
+
+ // Check that we haven't got any messages in the folder, if we have its a test
+ // setup issue.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+
+ do_test_pending();
+
+ testNext();
+});
diff --git a/comm/mailnews/local/test/unit/test_pop3Password3.js b/comm/mailnews/local/test/unit/test_pop3Password3.js
new file mode 100644
index 0000000000..b12181e746
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3Password3.js
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Extra tests for POP3 passwords (forgetPassword)
+ */
+
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/passwordStorage.js");
+
+var kUser1 = "testpop3";
+var kUser2 = "testpop3a";
+var kProtocol = "pop3";
+var kHostname = "localhost";
+var kServerUrl = "mailbox://" + kHostname;
+
+add_task(async function () {
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8-multiple.json");
+
+ // Set up the basic accounts and folders.
+ // We would use createPop3ServerAndLocalFolders() however we want to have
+ // a different username and NO password for this test (as we expect to load
+ // it from the signons json file in which the login information is stored).
+ localAccountUtils.loadLocalMailAccount();
+
+ let incomingServer1 = MailServices.accounts.createIncomingServer(
+ kUser1,
+ kHostname,
+ kProtocol
+ );
+
+ let incomingServer2 = MailServices.accounts.createIncomingServer(
+ kUser2,
+ kHostname,
+ kProtocol
+ );
+
+ // Test - Check there are two logins to begin with.
+ var logins = Services.logins.findLogins(kServerUrl, null, kServerUrl);
+
+ Assert.equal(logins.length, 2);
+
+ // These will either be one way around or the other.
+ if (logins[0].username == kUser1) {
+ Assert.equal(logins[1].username, kUser2);
+ } else {
+ Assert.equal(logins[0].username, kUser2);
+ Assert.equal(logins[1].username, kUser1);
+ }
+
+ // Test - Remove a login via the incoming server
+ incomingServer1.forgetPassword();
+
+ logins = Services.logins.findLogins(kServerUrl, null, kServerUrl);
+
+ // should be one login left for kUser2
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUser2);
+
+ // Bug 561056 - Expand username to also contain domain (i.e. full email).
+ incomingServer2.username = kUser2 + "@local.host";
+
+ logins = Services.logins.findLogins(kServerUrl, null, kServerUrl);
+
+ // There should still be the one login left for kUser2
+ Assert.equal(logins.length, 1);
+ // LoginInfo should be migrated in MsgIncomingServer.jsm.
+ Assert.equal(logins[0].username, incomingServer2.username);
+
+ // Change username to another one.
+ incomingServer2.username = "testpop";
+ logins = Services.logins.findLogins(kServerUrl, null, kServerUrl);
+
+ // LoginInfo should be migrated in MsgIncomingServer.jsm.
+ Assert.equal(logins.length, 1);
+});
diff --git a/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc1939.js b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc1939.js
new file mode 100644
index 0000000000..5a582649fa
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc1939.js
@@ -0,0 +1,216 @@
+/* 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/. */
+
+/**
+ * Tests password failure with RFC1939.
+ *
+ * This test checks to see if the pop3 password failure is handled correctly.
+ * The steps are:
+ * - Have an invalid password in the password database.
+ * - Check we get a prompt asking what to do.
+ * - Check retry does what it should do.
+ * - Check cancel does what it should do.
+ * - Re-initiate connection, this time select enter new password, check that
+ * we get a new password prompt and can enter the password.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/alertTestUtils.js");
+load("../../../resources/passwordStorage.js");
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var server;
+var daemon;
+var incomingServer;
+var attempt = 0;
+
+var kUserName = "testpop3";
+var kInvalidPassword = "pop3test";
+var kValidPassword = "testpop3";
+
+add_setup(async function () {
+ // Enable debug for the sign on.
+ Services.prefs.setBoolPref("signon.debug", true);
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8.json");
+
+ registerAlertTestUtils();
+
+ // Set up the Server
+ daemon = new Pop3Daemon();
+ function createHandler(d) {
+ var handler = new POP3_RFC1939_handler(d);
+ handler.dropOnAuthFailure = true;
+
+ // Login information needs to match the one stored in the signons json file.
+ handler.kUsername = kUserName;
+ handler.kPassword = kValidPassword;
+ return handler;
+ }
+ server = new nsMailServer(createHandler, daemon);
+ server.start();
+
+ // Set up the basic accounts and folders.
+ // We would use createPop3ServerAndLocalFolders() however we want to have
+ // a different username and NO password for this test (as we expect to load
+ // it from the signons json file in which the login information is stored).
+ localAccountUtils.loadLocalMailAccount();
+
+ incomingServer = MailServices.accounts.createIncomingServer(
+ kUserName,
+ "localhost",
+ "pop3"
+ );
+
+ incomingServer.port = server.port;
+
+ // Check that we haven't got any messages in the folder, if we have its a test
+ // setup issue.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+
+ daemon.setMessages(["message1.eml"]);
+});
+
+add_task(async function getMail1() {
+ // Now get the mail.
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.pop3.GetNewMail(
+ gDummyMsgWindow,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ server.performTest();
+ await Assert.rejects(
+ urlListener.promise,
+ reason => {
+ return reason === Cr.NS_ERROR_FAILURE;
+ },
+ "Check that wrong password is entered and thrown"
+ );
+ // We shouldn't have emails as the auth failed.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+ // Sanity check that we are at attempt 2.
+ Assert.equal(attempt, 2);
+
+ // Check that we haven't forgotten the login even though we've retried and cancelled.
+ let logins = Services.logins.findLogins(
+ "mailbox://localhost",
+ null,
+ "mailbox://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUserName);
+ Assert.equal(logins[0].password, kInvalidPassword);
+
+ server.resetTest();
+});
+
+add_task(async function getMail2() {
+ // Now get the mail
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.pop3.GetNewMail(
+ gDummyMsgWindow,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ server.performTest();
+ await urlListener.promise;
+ Assert.equal(attempt, 4);
+ // On the last attempt (4th), we should have successfully got one mail.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 1);
+
+ // Now check the new one has been saved.
+ let logins = Services.logins.findLogins(
+ "mailbox://localhost",
+ null,
+ "mailbox://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUserName);
+ Assert.equal(logins[0].password, kValidPassword);
+});
+
+add_task(function endTest() {
+ // Cleanup for potential Sockets/Ports leakage.
+ server.stop();
+ server = null;
+ daemon = null;
+ incomingServer = null;
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
+
+/* exported alert, confirmEx, promptPasswordPS */
+function alertPS(parent, aDialogText, aText) {
+ // The first few attempts may prompt about the password problem, the last
+ // attempt shouldn't.
+ Assert.ok(attempt < 4);
+
+ // Log the fact we've got an alert, but we don't need to test anything here.
+ info("Alert Title: " + aDialogText + "\nAlert Text: " + aText);
+}
+
+function confirmExPS(
+ parent,
+ aDialogTitle,
+ aText,
+ aButtonFlags,
+ aButton0Title,
+ aButton1Title,
+ aButton2Title,
+ aCheckMsg,
+ aCheckState
+) {
+ switch (++attempt) {
+ // First attempt, retry.
+ case 1:
+ info("Attempting retry");
+ return 0;
+ // Second attempt, cancel.
+ case 2:
+ info("Cancelling login attempt");
+ return 1;
+ // Third attempt, retry.
+ case 3:
+ info("Attempting Retry");
+ return 0;
+ // Fourth attempt, enter a new password.
+ case 4:
+ info("Enter new password");
+ return 2;
+ default:
+ throw new Error("unexpected attempt number " + attempt);
+ }
+}
+
+/**
+ * Extension for alertTestUtils.
+ * Make sure that at the 4th attempt the correct password is used.
+ */
+function promptPasswordPS(
+ aParent,
+ aDialogTitle,
+ aText,
+ aPassword,
+ aCheckMsg,
+ aCheckState
+) {
+ if (attempt == 4) {
+ aPassword.value = kValidPassword;
+ aCheckState.value = true;
+ return true;
+ }
+ return false;
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc2449.js b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc2449.js
new file mode 100644
index 0000000000..1701f999c9
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc2449.js
@@ -0,0 +1,215 @@
+/* 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/. */
+
+/**
+ * Tests password failure with RFC2449 auth.
+ *
+ * This test checks to see if the pop3 password failure is handled correctly.
+ * The steps are:
+ * - Have an invalid password in the password database.
+ * - Check we get a prompt asking what to do.
+ * - Check retry does what it should do.
+ * - Check cancel does what it should do.
+ * - Re-initiate connection, this time select enter new password, check that
+ * we get a new password prompt and can enter the password.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/alertTestUtils.js");
+load("../../../resources/passwordStorage.js");
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var server;
+var daemon;
+var incomingServer;
+var attempt = 0;
+
+var kUserName = "testpop3";
+var kInvalidPassword = "pop3test";
+var kValidPassword = "testpop3";
+
+add_setup(async function () {
+ // Enable debug for the sign on.
+ Services.prefs.setBoolPref("signon.debug", true);
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8.json");
+
+ registerAlertTestUtils();
+
+ // Set up the Server
+ daemon = new Pop3Daemon();
+ function createHandler(d) {
+ var handler = new POP3_RFC2449_handler(d);
+ handler.dropOnAuthFailure = true;
+
+ // Login information needs to match the one stored in the signons json file.
+ handler.kUsername = kUserName;
+ handler.kPassword = kValidPassword;
+ return handler;
+ }
+ server = new nsMailServer(createHandler, daemon);
+ server.start();
+
+ // Set up the basic accounts and folders.
+ // We would use createPop3ServerAndLocalFolders() however we want to have
+ // a different username and NO password for this test (as we expect to load
+ // it from the signons json file in which the login information is stored).
+ localAccountUtils.loadLocalMailAccount();
+
+ incomingServer = MailServices.accounts.createIncomingServer(
+ kUserName,
+ "localhost",
+ "pop3"
+ );
+
+ incomingServer.port = server.port;
+
+ // Check that we haven't got any messages in the folder, if we have its a test
+ // setup issue.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+
+ daemon.setMessages(["message1.eml"]);
+});
+
+add_task(async function getMail1() {
+ // Now get the mail.
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.pop3.GetNewMail(
+ gDummyMsgWindow,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ server.performTest();
+ await Assert.rejects(
+ urlListener.promise,
+ reason => {
+ return reason === Cr.NS_ERROR_FAILURE;
+ },
+ "Check that wrong password is entered and thrown"
+ );
+ // We shouldn't have emails as the auth failed.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+ // Sanity check that we are at attempt 2.
+ Assert.equal(attempt, 2);
+
+ // Check that we haven't forgotten the login even though we've retried and cancelled.
+ let logins = Services.logins.findLogins(
+ "mailbox://localhost",
+ null,
+ "mailbox://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUserName);
+ Assert.equal(logins[0].password, kInvalidPassword);
+
+ server.resetTest();
+});
+
+add_task(async function getMail2() {
+ // Now get the mail.
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.pop3.GetNewMail(
+ gDummyMsgWindow,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ server.performTest();
+ await urlListener.promise;
+ Assert.equal(attempt, 4);
+ // On the last attempt (4th), we should have successfully got one mail.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 1);
+
+ // Now check the new one has been saved.
+ let logins = Services.logins.findLogins(
+ "mailbox://localhost",
+ null,
+ "mailbox://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUserName);
+ Assert.equal(logins[0].password, kValidPassword);
+});
+
+add_task(function endTest() {
+ // Cleanup for potential Sockets/Ports leakage.
+ server.stop();
+ server = null;
+ daemon = null;
+ incomingServer = null;
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
+
+function alertPS(parent, aDialogText, aText) {
+ // The first few attempts may prompt about the password problem, the last
+ // attempt shouldn't.
+ Assert.ok(attempt < 4);
+
+ // Log the fact we've got an alert, but we don't need to test anything here.
+ info("Alert Title: " + aDialogText + "\nAlert Text: " + aText);
+}
+
+function confirmExPS(
+ parent,
+ aDialogTitle,
+ aText,
+ aButtonFlags,
+ aButton0Title,
+ aButton1Title,
+ aButton2Title,
+ aCheckMsg,
+ aCheckState
+) {
+ switch (++attempt) {
+ // First attempt, retry.
+ case 1:
+ info("Attempting retry");
+ return 0;
+ // Second attempt, cancel.
+ case 2:
+ info("Cancelling login attempt");
+ return 1;
+ // Third attempt, retry.
+ case 3:
+ info("Attempting Retry");
+ return 0;
+ // Fourth attempt, enter a new password.
+ case 4:
+ info("Enter new password");
+ return 2;
+ default:
+ throw new Error("unexpected attempt number " + attempt);
+ }
+}
+
+/**
+ * Extension for alertTestUtils.
+ * Make sure that at the 4th attempt the correct password is used.
+ */
+function promptPasswordPS(
+ aParent,
+ aDialogTitle,
+ aText,
+ aPassword,
+ aCheckMsg,
+ aCheckState
+) {
+ if (attempt == 4) {
+ aPassword.value = kValidPassword;
+ aCheckState.value = true;
+ return true;
+ }
+ return false;
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc5034.js b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc5034.js
new file mode 100644
index 0000000000..20e63d1358
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc5034.js
@@ -0,0 +1,217 @@
+/* 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/. */
+
+/**
+ * Tests password failure with RFC5034 auth.
+ *
+ * This test checks to see if the pop3 password failure is handled correctly
+ * in the case of the server dropping the connection during auth login.
+ * We use POP3_RFC5034_handler so auth=login will be supported.
+ * The steps are:
+ * - Have an invalid password in the password database.
+ * - Check we get a prompt asking what to do.
+ * - Check retry does what it should do.
+ * - Check cancel does what it should do.
+ * - Re-initiate connection, this time select enter new password, check that
+ * we get a new password prompt and can enter the password.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/alertTestUtils.js");
+load("../../../resources/passwordStorage.js");
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var server;
+var daemon;
+var incomingServer;
+var attempt = 0;
+
+var kUserName = "testpop3";
+var kInvalidPassword = "pop3test";
+var kValidPassword = "testpop3";
+
+add_setup(async function () {
+ // Enable debug for the sign on.
+ Services.prefs.setBoolPref("signon.debug", true);
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8.json");
+
+ registerAlertTestUtils();
+
+ // Set up the Server
+ daemon = new Pop3Daemon();
+ function createHandler(d) {
+ var handler = new POP3_RFC5034_handler(d);
+ handler.dropOnAuthFailure = true;
+ // Login information needs to match the one stored in the signons json file.
+ handler.kUsername = kUserName;
+ handler.kPassword = kValidPassword;
+ return handler;
+ }
+ server = new nsMailServer(createHandler, daemon);
+ server.start();
+
+ // Set up the basic accounts and folders.
+ // We would use createPop3ServerAndLocalFolders() however we want to have
+ // a different username and NO password for this test (as we expect to load
+ // it from the signons json file in which the login information is stored).
+ localAccountUtils.loadLocalMailAccount();
+
+ incomingServer = MailServices.accounts.createIncomingServer(
+ kUserName,
+ "localhost",
+ "pop3"
+ );
+
+ incomingServer.port = server.port;
+
+ // Check that we haven't got any messages in the folder, if we have its a test
+ // setup issue.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+
+ daemon.setMessages(["message1.eml"]);
+});
+
+add_task(async function getMail1() {
+ // Now get the mail.
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.pop3.GetNewMail(
+ gDummyMsgWindow,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ server.performTest();
+ await Assert.rejects(
+ urlListener.promise,
+ reason => {
+ return reason === Cr.NS_ERROR_FAILURE;
+ },
+ "Check that wrong password is entered and thrown"
+ );
+ // We shouldn't have emails as the auth failed.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+ // Sanity check that we are at attempt 2.
+ Assert.equal(attempt, 2);
+
+ // Check that we haven't forgotten the login even though we've retried and cancelled.
+ let logins = Services.logins.findLogins(
+ "mailbox://localhost",
+ null,
+ "mailbox://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUserName);
+ Assert.equal(logins[0].password, kInvalidPassword);
+
+ server.resetTest();
+});
+
+add_task(async function getMail2() {
+ // Now get the mail.
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.pop3.GetNewMail(
+ gDummyMsgWindow,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ server.performTest();
+ await urlListener.promise;
+ Assert.equal(attempt, 4);
+ // On the last attempt (4th), we should have successfully got one mail.
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 1);
+
+ // Now check the new one has been saved.
+ let logins = Services.logins.findLogins(
+ "mailbox://localhost",
+ null,
+ "mailbox://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUserName);
+ Assert.equal(logins[0].password, kValidPassword);
+});
+
+add_task(function endTest() {
+ // Cleanup for potential Sockets/Ports leakage.
+ server.stop();
+ server = null;
+ daemon = null;
+ incomingServer = null;
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
+
+/* exported alert, confirmEx, promptPasswordPS */
+function alertPS(parent, aDialogText, aText) {
+ // The first few attempts may prompt about the password problem, the last
+ // attempt shouldn't.
+ Assert.ok(attempt < 4);
+
+ // Log the fact we've got an alert, but we don't need to test anything here.
+ info("Alert Title: " + aDialogText + "\nAlert Text: " + aText);
+}
+
+function confirmExPS(
+ parent,
+ aDialogTitle,
+ aText,
+ aButtonFlags,
+ aButton0Title,
+ aButton1Title,
+ aButton2Title,
+ aCheckMsg,
+ aCheckState
+) {
+ switch (++attempt) {
+ // First attempt, retry.
+ case 1:
+ info("Attempting retry");
+ return 0;
+ // Second attempt, cancel.
+ case 2:
+ info("Cancelling login attempt");
+ return 1;
+ // Third attempt, retry.
+ case 3:
+ info("Attempting Retry");
+ return 0;
+ // Fourth attempt, enter a new password.
+ case 4:
+ info("Enter new password");
+ return 2;
+ default:
+ throw new Error("unexpected attempt number " + attempt);
+ }
+}
+
+/**
+ * Extension for alertTestUtils.
+ * Make sure that at the 4th attempt the correct password is used.
+ */
+function promptPasswordPS(
+ aParent,
+ aDialogTitle,
+ aText,
+ aPassword,
+ aCheckMsg,
+ aCheckState
+) {
+ if (attempt == 4) {
+ aPassword.value = kValidPassword;
+ aCheckState.value = true;
+ return true;
+ }
+ return false;
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3Proxy.js b/comm/mailnews/local/test/unit/test_pop3Proxy.js
new file mode 100644
index 0000000000..248bbaf92b
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3Proxy.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+// Test that POP3 over a proxy works.
+
+const { NetworkTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/NetworkTestUtils.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+const PORT = 110;
+
+var server, daemon, incomingServer;
+
+add_setup(async function () {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ [daemon, server] = setupServerDaemon();
+ server.start();
+ NetworkTestUtils.configureProxy("pop.tinderbox.invalid", PORT, server.port);
+
+ // Set up the basic accounts and folders
+ incomingServer = createPop3ServerAndLocalFolders(
+ PORT,
+ "pop.tinderbox.invalid"
+ );
+
+ // Add a message to download
+ daemon.setMessages(["message1.eml"]);
+});
+
+add_task(async function downloadEmail() {
+ // Check that we haven't got any messages in the folder, if we have its a test
+ // setup issue.
+ equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+
+ // Now get the mail
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ await urlListener.promise;
+
+ // We downloaded a message, so it works!
+ equal(localAccountUtils.inboxFolder.getTotalMessages(false), 1);
+});
+
+add_task(async function cleanUp() {
+ NetworkTestUtils.shutdownServers();
+ incomingServer.closeCachedConnections();
+ server.stop();
+});
diff --git a/comm/mailnews/local/test/unit/test_pop3Pump.js b/comm/mailnews/local/test/unit/test_pop3Pump.js
new file mode 100644
index 0000000000..239d284abb
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3Pump.js
@@ -0,0 +1,32 @@
+/**
+ * The intent of this file is to demonstrate a minimal
+ * POP3 unit test using the testing file POP3Pump.js
+ */
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var testSubjects = [
+ "[Bug 397009] A filter will let me tag, but not untag",
+ "Hello, did you receive my bugmail?",
+];
+
+add_task(async function runPump() {
+ // demonstration of access to the local inbox folder
+ dump(
+ "local inbox folder " + localAccountUtils.inboxFolder.URI + " is loaded\n"
+ );
+ // demonstration of access to the fake server
+ dump("Server " + gPOP3Pump.fakeServer.prettyName + " is loaded\n");
+
+ gPOP3Pump.files = ["../../../data/bugmail1", "../../../data/draft1"];
+ await gPOP3Pump.run();
+
+ // get message headers for the inbox folder
+ var msgCount = 0;
+ for (let hdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) {
+ msgCount++;
+ Assert.equal(hdr.subject, testSubjects[msgCount - 1]);
+ }
+ Assert.equal(msgCount, 2);
+ gPOP3Pump = null;
+});
diff --git a/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMDisconnect.js b/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMDisconnect.js
new file mode 100644
index 0000000000..ff13870352
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMDisconnect.js
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Server which advertises CRAM-MD5, but is impolite enough to just
+ * disconnect (close the TCP connection) when we try it.
+ *
+ * This is a tough one, because we may lose state on which auth schemes
+ * are allowed and which ones failed and may restart from scratch, and
+ * retry, never skipping the failed scheme.
+ * Dear server implementors, NEVER DO THAT! Be polite, give an error
+ * with explanation and by all means keep the connection open.
+ *
+ * I don't know if real servers do that, but bienvenu says they exist.
+ *
+ * TODO:
+ * This test shows that the current situation is not good.
+ * Problems:
+ * - We should reopen the connection, remember which auth scheme failed
+ * and start with the next in list, not trying the broken one again.
+ * We currently neither retry nor remember.
+ * - incomingServer thinks it is still running/busy although the connection is
+ * clearly done and over.
+ *
+ * @author Ben Bucksch
+ */
+
+var server;
+var daemon;
+var incomingServer;
+test =
+ "Server which advertises CRAM-MD5, but closes the connection when it's tried";
+// that's how it currently looks like (we fail to log in):
+var expectedTransaction = ["AUTH", "CAPA", "AUTH CRAM-MD5"];
+// TODO that's how it should look like (we start a new connection and try another scheme):
+// const expectedTransaction = ["AUTH", "CAPA", "AUTH CRAM-MD5", "CAPA", "AUTH PLAIN", "STAT"];
+
+var urlListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, result) {
+ try {
+ // We should be getting an error here, because we couldn't log in.
+ Assert.equal(result, Cr.NS_ERROR_FAILURE);
+
+ var transaction = server.playTransaction();
+ do_check_transaction(transaction, expectedTransaction);
+
+ do_timeout(0, endTest);
+ } catch (e) {
+ server.stop();
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_throw(e);
+ }
+ },
+};
+
+function endTest() {
+ // No more tests, let everything finish
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+}
+
+function CRAMFail_handler(daemon_) {
+ POP3_RFC5034_handler.call(this, daemon_);
+
+ this._kAuthSchemeStartFunction["CRAM-MD5"] = this.killConn;
+}
+CRAMFail_handler.prototype = {
+ __proto__: POP3_RFC5034_handler.prototype, // inherit
+
+ killConn() {
+ this.closing = true;
+ return "-ERR I don't feel like it";
+ },
+};
+
+function run_test() {
+ try {
+ do_test_pending();
+
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ daemon = new Pop3Daemon();
+ function createHandler(d) {
+ return new CRAMFail_handler(d);
+ }
+ server = new nsMailServer(createHandler, daemon);
+ server.start();
+
+ incomingServer = createPop3ServerAndLocalFolders(server.port);
+ let msgServer = incomingServer;
+ msgServer.QueryInterface(Ci.nsIMsgIncomingServer);
+ // Need to allow any auth here, although that's not use in TB really,
+ // because we need to fall back to something after CRAM-MD5 and
+ // check that login works after we fell back.
+ msgServer.authMethod = Ci.nsMsgAuthMethod.anything;
+
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ server.performTest();
+ } catch (e) {
+ server.stop();
+
+ do_throw(e);
+ } finally {
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
diff --git a/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMFail.js b/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMFail.js
new file mode 100644
index 0000000000..105f3fb0a0
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMFail.js
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/**
+ * Server which advertises CRAM-MD5, but fails when it's tried.
+ * This reportedly happens for some misconfigured servers.
+ */
+
+var server;
+var daemon;
+var incomingServer;
+test = "Server which advertises CRAM-MD5, but fails when it's tried";
+var expectedTransaction = [
+ "AUTH",
+ "CAPA",
+ "AUTH CRAM-MD5",
+ "AUTH PLAIN",
+ "STAT",
+];
+
+const kStateAuthNeeded = 1; // the same value as in Pop3d.jsm
+
+var urlListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, result) {
+ try {
+ Assert.equal(result, 0);
+
+ var transaction = server.playTransaction();
+ do_check_transaction(transaction, expectedTransaction);
+
+ do_timeout(0, checkBusy);
+ } catch (e) {
+ server.stop();
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_throw(e);
+ }
+ },
+};
+
+function checkBusy() {
+ // If the server hasn't quite finished, just delay a little longer.
+ if (incomingServer.serverBusy) {
+ do_timeout(20, checkBusy);
+ return;
+ }
+
+ endTest();
+}
+
+function endTest() {
+ incomingServer.closeCachedConnections();
+
+ // No more tests, let everything finish
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+}
+
+function CRAMFail_handler(daemon_) {
+ POP3_RFC5034_handler.call(this, daemon_);
+
+ this._kAuthSchemeStartFunction["CRAM-MD5"] = this.killConn;
+}
+CRAMFail_handler.prototype = {
+ __proto__: POP3_RFC5034_handler.prototype, // inherit
+
+ killConn() {
+ this._multiline = false;
+ this._state = kStateAuthNeeded;
+ return "-ERR I just pretended to implement CRAM-MD5";
+ },
+};
+
+function run_test() {
+ try {
+ do_test_pending();
+
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ daemon = new Pop3Daemon();
+ function createHandler(d) {
+ return new CRAMFail_handler(d);
+ }
+ server = new nsMailServer(createHandler, daemon);
+ server.start();
+
+ incomingServer = createPop3ServerAndLocalFolders(server.port);
+ let msgServer = incomingServer;
+ msgServer.QueryInterface(Ci.nsIMsgIncomingServer);
+ // Need to allow any auth here, although that's not use in TB really,
+ // because we need to fall back to something after CRAM-MD5 and
+ // check that login works after we fell back.
+ msgServer.authMethod = Ci.nsMsgAuthMethod.anything;
+
+ MailServices.pop3.GetNewMail(
+ null,
+ urlListener,
+ localAccountUtils.inboxFolder,
+ incomingServer
+ );
+ server.performTest();
+ } catch (e) {
+ server.stop();
+
+ do_throw(e);
+ } finally {
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ }
+}
diff --git a/comm/mailnews/local/test/unit/test_preview.js b/comm/mailnews/local/test/unit/test_preview.js
new file mode 100644
index 0000000000..83cfce256b
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_preview.js
@@ -0,0 +1,40 @@
+var bugmail10 = do_get_file("../../../data/bugmail10");
+var bugmail11 = do_get_file("../../../data/bugmail11");
+var bugmail10_preview =
+ "Do not reply to this email. You can add comments to this bug at https://bugzilla.mozilla.org/show_bug.cgi?id=436880 -- Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email ------- You are receiving this mail because: -----";
+var bugmail11_preview =
+ "Bugzilla has received a request to create a user account using your email address (example@example.org). To confirm that you want to create an account using that email address, visit the following link: https://bugzilla.mozilla.org/token.cgi?t=xxx";
+
+function run_test() {
+ do_test_pending();
+ copyFileMessageInLocalFolder(bugmail10, 0, "", null, copy_next_message);
+}
+
+function copy_next_message(aMessageHeaderKeys, aStatus) {
+ copyFileMessageInLocalFolder(bugmail11, 0, "", null, test_preview);
+}
+
+function test_preview(aMessageHeaderKeys, aStatus) {
+ let headerKeys = aMessageHeaderKeys;
+ Assert.notEqual(headerKeys, null);
+ Assert.equal(headerKeys.length, 2);
+ try {
+ localAccountUtils.inboxFolder.fetchMsgPreviewText(headerKeys, null);
+ Assert.equal(
+ localAccountUtils.inboxFolder
+ .GetMessageHeader(headerKeys[0])
+ .getStringProperty("preview"),
+ bugmail10_preview
+ );
+ Assert.equal(
+ localAccountUtils.inboxFolder
+ .GetMessageHeader(headerKeys[1])
+ .getStringProperty("preview"),
+ bugmail11_preview
+ );
+ } catch (ex) {
+ dump(ex);
+ do_throw(ex);
+ }
+ do_test_finished();
+}
diff --git a/comm/mailnews/local/test/unit/test_saveMessage.js b/comm/mailnews/local/test/unit/test_saveMessage.js
new file mode 100644
index 0000000000..e488a0ed5b
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_saveMessage.js
@@ -0,0 +1,69 @@
+/**
+ * Test bug 460636 - Saving message in local folder as .EML removes starting dot in all lines, and ignores line if single dot only line.
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var MSG_LINEBREAK = "\r\n";
+var dot = do_get_file("data/dot");
+var saveFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+saveFile.append(dot.leafName + ".eml");
+saveFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+function run_test() {
+ registerCleanupFunction(teardown);
+ do_test_pending();
+ do_timeout(10000, function () {
+ do_throw(
+ "SaveMessageToDisk did not complete within 10 seconds" +
+ "(incorrect messageURI?). ABORTING."
+ );
+ });
+ copyFileMessageInLocalFolder(dot, 0, "", null, save_message);
+}
+
+async function save_message(aMessageHeaderKeys, aStatus) {
+ let headerKeys = aMessageHeaderKeys;
+ Assert.notEqual(headerKeys, null);
+
+ let message = localAccountUtils.inboxFolder.GetMessageHeader(headerKeys[0]);
+ let msgURI = localAccountUtils.inboxFolder.getUriForMsg(message);
+ let messageService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=mailbox-message"
+ ].getService(Ci.nsIMsgMessageService);
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ messageService.SaveMessageToDisk(
+ msgURI,
+ saveFile,
+ false,
+ promiseUrlListener,
+ {},
+ true,
+ null
+ );
+ await promiseUrlListener.promise;
+ check_each_line(
+ await IOUtils.readUTF8(dot.path),
+ await IOUtils.readUTF8(saveFile.path)
+ );
+ do_test_finished();
+}
+
+function check_each_line(aExpectedLines, aActualLines) {
+ let expectedStrings = aExpectedLines.split(MSG_LINEBREAK);
+ let actualStrings = aActualLines.split(MSG_LINEBREAK);
+
+ expectedStrings.shift();
+ Assert.equal(expectedStrings.length, actualStrings.length);
+ for (let line = 0; line < expectedStrings.length; line++) {
+ Assert.equal(expectedStrings[line], actualStrings[line]);
+ }
+}
+
+function teardown() {
+ if (saveFile.exists()) {
+ saveFile.remove(false);
+ }
+}
diff --git a/comm/mailnews/local/test/unit/test_streamHeaders.js b/comm/mailnews/local/test/unit/test_streamHeaders.js
new file mode 100644
index 0000000000..a1e7ef1640
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_streamHeaders.js
@@ -0,0 +1,90 @@
+/* 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 mainly tests that streamHeaders does not result in the crash
+ * of bug 752768
+ *
+ * adapted from test_pop3Pump.js by Kent James <kent@caspia.com>
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var testSubjects = ["Hello, did you receive my bugmail?"];
+
+var gHdr;
+
+add_task(async function loadMessages() {
+ let pop3Resolve;
+ let pop3Promise = new Promise(resolve => {
+ pop3Resolve = resolve;
+ });
+ gPOP3Pump.files = ["../../../data/draft1"];
+ gPOP3Pump.onDone = pop3Resolve;
+ gPOP3Pump.run();
+ await pop3Promise;
+
+ // Get message headers for the inbox folder.
+ var msgCount = 0;
+ for (gHdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) {
+ msgCount++;
+ Assert.equal(gHdr.subject, testSubjects[msgCount - 1]);
+ }
+ Assert.equal(msgCount, 1);
+ gPOP3Pump = null;
+});
+
+add_task(async function goodStreaming() {
+ // Try to stream the headers of the last message.
+ let uri = gHdr.folder.getUriForMsg(gHdr);
+ let messageService = MailServices.messageServiceFromURI(uri);
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ messageService.streamHeaders(uri, streamListener, null, true);
+ // The message contains this header.
+ let streamData = await streamListener.promise;
+ Assert.ok(
+ streamData.includes(
+ "X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0"
+ )
+ );
+});
+
+/**
+ * Crash from bug 752768.
+ */
+add_task(async function badStreaming() {
+ // Try to stream the headers of the last message.
+ let folder = gHdr.folder;
+ let uri = folder.getUriForMsg(gHdr);
+
+ let dbFile = folder.summaryFile;
+ // Force an invalid database.
+ folder.msgDatabase.forceClosed();
+ dbFile.remove(false);
+ folder.msgDatabase = null;
+
+ let messageService = MailServices.messageServiceFromURI(uri);
+ let haveError = false;
+ try {
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ messageService.streamHeaders(uri, streamListener, null, true);
+ await streamListener.promise;
+ } catch (e) {
+ // Should throw NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE (0x80550005).
+ haveError = true;
+ } finally {
+ Assert.ok(
+ haveError,
+ "Ensure that the stream crashes with NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE"
+ );
+ }
+});
diff --git a/comm/mailnews/local/test/unit/test_undoDelete.js b/comm/mailnews/local/test/unit/test_undoDelete.js
new file mode 100644
index 0000000000..a3a52e2fd5
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_undoDelete.js
@@ -0,0 +1,80 @@
+/* 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 file tests undoing of an local folder message deleted to the trash.
+//
+// Original Author: David Bienvenu <dbienvenu@mozilla.com>
+
+// Globals
+let gMsg1;
+let gMessages = [];
+let gMsgWindow;
+let gCurTestNum;
+let gMsgId1;
+let gTestFolder;
+
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var messageInjection = new MessageInjection({ mode: "local" });
+
+add_setup(async function () {
+ gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+ );
+
+ var messageGenerator = new MessageGenerator();
+ gMsg1 = messageGenerator.makeMessage();
+ let msg2 = messageGenerator.makeMessage({ inReplyTo: gMsg1 });
+
+ let messages = [];
+ messages = messages.concat([gMsg1, msg2]);
+ let msgSet = new SyntheticMessageSet(messages);
+ gTestFolder = await messageInjection.makeEmptyFolder();
+ await messageInjection.addSetsToFolders([gTestFolder], [msgSet]);
+});
+
+add_task(async function deleteMessage() {
+ let msgToDelete = mailTestUtils.firstMsgHdr(gTestFolder);
+ gMsgId1 = msgToDelete.messageId;
+ gMessages.push(msgToDelete);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ gTestFolder.deleteMessages(
+ gMessages,
+ gMsgWindow,
+ false,
+ true,
+ copyListener,
+ true
+ );
+ await copyListener.promise;
+});
+
+add_task(async function undoDelete() {
+ gMsgWindow.transactionManager.undoTransaction();
+ // There's no listener for this, so we'll just have to wait a little.
+ await PromiseTestUtils.promiseDelay(1500);
+});
+
+add_task(function verifyFolders() {
+ let msgRestored = gTestFolder.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ let msg = mailTestUtils.loadMessageToString(gTestFolder, msgRestored);
+ Assert.equal(msg, gMsg1.toMboxString());
+});
+
+add_task(function endTest() {
+ // Cleanup, null out everything.
+ gMessages = [];
+ gMsgWindow.closeWindow();
+ gMsgWindow = null;
+ localAccountUtils.inboxFolder = null;
+ localAccountUtils.incomingServer = null;
+});
diff --git a/comm/mailnews/local/test/unit/test_verifyLogon.js b/comm/mailnews/local/test/unit/test_verifyLogon.js
new file mode 100644
index 0000000000..6993be7203
--- /dev/null
+++ b/comm/mailnews/local/test/unit/test_verifyLogon.js
@@ -0,0 +1,93 @@
+/**
+ * This test checks to see if the pop3 verify logon handles password failure correctly.
+ * The steps are:
+ * - Set an invalid password on the server object.
+ * - Check that verifyLogon fails
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var server;
+var daemon;
+var incomingServer;
+
+var kUserName = "testpop3";
+var kInvalidPassword = "pop3test";
+var kValidPassword = "testpop3";
+
+function verifyPop3Logon(validPassword) {
+ incomingServer.password = validPassword ? kValidPassword : kInvalidPassword;
+ urlListener.expectSuccess = validPassword;
+ let uri = incomingServer.verifyLogon(urlListener, gDummyMsgWindow);
+ // clear msgWindow so url won't prompt for passwords.
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl).msgWindow = null;
+
+ server.performTest();
+ return false;
+}
+
+var urlListener = {
+ expectSucess: false,
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, aResult) {
+ Assert.equal(Components.isSuccessCode(aResult), this.expectSuccess);
+ },
+};
+
+function actually_run_test() {
+ daemon.setMessages(["message1.eml"]);
+
+ // check that verifyLogon fails with bad password
+ verifyPop3Logon(false);
+
+ dump("\nverify logon false 1\n");
+ do_timeout(1000, verifyGoodLogon);
+}
+
+function verifyGoodLogon() {
+ server.resetTest();
+
+ // check that verifyLogon succeeds with good password
+ verifyPop3Logon(true);
+
+ dump("\nverify logon true 1\n");
+ do_test_finished();
+}
+
+function run_test() {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+ // Set up the Server
+ daemon = new Pop3Daemon();
+ function createHandler(d) {
+ var handler = new POP3_RFC1939_handler(d);
+ // Login information needs to match the one stored in the signons json file.
+ handler.kUsername = kUserName;
+ handler.kPassword = kValidPassword;
+ handler.dropOnAuthFailure = true;
+ return handler;
+ }
+ server = new nsMailServer(createHandler, daemon);
+ server.start();
+
+ // Set up the basic accounts and folders.
+ // We would use createPop3ServerAndLocalFolders() however we want to have
+ // a different username and NO password for this test (as we expect to load
+ // it from the signons json file in which the login information is stored).
+ localAccountUtils.loadLocalMailAccount();
+
+ incomingServer = MailServices.accounts.createIncomingServer(
+ kUserName,
+ "localhost",
+ "pop3"
+ );
+ incomingServer.port = server.port;
+
+ do_test_pending();
+
+ actually_run_test();
+}
diff --git a/comm/mailnews/local/test/unit/xpcshell.ini b/comm/mailnews/local/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..43fd637fe3
--- /dev/null
+++ b/comm/mailnews/local/test/unit/xpcshell.ini
@@ -0,0 +1,57 @@
+[DEFAULT]
+head = head_maillocal.js
+tail =
+support-files = data/*
+prefs =
+ mail.biff.play_sound=false
+ mail.biff.show_alert=false
+ mail.biff.show_tray_icon=false
+ mail.biff.animate_dock_icon=false
+
+[test_bug457168.js]
+[test_duplicateKey.js]
+[test_fileName.js]
+[test_folderLoaded.js]
+[test_localFolder.js]
+[test_mailboxContentLength.js]
+[test_mailboxProtocol.js]
+[test_mailboxURL.js]
+[test_msgCopy.js]
+[test_msgIDParsing.js]
+[test_noTop.js]
+[test_noUidl.js]
+[test_nsIMsgLocalMailFolder.js]
+[test_nsIMsgParseMailMsgState.js]
+[test_nsIMsgPluggableStore.js]
+[test_over2GBMailboxes.js]
+[test_over4GBMailboxes.js]
+# This one needs a longer timeout for working with a 4GiB file.
+requesttimeoutfactor = 2
+[test_Pop3Channel.js]
+[test_pop3AuthMethods.js]
+[test_pop3Client.js]
+[test_pop3Download.js]
+[test_pop3DownloadTempFileHandling.js]
+[test_pop3Duplicates.js]
+[test_pop3FilterActions.js]
+[test_pop3Filters.js]
+[test_pop3GetNewMail.js]
+[test_pop3GSSAPIFail.js]
+[test_pop3MoveFilter.js]
+[test_pop3MoveFilter2.js]
+[test_pop3MultiCopy.js]
+[test_pop3MultiCopy2.js]
+[test_pop3Password.js]
+[test_pop3Password2.js]
+skip-if = true # realhostname and realuserName don't exist anymore
+[test_pop3Password3.js]
+[test_pop3PasswordFailure_rfc1939.js]
+[test_pop3PasswordFailure_rfc2449.js]
+[test_pop3PasswordFailure_rfc5034.js]
+[test_pop3Proxy.js]
+[test_pop3Pump.js]
+[test_preview.js]
+[test_saveMessage.js]
+[test_streamHeaders.js]
+[test_undoDelete.js]
+[test_verifyLogon.js]