summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/import/src/nsBeckyMail.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/import/src/nsBeckyMail.cpp')
-rw-r--r--comm/mailnews/import/src/nsBeckyMail.cpp566
1 files changed, 566 insertions, 0 deletions
diff --git a/comm/mailnews/import/src/nsBeckyMail.cpp b/comm/mailnews/import/src/nsBeckyMail.cpp
new file mode 100644
index 0000000000..36ab1e1104
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyMail.cpp
@@ -0,0 +1,566 @@
+/* vim: set ts=2 et sw=2 tw=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/. */
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsILineInputStream.h"
+#include "nsNetUtil.h"
+#include "nsIImportService.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsMsgUtils.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMsgMessageFlags.h"
+#include "nsTArray.h"
+#include "nspr.h"
+#include "nsThreadUtils.h"
+#include "nsIDirectoryEnumerator.h"
+
+#include "nsBeckyMail.h"
+#include "nsBeckyUtils.h"
+#include "nsBeckyStringBundle.h"
+
+#define FROM_LINE "From - Mon Jan 1 00:00:00 1965" MSG_LINEBREAK
+#define X_BECKY_STATUS_HEADER "X-Becky-Status"
+#define X_BECKY_INCLUDE_HEADER "X-Becky-Include"
+
+enum {
+ BECKY_STATUS_READ = 1 << 0,
+ BECKY_STATUS_FORWARDED = 1 << 1,
+ BECKY_STATUS_REPLIED = 1 << 2
+};
+
+NS_IMPL_ISUPPORTS(nsBeckyMail, nsIImportMail)
+
+nsresult nsBeckyMail::Create(nsIImportMail** aImport) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new nsBeckyMail());
+ return NS_OK;
+}
+
+nsBeckyMail::nsBeckyMail() : mReadBytes(0) {}
+
+nsBeckyMail::~nsBeckyMail() {}
+
+NS_IMETHODIMP
+nsBeckyMail::GetDefaultLocation(nsIFile** aLocation, bool* aFound,
+ bool* aUserVerify) {
+ NS_ENSURE_ARG_POINTER(aFound);
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(aUserVerify);
+
+ *aLocation = nullptr;
+ *aUserVerify = true;
+ *aFound = false;
+ if (NS_SUCCEEDED(nsBeckyUtils::GetDefaultMailboxDirectory(aLocation)))
+ *aFound = true;
+
+ return NS_OK;
+}
+
+nsresult nsBeckyMail::CreateMailboxDescriptor(
+ nsIImportMailboxDescriptor** aDescriptor) {
+ nsresult rv;
+ nsCOMPtr<nsIImportService> importService;
+ importService = do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return importService->CreateNewMailboxDescriptor(aDescriptor);
+}
+
+nsresult nsBeckyMail::GetMailboxName(nsIFile* aMailbox, nsAString& aName) {
+ nsCOMPtr<nsIFile> iniFile;
+ nsBeckyUtils::GetMailboxINIFile(aMailbox, getter_AddRefs(iniFile));
+ if (iniFile) {
+ nsCOMPtr<nsIFile> convertedFile;
+ nsBeckyUtils::ConvertToUTF8File(iniFile, getter_AddRefs(convertedFile));
+ if (convertedFile) {
+ nsAutoCString utf8Name;
+ nsBeckyUtils::GetMailboxNameFromINIFile(convertedFile, utf8Name);
+ convertedFile->Remove(false);
+ CopyUTF8toUTF16(utf8Name, aName);
+ }
+ }
+
+ if (aName.IsEmpty()) {
+ nsAutoString name;
+ aMailbox->GetLeafName(name);
+ name.Trim("!", true, false);
+ aName.Assign(name);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsBeckyMail::AppendMailboxDescriptor(
+ nsIFile* aEntry, const nsString& aName, uint32_t aDepth,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aCollected) {
+ nsresult rv;
+ nsCOMPtr<nsIImportMailboxDescriptor> descriptor;
+ rv = CreateMailboxDescriptor(getter_AddRefs(descriptor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t size;
+ rv = aEntry->GetFileSize(&size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = descriptor->SetSize(size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = descriptor->SetDisplayName(aName.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> mailboxFile;
+ rv = descriptor->GetFile(getter_AddRefs(mailboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ descriptor->SetDepth(aDepth);
+
+ mailboxFile->InitWithFile(aEntry);
+ aCollected.AppendElement(descriptor);
+
+ return NS_OK;
+}
+
+nsresult nsBeckyMail::CollectMailboxesInFolderListFile(
+ nsIFile* aListFile, uint32_t aDepth,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aCollected) {
+ nsresult rv;
+ nsCOMPtr<nsILineInputStream> lineStream;
+ rv = nsBeckyUtils::CreateLineInputStream(aListFile,
+ getter_AddRefs(lineStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> parent;
+ rv = aListFile->GetParent(getter_AddRefs(parent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsAutoCString folderName;
+ bool isEmpty = true;
+ while (more && NS_SUCCEEDED(rv)) {
+ rv = lineStream->ReadLine(folderName, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (folderName.IsEmpty()) continue;
+
+ nsCOMPtr<nsIFile> folder;
+ rv = parent->Clone(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = folder->AppendNative(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ isEmpty = false;
+ rv = CollectMailboxesInDirectory(folder, aDepth + 1, aCollected);
+ }
+
+ return isEmpty ? NS_ERROR_FILE_NOT_FOUND : NS_OK;
+}
+
+nsresult nsBeckyMail::CollectMailboxesInDirectory(
+ nsIFile* aDirectory, uint32_t aDepth,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aCollected) {
+ nsAutoString mailboxName;
+ nsresult rv = GetMailboxName(aDirectory, mailboxName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDepth != 0)
+ AppendMailboxDescriptor(aDirectory, mailboxName, aDepth, aCollected);
+
+ nsCOMPtr<nsIFile> folderListFile;
+ rv = nsBeckyUtils::GetFolderListFile(aDirectory,
+ getter_AddRefs(folderListFile));
+ bool folderListExists = false;
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = CollectMailboxesInFolderListFile(folderListFile, aDepth, aCollected);
+ folderListExists = true;
+ }
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsIFile> file;
+ rv = entries->GetNextFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString name;
+ rv = file->GetLeafName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (StringEndsWith(name, u".bmf"_ns)) {
+ AppendMailboxDescriptor(file, mailboxName, aDepth, aCollected);
+ }
+
+ // The Folder.lst file is not created if there is only one sub folder,
+ // so we need to find the sub folder by our hands.
+ // The folder name does not begin with # or ! maybe. Yes, maybe...
+ if (!folderListExists) {
+ if (StringBeginsWith(name, u"#"_ns) || StringBeginsWith(name, u"!"_ns))
+ continue;
+
+ bool isDirectory = false;
+ rv = file->IsDirectory(&isDirectory);
+ if (isDirectory) {
+ CollectMailboxesInDirectory(file, aDepth + 1, aCollected);
+ continue;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyMail::FindMailboxes(
+ nsIFile* aLocation, nsTArray<RefPtr<nsIImportMailboxDescriptor>>& boxes) {
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ boxes.Clear();
+ nsresult rv = CollectMailboxesInDirectory(aLocation, 0, boxes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+static nsresult GetBeckyStatusValue(const nsCString& aHeader,
+ nsACString& aValue) {
+ int32_t valueStartPosition;
+
+ valueStartPosition = aHeader.FindChar(':');
+ if (valueStartPosition < 0) return NS_ERROR_UNEXPECTED;
+
+ valueStartPosition++;
+
+ int32_t commaPosition = aHeader.FindChar(',', valueStartPosition);
+ if (commaPosition < 0) return NS_ERROR_UNEXPECTED;
+
+ nsAutoCString value(Substring(aHeader, valueStartPosition,
+ commaPosition - valueStartPosition));
+ value.Trim(" \t");
+
+ aValue.Assign(value);
+
+ return NS_OK;
+}
+
+static nsresult GetBeckyIncludeValue(const nsCString& aHeader,
+ nsACString& aValue) {
+ int32_t valueStartPosition;
+
+ valueStartPosition = aHeader.FindChar(':');
+ if (valueStartPosition < 0) return NS_ERROR_FAILURE;
+
+ valueStartPosition++;
+ nsAutoCString value(Substring(aHeader, valueStartPosition));
+ value.Trim(" \t");
+
+ aValue.Assign(value);
+
+ return NS_OK;
+}
+
+static bool ConvertBeckyStatusToMozillaStatus(
+ const nsCString& aHeader, nsMsgMessageFlagType* aMozillaStatusFlag) {
+ nsresult rv;
+ nsAutoCString statusString;
+ rv = GetBeckyStatusValue(aHeader, statusString);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsresult errorCode;
+ uint32_t beckyStatusFlag =
+ static_cast<uint32_t>(statusString.ToInteger(&errorCode, 16));
+ if (NS_FAILED(errorCode)) return false;
+
+ if (beckyStatusFlag & BECKY_STATUS_READ)
+ *aMozillaStatusFlag |= nsMsgMessageFlags::Read;
+ if (beckyStatusFlag & BECKY_STATUS_FORWARDED)
+ *aMozillaStatusFlag |= nsMsgMessageFlags::Forwarded;
+ if (beckyStatusFlag & BECKY_STATUS_REPLIED)
+ *aMozillaStatusFlag |= nsMsgMessageFlags::Replied;
+
+ return true;
+}
+
+static inline bool CheckHeaderKey(const nsCString& aHeader,
+ const char* aKeyString) {
+ nsAutoCString key(StringHead(aHeader, aHeader.FindChar(':')));
+ key.Trim(" \t");
+ return key.Equals(aKeyString);
+}
+
+static inline bool IsBeckyStatusHeader(const nsCString& aHeader) {
+ return CheckHeaderKey(aHeader, X_BECKY_STATUS_HEADER);
+}
+
+static inline bool IsBeckyIncludeLine(const nsCString& aLine) {
+ return CheckHeaderKey(aLine, X_BECKY_INCLUDE_HEADER);
+}
+
+static inline bool IsEndOfHeaders(const nsCString& aLine) {
+ return aLine.IsEmpty();
+}
+
+static inline bool IsEndOfMessage(const nsCString& aLine) {
+ return aLine.EqualsLiteral(".");
+}
+
+class ImportMessageRunnable : public mozilla::Runnable {
+ public:
+ ImportMessageRunnable(nsIFile* aMessageFile, nsIMsgFolder* aFolder);
+ NS_DECL_NSIRUNNABLE
+ nsresult mResult;
+
+ private:
+ nsresult WriteHeaders(nsCString& aHeaders, nsIOutputStream* aOutputStream);
+ nsresult HandleHeaderLine(const nsCString& aHeaderLine, nsACString& aHeaders);
+ nsresult GetAttachmentFile(nsIFile* aMailboxFile, const nsCString& aHeader,
+ nsIFile** _retval);
+ nsresult WriteAttachmentFile(nsIFile* aMailboxFile, const nsCString& aHeader,
+ nsIOutputStream* aOutputStream);
+
+ nsCOMPtr<nsIFile> mMessageFile;
+ nsCOMPtr<nsIMsgFolder> mFolder;
+};
+
+ImportMessageRunnable::ImportMessageRunnable(nsIFile* aMessageFile,
+ nsIMsgFolder* aFolder)
+ : mozilla::Runnable("ImportMessageRunnable"),
+ mMessageFile(aMessageFile),
+ mFolder(aFolder) {}
+
+nsresult ImportMessageRunnable::WriteHeaders(nsCString& aHeaders,
+ nsIOutputStream* aOutputStream) {
+ nsresult rv;
+ uint32_t writtenBytes = 0;
+
+ rv = aOutputStream->Write(FROM_LINE, strlen(FROM_LINE), &writtenBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aOutputStream->Write(aHeaders.get(), aHeaders.Length(), &writtenBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv =
+ aOutputStream->Write(MSG_LINEBREAK, strlen(MSG_LINEBREAK), &writtenBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aHeaders.Truncate();
+
+ return NS_OK;
+}
+
+nsresult ImportMessageRunnable::HandleHeaderLine(const nsCString& aHeaderLine,
+ nsACString& aHeaders) {
+ aHeaders.Append(aHeaderLine);
+ aHeaders.AppendLiteral(MSG_LINEBREAK);
+
+ nsMsgMessageFlagType flag = 0;
+ if (IsBeckyStatusHeader(aHeaderLine) &&
+ ConvertBeckyStatusToMozillaStatus(aHeaderLine, &flag)) {
+ char* statusLine;
+ statusLine = PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, flag);
+ aHeaders.Append(statusLine);
+ PR_smprintf_free(statusLine);
+ aHeaders.AppendLiteral(X_MOZILLA_KEYWORDS);
+ }
+
+ return NS_OK;
+}
+
+nsresult ImportMessageRunnable::GetAttachmentFile(nsIFile* aMailboxFile,
+ const nsCString& aHeader,
+ nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> attachmentFile;
+
+ rv = aMailboxFile->Clone(getter_AddRefs(attachmentFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = attachmentFile->Append(u"#Attach"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString nativeAttachmentPath;
+ rv = GetBeckyIncludeValue(aHeader, nativeAttachmentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = attachmentFile->AppendRelativeNativePath(nativeAttachmentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ attachmentFile->Exists(&exists);
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ attachmentFile.forget(_retval);
+ return NS_OK;
+}
+
+nsresult ImportMessageRunnable::WriteAttachmentFile(
+ nsIFile* aMailboxFile, const nsCString& aHeader,
+ nsIOutputStream* aOutputStream) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> parentDirectory;
+ rv = aMailboxFile->GetParent(getter_AddRefs(parentDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> attachmentFile;
+ rv = GetAttachmentFile(parentDirectory, aHeader,
+ getter_AddRefs(attachmentFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), attachmentFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char buffer[FILE_IO_BUFFER_SIZE];
+ uint32_t readBytes = 0;
+ uint32_t writtenBytes = 0;
+ rv =
+ aOutputStream->Write(MSG_LINEBREAK, strlen(MSG_LINEBREAK), &writtenBytes);
+ while (NS_SUCCEEDED(inputStream->Read(buffer, sizeof(buffer), &readBytes)) &&
+ readBytes > 0) {
+ rv = aOutputStream->Write(buffer, readBytes, &writtenBytes);
+ if (NS_FAILED(rv)) break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP ImportMessageRunnable::Run() {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ mResult = mFolder->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(mResult, NS_OK);
+
+ nsCOMPtr<nsILineInputStream> lineStream;
+ mResult = nsBeckyUtils::CreateLineInputStream(mMessageFile,
+ getter_AddRefs(lineStream));
+ NS_ENSURE_SUCCESS(mResult, NS_OK);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIOutputStream> outputStream;
+ mResult = msgStore->GetNewMsgOutputStream(mFolder, getter_AddRefs(msgHdr),
+ getter_AddRefs(outputStream));
+ NS_ENSURE_SUCCESS(mResult, NS_OK);
+
+ bool inHeader = true;
+ bool more = true;
+ nsAutoCString headers;
+ while (NS_SUCCEEDED(mResult) && more) {
+ nsAutoCString line;
+ mResult = lineStream->ReadLine(line, &more);
+ if (NS_FAILED(mResult)) break;
+
+ if (inHeader) {
+ if (IsEndOfHeaders(line)) {
+ inHeader = false;
+ mResult = WriteHeaders(headers, outputStream);
+ } else {
+ mResult = HandleHeaderLine(line, headers);
+ }
+ } else if (IsEndOfMessage(line)) {
+ inHeader = true;
+ mResult = msgStore->FinishNewMessage(outputStream, msgHdr);
+ // outputStream is closed by FinishNewMessage().
+ outputStream = nullptr;
+ mResult = msgStore->GetNewMsgOutputStream(mFolder, getter_AddRefs(msgHdr),
+ getter_AddRefs(outputStream));
+ } else if (IsBeckyIncludeLine(line)) {
+ mResult = WriteAttachmentFile(mMessageFile, line, outputStream);
+ } else {
+ uint32_t writtenBytes = 0;
+ if (StringBeginsWith(line, ".."_ns))
+ line.Cut(0, 1);
+ else if (CheckHeaderKey(line, "From"))
+ line.Insert('>', 0);
+
+ line.AppendLiteral(MSG_LINEBREAK);
+ mResult = outputStream->Write(line.get(), line.Length(), &writtenBytes);
+ }
+ }
+
+ if (outputStream) {
+ // DiscardNewMessage() closes outputStream.
+ if (NS_FAILED(mResult))
+ msgStore->DiscardNewMessage(outputStream, msgHdr);
+ else
+ outputStream->Close(); /* No check? */
+ }
+
+ return NS_OK;
+}
+
+static nsresult ProxyImportMessage(nsIFile* aMessageFile,
+ nsIMsgFolder* aFolder) {
+ RefPtr<ImportMessageRunnable> importMessage =
+ new ImportMessageRunnable(aMessageFile, aFolder);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxyImportMessage"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(importMessage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return importMessage->mResult;
+}
+
+nsresult nsBeckyMail::ImportMailFile(nsIFile* aMailFile,
+ nsIMsgFolder* aDestination) {
+ int64_t size;
+ aMailFile->GetFileSize(&size);
+ if (size == 0) return NS_OK;
+
+ return ProxyImportMessage(aMailFile, aDestination);
+}
+
+NS_IMETHODIMP
+nsBeckyMail::ImportMailbox(nsIImportMailboxDescriptor* aSource,
+ nsIMsgFolder* aDestination, char16_t** aErrorLog,
+ char16_t** aSuccessLog, bool* aFatalError) {
+ NS_ENSURE_ARG_POINTER(aSource);
+ NS_ENSURE_ARG_POINTER(aDestination);
+ NS_ENSURE_ARG_POINTER(aErrorLog);
+ NS_ENSURE_ARG_POINTER(aSuccessLog);
+ NS_ENSURE_ARG_POINTER(aFatalError);
+
+ mReadBytes = 0;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> mailboxFolder;
+ rv = aSource->GetFile(getter_AddRefs(mailboxFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ImportMailFile(mailboxFolder, aDestination);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t finalSize;
+ aSource->GetSize(&finalSize);
+ mReadBytes = finalSize;
+
+ nsAutoString name;
+ aSource->GetDisplayName(getter_Copies(name));
+
+ nsAutoString successMessage;
+ AutoTArray<nsString, 1> format = {name};
+ rv = nsBeckyStringBundle::FormatStringFromName("BeckyImportMailboxSuccess",
+ format, successMessage);
+ successMessage.AppendLiteral("\n");
+ *aSuccessLog = ToNewUnicode(successMessage);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBeckyMail::GetImportProgress(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mReadBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyMail::TranslateFolderName(const nsAString& aFolderName,
+ nsAString& _retval) {
+ return nsBeckyUtils::TranslateFolderName(aFolderName, _retval);
+}