From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mailnews/local/public/moz.build | 26 + .../local/public/nsILocalMailIncomingServer.idl | 23 + comm/mailnews/local/public/nsIMailboxService.idl | 34 + comm/mailnews/local/public/nsIMailboxUrl.idl | 58 + .../local/public/nsIMsgLocalMailFolder.idl | 134 + .../local/public/nsIMsgParseMailMsgState.idl | 46 + .../local/public/nsINewsBlogFeedDownloader.idl | 32 + comm/mailnews/local/public/nsINoIncomingServer.idl | 16 + comm/mailnews/local/public/nsINoneService.idl | 11 + .../local/public/nsIPop3IncomingServer.idl | 33 + comm/mailnews/local/public/nsIPop3Protocol.idl | 23 + comm/mailnews/local/public/nsIPop3Service.idl | 128 + comm/mailnews/local/public/nsIPop3Sink.idl | 42 + comm/mailnews/local/public/nsIPop3URL.idl | 19 + .../mailnews/local/public/nsIRssIncomingServer.idl | 16 + comm/mailnews/local/public/nsIRssService.idl | 10 + comm/mailnews/local/src/Pop3Channel.jsm | 105 + comm/mailnews/local/src/Pop3Client.jsm | 1570 +++++++++ comm/mailnews/local/src/Pop3IncomingServer.jsm | 308 ++ comm/mailnews/local/src/Pop3ProtocolHandler.jsm | 40 + comm/mailnews/local/src/Pop3ProtocolInfo.jsm | 44 + comm/mailnews/local/src/Pop3Service.jsm | 77 + comm/mailnews/local/src/components.conf | 150 + comm/mailnews/local/src/moz.build | 40 + comm/mailnews/local/src/nsLocalMailFolder.cpp | 3468 ++++++++++++++++++++ comm/mailnews/local/src/nsLocalMailFolder.h | 285 ++ comm/mailnews/local/src/nsLocalUndoTxn.cpp | 495 +++ comm/mailnews/local/src/nsLocalUndoTxn.h | 79 + comm/mailnews/local/src/nsLocalUtils.cpp | 208 ++ comm/mailnews/local/src/nsLocalUtils.h | 29 + comm/mailnews/local/src/nsMailboxProtocol.cpp | 657 ++++ comm/mailnews/local/src/nsMailboxProtocol.h | 113 + comm/mailnews/local/src/nsMailboxServer.cpp | 28 + comm/mailnews/local/src/nsMailboxServer.h | 22 + comm/mailnews/local/src/nsMailboxService.cpp | 559 ++++ comm/mailnews/local/src/nsMailboxService.h | 59 + comm/mailnews/local/src/nsMailboxUrl.cpp | 474 +++ comm/mailnews/local/src/nsMailboxUrl.h | 106 + comm/mailnews/local/src/nsMsgBrkMBoxStore.cpp | 1033 ++++++ comm/mailnews/local/src/nsMsgBrkMBoxStore.h | 57 + comm/mailnews/local/src/nsMsgFileHdr.cpp | 389 +++ comm/mailnews/local/src/nsMsgFileHdr.h | 41 + comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp | 380 +++ comm/mailnews/local/src/nsMsgLocalStoreUtils.h | 39 + comm/mailnews/local/src/nsMsgMaildirStore.cpp | 1380 ++++++++ comm/mailnews/local/src/nsMsgMaildirStore.h | 39 + comm/mailnews/local/src/nsNoIncomingServer.cpp | 189 ++ comm/mailnews/local/src/nsNoIncomingServer.h | 41 + comm/mailnews/local/src/nsNoneService.cpp | 147 + comm/mailnews/local/src/nsNoneService.h | 26 + comm/mailnews/local/src/nsParseMailbox.cpp | 2354 +++++++++++++ comm/mailnews/local/src/nsParseMailbox.h | 256 ++ comm/mailnews/local/src/nsPop3Sink.cpp | 740 +++++ comm/mailnews/local/src/nsPop3Sink.h | 70 + comm/mailnews/local/src/nsPop3URL.cpp | 202 ++ comm/mailnews/local/src/nsPop3URL.h | 36 + comm/mailnews/local/src/nsRssIncomingServer.cpp | 248 ++ comm/mailnews/local/src/nsRssIncomingServer.h | 47 + comm/mailnews/local/src/nsRssService.cpp | 113 + comm/mailnews/local/src/nsRssService.h | 23 + comm/mailnews/local/test/moz.build | 6 + comm/mailnews/local/test/unit/data/dot | 10 + .../local/test/unit/data/invalid_mozilla_keys.eml | 5 + .../local/test/unit/data/mailformed_recipients.eml | 66 + .../local/test/unit/data/mailformed_subject.eml | 1934 +++++++++++ comm/mailnews/local/test/unit/data/message1.eml | 7 + comm/mailnews/local/test/unit/data/message2.eml | 9 + comm/mailnews/local/test/unit/data/message3.eml | 7 + comm/mailnews/local/test/unit/data/movemailspool | 169 + comm/mailnews/local/test/unit/head_maillocal.js | 214 ++ comm/mailnews/local/test/unit/test_Pop3Channel.js | 75 + comm/mailnews/local/test/unit/test_bug457168.js | 165 + comm/mailnews/local/test/unit/test_duplicateKey.js | 81 + comm/mailnews/local/test/unit/test_fileName.js | 112 + comm/mailnews/local/test/unit/test_folderLoaded.js | 89 + comm/mailnews/local/test/unit/test_localFolder.js | 164 + .../local/test/unit/test_mailboxContentLength.js | 62 + .../local/test/unit/test_mailboxProtocol.js | 52 + comm/mailnews/local/test/unit/test_mailboxURL.js | 82 + comm/mailnews/local/test/unit/test_msgCopy.js | 27 + comm/mailnews/local/test/unit/test_msgIDParsing.js | 24 + comm/mailnews/local/test/unit/test_noTop.js | 64 + comm/mailnews/local/test/unit/test_noUidl.js | 89 + .../local/test/unit/test_nsIMsgLocalMailFolder.js | 321 ++ .../test/unit/test_nsIMsgParseMailMsgState.js | 42 + .../local/test/unit/test_nsIMsgPluggableStore.js | 52 + .../local/test/unit/test_over2GBMailboxes.js | 129 + .../local/test/unit/test_over4GBMailboxes.js | 640 ++++ .../local/test/unit/test_pop3AuthMethods.js | 201 ++ comm/mailnews/local/test/unit/test_pop3Client.js | 145 + comm/mailnews/local/test/unit/test_pop3Download.js | 81 + .../test/unit/test_pop3DownloadTempFileHandling.js | 62 + .../local/test/unit/test_pop3Duplicates.js | 40 + .../local/test/unit/test_pop3FilterActions.js | 143 + comm/mailnews/local/test/unit/test_pop3Filters.js | 114 + .../local/test/unit/test_pop3GSSAPIFail.js | 222 ++ .../local/test/unit/test_pop3GetNewMail.js | 147 + .../local/test/unit/test_pop3MoveFilter.js | 137 + .../local/test/unit/test_pop3MoveFilter2.js | 108 + .../mailnews/local/test/unit/test_pop3MultiCopy.js | 97 + .../local/test/unit/test_pop3MultiCopy2.js | 179 + comm/mailnews/local/test/unit/test_pop3Password.js | 162 + .../mailnews/local/test/unit/test_pop3Password2.js | 213 ++ .../mailnews/local/test/unit/test_pop3Password3.js | 75 + .../test/unit/test_pop3PasswordFailure_rfc1939.js | 216 ++ .../test/unit/test_pop3PasswordFailure_rfc2449.js | 215 ++ .../test/unit/test_pop3PasswordFailure_rfc5034.js | 217 ++ comm/mailnews/local/test/unit/test_pop3Proxy.js | 60 + comm/mailnews/local/test/unit/test_pop3Pump.js | 32 + .../unit/test_pop3ServerBrokenCRAMDisconnect.js | 127 + .../test/unit/test_pop3ServerBrokenCRAMFail.js | 124 + comm/mailnews/local/test/unit/test_preview.js | 40 + comm/mailnews/local/test/unit/test_saveMessage.js | 69 + .../mailnews/local/test/unit/test_streamHeaders.js | 90 + comm/mailnews/local/test/unit/test_undoDelete.js | 80 + comm/mailnews/local/test/unit/test_verifyLogon.js | 93 + comm/mailnews/local/test/unit/xpcshell.ini | 57 + 117 files changed, 25660 insertions(+) create mode 100644 comm/mailnews/local/public/moz.build create mode 100644 comm/mailnews/local/public/nsILocalMailIncomingServer.idl create mode 100644 comm/mailnews/local/public/nsIMailboxService.idl create mode 100644 comm/mailnews/local/public/nsIMailboxUrl.idl create mode 100644 comm/mailnews/local/public/nsIMsgLocalMailFolder.idl create mode 100644 comm/mailnews/local/public/nsIMsgParseMailMsgState.idl create mode 100644 comm/mailnews/local/public/nsINewsBlogFeedDownloader.idl create mode 100644 comm/mailnews/local/public/nsINoIncomingServer.idl create mode 100644 comm/mailnews/local/public/nsINoneService.idl create mode 100644 comm/mailnews/local/public/nsIPop3IncomingServer.idl create mode 100644 comm/mailnews/local/public/nsIPop3Protocol.idl create mode 100644 comm/mailnews/local/public/nsIPop3Service.idl create mode 100644 comm/mailnews/local/public/nsIPop3Sink.idl create mode 100644 comm/mailnews/local/public/nsIPop3URL.idl create mode 100644 comm/mailnews/local/public/nsIRssIncomingServer.idl create mode 100644 comm/mailnews/local/public/nsIRssService.idl create mode 100644 comm/mailnews/local/src/Pop3Channel.jsm create mode 100644 comm/mailnews/local/src/Pop3Client.jsm create mode 100644 comm/mailnews/local/src/Pop3IncomingServer.jsm create mode 100644 comm/mailnews/local/src/Pop3ProtocolHandler.jsm create mode 100644 comm/mailnews/local/src/Pop3ProtocolInfo.jsm create mode 100644 comm/mailnews/local/src/Pop3Service.jsm create mode 100644 comm/mailnews/local/src/components.conf create mode 100644 comm/mailnews/local/src/moz.build create mode 100644 comm/mailnews/local/src/nsLocalMailFolder.cpp create mode 100644 comm/mailnews/local/src/nsLocalMailFolder.h create mode 100644 comm/mailnews/local/src/nsLocalUndoTxn.cpp create mode 100644 comm/mailnews/local/src/nsLocalUndoTxn.h create mode 100644 comm/mailnews/local/src/nsLocalUtils.cpp create mode 100644 comm/mailnews/local/src/nsLocalUtils.h create mode 100644 comm/mailnews/local/src/nsMailboxProtocol.cpp create mode 100644 comm/mailnews/local/src/nsMailboxProtocol.h create mode 100644 comm/mailnews/local/src/nsMailboxServer.cpp create mode 100644 comm/mailnews/local/src/nsMailboxServer.h create mode 100644 comm/mailnews/local/src/nsMailboxService.cpp create mode 100644 comm/mailnews/local/src/nsMailboxService.h create mode 100644 comm/mailnews/local/src/nsMailboxUrl.cpp create mode 100644 comm/mailnews/local/src/nsMailboxUrl.h create mode 100644 comm/mailnews/local/src/nsMsgBrkMBoxStore.cpp create mode 100644 comm/mailnews/local/src/nsMsgBrkMBoxStore.h create mode 100644 comm/mailnews/local/src/nsMsgFileHdr.cpp create mode 100644 comm/mailnews/local/src/nsMsgFileHdr.h create mode 100644 comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp create mode 100644 comm/mailnews/local/src/nsMsgLocalStoreUtils.h create mode 100644 comm/mailnews/local/src/nsMsgMaildirStore.cpp create mode 100644 comm/mailnews/local/src/nsMsgMaildirStore.h create mode 100644 comm/mailnews/local/src/nsNoIncomingServer.cpp create mode 100644 comm/mailnews/local/src/nsNoIncomingServer.h create mode 100644 comm/mailnews/local/src/nsNoneService.cpp create mode 100644 comm/mailnews/local/src/nsNoneService.h create mode 100644 comm/mailnews/local/src/nsParseMailbox.cpp create mode 100644 comm/mailnews/local/src/nsParseMailbox.h create mode 100644 comm/mailnews/local/src/nsPop3Sink.cpp create mode 100644 comm/mailnews/local/src/nsPop3Sink.h create mode 100644 comm/mailnews/local/src/nsPop3URL.cpp create mode 100644 comm/mailnews/local/src/nsPop3URL.h create mode 100644 comm/mailnews/local/src/nsRssIncomingServer.cpp create mode 100644 comm/mailnews/local/src/nsRssIncomingServer.h create mode 100644 comm/mailnews/local/src/nsRssService.cpp create mode 100644 comm/mailnews/local/src/nsRssService.h create mode 100644 comm/mailnews/local/test/moz.build create mode 100644 comm/mailnews/local/test/unit/data/dot create mode 100644 comm/mailnews/local/test/unit/data/invalid_mozilla_keys.eml create mode 100644 comm/mailnews/local/test/unit/data/mailformed_recipients.eml create mode 100644 comm/mailnews/local/test/unit/data/mailformed_subject.eml create mode 100644 comm/mailnews/local/test/unit/data/message1.eml create mode 100644 comm/mailnews/local/test/unit/data/message2.eml create mode 100644 comm/mailnews/local/test/unit/data/message3.eml create mode 100644 comm/mailnews/local/test/unit/data/movemailspool create mode 100644 comm/mailnews/local/test/unit/head_maillocal.js create mode 100644 comm/mailnews/local/test/unit/test_Pop3Channel.js create mode 100644 comm/mailnews/local/test/unit/test_bug457168.js create mode 100644 comm/mailnews/local/test/unit/test_duplicateKey.js create mode 100644 comm/mailnews/local/test/unit/test_fileName.js create mode 100644 comm/mailnews/local/test/unit/test_folderLoaded.js create mode 100644 comm/mailnews/local/test/unit/test_localFolder.js create mode 100644 comm/mailnews/local/test/unit/test_mailboxContentLength.js create mode 100644 comm/mailnews/local/test/unit/test_mailboxProtocol.js create mode 100644 comm/mailnews/local/test/unit/test_mailboxURL.js create mode 100644 comm/mailnews/local/test/unit/test_msgCopy.js create mode 100644 comm/mailnews/local/test/unit/test_msgIDParsing.js create mode 100644 comm/mailnews/local/test/unit/test_noTop.js create mode 100644 comm/mailnews/local/test/unit/test_noUidl.js create mode 100644 comm/mailnews/local/test/unit/test_nsIMsgLocalMailFolder.js create mode 100644 comm/mailnews/local/test/unit/test_nsIMsgParseMailMsgState.js create mode 100644 comm/mailnews/local/test/unit/test_nsIMsgPluggableStore.js create mode 100644 comm/mailnews/local/test/unit/test_over2GBMailboxes.js create mode 100644 comm/mailnews/local/test/unit/test_over4GBMailboxes.js create mode 100644 comm/mailnews/local/test/unit/test_pop3AuthMethods.js create mode 100644 comm/mailnews/local/test/unit/test_pop3Client.js create mode 100644 comm/mailnews/local/test/unit/test_pop3Download.js create mode 100644 comm/mailnews/local/test/unit/test_pop3DownloadTempFileHandling.js create mode 100644 comm/mailnews/local/test/unit/test_pop3Duplicates.js create mode 100644 comm/mailnews/local/test/unit/test_pop3FilterActions.js create mode 100644 comm/mailnews/local/test/unit/test_pop3Filters.js create mode 100644 comm/mailnews/local/test/unit/test_pop3GSSAPIFail.js create mode 100644 comm/mailnews/local/test/unit/test_pop3GetNewMail.js create mode 100644 comm/mailnews/local/test/unit/test_pop3MoveFilter.js create mode 100644 comm/mailnews/local/test/unit/test_pop3MoveFilter2.js create mode 100644 comm/mailnews/local/test/unit/test_pop3MultiCopy.js create mode 100644 comm/mailnews/local/test/unit/test_pop3MultiCopy2.js create mode 100644 comm/mailnews/local/test/unit/test_pop3Password.js create mode 100644 comm/mailnews/local/test/unit/test_pop3Password2.js create mode 100644 comm/mailnews/local/test/unit/test_pop3Password3.js create mode 100644 comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc1939.js create mode 100644 comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc2449.js create mode 100644 comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc5034.js create mode 100644 comm/mailnews/local/test/unit/test_pop3Proxy.js create mode 100644 comm/mailnews/local/test/unit/test_pop3Pump.js create mode 100644 comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMDisconnect.js create mode 100644 comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMFail.js create mode 100644 comm/mailnews/local/test/unit/test_preview.js create mode 100644 comm/mailnews/local/test/unit/test_saveMessage.js create mode 100644 comm/mailnews/local/test/unit/test_streamHeaders.js create mode 100644 comm/mailnews/local/test/unit/test_undoDelete.js create mode 100644 comm/mailnews/local/test/unit/test_verifyLogon.js create mode 100644 comm/mailnews/local/test/unit/xpcshell.ini (limited to 'comm/mailnews/local') 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 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 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 addMessageBatch(in Array 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/ to /. + * 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 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); + +%{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} 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} - 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 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 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 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>& folders) { + if (!mInitialized) { + nsCOMPtr server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + nsCOMPtr 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 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 server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr 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 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 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 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 dbFolderInfo; + nsCOMPtr 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 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 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 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 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 newFolder; + nsresult rv = + CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 rootFolder; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + bool storeSupportsCompaction; + msgStore->GetSupportsCompaction(&storeSupportsCompaction); + nsTArray> folderArray; + if (storeSupportsCompaction) { + nsTArray> 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 folderCompactor = + do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv); + return folderCompactor->CompactFolders(folderArray, aListener, aMsgWindow); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::Compact(nsIUrlListener* aListener, + nsIMsgWindow* aMsgWindow) { + nsCOMPtr 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 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 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> subFolders; + rv = trashFolder->GetSubFolders(subFolders); + NS_ENSURE_SUCCESS(rv, rv); + if (subFolders.IsEmpty()) { + return NS_OK; + } + } + nsCOMPtr parentFolder; + rv = trashFolder->GetParent(getter_AddRefs(parentFolder)); + if (NS_SUCCEEDED(rv) && parentFolder) { + nsCOMPtr transferInfo; + trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo)); + trashFolder->SetParent(nullptr); + parentFolder->PropagateDelete(trashFolder, true); + parentFolder->CreateSubfolder(u"Trash"_ns, nullptr); + nsCOMPtr newTrashFolder; + rv = GetTrashFolder(getter_AddRefs(newTrashFolder)); + if (NS_SUCCEEDED(rv) && newTrashFolder) { + nsCOMPtr 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 dbFolderInfo; + nsCOMPtr 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 parentFolder; + nsCOMPtr 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 trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr 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 docShell; + aMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); + if (docShell) { + bool confirmDeletion = true; + nsresult rv; + nsCOMPtr pPrefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + pPrefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash", + &confirmDeletion); + if (confirmDeletion) { + nsCOMPtr bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr 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 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 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 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 msgStore; + nsCOMPtr 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> 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 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> subFolders; + rv = oldFolder->GetSubFolders(subFolders); + NS_ENSURE_SUCCESS(rv, rv); + + for (nsIMsgFolder* msgFolder : subFolders) { + nsString folderName; + rv = msgFolder->GetName(folderName); + nsCOMPtr 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 msgDBService = + do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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 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 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> 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> 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 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 trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr 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 msgDB; + rv = GetDatabaseWOReparse(getter_AddRefs(msgDB)); + if (NS_SUCCEEDED(rv)) { + if (deleteStorage && isMove && GetDeleteFromServerOnMove()) + MarkMsgsOnPop3Server(msgHeaders, POP3_DELETE); + + nsCOMPtr msgSupport; + rv = EnableNotifications(allMessageCountNotifications, false); + if (NS_SUCCEEDED(rv)) { + // First, delete the actual messages in the store. + nsCOMPtr 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 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 msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeFlags({aMessage}, msgFlag, true); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::MarkMessagesRead( + const nsTArray>& aMessages, bool aMarkRead) { + nsresult rv = nsMsgDBFolder::MarkMessagesRead(aMessages, aMarkRead); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Read, aMarkRead); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::MarkMessagesFlagged( + const nsTArray>& aMessages, bool aMarkFlagged) { + nsresult rv = nsMsgDBFolder::MarkMessagesFlagged(aMessages, aMarkFlagged); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 thoseMarked; + EnableNotifications(allMessageCountNotifications, false); + rv = mDatabase->MarkAllRead(thoseMarked); + EnableNotifications(allMessageCountNotifications, true); + NS_ENSURE_SUCCESS(rv, rv); + + if (thoseMarked.IsEmpty()) { + return NS_OK; + } + + nsTArray> messages; + rv = MsgGetHeadersFromKeys(mDatabase, thoseMarked, messages); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 thoseMarked; + rv = mDatabase->MarkThreadRead(thread, nullptr, thoseMarked); + NS_ENSURE_SUCCESS(rv, rv); + if (thoseMarked.IsEmpty()) { + return NS_OK; + } + + nsTArray> messages; + rv = MsgGetHeadersFromKeys(mDatabase, thoseMarked, messages); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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> const& messages, + bool isMove, nsIMsgCopyServiceListener* listener, nsIMsgWindow* msgWindow, + bool isFolder, bool allowUndo) { + nsCOMPtr 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 msgDB; + GetDatabaseWOReparse(getter_AddRefs(msgDB)); + bool isLocked; + + GetLocked(&isLocked); + if (isLocked) return NS_MSG_FOLDER_BUSY; + + AcquireSemaphore(static_cast(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(this), &haveSemaphore); + if (NS_SUCCEEDED(rv) && haveSemaphore) + ReleaseSemaphore(static_cast(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 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> const& srcHdrs, + bool isMove, nsIMsgWindow* msgWindow, + nsIMsgCopyServiceListener* listener, + bool isFolder, bool allowUndo) { + nsCOMPtr 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 msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr undoTxn; + nsTArray> 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 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 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 keyArray(srcHdrs.Length()); + nsTArray> 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 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 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 newMsgFolder; + nsresult rv = CreateSubfolderInternal(folderName, msgWindow, + getter_AddRefs(newMsgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr messages; + rv = srcFolder->GetMessages(getter_AddRefs(messages)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray> msgArray; + bool hasMoreElements = false; + + if (messages) rv = messages->HasMoreElements(&hasMoreElements); + + while (NS_SUCCEEDED(rv) && hasMoreElements) { + nsCOMPtr 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 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 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> 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 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 fileSupport(aFile); + + aFile->GetFileSize(&fileSize); + if (!CheckIfSpaceForCopy(msgWindow, nullptr, fileSupport, false, fileSize)) + return NS_OK; + + nsTArray> 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 msgDb; + mCopyState->m_parseMsgState = parseMsgState; + GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (msgDb) parseMsgState->SetMailDB(msgDb); + + nsCOMPtr 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 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 server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr 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 rssServer = do_QueryInterface(server, &rv); + mozilla::Unused << rssServer; + if (NS_SUCCEEDED(rv)) { + nsCOMPtr resultURI; + return localMailServer->GetNewMail(aWindow, aListener, this, + getter_AddRefs(resultURI)); + } + + nsCOMPtr inbox; + nsCOMPtr rootFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder) { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, + getter_AddRefs(inbox)); + } + nsCOMPtr localInbox = do_QueryInterface(inbox, &rv); + if (NS_SUCCEEDED(rv)) { + bool valid = false; + nsCOMPtr 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 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 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 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(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 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 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 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 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 newHdr; + // CopyFileMessage() and CopyMessages() from servers other than mailbox + if (mCopyState->m_parseMsgState) { + nsCOMPtr 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 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 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 txnMgr; + mCopyState->m_msgWindow->GetTransactionManager( + getter_AddRefs(txnMgr)); + if (txnMgr) { + RefPtr 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 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 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 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 srcFolder = + do_QueryInterface(mCopyState->m_srcSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 txnMgr; + mCopyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) { + RefPtr 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 localUndoTxn = mCopyState->m_undoMsgTxn; + nsCOMPtr 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 msgDb; + nsCOMPtr newHdr; + + mCopyState->m_parseMsgState->FinishHeader(); + + rv = mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr)); + if (NS_SUCCEEDED(rv) && newHdr) { + nsCOMPtr srcFolder = + do_QueryInterface(mCopyState->m_srcSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr srcDB; + srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + if (srcDB) { + nsCOMPtr 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& keyArray, + nsIMsgWindow* aMsgWindow, + bool isMove) { + if (!mCopyState) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + + nsCOMPtr copyStreamListener = do_CreateInstance( + "@mozilla.org/messenger/copymessagestreamlistener;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 srcLocalFolder = + do_QueryInterface(srcFolder); + if (srcLocalFolder) { + StartMessage(); + } + nsCOMPtr 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 msgHdr(do_QueryInterface(message, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + mCopyState->m_message = msgHdr; + + nsCOMPtr srcFolder( + do_QueryInterface(mCopyState->m_srcSupport, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + nsCString uri; + srcFolder->GetUriForMsg(msgHdr, uri); + + nsCOMPtr 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 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>& aMessages, int32_t aMark) { + nsLocalFolderScanState folderScanState; + nsCOMPtr curFolderPop3MailServer; + nsCOMArray + pop3Servers; // servers with msgs deleted... + + nsCOMPtr incomingServer; + nsresult rv = GetServer(getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr 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 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 account; + rv = accountManager->GetAccount(folderScanState.m_accountKey, + getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + account->GetIncomingServer(getter_AddRefs(incomingServer)); + nsCOMPtr 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 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> 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 server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr localMailServer = + do_QueryInterface(server, &rv); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + nsCOMPtr 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 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 fileStream; + nsresult rv = GetMsgInputStream(hdr, getter_AddRefs(fileStream)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr 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 url; + rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .SetSpec(mURI) + .Finalize(url); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr 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 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 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 mailSession = + do_GetService("@mozilla.org/messenger/services/session;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 saveReparseListener = mReparseListener; + mReparseListener = nullptr; + saveReparseListener->OnStopRunningUrl(aUrl, aExitCode); + } + } + if (mFlags & nsMsgFolderFlags::Inbox) { + // if we are the inbox and running pop url + nsCOMPtr popurl = do_QueryInterface(aUrl, &rv); + mozilla::Unused << popurl; + if (NS_SUCCEEDED(rv)) { + nsCOMPtr 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 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 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 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 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 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 server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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> messages; + if (!mSpamKeysToMove.IsEmpty()) { + nsCOMPtr 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 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 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(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> lineBuffer(new nsLineBuffer); + + 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 aMessages = {nsDependentCString(aMessage)}; + nsTArray> hdrs; + nsresult rv = AddMessageBatch(aMessages, hdrs); + NS_ENSURE_SUCCESS(rv, rv); + NS_ADDREF(*aHdr = hdrs[0]); + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::AddMessageBatch( + const nsTArray& aMessages, + nsTArray>& aHdrArray) { + aHdrArray.ClearAndRetainStorage(); + aHdrArray.SetCapacity(aMessages.Length()); + + nsCOMPtr server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr msgStore; + nsCOMPtr outFileStream; + nsCOMPtr newHdr; + + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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(this)); + + if (NS_SUCCEEDED(rv)) { + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < aMessages.Length(); i++) { + RefPtr 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 msgWindow; + nsCOMPtr 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(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 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 const& aKeysToFetch, nsIUrlListener* aUrlListener, + bool* aAsyncResults) { + NS_ENSURE_ARG_POINTER(aAsyncResults); + + *aAsyncResults = false; + nsCOMPtr inputStream; + + for (uint32_t i = 0; i < aKeysToFetch.Length(); i++) { + nsCOMPtr 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>& aMessages, + const nsACString& aKeywords) { + nsresult rv = nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeKeywords(aMessages, aKeywords, true /* add */); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::RemoveKeywordsFromMessages( + const nsTArray>& aMessages, + const nsACString& aKeywords) { + nsresult rv = nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 m_fileStream; + nsCOMPtr m_msgStore; + nsCOMPtr m_srcSupport; + /// Source nsIMsgDBHdr instances. + nsTArray> m_messages; + /// Destination nsIMsgDBHdr instances. + nsTArray> m_destMessages; + RefPtr m_undoMsgTxn; + nsCOMPtr m_message; // current copy message + nsMsgMessageFlagType m_flags; // current copy message flags + RefPtr m_parseMsgState; + nsCOMPtr m_listener; + nsCOMPtr m_msgWindow; + nsCOMPtr m_destDB; + + // for displaying status; + nsCOMPtr m_statusFeedback; + nsCOMPtr m_stringBundle; + int64_t m_lastProgressTime; + + nsMsgKey m_curDstKey; + uint32_t m_curCopyIndex; + nsCOMPtr 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 m_newHdr; +}; + +struct nsLocalFolderScanState { + nsLocalFolderScanState(); + ~nsLocalFolderScanState(); + + nsCOMPtr m_inputStream; + nsCOMPtr 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>& 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> const& messages, + nsIMsgWindow* msgWindow, bool deleteStorage, + bool isMove, nsIMsgCopyServiceListener* listener, + bool allowUndo) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD CopyMessages( + nsIMsgFolder* srcFolder, nsTArray> 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>& aMessages, + bool aMarkRead) override; + NS_IMETHOD MarkMessagesFlagged(const nsTArray>& 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> 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 const& aKeysToFetch, + nsIUrlListener* aUrlListener, + bool* aAsyncResults) override; + NS_IMETHOD AddKeywordsToMessages( + const nsTArray>& aMessages, + const nsACString& aKeywords) override; + NS_IMETHOD RemoveKeywordsFromMessages( + const nsTArray>& 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& keyArray, + nsIMsgWindow* aMsgWindow, bool isMove); + nsresult InitCopyState(nsISupports* aSupport, + nsTArray> 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> 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 mReparseListener; + nsTArray mSpamKeysToMove; + nsresult setSubfolderFlag(const nsAString& aFolderName, uint32_t flags); + + // state variables for DownloadMessagesForOffline + + nsCOMArray mDownloadMessages; + nsCOMPtr 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& keyArray, + bool deleteFlag) { + nsresult rv = NS_ERROR_FAILURE; + if (m_srcIsImap4) { + nsCOMPtr imapService = + do_GetService("@mozilla.org/messenger/imapservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 dstDB; + + nsCOMPtr dstFolder = do_QueryReferent(m_dstFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 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 srcDB; + nsCOMPtr dstDB; + nsCOMPtr srcFolder = do_QueryReferent(m_srcFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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> srcMessages(count); + nsTArray> destMessages(count); + + for (i = 0; i < count; i++) { + nsCOMPtr 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 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 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 localFolder = + do_QueryInterface(srcFolder); + if (localFolder) { + localFolder->MarkMsgsOnPop3Server(srcMessages, + POP3_NONE /*deleteMsgs*/); + } + } else // undoing a move means moving the messages back. + { + nsTArray> 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 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 srcDB; + nsCOMPtr dstDB; + + nsCOMPtr srcFolder = do_QueryReferent(m_srcFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 oldHdr; + nsCOMPtr newHdr; + + nsTArray> 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 localFolder = + do_QueryInterface(srcFolder); + if (localFolder) { + localFolder->MarkMsgsOnPop3Server(srcMessages, + POP3_DELETE /*deleteMsgs*/); + } + + rv = srcDB->DeleteMessages(m_srcKeyArray, nullptr); + srcDB->SetSummaryValid(true); + } else { + nsCOMPtr 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 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& 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 m_srcKeyArray; // used when src is local or imap + nsWeakPtr m_dstFolder; + nsTArray m_dstKeyArray; + bool m_isMove; + bool m_srcIsImap4; + bool m_canUndelete; + nsTArray m_dstSizeArray; + bool m_undoing; // if false, re-doing + uint32_t m_numHdrsCopied; + nsTArray 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 url; + rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .SetSpec(nsDependentCString(uriStr)) + .Finalize(url); + if (NS_FAILED(rv)) return rv; + + // retrieve the AccountManager + nsCOMPtr 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 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 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 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 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 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 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 serv = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr clonedStream; + nsCOMPtr 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 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 window; + rv = m_runningUrl->GetMailboxAction(&m_mailboxAction); + // clear stopped flag on msg window, because we care. + nsCOMPtr 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 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 msgUrl = + do_QueryInterface(m_runningUrl, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr folder; + nsCOMPtr 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 stream; + int64_t offset = 0; + rv = folder->GetMsgInputStream(msgHdr, getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr seekableStream( + do_QueryInterface(stream, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + seekableStream->Tell(&offset); + // create input stream transport + nsCOMPtr sts = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + m_readCount = msgSize; + + RefPtr 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 mailnewsUrl = do_QueryInterface(m_runningUrl); + if (mailnewsUrl) { + nsCOMPtr 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 listener = + do_QueryInterface(m_channelListener, &rv); + if (listener) { + listener->EndCopy(mailnewsUrl, aStatus); + listener->StartMessage(); // start next message. + } + } + m_runningUrl->SetCurMoveCopyMsgIndex(curMoveCopyMsgIndex); + nsCOMPtr nextMsg; + rv = m_runningUrl->GetMoveCopyMsgHdrForIndex(curMoveCopyMsgIndex, + getter_AddRefs(nextMsg)); + if (NS_SUCCEEDED(rv) && nextMsg) { + uint32_t msgSize = 0; + nsCOMPtr 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 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 stream; + rv = msgFolder->GetMsgInputStream(nextMsg, + getter_AddRefs(stream)); + if (NS_SUCCEEDED(rv)) { + // create input stream transport + nsCOMPtr sts = do_GetService( + NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) { + m_readCount = msgSize; + RefPtr slicedStream = + new SlicedInputStream(stream.forget(), msgOffset, + uint64_t(msgSize)); + rv = sts->CreateInputTransport( + slicedStream, true, getter_AddRefs(m_transport)); + } + } + } + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr stream; + rv = m_transport->OpenInputStream(0, 0, 0, + getter_AddRefs(stream)); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr 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(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 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 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 streamConverter = + do_GetService("@mozilla.org/streamConverters;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr conversionListener; + nsCOMPtr 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 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 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 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 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 + m_runningUrl; // the nsIMailboxURL that is currently running + nsMailboxAction m_mailboxAction; // current mailbox action associated with + // this connection... + // Event sink handles + nsCOMPtr m_mailboxParser; + + // Local state for the current operation + RefPtr + 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 m_tempMessageFile; + nsCOMPtr m_msgFileOutputStream; + + // this is used to hold the source mailbox file open when move/copying + // multiple messages. + nsCOMPtr 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 mailboxurl = + do_CreateInstance("@mozilla.org/messenger/mailboxurl;1", &rv); + if (NS_SUCCEEDED(rv) && mailboxurl) { + nsCOMPtr 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 aURL; // unused... + if (!moveMessage) mailboxAction = nsIMailboxUrl::ActionCopyMessage; + return FetchMessage(aSrcMailboxURI, aMailboxCopyHandler, aMsgWindow, + aUrlListener, nullptr, mailboxAction, false, + getter_AddRefs(aURL)); +} + +nsresult nsMailboxService::CopyMessages( + const nsTArray& 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 mailboxurl; + + nsMailboxAction actionToUse = nsIMailboxUrl::ActionMoveMessage; + if (!moveMessage) actionToUse = nsIMailboxUrl::ActionCopyMessage; + + nsCOMPtr msgHdr; + nsCOMPtr 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 url = do_QueryInterface(mailboxurl); + nsCOMPtr msgUrl(do_QueryInterface(url)); + nsCOMPtr 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 mailboxurl; + nsMailboxAction actionToUse = mailboxAction; + nsCOMPtr url; + nsCOMPtr msgUrl; + nsAutoCString uriString(aMessageURI); + + if (StringBeginsWith(aMessageURI, "file:"_ns)) { + int64_t fileSize; + nsCOMPtr fileUri; + rv = NS_NewURI(getter_AddRefs(fileUri), aMessageURI); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr fileUrl = do_QueryInterface(fileUri, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 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 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 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 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 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 folder; + nsresult rv = + DecomposeMailboxURI(aMessageURI, getter_AddRefs(folder), &msgKey); + if (msgKey == nsMsgKey_None) return NS_MSG_MESSAGE_NOT_FOUND; + + nsCOMPtr db; + rv = folder->GetMsgDatabase(getter_AddRefs(db)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgHdr; + rv = db->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 mailboxurl; + + rv = PrepareMessageUrl(aMessageURI, aUrlListener, + nsIMailboxUrl::ActionSaveMessageToDisk, + getter_AddRefs(mailboxurl), aMsgWindow); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr msgUrl = do_QueryInterface(mailboxurl); + if (msgUrl) { + msgUrl->SetMessageFile(aFile); + msgUrl->SetAddDummyEnvelope(aAddDummyEnvelope); + msgUrl->SetCanonicalLineEnding(canonicalLineEnding); + } + + nsCOMPtr 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 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 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 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 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 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 handler = + do_GetService("@mozilla.org/network/protocol;1?name=pop", &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr pop3Uri; + + rv = nsPop3URL::NewURI(spec, aURI, getter_AddRefs(pop3Uri)); + NS_ENSURE_SUCCESS(rv, rv); + return handler->NewChannel(pop3Uri, aLoadInfo, _retval); + } + } + + RefPtr 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 msgHdr = new nsMsgFileHdr(uri); + msgHdr.forget(_retval); + return NS_OK; + } + + nsresult rv = NS_OK; + + nsCOMPtr 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 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 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 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 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 mailDBFactory; + nsCOMPtr mailDB; + nsCOMPtr 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 ioService = mozilla::components::IO::Service(); + NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED); + nsCOMPtr uri; + rv = ioService->NewURI(fileUri, nullptr, nullptr, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr fileURL = do_QueryInterface(uri); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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& 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& 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 m_mailboxParser; + nsCOMPtr m_mailboxCopyHandler; + + nsMailboxAction m_mailboxAction; // the action this url represents...parse + // mailbox, display messages, etc. + nsCOMPtr m_filePath; + char* m_messageID; + uint32_t m_messageSize; + nsMsgKey m_messageKey; + nsCString m_file; + + // used by save message to disk + nsCOMPtr m_messageFile; + bool m_addDummyEnvelope; + bool m_canonicalLineEnding; + nsresult ParseSearchPart(); + + // for multiple msg move/copy + nsTArray 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 // for std::abs(int/long) +#include // 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 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 path; + nsCOMPtr 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 msgDBService = + do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); + if (msgDBService) { + nsCOMPtr 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 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 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 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 localFolder(do_QueryInterface(aFolder)); + if (!localFolder) { + *aResult = true; + return NS_OK; + } + + nsCOMPtr pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 localFolder(do_QueryInterface(aFolder)); + if (!localFolder) return NS_OK; + + nsCOMPtr pathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 oldPathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(oldPathFile)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr parentFolder; + rv = aFolder->GetParent(getter_AddRefs(parentFolder)); + if (!parentFolder) return NS_ERROR_NULL_POINTER; + + nsCOMPtr oldSummaryFile; + rv = aFolder->GetSummaryFile(getter_AddRefs(oldSummaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 localSrcFolder(do_QueryInterface(aSrcFolder)); + nsCOMPtr srcDB; + if (localSrcFolder) + localSrcFolder->GetDatabaseWOReparse(getter_AddRefs(srcDB)); + bool summaryValid = !!srcDB; + srcDB = nullptr; + aSrcFolder->ForceDBClosed(); + + nsCOMPtr oldPath; + nsresult rv = aSrcFolder->GetFilePath(getter_AddRefs(oldPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr summaryFile; + GetSummaryFileLocation(oldPath, getter_AddRefs(summaryFile)); + + nsCOMPtr 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 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 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 destDB; + if (summaryValid) { + nsAutoString folderLeafName; + origPath->GetLeafName(folderLeafName); + newPath->Append(folderLeafName); + nsCOMPtr 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> subFolders; + rv = aSrcFolder->GetSubFolders(subFolders); + NS_ENSURE_SUCCESS(rv, rv); + + // Copy subfolders to the new location + nsresult copyStatus = NS_OK; + nsCOMPtr 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 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 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 parentPath; + rv = msgParent->GetFilePath(getter_AddRefs(parentPath)); + NS_ENSURE_SUCCESS(rv, rv); + + AddDirectorySeparator(parentPath); + nsCOMPtr 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 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 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 mboxStream; + nsresult rv = InternalGetNewMsgOutputStream(aFolder, aNewMsgHdr, + getter_AddRefs(mboxStream)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr 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 mboxFile; + rv = aFolder->GetFilePath(getter_AddRefs(mboxFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 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 mboxFile; + nsCOMPtr 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 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 mboxFile; + nsresult rv = aMsgFolder->GetFilePath(getter_AddRefs(mboxFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(msgStream), mboxFile); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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>& aHdrArray) { + return ChangeFlags(aHdrArray, nsMsgMessageFlags::Expunged, true); +} + +NS_IMETHODIMP +nsMsgBrkMBoxStore::CopyMessages(bool isMove, + const nsTArray>& aHdrArray, + nsIMsgFolder* aDstFolder, + nsTArray>& 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 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 mailboxService = + do_GetService("@mozilla.org/messenger/mailboxservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr 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& outputStream, + nsCOMPtr& seekableStream, int64_t& restorePos) { + nsCOMPtr 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 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 folder; + aHdr->GetFolder(getter_AddRefs(folder)); + if (folder) { + nsCOMPtr db; + folder->GetMsgDatabase(getter_AddRefs(db)); + if (db) SetSummaryFileValid(folder, db, true); + } +} + +NS_IMETHODIMP nsMsgBrkMBoxStore::ChangeFlags( + const nsTArray>& aHdrArray, uint32_t aFlags, + bool aSet) { + if (aHdrArray.IsEmpty()) return NS_ERROR_INVALID_ARG; + + nsCOMPtr outputStream; + nsCOMPtr seekableStream; + int64_t restoreStreamPos; + nsresult rv = GetOutputStream(aHdrArray[0], outputStream, seekableStream, + restoreStreamPos); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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>& aHdrArray, const nsACString& aKeywords, + bool aAdd) { + if (aHdrArray.IsEmpty()) return NS_ERROR_INVALID_ARG; + + nsTArray keywordsToAdd; + nsTArray keywordsToRemove; + if (aAdd) { + ParseString(aKeywords, ' ', keywordsToAdd); + } else { + ParseString(aKeywords, ' ', keywordsToRemove); + } + + // Get the (possibly-cached) seekable & writable stream for this mbox. + nsCOMPtr output; + nsCOMPtr 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& path, bool deep) { + nsresult rv; + nsCOMPtr 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 currentDirEntries; + nsCOMPtr directoryEnumerator; + rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + nsCOMPtr 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 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 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 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 in(inStream); + RefPtr 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& path, + bool deep); + nsresult CreateDirectoryForFolder(nsIFile* path); + nsresult GetOutputStream(nsIMsgDBHdr* aHdr, + nsCOMPtr& outputStream, + nsCOMPtr& 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 m_outputStreams; + +#ifdef _DEBUG + nsCOMPtr 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 uri; + NS_NewURI(getter_AddRefs(uri), mUri); + nsCOMPtr fileUrl = do_QueryInterface(uri); + rv = fileUrl->GetFile(getter_AddRefs(mFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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& _retval) { + return NS_OK; +} + +NS_IMETHODIMP nsMsgFileHdr::GetSubjectCollationKey(nsTArray& _retval) { + return NS_OK; +} + +NS_IMETHODIMP nsMsgFileHdr::GetRecipientsCollationKey( + nsTArray& _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& 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 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 readBuf(nsIInputStream* readable, + mozilla::Buffer& 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(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 readable(do_QueryInterface(seekable, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 buf(512); + mozilla::Span data = readBuf(readable, buf); + + // If there's a "From " line, consume it. + mozilla::Span 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 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 const& keywordsToAdd, + nsTArray 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 readable(do_QueryInterface(seekable, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 buf(512); + mozilla::Span data = readBuf(readable, buf); + + // If there's a "From " line, consume it. + mozilla::Span 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 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 const& keywordsToAdd, + nsTArray 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 currentDirEntries; + + nsCOMPtr directoryEnumerator; + nsresult rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + nsCOMPtr 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 currentFile(currentDirEntries[i]); + + nsAutoString leafName; + currentFile->GetLeafName(leafName); + + nsCOMPtr 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 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 .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 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 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 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 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 msgDBService = + do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); + if (msgDBService) { + nsCOMPtr 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 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 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 dbFolderInfo; + aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + nsresult rv = + dbFolderInfo->GetBooleanProperty("maildirValid", false, aResult); + if (!*aResult) { + nsCOMPtr 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 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 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 oldPathFile; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(oldPathFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // old sbd directory + nsCOMPtr 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 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 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 oldPath; + nsresult rv = aSrcFolder->GetFilePath(getter_AddRefs(oldPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr summaryFile; + GetSummaryFileLocation(oldPath, getter_AddRefs(summaryFile)); + + nsCOMPtr 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 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 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> subFolders; + rv = aSrcFolder->GetSubFolders(subFolders); + NS_ENSURE_SUCCESS(rv, rv); + + // Copy subfolders to the new location + nsresult copyStatus = NS_OK; + nsCOMPtr 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 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 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 parentPath; + rv = msgParent->GetFilePath(getter_AddRefs(parentPath)); + NS_ENSURE_SUCCESS(rv, rv); + + AddDirectorySeparator(parentPath); + nsCOMPtr 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 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 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 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(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 path; + nsCOMPtr 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 folderPath; + nsCOMPtr 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 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 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 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(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 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(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 folderPath; + nsCOMPtr 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 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 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 destMailDB; + rv = aDestFolder->GetMsgDatabase(getter_AddRefs(destMailDB)); + NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv), + "failed to open mail db moving message"); + + nsCOMPtr 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 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 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 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 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>& aHdrArray) { + nsCOMPtr folder; + + for (auto msgHdr : aHdrArray) { + msgHdr->GetFolder(getter_AddRefs(folder)); + nsCOMPtr 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>& aHdrArray, + nsIMsgFolder* aDstFolder, + nsTArray>& 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 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 srcStore; + nsAutoCString srcType; + srcFolder->GetMsgStore(getter_AddRefs(srcStore)); + if (srcStore) srcStore->GetStoreType(srcType); + nsCOMPtr 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 destLocalFolder( + do_QueryInterface(aDstFolder)); + if (!destLocalFolder) return NS_OK; + nsCOMPtr srcLocalFolder(do_QueryInterface(srcFolder)); + if (!srcLocalFolder) return NS_OK; + + // We should be able to use a file move for an efficient copy. + + nsCOMPtr destFolderPath; + nsCOMPtr destDB; + aDstFolder->GetMsgDatabase(getter_AddRefs(destDB)); + rv = aDstFolder->GetFilePath(getter_AddRefs(destFolderPath)); + NS_ENSURE_SUCCESS(rv, rv); + destFolderPath->Append(u"cur"_ns); + + nsCOMPtr srcFolderPath; + rv = srcFolder->GetFilePath(getter_AddRefs(srcFolderPath)); + NS_ENSURE_SUCCESS(rv, rv); + srcFolderPath->Append(u"cur"_ns); + + nsCOMPtr srcDB; + srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + RefPtr 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 srcFile; + rv = srcFolderPath->Clone(getter_AddRefs(srcFile)); + NS_ENSURE_SUCCESS(rv, rv); + srcFile->AppendNative(fileName); + + nsCOMPtr 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 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 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 m_directoryEnumerator; + nsCOMPtr m_folder; + nsCOMPtr m_db; + nsCOMPtr m_timer; + nsCOMPtr 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 inputStream; + nsCOMPtr msgParser = + do_CreateInstance("@mozilla.org/messenger/messagestateparser;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgParser->SetMailDB(m_db); + nsCOMPtr 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 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 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 mailboxurl = + do_CreateInstance("@mozilla.org/messenger/mailboxurl;1", &rv); + if (NS_SUCCEEDED(rv) && mailboxurl) { + nsCOMPtr 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 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 path; + nsresult rv = aFolder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + path->Append(u"cur"_ns); + + nsCOMPtr 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>& aHdrArray, uint32_t aFlags, + bool aSet) { + for (auto msgHdr : aHdrArray) { + // get output stream for header + nsCOMPtr 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 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& aOutputStream) { + // file name is stored in message header property "storeToken" + nsAutoCString fileName; + aHdr->GetStringProperty("storeToken", fileName); + if (fileName.IsEmpty()) return NS_ERROR_FAILURE; + + nsCOMPtr folder; + nsresult rv = aHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr folderPath; + rv = folder->GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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>& aHdrArray, const nsACString& aKeywords, + bool aAdd) { + if (aHdrArray.IsEmpty()) return NS_ERROR_INVALID_ARG; + + nsTArray keywordsToAdd; + nsTArray keywordsToRemove; + if (aAdd) { + ParseString(aKeywords, ' ', keywordsToAdd); + } else { + ParseString(aKeywords, ' ', keywordsToRemove); + } + + for (auto msgHdr : aHdrArray) { + // Open the message file. + nsCOMPtr output; + nsresult rv = GetOutputStream(msgHdr, output); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 in(inStream); + RefPtr 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& 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 rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 defaultMessagesFile; + rv = mailSession->GetDataFilesDir("messenger", + getter_AddRefs(defaultMessagesFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // check if bin/defaults/messenger/ + // (or bin/defaults/messenger// 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 parentDir; + rv = GetLocalPath(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + // check if parentDir/ exists + { + nsCOMPtr 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> 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 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 +#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 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 channel = do_QueryInterface(request, &rv); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "error QI nsIRequest to nsIChannel failed"); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr uri; + rv = channel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr runningUrl = do_QueryInterface(uri, &rv); + + nsCOMPtr url = do_QueryInterface(uri); + nsCOMPtr 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 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 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 bundleService = + mozilla::components::StringBundle::Service(); + if (!bundleService) return; + nsCOMPtr bundle; + rv = bundleService->CreateBundle( + "chrome://messenger/locale/localMsgs.properties", + getter_AddRefs(bundle)); + if (NS_FAILED(rv)) return; + nsString finalString; + AutoTArray 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 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 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 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 folder = do_QueryReferent(m_folder); + if (!folder) return; + bool haveSemaphore; + nsCOMPtr supports = + do_QueryInterface(static_cast(this)); + result = folder->TestSemaphore(supports, &haveSemaphore); + if (NS_SUCCEEDED(result) && haveSemaphore) + (void)folder->ReleaseSemaphore(supports); +} + +nsresult nsMsgMailboxParser::AcquireFolderLock() { + nsCOMPtr folder = do_QueryReferent(m_folder); + if (!folder) return NS_ERROR_NULL_POINTER; + nsCOMPtr 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 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 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& 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& 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& 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 [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 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 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_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 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 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 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 deferredToRootFolder; + server->GetRootMsgFolder(getter_AddRefs(deferredToRootFolder)); + if (serverFolder != deferredToRootFolder) { + nsCOMPtr 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 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 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 trash; + GetTrashFolder(getter_AddRefs(trash)); + if (trash) { + uint32_t newFlags; + bool msgMoved; + m_newMsgHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags); + nsCOMPtr 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 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 incomingServer; + m_downloadFolder->GetServer(getter_AddRefs(incomingServer)); + nsCOMPtr 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 msgHdr = m_newMsgHdr; + nsCOMPtr 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 msgHdr = m_newMsgHdr; + + nsTArray> 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 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 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 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 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 dstFolder; + nsCOMPtr 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 downloadFolder; + msgHdr->GetFolder(getter_AddRefs(downloadFolder)); + nsCOMPtr 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 downloadFolder; + msgHdr->GetFolder(getter_AddRefs(downloadFolder)); + nsCOMPtr 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 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(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 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 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 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 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 store; + nsCOMPtr 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 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 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 myISupports = + do_QueryInterface(static_cast(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 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 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 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 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 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 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& list); + void GetAggregateHeader(nsTArray& list, + struct message_header*); + void ClearAggregateHeader(nsTArray& list); + + nsCOMPtr m_newMsgHdr; /* current message header we're building */ + nsCOMPtr m_mailDB; + nsCOMPtr 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 m_toList; + nsTArray 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 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 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 m_filterList; + nsCOMPtr m_deferredToServerFilterList; + nsCOMPtr m_rootFolder; + nsCOMPtr m_msgWindow; + nsCOMPtr m_downloadFolder; + nsCOMPtr m_outputStream; + nsCOMArray m_filterTargetFolders; + + RefPtr 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 m_forwardTo; + nsTArray m_replyTemplateUri; + nsCOMPtr m_msgToForwardOrReply; + nsCOMPtr m_filter; + nsCOMPtr 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 +#include +#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 messages; + bool hasMore = false; + bool isOpen = false; + nsLocalFolderScanState folderScanState; + nsCOMPtr db; + nsCOMPtr 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 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 localFolder = do_QueryInterface(m_folder); + if (localFolder) localFolder->NotifyDelete(); + } +} + +nsresult nsPop3Sink::BeginMailDelivery(bool uidlDownload, + nsIMsgWindow* aMsgWindow, bool* aBool) { + nsresult rv; + nsCOMPtr server = do_QueryInterface(m_popServer); + if (!server) return NS_ERROR_UNEXPECTED; + + m_window = aMsgWindow; + + nsCOMPtr acctMgr = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + nsCOMPtr account; + NS_ENSURE_SUCCESS(rv, rv); + acctMgr->FindAccountForServer(server, getter_AddRefs(account)); + if (account) account->GetKey(m_accountKey); + + bool isLocked; + nsCOMPtr supports = + do_QueryInterface(static_cast(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 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 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 localFolder = do_QueryInterface(m_folder); + if (localFolder) (void)localFolder->RefreshSizeOnDisk(); + nsCOMPtr server = do_QueryInterface(m_popServer); + if (server) { + nsCOMPtr 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 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 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 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 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 supports = + do_QueryInterface(static_cast(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 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 newHdr; + + nsCOMPtr 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 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 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 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 hdr = m_newMailParser->m_newMsgHdr; + NS_ASSERTION(hdr, "m_newMailParser->m_newMsgHdr wasn't set"); + if (!hdr) return NS_ERROR_FAILURE; + + nsCOMPtr 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 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 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 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 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 obsServ = + mozilla::services::GetObserverService(); + nsCOMPtr 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 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 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 m_popServer; + // Currently the folder we want to update about biff info + nsCOMPtr m_folder; + RefPtr m_newMailParser; + nsCOMPtr + m_outFileStream; // the file we write to, which may be temporary + nsCOMPtr m_msgStore; + bool m_uidlDownload; + bool m_buildMessageUri; + nsCOMPtr m_window; + nsCString m_messageUri; + nsCString m_baseMessageUri; + nsCString m_origMessageUri; + nsCString m_accountKey; + nsTArray 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 pop3Url = do_CreateInstance(kPop3UrlCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + pop3Url->SetPop3Sink(pop3Sink); + + nsCOMPtr 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 folder; + rv = GetOrCreateFolder(folderUri, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr server; + + nsLocalFolderScanState folderScanState; + nsCOMPtr localFolder = do_QueryInterface(folder); + nsCOMPtr mailboxUrl = do_QueryInterface(aBaseURI); + + if (mailboxUrl && localFolder) { + rv = localFolder->GetFolderScanState(&folderScanState); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + if (accountManager) { + nsCOMPtr 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 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 urlListener = do_QueryInterface(folder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr newUri; + rv = BuildPop3Url(popSpec.get(), folder, popServer, urlListener, + getter_AddRefs(newUri), nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr mailnewsurl = do_QueryInterface(newUri, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mailnewsurl->SetUsernameInternal(escapedUsername); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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 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 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 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 rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 rootRSSFolder; + GetRootMsgFolder(getter_AddRefs(rootRSSFolder)); + nsCOMPtr urlListener = do_QueryInterface(rootRSSFolder); + nsresult rv; + bool isBiff = true; + nsCOMPtr 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 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>& aMsgs, bool aJunkProcessed, + bool aTraitProcessed) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssIncomingServer::MsgsJunkStatusChanged( + const nsTArray>& messages) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssIncomingServer::MsgsDeleted( + const nsTArray>& aMsgs) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsRssIncomingServer::MsgsMoveCopyCompleted( + bool aMove, const nsTArray>& aSrcMsgs, + nsIMsgFolder* aDestFolder, const nsTArray>& 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 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 localFile; + nsCOMPtr 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: +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" +X-FID: 1D3F6781-6356-4D2B-9E2A-C2FC357A0AB3 +X-Priority: 3 +To: +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 + + + + + + + + + + + + + + +
+
+
ist bearbeitet;
+
Ku=DF!



+ + + + + +
3D"Tierisch +--------------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: + +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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: +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 ) + 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 ) + 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 ; 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: +References: +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 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 , Another Person +X-Identity-Key: id2 +Message-ID: <4849BF7B.2030800@example.com> +Date: Sat, 07 Jun 2008 04:23:42 +0530 +From: Some User +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 + + + + + + + +This is an HTML message. Did you receive my bugmail?
+
+Thanks, and goodbye.
+
+--
+Some User
+
+ + + + +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: +References: + <4DC2493C.4060403@gmx.invalid> + + <175221688.20110506234025@my_localhost> + <201105072315.25120@thufir.ingo-kloecker.invalid> + + <05433510.20110507224940@my_localhost> + <4DC5C015.7050800@sixdemonbag.invalid> + + +Reply-To: FOO +List-Id: Help and discussion among users of GnuPG +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 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 \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 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 + // +--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 + // +--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 + // +--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 + */ + +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); + } +} + +// +function createPop3Server() { + let incoming = MailServices.accounts.createIncomingServer( + "fred", + "localhost", + "pop3" + ); + incoming.port = server.port; + incoming.password = "wilma"; + return incoming; +} +// + +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 + */ + +/* 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 + */ + +/* 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); + } +} + +// +function createPop3Server() { + let incoming = MailServices.accounts.createIncomingServer( + "fred", + "localhost", + "pop3" + ); + incoming.port = server.port; + incoming.password = "wilma"; + return incoming; +} +// + +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 + */ + +/* 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 + */ + +/* 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 + */ + +/* 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 + +// 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] -- cgit v1.2.3