diff options
Diffstat (limited to 'mailnews/import')
134 files changed, 30850 insertions, 0 deletions
diff --git a/mailnews/import/applemail/src/moz.build b/mailnews/import/applemail/src/moz.build new file mode 100644 index 000000000..1955cf30a --- /dev/null +++ b/mailnews/import/applemail/src/moz.build @@ -0,0 +1,15 @@ +# 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 += [ + 'nsAppleMailImport.cpp', +] + +SOURCES += [ + 'nsEmlxHelperUtils.mm', +] + +FINAL_LIBRARY = 'import' + diff --git a/mailnews/import/applemail/src/nsAppleMailImport.cpp b/mailnews/import/applemail/src/nsAppleMailImport.cpp new file mode 100644 index 000000000..3097b7df5 --- /dev/null +++ b/mailnews/import/applemail/src/nsAppleMailImport.cpp @@ -0,0 +1,623 @@ +/* -*- 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 "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsISupportsPrimitives.h" +#include "nsIImportService.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIImportGeneric.h" +#include "nsIFile.h" +#include "nsIStringBundle.h" +#include "nsIMsgFolder.h" +#include "nsIMsgHdr.h" +#include "nsIMsgPluggableStore.h" +#include "nsIMutableArray.h" +#include "nsNetUtil.h" +#include "nsMsgUtils.h" +#include "mozilla/Services.h" + +#include "nsEmlxHelperUtils.h" +#include "nsAppleMailImport.h" +#include "nsIOutputStream.h" + +PRLogModuleInfo *APPLEMAILLOGMODULE = nullptr; + +// some hard-coded strings +#define DEFAULT_MAIL_FOLDER "~/Library/Mail/" +#define POP_MBOX_SUFFIX ".mbox" +#define IMAP_MBOX_SUFFIX ".imapmbox" + +// stringbundle URI +#define APPLEMAIL_MSGS_URL "chrome://messenger/locale/appleMailImportMsgs.properties" + +// magic constants +#define kAccountMailboxID 1234 + +nsAppleMailImportModule::nsAppleMailImportModule() +{ + // Init logging module. + if (!APPLEMAILLOGMODULE) + APPLEMAILLOGMODULE = PR_NewLogModule("APPLEMAILIMPORTLOG"); + + IMPORT_LOG0("nsAppleMailImportModule Created"); + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (bundleService) + bundleService->CreateBundle(APPLEMAIL_MSGS_URL, getter_AddRefs(mBundle)); +} + + +nsAppleMailImportModule::~nsAppleMailImportModule() +{ + IMPORT_LOG0("nsAppleMailImportModule Deleted"); +} + + +NS_IMPL_ISUPPORTS(nsAppleMailImportModule, nsIImportModule) + + +NS_IMETHODIMP nsAppleMailImportModule::GetName(char16_t **aName) +{ + return mBundle ? + mBundle->GetStringFromName(u"ApplemailImportName", aName) : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsAppleMailImportModule::GetDescription(char16_t **aName) +{ + return mBundle ? + mBundle->GetStringFromName(u"ApplemailImportDescription", aName) : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsAppleMailImportModule::GetSupports(char **aSupports) +{ + NS_ENSURE_ARG_POINTER(aSupports); + *aSupports = strdup(NS_IMPORT_MAIL_STR); + return NS_OK; +} + +NS_IMETHODIMP nsAppleMailImportModule::GetSupportsUpgrade(bool *aUpgrade) +{ + NS_ENSURE_ARG_POINTER(aUpgrade); + *aUpgrade = false; + return NS_OK; +} + +NS_IMETHODIMP nsAppleMailImportModule::GetImportInterface(const char *aImportType, nsISupports **aInterface) +{ + NS_ENSURE_ARG_POINTER(aImportType); + NS_ENSURE_ARG_POINTER(aInterface); + *aInterface = nullptr; + nsresult rv = NS_ERROR_NOT_AVAILABLE; + + if (!strcmp(aImportType, "mail")) { + nsCOMPtr<nsIImportMail> mail(do_CreateInstance(NS_APPLEMAILIMPL_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImportGeneric> generic; + rv = impSvc->CreateNewGenericMail(getter_AddRefs(generic)); + if (NS_SUCCEEDED(rv)) { + nsAutoString name; + rv = mBundle->GetStringFromName(u"ApplemailImportName", getter_Copies(name)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsString> nameString(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nameString->SetData(name); + + generic->SetData("name", nameString); + generic->SetData("mailInterface", mail); + + generic.forget(aInterface); + } + } + } + } + + return rv; +} + +#pragma mark - + +nsAppleMailImportMail::nsAppleMailImportMail() : mProgress(0), mCurDepth(0) +{ + IMPORT_LOG0("nsAppleMailImportMail created"); +} + +nsresult nsAppleMailImportMail::Initialize() +{ + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + return bundleService->CreateBundle(APPLEMAIL_MSGS_URL, getter_AddRefs(mBundle)); +} + +nsAppleMailImportMail::~nsAppleMailImportMail() +{ + IMPORT_LOG0("nsAppleMailImportMail destroyed"); +} + +NS_IMPL_ISUPPORTS(nsAppleMailImportMail, nsIImportMail) + +NS_IMETHODIMP nsAppleMailImportMail::GetDefaultLocation(nsIFile **aLocation, bool *aFound, bool *aUserVerify) +{ + NS_ENSURE_ARG_POINTER(aFound); + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(aUserVerify); + + *aLocation = nullptr; + *aFound = false; + *aUserVerify = true; + + // try to find current user's top-level Mail folder + nsCOMPtr<nsIFile> mailFolder(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + if (mailFolder) { + nsresult rv = mailFolder->InitWithNativePath(NS_LITERAL_CSTRING(DEFAULT_MAIL_FOLDER)); + if (NS_SUCCEEDED(rv)) { + *aFound = true; + *aUserVerify = false; + mailFolder.forget(aLocation); + } + } + + return NS_OK; +} + +// this is the method that initiates all searching for mailboxes. +// it will assume that it has a directory like ~/Library/Mail/ +NS_IMETHODIMP nsAppleMailImportMail::FindMailboxes(nsIFile *aMailboxFile, nsIArray **aResult) +{ + NS_ENSURE_ARG_POINTER(aMailboxFile); + NS_ENSURE_ARG_POINTER(aResult); + + IMPORT_LOG0("FindMailboxes for Apple mail invoked"); + + bool exists = false; + nsresult rv = aMailboxFile->Exists(&exists); + if (NS_FAILED(rv) || !exists) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIImportService> importService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> resultsArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (!resultsArray) + return NS_ERROR_OUT_OF_MEMORY; + + mCurDepth = 1; + + // 1. look for accounts with mailboxes + FindAccountMailDirs(aMailboxFile, resultsArray, importService); + mCurDepth--; + + if (NS_SUCCEEDED(rv)) { + // 2. look for "global" mailboxes, that don't belong to any specific account. they are inside the + // root's Mailboxes/ folder + nsCOMPtr<nsIFile> mailboxesDir(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + mailboxesDir->InitWithFile(aMailboxFile); + rv = mailboxesDir->Append(NS_LITERAL_STRING("Mailboxes")); + if (NS_SUCCEEDED(rv)) { + IMPORT_LOG0("Looking for global Apple mailboxes"); + + mCurDepth++; + rv = FindMboxDirs(mailboxesDir, resultsArray, importService); + mCurDepth--; + } + } + } + + if (NS_SUCCEEDED(rv) && resultsArray) + resultsArray.forget(aResult); + + return rv; +} + +// operates on the Mail/ directory root, trying to find accounts (which are folders named something like "POP-hwaara@gmail.com") +// and add their .mbox dirs +void nsAppleMailImportMail::FindAccountMailDirs(nsIFile *aRoot, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService) +{ + nsCOMPtr<nsISimpleEnumerator> directoryEnumerator; + nsresult rv = aRoot->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + if (NS_FAILED(rv)) + return; + + bool hasMore = false; + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && hasMore) { + + // get the next file entry + nsCOMPtr<nsIFile> currentEntry; + { + nsCOMPtr<nsISupports> rawSupports; + directoryEnumerator->GetNext(getter_AddRefs(rawSupports)); + if (!rawSupports) + continue; + currentEntry = do_QueryInterface(rawSupports); + if (!currentEntry) + continue; + } + + // make sure it's a directory + bool isDirectory = false; + currentEntry->IsDirectory(&isDirectory); + + if (isDirectory) { + // now let's see if it's an account folder. if so, we want to traverse it for .mbox children + nsAutoString folderName; + currentEntry->GetLeafName(folderName); + bool isAccountFolder = false; + + if (StringBeginsWith(folderName, NS_LITERAL_STRING("POP-"))) { + // cut off "POP-" prefix so we get a nice folder name + folderName.Cut(0, 4); + isAccountFolder = true; + } + else if (StringBeginsWith(folderName, NS_LITERAL_STRING("IMAP-"))) { + // cut off "IMAP-" prefix so we get a nice folder name + folderName.Cut(0, 5); + isAccountFolder = true; + } + + if (isAccountFolder) { + IMPORT_LOG1("Found account: %s\n", NS_ConvertUTF16toUTF8(folderName).get()); + + // create a mailbox for this account, so we get a parent for "Inbox", "Sent Messages", etc. + nsCOMPtr<nsIImportMailboxDescriptor> desc; + nsresult rv = aImportService->CreateNewMailboxDescriptor(getter_AddRefs(desc)); + desc->SetSize(1); + desc->SetDepth(mCurDepth); + desc->SetDisplayName(folderName.get()); + desc->SetIdentifier(kAccountMailboxID); + + nsCOMPtr<nsIFile> mailboxDescFile; + rv = desc->GetFile(getter_AddRefs(mailboxDescFile)); + if (!mailboxDescFile) + continue; + + mailboxDescFile->InitWithFile(currentEntry); + + // add this mailbox descriptor to the list + aMailboxDescs->AppendElement(desc, false); + + // now add all the children mailboxes + mCurDepth++; + FindMboxDirs(currentEntry, aMailboxDescs, aImportService); + mCurDepth--; + } + } + } +} + +// adds the specified file as a mailboxdescriptor to the array +nsresult nsAppleMailImportMail::AddMboxDir(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService) +{ + nsAutoString folderName; + aFolder->GetLeafName(folderName); + + // cut off the suffix, if any, or prefix if this is an account folder. + if (StringEndsWith(folderName, NS_LITERAL_STRING(POP_MBOX_SUFFIX))) + folderName.SetLength(folderName.Length()-5); + else if (StringEndsWith(folderName, NS_LITERAL_STRING(IMAP_MBOX_SUFFIX))) + folderName.SetLength(folderName.Length()-9); + else if (StringBeginsWith(folderName, NS_LITERAL_STRING("POP-"))) + folderName.Cut(4, folderName.Length()); + else if (StringBeginsWith(folderName, NS_LITERAL_STRING("IMAP-"))) + folderName.Cut(5, folderName.Length()); + + nsCOMPtr<nsIImportMailboxDescriptor> desc; + nsresult rv = aImportService->CreateNewMailboxDescriptor(getter_AddRefs(desc)); + if (NS_SUCCEEDED(rv)) { + // find out number of messages in this .mbox + uint32_t numMessages = 0; + { + // move to the .mbox's Messages folder + nsCOMPtr<nsIFile> messagesFolder; + aFolder->Clone(getter_AddRefs(messagesFolder)); + nsresult rv = messagesFolder->Append(NS_LITERAL_STRING("Messages")); + NS_ENSURE_SUCCESS(rv, rv); + + // count the number of messages in this folder. it sucks that we have to iterate through the folder + // but XPCOM doesn't give us any way to just get the file count, unfortunately. :-( + nsCOMPtr<nsISimpleEnumerator> dirEnumerator; + messagesFolder->GetDirectoryEntries(getter_AddRefs(dirEnumerator)); + if (dirEnumerator) { + bool hasMore = false; + while (NS_SUCCEEDED(dirEnumerator->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> rawSupports; + dirEnumerator->GetNext(getter_AddRefs(rawSupports)); + if (!rawSupports) + continue; + + nsCOMPtr<nsIFile> file(do_QueryInterface(rawSupports)); + if (file) { + bool isFile = false; + file->IsFile(&isFile); + if (isFile) + numMessages++; + } + } + } + } + + desc->SetSize(numMessages); + desc->SetDisplayName(folderName.get()); + desc->SetDepth(mCurDepth); + + IMPORT_LOG3("Will import %s with approx %d messages, depth is %d", NS_ConvertUTF16toUTF8(folderName).get(), numMessages, mCurDepth); + + // XXX: this is silly. there's no setter for the mailbox descriptor's file, so we need to get it, and then modify it. + nsCOMPtr<nsIFile> mailboxDescFile; + rv = desc->GetFile(getter_AddRefs(mailboxDescFile)); + NS_ENSURE_SUCCESS(rv, rv); + + if (mailboxDescFile) + mailboxDescFile->InitWithFile(aFolder); + + // add this mailbox descriptor to the list + aMailboxDescs->AppendElement(desc, false); + } + + return NS_OK; +} + +// Starts looking for .mbox dirs in the specified dir. The .mbox dirs contain messages and can be considered leafs in a tree of +// nested mailboxes (subfolders). +// +// If a mailbox has sub-mailboxes, they are contained in a sibling folder with the same name without the ".mbox" part. +// example: +// MyParentMailbox.mbox/ +// MyParentMailbox/ +// MyChildMailbox.mbox/ +// MyOtherChildMailbox.mbox/ +// +nsresult nsAppleMailImportMail::FindMboxDirs(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aMailboxDescs); + NS_ENSURE_ARG_POINTER(aImportService); + + // make sure this is a directory. + bool isDir = false; + if (NS_FAILED(aFolder->IsDirectory(&isDir)) || !isDir) + return NS_ERROR_FAILURE; + + // iterate through the folder contents + nsCOMPtr<nsISimpleEnumerator> directoryEnumerator; + nsresult rv = aFolder->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + if (NS_FAILED(rv) || !directoryEnumerator) + return rv; + + bool hasMore = false; + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && hasMore) { + + // get the next file entry + nsCOMPtr<nsIFile> currentEntry; + { + nsCOMPtr<nsISupports> rawSupports; + directoryEnumerator->GetNext(getter_AddRefs(rawSupports)); + if (!rawSupports) + continue; + currentEntry = do_QueryInterface(rawSupports); + if (!currentEntry) + continue; + } + + // we only care about directories... + if (NS_FAILED(currentEntry->IsDirectory(&isDir)) || !isDir) + continue; + + // now find out if this is a .mbox dir + nsAutoString currentFolderName; + if (NS_SUCCEEDED(currentEntry->GetLeafName(currentFolderName)) && + (StringEndsWith(currentFolderName, NS_LITERAL_STRING(POP_MBOX_SUFFIX)) || + StringEndsWith(currentFolderName, NS_LITERAL_STRING(IMAP_MBOX_SUFFIX)))) { + IMPORT_LOG1("Adding .mbox dir: %s", NS_ConvertUTF16toUTF8(currentFolderName).get()); + + // add this .mbox + rv = AddMboxDir(currentEntry, aMailboxDescs, aImportService); + if (NS_FAILED(rv)) { + IMPORT_LOG1("Couldn't add .mbox for import: %s ... continuing anyway", NS_ConvertUTF16toUTF8(currentFolderName).get()); + continue; + } + + // see if this .mbox dir has any sub-mailboxes + nsAutoString siblingMailboxDirPath; + currentEntry->GetPath(siblingMailboxDirPath); + + // cut off suffix + if (StringEndsWith(siblingMailboxDirPath, NS_LITERAL_STRING(IMAP_MBOX_SUFFIX))) + siblingMailboxDirPath.SetLength(siblingMailboxDirPath.Length()-9); + else if (StringEndsWith(siblingMailboxDirPath, NS_LITERAL_STRING(POP_MBOX_SUFFIX))) + siblingMailboxDirPath.SetLength(siblingMailboxDirPath.Length()-5); + + IMPORT_LOG1("trying to locate a '%s'", NS_ConvertUTF16toUTF8(siblingMailboxDirPath).get()); + nsCOMPtr<nsIFile> siblingMailboxDir(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + continue; + + rv = siblingMailboxDir->InitWithPath(siblingMailboxDirPath); + bool reallyExists = false; + siblingMailboxDir->Exists(&reallyExists); + + if (NS_SUCCEEDED(rv) && reallyExists) { + IMPORT_LOG1("Found what looks like an .mbox container: %s", NS_ConvertUTF16toUTF8(currentFolderName).get()); + + // traverse this folder for other .mboxes + mCurDepth++; + FindMboxDirs(siblingMailboxDir, aMailboxDescs, aImportService); + mCurDepth--; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAppleMailImportMail::ImportMailbox(nsIImportMailboxDescriptor *aMailbox, + nsIMsgFolder *aDstFolder, + char16_t **aErrorLog, + char16_t **aSuccessLog, bool *aFatalError) +{ + nsAutoString errorLog, successLog; + + // reset progress + mProgress = 0; + + nsAutoString mailboxName; + aMailbox->GetDisplayName(getter_Copies(mailboxName)); + + nsCOMPtr<nsIFile> mboxFolder; + nsresult rv = aMailbox->GetFile(getter_AddRefs(mboxFolder)); + if (NS_FAILED(rv) || !mboxFolder) { + ReportStatus(u"ApplemailImportMailboxConverterror", mailboxName, errorLog); + SetLogs(successLog, errorLog, aSuccessLog, aErrorLog); + return NS_ERROR_FAILURE; + } + + // if we're an account mailbox, nothing do. if we're a real mbox + // then we've got some messages to import! + uint32_t mailboxIdentifier; + aMailbox->GetIdentifier(&mailboxIdentifier); + + if (mailboxIdentifier != kAccountMailboxID) { + // move to the .mbox's Messages folder + nsCOMPtr<nsIFile> messagesFolder; + mboxFolder->Clone(getter_AddRefs(messagesFolder)); + rv = messagesFolder->Append(NS_LITERAL_STRING("Messages")); + if (NS_FAILED(rv)) { + // even if there are no messages, it might still be a valid mailbox, or even + // a parent for other mailboxes. + // + // just indicate that we're done, using the same number that we used to estimate + // number of messages earlier. + uint32_t finalSize; + aMailbox->GetSize(&finalSize); + mProgress = finalSize; + + // report that we successfully imported this mailbox + ReportStatus(u"ApplemailImportMailboxSuccess", mailboxName, successLog); + SetLogs(successLog, errorLog, aSuccessLog, aErrorLog); + return NS_OK; + } + + // let's import the messages! + nsCOMPtr<nsISimpleEnumerator> directoryEnumerator; + rv = messagesFolder->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + if (NS_FAILED(rv)) { + ReportStatus(u"ApplemailImportMailboxConvertError", mailboxName, errorLog); + SetLogs(successLog, errorLog, aSuccessLog, aErrorLog); + return NS_ERROR_FAILURE; + } + + // prepare an outstream to the destination file + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = aDstFolder->GetMsgStore(getter_AddRefs(msgStore)); + if (!msgStore || NS_FAILED(rv)) { + ReportStatus(u"ApplemailImportMailboxConverterror", mailboxName, errorLog); + SetLogs(successLog, errorLog, aSuccessLog, aErrorLog); + return NS_ERROR_FAILURE; + } + + bool hasMore = false; + nsCOMPtr<nsIOutputStream> outStream; + + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && hasMore) { + // get the next file entry + nsCOMPtr<nsIFile> currentEntry; + { + nsCOMPtr<nsISupports> rawSupports; + directoryEnumerator->GetNext(getter_AddRefs(rawSupports)); + if (!rawSupports) + continue; + currentEntry = do_QueryInterface(rawSupports); + if (!currentEntry) + continue; + } + + // make sure it's an .emlx file + bool isFile = false; + currentEntry->IsFile(&isFile); + if (!isFile) + continue; + + nsAutoString leafName; + currentEntry->GetLeafName(leafName); + if (!StringEndsWith(leafName, NS_LITERAL_STRING(".emlx"))) + continue; + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + bool reusable; + rv = msgStore->GetNewMsgOutputStream(aDstFolder, getter_AddRefs(msgHdr), + &reusable, + getter_AddRefs(outStream)); + if (NS_FAILED(rv)) + break; + + // add the data to the mbox stream + if (NS_SUCCEEDED(nsEmlxHelperUtils::AddEmlxMessageToStream(currentEntry, outStream))) { + mProgress++; + msgStore->FinishNewMessage(outStream, msgHdr); + } + else { + msgStore->DiscardNewMessage(outStream, msgHdr); + break; + } + if (!reusable) + outStream->Close(); + } + if (outStream) + outStream->Close(); + } + // just indicate that we're done, using the same number that we used to estimate + // number of messages earlier. + uint32_t finalSize; + aMailbox->GetSize(&finalSize); + mProgress = finalSize; + + // report that we successfully imported this mailbox + ReportStatus(u"ApplemailImportMailboxSuccess", mailboxName, successLog); + SetLogs(successLog, errorLog, aSuccessLog, aErrorLog); + + return NS_OK; +} + +void nsAppleMailImportMail::ReportStatus(const char16_t* aErrorName, nsString &aName, + nsAString &aStream) +{ + // get (and format, if needed) the error string from the bundle + nsAutoString outString; + const char16_t *fmt = { aName.get() }; + nsresult rv = mBundle->FormatStringFromName(aErrorName, &fmt, 1, getter_Copies(outString)); + // write it out the stream + if (NS_SUCCEEDED(rv)) { + aStream.Append(outString); + aStream.Append(char16_t('\n')); + } +} + +void nsAppleMailImportMail::SetLogs(const nsAString &aSuccess, const nsAString &aError, char16_t **aOutSuccess, char16_t **aOutError) +{ + if (aOutError && !*aOutError) + *aOutError = ToNewUnicode(aError); + if (aOutSuccess && !*aOutSuccess) + *aOutSuccess = ToNewUnicode(aSuccess); +} + +NS_IMETHODIMP nsAppleMailImportMail::GetImportProgress(uint32_t *aDoneSoFar) +{ + NS_ENSURE_ARG_POINTER(aDoneSoFar); + *aDoneSoFar = mProgress; + return NS_OK; +} + +NS_IMETHODIMP nsAppleMailImportMail::TranslateFolderName(const nsAString &aFolderName, nsAString &aResult) +{ + aResult = aFolderName; + return NS_OK; +} diff --git a/mailnews/import/applemail/src/nsAppleMailImport.h b/mailnews/import/applemail/src/nsAppleMailImport.h new file mode 100644 index 000000000..b906aecf5 --- /dev/null +++ b/mailnews/import/applemail/src/nsAppleMailImport.h @@ -0,0 +1,78 @@ +/* -*- 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 nsAppleMailImport_h___ +#define nsAppleMailImport_h___ + +#include "mozilla/Logging.h" +#include "nsIImportModule.h" +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" +#include "nsIImportMail.h" + +// logging facilities +extern PRLogModuleInfo *APPLEMAILLOGMODULE; + +#define IMPORT_LOG0(x) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (x)) +#define IMPORT_LOG1(x, y) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (x, y)) +#define IMPORT_LOG2(x, y, z) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (x, y, z)) +#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d)) + +#define NS_APPLEMAILIMPL_CID \ +{ 0x9117a1ea, 0xe012, 0x43b5, { 0xa0, 0x20, 0xcb, 0x8a, 0x66, 0xcc, 0x09, 0xe1 } } + +#define NS_APPLEMAILIMPORT_CID \ +{ 0x6d3f101c, 0x70ec, 0x4e04, { 0xb6, 0x8d, 0x99, 0x08, 0xd1, 0xae, 0xdd, 0xf3 } } + +#define NS_APPLEMAILIMPL_CONTRACTID "@mozilla.org/import/import-appleMailImpl;1" + +#define kAppleMailSupportsString "mail" + +class nsIImportService; +class nsIMutableArray; + +class nsAppleMailImportModule : public nsIImportModule +{ + public: + + nsAppleMailImportModule(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMPORTMODULE + + private: + virtual ~nsAppleMailImportModule(); + + nsCOMPtr<nsIStringBundle> mBundle; +}; + +class nsAppleMailImportMail : public nsIImportMail +{ + public: + + nsAppleMailImportMail(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMPORTMAIL + + nsresult Initialize(); + + private: + virtual ~nsAppleMailImportMail(); + + void FindAccountMailDirs(nsIFile *aRoot, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService); + nsresult FindMboxDirs(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService); + nsresult AddMboxDir(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService); + + // aInfoString is the format to a "foo %s" string. It may be NULL if the error string needs no such format. + void ReportStatus(const char16_t* aErrorName, nsString &aName, nsAString &aStream); + static void SetLogs(const nsAString& success, const nsAString& error, char16_t **aOutErrorLog, char16_t **aSuccessLog); + + nsCOMPtr<nsIStringBundle> mBundle; + uint32_t mProgress; + uint16_t mCurDepth; +}; + +#endif /* nsAppleMailImport_h___ */ diff --git a/mailnews/import/applemail/src/nsEmlxHelperUtils.h b/mailnews/import/applemail/src/nsEmlxHelperUtils.h new file mode 100644 index 000000000..728b725b6 --- /dev/null +++ b/mailnews/import/applemail/src/nsEmlxHelperUtils.h @@ -0,0 +1,55 @@ +/* -*- 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 nsEmlxHelperUtils_h___ +#define nsEmlxHelperUtils_h___ + +#include "nscore.h" +#include "nsStringGlue.h" + +class nsIOutputStream; +class nsIFile; + +class nsEmlxHelperUtils { + /* All emlx messages have a "flags" number in the metadata. + These are the masks to decode that, found via http://jwz.livejournal.com/505711.html */ + enum EmlxMetadataMask { + kRead = 1 << 0, // read + // 1 << 1, // deleted + kAnswered = 1 << 2, // answered + // 1 << 3, // encrypted + kFlagged = 1 << 4, // flagged + // 1 << 5, // recent + // 1 << 6, // draft + // 1 << 7, // initial (no longer used) + kForwarded = 1 << 8, // forwarded + // 1 << 9, // redirected + // 3F << 10, // attachment count (6 bits) + // 7F << 16, // priority level (7 bits) + // 1 << 23, // signed + // 1 << 24, // is junk + // 1 << 25, // is not junk + // 1 << 26, // font size delta 7 (3 bits) + // 1 << 29, // junk mail level recorded + // 1 << 30, // highlight text in toc + // 1 << 31 // (unused) + }; + + // This method will scan the raw EMLX message buffer for "dangerous" so-called "From-lines" that we need to escape. + // If it needs to modify any lines, it will return a non-NULL aOutBuffer. If aOutBuffer is NULL, no modification needed + // to be made. + static nsresult ConvertToMboxRD(const char *aMessageBufferStart, const char *aMessageBufferEnd, nsCString &aOutBuffer); + + // returns an int representing the X-Mozilla-Status flags set (e.g. "read", "flagged") converted from EMLX flags. + static nsresult ConvertToMozillaStatusFlags(const char *aXMLBufferStart, const char *aXMLBufferEnd, uint32_t *aMozillaStatusFlags); + + public: + + // add an .emlx message to the mbox output + static nsresult AddEmlxMessageToStream(nsIFile *aEmlxFile, nsIOutputStream *aOutoutStream); + +}; + +#endif // nsEmlxHelperUtils_h___ diff --git a/mailnews/import/applemail/src/nsEmlxHelperUtils.mm b/mailnews/import/applemail/src/nsEmlxHelperUtils.mm new file mode 100644 index 000000000..d2feb166e --- /dev/null +++ b/mailnews/import/applemail/src/nsEmlxHelperUtils.mm @@ -0,0 +1,240 @@ +/* -*- 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 "nsEmlxHelperUtils.h" +#include "nsIFileStreams.h" +#include "nsIBufferedStreams.h" +#include "nsIOutputStream.h" +#include "nsNetUtil.h" +#include "nsCOMPtr.h" +#include "nsObjCExceptions.h" +#include "nsMsgMessageFlags.h" +#include "nsMsgLocalFolderHdrs.h" +#include "msgCore.h" +#include "nsTArray.h" +#include "nsAppleMailImport.h" +#include "prprf.h" +#include "nsIFile.h" + +#import <Cocoa/Cocoa.h> + + +nsresult nsEmlxHelperUtils::ConvertToMozillaStatusFlags(const char *aXMLBufferStart, + const char *aXMLBufferEnd, + uint32_t *aMozillaStatusFlags) +{ + // create a NSData wrapper around the buffer, so we can use the Cocoa call below + NSData *metadata = + [[[NSData alloc] initWithBytesNoCopy:(void *)aXMLBufferStart length:(aXMLBufferEnd-aXMLBufferStart) freeWhenDone:NO] autorelease]; + + // get the XML data as a dictionary + NSPropertyListFormat format; + id plist = [NSPropertyListSerialization propertyListWithData:metadata + options:NSPropertyListImmutable + format:&format + error:NULL]; + + if (!plist) + return NS_ERROR_FAILURE; + + // find the <flags>...</flags> value and convert to int + const uint32_t emlxMessageFlags = [[(NSDictionary *)plist objectForKey:@"flags"] intValue]; + + if (emlxMessageFlags == 0) + return NS_ERROR_FAILURE; + + if (emlxMessageFlags & nsEmlxHelperUtils::kRead) + *aMozillaStatusFlags |= nsMsgMessageFlags::Read; + if (emlxMessageFlags & nsEmlxHelperUtils::kForwarded) + *aMozillaStatusFlags |= nsMsgMessageFlags::Forwarded; + if (emlxMessageFlags & nsEmlxHelperUtils::kAnswered) + *aMozillaStatusFlags |= nsMsgMessageFlags::Replied; + if (emlxMessageFlags & nsEmlxHelperUtils::kFlagged) + *aMozillaStatusFlags |= nsMsgMessageFlags::Marked; + + return NS_OK; +} + +nsresult nsEmlxHelperUtils::ConvertToMboxRD(const char *aMessageBufferStart, const char *aMessageBufferEnd, nsCString &aOutBuffer) +{ + nsTArray<const char *> foundFromLines; + + const char *cur = aMessageBufferStart; + while (cur < aMessageBufferEnd) { + + const char *foundFromStr = strnstr(cur, "From ", aMessageBufferEnd-cur); + + if (foundFromStr) { + // skip all prepending '>' chars + const char *fromLineStart = foundFromStr; + while (fromLineStart-- >= aMessageBufferStart) { + if (*fromLineStart == '\n' || fromLineStart == aMessageBufferStart) { + if (fromLineStart > aMessageBufferStart) + fromLineStart++; + foundFromLines.AppendElement(fromLineStart); + break; + } + else if (*fromLineStart != '>') + break; + } + + // advance past the last found From string. + cur = foundFromStr + 5; + + // look for more From lines. + continue; + } + + break; + } + + // go through foundFromLines + if (foundFromLines.Length()) { + + const char *chunkStart = aMessageBufferStart; + for (unsigned i=0; i<foundFromLines.Length(); ++i) { + aOutBuffer.Append(chunkStart, (foundFromLines[i]-chunkStart)); + aOutBuffer.Append(NS_LITERAL_CSTRING(">")); + + chunkStart = foundFromLines[i]; + } + aOutBuffer.Append(chunkStart, (aMessageBufferEnd - chunkStart)); + } + + return NS_OK; +} + +nsresult nsEmlxHelperUtils::AddEmlxMessageToStream(nsIFile *aMessage, nsIOutputStream *aOut) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + // needed to be sure autoreleased objects are released too, which they might not + // in a C++ environment where the main event loop has no autorelease pool (e.g on a XPCOM thread) + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + nsresult rv = NS_ERROR_FAILURE; + + nsAutoCString path; + aMessage->GetNativePath(path); + + NSData *data = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:path.get()]]; + if (!data) { + [pool release]; + return NS_ERROR_FAILURE; + } + + char *startOfMessageData = NULL; + uint32_t actualBytesWritten = 0; + + // The anatomy of an EMLX file: + // + // ------------------------------- + // < A number describing how many bytes ahead there is message data > + // < Message data > + // < XML metadata for this message > + // ------------------------------- + + // read the first line of the emlx file, which is a number of how many bytes ahead the actual + // message data is. + uint64_t numberOfBytesToRead = strtol((char *)[data bytes], &startOfMessageData, 10); + if (numberOfBytesToRead <= 0 || !startOfMessageData) { + [pool release]; + return NS_ERROR_FAILURE; + } + + // skip whitespace + while (*startOfMessageData == ' ' || + *startOfMessageData == '\n' || + *startOfMessageData == '\r' || + *startOfMessageData == '\t') + ++startOfMessageData; + + NS_NAMED_LITERAL_CSTRING(kBogusFromLine, "From \n"); + NS_NAMED_LITERAL_CSTRING(kEndOfMessage, "\n\n"); + + // write the bogus "From " line which is a magic separator in the mbox format + rv = aOut->Write(kBogusFromLine.get(), kBogusFromLine.Length(), &actualBytesWritten); + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + // now read the XML metadata, so we can extract info like which flags (read? replied? flagged? etc) this message has. + const char *startOfXMLMetadata = startOfMessageData + numberOfBytesToRead; + const char *endOfXMLMetadata = (char *)[data bytes] + [data length]; + + uint32_t x_mozilla_flags = 0; + ConvertToMozillaStatusFlags(startOfXMLMetadata, endOfXMLMetadata, &x_mozilla_flags); + + // write the X-Mozilla-Status header according to which flags we've gathered above. + uint32_t dummyRv; + nsAutoCString buf(PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, x_mozilla_flags)); + NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status header"); + if (buf.IsEmpty()) { + [pool release]; + return rv; + } + + rv = aOut->Write(buf.get(), buf.Length(), &dummyRv); + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + // write out X-Mozilla-Keywords header as well to reserve some space for it + // in the mbox file. + rv = aOut->Write(X_MOZILLA_KEYWORDS, X_MOZILLA_KEYWORDS_LEN, &dummyRv); + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + // write out empty X-Mozilla_status2 header + buf.Adopt(PR_smprintf(X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, 0)); + NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status2 header"); + if (buf.IsEmpty()) { + [pool release]; + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = aOut->Write(buf.get(), buf.Length(), &dummyRv); + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + // do any conversion needed for the mbox data to be valid mboxrd. + nsCString convertedData; + rv = ConvertToMboxRD(startOfMessageData, (startOfMessageData + numberOfBytesToRead), convertedData); + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + // write the actual message data. + if (convertedData.IsEmpty()) + rv = aOut->Write(startOfMessageData, (uint32_t)numberOfBytesToRead, &actualBytesWritten); + else { + IMPORT_LOG1("Escaped From-lines in %s!", path.get()); + rv = aOut->Write(convertedData.get(), convertedData.Length(), &actualBytesWritten); + } + + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + NS_ASSERTION(actualBytesWritten == (convertedData.IsEmpty() ? numberOfBytesToRead : convertedData.Length()), + "Didn't write as many bytes as expected for .emlx file?"); + + // add newlines to denote the end of this message in the mbox + rv = aOut->Write(kEndOfMessage.get(), kEndOfMessage.Length(), &actualBytesWritten); + + [pool release]; + + return rv; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} diff --git a/mailnews/import/becky/src/moz.build b/mailnews/import/becky/src/moz.build new file mode 100644 index 000000000..ce7651aa8 --- /dev/null +++ b/mailnews/import/becky/src/moz.build @@ -0,0 +1,16 @@ +# 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/. + +UNIFIED_SOURCES += [ + 'nsBeckyAddressBooks.cpp', + 'nsBeckyFilters.cpp', + 'nsBeckyImport.cpp', + 'nsBeckyMail.cpp', + 'nsBeckySettings.cpp', + 'nsBeckyStringBundle.cpp', + 'nsBeckyUtils.cpp', +] + +FINAL_LIBRARY = 'import' diff --git a/mailnews/import/becky/src/nsBeckyAddressBooks.cpp b/mailnews/import/becky/src/nsBeckyAddressBooks.cpp new file mode 100644 index 000000000..86654ac0e --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyAddressBooks.cpp @@ -0,0 +1,383 @@ +/* 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 "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIFile.h" +#include "nsISimpleEnumerator.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIMutableArray.h" +#include "nsStringGlue.h" +#include "nsAbBaseCID.h" +#include "nsIAbManager.h" +#include "nsIImportService.h" +#include "nsIImportABDescriptor.h" +#include "nsMsgUtils.h" +#include "nsIStringBundle.h" +#include "nsVCardAddress.h" + +#include "nsBeckyAddressBooks.h" +#include "nsBeckyStringBundle.h" +#include "nsBeckyUtils.h" + +NS_IMPL_ISUPPORTS(nsBeckyAddressBooks, nsIImportAddressBooks) + +nsresult +nsBeckyAddressBooks::Create(nsIImportAddressBooks **aImport) +{ + NS_ENSURE_ARG_POINTER(aImport); + + *aImport = new nsBeckyAddressBooks(); + + NS_ADDREF(*aImport); + return NS_OK; +} + +nsBeckyAddressBooks::nsBeckyAddressBooks() +: mReadBytes(0) +{ +} + +nsBeckyAddressBooks::~nsBeckyAddressBooks() +{ +} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetSupportsMultiple(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetAutoFind(char16_t **aDescription, + bool *_retval) +{ + NS_ENSURE_ARG_POINTER(aDescription); + NS_ENSURE_ARG_POINTER(_retval); + + *aDescription = + nsBeckyStringBundle::GetStringByName(u"BeckyImportDescription"); + *_retval = false; + + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetNeedsFieldMap(nsIFile *aLocation, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = false; + return NS_OK; +} + +nsresult +nsBeckyAddressBooks::FindAddressBookDirectory(nsIFile **aAddressBookDirectory) +{ + nsCOMPtr<nsIFile> userDirectory; + nsresult rv = nsBeckyUtils::FindUserDirectory(getter_AddRefs(userDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = userDirectory->Append(NS_LITERAL_STRING("AddrBook")); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + rv = userDirectory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + + bool isDirectory = false; + rv = userDirectory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (!isDirectory) + return NS_ERROR_FILE_NOT_FOUND; + + userDirectory.forget(aAddressBookDirectory); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetDefaultLocation(nsIFile **aLocation, + bool *aFound, + bool *aUserVerify) +{ + NS_ENSURE_ARG_POINTER(aFound); + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(aUserVerify); + + *aLocation = nullptr; + *aFound = false; + *aUserVerify = true; + + if (NS_SUCCEEDED(nsBeckyAddressBooks::FindAddressBookDirectory(aLocation))) { + *aFound = true; + *aUserVerify = false; + } + + return NS_OK; +} + +nsresult +nsBeckyAddressBooks::CreateAddressBookDescriptor(nsIImportABDescriptor **aDescriptor) +{ + nsresult rv; + nsCOMPtr<nsIImportService> importService = do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return importService->CreateNewABDescriptor(aDescriptor); +} + +bool +nsBeckyAddressBooks::IsAddressBookFile(nsIFile *aFile) +{ + if (!aFile) + return false; + + nsresult rv; + bool isFile = false; + rv = aFile->IsFile(&isFile); + if (NS_FAILED(rv) && !isFile) + return false; + + nsAutoString name; + rv = aFile->GetLeafName(name); + return StringEndsWith(name, NS_LITERAL_STRING(".bab")); +} + +bool +nsBeckyAddressBooks::HasAddressBookFile(nsIFile *aDirectory) +{ + if (!aDirectory) + return false; + + nsresult rv; + bool isDirectory = false; + rv = aDirectory->IsDirectory(&isDirectory); + if (NS_FAILED(rv) || !isDirectory) + return false; + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, false); + + bool more; + nsCOMPtr<nsISupports> entry; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv); + NS_ENSURE_SUCCESS(rv, false); + if (IsAddressBookFile(file)) + return true; + } + + return false; +} + +uint32_t +nsBeckyAddressBooks::CountAddressBookSize(nsIFile *aDirectory) +{ + if (!aDirectory) + return 0; + + nsresult rv; + bool isDirectory = false; + rv = aDirectory->IsDirectory(&isDirectory); + if (NS_FAILED(rv) || !isDirectory) + return 0; + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, 0); + + uint32_t total = 0; + bool more; + nsCOMPtr<nsISupports> entry; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, 0); + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv); + NS_ENSURE_SUCCESS(rv, 0); + + int64_t size; + file->GetFileSize(&size); + if (total + size > std::numeric_limits<uint32_t>::max()) + return std::numeric_limits<uint32_t>::max(); + + total += static_cast<uint32_t>(size); + } + + return total; +} + +nsresult +nsBeckyAddressBooks::AppendAddressBookDescriptor(nsIFile *aEntry, + nsIMutableArray *aCollected) +{ + NS_ENSURE_ARG_POINTER(aCollected); + + if (!HasAddressBookFile(aEntry)) + return NS_OK; + + nsresult rv; + nsCOMPtr<nsIImportABDescriptor> descriptor; + rv = CreateAddressBookDescriptor(getter_AddRefs(descriptor)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t size = CountAddressBookSize(aEntry); + descriptor->SetSize(size); + descriptor->SetAbFile(aEntry); + + nsAutoString name; + aEntry->GetLeafName(name); + descriptor->SetPreferredName(name); + + return aCollected->AppendElement(descriptor, false); +} + +nsresult +nsBeckyAddressBooks::CollectAddressBooks(nsIFile *aTarget, + nsIMutableArray *aCollected) +{ + nsresult rv = AppendAddressBookDescriptor(aTarget, aCollected); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = aTarget->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more; + nsCOMPtr<nsISupports> entry; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDirectory = false; + rv = file->IsDirectory(&isDirectory); + if (NS_SUCCEEDED(rv) && isDirectory) + rv = CollectAddressBooks(file, aCollected); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::FindAddressBooks(nsIFile *aLocation, + nsIArray **_retval) +{ + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv; + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDirectory = false; + rv = aLocation->IsDirectory(&isDirectory); + if (NS_FAILED(rv) || !isDirectory) + return NS_ERROR_FAILURE; + + rv = CollectAddressBooks(aLocation, array); + NS_ENSURE_SUCCESS(rv, rv); + + array.forget(_retval); + + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::InitFieldMap(nsIImportFieldMap *aFieldMap) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::ImportAddressBook(nsIImportABDescriptor *aSource, + nsIAddrDatabase *aDestination, + nsIImportFieldMap *aFieldMap, + nsISupports *aSupportService, + 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; + + nsCOMPtr<nsIFile> file; + nsresult rv = aSource->GetAbFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = file->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more; + nsCOMPtr<nsISupports> entry; + nsAutoString error; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (!IsAddressBookFile(file)) + continue; + + bool aborted = false; + nsAutoString name; + aSource->GetPreferredName(name); + nsVCardAddress vcard; + rv = vcard.ImportAddresses(&aborted, name.get(), file, aDestination, error, &mReadBytes); + if (NS_FAILED(rv)) { + break; + } + } + + if (!error.IsEmpty()) + *aErrorLog = ToNewUnicode(error); + else + *aSuccessLog = nsBeckyStringBundle::GetStringByName(u"BeckyImportAddressSuccess"); + + return rv; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetImportProgress(uint32_t *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mReadBytes; + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::SetSampleLocation(nsIFile *aLocation) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetSampleData(int32_t aRecordNumber, + bool *aRecordExists, + char16_t **_retval) +{ + return NS_ERROR_FAILURE; +} + diff --git a/mailnews/import/becky/src/nsBeckyAddressBooks.h b/mailnews/import/becky/src/nsBeckyAddressBooks.h new file mode 100644 index 000000000..83eb4c895 --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyAddressBooks.h @@ -0,0 +1,35 @@ +/* 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/. */ + +#ifndef nsBeckyAddressBooks_h___ +#define nsBeckyAddressBooks_h___ + +#include "nsIImportAddressBooks.h" + +class nsBeckyAddressBooks final : public nsIImportAddressBooks +{ +public: + nsBeckyAddressBooks(); + static nsresult Create(nsIImportAddressBooks **aImport); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTADDRESSBOOKS + +private: + virtual ~nsBeckyAddressBooks(); + + uint32_t mReadBytes; + + nsresult CollectAddressBooks(nsIFile *aTarget, nsIMutableArray *aCollected); + nsresult FindAddressBookDirectory(nsIFile **aAddressBookDirectory); + nsresult AppendAddressBookDescriptor(nsIFile *aEntry, + nsIMutableArray *aCollected); + uint32_t CountAddressBookSize(nsIFile *aDirectory); + bool HasAddressBookFile(nsIFile *aDirectory); + bool IsAddressBookFile(nsIFile *aFile); + nsresult CreateAddressBookDescriptor(nsIImportABDescriptor **aDescriptor); +}; + +#endif /* nsBeckyAddressBooks_h___ */ diff --git a/mailnews/import/becky/src/nsBeckyFilters.cpp b/mailnews/import/becky/src/nsBeckyFilters.cpp new file mode 100644 index 000000000..517a18014 --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyFilters.cpp @@ -0,0 +1,793 @@ +/* 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 "nsArrayUtils.h" +#include "nsILineInputStream.h" +#include "nsIStringBundle.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIMsgFilter.h" +#include "nsIMsgFilterList.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAccount.h" +#include "nsIMsgSearchTerm.h" +#include "nsIMsgFolder.h" +#include "nsCOMPtr.h" +#include "nsMsgSearchCore.h" +#include "nsMsgBaseCID.h" +#include "nsMsgUtils.h" +#include "msgCore.h" + +#include "nsBeckyFilters.h" +#include "nsBeckyStringBundle.h" +#include "nsBeckyUtils.h" + +NS_IMPL_ISUPPORTS(nsBeckyFilters, nsIImportFilters) + +nsresult +nsBeckyFilters::Create(nsIImportFilters **aImport) +{ + NS_ENSURE_ARG_POINTER(aImport); + + *aImport = new nsBeckyFilters(); + + NS_ADDREF(*aImport); + return NS_OK; +} + +nsBeckyFilters::nsBeckyFilters() +: mLocation(nullptr), + mServer(nullptr), + mConvertedFile(nullptr) +{ +} + +nsBeckyFilters::~nsBeckyFilters() +{ +} + +nsresult +nsBeckyFilters::GetDefaultFilterLocation(nsIFile **aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + + nsresult rv; + nsCOMPtr<nsIFile> filterDir; + rv = nsBeckyUtils::GetDefaultMailboxDirectory(getter_AddRefs(filterDir)); + NS_ENSURE_SUCCESS(rv, rv); + + filterDir.forget(aFile); + return NS_OK; +} + +nsresult +nsBeckyFilters::GetFilterFile(bool aIncoming, nsIFile *aLocation, nsIFile **aFile) +{ + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(aFile); + + // We assume the caller has already checked that aLocation is a directory, + // otherwise it would not make sense to call us. + + nsresult rv; + nsCOMPtr<nsIFile> filter; + aLocation->Clone(getter_AddRefs(filter)); + if (aIncoming) + rv = filter->Append(NS_LITERAL_STRING("IFilter.def")); + else + rv = filter->Append(NS_LITERAL_STRING("OFilter.def")); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + rv = filter->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + + filter.forget(aFile); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyFilters::AutoLocate(char16_t **aDescription, + nsIFile **aLocation, + bool *_retval) +{ + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(_retval); + + if (aDescription) { + *aDescription = + nsBeckyStringBundle::GetStringByName(u"BeckyImportDescription"); + } + *aLocation = nullptr; + *_retval = false; + + nsresult rv; + nsCOMPtr<nsIFile> location; + rv = GetDefaultFilterLocation(getter_AddRefs(location)); + if (NS_FAILED(rv)) + location = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + else + *_retval = true; + + location.forget(aLocation); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyFilters::SetLocation(nsIFile *aLocation) +{ + NS_ENSURE_ARG_POINTER(aLocation); + + bool exists = false; + nsresult rv = aLocation->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + + mLocation = aLocation; + return NS_OK; +} + +static nsMsgSearchAttribValue +ConvertSearchKeyToAttrib(const nsACString &aKey) +{ + if (aKey.EqualsLiteral("From") || + aKey.EqualsLiteral("Sender") || + aKey.EqualsLiteral("From, Sender, X-Sender")) { + return nsMsgSearchAttrib::Sender; + } else if (aKey.EqualsLiteral("Subject")) { + return nsMsgSearchAttrib::Subject; + } else if (aKey.EqualsLiteral("[body]")) { + return nsMsgSearchAttrib::Body; + } else if (aKey.EqualsLiteral("Date")) { + return nsMsgSearchAttrib::Date; + } else if (aKey.EqualsLiteral("To")) { + return nsMsgSearchAttrib::To; + } else if (aKey.EqualsLiteral("Cc")) { + return nsMsgSearchAttrib::CC; + } else if (aKey.EqualsLiteral("To, Cc, Bcc:")) { + return nsMsgSearchAttrib::ToOrCC; + } + return -1; +} + +static nsMsgSearchOpValue +ConvertSearchFlagsToOperator(const nsACString &aFlags) +{ + nsCString flags(aFlags); + int32_t lastTabPosition = flags.RFindChar('\t'); + if ((lastTabPosition == -1) || + ((int32_t)aFlags.Length() == lastTabPosition - 1)) { + return -1; + } + + switch (aFlags.CharAt(0)) { + case 'X': + return nsMsgSearchOp::DoesntContain; + case 'O': + if (aFlags.FindChar('T', lastTabPosition + 1) >= 0) + return nsMsgSearchOp::BeginsWith; + return nsMsgSearchOp::Contains; + default: + return -1; + } +} + +nsresult +nsBeckyFilters::ParseRuleLine(const nsCString &aLine, + nsMsgSearchAttribValue *aSearchAttribute, + nsMsgSearchOpValue *aSearchOperator, + nsString &aSearchKeyword) +{ + int32_t firstColonPosition = aLine.FindChar(':'); + if (firstColonPosition == -1 || + (int32_t)aLine.Length() == firstColonPosition - 1) { + return NS_ERROR_FAILURE; + } + + int32_t secondColonPosition = aLine.FindChar(':', firstColonPosition + 1); + if (secondColonPosition == -1 || + (int32_t)aLine.Length() == secondColonPosition - 1) { + return NS_ERROR_FAILURE; + } + + int32_t length = secondColonPosition - firstColonPosition - 1; + nsMsgSearchAttribValue searchAttribute; + searchAttribute = ConvertSearchKeyToAttrib(Substring(aLine, firstColonPosition + 1, length)); + if (searchAttribute < 0) + return NS_ERROR_FAILURE; + + int32_t tabPosition = aLine.FindChar('\t'); + if (tabPosition == -1 || + (int32_t)aLine.Length() == tabPosition - 1) { + return NS_ERROR_FAILURE; + } + + nsMsgSearchOpValue searchOperator; + searchOperator = ConvertSearchFlagsToOperator(Substring(aLine, tabPosition + 1)); + if (searchOperator < 0) + return NS_ERROR_FAILURE; + + *aSearchOperator = searchOperator; + *aSearchAttribute = searchAttribute; + length = tabPosition - secondColonPosition - 1; + CopyUTF8toUTF16(Substring(aLine, secondColonPosition + 1, length), aSearchKeyword); + return NS_OK; +} + +nsresult +nsBeckyFilters::SetSearchTerm(const nsCString &aLine, nsIMsgFilter *aFilter) +{ + NS_ENSURE_ARG_POINTER(aFilter); + + nsresult rv; + nsMsgSearchAttribValue searchAttribute = -1; + nsMsgSearchOpValue searchOperator = -1; + nsAutoString searchKeyword; + rv = ParseRuleLine(aLine, &searchAttribute, &searchOperator, searchKeyword); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgSearchTerm> term; + rv = aFilter->CreateTerm(getter_AddRefs(term)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = term->SetAttrib(searchAttribute); + NS_ENSURE_SUCCESS(rv, rv); + rv = term->SetOp(searchOperator); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgSearchValue> value; + rv = term->GetValue(getter_AddRefs(value)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = value->SetAttrib(searchAttribute); + NS_ENSURE_SUCCESS(rv, rv); + rv = value->SetStr(searchKeyword); + NS_ENSURE_SUCCESS(rv, rv); + rv = term->SetValue(value); + NS_ENSURE_SUCCESS(rv, rv); + rv = term->SetBooleanAnd(false); + NS_ENSURE_SUCCESS(rv, rv); + + if (!searchKeyword.IsEmpty()) + rv = aFilter->SetFilterName(searchKeyword); + else + rv = aFilter->SetFilterName(NS_LITERAL_STRING("No name")); + NS_ENSURE_SUCCESS(rv, rv); + + return aFilter->AppendTerm(term); +} + +nsresult +nsBeckyFilters::CreateRuleAction(nsIMsgFilter *aFilter, + nsMsgRuleActionType actionType, + nsIMsgRuleAction **_retval) +{ + nsresult rv; + nsCOMPtr<nsIMsgRuleAction> action; + rv = aFilter->CreateAction(getter_AddRefs(action)); + NS_ENSURE_SUCCESS(rv, rv); + rv = action->SetType(actionType); + NS_ENSURE_SUCCESS(rv, rv); + + action.forget(_retval); + + return NS_OK; +} + +nsresult +nsBeckyFilters::GetActionTarget(const nsCString &aLine, + nsCString &aTarget) +{ + int32_t firstColonPosition = aLine.FindChar(':'); + if (firstColonPosition < -1 || + aLine.Length() == static_cast<uint32_t>(firstColonPosition)) { + return NS_ERROR_FAILURE; + } + + aTarget.Assign(Substring(aLine, firstColonPosition + 1)); + + return NS_OK; +} + +nsresult +nsBeckyFilters::GetResendTarget(const nsCString &aLine, + nsCString &aTemplate, + nsCString &aTargetAddress) +{ + nsresult rv; + nsAutoCString target; + rv = GetActionTarget(aLine, target); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t asteriskPosition = target.FindChar('*'); + if (asteriskPosition < 0) { + aTemplate.Assign(target); + return NS_OK; + } + + if (target.Length() == static_cast<uint32_t>(asteriskPosition)) + return NS_ERROR_FAILURE; + + aTemplate.Assign(StringHead(target, asteriskPosition - 1)); + aTargetAddress.Assign(Substring(target, asteriskPosition + 1)); + + return NS_OK; +} + +nsresult +nsBeckyFilters::CreateResendAction(const nsCString &aLine, + nsIMsgFilter *aFilter, + const nsMsgRuleActionType &aActionType, + nsIMsgRuleAction **_retval) +{ + nsresult rv; + nsCOMPtr<nsIMsgRuleAction> action; + rv = CreateRuleAction(aFilter, aActionType, getter_AddRefs(action)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString templateString; + nsAutoCString targetAddress; + rv = GetResendTarget(aLine, templateString, targetAddress); + NS_ENSURE_SUCCESS(rv, rv); + + if (aActionType == nsMsgFilterAction::Forward) + rv = action->SetStrValue(targetAddress); + else + rv = action->SetStrValue(templateString); + NS_ENSURE_SUCCESS(rv, rv); + + action.forget(_retval); + + return NS_OK; +} + +nsresult +nsBeckyFilters::GetFolderNameFromTarget(const nsCString &aTarget, nsAString &aName) +{ + int32_t backslashPosition = aTarget.RFindChar('\\'); + if (backslashPosition > 0) { + NS_ConvertUTF8toUTF16 utf16String(Substring(aTarget, backslashPosition + 1)); + nsBeckyUtils::TranslateFolderName(utf16String, aName); + } + + return NS_OK; +} + +nsresult +nsBeckyFilters::GetDistributeTarget(const nsCString &aLine, + nsCString &aTargetFolder) +{ + nsresult rv; + nsAutoCString target; + rv = GetActionTarget(aLine, target); + NS_ENSURE_SUCCESS(rv, rv); + + target.Trim("\\", false, true); + nsAutoString folderName; + rv = GetFolderNameFromTarget(target, folderName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIMsgFolder> folder; + rv = GetMessageFolder(folderName, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!folder) { + rv = mServer->GetRootMsgFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + } + return folder->GetURI(aTargetFolder); +} + +nsresult +nsBeckyFilters::CreateDistributeAction(const nsCString &aLine, + nsIMsgFilter *aFilter, + const nsMsgRuleActionType &aActionType, + nsIMsgRuleAction **_retval) +{ + nsresult rv; + nsCOMPtr<nsIMsgRuleAction> action; + rv = CreateRuleAction(aFilter, aActionType, getter_AddRefs(action)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString targetFolder; + rv = GetDistributeTarget(aLine, targetFolder); + NS_ENSURE_SUCCESS(rv, rv); + rv = action->SetTargetFolderUri(targetFolder); + NS_ENSURE_SUCCESS(rv, rv); + + action.forget(_retval); + + return NS_OK; +} + +nsresult +nsBeckyFilters::CreateLeaveOrDeleteAction(const nsCString &aLine, + nsIMsgFilter *aFilter, + nsIMsgRuleAction **_retval) +{ + nsresult rv; + nsMsgRuleActionType actionType; + if (aLine.CharAt(3) == '0') { + actionType = nsMsgFilterAction::LeaveOnPop3Server; + } else if (aLine.CharAt(3) == '1') { + if (aLine.CharAt(5) == '1') + actionType = nsMsgFilterAction::Delete; + else + actionType = nsMsgFilterAction::DeleteFromPop3Server; + } else { + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIMsgRuleAction> action; + rv = CreateRuleAction(aFilter, actionType, getter_AddRefs(action)); + NS_ENSURE_SUCCESS(rv, rv); + + action.forget(_retval); + + return NS_OK; +} + +nsresult +nsBeckyFilters::SetRuleAction(const nsCString &aLine, nsIMsgFilter *aFilter) +{ + if (!aFilter || aLine.Length() < 4) + return NS_ERROR_FAILURE; + + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgRuleAction> action; + switch (aLine.CharAt(1)) { + case 'R': // Reply + rv = CreateResendAction(aLine, + aFilter, + nsMsgFilterAction::Reply, + getter_AddRefs(action)); + break; + case 'F': // Forward + rv = CreateResendAction(aLine, + aFilter, + nsMsgFilterAction::Forward, + getter_AddRefs(action)); + break; + case 'L': // Leave or delete + rv = CreateLeaveOrDeleteAction(aLine, aFilter, getter_AddRefs(action)); + break; + case 'Y': // Copy + rv = CreateDistributeAction(aLine, + aFilter, + nsMsgFilterAction::CopyToFolder, + getter_AddRefs(action)); + break; + case 'M': // Move + rv = CreateDistributeAction(aLine, + aFilter, + nsMsgFilterAction::MoveToFolder, + getter_AddRefs(action)); + break; + case 'G': // Set flag + if (aLine.CharAt(3) == 'R') // Read + rv = CreateRuleAction(aFilter, nsMsgFilterAction::MarkRead, getter_AddRefs(action)); + break; + default: + return NS_OK; + } + NS_ENSURE_SUCCESS(rv, rv); + + if (action) { + rv = aFilter->AppendAction(action); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +nsBeckyFilters::CreateFilter(bool aIncoming, nsIMsgFilter **_retval) +{ + NS_ENSURE_STATE(mServer); + + nsCOMPtr <nsIMsgFilterList> filterList; + nsresult rv = mServer->GetFilterList(nullptr, getter_AddRefs(filterList)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFilter> filter; + rv = filterList->CreateFilter(EmptyString(), getter_AddRefs(filter)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aIncoming) + filter->SetFilterType(nsMsgFilterType::InboxRule | nsMsgFilterType::Manual); + else + filter->SetFilterType(nsMsgFilterType::PostOutgoing | nsMsgFilterType::Manual); + + filter->SetEnabled(true); + filter.forget(_retval); + + return NS_OK; +} + +nsresult +nsBeckyFilters::AppendFilter(nsIMsgFilter *aFilter) +{ + NS_ENSURE_STATE(mServer); + + nsCOMPtr <nsIMsgFilterList> filterList; + nsresult rv = mServer->GetFilterList(nullptr, getter_AddRefs(filterList)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t count; + rv = filterList->GetFilterCount(&count); + NS_ENSURE_SUCCESS(rv, rv); + + return filterList->InsertFilterAt(count, aFilter); +} + +nsresult +nsBeckyFilters::ParseFilterFile(nsIFile *aFile, bool aIncoming) +{ + nsresult rv; + nsCOMPtr<nsILineInputStream> lineStream; + rv = nsBeckyUtils::CreateLineInputStream(aFile, getter_AddRefs(lineStream)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more = true; + nsAutoCString line; + + nsCOMPtr<nsIMsgFilter> filter; + while (NS_SUCCEEDED(rv) && more) { + rv = lineStream->ReadLine(line, &more); + + switch (line.CharAt(0)) { + case ':': + if (line.EqualsLiteral(":Begin \"\"")) { + CreateFilter(aIncoming, getter_AddRefs(filter)); + } else if (line.EqualsLiteral(":End \"\"")) { + if (filter) + AppendFilter(filter); + filter = nullptr; + } + break; + case '!': + SetRuleAction(line, filter); + break; + case '@': + SetSearchTerm(line, filter); + break; + case '$': // $X: disabled + if (StringBeginsWith(line, NS_LITERAL_CSTRING("$X")) && filter) { + filter->SetEnabled(false); + } + break; + default: + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyFilters::Import(char16_t **aError, + bool *_retval) +{ + NS_ENSURE_ARG_POINTER(aError); + NS_ENSURE_ARG_POINTER(_retval); + + // If mLocation is null, set it to the default filter directory. + // If mLocation is a file, we import it as incoming folder. + // If mLocation is a directory, we try to import incoming and outgoing folders + // from it (in default files). + + *_retval = false; + nsresult rv; + nsCOMPtr<nsIFile> filterFile; + + bool haveFile = false; + + if (!mLocation) { + bool retval = false; + rv = AutoLocate(nullptr, getter_AddRefs(mLocation), &retval); + NS_ENSURE_SUCCESS(rv, rv); + if (!retval) + return NS_ERROR_FILE_NOT_FOUND; + } + + // What type of location do we have? + bool isDirectory = false; + rv = mLocation->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (isDirectory) { + haveFile = false; + } else { + bool isFile = false; + rv = mLocation->IsFile(&isFile); + NS_ENSURE_SUCCESS(rv, rv); + if (isFile) { + haveFile = true; + mLocation->Clone(getter_AddRefs(filterFile)); + } else { + // mLocation is neither file nor directory. + return NS_ERROR_UNEXPECTED; + } + } + + bool haveIncoming = true; + if (haveFile) { + // If the passed filename equals OFilter.def, import as outgoing filters. + // Everything else is considered incoming. + nsAutoString fileName; + rv = mLocation->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + if (fileName.EqualsLiteral("OFilter.def")) + haveIncoming = false; + } + + // Try importing from the passed in file or the default incoming filters file. + if ((haveFile && haveIncoming) || (!haveFile && + NS_SUCCEEDED(GetFilterFile(true, mLocation, getter_AddRefs(filterFile))))) + { + rv = CollectServers(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsBeckyUtils::ConvertToUTF8File(filterFile, getter_AddRefs(mConvertedFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ParseFilterFile(mConvertedFile, true); + if (NS_SUCCEEDED(rv)) + *_retval = true; + + (void)RemoveConvertedFile(); + } + + // If we didn't have a file passed (but a directory), try finding also outgoing filters. + if ((haveFile && !haveIncoming) || (!haveFile && + NS_SUCCEEDED(GetFilterFile(false, mLocation, getter_AddRefs(filterFile))))) + { + rv = CollectServers(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsBeckyUtils::ConvertToUTF8File(filterFile, getter_AddRefs(mConvertedFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ParseFilterFile(mConvertedFile, false); + if (NS_SUCCEEDED(rv)) + *_retval = true; + + (void)RemoveConvertedFile(); + } + + return rv; +} + +nsresult +nsBeckyFilters::FindMessageFolder(const nsAString &aName, + nsIMsgFolder *aParentFolder, + nsIMsgFolder **_retval) +{ + nsresult rv; + + nsCOMPtr<nsIMsgFolder> found; + rv = aParentFolder->GetChildNamed(aName, getter_AddRefs(found)); + if (found) { + found.forget(_retval); + return NS_OK; + } + + nsCOMPtr<nsISimpleEnumerator> children; + rv = aParentFolder->GetSubFolders(getter_AddRefs(children)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more; + nsCOMPtr<nsISupports> entry; + while (NS_SUCCEEDED(children->HasMoreElements(&more)) && more) { + rv = children->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> child = do_QueryInterface(entry, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = FindMessageFolder(aName, child, getter_AddRefs(found)); + if (found) { + found.forget(_retval); + return NS_OK; + } + } + + return NS_MSG_ERROR_INVALID_FOLDER_NAME; +} + +nsresult +nsBeckyFilters::FindMessageFolderInServer(const nsAString &aName, + nsIMsgIncomingServer *aServer, + nsIMsgFolder **_retval) +{ + nsresult rv; + nsCOMPtr <nsIMsgFolder> rootFolder; + rv = aServer->GetRootMsgFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + return FindMessageFolder(aName, rootFolder, _retval); +} + +nsresult +nsBeckyFilters::GetMessageFolder(const nsAString &aName, + nsIMsgFolder **_retval) +{ + nsresult rv; + + nsCOMPtr<nsIMsgAccountManager> accountManager; + accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIArray> accounts; + rv = accountManager->GetAccounts(getter_AddRefs(accounts)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t accountCount; + rv = accounts->GetLength(&accountCount); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> found; + for (uint32_t i = 0; i < accountCount; i++) { + nsCOMPtr<nsIMsgAccount> account(do_QueryElementAt(accounts, i)); + if (!account) + continue; + + nsCOMPtr<nsIMsgIncomingServer> server; + account->GetIncomingServer(getter_AddRefs(server)); + if (!server) + continue; + FindMessageFolderInServer(aName, server, getter_AddRefs(found)); + if (found) + break; + } + + if (!found) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + FindMessageFolderInServer(aName, server, getter_AddRefs(found)); + } + + if (!found) + return NS_MSG_ERROR_INVALID_FOLDER_NAME; + + found.forget(_retval); + + return NS_OK; +} + +nsresult +nsBeckyFilters::CollectServers() +{ + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager; + accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> defaultAccount; + rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgIncomingServer> server; + return defaultAccount->GetIncomingServer(getter_AddRefs(mServer)); +} + +nsresult +nsBeckyFilters::RemoveConvertedFile() +{ + nsresult rv = NS_OK; + if (mConvertedFile) { + bool exists = false; + mConvertedFile->Exists(&exists); + if (exists) { + rv = mConvertedFile->Remove(false); + if (NS_SUCCEEDED(rv)) + mConvertedFile = nullptr; + } + } + return rv; +} + diff --git a/mailnews/import/becky/src/nsBeckyFilters.h b/mailnews/import/becky/src/nsBeckyFilters.h new file mode 100644 index 000000000..20dd6d5ee --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyFilters.h @@ -0,0 +1,77 @@ +/* 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/. */ + +#ifndef nsBeckyFilters_h___ +#define nsBeckyFilters_h___ + +#include "nsIImportFilters.h" +#include "nsIFile.h" +#include "nsIMsgIncomingServer.h" +#include "nsMsgFilterCore.h" + +class nsIMsgFilter; +class nsIMsgRuleAction; +class nsCString; + +class nsBeckyFilters final : public nsIImportFilters +{ +public: + nsBeckyFilters(); + static nsresult Create(nsIImportFilters **aImport); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTFILTERS + +private: + virtual ~nsBeckyFilters(); + + nsCOMPtr<nsIFile> mLocation; + nsCOMPtr<nsIMsgIncomingServer> mServer; + nsCOMPtr<nsIFile> mConvertedFile; + + nsresult GetDefaultFilterLocation(nsIFile **aFile); + nsresult GetFilterFile(bool aIncoming, nsIFile *aLocation, nsIFile **aFile); + nsresult ParseFilterFile(nsIFile *aFile, bool aIncoming); + nsresult ParseRuleLine(const nsCString &aLine, + nsMsgSearchAttribValue *aSearchAttribute, + nsMsgSearchOpValue *aSearchOperator, + nsString &aSearchKeyword); + nsresult CollectServers(); + nsresult FindMessageFolder(const nsAString& aName, + nsIMsgFolder *aParantFolder, + nsIMsgFolder **_retval); + nsresult FindMessageFolderInServer(const nsAString& aName, + nsIMsgIncomingServer *aServer, + nsIMsgFolder **_retval); + nsresult GetMessageFolder(const nsAString& aName, nsIMsgFolder **_retval); + nsresult GetActionTarget(const nsCString &aLine, nsCString &aTarget); + nsresult GetFolderNameFromTarget(const nsCString &aTarget, nsAString &aName); + nsresult GetDistributeTarget(const nsCString &aLine, + nsCString &aTargetFolder); + nsresult GetResendTarget(const nsCString &aLine, + nsCString &aTemplate, + nsCString &aTargetAddress); + nsresult CreateRuleAction(nsIMsgFilter *aFilter, + nsMsgRuleActionType actionType, + nsIMsgRuleAction **_retval); + nsresult CreateDistributeAction(const nsCString &aLine, + nsIMsgFilter *aFilter, + const nsMsgRuleActionType &aActionType, + nsIMsgRuleAction **_retval); + nsresult CreateLeaveOrDeleteAction(const nsCString &aLine, + nsIMsgFilter *aFilter, + nsIMsgRuleAction **_retval); + nsresult CreateResendAction(const nsCString &aLine, + nsIMsgFilter *aFilter, + const nsMsgRuleActionType &aActionType, + nsIMsgRuleAction **_retval); + nsresult CreateFilter(bool aIncoming, nsIMsgFilter **_retval); + nsresult AppendFilter(nsIMsgFilter *aFilter); + nsresult SetRuleAction(const nsCString &aLine, nsIMsgFilter *aFilter); + nsresult SetSearchTerm(const nsCString &aLine, nsIMsgFilter *aFilter); + nsresult RemoveConvertedFile(); +}; + +#endif /* nsBeckyFilters_h___ */ diff --git a/mailnews/import/becky/src/nsBeckyImport.cpp b/mailnews/import/becky/src/nsBeckyImport.cpp new file mode 100644 index 000000000..d4820528a --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyImport.cpp @@ -0,0 +1,168 @@ +/* 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 "nscore.h" +#include "nsIServiceManager.h" +#include "nsIImportService.h" +#include "nsIComponentManager.h" +#include "nsIMemory.h" +#include "nsIImportMail.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIImportGeneric.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportSettings.h" +#include "nsIImportFilters.h" +#include "nsIImportFieldMap.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIOutputStream.h" +#include "nsIAddrDatabase.h" +#include "nsTextFormatter.h" +#include "nsIStringBundle.h" +#include "nsUnicharUtils.h" +#include "nsIMsgTagService.h" +#include "nsMsgBaseCID.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" + +#include "nsBeckyImport.h" +#include "nsBeckyMail.h" +#include "nsBeckyAddressBooks.h" +#include "nsBeckySettings.h" +#include "nsBeckyFilters.h" +#include "nsBeckyStringBundle.h" + +nsBeckyImport::nsBeckyImport() +{ +} + +nsBeckyImport::~nsBeckyImport() +{ +} + +NS_IMPL_ISUPPORTS(nsBeckyImport, nsIImportModule) + +NS_IMETHODIMP +nsBeckyImport::GetName(char16_t **aName) +{ + NS_ENSURE_ARG_POINTER(aName); + *aName = + nsBeckyStringBundle::GetStringByName(u"BeckyImportName"); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyImport::GetDescription(char16_t **aDescription) +{ + NS_ENSURE_ARG_POINTER(aDescription); + *aDescription = + nsBeckyStringBundle::GetStringByName(u"BeckyImportDescription"); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyImport::GetSupports(char **aSupports) +{ + NS_ENSURE_ARG_POINTER(aSupports); + *aSupports = strdup(kBeckySupportsString); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyImport::GetSupportsUpgrade(bool *aUpgrade) +{ + NS_ENSURE_ARG_POINTER(aUpgrade); + *aUpgrade = true; + return NS_OK; +} + +nsresult +nsBeckyImport::GetMailImportInterface(nsISupports **aInterface) +{ + nsCOMPtr<nsIImportMail> importer; + nsresult rv = nsBeckyMail::Create(getter_AddRefs(importer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIImportService> importService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIImportGeneric> generic; + rv = importService->CreateNewGenericMail(getter_AddRefs(generic)); + NS_ENSURE_SUCCESS(rv, rv); + + generic->SetData("mailInterface", importer); + + nsString name; + name.Adopt(nsBeckyStringBundle::GetStringByName(u"BeckyImportName")); + + nsCOMPtr<nsISupportsString> nameString(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nameString->SetData(name); + generic->SetData("name", nameString); + + return CallQueryInterface(generic, aInterface); +} + +nsresult +nsBeckyImport::GetAddressBookImportInterface(nsISupports **aInterface) +{ + nsresult rv; + nsCOMPtr<nsIImportAddressBooks> importer; + rv = nsBeckyAddressBooks::Create(getter_AddRefs(importer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIImportService> importService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIImportGeneric> generic; + rv = importService->CreateNewGenericAddressBooks(getter_AddRefs(generic)); + NS_ENSURE_SUCCESS(rv, rv); + + generic->SetData("addressInterface", importer); + return CallQueryInterface(generic, aInterface); +} + +nsresult +nsBeckyImport::GetSettingsImportInterface(nsISupports **aInterface) +{ + nsresult rv; + nsCOMPtr<nsIImportSettings> importer; + rv = nsBeckySettings::Create(getter_AddRefs(importer)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(importer, aInterface); +} + +nsresult +nsBeckyImport::GetFiltersImportInterface(nsISupports **aInterface) +{ + nsresult rv; + nsCOMPtr<nsIImportFilters> importer; + rv = nsBeckyFilters::Create(getter_AddRefs(importer)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(importer, aInterface); +} + +NS_IMETHODIMP +nsBeckyImport::GetImportInterface(const char *aImportType, nsISupports **aInterface) +{ + NS_ENSURE_ARG_POINTER(aImportType); + NS_ENSURE_ARG_POINTER(aInterface); + + *aInterface = nullptr; + if (!strcmp(aImportType, "mail")) + return GetMailImportInterface(aInterface); + if (!strcmp(aImportType, "addressbook")) + return GetAddressBookImportInterface(aInterface); + if (!strcmp(aImportType, "settings")) + return GetSettingsImportInterface(aInterface); + if (!strcmp(aImportType, "filters")) + return GetFiltersImportInterface(aInterface); + + return NS_ERROR_NOT_AVAILABLE; +} diff --git a/mailnews/import/becky/src/nsBeckyImport.h b/mailnews/import/becky/src/nsBeckyImport.h new file mode 100644 index 000000000..60d81c18c --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyImport.h @@ -0,0 +1,36 @@ +/* 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/. */ + +#ifndef nsBeckyImport_h___ +#define nsBeckyImport_h___ + +#include "nsIImportModule.h" + +#define NS_BECKYIMPORT_CID \ +{ \ + 0x7952a6cf, 0x2442,0x4c04, \ + {0x9f, 0x02, 0x15, 0x0b, 0x15, 0xa0, 0xa8, 0x41}} + +#define kBeckySupportsString NS_IMPORT_MAIL_STR "," NS_IMPORT_ADDRESS_STR "," NS_IMPORT_SETTINGS_STR "," NS_IMPORT_FILTERS_STR + +class nsBeckyImport final : public nsIImportModule +{ +public: + nsBeckyImport(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTMODULE + +private: + virtual ~nsBeckyImport(); + + nsresult GetMailImportInterface(nsISupports **aInterface); + nsresult GetAddressBookImportInterface(nsISupports **aInterface); + nsresult GetSettingsImportInterface(nsISupports **aInterface); + nsresult GetFiltersImportInterface(nsISupports **aInterface); + +}; + +#endif /* nsBeckyImport_h___ */ diff --git a/mailnews/import/becky/src/nsBeckyMail.cpp b/mailnews/import/becky/src/nsBeckyMail.cpp new file mode 100644 index 000000000..9c837d190 --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyMail.cpp @@ -0,0 +1,641 @@ +/* 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 "nsIArray.h" +#include "nsIImportService.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFolder.h" +#include "nsIMsgPluggableStore.h" +#include "nsIMutableArray.h" +#include "nsMsgUtils.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsMsgMessageFlags.h" +#include "nsTArray.h" +#include "nspr.h" +#include "nsIStringBundle.h" +#include "nsThreadUtils.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); + + *aImport = new nsBeckyMail(); + + NS_ADDREF(*aImport); + 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, + nsIMutableArray *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, false); + + return NS_OK; +} + +nsresult +nsBeckyMail::CollectMailboxesInFolderListFile(nsIFile *aListFile, + uint32_t aDepth, + nsIMutableArray *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, + nsIMutableArray *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<nsISimpleEnumerator> entries; + rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString name; + rv = file->GetLeafName(name); + NS_ENSURE_SUCCESS(rv, rv); + + if (StringEndsWith(name, NS_LITERAL_STRING(".bmf"))) { + 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, NS_LITERAL_STRING("#")) || + StringBeginsWith(name, NS_LITERAL_STRING("!"))) + 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, nsIArray **_retval) +{ + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv; + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CollectMailboxesInDirectory(aLocation, 0, array); + NS_ENSURE_SUCCESS(rv, rv); + + array.forget(_retval); + + 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.Equals("."); +} + +class ImportMessageRunnable: public mozilla::Runnable +{ +public: + ImportMessageRunnable(nsIFile *aMessageFile, + nsIMsgFolder *aFolder); + NS_DECL_NSIRUNNABLE +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) : + 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(NS_LITERAL_STRING("#Attach")); + 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; + nsresult rv = mFolder->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILineInputStream> lineStream; + rv = nsBeckyUtils::CreateLineInputStream(mMessageFile, + getter_AddRefs(lineStream)); + NS_ENSURE_SUCCESS(rv, rv); + + bool reusable; + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCOMPtr<nsIOutputStream> outputStream; + rv = msgStore->GetNewMsgOutputStream(mFolder, getter_AddRefs(msgHdr), &reusable, + getter_AddRefs(outputStream)); + NS_ENSURE_SUCCESS(rv, rv); + + bool inHeader = true; + bool more = true; + nsAutoCString headers; + while (NS_SUCCEEDED(rv) && more) { + nsAutoCString line; + rv = lineStream->ReadLine(line, &more); + if (NS_FAILED(rv)) + break; + + if (inHeader) { + if (IsEndOfHeaders(line)) { + inHeader = false; + rv = WriteHeaders(headers, outputStream); + } else { + rv = HandleHeaderLine(line, headers); + } + } else if (IsEndOfMessage(line)) { + inHeader = true; + rv = msgStore->FinishNewMessage(outputStream, msgHdr); + if (!reusable) + outputStream->Close(); + rv = msgStore->GetNewMsgOutputStream(mFolder, getter_AddRefs(msgHdr), &reusable, + getter_AddRefs(outputStream)); + } else if (IsBeckyIncludeLine(line)) { + rv = WriteAttachmentFile(mMessageFile, line, outputStream); + } else { + uint32_t writtenBytes = 0; + if (StringBeginsWith(line, NS_LITERAL_CSTRING(".."))) + line.Cut(0, 1); + else if (CheckHeaderKey(line, "From")) + line.Insert('>', 0); + + line.AppendLiteral(MSG_LINEBREAK); + rv = outputStream->Write(line.get(), line.Length(), &writtenBytes); + } + } + + if (outputStream) { + if (NS_FAILED(rv)) + msgStore->DiscardNewMessage(outputStream, msgHdr); + outputStream->Close(); + } + + return rv; +} + +static +nsresult ProxyImportMessage(nsIFile *aMessageFile, + nsIMsgFolder *aFolder) +{ + RefPtr<ImportMessageRunnable> importMessage = + new ImportMessageRunnable(aMessageFile, aFolder); + return NS_DispatchToMainThread(importMessage, NS_DISPATCH_SYNC); +} + +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; + const char16_t *format = { name.get() }; + rv = + nsBeckyStringBundle::FormatStringFromName(u"BeckyImportMailboxSuccess", + &format, + 1, + getter_Copies(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); +} + diff --git a/mailnews/import/becky/src/nsBeckyMail.h b/mailnews/import/becky/src/nsBeckyMail.h new file mode 100644 index 000000000..ae287a05f --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyMail.h @@ -0,0 +1,45 @@ +/* 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/. */ + +#ifndef nsBeckyMail_h___ +#define nsBeckyMail_h___ + +#include "nsIImportMail.h" + +class nsIFile; +class nsIMutableArray; +class nsIMsgFolder; + +class nsBeckyMail final : public nsIImportMail +{ +public: + nsBeckyMail(); + static nsresult Create(nsIImportMail **aImport); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTMAIL + +private: + virtual ~nsBeckyMail(); + + uint32_t mReadBytes; + + nsresult CollectMailboxesInDirectory(nsIFile *aDirectory, + uint32_t aDepth, + nsIMutableArray *aCollected); + nsresult CollectMailboxesInFolderListFile(nsIFile *aListFile, + uint32_t aDepth, + nsIMutableArray *aCollected); + nsresult AppendMailboxDescriptor(nsIFile *aEntry, + const nsString &aName, + uint32_t aDepth, + nsIMutableArray *aCollected); + nsresult ImportMailFile(nsIFile *aMailFile, + nsIMsgFolder *aDestination); + nsresult CreateMailboxDescriptor(nsIImportMailboxDescriptor **aDescriptor); + nsresult GetMailboxName(nsIFile *aMailbox, nsAString &aName); +}; + +#endif /* nsBeckyMail_h___ */ diff --git a/mailnews/import/becky/src/nsBeckySettings.cpp b/mailnews/import/becky/src/nsBeckySettings.cpp new file mode 100644 index 000000000..8e1cab960 --- /dev/null +++ b/mailnews/import/becky/src/nsBeckySettings.cpp @@ -0,0 +1,471 @@ +/* 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 "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsIMsgAccountManager.h" +#include "nsServiceManagerUtils.h" +#include "nsIINIParser.h" +#include "nsISmtpService.h" +#include "nsISmtpServer.h" +#include "nsIPop3IncomingServer.h" +#include "nsIStringEnumerator.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsILineInputStream.h" +#include "nsNetUtil.h" +#include "nsString.h" +#include "msgCore.h" +#include "nsIStringBundle.h" + +#include "nsBeckySettings.h" +#include "nsBeckyStringBundle.h" +#include "nsBeckyUtils.h" + +NS_IMPL_ISUPPORTS(nsBeckySettings, nsIImportSettings) + +nsresult +nsBeckySettings::Create(nsIImportSettings **aImport) +{ + NS_ENSURE_ARG_POINTER(aImport); + + *aImport = new nsBeckySettings(); + + NS_ADDREF(*aImport); + return NS_OK; +} + +nsBeckySettings::nsBeckySettings() +{ +} + +nsBeckySettings::~nsBeckySettings() +{ +} + +NS_IMETHODIMP +nsBeckySettings::AutoLocate(char16_t **aDescription, + nsIFile **aLocation, + bool *_retval) +{ + NS_ENSURE_ARG_POINTER(aDescription); + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(_retval); + + *aDescription = + nsBeckyStringBundle::GetStringByName(u"BeckyImportName"); + *aLocation = nullptr; + *_retval = false; + + nsCOMPtr<nsIFile> location; + nsresult rv = nsBeckyUtils::GetDefaultMailboxINIFile(getter_AddRefs(location)); + if (NS_FAILED(rv)) + location = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + else + *_retval = true; + + location.forget(aLocation); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckySettings::SetLocation(nsIFile *aLocation) +{ + mLocation = aLocation; + return NS_OK; +} + +nsresult +nsBeckySettings::CreateParser() +{ + if (!mLocation) { + nsresult rv = nsBeckyUtils::GetDefaultMailboxINIFile(getter_AddRefs(mLocation)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // nsIINIParser accepts only UTF-8 encoding, so we need to convert the file + // first. + nsresult rv; + rv = nsBeckyUtils::ConvertToUTF8File(mLocation, getter_AddRefs(mConvertedFile)); + NS_ENSURE_SUCCESS(rv, rv); + + return nsBeckyUtils::CreateINIParserForFile(mConvertedFile, + getter_AddRefs(mParser)); +} + +nsresult +nsBeckySettings::CreateSmtpServer(const nsCString &aUserName, + const nsCString &aServerName, + nsISmtpServer **aServer, + bool *existing) +{ + nsresult rv; + + nsCOMPtr<nsISmtpService> smtpService = do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISmtpServer> server; + rv = smtpService->FindServer(aUserName.get(), + aServerName.get(), + getter_AddRefs(server)); + + if (NS_FAILED(rv) || !server) { + rv = smtpService->CreateServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + server->SetHostname(aServerName); + server->SetUsername(aUserName); + *existing = false; + } else { + *existing = true; + } + + server.forget(aServer); + + return NS_OK; +} + +nsresult +nsBeckySettings::CreateIncomingServer(const nsCString &aUserName, + const nsCString &aServerName, + const nsCString &aProtocol, + nsIMsgIncomingServer **aServer) +{ + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgIncomingServer> incomingServer; + rv = accountManager->FindServer(aUserName, + aServerName, + aProtocol, + getter_AddRefs(incomingServer)); + + if (NS_FAILED(rv) || !incomingServer) { + rv = accountManager->CreateIncomingServer(aUserName, + aServerName, + aProtocol, + getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv, rv); + } + incomingServer.forget(aServer); + + return NS_OK; +} + +nsresult +nsBeckySettings::SetupSmtpServer(nsISmtpServer **aServer) +{ + nsresult rv; + nsAutoCString userName, serverName; + + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("SMTPServer"), + serverName); + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("UserID"), + userName); + + nsCOMPtr<nsISmtpServer> server; + bool existing = false; + rv = CreateSmtpServer(userName, serverName, getter_AddRefs(server), &existing); + NS_ENSURE_SUCCESS(rv, rv); + + // If we already have an existing server, do not touch it's settings. + if (existing) { + server.forget(aServer); + return NS_OK; + } + + nsAutoCString value; + rv = mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("SMTPPort"), + value); + int32_t port = 25; + if (NS_SUCCEEDED(rv)) { + nsresult errorCode; + port = value.ToInteger(&errorCode, 10); + } + server->SetPort(port); + + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("SSLSMTP"), + value); + if (value.Equals("1")) + server->SetSocketType(nsMsgSocketType::SSL); + + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("SMTPAUTH"), + value); + if (value.Equals("1")) { + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("SMTPAUTHMODE"), + value); + nsMsgAuthMethodValue authMethod = nsMsgAuthMethod::none; + if (value.Equals("1")) { + authMethod = nsMsgAuthMethod::passwordEncrypted; + } else if (value.Equals("2") || + value.Equals("4") || + value.Equals("6")) { + authMethod = nsMsgAuthMethod::passwordCleartext; + } else { + authMethod = nsMsgAuthMethod::anything; + } + server->SetAuthMethod(authMethod); + } + + server.forget(aServer); + + return NS_OK; +} + +nsresult +nsBeckySettings::SetPop3ServerProperties(nsIMsgIncomingServer *aServer) +{ + nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(aServer); + + nsAutoCString value; + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("POP3Auth"), + value); // 0: plain, 1: APOP, 2: CRAM-MD5, 3: NTLM + nsMsgAuthMethodValue authMethod; + if (value.IsEmpty() || value.Equals("0")) { + authMethod = nsMsgAuthMethod::passwordCleartext; + } else if (value.Equals("1")) { + authMethod = nsMsgAuthMethod::old; + } else if (value.Equals("2")) { + authMethod = nsMsgAuthMethod::passwordEncrypted; + } else if (value.Equals("3")) { + authMethod = nsMsgAuthMethod::NTLM; + } else { + authMethod = nsMsgAuthMethod::none; + } + aServer->SetAuthMethod(authMethod); + + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("LeaveServer"), + value); + if (value.Equals("1")) { + pop3Server->SetLeaveMessagesOnServer(true); + nsresult rv = mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("KeepDays"), + value); + if (NS_FAILED(rv)) + return NS_OK; + + nsresult errorCode; + int32_t leftDays = value.ToInteger(&errorCode, 10); + if (NS_SUCCEEDED(errorCode)) { + pop3Server->SetNumDaysToLeaveOnServer(leftDays); + pop3Server->SetDeleteByAgeFromServer(true); + } + } + + return NS_OK; +} + +nsresult +nsBeckySettings::SetupIncomingServer(nsIMsgIncomingServer **aServer) +{ + nsAutoCString value; + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("Protocol"), + value); + nsCString protocol; + if (value.Equals("1")) { + protocol = NS_LITERAL_CSTRING("imap"); + } else { + protocol = NS_LITERAL_CSTRING("pop3"); + } + + nsAutoCString userName, serverName; + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("MailServer"), + serverName); + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("UserID"), + userName); + + nsresult rv; + nsCOMPtr<nsIMsgIncomingServer> server; + rv = CreateIncomingServer(userName, serverName, protocol, getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isSecure = false; + int32_t port = 0; + nsresult errorCode; + if (protocol.EqualsLiteral("pop3")) { + SetPop3ServerProperties(server); + rv = mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("POP3Port"), + value); + if (NS_SUCCEEDED(rv)) + port = value.ToInteger(&errorCode, 10); + else + port = 110; + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("SSLPOP"), + value); + if (value.Equals("1")) + isSecure = true; + } else if (protocol.EqualsLiteral("imap")) { + rv = mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("IMAP4Port"), + value); + if (NS_SUCCEEDED(rv)) + port = value.ToInteger(&errorCode, 10); + else + port = 143; + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("SSLIMAP"), + value); + if (value.Equals("1")) + isSecure = true; + } + + server->SetPort(port); + if (isSecure) + server->SetSocketType(nsMsgSocketType::SSL); + + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("CheckInt"), + value); + if (value.Equals("1")) + server->SetDoBiff(true); + rv = mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("CheckEvery"), + value); + if (NS_SUCCEEDED(rv)) { + int32_t minutes = value.ToInteger(&errorCode, 10); + if (NS_SUCCEEDED(errorCode)) + server->SetBiffMinutes(minutes); + } + + server.forget(aServer); + + return NS_OK; +} + +nsresult +nsBeckySettings::CreateIdentity(nsIMsgIdentity **aIdentity) +{ + nsAutoCString email, fullName, identityName, bccAddress; + + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("Name"), + identityName); + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("YourName"), + fullName); + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("MailAddress"), + email); + mParser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("PermBcc"), + bccAddress); + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgIdentity> identity; + rv = accountManager->CreateIdentity(getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv, rv); + + identity->SetLabel(NS_ConvertUTF8toUTF16(identityName)); + identity->SetFullName(NS_ConvertUTF8toUTF16(fullName)); + identity->SetEmail(email); + if (!bccAddress.IsEmpty()) { + identity->SetDoBcc(true); + identity->SetDoBccList(bccAddress); + } + + identity.forget(aIdentity); + + return NS_OK; +} + +nsresult +nsBeckySettings::CreateAccount(nsIMsgIdentity *aIdentity, + nsIMsgIncomingServer *aIncomingServer, + nsIMsgAccount **aAccount) +{ + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> account; + rv = accountManager->CreateAccount(getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = account->AddIdentity(aIdentity); + NS_ENSURE_SUCCESS(rv, rv); + + rv = account->SetIncomingServer(aIncomingServer); + NS_ENSURE_SUCCESS(rv, rv); + + account.forget(aAccount); + + return NS_OK; +} + +nsresult +nsBeckySettings::RemoveConvertedFile() +{ + if (mConvertedFile) { + bool exists; + mConvertedFile->Exists(&exists); + if (exists) + mConvertedFile->Remove(false); + mConvertedFile = nullptr; + } + return NS_OK; +} + +#define NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(expr, rv) \ + if (NS_FAILED(expr)) { \ + RemoveConvertedFile(); \ + return rv; \ + } + +NS_IMETHODIMP +nsBeckySettings::Import(nsIMsgAccount **aLocalMailAccount, + bool *_retval) +{ + NS_ENSURE_ARG_POINTER(aLocalMailAccount); + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv = CreateParser(); + NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv); + + nsCOMPtr<nsIMsgIncomingServer> incomingServer; + rv = SetupIncomingServer(getter_AddRefs(incomingServer)); + NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv); + + nsCOMPtr<nsISmtpServer> smtpServer; + rv = SetupSmtpServer(getter_AddRefs(smtpServer)); + NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv); + + nsCOMPtr<nsIMsgIdentity> identity; + rv = CreateIdentity(getter_AddRefs(identity)); + NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv); + + nsAutoCString smtpKey; + smtpServer->GetKey(getter_Copies(smtpKey)); + identity->SetSmtpServerKey(smtpKey); + + nsCOMPtr<nsIMsgAccount> account; + rv = CreateAccount(identity, incomingServer, getter_AddRefs(account)); + NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv); + + RemoveConvertedFile(); + if (aLocalMailAccount) + account.forget(aLocalMailAccount); + *_retval = true; + return NS_OK; +} + diff --git a/mailnews/import/becky/src/nsBeckySettings.h b/mailnews/import/becky/src/nsBeckySettings.h new file mode 100644 index 000000000..19e7d45ed --- /dev/null +++ b/mailnews/import/becky/src/nsBeckySettings.h @@ -0,0 +1,52 @@ +/* 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/. */ + +#ifndef nsBeckySettings_h___ +#define nsBeckySettings_h___ + +#include "nsIImportSettings.h" +#include "nsIFile.h" +#include "nsIINIParser.h" + +class nsIMsgIncomingServer; +class nsIMsgIdentity; +class nsISmtpServer; + +class nsBeckySettings final : public nsIImportSettings +{ +public: + nsBeckySettings(); + static nsresult Create(nsIImportSettings **aImport); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTSETTINGS + +private: + virtual ~nsBeckySettings(); + + nsCOMPtr<nsIFile> mLocation; + nsCOMPtr<nsIFile> mConvertedFile; + nsCOMPtr<nsIINIParser> mParser; + + nsresult CreateParser(); + nsresult CreateIdentity(nsIMsgIdentity **aIdentity); + nsresult CreateAccount(nsIMsgIdentity *aIdentity, + nsIMsgIncomingServer *aIncomingServer, + nsIMsgAccount **aAccount); + nsresult CreateSmtpServer(const nsCString &aUserName, + const nsCString &aServerName, + nsISmtpServer **aServer, + bool *existing); + nsresult CreateIncomingServer(const nsCString &aUserName, + const nsCString &aServerName, + const nsCString &aProtocol, + nsIMsgIncomingServer **aServer); + nsresult SetupIncomingServer(nsIMsgIncomingServer **aServer); + nsresult SetupSmtpServer(nsISmtpServer **aServer); + nsresult SetPop3ServerProperties(nsIMsgIncomingServer *aServer); + nsresult RemoveConvertedFile(); +}; + +#endif /* nsBeckySettings_h___ */ diff --git a/mailnews/import/becky/src/nsBeckyStringBundle.cpp b/mailnews/import/becky/src/nsBeckyStringBundle.cpp new file mode 100644 index 000000000..41209dff5 --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyStringBundle.cpp @@ -0,0 +1,74 @@ +/* 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 "prprf.h" +#include "prmem.h" +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" +#include "nsIServiceManager.h" +#include "nsIURI.h" +#include "nsServiceManagerUtils.h" +#include "nsXPCOMCIDInternal.h" + +#include "nsBeckyStringBundle.h" + +#define BECKY_MESSAGES_URL "chrome://messenger/locale/beckyImportMsgs.properties" + +nsIStringBundle *nsBeckyStringBundle::mBundle = nullptr; + +nsIStringBundle * +nsBeckyStringBundle::GetStringBundle(void) +{ + if (mBundle) + return mBundle; + + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && bundleService) + rv = bundleService->CreateBundle(BECKY_MESSAGES_URL, &mBundle); + + return mBundle; +} + +void +nsBeckyStringBundle::EnsureStringBundle(void) +{ + if (!mBundle) + (void) GetStringBundle(); +} + +char16_t * +nsBeckyStringBundle::GetStringByName(const char16_t *aName) +{ + EnsureStringBundle(); + + char16_t *string = nullptr; + if (mBundle) + mBundle->GetStringFromName(aName, &string); + + return string; +} + +nsresult +nsBeckyStringBundle::FormatStringFromName(const char16_t *name, + const char16_t **params, + uint32_t length, + char16_t **_retval) +{ + EnsureStringBundle(); + + return mBundle->FormatStringFromName(name, + params, + length, + _retval); +} + +void +nsBeckyStringBundle::Cleanup(void) +{ + if (mBundle) + mBundle->Release(); + mBundle = nullptr; +} diff --git a/mailnews/import/becky/src/nsBeckyStringBundle.h b/mailnews/import/becky/src/nsBeckyStringBundle.h new file mode 100644 index 000000000..190208c9d --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyStringBundle.h @@ -0,0 +1,33 @@ +/* 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 _nsBeckyStringBundle_H__ +#define _nsBeckyStringBundle_H__ + +#include "nsString.h" + +class nsIStringBundle; + +class nsBeckyStringBundle final { +public: + static char16_t *GetStringByName(const char16_t *name); + static nsresult FormatStringFromName(const char16_t *name, + const char16_t **params, + uint32_t length, + char16_t **_retval); + static nsIStringBundle * GetStringBundle(void); // don't release + static void EnsureStringBundle(void); + static void Cleanup(void); +private: + static nsIStringBundle *mBundle; +}; + +#define BECKYIMPORT_NAME 2000 +#define BECKYIMPORT_DESCRIPTION 2001 +#define BECKYIMPORT_MAILBOX_SUCCESS 2002 +#define BECKYIMPORT_MAILBOX_BADPARAM 2003 +#define BECKYIMPORT_MAILBOX_CONVERTERROR 2004 +#define BECKYIMPORT_ADDRESS_SUCCESS 2005 + + +#endif /* _nsBeckyStringBundle_H__ */ diff --git a/mailnews/import/becky/src/nsBeckyUtils.cpp b/mailnews/import/becky/src/nsBeckyUtils.cpp new file mode 100644 index 000000000..2e9af84a5 --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyUtils.cpp @@ -0,0 +1,334 @@ +/* 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 "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsISimpleEnumerator.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsString.h" +#include "nsIUTF8ConverterService.h" +#include "nsUConvCID.h" +#include "nsNativeCharsetUtils.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsILineInputStream.h" +#include "nsIConverterInputStream.h" +#include "nsIConverterOutputStream.h" +#include "nsMsgI18N.h" +#include "nsNetUtil.h" +#include "nsIINIParser.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsMsgUtils.h" +#include "msgCore.h" +#include "nsIImportMail.h" +#include "nsThreadUtils.h" + +#include "nsBeckyUtils.h" + +nsresult +nsBeckyUtils::FindUserDirectoryOnWindows7(nsIFile **aLocation) +{ + NS_ENSURE_ARG_POINTER(aLocation); + + nsresult rv; + nsCOMPtr<nsIFile> directory; + rv = GetSpecialDirectoryWithFileName(NS_WIN_DOCUMENTS_DIR, + "Becky", + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + rv = directory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + + bool isDirectory = false; + rv = directory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (!isDirectory) + return NS_ERROR_FILE_NOT_FOUND; + + directory.forget(aLocation); + return NS_OK; +} + +nsresult +nsBeckyUtils::FindUserDirectoryOnWindowsXP(nsIFile **aLocation) +{ + NS_ENSURE_ARG_POINTER(aLocation); + + nsresult rv; + nsCOMPtr<nsIFile> directory = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->InitWithPath(NS_LITERAL_STRING("C:\\Becky!")); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + rv = directory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + + bool isDirectory = false; + rv = directory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (!isDirectory) + return NS_ERROR_FILE_NOT_FOUND; + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more; + nsCOMPtr<nsISupports> entry; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + rv = entries->GetNext(getter_AddRefs(entry)); + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDirectory = false; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (isDirectory) { + file.forget(aLocation); + return NS_OK; + } + } + + directory.forget(aLocation); + return NS_OK; +} + +nsresult +nsBeckyUtils::FindUserDirectory(nsIFile **aLocation) +{ + nsresult rv = FindUserDirectoryOnWindows7(aLocation); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + rv = FindUserDirectoryOnWindowsXP(aLocation); + } + return rv; +} + +nsresult +nsBeckyUtils::ConvertNativeStringToUTF8(const nsACString& aOriginal, + nsACString& _retval) +{ + nsresult rv; + nsAutoString unicodeString; + rv = NS_CopyNativeToUnicode(aOriginal, unicodeString); + NS_ENSURE_SUCCESS(rv, rv); + + CopyUTF16toUTF8(unicodeString, _retval); + return NS_OK; +} + +nsresult +nsBeckyUtils::CreateLineInputStream(nsIFile *aFile, + nsILineInputStream **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(inputStream, _retval); +} + +nsresult +nsBeckyUtils::GetFolderListFile(nsIFile *aLocation, nsIFile **_retval) +{ + nsresult rv; + nsCOMPtr<nsIFile> folderListFile; + rv = aLocation->Clone(getter_AddRefs(folderListFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = folderListFile->Append(NS_LITERAL_STRING("Folder.lst")); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = folderListFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + + folderListFile.forget(_retval); + return NS_OK; +} + +nsresult +nsBeckyUtils::GetDefaultFolderName(nsIFile *aFolderListFile, nsACString& name) +{ + nsresult rv; + nsCOMPtr<nsILineInputStream> lineStream; + rv = CreateLineInputStream(aFolderListFile, getter_AddRefs(lineStream)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more = true; + rv = lineStream->ReadLine(name, &more); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +nsBeckyUtils::GetDefaultMailboxDirectory(nsIFile **_retval) +{ + nsCOMPtr<nsIFile> userDirectory; + nsresult rv = FindUserDirectory(getter_AddRefs(userDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> folderListFile; + rv = GetFolderListFile(userDirectory, getter_AddRefs(folderListFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString defaultFolderName; + rv = GetDefaultFolderName(folderListFile, defaultFolderName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = userDirectory->AppendNative(defaultFolderName); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = userDirectory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + + bool isDirectory = false; + rv = userDirectory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (!isDirectory) + return NS_ERROR_FILE_NOT_FOUND; + + userDirectory.forget(_retval); + return NS_OK; +} + +nsresult +nsBeckyUtils::GetDefaultMailboxINIFile(nsIFile **_retval) +{ + nsresult rv; + nsCOMPtr<nsIFile> mailboxDirectory; + rv = GetDefaultMailboxDirectory(getter_AddRefs(mailboxDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + return GetMailboxINIFile(mailboxDirectory, _retval); +} + +nsresult +nsBeckyUtils::GetMailboxINIFile(nsIFile *aDirectory, nsIFile **_retval) +{ + nsresult rv; + nsCOMPtr<nsIFile> target; + rv = aDirectory->Clone(getter_AddRefs(target)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = target->Append(NS_LITERAL_STRING("Mailbox.ini")); + NS_ENSURE_SUCCESS(rv, rv); + bool exists; + rv = target->Exists(&exists); + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + + target.forget(_retval); + return NS_OK; +} + +nsresult +nsBeckyUtils::CreateINIParserForFile(nsIFile *aFile, + nsIINIParser **aParser) +{ + nsresult rv; + nsCOMPtr<nsIINIParserFactory> factory = + do_GetService("@mozilla.org/xpcom/ini-processor-factory;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return factory->CreateINIParser(aFile, aParser); +} + +nsresult +nsBeckyUtils::GetMailboxNameFromINIFile(nsIFile *aFile, nsCString &aName) +{ + nsresult rv; + nsCOMPtr<nsIINIParser> parser; + rv = CreateINIParserForFile(aFile, getter_AddRefs(parser)); + NS_ENSURE_SUCCESS(rv, rv); + + return parser->GetString(NS_LITERAL_CSTRING("Account"), + NS_LITERAL_CSTRING("Name"), + aName); +} + +nsresult +nsBeckyUtils::ConvertToUTF8File(nsIFile *aSourceFile, + nsIFile **_retval) +{ + nsresult rv; + nsCOMPtr<nsIFile> convertedFile; + rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + "thunderbird-becky-import", + getter_AddRefs(convertedFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = convertedFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> source; + rv = NS_NewLocalFileInputStream(getter_AddRefs(source), aSourceFile); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIOutputStream> destination; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(destination), + convertedFile); + NS_ENSURE_SUCCESS(rv, rv); + + const uint32_t kBlock = 8192; + + nsCOMPtr<nsIConverterInputStream> convertedInput = + do_CreateInstance("@mozilla.org/intl/converter-input-stream;1"); + convertedInput->Init(source, nsMsgI18NFileSystemCharset(), kBlock, 0x0000); + + nsCOMPtr<nsIConverterOutputStream> convertedOutput = + do_CreateInstance("@mozilla.org/intl/converter-output-stream;1"); + convertedOutput->Init(destination, "UTF-8", kBlock, 0x0000); + + char16_t *line = (char16_t *)moz_xmalloc(kBlock); + uint32_t readBytes = kBlock; + bool writtenBytes; + while (readBytes == kBlock) { + rv = convertedInput->Read(line, kBlock, &readBytes); + rv = convertedOutput->Write(readBytes, line, &writtenBytes); + } + convertedOutput->Close(); + convertedInput->Close(); + + convertedFile.forget(_retval); + return NS_OK; +} + +nsresult +nsBeckyUtils::TranslateFolderName(const nsAString & aFolderName, + nsAString & _retval) +{ + if (aFolderName.LowerCaseEqualsLiteral("!trash")) + _retval = NS_LITERAL_STRING(kDestTrashFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("!!!!inbox")) + _retval = NS_LITERAL_STRING(kDestInboxFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("!!!!outbox")) + _retval = NS_LITERAL_STRING(kDestSentFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("!!!!unsent")) + _retval = NS_LITERAL_STRING(kDestUnsentMessagesFolderName); + else + _retval = aFolderName; + + return NS_OK; +} diff --git a/mailnews/import/becky/src/nsBeckyUtils.h b/mailnews/import/becky/src/nsBeckyUtils.h new file mode 100644 index 000000000..8b6e3a542 --- /dev/null +++ b/mailnews/import/becky/src/nsBeckyUtils.h @@ -0,0 +1,37 @@ +/* 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 _nsBeckyUtils_H__ +#define _nsBeckyUtils_H__ + +class nsIFile; +class nsILineInputStream; +class nsIINIParser; + +class nsBeckyUtils final { +public: + static nsresult FindUserDirectoryOnWindows7(nsIFile **aLocation); + static nsresult FindUserDirectoryOnWindowsXP(nsIFile **aLocation); + static nsresult FindUserDirectory(nsIFile **aFile); + static nsresult ConvertNativeStringToUTF8(const nsACString& aOriginal, + nsACString& _retval); + static nsresult CreateLineInputStream(nsIFile *aFile, + nsILineInputStream **_retval); + static nsresult GetDefaultMailboxDirectory(nsIFile **_retval); + static nsresult GetFolderListFile(nsIFile *aLocation, + nsIFile **_retval); + static nsresult GetDefaultFolderName(nsIFile *aFolderListFile, + nsACString& name); + static nsresult GetDefaultMailboxINIFile(nsIFile **_retval); + static nsresult GetMailboxINIFile(nsIFile *aDirectory, nsIFile **_retval); + static nsresult CreateINIParserForFile(nsIFile *aFile, + nsIINIParser **aParser); + static nsresult GetMailboxNameFromINIFile(nsIFile *aFile, nsCString &aName); + static nsresult ConvertToUTF8File(nsIFile *aSourceFile, + nsIFile **_retval); + static nsresult TranslateFolderName(const nsAString & aFolderName, + nsAString & _retval); +}; + + +#endif /* _nsBeckyUtils_H__ */ diff --git a/mailnews/import/build/moz.build b/mailnews/import/build/moz.build new file mode 100644 index 000000000..76cd0cceb --- /dev/null +++ b/mailnews/import/build/moz.build @@ -0,0 +1,62 @@ +# 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 += [ + 'nsImportModule.cpp', +] + +USE_LIBS += [ + 'nspr', +] + +if CONFIG['MOZ_INCOMPLETE_EXTERNAL_LINKAGE']: + XPCOMBinaryComponent('import') + USE_LIBS += [ + 'msgbsutl_s', + 'rdfutil_external_s', + 'unicharutil_external_s', + 'xpcomglue_s', + 'xul', + ] +else: + Library('import') + FINAL_LIBRARY = 'xul' + +# js needs to come after xul for now, because it is an archive and its content +# is discarded when it comes first. +USE_LIBS += [ + 'js', +] + +LOCAL_INCLUDES += [ + '../src', + '../text/src', + '../vcard/src', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '../applemail/src', + ] + OS_LIBS += CONFIG['TK_LIBS'] + OS_LIBS += ['-framework Cocoa'] + +if CONFIG['OS_ARCH'] == 'WINNT': + LOCAL_INCLUDES += [ + ] + if not CONFIG['GNU_CC']: + LOCAL_INCLUDES += [ + '../becky/src', + '../oexpress', + '../outlook/src', + '../winlivemail', + ] + if CONFIG['MOZ_MAPI_SUPPORT']: + LOCAL_INCLUDES += [ + '../outlook/src', + ] +else: + OS_LIBS += CONFIG['MOZ_ZLIB_LIBS'] + diff --git a/mailnews/import/build/nsImportModule.cpp b/mailnews/import/build/nsImportModule.cpp new file mode 100644 index 000000000..f251e5660 --- /dev/null +++ b/mailnews/import/build/nsImportModule.cpp @@ -0,0 +1,203 @@ +/* -*- 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/. */ + +//////////////////////////////////////////////////////////////////////////////// +// Core Module Include Files +//////////////////////////////////////////////////////////////////////////////// +#include "nsCOMPtr.h" +#include "mozilla/ModuleUtils.h" + +//////////////////////////////////////////////////////////////////////////////// +// core import Include Files +//////////////////////////////////////////////////////////////////////////////// +#include "nsImportService.h" +#include "nsImportMimeEncode.h" +#include "nsImportStringBundle.h" + +NS_DEFINE_NAMED_CID(NS_IMPORTSERVICE_CID); +NS_DEFINE_NAMED_CID(NS_IMPORTMIMEENCODE_CID); +//////////////////////////////////////////////////////////////////////////////// +// text import Include Files +//////////////////////////////////////////////////////////////////////////////// +#include "nsTextImport.h" + +NS_DEFINE_NAMED_CID(NS_TEXTIMPORT_CID); + +//////////////////////////////////////////////////////////////////////////////// +// vCard import Include Files +//////////////////////////////////////////////////////////////////////////////// +#include "nsVCardImport.h" + +NS_DEFINE_NAMED_CID(NS_VCARDIMPORT_CID); + +//////////////////////////////////////////////////////////////////////////////// +// Apple Mail import Include Files +//////////////////////////////////////////////////////////////////////////////// +#if defined(XP_MACOSX) +#include "nsAppleMailImport.h" + +NS_DEFINE_NAMED_CID(NS_APPLEMAILIMPORT_CID); +NS_DEFINE_NAMED_CID(NS_APPLEMAILIMPL_CID); +#endif + +//////////////////////////////////////////////////////////////////////////////// +// outlook import Include Files +//////////////////////////////////////////////////////////////////////////////// +#ifdef XP_WIN +#include "nsOEImport.h" +#include "nsOEStringBundle.h" +#ifdef MOZ_MAPI_SUPPORT +#include "nsOutlookImport.h" +#include "nsOutlookStringBundle.h" +#endif +#include "nsWMImport.h" +#include "nsWMStringBundle.h" + +NS_DEFINE_NAMED_CID(NS_OEIMPORT_CID); +NS_DEFINE_NAMED_CID(NS_WMIMPORT_CID); +#ifdef MOZ_MAPI_SUPPORT +NS_DEFINE_NAMED_CID(NS_OUTLOOKIMPORT_CID); +#endif +#endif // XP_WIN + +//////////////////////////////////////////////////////////////////////////////// +// becky import Include Files +//////////////////////////////////////////////////////////////////////////////// +#ifdef XP_WIN +#include "nsBeckyImport.h" +#include "nsBeckyStringBundle.h" + +NS_DEFINE_NAMED_CID(NS_BECKYIMPORT_CID); +#endif // XP_WIN + +//////////////////////////////////////////////////////////////////////////////// +// core import factories +//////////////////////////////////////////////////////////////////////////////// +NS_GENERIC_FACTORY_CONSTRUCTOR(nsImportService) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsIImportMimeEncodeImpl) + +//////////////////////////////////////////////////////////////////////////////// +// text import factories +//////////////////////////////////////////////////////////////////////////////// +NS_GENERIC_FACTORY_CONSTRUCTOR(nsTextImport) + +//////////////////////////////////////////////////////////////////////////////// +// vcard import factories +//////////////////////////////////////////////////////////////////////////////// +NS_GENERIC_FACTORY_CONSTRUCTOR(nsVCardImport) + +//////////////////////////////////////////////////////////////////////////////// +// apple mail import factories +//////////////////////////////////////////////////////////////////////////////// +#if defined(XP_MACOSX) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsAppleMailImportModule) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppleMailImportMail, Initialize) +#endif + +//////////////////////////////////////////////////////////////////////////////// +// outlook import factories +//////////////////////////////////////////////////////////////////////////////// +#ifdef XP_WIN +NS_GENERIC_FACTORY_CONSTRUCTOR(nsOEImport) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWMImport) +#ifdef MOZ_MAPI_SUPPORT +NS_GENERIC_FACTORY_CONSTRUCTOR(nsOutlookImport) +#endif +#endif // XP_WIN +//////////////////////////////////////////////////////////////////////////////// +// becky import factory +//////////////////////////////////////////////////////////////////////////////// +#ifdef XP_WIN +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBeckyImport) +#endif // XP_WIN + +static const mozilla::Module::CategoryEntry kMailNewsImportCategories[] = { + // XXX These CIDs should match the explicit CIDs defined in the header files, + // or be changed so that they are contract IDs (with appropriate code updates) + { "mailnewsimport", "{A5991D01-ADA7-11d3-A9C2-00A0CC26DA63}", NS_IMPORT_ADDRESS_STR }, + { "mailnewsimport", "{0eb034a3-964a-4e2f-92eb-cc55d9ae9dd2}", NS_IMPORT_ADDRESS_STR }, +#ifdef XP_WIN + { "mailnewsimport", "{42bc82bc-8e9f-4597-8b6e-e529daaf3af1}", kWMSupportsString }, + { "mailnewsimport", "{be0bc880-1742-11d3-a206-00a0cc26da63}", kOESupportsString }, + { "mailnewsimport", "{7952a6cf-2442-4c04-9f02-150b15a0a841}", kBeckySupportsString }, +#ifdef MOZ_MAPI_SUPPORT + { "mailnewsimport", "{1DB469A0-8B00-11d3-A206-00A0CC26DA63}", kOutlookSupportsString }, +#endif +#endif +#if defined(XP_MACOSX) + { "mailnewsimport", "{6d3f101c-70ec-4e04-b68d-9908d1aeddf3}", kAppleMailSupportsString }, +#endif + { NULL } +}; + +const mozilla::Module::CIDEntry kMailNewsImportCIDs[] = { + { &kNS_IMPORTSERVICE_CID, false, NULL, nsImportServiceConstructor }, + { &kNS_IMPORTMIMEENCODE_CID, false, NULL, nsIImportMimeEncodeImplConstructor }, + { &kNS_TEXTIMPORT_CID, false, NULL, nsTextImportConstructor }, + { &kNS_VCARDIMPORT_CID, false, NULL, nsVCardImportConstructor }, +#if defined(XP_MACOSX) + { &kNS_APPLEMAILIMPORT_CID, false, NULL, nsAppleMailImportModuleConstructor }, + { &kNS_APPLEMAILIMPL_CID, false, NULL, nsAppleMailImportMailConstructor }, +#endif + +#ifdef XP_WIN + { &kNS_OEIMPORT_CID, false, NULL, nsOEImportConstructor }, + { &kNS_WMIMPORT_CID, false, NULL, nsWMImportConstructor }, + { &kNS_BECKYIMPORT_CID, false, NULL, nsBeckyImportConstructor }, +#ifdef MOZ_MAPI_SUPPORT + { &kNS_OUTLOOKIMPORT_CID, false, NULL, nsOutlookImportConstructor }, +#endif +#endif + { NULL } +}; + +const mozilla::Module::ContractIDEntry kMailNewsImportContracts[] = { + { NS_IMPORTSERVICE_CONTRACTID, &kNS_IMPORTSERVICE_CID }, + { "@mozilla.org/import/import-mimeencode;1", &kNS_IMPORTMIMEENCODE_CID }, + { "@mozilla.org/import/import-text;1", &kNS_TEXTIMPORT_CID }, + { "@mozilla.org/import/import-vcard;1", &kNS_VCARDIMPORT_CID }, +#if defined(XP_MACOSX) + { "@mozilla.org/import/import-applemail;1", &kNS_APPLEMAILIMPORT_CID }, + { NS_APPLEMAILIMPL_CONTRACTID, &kNS_APPLEMAILIMPL_CID }, +#endif + +#ifdef XP_WIN + { "@mozilla.org/import/import-oe;1", &kNS_OEIMPORT_CID }, + { "@mozilla.org/import/import-wm;1", &kNS_WMIMPORT_CID }, + { "@mozilla.org/import/import-becky;1", &kNS_BECKYIMPORT_CID }, +#ifdef MOZ_MAPI_SUPPORT + { "@mozilla.org/import/import-outlook;1", &kNS_OUTLOOKIMPORT_CID }, +#endif +#endif + { NULL } +}; + + +static void importModuleDtor() +{ +#ifdef XP_WIN + + nsOEStringBundle::Cleanup(); + nsWMStringBundle::Cleanup(); + nsBeckyStringBundle::Cleanup(); +#ifdef MOZ_MAPI_SUPPORT + nsOutlookStringBundle::Cleanup(); +#endif +#endif +} + +static const mozilla::Module kMailNewsImportModule = { + mozilla::Module::kVersion, + kMailNewsImportCIDs, + kMailNewsImportContracts, + kMailNewsImportCategories, + NULL, + NULL, + importModuleDtor +}; + +NSMODULE_DEFN(nsImportServiceModule) = &kMailNewsImportModule; + + diff --git a/mailnews/import/content/fieldMapImport.js b/mailnews/import/content/fieldMapImport.js new file mode 100644 index 000000000..cd0f2d5da --- /dev/null +++ b/mailnews/import/content/fieldMapImport.js @@ -0,0 +1,186 @@ +/* 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 importService; +var fieldMap = null; +var recordNum = 0; +var addInterface = null; +var dialogResult = null; +var gPreviousButton; +var gNextButton; +var gMoveUpButton; +var gMoveDownButton; +var gListbox; +var gSkipFirstRecordButton; + +function OnLoadFieldMapImport() +{ + top.importService = Components.classes["@mozilla.org/import/import-service;1"] + .getService(Components.interfaces.nsIImportService); + + // We need a field map object... + // assume we have one passed in? or just make one? + if (window.arguments && window.arguments[0]) { + top.fieldMap = window.arguments[0].fieldMap; + top.addInterface = window.arguments[0].addInterface; + top.dialogResult = window.arguments[0].result; + } + if (top.fieldMap == null) { + top.fieldMap = top.importService.CreateNewFieldMap(); + top.fieldMap.DefaultFieldMap( top.fieldMap.numMozFields); + } + + gMoveUpButton = document.getElementById("upButton"); + gMoveDownButton = document.getElementById("downButton"); + gPreviousButton = document.getElementById("previous"); + gNextButton = document.getElementById("next"); + gListbox = document.getElementById("fieldList"); + gSkipFirstRecordButton = document.getElementById("skipFirstRecord"); + + // Set the state of the skip first record button + gSkipFirstRecordButton.checked = top.fieldMap.skipFirstRecord; + + ListFields(); + Browse(1); + gListbox.selectedItem = gListbox.getItemAtIndex(0); + disableMoveButtons(); +} + +function IndexInMap( index) +{ + var count = top.fieldMap.mapSize; + for (var i = 0; i < count; i++) { + if (top.fieldMap.GetFieldMap( i) == index) + return( true); + } + + return( false); +} + +function ListFields() { + if (top.fieldMap == null) + return; + + var count = top.fieldMap.mapSize; + var index; + var i; + for (i = 0; i < count; i++) { + index = top.fieldMap.GetFieldMap( i); + AddFieldToList(top.fieldMap.GetFieldDescription( index), index, top.fieldMap.GetFieldActive( i)); + } + + count = top.fieldMap.numMozFields; + for (i = 0; i < count; i++) { + if (!IndexInMap( i)) + AddFieldToList(top.fieldMap.GetFieldDescription( i), i, false); + } +} + +function CreateField( name, index, on) +{ + var item = document.createElement('listitem'); + item.setAttribute('field-index', index); + item.setAttribute('type', "checkbox"); + var cell = document.createElement('listcell'); + var cCell = document.createElement( 'listcell'); + cCell.setAttribute('type', "checkbox"); + cCell.setAttribute( 'label', name); + if (on == true) + cCell.setAttribute( 'checked', "true"); + item.appendChild( cCell); + cell.setAttribute( "class", "importsampledata"); + cell.setAttribute( 'label', ""); + item.appendChild( cell); + return( item); +} + +function AddFieldToList(name, index, on) +{ + var item = CreateField(name, index, on); + gListbox.appendChild(item); +} + +function itemClicked(event) +{ + if (event.button == 0) { + var on = gListbox.selectedItem.firstChild.getAttribute('checked'); + gListbox.selectedItem.firstChild.setAttribute('checked', (on != "true")); + } +} + +// The "Move Up/Move Down" buttons should move the items in the left column +// up/down but the values in the right column should not change. +function moveItem(up) +{ + var selectedItem = gListbox.selectedItem; + var swapPartner = (up ? gListbox.getPreviousItem(selectedItem, 1) + : gListbox.getNextItem(selectedItem, 1)); + + var tmpLabel = swapPartner.lastChild.getAttribute('label'); + swapPartner.lastChild.setAttribute('label', selectedItem.lastChild.getAttribute('label')); + selectedItem.lastChild.setAttribute('label', tmpLabel); + + var newItemPosition = (up ? selectedItem.nextSibling : selectedItem); + gListbox.insertBefore(swapPartner, newItemPosition); + gListbox.ensureElementIsVisible(selectedItem); + disableMoveButtons(); +} + +function disableMoveButtons() +{ + var selectedIndex = gListbox.selectedIndex; + gMoveUpButton.disabled = (selectedIndex == 0); + gMoveDownButton.disabled = (selectedIndex == (gListbox.getRowCount() - 1)); +} + +function ShowSampleData(data) +{ + var fields = data.split("\n"); + for (var i = 0; i < gListbox.getRowCount(); i++) + gListbox.getItemAtIndex(i).lastChild.setAttribute('label', (i < fields.length) ? fields[i] : ''); +} + +function FetchSampleData(num) +{ + if (!top.addInterface) + return false; + + var data = top.addInterface.GetData( "sampleData-" + num); + if (!(data instanceof Components.interfaces.nsISupportsString)) + return false; + ShowSampleData( data.data); + return true; +} + +function Browse(step) +{ + recordNum += step; + if (FetchSampleData(recordNum - 1)) + document.getElementById('recordNumber').setAttribute('value', ("" + recordNum)); + + gPreviousButton.disabled = (recordNum == 1); + gNextButton.disabled = (addInterface.GetData("sampleData-" + recordNum) == null); +} + +function FieldImportOKButton() +{ + var max = gListbox.getRowCount(); + var fIndex; + var on; + // Ensure field map is the right size + top.fieldMap.SetFieldMapSize(max); + + for (var i = 0; i < max; i++) { + fIndex = gListbox.getItemAtIndex(i).getAttribute( 'field-index'); + on = gListbox.getItemAtIndex(i).firstChild.getAttribute('checked'); + top.fieldMap.SetFieldMap( i, fIndex); + top.fieldMap.SetFieldActive( i, (on == "true")); + } + + top.fieldMap.skipFirstRecord = gSkipFirstRecordButton.checked; + + top.dialogResult.ok = true; + + return true; +} diff --git a/mailnews/import/content/fieldMapImport.xul b/mailnews/import/content/fieldMapImport.xul new file mode 100644 index 000000000..abaca10ba --- /dev/null +++ b/mailnews/import/content/fieldMapImport.xul @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/fieldMapImport.dtd"> + +<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept,cancel" + title="&fieldMapImport.title;" + style="&fieldMapImport.size;" + ondialogaccept="FieldImportOKButton();" + onload="OnLoadFieldMapImport();"> + + <script type="application/javascript" src="chrome://messenger/content/fieldMapImport.js"/> + + <hbox align="center"> + <label value="&fieldMapImport.recordNumber;"/> + <label id="recordNumber"/> + <spacer flex="1"/> + <button id="previous" oncommand="Browse(-1);" + label="&fieldMapImport.previous.label;" + accesskey="&fieldMapImport.previous.accesskey;"/> + <button id="next" oncommand="Browse(1);" + label="&fieldMapImport.next.label;" + accesskey="&fieldMapImport.next.accesskey;"/> + </hbox> + + <hbox align="center"> + <checkbox id="skipFirstRecord" + label="&fieldMapImport.skipFirstRecord.label;" + accesskey="&fieldMapImport.skipFirstRecord.accessKey;"/> + </hbox> + + <separator class="thin"/> + <label control="fieldList">&fieldMapImport.text;</label> + <separator class="thin"/> + + <!-- field list --> + <hbox flex="1"> + <listbox id="fieldList" flex="1" onselect="disableMoveButtons();" + onclick="itemClicked(event);"> + <listcols> + <listcol flex="7"/> + <listcol flex="13"/> + </listcols> + + <listhead> + <listheader id="fieldNameHeader" label="&fieldMapImport.fieldListTitle;"/> + <listheader id="sampleDataHeader" label="&fieldMapImport.dataTitle;"/> + </listhead> + </listbox> + + <vbox> + <spacer flex="1"/> + <button id="upButton" class="up" label="&fieldMapImport.up.label;" + accesskey="&fieldMapImport.up.accesskey;" + oncommand="moveItem(true);"/> + <button id="downButton" class="down" label="&fieldMapImport.down.label;" + accesskey="&fieldMapImport.down.accesskey;" + oncommand="moveItem(false);"/> + <spacer flex="1"/> + </vbox> + </hbox> + +</dialog> diff --git a/mailnews/import/content/import-test.html b/mailnews/import/content/import-test.html new file mode 100644 index 000000000..ef9c5ed7f --- /dev/null +++ b/mailnews/import/content/import-test.html @@ -0,0 +1,36 @@ +<!-- 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/. --> + +<html> +<body> + +<script type="application/javascript"> + +function toImport(importType) +{ + /* + window.openDialog("chrome:/messenger/content/fieldMapImport.xul", + "fieldMapImportDialog", + "chrome,modal"); + */ + window.openDialog("chrome:/messenger/content/importDialog.xul", + "", + "chrome,modal", + {importType:importType}); + +} + + +</script> + +<p> +<form name="form"> +<input type="button" value="Import Address Books" onclick="toImport( 'addressbook');"><br> +<input type="button" value="Import Mail" onclick="toImport( 'mail');"><br> +<input type="button" value="Import Settings" onclick="toImport( 'settings');"><br> +<input type="button" value="Import Filters" onclick="toImport( 'filters');"><br> +<form> + +</body> +</html> diff --git a/mailnews/import/content/importDialog.js b/mailnews/import/content/importDialog.js new file mode 100644 index 000000000..2ad7c6fb3 --- /dev/null +++ b/mailnews/import/content/importDialog.js @@ -0,0 +1,1066 @@ +/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +Components.utils.import("resource:///modules/mailServices.js"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +var gImportType = null; +var gImportMsgsBundle; +var gFeedsBundle; +var gImportService = null; +var gSuccessStr = null; +var gErrorStr = null; +var gInputStr = null; +var gProgressInfo = null; +var gSelectedModuleName = null; +var gAddInterface = null; +var gNewFeedAcctCreated = false; + +var Ci = Components.interfaces; +var nsISupportsString = Ci.nsISupportsString; + +function OnLoadImportDialog() +{ + gImportMsgsBundle = document.getElementById("bundle_importMsgs"); + gFeedsBundle = document.getElementById("bundle_feeds"); + gImportService = Components.classes["@mozilla.org/import/import-service;1"] + .getService(Ci.nsIImportService); + + gProgressInfo = { }; + gProgressInfo.progressWindow = null; + gProgressInfo.importInterface = null; + gProgressInfo.mainWindow = window; + gProgressInfo.intervalState = 0; + gProgressInfo.importSuccess = false; + gProgressInfo.importType = null; + gProgressInfo.localFolderExists = false; + + gSuccessStr = Components.classes["@mozilla.org/supports-string;1"] + .createInstance(nsISupportsString); + gErrorStr = Components.classes["@mozilla.org/supports-string;1"] + .createInstance(nsISupportsString); + gInputStr = Components.classes["@mozilla.org/supports-string;1"] + .createInstance(nsISupportsString); + + // look in arguments[0] for parameters + if (("arguments" in window) && window.arguments.length >= 1 && + ("importType" in window.arguments[0]) && window.arguments[0].importType) + { + // keep parameters in global for later + gImportType = window.arguments[0].importType; + gProgressInfo.importType = gImportType; + } + else + { + gImportType = "all"; + gProgressInfo.importType = "all"; + } + + SetUpImportType(); + + // on startup, set the focus to the control element + // for accessibility reasons. + // if we used the wizardOverlay, we would get this for free. + // see bug #101874 + document.getElementById("importFields").focus(); +} + + +function SetUpImportType() +{ + // set dialog title + document.getElementById("importFields").value = gImportType; + + // Mac migration not working right now, so disable it. + if (Services.appinfo.OS == "Darwin") + { + document.getElementById("allRadio").setAttribute("disabled", "true"); + if (gImportType == "all") + document.getElementById("importFields").value = "addressbook"; + } + + let descriptionDeck = document.getElementById("selectDescriptionDeck"); + descriptionDeck.selectedIndex = 0; + if (gImportType == "feeds") + { + descriptionDeck.selectedIndex = 1; + ListFeedAccounts(); + } + else + ListModules(); +} + + +function SetDivText(id, text) +{ + var div = document.getElementById(id); + + if (div) { + if (!div.hasChildNodes()) { + var textNode = document.createTextNode(text); + div.appendChild(textNode); + } + else if (div.childNodes.length == 1) { + div.childNodes[0].nodeValue = text; + } + } +} + +function CheckIfLocalFolderExists() +{ + try { + if (MailServices.accounts.localFoldersServer) + gProgressInfo.localFolderExists = true; + } + catch (ex) { + gProgressInfo.localFolderExists = false; + } +} + +function ImportDialogOKButton() +{ + var listbox = document.getElementById("moduleList"); + var deck = document.getElementById("stateDeck"); + var header = document.getElementById("header"); + var progressMeterEl = document.getElementById("progressMeter"); + progressMeterEl.mode = "determined"; + var progressStatusEl = document.getElementById("progressStatus"); + var progressTitleEl = document.getElementById("progressTitle"); + + // better not mess around with navigation at this point + var nextButton = document.getElementById("forward"); + nextButton.setAttribute("disabled", "true"); + var backButton = document.getElementById("back"); + backButton.setAttribute("disabled", "true"); + + if (listbox && (listbox.selectedCount == 1)) + { + let module = ""; + let name = ""; + gImportType = document.getElementById("importFields").value; + let index = listbox.selectedItem.getAttribute("list-index"); + if (index == -1) + return false; + if (gImportType == "feeds") + module = "Feeds"; + else + { + module = gImportService.GetModule(gImportType, index); + name = gImportService.GetModuleName(gImportType, index); + } + gSelectedModuleName = name; + if (module) + { + // Fix for Bug 57839 & 85219 + // We use localFoldersServer(in nsIMsgAccountManager) to check if Local Folder exists. + // We need to check localFoldersServer before importing "mail", "settings", or "filters". + // Reason: We will create an account with an incoming server of type "none" after + // importing "mail", so the localFoldersServer is valid even though the Local Folder + // is not created. + if (gImportType == "mail" || gImportType == "settings" || gImportType == "filters") + CheckIfLocalFolderExists(); + + let meterText = ""; + let error = {}; + switch(gImportType) + { + case "mail": + if (ImportMail(module, gSuccessStr, gErrorStr)) + { + // We think it was a success, either, we need to + // wait for the import to finish + // or we are done! + if (gProgressInfo.importInterface == null) { + ShowImportResults(true, 'Mail'); + return true; + } + else { + meterText = gImportMsgsBundle.getFormattedString('MailProgressMeterText', + [ name ]); + header.setAttribute("description", meterText); + + progressStatusEl.setAttribute("label", ""); + progressTitleEl.setAttribute("label", meterText); + + deck.selectedIndex = 2; + gProgressInfo.progressWindow = window; + gProgressInfo.intervalState = setInterval(ContinueImportCallback, 100); + return true; + } + } + else + { + ShowImportResults(false, 'Mail'); + // Re-enable the next button, as we are here, because the user cancelled the picking. + // Enable next, so they can try again. + nextButton.removeAttribute("disabled"); + // Also enable back button so that users can pick other import options. + backButton.removeAttribute("disabled"); + return false; + } + break; + + case "feeds": + if (ImportFeeds()) + { + // Successful completion of pre processing and launch of async import. + meterText = document.getElementById("description").textContent; + header.setAttribute("description", meterText); + + progressStatusEl.setAttribute("label", ""); + progressTitleEl.setAttribute("label", meterText); + progressMeterEl.mode = "undetermined"; + + deck.selectedIndex = 2; + return true; + } + else + { + // Re-enable the next button, as we are here, because the user cancelled the picking. + // Enable next, so they can try again. + nextButton.removeAttribute("disabled"); + // Also enable back button so that users can pick other import options. + backButton.removeAttribute("disabled"); + return false; + } + break; + + case "addressbook": + if (ImportAddress(module, gSuccessStr, gErrorStr)) { + // We think it was a success, either, we need to + // wait for the import to finish + // or we are done! + if (gProgressInfo.importInterface == null) { + ShowImportResults(true, 'Address'); + return true; + } + else { + meterText = gImportMsgsBundle.getFormattedString('AddrProgressMeterText', + [ name ]); + header.setAttribute("description", meterText); + + progressStatusEl.setAttribute("label", ""); + progressTitleEl.setAttribute("label", meterText); + + deck.selectedIndex = 2; + gProgressInfo.progressWindow = window; + gProgressInfo.intervalState = setInterval(ContinueImportCallback, 100); + + return true; + } + } + else + { + ShowImportResults(false, 'Address'); + // Re-enable the next button, as we are here, because the user cancelled the picking. + // Enable next, so they can try again. + nextButton.removeAttribute("disabled"); + // Also enable back button so that users can pick other import options. + backButton.removeAttribute("disabled"); + return false; + } + break; + + case "settings": + error.value = null; + let newAccount = {}; + if (!ImportSettings(module, newAccount, error)) + { + if (error.value) + ShowImportResultsRaw(gImportMsgsBundle.getString("ImportSettingsFailed"), + null, false); + // Re-enable the next button, as we are here, because the user cancelled the picking. + // Enable next, so they can try again. + nextButton.removeAttribute("disabled"); + // Also enable back button so that users can pick other import options. + backButton.removeAttribute("disabled"); + return false; + } + else + ShowImportResultsRaw( + gImportMsgsBundle.getFormattedString("ImportSettingsSuccess", [ name ]), + null, true); + break; + + case "filters": + error.value = null; + if (!ImportFilters(module, error)) + { + if (error.value) + ShowImportResultsRaw( + gImportMsgsBundle.getFormattedString("ImportFiltersFailed", [ name ]), + error.value, false); + // Re-enable the next button, as we are here, because the user cancelled the picking. + // Enable next, so they can try again. + nextButton.removeAttribute("disabled"); + // Also enable back button so that users can pick other import options. + backButton.removeAttribute("disabled"); + return false; + } + else + { + if (error.value) + ShowImportResultsRaw( + gImportMsgsBundle.getFormattedString('ImportFiltersPartial', [ name ]), + error.value, true); + else + ShowImportResultsRaw( + gImportMsgsBundle.getFormattedString('ImportFiltersSuccess', [ name ]), + null, true); + } + break; + } + } + } + + return true; +} + +function SetStatusText(val) +{ + var progressStatus = document.getElementById("progressStatus"); + progressStatus.setAttribute("label", val); +} + +function SetProgress(val) +{ + var progressMeter = document.getElementById("progressMeter"); + progressMeter.value = val; +} + +function ContinueImportCallback() +{ + gProgressInfo.mainWindow.ContinueImport(gProgressInfo); +} + +function ImportSelectionChanged() +{ + let listbox = document.getElementById('moduleList'); + let acctNameBox = document.getElementById('acctName-box'); + if (listbox && (listbox.selectedCount == 1)) + { + let index = listbox.selectedItem.getAttribute("list-index"); + if (index == -1) + return; + acctNameBox.setAttribute('style', 'visibility: hidden;'); + if (gImportType == "feeds") + { + if (index == 0) + { + SetDivText('description', gFeedsBundle.getString('ImportFeedsNewAccount')); + let defaultName = gFeedsBundle.getString("feeds-accountname"); + document.getElementById("acctName").value = defaultName; + acctNameBox.removeAttribute('style'); + } + else + SetDivText('description', gFeedsBundle.getString('ImportFeedsExistingAccount')); + } + else + SetDivText("description", gImportService.GetModuleDescription(gImportType, index)); + } +} + +function CompareImportModuleName(a, b) +{ + if (a.name > b.name) + return 1; + if (a.name < b.name) + return -1; + return 0; +} + +function ListModules() { + if (gImportService == null) + return; + + var body = document.getElementById("moduleList"); + while (body.hasChildNodes()) { + body.lastChild.remove(); + } + + var count = gImportService.GetModuleCount(gImportType); + var i; + + var moduleArray = new Array(count); + for (i = 0; i < count; i++) { + moduleArray[i] = { name: gImportService.GetModuleName(gImportType, i), index: i }; + } + + // sort the array of modules by name, so that they'll show up in the right order + moduleArray.sort(CompareImportModuleName); + + for (i = 0; i < count; i++) { + AddModuleToList(moduleArray[i].name, moduleArray[i].index); + } +} + +function AddModuleToList(moduleName, index) +{ + var body = document.getElementById("moduleList"); + + var item = document.createElement('listitem'); + item.setAttribute('label', moduleName); + + // Temporarily skip Outlook Import which are busted (Bug 1175055). + if (moduleName == "Outlook") { + item.setAttribute('list-index', -1); + item.setAttribute('disabled', true); + item.setAttribute('tooltiptext', "Currently disabled due to bug 1175055"); + } else { + item.setAttribute('list-index', index); + } + body.appendChild(item); +} + +function ListFeedAccounts() { + let body = document.getElementById("moduleList"); + while (body.hasChildNodes()) + body.lastChild.remove(); + + // Add item to allow for new account creation. + let item = document.createElement("listitem"); + item.setAttribute("label", gFeedsBundle.getString('ImportFeedsCreateNewListItem')); + item.setAttribute("list-index", 0); + body.appendChild(item); + + let index = 0; + let feedRootFolders = FeedUtils.getAllRssServerRootFolders(); + + feedRootFolders.forEach(function(rootFolder) { + item = document.createElement("listitem"); + item.setAttribute("label", rootFolder.prettyName); + item.setAttribute("list-index", ++index); + item.server = rootFolder.server; + body.appendChild(item); + }, this); + + if (index) + // If there is an existing feed account, select the first one. + body.selectedIndex = 1; +} + +function ContinueImport(info) { + var isMail = info.importType == 'mail'; + var clear = true; + var deck; + var pcnt; + + if (info.importInterface) { + if (!info.importInterface.ContinueImport()) { + info.importSuccess = false; + clearInterval(info.intervalState); + if (info.progressWindow != null) { + deck = document.getElementById("stateDeck"); + deck.selectedIndex = 3; + info.progressWindow = null; + } + + ShowImportResults(false, isMail ? 'Mail' : 'Address'); + } + else if ((pcnt = info.importInterface.GetProgress()) < 100) { + clear = false; + if (info.progressWindow != null) { + if (pcnt < 5) + pcnt = 5; + SetProgress(pcnt); + if (isMail) { + let mailName = info.importInterface.GetData("currentMailbox"); + if (mailName) { + mailName = mailName.QueryInterface(Ci.nsISupportsString); + if (mailName) + SetStatusText(mailName.data); + } + } + } + } + else { + dump("*** WARNING! sometimes this shows results too early. \n"); + dump(" something screwy here. this used to work fine.\n"); + clearInterval(info.intervalState); + info.importSuccess = true; + if (info.progressWindow) { + deck = document.getElementById("stateDeck"); + deck.selectedIndex = 3; + info.progressWindow = null; + } + + ShowImportResults(true, isMail ? 'Mail' : 'Address'); + } + } + if (clear) { + info.intervalState = null; + info.importInterface = null; + } +} + + +function ShowResults(doesWantProgress, result) +{ + if (result) + { + if (doesWantProgress) + { + let deck = document.getElementById("stateDeck"); + let header = document.getElementById("header"); + let progressStatusEl = document.getElementById("progressStatus"); + let progressTitleEl = document.getElementById("progressTitle"); + + let meterText = gImportMsgsBundle.getFormattedString("AddrProgressMeterText", [ name ]); + header.setAttribute("description", meterText); + + progressStatusEl.setAttribute("label", ""); + progressTitleEl.setAttribute("label", meterText); + + deck.selectedIndex = 2; + gProgressInfo.progressWindow = window; + gProgressInfo.intervalState = setInterval(ContinueImportCallback, 100); + } + else + { + ShowImportResults(true, 'Address'); + } + } + else + { + ShowImportResults(false, 'Address'); + } + + return true; +} + +function ShowImportResults(good, module) +{ + // String keys for ImportSettingsSuccess, ImportSettingsFailed, + // ImportMailSuccess, ImportMailFailed, ImportAddressSuccess, + // ImportAddressFailed, ImportFiltersSuccess, and ImportFiltersFailed. + var modSuccess = 'Import' + module + 'Success'; + var modFailed = 'Import' + module + 'Failed'; + + // The callers seem to set 'good' to true even if there's something + // in the error log. So we should only make it a success case if + // error log/str is empty. + var results, title; + var moduleName = gSelectedModuleName ? gSelectedModuleName : ""; + if (good && !gErrorStr.data) { + title = gImportMsgsBundle.getFormattedString(modSuccess, [ moduleName ]); + results = gSuccessStr.data; + } + else if (gErrorStr.data) { + title = gImportMsgsBundle.getFormattedString(modFailed, [ moduleName ]); + results = gErrorStr.data; + } + + if (results && title) + ShowImportResultsRaw(title, results, good); +} + +function ShowImportResultsRaw(title, results, good) +{ + SetDivText("status", title); + var header = document.getElementById("header"); + header.setAttribute("description", title); + dump("*** results = " + results + "\n"); + attachStrings("results", results); + var deck = document.getElementById("stateDeck"); + deck.selectedIndex = 3; + var nextButton = document.getElementById("forward"); + nextButton.label = nextButton.getAttribute("finishedval"); + nextButton.removeAttribute("disabled"); + var cancelButton = document.getElementById("cancel"); + cancelButton.setAttribute("disabled", "true"); + var backButton = document.getElementById("back"); + backButton.setAttribute("disabled", "true"); + + // If the Local Folder doesn't exist, create it after successfully + // importing "mail" and "settings" + var checkLocalFolder = (gProgressInfo.importType == "mail" || + gProgressInfo.importType == "settings"); + if (good && checkLocalFolder && !gProgressInfo.localFolderExists) { + MailServices.accounts.createLocalMailAccount(); + } +} + +function attachStrings(aNode, aString) +{ + var attachNode = document.getElementById(aNode); + if (!aString) { + attachNode.parentNode.setAttribute("hidden", "true"); + return; + } + var strings = aString.split("\n"); + for (let string of strings) { + if (string) { + let currNode = document.createTextNode(string); + attachNode.appendChild(currNode); + let br = document.createElementNS("http://www.w3.org/1999/xhtml", 'br'); + attachNode.appendChild(br); + } + } +} + +/* + Import Settings from a specific module, returns false if it failed + and true if successful. A "local mail" account is returned in newAccount. + This is only useful in upgrading - import the settings first, then + import mail into the account returned from ImportSettings, then + import address books. + An error string is returned as error.value +*/ +function ImportSettings(module, newAccount, error) { + var setIntf = module.GetImportInterface("settings"); + if (!(setIntf instanceof Ci.nsIImportSettings)) { + error.value = gImportMsgsBundle.getString('ImportSettingsBadModule'); + return false; + } + + // determine if we can auto find the settings or if we need to ask the user + var location = {}; + var description = {}; + var result = setIntf.AutoLocate(description, location); + if (!result) { + // In this case, we couldn't find the settings + if (location.value != null) { + // Settings were not found, however, they are specified + // in a file, so ask the user for the settings file. + let filePicker = Components.classes["@mozilla.org/filepicker;1"].createInstance(); + if (filePicker instanceof Ci.nsIFilePicker) { + let file = null; + try { + filePicker.init(window, + gImportMsgsBundle.getString("ImportSelectSettings"), + filePicker.modeOpen); + filePicker.appendFilters(filePicker.filterAll); + filePicker.show(); + file = filePicker.file; + } + catch(ex) { + file = null; + error.value = null; + return false; + } + if (file != null) { + setIntf.SetLocation(file); + } + else { + error.value = null; + return false; + } + } + else { + error.value = gImportMsgsBundle.getString('ImportSettingsNotFound'); + return false; + } + } + else { + error.value = gImportMsgsBundle.getString('ImportSettingsNotFound'); + return false; + } + } + + // interesting, we need to return the account that new + // mail should be imported into? + // that's really only useful for "Upgrade" + result = setIntf.Import(newAccount); + if (!result) { + error.value = gImportMsgsBundle.getString('ImportSettingsFailed'); + } + return result; +} + +function ImportMail(module, success, error) { + if (gProgressInfo.importInterface || gProgressInfo.intervalState) { + error.data = gImportMsgsBundle.getString('ImportAlreadyInProgress'); + return false; + } + + gProgressInfo.importSuccess = false; + + var mailInterface = module.GetImportInterface("mail"); + if (!(mailInterface instanceof Ci.nsIImportGeneric)) { + error.data = gImportMsgsBundle.getString('ImportMailBadModule'); + return false; + } + + var loc = mailInterface.GetData("mailLocation"); + + if (loc == null) { + // No location found, check to see if we can ask the user. + if (mailInterface.GetStatus("canUserSetLocation") != 0) { + let filePicker = Components.classes["@mozilla.org/filepicker;1"].createInstance(); + if (filePicker instanceof Ci.nsIFilePicker) { + try { + filePicker.init(window, + gImportMsgsBundle.getString("ImportSelectMailDir"), + filePicker.modeGetFolder); + filePicker.appendFilters(filePicker.filterAll); + filePicker.show(); + if (filePicker.file && (filePicker.file.path.length > 0)) + mailInterface.SetData("mailLocation", filePicker.file); + else + return false; + } catch(ex) { + // don't show an error when we return! + return false; + } + } + else { + error.data = gImportMsgsBundle.getString('ImportMailNotFound'); + return false; + } + } + else { + error.data = gImportMsgsBundle.getString('ImportMailNotFound'); + return false; + } + } + + if (mailInterface.WantsProgress()) { + if (mailInterface.BeginImport(success, error)) { + gProgressInfo.importInterface = mailInterface; + // intervalState = setInterval(ContinueImport, 100); + return true; + } + else + return false; + } + else + return mailInterface.BeginImport(success, error); +} + + +// The address import! A little more complicated than the mail import +// due to field maps... +function ImportAddress(module, success, error) { + if (gProgressInfo.importInterface || gProgressInfo.intervalState) { + error.data = gImportMsgsBundle.getString('ImportAlreadyInProgress'); + return false; + } + + gProgressInfo.importSuccess = false; + + gAddInterface = module.GetImportInterface("addressbook"); + if (!(gAddInterface instanceof Ci.nsIImportGeneric)) { + error.data = gImportMsgsBundle.getString('ImportAddressBadModule'); + return false; + } + + var loc = gAddInterface.GetStatus("autoFind"); + if (loc == 0) { + loc = gAddInterface.GetData("addressLocation"); + if ((loc instanceof Ci.nsIFile) && !loc.exists) + loc = null; + } + + if (loc == null) { + // Couldn't find the address book, see if we can + // as the user for the location or not? + if (gAddInterface.GetStatus("canUserSetLocation") == 0) { + // an autofind address book that could not be found! + error.data = gImportMsgsBundle.getString('ImportAddressNotFound'); + return false; + } + + let filePicker = Components.classes["@mozilla.org/filepicker;1"].createInstance(); + if (!(filePicker instanceof Ci.nsIFilePicker)) { + error.data = gImportMsgsBundle.getString('ImportAddressNotFound'); + return false; + } + + // The address book location was not found. + // Determine if we need to ask for a directory + // or a single file. + let file = null; + let fileIsDirectory = false; + if (gAddInterface.GetStatus("supportsMultiple") != 0) { + // ask for dir + try { + filePicker.init(window, + gImportMsgsBundle.getString("ImportSelectAddrDir"), + filePicker.modeGetFolder); + filePicker.appendFilters(filePicker.filterAll); + filePicker.show(); + if (filePicker.file && (filePicker.file.path.length > 0)) { + file = filePicker.file; + fileIsDirectory = true; + } + else { + file = null; + } + } catch(ex) { + file = null; + } + } + else { + // ask for file + try { + filePicker.init(window, + gImportMsgsBundle.getString("ImportSelectAddrFile"), + filePicker.modeOpen); + let addressbookBundle = document.getElementById("bundle_addressbook"); + if (gSelectedModuleName == + document.getElementById("bundle_vcardImportMsgs") + .getString("vCardImportName")) { + filePicker.appendFilter(addressbookBundle.getString('VCFFiles'), "*.vcf"); + } else { + filePicker.appendFilter(addressbookBundle.getString('LDIFFiles'), "*.ldi; *.ldif"); + filePicker.appendFilter(addressbookBundle.getString('CSVFiles'), "*.csv"); + filePicker.appendFilter(addressbookBundle.getString('TABFiles'), "*.tab; *.txt"); + filePicker.appendFilters(filePicker.filterAll); + } + + if (filePicker.show() == filePicker.returnCancel) + return false; + + if (filePicker.file && (filePicker.file.path.length > 0)) + file = filePicker.file; + else + file = null; + } catch(ex) { + dump("ImportAddress(): failure when picking a file to import: " + ex + "\n"); + file = null; + } + } + + if (file == null) { + return false; + } + + if (!fileIsDirectory && (file.fileSize == 0)) { + let errorText = gImportMsgsBundle.getFormattedString("ImportEmptyAddressBook", + [filePicker.file.leafName]); + + Services.prompt.alert(window, document.title, errorText); + return false; + } + gAddInterface.SetData("addressLocation", file); + } + + var map = gAddInterface.GetData("fieldMap"); + if (map instanceof Ci.nsIImportFieldMap) { + let result = {}; + result.ok = false; + window.openDialog( + "chrome://messenger/content/fieldMapImport.xul", + "", + "chrome,modal,titlebar", + { fieldMap: map, + addInterface: gAddInterface, + result: result }); + + if (!result.ok) + return false; + } + + if (gAddInterface.WantsProgress()) { + if (gAddInterface.BeginImport(success, error)) { + gProgressInfo.importInterface = gAddInterface; + // intervalState = setInterval(ContinueImport, 100); + return true; + } + return false; + } + + return gAddInterface.BeginImport(success, error); +} + +/* + Import filters from a specific module. + Returns false if it failed and true if it succeeded. + An error string is returned as error.value. +*/ +function ImportFilters(module, error) +{ + if (gProgressInfo.importInterface || gProgressInfo.intervalState) { + error.data = gImportMsgsBundle.getString('ImportAlreadyInProgress'); + return false; + } + + gProgressInfo.importSuccess = false; + + var filtersInterface = module.GetImportInterface("filters"); + if (!(filtersInterface instanceof Ci.nsIImportFilters)) { + error.data = gImportMsgsBundle.getString('ImportFiltersBadModule'); + return false; + } + + return filtersInterface.Import(error); +} + +/* + Import feeds. +*/ +function ImportFeeds() +{ + // Get file to open from filepicker. + let openFile = FeedSubscriptions.opmlPickOpenFile(); + if (!openFile) + return false; + + let acctName; + let acctNewExist = gFeedsBundle.getString("ImportFeedsExisting"); + let fileName = openFile.path; + let server = document.getElementById("moduleList").selectedItem.server; + gNewFeedAcctCreated = false; + + if (!server) + { + // Create a new Feeds account. + acctName = document.getElementById("acctName").value; + server = FeedUtils.createRssAccount(acctName).incomingServer; + acctNewExist = gFeedsBundle.getString("ImportFeedsNew"); + gNewFeedAcctCreated = true; + } + + acctName = server.rootFolder.prettyName; + + let callback = function(aStatusReport, aLastFolder , aFeedWin) + { + let message = gFeedsBundle.getFormattedString("ImportFeedsDone", + [fileName, acctNewExist, acctName]); + ShowImportResultsRaw(message + " " + aStatusReport, null, true); + document.getElementById("back").removeAttribute("disabled"); + + let subscriptionsWindow = Services.wm.getMostRecentWindow("Mail:News-BlogSubscriptions"); + if (subscriptionsWindow) + { + let feedWin = subscriptionsWindow.FeedSubscriptions; + if (aLastFolder) + feedWin.FolderListener.folderAdded(aLastFolder); + feedWin.mActionMode = null; + feedWin.updateButtons(feedWin.mView.currentItem); + feedWin.clearStatusInfo(); + feedWin.updateStatusItem("statusText", aStatusReport); + } + } + + if (!FeedSubscriptions.importOPMLFile(openFile, server, callback)) + return false; + + let subscriptionsWindow = Services.wm.getMostRecentWindow("Mail:News-BlogSubscriptions"); + if (subscriptionsWindow) + { + let feedWin = subscriptionsWindow.FeedSubscriptions; + feedWin.mActionMode = feedWin.kImportingOPML; + feedWin.updateButtons(null); + let statusReport = gFeedsBundle.getString("subscribe-loading"); + feedWin.updateStatusItem("statusText", statusReport); + feedWin.updateStatusItem("progressMeter", "?"); + } + + return true; +} + +function SwitchType(newType) +{ + if (gImportType == newType) + return; + + gImportType = newType; + gProgressInfo.importType = newType; + + SetUpImportType(); + + SetDivText('description', ""); +} + + +function next() +{ + var deck = document.getElementById("stateDeck"); + switch (deck.selectedIndex) { + case "0": + let backButton = document.getElementById("back"); + backButton.removeAttribute("disabled"); + let radioGroup = document.getElementById("importFields"); + + if (radioGroup.value == "all") + { + let args = { closeMigration: true }; + let SEAMONKEY_ID = "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}"; + if (Services.appinfo.ID == SEAMONKEY_ID) { + window.openDialog("chrome://communicator/content/migration/migration.xul", + "", "chrome,dialog,modal,centerscreen"); + } else { + // Running as Thunderbird or its clone. + window.openDialog("chrome://messenger/content/migration/migration.xul", + "", "chrome,dialog,modal,centerscreen", null, null, null, args); + } + if (args.closeMigration) + close(); + } + else + { + SwitchType(radioGroup.value); + deck.selectedIndex = 1; + document.getElementById("modulesFound").selectedIndex = + (document.getElementById("moduleList").itemCount > 0) ? 0 : 1; + SelectFirstItem(); + enableAdvance(); + } + break; + case "1": + ImportDialogOKButton(); + break; + case "3": + close(); + break; + } +} + +function SelectFirstItem() +{ + var listbox = document.getElementById("moduleList"); + if ((listbox.selectedIndex == -1) && (listbox.itemCount > 0)) + listbox.selectedIndex = 0; + ImportSelectionChanged(); +} + +function enableAdvance() +{ + var listbox = document.getElementById("moduleList"); + var nextButton = document.getElementById("forward"); + if (listbox.selectedCount > 0) + nextButton.removeAttribute("disabled"); + else + nextButton.setAttribute("disabled", "true"); +} + +function back() +{ + var deck = document.getElementById("stateDeck"); + var backButton = document.getElementById("back"); + var nextButton = document.getElementById("forward"); + switch (deck.selectedIndex) { + case "1": + backButton.setAttribute("disabled", "true"); + nextButton.label = nextButton.getAttribute("nextval"); + nextButton.removeAttribute("disabled"); + deck.selectedIndex = 0; + break; + case "3": + // Clear out the results box. + let results = document.getElementById("results"); + while (results.hasChildNodes()) + results.lastChild.remove(); + + // Reset the next button. + nextButton.label = nextButton.getAttribute("nextval"); + nextButton.removeAttribute("disabled"); + + // Enable the cancel button again. + document.getElementById("cancel").removeAttribute("disabled"); + + // If a new Feed account has been created, rebuild the list. + if (gNewFeedAcctCreated) + ListFeedAccounts(); + + // Now go back to the second page. + deck.selectedIndex = 1; + break; + } +} diff --git a/mailnews/import/content/importDialog.xul b/mailnews/import/content/importDialog.xul new file mode 100644 index 000000000..383585f83 --- /dev/null +++ b/mailnews/import/content/importDialog.xul @@ -0,0 +1,143 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?> + +<!DOCTYPE window [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > +%brandDTD; +<!ENTITY % importDTD SYSTEM "chrome://messenger/locale/importDialog.dtd" > +%importDTD; +]> + +<window xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="OnLoadImportDialog()" +#ifdef XP_MACOSX + style="width: &window.macWidth; !important;" +#else + style="width: &window.width; !important;" +#endif + title="&importDialog.windowTitle;"> + + <stringbundle id="bundle_importMsgs" src="chrome://messenger/locale/importMsgs.properties"/> + <stringbundle id="bundle_addressbook" src="chrome://messenger/locale/addressbook/addressBook.properties"/> + <stringbundle id="bundle_vcardImportMsgs" src="chrome://messenger/locale/vCardImportMsgs.properties"/> + <stringbundle id="bundle_feeds" src="chrome://messenger-newsblog/locale/newsblog.properties"/> + <script type="application/javascript" src="chrome://messenger/content/importDialog.js"/> + <script type="application/javascript" src="chrome://messenger-newsblog/content/feed-subscriptions.js"/> + + <keyset id="dialogKeys"/> + + <hbox class="box-header" id="header" + title="&importTitle.label;" + description="&importShortDesc.label;"/> + + <deck id="stateDeck" selectedIndex="0" style="min-height: 30em"> + <vbox class="wizard-box"> + <description>&importDescription1.label;</description> + <description>&importDescription2.label;</description> + <separator/> + <radiogroup id="importFields"> + <radio id="allRadio" + value="all" + label="&importAll.label;" + accesskey="&importAll.accesskey;"/> + <separator/> + <label control="importFields">&select.label;</label> + <separator class="thin"/> + <vbox class="indent"> + <radio id="addressbookRadio" + value="addressbook" + label="&importAddressbook.label;" + accesskey="&importAddressbook.accesskey;"/> + <radio id="mailRadio" + value="mail" + label="&importMail.label;" + accesskey="&importMail.accesskey;"/> + <radio id="feedsRadio" + value="feeds" + label="&importFeeds.label;" + accesskey="&importFeeds.accesskey;"/> + <radio id="settingsRadio" + value="settings" + label="&importSettings.label;" + accesskey="&importSettings.accesskey;"/> + <radio id="filtersRadio" + value="filters" + label="&importFilters.label;" + accesskey="&importFilters.accesskey;"/> + </vbox> + </radiogroup> + </vbox> + <vbox class="wizard-box"> + <deck id="modulesFound" + selectedIndex="0"> + <vbox> + <deck id="selectDescriptionDeck" + selectedIndex="0"> + <label control="moduleList" + value="&selectDescription.label;" + accesskey="&selectDescription.accesskey;"/> + <label control="moduleList" + value="&selectDescriptionB.label;" + accesskey="&selectDescription.accesskey;"/> + </deck> + <listbox id="moduleList" flex="3" + onselect="ImportSelectionChanged(); enableAdvance();"/> + </vbox> + <label>&noModulesFound.label;</label> + </deck> + <grid flex="1"> + <columns><column flex="1"/></columns> + <rows> + <row> + <description control="moduleList" id="description" class="box-padded"/> + </row> + <row> + <hbox id="acctName-box" flex="1" style="visibility: hidden;"> + <label control="acctName" class="box-padded" + accesskey="&acctName.accesskey;" + value="&acctName.label;"/> + <textbox id="acctName" clickSelectsAll="true"/> + </hbox> + </row> + </rows> + </grid> + </vbox> + <vbox class="wizard-box"> + <spacer flex="1"/> + <groupbox> + <caption id="progressTitle" label="&title.label;"/> + <label class="indent" id="progressStatus" value="&processing.label;"/> + <vbox class="box-padded"> + <progressmeter id="progressMeter" mode="determined" value="5"/> + </vbox> + </groupbox> + </vbox> + <vbox class="wizard-box"> + <description id="status"/> + <hbox style="overflow: auto" class="inset" flex="1"> + <description id="results" flex="1"/> + </hbox> + </vbox> + </deck> + + <separator/> + + <separator class="groove"/> + + <hbox class="box-padded"> + <spacer flex="1"/> + <button id="back" label="&back.label;" disabled="true" + oncommand="back();"/> + <button id="forward" label="&forward.label;" nextval="&forward.label;" finishedval="&finish.label;" + oncommand="next();"/> + <separator orient="vertical"/> + <button id="cancel" label="&cancel.label;" + oncommand="close();"/> + </hbox> + +</window> diff --git a/mailnews/import/oexpress/OEDebugLog.h b/mailnews/import/oexpress/OEDebugLog.h new file mode 100644 index 000000000..47cc6b2ea --- /dev/null +++ b/mailnews/import/oexpress/OEDebugLog.h @@ -0,0 +1,20 @@ +/* -*- 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 OEDebugLog_h___ +#define OEDebugLog_h___ + +// Use MOZ_LOG for logging. +#include "mozilla/Logging.h" +extern PRLogModuleInfo *OELOGMODULE; // Logging module + +#define IMPORT_LOG0(x) MOZ_LOG(OELOGMODULE, mozilla::LogLevel::Debug, (x)) +#define IMPORT_LOG1(x, y) MOZ_LOG(OELOGMODULE, mozilla::LogLevel::Debug, (x, y)) +#define IMPORT_LOG2(x, y, z) MOZ_LOG(OELOGMODULE, mozilla::LogLevel::Debug, (x, y, z)) +#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(OELOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d)) + + + +#endif /* OEDebugLog_h___ */ diff --git a/mailnews/import/oexpress/WabObject.cpp b/mailnews/import/oexpress/WabObject.cpp new file mode 100644 index 000000000..e206b74f8 --- /dev/null +++ b/mailnews/import/oexpress/WabObject.cpp @@ -0,0 +1,1132 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <tchar.h> +#include "nscore.h" +#include "nsOE5File.h" +#include "wabobject.h" +#include <algorithm> + +enum { + ieidPR_DISPLAY_NAME = 0, + ieidPR_ENTRYID, + ieidPR_OBJECT_TYPE, + ieidMax +}; + +static const SizedSPropTagArray(ieidMax, ptaEid)= +{ + ieidMax, + { + PR_DISPLAY_NAME, + PR_ENTRYID, + PR_OBJECT_TYPE, + } +}; + + +enum { + iemailPR_DISPLAY_NAME = 0, + iemailPR_ENTRYID, + iemailPR_EMAIL_ADDRESS, + iemailPR_OBJECT_TYPE, + iemailMax +}; +static const SizedSPropTagArray(iemailMax, ptaEmail)= +{ + iemailMax, + { + PR_DISPLAY_NAME, + PR_ENTRYID, + PR_EMAIL_ADDRESS, + PR_OBJECT_TYPE + } +}; + +typedef struct { + bool multiLine; + ULONG tag; + char * pLDIF; +} AddrImportField; + +#define kExtraUserFields 10 +AddrImportField extraUserFields[kExtraUserFields] = { + {true, PR_COMMENT, "description:"}, + {false, PR_BUSINESS_TELEPHONE_NUMBER, "telephonenumber:"}, + {false, PR_HOME_TELEPHONE_NUMBER, "homephone:"}, + {false, PR_COMPANY_NAME, "o:"}, + {false, PR_TITLE, "title:"}, + {false, PR_BUSINESS_FAX_NUMBER, "facsimiletelephonenumber:"}, + {false, PR_LOCALITY, "locality:"}, + {false, PR_STATE_OR_PROVINCE, "st:"}, + {true, PR_STREET_ADDRESS, "streetaddress:"}, + {false, PR_POSTAL_CODE, "postalcode:"} +}; + +#define kWhitespace " \t\b\r\n" + +#define TR_OUTPUT_EOL "\r\n" + +#define kLDIFPerson "objectclass: top" TR_OUTPUT_EOL "objectclass: person" TR_OUTPUT_EOL +#define kLDIFGroup "objectclass: top" TR_OUTPUT_EOL "objectclass: groupOfNames" TR_OUTPUT_EOL + +/*********************************************************************************/ + + +// contructor for CWAB object +// +// pszFileName - FileName of WAB file to open +// if no file name is specified, opens the default +// +CWAB::CWAB(nsIFile *file) +{ + // Here we load the WAB Object and initialize it + m_pUniBuff = NULL; + m_uniBuffLen = 0; + + m_bInitialized = false; + m_lpAdrBook = NULL; + m_lpWABObject = NULL; + m_hinstWAB = NULL; + + { + TCHAR szWABDllPath[MAX_PATH]; + DWORD dwType = 0; + ULONG cbData = sizeof(szWABDllPath); + HKEY hKey = NULL; + + *szWABDllPath = '\0'; + + // First we look under the default WAB DLL path location in the + // Registry. + // WAB_DLL_PATH_KEY is defined in wabapi.h + // + if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, WAB_DLL_PATH_KEY, 0, KEY_READ, &hKey)) { + RegQueryValueEx(hKey, "", NULL, &dwType, (LPBYTE) szWABDllPath, &cbData); + if (dwType == REG_EXPAND_SZ) { + // Expand the environment variables + DWORD bufferSize = ExpandEnvironmentStrings(szWABDllPath, NULL, 0); + if (bufferSize && bufferSize < MAX_PATH) { + TCHAR tmp[MAX_PATH]; + ExpandEnvironmentStrings(szWABDllPath, tmp, bufferSize); + _tcscpy(szWABDllPath, tmp); + } + else { + // This is an error condition. Nothing else is initialized yet, so simply return. + return; + } + + } + } + else { + if (GetSystemDirectory(szWABDllPath, MAX_PATH)) { + _tcsncat(szWABDllPath, WAB_DLL_NAME, + std::min(_tcslen(WAB_DLL_NAME), MAX_PATH - _tcslen(szWABDllPath) - 1)); + } + else { + // Yet another error condition. + return; + } + } + + if(hKey) RegCloseKey(hKey); + + // if the Registry came up blank, we do a loadlibrary on the wab32.dll + // WAB_DLL_NAME is defined in wabapi.h + // + m_hinstWAB = LoadLibrary((lstrlen(szWABDllPath)) ? szWABDllPath : WAB_DLL_NAME); + } + + if(m_hinstWAB) + { + // if we loaded the dll, get the entry point + // + m_lpfnWABOpen = (LPWABOPEN) GetProcAddress(m_hinstWAB, "WABOpen"); + + if(m_lpfnWABOpen) + { + char fName[2] = {0, 0}; + HRESULT hr = E_FAIL; + WAB_PARAM wp = {0}; + wp.cbSize = sizeof(WAB_PARAM); + if (file != nullptr) { + nsCString path; + file->GetNativePath(path); + wp.szFileName = (LPTSTR) ToNewCString(path); + } + else + wp.szFileName = (LPTSTR) fName; + + // if we choose not to pass in a WAB_PARAM object, + // the default WAB file will be opened up + // + hr = m_lpfnWABOpen(&m_lpAdrBook,&m_lpWABObject,&wp,0); + + if(!hr) + m_bInitialized = TRUE; + + } + } + +} + + +// Destructor +// +CWAB::~CWAB() +{ + if (m_pUniBuff) + delete [] m_pUniBuff; + + if(m_bInitialized) + { + if(m_lpAdrBook) + m_lpAdrBook->Release(); + + if(m_lpWABObject) + m_lpWABObject->Release(); + + if(m_hinstWAB) + FreeLibrary(m_hinstWAB); + } +} + + +HRESULT CWAB::IterateWABContents(CWabIterator *pIter, int *pDone) +{ + if (!m_bInitialized || !m_lpAdrBook) + return E_FAIL; + + ULONG ulObjType = 0; + LPMAPITABLE lpAB = NULL; + ULONG cRows = 0; + LPSRowSet lpRowAB = NULL; + LPABCONT lpContainer = NULL; + int cNumRows = 0; + nsresult keepGoing; + + HRESULT hr = E_FAIL; + + ULONG lpcbEID = 0; + LPENTRYID lpEID = NULL; + ULONG rowCount = 0; + ULONG curCount = 0; + + nsString uniStr; + + // Get the entryid of the root PAB container + // + hr = m_lpAdrBook->GetPAB(&lpcbEID, &lpEID); + + if (HR_FAILED(hr)) + goto exit; + + ulObjType = 0; + + // Open the root PAB container + // This is where all the WAB contents reside + // + hr = m_lpAdrBook->OpenEntry(lpcbEID, + (LPENTRYID)lpEID, + NULL, + 0, + &ulObjType, + (LPUNKNOWN *)&lpContainer); + + m_lpWABObject->FreeBuffer(lpEID); + + lpEID = NULL; + + if(HR_FAILED(hr)) + goto exit; + + // Get a contents table of all the contents in the + // WABs root container + // + hr = lpContainer->GetContentsTable(0, &lpAB); + + if(HR_FAILED(hr)) + goto exit; + + hr = lpAB->GetRowCount(0, &rowCount); + if (HR_FAILED(hr)) + rowCount = 100; + if (rowCount == 0) + rowCount = 1; + + // Order the columns in the ContentsTable to conform to the + // ones we want - which are mainly DisplayName, EntryID and + // ObjectType + // The table is gauranteed to set the columns in the order + // requested + // + hr =lpAB->SetColumns((LPSPropTagArray)&ptaEid, 0); + + if(HR_FAILED(hr)) + goto exit; + + + // Reset to the beginning of the table + // + hr = lpAB->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + + if(HR_FAILED(hr)) + goto exit; + + // Read all the rows of the table one by one + // + + do { + + hr = lpAB->QueryRows(1, 0, &lpRowAB); + + if(HR_FAILED(hr)) + break; + + if(lpRowAB) + { + cNumRows = lpRowAB->cRows; + + if (cNumRows) + { + LPTSTR lpsz = lpRowAB->aRow[0].lpProps[ieidPR_DISPLAY_NAME].Value.lpszA; + LPENTRYID lpEID = (LPENTRYID) lpRowAB->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRowAB->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + + // There are 2 kinds of objects - the MAPI_MAILUSER contact object + // and the MAPI_DISTLIST contact object + // For the purposes of this sample, we will only consider MAILUSER + // objects + // + if(lpRowAB->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.l == MAPI_MAILUSER) + { + // We will now take the entry-id of each object and cache it + // on the listview item representing that object. This enables + // us to uniquely identify the object later if we need to + // + CStrToUnicode(lpsz, uniStr); + keepGoing = pIter->EnumUser(uniStr.get(), lpEID, cbEID); + curCount++; + if (pDone) { + *pDone = (curCount * 100) / rowCount; + if (*pDone > 100) + *pDone = 100; + } + } + } + FreeProws(lpRowAB); + } + + + } while (SUCCEEDED(hr) && cNumRows && lpRowAB && NS_SUCCEEDED(keepGoing)) ; + + hr = lpAB->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + + if(HR_FAILED(hr)) + goto exit; + + // Read all the rows of the table one by one + // + keepGoing = NS_OK; + do { + + hr = lpAB->QueryRows(1, 0, &lpRowAB); + + if(HR_FAILED(hr)) + break; + + if(lpRowAB) + { + cNumRows = lpRowAB->cRows; + + if (cNumRows) + { + LPTSTR lpsz = lpRowAB->aRow[0].lpProps[ieidPR_DISPLAY_NAME].Value.lpszA; + LPENTRYID lpEID = (LPENTRYID) lpRowAB->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRowAB->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + + // There are 2 kinds of objects - the MAPI_MAILUSER contact object + // and the MAPI_DISTLIST contact object + // For the purposes of this sample, we will only consider MAILUSER + // objects + // + if(lpRowAB->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.l == MAPI_DISTLIST) + { + LPABCONT distListContainer = NULL; + // We will now take the entry-id of each object and cache it + // on the listview item representing that object. This enables + // us to uniquely identify the object later if we need to + // + hr = m_lpAdrBook->OpenEntry(cbEID, lpEID, NULL, + 0,&ulObjType,(LPUNKNOWN *)&distListContainer); + + LPMAPITABLE distListTable = NULL; + + + // Get a contents table of the dist list + // + hr = distListContainer->GetContentsTable(0, &distListTable); + if (lpAB) + { + hr = distListTable->GetRowCount(0, &rowCount); + if (HR_FAILED(hr)) + rowCount = 100; + if (rowCount == 0) + rowCount = 1; + + // Order the columns in the ContentsTable to conform to the + // ones we want - which are mainly DisplayName, EntryID and + // ObjectType + // The table is gauranteed to set the columns in the order + // requested + // + hr = distListTable->SetColumns((LPSPropTagArray)&ptaEid, 0); + CStrToUnicode(lpsz, uniStr); + keepGoing = pIter->EnumList(uniStr.get(), lpEID, cbEID, distListTable); + curCount++; + if (pDone) { + *pDone = (curCount * 100) / rowCount; + if (*pDone > 100) + *pDone = 100; + } + } + if (distListContainer) + distListContainer->Release(); + if (distListTable) + distListTable->Release(); + } + } + FreeProws(lpRowAB); + } + + } while (SUCCEEDED(hr) && cNumRows && lpRowAB && NS_SUCCEEDED(keepGoing)) ; + + +exit: + + if (lpContainer) + lpContainer->Release(); + + if (lpAB) + lpAB->Release(); + + return hr; +} + + + + + + +void CWAB::FreeProws(LPSRowSet prows) +{ + ULONG irow; + if (!prows) + return; + for (irow = 0; irow < prows->cRows; ++irow) + m_lpWABObject->FreeBuffer(prows->aRow[irow].lpProps); + m_lpWABObject->FreeBuffer(prows); +} + + +LPDISTLIST CWAB::GetDistList(ULONG cbEid, LPENTRYID pEid) +{ + if (!m_bInitialized || !m_lpAdrBook) + return NULL; + + LPDISTLIST lpDistList = NULL; + ULONG ulObjType; + + m_lpAdrBook->OpenEntry(cbEid, pEid, NULL, 0, &ulObjType, (LPUNKNOWN *)&lpDistList); + return lpDistList; +} + +LPSPropValue CWAB::GetListProperty(LPDISTLIST pUser, ULONG tag) +{ + if (!pUser) + return NULL; + + int sz = CbNewSPropTagArray(1); + SPropTagArray *pTag = (SPropTagArray *) new char[sz]; + pTag->cValues = 1; + pTag->aulPropTag[0] = tag; + LPSPropValue lpProp = NULL; + ULONG cValues = 0; + HRESULT hr = pUser->GetProps(pTag, 0, &cValues, &lpProp); + delete [] pTag; + if (HR_FAILED(hr) || (cValues != 1)) { + if (lpProp) + m_lpWABObject->FreeBuffer(lpProp); + return NULL; + } + return lpProp; +} + +LPMAILUSER CWAB::GetUser(ULONG cbEid, LPENTRYID pEid) +{ + if (!m_bInitialized || !m_lpAdrBook) + return NULL; + + LPMAILUSER lpMailUser = NULL; + ULONG ulObjType; + + m_lpAdrBook->OpenEntry(cbEid, pEid, NULL, 0, &ulObjType, (LPUNKNOWN *)&lpMailUser); + return lpMailUser; +} + +LPSPropValue CWAB::GetUserProperty(LPMAILUSER pUser, ULONG tag) +{ + if (!pUser) + return NULL; + + ULONG uTag = tag; + /* + Getting Unicode does not help with getting the right + international charset. Windoze bloze. + */ + /* + if (PROP_TYPE(uTag) == PT_STRING8) { + uTag = CHANGE_PROP_TYPE(tag, PT_UNICODE); + } + */ + + int sz = CbNewSPropTagArray(1); + SPropTagArray *pTag = (SPropTagArray *) new char[sz]; + pTag->cValues = 1; + pTag->aulPropTag[0] = uTag; + LPSPropValue lpProp = NULL; + ULONG cValues = 0; + HRESULT hr = pUser->GetProps(pTag, 0, &cValues, &lpProp); + if (HR_FAILED(hr) || (cValues != 1)) { + if (lpProp) + m_lpWABObject->FreeBuffer(lpProp); + lpProp = NULL; + if (uTag != tag) { + pTag->cValues = 1; + pTag->aulPropTag[0] = tag; + cValues = 0; + hr = pUser->GetProps(pTag, 0, &cValues, &lpProp); + if (HR_FAILED(hr) || (cValues != 1)) { + if (lpProp) + m_lpWABObject->FreeBuffer(lpProp); + lpProp = NULL; + } + } + } + delete [] pTag; + return lpProp; +} + +void CWAB::CStrToUnicode(const char *pStr, nsString& result) +{ + result.Truncate(); + int wLen = MultiByteToWideChar(CP_ACP, 0, pStr, -1, wwc(m_pUniBuff), 0); + if (wLen >= m_uniBuffLen) { + if (m_pUniBuff) + delete [] m_pUniBuff; + m_pUniBuff = new char16_t[wLen + 64]; + m_uniBuffLen = wLen + 64; + } + if (wLen) { + MultiByteToWideChar(CP_ACP, 0, pStr, -1, wwc(m_pUniBuff), m_uniBuffLen); + result = m_pUniBuff; + } +} + +// If the value is a string, get it... +void CWAB::GetValueString(LPSPropValue pVal, nsString& val) +{ + val.Truncate(); + + if (!pVal) + return; + + switch(PROP_TYPE(pVal->ulPropTag)) { + case PT_STRING8: + CStrToUnicode((const char *) (pVal->Value.lpszA), val); + break; + case PT_UNICODE: + val = (char16_t *) (pVal->Value.lpszW); + break; + case PT_MV_STRING8: { + nsString tmp; + ULONG j; + for(j = 0; j < pVal->Value.MVszA.cValues; j++) { + CStrToUnicode((const char *) (pVal->Value.MVszA.lppszA[j]), tmp); + val += tmp; + val.Append(NS_ConvertASCIItoUTF16(TR_OUTPUT_EOL)); + } + break; + } + case PT_MV_UNICODE: { + ULONG j; + for(j = 0; j < pVal->Value.MVszW.cValues; j++) { + val += (char16_t *) (pVal->Value.MVszW.lppszW[j]); + val.Append(NS_ConvertASCIItoUTF16(TR_OUTPUT_EOL)); + } + break; + } + case PT_I2: + case PT_LONG: + case PT_R4: + case PT_DOUBLE: + case PT_BOOLEAN: { + /* + TCHAR sz[256]; + wsprintf(sz,"%d", pVal->Value.l); + val = sz; + */ + break; + } + + case PT_BINARY: + break; + + default: + break; + } + + val.Trim(kWhitespace, true, true); +} + + +void CWAB::GetValueTime(LPSPropValue pVal, PRTime& val) +{ + if (!pVal) + return; + + if (PROP_TYPE(pVal->ulPropTag) != PT_SYSTIME) + return; + + nsOE5File::FileTimeToPRTime(&pVal->Value.ft, &val); +} + +bool CWAB::IsAvailable() +{ + if (!m_bInitialized || !m_lpAdrBook) + return false; + + ULONG lpcbEID = 0; + LPENTRYID lpEID = NULL; + HRESULT hr = m_lpAdrBook->GetPAB(&lpcbEID, &lpEID); + if (HR_FAILED(hr)) + return false; + + ULONG ulObjType = 0; + LPABCONT lpContainer = NULL; + hr = m_lpAdrBook->OpenEntry(lpcbEID, + (LPENTRYID)lpEID, + NULL, + 0, + &ulObjType, + (LPUNKNOWN *)&lpContainer); + m_lpWABObject->FreeBuffer(lpEID); + + LPMAPITABLE lpAB = NULL; + hr = lpContainer->GetContentsTable(0, &lpAB); + if(HR_FAILED(hr)) { + lpContainer->Release(); + return false; + } + + ULONG rowCount = 0; + hr = lpAB->GetRowCount(0, &rowCount); + lpContainer->Release(); + lpAB->Release(); + return (rowCount != 0); +} + +/* +BOOL CWabIterateProcess::SanitizeMultiLine(CString& val) +{ + val.TrimLeft(); + val.TrimRight(); + int idx = val.FindOneOf("\x0D\x0A"); + if (idx == -1) + return FALSE; + + // needs encoding + U32 bufSz = UMimeEncode::GetBufferSize(val.GetLength()); + P_U8 pBuf = new U8[bufSz]; + U32 len = UMimeEncode::ConvertBuffer((PC_U8)((PC_S8)val), val.GetLength(), pBuf, 66, 52, "\x0D\x0A "); + pBuf[len] = 0; + val = pBuf; + delete pBuf; + return TRUE; +} + +BOOL CWabIterateProcess::EnumUser(LPCTSTR pName, LPENTRYID pEid, ULONG cbEid) +{ + TRACE1("User: %s\n", pName); + + LPMAILUSER pUser = m_pWab->GetUser(cbEid, pEid); + + // Get the "required" strings first + CString lastName; + CString firstName; + CString eMail; + CString nickName; + CString middleName; + + if (!pUser) { + UDialogs::ErrMessage1(IDS_ENTRY_ERROR, pName); + return FALSE; + } + + LPSPropValue pProp = m_pWab->GetUserProperty(pUser, PR_EMAIL_ADDRESS); + if (pProp) { + m_pWab->GetValueString(pProp, eMail); + SanitizeValue(eMail); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_GIVEN_NAME); + if (pProp) { + m_pWab->GetValueString(pProp, firstName); + SanitizeValue(firstName); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_SURNAME); + if (pProp) { + m_pWab->GetValueString(pProp, lastName); + SanitizeValue(lastName); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_MIDDLE_NAME); + if (pProp) { + m_pWab->GetValueString(pProp, middleName); + SanitizeValue(middleName); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_NICKNAME); + if (pProp) { + m_pWab->GetValueString(pProp, nickName); + SanitizeValue(nickName); + m_pWab->FreeProperty(pProp); + } + if (nickName.IsEmpty()) + nickName = pName; + if (firstName.IsEmpty()) { + firstName = nickName; + middleName.Empty(); + lastName.Empty(); + } + if (lastName.IsEmpty()) + middleName.Empty(); + + if (eMail.IsEmpty()) + eMail = nickName; + + + // We now have the required fields + // write them out followed by any optional fields! + BOOL result = TRUE; + + if (m_recordsDone) + result = m_out.WriteEol(); + + CString line; + CString header; + line.LoadString(IDS_LDIF_DN_START); + line += firstName; + if (!middleName.IsEmpty()) { + line += ' '; + line += middleName; + } + if (!lastName.IsEmpty()) { + line += ' '; + line += lastName; + } + header.LoadString(IDS_LDIF_DN_MIDDLE); + line += header; + line += eMail; + result = result && m_out.WriteStr(line); + result = result && m_out.WriteEol(); + + line.LoadString(IDS_FIELD_LDIF_FULLNAME); + line += ' '; + line += firstName; + if (!middleName.IsEmpty()) { + line += ' '; + line += middleName; + } + if (!lastName.IsEmpty()) { + line += ' '; + line += lastName; + } + result = result && m_out.WriteStr(line); + result = result && m_out.WriteEol(); + + + line.LoadString(IDS_FIELD_LDIF_GIVENNAME); + line += ' '; + line += firstName; + result = result && m_out.WriteStr(line); + result = result && m_out.WriteEol(); + + if (!lastName.IsEmpty()) { + line.LoadString(IDS_FIELD_LDIF_LASTNAME); + if (!middleName.IsEmpty()) { + line += ' '; + line += middleName; + } + line += ' '; + line += lastName; + result = result && m_out.WriteStr(line); + result = result && m_out.WriteEol(); + } + + result = result && m_out.WriteStr(kLDIFPerson); + + line.LoadString(IDS_FIELD_LDIF_EMAIL); + line += ' '; + line += eMail; + result = result && m_out.WriteStr(line); + result = result && m_out.WriteEol(); + + line.LoadString(IDS_FIELD_LDIF_NICKNAME); + line += ' '; + line += nickName; + result = result && m_out.WriteStr(line); + result = result && m_out.WriteEol(); + + // Do all of the extra fields! + CString value; + BOOL encoded = FALSE; + for (int i = 0; i < kExtraUserFields; i++) { + value.Empty(); + pProp = m_pWab->GetUserProperty(pUser, extraUserFields[i].tag); + if (pProp) { + m_pWab->GetValueString(pProp, value); + m_pWab->FreeProperty(pProp); + } + if (extraUserFields[i].multiLine) { + encoded = SanitizeMultiLine(value); + } + else + SanitizeValue(value); + if (!value.IsEmpty()) { + line = extraUserFields[i].pLDIF; + if (encoded) { + line += ": "; + encoded = FALSE; + } + else + line += ' '; + line += value; + result = result && m_out.WriteStr(line); + result = result && m_out.WriteEol(); + } + } + + m_pWab->ReleaseUser(pUser); + + if (!result) { + UDialogs::ErrMessage0(IDS_ADDRESS_SAVE_ERROR); + } + + m_totalDone += kValuePerUser; + m_recordsDone++; + + return result; +} +*/ + + + + +/* +BOOL CWabIterateProcess::EnumList(LPCTSTR pName, LPENTRYID pEid, ULONG cbEid) +{ + TRACE1("List: %s\n", pName); + + LPDISTLIST pList = m_pWab->GetDistList(cbEid, pEid); + if (!pList) { + UDialogs::ErrMessage1(IDS_ENTRY_ERROR, pName); + return FALSE; + } + + // Find out if this is just a regular entry or a true list... + CString eMail; + LPSPropValue pProp = m_pWab->GetListProperty(pList, PR_EMAIL_ADDRESS); + if (pProp) { + m_pWab->GetValueString(pProp, eMail); + SanitizeValue(eMail); + m_pWab->FreeProperty(pProp); + // Treat this like a regular entry... + if (!eMail.IsEmpty()) { + m_pWab->ReleaseDistList(pList); + return WriteListUserEntry(pName, eMail); + } + } + + // This may very well be a list, find the entries... + m_pListTable = OpenDistList(pList); + if (m_pListTable) { + m_pList = pList; + m_listName = pName; + m_listDone = 0; + m_listHeaderDone = FALSE; + m_state = kEnumListState; + } + else { + m_pWab->ReleaseDistList(pList); + m_recordsDone++; + m_totalDone += kValuePerUser; + } + + return TRUE; +} + +BOOL CWabIterateProcess::EnumNextListUser(BOOL *pDone) +{ + HRESULT hr; + int cNumRows = 0; + LPSRowSet lpRowAB = NULL; + BOOL keepGoing = TRUE; + + if (!m_pListTable) + return FALSE; + + hr = m_pListTable->QueryRows(1, 0, &lpRowAB); + + if(HR_FAILED(hr)) { + UDialogs::ErrMessage0(IDS_ERROR_READING_WAB); + return FALSE; + } + + if(lpRowAB) { + cNumRows = lpRowAB->cRows; + + if (cNumRows) { + LPTSTR lpsz = lpRowAB->aRow[0].lpProps[ieidPR_DISPLAY_NAME].Value.lpszA; + LPENTRYID lpEID = (LPENTRYID) lpRowAB->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRowAB->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + if(lpRowAB->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.l == MAPI_DISTLIST) { + keepGoing = HandleListList(lpsz, lpEID, cbEID); + } + else if (lpRowAB->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.l == MAPI_MAILUSER) { + keepGoing = HandleListUser(lpsz, lpEID, cbEID); + } + } + m_pWab->FreeProws(lpRowAB); + } + + if (!cNumRows || !lpRowAB) { + *pDone = TRUE; + m_pListTable->Release(); + m_pListTable = NULL; + if (m_pList) + m_pWab->ReleaseDistList(m_pList); + m_pList = NULL; + if (m_listDone < kValuePerUser) + m_totalDone += (kValuePerUser - m_listDone); + m_recordsDone++; + return keepGoing; + } + + if (!keepGoing) + return FALSE; + + if (m_listDone < kValuePerUser) { + m_listDone++; + m_totalDone++; + } + + return TRUE; +} + +BOOL CWabIterateProcess::HandleListList(LPCTSTR pName, LPENTRYID lpEid, ULONG cbEid) +{ + BOOL result; + LPDISTLIST pList = m_pWab->GetDistList(cbEid, lpEid); + if (!pList) { + UDialogs::ErrMessage1(IDS_ENTRY_ERROR, pName); + return FALSE; + } + + CString eMail; + LPSPropValue pProp = m_pWab->GetListProperty(pList, PR_EMAIL_ADDRESS); + if (pProp) { + m_pWab->GetValueString(pProp, eMail); + SanitizeValue(eMail); + m_pWab->FreeProperty(pProp); + // Treat this like a regular entry... + if (!eMail.IsEmpty()) { + // write out a member based on pName and eMail + result = WriteGroupMember(pName, eMail); + m_pWab->ReleaseDistList(pList); + return result; + } + } + + // iterate the list and add each member to the top level list + LPMAPITABLE pTable = OpenDistList(pList); + if (!pTable) { + TRACE0("Error opening table for list\n"); + m_pWab->ReleaseDistList(pList); + UDialogs::ErrMessage1(IDS_ENTRY_ERROR, pName); + return FALSE; + } + + int cNumRows = 0; + LPSRowSet lpRowAB = NULL; + HRESULT hr; + BOOL keepGoing = TRUE; + + do { + hr = pTable->QueryRows(1, 0, &lpRowAB); + + if(HR_FAILED(hr)) { + UDialogs::ErrMessage0(IDS_ERROR_READING_WAB); + pTable->Release(); + m_pWab->ReleaseDistList(pList); + return FALSE; + } + + if(lpRowAB) { + cNumRows = lpRowAB->cRows; + + if (cNumRows) { + LPTSTR lpsz = lpRowAB->aRow[0].lpProps[ieidPR_DISPLAY_NAME].Value.lpszA; + LPENTRYID lpEID = (LPENTRYID) lpRowAB->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRowAB->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + if(lpRowAB->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.l == MAPI_DISTLIST) { + keepGoing = HandleListList(lpsz, lpEID, cbEID); + } + else if (lpRowAB->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.l == MAPI_MAILUSER) { + keepGoing = HandleListUser(lpsz, lpEID, cbEID); + } + } + m_pWab->FreeProws(lpRowAB); + } + } + while (keepGoing && cNumRows && lpRowAB); + + pTable->Release(); + m_pWab->ReleaseDistList(pList); + return keepGoing; +} + +BOOL CWabIterateProcess::HandleListUser(LPCTSTR pName, LPENTRYID lpEid, ULONG cbEid) +{ + // Get the basic properties for building the member line + LPMAILUSER pUser = m_pWab->GetUser(cbEid, lpEid); + + // Get the "required" strings first + CString lastName; + CString firstName; + CString eMail; + CString nickName; + CString middleName; + + if (!pUser) { + UDialogs::ErrMessage1(IDS_ENTRY_ERROR, pName); + return FALSE; + } + + LPSPropValue pProp = m_pWab->GetUserProperty(pUser, PR_EMAIL_ADDRESS); + if (pProp) { + m_pWab->GetValueString(pProp, eMail); + SanitizeValue(eMail); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_GIVEN_NAME); + if (pProp) { + m_pWab->GetValueString(pProp, firstName); + SanitizeValue(firstName); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_SURNAME); + if (pProp) { + m_pWab->GetValueString(pProp, lastName); + SanitizeValue(lastName); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_MIDDLE_NAME); + if (pProp) { + m_pWab->GetValueString(pProp, middleName); + SanitizeValue(middleName); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_NICKNAME); + if (pProp) { + m_pWab->GetValueString(pProp, nickName); + SanitizeValue(nickName); + m_pWab->FreeProperty(pProp); + } + if (nickName.IsEmpty()) + nickName = pName; + if (firstName.IsEmpty()) { + firstName = nickName; + middleName.Empty(); + lastName.Empty(); + } + if (lastName.IsEmpty()) + middleName.Empty(); + + if (eMail.IsEmpty()) + eMail = nickName; + + m_pWab->ReleaseUser(pUser); + + CString name = firstName; + if (!middleName.IsEmpty()) { + name += ' '; + name += middleName; + } + if (!lastName.IsEmpty()) { + name += ' '; + name += lastName; + } + return WriteGroupMember(name, eMail); +} + +BOOL CWabIterateProcess::WriteGroupMember(const char *pName, const char *pEmail) +{ + CString middle; + CString line; + BOOL result; + + // Check for the header first + if (!m_listHeaderDone) { + if (m_recordsDone) + result = m_out.WriteEol(); + else + result = TRUE; + line.LoadString(IDS_LDIF_DN_START); + line += m_listName; + line += TR_OUTPUT_EOL; + middle.LoadString(IDS_FIELD_LDIF_FULLNAME); + line += middle; + line += m_listName; + line += TR_OUTPUT_EOL; + if (!result || !m_out.WriteStr(line) || !m_out.WriteStr(kLDIFGroup)) { + UDialogs::ErrMessage0(IDS_ADDRESS_SAVE_ERROR); + return FALSE; + } + m_listHeaderDone = TRUE; + } + + + line.LoadString(IDS_FIELD_LDIF_MEMBER_START); + line += pName; + middle.LoadString(IDS_LDIF_DN_MIDDLE); + line += middle; + line += pEmail; + line += TR_OUTPUT_EOL; + if (!m_out.WriteStr(line)) { + UDialogs::ErrMessage0(IDS_ADDRESS_SAVE_ERROR); + return FALSE; + } + + if (m_listDone < kValuePerUser) { + m_listDone++; + m_totalDone++; + } + + return TRUE; +} +*/ + diff --git a/mailnews/import/oexpress/WabObject.h b/mailnews/import/oexpress/WabObject.h new file mode 100644 index 000000000..482615697 --- /dev/null +++ b/mailnews/import/oexpress/WabObject.h @@ -0,0 +1,64 @@ +/* -*- 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 WabObject_h___ +#define WabObject_h___ + +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsIFile.h" + +#include <windows.h> +#include <wab.h> + + +class CWabIterator { +public: + virtual nsresult EnumUser(const char16_t *pName, LPENTRYID pEid, ULONG cbEid) = 0; + virtual nsresult EnumList(const char16_t *pName, LPENTRYID pEid, ULONG cbEid, LPMAPITABLE lpTable) = 0; +}; + + +class CWAB +{ +public: + CWAB(nsIFile *fileName); + ~CWAB(); + + bool Loaded(void) { return m_bInitialized;} + + HRESULT IterateWABContents(CWabIterator *pIter, int *pDone); + + // Methods for User entries + LPDISTLIST GetDistList(ULONG cbEid, LPENTRYID pEid); + void ReleaseDistList(LPDISTLIST pList) { if (pList) pList->Release();} + LPMAILUSER GetUser(ULONG cbEid, LPENTRYID pEid); + void ReleaseUser(LPMAILUSER pUser) { if (pUser) pUser->Release();} + LPSPropValue GetUserProperty(LPMAILUSER pUser, ULONG tag); + LPSPropValue GetListProperty(LPDISTLIST pList, ULONG tag); + void FreeProperty(LPSPropValue pVal) { if (pVal) m_lpWABObject->FreeBuffer(pVal);} + void GetValueString(LPSPropValue pVal, nsString& val); + void GetValueTime(LPSPropValue pVal, PRTime& val); + + void CStrToUnicode(const char *pStr, nsString& result); + + // Utility stuff used by iterate + void FreeProws(LPSRowSet prows); + + bool IsAvailable(); + +private: + char16_t * m_pUniBuff; + int m_uniBuffLen; + bool m_bInitialized; + HINSTANCE m_hinstWAB; + LPWABOPEN m_lpfnWABOpen; + LPADRBOOK m_lpAdrBook; + LPWABOBJECT m_lpWABObject; +}; + +#endif // WABOBJECT_INCLUDED + + diff --git a/mailnews/import/oexpress/moz.build b/mailnews/import/oexpress/moz.build new file mode 100644 index 000000000..5a34ce8e6 --- /dev/null +++ b/mailnews/import/oexpress/moz.build @@ -0,0 +1,19 @@ +# 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 += [ + 'nsOE5File.cpp', + 'nsOEAddressIterator.cpp', + 'nsOEImport.cpp', + 'nsOEMailbox.cpp', + 'nsOERegUtil.cpp', + 'nsOEScanBoxes.cpp', + 'nsOESettings.cpp', + 'nsOEStringBundle.cpp', + 'WabObject.cpp', +] + +FINAL_LIBRARY = 'import' + diff --git a/mailnews/import/oexpress/nsOE5File.cpp b/mailnews/import/oexpress/nsOE5File.cpp new file mode 100644 index 000000000..fd1fd0e15 --- /dev/null +++ b/mailnews/import/oexpress/nsOE5File.cpp @@ -0,0 +1,631 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsOE5File.h" +#include "OEDebugLog.h" +#include "nsMsgUtils.h" +#include "msgCore.h" +#include "prprf.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsIMsgPluggableStore.h" +#include "nsIMsgHdr.h" +#include "nsNetUtil.h" +#include "nsISeekableStream.h" +#include "nsMsgMessageFlags.h" +#include <windows.h> + +#define kIndexGrowBy 100 +#define kSignatureSize 12 +#define kDontSeek 0xFFFFFFFF +#define MARKED 0x20 // 4 +#define READ 0x80 // 1 +#define HASATTACHMENT 0x4000 // 268435456 10000000h +#define ISANSWERED 0x80000 // 2 +#define ISFORWARDED 0x100000 // 4096 +#define ISWATCHED 0x400000 // 256 +#define ISIGNORED 0x800000 // 262144 +#define XLATFLAGS(s) (((MARKED & s) ? nsMsgMessageFlags::Marked : 0) | \ + ((READ & s) ? nsMsgMessageFlags::Read : 0) | \ + ((HASATTACHMENT & s) ? nsMsgMessageFlags::Attachment : 0) | \ + ((ISANSWERED & s) ? nsMsgMessageFlags::Replied : 0) | \ + ((ISFORWARDED & s) ? nsMsgMessageFlags::Forwarded : 0) | \ + ((ISWATCHED & s) ? nsMsgMessageFlags::Watched : 0) | \ + ((ISIGNORED & s) ? nsMsgMessageFlags::Ignored : 0)) + +static char *gSig = + "\xCF\xAD\x12\xFE\xC5\xFD\x74\x6F\x66\xE3\xD1\x11"; + +// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : +// PR_FileTimeToPRTime and _PR_FileTimeToPRTime +void nsOE5File::FileTimeToPRTime(const FILETIME *filetime, PRTime *prtm) +{ +#ifdef __GNUC__ + const PRTime _pr_filetime_offset = 116444736000000000LL; +#else + const PRTime _pr_filetime_offset = 116444736000000000i64; +#endif + + PR_ASSERT(sizeof(FILETIME) == sizeof(PRTime)); + ::CopyMemory(prtm, filetime, sizeof(PRTime)); +#ifdef __GNUC__ + *prtm = (*prtm - _pr_filetime_offset) / 10LL; +#else + *prtm = (*prtm - _pr_filetime_offset) / 10i64; +#endif +} + +bool nsOE5File::VerifyLocalMailFile(nsIFile *pFile) +{ + char sig[kSignatureSize]; + + nsCOMPtr <nsIInputStream> inputStream; + + if (NS_FAILED(NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pFile))) + return false; + + if (!ReadBytes(inputStream, sig, 0, kSignatureSize)) + return false; + + bool result = true; + + for (int i = 0; (i < kSignatureSize) && result; i++) { + if (sig[i] != gSig[i]) + result = false; + } + + char storeName[14]; + if (!ReadBytes(inputStream, storeName, 0x24C1, 12)) + result = false; + + storeName[12] = 0; + + if (PL_strcasecmp("LocalStore", storeName)) + result = false; + + return result; +} + +bool nsOE5File::IsLocalMailFile(nsIFile *pFile) +{ + nsresult rv; + bool isFile = false; + + rv = pFile->IsFile(&isFile); + if (NS_FAILED(rv) || !isFile) + return false; + + bool result = VerifyLocalMailFile(pFile); + + return result; +} + +bool nsOE5File::ReadIndex(nsIInputStream *pInputStream, uint32_t **ppIndex, uint32_t *pSize) +{ + *ppIndex = nullptr; + *pSize = 0; + + char signature[4]; + if (!ReadBytes(pInputStream, signature, 0, 4)) + return false; + + for (int i = 0; i < 4; i++) { + if (signature[i] != gSig[i]) { + IMPORT_LOG0("*** Outlook 5.0 dbx file signature doesn't match\n"); + return false; + } + } + + uint32_t offset = 0x00e4; + uint32_t indexStart = 0; + if (!ReadBytes(pInputStream, &indexStart, offset, 4)) { + IMPORT_LOG0("*** Unable to read offset to index start\n"); + return false; + } + + PRUint32Array array; + array.count = 0; + array.alloc = kIndexGrowBy; + array.pIndex = new uint32_t[kIndexGrowBy]; + + uint32_t next = ReadMsgIndex(pInputStream, indexStart, &array); + while (next) { + next = ReadMsgIndex(pInputStream, next, &array); + } + + if (array.count) { + *pSize = array.count; + *ppIndex = array.pIndex; + return true; + } + + delete [] array.pIndex; + return false; +} + + +uint32_t nsOE5File::ReadMsgIndex(nsIInputStream *pInputStream, uint32_t offset, PRUint32Array *pArray) +{ + // Record is: + // 4 byte marker + // 4 byte unknown + // 4 byte nextSubIndex + // 4 byte (parentIndex?) + // 2 bytes unknown + // 1 byte length - # of entries in this record + // 1 byte unknown + // 4 byte unknown + // length records consisting of 3 longs + // 1 - pointer to record + // 2 - child index pointer + // 3 - number of records in child + + uint32_t marker; + + if (!ReadBytes(pInputStream, &marker, offset, 4)) + return 0; + + if (marker != offset) + return 0; + + + uint32_t vals[3]; + + if (!ReadBytes(pInputStream, vals, offset + 4, 12)) + return 0; + + + uint8_t len[4]; + if (!ReadBytes(pInputStream, len, offset + 16, 4)) + return 0; + + + + uint32_t cnt = (uint32_t) len[1]; + cnt *= 3; + uint32_t *pData = new uint32_t[cnt]; + + if (!ReadBytes(pInputStream, pData, offset + 24, cnt * 4)) { + delete [] pData; + return 0; + } + + uint32_t next; + uint32_t indexOffset; + uint32_t * pRecord = pData; + uint32_t * pNewIndex; + + for (uint8_t i = 0; i < (uint8_t)len[1]; i++, pRecord += 3) { + indexOffset = pRecord[0]; + + if (pArray->count >= pArray->alloc) { + pNewIndex = new uint32_t[ pArray->alloc + kIndexGrowBy]; + memcpy(pNewIndex, pArray->pIndex, (pArray->alloc * 4)); + (pArray->alloc) += kIndexGrowBy; + delete [] pArray->pIndex; + pArray->pIndex = pNewIndex; + } + + /* + We could do some checking here if we wanted - + make sure the index is within the file, + make sure there isn't a duplicate index, etc. + */ + + pArray->pIndex[pArray->count] = indexOffset; + (pArray->count)++; + + + + next = pRecord[1]; + if (next) + while ((next = ReadMsgIndex(pInputStream, next, pArray)) != 0); + } + delete [] pData; + + // return the pointer to the next subIndex + return vals[1]; +} + +bool nsOE5File::IsFromLine(char *pLine, uint32_t len) +{ + return (len > 5 && (pLine[0] == 'F') && (pLine[1] == 'r') && (pLine[2] == 'o') && (pLine[3] == 'm') && (pLine[4] == ' ')); +} + +// Anything over 16K will be assumed BAD, BAD, BAD! +#define kMailboxBufferSize 0x4000 +#define kMaxAttrCount 0x0030 +const char *nsOE5File::m_pFromLineSep = "From - Mon Jan 1 00:00:00 1965\x0D\x0A"; + +nsresult nsOE5File::ImportMailbox(uint32_t *pBytesDone, bool *pAbort, + nsString& name, nsIFile *inFile, + nsIMsgFolder *dstFolder, uint32_t *pCount) +{ + int32_t msgCount = 0; + if (pCount) + *pCount = 0; + + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), inFile); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = dstFolder->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t * pIndex; + uint32_t indexSize; + uint32_t * pFlags; + uint64_t * pTime; + + if (!ReadIndex(inputStream, &pIndex, &indexSize)) { + IMPORT_LOG1("No messages found in mailbox: %s\n", NS_LossyConvertUTF16toASCII(name.get())); + return NS_OK; + } + + pTime = new uint64_t[ indexSize]; + pFlags = new uint32_t[ indexSize]; + char * pBuffer = new char[kMailboxBufferSize]; + if (!(*pAbort)) + ConvertIndex(inputStream, pBuffer, pIndex, indexSize, pFlags, pTime); + + uint32_t block[4]; + int32_t sepLen = (int32_t) strlen(m_pFromLineSep); + uint32_t written; + + /* + Each block is: + marker - matches file offset + block length + text length in block + pointer to next block. (0 if end) + + Each message is made up of a linked list of block data. + So what we do for each message is: + 1. Read the first block data. + 2. Write out the From message separator if the message doesn't already + start with one. + 3. If the block of data doesn't end with CRLF then a line is broken into two blocks, + so save the incomplete line for later process when we read the next block. Then + write out the block excluding the partial line at the end of the block (if exists). + 4. If there's next block of data then read next data block. Otherwise we're done. + If we found a partial line in step #3 then find the rest of the line from the + current block and write out this line separately. + 5. Reset some of the control variables and repeat step #3. + */ + + uint32_t didBytes = 0; + uint32_t next, size; + char *pStart, *pEnd, *partialLineStart; + nsAutoCString partialLine, tempLine; + nsCOMPtr<nsIOutputStream> outputStream; + rv = NS_OK; + + for (uint32_t i = 0; (i < indexSize) && !(*pAbort); i++) + { + if (! pIndex[i]) + continue; + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + bool reusable; + + rv = msgStore->GetNewMsgOutputStream(dstFolder, getter_AddRefs(msgHdr), &reusable, + getter_AddRefs(outputStream)); + if (NS_FAILED(rv)) + { + IMPORT_LOG1( "Mbx getting outputstream error: 0x%lx\n", rv); + break; + } + + if (ReadBytes(inputStream, block, pIndex[i], 16) && (block[0] == pIndex[i]) && + (block[2] < kMailboxBufferSize) && (ReadBytes(inputStream, pBuffer, kDontSeek, block[2]))) + { + // block[2] contains the chars in the buffer (ie, buf content size). + // block[3] contains offset to the next block of data (0 means no more data). + size = block[2]; + pStart = pBuffer; + pEnd = pStart + size; + + // write out the from separator. + rv = NS_ERROR_FAILURE; + if (IsFromLine(pBuffer, size)) + { + char *pChar = pStart; + while ((pChar < pEnd) && (*pChar != '\r') && (*(pChar+1) != '\n')) + pChar++; + + if (pChar < pEnd) + { + // Get the "From " line so write it out. + rv = outputStream->Write(pStart, pChar-pStart+2, &written); + if (NS_SUCCEEDED(rv)) + // Now buffer starts from the 2nd line. + pStart = pChar + 2; + } + } + else if (pTime[i]) + { + char result[156] = ""; + PRExplodedTime xpldTime; + char buffer[128] = ""; + PRTime prt; + + nsOE5File::FileTimeToPRTime((FILETIME *)&pTime[i], &prt); + // modeled after nsMsgSend.cpp + PR_ExplodeTime(prt, PR_LocalTimeParameters, &xpldTime); + PR_FormatTimeUSEnglish(buffer, sizeof(buffer), + "%a %b %d %H:%M:%S %Y", + &xpldTime); + PL_strcpy(result, "From - "); + PL_strcpy(result + 7, buffer); + PL_strcpy(result + 7 + 24, CRLF); + + rv = outputStream->Write(result, (int32_t) strlen(result), &written); + } + if (NS_FAILED(rv)) + { + // Write out the default from line since there is none in the msg. + rv = outputStream->Write(m_pFromLineSep, sepLen, &written); + // FIXME: Do I need to check the return value of written??? + if (NS_FAILED(rv)) + break; + } + + char statusLine[50]; + uint32_t msgFlags = XLATFLAGS(pFlags[i]); + PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF); + rv = outputStream->Write(statusLine, strlen(statusLine), &written); + NS_ENSURE_SUCCESS(rv,rv); + PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000); + rv = outputStream->Write(statusLine, strlen(statusLine), &written); + NS_ENSURE_SUCCESS(rv,rv); + + do + { + partialLine.Truncate(); + partialLineStart = pEnd; + + // If the buffer doesn't end with CRLF then a line is broken into two blocks, + // so save the incomplete line for later process when we read the next block. + if ((size > 1) && !(*(pEnd - 2) == '\r' && *(pEnd - 1) == '\n')) + { + partialLineStart -= 2; + while ((partialLineStart >= pStart) && (*partialLineStart != '\r') && (*(partialLineStart+1) != '\n')) + partialLineStart--; + if (partialLineStart != (pEnd - 2)) + partialLineStart += 2; // skip over CRLF if we find them. + partialLine.Assign(partialLineStart, pEnd - partialLineStart); + } + + // Now process the block of data which ends with CRLF. + rv = EscapeFromSpaceLine(outputStream, pStart, partialLineStart); + if (NS_FAILED(rv)) + break; + + didBytes += block[2]; + + next = block[3]; + if (! next) + { + // OK, we're done so flush out the partial line if it's not empty. + if (partialLine.Length()) + rv = EscapeFromSpaceLine(outputStream, (char *)partialLine.get(), (partialLine.get()+partialLine.Length())); + } + else + if (ReadBytes(inputStream, block, next, 16) && (block[0] == next) && + (block[2] < kMailboxBufferSize) && (ReadBytes(inputStream, pBuffer, kDontSeek, block[2]))) + { + // See if we have a partial line from previous block. If so then build a complete + // line (ie, take the remaining chars from this block) and process this line. Need + // to adjust where data start and size in this case. + size = block[2]; + pStart = pBuffer; + pEnd = pStart + size; + if (partialLine.Length()) + { + while ((pStart < pEnd) && (*pStart != '\r') && (*(pStart+1) != '\n')) + pStart++; + if (pStart < pEnd) // if we found a CRLF .. + pStart += 2; // .. then copy that too. + tempLine.Assign(pBuffer, pStart - pBuffer); + partialLine.Append(tempLine); + rv = EscapeFromSpaceLine(outputStream, (char *)partialLine.get(), (partialLine.get()+partialLine.Length())); + if (NS_FAILED(rv)) + break; + + // Adjust where data start and size (since some of the data has been processed). + size -= (pStart - pBuffer); + } + } + else + { + IMPORT_LOG2("Error reading message from %s at 0x%lx\n", NS_LossyConvertUTF16toASCII(name.get()), pIndex[i]); + rv = outputStream->Write("\x0D\x0A", 2, &written); + next = 0; + } + } while (next); + + // Always end a msg with CRLF. This will make sure that OE msgs without body is + // correctly recognized as msgs. Otherwise, we'll end up with the following in + // the msg folder where the 2nd msg starts right after the headers of the 1st msg: + // + // From - Jan 1965 00:00:00 <<<--- 1st msg starts here + // Subject: Test msg + // . . . (more headers) + // To: <someone@example.com> + // From - Jan 1965 00:00:00 <<<--- 2nd msg starts here + // Subject: How are you + // . . .(more headers) + // + // In this case, the 1st msg is not recognized as a msg (it's skipped) + // when you open the folder. + rv = outputStream->Write("\x0D\x0A", 2, &written); + + if (NS_FAILED(rv)) { + IMPORT_LOG0( "Error writing message during OE import\n"); + msgStore->DiscardNewMessage(outputStream, msgHdr); + break; + } + + msgStore->FinishNewMessage(outputStream, msgHdr); + + if (!reusable) + outputStream->Close(); + + msgCount++; + if (pCount) + *pCount = msgCount; + if (pBytesDone) + *pBytesDone = didBytes; + } + else { + // Error reading message, should this be logged??? + IMPORT_LOG2("Error reading message from %s at 0x%lx\n", NS_LossyConvertUTF16toASCII(name.get()), pIndex[i]); + *pAbort = true; + } + } + if (outputStream) + outputStream->Close(); + delete [] pBuffer; + delete [] pFlags; + delete [] pTime; + + if (NS_FAILED(rv)) + *pAbort = true; + + return rv; +} + + +/* + A message index record consists of: + 4 byte marker - matches record offset + 4 bytes size - size of data after this header + 2 bytes header length - not dependable + 1 bytes - number of attributes + 1 byte changes on this object + Each attribute is a 4 byte value with the 1st byte being the tag + and the remaing 3 bytes being data. The data is either a direct + offset of an offset within the message index that points to the + data for the tag. + attr[0]: + -hi bit== 1 means PRUint24 data = attr[1] + -hi bit== 0 means (PRUint24) attr[1] = offset into data segment for data + -attr[0] & 7f == tag index + Header above is 0xC bytes, attr's are number * 4 bytes then follows data segment + The data segment length is undefined. The index data is either part of the + index structure or the index structure points to address in data that follows + index structure table. Use that location to calculate file position of data. + MSDN indicates 0x28 attributes possible. + + Current known tags are: + 0x01 - flags addressed + 0x81 - flags in attr's next 3 bytes + 0x02 - a time value - addressed- 8 bytes + 0x04 - text offset pointer, the data is the offset after the attribute + of a 4 byte pointer to the message text <-- addr into data + 0x05 - offset to truncated subject + 0x08 - offste to subject + 0x0D - offset to from + 0x0E - offset to from addresses + 0x13 - offset to to name + 0x45 - offset to to address <----correct --> 0x14 + 0x80 - msgId <-correction-> 0x07 addr to msg id + 0x84 - direct text offset, direct pointer to message text +*/ + +void nsOE5File::ConvertIndex(nsIInputStream *pFile, char *pBuffer, + uint32_t *pIndex, uint32_t size, + uint32_t *pFlags, uint64_t *pTime) +{ + // for each index record, get the actual message offset! If there is a + // problem just record the message offset as 0 and the message reading code + // can log that error information. + // XXXTODO- above error reporting is not done + + uint8_t recordHead[12]; + uint32_t marker; + uint32_t recordSize; + uint32_t numAttrs; + uint32_t offset; + uint32_t attrIndex; + uint32_t attrOffset; + uint8_t tag; + uint32_t tagData; + uint32_t flags; + uint64_t time; + uint32_t dataStart; + + for (uint32_t i = 0; i < size; i++) { + offset = 0; + flags = 0; + time = 0; + if (ReadBytes(pFile, recordHead, pIndex[i], 12)) { + memcpy(&marker, recordHead, 4); + memcpy(&recordSize, recordHead + 4, 4); + numAttrs = (uint32_t) recordHead[10]; + if (marker == pIndex[i] && numAttrs <= kMaxAttrCount) { + dataStart = pIndex[i] + 12 + (numAttrs * 4); + if (ReadBytes(pFile, pBuffer, kDontSeek, numAttrs * 4)) { + attrOffset = 0; + for (attrIndex = 0; attrIndex < numAttrs; attrIndex++, attrOffset += 4) { + tag = (uint8_t) pBuffer[attrOffset]; + if (tag == (uint8_t) 0x84) { + tagData = 0; + memcpy(&tagData, pBuffer + attrOffset + 1, 3); + offset = tagData; + } + else if (tag == (uint8_t) 0x04) { + tagData = 0; + memcpy(&tagData, pBuffer + attrOffset + 1, 3); + ReadBytes(pFile, &offset, dataStart + tagData, 4); + } + else if (tag == (uint8_t) 0x81) { + tagData = 0; + memcpy(&tagData, pBuffer + attrOffset +1, 3); + flags = tagData; + } + else if (tag == (uint8_t) 0x01) { + tagData = 0; + memcpy(&tagData, pBuffer + attrOffset +1, 3); + ReadBytes(pFile, &flags, dataStart + tagData, 4); + } + else if (tag == (uint8_t) 0x02) { + tagData = 0; + memcpy(&tagData, pBuffer + attrOffset +1, 3); + ReadBytes(pFile, &time, dataStart + tagData, 4); + } + } + } + } + } + pIndex[i] = offset; + pFlags[i] = flags; + pTime[i] = time; + } +} + + +bool nsOE5File::ReadBytes(nsIInputStream *stream, void *pBuffer, uint32_t offset, uint32_t bytes) +{ + nsresult rv; + + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(stream); + if (offset != kDontSeek) { + rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, offset); + if (NS_FAILED(rv)) + return false; + } + + if (!bytes) + return true; + + uint32_t cntRead; + char * pReadTo = (char *)pBuffer; + rv = stream->Read(pReadTo, bytes, &cntRead); + return NS_SUCCEEDED(rv) && cntRead == bytes; + +} + diff --git a/mailnews/import/oexpress/nsOE5File.h b/mailnews/import/oexpress/nsOE5File.h new file mode 100644 index 000000000..07498acfd --- /dev/null +++ b/mailnews/import/oexpress/nsOE5File.h @@ -0,0 +1,52 @@ +/* -*- 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 nsOE5File_h___ +#define nsOE5File_h___ + +#include "nsStringGlue.h" +#include "nsIFile.h" +#include "nsIMsgFolder.h" +#include <windows.h> + +class nsIInputStream; + +class nsOE5File +{ +public: + /* pFile must already be open for reading. */ + static bool VerifyLocalMailFile(nsIFile *pFile); + /* pFile must NOT be open for reading */ + static bool IsLocalMailFile(nsIFile *pFile); + + static bool ReadIndex(nsIInputStream *pFile, uint32_t **ppIndex, uint32_t *pSize); + + static nsresult ImportMailbox(uint32_t *pBytesDone, bool *pAbort, + nsString& name, nsIFile *inFile, + nsIMsgFolder *pDstFolder, uint32_t *pCount); + + static void FileTimeToPRTime(const FILETIME *filetime, PRTime *prtm); + +private: + typedef struct { + uint32_t * pIndex; + uint32_t count; + uint32_t alloc; + } PRUint32Array; + + static const char *m_pFromLineSep; + + static bool ReadBytes(nsIInputStream *stream, void *pBuffer, uint32_t offset, uint32_t bytes); + static uint32_t ReadMsgIndex(nsIInputStream *file, uint32_t offset, PRUint32Array *pArray); + static void ConvertIndex(nsIInputStream *pFile, char *pBuffer, uint32_t *pIndex, + uint32_t size, uint32_t *pFlags, uint64_t *pTime); + static bool IsFromLine(char *pLine, uint32_t len); + + +}; + + + +#endif /* nsOE5File_h___ */ diff --git a/mailnews/import/oexpress/nsOEAddressIterator.cpp b/mailnews/import/oexpress/nsOEAddressIterator.cpp new file mode 100644 index 000000000..bc33b45fb --- /dev/null +++ b/mailnews/import/oexpress/nsOEAddressIterator.cpp @@ -0,0 +1,396 @@ +/* -*- 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/. */ + + +/* + + A sample of XPConnect. This file contains an implementation of + nsISample. + +*/ +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsMsgUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIServiceManager.h" +#include "nsIImportService.h" +#include "nsIImportFieldMap.h" +#include "nsABBaseCID.h" +#include "nsIAbCard.h" +#include "prprf.h" + +#include "nsOEAddressIterator.h" + +#include "OEDebugLog.h" + +typedef struct { + int32_t mozField; + int32_t multiLine; + ULONG mapiTag; +} MAPIFields; + +enum { + ieidPR_DISPLAY_NAME = 0, + ieidPR_ENTRYID, + ieidPR_OBJECT_TYPE, + ieidMax +}; + +/* + Fields in MAPI, not in Mozilla + PR_OFFICE_LOCATION + FIX - PR_BIRTHDAY - stored as PT_SYSTIME - FIX to extract for moz address book birthday + PR_DISPLAY_NAME_PREFIX - Mr., Mrs. Dr., etc. + PR_SPOUSE_NAME + PR_GENDER - integer, not text + FIX - PR_CONTACT_EMAIL_ADDRESSES - multiuline strings for email addresses, needs + parsing to get secondary email address for mozilla +*/ + +#define kIsMultiLine -2 +#define kNoMultiLine -1 + +static MAPIFields gMapiFields[] = { + { 35, kIsMultiLine, PR_COMMENT}, + { 6, kNoMultiLine, PR_BUSINESS_TELEPHONE_NUMBER}, + { 7, kNoMultiLine, PR_HOME_TELEPHONE_NUMBER}, + { 25, kNoMultiLine, PR_COMPANY_NAME}, + { 23, kNoMultiLine, PR_TITLE}, + { 10, kNoMultiLine, PR_CELLULAR_TELEPHONE_NUMBER}, + { 9, kNoMultiLine, PR_PAGER_TELEPHONE_NUMBER}, + { 8, kNoMultiLine, PR_BUSINESS_FAX_NUMBER}, + { 8, kNoMultiLine, PR_HOME_FAX_NUMBER}, + { 22, kNoMultiLine, PR_COUNTRY}, + { 19, kNoMultiLine, PR_LOCALITY}, + { 20, kNoMultiLine, PR_STATE_OR_PROVINCE}, + { 17, 18, PR_STREET_ADDRESS}, + { 21, kNoMultiLine, PR_POSTAL_CODE}, + { 27, kNoMultiLine, PR_PERSONAL_HOME_PAGE}, + { 26, kNoMultiLine, PR_BUSINESS_HOME_PAGE}, + { 13, kNoMultiLine, PR_HOME_ADDRESS_CITY}, + { 16, kNoMultiLine, PR_HOME_ADDRESS_COUNTRY}, + { 15, kNoMultiLine, PR_HOME_ADDRESS_POSTAL_CODE}, + { 14, kNoMultiLine, PR_HOME_ADDRESS_STATE_OR_PROVINCE}, + { 11, 12, PR_HOME_ADDRESS_STREET}, + { 24, kNoMultiLine, PR_DEPARTMENT_NAME} +}; + +nsOEAddressIterator::nsOEAddressIterator(CWAB *pWab, nsIAddrDatabase *database) +{ + m_pWab = pWab; + m_database = database; +} + +nsOEAddressIterator::~nsOEAddressIterator() +{ +} + +nsresult nsOEAddressIterator::EnumUser(const char16_t * pName, LPENTRYID pEid, ULONG cbEid) +{ + IMPORT_LOG1("User: %S\n", pName); + nsresult rv = NS_OK; + + if (m_database) { + LPMAILUSER pUser = m_pWab->GetUser(cbEid, pEid); + if (pUser) { + // Get a new row from the database! + nsCOMPtr <nsIMdbRow> newRow; + rv = m_database->GetNewRow(getter_AddRefs(newRow)); + NS_ENSURE_SUCCESS(rv, rv); + if (newRow && BuildCard(pName, newRow, pUser)) + { + rv = m_database->AddCardRowToDB(newRow); + NS_ENSURE_SUCCESS(rv, rv); + IMPORT_LOG0("* Added entry to address book database\n"); + nsString eMail; + + LPSPropValue pProp = m_pWab->GetUserProperty(pUser, PR_EMAIL_ADDRESS); + if (pProp) + { + m_pWab->GetValueString(pProp, eMail); + SanitizeValue(eMail); + m_pWab->FreeProperty(pProp); + m_listRows.Put(eMail, newRow); + } + } + m_pWab->ReleaseUser(pUser); + } + } + + return rv; +} + +void nsOEAddressIterator::FindListRow(nsString &eMail, nsIMdbRow **cardRow) +{ + m_listRows.Get(eMail,cardRow); +} + +nsresult nsOEAddressIterator::EnumList(const char16_t * pName, LPENTRYID pEid, ULONG cbEid, LPMAPITABLE lpTable) +{ + // If no name provided then we're done. + if (!pName || !(*pName)) + return NS_OK; + + nsresult rv = NS_ERROR_FAILURE; + HRESULT hr = E_FAIL; + // Make sure we have db to work with. + if (!m_database) + return rv; + + nsCOMPtr <nsIMdbRow> listRow; + rv = m_database->GetNewListRow(getter_AddRefs(listRow)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = m_database->AddListName(listRow, NS_ConvertUTF16toUTF8(pName).get()); + NS_ENSURE_SUCCESS(rv, rv); + rv = m_database->AddCardRowToDB(listRow); + NS_ENSURE_SUCCESS(rv, rv); + rv = m_database->AddListDirNode(listRow); + NS_ENSURE_SUCCESS(rv, rv); + + LPSRowSet lpRowAB = NULL; + ULONG lpcbEID = 0; + LPENTRYID lpEID = NULL; + ULONG rowCount = 0; + int cNumRows = 0; + int numListElems = 0; + nsAutoString uniStr; + + hr = lpTable->GetRowCount(0, &rowCount); + // + hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + + if(HR_FAILED(hr)) + return NS_ERROR_FAILURE; + + // Read all the rows of the table one by one + do + { + hr = lpTable->QueryRows(1, 0, &lpRowAB); + + if(HR_FAILED(hr)) + break; + + if(lpRowAB) + { + cNumRows = lpRowAB->cRows; + if (cNumRows) + { + LPTSTR lpsz = lpRowAB->aRow[0].lpProps[ieidPR_DISPLAY_NAME].Value.lpszA; + LPENTRYID lpEID = (LPENTRYID) lpRowAB->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRowAB->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + + // There are 2 kinds of objects - the MAPI_MAILUSER contact object + // and the MAPI_DISTLIST contact object + // For distribution lists, we will only consider MAILUSER + // objects since we can't nest lists yet. + if(lpRowAB->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.l == MAPI_MAILUSER) + { + LPMAILUSER pUser = m_pWab->GetUser(cbEID, lpEID); + LPSPropValue pProp = m_pWab->GetUserProperty(pUser, PR_EMAIL_ADDRESS); + nsString eMail; + + nsCOMPtr <nsIMdbRow> cardRow; + + m_pWab->GetValueString(pProp, eMail); + SanitizeValue(eMail); + FindListRow(eMail, getter_AddRefs(cardRow)); + if (cardRow) + { + nsCOMPtr <nsIAbCard> userCard; + nsCOMPtr <nsIAbCard> newCard; + userCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + m_database->InitCardFromRow(userCard,cardRow); + + m_database->AddListCardColumnsToRow(userCard, listRow, ++numListElems, + getter_AddRefs(newCard), + true, nullptr, nullptr); + } + m_pWab->FreeProperty(pProp); + m_pWab->ReleaseUser(pUser); + } + } + m_pWab->FreeProws(lpRowAB); + } + } while (SUCCEEDED(hr) && cNumRows && lpRowAB); + + m_database->SetListAddressTotal(listRow, numListElems); + return rv; +} + +void nsOEAddressIterator::SanitizeValue(nsString& val) +{ + MsgReplaceSubstring(val, NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING(", ")); + MsgReplaceChar(val, "\r\n", ','); +} + +void nsOEAddressIterator::SplitString(nsString& val1, nsString& val2) +{ + // Find the last line if there is more than one! + int32_t idx = val1.RFind("\x0D\x0A"); + int32_t cnt = 2; + if (idx == -1) { + cnt = 1; + idx = val1.RFindChar(13); + } + if (idx == -1) + idx= val1.RFindChar(10); + if (idx != -1) { + val2 = Substring(val1, idx + cnt); + val1.SetLength(idx); + SanitizeValue(val1); + } +} + +void nsOEAddressIterator::SetBirthDay(nsIMdbRow *newRow, PRTime& birthDay) +{ + PRExplodedTime exploded; + PR_ExplodeTime(birthDay, PR_LocalTimeParameters, &exploded); + + char stringValue[5]; + PR_snprintf(stringValue, sizeof(stringValue), "%04d", exploded.tm_year); + m_database->AddBirthYear(newRow, stringValue); + PR_snprintf(stringValue, sizeof(stringValue), "%02d", exploded.tm_month + 1); + m_database->AddBirthMonth(newRow, stringValue); + PR_snprintf(stringValue, sizeof(stringValue), "%02d", exploded.tm_mday); + m_database->AddBirthDay(newRow, stringValue); +} + +bool nsOEAddressIterator::BuildCard(const char16_t * pName, nsIMdbRow *newRow, LPMAILUSER pUser) +{ + + nsString lastName; + nsString firstName; + nsString eMail; + nsString nickName; + nsString middleName; + PRTime birthDay = 0; + + LPSPropValue pProp = m_pWab->GetUserProperty(pUser, PR_EMAIL_ADDRESS); + if (pProp) { + m_pWab->GetValueString(pProp, eMail); + SanitizeValue(eMail); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_GIVEN_NAME); + if (pProp) { + m_pWab->GetValueString(pProp, firstName); + SanitizeValue(firstName); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_SURNAME); + if (pProp) { + m_pWab->GetValueString(pProp, lastName); + SanitizeValue(lastName); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_MIDDLE_NAME); + if (pProp) { + m_pWab->GetValueString(pProp, middleName); + SanitizeValue(middleName); + m_pWab->FreeProperty(pProp); + } + pProp = m_pWab->GetUserProperty(pUser, PR_NICKNAME); + if (pProp) { + m_pWab->GetValueString(pProp, nickName); + SanitizeValue(nickName); + m_pWab->FreeProperty(pProp); + } + + // The idea here is that firstName and lastName cannot both be empty! + if (firstName.IsEmpty() && lastName.IsEmpty()) + firstName = pName; + + nsString displayName; + pProp = m_pWab->GetUserProperty(pUser, PR_DISPLAY_NAME); + if (pProp) { + m_pWab->GetValueString(pProp, displayName); + SanitizeValue(displayName); + m_pWab->FreeProperty(pProp); + } + if (displayName.IsEmpty()) { + if (firstName.IsEmpty()) + displayName = pName; + else { + displayName = firstName; + if (!middleName.IsEmpty()) { + displayName.Append(char16_t(' ')); + displayName.Append(middleName); + } + if (!lastName.IsEmpty()) { + displayName.Append(char16_t(' ')); + displayName.Append(lastName); + } + } + } + + pProp = m_pWab->GetUserProperty(pUser, PR_BIRTHDAY); + if (pProp) { + m_pWab->GetValueTime(pProp, birthDay); + m_pWab->FreeProperty(pProp); + } + + // We now have the required fields + // write them out followed by any optional fields! + if (!displayName.IsEmpty()) + m_database->AddDisplayName(newRow, NS_ConvertUTF16toUTF8(displayName).get()); + if (!firstName.IsEmpty()) + m_database->AddFirstName(newRow, NS_ConvertUTF16toUTF8(firstName).get()); + if (!lastName.IsEmpty()) + m_database->AddLastName(newRow, NS_ConvertUTF16toUTF8(lastName).get()); + if (!nickName.IsEmpty()) + m_database->AddNickName(newRow, NS_ConvertUTF16toUTF8(nickName).get()); + if (!eMail.IsEmpty()) + m_database->AddPrimaryEmail(newRow, NS_ConvertUTF16toUTF8(eMail).get()); + + if (birthDay) + SetBirthDay(newRow, birthDay); + + // Do all of the extra fields! + + nsString value; + nsString line2; + nsresult rv; + // Create a field map + + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nsIImportFieldMap * pFieldMap = nullptr; + rv = impSvc->CreateNewFieldMap(&pFieldMap); + if (NS_SUCCEEDED(rv) && pFieldMap) { + int max = sizeof(gMapiFields) / sizeof(MAPIFields); + for (int i = 0; i < max; i++) { + pProp = m_pWab->GetUserProperty(pUser, gMapiFields[i].mapiTag); + if (pProp) { + m_pWab->GetValueString(pProp, value); + m_pWab->FreeProperty(pProp); + if (!value.IsEmpty()) { + if (gMapiFields[i].multiLine == kNoMultiLine) { + SanitizeValue(value); + pFieldMap->SetFieldValue(m_database, newRow, gMapiFields[i].mozField, value.get()); + } + else if (gMapiFields[i].multiLine == kIsMultiLine) { + pFieldMap->SetFieldValue(m_database, newRow, gMapiFields[i].mozField, value.get()); + } + else { + line2.Truncate(); + SplitString(value, line2); + if (!value.IsEmpty()) + pFieldMap->SetFieldValue(m_database, newRow, gMapiFields[i].mozField, value.get()); + if (!line2.IsEmpty()) + pFieldMap->SetFieldValue(m_database, newRow, gMapiFields[i].multiLine, line2.get()); + } + } + } + } + // call fieldMap SetFieldValue based on the table of fields + + NS_RELEASE(pFieldMap); + } + } + return true; +} diff --git a/mailnews/import/oexpress/nsOEAddressIterator.h b/mailnews/import/oexpress/nsOEAddressIterator.h new file mode 100644 index 000000000..fe6082736 --- /dev/null +++ b/mailnews/import/oexpress/nsOEAddressIterator.h @@ -0,0 +1,36 @@ +/* -*- 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 nsOEAddressIterator_h___ +#define nsOEAddressIterator_h___ + +#include "mozilla/Attributes.h" +#include "WabObject.h" +#include "nsIAddrDatabase.h" +#include "mdb.h" +#include "nsStringGlue.h" +#include "nsInterfaceHashtable.h" + +class nsOEAddressIterator : public CWabIterator { +public: + nsOEAddressIterator(CWAB *pWab, nsIAddrDatabase *database); + ~nsOEAddressIterator(); + + virtual nsresult EnumUser(const char16_t * pName, LPENTRYID pEid, ULONG cbEid) override; + virtual nsresult EnumList(const char16_t * pName, LPENTRYID pEid, ULONG cbEid, LPMAPITABLE table) override; + void FindListRow(nsString &eMail, nsIMdbRow **cardRow); + +private: + bool BuildCard(const char16_t * pName, nsIMdbRow *card, LPMAILUSER pUser); + void SanitizeValue(nsString& val); + void SplitString(nsString& val1, nsString& val2); + void SetBirthDay(nsIMdbRow *card, PRTime& birthDay); + + CWAB * m_pWab; + nsCOMPtr<nsIAddrDatabase> m_database; + nsInterfaceHashtable <nsStringHashKey, nsIMdbRow> m_listRows; +}; + +#endif diff --git a/mailnews/import/oexpress/nsOEImport.cpp b/mailnews/import/oexpress/nsOEImport.cpp new file mode 100644 index 000000000..21016c59e --- /dev/null +++ b/mailnews/import/oexpress/nsOEImport.cpp @@ -0,0 +1,657 @@ +/* -*- 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/. */ + + +/* + + Outlook Express (Win32) import mail and addressbook interfaces + +*/ +#include "nscore.h" +#include "nsMsgUtils.h" +#include "nsStringGlue.h" +#include "nsComponentManagerUtils.h" +#include "nsIImportService.h" +#include "nsOEImport.h" +#include "nsIMemory.h" +#include "nsOEScanBoxes.h" +#include "nsIImportService.h" +#include "nsIImportMail.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIImportGeneric.h" +#include "nsOEMailbox.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportFieldMap.h" +#include "nsIMutableArray.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "WabObject.h" +#include "nsOEAddressIterator.h" +#include "nsIOutputStream.h" +#include "nsOE5File.h" +#include "nsIAddrDatabase.h" +#include "nsOESettings.h" +#include "nsTextFormatter.h" +#include "nsOEStringBundle.h" +#include "nsIStringBundle.h" +#include "nsUnicharUtils.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" + +#include "OEDebugLog.h" + +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); +PRLogModuleInfo *OELOGMODULE = nullptr; + +class ImportOEMailImpl : public nsIImportMail +{ +public: + ImportOEMailImpl(); + + static nsresult Create(nsIImportMail** aImport); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportmail interface + + /* void GetDefaultLocation (out nsIFile location, out boolean found, out boolean userVerify); */ + NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found, bool *userVerify); + + /* nsIArray FindMailboxes (in nsIFile location); */ + NS_IMETHOD FindMailboxes(nsIFile *location, nsIArray **_retval); + + NS_IMETHOD ImportMailbox(nsIImportMailboxDescriptor *source, + nsIMsgFolder *dstFolder, + char16_t **pErrorLog, char16_t **pSuccessLog, + bool *fatalError); + + /* unsigned long GetImportProgress (); */ + NS_IMETHOD GetImportProgress(uint32_t *_retval); + + NS_IMETHOD TranslateFolderName(const nsAString & aFolderName, nsAString & _retval); + +public: + static void ReportSuccess(nsString& name, int32_t count, nsString *pStream); + static void ReportError(int32_t errorNum, nsString& name, nsString *pStream); + static void AddLinebreak(nsString *pStream); + static void SetLogs(nsString& success, nsString& error, char16_t **pError, char16_t **pSuccess); + +private: + virtual ~ImportOEMailImpl(); + uint32_t m_bytesDone; +}; + + +class ImportOEAddressImpl : public nsIImportAddressBooks +{ +public: + ImportOEAddressImpl(); + + static nsresult Create(nsIImportAddressBooks** aImport); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportAddressBooks interface + + NS_IMETHOD GetSupportsMultiple(bool *_retval) { *_retval = false; return NS_OK;} + + NS_IMETHOD GetAutoFind(char16_t **description, bool *_retval); + + NS_IMETHOD GetNeedsFieldMap(nsIFile *pLoc, bool *_retval) { *_retval = false; return NS_OK;} + + NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found, bool *userVerify); + + NS_IMETHOD FindAddressBooks(nsIFile *location, nsIArray **_retval); + + NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap) + { return NS_ERROR_FAILURE; } + + NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source, + nsIAddrDatabase *destination, + nsIImportFieldMap *fieldMap, + nsISupports *aSupportService, + char16_t **errorLog, + char16_t **successLog, + bool *fatalError); + + NS_IMETHOD GetImportProgress(uint32_t *_retval); + + NS_IMETHOD GetSampleData(int32_t index, bool *pFound, char16_t **pStr) + { return NS_ERROR_FAILURE;} + + NS_IMETHOD SetSampleLocation(nsIFile *) { return NS_OK; } + +private: + virtual ~ImportOEAddressImpl(); + static void ReportSuccess(nsString& name, nsString *pStream); + +private: + CWAB * m_pWab; + int m_doneSoFar; +}; +//////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////// + + +nsOEImport::nsOEImport() +{ + // Init logging module. + if (!OELOGMODULE) + OELOGMODULE = PR_NewLogModule("IMPORT"); + IMPORT_LOG0("nsOEImport Module Created\n"); + nsOEStringBundle::GetStringBundle(); +} + + +nsOEImport::~nsOEImport() +{ + IMPORT_LOG0("nsOEImport Module Deleted\n"); +} + +NS_IMPL_ISUPPORTS(nsOEImport, nsIImportModule) + +NS_IMETHODIMP nsOEImport::GetName(char16_t **name) +{ + NS_ENSURE_ARG_POINTER(name); + + *name = nsOEStringBundle::GetStringByID(OEIMPORT_NAME); + + return NS_OK; +} + +NS_IMETHODIMP nsOEImport::GetDescription(char16_t **name) +{ + NS_ENSURE_ARG_POINTER(name); + + *name = nsOEStringBundle::GetStringByID(OEIMPORT_DESCRIPTION); + return NS_OK; +} + +NS_IMETHODIMP nsOEImport::GetSupports(char **supports) +{ + NS_PRECONDITION(supports != nullptr, "null ptr"); + if (! supports) + return NS_ERROR_NULL_POINTER; + + *supports = strdup(kOESupportsString); + return NS_OK; +} + + +NS_IMETHODIMP nsOEImport::GetSupportsUpgrade(bool *pUpgrade) +{ + NS_PRECONDITION(pUpgrade != nullptr, "null ptr"); + if (! pUpgrade) + return NS_ERROR_NULL_POINTER; + + *pUpgrade = true; + return NS_OK; +} + +NS_IMETHODIMP nsOEImport::GetImportInterface(const char *pImportType, nsISupports **ppInterface) +{ + NS_ENSURE_ARG_POINTER(pImportType); + NS_ENSURE_ARG_POINTER(ppInterface); + + *ppInterface = nullptr; + nsresult rv; + if (!strcmp(pImportType, "mail")) { + // create the nsIImportMail interface and return it! + nsIImportMail * pMail = nullptr; + nsIImportGeneric *pGeneric = nullptr; + rv = ImportOEMailImpl::Create(&pMail); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericMail(&pGeneric); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("mailInterface", pMail); + nsString name; + nsOEStringBundle::GetStringByID(OEIMPORT_NAME, name); + nsCOMPtr<nsISupportsString> nameString (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nameString->SetData(name); + pGeneric->SetData("name", nameString); + rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface); + } + } + } + } + NS_IF_RELEASE(pMail); + NS_IF_RELEASE(pGeneric); + return rv; + } + + if (!strcmp(pImportType, "addressbook")) { + // create the nsIImportMail interface and return it! + nsIImportAddressBooks * pAddress = nullptr; + nsIImportGeneric * pGeneric = nullptr; + rv = ImportOEAddressImpl::Create(&pAddress); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericAddressBooks(&pGeneric); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("addressInterface", pAddress); + rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface); + } + } + } + NS_IF_RELEASE(pAddress); + NS_IF_RELEASE(pGeneric); + return rv; + } + + if (!strcmp(pImportType, "settings")) { + nsIImportSettings *pSettings = nullptr; + rv = nsOESettings::Create(&pSettings); + if (NS_SUCCEEDED(rv)) + pSettings->QueryInterface(kISupportsIID, (void **)ppInterface); + NS_IF_RELEASE(pSettings); + return rv; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +///////////////////////////////////////////////////////////////////////////////// +nsresult ImportOEMailImpl::Create(nsIImportMail** aImport) +{ + NS_ENSURE_ARG_POINTER(aImport); + *aImport = new ImportOEMailImpl(); + NS_ENSURE_TRUE(*aImport, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(*aImport); + return NS_OK; +} + +ImportOEMailImpl::ImportOEMailImpl() +{ +} + + +ImportOEMailImpl::~ImportOEMailImpl() +{ +} + +NS_IMPL_ISUPPORTS(ImportOEMailImpl, nsIImportMail) + +NS_IMETHODIMP ImportOEMailImpl::TranslateFolderName(const nsAString & aFolderName, nsAString & _retval) +{ + if (aFolderName.LowerCaseEqualsLiteral("deleted items")) + _retval = NS_LITERAL_STRING(kDestTrashFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("sent items")) + _retval = NS_LITERAL_STRING(kDestSentFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("outbox")) + _retval = NS_LITERAL_STRING(kDestUnsentMessagesFolderName); + else + _retval = aFolderName; + + return NS_OK; +} + +NS_IMETHODIMP ImportOEMailImpl::GetDefaultLocation(nsIFile **ppLoc, bool *found, bool *userVerify) +{ + NS_PRECONDITION(ppLoc != nullptr, "null ptr"); + NS_PRECONDITION(found != nullptr, "null ptr"); + NS_PRECONDITION(userVerify != nullptr, "null ptr"); + if (!ppLoc || !found || !userVerify) + return NS_ERROR_NULL_POINTER; + + // use scanboxes to find the location. + nsresult rv; + nsCOMPtr <nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + if (nsOEScanBoxes::FindMail(file)) { + *found = true; + NS_IF_ADDREF(*ppLoc = file); + } + else { + *found = false; + *ppLoc = nullptr; + } + *userVerify = true; + return NS_OK; +} + + +NS_IMETHODIMP ImportOEMailImpl::FindMailboxes(nsIFile *pLoc, nsIArray **ppArray) +{ + NS_PRECONDITION(pLoc != nullptr, "null ptr"); + NS_PRECONDITION(ppArray != nullptr, "null ptr"); + if (!pLoc || !ppArray) + return NS_ERROR_NULL_POINTER; + + bool exists = false; + nsresult rv = pLoc->Exists(&exists); + if (NS_FAILED(rv) || !exists) + return NS_ERROR_FAILURE; + + nsOEScanBoxes scan; + + if (!scan.GetMailboxes(pLoc, ppArray)) + *ppArray = nullptr; + + return NS_OK; +} + +void ImportOEMailImpl::AddLinebreak(nsString *pStream) +{ + if (pStream) + pStream->Append(char16_t('\n')); +} + +void ImportOEMailImpl::ReportSuccess(nsString& name, int32_t count, nsString *pStream) +{ + if (!pStream) + return; + // load the success string + char16_t *pFmt = nsOEStringBundle::GetStringByID(OEIMPORT_MAILBOX_SUCCESS); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get(), count); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + nsOEStringBundle::FreeString(pFmt); + AddLinebreak(pStream); +} + +void ImportOEMailImpl::ReportError(int32_t errorNum, nsString& name, nsString *pStream) +{ + if (!pStream) + return; + // load the error string + char16_t *pFmt = nsOEStringBundle::GetStringByID(errorNum); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + nsOEStringBundle::FreeString(pFmt); + AddLinebreak(pStream); +} + + +void ImportOEMailImpl::SetLogs(nsString& success, nsString& error, char16_t **pError, char16_t **pSuccess) +{ + if (pError) + *pError = ToNewUnicode(error); + if (pSuccess) + *pSuccess = ToNewUnicode(success); +} + +NS_IMETHODIMP ImportOEMailImpl::ImportMailbox(nsIImportMailboxDescriptor *pSource, + nsIMsgFolder *dstFolder, + char16_t **pErrorLog, + char16_t **pSuccessLog, + bool *fatalError) +{ + NS_ENSURE_ARG_POINTER(pSource); + NS_ENSURE_ARG_POINTER(dstFolder); + NS_ENSURE_ARG_POINTER(fatalError); + + nsString success; + nsString error; + bool abort = false; + nsString name; + nsString pName; + if (NS_SUCCEEDED(pSource->GetDisplayName(getter_Copies(pName)))) + name = pName; + + uint32_t mailSize = 0; + pSource->GetSize(&mailSize); + if (mailSize == 0) { + ReportSuccess(name, 0, &success); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_OK; + } + + nsCOMPtr <nsIFile> inFile; + if (NS_FAILED(pSource->GetFile(getter_AddRefs(inFile)))) { + ReportError(OEIMPORT_MAILBOX_BADSOURCEFILE, name, &error); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_ERROR_FAILURE; + } + + nsCString pPath; + inFile->GetNativePath(pPath); + IMPORT_LOG1("Importing Outlook Express mailbox: %s\n", pPath.get()); + + m_bytesDone = 0; + uint32_t msgCount = 0; + nsresult rv; + if (nsOE5File::IsLocalMailFile(inFile)) { + IMPORT_LOG1("Importing OE5 mailbox: %s!\n", NS_LossyConvertUTF16toASCII(name.get())); + rv = nsOE5File::ImportMailbox( &m_bytesDone, &abort, name, inFile, dstFolder, &msgCount); + } + else { + if (CImportMailbox::ImportMailbox( &m_bytesDone, &abort, name, inFile, dstFolder, &msgCount)) + rv = NS_OK; + else + rv = NS_ERROR_FAILURE; + } + + if (NS_SUCCEEDED(rv)) + ReportSuccess(name, msgCount, &success); + else + ReportError(OEIMPORT_MAILBOX_CONVERTERROR, name, &error); + + SetLogs(success, error, pErrorLog, pSuccessLog); + + return rv; +} + +NS_IMETHODIMP ImportOEMailImpl::GetImportProgress(uint32_t *pDoneSoFar) +{ + NS_ENSURE_ARG_POINTER(pDoneSoFar); + *pDoneSoFar = m_bytesDone; + return NS_OK; +} + +nsresult ImportOEAddressImpl::Create(nsIImportAddressBooks** aImport) +{ + NS_ENSURE_ARG_POINTER(aImport); + + *aImport = new ImportOEAddressImpl(); + NS_ENSURE_TRUE(*aImport, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(*aImport); + return NS_OK; +} + +ImportOEAddressImpl::ImportOEAddressImpl() +{ + m_pWab = nullptr; +} + + +ImportOEAddressImpl::~ImportOEAddressImpl() +{ + if (m_pWab) + delete m_pWab; +} + +NS_IMPL_ISUPPORTS(ImportOEAddressImpl, nsIImportAddressBooks) + +NS_IMETHODIMP ImportOEAddressImpl::GetDefaultLocation(nsIFile **aLocation, + bool *aFound, + bool *aUserVerify) +{ + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(aFound); + NS_ENSURE_ARG_POINTER(aUserVerify); + + *aLocation = nullptr; + *aUserVerify = true; + + CWAB *wab = new CWAB(nullptr); + *aFound = wab->IsAvailable(); + delete wab; + + if (*aFound) { + // Unfortunately WAB interface has no function to obtain address book location. + // So we set a fake location here. + if (NS_SUCCEEDED(NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aLocation))) + *aUserVerify = false; + } + + return NS_OK; +} + +NS_IMETHODIMP ImportOEAddressImpl::GetAutoFind(char16_t **description, bool *_retval) +{ + NS_PRECONDITION(description != nullptr, "null ptr"); + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! description || !_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = false; + nsString str; + str.Append(nsOEStringBundle::GetStringByID(OEIMPORT_AUTOFIND)); + *description = ToNewUnicode(str); + return NS_OK; +} + + + +NS_IMETHODIMP ImportOEAddressImpl::FindAddressBooks(nsIFile *location, nsIArray **_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + // Make sure we can load up the windows address book... + rv = NS_ERROR_FAILURE; + + if (m_pWab) + delete m_pWab; + + nsCOMPtr<nsIFile> currentProcessDir; + rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, + getter_AddRefs(currentProcessDir)); + bool equals = false; + currentProcessDir->Equals(location, &equals); + // If the location is not a fake, use it. + if (location && !equals) { + nsCOMPtr<nsIFile> localFile = do_QueryInterface(location, &rv); + NS_ENSURE_SUCCESS(rv, rv); + m_pWab = new CWAB(localFile); + } else { + m_pWab = new CWAB(nullptr); + } + + nsIImportABDescriptor * pID; + nsISupports * pInterface; + nsString str; + str.Append(nsOEStringBundle::GetStringByID(OEIMPORT_DEFAULT_NAME)); + + if (m_pWab->Loaded()) { + // create a new nsIImportABDescriptor and add it to the array + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewABDescriptor(&pID); + if (NS_SUCCEEDED(rv)) { + pID->SetIdentifier(0x4F453334); + pID->SetRef(1); + pID->SetSize(100); + pID->SetPreferredName(str); + rv = pID->QueryInterface(kISupportsIID, (void **) &pInterface); + array->AppendElement(pInterface, false); + pInterface->Release(); + pID->Release(); + } + } + } + + if (NS_FAILED(rv)) { + delete m_pWab; + m_pWab = nullptr; + } + array.forget(_retval); + return NS_OK; +} + + + +NS_IMETHODIMP ImportOEAddressImpl::ImportAddressBook(nsIImportABDescriptor *source, + nsIAddrDatabase *destination, + nsIImportFieldMap *fieldMap, + nsISupports *aSupportService, + char16_t **errorLog, + char16_t **successLog, + bool *fatalError) +{ + NS_PRECONDITION(source != nullptr, "null ptr"); + // NS_PRECONDITION(destination != nullptr, "null ptr"); + // NS_PRECONDITION(fieldMap != nullptr, "null ptr"); + NS_PRECONDITION(fatalError != nullptr, "null ptr"); + if (!source || !fatalError) + return NS_ERROR_NULL_POINTER; + + // we assume it is our one and only address book. + if (!m_pWab) { + IMPORT_LOG0("Wab not loaded in ImportAddressBook call\n"); + return NS_ERROR_FAILURE; + } + + IMPORT_LOG0("IMPORTING OUTLOOK EXPRESS ADDRESS BOOK\n"); + + nsString success; + nsString error; + if (!source || !destination || !fatalError) + { + nsOEStringBundle::GetStringByID(OEIMPORT_ADDRESS_BADPARAM, error); + if (fatalError) + *fatalError = true; + ImportOEMailImpl::SetLogs(success, error, errorLog, successLog); + return NS_ERROR_NULL_POINTER; + } + + m_doneSoFar = 0; + nsOEAddressIterator * pIter = new nsOEAddressIterator(m_pWab, destination); + HRESULT hr = m_pWab->IterateWABContents(pIter, &m_doneSoFar); + delete pIter; + + nsString name; + if (SUCCEEDED(hr) && NS_SUCCEEDED(source->GetPreferredName(name))) + ReportSuccess(name, &success); + else + ImportOEMailImpl::ReportError(OEIMPORT_ADDRESS_CONVERTERROR, name, &error); + + ImportOEMailImpl::SetLogs(success, error, errorLog, successLog); + + nsresult rv = destination->Commit(nsAddrDBCommitType::kLargeCommit); + return rv; +} + + +NS_IMETHODIMP ImportOEAddressImpl::GetImportProgress(uint32_t *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! _retval) + return NS_ERROR_NULL_POINTER; + + *_retval = (uint32_t) m_doneSoFar; + return NS_OK; +} + +void ImportOEAddressImpl::ReportSuccess(nsString& name, nsString *pStream) +{ + if (!pStream) + return; + // load the success string + char16_t *pFmt = nsOEStringBundle::GetStringByID(OEIMPORT_ADDRESS_SUCCESS); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + nsOEStringBundle::FreeString(pFmt); + ImportOEMailImpl::AddLinebreak(pStream); +} diff --git a/mailnews/import/oexpress/nsOEImport.h b/mailnews/import/oexpress/nsOEImport.h new file mode 100644 index 000000000..c637a7461 --- /dev/null +++ b/mailnews/import/oexpress/nsOEImport.h @@ -0,0 +1,42 @@ +/* -*- 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 nsOEImport_h___ +#define nsOEImport_h___ + +#include "nsIImportModule.h" +#include "nsCOMPtr.h" + +#define NS_OEIMPORT_CID \ +{ /* be0bc880-1742-11d3-a206-00a0cc26da63 */ \ + 0xbe0bc880, 0x1742, 0x11d3, \ + {0xa2, 0x06, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63}} + + + +#define kOESupportsString NS_IMPORT_MAIL_STR "," NS_IMPORT_ADDRESS_STR "," NS_IMPORT_SETTINGS_STR + +class nsOEImport : public nsIImportModule +{ +public: + + nsOEImport(); + + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////////////////////// + // we suppport the nsIImportModule interface + //////////////////////////////////////////////////////////////////////////////////////// + + + NS_DECL_NSIIMPORTMODULE + +protected: + virtual ~nsOEImport(); +}; + + + +#endif /* nsOEImport_h___ */ diff --git a/mailnews/import/oexpress/nsOEMailbox.cpp b/mailnews/import/oexpress/nsOEMailbox.cpp new file mode 100644 index 000000000..7a1fb0be7 --- /dev/null +++ b/mailnews/import/oexpress/nsOEMailbox.cpp @@ -0,0 +1,673 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsOEMailbox.h" + +#include "OEDebugLog.h" +#include "msgCore.h" +#include "prprf.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIMsgFolder.h" +#include "nsIMsgHdr.h" +#include "nsIMsgPluggableStore.h" +#include "nsISeekableStream.h" +#include "nsMsgUtils.h" + +class CMbxScanner { +public: + CMbxScanner(nsString& name, nsIFile * mbxFile, nsIMsgFolder *dstFolder); + ~CMbxScanner(); + + virtual bool Initialize(void); + virtual bool DoWork(bool *pAbort, uint32_t *pDone, uint32_t *pCount); + + bool WasErrorFatal(void) { return m_fatalError;} + uint32_t BytesProcessed(void) { return m_didBytes;} + +protected: + bool WriteMailItem(uint32_t flags, uint32_t offset, uint32_t size, uint32_t *pTotalMsgSize = nullptr); + virtual void CleanUp(void); + +private: + void ReportWriteError(nsIMsgFolder *folder, bool fatal = true); + void ReportReadError(nsIFile * file, bool fatal = true); + bool CopyMbxFileBytes(uint32_t flags, uint32_t numBytes); + bool IsFromLineKey(uint8_t *pBuf, uint32_t max); + +public: + uint32_t m_msgCount; + +protected: + uint32_t * m_pDone; + nsString m_name; + nsCOMPtr<nsIFile> m_mbxFile; + nsCOMPtr<nsIMsgFolder> m_dstFolder; + nsCOMPtr<nsIInputStream> m_mbxFileInputStream; + nsCOMPtr<nsIOutputStream> m_dstOutputStream; + nsCOMPtr<nsIMsgPluggableStore> m_msgStore; + uint8_t * m_pInBuffer; + uint8_t * m_pOutBuffer; + uint32_t m_bufSz; + uint32_t m_didBytes; + bool m_fatalError; + int64_t m_mbxFileSize; + uint32_t m_mbxOffset; + + static const char * m_pFromLine; + +}; + + +class CIndexScanner : public CMbxScanner { +public: + CIndexScanner(nsString& name, nsIFile * idxFile, nsIFile * mbxFile, nsIMsgFolder *dstFolder); + ~CIndexScanner(); + + virtual bool Initialize(void); + virtual bool DoWork(bool *pAbort, uint32_t *pDone, uint32_t *pCount); + +protected: + virtual void CleanUp(void); + +private: + bool ValidateIdxFile(void); + bool GetMailItem(uint32_t *pFlags, uint32_t *pOffset, uint32_t *pSize); + + +private: + nsCOMPtr <nsIFile> m_idxFile; + nsCOMPtr <nsIInputStream> m_idxFileInputStream; + uint32_t m_numMessages; + uint32_t m_idxOffset; + uint32_t m_curItemIndex; +}; + + +bool CImportMailbox::ImportMailbox(uint32_t *pDone, bool *pAbort, + nsString& name, nsIFile * inFile, + nsIMsgFolder *outFolder, uint32_t *pCount) +{ + bool done = false; + nsresult rv; + nsCOMPtr <nsIFile> idxFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + rv = idxFile->InitWithFile(inFile); + if (NS_FAILED(rv)) { + IMPORT_LOG0("New file spec failed!\n"); + return false; + } + + if (GetIndexFile(idxFile)) { + + IMPORT_LOG1("Using index file for: %S\n", name.get()); + + CIndexScanner *pIdxScanner = new CIndexScanner(name, idxFile, inFile, outFolder); + if (pIdxScanner->Initialize()) { + if (pIdxScanner->DoWork(pAbort, pDone, pCount)) { + done = true; + } + else { + IMPORT_LOG0("CIndexScanner::DoWork() failed\n"); + } + } + else { + IMPORT_LOG0("CIndexScanner::Initialize() failed\n"); + } + + delete pIdxScanner; + } + + if (done) + return done; + + /* + something went wrong with the index file, just scan the mailbox + file itself. + */ + CMbxScanner *pMbx = new CMbxScanner(name, inFile, outFolder); + if (pMbx->Initialize()) { + if (pMbx->DoWork(pAbort, pDone, pCount)) { + done = true; + } + else { + IMPORT_LOG0("CMbxScanner::DoWork() failed\n"); + } + } + else { + IMPORT_LOG0("CMbxScanner::Initialize() failed\n"); + } + + delete pMbx; + return done; +} + + +bool CImportMailbox::GetIndexFile(nsIFile* file) +{ + nsCString pLeaf; + if (NS_FAILED(file->GetNativeLeafName(pLeaf))) + return false; + int32_t len = pLeaf.Length(); + if (len < 5) + return false; + + pLeaf.Replace(len - 3, 3, NS_LITERAL_CSTRING("idx")); + + IMPORT_LOG1("Looking for index leaf name: %s\n", pLeaf); + + nsresult rv; + rv = file->SetNativeLeafName(pLeaf); + + bool isFile = false; + bool exists = false; + if (NS_SUCCEEDED(rv)) rv = file->IsFile(&isFile); + if (NS_SUCCEEDED(rv)) rv = file->Exists(&exists); + + return (isFile && exists); +} + + +const char *CMbxScanner::m_pFromLine = "From - Mon Jan 1 00:00:00 1965\x0D\x0A"; +// let's try a 16K buffer and see how well that works? +#define kBufferKB 16 + + +CMbxScanner::CMbxScanner(nsString& name, nsIFile* mbxFile, + nsIMsgFolder* dstFolder) +{ + m_msgCount = 0; + m_name = name; + m_mbxFile = mbxFile; + m_dstFolder = dstFolder; + m_pInBuffer = nullptr; + m_pOutBuffer = nullptr; + m_bufSz = 0; + m_fatalError = false; + m_didBytes = 0; + m_mbxFileSize = 0; + m_mbxOffset = 0; +} + +CMbxScanner::~CMbxScanner() +{ + CleanUp(); +} + +void CMbxScanner::ReportWriteError(nsIMsgFolder * folder, bool fatal) +{ + m_fatalError = fatal; +} + +void CMbxScanner::ReportReadError(nsIFile * file, bool fatal) +{ + m_fatalError = fatal; +} + +bool CMbxScanner::Initialize(void) +{ + m_bufSz = (kBufferKB * 1024); + m_pInBuffer = new uint8_t[m_bufSz]; + m_pOutBuffer = new uint8_t[m_bufSz]; + if (!m_pInBuffer || !m_pOutBuffer) { + return false; + } + + m_mbxFile->GetFileSize(&m_mbxFileSize); + // open the mailbox file... + if (NS_FAILED(NS_NewLocalFileInputStream(getter_AddRefs(m_mbxFileInputStream), m_mbxFile))) { + CleanUp(); + return false; + } + + if (NS_FAILED(m_dstFolder->GetMsgStore(getter_AddRefs(m_msgStore)))) { + CleanUp(); + return false; + } + + return true; +} + + +#define kMbxHeaderSize 0x0054 +#define kMbxMessageHeaderSz 16 + +bool CMbxScanner::DoWork(bool *pAbort, uint32_t *pDone, uint32_t *pCount) +{ + m_mbxOffset = kMbxHeaderSize; + m_didBytes = kMbxHeaderSize; + + while (!(*pAbort) && ((m_mbxOffset + kMbxMessageHeaderSz) < m_mbxFileSize)) { + uint32_t msgSz; + if (!WriteMailItem(0, m_mbxOffset, 0, &msgSz)) { + if (!WasErrorFatal()) + ReportReadError(m_mbxFile); + return false; + } + m_mbxOffset += msgSz; + m_didBytes += msgSz; + m_msgCount++; + if (pDone) + *pDone = m_didBytes; + if (pCount) + *pCount = m_msgCount; + } + + CleanUp(); + + return true; +} + + +void CMbxScanner::CleanUp(void) +{ + if (m_mbxFileInputStream) + m_mbxFileInputStream->Close(); + if (m_dstOutputStream) + m_dstOutputStream->Close(); + + delete [] m_pInBuffer; + m_pInBuffer = nullptr; + + delete [] m_pOutBuffer; + m_pOutBuffer = nullptr; +} + + +#define kNumMbxLongsToRead 4 + +bool CMbxScanner::WriteMailItem(uint32_t flags, uint32_t offset, uint32_t size, + uint32_t *pTotalMsgSize) +{ + uint32_t values[kNumMbxLongsToRead]; + int32_t cnt = kNumMbxLongsToRead * sizeof(uint32_t); + nsresult rv; + uint32_t cntRead; + int8_t * pChar = (int8_t *) values; + + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(m_mbxFileInputStream); + rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, offset); + + if (NS_FAILED(rv)) { + IMPORT_LOG1("Mbx seek error: 0x%lx\n", offset); + return false; + } + rv = m_mbxFileInputStream->Read((char *) pChar, cnt, &cntRead); + if (NS_FAILED(rv) || (cntRead != cnt)) { + IMPORT_LOG1("Mbx read error at: 0x%lx\n", offset); + return false; + } + if (values[0] != 0x7F007F00) { + IMPORT_LOG2("Mbx tag field doesn't match: 0x%lx, at offset: 0x%lx\n", values[0], offset); + return false; + } + if (size && (values[2] != size)) { + IMPORT_LOG3("Mbx size doesn't match idx, mbx: %ld, idx: %ld, at offset: 0x%lx\n", values[2], size, offset); + return false; + } + + if (pTotalMsgSize != nullptr) + *pTotalMsgSize = values[2]; + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + bool reusable; + + rv = m_msgStore->GetNewMsgOutputStream(m_dstFolder, getter_AddRefs(msgHdr), &reusable, + getter_AddRefs(m_dstOutputStream)); + if (NS_FAILED(rv)) + { + IMPORT_LOG1( "Mbx getting outputstream error: 0x%lx\n", rv); + return false; + } + + // everything looks kosher... + // the actual message text follows and is values[3] bytes long... + bool copyOK = CopyMbxFileBytes(flags, values[3]); + if (copyOK) + m_msgStore->FinishNewMessage(m_dstOutputStream, msgHdr); + else { + m_msgStore->DiscardNewMessage(m_dstOutputStream, msgHdr); + IMPORT_LOG0( "Mbx CopyMbxFileBytes failed\n"); + } + if (!reusable) + { + m_dstOutputStream->Close(); + m_dstOutputStream = nullptr; + } + return copyOK; +} + +bool CMbxScanner::IsFromLineKey(uint8_t * pBuf, uint32_t max) +{ + return (max > 5 && (pBuf[0] == 'F') && (pBuf[1] == 'r') && (pBuf[2] == 'o') && (pBuf[3] == 'm') && (pBuf[4] == ' ')); +} + + +#define IS_ANY_SPACE(_ch) ((_ch == ' ') || (_ch == '\t') || (_ch == 10) || (_ch == 13)) + + +bool CMbxScanner::CopyMbxFileBytes(uint32_t flags, uint32_t numBytes) +{ + if (!numBytes) + return true; + + uint32_t cnt; + uint8_t last[2] = {0, 0}; + uint32_t inIdx = 0; + bool first = true; + uint8_t * pIn; + uint8_t * pStart; + int32_t fromLen = strlen(m_pFromLine); + nsresult rv; + uint32_t cntRead; + uint8_t * pChar; + + while (numBytes) { + if (numBytes > (m_bufSz - inIdx)) + cnt = m_bufSz - inIdx; + else + cnt = numBytes; + // Read some of the message from the file... + pChar = m_pInBuffer + inIdx; + rv = m_mbxFileInputStream->Read((char *) pChar, (int32_t)cnt, &cntRead); + if (NS_FAILED(rv) || (cntRead != (int32_t)cnt)) { + ReportReadError(m_mbxFile); + return false; + } + // Keep track of the last 2 bytes of the message for terminating EOL logic + if (cnt < 2) { + last[0] = last[1]; + last[1] = m_pInBuffer[cnt - 1]; + } + else { + last[0] = m_pInBuffer[cnt - 2]; + last[1] = m_pInBuffer[cnt - 1]; + } + + inIdx = 0; + // Handle the beginning line, don't duplicate an existing From separator + if (first) { + // check the first buffer to see if it already starts with a From line + // If it does, throw it away and use our own + if (IsFromLineKey(m_pInBuffer, cnt)) { + // skip past the first line + while ((inIdx < cnt) && (m_pInBuffer[inIdx] != nsCRT::CR)) + inIdx++; + while ((inIdx < cnt) && (IS_ANY_SPACE(m_pInBuffer[inIdx]))) + inIdx++; + if (inIdx >= cnt) { + // This should not occurr - it means the message starts + // with a From separator line that is longer than our + // file buffer! In this bizarre case, just skip this message + // since it is probably bogus anyway. + return true; + } + + } + // Begin every message with a From separator + rv = m_dstOutputStream->Write(m_pFromLine, fromLen, &cntRead); + if (NS_FAILED(rv) || (cntRead != fromLen)) { + ReportWriteError(m_dstFolder); + return false; + } + char statusLine[50]; + uint32_t msgFlags = flags; // need to convert from OE flags to mozilla flags + PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF); + rv = m_dstOutputStream->Write(statusLine, strlen(statusLine), &cntRead); + if (NS_SUCCEEDED(rv) && cntRead == fromLen) + { + PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000); + rv = m_dstOutputStream->Write(statusLine, strlen(statusLine), &cntRead); + } + if (NS_FAILED(rv) || (cntRead != fromLen)) { + ReportWriteError(m_dstFolder); + return false; + } + first = false; + } + + // Handle generic data, escape any lines that begin with "From " + pIn = m_pInBuffer + inIdx; + numBytes -= cnt; + m_didBytes += cnt; + pStart = pIn; + cnt -= inIdx; + inIdx = 0; + while (cnt) { + if (*pIn == nsCRT::CR) { + // need more in buffer? + if ((cnt < 7) && numBytes) + break; + + if (cnt > 6) { + if ((pIn[1] == nsCRT::LF) && IsFromLineKey(pIn + 2, cnt)) { + inIdx += 2; + // Match, escape it + rv = m_dstOutputStream->Write((const char *)pStart, (int32_t)inIdx, &cntRead); + if (NS_SUCCEEDED(rv) && (cntRead == (int32_t)inIdx)) + rv = m_dstOutputStream->Write(">", 1, &cntRead); + if (NS_FAILED(rv) || (cntRead != 1)) { + ReportWriteError(m_dstFolder); + return false; + } + + cnt -= 2; + pIn += 2; + inIdx = 0; + pStart = pIn; + continue; + } + } + } // == nsCRT::CR + + cnt--; + inIdx++; + pIn++; + } + rv = m_dstOutputStream->Write((const char *)pStart, (int32_t)inIdx, &cntRead); + if (NS_FAILED(rv) || (cntRead != (int32_t)inIdx)) { + ReportWriteError(m_dstFolder); + return false; + } + + if (cnt) { + inIdx = cnt; + memcpy(m_pInBuffer, pIn, cnt); + } + else + inIdx = 0; + } + + // I used to check for an eol before writing one but + // it turns out that adding a proper EOL before the next + // separator never really hurts so better to be safe + // and always do it. + // if ((last[0] != nsCRT::CR) || (last[1] != nsCRT::LF)) { + rv = m_dstOutputStream->Write("\x0D\x0A", 2, &cntRead); + if (NS_FAILED(rv) || (cntRead != 2)) { + ReportWriteError(m_dstFolder); + return false; + } + // } // != nsCRT::CR || != nsCRT::LF + + return true; +} + +CIndexScanner::CIndexScanner(nsString& name, nsIFile * idxFile, + nsIFile * mbxFile, nsIMsgFolder * dstFolder) + : CMbxScanner( name, mbxFile, dstFolder) +{ + m_idxFile = idxFile; + m_curItemIndex = 0; + m_idxOffset = 0; +} + +CIndexScanner::~CIndexScanner() +{ + CleanUp(); +} + +bool CIndexScanner::Initialize(void) +{ + if (!CMbxScanner::Initialize()) + return false; + + + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(m_idxFileInputStream), m_idxFile); + if (NS_FAILED(rv)) { + CleanUp(); + return false; + } + + return true; +} + +bool CIndexScanner::ValidateIdxFile(void) +{ + int8_t id[4]; + int32_t cnt = 4; + nsresult rv; + uint32_t cntRead; + int8_t * pReadTo; + + pReadTo = id; + rv = m_idxFileInputStream->Read((char *) pReadTo, cnt, &cntRead); + if (NS_FAILED(rv) || (cntRead != cnt)) + return false; + if ((id[0] != 'J') || (id[1] != 'M') || (id[2] != 'F') || (id[3] != '9')) + return false; + cnt = 4; + uint32_t subId; + pReadTo = (int8_t *) &subId; + rv = m_idxFileInputStream->Read((char *) pReadTo, cnt, &cntRead); + if (NS_FAILED(rv) || (cntRead != cnt)) + return false; + if (subId != 0x00010004) { + IMPORT_LOG1("Idx file subid doesn't match: 0x%lx\n", subId); + return false; + } + + pReadTo = (int8_t *) &m_numMessages; + rv = m_idxFileInputStream->Read((char *) pReadTo, cnt, &cntRead); + if (NS_FAILED(rv) || (cntRead != cnt)) + return false; + + IMPORT_LOG1("Idx file num messages: %ld\n", m_numMessages); + + m_didBytes += 80; + m_idxOffset = 80; + return true; +} + +/* +Idx file... +Header is 80 bytes, JMF9, subId? 0x00010004, numMessages, fileSize, 1, 0x00010010 +Entries start at byte 80 +4 byte numbers +Flags? maybe +?? who knows +index +start of this entry in the file +length of this record +msg offset in mbx +msg length in mbx + +*/ + +// #define DEBUG_SUBJECT_AND_FLAGS 1 +#define kNumIdxLongsToRead 7 + +bool CIndexScanner::GetMailItem(uint32_t *pFlags, uint32_t *pOffset, uint32_t *pSize) +{ + uint32_t values[kNumIdxLongsToRead]; + int32_t cnt = kNumIdxLongsToRead * sizeof(uint32_t); + int8_t * pReadTo = (int8_t *) values; + uint32_t cntRead; + nsresult rv; + + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(m_idxFileInputStream); + rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, m_idxOffset); + if (NS_FAILED(rv)) + return false; + + rv = m_idxFileInputStream->Read((char *) pReadTo, cnt, &cntRead); + if (NS_FAILED(rv) || (cntRead != cnt)) + return false; + + if (values[3] != m_idxOffset) { + IMPORT_LOG2("Self pointer invalid: m_idxOffset=0x%lx, self=0x%lx\n", m_idxOffset, values[3]); + return false; + } + + // So... what do we have here??? +#ifdef DEBUG_SUBJECT_AND_FLAGS + IMPORT_LOG2("Number: %ld, msg offset: 0x%lx, ", values[2], values[5]); + IMPORT_LOG2("msg length: %ld, Flags: 0x%lx\n", values[6], values[0]); + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, m_idxOffset + 212); + uint32_t subSz = 0; + cnt = 4; + pReadTo = (int8_t *) &subSz; + m_idxFileInputStream->Read((char *) pReadTo, cnt, &cntRead); + if ((subSz >= 0) && (subSz < 1024)) { + char *pSub = new char[subSz + 1]; + m_idxFileInputStream->Read(pSub, subSz, &cntRead); + pSub[subSz] = 0; + IMPORT_LOG1(" Subject: %s\n", pSub); + delete [] pSub; + } +#endif + + m_idxOffset += values[4]; + m_didBytes += values[4]; + + *pFlags = values[0]; + *pOffset = values[5]; + *pSize = values[6]; + return true; +} + +#define kOEDeletedFlag 0x0001 + +bool CIndexScanner::DoWork(bool *pAbort, uint32_t *pDone, uint32_t *pCount) +{ + m_didBytes = 0; + if (!ValidateIdxFile()) + return false; + + bool failed = false; + while ((m_curItemIndex < m_numMessages) && !failed && !(*pAbort)) { + uint32_t flags, offset, size; + if (!GetMailItem(&flags, &offset, &size)) { + CleanUp(); + return false; + } + m_curItemIndex++; + if (!(flags & kOEDeletedFlag)) { + if (!WriteMailItem(flags, offset, size)) + failed = true; + else { + m_msgCount++; + } + } + m_didBytes += size; + if (pDone) + *pDone = m_didBytes; + if (pCount) + *pCount = m_msgCount; + } + + CleanUp(); + return !failed; +} + + +void CIndexScanner::CleanUp(void) +{ + CMbxScanner::CleanUp(); + m_idxFileInputStream->Close(); +} diff --git a/mailnews/import/oexpress/nsOEMailbox.h b/mailnews/import/oexpress/nsOEMailbox.h new file mode 100644 index 000000000..656313aad --- /dev/null +++ b/mailnews/import/oexpress/nsOEMailbox.h @@ -0,0 +1,27 @@ +/* -*- 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 nsOEMailbox_h___ +#define nsOEMailbox_h___ + +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsIFile.h" + +class nsIMsgFolder; + +class CImportMailbox { +public: + static bool ImportMailbox(uint32_t *pDone, bool *pAbort, nsString& name, + nsIFile * inFile, nsIMsgFolder * outFolder, + uint32_t *pCount); + +private: + static bool GetIndexFile(nsIFile* mbxFile); +}; + + + +#endif // nsOEMailbox_h__ diff --git a/mailnews/import/oexpress/nsOERegUtil.cpp b/mailnews/import/oexpress/nsOERegUtil.cpp new file mode 100644 index 000000000..e3aa9d647 --- /dev/null +++ b/mailnews/import/oexpress/nsOERegUtil.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsOERegUtil.h" + +#include "OEDebugLog.h" +#include <windows.h> +#include "nsIWindowsRegKey.h" +#include "nsComponentManagerUtils.h" + +nsresult nsOERegUtil::GetDefaultUserId(nsAString &aUserId) +{ + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("Identities"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) + return rv; + + return key->ReadStringValue(NS_LITERAL_STRING("Default User ID"), aUserId); +} + diff --git a/mailnews/import/oexpress/nsOERegUtil.h b/mailnews/import/oexpress/nsOERegUtil.h new file mode 100644 index 000000000..9a873d63b --- /dev/null +++ b/mailnews/import/oexpress/nsOERegUtil.h @@ -0,0 +1,20 @@ +/* -*- 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 nsOERegUtil_h___ +#define nsOERegUtil_h___ + +#include <windows.h> +#include "nsStringGlue.h" + +class nsOERegUtil +{ +public: + static nsresult GetDefaultUserId(nsAString &aUserId); +}; + + + +#endif /* nsOERegUtil_h___ */ diff --git a/mailnews/import/oexpress/nsOEScanBoxes.cpp b/mailnews/import/oexpress/nsOEScanBoxes.cpp new file mode 100644 index 000000000..7c30aa68d --- /dev/null +++ b/mailnews/import/oexpress/nsOEScanBoxes.cpp @@ -0,0 +1,859 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsOEScanBoxes.h" +#include "nsMsgUtils.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIImportService.h" +#include "nsIFile.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsOERegUtil.h" +#include "nsOE5File.h" +#include "nsNetUtil.h" +#include "OEDebugLog.h" +#include "nsIInputStream.h" +#include "nsISeekableStream.h" +#include "plstr.h" +#include <windows.h> +#include "nsIWindowsRegKey.h" + +#ifdef MOZILLA_INTERNAL_API +#include "nsNativeCharsetUtils.h" +#else +#include "nsMsgI18N.h" +#define NS_CopyNativeToUnicode(source, dest) \ + nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), source, dest) +#define NS_CopyUnicodeToNative(source, dest) \ + nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(), source, dest) +#endif + +/* + .nch file format??? + + offset 20 - long = offset to first record + +*/ + +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); + +nsOEScanBoxes::nsOEScanBoxes() +{ + m_pFirst = nullptr; +} + +nsOEScanBoxes::~nsOEScanBoxes() +{ + int i, max; + MailboxEntry *pEntry; + for (i = 0, max = m_entryArray.Length(); i < max; i++) { + pEntry = m_entryArray.ElementAt(i); + delete pEntry; + } + // Now free the unprocessed child entries (ie, those without parents for some reason). + for (i = 0, max = m_pendingChildArray.Length(); i < max; i++) + { + pEntry = m_pendingChildArray.ElementAt(i); + if (!pEntry->processed) + delete pEntry; + } +} + +/* + 3.x & 4.x registry + Software/Microsoft/Outlook Express/ + + 5.0 registry + Identies - value of "Default User ID" is {GUID} + Identities/{GUID}/Software/Microsoft/Outlook Express/5.0/ +*/ + +bool nsOEScanBoxes::Find50Mail(nsIFile *pWhere) +{ + nsAutoString userId; + nsresult rv = nsOERegUtil::GetDefaultUserId(userId); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoString path(NS_LITERAL_STRING("Identities\\")); + path.Append(userId); + path.AppendLiteral("\\Software\\Microsoft\\Outlook Express\\5.0"); + + nsCOMPtr<nsIWindowsRegKey> key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, false); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + path, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoString storeRoot; + key->ReadStringValue(NS_LITERAL_STRING("Store Root"), storeRoot); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr<nsIFile> localWhere = do_QueryInterface(pWhere); + localWhere->InitWithPath(storeRoot); + + nsAutoCString nativeStoreRoot; + NS_CopyUnicodeToNative(storeRoot, nativeStoreRoot); + IMPORT_LOG1("Setting native path: %s\n", nativeStoreRoot.get()); + + bool isDir = false; + rv = localWhere->IsDirectory(&isDir); + return isDir; +} + +bool nsOEScanBoxes::FindMail(nsIFile *pWhere) +{ + if (Find50Mail(pWhere)) + return true; + + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, false); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("Software\\Microsoft\\Outlook Express"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoString storeRoot; + key->ReadStringValue(NS_LITERAL_STRING("Store Root"), storeRoot); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr<nsIFile> localWhere = do_QueryInterface(pWhere); + localWhere->InitWithPath(storeRoot); + localWhere->AppendNative(NS_LITERAL_CSTRING("Mail")); + + bool isDir = false; + localWhere->IsDirectory(&isDir); + + return isDir; +} + +bool nsOEScanBoxes::GetMailboxes(nsIFile *pWhere, nsIArray **pArray) +{ + nsCString path; + pWhere->GetNativePath(path); + if (!path.IsEmpty()) { + IMPORT_LOG1("Looking for mail in: %s\n", path.get()); + } + else { + pWhere->GetNativeLeafName(path); + if (!path.IsEmpty()) + IMPORT_LOG1("Looking for mail in: %s\n", path.get()); + else + IMPORT_LOG0("Unable to get info about where to look for mail\n"); + } + + nsCOMPtr <nsIFile> location; + pWhere->Clone(getter_AddRefs(location)); + // 1. Look for 5.0 folders.dbx + // 2. Look for 3.x & 4.x folders.nch + // 3. Look for 5.0 *.dbx mailboxes + // 4. Look for 3.x & 4.x *.mbx mailboxes + + bool result; + + location->AppendNative(NS_LITERAL_CSTRING("folders.dbx")); + if (Find50MailBoxes(location)) { + result = GetMailboxList(pWhere, pArray); + } + else { + // 2. Look for 4.x mailboxes + pWhere->Clone(getter_AddRefs(location)); + location->AppendNative(NS_LITERAL_CSTRING("folders.nch")); + + if (FindMailBoxes(location)) { + result = GetMailboxList(pWhere, pArray); + } + else { + // 3 & 4, look for the specific mailbox files. + pWhere->Clone(getter_AddRefs(location)); + ScanMailboxDir(location); + result = GetMailboxList(pWhere, pArray); + } + } + + return result; +} + + + +void nsOEScanBoxes::Reset(void) +{ + int max = m_entryArray.Length(); + for (int i = 0; i < max; i++) { + MailboxEntry *pEntry = m_entryArray.ElementAt(i); + delete pEntry; + } + m_entryArray.Clear(); + m_pFirst = nullptr; +} + + +bool nsOEScanBoxes::FindMailBoxes(nsIFile* descFile) +{ + Reset(); + + nsresult rv; + bool isFile = false; + + rv = descFile->IsFile(&isFile); + if (NS_FAILED(rv) || !isFile) + return false; + + nsCOMPtr <nsIInputStream> descInputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(descInputStream), descFile); + NS_ENSURE_SUCCESS(rv, false); + + IMPORT_LOG0("Reading the folders.nch file\n"); + + uint32_t curRec; + if (!ReadLong(descInputStream, curRec, 20)) { + return false; + } + + // Now for each record + bool done = false; + uint32_t equal; + uint32_t size; + uint32_t previous; + uint32_t next; + MailboxEntry * pEntry; + bool failed; + + while (!done) { + if (!ReadLong(descInputStream, equal, curRec)) return false; + if (curRec != equal) { + IMPORT_LOG1("Record start invalid: %ld\n", curRec); + break; + } + if (!ReadLong(descInputStream, size, curRec + 4)) return false; + if (!ReadLong(descInputStream, previous, curRec + 8)) return false; + if (!ReadLong(descInputStream, next, curRec + 12)) return false; + failed = false; + pEntry = new MailboxEntry; + if (!ReadLong(descInputStream, pEntry->index, curRec + 16)) failed = true; + if (!ReadString(descInputStream, pEntry->mailName, curRec + 20)) failed = true; + if (!ReadString(descInputStream, pEntry->fileName, curRec + 279)) failed = true; + if (!ReadLong(descInputStream, pEntry->parent, curRec + 539)) failed = true; + if (!ReadLong(descInputStream, pEntry->child, curRec + 543)) failed = true; + if (!ReadLong(descInputStream, pEntry->sibling, curRec + 547)) failed = true; + if (!ReadLong(descInputStream, pEntry->type, curRec + 551)) failed = true; + if (failed) { + delete pEntry; + return false; + } + + #ifdef _TRACE_MAILBOX_ENTRIES + IMPORT_LOG0("------------\n"); + IMPORT_LOG2(" Offset: %lx, index: %ld\n", curRec, pEntry->index); + IMPORT_LOG2(" previous: %lx, next: %lx\n", previous, next); + IMPORT_LOG2(" Name: %S, File: %s\n", (char16_t *) pEntry->mailName, (const char *) pEntry->fileName); + IMPORT_LOG3(" Parent: %ld, Child: %ld, Sibling: %ld\n", pEntry->parent, pEntry->child, pEntry->sibling); + #endif + + if (!StringEndsWith(pEntry->fileName, NS_LITERAL_CSTRING(".mbx"))) + pEntry->fileName.Append(".mbx"); + + m_entryArray.AppendElement(pEntry); + + curRec = next; + if (!next) + done = true; + } + + MailboxEntry *pZero = GetIndexEntry(0); + if (pZero) + m_pFirst = GetIndexEntry(pZero->child); + + IMPORT_LOG1("Read the folders.nch file, found %ld mailboxes\n", (long) m_entryArray.Length()); + + return true; +} + +bool nsOEScanBoxes::Find50MailBoxes(nsIFile* descFile) +{ + Reset(); + + nsresult rv; + bool isFile = false; + + rv = descFile->IsFile(&isFile); + if (NS_FAILED(rv) || !isFile) + return false; + + nsCOMPtr <nsIInputStream> descInputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(descInputStream), descFile); + NS_ENSURE_SUCCESS(rv, false); + + IMPORT_LOG0("Reading the folders.dbx file\n"); + + uint32_t *pIndex; + uint32_t indexSize = 0; + if (!nsOE5File::ReadIndex(descInputStream, &pIndex, &indexSize)) { + IMPORT_LOG0("*** NOT USING FOLDERS.DBX!!!\n"); + return false; + } + + uint32_t marker; + uint32_t size; + char *pBytes; + uint32_t cntRead; + int32_t recordId; + int32_t strOffset; + + uint8_t tag; + uint32_t data; + int32_t dataOffset; + + uint32_t id; + uint32_t parent; + uint32_t numMessages; + char * pFileName; + char * pDataSource; + + MailboxEntry * pEntry; + MailboxEntry * pLastEntry = nullptr; + + uint32_t localStoreId = 0; + + for (uint32_t i = 0; i < indexSize; i++) { + if (!ReadLong(descInputStream, marker, pIndex[i])) continue; + if (marker != pIndex[i]) continue; + if (!ReadLong(descInputStream, size, pIndex[i] + 4)) continue; + size += 4; + pBytes = new char[size]; + rv = descInputStream->Read(pBytes, size, &cntRead); + if (NS_FAILED(rv) || ((uint32_t)cntRead != size)) { + delete [] pBytes; + continue; + } + recordId = pBytes[2]; + strOffset = (recordId * 4) + 4; + if (recordId == 4) + strOffset += 4; + + id = 0; + parent = 0; + numMessages = 0; + pFileName = nullptr; + pDataSource = nullptr; + dataOffset = 4; + while (dataOffset < strOffset) { + tag = (uint8_t) pBytes[dataOffset]; + + data = 0; // make sure all bytes are 0 before copying 3 bytes over. + memcpy(&data, &(pBytes[dataOffset + 1]), 3); + switch(tag) { + case 0x80: // id record + id = data; + break; + case 0x81: // parent id + parent = data; + break; + case 0x87: // number of messages in this mailbox + numMessages = data; + break; + case 0x03: // file name for this mailbox + if (((uint32_t)strOffset + data) < size) + pFileName = (char *)(pBytes + strOffset + data); + break; + case 0x05: // data source for this record (this is not a mailbox!) + if (((uint32_t)strOffset + data) < size) + pDataSource = (char *) (pBytes + strOffset + data); + break; + } + dataOffset += 4; + } + + // now build an entry if necessary! + if (pDataSource) { + if (!PL_strcasecmp(pDataSource, "LocalStore")) + { + localStoreId = id; + // See if we have any child folders that need to be added/processed for this top level parent. + ProcessPendingChildEntries(localStoreId, localStoreId, m_pendingChildArray); + // Clean up the pending list. + RemoveProcessedChildEntries(); + } + } + else if (id && localStoreId && parent) { + // veryify that this mailbox is in the local store + data = parent; + while (data && (data != localStoreId)) { + pEntry = GetIndexEntry(data); + if (pEntry) + data = pEntry->parent; + else + data = 0; + } + if (data == localStoreId) { + // Create an entry for this bugger + pEntry = NewMailboxEntry(id, parent, (const char *) (pBytes + strOffset), pFileName); + if (pEntry) + { + AddChildEntry(pEntry, localStoreId); + pEntry->processed = true; + // See if we have any child folders that need to be added/processed. + ProcessPendingChildEntries(id, localStoreId, m_pendingChildArray); + // Clean up the pending list. + RemoveProcessedChildEntries(); + } + } + else + { + // Put this folder into child array and process it when its parent shows up. + pEntry = NewMailboxEntry(id, parent, (const char *) (pBytes + strOffset), pFileName); + if (pEntry) + m_pendingChildArray.AppendElement(pEntry); + } + } + else if (pFileName) + { + // Put this folder into child array and process it when its parent shows up. + // For some reason, it's likely that child folders come before their parents. + pEntry = NewMailboxEntry(id, parent, (const char *) (pBytes + strOffset), pFileName); + if (pEntry) + m_pendingChildArray.AppendElement(pEntry); + } + + delete [] pBytes; + } + + + delete [] pIndex; + + return m_entryArray.Length(); +} + +nsOEScanBoxes::MailboxEntry *nsOEScanBoxes::NewMailboxEntry(uint32_t id, uint32_t parent, const char *prettyName, char *pFileName) +{ + MailboxEntry *pEntry = new MailboxEntry(); + if (!pEntry) + return nullptr; + + pEntry->index = id; + pEntry->parent = parent; + pEntry->child = 0; + pEntry->type = 0; + pEntry->sibling = -1; + pEntry->processed = false; + NS_CopyNativeToUnicode(nsDependentCString(prettyName), pEntry->mailName); + if (pFileName) + pEntry->fileName = pFileName; + return pEntry; +} + +void nsOEScanBoxes::ProcessPendingChildEntries(uint32_t parent, uint32_t rootIndex, nsTArray<MailboxEntry*> &childArray) +{ + int32_t i, max; + MailboxEntry *pEntry; + for (i = 0, max = childArray.Length(); i < max; i++) + { + pEntry = childArray.ElementAt(i); + if ((!pEntry->processed) && (pEntry->parent == parent)) + { + AddChildEntry(pEntry, rootIndex); + pEntry->processed = true; // indicate it's been processed. + // See if there are unprocessed child folders for this child in the + // array as well (ie, both child and grand-child are on the list). + ProcessPendingChildEntries(pEntry->index, rootIndex, childArray); + } + } +} + +void nsOEScanBoxes::RemoveProcessedChildEntries() +{ + // Remove already processed entries from the pending list. Note that these entries are also + // on 'm_entryArray' list so we don't want to deallocate the space for the entries now. + MailboxEntry * pEntry; + int32_t i; + for (i = m_pendingChildArray.Length()-1; i >= 0; i--) + { + pEntry = m_pendingChildArray.ElementAt(i); + if (pEntry->processed) + m_pendingChildArray.RemoveElementAt(i); + } +} + +void nsOEScanBoxes::AddChildEntry(MailboxEntry *pEntry, uint32_t rootIndex) +{ + if (!m_pFirst) { + if (pEntry->parent == rootIndex) { + m_pFirst = pEntry; + m_entryArray.AppendElement(pEntry); + } + else { + delete pEntry; + } + return; + } + + MailboxEntry * pParent = nullptr; + MailboxEntry * pSibling = nullptr; + if (pEntry->parent == rootIndex) { + pSibling = m_pFirst; + } + else { + pParent = GetIndexEntry(pEntry->parent); + } + + if (!pParent && !pSibling) { + delete pEntry; + return; + } + + if (pParent && (pParent->child == 0)) { + pParent->child = pEntry->index; + m_entryArray.AppendElement(pEntry); + return; + } + + if (!pSibling) + pSibling = GetIndexEntry(pParent->child); + + while (pSibling && (pSibling->sibling != -1)) { + pSibling = GetIndexEntry(pSibling->sibling); + } + + if (!pSibling) { + delete pEntry; + return; + } + + pSibling->sibling = pEntry->index; + m_entryArray.AppendElement(pEntry); +} + +bool nsOEScanBoxes::Scan50MailboxDir(nsIFile * srcDir) +{ + Reset(); + + MailboxEntry *pEntry; + int32_t index = 1; + char *pLeaf; + + bool hasMore; + nsCOMPtr<nsISimpleEnumerator> directoryEnumerator; + nsresult rv = srcDir->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + NS_ENSURE_SUCCESS(rv, false); + + directoryEnumerator->HasMoreElements(&hasMore); + bool isFile; + nsCOMPtr<nsIFile> entry; + nsCString fName; + + while (hasMore && NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsISupports> aSupport; + rv = directoryEnumerator->GetNext(getter_AddRefs(aSupport)); + nsCOMPtr<nsIFile> entry(do_QueryInterface(aSupport, &rv)); + directoryEnumerator->HasMoreElements(&hasMore); + + isFile = false; + rv = entry->IsFile(&isFile); + if (NS_SUCCEEDED(rv) && isFile) { + pLeaf = nullptr; + rv = entry->GetNativeLeafName(fName); + if (NS_SUCCEEDED(rv) && + (StringEndsWith(fName, NS_LITERAL_CSTRING(".dbx")))) { + // This is a *.dbx file in the mail directory + if (nsOE5File::IsLocalMailFile(entry)) { + pEntry = new MailboxEntry; + pEntry->index = index; + index++; + pEntry->parent = 0; + pEntry->child = 0; + pEntry->sibling = index; + pEntry->type = -1; + fName.SetLength(fName.Length() - 4); + pEntry->fileName = fName.get(); + NS_CopyNativeToUnicode(fName, pEntry->mailName); + m_entryArray.AppendElement(pEntry); + } + } + } + } + + if (m_entryArray.Length() > 0) { + pEntry = m_entryArray.ElementAt(m_entryArray.Length() - 1); + pEntry->sibling = -1; + return true; + } + + return false; +} + +void nsOEScanBoxes::ScanMailboxDir(nsIFile * srcDir) +{ + if (Scan50MailboxDir(srcDir)) + return; + + Reset(); + + MailboxEntry * pEntry; + int32_t index = 1; + nsAutoCString pLeaf; + uint32_t sLen; + + bool hasMore; + nsCOMPtr<nsISimpleEnumerator> directoryEnumerator; + nsresult rv = srcDir->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + NS_ENSURE_SUCCESS_VOID(rv); + + directoryEnumerator->HasMoreElements(&hasMore); + bool isFile; + nsCOMPtr<nsIFile> entry; + nsCString fName; + nsCString ext; + nsCString name; + + while (hasMore && NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsISupports> aSupport; + rv = directoryEnumerator->GetNext(getter_AddRefs(aSupport)); + nsCOMPtr<nsIFile> entry(do_QueryInterface(aSupport, &rv)); + directoryEnumerator->HasMoreElements(&hasMore); + + isFile = false; + rv = entry->IsFile(&isFile); + if (NS_SUCCEEDED(rv) && isFile) + { + rv = entry->GetNativeLeafName(pLeaf); + if (NS_SUCCEEDED(rv) && !pLeaf.IsEmpty() && + ((sLen = pLeaf.Length()) > 4) && + (!PL_strcasecmp(pLeaf.get() + sLen - 3, "mbx"))) + { + // This is a *.mbx file in the mail directory + pEntry = new MailboxEntry; + pEntry->index = index; + index++; + pEntry->parent = 0; + pEntry->child = 0; + pEntry->sibling = index; + pEntry->type = -1; + pEntry->fileName = pLeaf; + pLeaf.SetLength(sLen - 4); + NS_CopyNativeToUnicode(pLeaf, pEntry->mailName); + m_entryArray.AppendElement(pEntry); + } + } + } + + if (m_entryArray.Length() > 0) { + pEntry = m_entryArray.ElementAt(m_entryArray.Length() - 1); + pEntry->sibling = -1; + } +} + +uint32_t nsOEScanBoxes::CountMailboxes(MailboxEntry *pBox) +{ + if (pBox == nullptr) { + if (m_pFirst != nullptr) + pBox = m_pFirst; + else { + if (m_entryArray.Length() > 0) + pBox = m_entryArray.ElementAt(0); + } + } + uint32_t count = 0; + + MailboxEntry * pChild; + while (pBox) { + count++; + if (pBox->child) { + pChild = GetIndexEntry(pBox->child); + if (pChild != nullptr) + count += CountMailboxes(pChild); + } + if (pBox->sibling != -1) { + pBox = GetIndexEntry(pBox->sibling); + } + else + pBox = nullptr; + } + + return count; +} + +bool nsOEScanBoxes::GetMailboxList(nsIFile * root, nsIArray **pArray) +{ + nsresult rv; + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("FAILED to allocate the nsIArray\n"); + return false; + } + + BuildMailboxList(nullptr, root, 1, array); + array.forget(pArray); + + return true; +} + +void nsOEScanBoxes::BuildMailboxList(MailboxEntry *pBox, nsIFile * root, int32_t depth, nsIMutableArray *pArray) +{ + if (pBox == nullptr) { + if (m_pFirst != nullptr) { + pBox = m_pFirst; + + IMPORT_LOG0("Assigning start of mailbox list to m_pFirst\n"); + } + else { + if (m_entryArray.Length() > 0) { + pBox = m_entryArray.ElementAt(0); + + IMPORT_LOG0("Assigning start of mailbox list to entry at index 0\n"); + } + } + + if (pBox == nullptr) { + IMPORT_LOG0("ERROR ASSIGNING STARTING MAILBOX\n"); + } + + } + + nsresult rv; + nsCOMPtr<nsIFile> file; + MailboxEntry *pChild; + nsIImportMailboxDescriptor *pID; + nsISupports *pInterface; + int64_t size; + + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + + while (pBox) { + rv = impSvc->CreateNewMailboxDescriptor(&pID); + if (NS_SUCCEEDED(rv)) { + pID->SetDepth(depth); + pID->SetIdentifier(pBox->index); + pID->SetDisplayName((char16_t *)pBox->mailName.get()); + if (!pBox->fileName.IsEmpty()) { + pID->GetFile(getter_AddRefs(file)); + file->InitWithFile(root); + file->AppendNative(pBox->fileName); + size = 0; + file->GetFileSize(&size); + pID->SetSize(size); + } + rv = pID->QueryInterface(kISupportsIID, (void **) &pInterface); + pArray->AppendElement(pInterface, false); + pInterface->Release(); + pID->Release(); + } + + if (pBox->child) { + pChild = GetIndexEntry(pBox->child); + if (pChild != nullptr) + BuildMailboxList(pChild, root, depth + 1, pArray); + } + if (pBox->sibling != -1) { + pBox = GetIndexEntry(pBox->sibling); + } + else + pBox = nullptr; + } + +} + +nsOEScanBoxes::MailboxEntry * nsOEScanBoxes::GetIndexEntry(uint32_t index) +{ + int32_t max = m_entryArray.Length(); + for (int32_t i = 0; i < max; i++) { + MailboxEntry *pEntry = m_entryArray.ElementAt(i); + if (pEntry->index == index) + return pEntry; + } + + return nullptr; +} + + +// ------------------------------------------------------- +// File utility routines +// ------------------------------------------------------- + +bool nsOEScanBoxes::ReadLong(nsIInputStream * stream, int32_t& val, uint32_t offset) +{ + nsresult rv; + nsCOMPtr <nsISeekableStream> seekStream = do_QueryInterface(stream, &rv); + NS_ENSURE_SUCCESS(rv, false); + + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, offset); + NS_ENSURE_SUCCESS(rv, false); + + uint32_t cntRead; + char * pReadTo = (char *)&val; + rv = stream->Read(pReadTo, sizeof(val), &cntRead); + + return NS_SUCCEEDED(rv) && cntRead == sizeof(val); +} + +bool nsOEScanBoxes::ReadLong(nsIInputStream * stream, uint32_t& val, uint32_t offset) +{ + nsresult rv; + nsCOMPtr <nsISeekableStream> seekStream = do_QueryInterface(stream, &rv); + NS_ENSURE_SUCCESS(rv, false); + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, offset); + NS_ENSURE_SUCCESS(rv, false); + + uint32_t cntRead; + char * pReadTo = (char *)&val; + rv = stream->Read(pReadTo, sizeof(val), &cntRead); + if (NS_FAILED(rv) || (cntRead != sizeof(val))) + return false; + + return true; +} + +// It appears as though the strings for file name and mailbox +// name are at least 254 chars - verified - they are probably 255 +// but why bother going that far! If a file name is that long then +// the heck with it. +#define kOutlookExpressStringLength 252 +bool nsOEScanBoxes::ReadString(nsIInputStream * stream, nsString& str, uint32_t offset) +{ + nsresult rv; + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(stream, &rv); + NS_ENSURE_SUCCESS(rv, false); + + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, offset); + NS_ENSURE_SUCCESS(rv, false); + + uint32_t cntRead; + char buffer[kOutlookExpressStringLength]; + char *pReadTo = buffer; + rv = stream->Read(pReadTo, kOutlookExpressStringLength, &cntRead); + if (NS_FAILED(rv) || (cntRead != kOutlookExpressStringLength)) + return false; + + buffer[kOutlookExpressStringLength - 1] = 0; + str.AssignASCII(buffer); + return true; +} + +bool nsOEScanBoxes::ReadString(nsIInputStream * stream, nsCString& str, uint32_t offset) +{ + nsresult rv; + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(stream, &rv); + NS_ENSURE_SUCCESS(rv, false); + + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, offset); + NS_ENSURE_SUCCESS(rv, false); + + uint32_t cntRead; + char buffer[kOutlookExpressStringLength]; + char *pReadTo = buffer; + rv = stream->Read(pReadTo, kOutlookExpressStringLength, &cntRead); + if (NS_FAILED(rv) || (cntRead != kOutlookExpressStringLength)) + return false; + + buffer[kOutlookExpressStringLength - 1] = 0; + str = buffer; + return true; +} diff --git a/mailnews/import/oexpress/nsOEScanBoxes.h b/mailnews/import/oexpress/nsOEScanBoxes.h new file mode 100644 index 000000000..e2e62b238 --- /dev/null +++ b/mailnews/import/oexpress/nsOEScanBoxes.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsOEScanBoxes_h___ +#define nsOEScanBoxes_h___ + +#include "nsStringGlue.h" +#include "nsIImportModule.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsIArray.h" +#include "nsIMutableArray.h" +#include "nsIFile.h" +#include "nsIImportService.h" + +class nsIInputStream; + +class nsOEScanBoxes { +public: + nsOEScanBoxes(); + ~nsOEScanBoxes(); + + static bool FindMail(nsIFile *pWhere); + + bool GetMailboxes(nsIFile *pWhere, nsIArray **pArray); + + +private: + typedef struct { + uint32_t index; + uint32_t parent; + int32_t child; + int32_t sibling; + int32_t type; + nsString mailName; + nsCString fileName; + bool processed; // used by entries on m_pendingChildArray list + } MailboxEntry; + + static bool Find50Mail(nsIFile *pWhere); + + void Reset(void); + bool FindMailBoxes(nsIFile * descFile); + bool Find50MailBoxes(nsIFile * descFile); + + // If find mailboxes fails you can use this routine to get the raw mailbox file names + void ScanMailboxDir(nsIFile * srcDir); + bool Scan50MailboxDir(nsIFile * srcDir); + + MailboxEntry * GetIndexEntry(uint32_t index); + void AddChildEntry(MailboxEntry *pEntry, uint32_t rootIndex); + MailboxEntry * NewMailboxEntry(uint32_t id, uint32_t parent, const char *prettyName, char *pFileName); + void ProcessPendingChildEntries(uint32_t parent, uint32_t rootIndex, nsTArray<MailboxEntry*> &childArray); + void RemoveProcessedChildEntries(); + + + bool ReadLong(nsIInputStream * stream, int32_t& val, uint32_t offset); + bool ReadLong(nsIInputStream * stream, uint32_t& val, uint32_t offset); + bool ReadString(nsIInputStream * stream, nsString& str, uint32_t offset); + bool ReadString(nsIInputStream * stream, nsCString& str, uint32_t offset); + uint32_t CountMailboxes(MailboxEntry *pBox); + + void BuildMailboxList(MailboxEntry *pBox, nsIFile * root, int32_t depth, nsIMutableArray *pArray); + bool GetMailboxList(nsIFile * root, nsIArray **pArray); + +private: + MailboxEntry * m_pFirst; + nsTArray<MailboxEntry*> m_entryArray; + nsTArray<MailboxEntry*> m_pendingChildArray; // contains child folders whose parent folders have not showed up. + + nsCOMPtr<nsIImportService> mService; +}; + +#endif // nsOEScanBoxes_h__ diff --git a/mailnews/import/oexpress/nsOESettings.cpp b/mailnews/import/oexpress/nsOESettings.cpp new file mode 100644 index 000000000..600acf74c --- /dev/null +++ b/mailnews/import/oexpress/nsOESettings.cpp @@ -0,0 +1,921 @@ +/* -*- 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/. */ + +/* + + Outlook Express (Win32) settings + +*/ + +#include "nsOESettings.h" +#include "mozilla/WindowsVersion.h" +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsMsgUtils.h" +#include "nsOEImport.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsOERegUtil.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAccount.h" +#include "nsIImportSettings.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsMsgI18N.h" +#include "nsISmtpService.h" +#include "nsISmtpServer.h" +#include "nsOEStringBundle.h" +#include "OEDebugLog.h" +#include "nsIPop3IncomingServer.h" +#include "nsIImapIncomingServer.h" +#include "nsINntpIncomingServer.h" +#include "stdlib.h" +#include <windows.h> +#include "nsIWindowsRegKey.h" +#include "nsComponentManagerUtils.h" + +#ifdef MOZILLA_INTERNAL_API +#include "nsNativeCharsetUtils.h" +#else +#include "nsMsgI18N.h" +#define NS_CopyNativeToUnicode(source, dest) \ + nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), source, dest) +#define NS_CopyUnicodeToNative(source, dest) \ + nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(), source, dest) +#endif + +class OESettings { +public: + static nsresult GetDefaultMailAccount(nsAString &aMailAccount); + static nsresult GetCheckMailInterval(uint32_t *aInterval); + static nsresult Find50Key(nsIWindowsRegKey **aKey); + static nsresult Find40Key(nsIWindowsRegKey **aKey); + static nsresult FindAccountsKey(nsIWindowsRegKey **aKey); + + static bool DoImport(nsIMsgAccount **ppAccount); + + static bool DoIMAPServer(nsIMsgAccountManager *aMgr, + nsIWindowsRegKey *aKey, + const nsString &aServerName, + nsIMsgAccount **ppAccount); + static bool DoPOP3Server(nsIMsgAccountManager *aMgr, + nsIWindowsRegKey *aKey, + const nsString &aServerName, + nsIMsgAccount **ppAccount); + static bool DoNNTPServer(nsIMsgAccountManager *aMgr, + nsIWindowsRegKey *aKey, + const nsString &aServerName, + nsIMsgAccount **ppAccount); + + static void SetIncomingServerProperties(nsIMsgIncomingServer *aServer, + nsIWindowsRegKey *aKey, + const nsString &aKeyNamePrefix); + + static void SetIdentities(nsIMsgAccountManager *aMgr, + nsIMsgAccount *aAccount, + nsIWindowsRegKey *aKey, + const nsString &aIncomgUserName, + int32_t authMethodIncoming, bool isNNTP); + static void SetSmtpServer(const nsString &aSmtpServer, + nsIWindowsRegKey *aKey, + nsIMsgIdentity *aId, + const nsString &aIncomgUserName, + int32_t authMethodIncoming); + static nsresult GetAccountName(nsIWindowsRegKey *aKey, + const nsString &aDefaultName, + nsAString &aAccountName); + static bool IsKB933612Applied(); +}; + +static uint32_t checkNewMailTime;// OE global setting, let's default to 30 +static bool checkNewMail; // OE global setting, let's default to false + // This won't cause unwanted autodownloads- + // user can set prefs after import + +//////////////////////////////////////////////////////////////////////// +nsresult nsOESettings::Create(nsIImportSettings** aImport) +{ + NS_PRECONDITION(aImport != nullptr, "null ptr"); + if (! aImport) + return NS_ERROR_NULL_POINTER; + + *aImport = new nsOESettings(); + if (! *aImport) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aImport); + return NS_OK; +} + +nsOESettings::nsOESettings() +{ +} + +nsOESettings::~nsOESettings() +{ +} + +NS_IMPL_ISUPPORTS(nsOESettings, nsIImportSettings) + +NS_IMETHODIMP nsOESettings::AutoLocate(char16_t **description, nsIFile **location, bool *_retval) +{ + NS_PRECONDITION(description != nullptr, "null ptr"); + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!description || !_retval) + return NS_ERROR_NULL_POINTER; + + *description = nsOEStringBundle::GetStringByID(OEIMPORT_NAME); + + if (location) + *location = nullptr; + + *_retval = false; + nsCOMPtr<nsIWindowsRegKey> key; + if (NS_FAILED(OESettings::Find50Key(getter_AddRefs(key))) && + NS_FAILED(OESettings::Find40Key(getter_AddRefs(key)))) + return NS_OK; + + if (NS_SUCCEEDED(OESettings::FindAccountsKey(getter_AddRefs(key)))) + *_retval = true; + + return NS_OK; +} + +NS_IMETHODIMP nsOESettings::SetLocation(nsIFile *location) +{ + return NS_OK; +} + +NS_IMETHODIMP nsOESettings::Import(nsIMsgAccount **localMailAccount, bool *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + + if (OESettings::DoImport(localMailAccount)) { + *_retval = true; + IMPORT_LOG0("Settings import appears successful\n"); + } + else { + *_retval = false; + IMPORT_LOG0("Settings import returned FALSE\n"); + } + + return NS_OK; +} + +nsresult OESettings::GetDefaultMailAccount(nsAString &aMailAccount) +{ + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString userId; + rv = nsOERegUtil::GetDefaultUserId(userId); + // OE has default mail account here when it has been + // set up by transfer or has multiple identities + // look below for orig code that looked in new OE installs + if (NS_SUCCEEDED(rv)) { + nsAutoString path(NS_LITERAL_STRING("Identities\\")); + path.Append(userId); + path.AppendLiteral("\\Software\\Microsoft\\Internet Account Manager"); + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + path, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) + key->ReadStringValue(NS_LITERAL_STRING("Default Mail Account"), aMailAccount); + } + + if (!aMailAccount.IsEmpty()) + return NS_OK; + + // else it must be here in original install location from orig code + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("Software\\Microsoft\\Outlook Express"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) + return rv; + + return key->ReadStringValue(NS_LITERAL_STRING("Default Mail Account"), aMailAccount); +} + +nsresult OESettings::GetCheckMailInterval(uint32_t *aInterval) +{ + nsCOMPtr<nsIWindowsRegKey> key; + // 'poll for messages' setting in OE is a global setting + // in OE options general tab and in following global OE + // registry location. + // for all accounts poll interval is a 32 bit value, 0 for + // "don't poll", else milliseconds + nsresult rv = Find50Key(getter_AddRefs(key)); + if (NS_FAILED(rv)) + rv = Find40Key(getter_AddRefs(key)); + + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIWindowsRegKey> subKey; + rv = key->OpenChild(NS_LITERAL_STRING("Mail"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE, + getter_AddRefs(subKey)); + if (NS_FAILED(rv)) + return rv; + + uint32_t intValue; + rv = subKey->ReadIntValue(NS_LITERAL_STRING("Poll For Mail"), &intValue); + if (NS_SUCCEEDED(rv) && intValue != PR_UINT32_MAX) + *aInterval = intValue / 60000; + + return rv; +} + +nsresult OESettings::FindAccountsKey(nsIWindowsRegKey **aKey) +{ + nsAutoString userId; + nsresult rv = nsOERegUtil::GetDefaultUserId(userId); + if (NS_FAILED(rv)) + return rv; + + nsAutoString path(NS_LITERAL_STRING("Identities\\")); + path.Append(userId); + path.AppendLiteral("\\Software\\Microsoft\\Internet Account Manager\\Accounts"); + + nsCOMPtr<nsIWindowsRegKey> key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + path, + nsIWindowsRegKey::ACCESS_QUERY_VALUE | + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS); + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(*aKey = key); + return rv; + } + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("Software\\Microsoft\\Internet Account Manager\\Accounts"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE | + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS); + NS_IF_ADDREF(*aKey = key); + return rv; +} + +nsresult OESettings::Find50Key(nsIWindowsRegKey **aKey) +{ + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString userId; + rv = nsOERegUtil::GetDefaultUserId(userId); + if (NS_FAILED(rv)) + return rv; + + nsAutoString path(NS_LITERAL_STRING("Identities\\")); + path.Append(userId); + path.AppendLiteral("\\Software\\Microsoft\\Outlook Express\\5.0"); + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + path, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + NS_IF_ADDREF(*aKey = key); + + return rv; +} + +nsresult OESettings::Find40Key(nsIWindowsRegKey **aKey) +{ + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("Software\\Microsoft\\Outlook Express"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + NS_IF_ADDREF(*aKey = key); + + return rv; +} + +bool OESettings::DoImport(nsIMsgAccount **aAccount) +{ + nsCOMPtr<nsIWindowsRegKey> key; + nsresult rv = FindAccountsKey(getter_AddRefs(key)); + if (NS_FAILED(rv)) { + IMPORT_LOG0( "*** Error finding Outlook Express registry account keys\n"); + return false; + } + + nsCOMPtr<nsIMsgAccountManager> accMgr = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create a account manager!\n"); + return false; + } + + nsAutoString defMailName; + rv = GetDefaultMailAccount(defMailName); + + checkNewMail = false; + checkNewMailTime = 30; + rv = GetCheckMailInterval(&checkNewMailTime); + if (NS_SUCCEEDED(rv)) + checkNewMail = true; + + // Iterate the accounts looking for POP3 & IMAP accounts... + // Ignore LDAP for now! + uint32_t accounts = 0; + nsAutoString keyComp; + uint32_t childCount = 0; + key->GetChildCount(&childCount); + for (uint32_t i = 0; i < childCount; i++) { + nsAutoString keyName; + key->GetChildName(i, keyName); + + nsCOMPtr<nsIWindowsRegKey> subKey; + rv = key->OpenChild(keyName, + nsIWindowsRegKey::ACCESS_QUERY_VALUE, + getter_AddRefs(subKey)); + if (NS_FAILED(rv)) + continue; + + nsAutoCString nativeKeyName; + NS_CopyUnicodeToNative(keyName, nativeKeyName); + IMPORT_LOG1("Opened Outlook Express account: %s\n", + nativeKeyName.get()); + + nsIMsgAccount *anAccount = nullptr; + nsAutoString value; + rv = subKey->ReadStringValue(NS_LITERAL_STRING("IMAP Server"), value); + if (NS_SUCCEEDED(rv) && DoIMAPServer(accMgr, subKey, value, &anAccount)) + accounts++; + + rv = subKey->ReadStringValue(NS_LITERAL_STRING("NNTP Server"), value); + if (NS_SUCCEEDED(rv) && DoNNTPServer(accMgr, subKey, value, &anAccount)) + accounts++; + + rv = subKey->ReadStringValue(NS_LITERAL_STRING("POP3 Server"), value); + if (NS_SUCCEEDED(rv) && DoPOP3Server(accMgr, subKey, value, &anAccount)) + accounts++; + + if (anAccount) { + // Is this the default account? + keyComp = keyName; + if (keyComp.Equals(defMailName)) + accMgr->SetDefaultAccount(anAccount); + NS_RELEASE(anAccount); + } + } + + // Now save the new acct info to pref file. + rv = accMgr->SaveAccountInfo(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file"); + + return accounts != 0; +} + +nsresult OESettings::GetAccountName(nsIWindowsRegKey *aKey, + const nsString &aDefaultName, + nsAString &aAccountName) +{ + nsresult rv; + rv = aKey->ReadStringValue(NS_LITERAL_STRING("Account Name"), aAccountName); + if (NS_FAILED(rv)) + aAccountName.Assign(aDefaultName); + + return NS_OK; +} + +bool OESettings::DoIMAPServer(nsIMsgAccountManager *aMgr, + nsIWindowsRegKey *aKey, + const nsString &aServerName, + nsIMsgAccount **ppAccount) +{ + if (ppAccount) + *ppAccount = nullptr; + + nsAutoString userName; + nsresult rv; + rv = aKey->ReadStringValue(NS_LITERAL_STRING("IMAP User Name"), userName); + if (NS_FAILED(rv)) + return false; + + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(userName, nativeUserName); + nsAutoCString nativeServerName; + NS_CopyUnicodeToNative(aServerName, nativeServerName); + // I now have a user name/server name pair, find out if it already exists? + nsCOMPtr<nsIMsgIncomingServer> in; + rv = aMgr->FindServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("imap"), + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv)) { + // for an existing server we create another identity, + // TB lists under 'manage identities' + nsCOMPtr<nsIMsgAccount> account; + rv = aMgr->FindAccountForServer(in, getter_AddRefs(account)); + if (NS_FAILED(rv)) + return false; + + IMPORT_LOG0("Created an identity and added to existing IMAP incoming server\n"); + // Fiddle with the identities + int32_t authMethod; + in->GetAuthMethod(&authMethod); + SetIdentities(aMgr, account, aKey, userName, authMethod, false); + if (ppAccount) + account->QueryInterface(NS_GET_IID(nsIMsgAccount), + (void **)ppAccount); + return true; + } + + // Create the incoming server and an account for it? + rv = aMgr->CreateIncomingServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("imap"), + getter_AddRefs(in)); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoString rootFolder; + rv = aKey->ReadStringValue(NS_LITERAL_STRING("IMAP Root Folder"), rootFolder); + if (NS_SUCCEEDED(rv) && !rootFolder.IsEmpty()) { + nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(in); + nsAutoCString nativeRootFolder; + NS_CopyUnicodeToNative(rootFolder, nativeRootFolder); + imapServer->SetServerDirectory(nativeRootFolder); + } + + SetIncomingServerProperties(in, aKey, NS_LITERAL_STRING("IMAP ")); + + IMPORT_LOG2("Created IMAP server named: %s, userName: %s\n", + nativeServerName.get(), nativeUserName.get()); + + nsAutoString prettyName; + if (NS_SUCCEEDED(GetAccountName(aKey, aServerName, prettyName))) + rv = in->SetPrettyName(prettyName); + + // We have a server, create an account. + nsCOMPtr<nsIMsgAccount> account; + rv = aMgr->CreateAccount(getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + rv = account->SetIncomingServer(in); + + IMPORT_LOG0("Created an account and set the IMAP server as the incoming server\n"); + + // Fiddle with the identities + int32_t authMethod; + in->GetAuthMethod(&authMethod); + SetIdentities(aMgr, account, aKey, userName, authMethod, false); + if (ppAccount) + account->QueryInterface(NS_GET_IID(nsIMsgAccount), (void **)ppAccount); + return true; + } + + return false; +} + +bool OESettings::DoPOP3Server(nsIMsgAccountManager *aMgr, + nsIWindowsRegKey *aKey, + const nsString &aServerName, + nsIMsgAccount **ppAccount) +{ + if (ppAccount) + *ppAccount = nullptr; + + nsAutoString userName; + nsresult rv; + rv = aKey->ReadStringValue(NS_LITERAL_STRING("POP3 User Name"), userName); + if (NS_FAILED(rv)) + return false; + + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(userName, nativeUserName); + nsAutoCString nativeServerName; + NS_CopyUnicodeToNative(aServerName, nativeServerName); + + // I now have a user name/server name pair, find out if it already exists? + nsCOMPtr<nsIMsgIncomingServer> in; + rv = aMgr->FindServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("pop3"), + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv)) { + IMPORT_LOG2("Existing POP3 server named: %s, userName: %s\n", + nativeUserName.get(), nativeServerName.get()); + // for an existing server we create another identity, + // TB listed under 'manage identities' + nsCOMPtr<nsIMsgAccount> account; + rv = aMgr->FindAccountForServer(in, getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + IMPORT_LOG0("Created identity and added to existing POP3 incoming server.\n"); + // Fiddle with the identities + int32_t authMethod; + in->GetAuthMethod(&authMethod); + SetIdentities(aMgr, account, aKey, userName, authMethod, false); + if (ppAccount) + account->QueryInterface(NS_GET_IID(nsIMsgAccount), (void **)ppAccount); + return true; + } + return false; + } + + // Create the incoming server and an account for it? + rv = aMgr->CreateIncomingServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("pop3"), + getter_AddRefs( in)); + if (NS_FAILED(rv)) + return false; + + SetIncomingServerProperties(in, aKey, NS_LITERAL_STRING("POP3 ")); + + nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(in); + if (pop3Server) { + // set local folders as the Inbox to use for this POP3 server + nsCOMPtr<nsIMsgIncomingServer> localFoldersServer; + aMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + + if (!localFoldersServer) + { + // If Local Folders does not exist already, create it + + if (NS_FAILED(aMgr->CreateLocalMailAccount())) { + IMPORT_LOG0("*** Failed to create Local Folders!\n"); + return false; + } + + aMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + } + + // now get the account for this server + nsCOMPtr<nsIMsgAccount> localFoldersAccount; + aMgr->FindAccountForServer(localFoldersServer, getter_AddRefs(localFoldersAccount)); + if (localFoldersAccount) + { + nsCString localFoldersAcctKey; + localFoldersAccount->GetKey(localFoldersAcctKey); + pop3Server->SetDeferredToAccount(localFoldersAcctKey); + } + + uint32_t intValue; + rv = aKey->ReadIntValue(NS_LITERAL_STRING("POP3 Skip Account"), &intValue); + // OE:0=='Include this account when receiving mail or synchronizing'== + // TB:1==AM:Server:advanced:Include this server when getting new mail + pop3Server->SetDeferGetNewMail(NS_SUCCEEDED(rv) && intValue == 0); + + rv = aKey->ReadIntValue(NS_LITERAL_STRING("Leave Mail On Server"), + &intValue); + pop3Server->SetLeaveMessagesOnServer(NS_SUCCEEDED(rv) && intValue == 1); + + rv = aKey->ReadIntValue(NS_LITERAL_STRING("Remove When Deleted"), + &intValue); + pop3Server->SetDeleteMailLeftOnServer(NS_SUCCEEDED(rv) && intValue == 1); + + rv = aKey->ReadIntValue(NS_LITERAL_STRING("Remove When Expired"), + &intValue); + pop3Server->SetDeleteByAgeFromServer(NS_SUCCEEDED(rv) && intValue == 1); + + rv = aKey->ReadIntValue(NS_LITERAL_STRING("Expire Days"), + &intValue); + if (NS_SUCCEEDED(rv)) + pop3Server->SetNumDaysToLeaveOnServer(static_cast<int32_t>(intValue)); + } + IMPORT_LOG2("Created POP3 server named: %s, userName: %s\n", + nativeServerName.get(), nativeUserName.get()); + nsString prettyName; + if (NS_SUCCEEDED(GetAccountName(aKey, aServerName, prettyName))) + rv = in->SetPrettyName(prettyName); + + // We have a server, create an account. + nsCOMPtr<nsIMsgAccount> account; + rv = aMgr->CreateAccount(getter_AddRefs( account)); + if (NS_SUCCEEDED( rv) && account) { + rv = account->SetIncomingServer(in); + IMPORT_LOG0("Created a new account and set the incoming server to the POP3 server.\n"); + + int32_t authMethod; + in->GetAuthMethod(&authMethod); + // Fiddle with the identities + SetIdentities(aMgr, account, aKey, userName, authMethod, false); + if (ppAccount) + account->QueryInterface(NS_GET_IID(nsIMsgAccount), + (void **)ppAccount); + return true; + } + + return false; +} + +bool OESettings::DoNNTPServer(nsIMsgAccountManager *aMgr, + nsIWindowsRegKey *aKey, + const nsString &aServerName, + nsIMsgAccount **ppAccount) +{ + if (ppAccount) + *ppAccount = nullptr; + + nsAutoString userName; + nsresult rv; + // this only exists if NNTP server requires it or not anon login + rv = aKey->ReadStringValue(NS_LITERAL_STRING("NNTP User Name"), userName); + + bool result = false; + + nsAutoCString nativeServerName; + NS_CopyUnicodeToNative(aServerName, nativeServerName); + // I now have a user name/server name pair, find out if it already exists? + // NNTP can have empty user name. This is wild card in findserver + nsCOMPtr<nsIMsgIncomingServer> in; + rv = aMgr->FindServer(EmptyCString(), + nativeServerName, + NS_LITERAL_CSTRING("nntp"), + getter_AddRefs(in)); + if (NS_FAILED(rv) || (in == nullptr)) { + // Create the incoming server and an account for it? + rv = aMgr->CreateIncomingServer(EmptyCString(), + nativeServerName, + NS_LITERAL_CSTRING("nntp"), + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv) && in) { + uint32_t port = 0; + rv = aKey->ReadIntValue(NS_LITERAL_STRING("NNTP Port"), + &port); + if (NS_SUCCEEDED(rv) && port && port != 119) + in->SetPort(static_cast<int32_t>(port)); + + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(userName, nativeUserName); + // do nntpincomingserver stuff + nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(in); + if (nntpServer && !userName.IsEmpty()) { + nntpServer->SetPushAuth(true); + in->SetUsername(nativeUserName); + } + + IMPORT_LOG2("Created NNTP server named: %s, userName: %s\n", + nativeServerName.get(), nativeUserName.get()); + + nsString prettyName; + if (NS_SUCCEEDED(GetAccountName(aKey, aServerName, prettyName))) + rv = in->SetPrettyName(prettyName); + + // We have a server, create an account. + nsCOMPtr<nsIMsgAccount> account; + rv = aMgr->CreateAccount(getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + rv = account->SetIncomingServer(in); + + IMPORT_LOG0("Created an account and set the NNTP server as the incoming server\n"); + + // Fiddle with the identities + SetIdentities(aMgr, account, aKey, userName, 0, true); + result = true; + if (ppAccount) + account->QueryInterface(NS_GET_IID(nsIMsgAccount), (void **)ppAccount); + } + } + } + else if (NS_SUCCEEDED(rv) && in) { + // for the existing server... + nsCOMPtr<nsIMsgAccount> account; + rv = aMgr->FindAccountForServer(in, getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + IMPORT_LOG0("Using existing account and set the NNTP server as the incoming server\n"); + // Fiddle with the identities + SetIdentities(aMgr, account, aKey, userName, 0, true); + if (ppAccount) + account->QueryInterface(NS_GET_IID(nsIMsgAccount), + (void **)ppAccount); + return true; + } + } + else + result = true; + + return result; +} + +void OESettings::SetIncomingServerProperties(nsIMsgIncomingServer *aServer, + nsIWindowsRegKey *aKey, + const nsString &aKeyNamePrefix) +{ + nsresult rv; + uint32_t secureConnection = 0; + nsString keyName(aKeyNamePrefix); + keyName.AppendLiteral("Secure Connection"); + rv = aKey->ReadIntValue(keyName, &secureConnection); + if (NS_SUCCEEDED(rv) && secureConnection == 1) + aServer->SetSocketType(nsMsgSocketType::SSL); + + uint32_t port = 0; + keyName.SetLength(aKeyNamePrefix.Length()); + keyName.AppendLiteral("Port"); + rv = aKey->ReadIntValue(keyName, &port); + if (NS_SUCCEEDED(rv) && port) + aServer->SetPort(static_cast<int32_t>(port)); + + int32_t authMethod; + uint32_t useSicily = 0; + keyName.SetLength(aKeyNamePrefix.Length()); + keyName.AppendLiteral("Use Sicily"); + rv = aKey->ReadIntValue(keyName, &useSicily); + if (NS_SUCCEEDED(rv) && useSicily) + authMethod = nsMsgAuthMethod::secure; + else + authMethod = nsMsgAuthMethod::passwordCleartext; + aServer->SetAuthMethod(authMethod); + + aServer->SetDoBiff(checkNewMail); + aServer->SetBiffMinutes(checkNewMailTime); +} + +void OESettings::SetIdentities(nsIMsgAccountManager *aMgr, + nsIMsgAccount *pAcc, + nsIWindowsRegKey *aKey, + const nsString &aIncomgUserName, + int32_t authMethodIncoming, + bool isNNTP) +{ + // Get the relevant information for an identity + nsresult rv; + nsAutoString name; + rv = aKey->ReadStringValue(isNNTP ? + NS_LITERAL_STRING("NNTP Display Name") : + NS_LITERAL_STRING("SMTP Display Name"), + name); + nsAutoString email; + rv = aKey->ReadStringValue(isNNTP ? + NS_LITERAL_STRING("NNTP Email Address") : + NS_LITERAL_STRING("SMTP Email Address"), + email); + nsAutoString reply; + rv = aKey->ReadStringValue(isNNTP ? + NS_LITERAL_STRING("NNTP Reply To Email Address") : + NS_LITERAL_STRING("SMTP Reply To Email Address"), + reply); + nsAutoString orgName; + rv = aKey->ReadStringValue(isNNTP ? + NS_LITERAL_STRING("NNTP Organization Name") : + NS_LITERAL_STRING("SMTP Organization Name"), + orgName); + + nsCOMPtr<nsIMsgIdentity> id; + rv = aMgr->CreateIdentity(getter_AddRefs(id)); + if (NS_FAILED(rv)) + return; + + id->SetFullName(name); + id->SetOrganization(orgName); + + nsAutoCString nativeEmail; + NS_CopyUnicodeToNative(email, nativeEmail); + id->SetEmail(nativeEmail); + if (!reply.IsEmpty()) { + nsAutoCString nativeReply; + NS_CopyUnicodeToNative(reply, nativeReply); + id->SetReplyTo(nativeReply); + } + + // Outlook Express users are used to top style quoting. + id->SetReplyOnTop(isNNTP ? 0 : 1); + pAcc->AddIdentity(id); + + nsAutoCString nativeName; + NS_CopyUnicodeToNative(name, nativeName); + IMPORT_LOG0("Created identity and added to the account\n"); + IMPORT_LOG1("\tname: %s\n", nativeName.get()); + IMPORT_LOG1("\temail: %s\n", nativeEmail.get()); + + if (isNNTP) // NNTP does not use SMTP in OE or TB + return; + nsAutoString smtpServer; + rv = aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Server"), smtpServer); + SetSmtpServer(smtpServer, aKey, id, aIncomgUserName, authMethodIncoming); +} + +void OESettings::SetSmtpServer(const nsString &aSmtpServer, + nsIWindowsRegKey *aKey, + nsIMsgIdentity *aId, + const nsString &aIncomgUserName, + int32_t authMethodIncoming) +{ + // set the id.smtpserver accordingly + // first we have to calculate the smtp user name which is based on sicily + if (!aKey || !aId || aIncomgUserName.IsEmpty() || aSmtpServer.IsEmpty()) + return; + nsCString smtpServerKey; + // smtp user name depends on sicily which may or not exist + uint32_t useSicily = 0; + nsresult rv = aKey->ReadIntValue(NS_LITERAL_STRING("SMTP Use Sicily"), + &useSicily); + nsAutoString userName; + switch (useSicily) { + case 1: + case 3: + // has to go in whether empty or no + // shouldn't be empty but better safe than sorry + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP User Name"), userName); + break; + case 2: + userName = aIncomgUserName; + break; + default: + break; // initial userName == "" + } + + nsCOMPtr<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && smtpService) { + nsCOMPtr<nsISmtpServer> foundServer; + // don't try to make another server + // regardless if username doesn't match + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(userName, nativeUserName); + nsAutoCString nativeSmtpServer; + NS_CopyUnicodeToNative(aSmtpServer, nativeSmtpServer); + rv = smtpService->FindServer(nativeUserName.get(), + nativeSmtpServer.get(), + getter_AddRefs(foundServer)); + if (NS_SUCCEEDED(rv) && foundServer) { + // set our account keyed to this smptserver key + foundServer->GetKey(getter_Copies(smtpServerKey)); + aId->SetSmtpServerKey(smtpServerKey); + + IMPORT_LOG1("SMTP server already exists: %s\n", + nativeSmtpServer.get()); + } + else { + nsCOMPtr<nsISmtpServer> smtpServer; + rv = smtpService->CreateServer(getter_AddRefs(smtpServer)); + if (NS_SUCCEEDED(rv) && smtpServer) { + uint32_t port = 0; + rv = aKey->ReadIntValue(NS_LITERAL_STRING("SMTP Port"), + &port); + if (NS_SUCCEEDED(rv) && port) + smtpServer->SetPort(static_cast<int32_t>(port)); + + int32_t socketType = nsMsgSocketType::plain; + uint32_t secureConnection = 0; + rv = aKey->ReadIntValue(NS_LITERAL_STRING("SMTP Secure Connection"), + &secureConnection); + if (NS_SUCCEEDED(rv) && secureConnection == 1) { + // Outlook Express does not support STARTTLS without KB933612 fix. + if (IsKB933612Applied() && port != 465) + socketType = nsMsgSocketType::alwaysSTARTTLS; + else + socketType = nsMsgSocketType::SSL; + } + smtpServer->SetSocketType(socketType); + smtpServer->SetUsername(nativeUserName); + switch (useSicily) { + case 1 : + smtpServer->SetAuthMethod(nsMsgAuthMethod::secure); + break; + case 2 : // requires SMTP authentication to use the incoming server settings + smtpServer->SetAuthMethod(authMethodIncoming); + break; + case 3 : + smtpServer->SetAuthMethod(nsMsgAuthMethod::passwordCleartext); + break; + default: + smtpServer->SetAuthMethod(nsMsgAuthMethod::none); + } + + smtpServer->SetHostname(nativeSmtpServer); + + smtpServer->GetKey(getter_Copies(smtpServerKey)); + aId->SetSmtpServerKey(smtpServerKey); + + IMPORT_LOG1("Created new SMTP server: %s\n", + nativeSmtpServer.get()); + } + } + } +} + +bool OESettings::IsKB933612Applied() +{ + // The following versions of Windows include KB933612 fix: + // - Windows 7 and future versions of Windows + // - Windows Vista, SP1 or later + // - Windows Server 2003, SP2 or later + // - Windows XP, SP3 or later + // + // The following versions do not: + // - Windows Vista SP0 + // - Windows Server 2003, SP1 or earlier + // - Windows XP, SP2 or earlier + // + // See http://support.microsoft.com/kb/929123 and + // http://support.microsoft.com/kb/933612 + // + // Note that mozilla::IsWin2003SP2OrLater() will return true for + // Windows Vista and mozilla::IsXPSP3OrLater() will return true + // for Windows Server 2003. + return mozilla::IsVistaSP1OrLater() || + !mozilla::IsWin2003OrLater() && mozilla::IsXPSP3OrLater() || + !mozilla::IsVistaOrLater() && mozilla::IsWin2003SP2OrLater(); +} + diff --git a/mailnews/import/oexpress/nsOESettings.h b/mailnews/import/oexpress/nsOESettings.h new file mode 100644 index 000000000..8d2525423 --- /dev/null +++ b/mailnews/import/oexpress/nsOESettings.h @@ -0,0 +1,22 @@ +/* -*- 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 nsOESettings_h___ +#define nsOESettings_h___ + +#include "nsIImportSettings.h" + +class nsOESettings : public nsIImportSettings { +public: + nsOESettings(); + static nsresult Create(nsIImportSettings** aImport); + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTSETTINGS + +private: + virtual ~nsOESettings(); +}; + +#endif /* nsOESettings_h___ */ diff --git a/mailnews/import/oexpress/nsOEStringBundle.cpp b/mailnews/import/oexpress/nsOEStringBundle.cpp new file mode 100644 index 000000000..3d3b4035a --- /dev/null +++ b/mailnews/import/oexpress/nsOEStringBundle.cpp @@ -0,0 +1,71 @@ +/* -*- 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 "prprf.h" +#include "prmem.h" +#include "nsCOMPtr.h" +#include "nsMsgUtils.h" +#include "nsIStringBundle.h" +#include "nsOEStringBundle.h" +#include "nsIServiceManager.h" +#include "nsIURI.h" +#include "mozilla/Services.h" + +#define OE_MSGS_URL "chrome://messenger/locale/oeImportMsgs.properties" + +nsIStringBundle * nsOEStringBundle::m_pBundle = nullptr; + +nsIStringBundle *nsOEStringBundle::GetStringBundle(void) +{ + if (m_pBundle) + return m_pBundle; + + char* propertyURL = OE_MSGS_URL; + nsIStringBundle* sBundle = nullptr; + + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + if (sBundleService) { + sBundleService->CreateBundle(propertyURL, &sBundle); + } + + m_pBundle = sBundle; + + return sBundle; +} + + +void nsOEStringBundle::GetStringByID(int32_t stringID, nsString& result) +{ + char16_t *ptrv = GetStringByID(stringID); + result.Adopt(ptrv); +} + +char16_t *nsOEStringBundle::GetStringByID(int32_t stringID) +{ + if (!m_pBundle) + m_pBundle = GetStringBundle(); + + if (m_pBundle) { + char16_t *ptrv = nullptr; + nsresult rv = m_pBundle->GetStringFromID(stringID, &ptrv); + + if (NS_SUCCEEDED(rv) && ptrv) + return ptrv; + } + + nsString resultString; + resultString.AppendLiteral("[StringID "); + resultString.AppendInt(stringID); + resultString.AppendLiteral("?]"); + + return ToNewUnicode(resultString); +} + +void nsOEStringBundle::Cleanup(void) +{ + if (m_pBundle) + m_pBundle->Release(); + m_pBundle = nullptr; +} diff --git a/mailnews/import/oexpress/nsOEStringBundle.h b/mailnews/import/oexpress/nsOEStringBundle.h new file mode 100644 index 000000000..36829e5e3 --- /dev/null +++ b/mailnews/import/oexpress/nsOEStringBundle.h @@ -0,0 +1,38 @@ +/* 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 _nsOEStringBundle_H__ +#define _nsOEStringBundle_H__ + +#include "nsStringGlue.h" + +class nsIStringBundle; + +class nsOEStringBundle { +public: + static char16_t * GetStringByID(int32_t stringID); + static void GetStringByID(int32_t stringID, nsString& result); + static nsIStringBundle * GetStringBundle(void); // don't release + static void FreeString(char16_t *pStr) { NS_Free(pStr);} + static void Cleanup(void); + +private: + static nsIStringBundle * m_pBundle; +}; + + + +#define OEIMPORT_NAME 2000 +#define OEIMPORT_DESCRIPTION 2011 +#define OEIMPORT_MAILBOX_SUCCESS 2002 +#define OEIMPORT_MAILBOX_BADPARAM 2003 +#define OEIMPORT_MAILBOX_BADSOURCEFILE 2004 +#define OEIMPORT_MAILBOX_CONVERTERROR 2005 +#define OEIMPORT_DEFAULT_NAME 2006 +#define OEIMPORT_AUTOFIND 2007 +#define OEIMPORT_ADDRESS_SUCCESS 2008 +#define OEIMPORT_ADDRESS_CONVERTERROR 2009 +#define OEIMPORT_ADDRESS_BADPARAM 2010 + +#endif /* _nsOEStringBundle_H__ */ diff --git a/mailnews/import/outlook/src/MapiApi.cpp b/mailnews/import/outlook/src/MapiApi.cpp new file mode 100644 index 000000000..d6a159754 --- /dev/null +++ b/mailnews/import/outlook/src/MapiApi.cpp @@ -0,0 +1,1940 @@ +/* -*- 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 "MapiDbgLog.h" +#include "MapiApi.h" + +#include <sstream> +#include "rtfMailDecoder.h" + +#include "prprf.h" +#include "nsMemory.h" +#include "nsMsgUtils.h" +#include "nsUnicharUtils.h" + +int CMapiApi::m_clients = 0; +BOOL CMapiApi::m_initialized = false; +nsTArray<CMsgStore*> *CMapiApi::m_pStores = NULL; +LPMAPISESSION CMapiApi::m_lpSession = NULL; +LPMDB CMapiApi::m_lpMdb = NULL; +HRESULT CMapiApi::m_lastError; +char16_t * CMapiApi::m_pUniBuff = NULL; +int CMapiApi::m_uniBuffLen = 0; +/* +Type: 1, name: Calendar, class: IPF.Appointment +Type: 1, name: Contacts, class: IPF.Contact +Type: 1, name: Journal, class: IPF.Journal +Type: 1, name: Notes, class: IPF.StickyNote +Type: 1, name: Tasks, class: IPF.Task +Type: 1, name: Drafts, class: IPF.Note +*/ + +HINSTANCE CMapiApi::m_hMapi32 = NULL; + +LPMAPIUNINITIALIZE gpMapiUninitialize = NULL; +LPMAPIINITIALIZE gpMapiInitialize = NULL; +LPMAPIALLOCATEBUFFER gpMapiAllocateBuffer = NULL; +LPMAPIFREEBUFFER gpMapiFreeBuffer = NULL; +LPMAPILOGONEX gpMapiLogonEx = NULL; +LPOPENSTREAMONFILE gpMapiOpenStreamOnFile = NULL; + +typedef HRESULT (STDMETHODCALLTYPE WRAPCOMPRESSEDRTFSTREAM) ( + LPSTREAM lpCompressedRTFStream, ULONG ulFlags, LPSTREAM FAR *lpUncompressedRTFStream); +typedef WRAPCOMPRESSEDRTFSTREAM *LPWRAPCOMPRESSEDRTFSTREAM; +LPWRAPCOMPRESSEDRTFSTREAM gpWrapCompressedRTFStream = NULL; + +// WrapCompressedRTFStreamEx related stuff - see http://support.microsoft.com/kb/839560 +typedef struct { + ULONG size; + ULONG ulFlags; + ULONG ulInCodePage; + ULONG ulOutCodePage; +} RTF_WCSINFO; +typedef struct { + ULONG size; + ULONG ulStreamFlags; +} RTF_WCSRETINFO; + +typedef HRESULT (STDMETHODCALLTYPE WRAPCOMPRESSEDRTFSTREAMEX) ( + LPSTREAM lpCompressedRTFStream, CONST RTF_WCSINFO * pWCSInfo, + LPSTREAM * lppUncompressedRTFStream, RTF_WCSRETINFO * pRetInfo); +typedef WRAPCOMPRESSEDRTFSTREAMEX *LPWRAPCOMPRESSEDRTFSTREAMEX; +LPWRAPCOMPRESSEDRTFSTREAMEX gpWrapCompressedRTFStreamEx = NULL; + +BOOL CMapiApi::LoadMapiEntryPoints(void) +{ + if (!(gpMapiUninitialize = (LPMAPIUNINITIALIZE) GetProcAddress( + m_hMapi32, "MAPIUninitialize"))) + return FALSE; + if (!(gpMapiInitialize = (LPMAPIINITIALIZE) GetProcAddress( + m_hMapi32, "MAPIInitialize"))) + return FALSE; + if (!(gpMapiAllocateBuffer = (LPMAPIALLOCATEBUFFER) GetProcAddress( + m_hMapi32, "MAPIAllocateBuffer"))) + return FALSE; + if (!(gpMapiFreeBuffer = (LPMAPIFREEBUFFER) GetProcAddress( + m_hMapi32, "MAPIFreeBuffer"))) + return FALSE; + if (!(gpMapiLogonEx = (LPMAPILOGONEX) GetProcAddress(m_hMapi32, + "MAPILogonEx"))) + return FALSE; + if (!(gpMapiOpenStreamOnFile = (LPOPENSTREAMONFILE) GetProcAddress( + m_hMapi32, "OpenStreamOnFile"))) + return FALSE; + + // Available from the Outlook 2002 post-SP3 hotfix (http://support.microsoft.com/kb/883924/) + // Exported by msmapi32.dll; so it's unavailable to us using mapi32.dll + gpWrapCompressedRTFStreamEx = (LPWRAPCOMPRESSEDRTFSTREAMEX) GetProcAddress( + m_hMapi32, "WrapCompressedRTFStreamEx"); + // Available always + gpWrapCompressedRTFStream = (LPWRAPCOMPRESSEDRTFSTREAM) GetProcAddress( + m_hMapi32, "WrapCompressedRTFStream"); + + return TRUE; +} + +// Gets the PR_RTF_COMPRESSED tag property +// Codepage is used only if the WrapCompressedRTFStreamEx is available +BOOL CMapiApi::GetRTFPropertyDecodedAsUTF16(LPMAPIPROP pProp, nsString& val, + unsigned long& nativeBodyType, + unsigned long codepage) +{ + if (!m_hMapi32 || !(gpWrapCompressedRTFStreamEx || gpWrapCompressedRTFStream)) + return FALSE; // Fallback to the default processing + + LPSTREAM icstream = 0; // for the compressed stream + LPSTREAM iunstream = 0; // for the uncompressed stream + HRESULT hr = pProp->OpenProperty(PR_RTF_COMPRESSED, + &IID_IStream, STGM_READ | STGM_DIRECT, + 0, (LPUNKNOWN *)&icstream); + if (HR_FAILED(hr)) + return FALSE; + + if (gpWrapCompressedRTFStreamEx) { // Impossible - we use mapi32.dll! + RTF_WCSINFO wcsinfo = {0}; + RTF_WCSRETINFO retinfo = {0}; + + retinfo.size = sizeof(RTF_WCSRETINFO); + + wcsinfo.size = sizeof(RTF_WCSINFO); + wcsinfo.ulFlags = MAPI_NATIVE_BODY; + wcsinfo.ulInCodePage = codepage; + wcsinfo.ulOutCodePage = CP_UTF8; + + if(HR_SUCCEEDED(hr = gpWrapCompressedRTFStreamEx(icstream, &wcsinfo, + &iunstream, &retinfo))) + nativeBodyType = retinfo.ulStreamFlags; + } + else { // mapi32.dll + gpWrapCompressedRTFStream(icstream,0,&iunstream); + } + icstream->Release(); + + if(iunstream) { // Succeeded + std::string streamData; + // Stream.Stat doesn't work for this stream! + bool done = false; + while (!done) { + // I think 10K is a good guess to minimize the number of reads while keeping memory usage low + const int bufsize = 10240; + char buf[bufsize]; + ULONG read; + hr = iunstream->Read(buf, bufsize, &read); + done = (read < bufsize) || (hr != S_OK); + if (read) + streamData.append(buf, read); + } + iunstream->Release(); + // if rtf -> convert to plain text. + if (!gpWrapCompressedRTFStreamEx || + (nativeBodyType==MAPI_NATIVE_BODY_TYPE_RTF)) { + std::stringstream s(streamData); + CRTFMailDecoder decoder; + DecodeRTF(s, decoder); + if (decoder.mode() == CRTFMailDecoder::mHTML) + nativeBodyType = MAPI_NATIVE_BODY_TYPE_HTML; + else if (decoder.mode() == CRTFMailDecoder::mText) + nativeBodyType = MAPI_NATIVE_BODY_TYPE_PLAINTEXT; + else + nativeBodyType = MAPI_NATIVE_BODY_TYPE_RTF; + val.Assign(decoder.text(), decoder.textSize()); + } + else { // WrapCompressedRTFStreamEx available and original type is not rtf + CopyUTF8toUTF16(nsDependentCString(streamData.c_str()), val); + } + return TRUE; + } + return FALSE; +} + +void CMapiApi::MAPIUninitialize(void) +{ + if (m_hMapi32 && gpMapiUninitialize) + (*gpMapiUninitialize)(); +} + +HRESULT CMapiApi::MAPIInitialize(LPVOID lpInit) +{ + return (m_hMapi32 && gpMapiInitialize) ? (*gpMapiInitialize)(lpInit) : + MAPI_E_NOT_INITIALIZED; +} + +SCODE CMapiApi::MAPIAllocateBuffer(ULONG cbSize, LPVOID FAR * lppBuffer) +{ + return (m_hMapi32 && gpMapiAllocateBuffer) ? + (*gpMapiAllocateBuffer)(cbSize, lppBuffer) : MAPI_E_NOT_INITIALIZED; +} + +ULONG CMapiApi::MAPIFreeBuffer(LPVOID lpBuff) +{ + return (m_hMapi32 && gpMapiFreeBuffer) ? (*gpMapiFreeBuffer)(lpBuff) : + MAPI_E_NOT_INITIALIZED; +} + +HRESULT CMapiApi::MAPILogonEx(ULONG ulUIParam, LPTSTR lpszProfileName, + LPTSTR lpszPassword, FLAGS flFlags, + LPMAPISESSION FAR * lppSession) +{ + return (m_hMapi32 && gpMapiLogonEx) ? + (*gpMapiLogonEx)(ulUIParam, lpszProfileName, lpszPassword, flFlags, lppSession) : + MAPI_E_NOT_INITIALIZED; +} + +HRESULT CMapiApi::OpenStreamOnFile(LPALLOCATEBUFFER lpAllocateBuffer, + LPFREEBUFFER lpFreeBuffer, ULONG ulFlags, + LPTSTR lpszFileName, LPTSTR lpszPrefix, + LPSTREAM FAR * lppStream) +{ + return (m_hMapi32 && gpMapiOpenStreamOnFile) ? + (*gpMapiOpenStreamOnFile)(lpAllocateBuffer, lpFreeBuffer, ulFlags, + lpszFileName, lpszPrefix, lppStream) : + MAPI_E_NOT_INITIALIZED; +} + +void CMapiApi::FreeProws(LPSRowSet prows) +{ + ULONG irow; + if (!prows) + return; + for (irow = 0; irow < prows->cRows; ++irow) + MAPIFreeBuffer(prows->aRow[irow].lpProps); + MAPIFreeBuffer(prows); +} + +BOOL CMapiApi::LoadMapi(void) +{ + if (m_hMapi32) + return TRUE; + + HINSTANCE hInst = ::LoadLibrary("MAPI32.DLL"); + if (!hInst) + return FALSE; + FARPROC pProc = GetProcAddress(hInst, "MAPIGetNetscapeVersion"); + if (pProc) { + ::FreeLibrary(hInst); + hInst = ::LoadLibrary("MAPI32BAK.DLL"); + if (!hInst) + return FALSE; + } + + m_hMapi32 = hInst; + return LoadMapiEntryPoints(); +} + +void CMapiApi::UnloadMapi(void) +{ + if (m_hMapi32) + ::FreeLibrary(m_hMapi32); + m_hMapi32 = NULL; +} + +CMapiApi::CMapiApi() +{ + m_clients++; + LoadMapi(); + if (!m_pStores) + m_pStores = new nsTArray<CMsgStore*>(); +} + +CMapiApi::~CMapiApi() +{ + m_clients--; + if (!m_clients) { + HRESULT hr; + + ClearMessageStores(); + delete m_pStores; + m_pStores = NULL; + + m_lpMdb = NULL; + + if (m_lpSession) { + hr = m_lpSession->Logoff(NULL, 0, 0); + if (FAILED(hr)) { + MAPI_TRACE2("Logoff failed: 0x%lx, %d\n", (long)hr, (int)hr); + } + m_lpSession->Release(); + m_lpSession = NULL; + } + + if (m_initialized) { + MAPIUninitialize(); + m_initialized = FALSE; + } + + UnloadMapi(); + + if (m_pUniBuff) + delete [] m_pUniBuff; + m_pUniBuff = NULL; + m_uniBuffLen = 0; + } +} + +void CMapiApi::CStrToUnicode(const char *pStr, nsString& result) +{ + result.Truncate(); + int wLen = MultiByteToWideChar(CP_ACP, 0, pStr, -1, wwc(m_pUniBuff), 0); + if (wLen >= m_uniBuffLen) { + if (m_pUniBuff) + delete [] m_pUniBuff; + m_pUniBuff = new char16_t[wLen + 64]; + m_uniBuffLen = wLen + 64; + } + if (wLen) { + MultiByteToWideChar(CP_ACP, 0, pStr, -1, wwc(m_pUniBuff), m_uniBuffLen); + result = m_pUniBuff; + } +} + +BOOL CMapiApi::Initialize(void) +{ + if (m_initialized) + return TRUE; + + HRESULT hr; + + hr = MAPIInitialize(NULL); + + if (FAILED(hr)) { + MAPI_TRACE2("MAPI Initialize failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + m_initialized = TRUE; + MAPI_TRACE0("MAPI Initialized\n"); + + return TRUE; +} + +BOOL CMapiApi::LogOn(void) +{ + if (!m_initialized) { + MAPI_TRACE0("Tried to LogOn before initializing MAPI\n"); + return FALSE; + } + + if (m_lpSession) + return TRUE; + + HRESULT hr; + + hr = MAPILogonEx( 0, // might need to be passed in HWND + NULL, // profile name, 64 char max (LPTSTR) + NULL, // profile password, 64 char max (LPTSTR) + // MAPI_NEW_SESSION | MAPI_NO_MAIL | MAPI_LOGON_UI | MAPI_EXPLICIT_PROFILE, + // MAPI_NEW_SESSION | MAPI_NO_MAIL | MAPI_LOGON_UI, + // MAPI_NO_MAIL | MAPI_LOGON_UI, + MAPI_NO_MAIL | MAPI_USE_DEFAULT | MAPI_EXTENDED | MAPI_NEW_SESSION, + &m_lpSession); + + if (FAILED(hr)) { + m_lpSession = NULL; + MAPI_TRACE2("LogOn failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + MAPI_TRACE0("MAPI Logged on\n"); + return TRUE; +} + +class CGetStoreFoldersIter : public CMapiHierarchyIter { +public: + CGetStoreFoldersIter(CMapiApi *pApi, CMapiFolderList& folders, int depth, BOOL isMail = TRUE); + + virtual BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry); + +protected: + BOOL ExcludeFolderClass(const char16_t *pName); + + BOOL m_isMail; + CMapiApi * m_pApi; + CMapiFolderList * m_pList; + int m_depth; +}; + +CGetStoreFoldersIter::CGetStoreFoldersIter(CMapiApi *pApi, CMapiFolderList& folders, int depth, BOOL isMail) +{ + m_pApi = pApi; + m_pList = &folders; + m_depth = depth; + m_isMail = isMail; +} + +BOOL CGetStoreFoldersIter::ExcludeFolderClass(const char16_t *pName) +{ + BOOL bResult; + nsDependentString pNameStr(pName); + if (m_isMail) { + bResult = FALSE; + if (pNameStr.EqualsLiteral("IPF.Appointment")) + bResult = TRUE; + else if (pNameStr.EqualsLiteral("IPF.Contact")) + bResult = TRUE; + else if (pNameStr.EqualsLiteral("IPF.Journal")) + bResult = TRUE; + else if (pNameStr.EqualsLiteral("IPF.StickyNote")) + bResult = TRUE; + else if (pNameStr.EqualsLiteral("IPF.Task")) + bResult = TRUE; + // Skip IMAP folders + else if (pNameStr.EqualsLiteral("IPF.Imap")) + bResult = TRUE; + // else if (!stricmp(pName, "IPF.Note")) + // bResult = TRUE; + } + else { + bResult = TRUE; + if (pNameStr.EqualsLiteral("IPF.Contact")) + bResult = FALSE; + } + + return bResult; +} + +BOOL CGetStoreFoldersIter::HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry) +{ + if (oType == MAPI_FOLDER) { + LPMAPIFOLDER pFolder; + if (m_pApi->OpenEntry(cb, pEntry, (LPUNKNOWN *) &pFolder)) { + LPSPropValue pVal; + nsString name; + + pVal = m_pApi->GetMapiProperty(pFolder, PR_CONTAINER_CLASS); + if (pVal) + m_pApi->GetStringFromProp(pVal, name); + else + name.Truncate(); + + if ((name.IsEmpty() && m_isMail) || (!ExcludeFolderClass(name.get()))) { + pVal = m_pApi->GetMapiProperty(pFolder, PR_DISPLAY_NAME); + m_pApi->GetStringFromProp(pVal, name); + CMapiFolder *pNewFolder = new CMapiFolder(name.get(), cb, pEntry, m_depth); + m_pList->AddItem(pNewFolder); + + pVal = m_pApi->GetMapiProperty(pFolder, PR_FOLDER_TYPE); + MAPI_TRACE2("Type: %d, name: %s\n", + m_pApi->GetLongFromProp(pVal), name.get()); + // m_pApi->ListProperties(pFolder); + + CGetStoreFoldersIter nextIter(m_pApi, *m_pList, m_depth + 1, m_isMail); + m_pApi->IterateHierarchy(&nextIter, pFolder); + } + pFolder->Release(); + } + else { + MAPI_TRACE0("GetStoreFolders - HandleHierarchyItem: Error opening folder entry.\n"); + return FALSE; + } + } + else + MAPI_TRACE1("GetStoreFolders - HandleHierarchyItem: Unhandled ObjectType: %ld\n", oType); + return TRUE; +} + +BOOL CMapiApi::GetStoreFolders(ULONG cbEid, LPENTRYID lpEid, CMapiFolderList& folders, int startDepth) +{ + // Fill in the array with the folders in the given store + if (!m_initialized || !m_lpSession) { + MAPI_TRACE0("MAPI not initialized for GetStoreFolders\n"); + return FALSE; + } + + m_lpMdb = NULL; + + CMsgStore * pStore = FindMessageStore(cbEid, lpEid); + BOOL bResult = FALSE; + LPSPropValue pVal; + + if (pStore && pStore->Open(m_lpSession, &m_lpMdb)) { + // Successful open, do the iteration of the store + pVal = GetMapiProperty(m_lpMdb, PR_IPM_SUBTREE_ENTRYID); + if (pVal) { + ULONG cbEntry; + LPENTRYID pEntry; + LPMAPIFOLDER lpSubTree = NULL; + + if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) { + // Open up the folder! + bResult = OpenEntry(cbEntry, pEntry, (LPUNKNOWN *)&lpSubTree); + MAPIFreeBuffer(pEntry); + if (bResult && lpSubTree) { + // Iterate the subtree with the results going into the folder list + CGetStoreFoldersIter iterHandler(this, folders, startDepth); + bResult = IterateHierarchy(&iterHandler, lpSubTree); + lpSubTree->Release(); + } + else { + MAPI_TRACE0("GetStoreFolders: Error opening sub tree.\n"); + } + } + else { + MAPI_TRACE0("GetStoreFolders: Error getting entryID from sub tree property val.\n"); + } + } + else { + MAPI_TRACE0("GetStoreFolders: Error getting sub tree property.\n"); + } + } + else { + MAPI_TRACE0("GetStoreFolders: Error opening message store.\n"); + } + + return bResult; +} + +BOOL CMapiApi::GetStoreAddressFolders(ULONG cbEid, LPENTRYID lpEid, CMapiFolderList& folders) +{ + // Fill in the array with the folders in the given store + if (!m_initialized || !m_lpSession) { + MAPI_TRACE0("MAPI not initialized for GetStoreAddressFolders\n"); + return FALSE; + } + + m_lpMdb = NULL; + + CMsgStore * pStore = FindMessageStore(cbEid, lpEid); + BOOL bResult = FALSE; + LPSPropValue pVal; + + if (pStore && pStore->Open(m_lpSession, &m_lpMdb)) { + // Successful open, do the iteration of the store + pVal = GetMapiProperty(m_lpMdb, PR_IPM_SUBTREE_ENTRYID); + if (pVal) { + ULONG cbEntry; + LPENTRYID pEntry; + LPMAPIFOLDER lpSubTree = NULL; + + if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) { + // Open up the folder! + bResult = OpenEntry(cbEntry, pEntry, (LPUNKNOWN *)&lpSubTree); + MAPIFreeBuffer(pEntry); + if (bResult && lpSubTree) { + // Iterate the subtree with the results going into the folder list + CGetStoreFoldersIter iterHandler(this, folders, 1, FALSE); + bResult = IterateHierarchy(&iterHandler, lpSubTree); + lpSubTree->Release(); + } + else { + MAPI_TRACE0("GetStoreAddressFolders: Error opening sub tree.\n"); + } + } + else { + MAPI_TRACE0("GetStoreAddressFolders: Error getting entryID from sub tree property val.\n"); + } + } + else { + MAPI_TRACE0("GetStoreAddressFolders: Error getting sub tree property.\n"); + } + } + else + MAPI_TRACE0("GetStoreAddressFolders: Error opening message store.\n"); + + return bResult; +} + + +BOOL CMapiApi::OpenStore(ULONG cbEid, LPENTRYID lpEid, LPMDB *ppMdb) +{ + if (!m_lpSession) { + MAPI_TRACE0("OpenStore called before a session was opened\n"); + return FALSE; + } + + CMsgStore * pStore = FindMessageStore(cbEid, lpEid); + if (pStore && pStore->Open(m_lpSession, ppMdb)) + return TRUE; + return FALSE; +} + + +BOOL CMapiApi::OpenEntry(ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN *ppOpen) +{ + if (!m_lpMdb) { + MAPI_TRACE0("OpenEntry called before the message store is open\n"); + return FALSE; + } + + return OpenMdbEntry(m_lpMdb, cbEntry, pEntryId, ppOpen); +} + +BOOL CMapiApi::OpenMdbEntry(LPMDB lpMdb, ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN *ppOpen) +{ + ULONG ulObjType; + HRESULT hr; + hr = m_lpSession->OpenEntry(cbEntry, + pEntryId, + NULL, + 0, + &ulObjType, + (LPUNKNOWN *) ppOpen); + if (FAILED(hr)) { + MAPI_TRACE2("OpenMdbEntry failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + return TRUE; +} + +enum { + ieidPR_ENTRYID = 0, + ieidPR_OBJECT_TYPE, + ieidMax +}; + +static const SizedSPropTagArray(ieidMax, ptaEid)= +{ + ieidMax, + { + PR_ENTRYID, + PR_OBJECT_TYPE, + } +}; + +BOOL CMapiApi::IterateContents(CMapiContentIter *pIter, LPMAPIFOLDER pFolder, ULONG flags) +{ + // flags can be 0 or MAPI_ASSOCIATED + // MAPI_ASSOCIATED is usually used for forms and views + + HRESULT hr; + LPMAPITABLE lpTable; + hr = pFolder->GetContentsTable(flags, &lpTable); + if (FAILED(hr)) { + MAPI_TRACE2("GetContentsTable failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + ULONG rowCount; + hr = lpTable->GetRowCount(0, &rowCount); + if (!rowCount) { + MAPI_TRACE0(" Empty Table\n"); + } + + hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0); + if (FAILED(hr)) { + lpTable->Release(); + MAPI_TRACE2("SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + if (FAILED(hr)) { + lpTable->Release(); + MAPI_TRACE2("SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + int cNumRows = 0; + LPSRowSet lpRow; + BOOL keepGoing = TRUE; + BOOL bResult = TRUE; + do { + lpRow = NULL; + hr = lpTable->QueryRows(1, 0, &lpRow); + if(HR_FAILED(hr)) { + MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr); + bResult = FALSE; + break; + } + + if(lpRow) { + cNumRows = lpRow->cRows; + if (cNumRows) { + LPENTRYID lpEID = (LPENTRYID) lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul; + keepGoing = HandleContentsItem(oType, cbEID, lpEID); + MAPI_TRACE1(" ObjectType: %ld\n", oType); + } + FreeProws(lpRow); + } + + } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing); + + lpTable->Release(); + return bResult; +} + +BOOL CMapiApi::HandleContentsItem(ULONG oType, ULONG cb, LPENTRYID pEntry) +{ + if (oType == MAPI_MESSAGE) { + LPMESSAGE pMsg; + if (OpenEntry(cb, pEntry, (LPUNKNOWN *) &pMsg)) { + LPSPropValue pVal; + pVal = GetMapiProperty(pMsg, PR_SUBJECT); + ReportStringProp("PR_SUBJECT:", pVal); + pVal = GetMapiProperty(pMsg, PR_DISPLAY_BCC); + ReportStringProp("PR_DISPLAY_BCC:", pVal); + pVal = GetMapiProperty(pMsg, PR_DISPLAY_CC); + ReportStringProp("PR_DISPLAY_CC:", pVal); + pVal = GetMapiProperty(pMsg, PR_DISPLAY_TO); + ReportStringProp("PR_DISPLAY_TO:", pVal); + pVal = GetMapiProperty(pMsg, PR_MESSAGE_CLASS); + ReportStringProp("PR_MESSAGE_CLASS:", pVal); + ListProperties(pMsg); + pMsg->Release(); + } + else { + MAPI_TRACE0(" Folder type - error opening\n"); + } + } + else + MAPI_TRACE1(" ObjectType: %ld\n", oType); + + return TRUE; +} + +void CMapiApi::ListProperties(LPMAPIPROP lpProp, BOOL getValues) +{ + LPSPropTagArray pArray; + HRESULT hr = lpProp->GetPropList(0, &pArray); + if (FAILED(hr)) { + MAPI_TRACE0(" Unable to retrieve property list\n"); + return; + } + ULONG count = 0; + LPMAPINAMEID FAR * lppPropNames; + SPropTagArray tagArray; + LPSPropTagArray lpTagArray = &tagArray; + tagArray.cValues = (ULONG)1; + nsCString desc; + for (ULONG i = 0; i < pArray->cValues; i++) { + GetPropTagName(pArray->aulPropTag[i], desc); + if (getValues) { + tagArray.aulPropTag[0] = pArray->aulPropTag[i]; + hr = lpProp->GetNamesFromIDs(&lpTagArray, nullptr, 0, &count, &lppPropNames); + if (hr == S_OK) + MAPIFreeBuffer(lppPropNames); + + LPSPropValue pVal = GetMapiProperty(lpProp, pArray->aulPropTag[i]); + if (pVal) { + desc += ", "; + ListPropertyValue(pVal, desc); + MAPIFreeBuffer(pVal); + } + } + MAPI_TRACE2(" Tag #%d: %s\n", (int) i, desc.get()); + } + + MAPIFreeBuffer(pArray); +} + +ULONG CMapiApi::GetEmailPropertyTag(LPMAPIPROP lpProp, LONG nameID) +{ +static GUID emailGUID = { + 0x00062004, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 +}; + + MAPINAMEID mapiNameID; + mapiNameID.lpguid = &emailGUID; + mapiNameID.ulKind = MNID_ID; + mapiNameID.Kind.lID = nameID; + + LPMAPINAMEID lpMapiNames = &mapiNameID; + LPSPropTagArray lpMailTagArray = nullptr; + + HRESULT result = lpProp->GetIDsFromNames(1L, &lpMapiNames, 0, &lpMailTagArray); + if (result == S_OK) + { + ULONG lTag = lpMailTagArray->aulPropTag[0]; + MAPIFreeBuffer(lpMailTagArray); + return lTag; + } + else + return 0L; +} + +BOOL CMapiApi::HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry) +{ + if (oType == MAPI_FOLDER) { + LPMAPIFOLDER pFolder; + if (OpenEntry(cb, pEntry, (LPUNKNOWN *) &pFolder)) { + LPSPropValue pVal; + pVal = GetMapiProperty(pFolder, PR_DISPLAY_NAME); + ReportStringProp("Folder name:", pVal); + IterateContents(NULL, pFolder); + IterateHierarchy(NULL, pFolder); + pFolder->Release(); + } + else { + MAPI_TRACE0(" Folder type - error opening\n"); + } + } + else + MAPI_TRACE1(" ObjectType: %ld\n", oType); + + return TRUE; +} + +BOOL CMapiApi::IterateHierarchy(CMapiHierarchyIter *pIter, LPMAPIFOLDER pFolder, ULONG flags) +{ + // flags can be CONVENIENT_DEPTH or 0 + // CONVENIENT_DEPTH will return all depths I believe instead + // of just children + HRESULT hr; + LPMAPITABLE lpTable; + hr = pFolder->GetHierarchyTable(flags, &lpTable); + if (HR_FAILED(hr)) { + m_lastError = hr; + MAPI_TRACE2("IterateHierarchy: GetContentsTable failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + ULONG rowCount; + hr = lpTable->GetRowCount(0, &rowCount); + if (!rowCount) { + lpTable->Release(); + return TRUE; + } + + hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0); + if (HR_FAILED(hr)) { + m_lastError = hr; + lpTable->Release(); + MAPI_TRACE2("IterateHierarchy: SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + if (HR_FAILED(hr)) { + m_lastError = hr; + lpTable->Release(); + MAPI_TRACE2("IterateHierarchy: SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + int cNumRows = 0; + LPSRowSet lpRow; + BOOL keepGoing = TRUE; + BOOL bResult = TRUE; + do { + lpRow = NULL; + hr = lpTable->QueryRows(1, 0, &lpRow); + + if(HR_FAILED(hr)) { + MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr); + m_lastError = hr; + bResult = FALSE; + break; + } + + if(lpRow) { + cNumRows = lpRow->cRows; + + if (cNumRows) { + LPENTRYID lpEntry = (LPENTRYID) lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cb = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul; + + if (pIter) + keepGoing = pIter->HandleHierarchyItem(oType, cb, lpEntry); + else + keepGoing = HandleHierarchyItem(oType, cb, lpEntry); + + } + FreeProws(lpRow); + } + } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing); + + lpTable->Release(); + + if (bResult && !keepGoing) + bResult = FALSE; + + return bResult; +} + + +enum { + itblPR_DISPLAY_NAME, + itblPR_ENTRYID, + itblMax +}; + +static const SizedSPropTagArray(itblMax, ptaTbl)= +{ + itblMax, + { + PR_DISPLAY_NAME, + PR_ENTRYID, + } +}; + +BOOL CMapiApi::IterateStores(CMapiFolderList& stores) +{ + stores.ClearAll(); + + if (!m_lpSession) { + MAPI_TRACE0("IterateStores called before session is open\n"); + m_lastError = -1; + return FALSE; + } + + + HRESULT hr; + + /* -- Some Microsoft sample code just to see if things are working --- *//* + + ULONG cbEIDStore; + LPENTRYID lpEIDStore; + + hr = HrMAPIFindDefaultMsgStore(m_lpSession, &cbEIDStore, &lpEIDStore); + if (HR_FAILED(hr)) { + MAPI_TRACE0("Default message store not found\n"); + // MessageBoxW(NULL, L"Message Store Not Found", NULL, MB_OK); + } + else { + LPMDB lpStore; + MAPI_TRACE0("Default Message store FOUND\n"); + hr = m_lpSession->OpenMsgStore(NULL, cbEIDStore, + lpEIDStore, NULL, + MDB_NO_MAIL | MDB_NO_DIALOG, &lpStore); + if (HR_FAILED(hr)) { + MAPI_TRACE1("Unable to open default message store: 0x%lx\n", hr); + } + else { + MAPI_TRACE0("Default message store OPENED\n"); + lpStore->Release(); + } + } + */ + + + + LPMAPITABLE lpTable; + + hr = m_lpSession->GetMsgStoresTable(0, &lpTable); + if (FAILED(hr)) { + MAPI_TRACE0("GetMsgStoresTable failed\n"); + m_lastError = hr; + return FALSE; + } + + + ULONG rowCount; + hr = lpTable->GetRowCount(0, &rowCount); + MAPI_TRACE1("MsgStores Table rowCount: %ld\n", rowCount); + + hr = lpTable->SetColumns((LPSPropTagArray)&ptaTbl, 0); + if (FAILED(hr)) { + lpTable->Release(); + MAPI_TRACE2("SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr); + m_lastError = hr; + return FALSE; + } + + hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + if (FAILED(hr)) { + lpTable->Release(); + MAPI_TRACE2("SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr); + m_lastError = hr; + return FALSE; + } + + int cNumRows = 0; + LPSRowSet lpRow; + BOOL keepGoing = TRUE; + BOOL bResult = TRUE; + do { + lpRow = NULL; + hr = lpTable->QueryRows(1, 0, &lpRow); + + if(HR_FAILED(hr)) { + MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr); + bResult = FALSE; + m_lastError = hr; + break; + } + + if(lpRow) { + cNumRows = lpRow->cRows; + + if (cNumRows) { + LPCTSTR lpStr = (LPCTSTR) lpRow->aRow[0].lpProps[itblPR_DISPLAY_NAME].Value.LPSZ; + LPENTRYID lpEID = (LPENTRYID) lpRow->aRow[0].lpProps[itblPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRow->aRow[0].lpProps[itblPR_ENTRYID].Value.bin.cb; + + // In the future, GetStoreInfo needs to somehow return + // whether or not the store is from an IMAP server. + // Currently, GetStoreInfo opens the store and attempts + // to get the hierarchy tree. If the tree is empty or + // does not exist, then szContents will be zero. We'll + // assume that any store that doesn't have anything in + // it's hierarchy tree is not a store we want to import - + // there would be nothing to import from anyway! + // Currently, this does exclude IMAP server accounts + // which is the desired behaviour. + + int strLen = strlen(lpStr); + char16_t * pwszStr = (char16_t *) moz_xmalloc((strLen + 1) * sizeof(WCHAR)); + if (!pwszStr) { + // out of memory + FreeProws(lpRow); + lpTable->Release(); + return FALSE; + } + ::MultiByteToWideChar(CP_ACP, 0, lpStr, strlen(lpStr) + 1, wwc(pwszStr), (strLen + 1) * sizeof(WCHAR)); + CMapiFolder *pFolder = new CMapiFolder(pwszStr, cbEID, lpEID, 0, MAPI_STORE); + free(pwszStr); + + long szContents = 1; + GetStoreInfo(pFolder, &szContents); + + MAPI_TRACE1(" DisplayName: %s\n", lpStr); + if (szContents) + stores.AddItem(pFolder); + else { + delete pFolder; + MAPI_TRACE0(" ^^^^^ Not added to store list\n"); + } + + keepGoing = TRUE; + } + FreeProws(lpRow); + } + } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing); + + lpTable->Release(); + + return bResult; +} + +void CMapiApi::GetStoreInfo(CMapiFolder *pFolder, long *pSzContents) +{ + HRESULT hr; + LPMDB lpMdb; + + if (pSzContents) + *pSzContents = 0; + + if (!OpenStore(pFolder->GetCBEntryID(), pFolder->GetEntryID(), &lpMdb)) + return; + + LPSPropValue pVal; + /* + pVal = GetMapiProperty(lpMdb, PR_DISPLAY_NAME); + ReportStringProp(" Message store name:", pVal); + pVal = GetMapiProperty(lpMdb, PR_MDB_PROVIDER); + ReportUIDProp(" Message store provider:", pVal); + pVal = GetMapiProperty(lpMdb, PR_COMMENT); + ReportStringProp(" Message comment:", pVal); + pVal = GetMapiProperty(lpMdb, PR_ACCESS_LEVEL); + ReportLongProp(" Message store Access Level:", pVal); + pVal = GetMapiProperty(lpMdb, PR_STORE_SUPPORT_MASK); + ReportLongProp(" Message store support mask:", pVal); + pVal = GetMapiProperty(lpMdb, PR_STORE_STATE); + ReportLongProp(" Message store state:", pVal); + pVal = GetMapiProperty(lpMdb, PR_OBJECT_TYPE); + ReportLongProp(" Message store object type:", pVal); + pVal = GetMapiProperty(lpMdb, PR_VALID_FOLDER_MASK); + ReportLongProp(" Message store valid folder mask:", pVal); + + pVal = GetMapiProperty(lpMdb, 0x8001001e); + ReportStringProp(" Message prop 0x8001001e:", pVal); + + // This key appears to be the OMI Account Manager account that corresponds + // to this message store. This is important for IMAP accounts + // since we may not want to import messages from an IMAP store! + // Seems silly if you ask me! + // In order to test this, we'll need the registry key to look under to determine + // if it contains the "IMAP Server" value, if it does then we are an + // IMAP store, if not, then we are a non-IMAP store - which may always mean + // a regular store that should be imported. + + pVal = GetMapiProperty(lpMdb, 0x80000003); + ReportLongProp(" Message prop 0x80000003:", pVal); + + // ListProperties(lpMdb); + */ + + pVal = GetMapiProperty(lpMdb, PR_IPM_SUBTREE_ENTRYID); + if (pVal) { + ULONG cbEntry; + LPENTRYID pEntry; + LPMAPIFOLDER lpSubTree = NULL; + + if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) { + // Open up the folder! + ULONG ulObjType; + hr = lpMdb->OpenEntry(cbEntry, pEntry, NULL, 0, &ulObjType, (LPUNKNOWN *) &lpSubTree); + MAPIFreeBuffer(pEntry); + if (SUCCEEDED(hr) && lpSubTree) { + // Find out if there are any contents in the + // tree. + LPMAPITABLE lpTable; + hr = lpSubTree->GetHierarchyTable(0, &lpTable); + if (HR_FAILED(hr)) { + MAPI_TRACE2("GetStoreInfo: GetHierarchyTable failed: 0x%lx, %d\n", (long)hr, (int)hr); + } + else { + ULONG rowCount; + hr = lpTable->GetRowCount(0, &rowCount); + lpTable->Release(); + if (SUCCEEDED(hr) && pSzContents) + *pSzContents = (long) rowCount; + } + + lpSubTree->Release(); + } + } + } +} + + +void CMapiApi::ClearMessageStores(void) +{ + if (m_pStores) { + CMsgStore * pStore; + for (size_t i = 0; i < m_pStores->Length(); i++) { + pStore = m_pStores->ElementAt(i); + delete pStore; + } + m_pStores->Clear(); + } +} + +void CMapiApi::AddMessageStore(CMsgStore *pStore) +{ + if (m_pStores) + m_pStores->AppendElement(pStore); +} + +CMsgStore * CMapiApi::FindMessageStore(ULONG cbEid, LPENTRYID lpEid) +{ + if (!m_lpSession) { + MAPI_TRACE0("FindMessageStore called before session is open\n"); + m_lastError = -1; + return NULL; + } + + ULONG result; + HRESULT hr; + CMsgStore * pStore; + for (size_t i = 0; i < m_pStores->Length(); i++) { + pStore = m_pStores->ElementAt(i); + hr = m_lpSession->CompareEntryIDs(cbEid, lpEid, pStore->GetCBEntryID(), pStore->GetLPEntryID(), + 0, &result); + if (HR_FAILED(hr)) { + MAPI_TRACE2("CompareEntryIDs failed: 0x%lx, %d\n", (long)hr, (int)hr); + m_lastError = hr; + return NULL; + } + if (result) { + return pStore; + } + } + + pStore = new CMsgStore(cbEid, lpEid); + AddMessageStore(pStore); + return pStore; +} + +// -------------------------------------------------------------------- +// Utility stuff +// -------------------------------------------------------------------- + +LPSPropValue CMapiApi::GetMapiProperty(LPMAPIPROP pProp, ULONG tag) +{ + if (!pProp) + return NULL; + + int sz = CbNewSPropTagArray(1); + SPropTagArray *pTag = (SPropTagArray *) new char[sz]; + pTag->cValues = 1; + pTag->aulPropTag[0] = tag; + LPSPropValue lpProp = NULL; + ULONG cValues = 0; + HRESULT hr = pProp->GetProps(pTag, 0, &cValues, &lpProp); + delete [] pTag; + if (HR_FAILED(hr) || (cValues != 1)) { + if (lpProp) + MAPIFreeBuffer(lpProp); + return NULL; + } + else { + if (PROP_TYPE(lpProp->ulPropTag) == PT_ERROR) { + if (lpProp->Value.l == MAPI_E_NOT_FOUND) { + MAPIFreeBuffer(lpProp); + lpProp = NULL; + } + } + } + + return lpProp; +} + +BOOL CMapiApi::IsLargeProperty(LPSPropValue pVal) +{ + return ((PROP_TYPE(pVal->ulPropTag) == PT_ERROR) && (pVal->Value.l == E_OUTOFMEMORY)); +} + +// The output buffer (result) must be freed with operator delete[] +BOOL CMapiApi::GetLargeProperty(LPMAPIPROP pProp, ULONG tag, void** result) +{ + LPSTREAM lpStream; + HRESULT hr = pProp->OpenProperty(tag, &IID_IStream, 0, 0, (LPUNKNOWN *)&lpStream); + if (HR_FAILED(hr)) + return FALSE; + STATSTG st; + BOOL bResult = TRUE; + hr = lpStream->Stat(&st, STATFLAG_NONAME); + if (HR_FAILED(hr)) + bResult = FALSE; + else { + if (!st.cbSize.QuadPart) + st.cbSize.QuadPart = 1; + char *pVal = new char[ (int) st.cbSize.QuadPart + 2]; + if (pVal) { + ULONG sz; + hr = lpStream->Read(pVal, (ULONG) st.cbSize.QuadPart, &sz); + if (HR_FAILED(hr)) { + bResult = FALSE; + delete[] pVal; + } + else { + // Just in case it's a UTF16 string + pVal[(int) st.cbSize.QuadPart] = pVal[(int) st.cbSize.QuadPart+1] = 0; + *result = pVal; + } + } + else + bResult = FALSE; + } + + lpStream->Release(); + + return bResult; +} + +BOOL CMapiApi::GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, nsCString& val) +{ + void* result; + if (!GetLargeProperty(pProp, tag, &result)) + return FALSE; + if (PROP_TYPE(tag) == PT_UNICODE) // unicode string + LossyCopyUTF16toASCII(nsDependentString(static_cast<wchar_t*>(result)), val); + else // either PT_STRING8 or some other binary - use as is + val.Assign(static_cast<char*>(result)); + delete[] result; + return TRUE; +} + +BOOL CMapiApi::GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, nsString& val) +{ + void* result; + if (!GetLargeProperty(pProp, tag, &result)) + return FALSE; + if (PROP_TYPE(tag) == PT_UNICODE) // We already get the unicode string + val.Assign(static_cast<wchar_t*>(result)); + else // either PT_STRING8 or some other binary + CStrToUnicode(static_cast<char*>(result), val); + delete[] result; + return TRUE; +} +// If the value is a string, get it... +BOOL CMapiApi::GetEntryIdFromProp(LPSPropValue pVal, ULONG& cbEntryId, + LPENTRYID& lpEntryId, BOOL delVal) +{ + if (!pVal) + return FALSE; + + BOOL bResult = TRUE; + switch(PROP_TYPE(pVal->ulPropTag)) { + case PT_BINARY: + cbEntryId = pVal->Value.bin.cb; + MAPIAllocateBuffer(cbEntryId, (LPVOID *) &lpEntryId); + memcpy(lpEntryId, pVal->Value.bin.lpb, cbEntryId); + break; + + default: + MAPI_TRACE0("EntryId not in BINARY prop value\n"); + bResult = FALSE; + break; + } + + if (pVal && delVal) + MAPIFreeBuffer(pVal); + + return bResult; +} + +BOOL CMapiApi::GetStringFromProp(LPSPropValue pVal, nsCString& val, BOOL delVal) +{ + BOOL bResult = TRUE; + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_STRING8)) + val = pVal->Value.lpszA; + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE)) + LossyCopyUTF16toASCII(nsDependentString(pVal->Value.lpszW), val); + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) + val.Truncate(); + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + val.Truncate(); + bResult = FALSE; + } + else { + if (pVal) { + MAPI_TRACE1("GetStringFromProp: invalid value, expecting string - %d\n", (int) PROP_TYPE(pVal->ulPropTag)); + } + else { + MAPI_TRACE0("GetStringFromProp: invalid value, expecting string, got null pointer\n"); + } + val.Truncate(); + bResult = FALSE; + } + if (pVal && delVal) + MAPIFreeBuffer(pVal); + + return bResult; +} + +BOOL CMapiApi::GetStringFromProp(LPSPropValue pVal, nsString& val, BOOL delVal) +{ + BOOL bResult = TRUE; + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_STRING8)) { + CStrToUnicode((const char *)pVal->Value.lpszA, val); + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE)) { + val = (char16_t *) pVal->Value.lpszW; + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { + val.Truncate(); + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + val.Truncate(); + bResult = FALSE; + } + else { + if (pVal) { + MAPI_TRACE1("GetStringFromProp: invalid value, expecting string - %d\n", (int) PROP_TYPE(pVal->ulPropTag)); + } + else { + MAPI_TRACE0("GetStringFromProp: invalid value, expecting string, got null pointer\n"); + } + val.Truncate(); + bResult = FALSE; + } + if (pVal && delVal) + MAPIFreeBuffer(pVal); + + return bResult; +} + + +LONG CMapiApi::GetLongFromProp(LPSPropValue pVal, BOOL delVal) +{ + LONG val = 0; + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_LONG)) { + val = pVal->Value.l; + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { + val = 0; + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + val = 0; + MAPI_TRACE0("GetLongFromProp: Error retrieving property\n"); + } + else { + MAPI_TRACE0("GetLongFromProp: invalid value, expecting long\n"); + } + if (pVal && delVal) + MAPIFreeBuffer(pVal); + + return val; +} + + +void CMapiApi::ReportUIDProp(const char *pTag, LPSPropValue pVal) +{ + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_BINARY)) { + if (pVal->Value.bin.cb != 16) { + MAPI_TRACE1("%s - INVALID, expecting 16 bytes of binary data for UID\n", pTag); + } + else { + nsIID uid; + memcpy(&uid, pVal->Value.bin.lpb, 16); + char * pStr = uid.ToString(); + if (pStr) { + MAPI_TRACE2("%s %s\n", pTag, (const char *)pStr); + NS_Free(pStr); + } + } + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { + MAPI_TRACE1("%s {NULL}\n", pTag); + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + MAPI_TRACE1("%s {Error retrieving property}\n", pTag); + } + else { + MAPI_TRACE1("%s invalid value, expecting binary\n", pTag); + } + if (pVal) + MAPIFreeBuffer(pVal); +} + +void CMapiApi::ReportLongProp(const char *pTag, LPSPropValue pVal) +{ + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_LONG)) { + nsCString num; + nsCString num2; + + num.AppendInt((int32_t) pVal->Value.l); + num2.AppendInt((int32_t) pVal->Value.l, 16); + MAPI_TRACE3("%s %s, 0x%s\n", pTag, num, num2); + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { + MAPI_TRACE1("%s {NULL}\n", pTag); + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + MAPI_TRACE1("%s {Error retrieving property}\n", pTag); + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + MAPI_TRACE1("%s {Error retrieving property}\n", pTag); + } + else { + MAPI_TRACE1("%s invalid value, expecting long\n", pTag); + } + if (pVal) + MAPIFreeBuffer(pVal); +} + +void CMapiApi::ReportStringProp(const char *pTag, LPSPropValue pVal) +{ + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_TSTRING)) { + nsCString val((LPCTSTR) (pVal->Value.LPSZ)); + MAPI_TRACE2("%s %s\n", pTag, val.get()); + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { + MAPI_TRACE1("%s {NULL}\n", pTag); + } + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + MAPI_TRACE1("%s {Error retrieving property}\n", pTag); + } + else { + MAPI_TRACE1("%s invalid value, expecting string\n", pTag); + } + if (pVal) + MAPIFreeBuffer(pVal); +} + +void CMapiApi::GetPropTagName(ULONG tag, nsCString& s) +{ + char numStr[256]; + PR_snprintf(numStr, 256, "0x%lx, %ld", tag, tag); + s = numStr; + switch(tag) { +#include "mapitagstrs.cpp" + } + s += ", data: "; + switch(PROP_TYPE(tag)) { + case PT_UNSPECIFIED: s += "PT_UNSPECIFIED"; break; + case PT_NULL: s += "PT_NULL"; break; + case PT_I2: s += "PT_I2"; break; + case PT_LONG: s += "PT_LONG"; break; + case PT_R4: s += "PT_R4"; break; + case PT_DOUBLE: s += "PT_DOUBLE"; break; + case PT_CURRENCY: s += "PT_CURRENCY"; break; + case PT_APPTIME: s += "PT_APPTIME"; break; + case PT_ERROR: s += "PT_ERROR"; break; + case PT_BOOLEAN: s += "PT_BOOLEAN"; break; + case PT_OBJECT: s += "PT_OBJECT"; break; + case PT_I8: s += "PT_I8"; break; + case PT_STRING8: s += "PT_STRING8"; break; + case PT_UNICODE: s += "PT_UNICODE"; break; + case PT_SYSTIME: s += "PT_SYSTIME"; break; + case PT_CLSID: s += "PT_CLSID"; break; + case PT_BINARY: s += "PT_BINARY"; break; + case PT_MV_I2: s += "PT_MV_I2"; break; + case PT_MV_LONG: s += "PT_MV_LONG"; break; + case PT_MV_R4: s += "PT_MV_R4"; break; + case PT_MV_DOUBLE: s += "PT_MV_DOUBLE"; break; + case PT_MV_CURRENCY: s += "PT_MV_CURRENCY"; break; + case PT_MV_APPTIME: s += "PT_MV_APPTIME"; break; + case PT_MV_SYSTIME: s += "PT_MV_SYSTIME"; break; + case PT_MV_STRING8: s += "PT_MV_STRING8"; break; + case PT_MV_BINARY: s += "PT_MV_BINARY"; break; + case PT_MV_UNICODE: s += "PT_MV_UNICODE"; break; + case PT_MV_CLSID: s += "PT_MV_CLSID"; break; + case PT_MV_I8: s += "PT_MV_I8"; break; + default: + s += "Unknown"; + } +} + +void CMapiApi::ListPropertyValue(LPSPropValue pVal, nsCString& s) +{ + nsCString strVal; + char nBuff[64]; + + s += "value: "; + switch (PROP_TYPE(pVal->ulPropTag)) { + case PT_STRING8: + GetStringFromProp(pVal, strVal, FALSE); + if (strVal.Length() > 60) { + strVal.SetLength(60); + strVal += "..."; + } + MsgReplaceSubstring(strVal, "\r", "\\r"); + MsgReplaceSubstring(strVal, "\n", "\\n"); + s += strVal; + break; + case PT_LONG: + s.AppendInt((int32_t) pVal->Value.l); + s += ", 0x"; + s.AppendInt((int32_t) pVal->Value.l, 16); + s += nBuff; + break; + case PT_BOOLEAN: + if (pVal->Value.b) + s += "True"; + else + s += "False"; + break; + case PT_NULL: + s += "--NULL--"; + break; + case PT_SYSTIME: { + /* + COleDateTime tm(pVal->Value.ft); + s += tm.Format(); + */ + s += "-- Figure out how to format time in mozilla, PT_SYSTIME --"; + } + break; + default: + s += "?"; + } +} + + + +// ------------------------------------------------------------------- +// Folder list stuff +// ------------------------------------------------------------------- +CMapiFolderList::CMapiFolderList() +{ +} + +CMapiFolderList::~CMapiFolderList() +{ + ClearAll(); +} + +void CMapiFolderList::AddItem(CMapiFolder *pFolder) +{ + EnsureUniqueName(pFolder); + GenerateFilePath(pFolder); + m_array.AppendElement(pFolder); +} + +void CMapiFolderList::ChangeName(nsString& name) +{ + if (name.IsEmpty()) { + name.AssignLiteral("1"); + return; + } + char16_t lastC = name.Last(); + if ((lastC >= '0') && (lastC <= '9')) { + lastC++; + if (lastC > '9') { + lastC = '1'; + name.SetCharAt(lastC, name.Length() - 1); + name.AppendLiteral("0"); + } + else { + name.SetCharAt(lastC, name.Length() - 1); + } + } + else { + name.AppendLiteral(" 2"); + } +} + +void CMapiFolderList::EnsureUniqueName(CMapiFolder *pFolder) +{ + // For everybody in the array before me with the SAME + // depth, my name must be unique + CMapiFolder * pCurrent; + int i; + BOOL done; + nsString name; + nsString cName; + + pFolder->GetDisplayName(name); + do { + done = TRUE; + i = m_array.Length() - 1; + while (i >= 0) { + pCurrent = GetAt(i); + if (pCurrent->GetDepth() == pFolder->GetDepth()) { + pCurrent->GetDisplayName(cName); + if (cName.Equals(name, nsCaseInsensitiveStringComparator())) { + ChangeName(name); + pFolder->SetDisplayName(name.get()); + done = FALSE; + break; + } + } + else if (pCurrent->GetDepth() < pFolder->GetDepth()) + break; + i--; + } + } while (!done); +} + +void CMapiFolderList::GenerateFilePath(CMapiFolder *pFolder) +{ + // A file path, includes all of my parent's path, plus mine + nsString name; + nsString path; + if (!pFolder->GetDepth()) { + pFolder->GetDisplayName(name); + pFolder->SetFilePath(name.get()); + return; + } + + CMapiFolder * pCurrent; + int i = m_array.Length() - 1; + while (i >= 0) { + pCurrent = GetAt(i); + if (pCurrent->GetDepth() == (pFolder->GetDepth() - 1)) { + pCurrent->GetFilePath(path); + path.AppendLiteral(".sbd\\"); + pFolder->GetDisplayName(name); + path += name; + pFolder->SetFilePath(path.get()); + return; + } + i--; + } + pFolder->GetDisplayName(name); + pFolder->SetFilePath(name.get()); +} + +void CMapiFolderList::ClearAll(void) +{ + CMapiFolder *pFolder; + for (size_t i = 0; i < m_array.Length(); i++) { + pFolder = GetAt(i); + delete pFolder; + } + m_array.Clear(); +} + +void CMapiFolderList::DumpList(void) +{ + CMapiFolder *pFolder; + nsString str; + int depth; + char prefix[256]; + + MAPI_TRACE0("Folder List ---------------------------------\n"); + for (size_t i = 0; i < m_array.Length(); i++) { + pFolder = GetAt(i); + depth = pFolder->GetDepth(); + pFolder->GetDisplayName(str); + depth *= 2; + if (depth > 255) + depth = 255; + memset(prefix, ' ', depth); + prefix[depth] = 0; +#ifdef MAPI_DEBUG + char *ansiStr = ToNewCString(str); + MAPI_TRACE2("%s%s: ", prefix, ansiStr); + NS_Free(ansiStr); +#endif + pFolder->GetFilePath(str); +#ifdef MAPI_DEBUG + ansiStr = ToNewCString(str); + MAPI_TRACE2("depth=%d, filePath=%s\n", pFolder->GetDepth(), ansiStr); + NS_Free(ansiStr); +#endif + } + MAPI_TRACE0("---------------------------------------------\n"); +} + + +CMapiFolder::CMapiFolder() +{ + m_objectType = MAPI_FOLDER; + m_cbEid = 0; + m_lpEid = NULL; + m_depth = 0; + m_doImport = TRUE; +} + +CMapiFolder::CMapiFolder(const char16_t *pDisplayName, ULONG cbEid, LPENTRYID lpEid, int depth, LONG oType) +{ + m_cbEid = 0; + m_lpEid = NULL; + SetDisplayName(pDisplayName); + SetEntryID(cbEid, lpEid); + SetDepth(depth); + SetObjectType(oType); + SetDoImport(TRUE); +} + +CMapiFolder::CMapiFolder(const CMapiFolder *pCopyFrom) +{ + m_lpEid = NULL; + m_cbEid = 0; + SetDoImport(pCopyFrom->GetDoImport()); + SetDisplayName(pCopyFrom->m_displayName.get()); + SetObjectType(pCopyFrom->GetObjectType()); + SetEntryID(pCopyFrom->GetCBEntryID(), pCopyFrom->GetEntryID()); + SetDepth(pCopyFrom->GetDepth()); + SetFilePath(pCopyFrom->m_mailFilePath.get()); +} + +CMapiFolder::~CMapiFolder() +{ + if (m_lpEid) + delete m_lpEid; +} + +void CMapiFolder::SetEntryID(ULONG cbEid, LPENTRYID lpEid) +{ + if (m_lpEid) + delete m_lpEid; + m_lpEid = NULL; + m_cbEid = cbEid; + if (cbEid) { + m_lpEid = new BYTE[cbEid]; + memcpy(m_lpEid, lpEid, cbEid); + } +} + +// --------------------------------------------------------------------- +// Message store stuff +// --------------------------------------------------------------------- + + +CMsgStore::CMsgStore(ULONG cbEid, LPENTRYID lpEid) +{ + m_lpEid = NULL; + m_lpMdb = NULL; + SetEntryID(cbEid, lpEid); +} + +CMsgStore::~CMsgStore() +{ + if (m_lpEid) + delete m_lpEid; + + if (m_lpMdb) { + ULONG flags = LOGOFF_NO_WAIT; + HRESULT hr = m_lpMdb->StoreLogoff(&flags); + m_lpMdb->Release(); + m_lpMdb = NULL; + } +} + +void CMsgStore::SetEntryID(ULONG cbEid, LPENTRYID lpEid) +{ + HRESULT hr; + + if (m_lpEid) + delete m_lpEid; + + m_lpEid = NULL; + if (cbEid) { + m_lpEid = new BYTE[cbEid]; + memcpy(m_lpEid, lpEid, cbEid); + } + m_cbEid = cbEid; + + if (m_lpMdb) { + ULONG flags = LOGOFF_NO_WAIT; + hr = m_lpMdb->StoreLogoff(&flags); + m_lpMdb->Release(); + m_lpMdb = NULL; + } +} + +BOOL CMsgStore::Open(LPMAPISESSION pSession, LPMDB *ppMdb) +{ + if (m_lpMdb) { + if (ppMdb) + *ppMdb = m_lpMdb; + return TRUE; + } + + BOOL bResult = TRUE; + HRESULT hr = pSession->OpenMsgStore(NULL, m_cbEid, (LPENTRYID)m_lpEid, NULL, MDB_NO_MAIL, &m_lpMdb); // MDB pointer + if (HR_FAILED(hr)) { + m_lpMdb = NULL; + MAPI_TRACE2("OpenMsgStore failed: 0x%lx, %d\n", (long)hr, (int)hr); + bResult = FALSE; + } + + if (ppMdb) + *ppMdb = m_lpMdb; + return bResult; +} + + + +// ------------------------------------------------------------ +// Contents Iterator +// ----------------------------------------------------------- + + +CMapiFolderContents::CMapiFolderContents(LPMDB lpMdb, ULONG cbEid, LPENTRYID lpEid) +{ + m_lpMdb = lpMdb; + m_fCbEid = cbEid; + m_fLpEid = new BYTE[cbEid]; + memcpy(m_fLpEid, lpEid, cbEid); + m_count = 0; + m_iterCount = 0; + m_failure = FALSE; + m_lastError = 0; + m_lpFolder = NULL; + m_lpTable = NULL; + m_lastLpEid = NULL; + m_lastCbEid = 0; +} + +CMapiFolderContents::~CMapiFolderContents() +{ + if (m_lastLpEid) + delete m_lastLpEid; + delete m_fLpEid; + if (m_lpTable) + m_lpTable->Release(); + if (m_lpFolder) + m_lpFolder->Release(); +} + + +BOOL CMapiFolderContents::SetUpIter(void) +{ + // First, open up the MAPIFOLDER object + ULONG ulObjType; + HRESULT hr; + hr = m_lpMdb->OpenEntry(m_fCbEid, (LPENTRYID) m_fLpEid, NULL, 0, &ulObjType, (LPUNKNOWN *) &m_lpFolder); + + if (FAILED(hr) || !m_lpFolder) { + m_lpFolder = NULL; + m_lastError = hr; + MAPI_TRACE2("CMapiFolderContents OpenEntry failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + if (ulObjType != MAPI_FOLDER) { + m_lastError = -1; + MAPI_TRACE0("CMapiFolderContents - bad object type, not a folder.\n"); + return FALSE; + } + + + hr = m_lpFolder->GetContentsTable(0, &m_lpTable); + if (FAILED(hr) || !m_lpTable) { + m_lastError = hr; + m_lpTable = NULL; + MAPI_TRACE2("CMapiFolderContents - GetContentsTable failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + hr = m_lpTable->GetRowCount(0, &m_count); + if (FAILED(hr)) { + m_lastError = hr; + MAPI_TRACE0("CMapiFolderContents - GetRowCount failed\n"); + return FALSE; + } + + hr = m_lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0); + if (FAILED(hr)) { + m_lastError = hr; + MAPI_TRACE2("CMapiFolderContents - SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + hr = m_lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + if (FAILED(hr)) { + m_lastError = hr; + MAPI_TRACE2("CMapiFolderContents - SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + return TRUE; +} + + +BOOL CMapiFolderContents::GetNext(ULONG *pcbEid, LPENTRYID *ppEid, ULONG *poType, BOOL *pDone) +{ + *pDone = FALSE; + if (m_failure) + return FALSE; + if (!m_lpFolder) { + if (!SetUpIter()) { + m_failure = TRUE; + return FALSE; + } + if (!m_count) { + *pDone = TRUE; + return TRUE; + } + } + + int cNumRows = 0; + LPSRowSet lpRow = NULL; + HRESULT hr = m_lpTable->QueryRows(1, 0, &lpRow); + + if(HR_FAILED(hr)) { + m_lastError = hr; + m_failure = TRUE; + MAPI_TRACE2("CMapiFolderContents - QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + if(lpRow) { + cNumRows = lpRow->cRows; + if (cNumRows) { + LPENTRYID lpEID = (LPENTRYID) lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul; + + if (m_lastCbEid != cbEID) { + if (m_lastLpEid) + delete m_lastLpEid; + m_lastLpEid = new BYTE[cbEID]; + m_lastCbEid = cbEID; + } + memcpy(m_lastLpEid, lpEID, cbEID); + + *ppEid = (LPENTRYID) m_lastLpEid; + *pcbEid = cbEID; + *poType = oType; + } + else + *pDone = TRUE; + CMapiApi::FreeProws(lpRow); + } + else + *pDone = TRUE; + + return TRUE; +} + diff --git a/mailnews/import/outlook/src/MapiApi.h b/mailnews/import/outlook/src/MapiApi.h new file mode 100644 index 000000000..8b59b80d8 --- /dev/null +++ b/mailnews/import/outlook/src/MapiApi.h @@ -0,0 +1,265 @@ +/* -*- 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 MapiApi_h___ +#define MapiApi_h___ + +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +#include <stdio.h> + +#include <windows.h> +#include <mapi.h> +#include <mapix.h> +#include <mapidefs.h> +#include <mapicode.h> +#include <mapitags.h> +#include <mapiutil.h> +// wabutil.h expects mapiutil to define _MAPIUTIL_H but it actually +// defines _MAPIUTIL_H_ +#define _MAPIUTIL_H + +#ifndef PR_INTERNET_CPID +#define PR_INTERNET_CPID (PROP_TAG(PT_LONG,0x3FDE)) +#endif +#ifndef MAPI_NATIVE_BODY +#define MAPI_NATIVE_BODY (0x00010000) +#endif +#ifndef MAPI_NATIVE_BODY_TYPE_RTF +#define MAPI_NATIVE_BODY_TYPE_RTF (0x00000001) +#endif +#ifndef MAPI_NATIVE_BODY_TYPE_HTML +#define MAPI_NATIVE_BODY_TYPE_HTML (0x00000002) +#endif +#ifndef MAPI_NATIVE_BODY_TYPE_PLAINTEXT +#define MAPI_NATIVE_BODY_TYPE_PLAINTEXT (0x00000004) +#endif +#ifndef PR_BODY_HTML_A +#define PR_BODY_HTML_A (PROP_TAG(PT_STRING8,0x1013)) +#endif +#ifndef PR_BODY_HTML_W +#define PR_BODY_HTML_W (PROP_TAG(PT_UNICODE,0x1013)) +#endif +#ifndef PR_BODY_HTML +#define PR_BODY_HTML (PROP_TAG(PT_TSTRING,0x1013)) +#endif + +class CMapiFolderList; +class CMsgStore; +class CMapiFolder; + +class CMapiContentIter { +public: + virtual BOOL HandleContentItem(ULONG oType, ULONG cb, LPENTRYID pEntry) = 0; +}; + +class CMapiHierarchyIter { +public: + virtual BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry) = 0; +}; + +class CMapiApi { +public: + CMapiApi(); + ~CMapiApi(); + + static BOOL LoadMapi(void); + static BOOL LoadMapiEntryPoints(void); + static void UnloadMapi(void); + + static HINSTANCE m_hMapi32; + + static void MAPIUninitialize(void); + static HRESULT MAPIInitialize(LPVOID lpInit); + static SCODE MAPIAllocateBuffer(ULONG cbSize, LPVOID FAR * lppBuffer); + static ULONG MAPIFreeBuffer(LPVOID lpBuff); + static HRESULT MAPILogonEx(ULONG ulUIParam, LPTSTR lpszProfileName, LPTSTR lpszPassword, FLAGS flFlags, LPMAPISESSION FAR * lppSession); + static HRESULT OpenStreamOnFile(LPALLOCATEBUFFER lpAllocateBuffer, LPFREEBUFFER lpFreeBuffer, ULONG ulFlags, LPTSTR lpszFileName, LPTSTR lpszPrefix, LPSTREAM FAR * lppStream); + static void FreeProws(LPSRowSet prows); + + + BOOL Initialize(void); + BOOL LogOn(void); + + void AddMessageStore(CMsgStore *pStore); + void SetCurrentMsgStore(LPMDB lpMdb) { m_lpMdb = lpMdb;} + + // Open any given entry from the current Message Store + BOOL OpenEntry(ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN *ppOpen); + static BOOL OpenMdbEntry(LPMDB lpMdb, ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN *ppOpen); + + // Fill in the folders list with the hierarchy from the given + // message store. + BOOL GetStoreFolders(ULONG cbEid, LPENTRYID lpEid, CMapiFolderList& folders, int startDepth); + BOOL GetStoreAddressFolders(ULONG cbEid, LPENTRYID lpEid, CMapiFolderList& folders); + BOOL OpenStore(ULONG cbEid, LPENTRYID lpEid, LPMDB *ppMdb); + + // Iteration + BOOL IterateStores(CMapiFolderList& list); + BOOL IterateContents(CMapiContentIter *pIter, LPMAPIFOLDER pFolder, ULONG flags = 0); + BOOL IterateHierarchy(CMapiHierarchyIter *pIter, LPMAPIFOLDER pFolder, ULONG flags = 0); + + // Properties + static LPSPropValue GetMapiProperty(LPMAPIPROP pProp, ULONG tag); + // If delVal is true, functions will call CMapiApi::MAPIFreeBuffer on pVal. + static BOOL GetEntryIdFromProp(LPSPropValue pVal, ULONG& cbEntryId, + LPENTRYID& lpEntryId, BOOL delVal = TRUE); + static BOOL GetStringFromProp(LPSPropValue pVal, nsCString& val, BOOL delVal = TRUE); + static BOOL GetStringFromProp(LPSPropValue pVal, nsString& val, BOOL delVal = TRUE); + static LONG GetLongFromProp(LPSPropValue pVal, BOOL delVal = TRUE); + static BOOL GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, nsCString& val); + static BOOL GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, nsString& val); + static BOOL IsLargeProperty(LPSPropValue pVal); + static ULONG GetEmailPropertyTag(LPMAPIPROP lpProp, LONG nameID); + + static BOOL GetRTFPropertyDecodedAsUTF16(LPMAPIPROP pProp, nsString& val, + unsigned long& nativeBodyType, + unsigned long codepage = 0); + + // Debugging & reporting stuff + static void ListProperties(LPMAPIPROP lpProp, BOOL getValues = TRUE); + static void ListPropertyValue(LPSPropValue pVal, nsCString& s); + +protected: + BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry); + BOOL HandleContentsItem(ULONG oType, ULONG cb, LPENTRYID pEntry); + void GetStoreInfo(CMapiFolder *pFolder, long *pSzContents); + + // array of available message stores, cached so that + // message stores are only opened once, preventing multiple + // logon's by the user if the store requires a logon. + CMsgStore * FindMessageStore(ULONG cbEid, LPENTRYID lpEid); + void ClearMessageStores(void); + + static void CStrToUnicode(const char *pStr, nsString& result); + + // Debugging & reporting stuff + static void GetPropTagName(ULONG tag, nsCString& s); + static void ReportStringProp(const char *pTag, LPSPropValue pVal); + static void ReportUIDProp(const char *pTag, LPSPropValue pVal); + static void ReportLongProp(const char *pTag, LPSPropValue pVal); + + +private: + static int m_clients; + static BOOL m_initialized; + static nsTArray<CMsgStore*> * m_pStores; + static LPMAPISESSION m_lpSession; + static LPMDB m_lpMdb; + static HRESULT m_lastError; + static char16_t * m_pUniBuff; + static int m_uniBuffLen; + + static BOOL GetLargeProperty(LPMAPIPROP pProp, ULONG tag, void** result); +}; + +class CMapiFolder { +public: + CMapiFolder(); + CMapiFolder(const CMapiFolder *pCopyFrom); + CMapiFolder(const char16_t *pDisplayName, ULONG cbEid, LPENTRYID lpEid, int depth, LONG oType = MAPI_FOLDER); + ~CMapiFolder(); + + void SetDoImport(BOOL doIt) { m_doImport = doIt;} + void SetObjectType(long oType) { m_objectType = oType;} + void SetDisplayName(const char16_t *pDisplayName) { m_displayName = pDisplayName;} + void SetEntryID(ULONG cbEid, LPENTRYID lpEid); + void SetDepth(int depth) { m_depth = depth;} + void SetFilePath(const char16_t *pFilePath) { m_mailFilePath = pFilePath;} + + BOOL GetDoImport(void) const { return m_doImport;} + LONG GetObjectType(void) const { return m_objectType;} + void GetDisplayName(nsString& name) const { name = m_displayName;} + void GetFilePath(nsString& path) const { path = m_mailFilePath;} + BOOL IsStore(void) const { return m_objectType == MAPI_STORE;} + BOOL IsFolder(void) const { return m_objectType == MAPI_FOLDER;} + int GetDepth(void) const { return m_depth;} + + LPENTRYID GetEntryID(ULONG *pCb = NULL) const { if (pCb) *pCb = m_cbEid; return (LPENTRYID) m_lpEid;} + ULONG GetCBEntryID(void) const { return m_cbEid;} + +private: + LONG m_objectType; + ULONG m_cbEid; + BYTE * m_lpEid; + nsString m_displayName; + int m_depth; + nsString m_mailFilePath; + BOOL m_doImport; + +}; + +class CMapiFolderList { +public: + CMapiFolderList(); + ~CMapiFolderList(); + + void AddItem(CMapiFolder *pFolder); + CMapiFolder * GetItem(int index) { if ((index >= 0) && (index < (int)m_array.Length())) return GetAt(index); else return NULL;} + void ClearAll(void); + + // Debugging and reporting + void DumpList(void); + + CMapiFolder * GetAt(int index) { return m_array.ElementAt(index);} + int GetSize(void) { return m_array.Length();} + +protected: + void EnsureUniqueName(CMapiFolder *pFolder); + void GenerateFilePath(CMapiFolder *pFolder); + void ChangeName(nsString& name); + +private: + nsTArray<CMapiFolder*> m_array; +}; + + +class CMsgStore { +public: + CMsgStore(ULONG cbEid = 0, LPENTRYID lpEid = NULL); + ~CMsgStore(); + + void SetEntryID(ULONG cbEid, LPENTRYID lpEid); + BOOL Open(LPMAPISESSION pSession, LPMDB *ppMdb); + + ULONG GetCBEntryID(void) { return m_cbEid;} + LPENTRYID GetLPEntryID(void) { return (LPENTRYID) m_lpEid;} + +private: + ULONG m_cbEid; + BYTE * m_lpEid; + LPMDB m_lpMdb; +}; + + +class CMapiFolderContents { +public: + CMapiFolderContents(LPMDB lpMdb, ULONG cbEID, LPENTRYID lpEid); + ~CMapiFolderContents(); + + BOOL GetNext(ULONG *pcbEid, LPENTRYID *ppEid, ULONG *poType, BOOL *pDone); + + ULONG GetCount(void) { return m_count;} + +protected: + BOOL SetUpIter(void); + +private: + HRESULT m_lastError; + BOOL m_failure; + LPMDB m_lpMdb; + LPMAPIFOLDER m_lpFolder; + LPMAPITABLE m_lpTable; + ULONG m_fCbEid; + BYTE * m_fLpEid; + ULONG m_count; + ULONG m_iterCount; + BYTE * m_lastLpEid; + ULONG m_lastCbEid; +}; + + +#endif /* MapiApi_h__ */ diff --git a/mailnews/import/outlook/src/MapiDbgLog.h b/mailnews/import/outlook/src/MapiDbgLog.h new file mode 100644 index 000000000..668418c42 --- /dev/null +++ b/mailnews/import/outlook/src/MapiDbgLog.h @@ -0,0 +1,40 @@ +/* -*- 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 MapiDbgLog_h___ +#define MapiDbgLog_h___ + +/* +#ifdef NS_DEBUG +#define MAPI_DEBUG 1 +#endif +*/ + +#ifdef MAPI_DEBUG +#include <stdio.h> + +#define MAPI_DUMP_STRING(x) printf("%s", (const char *)x) +#define MAPI_TRACE0(x) printf(x) +#define MAPI_TRACE1(x, y) printf(x, y) +#define MAPI_TRACE2(x, y, z) printf(x, y, z) +#define MAPI_TRACE3(x, y, z, a) printf(x, y, z, a) +#define MAPI_TRACE4(x, y, z, a, b) printf(x, y, z, a, b) + + +#else + +#define MAPI_DUMP_STRING(x) +#define MAPI_TRACE0(x) +#define MAPI_TRACE1(x, y) +#define MAPI_TRACE2(x, y, z) +#define MAPI_TRACE3(x, y, z, a) +#define MAPI_TRACE4(x, y, z, a, b) + +#endif + + + +#endif /* MapiDbgLog_h___ */ + diff --git a/mailnews/import/outlook/src/MapiMessage.cpp b/mailnews/import/outlook/src/MapiMessage.cpp new file mode 100644 index 000000000..52ec2e4c0 --- /dev/null +++ b/mailnews/import/outlook/src/MapiMessage.cpp @@ -0,0 +1,1474 @@ +/* -*- 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 INITGUID +#define INITGUID +#endif + +#ifndef USES_IID_IMessage +#define USES_IID_IMessage +#endif + +#include "nscore.h" +#include <time.h> +#include "nsStringGlue.h" +#include "nsDirectoryServiceDefs.h" +#include "nsMsgUtils.h" +#include "nsMimeTypes.h" +#include "nsIOutputStream.h" + +#include "nsMsgCompCID.h" +#include "nsIMutableArray.h" +#include "MapiDbgLog.h" +#include "MapiApi.h" + +#include "MapiMimeTypes.h" + +#include <algorithm> +#include "nsMsgI18N.h" +#include "nsICharsetConverterManager.h" +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "MapiMessage.h" + +#include "nsOutlookMail.h" + +// needed for the call the OpenStreamOnFile +extern LPMAPIALLOCATEBUFFER gpMapiAllocateBuffer; +extern LPMAPIFREEBUFFER gpMapiFreeBuffer; + +// Sample From line: From - 1 Jan 1965 00:00:00 + +typedef const char * PC_S8; + +static const char * kWhitespace = "\b\t\r\n "; +static const char * sFromLine = "From - "; +static const char * sFromDate = "Mon Jan 1 00:00:00 1965"; +static const char * sDaysOfWeek[7] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +static const char *sMonths[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +CMapiMessage::CMapiMessage(LPMESSAGE lpMsg) + : m_lpMsg(lpMsg), m_dldStateHeadersOnly(false), m_msgFlags(0) +{ + nsresult rv; + m_pIOService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return; + + FetchHeaders(); + if (ValidState()) { + BuildFromLine(); + FetchFlags(); + GetDownloadState(); + if (FullMessageDownloaded()) { + FetchBody(); + ProcessAttachments(); + } + } +} + +CMapiMessage::~CMapiMessage() +{ + ClearAttachments(); + if (m_lpMsg) + m_lpMsg->Release(); +} + +void CMapiMessage::FormatDateTime(SYSTEMTIME& tm, nsCString& s, bool includeTZ) +{ + long offset = _timezone; + s += sDaysOfWeek[tm.wDayOfWeek]; + s += ", "; + s.AppendInt((int32_t) tm.wDay); + s += " "; + s += sMonths[tm.wMonth - 1]; + s += " "; + s.AppendInt((int32_t) tm.wYear); + s += " "; + int val = tm.wHour; + if (val < 10) + s += "0"; + s.AppendInt((int32_t) val); + s += ":"; + val = tm.wMinute; + if (val < 10) + s += "0"; + s.AppendInt((int32_t) val); + s += ":"; + val = tm.wSecond; + if (val < 10) + s += "0"; + s.AppendInt((int32_t) val); + if (includeTZ) { + s += " "; + if (offset < 0) { + offset *= -1; + s += "+"; + } + else + s += "-"; + offset /= 60; + val = (int) (offset / 60); + if (val < 10) + s += "0"; + s.AppendInt((int32_t) val); + val = (int) (offset % 60); + if (val < 10) + s += "0"; + s.AppendInt((int32_t) val); + } +} + +bool CMapiMessage::EnsureHeader(CMapiMessageHeaders::SpecialHeader special, + ULONG mapiTag) +{ + if (m_headers.Value(special)) + return true; + + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, mapiTag); + bool success = false; + if (pVal) { + if (PROP_TYPE(pVal->ulPropTag) == PT_STRING8) { + if (pVal->Value.lpszA && strlen(pVal->Value.lpszA)) { + m_headers.SetValue(special, pVal->Value.lpszA); + success = true; + } + } + else if (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) { + if (pVal->Value.lpszW && wcslen(pVal->Value.lpszW)) { + m_headers.SetValue(special, NS_ConvertUTF16toUTF8(pVal->Value.lpszW).get()); + success = true; + } + } + CMapiApi::MAPIFreeBuffer(pVal); + } + + return success; +} + +bool CMapiMessage::EnsureDate() +{ + if (m_headers.Value(CMapiMessageHeaders::hdrDate)) + return true; + + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_DELIVERY_TIME); + if (!pVal) + pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_CREATION_TIME); + if (pVal) { + SYSTEMTIME st; + // the following call returns UTC + ::FileTimeToSystemTime(&(pVal->Value.ft), &st); + CMapiApi::MAPIFreeBuffer(pVal); + // FormatDateTime would append the local time zone, so don't use it. + // Instead, we just append +0000 for GMT/UTC here. + nsCString str; + FormatDateTime(st, str, false); + str += " +0000"; + m_headers.SetValue(CMapiMessageHeaders::hdrDate, str.get()); + return true; + } + + return false; +} + +void CMapiMessage::BuildFromLine(void) +{ + m_fromLine = sFromLine; + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_CREATION_TIME); + if (pVal) { + SYSTEMTIME st; + ::FileTimeToSystemTime(&(pVal->Value.ft), &st); + CMapiApi::MAPIFreeBuffer(pVal); + FormatDateTime(st, m_fromLine, FALSE); + } + else + m_fromLine += sFromDate; + + m_fromLine += "\x0D\x0A"; +} + +#ifndef dispidHeaderItem +#define dispidHeaderItem 0x8578 +#endif +DEFINE_OLEGUID(PSETID_Common, MAKELONG(0x2000+(8),0x0006),0,0); + +void CMapiMessage::GetDownloadState() +{ + // See http://support.microsoft.com/kb/912239 + HRESULT hRes = S_OK; + ULONG ulVal = 0; + LPSPropValue lpPropVal = NULL; + LPSPropTagArray lpNamedPropTag = NULL; + MAPINAMEID NamedID = {0}; + LPMAPINAMEID lpNamedID = NULL; + + NamedID.lpguid = (LPGUID) &PSETID_Common; + NamedID.ulKind = MNID_ID; + NamedID.Kind.lID = dispidHeaderItem; + lpNamedID = &NamedID; + + hRes = m_lpMsg->GetIDsFromNames(1, &lpNamedID, NULL, &lpNamedPropTag); + + if (lpNamedPropTag && 1 == lpNamedPropTag->cValues) + { + lpNamedPropTag->aulPropTag[0] = CHANGE_PROP_TYPE(lpNamedPropTag->aulPropTag[0], PT_LONG); + + //Get the value of the property. + hRes = m_lpMsg->GetProps(lpNamedPropTag, 0, &ulVal, &lpPropVal); + if (lpPropVal && 1 == ulVal && PT_LONG == PROP_TYPE(lpPropVal->ulPropTag) && + lpPropVal->Value.ul) + m_dldStateHeadersOnly = true; + } + + CMapiApi::MAPIFreeBuffer(lpPropVal); + CMapiApi::MAPIFreeBuffer(lpNamedPropTag); +} + +// Headers - fetch will get PR_TRANSPORT_MESSAGE_HEADERS +// or if they do not exist will build a header from +// PR_DISPLAY_TO, _CC, _BCC +// PR_SUBJECT +// PR_MESSAGE_RECIPIENTS +// and PR_CREATION_TIME if needed? +bool CMapiMessage::FetchHeaders(void) +{ + ULONG tag = PR_TRANSPORT_MESSAGE_HEADERS_A; + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, tag); + if (!pVal) + pVal = CMapiApi::GetMapiProperty(m_lpMsg, tag = PR_TRANSPORT_MESSAGE_HEADERS_W); + if (pVal) { + if (CMapiApi::IsLargeProperty(pVal)) { + nsCString headers; + CMapiApi::GetLargeStringProperty(m_lpMsg, tag, headers); + m_headers.Assign(headers.get()); + } + else if ((PROP_TYPE(pVal->ulPropTag) == PT_STRING8) && + (pVal->Value.lpszA) && (*(pVal->Value.lpszA))) + m_headers.Assign(pVal->Value.lpszA); + else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) && + (pVal->Value.lpszW) && (*(pVal->Value.lpszW))) { + nsCString headers; + LossyCopyUTF16toASCII(nsDependentString(pVal->Value.lpszW), headers); + m_headers.Assign(headers.get()); + } + + CMapiApi::MAPIFreeBuffer(pVal); + } + + EnsureDate(); + if (!EnsureHeader(CMapiMessageHeaders::hdrFrom, PR_SENDER_NAME_W)) + EnsureHeader(CMapiMessageHeaders::hdrFrom, PR_SENDER_EMAIL_ADDRESS_W); + EnsureHeader(CMapiMessageHeaders::hdrSubject, PR_SUBJECT_W); + EnsureHeader(CMapiMessageHeaders::hdrTo, PR_DISPLAY_TO_W); + EnsureHeader(CMapiMessageHeaders::hdrCc, PR_DISPLAY_CC_W); + EnsureHeader(CMapiMessageHeaders::hdrBcc, PR_DISPLAY_BCC_W); + + ProcessContentType(); + + return !m_headers.IsEmpty(); +} + +// Mime-Version: 1.0 +// Content-Type: text/plain; charset="US-ASCII" +// Content-Type: multipart/mixed; boundary="=====================_874475278==_" + +void CMapiMessage::ProcessContentType() +{ + m_mimeContentType.Truncate(); + m_mimeBoundary.Truncate(); + m_mimeCharset.Truncate(); + + const char* contentType = m_headers.Value(CMapiMessageHeaders::hdrContentType); + if (!contentType) + return; + + const char *begin = contentType, *end; + nsCString tStr; + + // Note: this isn't a complete parser, the content type + // we extract could have rfc822 comments in it + while (*begin && IsSpace(*begin)) + begin++; + if (!(*begin)) + return; + end = begin; + while (*end && (*end != ';')) + end++; + m_mimeContentType.Assign(begin, end-begin); + if (!(*end)) + return; + // look for "boundary=" + begin = end + 1; + bool haveB; + bool haveC; + while (*begin) { + haveB = false; + haveC = false; + while (*begin && IsSpace(*begin)) + begin++; + if (!(*begin)) + return; + end = begin; + while (*end && (*end != '=')) + end++; + if (end - begin) { + tStr.Assign(begin, end-begin); + if (tStr.LowerCaseEqualsLiteral("boundary")) + haveB = true; + else if (tStr.LowerCaseEqualsLiteral("charset")) + haveC = true; + } + if (!(*end)) + return; + begin = end+1; + while (*begin && IsSpace(*begin)) + begin++; + if (*begin == '"') { + begin++; + bool slash = false; + tStr.Truncate(); + while (*begin) { + if (slash) { + slash = false; + tStr.Append(*begin); + } + else if (*begin == '"') + break; + else if (*begin != '\\') + tStr.Append(*begin); + else + slash = true; + begin++; + } + if (haveB) { + m_mimeBoundary = tStr; + haveB = false; + } + if (haveC) { + m_mimeCharset = tStr; + haveC = false; + } + if (!(*begin)) + return; + begin++; + } + tStr.Truncate(); + while (*begin && (*begin != ';')) { + tStr.Append(*(begin++)); + } + if (haveB) { + tStr.Trim(kWhitespace); + m_mimeBoundary = tStr; + } + if (haveC) { + tStr.Trim(kWhitespace); + m_mimeCharset = tStr; + } + if (*begin) + begin++; + } +} + +const char* CpToCharset(unsigned int cp) +{ + struct CODEPAGE_TO_CHARSET { + unsigned long cp; + const char* charset; + }; + + // This table is based on http://msdn.microsoft.com/en-us/library/dd317756(v=VS.85).aspx#1; + // Please extend as appropriate. The codepage values are sorted ascending. + static const CODEPAGE_TO_CHARSET cptocharset[] = + { + {37, "IBM037"}, // IBM EBCDIC US-Canada + {437, "IBM437"}, //OEM United States + {500, "IBM500"}, //IBM EBCDIC International + {708, "ASMO-708"}, //Arabic (ASMO 708) + //709 Arabic (ASMO-449+, BCON V4) + //710 Arabic - Transparent Arabic + {720, "DOS-720"}, //Arabic (Transparent ASMO); Arabic (DOS) + {737, "ibm737"}, // OEM Greek (formerly 437G); Greek (DOS) + {775, "ibm775"}, // OEM Baltic; Baltic (DOS) + {850, "ibm850"}, // OEM Multilingual Latin 1; Western European (DOS) + {852, "ibm852"}, // OEM Latin 2; Central European (DOS) + {855, "IBM855"}, // OEM Cyrillic (primarily Russian) + {857, "ibm857"}, // OEM Turkish; Turkish (DOS) + {858, "IBM00858"}, // OEM Multilingual Latin 1 + Euro symbol + {860, "IBM860"}, // OEM Portuguese; Portuguese (DOS) + {861, "ibm861"}, // OEM Icelandic; Icelandic (DOS) + {862, "DOS-862"}, // OEM Hebrew; Hebrew (DOS) + {863, "IBM863"}, // OEM French Canadian; French Canadian (DOS) + {864, "IBM864"}, // OEM Arabic; Arabic (864) + {865, "IBM865"}, // OEM Nordic; Nordic (DOS) + {866, "cp866"}, // OEM Russian; Cyrillic (DOS) + {869, "ibm869"}, // OEM Modern Greek; Greek, Modern (DOS) + {870, "IBM870"}, // IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2 + {874, "windows-874"}, // ANSI/OEM Thai (same as 28605, ISO 8859-15); Thai (Windows) + {875, "cp875"}, // IBM EBCDIC Greek Modern + {932, "shift_jis"}, // ANSI/OEM Japanese; Japanese (Shift-JIS) + {936, "gb2312"}, // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312) + {949, "ks_c_5601-1987"}, // ANSI/OEM Korean (Unified Hangul Code) + {950, "big5"}, // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5) + {1026, "IBM1026"}, // IBM EBCDIC Turkish (Latin 5) + {1047, "IBM01047"}, // IBM EBCDIC Latin 1/Open System + {1140, "IBM01140"}, // IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro) + {1141, "IBM01141"}, // IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro) + {1142, "IBM01142"}, // IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro) + {1143, "IBM01143"}, // IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro) + {1144, "IBM01144"}, // IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro) + {1145, "IBM01145"}, // IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro) + {1146, "IBM01146"}, // IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro) + {1147, "IBM01147"}, // IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro) + {1148, "IBM01148"}, // IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro) + {1149, "IBM01149"}, // IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro) + {1200, "utf-16"}, // Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications + {1201, "unicodeFFFE"}, // Unicode UTF-16, big endian byte order; available only to managed applications + {1250, "windows-1250"}, // ANSI Central European; Central European (Windows) + {1251, "windows-1251"}, // ANSI Cyrillic; Cyrillic (Windows) + {1252, "windows-1252"}, // ANSI Latin 1; Western European (Windows) + {1253, "windows-1253"}, // ANSI Greek; Greek (Windows) + {1254, "windows-1254"}, // ANSI Turkish; Turkish (Windows) + {1255, "windows-1255"}, // ANSI Hebrew; Hebrew (Windows) + {1256, "windows-1256"}, // ANSI Arabic; Arabic (Windows) + {1257, "windows-1257"}, // ANSI Baltic; Baltic (Windows) + {1258, "windows-1258"}, // ANSI/OEM Vietnamese; Vietnamese (Windows) + {1361, "Johab"}, // Korean (Johab) + {10000, "macintosh"}, // MAC Roman; Western European (Mac) + {10001, "x-mac-japanese"}, // Japanese (Mac) + {10002, "x-mac-chinesetrad"}, // MAC Traditional Chinese (Big5); Chinese Traditional (Mac) + {10003, "x-mac-korean"}, // Korean (Mac) + {10004, "x-mac-arabic"}, // Arabic (Mac) + {10005, "x-mac-hebrew"}, // Hebrew (Mac) + {10006, "x-mac-greek"}, // Greek (Mac) + {10007, "x-mac-cyrillic"}, // Cyrillic (Mac) + {10008, "x-mac-chinesesimp"}, // MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac) + {10010, "x-mac-romanian"}, // Romanian (Mac) + {10017, "x-mac-ukrainian"}, // Ukrainian (Mac) + {10021, "x-mac-thai"}, // Thai (Mac) + {10029, "x-mac-ce"}, // MAC Latin 2; Central European (Mac) + {10079, "x-mac-icelandic"}, // Icelandic (Mac) + {10081, "x-mac-turkish"}, // Turkish (Mac) + {10082, "x-mac-croatian"}, // Croatian (Mac) + // Unicode UTF-32, little endian byte order; available only to managed applications + // impossible in 8-bit mail + {12000, "utf-32"}, + // Unicode UTF-32, big endian byte order; available only to managed applications + // impossible in 8-bit mail + {12001, "utf-32BE"}, + {20000, "x-Chinese_CNS"}, // CNS Taiwan; Chinese Traditional (CNS) + {20001, "x-cp20001"}, // TCA Taiwan + {20002, "x_Chinese-Eten"}, // Eten Taiwan; Chinese Traditional (Eten) + {20003, "x-cp20003"}, // IBM5550 Taiwan + {20004, "x-cp20004"}, // TeleText Taiwan + {20005, "x-cp20005"}, // Wang Taiwan + {20105, "x-IA5"}, // IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5) + {20106, "x-IA5-German"}, // IA5 German (7-bit) + {20107, "x-IA5-Swedish"}, // IA5 Swedish (7-bit) + {20108, "x-IA5-Norwegian"}, // IA5 Norwegian (7-bit) + {20127, "us-ascii"}, // US-ASCII (7-bit) + {20261, "x-cp20261"}, // T.61 + {20269, "x-cp20269"}, // ISO 6937 Non-Spacing Accent + {20273, "IBM273"}, // IBM EBCDIC Germany + {20277, "IBM277"}, // IBM EBCDIC Denmark-Norway + {20278, "IBM278"}, // IBM EBCDIC Finland-Sweden + {20280, "IBM280"}, // IBM EBCDIC Italy + {20284, "IBM284"}, // IBM EBCDIC Latin America-Spain + {20285, "IBM285"}, // IBM EBCDIC United Kingdom + {20290, "IBM290"}, // IBM EBCDIC Japanese Katakana Extended + {20297, "IBM297"}, // IBM EBCDIC France + {20420, "IBM420"}, // IBM EBCDIC Arabic + {20423, "IBM423"}, // IBM EBCDIC Greek + {20424, "IBM424"}, // IBM EBCDIC Hebrew + {20833, "x-EBCDIC-KoreanExtended"}, // IBM EBCDIC Korean Extended + {20838, "IBM-Thai"}, // IBM EBCDIC Thai + {20866, "koi8-r"}, // Russian (KOI8-R); Cyrillic (KOI8-R) + {20871, "IBM871"}, // IBM EBCDIC Icelandic + {20880, "IBM880"}, // IBM EBCDIC Cyrillic Russian + {20905, "IBM905"}, // IBM EBCDIC Turkish + {20924, "IBM00924"}, // IBM EBCDIC Latin 1/Open System (1047 + Euro symbol) + {20932, "EUC-JP"}, // Japanese (JIS 0208-1990 and 0121-1990) + {20936, "x-cp20936"}, // Simplified Chinese (GB2312); Chinese Simplified (GB2312-80) + {20949, "x-cp20949"}, // Korean Wansung + {21025, "cp1025"}, // IBM EBCDIC Cyrillic Serbian-Bulgarian + //21027 (deprecated) + {21866, "koi8-u"}, // Ukrainian (KOI8-U); Cyrillic (KOI8-U) + {28591, "iso-8859-1"}, // ISO 8859-1 Latin 1; Western European (ISO) + {28592, "iso-8859-2"}, // ISO 8859-2 Central European; Central European (ISO) + {28593, "iso-8859-3"}, // ISO 8859-3 Latin 3 + {28594, "iso-8859-4"}, // ISO 8859-4 Baltic + {28595, "iso-8859-5"}, // ISO 8859-5 Cyrillic + {28596, "iso-8859-6"}, // ISO 8859-6 Arabic + {28597, "iso-8859-7"}, // ISO 8859-7 Greek + {28598, "iso-8859-8"}, // ISO 8859-8 Hebrew; Hebrew (ISO-Visual) + {28599, "iso-8859-9"}, // ISO 8859-9 Turkish + {28603, "iso-8859-13"}, // ISO 8859-13 Estonian + {28605, "iso-8859-15"}, // ISO 8859-15 Latin 9 + {29001, "x-Europa"}, // Europa 3 + {38598, "iso-8859-8-i"}, // ISO 8859-8 Hebrew; Hebrew (ISO-Logical) + {50220, "iso-2022-jp"}, // ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS) + {50221, "csISO2022JP"}, // ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana) + {50222, "iso-2022-jp"}, // ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI) + {50225, "iso-2022-kr"}, // ISO 2022 Korean + {50227, "x-cp50227"}, // ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022) + //50229 ISO 2022 Traditional Chinese + //50930 EBCDIC Japanese (Katakana) Extended + //50931 EBCDIC US-Canada and Japanese + //50933 EBCDIC Korean Extended and Korean + //50935 EBCDIC Simplified Chinese Extended and Simplified Chinese + //50936 EBCDIC Simplified Chinese + //50937 EBCDIC US-Canada and Traditional Chinese + //50939 EBCDIC Japanese (Latin) Extended and Japanese + {51932, "euc-jp"}, // EUC Japanese + {51936, "EUC-CN"}, // EUC Simplified Chinese; Chinese Simplified (EUC) + {51949, "euc-kr"}, // EUC Korean + //51950 EUC Traditional Chinese + {52936, "hz-gb-2312"}, // HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ) + {54936, "GB18030"}, // Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030) + {57002, "x-iscii-de"}, // ISCII Devanagari + {57003, "x-iscii-be"}, // ISCII Bengali + {57004, "x-iscii-ta"}, // ISCII Tamil + {57005, "x-iscii-te"}, // ISCII Telugu + {57006, "x-iscii-as"}, // ISCII Assamese + {57007, "x-iscii-or"}, // ISCII Oriya (Odia) + {57008, "x-iscii-ka"}, // ISCII Kannada + {57009, "x-iscii-ma"}, // ISCII Malayalam + {57010, "x-iscii-gu"}, // ISCII Gujarati + {57011, "x-iscii-pa"}, // ISCII Punjabi + {65000, "utf-7"}, // Unicode (UTF-7) + {65001, "utf-8"}, // Unicode (UTF-8) + }; + + // Binary search + int begin = 0, end = sizeof(cptocharset)/sizeof(cptocharset[0])-1; + while (begin <= end) { + int mid = (begin+end)/2; + unsigned int mid_cp = cptocharset[mid].cp; + if (cp == mid_cp) + return cptocharset[mid].charset; + if (cp < mid_cp) + end = mid - 1; + else // cp > cptocharset[mid].cp + begin = mid + 1; + } + return 0; // not found +} + +// We don't use nsMsgI18Ncheck_data_in_charset_range because it returns true +// even if there's no such charset: +// 1. result initialized by true and returned if, eg, GetUnicodeEncoderRaw fail +// 2. it uses GetUnicodeEncoderRaw(), not GetUnicodeEncoder() (to normalize the +// charset string) (see nsMsgI18N.cpp) +// This function returns true only if the unicode (utf-16) text can be +// losslessly represented in specified charset +bool CMapiMessage::CheckBodyInCharsetRange(const char* charset) +{ + if (m_body.IsEmpty()) + return true; + if (!_stricmp(charset, "utf-8")) + return true; + if (!_stricmp(charset, "utf-7")) + return true; + + nsresult rv; + static nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsIUnicodeEncoder> encoder; + + // get an unicode converter + rv = ccm->GetUnicodeEncoder(charset, getter_AddRefs(encoder)); + NS_ENSURE_SUCCESS(rv, false); + rv = encoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Signal, nullptr, 0); + NS_ENSURE_SUCCESS(rv, false); + + const char16_t *txt = m_body.get(); + int32_t txtLen = m_body.Length(); + const char16_t *currentSrcPtr = txt; + int srcLength; + int dstLength; + char localbuf[512]; + int consumedLen = 0; + + // convert + while (consumedLen < txtLen) { + srcLength = txtLen - consumedLen; + dstLength = sizeof(localbuf)/sizeof(localbuf[0]); + rv = encoder->Convert(currentSrcPtr, &srcLength, localbuf, &dstLength); + if (rv == NS_ERROR_UENC_NOMAPPING) + return false; + if (NS_FAILED(rv) || dstLength == 0) + break; + + currentSrcPtr += srcLength; + consumedLen = currentSrcPtr - txt; // src length used so far + } + return true; +} + +bool CaseInsensitiveComp (wchar_t elem1, wchar_t elem2) +{ + return _wcsnicmp(&elem1, &elem2, 1) == 0; +} + +void ExtractMetaCharset(const wchar_t* body, int bodySz, /*out*/nsCString& charset) +{ + charset.Truncate(); + const wchar_t* body_end = body+bodySz; + const wchar_t str_eohd[] = L"/head"; + const wchar_t *str_eohd_end = str_eohd+sizeof(str_eohd)/sizeof(str_eohd[0])-1; + const wchar_t* eohd_pos = std::search(body, body_end, str_eohd, str_eohd_end, + CaseInsensitiveComp); + if (eohd_pos == body_end) // No header! + return; + const wchar_t str_chset[] = L"charset="; + const wchar_t *str_chset_end = + str_chset + sizeof(str_chset)/sizeof(str_chset[0])-1; + const wchar_t* chset_pos = std::search(body, eohd_pos, str_chset, + str_chset_end, CaseInsensitiveComp); + if (chset_pos == eohd_pos) // No charset! + return; + chset_pos += 8; + + // remove everything from the string after the next ; or " or space, + // whichever comes first. + // The inital sting looks something like + // <META content="text/html; charset=utf-8" http-equiv=Content-Type> + // <META content="text/html; charset=utf-8;" http-equiv=Content-Type> + // <META content="text/html; charset=utf-8 ;" http-equiv=Content-Type> + // <META content="text/html; charset=utf-8 " http-equiv=Content-Type> + const wchar_t term[] = L";\" ", *term_end= term+sizeof(term)/sizeof(term[0])-1; + const wchar_t* chset_end = std::find_first_of(chset_pos, eohd_pos, term, + term_end); + if (chset_end != eohd_pos) + LossyCopyUTF16toASCII(Substring(wwc(const_cast<wchar_t *>(chset_pos)), + wwc(const_cast<wchar_t *>(chset_end))), + charset); +} + +bool CMapiMessage::FetchBody(void) +{ + m_bodyIsHtml = false; + m_body.Truncate(); + + // Get the Outlook codepage info; if unsuccessful then it defaults to 0 (CP_ACP) -> system default + // Maybe we can use this info later? + unsigned int codepage=0; + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_INTERNET_CPID); + if (pVal) { + if (PROP_TYPE(pVal->ulPropTag) == PT_LONG) + codepage = pVal->Value.l; + CMapiApi::MAPIFreeBuffer(pVal); + } + + unsigned long nativeBodyType = 0; + if (CMapiApi::GetRTFPropertyDecodedAsUTF16(m_lpMsg, m_body, nativeBodyType, + codepage)) { + m_bodyIsHtml = nativeBodyType == MAPI_NATIVE_BODY_TYPE_HTML; + } + else { // Cannot get RTF version + // Is it html? + pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_HTML_W); + if (pVal) { + if (CMapiApi::IsLargeProperty(pVal)) + CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_HTML_W, m_body); + else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) && + (pVal->Value.lpszW) && (*(pVal->Value.lpszW))) + m_body.Assign(pVal->Value.lpszW); + CMapiApi::MAPIFreeBuffer(pVal); + } + + // Kind-hearted Outlook will give us html even for a plain text message. + // But it will include a comment saying it did the conversion. + // We'll use this as a hack to really use the plain text part. + // + // Sadly there are cases where this string is returned despite the fact + // that the message is indeed HTML. + // + // To detect the "true" plain text messages, we look for our string + // immediately following the <BODY> tag. + if (!m_body.IsEmpty() && + m_body.Find("<BODY>\r\n<!-- Converted from text/plain format -->") == + kNotFound) { + m_bodyIsHtml = true; + } + else { + pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_W); + if (pVal) { + if (CMapiApi::IsLargeProperty(pVal)) + CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_W, m_body); + else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) && + (pVal->Value.lpszW) && (*(pVal->Value.lpszW))) + m_body.Assign(pVal->Value.lpszW); + CMapiApi::MAPIFreeBuffer(pVal); + } + } + } + + // OK, now let's restore the original encoding! + // 1. We may have a header defining the charset (we already called the FetchHeaders(), and there ProcessHeaders(); + // in this case, the m_mimeCharset is set. See nsOutlookMail::ImportMailbox()) + // 2. We may have the codepage walue provided by Outlook ("codepage" at the very beginning of this function) + // 3. We may have an HTML charset header. + + bool bFoundCharset = false; + + if (!m_mimeCharset.IsEmpty()) // The top-level header data + bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get()); + // No valid charset in the message header - try the HTML header. + // arguably may be useless + if (!bFoundCharset && m_bodyIsHtml) { + ExtractMetaCharset(m_body.get(), m_body.Length(), m_mimeCharset); + if (!m_mimeCharset.IsEmpty()) + bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get()); + } + // Get from Outlook (seems like it keeps the MIME part header encoding info) + if (!bFoundCharset && codepage) { + const char* charset = CpToCharset(codepage); + if (charset) { + bFoundCharset = CheckBodyInCharsetRange(charset); + if (bFoundCharset) + m_mimeCharset.Assign(charset); + } + } + if (!bFoundCharset) { // Use system default + const char* charset = nsMsgI18NFileSystemCharset(); + if (charset) { + bFoundCharset = CheckBodyInCharsetRange(charset); + if (bFoundCharset) + m_mimeCharset.Assign(charset); + } + } + if (!bFoundCharset) // Everything else failed, let's use the lossless utf-8... + m_mimeCharset.Assign("utf-8"); + + MAPI_DUMP_STRING(m_body.get()); + MAPI_TRACE0("\r\n"); + + return true; +} + +void CMapiMessage::GetBody(nsCString& dest) const +{ + nsMsgI18NConvertFromUnicode(m_mimeCharset.get(), m_body, dest); +} + +void CMapiMessage::FetchFlags(void) +{ + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_FLAGS); + if (pVal) + m_msgFlags = CMapiApi::GetLongFromProp(pVal); + pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_LAST_VERB_EXECUTED); + if (pVal) + m_msgLastVerb = CMapiApi::GetLongFromProp(pVal); +} + +enum { + ieidPR_ATTACH_NUM = 0, + ieidAttachMax +}; + +static const SizedSPropTagArray(ieidAttachMax, ptaEid)= +{ + ieidAttachMax, + { + PR_ATTACH_NUM + } +}; + +bool CMapiMessage::IterateAttachTable(LPMAPITABLE lpTable) +{ + ULONG rowCount; + HRESULT hr = lpTable->GetRowCount(0, &rowCount); + if (!rowCount) { + return true; + } + + hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0); + if (FAILED(hr)) { + MAPI_TRACE2("SetColumns for attachment table failed: 0x%lx, %d\r\n", (long)hr, (int)hr); + return false; + } + + hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + if (FAILED(hr)) { + MAPI_TRACE2("SeekRow for attachment table failed: 0x%lx, %d\r\n", (long)hr, (int)hr); + return false; + } + + int cNumRows = 0; + LPSRowSet lpRow; + bool bResult = true; + do { + + lpRow = NULL; + hr = lpTable->QueryRows(1, 0, &lpRow); + + if(HR_FAILED(hr)) { + MAPI_TRACE2("QueryRows for attachment table failed: 0x%lx, %d\n", (long)hr, (int)hr); + bResult = false; + break; + } + + if (lpRow) { + cNumRows = lpRow->cRows; + + if (cNumRows) { + DWORD aNum = lpRow->aRow[0].lpProps[ieidPR_ATTACH_NUM].Value.ul; + AddAttachment(aNum); + MAPI_TRACE1("\t\t****Attachment found - #%d\r\n", (int)aNum); + } + CMapiApi::FreeProws(lpRow); + } + + } while (SUCCEEDED(hr) && cNumRows && lpRow); + + return bResult; +} + +bool CMapiMessage::GetTmpFile(/*out*/ nsIFile **aResult) +{ + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + "mapiattach.tmp", + getter_AddRefs(tmpFile)); + if (NS_FAILED(rv)) + return false; + + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + if (NS_FAILED(rv)) + return false; + + tmpFile.forget(aResult); + return true; +} + +bool CMapiMessage::CopyMsgAttachToFile(LPATTACH lpAttach, /*out*/ nsIFile **tmp_file) +{ + bool bResult = true; + LPMESSAGE lpMsg; + HRESULT hr = lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, 0, + reinterpret_cast<LPUNKNOWN *>(&lpMsg)); + if (HR_FAILED(hr)) + return false; + + if (!GetTmpFile(tmp_file)) + return false; + + nsCOMPtr<nsIOutputStream> destOutputStream; + nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(destOutputStream), *tmp_file, -1, 0600); + if (NS_SUCCEEDED(rv)) + rv = nsOutlookMail::ImportMessage(lpMsg, destOutputStream, nsIMsgSend::nsMsgSaveAsDraft); + + if (NS_FAILED(rv)) { + (*tmp_file)->Remove(false); + (*tmp_file)->Release(); + *tmp_file = 0; + } + + return NS_SUCCEEDED(rv); +} + +bool CMapiMessage::CopyBinAttachToFile(LPATTACH lpAttach, + nsIFile **tmp_file) +{ + nsCOMPtr<nsIFile> _tmp_file; + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + "mapiattach.tmp", + getter_AddRefs(_tmp_file)); + NS_ENSURE_SUCCESS(rv, false); + + rv = _tmp_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + NS_ENSURE_SUCCESS(rv, false); + + nsCString tmpPath; + _tmp_file->GetNativePath(tmpPath); + LPSTREAM lpStreamFile; + HRESULT hr = CMapiApi::OpenStreamOnFile(gpMapiAllocateBuffer, gpMapiFreeBuffer, STGM_READWRITE | STGM_CREATE, + const_cast<char*>(tmpPath.get()), NULL, &lpStreamFile); + if (HR_FAILED(hr)) { + MAPI_TRACE1("~~ERROR~~ OpenStreamOnFile failed - temp path: %s\r\n", + tmpPath.get()); + return false; + } + + bool bResult = true; + LPSTREAM lpAttachStream; + hr = lpAttach->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0, (LPUNKNOWN *)&lpAttachStream); + + if (HR_FAILED(hr)) { + MAPI_TRACE0("~~ERROR~~ OpenProperty failed for PR_ATTACH_DATA_BIN.\r\n"); + lpAttachStream = NULL; + bResult = false; + } + else { + STATSTG st; + hr = lpAttachStream->Stat(&st, STATFLAG_NONAME); + if (HR_FAILED(hr)) { + MAPI_TRACE0("~~ERROR~~ Stat failed for attachment stream\r\n"); + bResult = false; + } + else { + hr = lpAttachStream->CopyTo(lpStreamFile, st.cbSize, NULL, NULL); + if (HR_FAILED(hr)) { + MAPI_TRACE0("~~ERROR~~ Attach Stream CopyTo temp file failed.\r\n"); + bResult = false; + } + } + } + + if (lpAttachStream) + lpAttachStream->Release(); + lpStreamFile->Release(); + if (!bResult) + _tmp_file->Remove(false); + else + _tmp_file.forget(tmp_file); + + return bResult; +} + +bool CMapiMessage::GetURL(nsIFile *aFile, nsIURI **url) +{ + if (!m_pIOService) + return false; + + nsresult rv = m_pIOService->NewFileURI(aFile, url); + return NS_SUCCEEDED(rv); +} + +bool CMapiMessage::AddAttachment(DWORD aNum) +{ + LPATTACH lpAttach = NULL; + HRESULT hr = m_lpMsg->OpenAttach(aNum, NULL, 0, &lpAttach); + if (HR_FAILED(hr)) { + MAPI_TRACE2("\t\t****Attachment error, unable to open attachment: %d, 0x%lx\r\n", idx, hr); + return false; + } + + bool bResult = false; + attach_data *data = new attach_data; + ULONG aMethod; + if (data) { + bResult = true; + + // 1. Get the file that contains the attachment data + LPSPropValue pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_METHOD); + if (pVal) { + aMethod = CMapiApi::GetLongFromProp(pVal); + switch (aMethod) { + case ATTACH_BY_VALUE: + MAPI_TRACE1("\t\t** Attachment #%d by value.\r\n", aNum); + bResult = CopyBinAttachToFile(lpAttach, getter_AddRefs(data->tmp_file)); + data->delete_file = true; + break; + case ATTACH_BY_REFERENCE: + case ATTACH_BY_REF_RESOLVE: + case ATTACH_BY_REF_ONLY: + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_PATHNAME_W); + if (pVal) { + nsCString path; + CMapiApi::GetStringFromProp(pVal, path); + nsresult rv; + data->tmp_file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_FAILED(rv) || !data->tmp_file) { + MAPI_TRACE0("*** Error creating file spec for attachment\n"); + bResult = false; + } + else data->tmp_file->InitWithNativePath(path); + } + MAPI_TRACE2("\t\t** Attachment #%d by ref: %s\r\n", + aNum, m_attachPath.get()); + break; + case ATTACH_EMBEDDED_MSG: + MAPI_TRACE1("\t\t** Attachment #%d by Embedded Message??\r\n", aNum); + // Convert the embedded IMessage from PR_ATTACH_DATA_OBJ to rfc822 attachment + // (see http://msdn.microsoft.com/en-us/library/cc842329.aspx) + // This is a recursive call. + bResult = CopyMsgAttachToFile(lpAttach, getter_AddRefs(data->tmp_file)); + data->delete_file = true; + break; + case ATTACH_OLE: + MAPI_TRACE1("\t\t** Attachment #%d by OLE - yuck!!!\r\n", aNum); + break; + default: + MAPI_TRACE2("\t\t** Attachment #%d unknown attachment method - 0x%lx\r\n", aNum, aMethod); + bResult = false; + } + } + else + bResult = false; + + if (bResult) + bResult = data->tmp_file; + + if (bResult) { + bool isFile = false; + bool exists = false; + data->tmp_file->Exists(&exists); + data->tmp_file->IsFile(&isFile); + + if (!exists || !isFile) { + bResult = false; + MAPI_TRACE0("Attachment file does not exist\n"); + } + } + + if (bResult) + bResult = GetURL(data->tmp_file, getter_AddRefs(data->orig_url)); + + if (bResult) { + // Now we have the file; proceed to the other properties + + data->encoding = NS_strdup(ENCODING_BINARY); + + nsString fname, fext; + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_LONG_FILENAME_W); + if (!pVal) + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FILENAME_W); + CMapiApi::GetStringFromProp(pVal, fname); + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_EXTENSION_W); + CMapiApi::GetStringFromProp(pVal, fext); + MAPI_TRACE2("\t\t\t--- File name: %s, extension: %s\r\n", + fname.get(), fext.get()); + + if (fext.IsEmpty()) { + int idx = fname.RFindChar(L'.'); + if (idx != -1) + fext = Substring(fname, idx); + } + else if (fname.RFindChar(L'.') == -1) { + fname += L"."; + fname += fext; + } + if (fname.IsEmpty()) { + // If no description use "Attachment i" format. + fname = L"Attachment "; + fname.AppendInt(static_cast<uint32_t>(aNum)); + } + data->real_name = ToNewUTF8String(fname); + + nsCString tmp; + // We have converted it to the rfc822 document + if (aMethod == ATTACH_EMBEDDED_MSG) { + data->type = NS_strdup(MESSAGE_RFC822); + } else { + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_MIME_TAG_A); + CMapiApi::GetStringFromProp(pVal, tmp); + MAPI_TRACE1("\t\t\t--- Mime type: %s\r\n", tmp.get()); + if (tmp.IsEmpty()) { + uint8_t *pType = NULL; + if (!fext.IsEmpty()) { + pType = CMimeTypes::GetMimeType(fext); + } + if (pType) + data->type = NS_strdup((PC_S8)pType); + else + data->type = NS_strdup(APPLICATION_OCTET_STREAM); + } + else + data->type = ToNewCString(tmp); + } + + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_CONTENT_ID_A); + CMapiApi::GetStringFromProp(pVal, tmp); + if (!tmp.IsEmpty()) + data->cid = ToNewCString(tmp); + } + if (bResult) { + // Now we need to decide if this attachment is embedded or not. + // At first, I tried to simply check for the presence of the Content-Id. + // But it turned out that this method is unreliable, since there exist cases + // when an attachment has a Content-Id while isn't embedded (even in a message + // with a plain-text body!). So next I tried to look for <img> tags that contain + // the found Content-Id. But this is unreliable, too, because there exist cases + // where other places of HTML reference the embedded messages (e.g. it may be + // a background of a table cell, or some CSS; further, it is possible that the + // reference to an embedded object is not in the main body, but in another + // embedded object - like body references a CSS attachment that in turn references + // a picture as a background of its element). From the other hand, it's unreliable + // to relax the search criteria to any occurence of the Content-Id string in the body - + // partly because the string may be simply in a text or other non-referencing part, + // partly because of the abovementioned possibility that the reference is outside + // the body at all. + // There exist the PR_ATTACH_FLAGS property of the attachment. The MS documentation + // tells about two possible flags in it: ATT_INVISIBLE_IN_HTML and ATT_INVISIBLE_IN_RTF. + // There is at least one more undocumented flag: ATT_MHTML_REF. Some sources in Internet + // suggest simply check for the latter flag to distinguish between the embedded + // and ordinary attachments. But my observations indicate that even if the flags + // don't include ATT_MHTML_REF, the attachment is still may be embedded. + // However, my observations always show that the message is embedded if the flags + // is not 0. + // So now I will simply test for the non-zero flags to decide whether the attachment + // is embedded or not. Possible advantage is reliability (I hope). + // Another advantage is that it's much faster than search the body for Content-Id. + + DWORD flags = 0; + + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FLAGS); + if (pVal) + flags = CMapiApi::GetLongFromProp(pVal); + if (m_bodyIsHtml && data->cid && (flags != 0)) // this is the embedded attachment + m_embattachments.push_back(data); + else // this is ordinary attachment + m_stdattachments.push_back(data); + } + else { + delete data; + } + } + + lpAttach->Release(); + return bResult; +} + +void CMapiMessage::ClearAttachment(attach_data* data) +{ + if (data->delete_file && data->tmp_file) + data->tmp_file->Remove(false); + + if (data->type) + NS_Free(data->type); + if (data->encoding) + NS_Free(data->encoding); + if (data->real_name) + NS_Free(data->real_name); + if (data->cid) + NS_Free(data->cid); + + delete data; +} + +void CMapiMessage::ClearAttachments() +{ + std::for_each(m_stdattachments.begin(), m_stdattachments.end(), ClearAttachment); + m_stdattachments.clear(); + std::for_each(m_embattachments.begin(), m_embattachments.end(), ClearAttachment); + m_embattachments.clear(); +} + +// This method must be called AFTER the retrieval of the body, +// since the decision if an attachment is embedded or not is made +// based on the body type and contents +void CMapiMessage::ProcessAttachments() +{ + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_HASATTACH); + bool hasAttach = true; + + if (pVal) { + if (PROP_TYPE(pVal->ulPropTag) == PT_BOOLEAN) + hasAttach = (pVal->Value.b != 0); + CMapiApi::MAPIFreeBuffer(pVal); + } + + if (!hasAttach) + return; + + // Get the attachment table? + LPMAPITABLE pTable = NULL; + HRESULT hr = m_lpMsg->GetAttachmentTable(0, &pTable); + if (FAILED(hr) || !pTable) + return; + IterateAttachTable(pTable); + pTable->Release(); +} + +nsresult CMapiMessage::GetAttachments(nsIArray **aArray) +{ + nsresult rv; + nsCOMPtr<nsIMutableArray> attachments (do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*aArray = attachments); + + for (std::vector<attach_data*>::const_iterator it = m_stdattachments.begin(); + it != m_stdattachments.end(); it++) { + nsCOMPtr<nsIMsgAttachedFile> a(do_CreateInstance(NS_MSGATTACHEDFILE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + a->SetOrigUrl((*it)->orig_url); + a->SetTmpFile((*it)->tmp_file); + a->SetEncoding(nsDependentCString((*it)->encoding)); + a->SetRealName(nsDependentCString((*it)->real_name)); + a->SetType(nsDependentCString((*it)->type)); + attachments->AppendElement(a, false); + } + return rv; +} + +bool CMapiMessage::GetEmbeddedAttachmentInfo(unsigned int i, nsIURI **uri, + const char **cid, + const char **name) const +{ + if ((i < 0) || (i >= m_embattachments.size())) + return false; + attach_data* data = m_embattachments[i]; + if (!data) + return false; + *uri = data->orig_url; + *cid = data->cid; + *name = data->real_name; + return true; +} + +////////////////////////////////////////////////////// + +// begin and end MUST point to the same string +char* dup(const char* begin, const char* end) +{ + if (begin >= end) + return 0; + char* str = new char[end-begin+1]; + memcpy(str, begin, (end-begin)*sizeof(begin[0])); + str[end - begin] = 0; + return str; +} + +// See RFC822 +// 9 = '\t', 32 = ' '. +inline bool IsPrintableASCII(char c) { return (c > ' ') && (c < 127); } +inline bool IsWSP(char c) { return (c == ' ') || (c == '\t'); } + +CMapiMessageHeaders::CHeaderField::CHeaderField(const char* begin, int len) + : m_fname(0), m_fbody(0), m_fbody_utf8(false) +{ + const char *end = begin+len, *fname_end = begin; + while ((fname_end < end) && IsPrintableASCII(*fname_end) && (*fname_end != ':')) + ++fname_end; + if ((fname_end == end) || (*fname_end != ':')) + return; // Not a valid header! + m_fname = dup(begin, fname_end+1); // including colon + m_fbody = dup(fname_end+1, end); +} + +CMapiMessageHeaders::CHeaderField::CHeaderField(const char* name, const char* body, bool utf8) + : m_fname(dup(name, name+strlen(name))), m_fbody(dup(body, body+strlen(body))), m_fbody_utf8(utf8) +{ +} + +CMapiMessageHeaders::CHeaderField::~CHeaderField() +{ + delete[] m_fname; + delete[] m_fbody; +} + +void CMapiMessageHeaders::CHeaderField::set_fbody(const char* txt) +{ + if (m_fbody == txt) + return; // to avoid assigning to self + char* oldbody = m_fbody; + m_fbody = dup(txt, txt+strlen(txt)); + delete[] oldbody; + m_fbody_utf8 = true; +} + +void CMapiMessageHeaders::CHeaderField::GetUnfoldedString(nsString& dest, + const char* fallbackCharset) const +{ + dest.Truncate(); + if (!m_fbody) + return; + + nsCString unfolded; + const char* pos = m_fbody; + while (*pos) { + if ((*pos == nsCRT::CR) && (*(pos+1) == nsCRT::LF) && IsWSP(*(pos+2))) + pos += 2; // Skip CRLF if it is followed by SPACE or TAB + else + unfolded.Append(*(pos++)); + } + if (m_fbody_utf8) + CopyUTF8toUTF16(unfolded, dest); + else + nsMsgI18NConvertToUnicode(fallbackCharset, unfolded, dest); +} + +//////////////////////////////////////// + +const char* CMapiMessageHeaders::Specials[hdrMax] = { + "Date:", + "From:", + "Sender:", + "Reply-To:", + "To:", + "Cc:", + "Bcc:", + "Message-ID:", + "Subject:", + "Mime-Version:", + "Content-Type:", + "Content-Transfer-Encoding:" +}; + +CMapiMessageHeaders::~CMapiMessageHeaders() +{ + ClearHeaderFields(); +} + +void Delete(void* p) { delete p; } + +void CMapiMessageHeaders::ClearHeaderFields() +{ + std::for_each(m_headerFields.begin(), m_headerFields.end(), Delete); + m_headerFields.clear(); +} + +void CMapiMessageHeaders::Assign(const char* headers) +{ + for (int i=0; i<hdrMax; i++) + m_SpecialHeaders[i] = 0; + ClearHeaderFields(); + if (!headers) + return; + + const char *start=headers, *end=headers; + while (*end) { + if ((*end == nsCRT::CR) && (*(end+1) == nsCRT::LF)) { + if (!IsWSP(*(end+2))) { // Not SPACE nor TAB (avoid FSP) -> next header or EOF + Add(new CHeaderField(start, end-start)); + start = ++end + 1; + } + } + ++end; + } + + if (start < end) { // Last header left + Add(new CHeaderField(start, end-start)); + } +} + +void CMapiMessageHeaders::Add(CHeaderField* f) +{ + if (!f) + return; + if (!f->Valid()) { + delete f; + return; + } + + SpecialHeader idx = CheckSpecialHeader(f->fname()); + if (idx != hdrNone) { + // Now check if the special header was already inserted; + // if so, remove previous and add this new + CHeaderField* PrevSpecial = m_SpecialHeaders[idx]; + if (PrevSpecial) { + std::vector<CHeaderField*>::iterator iter = std::find(m_headerFields.begin(), m_headerFields.end(), PrevSpecial); + if (iter != m_headerFields.end()) + m_headerFields.erase(iter); + delete PrevSpecial; + } + m_SpecialHeaders[idx] = f; + } + m_headerFields.push_back(f); +} + +CMapiMessageHeaders::SpecialHeader CMapiMessageHeaders::CheckSpecialHeader(const char* fname) +{ + for (int i = hdrFirst; i < hdrMax; i++) + if (stricmp(fname, Specials[i]) == 0) + return static_cast<SpecialHeader>(i); + + return hdrNone; +} + +const CMapiMessageHeaders::CHeaderField* CMapiMessageHeaders::CFind(const char* name) const +{ + SpecialHeader special = CheckSpecialHeader(name); + if ((special > hdrNone) && (special < hdrMax)) + return m_SpecialHeaders[special]; // No need to search further, because it MUST be here + + std::vector<CHeaderField*>::const_iterator iter = std::find_if(m_headerFields.begin(), m_headerFields.end(), fname_equals(name)); + if (iter == m_headerFields.end()) + return 0; + return *iter; +} + +const char* CMapiMessageHeaders::SpecialName(SpecialHeader special) +{ + if ((special <= hdrNone) || (special >= hdrMax)) + return 0; + return Specials[special]; +} + +const char* CMapiMessageHeaders::Value(SpecialHeader special) const +{ + if ((special <= hdrNone) || (special >= hdrMax)) + return 0; + return (m_SpecialHeaders[special]) ? m_SpecialHeaders[special]->fbody() : 0; +} + +const char* CMapiMessageHeaders::Value(const char* name) const +{ + const CHeaderField* result = CFind(name); + return result ? result->fbody() : 0; +} + +void CMapiMessageHeaders::UnfoldValue(const char* name, nsString& dest, const char* fallbackCharset) const +{ + const CHeaderField* result = CFind(name); + if (result) + result->GetUnfoldedString(dest, fallbackCharset); + else + dest.Truncate(); +} + +void CMapiMessageHeaders::UnfoldValue(SpecialHeader special, nsString& dest, const char* fallbackCharset) const +{ + if ((special <= hdrNone) || (special >= hdrMax) || (!m_SpecialHeaders[special])) + dest.Truncate(); + else + m_SpecialHeaders[special]->GetUnfoldedString(dest, fallbackCharset); +} + +int CMapiMessageHeaders::SetValue(const char* name, const char* value, bool replace) +{ + if (!replace) { + CHeaderField* result = Find(name); + if (result) { + result->set_fbody(value); + return 0; + } + } + Add(new CHeaderField(name, value, true)); + return 0; // No sensible result is returned; maybe do something senseful later +} + +int CMapiMessageHeaders::SetValue(SpecialHeader special, const char* value) +{ + CHeaderField* result = m_SpecialHeaders[special]; + if (result) + result->set_fbody(value); + else + Add(new CHeaderField(Specials[special], value, true)); + return 0; +} + +void CMapiMessageHeaders::write_to_stream::operator () (const CHeaderField* f) +{ + if (!f || NS_FAILED(m_rv)) + return; + + uint32_t written; + m_rv = m_pDst->Write(f->fname(), strlen(f->fname()), &written); + NS_ENSURE_SUCCESS_VOID(m_rv); + if (f->fbody()) { + m_rv = m_pDst->Write(f->fbody(), strlen(f->fbody()), &written); + NS_ENSURE_SUCCESS_VOID(m_rv); + } + m_rv = m_pDst->Write("\x0D\x0A", 2, &written); +} + +nsresult CMapiMessageHeaders::ToStream(nsIOutputStream *pDst) const +{ + nsresult rv = std::for_each(m_headerFields.begin(), m_headerFields.end(), + write_to_stream(pDst)); + if (NS_SUCCEEDED(rv)) { + uint32_t written; + rv = pDst->Write("\x0D\x0A", 2, &written); // Separator line + } + return rv; +} diff --git a/mailnews/import/outlook/src/MapiMessage.h b/mailnews/import/outlook/src/MapiMessage.h new file mode 100644 index 000000000..dc6a6cda1 --- /dev/null +++ b/mailnews/import/outlook/src/MapiMessage.h @@ -0,0 +1,271 @@ +/* -*- 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 MapiMessage_h___ +#define MapiMessage_h___ + +#include "nsTArray.h" +#include "nsStringGlue.h" +#include "nsIFile.h" +#include "MapiApi.h" +#include "nsIMsgSend.h" + +#include <vector> + +#ifndef PR_LAST_VERB_EXECUTED +#define PR_LAST_VERB_EXECUTED PROP_TAG(PT_LONG, 0x1081) +#endif + +#define EXCHIVERB_REPLYTOSENDER (102) +#define EXCHIVERB_REPLYTOALL (103) +#define EXCHIVERB_FORWARD (104) + +#ifndef PR_ATTACH_CONTENT_ID +#define PR_ATTACH_CONTENT_ID PROP_TAG(PT_TSTRING, 0x3712) +#endif +#ifndef PR_ATTACH_CONTENT_ID_W +#define PR_ATTACH_CONTENT_ID_W PROP_TAG(PT_UNICODE, 0x3712) +#endif +#ifndef PR_ATTACH_CONTENT_ID_A +#define PR_ATTACH_CONTENT_ID_A PROP_TAG(PT_STRING8, 0x3712) +#endif + +#ifndef PR_ATTACH_FLAGS +#define PR_ATTACH_FLAGS PROP_TAG(PT_LONG, 0x3714) +#endif + +#ifndef ATT_INVISIBLE_IN_HTML +#define ATT_INVISIBLE_IN_HTML (0x1) +#endif +#ifndef ATT_INVISIBLE_IN_RTF +#define ATT_INVISIBLE_IN_RTF (0x2) +#endif +#ifndef ATT_MHTML_REF +#define ATT_MHTML_REF (0x4) +#endif + +////////////////////////////////////////////////////////////////////////////// + +class CMapiMessageHeaders { +public: + // Special headers that MUST appear at most once (see RFC822) + enum SpecialHeader { hdrNone=-1, hdrFirst = 0, // utility values + hdrDate=hdrFirst, + hdrFrom, + hdrSender, + hdrReplyTo, + hdrTo, + hdrCc, + hdrBcc, + hdrMessageID, + hdrSubject, + hdrMimeVersion, + hdrContentType, + hdrContentTransferEncoding, + hdrMax // utility value + }; + + CMapiMessageHeaders(const char* headers = 0) { Assign(headers); } + ~CMapiMessageHeaders(); + void Assign(const char* headers); + + inline bool IsEmpty() const { return m_headerFields.empty(); } + // if no such header exists then 0 is returned, else the first value returned + const char* Value(const char* name) const; + // if no such header exists then 0 is returned + const char* Value(SpecialHeader special) const; + + void UnfoldValue(const char* name, nsString& dest, const char* fallbackCharset) const; + void UnfoldValue(SpecialHeader special, nsString& dest, const char* fallbackCharset) const; + + // value must be utf-8 or 7-bit; supposed that this function will be called + // when the charset of the value is known + // TODO: if replace is set, then all headers with this name will be removed + // and one with this value will be added, otherwise a new header is added + // (Unnecessary for now) + int SetValue(const char* name, const char* value, bool replace = true); + int SetValue(SpecialHeader special, const char* value); + + static const char* SpecialName(SpecialHeader special); + + nsresult ToStream(nsIOutputStream *pDst) const; +private: + class CHeaderField { + public: + CHeaderField(const char* begin, int len); + CHeaderField(const char* name, const char* body, bool utf8 = false); + ~CHeaderField(); + inline bool Valid() const { return m_fname; } + inline const char* fname() const { return m_fname; } + inline const char* fbody() const { return m_fbody; } + + // txt must be utf-8 or 7-bit; supposed that this function will be called + // when the charset of the txt is known + void set_fbody(const char* txt); + + void GetUnfoldedString(nsString& dest, const char* fallbackCharset) const; + private: + char* m_fname; + char* m_fbody; + bool m_fbody_utf8; + }; //class HeaderField + + class write_to_stream { + public: + write_to_stream(nsIOutputStream *pDst) : m_pDst(pDst), m_rv(NS_OK) {} + void operator () (const CHeaderField* f); + inline operator nsresult() const { return m_rv; } + private: + nsIOutputStream *m_pDst; + nsresult m_rv; + }; + + // Search helper + class fname_equals { + public: + fname_equals(const char* search) : m_search(search) {} + inline bool operator () (const CHeaderField* f) const { return stricmp(f->fname(), m_search) == 0; } + private: + const char* m_search; + }; // class fname_equals + + // The common array of special headers' names + static const char* Specials[hdrMax]; + + std::vector<CHeaderField*> m_headerFields; + CHeaderField* m_SpecialHeaders[hdrMax]; // Pointers into the m_headerFields + + void ClearHeaderFields(); + void Add(CHeaderField* f); + static SpecialHeader CheckSpecialHeader(const char* fname); + const CHeaderField* CFind(const char* name) const; + inline CHeaderField* Find(const char* name) { return const_cast<CHeaderField*>(CFind(name)); } + +}; // class CMapiMessageHeaders + +////////////////////////////////////////////////////// + +class CMapiMessage { +public: + CMapiMessage(LPMESSAGE lpMsg); + ~CMapiMessage(); + + // Attachments + // Ordinary (not embedded) attachments. + nsresult GetAttachments(nsIArray **aArray); + // Embedded attachments + size_t EmbeddedAttachmentsCount() const { return m_embattachments.size(); } + bool GetEmbeddedAttachmentInfo(unsigned int i, nsIURI **uri, const char **cid, + const char **name) const; + // We don't check MSGFLAG_HASATTACH, since it returns true even if there are + // only embedded attachmentsin the message. TB only counts the ordinary + // attachments when shows the message status, so here we check only for the + // ordinary attachments. + inline bool HasAttach() const { return !m_stdattachments.empty(); } + + // Retrieve info for message + inline bool BodyIsHtml(void) const { return m_bodyIsHtml;} + const char *GetFromLine(int& len) const { + if (m_fromLine.IsEmpty()) + return NULL; + else { + len = m_fromLine.Length(); + return m_fromLine.get();} + } + inline CMapiMessageHeaders *GetHeaders() { return &m_headers; } + inline const wchar_t *GetBody(void) const { return m_body.get(); } + inline size_t GetBodyLen(void) const { return m_body.Length(); } + void GetBody(nsCString& dest) const; + inline const char *GetBodyCharset(void) const { return m_mimeCharset.get();} + inline bool IsRead() const { return m_msgFlags & MSGFLAG_READ; } + inline bool IsReplied() const { + return (m_msgLastVerb == EXCHIVERB_REPLYTOSENDER) || + (m_msgLastVerb == EXCHIVERB_REPLYTOALL); } + inline bool IsForvarded() const { + return m_msgLastVerb == EXCHIVERB_FORWARD; } + + bool HasContentHeader(void) const { + return !m_mimeContentType.IsEmpty();} + bool HasMimeVersion(void) const { + return m_headers.Value(CMapiMessageHeaders::hdrMimeVersion); } + const char *GetMimeContent(void) const { return m_mimeContentType.get();} + int32_t GetMimeContentLen(void) const { return m_mimeContentType.Length();} + const char *GetMimeBoundary(void) const { return m_mimeBoundary.get();} + + // The only required part of a message is its header + inline bool ValidState() const { return !m_headers.IsEmpty(); } + inline bool FullMessageDownloaded() const { return !m_dldStateHeadersOnly; } + +private: + struct attach_data { + nsCOMPtr<nsIURI> orig_url; + nsCOMPtr<nsIFile> tmp_file; + char *type; + char *encoding; + char *real_name; + char *cid; + bool delete_file; + attach_data() : type(0), encoding(0), real_name(0), cid(0), delete_file(false) {} + }; + + static const nsCString m_whitespace; + + LPMESSAGE m_lpMsg; + + bool m_dldStateHeadersOnly; // if the message has not been downloaded yet + CMapiMessageHeaders m_headers; + nsCString m_fromLine; // utf-8 + nsCString m_mimeContentType; // utf-8 + nsCString m_mimeBoundary; // utf-8 + nsCString m_mimeCharset; // utf-8 + + std::vector<attach_data*> m_stdattachments; + std::vector<attach_data*> m_embattachments; // Embedded + + nsString m_body; // to be converted from UTF-16 using m_mimeCharset + bool m_bodyIsHtml; + + uint32_t m_msgFlags; + uint32_t m_msgLastVerb; + + nsCOMPtr<nsIIOService> m_pIOService; + + void GetDownloadState(); + + // Headers - fetch will get PR_TRANSPORT_MESSAGE_HEADERS + // or if they do not exist will build a header from + // PR_DISPLAY_TO, _CC, _BCC + // PR_SUBJECT + // PR_MESSAGE_RECIPIENTS + // and PR_CREATION_TIME if needed? + bool FetchHeaders(void); + bool FetchBody(void); + void FetchFlags(void); + + static bool GetTmpFile(/*out*/ nsIFile **aResult); + static bool CopyMsgAttachToFile(LPATTACH lpAttach, /*out*/ nsIFile **tmp_file); + static bool CopyBinAttachToFile(LPATTACH lpAttach, nsIFile **tmp_file); + + static void ClearAttachment(attach_data* data); + void ClearAttachments(); + bool AddAttachment(DWORD aNum); + bool IterateAttachTable(LPMAPITABLE tbl); + bool GetURL(nsIFile *aFile, nsIURI **url); + void ProcessAttachments(); + + bool EnsureHeader(CMapiMessageHeaders::SpecialHeader special, ULONG mapiTag); + bool EnsureDate(); + + void ProcessContentType(); + bool CheckBodyInCharsetRange(const char* charset); + void FormatDateTime(SYSTEMTIME& tm, nsCString& s, bool includeTZ = true); + void BuildFromLine(void); + + inline static bool IsSpace(char c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\b' || c == '\t';} + inline static bool IsSpace(wchar_t c) { + return ((c & 0xFF) == c) && IsSpace(static_cast<char>(c)); } // Avoid false detections +}; + +#endif /* MapiMessage_h__ */ diff --git a/mailnews/import/outlook/src/MapiMimeTypes.cpp b/mailnews/import/outlook/src/MapiMimeTypes.cpp new file mode 100644 index 000000000..38b2046db --- /dev/null +++ b/mailnews/import/outlook/src/MapiMimeTypes.cpp @@ -0,0 +1,96 @@ +/* -*- 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 "nscore.h" +#include "nsStringGlue.h" +#include "MapiMimeTypes.h" + +uint8_t CMimeTypes::m_mimeBuffer[kMaxMimeTypeSize]; + + +BOOL CMimeTypes::GetKey(HKEY root, LPCTSTR pName, PHKEY pKey) +{ + LONG result = RegOpenKeyEx(root, pName, 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, pKey); + return result == ERROR_SUCCESS; +} + +BOOL CMimeTypes::GetValueBytes(HKEY rootKey, LPCTSTR pValName, LPBYTE *ppBytes) +{ + LONG err; + DWORD bufSz; + + *ppBytes = NULL; + // Get the installed directory + err = RegQueryValueEx(rootKey, pValName, NULL, NULL, NULL, &bufSz); + if (err == ERROR_SUCCESS) { + *ppBytes = new BYTE[bufSz]; + err = RegQueryValueEx(rootKey, pValName, NULL, NULL, *ppBytes, &bufSz); + if (err == ERROR_SUCCESS) { + return TRUE; + } + delete *ppBytes; + *ppBytes = NULL; + } + return FALSE; +} + +void CMimeTypes::ReleaseValueBytes(LPBYTE pBytes) +{ + if (pBytes) + delete pBytes; +} + +BOOL CMimeTypes::GetMimeTypeFromReg(const nsCString& ext, LPBYTE *ppBytes) +{ + HKEY extensionKey; + BOOL result = FALSE; + *ppBytes = NULL; + if (GetKey(HKEY_CLASSES_ROOT, ext.get(), &extensionKey)) { + result = GetValueBytes(extensionKey, "Content Type", ppBytes); + RegCloseKey(extensionKey); + } + + return result; +} + +uint8_t * CMimeTypes::GetMimeType(const nsString& theExt) +{ + nsCString ext; + LossyCopyUTF16toASCII(theExt, ext); + return GetMimeType(ext); +} + +uint8_t * CMimeTypes::GetMimeType(const nsCString& theExt) +{ + nsCString ext = theExt; + if (ext.Length()) { + if (ext.First() != '.') { + ext = "."; + ext += theExt; + } + } + + + BOOL result = FALSE; + int len; + + if (!ext.Length()) + return NULL; + LPBYTE pByte; + if (GetMimeTypeFromReg(ext, &pByte)) { + len = strlen((const char *) pByte); + if (len && (len < kMaxMimeTypeSize)) { + memcpy(m_mimeBuffer, pByte, len); + m_mimeBuffer[len] = 0; + result = TRUE; + } + ReleaseValueBytes(pByte); + } + + if (result) + return m_mimeBuffer; + + return NULL; +} diff --git a/mailnews/import/outlook/src/MapiMimeTypes.h b/mailnews/import/outlook/src/MapiMimeTypes.h new file mode 100644 index 000000000..61a90dd19 --- /dev/null +++ b/mailnews/import/outlook/src/MapiMimeTypes.h @@ -0,0 +1,31 @@ +/* -*- 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 MapiMimeTypes_h___ +#define MapiMimeTypes_h___ + +#include <windows.h> + +#define kMaxMimeTypeSize 256 + +class CMimeTypes { +public: + +static uint8_t * GetMimeType(const nsCString& theExt); +static uint8_t * GetMimeType(const nsString& theExt); + +protected: + // Registry stuff +static BOOL GetKey(HKEY root, LPCTSTR pName, PHKEY pKey); +static BOOL GetValueBytes(HKEY rootKey, LPCTSTR pValName, LPBYTE *ppBytes); +static void ReleaseValueBytes(LPBYTE pBytes); +static BOOL GetMimeTypeFromReg(const nsCString& ext, LPBYTE *ppBytes); + + +static uint8_t m_mimeBuffer[kMaxMimeTypeSize]; +}; + +#endif /* MapiMimeTypes_h__ */ + diff --git a/mailnews/import/outlook/src/MapiTagStrs.cpp b/mailnews/import/outlook/src/MapiTagStrs.cpp new file mode 100644 index 000000000..217b2186c --- /dev/null +++ b/mailnews/import/outlook/src/MapiTagStrs.cpp @@ -0,0 +1,1070 @@ +/* -*- 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/. */ + +/* + * Message envelope properties + */ + +case PR_ACKNOWLEDGEMENT_MODE: + s = "PR_ACKNOWLEDGEMENT_MODE"; break; +case PR_ALTERNATE_RECIPIENT_ALLOWED: + s = "PR_ALTERNATE_RECIPIENT_ALLOWED"; break; +case PR_AUTHORIZING_USERS: + s = "PR_AUTHORIZING_USERS"; break; +case PR_AUTO_FORWARD_COMMENT: + s = "PR_AUTO_FORWARD_COMMENT"; break; +case PR_AUTO_FORWARDED: + s = "PR_AUTO_FORWARDED"; break; +case PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID: + s = "PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID"; break; +case PR_CONTENT_CORRELATOR: + s = "PR_CONTENT_CORRELATOR"; break; +case PR_CONTENT_IDENTIFIER: + s = "PR_CONTENT_IDENTIFIER"; break; +case PR_CONTENT_LENGTH: + s = "PR_CONTENT_LENGTH"; break; +case PR_CONTENT_RETURN_REQUESTED: + s = "PR_CONTENT_RETURN_REQUESTED"; break; + + + +case PR_CONVERSATION_KEY: + s = "PR_CONVERSATION_KEY"; break; + +case PR_CONVERSION_EITS: + s = "PR_CONVERSION_EITS"; break; +case PR_CONVERSION_WITH_LOSS_PROHIBITED: + s = "PR_CONVERSION_WITH_LOSS_PROHIBITED"; break; +case PR_CONVERTED_EITS: + s = "PR_CONVERTED_EITS"; break; +case PR_DEFERRED_DELIVERY_TIME: + s = "PR_DEFERRED_DELIVERY_TIME"; break; +case PR_DELIVER_TIME: + s = "PR_DELIVER_TIME"; break; +case PR_DISCARD_REASON: + s = "PR_DISCARD_REASON"; break; +case PR_DISCLOSURE_OF_RECIPIENTS: + s = "PR_DISCLOSURE_OF_RECIPIENTS"; break; +case PR_DL_EXPANSION_HISTORY: + s = "PR_DL_EXPANSION_HISTORY"; break; +case PR_DL_EXPANSION_PROHIBITED: + s = "PR_DL_EXPANSION_PROHIBITED"; break; +case PR_EXPIRY_TIME: + s = "PR_EXPIRY_TIME"; break; +case PR_IMPLICIT_CONVERSION_PROHIBITED: + s = "PR_IMPLICIT_CONVERSION_PROHIBITED"; break; +case PR_IMPORTANCE: + s = "PR_IMPORTANCE"; break; +case PR_IPM_ID: + s = "PR_IPM_ID"; break; +case PR_LATEST_DELIVERY_TIME: + s = "PR_LATEST_DELIVERY_TIME"; break; +case PR_MESSAGE_CLASS: + s = "PR_MESSAGE_CLASS"; break; +case PR_MESSAGE_DELIVERY_ID: + s = "PR_MESSAGE_DELIVERY_ID"; break; + + + + + +case PR_MESSAGE_SECURITY_LABEL: + s = "PR_MESSAGE_SECURITY_LABEL"; break; +case PR_OBSOLETED_IPMS: + s = "PR_OBSOLETED_IPMS"; break; +case PR_ORIGINALLY_INTENDED_RECIPIENT_NAME: + s = "PR_ORIGINALLY_INTENDED_RECIPIENT_NAME"; break; +case PR_ORIGINAL_EITS: + s = "PR_ORIGINAL_EITS"; break; +case PR_ORIGINATOR_CERTIFICATE: + s = "PR_ORIGINATOR_CERTIFICATE"; break; +case PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED: + s = "PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED"; break; +case PR_ORIGINATOR_RETURN_ADDRESS: + s = "PR_ORIGINATOR_RETURN_ADDRESS"; break; + + + +case PR_PARENT_KEY: + s = "PR_PARENT_KEY"; break; +case PR_PRIORITY: + s = "PR_PRIORITY"; break; + + + +case PR_ORIGIN_CHECK: + s = "PR_ORIGIN_CHECK"; break; +case PR_PROOF_OF_SUBMISSION_REQUESTED: + s = "PR_PROOF_OF_SUBMISSION_REQUESTED"; break; +case PR_READ_RECEIPT_REQUESTED: + s = "PR_READ_RECEIPT_REQUESTED"; break; +case PR_RECEIPT_TIME: + s = "PR_RECEIPT_TIME"; break; +case PR_RECIPIENT_REASSIGNMENT_PROHIBITED: + s = "PR_RECIPIENT_REASSIGNMENT_PROHIBITED"; break; +case PR_REDIRECTION_HISTORY: + s = "PR_REDIRECTION_HISTORY"; break; +case PR_RELATED_IPMS: + s = "PR_RELATED_IPMS"; break; +case PR_ORIGINAL_SENSITIVITY: + s = "PR_ORIGINAL_SENSITIVITY"; break; +case PR_LANGUAGES: + s = "PR_LANGUAGES"; break; +case PR_REPLY_TIME: + s = "PR_REPLY_TIME"; break; +case PR_REPORT_TAG: + s = "PR_REPORT_TAG"; break; +case PR_REPORT_TIME: + s = "PR_REPORT_TIME"; break; +case PR_RETURNED_IPM: + s = "PR_RETURNED_IPM"; break; +case PR_SECURITY: + s = "PR_SECURITY"; break; +case PR_INCOMPLETE_COPY: + s = "PR_INCOMPLETE_COPY"; break; +case PR_SENSITIVITY: + s = "PR_SENSITIVITY"; break; +case PR_SUBJECT: + s = "PR_SUBJECT"; break; +case PR_SUBJECT_IPM: + s = "PR_SUBJECT_IPM"; break; +case PR_CLIENT_SUBMIT_TIME: + s = "PR_CLIENT_SUBMIT_TIME"; break; +case PR_REPORT_NAME: + s = "PR_REPORT_NAME"; break; +case PR_SENT_REPRESENTING_SEARCH_KEY: + s = "PR_SENT_REPRESENTING_SEARCH_KEY"; break; +case PR_X400_CONTENT_TYPE: + s = "PR_X400_CONTENT_TYPE"; break; +case PR_SUBJECT_PREFIX: + s = "PR_SUBJECT_PREFIX"; break; +case PR_NON_RECEIPT_REASON: + s = "PR_NON_RECEIPT_REASON"; break; +case PR_RECEIVED_BY_ENTRYID: + s = "PR_RECEIVED_BY_ENTRYID"; break; +case PR_RECEIVED_BY_NAME: + s = "PR_RECEIVED_BY_NAME"; break; +case PR_SENT_REPRESENTING_ENTRYID: + s = "PR_SENT_REPRESENTING_ENTRYID"; break; +case PR_SENT_REPRESENTING_NAME: + s = "PR_SENT_REPRESENTING_NAME"; break; +case PR_RCVD_REPRESENTING_ENTRYID: + s = "PR_RCVD_REPRESENTING_ENTRYID"; break; +case PR_RCVD_REPRESENTING_NAME: + s = "PR_RCVD_REPRESENTING_NAME"; break; +case PR_REPORT_ENTRYID: + s = "PR_REPORT_ENTRYID"; break; +case PR_READ_RECEIPT_ENTRYID: + s = "PR_READ_RECEIPT_ENTRYID"; break; +case PR_MESSAGE_SUBMISSION_ID: + s = "PR_MESSAGE_SUBMISSION_ID"; break; +case PR_PROVIDER_SUBMIT_TIME: + s = "PR_PROVIDER_SUBMIT_TIME"; break; +case PR_ORIGINAL_SUBJECT: + s = "PR_ORIGINAL_SUBJECT"; break; +case PR_DISC_VAL: + s = "PR_DISC_VAL"; break; +case PR_ORIG_MESSAGE_CLASS: + s = "PR_ORIG_MESSAGE_CLASS"; break; +case PR_ORIGINAL_AUTHOR_ENTRYID: + s = "PR_ORIGINAL_AUTHOR_ENTRYID"; break; +case PR_ORIGINAL_AUTHOR_NAME: + s = "PR_ORIGINAL_AUTHOR_NAME"; break; +case PR_ORIGINAL_SUBMIT_TIME: + s = "PR_ORIGINAL_SUBMIT_TIME"; break; +case PR_REPLY_RECIPIENT_ENTRIES: + s = "PR_REPLY_RECIPIENT_ENTRIES"; break; +case PR_REPLY_RECIPIENT_NAMES: + s = "PR_REPLY_RECIPIENT_NAMES"; break; + +case PR_RECEIVED_BY_SEARCH_KEY: + s = "PR_RECEIVED_BY_SEARCH_KEY"; break; +case PR_RCVD_REPRESENTING_SEARCH_KEY: + s = "PR_RCVD_REPRESENTING_SEARCH_KEY"; break; +case PR_READ_RECEIPT_SEARCH_KEY: + s = "PR_READ_RECEIPT_SEARCH_KEY"; break; +case PR_REPORT_SEARCH_KEY: + s = "PR_REPORT_SEARCH_KEY"; break; +case PR_ORIGINAL_DELIVERY_TIME: + s = "PR_ORIGINAL_DELIVERY_TIME"; break; +case PR_ORIGINAL_AUTHOR_SEARCH_KEY: + s = "PR_ORIGINAL_AUTHOR_SEARCH_KEY"; break; + +case PR_MESSAGE_TO_ME: + s = "PR_MESSAGE_TO_ME"; break; +case PR_MESSAGE_CC_ME: + s = "PR_MESSAGE_CC_ME"; break; +case PR_MESSAGE_RECIP_ME: + s = "PR_MESSAGE_RECIP_ME"; break; + +case PR_ORIGINAL_SENDER_NAME: + s = "PR_ORIGINAL_SENDER_NAME"; break; +case PR_ORIGINAL_SENDER_ENTRYID: + s = "PR_ORIGINAL_SENDER_ENTRYID"; break; +case PR_ORIGINAL_SENDER_SEARCH_KEY: + s = "PR_ORIGINAL_SENDER_SEARCH_KEY"; break; +case PR_ORIGINAL_SENT_REPRESENTING_NAME: + s = "PR_ORIGINAL_SENT_REPRESENTING_NAME"; break; +case PR_ORIGINAL_SENT_REPRESENTING_ENTRYID: + s = "PR_ORIGINAL_SENT_REPRESENTING_ENTRYID"; break; +case PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY: + s = "PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY"; break; + +case PR_START_DATE: + s = "PR_START_DATE"; break; +case PR_END_DATE: + s = "PR_END_DATE"; break; +case PR_OWNER_APPT_ID: + s = "PR_OWNER_APPT_ID"; break; +case PR_RESPONSE_REQUESTED: + s = "PR_RESPONSE_REQUESTED"; break; + +case PR_SENT_REPRESENTING_ADDRTYPE: + s = "PR_SENT_REPRESENTING_ADDRTYPE"; break; +case PR_SENT_REPRESENTING_EMAIL_ADDRESS: + s = "PR_SENT_REPRESENTING_EMAIL_ADDRESS"; break; + +case PR_ORIGINAL_SENDER_ADDRTYPE: + s = "PR_ORIGINAL_SENDER_ADDRTYPE"; break; +case PR_ORIGINAL_SENDER_EMAIL_ADDRESS: + s = "PR_ORIGINAL_SENDER_EMAIL_ADDRESS"; break; + +case PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE: + s = "PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE"; break; +case PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS: + s = "PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS"; break; + +case PR_CONVERSATION_TOPIC: + s = "PR_CONVERSATION_TOPIC"; break; +case PR_CONVERSATION_INDEX: + s = "PR_CONVERSATION_INDEX"; break; + +case PR_ORIGINAL_DISPLAY_BCC: + s = "PR_ORIGINAL_DISPLAY_BCC"; break; +case PR_ORIGINAL_DISPLAY_CC: + s = "PR_ORIGINAL_DISPLAY_CC"; break; +case PR_ORIGINAL_DISPLAY_TO: + s = "PR_ORIGINAL_DISPLAY_TO"; break; + +case PR_RECEIVED_BY_ADDRTYPE: + s = "PR_RECEIVED_BY_ADDRTYPE"; break; +case PR_RECEIVED_BY_EMAIL_ADDRESS: + s = "PR_RECEIVED_BY_EMAIL_ADDRESS"; break; + +case PR_RCVD_REPRESENTING_ADDRTYPE: + s = "PR_RCVD_REPRESENTING_ADDRTYPE"; break; +case PR_RCVD_REPRESENTING_EMAIL_ADDRESS: + s = "PR_RCVD_REPRESENTING_EMAIL_ADDRESS"; break; + +case PR_ORIGINAL_AUTHOR_ADDRTYPE: + s = "PR_ORIGINAL_AUTHOR_ADDRTYPE"; break; +case PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS: + s = "PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS"; break; + +case PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE: + s = "PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE"; break; +case PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS: + s = "PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS"; break; + +case PR_TRANSPORT_MESSAGE_HEADERS: + s = "PR_TRANSPORT_MESSAGE_HEADERS"; break; + +case PR_DELEGATION: + s = "PR_DELEGATION"; break; + +case PR_TNEF_CORRELATION_KEY: + s = "PR_TNEF_CORRELATION_KEY"; break; + + + +/* + * Message content properties + */ + +case PR_BODY: + s = "PR_BODY"; break; +case PR_REPORT_TEXT: + s = "PR_REPORT_TEXT"; break; +case PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY: + s = "PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY"; break; +case PR_REPORTING_DL_NAME: + s = "PR_REPORTING_DL_NAME"; break; +case PR_REPORTING_MTA_CERTIFICATE: + s = "PR_REPORTING_MTA_CERTIFICATE"; break; + +/* Removed PR_REPORT_ORIGIN_AUTHENTICATION_CHECK with DCR 3865, use PR_ORIGIN_CHECK */ + +case PR_RTF_SYNC_BODY_CRC: + s = "PR_RTF_SYNC_BODY_CRC"; break; +case PR_RTF_SYNC_BODY_COUNT: + s = "PR_RTF_SYNC_BODY_COUNT"; break; +case PR_RTF_SYNC_BODY_TAG: + s = "PR_RTF_SYNC_BODY_TAG"; break; +case PR_RTF_COMPRESSED: + s = "PR_RTF_COMPRESSED"; break; +case PR_RTF_SYNC_PREFIX_COUNT: + s = "PR_RTF_SYNC_PREFIX_COUNT"; break; +case PR_RTF_SYNC_TRAILING_COUNT: + s = "PR_RTF_SYNC_TRAILING_COUNT"; break; +case PR_ORIGINALLY_INTENDED_RECIP_ENTRYID: + s = "PR_ORIGINALLY_INTENDED_RECIP_ENTRYID"; break; + +/* + * Reserved 0x1100-0x1200 + */ + + +/* + * Message recipient properties + */ + +case PR_CONTENT_INTEGRITY_CHECK: + s = "PR_CONTENT_INTEGRITY_CHECK"; break; +case PR_EXPLICIT_CONVERSION: + s = "PR_EXPLICIT_CONVERSION"; break; +case PR_IPM_RETURN_REQUESTED: + s = "PR_IPM_RETURN_REQUESTED"; break; +case PR_MESSAGE_TOKEN: + s = "PR_MESSAGE_TOKEN"; break; +case PR_NDR_REASON_CODE: + s = "PR_NDR_REASON_CODE"; break; +case PR_NDR_DIAG_CODE: + s = "PR_NDR_DIAG_CODE"; break; +case PR_NON_RECEIPT_NOTIFICATION_REQUESTED: + s = "PR_NON_RECEIPT_NOTIFICATION_REQUESTED"; break; +case PR_DELIVERY_POINT: + s = "PR_DELIVERY_POINT"; break; + +case PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED: + s = "PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED"; break; +case PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT: + s = "PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT"; break; +case PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY: + s = "PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY"; break; +case PR_PHYSICAL_DELIVERY_MODE: + s = "PR_PHYSICAL_DELIVERY_MODE"; break; +case PR_PHYSICAL_DELIVERY_REPORT_REQUEST: + s = "PR_PHYSICAL_DELIVERY_REPORT_REQUEST"; break; +case PR_PHYSICAL_FORWARDING_ADDRESS: + s = "PR_PHYSICAL_FORWARDING_ADDRESS"; break; +case PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED: + s = "PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED"; break; +case PR_PHYSICAL_FORWARDING_PROHIBITED: + s = "PR_PHYSICAL_FORWARDING_PROHIBITED"; break; +case PR_PHYSICAL_RENDITION_ATTRIBUTES: + s = "PR_PHYSICAL_RENDITION_ATTRIBUTES"; break; +case PR_PROOF_OF_DELIVERY: + s = "PR_PROOF_OF_DELIVERY"; break; +case PR_PROOF_OF_DELIVERY_REQUESTED: + s = "PR_PROOF_OF_DELIVERY_REQUESTED"; break; +case PR_RECIPIENT_CERTIFICATE: + s = "PR_RECIPIENT_CERTIFICATE"; break; +case PR_RECIPIENT_NUMBER_FOR_ADVICE: + s = "PR_RECIPIENT_NUMBER_FOR_ADVICE"; break; +case PR_RECIPIENT_TYPE: + s = "PR_RECIPIENT_TYPE"; break; +case PR_REGISTERED_MAIL_TYPE: + s = "PR_REGISTERED_MAIL_TYPE"; break; +case PR_REPLY_REQUESTED: + s = "PR_REPLY_REQUESTED"; break; +case PR_REQUESTED_DELIVERY_METHOD: + s = "PR_REQUESTED_DELIVERY_METHOD"; break; +case PR_SENDER_ENTRYID: + s = "PR_SENDER_ENTRYID"; break; +case PR_SENDER_NAME: + s = "PR_SENDER_NAME"; break; +case PR_SUPPLEMENTARY_INFO: + s = "PR_SUPPLEMENTARY_INFO"; break; +case PR_TYPE_OF_MTS_USER: + s = "PR_TYPE_OF_MTS_USER"; break; +case PR_SENDER_SEARCH_KEY: + s = "PR_SENDER_SEARCH_KEY"; break; +case PR_SENDER_ADDRTYPE: + s = "PR_SENDER_ADDRTYPE"; break; +case PR_SENDER_EMAIL_ADDRESS: + s = "PR_SENDER_EMAIL_ADDRESS"; break; + +/* + * Message non-transmittable properties + */ + +/* + * The two tags, PR_MESSAGE_RECIPIENTS and PR_MESSAGE_ATTACHMENTS, + * are to be used in the exclude list passed to + * IMessage::CopyTo when the caller wants either the recipients or attachments + * of the message to not get copied. It is also used in the ProblemArray + * return from IMessage::CopyTo when an error is encountered copying them + */ + +case PR_CURRENT_VERSION: + s = "PR_CURRENT_VERSION"; break; +case PR_DELETE_AFTER_SUBMIT: + s = "PR_DELETE_AFTER_SUBMIT"; break; +case PR_DISPLAY_BCC: + s = "PR_DISPLAY_BCC"; break; +case PR_DISPLAY_CC: + s = "PR_DISPLAY_CC"; break; +case PR_DISPLAY_TO: + s = "PR_DISPLAY_TO"; break; +case PR_PARENT_DISPLAY: + s = "PR_PARENT_DISPLAY"; break; +case PR_MESSAGE_DELIVERY_TIME: + s = "PR_MESSAGE_DELIVERY_TIME"; break; +case PR_MESSAGE_FLAGS: + s = "PR_MESSAGE_FLAGS"; break; +case PR_MESSAGE_SIZE: + s = "PR_MESSAGE_SIZE"; break; +case PR_PARENT_ENTRYID: + s = "PR_PARENT_ENTRYID"; break; +case PR_SENTMAIL_ENTRYID: + s = "PR_SENTMAIL_ENTRYID"; break; +case PR_CORRELATE: + s = "PR_CORRELATE"; break; +case PR_CORRELATE_MTSID: + s = "PR_CORRELATE_MTSID"; break; +case PR_DISCRETE_VALUES: + s = "PR_DISCRETE_VALUES"; break; +case PR_RESPONSIBILITY: + s = "PR_RESPONSIBILITY"; break; +case PR_SPOOLER_STATUS: + s = "PR_SPOOLER_STATUS"; break; +case PR_TRANSPORT_STATUS: + s = "PR_TRANSPORT_STATUS"; break; +case PR_MESSAGE_RECIPIENTS: + s = "PR_MESSAGE_RECIPIENTS"; break; +case PR_MESSAGE_ATTACHMENTS: + s = "PR_MESSAGE_ATTACHMENTS"; break; +case PR_SUBMIT_FLAGS: + s = "PR_SUBMIT_FLAGS"; break; +case PR_RECIPIENT_STATUS: + s = "PR_RECIPIENT_STATUS"; break; +case PR_TRANSPORT_KEY: + s = "PR_TRANSPORT_KEY"; break; +case PR_MSG_STATUS: + s = "PR_MSG_STATUS"; break; +case PR_MESSAGE_DOWNLOAD_TIME: + s = "PR_MESSAGE_DOWNLOAD_TIME"; break; +case PR_CREATION_VERSION: + s = "PR_CREATION_VERSION"; break; +case PR_MODIFY_VERSION: + s = "PR_MODIFY_VERSION"; break; +case PR_HASATTACH: + s = "PR_HASATTACH"; break; +case PR_BODY_CRC: + s = "PR_BODY_CRC"; break; +case PR_NORMALIZED_SUBJECT: + s = "PR_NORMALIZED_SUBJECT"; break; +case PR_RTF_IN_SYNC: + s = "PR_RTF_IN_SYNC"; break; +case PR_ATTACH_SIZE: + s = "PR_ATTACH_SIZE"; break; +case PR_ATTACH_NUM: + s = "PR_ATTACH_NUM"; break; +case PR_PREPROCESS: + s = "PR_PREPROCESS"; break; + +/* PR_ORIGINAL_DISPLAY_TO, _CC, and _BCC moved to transmittible range 03/09/95 */ + +case PR_ORIGINATING_MTA_CERTIFICATE: + s = "PR_ORIGINATING_MTA_CERTIFICATE"; break; +case PR_PROOF_OF_SUBMISSION: + s = "PR_PROOF_OF_SUBMISSION"; break; + + +/* + * The range of non-message and non-recipient property IDs (0x3000 - 0x3FFF) is + * further broken down into ranges to make assigning new property IDs easier. + * + * From To Kind of property + * -------------------------------- + * 3000 32FF MAPI_defined common property + * 3200 33FF MAPI_defined form property + * 3400 35FF MAPI_defined message store property + * 3600 36FF MAPI_defined Folder or AB Container property + * 3700 38FF MAPI_defined attachment property + * 3900 39FF MAPI_defined address book property + * 3A00 3BFF MAPI_defined mailuser property + * 3C00 3CFF MAPI_defined DistList property + * 3D00 3DFF MAPI_defined Profile Section property + * 3E00 3EFF MAPI_defined Status property + * 3F00 3FFF MAPI_defined display table property + */ + +/* + * Properties common to numerous MAPI objects. + * + * Those properties that can appear on messages are in the + * non-transmittable range for messages. They start at the high + * end of that range and work down. + * + * Properties that never appear on messages are defined in the common + * property range (see above). + */ + +/* + * properties that are common to multiple objects (including message objects) + * -- these ids are in the non-transmittable range + */ + +case PR_ENTRYID: + s = "PR_ENTRYID"; break; +case PR_OBJECT_TYPE: + s = "PR_OBJECT_TYPE"; break; +case PR_ICON: + s = "PR_ICON"; break; +case PR_MINI_ICON: + s = "PR_MINI_ICON"; break; +case PR_STORE_ENTRYID: + s = "PR_STORE_ENTRYID"; break; +case PR_STORE_RECORD_KEY: + s = "PR_STORE_RECORD_KEY"; break; +case PR_RECORD_KEY: + s = "PR_RECORD_KEY"; break; +case PR_MAPPING_SIGNATURE: + s = "PR_MAPPING_SIGNATURE"; break; +case PR_ACCESS_LEVEL: + s = "PR_ACCESS_LEVEL"; break; +case PR_INSTANCE_KEY: + s = "PR_INSTANCE_KEY"; break; +case PR_ROW_TYPE: + s = "PR_ROW_TYPE"; break; +case PR_ACCESS: + s = "PR_ACCESS"; break; + +/* + * properties that are common to multiple objects (usually not including message objects) + * -- these ids are in the transmittable range + */ + +case PR_ROWID: + s = "PR_ROWID"; break; +case PR_DISPLAY_NAME: + s = "PR_DISPLAY_NAME"; break; +case PR_ADDRTYPE: + s = "PR_ADDRTYPE"; break; +case PR_EMAIL_ADDRESS: + s = "PR_EMAIL_ADDRESS"; break; +case PR_COMMENT: + s = "PR_COMMENT"; break; +case PR_DEPTH: + s = "PR_DEPTH"; break; +case PR_PROVIDER_DISPLAY: + s = "PR_PROVIDER_DISPLAY"; break; +case PR_CREATION_TIME: + s = "PR_CREATION_TIME"; break; +case PR_LAST_MODIFICATION_TIME: + s = "PR_LAST_MODIFICATION_TIME"; break; +case PR_RESOURCE_FLAGS: + s = "PR_RESOURCE_FLAGS"; break; +case PR_PROVIDER_DLL_NAME: + s = "PR_PROVIDER_DLL_NAME"; break; +case PR_SEARCH_KEY: + s = "PR_SEARCH_KEY"; break; +case PR_PROVIDER_UID: + s = "PR_PROVIDER_UID"; break; +case PR_PROVIDER_ORDINAL: + s = "PR_PROVIDER_ORDINAL"; break; + +/* + * MAPI Form properties + */ +case PR_FORM_VERSION: + s = "PR_FORM_VERSION"; break; +case PR_FORM_CLSID: + s = "PR_FORM_CLSID"; break; +case PR_FORM_CONTACT_NAME: + s = "PR_FORM_CONTACT_NAME"; break; +case PR_FORM_CATEGORY: + s = "PR_FORM_CATEGORY"; break; +case PR_FORM_CATEGORY_SUB: + s = "PR_FORM_CATEGORY_SUB"; break; +case PR_FORM_HOST_MAP: + s = "PR_FORM_HOST_MAP"; break; +case PR_FORM_HIDDEN: + s = "PR_FORM_HIDDEN"; break; +case PR_FORM_DESIGNER_NAME: + s = "PR_FORM_DESIGNER_NAME"; break; +case PR_FORM_DESIGNER_GUID: + s = "PR_FORM_DESIGNER_GUID"; break; +case PR_FORM_MESSAGE_BEHAVIOR: + s = "PR_FORM_MESSAGE_BEHAVIOR"; break; + +/* + * Message store properties + */ + +case PR_DEFAULT_STORE: + s = "PR_DEFAULT_STORE"; break; +case PR_STORE_SUPPORT_MASK: + s = "PR_STORE_SUPPORT_MASK"; break; +case PR_STORE_STATE: + s = "PR_STORE_STATE"; break; + +case PR_IPM_SUBTREE_SEARCH_KEY: + s = "PR_IPM_SUBTREE_SEARCH_KEY"; break; +case PR_IPM_OUTBOX_SEARCH_KEY: + s = "PR_IPM_OUTBOX_SEARCH_KEY"; break; +case PR_IPM_WASTEBASKET_SEARCH_KEY: + s = "PR_IPM_WASTEBASKET_SEARCH_KEY"; break; +case PR_IPM_SENTMAIL_SEARCH_KEY: + s = "PR_IPM_SENTMAIL_SEARCH_KEY"; break; +case PR_MDB_PROVIDER: + s = "PR_MDB_PROVIDER"; break; +case PR_RECEIVE_FOLDER_SETTINGS: + s = "PR_RECEIVE_FOLDER_SETTINGS"; break; + +case PR_VALID_FOLDER_MASK: + s = "PR_VALID_FOLDER_MASK"; break; +case PR_IPM_SUBTREE_ENTRYID: + s = "PR_IPM_SUBTREE_ENTRYID"; break; + +case PR_IPM_OUTBOX_ENTRYID: + s = "PR_IPM_OUTBOX_ENTRYID"; break; +case PR_IPM_WASTEBASKET_ENTRYID: + s = "PR_IPM_WASTEBASKET_ENTRYID"; break; +case PR_IPM_SENTMAIL_ENTRYID: + s = "PR_IPM_SENTMAIL_ENTRYID"; break; +case PR_VIEWS_ENTRYID: + s = "PR_VIEWS_ENTRYID"; break; +case PR_COMMON_VIEWS_ENTRYID: + s = "PR_COMMON_VIEWS_ENTRYID"; break; +case PR_FINDER_ENTRYID: + s = "PR_FINDER_ENTRYID"; break; + +/* Proptags 0x35E8-0x35FF reserved for folders "guaranteed" by PR_VALID_FOLDER_MASK */ + + +/* + * Folder and AB Container properties + */ + +case PR_CONTAINER_FLAGS: + s = "PR_CONTAINER_FLAGS"; break; +case PR_FOLDER_TYPE: + s = "PR_FOLDER_TYPE"; break; +case PR_CONTENT_COUNT: + s = "PR_CONTENT_COUNT"; break; +case PR_CONTENT_UNREAD: + s = "PR_CONTENT_UNREAD"; break; +case PR_CREATE_TEMPLATES: + s = "PR_CREATE_TEMPLATES"; break; +case PR_DETAILS_TABLE: + s = "PR_DETAILS_TABLE"; break; +case PR_SEARCH: + s = "PR_SEARCH"; break; +case PR_SELECTABLE: + s = "PR_SELECTABLE"; break; +case PR_SUBFOLDERS: + s = "PR_SUBFOLDERS"; break; +case PR_STATUS: + s = "PR_STATUS"; break; +case PR_ANR: + s = "PR_ANR"; break; +case PR_CONTENTS_SORT_ORDER: + s = "PR_CONTENTS_SORT_ORDER"; break; +case PR_CONTAINER_HIERARCHY: + s = "PR_CONTAINER_HIERARCHY"; break; +case PR_CONTAINER_CONTENTS: + s = "PR_CONTAINER_CONTENTS"; break; +case PR_FOLDER_ASSOCIATED_CONTENTS: + s = "PR_FOLDER_ASSOCIATED_CONTENTS"; break; +case PR_DEF_CREATE_DL: + s = "PR_DEF_CREATE_DL"; break; +case PR_DEF_CREATE_MAILUSER: + s = "PR_DEF_CREATE_MAILUSER"; break; +case PR_CONTAINER_CLASS: + s = "PR_CONTAINER_CLASS"; break; +case PR_CONTAINER_MODIFY_VERSION: + s = "PR_CONTAINER_MODIFY_VERSION"; break; +case PR_AB_PROVIDER_ID: + s = "PR_AB_PROVIDER_ID"; break; +case PR_DEFAULT_VIEW_ENTRYID: + s = "PR_DEFAULT_VIEW_ENTRYID"; break; +case PR_ASSOC_CONTENT_COUNT: + s = "PR_ASSOC_CONTENT_COUNT"; break; + +/* Reserved 0x36C0-0x36FF */ + +/* + * Attachment properties + */ + +case PR_ATTACHMENT_X400_PARAMETERS: + s = "PR_ATTACHMENT_X400_PARAMETERS"; break; +case PR_ATTACH_DATA_OBJ: + s = "PR_ATTACH_DATA_OBJ"; break; +case PR_ATTACH_DATA_BIN: + s = "PR_ATTACH_DATA_BIN"; break; +case PR_ATTACH_ENCODING: + s = "PR_ATTACH_ENCODING"; break; +case PR_ATTACH_EXTENSION: + s = "PR_ATTACH_EXTENSION"; break; +case PR_ATTACH_FILENAME: + s = "PR_ATTACH_FILENAME"; break; +case PR_ATTACH_METHOD: + s = "PR_ATTACH_METHOD"; break; +case PR_ATTACH_LONG_FILENAME: + s = "PR_ATTACH_LONG_FILENAME"; break; +case PR_ATTACH_PATHNAME: + s = "PR_ATTACH_PATHNAME"; break; +case PR_ATTACH_RENDERING: + s = "PR_ATTACH_RENDERING"; break; +case PR_ATTACH_TAG: + s = "PR_ATTACH_TAG"; break; +case PR_RENDERING_POSITION: + s = "PR_RENDERING_POSITION"; break; +case PR_ATTACH_TRANSPORT_NAME: + s = "PR_ATTACH_TRANSPORT_NAME"; break; +case PR_ATTACH_LONG_PATHNAME: + s = "PR_ATTACH_LONG_PATHNAME"; break; +case PR_ATTACH_MIME_TAG: + s = "PR_ATTACH_MIME_TAG"; break; +case PR_ATTACH_ADDITIONAL_INFO: + s = "PR_ATTACH_ADDITIONAL_INFO"; break; + +/* + * AB Object properties + */ + +case PR_DISPLAY_TYPE: + s = "PR_DISPLAY_TYPE"; break; +case PR_TEMPLATEID: + s = "PR_TEMPLATEID"; break; +case PR_PRIMARY_CAPABILITY: + s = "PR_PRIMARY_CAPABILITY"; break; + + +/* + * Mail user properties + */ +case PR_7BIT_DISPLAY_NAME: + s = "PR_7BIT_DISPLAY_NAME"; break; +case PR_ACCOUNT: + s = "PR_ACCOUNT"; break; +case PR_ALTERNATE_RECIPIENT: + s = "PR_ALTERNATE_RECIPIENT"; break; +case PR_CALLBACK_TELEPHONE_NUMBER: + s = "PR_CALLBACK_TELEPHONE_NUMBER"; break; +case PR_CONVERSION_PROHIBITED: + s = "PR_CONVERSION_PROHIBITED"; break; +case PR_DISCLOSE_RECIPIENTS: + s = "PR_DISCLOSE_RECIPIENTS"; break; +case PR_GENERATION: + s = "PR_GENERATION"; break; +case PR_GIVEN_NAME: + s = "PR_GIVEN_NAME"; break; +case PR_GOVERNMENT_ID_NUMBER: + s = "PR_GOVERNMENT_ID_NUMBER"; break; +case PR_BUSINESS_TELEPHONE_NUMBER: + s = "PR_BUSINESS_TELEPHONE_NUMBER or PR_OFFICE_TELEPHONE_NUMBER"; break; +case PR_HOME_TELEPHONE_NUMBER: + s = "PR_HOME_TELEPHONE_NUMBER"; break; +case PR_INITIALS: + s = "PR_INITIALS"; break; +case PR_KEYWORD: + s = "PR_KEYWORD"; break; +case PR_LANGUAGE: + s = "PR_LANGUAGE"; break; +case PR_LOCATION: + s = "PR_LOCATION"; break; +case PR_MAIL_PERMISSION: + s = "PR_MAIL_PERMISSION"; break; +case PR_MHS_COMMON_NAME: + s = "PR_MHS_COMMON_NAME"; break; +case PR_ORGANIZATIONAL_ID_NUMBER: + s = "PR_ORGANIZATIONAL_ID_NUMBER"; break; +case PR_SURNAME: + s = "PR_SURNAME"; break; +case PR_ORIGINAL_ENTRYID: + s = "PR_ORIGINAL_ENTRYID"; break; +case PR_ORIGINAL_DISPLAY_NAME: + s = "PR_ORIGINAL_DISPLAY_NAME"; break; +case PR_ORIGINAL_SEARCH_KEY: + s = "PR_ORIGINAL_SEARCH_KEY"; break; +case PR_POSTAL_ADDRESS: + s = "PR_POSTAL_ADDRESS"; break; +case PR_COMPANY_NAME: + s = "PR_COMPANY_NAME"; break; +case PR_TITLE: + s = "PR_TITLE"; break; +case PR_DEPARTMENT_NAME: + s = "PR_DEPARTMENT_NAME"; break; +case PR_OFFICE_LOCATION: + s = "PR_OFFICE_LOCATION"; break; +case PR_PRIMARY_TELEPHONE_NUMBER: + s = "PR_PRIMARY_TELEPHONE_NUMBER"; break; +case PR_BUSINESS2_TELEPHONE_NUMBER: + s = "PR_BUSINESS2_TELEPHONE_NUMBER or PR_OFFICE2_TELEPHONE_NUMBER"; break; +case PR_MOBILE_TELEPHONE_NUMBER: + s = "PR_MOBILE_TELEPHONE_NUMBER or PR_CELLULAR_TELEPHONE_NUMBER"; break; +case PR_RADIO_TELEPHONE_NUMBER: + s = "PR_RADIO_TELEPHONE_NUMBER"; break; +case PR_CAR_TELEPHONE_NUMBER: + s = "PR_CAR_TELEPHONE_NUMBER"; break; +case PR_OTHER_TELEPHONE_NUMBER: + s = "PR_OTHER_TELEPHONE_NUMBER"; break; +case PR_TRANSMITABLE_DISPLAY_NAME: + s = "PR_TRANSMITABLE_DISPLAY_NAME"; break; +case PR_PAGER_TELEPHONE_NUMBER: + s = "PR_PAGER_TELEPHONE_NUMBER or PR_BEEPER_TELEPHONE_NUMBER"; break; +case PR_USER_CERTIFICATE: + s = "PR_USER_CERTIFICATE"; break; +case PR_PRIMARY_FAX_NUMBER: + s = "PR_PRIMARY_FAX_NUMBER"; break; +case PR_BUSINESS_FAX_NUMBER: + s = "PR_BUSINESS_FAX_NUMBER"; break; +case PR_HOME_FAX_NUMBER: + s = "PR_HOME_FAX_NUMBER"; break; +case PR_COUNTRY: + s = "PR_COUNTRY or PR_BUSINESS_ADDRESS_COUNTRY"; break; + +case PR_LOCALITY: + s = "PR_LOCALITY or PR_BUSINESS_ADDRESS_CITY"; break; + +case PR_STATE_OR_PROVINCE: + s = "PR_STATE_OR_PROVINCE or PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE"; break; + +case PR_STREET_ADDRESS: + s = "PR_STREET_ADDRESS or PR_BUSINESS_ADDRESS_STREET"; break; + +case PR_POSTAL_CODE: + s = "PR_POSTAL_CODE or PR_BUSINESS_ADDRESS_POSTAL_CODE"; break; + + +case PR_POST_OFFICE_BOX: + s = "PR_POST_OFFICE_BOX or PR_BUSINESS_ADDRESS_POST_OFFICE_BOX"; break; + + +case PR_TELEX_NUMBER: + s = "PR_TELEX_NUMBER"; break; +case PR_ISDN_NUMBER: + s = "PR_ISDN_NUMBER"; break; +case PR_ASSISTANT_TELEPHONE_NUMBER: + s = "PR_ASSISTANT_TELEPHONE_NUMBER"; break; +case PR_HOME2_TELEPHONE_NUMBER: + s = "PR_HOME2_TELEPHONE_NUMBER"; break; +case PR_ASSISTANT: + s = "PR_ASSISTANT"; break; +case PR_SEND_RICH_INFO: + s = "PR_SEND_RICH_INFO"; break; + +case PR_WEDDING_ANNIVERSARY: + s = "PR_WEDDING_ANNIVERSARY"; break; +case PR_BIRTHDAY: + s = "PR_BIRTHDAY"; break; + + +case PR_HOBBIES: + s = "PR_HOBBIES"; break; + +case PR_MIDDLE_NAME: + s = "PR_MIDDLE_NAME"; break; + +case PR_DISPLAY_NAME_PREFIX: + s = "PR_DISPLAY_NAME_PREFIX"; break; + +case PR_PROFESSION: + s = "PR_PROFESSION"; break; + +case PR_PREFERRED_BY_NAME: + s = "PR_PREFERRED_BY_NAME"; break; + +case PR_SPOUSE_NAME: + s = "PR_SPOUSE_NAME"; break; + +case PR_COMPUTER_NETWORK_NAME: + s = "PR_COMPUTER_NETWORK_NAME"; break; + +case PR_CUSTOMER_ID: + s = "PR_CUSTOMER_ID"; break; + +case PR_TTYTDD_PHONE_NUMBER: + s = "PR_TTYTDD_PHONE_NUMBER"; break; + +case PR_FTP_SITE: + s = "PR_FTP_SITE"; break; + +case PR_GENDER: + s = "PR_GENDER"; break; + +case PR_MANAGER_NAME: + s = "PR_MANAGER_NAME"; break; + +case PR_NICKNAME: + s = "PR_NICKNAME"; break; + +case PR_PERSONAL_HOME_PAGE: + s = "PR_PERSONAL_HOME_PAGE"; break; + + +case PR_BUSINESS_HOME_PAGE: + s = "PR_BUSINESS_HOME_PAGE"; break; + +case PR_CONTACT_VERSION: + s = "PR_CONTACT_VERSION"; break; +case PR_CONTACT_ENTRYIDS: + s = "PR_CONTACT_ENTRYIDS"; break; + +case PR_CONTACT_ADDRTYPES: + s = "PR_CONTACT_ADDRTYPES"; break; + +case PR_CONTACT_DEFAULT_ADDRESS_INDEX: + s = "PR_CONTACT_DEFAULT_ADDRESS_INDEX"; break; + +case PR_CONTACT_EMAIL_ADDRESSES: + s = "PR_CONTACT_EMAIL_ADDRESSES"; break; + + +case PR_COMPANY_MAIN_PHONE_NUMBER: + s = "PR_COMPANY_MAIN_PHONE_NUMBER"; break; + +case PR_CHILDRENS_NAMES: + s = "PR_CHILDRENS_NAMES"; break; + + + +case PR_HOME_ADDRESS_CITY: + s = "PR_HOME_ADDRESS_CITY"; break; + +case PR_HOME_ADDRESS_COUNTRY: + s = "PR_HOME_ADDRESS_COUNTRY"; break; + +case PR_HOME_ADDRESS_POSTAL_CODE: + s = "PR_HOME_ADDRESS_POSTAL_CODE"; break; + +case PR_HOME_ADDRESS_STATE_OR_PROVINCE: + s = "PR_HOME_ADDRESS_STATE_OR_PROVINCE"; break; + +case PR_HOME_ADDRESS_STREET: + s = "PR_HOME_ADDRESS_STREET"; break; + +case PR_HOME_ADDRESS_POST_OFFICE_BOX: + s = "PR_HOME_ADDRESS_POST_OFFICE_BOX"; break; + +case PR_OTHER_ADDRESS_CITY: + s = "PR_OTHER_ADDRESS_CITY"; break; + +case PR_OTHER_ADDRESS_COUNTRY: + s = "PR_OTHER_ADDRESS_COUNTRY"; break; + +case PR_OTHER_ADDRESS_POSTAL_CODE: + s = "PR_OTHER_ADDRESS_POSTAL_CODE"; break; + +case PR_OTHER_ADDRESS_STATE_OR_PROVINCE: + s = "PR_OTHER_ADDRESS_STATE_OR_PROVINCE"; break; + +case PR_OTHER_ADDRESS_STREET: + s = "PR_OTHER_ADDRESS_STREET"; break; + +case PR_OTHER_ADDRESS_POST_OFFICE_BOX: + s = "PR_OTHER_ADDRESS_POST_OFFICE_BOX"; break; + + +/* + * Profile section properties + */ + +case PR_STORE_PROVIDERS: + s = "PR_STORE_PROVIDERS"; break; +case PR_AB_PROVIDERS: + s = "PR_AB_PROVIDERS"; break; +case PR_TRANSPORT_PROVIDERS: + s = "PR_TRANSPORT_PROVIDERS"; break; + +case PR_DEFAULT_PROFILE: + s = "PR_DEFAULT_PROFILE"; break; +case PR_AB_SEARCH_PATH: + s = "PR_AB_SEARCH_PATH"; break; +case PR_AB_DEFAULT_DIR: + s = "PR_AB_DEFAULT_DIR"; break; +case PR_AB_DEFAULT_PAB: + s = "PR_AB_DEFAULT_PAB"; break; + +case PR_FILTERING_HOOKS: + s = "PR_FILTERING_HOOKS"; break; +case PR_SERVICE_NAME: + s = "PR_SERVICE_NAME"; break; +case PR_SERVICE_DLL_NAME: + s = "PR_SERVICE_DLL_NAME"; break; +case PR_SERVICE_ENTRY_NAME: + s = "PR_SERVICE_ENTRY_NAME"; break; +case PR_SERVICE_UID: + s = "PR_SERVICE_UID"; break; +case PR_SERVICE_EXTRA_UIDS: + s = "PR_SERVICE_EXTRA_UIDS"; break; +case PR_SERVICES: + s = "PR_SERVICES"; break; +case PR_SERVICE_SUPPORT_FILES: + s = "PR_SERVICE_SUPPORT_FILES"; break; +case PR_SERVICE_DELETE_FILES: + s = "PR_SERVICE_DELETE_FILES"; break; +case PR_AB_SEARCH_PATH_UPDATE: + s = "PR_AB_SEARCH_PATH_UPDATE"; break; +case PR_PROFILE_NAME: + s = "PR_PROFILE_NAME"; break; + +/* + * Status object properties + */ + +case PR_IDENTITY_DISPLAY: + s = "PR_IDENTITY_DISPLAY"; break; +case PR_IDENTITY_ENTRYID: + s = "PR_IDENTITY_ENTRYID"; break; +case PR_RESOURCE_METHODS: + s = "PR_RESOURCE_METHODS"; break; +case PR_RESOURCE_TYPE: + s = "PR_RESOURCE_TYPE"; break; +case PR_STATUS_CODE: + s = "PR_STATUS_CODE"; break; +case PR_IDENTITY_SEARCH_KEY: + s = "PR_IDENTITY_SEARCH_KEY"; break; +case PR_OWN_STORE_ENTRYID: + s = "PR_OWN_STORE_ENTRYID"; break; +case PR_RESOURCE_PATH: + s = "PR_RESOURCE_PATH"; break; +case PR_STATUS_STRING: + s = "PR_STATUS_STRING"; break; +case PR_X400_DEFERRED_DELIVERY_CANCEL: + s = "PR_X400_DEFERRED_DELIVERY_CANCEL"; break; +case PR_HEADER_FOLDER_ENTRYID: + s = "PR_HEADER_FOLDER_ENTRYID"; break; +case PR_REMOTE_PROGRESS: + s = "PR_REMOTE_PROGRESS"; break; +case PR_REMOTE_PROGRESS_TEXT: + s = "PR_REMOTE_PROGRESS_TEXT"; break; +case PR_REMOTE_VALIDATE_OK: + s = "PR_REMOTE_VALIDATE_OK"; break; + +/* + * Display table properties + */ + +case PR_CONTROL_FLAGS: + s = "PR_CONTROL_FLAGS"; break; +case PR_CONTROL_STRUCTURE: + s = "PR_CONTROL_STRUCTURE"; break; +case PR_CONTROL_TYPE: + s = "PR_CONTROL_TYPE"; break; +case PR_DELTAX: + s = "PR_DELTAX"; break; +case PR_DELTAY: + s = "PR_DELTAY"; break; +case PR_XPOS: + s = "PR_XPOS"; break; +case PR_YPOS: + s = "PR_YPOS"; break; +case PR_CONTROL_ID: + s = "PR_CONTROL_ID"; break; +case PR_INITIAL_DETAILS_PANE: + s = "PR_INITIAL_DETAILS_PANE"; break; +/* + * Secure property id range + */ + +case PROP_ID_SECURE_MIN: + s = "PROP_ID_SECURE_MIN"; break; +case PROP_ID_SECURE_MAX: + s = "PROP_ID_SECURE_MAX"; break; diff --git a/mailnews/import/outlook/src/OutlookDebugLog.h b/mailnews/import/outlook/src/OutlookDebugLog.h new file mode 100644 index 000000000..5b189bf9b --- /dev/null +++ b/mailnews/import/outlook/src/OutlookDebugLog.h @@ -0,0 +1,24 @@ +/* -*- 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 OutlookDebugLog_h___ +#define OutlookDebugLog_h___ + +#ifdef NS_DEBUG +#define IMPORT_DEBUG 1 +#endif + +// Use PR_LOG for logging. +#include "mozilla/Logging.h" +extern PRLogModuleInfo *OUTLOOKLOGMODULE; // Logging module + +#define IMPORT_LOG0(x) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (x)) +#define IMPORT_LOG1(x, y) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (x, y)) +#define IMPORT_LOG2(x, y, z) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (x, y, z)) +#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d)) + + + +#endif /* OutlookDebugLog_h___ */ diff --git a/mailnews/import/outlook/src/moz.build b/mailnews/import/outlook/src/moz.build new file mode 100644 index 000000000..4ffbad572 --- /dev/null +++ b/mailnews/import/outlook/src/moz.build @@ -0,0 +1,24 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'MapiApi.cpp', + 'MapiMessage.cpp', + 'MapiMimeTypes.cpp', + 'nsOutlookCompose.cpp', + 'nsOutlookImport.cpp', + 'nsOutlookMail.cpp', + 'nsOutlookSettings.cpp', + 'nsOutlookStringBundle.cpp', + 'rtfDecoder.cpp', + 'rtfMailDecoder.cpp', +] + +FINAL_LIBRARY = 'import' + +LOCAL_INCLUDES += [ + '../../src' +] + diff --git a/mailnews/import/outlook/src/nsOutlookCompose.cpp b/mailnews/import/outlook/src/nsOutlookCompose.cpp new file mode 100644 index 000000000..eb47a29fd --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookCompose.cpp @@ -0,0 +1,815 @@ +/* -*- 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 "nscore.h" +#include "prthread.h" +#include "nsStringGlue.h" +#include "nsMsgUtils.h" +#include "nsUnicharUtils.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIIOService.h" +#include "nsIURI.h" +#include "nsMsgI18N.h" +#include "nsINetUtil.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsMsgAttachmentData.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsIArray.h" +#include "nsIMsgCompose.h" +#include "nsIMsgCompFields.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgSend.h" +#include "nsIMutableArray.h" +#include "nsImportEmbeddedImageData.h" +#include "nsNetCID.h" +#include "nsCRT.h" +#include "nsOutlookCompose.h" + +#include "OutlookDebugLog.h" + +#include "nsMimeTypes.h" +#include "nsMsgUtils.h" + +#include "nsAutoPtr.h" + +#include "nsMsgMessageFlags.h" +#include "nsMsgLocalFolderHdrs.h" + +#include <algorithm> + +static NS_DEFINE_CID(kMsgSendCID, NS_MSGSEND_CID); +static NS_DEFINE_CID(kMsgCompFieldsCID, NS_MSGCOMPFIELDS_CID); + +// We need to do some calculations to set these numbers to something reasonable! +// Unless of course, CreateAndSendMessage will NEVER EVER leave us in the lurch +#define kHungCount 100000 +#define kHungAbortCount 1000 + +#ifdef IMPORT_DEBUG +static const char *p_test_headers = +"Received: from netppl.invalid (IDENT:monitor@get.freebsd.because.microsoftsucks.invalid [209.3.31.115])\n\ + by mail4.sirius.invalid (8.9.1/8.9.1) with SMTP id PAA27232;\n\ + Mon, 17 May 1999 15:27:43 -0700 (PDT)\n\ +Message-ID: <ikGD3jRTsKklU.Ggm2HmE2A1Jsqd0p@netppl.invalid>\n\ +From: \"adsales@qualityservice.invalid\" <adsales@qualityservice.invalid>\n\ +Subject: Re: Your College Diploma (36822)\n\ +Date: Mon, 17 May 1999 15:09:29 -0400 (EDT)\n\ +MIME-Version: 1.0\n\ +Content-Type: TEXT/PLAIN; charset=\"US-ASCII\"\n\ +Content-Transfer-Encoding: 7bit\n\ +X-UIDL: 19990517.152941\n\ +Status: RO"; + +static const char *p_test_body = +"Hello world?\n\ +"; +#else +#define p_test_headers nullptr +#define p_test_body nullptr +#endif + +#define kWhitespace "\b\t\r\n " + +////////////////////////////////////////////////////////////////////////////////////////////////// + +// A replacement for SimpleBufferTonyRCopiedTwice round-robin buffer and ReadFileState classes +class CCompositionFile { +public: + // fifoBuffer is used for memory allocation optimization + // convertCRs controls if we want to convert standalone CRs to CRLFs + CCompositionFile(nsIFile* aFile, void* fifoBuffer, uint32_t fifoBufferSize, bool convertCRs=false); + + operator bool() const { return m_fileSize && m_pInputStream; } + + // Reads up to and including the term sequence, or entire file if term isn't found + // termSize may be used to include NULLs in the terminator sequences. + // termSize value of -1 means "zero-terminated string" -> size is calculated with strlen + nsresult ToString(nsCString& dest, const char* term=0, int termSize=-1); + nsresult ToStream(nsIOutputStream *dest, const char* term=0, int termSize=-1); + char LastChar() { return m_lastChar; } +private: + nsCOMPtr<nsIFile> m_pFile; + nsCOMPtr<nsIInputStream> m_pInputStream; + int64_t m_fileSize; + int64_t m_fileReadPos; + char* m_fifoBuffer; + uint32_t m_fifoBufferSize; + char* m_fifoBufferReadPos; // next character to read + char* m_fifoBufferWrittenPos; // if we have read less than buffer size then this will show it + bool m_convertCRs; + char m_lastChar; + + nsresult EnsureHasDataInBuffer(); + template <class _OutFn> nsresult ToDest(_OutFn dest, const char* term, int termSize); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////// + +// First off, a listener +class OutlookSendListener : public nsIMsgSendListener +{ +public: + OutlookSendListener() { + m_done = false; + m_location = nullptr; + } + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + /* void OnStartSending (in string aMsgID, in uint32_t aMsgSize); */ + NS_IMETHOD OnStartSending(const char *aMsgID, uint32_t aMsgSize) {return NS_OK;} + + /* void OnProgress (in string aMsgID, in uint32_t aProgress, in uint32_t aProgressMax); */ + NS_IMETHOD OnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax) {return NS_OK;} + + /* void OnStatus (in string aMsgID, in wstring aMsg); */ + NS_IMETHOD OnStatus(const char *aMsgID, const char16_t *aMsg) {return NS_OK;} + + /* void OnStopSending (in string aMsgID, in nsresult aStatus, in wstring aMsg, in nsIFile returnFile); */ + NS_IMETHOD OnStopSending(const char *aMsgID, nsresult aStatus, const char16_t *aMsg, + nsIFile *returnFile) { + m_done = true; + NS_IF_ADDREF(m_location = returnFile); + return NS_OK; + } + + /* void OnSendNotPerformed */ + NS_IMETHOD OnSendNotPerformed(const char *aMsgID, nsresult aStatus) {return NS_OK;} + + /* void OnGetDraftFolderURI (); */ + NS_IMETHOD OnGetDraftFolderURI(const char *aFolderURI) {return NS_OK;} + + static nsresult CreateSendListener(nsIMsgSendListener **ppListener); + void Reset() { m_done = false; NS_IF_RELEASE(m_location);} + +public: + virtual ~OutlookSendListener() { NS_IF_RELEASE(m_location); } + + bool m_done; + nsIFile * m_location; +}; + +NS_IMPL_ISUPPORTS(OutlookSendListener, nsIMsgSendListener) + +nsresult OutlookSendListener::CreateSendListener(nsIMsgSendListener **ppListener) +{ + NS_PRECONDITION(ppListener != nullptr, "null ptr"); + NS_ENSURE_ARG_POINTER(ppListener); + + *ppListener = new OutlookSendListener(); + if (! *ppListener) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*ppListener); + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////// + +#define hackBeginA "begin" +#define hackBeginW u"begin" +#define hackEndA "\015\012end" +#define hackEndW u"\015\012end" +#define hackCRLFA "crlf" +#define hackCRLFW u"crlf" +#define hackAmpersandA "amp" +#define hackAmpersandW u"amp" + +nsOutlookCompose::nsOutlookCompose() +{ + m_pListener = nullptr; + m_pMsgFields = nullptr; + + m_optimizationBuffer = new char[FILE_IO_BUFFER_SIZE]; +} + +nsOutlookCompose::~nsOutlookCompose() +{ + NS_IF_RELEASE(m_pListener); + NS_IF_RELEASE(m_pMsgFields); + if (m_pIdentity) { + nsresult rv = m_pIdentity->ClearAllValues(); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to clear values"); + if (NS_FAILED(rv)) + return; + } + delete[] m_optimizationBuffer; +} + +nsIMsgIdentity * nsOutlookCompose::m_pIdentity = nullptr; + +nsresult nsOutlookCompose::CreateIdentity(void) +{ + if (m_pIdentity) + return NS_OK; + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accMgr = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = accMgr->CreateIdentity(&m_pIdentity); + nsString name; + name.AssignLiteral("Import Identity"); + if (m_pIdentity) { + m_pIdentity->SetFullName(name); + m_pIdentity->SetEmail(NS_LITERAL_CSTRING("import@service.invalid")); + } + return rv; +} + +void nsOutlookCompose::ReleaseIdentity() +{ + NS_IF_RELEASE(m_pIdentity); +} + +nsresult nsOutlookCompose::CreateComponents(void) +{ + nsresult rv = NS_OK; + + NS_IF_RELEASE(m_pMsgFields); + if (!m_pListener && NS_SUCCEEDED(rv)) + rv = OutlookSendListener::CreateSendListener(&m_pListener); + + if (NS_SUCCEEDED(rv)) { + rv = CallCreateInstance(kMsgCompFieldsCID, &m_pMsgFields); + if (NS_SUCCEEDED(rv) && m_pMsgFields) { + // IMPORT_LOG0("nsOutlookCompose - CreateComponents succeeded\n"); + m_pMsgFields->SetForcePlainText(false); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +nsresult nsOutlookCompose::ComposeTheMessage(nsMsgDeliverMode mode, CMapiMessage &msg, nsIFile **pMsg) +{ + nsresult rv = CreateComponents(); + NS_ENSURE_SUCCESS(rv, rv); + rv = CreateIdentity(); + NS_ENSURE_SUCCESS(rv, rv); + + // IMPORT_LOG0("Outlook Compose created necessary components\n"); + + CMapiMessageHeaders* headers = msg.GetHeaders(); + + nsString unival; + headers->UnfoldValue(CMapiMessageHeaders::hdrFrom, unival, msg.GetBodyCharset()); + m_pMsgFields->SetFrom(unival); + headers->UnfoldValue(CMapiMessageHeaders::hdrTo, unival, msg.GetBodyCharset()); + m_pMsgFields->SetTo(unival); + headers->UnfoldValue(CMapiMessageHeaders::hdrSubject, unival, msg.GetBodyCharset()); + m_pMsgFields->SetSubject(unival); + m_pMsgFields->SetCharacterSet(msg.GetBodyCharset()); + headers->UnfoldValue(CMapiMessageHeaders::hdrCc, unival, msg.GetBodyCharset()); + m_pMsgFields->SetCc(unival); + headers->UnfoldValue(CMapiMessageHeaders::hdrReplyTo, unival, msg.GetBodyCharset()); + m_pMsgFields->SetReplyTo(unival); + m_pMsgFields->SetMessageId(headers->Value(CMapiMessageHeaders::hdrMessageID)); + + // We only use those headers that may need to be processed by Thunderbird + // to create a good rfc822 document, or need to be encoded (like To and Cc). + // These will replace the originals on import. All the other headers + // will be copied to the destination unaltered in CopyComposedMessage(). + + nsCOMPtr<nsIArray> pAttach; + msg.GetAttachments(getter_AddRefs(pAttach)); + + nsString bodyW; + // Bug 593907 + if (GenerateHackSequence(msg.GetBody(), msg.GetBodyLen())) + HackBody(msg.GetBody(), msg.GetBodyLen(), bodyW); + else + bodyW = msg.GetBody(); + // End Bug 593907 + + nsCOMPtr<nsIMutableArray> embeddedObjects; + + if (msg.BodyIsHtml()) { + for (unsigned int i = 0; i <msg.EmbeddedAttachmentsCount(); i++) { + nsIURI *uri; + const char* cid; + const char* name; + if (msg.GetEmbeddedAttachmentInfo(i, &uri, &cid, &name)) { + if (!embeddedObjects) { + embeddedObjects = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCOMPtr<nsIMsgEmbeddedImageData> imageData = + new nsImportEmbeddedImageData(uri, nsDependentCString(cid), + nsDependentCString(name)); + embeddedObjects->AppendElement(imageData, false); + } + } + } + + nsCString bodyA; + nsMsgI18NConvertFromUnicode(msg.GetBodyCharset(), bodyW, bodyA); + + nsCOMPtr<nsIImportService> impService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = impService->CreateRFC822Message( + m_pIdentity, // dummy identity + m_pMsgFields, // message fields + msg.BodyIsHtml() ? "text/html" : "text/plain", + bodyA, // body pointer + mode == nsIMsgSend::nsMsgSaveAsDraft, + pAttach, // local attachments + embeddedObjects, + m_pListener); // listener + + OutlookSendListener *pListen = (OutlookSendListener *)m_pListener; + if (NS_FAILED(rv)) { + IMPORT_LOG1("*** Error, CreateAndSendMessage FAILED: 0x%lx\n", rv); + } + else { + // wait for the listener to get done! + int32_t abortCnt = 0; + int32_t cnt = 0; + int32_t sleepCnt = 1; + while (!pListen->m_done && (abortCnt < kHungAbortCount)) { + PR_Sleep(sleepCnt); + cnt++; + if (cnt > kHungCount) { + abortCnt++; + sleepCnt *= 2; + cnt = 0; + } + } + + if (abortCnt >= kHungAbortCount) { + IMPORT_LOG0("**** Create and send message hung\n"); + rv = NS_ERROR_FAILURE; + } + } + + if (pListen->m_location) { + pListen->m_location->Clone(pMsg); + rv = NS_OK; + } + else { + rv = NS_ERROR_FAILURE; + IMPORT_LOG0("*** Error, Outlook compose unsuccessful\n"); + } + + pListen->Reset(); + return rv; +} + +nsresult nsOutlookCompose::CopyComposedMessage(nsIFile *pSrc, + nsIOutputStream *pDst, + CMapiMessage& origMsg) +{ + // I'm unsure if we really need the convertCRs feature here. + // The headers in the file are generated by TB, the body was generated by rtf reader that always used CRLF, + // and the attachments were processed by TB either... However, I let it stay as it was in the original code. + CCompositionFile f(pSrc, m_optimizationBuffer, FILE_IO_BUFFER_SIZE, true); + if (!f) { + IMPORT_LOG0("*** Error, unexpected zero file size for composed message\n"); + return NS_ERROR_FAILURE; + } + + // The "From ..." separates the messages. Without it, TB cannot see the messages in the mailbox file. + // Thus, the lines that look like "From ..." in the message must be escaped (see EscapeFromSpaceLine()) + int fromLineLen; + const char* fromLine = origMsg.GetFromLine(fromLineLen); + uint32_t written; + nsresult rv = pDst->Write(fromLine, fromLineLen, &written); + + // Bug 219269 + // Write out the x-mozilla-status headers. + char statusLine[50]; + uint32_t msgFlags = 0; + if (origMsg.IsRead()) + msgFlags |= nsMsgMessageFlags::Read; + if (!origMsg.FullMessageDownloaded()) + msgFlags |= nsMsgMessageFlags::Partial; + if (origMsg.IsForvarded()) + msgFlags |= nsMsgMessageFlags::Forwarded; + if (origMsg.IsReplied()) + msgFlags |= nsMsgMessageFlags::Replied; + if (origMsg.HasAttach()) + msgFlags |= nsMsgMessageFlags::Attachment; + _snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF); + rv = pDst->Write(statusLine, strlen(statusLine), &written); + _snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000); + rv = pDst->Write(statusLine, strlen(statusLine), &written); + // End Bug 219269 + + // well, isn't this a hoot! + // Read the headers from the new message, get the ones we like + // and write out only the headers we want from the new message, + // along with all of the other headers from the "old" message! + + nsCString newHeadersStr; + rv = f.ToString(newHeadersStr, MSG_LINEBREAK MSG_LINEBREAK); // Read all the headers + NS_ENSURE_SUCCESS(rv, rv); + UpdateHeaders(*origMsg.GetHeaders(), newHeadersStr.get()); + rv = origMsg.GetHeaders()->ToStream(pDst); + NS_ENSURE_SUCCESS(rv, rv); + + // Bug 593907 + if (!m_hackedPostfix.IsEmpty()) { + nsCString hackedPartEnd; + LossyCopyUTF16toASCII(m_hackedPostfix, hackedPartEnd); + hackedPartEnd.Insert(hackEndA, 0); + nsCString body; + rv = f.ToString(body, hackedPartEnd.get(), hackedPartEnd.Length()); + UnhackBody(body); + EscapeFromSpaceLine(pDst, const_cast<char*>(body.get()), body.get()+body.Length()); + } + // End Bug 593907 + + // I use the terminating sequence here to avoid a possible situation when a "From " line + // gets split over two sequential reads and thus will not be escaped. + // This is done by reading up to CRLF (one line every time), though it may be slower + + // Here I revert the changes that were made when the multipart/related message + // was composed in nsMsgSend::ProcessMultipartRelated() - the Content-Ids of + // attachments were replaced with new ones. + nsCString line; + while (NS_SUCCEEDED(f.ToString(line, MSG_LINEBREAK))) { + EscapeFromSpaceLine(pDst, const_cast<char*>(line.get()), line.get()+line.Length()); + } + + if (f.LastChar() != nsCRT::LF) { + rv = pDst->Write(MSG_LINEBREAK, 2, &written); + if (written != 2) + rv = NS_ERROR_FAILURE; + } + + return rv; +} + +nsresult nsOutlookCompose::ProcessMessage(nsMsgDeliverMode mode, + CMapiMessage &msg, + nsIOutputStream *pDst) +{ + nsCOMPtr<nsIFile> compositionFile; + nsresult rv = ComposeTheMessage(mode, msg, getter_AddRefs(compositionFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = CopyComposedMessage(compositionFile, pDst, msg); + compositionFile->Remove(false); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error copying composed message to destination mailbox\n"); + } + return rv; +} + +void nsOutlookCompose::UpdateHeader(CMapiMessageHeaders& oldHeaders, + const CMapiMessageHeaders& newHeaders, + CMapiMessageHeaders::SpecialHeader header, + bool addIfAbsent) +{ + const char* oldVal = oldHeaders.Value(header); + if (!addIfAbsent && !oldVal) + return; + const char* newVal = newHeaders.Value(header); + if (!newVal) + return; + // Bug 145150 - Turn "Content-Type: application/ms-tnef" into "Content-Type: text/plain" + // so the body text can be displayed normally (instead of in an attachment). + if (header == CMapiMessageHeaders::hdrContentType) + if (stricmp(newVal, "application/ms-tnef") == 0) + newVal = "text/plain"; + // End Bug 145150 + if (oldVal) { + if (strcmp(oldVal, newVal) == 0) + return; + // Backup the old header value + nsCString backupHdrName("X-MozillaBackup-"); + backupHdrName += CMapiMessageHeaders::SpecialName(header); + oldHeaders.SetValue(backupHdrName.get(), oldVal, false); + } + // Now replace it with new value + oldHeaders.SetValue(header, newVal); +} + +void nsOutlookCompose::UpdateHeaders(CMapiMessageHeaders& oldHeaders, const CMapiMessageHeaders& newHeaders) +{ + // Well, ain't this a peach? + // This is rather disgusting but there really isn't much to be done about it.... + + // 1. For each "old" header, replace it with the new one if we want, + // then right it out. + // 2. Then if we haven't written the "important" new headers, write them out + // 3. Terminate the headers with an extra eol. + + // Important headers: + // "Content-type", + // "MIME-Version", + // "Content-transfer-encoding" + // consider "X-Accept-Language"? + + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrContentType); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrMimeVersion); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrContentTransferEncoding); + + // Other replaced headers (only if they exist): + // "From", + // "To", + // "Subject", + // "Reply-to", + // "Cc" + + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrFrom, false); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrTo, false); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrSubject, false); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrReplyTo, false); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrCc, false); +} + +// Bug 593907 +// This is just a workaround of the deficiency of the nsMsgComposeAndSend::EnsureLineBreaks(). +// The import from Outlook will stay OK (I hope), but other messages may still suffer. +// However, I cannot deny the possibility that the (possible) recode of the body +// may interfere with this hack. A possible scenario is if a multi-byte character will either +// contain 0x0D 0x0A sequence, or end with 0x0D, after which MAC-style standalone LF will go. +// I hope that this possibility is insignificant (eg, utf-8 doesn't contain such sequences). +// This hack will slow down the import, but as the import is one-time procedure, I hope that +// the user will agree to wait a little longer to get better results. + +// The process of composing the message differs depending on whether the editor is present or not. +// If the editor is absent, the "attachment1_body" parameter of CreateAndSendMessage() is taken as is, +// while in the presence o the editor, the body that is taken from it is further processed in the +// nsMsgComposeAndSend::GetBodyFromEditor(). Specifically, the TXTToHTML::ScanHTML() first calls +// UnescapeStr() to properly handle a limited number of HTML character entities (namely & < > ") +// and then calls ScanTXT() where escapes all ampersands and quotes again. As the UnescapeStr() works so +// selectively (i.e. handling only a subset of valid entities), the so often seen " " becomes "&nbsp;" +// in the resulting body, which leads to text " " interspersed all over the imported mail. The same +// applies to html &#XXXX; (where XXXX is unicode codepoint). +// See also Bug 503690, where the same issue in Eudora import is reported. +// By the way, the root of the Bug 359303 lies in the same place - the nsMsgComposeAndSend::GetBodyFromEditor() +// changes the 0xA0 codes to 0x20 when it converts the body to plain text. +// We scan the body here to find all the & and convert them to the safe character sequense to revert later. + +void nsOutlookCompose::HackBody(const wchar_t* orig, size_t origLen, nsString& hack) +{ +#ifdef MOZILLA_INTERNAL_API + hack.SetCapacity(static_cast<size_t>(origLen*1.4)); +#endif + hack.Assign(hackBeginW); + hack.Append(m_hackedPostfix); + + while (*orig) { + if (*orig == L'&') { + hack.Append(hackAmpersandW); + hack.Append(m_hackedPostfix); + } else if ((*orig == L'\x0D') && (*(orig+1) == L'\x0A')) { + hack.Append(hackCRLFW); + hack.Append(m_hackedPostfix); + ++orig; + } else + hack.Append(*orig); + ++orig; + } + + hack.Append(hackEndW); + hack.Append(m_hackedPostfix); +} + +void nsOutlookCompose::UnhackBody(nsCString& txt) +{ + nsCString hackedPostfixA; + LossyCopyUTF16toASCII(m_hackedPostfix, hackedPostfixA); + + nsCString hackedString(hackBeginA); + hackedString.Append(hackedPostfixA); + int32_t begin = txt.Find(hackedString); + if (begin == kNotFound) + return; + txt.Cut(begin, hackedString.Length()); + + hackedString.Assign(hackEndA); + hackedString.Append(hackedPostfixA); + int32_t end = MsgFind(txt, hackedString, false, begin); + if (end == kNotFound) + return; // ? + txt.Cut(end, hackedString.Length()); + + nsCString range; + range.Assign(Substring(txt, begin, end - begin)); + // 1. Remove all CRLFs from the selected range + MsgReplaceSubstring(range, MSG_LINEBREAK, ""); + // 2. Restore the original CRLFs + hackedString.Assign(hackCRLFA); + hackedString.Append(hackedPostfixA); + MsgReplaceSubstring(range, hackedString.get(), MSG_LINEBREAK); + + // 3. Restore the original ampersands + hackedString.Assign(hackAmpersandA); + hackedString.Append(hackedPostfixA); + MsgReplaceSubstring(range, hackedString.get(), "&"); + + txt.Replace(begin, end - begin, range); +} + +bool nsOutlookCompose::GenerateHackSequence(const wchar_t* body, size_t origLen) +{ + nsDependentString nsBody(body, origLen); + const wchar_t* hack_base = L"hacked"; + int i = 0; + do { + if (++i == 0) { // Cycle complete :) - could not generate an unique string + m_hackedPostfix.Truncate(); + return false; + } + m_hackedPostfix.Assign(hack_base); + m_hackedPostfix.AppendInt(i); + } while (nsBody.Find(m_hackedPostfix) != kNotFound); + + return true; +} +// End Bug 593907 + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +CCompositionFile::CCompositionFile(nsIFile* aFile, void* fifoBuffer, + uint32_t fifoBufferSize, bool convertCRs) + : m_pFile(aFile), m_fileSize(0), m_fileReadPos(0), + m_fifoBuffer(static_cast<char*>(fifoBuffer)), + m_fifoBufferSize(fifoBufferSize), + m_fifoBufferReadPos(static_cast<char*>(fifoBuffer)), + m_fifoBufferWrittenPos(static_cast<char*>(fifoBuffer)), + m_convertCRs(convertCRs), + m_lastChar(0) +{ + m_pFile->GetFileSize(&m_fileSize); + if (!m_fileSize) { + IMPORT_LOG0("*** Error, unexpected zero file size for composed message\n"); + return; + } + + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(m_pInputStream), m_pFile); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error, unable to open composed message file\n"); + return; + } +} + +nsresult CCompositionFile::EnsureHasDataInBuffer() +{ + if (m_fifoBufferReadPos < m_fifoBufferWrittenPos) + return NS_OK; + // Populate the buffer with new data! + uint32_t count = m_fifoBufferSize; + if ((m_fileReadPos + count) > m_fileSize) + count = m_fileSize - m_fileReadPos; + if (!count) + return NS_ERROR_FAILURE; // Isn't there a "No more data" error? + + uint32_t bytesRead = 0; + nsresult rv = m_pInputStream->Read(m_fifoBuffer, count, &bytesRead); + NS_ENSURE_SUCCESS(rv, rv); + if (!bytesRead || (bytesRead > count)) + return NS_ERROR_FAILURE; + m_fifoBufferWrittenPos = m_fifoBuffer+bytesRead; + m_fifoBufferReadPos = m_fifoBuffer; + m_fileReadPos += bytesRead; + + return NS_OK; +} + +class CTermGuard { +public: + CTermGuard(const char* term, int termSize) + : m_term(term), + m_termSize(term ? ((termSize!=-1) ? termSize : strlen(term)) : 0), + m_matchPos(0) + {} + + // if the guard triggered + inline bool IsTriggered() const { + return m_termSize && (m_matchPos == m_termSize); } + // indicates if the guard has something to check + inline bool IsChecking() const { return m_termSize; } + + bool Check(char c) // returns true only if the whole sequence passed + { + if (!m_termSize) // no guard + return false; + if (m_matchPos >= m_termSize) // check past success! + m_matchPos = 0; + if (m_term[m_matchPos] != c) // Reset sequence + m_matchPos = 0; + if (m_term[m_matchPos] == c) { // Sequence continues + return ++m_matchPos == m_termSize; // If equal then sequence complete! + } + // Sequence broken + return false; + } +private: + const char* m_term; + int m_termSize; + int m_matchPos; +}; + +template <class _OutFn> +nsresult CCompositionFile::ToDest(_OutFn dest, const char* term, int termSize) +{ + CTermGuard guard(term, termSize); + +#ifdef MOZILLA_INTERNAL_API + // We already know the required string size, so reduce future reallocations + if (!guard.IsChecking() && !m_convertCRs) + dest.SetCapacity(m_fileSize - m_fileReadPos); +#endif + + bool wasCR = false; + char c = 0; + nsresult rv; + while (NS_SUCCEEDED(rv = EnsureHasDataInBuffer())) { + if (!guard.IsChecking() && !m_convertCRs) { // Use efficient algorithm + dest.Append(m_fifoBufferReadPos, m_fifoBufferWrittenPos-m_fifoBufferReadPos); + } + else { // Check character by character to convert CRs and find terminating sequence + while (m_fifoBufferReadPos < m_fifoBufferWrittenPos) { + c = *m_fifoBufferReadPos; + if (m_convertCRs && wasCR) { + wasCR = false; + if (c != nsCRT::LF) { + const char kTmpLF = nsCRT::LF; + dest.Append(&kTmpLF, 1); + if (guard.Check(nsCRT::LF)) { + c = nsCRT::LF; // save last char + break; + } + } + } + dest.Append(&c, 1); + m_fifoBufferReadPos++; + + if (guard.Check(c)) + break; + + if (m_convertCRs && (c == nsCRT::CR)) + wasCR = true; + } + if (guard.IsTriggered()) + break; + } + } + + // check for trailing CR (only if caller didn't specify the terminating sequence that ends with CR - + // in this case he knows what he does!) + if (m_convertCRs && !guard.IsTriggered() && (c == nsCRT::CR)) { + c = nsCRT::LF; + dest.Append(&c, 1); + } + + NS_ENSURE_SUCCESS(rv, rv); + + m_lastChar = c; + return NS_OK; +} + +class dest_nsCString { +public: + dest_nsCString(nsCString& str) : m_str(str) { m_str.Truncate(); } +#ifdef MOZILLA_INTERNAL_API + void SetCapacity(int32_t sz) { m_str.SetCapacity(sz); } +#endif + nsresult Append(const char* buf, uint32_t count) { + m_str.Append(buf, count); return NS_OK; } +private: + nsCString& m_str; +}; + +class dest_Stream { +public: + dest_Stream(nsIOutputStream *dest) : m_stream(dest) {} +#ifdef MOZILLA_INTERNAL_API + void SetCapacity(int32_t) { /*do nothing*/ } +#endif + // const_cast here is due to the poor design of the EscapeFromSpaceLine() + // that requires a non-constant pointer while doesn't modify its data + nsresult Append(const char* buf, uint32_t count) { + return EscapeFromSpaceLine(m_stream, const_cast<char*>(buf), buf+count); } +private: + nsIOutputStream *m_stream; +}; + +nsresult CCompositionFile::ToString(nsCString& dest, const char* term, + int termSize) +{ + return ToDest(dest_nsCString(dest), term, termSize); +} + +nsresult CCompositionFile::ToStream(nsIOutputStream *dest, const char* term, + int termSize) +{ + return ToDest(dest_Stream(dest), term, termSize); +} diff --git a/mailnews/import/outlook/src/nsOutlookCompose.h b/mailnews/import/outlook/src/nsOutlookCompose.h new file mode 100644 index 000000000..68f07f754 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookCompose.h @@ -0,0 +1,66 @@ +/* -*- 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 nsOutlookCompose_h__ +#define nsOutlookCompose_h__ + +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsIFile.h" +#include "nsIImportService.h" + +class nsIMsgSend; +class nsIMsgCompFields; +class nsIMsgIdentity; +class nsIMsgSendListener; +class nsIIOService; + +#include "nsIMsgSend.h" +#include "nsNetUtil.h" + +#include "MapiMessage.h" + +#include <list> + +/////////////////////////////////////////////////////////////////////////////////////////////// + +class nsOutlookCompose { +public: + nsOutlookCompose(); + ~nsOutlookCompose(); + + nsresult ProcessMessage(nsMsgDeliverMode mode, CMapiMessage &msg, nsIOutputStream *pDst); + static nsresult CreateIdentity(void); + static void ReleaseIdentity(void); +private: + nsresult CreateComponents(void); + + void UpdateHeader(CMapiMessageHeaders& oldHeaders, const CMapiMessageHeaders& newHeaders, CMapiMessageHeaders::SpecialHeader header, bool addIfAbsent = true); + void UpdateHeaders(CMapiMessageHeaders& oldHeaders, const CMapiMessageHeaders& newHeaders); + + nsresult ComposeTheMessage(nsMsgDeliverMode mode, CMapiMessage &msg, nsIFile **pMsg); + nsresult CopyComposedMessage(nsIFile *pSrc, nsIOutputStream *pDst, CMapiMessage& origMsg); + + // Bug 593907 + void HackBody(const wchar_t* orig, size_t origLen, nsString& hack); + void UnhackBody(nsCString& body); + bool GenerateHackSequence(const wchar_t* body, size_t origLen); + // End Bug 593907 + +private: + nsIMsgSendListener * m_pListener; + nsIMsgCompFields * m_pMsgFields; + static nsIMsgIdentity * m_pIdentity; + char* m_optimizationBuffer; + nsCOMPtr<nsIImportService> m_pImportService; + + // Bug 593907 + nsString m_hackedPostfix; + // End Bug 593907 +}; + + +#endif /* nsOutlookCompose_h__ */ diff --git a/mailnews/import/outlook/src/nsOutlookImport.cpp b/mailnews/import/outlook/src/nsOutlookImport.cpp new file mode 100644 index 000000000..eaaf24fc3 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookImport.cpp @@ -0,0 +1,589 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +/* + Outlook Express (Win32) import mail and addressbook interfaces +*/ +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsMsgUtils.h" +#include "nsIServiceManager.h" +#include "nsIImportService.h" +#include "nsIComponentManager.h" +#include "nsOutlookImport.h" +#include "nsIMemory.h" +#include "nsIImportService.h" +#include "nsIImportMail.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIImportGeneric.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportFieldMap.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIOutputStream.h" +#include "nsIAddrDatabase.h" +#include "nsOutlookSettings.h" +#include "nsTextFormatter.h" +#include "nsOutlookStringBundle.h" +#include "nsIStringBundle.h" +#include "OutlookDebugLog.h" +#include "nsUnicharUtils.h" + +#include "nsOutlookMail.h" + +#include "MapiApi.h" + +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); +PRLogModuleInfo *OUTLOOKLOGMODULE = nullptr; + +class ImportOutlookMailImpl : public nsIImportMail +{ +public: + ImportOutlookMailImpl(); + + static nsresult Create(nsIImportMail** aImport); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportmail interface + + /* void GetDefaultLocation (out nsIFile location, out boolean found, out boolean userVerify); */ + NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found, bool *userVerify); + + /* nsIArray FindMailboxes (in nsIFile location); */ + NS_IMETHOD FindMailboxes(nsIFile *location, nsIArray **_retval); + + NS_IMETHOD ImportMailbox(nsIImportMailboxDescriptor *source, + nsIMsgFolder *dstFolder, + char16_t **pErrorLog, char16_t **pSuccessLog, + bool *fatalError); + + /* unsigned long GetImportProgress (); */ + NS_IMETHOD GetImportProgress(uint32_t *_retval); + + NS_IMETHOD TranslateFolderName(const nsAString & aFolderName, nsAString & _retval); + +public: + static void ReportSuccess(nsString& name, int32_t count, nsString *pStream); + static void ReportError(int32_t errorNum, nsString& name, nsString *pStream); + static void AddLinebreak(nsString *pStream); + static void SetLogs(nsString& success, nsString& error, char16_t **pError, char16_t **pSuccess); + +private: + virtual ~ImportOutlookMailImpl(); + nsOutlookMail m_mail; + uint32_t m_bytesDone; +}; + + +class ImportOutlookAddressImpl : public nsIImportAddressBooks +{ +public: + ImportOutlookAddressImpl(); + + static nsresult Create(nsIImportAddressBooks** aImport); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportAddressBooks interface + + NS_IMETHOD GetSupportsMultiple(bool *_retval) { *_retval = true; return NS_OK;} + + NS_IMETHOD GetAutoFind(char16_t **description, bool *_retval); + + NS_IMETHOD GetNeedsFieldMap(nsIFile *location, bool *_retval) { *_retval = false; return NS_OK;} + + NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found, bool *userVerify) + { return NS_ERROR_FAILURE;} + + NS_IMETHOD FindAddressBooks(nsIFile *location, nsIArray **_retval); + + NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap) + { return NS_ERROR_FAILURE; } + + NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source, + nsIAddrDatabase *destination, + nsIImportFieldMap *fieldMap, + nsISupports *aSupportService, + char16_t **errorLog, + char16_t **successLog, + bool *fatalError); + + NS_IMETHOD GetImportProgress(uint32_t *_retval); + + NS_IMETHOD GetSampleData(int32_t index, bool *pFound, char16_t **pStr) + { return NS_ERROR_FAILURE;} + + NS_IMETHOD SetSampleLocation(nsIFile *) { return NS_OK; } + +private: + virtual ~ImportOutlookAddressImpl(); + void ReportSuccess(nsString& name, nsString *pStream); + +private: + uint32_t m_msgCount; + uint32_t m_msgTotal; + nsOutlookMail m_address; +}; +//////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////// + + +nsOutlookImport::nsOutlookImport() +{ + // Init logging module. + if (!OUTLOOKLOGMODULE) + OUTLOOKLOGMODULE = PR_NewLogModule("IMPORT"); + + IMPORT_LOG0("nsOutlookImport Module Created\n"); + + nsOutlookStringBundle::GetStringBundle(); +} + + +nsOutlookImport::~nsOutlookImport() +{ + IMPORT_LOG0("nsOutlookImport Module Deleted\n"); +} + +NS_IMPL_ISUPPORTS(nsOutlookImport, nsIImportModule) + +NS_IMETHODIMP nsOutlookImport::GetName(char16_t **name) +{ + NS_PRECONDITION(name != nullptr, "null ptr"); + if (! name) + return NS_ERROR_NULL_POINTER; + + *name = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME); + return NS_OK; +} + +NS_IMETHODIMP nsOutlookImport::GetDescription(char16_t **name) +{ + NS_PRECONDITION(name != nullptr, "null ptr"); + if (!name) + return NS_ERROR_NULL_POINTER; + + *name = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_DESCRIPTION); + + return NS_OK; +} + +NS_IMETHODIMP nsOutlookImport::GetSupports(char **supports) +{ + NS_PRECONDITION(supports != nullptr, "null ptr"); + if (! supports) + return NS_ERROR_NULL_POINTER; + + *supports = strdup(kOutlookSupportsString); + return NS_OK; +} + +NS_IMETHODIMP nsOutlookImport::GetSupportsUpgrade(bool *pUpgrade) +{ + NS_PRECONDITION(pUpgrade != nullptr, "null ptr"); + if (! pUpgrade) + return NS_ERROR_NULL_POINTER; + + *pUpgrade = true; + return NS_OK; +} + +NS_IMETHODIMP nsOutlookImport::GetImportInterface(const char *pImportType, nsISupports **ppInterface) +{ + NS_PRECONDITION(pImportType != nullptr, "null ptr"); + if (! pImportType) + return NS_ERROR_NULL_POINTER; + NS_PRECONDITION(ppInterface != nullptr, "null ptr"); + if (! ppInterface) + return NS_ERROR_NULL_POINTER; + + *ppInterface = nullptr; + nsresult rv; + if (!strcmp(pImportType, "mail")) { + // create the nsIImportMail interface and return it! + nsIImportMail * pMail = nullptr; + nsIImportGeneric *pGeneric = nullptr; + rv = ImportOutlookMailImpl::Create(&pMail); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericMail(&pGeneric); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("mailInterface", pMail); + nsString name; + nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME, name); + nsCOMPtr<nsISupportsString> nameString (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nameString->SetData(name); + pGeneric->SetData("name", nameString); + rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface); + } + } + } + } + NS_IF_RELEASE(pMail); + NS_IF_RELEASE(pGeneric); + return rv; + } + + if (!strcmp(pImportType, "addressbook")) { + // create the nsIImportAddressBook interface and return it! + nsIImportAddressBooks * pAddress = nullptr; + nsIImportGeneric * pGeneric = nullptr; + rv = ImportOutlookAddressImpl::Create(&pAddress); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericAddressBooks(&pGeneric); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("addressInterface", pAddress); + rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface); + } + } + } + NS_IF_RELEASE(pAddress); + NS_IF_RELEASE(pGeneric); + return rv; + } + + if (!strcmp(pImportType, "settings")) { + nsIImportSettings *pSettings = nullptr; + rv = nsOutlookSettings::Create(&pSettings); + if (NS_SUCCEEDED(rv)) + pSettings->QueryInterface(kISupportsIID, (void **)ppInterface); + NS_IF_RELEASE(pSettings); + return rv; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +///////////////////////////////////////////////////////////////////////////////// +nsresult ImportOutlookMailImpl::Create(nsIImportMail** aImport) +{ + NS_PRECONDITION(aImport != nullptr, "null ptr"); + if (! aImport) + return NS_ERROR_NULL_POINTER; + + *aImport = new ImportOutlookMailImpl(); + if (! *aImport) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aImport); + return NS_OK; +} + +ImportOutlookMailImpl::ImportOutlookMailImpl() +{ + nsOutlookCompose::CreateIdentity(); +} + +ImportOutlookMailImpl::~ImportOutlookMailImpl() +{ + nsOutlookCompose::ReleaseIdentity(); +} + +NS_IMPL_ISUPPORTS(ImportOutlookMailImpl, nsIImportMail) + +NS_IMETHODIMP ImportOutlookMailImpl::GetDefaultLocation(nsIFile **ppLoc, bool *found, bool *userVerify) +{ + NS_PRECONDITION(ppLoc != nullptr, "null ptr"); + NS_PRECONDITION(found != nullptr, "null ptr"); + NS_PRECONDITION(userVerify != nullptr, "null ptr"); + if (!ppLoc || !found || !userVerify) + return NS_ERROR_NULL_POINTER; + + *found = false; + *ppLoc = nullptr; + *userVerify = false; + // We need to verify here that we can get the mail, if true then + // return a dummy location, otherwise return no location + CMapiApi mapi; + if (!mapi.Initialize()) + return NS_OK; + if (!mapi.LogOn()) + return NS_OK; + + CMapiFolderList store; + if (!mapi.IterateStores(store)) + return NS_OK; + + if (store.GetSize() == 0) + return NS_OK; + + + nsresult rv; + nsCOMPtr <nsIFile> resultFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + *found = true; + NS_IF_ADDREF(*ppLoc = resultFile); + *userVerify = false; + + return NS_OK; +} + + +NS_IMETHODIMP ImportOutlookMailImpl::FindMailboxes(nsIFile *pLoc, nsIArray **ppArray) +{ + NS_PRECONDITION(pLoc != nullptr, "null ptr"); + NS_PRECONDITION(ppArray != nullptr, "null ptr"); + if (!pLoc || !ppArray) + return NS_ERROR_NULL_POINTER; + return m_mail.GetMailFolders(ppArray); +} + +void ImportOutlookMailImpl::AddLinebreak(nsString *pStream) +{ + if (pStream) + pStream->Append(char16_t('\n')); +} + +void ImportOutlookMailImpl::ReportSuccess(nsString& name, int32_t count, nsString *pStream) +{ + if (!pStream) + return; + // load the success string + char16_t *pFmt = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_MAILBOX_SUCCESS); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get(), count); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + nsOutlookStringBundle::FreeString(pFmt); + AddLinebreak(pStream); +} + +void ImportOutlookMailImpl::ReportError(int32_t errorNum, nsString& name, nsString *pStream) +{ + if (!pStream) + return; + // load the error string + char16_t *pFmt = nsOutlookStringBundle::GetStringByID(errorNum); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + nsOutlookStringBundle::FreeString(pFmt); + AddLinebreak(pStream); +} + + +void ImportOutlookMailImpl::SetLogs(nsString& success, nsString& error, char16_t **pError, char16_t **pSuccess) +{ + if (pError) + *pError = ToNewUnicode(error); + if (pSuccess) + *pSuccess = ToNewUnicode(success); +} + +NS_IMETHODIMP +ImportOutlookMailImpl::ImportMailbox(nsIImportMailboxDescriptor *pSource, + nsIMsgFolder *dstFolder, + char16_t **pErrorLog, + char16_t **pSuccessLog, + bool *fatalError) +{ + NS_ENSURE_ARG_POINTER(pSource); + NS_ENSURE_ARG_POINTER(dstFolder); + NS_ENSURE_ARG_POINTER(fatalError); + + nsString success; + nsString error; + bool abort = false; + nsString name; + char16_t *pName; + if (NS_SUCCEEDED( pSource->GetDisplayName( &pName))) { + name = pName; + NS_Free( pName); + } + + uint32_t mailSize = 0; + pSource->GetSize(&mailSize); + if (mailSize == 0) { + ReportSuccess(name, 0, &success); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_OK; + } + + uint32_t index = 0; + pSource->GetIdentifier(&index); + int32_t msgCount = 0; + nsresult rv = NS_OK; + + m_bytesDone = 0; + + rv = m_mail.ImportMailbox(&m_bytesDone, &abort, (int32_t)index, name.get(), + dstFolder, &msgCount); + + if (NS_SUCCEEDED(rv)) + ReportSuccess(name, msgCount, &success); + else + ReportError(OUTLOOKIMPORT_MAILBOX_CONVERTERROR, name, &error); + + SetLogs(success, error, pErrorLog, pSuccessLog); + + return rv; +} + + +NS_IMETHODIMP ImportOutlookMailImpl::GetImportProgress(uint32_t *pDoneSoFar) +{ + NS_PRECONDITION(pDoneSoFar != nullptr, "null ptr"); + if (! pDoneSoFar) + return NS_ERROR_NULL_POINTER; + + *pDoneSoFar = m_bytesDone; + return NS_OK; +} + +NS_IMETHODIMP ImportOutlookMailImpl::TranslateFolderName(const nsAString & aFolderName, nsAString & _retval) +{ + if (aFolderName.LowerCaseEqualsLiteral("deleted items")) + _retval = NS_LITERAL_STRING(kDestTrashFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("sent items")) + _retval = NS_LITERAL_STRING(kDestSentFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("outbox")) + _retval = NS_LITERAL_STRING(kDestUnsentMessagesFolderName); + else + _retval = aFolderName; + return NS_OK; +} + +nsresult ImportOutlookAddressImpl::Create(nsIImportAddressBooks** aImport) +{ + NS_PRECONDITION(aImport != nullptr, "null ptr"); + if (! aImport) + return NS_ERROR_NULL_POINTER; + + *aImport = new ImportOutlookAddressImpl(); + if (! *aImport) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aImport); + return NS_OK; +} + +ImportOutlookAddressImpl::ImportOutlookAddressImpl() +{ + m_msgCount = 0; + m_msgTotal = 0; +} + +ImportOutlookAddressImpl::~ImportOutlookAddressImpl() +{ +} + +NS_IMPL_ISUPPORTS(ImportOutlookAddressImpl, nsIImportAddressBooks) + +NS_IMETHODIMP ImportOutlookAddressImpl::GetAutoFind(char16_t **description, bool *_retval) +{ + NS_PRECONDITION(description != nullptr, "null ptr"); + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! description || !_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = true; + nsString str; + nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRNAME, str); + *description = ToNewUnicode(str); + return NS_OK; +} + +NS_IMETHODIMP ImportOutlookAddressImpl::FindAddressBooks(nsIFile *location, nsIArray **_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + return m_address.GetAddressBooks(_retval); +} + +NS_IMETHODIMP ImportOutlookAddressImpl::ImportAddressBook(nsIImportABDescriptor *source, + nsIAddrDatabase *destination, + nsIImportFieldMap *fieldMap, + nsISupports *aSupportService, + char16_t **pErrorLog, + char16_t **pSuccessLog, + bool *fatalError) +{ + m_msgCount = 0; + m_msgTotal = 0; + NS_PRECONDITION(source != nullptr, "null ptr"); + NS_PRECONDITION(destination != nullptr, "null ptr"); + NS_PRECONDITION(fatalError != nullptr, "null ptr"); + + nsString success; + nsString error; + if (!source || !destination || !fatalError) { + IMPORT_LOG0("*** Bad param passed to outlook address import\n"); + nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRESS_BADPARAM, error); + if (fatalError) + *fatalError = true; + ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_ERROR_NULL_POINTER; + } + + nsString name; + source->GetPreferredName(name); + + uint32_t id; + if (NS_FAILED(source->GetIdentifier(&id))) { + ImportOutlookMailImpl::ReportError(OUTLOOKIMPORT_ADDRESS_BADSOURCEFILE, name, &error); + ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_OK; + rv = m_address.ImportAddresses(&m_msgCount, &m_msgTotal, name.get(), id, destination, error); + if (NS_SUCCEEDED(rv) && error.IsEmpty()) + ReportSuccess(name, &success); + else + ImportOutlookMailImpl::ReportError(OUTLOOKIMPORT_ADDRESS_CONVERTERROR, name, &error); + + ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog); + IMPORT_LOG0("*** Returning from outlook address import\n"); + return destination->Commit(nsAddrDBCommitType::kLargeCommit); +} + + +NS_IMETHODIMP ImportOutlookAddressImpl::GetImportProgress(uint32_t *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + uint32_t result = m_msgCount; + if (m_msgTotal) { + result *= 100; + result /= m_msgTotal; + } + else + result = 0; + + if (result > 100) + result = 100; + + *_retval = result; + + return NS_OK; +} + +void ImportOutlookAddressImpl::ReportSuccess(nsString& name, nsString *pStream) +{ + if (!pStream) + return; + // load the success string + char16_t *pFmt = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRESS_SUCCESS); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + nsOutlookStringBundle::FreeString(pFmt); + ImportOutlookMailImpl::AddLinebreak(pStream); +} diff --git a/mailnews/import/outlook/src/nsOutlookImport.h b/mailnews/import/outlook/src/nsOutlookImport.h new file mode 100644 index 000000000..e017d6efc --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookImport.h @@ -0,0 +1,44 @@ +/* -*- 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 nsOutlookImport_h___ +#define nsOutlookImport_h___ + +#include "nsIImportModule.h" +#include "nsCOMPtr.h" + + +#define NS_OUTLOOKIMPORT_CID \ +{ /* 1DB469A0-8B00-11d3-A206-00A0CC26DA63 */ \ + 0x1db469a0, 0x8b00, 0x11d3, \ + {0xa2, 0x6, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63 }} + + + + +#define kOutlookSupportsString NS_IMPORT_MAIL_STR "," NS_IMPORT_ADDRESS_STR "," NS_IMPORT_SETTINGS_STR + +class nsOutlookImport : public nsIImportModule +{ +public: + + nsOutlookImport(); + + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////////////////////// + // we suppport the nsIImportModule interface + //////////////////////////////////////////////////////////////////////////////////////// + + NS_DECL_NSIIMPORTMODULE + +protected: + virtual ~nsOutlookImport(); +}; + + + + +#endif /* nsOutlookImport_h___ */ diff --git a/mailnews/import/outlook/src/nsOutlookMail.cpp b/mailnews/import/outlook/src/nsOutlookMail.cpp new file mode 100644 index 000000000..b9a851ce8 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookMail.cpp @@ -0,0 +1,863 @@ +/* -*- 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/. */ + +/* + Outlook mail import +*/ + +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsMsgUtils.h" +#include "nsIServiceManager.h" +#include "nsIImportService.h" +#include "nsIImportFieldMap.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIImportABDescriptor.h" +#include "nsIMutableArray.h" +#include "nsOutlookStringBundle.h" +#include "nsABBaseCID.h" +#include "nsIAbCard.h" +#include "mdb.h" +#include "OutlookDebugLog.h" +#include "nsOutlookMail.h" +#include "nsUnicharUtils.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIMsgPluggableStore.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFolder.h" +#include "nsMsgI18N.h" +#include "nsNetUtil.h" + +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); + +/* ------------ Address book stuff ----------------- */ +typedef struct { + int32_t mozField; + int32_t multiLine; + ULONG mapiTag; +} MAPIFields; + +/* + Fields in MAPI, not in Mozilla + PR_OFFICE_LOCATION + FIX - PR_BIRTHDAY - stored as PT_SYSTIME - FIX to extract for moz address book birthday + PR_DISPLAY_NAME_PREFIX - Mr., Mrs. Dr., etc. + PR_SPOUSE_NAME + PR_GENDER - integer, not text + FIX - PR_CONTACT_EMAIL_ADDRESSES - multiuline strings for email addresses, needs + parsing to get secondary email address for mozilla +*/ + +#define kIsMultiLine -2 +#define kNoMultiLine -1 + +static MAPIFields gMapiFields[] = { + { 35, kIsMultiLine, PR_BODY}, + { 6, kNoMultiLine, PR_BUSINESS_TELEPHONE_NUMBER}, + { 7, kNoMultiLine, PR_HOME_TELEPHONE_NUMBER}, + { 25, kNoMultiLine, PR_COMPANY_NAME}, + { 23, kNoMultiLine, PR_TITLE}, + { 10, kNoMultiLine, PR_CELLULAR_TELEPHONE_NUMBER}, + { 9, kNoMultiLine, PR_PAGER_TELEPHONE_NUMBER}, + { 8, kNoMultiLine, PR_BUSINESS_FAX_NUMBER}, + { 8, kNoMultiLine, PR_HOME_FAX_NUMBER}, + { 22, kNoMultiLine, PR_COUNTRY}, + { 19, kNoMultiLine, PR_LOCALITY}, + { 20, kNoMultiLine, PR_STATE_OR_PROVINCE}, + { 17, 18, PR_STREET_ADDRESS}, + { 21, kNoMultiLine, PR_POSTAL_CODE}, + { 27, kNoMultiLine, PR_PERSONAL_HOME_PAGE}, + { 26, kNoMultiLine, PR_BUSINESS_HOME_PAGE}, + { 13, kNoMultiLine, PR_HOME_ADDRESS_CITY}, + { 16, kNoMultiLine, PR_HOME_ADDRESS_COUNTRY}, + { 15, kNoMultiLine, PR_HOME_ADDRESS_POSTAL_CODE}, + { 14, kNoMultiLine, PR_HOME_ADDRESS_STATE_OR_PROVINCE}, + { 11, 12, PR_HOME_ADDRESS_STREET}, + { 24, kNoMultiLine, PR_DEPARTMENT_NAME} +}; +/* ---------------------------------------------------- */ + + +#define kCopyBufferSize (16 * 1024) + +// The email address in Outlook Contacts doesn't have a named +// property, we need to use this mapi name ID to access the email +// The MAPINAMEID for email address has ulKind=MNID_ID +// Outlook stores each email address in two IDs, 32899/32900 for Email1 +// 32915/32916 for Email2, 32931/32932 for Email3 +// Current we use OUTLOOK_EMAIL1_MAPI_ID1 for primary email +// OUTLOOK_EMAIL2_MAPI_ID1 for secondary email +#define OUTLOOK_EMAIL1_MAPI_ID1 32899 +#define OUTLOOK_EMAIL1_MAPI_ID2 32900 +#define OUTLOOK_EMAIL2_MAPI_ID1 32915 +#define OUTLOOK_EMAIL2_MAPI_ID2 32916 +#define OUTLOOK_EMAIL3_MAPI_ID1 32931 +#define OUTLOOK_EMAIL3_MAPI_ID2 32932 + +nsOutlookMail::nsOutlookMail() +{ + m_gotAddresses = false; + m_gotFolders = false; + m_haveMapi = CMapiApi::LoadMapi(); + m_lpMdb = NULL; +} + +nsOutlookMail::~nsOutlookMail() +{ +// EmptyAttachments(); +} + +nsresult nsOutlookMail::GetMailFolders(nsIArray **pArray) +{ + if (!m_haveMapi) { + IMPORT_LOG0("GetMailFolders called before Mapi is initialized\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv; + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("FAILED to allocate the nsIMutableArray for the mail folder list\n"); + return rv; + } + + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + m_gotFolders = true; + + m_folderList.ClearAll(); + + m_mapi.Initialize(); + m_mapi.LogOn(); + + if (m_storeList.GetSize() == 0) + m_mapi.IterateStores(m_storeList); + + int i = 0; + CMapiFolder *pFolder; + if (m_storeList.GetSize() > 1) { + while ((pFolder = m_storeList.GetItem(i))) { + CMapiFolder *pItem = new CMapiFolder(pFolder); + pItem->SetDepth(1); + m_folderList.AddItem(pItem); + if (!m_mapi.GetStoreFolders(pItem->GetCBEntryID(), pItem->GetEntryID(), m_folderList, 2)) { + IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i); + } + i++; + } + } + else { + if ((pFolder = m_storeList.GetItem(i))) { + if (!m_mapi.GetStoreFolders(pFolder->GetCBEntryID(), pFolder->GetEntryID(), m_folderList, 1)) { + IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i); + } + } + } + + // Create the mailbox descriptors for the list of folders + nsIImportMailboxDescriptor * pID; + nsISupports * pInterface; + nsString name; + nsString uniName; + + for (i = 0; i < m_folderList.GetSize(); i++) { + pFolder = m_folderList.GetItem(i); + rv = impSvc->CreateNewMailboxDescriptor(&pID); + if (NS_SUCCEEDED(rv)) { + pID->SetDepth(pFolder->GetDepth()); + pID->SetIdentifier(i); + + pFolder->GetDisplayName(name); + pID->SetDisplayName(name.get()); + + pID->SetSize(1000); + rv = pID->QueryInterface(kISupportsIID, (void **) &pInterface); + array->AppendElement(pInterface, false); + pInterface->Release(); + pID->Release(); + } + } + array.forget(pArray); + return NS_OK; +} + +bool nsOutlookMail::IsAddressBookNameUnique(nsString& name, nsString& list) +{ + nsString usedName; + usedName.AppendLiteral("["); + usedName.Append(name); + usedName.AppendLiteral("],"); + + return list.Find(usedName) == -1; +} + +void nsOutlookMail::MakeAddressBookNameUnique(nsString& name, nsString& list) +{ + nsString newName; + int idx = 1; + + newName = name; + while (!IsAddressBookNameUnique(newName, list)) { + newName = name; + newName.Append(char16_t(' ')); + newName.AppendInt((int32_t) idx); + idx++; + } + + name = newName; + list.AppendLiteral("["); + list.Append(name); + list.AppendLiteral("],"); +} + +nsresult nsOutlookMail::GetAddressBooks(nsIArray **pArray) +{ + if (!m_haveMapi) { + IMPORT_LOG0("GetAddressBooks called before Mapi is initialized\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv; + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("FAILED to allocate the nsIMutableArray for the address book list\n"); + return rv; + } + + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + m_gotAddresses = true; + + m_addressList.ClearAll(); + m_mapi.Initialize(); + m_mapi.LogOn(); + if (m_storeList.GetSize() == 0) + m_mapi.IterateStores(m_storeList); + + int i = 0; + CMapiFolder *pFolder; + if (m_storeList.GetSize() > 1) { + while ((pFolder = m_storeList.GetItem(i))) { + CMapiFolder *pItem = new CMapiFolder(pFolder); + pItem->SetDepth(1); + m_addressList.AddItem(pItem); + if (!m_mapi.GetStoreAddressFolders(pItem->GetCBEntryID(), pItem->GetEntryID(), m_addressList)) { + IMPORT_LOG1("GetStoreAddressFolders for index %d failed.\n", i); + } + i++; + } + } + else { + if ((pFolder = m_storeList.GetItem(i))) { + if (!m_mapi.GetStoreAddressFolders(pFolder->GetCBEntryID(), pFolder->GetEntryID(), m_addressList)) { + IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i); + } + } + } + + // Create the mailbox descriptors for the list of folders + nsIImportABDescriptor * pID; + nsISupports * pInterface; + nsString name; + nsString list; + + for (i = 0; i < m_addressList.GetSize(); i++) { + pFolder = m_addressList.GetItem(i); + if (!pFolder->IsStore()) { + rv = impSvc->CreateNewABDescriptor(&pID); + if (NS_SUCCEEDED(rv)) { + pID->SetIdentifier(i); + pFolder->GetDisplayName(name); + MakeAddressBookNameUnique(name, list); + pID->SetPreferredName(name); + pID->SetSize(100); + rv = pID->QueryInterface(kISupportsIID, (void **) &pInterface); + array->AppendElement(pInterface, false); + pInterface->Release(); + pID->Release(); + } + } + } + array.forget(pArray); + return NS_OK; +} + +void nsOutlookMail::OpenMessageStore(CMapiFolder *pNextFolder) +{ + // Open the store specified + if (pNextFolder->IsStore()) { + if (!m_mapi.OpenStore(pNextFolder->GetCBEntryID(), pNextFolder->GetEntryID(), &m_lpMdb)) { + m_lpMdb = NULL; + IMPORT_LOG0("CMapiApi::OpenStore failed\n"); + } + + return; + } + + // Check to see if we should open the one and only store + if (!m_lpMdb) { + if (m_storeList.GetSize() == 1) { + CMapiFolder * pFolder = m_storeList.GetItem(0); + if (pFolder) { + if (!m_mapi.OpenStore(pFolder->GetCBEntryID(), pFolder->GetEntryID(), &m_lpMdb)) { + m_lpMdb = NULL; + IMPORT_LOG0("CMapiApi::OpenStore failed\n"); + } + } + else { + IMPORT_LOG0("Error retrieving the one & only message store\n"); + } + } + else { + IMPORT_LOG0("*** Error importing a folder without a valid message store\n"); + } + } +} + +// Roles and responsibilities: +// nsOutlookMail +// - Connect to Outlook +// - Enumerate the mailboxes +// - Iterate the mailboxes +// - For each mail, create one nsOutlookCompose object +// - For each mail, create one CMapiMessage object +// +// nsOutlookCompose +// - Establish a TB session +// - Connect to all required services +// - Perform the composition of the RC822 document from the data gathered by CMapiMessage +// - Save the composed message to the TB mailbox +// - Ensure the proper cleanup +// +// CMapiMessage +// - Encapsulate the MAPI message interface +// - Gather the information required to (re)compose the message + +nsresult nsOutlookMail::ImportMailbox(uint32_t *pDoneSoFar, bool *pAbort, + int32_t index, const char16_t *pName, + nsIMsgFolder *dstFolder, + int32_t *pMsgCount) +{ + if ((index < 0) || (index >= m_folderList.GetSize())) { + IMPORT_LOG0("*** Bad mailbox identifier, unable to import\n"); + *pAbort = true; + return NS_ERROR_FAILURE; + } + + int32_t dummyMsgCount = 0; + if (pMsgCount) + *pMsgCount = 0; + else + pMsgCount = &dummyMsgCount; + + CMapiFolder *pFolder = m_folderList.GetItem(index); + OpenMessageStore(pFolder); + if (!m_lpMdb) { + IMPORT_LOG1("*** Unable to obtain mapi message store for mailbox: %S\n", pName); + return NS_ERROR_FAILURE; + } + + if (pFolder->IsStore()) + return NS_OK; + + // now what? + CMapiFolderContents contents(m_lpMdb, pFolder->GetCBEntryID(), pFolder->GetEntryID()); + + BOOL done = FALSE; + ULONG cbEid; + LPENTRYID lpEid; + ULONG oType; + LPMESSAGE lpMsg = nullptr; + ULONG totalCount; + double doneCalc; + + nsCOMPtr<nsIOutputStream> outputStream; + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsresult rv = dstFolder->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + while (!done) { + if (!contents.GetNext(&cbEid, &lpEid, &oType, &done)) { + IMPORT_LOG1("*** Error iterating mailbox: %S\n", pName); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + bool reusable; + + rv = msgStore->GetNewMsgOutputStream(dstFolder, getter_AddRefs(msgHdr), &reusable, + getter_AddRefs(outputStream)); + if (NS_FAILED(rv)) { + IMPORT_LOG1("*** Error getting nsIOutputStream of mailbox: %S\n", pName); + return rv; + } + totalCount = contents.GetCount(); + doneCalc = *pMsgCount; + doneCalc /= totalCount; + doneCalc *= 1000; + if (pDoneSoFar) { + *pDoneSoFar = (uint32_t) doneCalc; + if (*pDoneSoFar > 1000) + *pDoneSoFar = 1000; + } + + if (!done && (oType == MAPI_MESSAGE)) { + if (!m_mapi.OpenMdbEntry(m_lpMdb, cbEid, lpEid, (LPUNKNOWN *) &lpMsg)) { + IMPORT_LOG1("*** Error opening messages in mailbox: %S\n", pName); + return NS_ERROR_FAILURE; + } + + // See if it's a drafts folder. Outlook doesn't allow drafts + // folder to be configured so it's ok to hard code it here. + nsAutoString folderName(pName); + nsMsgDeliverMode mode = nsIMsgSend::nsMsgDeliverNow; + mode = nsIMsgSend::nsMsgSaveAsDraft; + if (folderName.LowerCaseEqualsLiteral("drafts")) + mode = nsIMsgSend::nsMsgSaveAsDraft; + + rv = ImportMessage(lpMsg, outputStream, mode); + if (NS_SUCCEEDED(rv)){ // No errors & really imported + (*pMsgCount)++; + msgStore->FinishNewMessage(outputStream, msgHdr); + } + else { + IMPORT_LOG1( "*** Error reading message from mailbox: %S\n", pName); + msgStore->DiscardNewMessage(outputStream, msgHdr); + } + if (!reusable) + outputStream->Close(); + } + } + + if (outputStream) + outputStream->Close(); + return NS_OK; +} + +nsresult nsOutlookMail::ImportMessage(LPMESSAGE lpMsg, nsIOutputStream *pDest, nsMsgDeliverMode mode) +{ + CMapiMessage msg(lpMsg); + // If we wanted to skip messages that were downloaded in header only mode, we + // would return NS_ERROR_FAILURE if !msg.FullMessageDownloaded. However, we + // don't do this because it may cause seemingly wrong import results. + // A user will get less mails in his imported folder than were in the original folder, + // and this may make user feel like TB import is bad. + // In reality, the skipped messages are those that have not been downloaded yet, because + // they were downloaded in the "headers-only" mode. This is different from the case when + // the message is downloaded completely, but consists only of headers - in this case + // the message will be imported anyway. + + if (!msg.ValidState()) + return NS_ERROR_FAILURE; + + // I have to create a composer for each message, since it turns out that if we create + // one composer for several messages, the Send Proxy object that is shared between those messages + // isn't reset properly (at least in the current implementation), which leads to crash. + // If there's a proper way to reinitialize the Send Proxy object, + // then we could slightly optimize the send process. + nsOutlookCompose compose; + nsresult rv = compose.ProcessMessage(mode, msg, pDest); + + // Just for YUCKS, let's try an extra endline + WriteData(pDest, "\x0D\x0A", 2); + + return rv; +} + +BOOL nsOutlookMail::WriteData(nsIOutputStream *pDest, const char *pData, int32_t len) +{ + uint32_t written; + nsresult rv = pDest->Write(pData, len, &written); + return NS_SUCCEEDED(rv) && written == len; +} + +nsresult nsOutlookMail::ImportAddresses(uint32_t *pCount, uint32_t *pTotal, const char16_t *pName, uint32_t id, nsIAddrDatabase *pDb, nsString& errors) +{ + if (id >= (uint32_t)(m_addressList.GetSize())) { + IMPORT_LOG0("*** Bad address identifier, unable to import\n"); + return NS_ERROR_FAILURE; + } + + uint32_t dummyCount = 0; + if (pCount) + *pCount = 0; + else + pCount = &dummyCount; + + CMapiFolder *pFolder; + if (id > 0) { + int32_t idx = (int32_t) id; + idx--; + while (idx >= 0) { + pFolder = m_addressList.GetItem(idx); + if (pFolder->IsStore()) { + OpenMessageStore(pFolder); + break; + } + idx--; + } + } + + pFolder = m_addressList.GetItem(id); + OpenMessageStore(pFolder); + if (!m_lpMdb) { + IMPORT_LOG1("*** Unable to obtain mapi message store for address book: %S\n", pName); + return NS_ERROR_FAILURE; + } + + if (pFolder->IsStore()) + return NS_OK; + + nsresult rv; + + nsCOMPtr<nsIImportFieldMap> pFieldMap; + + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewFieldMap(getter_AddRefs(pFieldMap)); + } + + CMapiFolderContents contents(m_lpMdb, pFolder->GetCBEntryID(), pFolder->GetEntryID()); + + BOOL done = FALSE; + ULONG cbEid; + LPENTRYID lpEid; + ULONG oType; + LPMESSAGE lpMsg; + nsCString type; + LPSPropValue pVal; + nsString subject; + + while (!done) { + (*pCount)++; + + if (!contents.GetNext(&cbEid, &lpEid, &oType, &done)) { + IMPORT_LOG1("*** Error iterating address book: %S\n", pName); + return NS_ERROR_FAILURE; + } + + if (pTotal && (*pTotal == 0)) + *pTotal = contents.GetCount(); + + if (!done && (oType == MAPI_MESSAGE)) { + if (!m_mapi.OpenMdbEntry(m_lpMdb, cbEid, lpEid, (LPUNKNOWN *) &lpMsg)) { + IMPORT_LOG1("*** Error opening messages in mailbox: %S\n", pName); + return NS_ERROR_FAILURE; + } + + // Get the PR_MESSAGE_CLASS attribute, + // ensure that it is IPM.Contact + pVal = m_mapi.GetMapiProperty(lpMsg, PR_MESSAGE_CLASS); + if (pVal) { + type.Truncate(); + m_mapi.GetStringFromProp(pVal, type); + if (type.EqualsLiteral("IPM.Contact")) { + // This is a contact, add it to the address book! + subject.Truncate(); + pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT); + if (pVal) + m_mapi.GetStringFromProp(pVal, subject); + + nsIMdbRow* newRow = nullptr; + pDb->GetNewRow(&newRow); + // FIXME: Check with Candice about releasing the newRow if it + // isn't added to the database. Candice's code in nsAddressBook + // never releases it but that doesn't seem right to me! + if (newRow) { + if (BuildCard(subject.get(), pDb, newRow, lpMsg, pFieldMap)) { + pDb->AddCardRowToDB(newRow); + } + } + } + else if (type.EqualsLiteral("IPM.DistList")) + { + // This is a list/group, add it to the address book! + subject.Truncate(); + pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT); + if (pVal) + m_mapi.GetStringFromProp(pVal, subject); + CreateList(subject.get(), pDb, lpMsg, pFieldMap); + } + } + + lpMsg->Release(); + } + } + + rv = pDb->Commit(nsAddrDBCommitType::kLargeCommit); + return rv; +} +nsresult nsOutlookMail::CreateList(const char16_t * pName, + nsIAddrDatabase *pDb, + LPMAPIPROP pUserList, + nsIImportFieldMap *pFieldMap) +{ + // If no name provided then we're done. + if (!pName || !(*pName)) + return NS_OK; + + nsresult rv = NS_ERROR_FAILURE; + // Make sure we have db to work with. + if (!pDb) + return rv; + + nsCOMPtr <nsIMdbRow> newListRow; + rv = pDb->GetNewListRow(getter_AddRefs(newListRow)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString column; + LossyCopyUTF16toASCII(nsDependentString(pName), column); + rv = pDb->AddListName(newListRow, column.get()); + NS_ENSURE_SUCCESS(rv, rv); + + HRESULT hr; + LPSPropValue value = NULL; + ULONG valueCount = 0; + + LPSPropTagArray properties = NULL; + m_mapi.MAPIAllocateBuffer(CbNewSPropTagArray(1), + (void **)&properties); + properties->cValues = 1; + properties->aulPropTag [0] = m_mapi.GetEmailPropertyTag(pUserList, 0x8054); + hr = pUserList->GetProps(properties, 0, &valueCount, &value); + m_mapi.MAPIFreeBuffer(properties); + if (HR_FAILED(hr)) + return NS_ERROR_FAILURE; + if (!value) + return NS_ERROR_NOT_AVAILABLE; + // XXX from here out, value must be freed with MAPIFreeBuffer + + SBinaryArray *sa=(SBinaryArray *)&value->Value.bin; + if (!sa || !sa->lpbin) { + m_mapi.MAPIFreeBuffer(value); + return NS_ERROR_NULL_POINTER; + } + + LPENTRYID lpEid; + ULONG cbEid; + ULONG idx; + LPMESSAGE lpMsg; + nsCString type; + LPSPropValue pVal; + nsString subject; + ULONG total; + + total = sa->cValues; + for (idx = 0; idx < total; idx++) + { + lpEid= (LPENTRYID) sa->lpbin[idx].lpb; + cbEid = sa->lpbin[idx].cb; + + if (!m_mapi.OpenEntry(cbEid, lpEid, (LPUNKNOWN *) &lpMsg)) + { + + IMPORT_LOG1("*** Error opening messages in mailbox: %S\n", pName); + m_mapi.MAPIFreeBuffer(value); + return NS_ERROR_FAILURE; + } + // This is a contact, add it to the address book! + subject.Truncate(); + pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT); + if (pVal) + m_mapi.GetStringFromProp(pVal, subject); + + nsCOMPtr <nsIMdbRow> newRow; + nsCOMPtr <nsIMdbRow> oldRow; + pDb->GetNewRow(getter_AddRefs(newRow)); + if (newRow) { + if (BuildCard(subject.get(), pDb, newRow, lpMsg, pFieldMap)) + { + nsCOMPtr <nsIAbCard> userCard; + nsCOMPtr <nsIAbCard> newCard; + userCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + pDb->InitCardFromRow(userCard,newRow); + + //add card to db + pDb->FindRowByCard(userCard,getter_AddRefs(oldRow)); + if (oldRow) + newRow = oldRow; + else + pDb->AddCardRowToDB(newRow); + + //add card list + pDb->AddListCardColumnsToRow(userCard, + newListRow,idx+1, getter_AddRefs(newCard), + true, nullptr, nullptr); + } + } + } + m_mapi.MAPIFreeBuffer(value); + + rv = pDb->AddCardRowToDB(newListRow); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pDb->SetListAddressTotal(newListRow, (uint32_t)total); + rv = pDb->AddListDirNode(newListRow); + return rv; +} + +void nsOutlookMail::SanitizeValue(nsString& val) +{ + MsgReplaceSubstring(val, NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING(", ")); + MsgReplaceChar(val, "\r\n", ','); +} + +void nsOutlookMail::SplitString(nsString& val1, nsString& val2) +{ + // Find the last line if there is more than one! + int32_t idx = val1.RFind("\x0D\x0A"); + int32_t cnt = 2; + if (idx == -1) { + cnt = 1; + idx = val1.RFindChar(13); + } + if (idx == -1) + idx= val1.RFindChar(10); + if (idx != -1) { + val2 = Substring(val1, idx + cnt); + val1.SetLength(idx); + SanitizeValue(val1); + } +} + +bool nsOutlookMail::BuildCard(const char16_t *pName, nsIAddrDatabase *pDb, nsIMdbRow *newRow, LPMAPIPROP pUser, nsIImportFieldMap *pFieldMap) +{ + + nsString lastName; + nsString firstName; + nsString eMail; + nsString nickName; + nsString middleName; + nsString secondEMail; + ULONG emailTag; + + LPSPropValue pProp = m_mapi.GetMapiProperty(pUser, PR_EMAIL_ADDRESS); + if (!pProp) { + emailTag = m_mapi.GetEmailPropertyTag(pUser, OUTLOOK_EMAIL1_MAPI_ID1); + if (emailTag) { + pProp = m_mapi.GetMapiProperty(pUser, emailTag); + } + } + if (pProp) { + m_mapi.GetStringFromProp(pProp, eMail); + SanitizeValue(eMail); + } + + // for secondary email + emailTag = m_mapi.GetEmailPropertyTag(pUser, OUTLOOK_EMAIL2_MAPI_ID1); + if (emailTag) { + pProp = m_mapi.GetMapiProperty(pUser, emailTag); + if (pProp) { + m_mapi.GetStringFromProp(pProp, secondEMail); + SanitizeValue(secondEMail); + } + } + + pProp = m_mapi.GetMapiProperty(pUser, PR_GIVEN_NAME); + if (pProp) { + m_mapi.GetStringFromProp(pProp, firstName); + SanitizeValue(firstName); + } + pProp = m_mapi.GetMapiProperty(pUser, PR_SURNAME); + if (pProp) { + m_mapi.GetStringFromProp(pProp, lastName); + SanitizeValue(lastName); + } + pProp = m_mapi.GetMapiProperty(pUser, PR_MIDDLE_NAME); + if (pProp) { + m_mapi.GetStringFromProp(pProp, middleName); + SanitizeValue(middleName); + } + pProp = m_mapi.GetMapiProperty(pUser, PR_NICKNAME); + if (pProp) { + m_mapi.GetStringFromProp(pProp, nickName); + SanitizeValue(nickName); + } + if (firstName.IsEmpty() && lastName.IsEmpty()) { + firstName = pName; + } + + nsString displayName; + pProp = m_mapi.GetMapiProperty(pUser, PR_DISPLAY_NAME); + if (pProp) { + m_mapi.GetStringFromProp(pProp, displayName); + SanitizeValue(displayName); + } + if (displayName.IsEmpty()) { + if (firstName.IsEmpty()) + displayName = pName; + else { + displayName = firstName; + if (!middleName.IsEmpty()) { + displayName.Append(char16_t(' ')); + displayName.Append(middleName); + } + if (!lastName.IsEmpty()) { + displayName.Append(char16_t(' ')); + displayName.Append(lastName); + } + } + } + + // We now have the required fields + // write them out followed by any optional fields! + if (!displayName.IsEmpty()) { + pDb->AddDisplayName(newRow, NS_ConvertUTF16toUTF8(displayName).get()); + } + if (!firstName.IsEmpty()) { + pDb->AddFirstName(newRow, NS_ConvertUTF16toUTF8(firstName).get()); + } + if (!lastName.IsEmpty()) { + pDb->AddLastName(newRow, NS_ConvertUTF16toUTF8(lastName).get()); + } + if (!nickName.IsEmpty()) { + pDb->AddNickName(newRow, NS_ConvertUTF16toUTF8(nickName).get()); + } + if (!eMail.IsEmpty()) { + pDb->AddPrimaryEmail(newRow, NS_ConvertUTF16toUTF8(eMail).get()); + } + if (!secondEMail.IsEmpty()) { + pDb->Add2ndEmail(newRow, NS_ConvertUTF16toUTF8(secondEMail).get()); + } + + // Do all of the extra fields! + + nsString value; + nsString line2; + + if (pFieldMap) { + int max = sizeof(gMapiFields) / sizeof(MAPIFields); + for (int i = 0; i < max; i++) { + pProp = m_mapi.GetMapiProperty(pUser, gMapiFields[i].mapiTag); + if (pProp) { + m_mapi.GetStringFromProp(pProp, value); + if (!value.IsEmpty()) { + if (gMapiFields[i].multiLine == kNoMultiLine) { + SanitizeValue(value); + pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].mozField, value.get()); + } + else if (gMapiFields[i].multiLine == kIsMultiLine) { + pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].mozField, value.get()); + } + else { + line2.Truncate(); + SplitString(value, line2); + if (!value.IsEmpty()) + pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].mozField, value.get()); + if (!line2.IsEmpty()) + pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].multiLine, line2.get()); + } + } + } + } + } + + return true; +} diff --git a/mailnews/import/outlook/src/nsOutlookMail.h b/mailnews/import/outlook/src/nsOutlookMail.h new file mode 100644 index 000000000..3aa317ece --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookMail.h @@ -0,0 +1,54 @@ +/* -*- 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 nsOutlookMail_h___ +#define nsOutlookMail_h___ + +#include "nsIArray.h" +#include "nsStringGlue.h" +#include "nsOutlookCompose.h" +#include "nsIFile.h" +#include "MapiApi.h" +#include "MapiMessage.h" +#include "nsIAddrDatabase.h" + +class nsIAddrDatabase; +class nsIImportFieldMap; + +class nsOutlookMail { +public: + nsOutlookMail(); + ~nsOutlookMail(); + + nsresult GetMailFolders(nsIArray **pArray); + nsresult GetAddressBooks(nsIArray **pArray); + nsresult ImportMailbox(uint32_t *pDoneSoFar, bool *pAbort, int32_t index, + const char16_t *pName, nsIMsgFolder *pDest, + int32_t *pMsgCount); + static nsresult ImportMessage(LPMESSAGE lpMsg, nsIOutputStream *destOutputStream, nsMsgDeliverMode mode); + nsresult ImportAddresses(uint32_t *pCount, uint32_t *pTotal, const char16_t *pName, uint32_t id, nsIAddrDatabase *pDb, nsString& errors); +private: + void OpenMessageStore(CMapiFolder *pNextFolder); + static BOOL WriteData(nsIOutputStream *pDest, const char *pData, int32_t len); + + bool IsAddressBookNameUnique(nsString& name, nsString& list); + void MakeAddressBookNameUnique(nsString& name, nsString& list); + void SanitizeValue(nsString& val); + void SplitString(nsString& val1, nsString& val2); + bool BuildCard(const char16_t *pName, nsIAddrDatabase *pDb, nsIMdbRow *newRow, LPMAPIPROP pUser, nsIImportFieldMap *pFieldMap); + nsresult CreateList(const char16_t * pName, nsIAddrDatabase *pDb, LPMAPIPROP pUserList, nsIImportFieldMap *pFieldMap); + +private: + bool m_gotFolders; + bool m_gotAddresses; + bool m_haveMapi; + CMapiApi m_mapi; + CMapiFolderList m_folderList; + CMapiFolderList m_addressList; + CMapiFolderList m_storeList; + LPMDB m_lpMdb; +}; + +#endif /* nsOutlookMail_h___ */ diff --git a/mailnews/import/outlook/src/nsOutlookSettings.cpp b/mailnews/import/outlook/src/nsOutlookSettings.cpp new file mode 100644 index 000000000..ec03c8ee2 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookSettings.cpp @@ -0,0 +1,567 @@ +/* -*- 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/. */ + +/* + + Outlook (Win32) settings + +*/ + +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsMsgUtils.h" +#include "nsOutlookImport.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIImportService.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAccount.h" +#include "nsIImportSettings.h" +#include "nsOutlookSettings.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsISmtpService.h" +#include "nsISmtpServer.h" +#include "nsOutlookStringBundle.h" +#include "OutlookDebugLog.h" +#include "nsIPop3IncomingServer.h" +#include "nsMsgI18N.h" +#include <windows.h> +#include "nsIWindowsRegKey.h" +#include "nsComponentManagerUtils.h" +#ifdef MOZILLA_INTERNAL_API +#include "nsNativeCharsetUtils.h" +#else +#include "nsMsgI18N.h" +#define NS_CopyNativeToUnicode(source, dest) \ + nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), source, dest) +#define NS_CopyUnicodeToNative(source, dest) \ + nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(), source, dest) +#endif + +class OutlookSettings { +public: + static nsresult FindAccountsKey(nsIWindowsRegKey **aKey); + static nsresult QueryAccountSubKey(nsIWindowsRegKey **aKey); + static nsresult GetDefaultMailAccountName(nsAString &aName); + + static bool DoImport(nsIMsgAccount **aAccount); + + static bool DoIMAPServer(nsIMsgAccountManager *aMgr, + nsIWindowsRegKey *aKey, + const nsString &aServerName, + nsIMsgAccount **aAccount); + static bool DoPOP3Server(nsIMsgAccountManager *aMgr, + nsIWindowsRegKey *aKey, + const nsString &aServerName, + nsIMsgAccount **aAccount); + + static void SetIdentities(nsIMsgAccountManager *pMgr, + nsIMsgAccount *pAcc, + nsIWindowsRegKey *aKey); + + static nsresult SetSmtpServer(nsIMsgAccountManager *aMgr, + nsIMsgAccount *aAcc, + nsIMsgIdentity *aId, + const nsString &aServer, + const nsString &aUser); + static nsresult SetSmtpServerKey(nsIMsgIdentity *aId, + nsISmtpServer *aServer); + static nsresult GetAccountName(nsIWindowsRegKey *aKey, + const nsString &aDefaultName, + nsAString &aAccountName); +}; + +#define OUTLOOK2003_REGISTRY_KEY "Software\\Microsoft\\Office\\Outlook\\OMI Account Manager" +#define OUTLOOK98_REGISTRY_KEY "Software\\Microsoft\\Office\\8.0\\Outlook\\OMI Account Manager" + +//////////////////////////////////////////////////////////////////////// +nsresult nsOutlookSettings::Create(nsIImportSettings** aImport) +{ + NS_PRECONDITION(aImport != nullptr, "null ptr"); + if (! aImport) + return NS_ERROR_NULL_POINTER; + + *aImport = new nsOutlookSettings(); + if (! *aImport) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aImport); + return NS_OK; +} + +nsOutlookSettings::nsOutlookSettings() +{ +} + +nsOutlookSettings::~nsOutlookSettings() +{ +} + +NS_IMPL_ISUPPORTS(nsOutlookSettings, nsIImportSettings) + +NS_IMETHODIMP nsOutlookSettings::AutoLocate(char16_t **description, nsIFile **location, bool *_retval) +{ + NS_PRECONDITION(description != nullptr, "null ptr"); + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!description || !_retval) + return NS_ERROR_NULL_POINTER; + + *description = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME); + *_retval = false; + + if (location) + *location = nullptr; + + // look for the registry key for the accounts + nsCOMPtr<nsIWindowsRegKey> key; + *_retval = NS_SUCCEEDED(OutlookSettings::FindAccountsKey(getter_AddRefs(key))); + + return NS_OK; +} + +NS_IMETHODIMP nsOutlookSettings::SetLocation(nsIFile *location) +{ + return NS_OK; +} + +NS_IMETHODIMP nsOutlookSettings::Import(nsIMsgAccount **localMailAccount, bool *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + + if (OutlookSettings::DoImport(localMailAccount)) { + *_retval = true; + IMPORT_LOG0("Settings import appears successful\n"); + } + else { + *_retval = false; + IMPORT_LOG0("Settings import returned FALSE\n"); + } + + return NS_OK; +} + + +nsresult OutlookSettings::FindAccountsKey(nsIWindowsRegKey **aKey) +{ + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING(OUTLOOK2003_REGISTRY_KEY), + nsIWindowsRegKey::ACCESS_QUERY_VALUE | + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS); + + if (NS_FAILED(rv)) { + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING(OUTLOOK98_REGISTRY_KEY), + nsIWindowsRegKey::ACCESS_QUERY_VALUE | + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS); + } + + if (NS_SUCCEEDED(rv)) + key.forget(aKey); + + return rv; +} + +nsresult OutlookSettings::QueryAccountSubKey(nsIWindowsRegKey **aKey) +{ + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING(OUTLOOK2003_REGISTRY_KEY), + nsIWindowsRegKey::ACCESS_QUERY_VALUE | + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS); + if (NS_SUCCEEDED(rv)) { + key.forget(aKey); + return rv; + } + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING(OUTLOOK98_REGISTRY_KEY), + nsIWindowsRegKey::ACCESS_QUERY_VALUE | + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS); + if (NS_SUCCEEDED(rv)) { + key.forget(aKey); + return rv; + } + + return NS_ERROR_FAILURE; +} + +nsresult OutlookSettings::GetDefaultMailAccountName(nsAString &aName) +{ + nsCOMPtr<nsIWindowsRegKey> key; + nsresult rv = QueryAccountSubKey(getter_AddRefs(key)); + if (NS_FAILED(rv)) + return rv; + + return key->ReadStringValue(NS_LITERAL_STRING("Default Mail Account"), aName); +} + +bool OutlookSettings::DoImport(nsIMsgAccount **aAccount) +{ + nsCOMPtr<nsIWindowsRegKey> key; + nsresult rv = OutlookSettings::FindAccountsKey(getter_AddRefs(key)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error finding Outlook registry account keys\n"); + return false; + } + + nsCOMPtr<nsIMsgAccountManager> accMgr = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create a account manager!\n"); + return false; + } + + nsAutoString defMailName; + rv = GetDefaultMailAccountName(defMailName); + + uint32_t childCount; + key->GetChildCount(&childCount); + + uint32_t accounts = 0; + uint32_t popCount = 0; + for (uint32_t i = 0; i < childCount; i++) { + nsAutoString keyName; + key->GetChildName(i, keyName); + nsCOMPtr<nsIWindowsRegKey> subKey; + rv = key->OpenChild(keyName, + nsIWindowsRegKey::ACCESS_QUERY_VALUE, + getter_AddRefs(subKey)); + if (NS_FAILED(rv)) + continue; + + // Get the values for this account. + nsAutoCString nativeKeyName; + NS_CopyUnicodeToNative(keyName, nativeKeyName); + IMPORT_LOG1("Opened Outlook account: %s\n", nativeKeyName.get()); + + nsCOMPtr<nsIMsgAccount> account; + nsAutoString value; + rv = subKey->ReadStringValue(NS_LITERAL_STRING("IMAP Server"), value); + if (NS_SUCCEEDED(rv) && + DoIMAPServer(accMgr, subKey, value, getter_AddRefs(account))) + accounts++; + + rv = subKey->ReadStringValue(NS_LITERAL_STRING("POP3 Server"), value); + if (NS_SUCCEEDED(rv) && + DoPOP3Server(accMgr, subKey, value, getter_AddRefs(account))) { + popCount++; + accounts++; + if (aAccount && account) { + // If we created a mail account, get rid of it since + // we have 2 POP accounts! + if (popCount > 1) + NS_RELEASE(*aAccount); + else + NS_ADDREF(*aAccount = account); + } + } + + // Is this the default account? + if (account && keyName.Equals(defMailName)) + accMgr->SetDefaultAccount(account); + } + + // Now save the new acct info to pref file. + rv = accMgr->SaveAccountInfo(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file"); + + return accounts != 0; +} + +nsresult OutlookSettings::GetAccountName(nsIWindowsRegKey *aKey, + const nsString &aDefaultName, + nsAString &aAccountName) +{ + nsresult rv; + rv = aKey->ReadStringValue(NS_LITERAL_STRING("Account Name"), aAccountName); + if (NS_FAILED(rv)) + aAccountName.Assign(aDefaultName); + + return NS_OK; +} + +bool OutlookSettings::DoIMAPServer(nsIMsgAccountManager *aMgr, + nsIWindowsRegKey *aKey, + const nsString &aServerName, + nsIMsgAccount **aAccount) +{ + nsAutoString userName; + nsresult rv; + rv = aKey->ReadStringValue(NS_LITERAL_STRING("IMAP User Name"), userName); + if (NS_FAILED(rv)) + return false; + + bool result = false; + + // I now have a user name/server name pair, find out if it already exists? + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(userName, nativeUserName); + nsAutoCString nativeServerName; + NS_CopyUnicodeToNative(aServerName, nativeServerName); + nsCOMPtr<nsIMsgIncomingServer> in; + rv = aMgr->FindServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("imap"), + getter_AddRefs(in)); + if (NS_FAILED(rv) || (in == nullptr)) { + // Create the incoming server and an account for it? + rv = aMgr->CreateIncomingServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("imap"), + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv) && in) { + rv = in->SetType(NS_LITERAL_CSTRING("imap")); + // TODO SSL, auth method + + IMPORT_LOG2("Created IMAP server named: %s, userName: %s\n", + nativeServerName.get(), nativeUserName.get()); + + nsAutoString prettyName; + if (NS_SUCCEEDED(GetAccountName(aKey, aServerName, prettyName))) + rv = in->SetPrettyName(prettyName); + // We have a server, create an account. + nsCOMPtr<nsIMsgAccount> account; + rv = aMgr->CreateAccount(getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + rv = account->SetIncomingServer(in); + + IMPORT_LOG0("Created an account and set the IMAP server as the incoming server\n"); + + // Fiddle with the identities + SetIdentities(aMgr, account, aKey); + result = true; + if (aAccount) + account.forget(aAccount); + } + } + } + else + result = true; + + return result; +} + +bool OutlookSettings::DoPOP3Server(nsIMsgAccountManager *aMgr, + nsIWindowsRegKey *aKey, + const nsString &aServerName, + nsIMsgAccount **aAccount) +{ + nsAutoString userName; + nsresult rv; + rv = aKey->ReadStringValue(NS_LITERAL_STRING("POP3 User Name"), userName); + if (NS_FAILED(rv)) + return false; + + // I now have a user name/server name pair, find out if it already exists? + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(userName, nativeUserName); + nsAutoCString nativeServerName; + NS_CopyUnicodeToNative(aServerName, nativeServerName); + nsCOMPtr<nsIMsgIncomingServer> in; + rv = aMgr->FindServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("pop3"), + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv)) + return true; + + // Create the incoming server and an account for it? + rv = aMgr->CreateIncomingServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("pop3"), + getter_AddRefs(in)); + rv = in->SetType(NS_LITERAL_CSTRING("pop3")); + + // TODO SSL, auth method + + nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(in); + NS_ENSURE_SUCCESS(rv, false); + + // set local folders as the Inbox to use for this POP3 server + nsCOMPtr<nsIMsgIncomingServer> localFoldersServer; + aMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + + if (!localFoldersServer) { + // XXX: We may need to move this local folder creation code to the generic nsImportSettings code + // if the other import modules end up needing to do this too. + // if Local Folders does not exist already, create it + rv = aMgr->CreateLocalMailAccount(); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create Local Folders!\n"); + return false; + } + aMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + } + + // now get the account for this server + nsCOMPtr<nsIMsgAccount> localFoldersAccount; + aMgr->FindAccountForServer(localFoldersServer, getter_AddRefs(localFoldersAccount)); + if (localFoldersAccount) { + nsCString localFoldersAcctKey; + localFoldersAccount->GetKey(localFoldersAcctKey); + pop3Server->SetDeferredToAccount(localFoldersAcctKey); + pop3Server->SetDeferGetNewMail(true); + } + + IMPORT_LOG2("Created POP3 server named: %s, userName: %s\n", + nativeServerName.get(), + nativeUserName.get()); + + nsString prettyName; + rv = GetAccountName(aKey, aServerName, prettyName); + if (NS_FAILED(rv)) + return false; + + rv = in->SetPrettyName(prettyName); + // We have a server, create an account. + nsCOMPtr<nsIMsgAccount> account; + rv = aMgr->CreateAccount(getter_AddRefs(account)); + if (NS_FAILED(rv)) + return false; + + rv = account->SetIncomingServer(in); + + IMPORT_LOG0("Created a new account and set the incoming server to the POP3 server.\n"); + + uint32_t leaveOnServer; + rv = aKey->ReadIntValue(NS_LITERAL_STRING("Leave Mail On Server"), &leaveOnServer); + if (NS_SUCCEEDED(rv)) + pop3Server->SetLeaveMessagesOnServer(leaveOnServer == 1 ? true : false); + + // Fiddle with the identities + SetIdentities(aMgr, account, aKey); + + if (aAccount) + account.forget(aAccount); + + return true; +} + +void OutlookSettings::SetIdentities(nsIMsgAccountManager *aMgr, + nsIMsgAccount *aAcc, + nsIWindowsRegKey *aKey) +{ + // Get the relevant information for an identity + nsAutoString name; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Display Name"), name); + + nsAutoString server; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Server"), server); + + nsAutoString email; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Email Address"), email); + + nsAutoString reply; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Reply To Email Address"), reply); + + nsAutoString userName; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP User Name"), userName); + + nsAutoString orgName; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Organization Name"), orgName); + + nsresult rv; + nsCOMPtr<nsIMsgIdentity> id; + if (!email.IsEmpty() && !name.IsEmpty() && !server.IsEmpty()) { + // The default identity, nor any other identities matched, + // create a new one and add it to the account. + rv = aMgr->CreateIdentity(getter_AddRefs(id)); + if (id) { + id->SetFullName(name); + id->SetOrganization(orgName); + + nsAutoCString nativeEmail; + NS_CopyUnicodeToNative(email, nativeEmail); + id->SetEmail(nativeEmail); + if (!reply.IsEmpty()) { + nsAutoCString nativeReply; + NS_CopyUnicodeToNative(reply, nativeReply); + id->SetReplyTo(nativeReply); + } + aAcc->AddIdentity(id); + + nsAutoCString nativeName; + NS_CopyUnicodeToNative(name, nativeName); + IMPORT_LOG0("Created identity and added to the account\n"); + IMPORT_LOG1("\tname: %s\n", nativeName.get()); + IMPORT_LOG1("\temail: %s\n", nativeEmail.get()); + } + } + + if (userName.IsEmpty()) { + nsCOMPtr<nsIMsgIncomingServer> incomingServer; + rv = aAcc->GetIncomingServer(getter_AddRefs(incomingServer)); + if (NS_SUCCEEDED(rv) && incomingServer) { + nsAutoCString nativeUserName; + rv = incomingServer->GetUsername(nativeUserName); + NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get UserName from incomingServer"); + NS_CopyNativeToUnicode(nativeUserName, userName); + } + } + + SetSmtpServer(aMgr, aAcc, id, server, userName); +} + +nsresult OutlookSettings::SetSmtpServerKey(nsIMsgIdentity *aId, + nsISmtpServer *aServer) +{ + nsAutoCString smtpServerKey; + aServer->GetKey(getter_Copies(smtpServerKey)); + return aId->SetSmtpServerKey(smtpServerKey); +} + +nsresult OutlookSettings::SetSmtpServer(nsIMsgAccountManager *aMgr, + nsIMsgAccount *aAcc, + nsIMsgIdentity *aId, + const nsString &aServer, + const nsString &aUser) +{ + nsresult rv; + nsCOMPtr<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(aUser, nativeUserName); + nsAutoCString nativeServerName; + NS_CopyUnicodeToNative(aServer, nativeServerName); + nsCOMPtr<nsISmtpServer> foundServer; + rv = smtpService->FindServer(nativeUserName.get(), + nativeServerName.get(), + getter_AddRefs(foundServer)); + if (NS_SUCCEEDED(rv) && foundServer) { + if (aId) + SetSmtpServerKey(aId, foundServer); + IMPORT_LOG1("SMTP server already exists: %s\n", + nativeServerName.get()); + return rv; + } + + nsCOMPtr<nsISmtpServer> smtpServer; + rv = smtpService->CreateServer(getter_AddRefs(smtpServer)); + NS_ENSURE_SUCCESS(rv, rv); + + smtpServer->SetHostname(nativeServerName); + if (!aUser.IsEmpty()) + smtpServer->SetUsername(nativeUserName); + + if (aId) + SetSmtpServerKey(aId, smtpServer); + + // TODO SSL, auth method + IMPORT_LOG1("Ceated new SMTP server: %s\n", + nativeServerName.get()); + return NS_OK; +} + diff --git a/mailnews/import/outlook/src/nsOutlookSettings.h b/mailnews/import/outlook/src/nsOutlookSettings.h new file mode 100644 index 000000000..ea074e5b4 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookSettings.h @@ -0,0 +1,29 @@ +/* -*- 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 nsOutlookSettings_h___ +#define nsOutlookSettings_h___ + +#include "nsIImportSettings.h" + + +class nsOutlookSettings : public nsIImportSettings { +public: + nsOutlookSettings(); + + static nsresult Create(nsIImportSettings** aImport); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsIImportSettings interface + NS_DECL_NSIIMPORTSETTINGS + +private: + virtual ~nsOutlookSettings(); + +}; + +#endif /* nsOutlookSettings_h___ */ diff --git a/mailnews/import/outlook/src/nsOutlookStringBundle.cpp b/mailnews/import/outlook/src/nsOutlookStringBundle.cpp new file mode 100644 index 000000000..826e30e40 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookStringBundle.cpp @@ -0,0 +1,71 @@ +/* -*- 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 "prprf.h" +#include "prmem.h" +#include "nsCOMPtr.h" +#include "nsMsgUtils.h" +#include "nsIStringBundle.h" +#include "nsOutlookStringBundle.h" +#include "nsIServiceManager.h" +#include "nsIURI.h" +#include "mozilla/Services.h" + +#define OUTLOOK_MSGS_URL "chrome://messenger/locale/outlookImportMsgs.properties" + +nsIStringBundle * nsOutlookStringBundle::m_pBundle = nullptr; + +nsIStringBundle *nsOutlookStringBundle::GetStringBundle(void) +{ + if (m_pBundle) + return m_pBundle; + + char* propertyURL = OUTLOOK_MSGS_URL; + nsIStringBundle* sBundle = nullptr; + + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + if (sBundleService) { + sBundleService->CreateBundle(propertyURL, &sBundle); + } + + m_pBundle = sBundle; + + return sBundle; +} + +void nsOutlookStringBundle::GetStringByID(int32_t stringID, nsString& result) +{ + char16_t *ptrv = GetStringByID(stringID); + result = ptrv; + FreeString(ptrv); +} + +char16_t *nsOutlookStringBundle::GetStringByID(int32_t stringID) +{ + if (m_pBundle) + m_pBundle = GetStringBundle(); + + if (m_pBundle) { + char16_t *ptrv = nullptr; + nsresult rv = m_pBundle->GetStringFromID(stringID, &ptrv); + + if (NS_SUCCEEDED(rv) && ptrv) + return ptrv; + } + + nsString resultString; + resultString.AppendLiteral("[StringID "); + resultString.AppendInt(stringID); + resultString.AppendLiteral("?]"); + + return ToNewUnicode(resultString); +} + +void nsOutlookStringBundle::Cleanup(void) +{ + if (m_pBundle) + m_pBundle->Release(); + m_pBundle = nullptr; +} diff --git a/mailnews/import/outlook/src/nsOutlookStringBundle.h b/mailnews/import/outlook/src/nsOutlookStringBundle.h new file mode 100644 index 000000000..1351088d4 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookStringBundle.h @@ -0,0 +1,38 @@ +/* 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 _nsOutlookStringBundle_H__ +#define _nsOutlookStringBundle_H__ + +#include "nsCRTGlue.h" +#include "nsStringGlue.h" + +class nsIStringBundle; + +class nsOutlookStringBundle { +public: + static char16_t * GetStringByID(int32_t stringID); + static void GetStringByID(int32_t stringID, nsString& result); + static nsIStringBundle * GetStringBundle(void); // don't release + static void FreeString(char16_t *pStr) { NS_Free(pStr);} + static void Cleanup(void); +private: + static nsIStringBundle * m_pBundle; +}; + + + +#define OUTLOOKIMPORT_NAME 2000 +#define OUTLOOKIMPORT_DESCRIPTION 2010 +#define OUTLOOKIMPORT_MAILBOX_SUCCESS 2002 +#define OUTLOOKIMPORT_MAILBOX_BADPARAM 2003 +#define OUTLOOKIMPORT_MAILBOX_CONVERTERROR 2004 +#define OUTLOOKIMPORT_ADDRNAME 2005 +#define OUTLOOKIMPORT_ADDRESS_SUCCESS 2006 +#define OUTLOOKIMPORT_ADDRESS_BADPARAM 2007 +#define OUTLOOKIMPORT_ADDRESS_BADSOURCEFILE 2008 +#define OUTLOOKIMPORT_ADDRESS_CONVERTERROR 2009 + + +#endif /* _nsOutlookStringBundle_H__ */ diff --git a/mailnews/import/outlook/src/rtfDecoder.cpp b/mailnews/import/outlook/src/rtfDecoder.cpp new file mode 100644 index 000000000..837beec0b --- /dev/null +++ b/mailnews/import/outlook/src/rtfDecoder.cpp @@ -0,0 +1,520 @@ +/* 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 <stack> +#include <map> +#include <sstream> +#include "Windows.h" +#include "rtfDecoder.h" + +#define SIZEOF(x) (sizeof(x)/sizeof((x)[0])) +#define IS_DIGIT(i) ((i) >= '0' && (i) <= '9') +#define IS_ALPHA(VAL) (((VAL) >= 'a' && (VAL) <= 'z') || ((VAL) >= 'A' && (VAL) <= 'Z')) + +inline int HexToInt(char ch) +{ + switch (ch) { + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + return ch-'0'; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + return ch-'A'+10; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + return ch-'a'+10; + default: + return 0; + } +} + +inline int CharsetToCP(int charset) +{ + // We don't know the Code page for the commented out charsets. + switch (charset) { + case 0: return 1252; // ANSI + case 1: return 0; // Default +//case 2: return 42; // Symbol + case 2: return 1252; // Symbol + case 77: return 10000; // Mac Roman + case 78: return 10001; // Mac Shift Jis + case 79: return 10003; // Mac Hangul + case 80: return 10008; // Mac GB2312 + case 81: return 10002; // Mac Big5 +//case 82: Mac Johab (old) + case 83: return 10005; // Mac Hebrew + case 84: return 10004; // Mac Arabic + case 85: return 10006; // Mac Greek + case 86: return 10081; // Mac Turkish + case 87: return 10021; // Mac Thai + case 88: return 10029; // Mac East Europe + case 89: return 10007; // Mac Russian + case 128: return 932; // Shift JIS + case 129: return 949; // Hangul + case 130: return 1361; // Johab + case 134: return 936; // GB2312 + case 136: return 950; // Big5 + case 161: return 1253; // Greek + case 162: return 1254; // Turkish + case 163: return 1258; // Vietnamese + case 177: return 1255; // Hebrew + case 178: return 1256; // Arabic +//case 179: Arabic Traditional (old) +//case 180: Arabic user (old) +//case 181: Hebrew user (old) + case 186: return 1257; // Baltic + case 204: return 1251; // Russian + case 222: return 874; // Thai + case 238: return 1250; // Eastern European + case 254: return 437; // PC 437 + case 255: return 850; // OEM + default: return CP_ACP; + } +} + +struct FontInfo { + enum Options {has_fcharset = 0x0001, + has_cpg = 0x0002}; + unsigned int options; + int fcharset; + unsigned int cpg; + FontInfo() : options(0), fcharset(0), cpg(0xFFFFFFFF) {} + unsigned int Codepage() + { + if (options & has_cpg) + return cpg; + else if (options & has_fcharset) + return CharsetToCP(fcharset); + else return 0xFFFFFFFF; + } +}; +typedef std::map<int, FontInfo> Fonttbl; + +struct LocalState { + bool fonttbl; // When fonts are being defined + int f; // Index of the font being defined/used; defines the codepage if no \cpg + unsigned int uc; // ucN keyword value; its default is 1 + unsigned int codepage;// defined by \cpg +}; +typedef std::stack<LocalState> StateStack; + +struct GlobalState { + enum Pcdata_state { pcdsno, pcdsin, pcdsfinished }; + std::istream& stream; + Fonttbl fonttbl; + StateStack stack; + unsigned int codepage; // defined by \ansi, \mac, \pc, \pca, and \ansicpgN + int deff; + std::stringstream pcdata_a; + unsigned int pcdata_a_codepage; + Pcdata_state pcdata_a_state; + + GlobalState(std::istream& s) + : stream(s), codepage(CP_ACP), deff(-1), pcdata_a_state(pcdsno) + { + LocalState st; + st.fonttbl = false; + st.f = -1; + st.uc = 1; + st.codepage = 0xFFFFFFFF; + stack.push(st); + } + unsigned int GetCurrentCP() + { + if (stack.top().codepage != 0xFFFFFFFF) // \cpg in use + return stack.top().codepage; + // \cpg not used; use font settings + int f = (stack.top().f != -1) ? stack.top().f : deff; + if (f != -1) { + Fonttbl::iterator iter = fonttbl.find(f); + if (iter != fonttbl.end()) { + unsigned int cp = iter->second.Codepage(); + if (cp != 0xFFFFFFFF) + return cp; + } + } + return codepage; // No overrides; use the top-level legacy setting + } +}; + +struct Keyword { + char name[33]; + bool hasVal; + int val; +}; + +class Lexem { +public: + enum Type {ltGroupBegin, ltGroupEnd, ltKeyword, ltPCDATA_A, ltPCDATA_W, + ltBDATA, ltEOF, ltError}; + Lexem(Type t=ltError) : m_type(t) {} + Lexem(Lexem& from) // Move pointers when copying + { + switch (m_type = from.m_type) { + case ltKeyword: + m_keyword = from.m_keyword; + break; + case ltPCDATA_A: + m_pcdata_a = from.m_pcdata_a; + break; + case ltPCDATA_W: + m_pcdata_w = from.m_pcdata_w; + break; + case ltBDATA: + m_bdata = from.m_bdata; + from.m_type = ltError; + break; + } + } + ~Lexem() { Clear(); } + Lexem& operator = (Lexem& from) + { + if (&from != this) { + Clear(); + switch (m_type = from.m_type) { + case ltKeyword: + m_keyword = from.m_keyword; + break; + case ltPCDATA_A: + m_pcdata_a = from.m_pcdata_a; + break; + case ltPCDATA_W: + m_pcdata_w = from.m_pcdata_w; + break; + case ltBDATA: + m_bdata = from.m_bdata; + from.m_type = ltError; + break; + } + } + return *this; + } + Type type() const { return m_type; } + void SetPCDATA_A(char chdata) + { + Clear(); + m_pcdata_a = chdata; + m_type = ltPCDATA_A; + } + void SetPCDATA_W(wchar_t chdata) + { + Clear(); + m_pcdata_w = chdata; + m_type = ltPCDATA_W; + } + void SetBDATA(const char* data, int sz) + { + char* tmp = new char[sz]; // to allow getting the data from itself + if (tmp) { + memcpy(tmp, data, sz); + Clear(); + m_bdata.data = tmp; + m_bdata.sz = sz; + m_type = ltBDATA; + } + else m_type = ltError; + } + void SetKeyword(const Keyword& src) + { + Clear(); + m_type = ltKeyword; + m_keyword = src; + } + void SetKeyword(const char* name, bool hasVal=false, int val=0) + { + char tmp[SIZEOF(m_keyword.name)]; + strncpy(tmp, name, SIZEOF(m_keyword.name)-1); // to allow copy drom itself + tmp[SIZEOF(m_keyword.name)-1]=0; + Clear(); + m_type = ltKeyword; + memcpy(m_keyword.name, tmp, SIZEOF(m_keyword.name)); + m_keyword.hasVal=hasVal; + m_keyword.val=val; + } + const char* KeywordName() const { + return (m_type == ltKeyword) ? m_keyword.name : 0; } + const int* KeywordVal() const { + return ((m_type == ltKeyword) && m_keyword.hasVal) ? &m_keyword.val : 0; } + char pcdata_a() const { return (m_type == ltPCDATA_A) ? m_pcdata_a : 0; } + wchar_t pcdata_w() const { return (m_type == ltPCDATA_W) ? m_pcdata_w : 0; } + const char* bdata() const { return (m_type == ltBDATA) ? m_bdata.data : 0; } + int bdata_sz() const { return (m_type == ltBDATA) ? m_bdata.sz : 0; } + static Lexem eof; + static Lexem groupBegin; + static Lexem groupEnd; + static Lexem error; +private: + struct BDATA { + size_t sz; + char* data; + }; + + Type m_type; + union { + Keyword m_keyword; + char m_pcdata_a; + wchar_t m_pcdata_w; + BDATA m_bdata; + }; + // This function leaves the object in the broken state. Must be followed + // by a correct initialization. + void Clear() + { + switch (m_type) { + case ltBDATA: + delete[] m_bdata.data; + break; + } +// m_type = ltError; + } +}; + +Lexem Lexem::eof(ltEOF); +Lexem Lexem::groupBegin(ltGroupBegin); +Lexem Lexem::groupEnd(ltGroupEnd); +Lexem Lexem::error(ltError); + +// This function moves pos. When calling the function, pos must be next to the +// backslash; pos must be in the same sequence and before end! +Keyword GetKeyword(std::istream& stream) +{ + Keyword keyword = {"", false, 0}; + char ch; + if (stream.get(ch).eof()) + return keyword; + // Control word; maybe delimiter and value + if (IS_ALPHA(ch)) { + int i = 0; + do { + // We take up to 32 characters into account, skipping over extra + // characters (allowing for some non-conformant implementation). + if (i < 32) + keyword.name[i++] = ch; + } while (!stream.get(ch).eof() && IS_ALPHA(ch)); + keyword.name[i] = 0; // NULL-terminating + if (!stream.eof() && (IS_DIGIT(ch) || (ch == '-'))) { // Value begin + keyword.hasVal = true; + bool negative = (ch == '-'); + if (negative) stream.get(ch); + i = 0; + while (!stream.eof() && IS_DIGIT(ch)) { + // We take into account only 10 digits, skip other. Older specs stated + // that we must be ready for an arbitrary number of digits. + if (i++ < 10) + keyword.val = keyword.val*10 + (ch - '0'); + stream.get(ch); + } + if (negative) keyword.val = -keyword.val; + } + // End of control word; the space is just a delimiter - skip it + if (!stream.eof() && !(ch == ' ')) + stream.unget(); + } + else { // Control symbol + keyword.name[0] = ch, keyword.name[1] = 0; + } + return keyword; +} + +Lexem GetLexem(std::istream& stream) +{ + Lexem result; + // We always stay at the beginning of the next lexem or a crlf + // If it's a brace then it's group begin/end + // If it's a backslash -> Preprocess + // - if it's a \u or \' -> make UTF16 character + // - else it's a keyword -> Process (e.g., remember the codepage) + // - (if the keyword is \bin then the following is #BDATA) + // If it's some other character -> Preprocess + // - if it's 0x09 -> it's the keyword \tab + // - else it's a PCDATA + char ch; + while (!stream.get(ch).eof() && ((ch == '\n') || (ch == '\r'))); // Skip crlf + if (stream.eof()) + result = Lexem::eof; + else { + switch (ch) { + case '{': // Group begin + case '}': // Group end + result = (ch == '{') ? Lexem::groupBegin : Lexem::groupEnd; + break; + case '\\': // Keyword + result.SetKeyword(GetKeyword(stream)); + break; + case '\t': // tab + result.SetKeyword("tab"); + break; + default: // PSDATA? + result.SetPCDATA_A(ch); + break; + } + } + return result; +} + +void PreprocessLexem(/*inout*/Lexem& lexem, std::istream& stream, int uc) +{ + if (lexem.type() == Lexem::ltKeyword) { + if (lexem.KeywordName()[0] == 0) // Empty keyword - maybe eof? + lexem = Lexem::error; + else if (eq(lexem.KeywordName(), "u")) { + // Unicode character - get the UTF16 and skip the uc characters + if (const int* val = lexem.KeywordVal()) { + lexem.SetPCDATA_W(*val); + stream.ignore(uc); + } + else lexem = Lexem::error; + } + else if (eq(lexem.KeywordName(), "'")) { + // 8-bit character (\'hh) -> use current codepage + char ch, ch1; + if (!stream.get(ch).eof()) ch1 = HexToInt(ch); + if (!stream.get(ch).eof()) (ch1 <<= 4) += HexToInt(ch); + lexem.SetPCDATA_A(ch1); + } + else if (eq(lexem.KeywordName(), "\\") || eq(lexem.KeywordName(), "{") || + eq(lexem.KeywordName(), "}")) // escaped characters + lexem.SetPCDATA_A(lexem.KeywordName()[0]); + else if (eq(lexem.KeywordName(), "bin")) { + if (const int* i = lexem.KeywordVal()) { + char* data = new char[*i]; + if (data) { + stream.read(data, *i); + if (stream.fail()) + lexem = Lexem::error; + else + lexem.SetBDATA(data, *i); + delete[] data; + } + else lexem = Lexem::error; + } + else lexem = Lexem::error; + } + else if (eq(lexem.KeywordName(), "\n") || eq(lexem.KeywordName(), "\r")) { + // escaped cr or lf + lexem.SetKeyword("par"); + } + } +} + +void UpdateState(const Lexem& lexem, /*inout*/GlobalState& globalState) +{ + switch (globalState.pcdata_a_state) { + case GlobalState::pcdsfinished: // Last time we finished the pcdata + globalState.pcdata_a_state = GlobalState::pcdsno; + break; + case GlobalState::pcdsin: + // to be reset later if still in the pcdata + globalState.pcdata_a_state = GlobalState::pcdsfinished; + break; + } + + switch (lexem.type()) { + case Lexem::ltGroupBegin: + globalState.stack.push(globalState.stack.top()); + break; + case Lexem::ltGroupEnd: + globalState.stack.pop(); + break; + case Lexem::ltKeyword: + { + const int* val = lexem.KeywordVal(); + if (eq(lexem.KeywordName(), "ansi")) globalState.codepage = CP_ACP; + else if (eq(lexem.KeywordName(), "mac")) globalState.codepage = CP_MACCP; + else if (eq(lexem.KeywordName(), "pc")) globalState.codepage = 437; + else if (eq(lexem.KeywordName(), "pca")) globalState.codepage = 850; + else if (eq(lexem.KeywordName(), "ansicpg") && val) + globalState.codepage = static_cast<unsigned int>(*val); + else if (eq(lexem.KeywordName(), "deff") && val) + globalState.deff = *val; + else if (eq(lexem.KeywordName(), "fonttbl")) globalState.stack.top().fonttbl = true; + else if (eq(lexem.KeywordName(), "f") && val) { + globalState.stack.top().f = *val; + } + else if (eq(lexem.KeywordName(), "fcharset") && + globalState.stack.top().fonttbl && + (globalState.stack.top().f != -1) && val) { + FontInfo& f = globalState.fonttbl[globalState.stack.top().f]; + f.options |= FontInfo::has_fcharset; + f.fcharset = *val; + } + else if (eq(lexem.KeywordName(), "cpg") && val) { + if (globalState.stack.top().fonttbl && (globalState.stack.top().f != -1)) { // Defining a font + FontInfo& f = globalState.fonttbl[globalState.stack.top().f]; + f.options |= FontInfo::has_cpg; + f.cpg = *val; + } + else { // Overriding the codepage for the block - may be in filenames + globalState.stack.top().codepage = *val; + } + } + else if (eq(lexem.KeywordName(), "plain")) + globalState.stack.top().f = -1; + else if (eq(lexem.KeywordName(), "uc") && val) + globalState.stack.top().uc = *val; + } + break; + case Lexem::ltPCDATA_A: + if (globalState.pcdata_a_state == GlobalState::pcdsno) // Beginning of the pcdata + globalState.pcdata_a_codepage = globalState.GetCurrentCP(); // to use later to convert to utf16 + globalState.pcdata_a_state = GlobalState::pcdsin; + globalState.pcdata_a << lexem.pcdata_a(); + break; + } +} + +void DecodeRTF(std::istream& rtf, CRTFDecoder& decoder) +{ + // Check if this is the rtf + Lexem lexem = GetLexem(rtf); + if (lexem.type() != Lexem::ltGroupBegin) + return; + decoder.BeginGroup(); + lexem = GetLexem(rtf); + if ((lexem.type() != Lexem::ltKeyword) || !eq(lexem.KeywordName(), "rtf") || + !lexem.KeywordVal() || (*lexem.KeywordVal() != 1)) + return; + decoder.Keyword(lexem.KeywordName(), lexem.KeywordVal()); + + GlobalState state(rtf); + // Level is the count of elements in the stack + + while (!state.stream.eof() && (state.stack.size()>0)) { // Don't go past the global group + lexem = GetLexem(state.stream); + PreprocessLexem(lexem, state.stream, state.stack.top().uc); + UpdateState(lexem, state); + + if (state.pcdata_a_state == GlobalState::pcdsfinished) { + std::string s = state.pcdata_a.str(); + int sz = ::MultiByteToWideChar(state.pcdata_a_codepage, 0, s.c_str(), s.size(), 0, 0); + if (sz) { + wchar_t* data = new wchar_t[sz]; + ::MultiByteToWideChar(state.pcdata_a_codepage, 0, s.c_str(), s.size(), data, sz); + decoder.PCDATA(data, sz); + delete[] data; + } + state.pcdata_a.str(""); // reset + } + + switch (lexem.type()) { + case Lexem::ltGroupBegin: + decoder.BeginGroup(); + break; + case Lexem::ltGroupEnd: + decoder.EndGroup(); + break; + case Lexem::ltKeyword: + decoder.Keyword(lexem.KeywordName(), lexem.KeywordVal()); + break; + case Lexem::ltPCDATA_W: + { + wchar_t ch = lexem.pcdata_w(); + decoder.PCDATA(&ch, 1); + } + break; + case Lexem::ltBDATA: + decoder.BDATA(lexem.bdata(), lexem.bdata_sz()); + break; + case Lexem::ltError: + break; // Just silently skip the erroneous data - basic error recovery + } + } // while +} // DecodeRTF diff --git a/mailnews/import/outlook/src/rtfDecoder.h b/mailnews/import/outlook/src/rtfDecoder.h new file mode 100644 index 000000000..85a17721d --- /dev/null +++ b/mailnews/import/outlook/src/rtfDecoder.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/. */ + +#include <istream> + +template <size_t len> +inline bool eq(const char* str1, const char (&str2)[len]) +{ + return ::strncmp(str1, str2, len) == 0; +}; + +class CRTFDecoder { +public: + virtual void BeginGroup() = 0; + virtual void EndGroup() = 0; + virtual void Keyword(const char* name, const int* Val) = 0; + virtual void PCDATA(const wchar_t* data, size_t cch) = 0; + virtual void BDATA(const char* data, size_t sz) = 0; +}; + +void DecodeRTF(std::istream& rtf, CRTFDecoder& decoder); diff --git a/mailnews/import/outlook/src/rtfMailDecoder.cpp b/mailnews/import/outlook/src/rtfMailDecoder.cpp new file mode 100644 index 000000000..9a6b34725 --- /dev/null +++ b/mailnews/import/outlook/src/rtfMailDecoder.cpp @@ -0,0 +1,79 @@ +/* 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 "rtfMailDecoder.h" + +void CRTFMailDecoder::BeginGroup() +{ + ClearState(sAsterisk); + SetState(sBeginGroup); + if (m_skipLevel) + ++m_skipLevel; +} + +void CRTFMailDecoder::EndGroup() +{ + ClearState(sAsterisk|sBeginGroup); + if (m_skipLevel) + --m_skipLevel; +} + +void CRTFMailDecoder::AddText(const wchar_t* txt, size_t cch) +{ + if (!IsHtmlRtf()) { + if (cch == static_cast<size_t>(-1)) + m_text += txt; + else + m_text.append(txt, cch); + } +} + +void CRTFMailDecoder::Keyword(const char* name, const int* Val) +{ + bool asterisk = IsAsterisk(); ClearState(sAsterisk); // for inside use only + bool beginGroup = IsBeginGroup(); ClearState(sBeginGroup); // for inside use only + if (!m_skipLevel) { + if (eq(name, "*") && beginGroup) SetState(sAsterisk); + else if (asterisk) { + if (eq(name, "htmltag") && (m_mode == mHTML)) { // \*\htmltag -> don't ignore; include the following text + } + else ++m_skipLevel; + } + else if (eq(name, "htmlrtf")) { + if (Val && (*Val==0)) + ClearState(sHtmlRtf); + else + SetState(sHtmlRtf); + } + else if (eq(name, "par") || eq(name, "line")) { + AddText(L"\r\n"); + } + else if (eq(name, "tab")) { + AddText(L"\t"); + } + else if (eq(name, "rquote")) { + AddText(L"\x2019"); // Unicode right single quotation mark + } + else if (eq(name, "fromtext") && (m_mode==mNone)) { // avoid double "fromX" + m_mode = mText; + } + else if (eq(name, "fromhtml") && (m_mode==mNone)) { // avoid double "fromX" + m_mode = mHTML; + } + else if (eq(name, "fonttbl") || eq(name, "colortbl") || eq(name, "stylesheet") || eq(name, "pntext")) + ++m_skipLevel; + } +} + +void CRTFMailDecoder::PCDATA(const wchar_t* data, size_t cch) +{ + ClearState(sAsterisk|sBeginGroup); + if (!m_skipLevel) + AddText(data, cch); +} + +void CRTFMailDecoder::BDATA(const char* data, size_t sz) +{ + ClearState(sAsterisk|sBeginGroup); +} diff --git a/mailnews/import/outlook/src/rtfMailDecoder.h b/mailnews/import/outlook/src/rtfMailDecoder.h new file mode 100644 index 000000000..2b621577f --- /dev/null +++ b/mailnews/import/outlook/src/rtfMailDecoder.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/. */ + +#include "mozilla/Attributes.h" +#include <string> +#include "rtfDecoder.h" + +class CRTFMailDecoder: public CRTFDecoder { +public: + enum Mode {mNone, mText, mHTML}; + CRTFMailDecoder() : m_mode(mNone), m_state(sNormal), m_skipLevel(0) {} + void BeginGroup() override; + void EndGroup() override; + void Keyword(const char* name, const int* Val) override; + void PCDATA(const wchar_t* data, size_t cch) override; + void BDATA(const char* data, size_t sz) override; + const wchar_t* text() { return m_text.c_str(); } + std::wstring::size_type textSize() { return m_text.size(); } + Mode mode() { return m_mode; } +private: + enum State {sNormal = 0x0000, + sBeginGroup = 0x0001, + sAsterisk = 0x0002, + sHtmlRtf = 0x0004}; + + std::wstring m_text; + Mode m_mode; + unsigned int m_state; // bitmask of State +// bool m_beginGroup; // true just after the { +//bool m_asterisk; // true just after the {\* + int m_skipLevel; // if >0 then we ignore everything +// bool m_htmlrtf; + inline void SetState(unsigned int s) { m_state |= s; } + inline void ClearState(unsigned int s) { m_state &= ~s; } + inline bool CheckState(State s) { return (m_state & s) != 0; } + inline bool IsAsterisk() { return CheckState(sAsterisk); } + inline bool IsBeginGroup() { return CheckState(sBeginGroup); } + inline bool IsHtmlRtf() { return CheckState(sHtmlRtf); } + void AddText(const wchar_t* txt, size_t cch=static_cast<size_t>(-1)); +}; diff --git a/mailnews/import/public/moz.build b/mailnews/import/public/moz.build new file mode 100644 index 000000000..69efefb66 --- /dev/null +++ b/mailnews/import/public/moz.build @@ -0,0 +1,21 @@ +# 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 += [ + 'nsIImportABDescriptor.idl', + 'nsIImportAddressBooks.idl', + 'nsIImportFieldMap.idl', + 'nsIImportFilters.idl', + 'nsIImportGeneric.idl', + 'nsIImportMail.idl', + 'nsIImportMailboxDescriptor.idl', + 'nsIImportMimeEncode.idl', + 'nsIImportModule.idl', + 'nsIImportService.idl', + 'nsIImportSettings.idl', +] + +XPIDL_MODULE = 'import' + diff --git a/mailnews/import/public/nsIImportABDescriptor.idl b/mailnews/import/public/nsIImportABDescriptor.idl new file mode 100644 index 000000000..fe0ca1b31 --- /dev/null +++ b/mailnews/import/public/nsIImportABDescriptor.idl @@ -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/. */ + +/* + + Interface for importing mail - ui provided by the import module. If + you wish to provide your own UI then implement the nsIImportGeneric + interface. + + Can I get an attribute set method to take a const value??? + + */ + +#include "nsISupports.idl" + +interface nsIFile; + +/** + * Implementation Note: + * + * The default implementation can be obtained from + * nsIImportService::CreateNewABDescriptor(); + * + * You should only be interested in using this class if you implement + * the nsIImportAddressBooks interface in which case, just using the service to + * create new ones should work fine for you. If not, implement your + * own. + */ +[scriptable, uuid(2d8983b2-cea6-4ae2-9145-eb772481fa18)] +interface nsIImportABDescriptor : nsISupports +{ + /** + * use the following 2 attributes however you'd like to + * refer to a specific address book + */ + attribute unsigned long identifier; + attribute unsigned long ref; + + /** + * Doesn't have to be accurate, this is merely used to report progress. + * If you're importing a file, using file size and reporting progress + * as the number of bytes processed so far makes sense. For other formats + * returning the number of records may make more sense. + */ + attribute unsigned long size; + + /** + * The preferred name for this address book. Depending upon how the + * user selected import, the caller of the nsIImportAddressBooks interface + * may use this name to create the destination address book or it may + * ignore it. However, this must be provided in all cases as it is + * also displayed in the UI to the user. + */ + attribute AString preferredName; + + /** + * For address books that want a file descriptor to locate the address book. + * For formats that do not, use identifier & ref to refer to the address book + * OR implement your own nsIImportABDescriptor that contains additional data + * necessary to identify specific address books, + */ + attribute nsIFile abFile; + + /** + * Set by the UI to indicate whether or not this address book should be imported. + */ + attribute boolean import; +}; diff --git a/mailnews/import/public/nsIImportAddressBooks.idl b/mailnews/import/public/nsIImportAddressBooks.idl new file mode 100644 index 000000000..a6f9a7800 --- /dev/null +++ b/mailnews/import/public/nsIImportAddressBooks.idl @@ -0,0 +1,153 @@ +/* -*- 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/. */ + +/* + Interface for importing address books using the standard UI. Address + book import occurs in several forms (yuck!). + The destination can be 1..n new address books corresponding to the source + format. For instance a text file would import into a new address book + with the same name as the text file. + The destination can be 1 pre-defined address book, all entries will be + added to the supplied address book - this allows the address book UI so provide + an import command specific for an individual address book. + + The source can import 1 or multiple address books. + The address books can be auto-discoverable or user specified. + The address books can require field mapping or not. + + All of this is rather complicated but it should work out OK. + 1) The first UI + panel will allow selection of the address book and will indicate to the user + if the address book will be imported into an existing address book or new address + books. (This could be 2 separate xul UI's?). + 2) The second panel will show field mapping if it is required - if it is required then + there will be one panel per address book for formats that support multiple + address books. If it is not required then there will be no second panel. + 3) Show the progress dialog for the import - this could be per address book if + mapping is required? what to do, what to doooooo..... + 4) All done, maybe a what was done panel?? +*/ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIArray; +interface nsIImportABDescriptor; +interface nsIAddrDatabase; +interface nsIImportFieldMap; + +[scriptable, uuid(6bba48be-331c-41e3-bc9f-c2ea3754d977)] +interface nsIImportAddressBooks : nsISupports +{ + + /* + Does this interface supports 1 or 1..n address books. You only + get to choose 1 location so for formats where 1..n address books + are imported from a directory, then return true. For a 1 to 1 relationship + between location and address books return false. + */ + boolean GetSupportsMultiple(); + + /* + If the address book is not found via a file location.then return true + along with a description string of how or where the address book is + located. If it is a file location then return false. + If true, return a string like: "Outlook Express standard address book, + also known as the Windows address book" or just "Outlook Express address book". + If false, GetDefaultLocation will be called. + */ + + boolean GetAutoFind( out wstring description); + + /* + Returns true if the address book needs the user to specify a field map + for address books imported from this format. + */ + boolean GetNeedsFieldMap( in nsIFile location); + + /* + If found and userVerify BOTH return false, then it is assumed that this + means an error - address book cannot be found on this machine. + If userVerify is true, the user will have an opportunity to specify + a different location to import address book from. + */ + void GetDefaultLocation( out nsIFile location, + out boolean found, + out boolean userVerify); + /* + Returns an nsIArray which contains an nsIImportABDescriptor for each + address book. The array is not sorted before display to the user. + location is null if GetAutoFind returned true. + */ + nsIArray FindAddressBooks(in nsIFile location); + + /* + Fill in defaults (if any) for a field map for importing address + books from this location. + */ + void InitFieldMap(in nsIImportFieldMap fieldMap); + + /** + * Import a specific address book into the destination file supplied. + * If an error occurs that is non-fatal, the destination will be deleted and + * other adress book will be imported. If a fatal error occurs, the + * destination will be deleted and the import operation will abort. + * + * @param aSource The source data for the import. + * @param aDestination The proxy database for the destination of the + * import. + * @param aFieldMap The field map containing the mapping of fields to be + * used in cvs and tab type imports. + * @param aSupportService An optional proxy support service (nullptr is + * acceptable if it is not required), may be required + * for certain import types (e.g. nsIAbLDIFService for + * LDIF import). + * @param aErrorLog The error log from the import. + * @param aSuccessLog The success log from the import. + * @param aFatalError True if there was a fatal error doing the import. + */ + void ImportAddressBook(in nsIImportABDescriptor aSource, + in nsIAddrDatabase aDestination, + in nsIImportFieldMap aFieldMap, + in nsISupports aSupportService, + out wstring aErrorLog, + out wstring aSuccessLog, + out boolean aFatalError); + + /* + Return the amount of the address book that has been imported so far. This number + is used to present progress information and must never be larger than the + size specified in nsIImportABDescriptor.GetSize(); May be called from + a different thread than ImportAddressBook() + */ + unsigned long GetImportProgress(); + + /* + Set the location for reading sample data, this should be the same + as what is passed later to FindAddressBooks + */ + void SetSampleLocation( in nsIFile location); + + /* + * Return a string of sample data for a record, each field + * is separated by a newline (which means no newlines in the fields!) + * This is only supported by address books which use field maps and + * is used by the field map UI to allow the user to properly + * align fields to be imported. + * + * @param recordNumber index of the recrds, starting from 0. + * @param recordExists true if the record exists. + * + * @returns a string of sample data for the desired record + */ + wstring GetSampleData(in long recordNumber, out boolean recordExists); + +}; + + + +%{ C++ + +%} diff --git a/mailnews/import/public/nsIImportFieldMap.idl b/mailnews/import/public/nsIImportFieldMap.idl new file mode 100644 index 000000000..693e4be87 --- /dev/null +++ b/mailnews/import/public/nsIImportFieldMap.idl @@ -0,0 +1,72 @@ +/* -*- 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/. */ + +/* + Field map interface for importing address books + + A field map is an arbitrary sized list of mozilla address book fields. + The field map is used by import to map fields from the import format + to mozilla fields. + For export, the map contains the ordered list of mozilla fields to + export! +*/ + +#include "nsISupports.idl" + +interface nsIAddrDatabase; +interface nsIMdbRow; +interface nsIAbCard; + +[scriptable, uuid(deee9264-1fe3-47b1-b745-47b22de454e2)] +interface nsIImportFieldMap : nsISupports +{ + /* + Flag to indicate whether or not to skip the first record, + for instance csv files often have field names as the first + record + */ + attribute boolean skipFirstRecord; + + readonly attribute long numMozFields; + readonly attribute long mapSize; + + wstring GetFieldDescription( in long index); + + /* + Set the size of the field map, all unpopulated entries + will default to -1 + */ + void SetFieldMapSize( in long size); + + /* + Initialize the field map to a given size with default values + */ + void DefaultFieldMap( in long size); + + /* + Return the field number that this index maps to, -1 for no field + */ + long GetFieldMap( in long index); + + /* + Set the field that this index maps to, -1 for no field + */ + void SetFieldMap( in long index, in long fieldNum); + + /* + Return if this field is "active" in the map. + */ + boolean GetFieldActive( in long index); + + /* + Set the active state of this field + */ + void SetFieldActive( in long index, in boolean active); + + /* + Set the value of the given field in the database row + */ + void SetFieldValue( in nsIAddrDatabase database, in nsIMdbRow row, in long fieldNum, in wstring value); +}; diff --git a/mailnews/import/public/nsIImportFilters.idl b/mailnews/import/public/nsIImportFilters.idl new file mode 100644 index 000000000..d8e480617 --- /dev/null +++ b/mailnews/import/public/nsIImportFilters.idl @@ -0,0 +1,32 @@ +/* -*- 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 importing filters. +*/ + +#include "nsISupports.idl" + +interface nsIMsgAccount; +interface nsIFile; + +[scriptable, uuid(f2680ccf-d110-4b5b-954d-e072d4a16129)] +interface nsIImportFilters : nsISupports +{ + boolean AutoLocate( out wstring aDescription, out nsIFile aLocation); + + void SetLocation( in nsIFile aLocation); + + /* + Import filters and put any problems in the error out parameter. + */ + boolean Import( out wstring aError); +}; + + + +%{ C++ + +%} diff --git a/mailnews/import/public/nsIImportGeneric.idl b/mailnews/import/public/nsIImportGeneric.idl new file mode 100644 index 000000000..1496bac65 --- /dev/null +++ b/mailnews/import/public/nsIImportGeneric.idl @@ -0,0 +1,89 @@ +/* -*- 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/. */ + +/* + Interface for importing anything. You are responsible for opening + up UI and doing all of the work to make it happen. + +*/ + +#include "nsISupports.idl" + +interface nsISupportsString; + +[scriptable, uuid(469d7d5f-144c-4f07-9661-e49e40156348)] +interface nsIImportGeneric : nsISupports +{ + /* Use these to prepare for the import */ + /* + "mailInterface" - nsIImportMail interface + "mailBoxes" - nsIArray of nsIImportMailboxDescriptors + "mailLocation" - nsIFile, source location for mail + + "addressInterface" - nsIImportAddressBooks interface + "addressBooks" - nsIArray of nsIImportABDescriptors + "addressLocation" - src location of address books (if needed!) + "addressDestination" - uri of destination address book or null if + new address books will be created. + */ + nsISupports GetData( in string dataId); + + void SetData( in string dataId, in nsISupports pData); + + /* + "isInstalled" - if true then mail can be automatically located. + "canUserSetLocation" - if true then the user can specify the location + to look for mail. If both are false, then there is no way + to import mail from this format! + TBD: How to specify whether or not a file or a directory + should be specified? + "autoFind" - for address books, is the address book located without + using the file system - i.e. addressLocation is irrelevant. + "supportsMultiple" - 1 or 1..n address books are imported by this format? + + */ + long GetStatus( in string statusKind); + + /* + When you are ready to import call this. If it returns TRUE then + you must call BeginImport and then repeatedly call GetProgress until + it returns 100 % done or until ContinueImport returns FALSE. + If this returns FALSE then BeginImport will begin and finish the import + before it returns. + */ + boolean WantsProgress(); + + /* Use these for the actual import */ + /* Begin import is expected to start a new thread UNLESS WantsProgress returned + FALSE. It is REQUIRED to call WantsProgress before calling BeginImport. + If WantsProgress was false then this will return the success or + failure of the import. Failure can be reported even if WantsProgress + returned TRUE. + */ + boolean BeginImport(in nsISupportsString successLog, + in nsISupportsString errorLog); + /* + If WantsProgress returned TRUE then this will indicate if the import should + continue. If this returns FALSE then no other methods should be called + and the error log should be shown to the user. + */ + boolean ContinueImport(); + /* + Returns the percentage done. When this returns 100 then the import is done. + (only valid if WantsProgress returned true) + */ + long GetProgress(); + /* + Cancel an import in progress. Again, this is only valid if WantsProgress + returned true. + */ + void CancelImport(); +}; + + + +%{ C++ + +%} diff --git a/mailnews/import/public/nsIImportMail.idl b/mailnews/import/public/nsIImportMail.idl new file mode 100644 index 000000000..dcc4dd7ba --- /dev/null +++ b/mailnews/import/public/nsIImportMail.idl @@ -0,0 +1,98 @@ +/* -*- 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/. */ + +/* + + Interface for importing mail - ui provided by the import module. If + you wish to provide your own UI then implement the nsIImportGeneric + interface. + +*/ + +/* + If you support this interface then the standard mailbox import UI + can be used to drive your import of mailboxes, which means you don't have + to worry about anything other than implementing this interface + (and nsIImportModule) to import mailboxes. +*/ + +/* + The general process is: + 1) Do you know where the mail is located + 2) Do you want the user to "verify" this location and have + the option of specifying a different mail directory? + 3) Given a directory (either specified in 1 or 2) build a list + of all of the mailboxes to be imported. + 4) Import each mail box to the destination provided! + 5) Update the portion of the mailbox imported so far. This should + always be less than the mailbox size until you are done. This + is used for progress bar updating and MAY BE CALLED FROM ANOTHER + THREAD! + +*/ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIArray; +interface nsIImportMailboxDescriptor; +interface nsIMsgFolder; + +[scriptable, uuid(a14a3308-0849-420b-86d3-13a2948b5504)] +interface nsIImportMail : nsISupports +{ + + /* + If found and userVerify BOTH return false, then it is assumed that this + means an error - mail cannot be found on this machine. + If userVerify is true, the user will have an opportunity to specify + a different location to import mail from. + */ + void GetDefaultLocation( out nsIFile location, + out boolean found, + out boolean userVerify); + /* + Returns an nsIArray which contains an nsIImportMailboxID for each + mailbox. The array is not sorted before display to the user. + */ + nsIArray FindMailboxes(in nsIFile location); + + /* + Import a specific mailbox into the destination folder supplied. If an error + occurs that is non-fatal, the destination will be deleted and other mailboxes + will be imported. If a fatal error occurs, the destination will be deleted + and the import operation will abort. + */ + void ImportMailbox(in nsIImportMailboxDescriptor source, + in nsIMsgFolder dstFolder, + out wstring errorLog, + out wstring successLog, + out boolean fatalError); + + /* + Return the amount of the mailbox that has been imported so far. This number + is used to present progress information and must never be larger than the + size specified in nsIImportMailboxID.GetSize(); May be called from + a different thread than ImportMailbox() + */ + unsigned long GetImportProgress(); + + /* + * When migrating the local folders from the import source into mozilla, + * we want to translate reserved folder names from the import source to + * equivalent values for Mozilla. + * Localization Impact is unknown here. + */ + AString translateFolderName(in AString aFolderName); +}; + + + +%{ C++ +#define kDestTrashFolderName "Trash" +#define kDestUnsentMessagesFolderName "Unsent Messages" +#define kDestSentFolderName "Sent" +#define kDestInboxFolderName "Inbox" +%} diff --git a/mailnews/import/public/nsIImportMailboxDescriptor.idl b/mailnews/import/public/nsIImportMailboxDescriptor.idl new file mode 100644 index 000000000..24d24e694 --- /dev/null +++ b/mailnews/import/public/nsIImportMailboxDescriptor.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/. */ + +/* + + Interface for importing mail - ui provided by the import module. If + you wish to provide your own UI then implement the nsIImportGeneric + interface. + + */ + +#include "nsISupports.idl" + +interface nsIFile; + +[scriptable, uuid(69eba744-9c4f-4f79-a964-2134746b3656)] +interface nsIImportMailboxDescriptor : nsISupports +{ + attribute unsigned long identifier; + attribute unsigned long depth; + attribute unsigned long size; + + wstring GetDisplayName(); + void SetDisplayName( [const] in wstring name); + + attribute boolean import; + readonly attribute nsIFile file; +}; + + + +%{ C++ + +/* + The default implementation can be obtained from + nsIImportService::CreateNewMailboxDescriptor(); + + You should only be interested in using this class if you implement + the nsIImportMail interface in which case, just using the service to + create new ones should work fine for you. If not, implement your + own. +*/ + +%} diff --git a/mailnews/import/public/nsIImportMimeEncode.idl b/mailnews/import/public/nsIImportMimeEncode.idl new file mode 100644 index 000000000..a03646831 --- /dev/null +++ b/mailnews/import/public/nsIImportMimeEncode.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/. */ + +/* + Encodes a file from disk into an output stream including properly + encoded mime headers. +*/ + +#include "nsISupports.idl" + +interface nsIFile; + +[noscript, uuid(1d63892f-f660-465c-a550-95d4cb089de4)] +interface nsIImportMimeEncode : nsISupports +{ + + void EncodeFile( in nsIFile inFile, in nsIFile outFile, [const] in string fileName, [const] in string mimeType); + + boolean DoWork( out boolean done); + + long NumBytesProcessed(); + + boolean DoEncoding(); + void Initialize( in nsIFile inFile, in nsIFile outFile, [const] in string fileName, [const] in string mimeType); + +}; + + + +%{ C++ + +#define NS_IMPORTMIMEENCODE_CID \ +{ /* e4a1a340-8de2-11d3-a206-00a0cc26da63 */ \ + 0xe4a1a340, \ + 0x8de2, \ + 0x11d3, \ + {0xa2, 0x06, 0x00, 0xa0, 0xcc, 0x26, 0xda, 0x63} \ +} + +%} diff --git a/mailnews/import/public/nsIImportModule.idl b/mailnews/import/public/nsIImportModule.idl new file mode 100644 index 000000000..116cad767 --- /dev/null +++ b/mailnews/import/public/nsIImportModule.idl @@ -0,0 +1,32 @@ +/* -*- 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/. */ + +/* + + An import module. + + */ + +#include "nsISupports.idl" + + +[scriptable, uuid(624f0280-173f-11d3-a206-00a0cc26da63)] +interface nsIImportModule : nsISupports +{ + readonly attribute wstring name; + readonly attribute wstring description; + readonly attribute string supports; + readonly attribute boolean supportsUpgrade; + + nsISupports GetImportInterface( in string importType); +}; + + +%{ C++ +#define NS_IMPORT_MAIL_STR "mail" +#define NS_IMPORT_ADDRESS_STR "addressbook" +#define NS_IMPORT_SETTINGS_STR "settings" +#define NS_IMPORT_FILTERS_STR "filters" +%} diff --git a/mailnews/import/public/nsIImportService.idl b/mailnews/import/public/nsIImportService.idl new file mode 100644 index 000000000..160fdccdf --- /dev/null +++ b/mailnews/import/public/nsIImportService.idl @@ -0,0 +1,59 @@ +/* -*- 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/. */ + +/* + + The import service. + + */ + +#include "nsISupports.idl" + +interface nsIImportModule; +interface nsIImportMailboxDescriptor; +interface nsIImportABDescriptor; +interface nsIImportGeneric; +interface nsIImportFieldMap; +interface nsIMsgSendListener; +interface nsIMsgIdentity; +interface nsIMsgCompFields; +interface nsIArray; + +[scriptable, uuid(d0ed4c50-5997-49c9-8a6a-045f0680ed29)] +interface nsIImportService : nsISupports +{ + void DiscoverModules(); + + long GetModuleCount( in string filter); + void GetModuleInfo( in string filter, in long index, out wstring name, out wstring description); + wstring GetModuleName( in string filter, in long index); + wstring GetModuleDescription( in string filter, in long index); + nsIImportModule GetModule( in string filter, in long index); + nsIImportModule GetModuleWithCID( in nsCIDRef cid); + + nsIImportFieldMap CreateNewFieldMap(); + nsIImportMailboxDescriptor CreateNewMailboxDescriptor(); + nsIImportABDescriptor CreateNewABDescriptor(); + nsIImportGeneric CreateNewGenericMail(); + nsIImportGeneric CreateNewGenericAddressBooks(); + void CreateRFC822Message(in nsIMsgIdentity aIdentity, + in nsIMsgCompFields aMsgFields, + in string aBodytype, + in ACString aBody, + in boolean aCreateAsDraft, + in nsIArray aLoadedAttachments, + in nsIArray aEmbeddedObjects, + in nsIMsgSendListener aListener); + +}; + +%{ C++ +#define NS_IMPORTSERVICE_CID \ +{ /* 5df96d60-1726-11d3-a206-00a0cc26da63 */ \ + 0x5df96d60, 0x1726, 0x11d3, \ + {0xa2, 0x06, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63}} + +#define NS_IMPORTSERVICE_CONTRACTID "@mozilla.org/import/import-service;1" +%} diff --git a/mailnews/import/public/nsIImportSettings.idl b/mailnews/import/public/nsIImportSettings.idl new file mode 100644 index 000000000..9ff90d168 --- /dev/null +++ b/mailnews/import/public/nsIImportSettings.idl @@ -0,0 +1,39 @@ +/* -*- 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/. */ + +/* + Interface for importing settings. Settings can be auto-located or + specified by a specific file. Depends upon the app that the settings + are coming from. + +*/ + +#include "nsISupports.idl" + +interface nsIMsgAccount; +interface nsIFile; + +[scriptable, uuid(1c0e3012-bc4d-4fb2-be6a-0335c7bab9ac)] +interface nsIImportSettings : nsISupports +{ + boolean AutoLocate( out wstring description, out nsIFile location); + + void SetLocation( in nsIFile location); + + /* + Create all of the accounts, identities, and servers. Return an + account where any local mail from this app should be imported. + The returned account can be null which indicates that no suitable + account for local mail was created and a new account specifically for + the imported mail should be created. + */ + boolean Import( out nsIMsgAccount localMailAccount); +}; + + + +%{ C++ + +%} diff --git a/mailnews/import/src/ImportCharSet.cpp b/mailnews/import/src/ImportCharSet.cpp new file mode 100644 index 000000000..a8bc48d19 --- /dev/null +++ b/mailnews/import/src/ImportCharSet.cpp @@ -0,0 +1,58 @@ +/* -*- 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 "ImportCharSet.h" + +char ImportCharSet::m_upperCaseMap[256]; +char ImportCharSet::m_Ascii[256] = {0}; // the initialiser makes it strong + +class UInitMaps { +public: + UInitMaps(); +}; + +UInitMaps gInitMaps; + +UInitMaps::UInitMaps() +{ + int i; + + for (i = 0; i < 256; i++) + ImportCharSet::m_upperCaseMap[i] = i; + for (i = 'a'; i <= 'z'; i++) + ImportCharSet::m_upperCaseMap[i] = i - 'a' + 'A'; + + for (i = 0; i < 256; i++) + ImportCharSet::m_Ascii[i] = 0; + + for (i = ImportCharSet::cUpperAChar; i <= ImportCharSet::cUpperZChar; i++) + ImportCharSet::m_Ascii[i] |= (ImportCharSet::cAlphaNumChar | ImportCharSet::cAlphaChar); + for (i = ImportCharSet::cLowerAChar; i <= ImportCharSet::cLowerZChar; i++) + ImportCharSet::m_Ascii[i] |= (ImportCharSet::cAlphaNumChar | ImportCharSet::cAlphaChar); + for (i = ImportCharSet::cZeroChar; i <= ImportCharSet::cNineChar; i++) + ImportCharSet::m_Ascii[i] |= (ImportCharSet::cAlphaNumChar | ImportCharSet::cDigitChar); + + ImportCharSet::m_Ascii[ImportCharSet::cTabChar] |= ImportCharSet::cWhiteSpaceChar; + ImportCharSet::m_Ascii[ImportCharSet::cCRChar] |= ImportCharSet::cWhiteSpaceChar; + ImportCharSet::m_Ascii[ImportCharSet::cLinefeedChar] |= ImportCharSet::cWhiteSpaceChar; + ImportCharSet::m_Ascii[ImportCharSet::cSpaceChar] |= ImportCharSet::cWhiteSpaceChar; + + ImportCharSet::m_Ascii[uint8_t('(')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t(')')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('<')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('>')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('@')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t(',')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t(';')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t(':')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('\\')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('"')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('.')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('[')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t(']')] |= ImportCharSet::c822SpecialChar; + + +} diff --git a/mailnews/import/src/ImportCharSet.h b/mailnews/import/src/ImportCharSet.h new file mode 100644 index 000000000..c1f649ef2 --- /dev/null +++ b/mailnews/import/src/ImportCharSet.h @@ -0,0 +1,175 @@ +/* -*- 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 ImportCharSet_h___ +#define ImportCharSet_h___ + +#include "nscore.h" + + +// Some useful ASCII values +// 'A' = 65, 0x41 +// 'Z' = 90, 0x5a +// '_' = 95, 0x5f +// 'a' = 97, 0x61 +// 'z' = 122, 0x7a +// '0' = 48, 0x30 +// '1' = 49, 0x31 +// '9' = 57, 0x39 +// ' ' = 32, 0x20 +// whitespace, 10, 13, 32, 9 (linefeed, cr, space, tab) - 0x0a, 0x0d, 0x20, 0x09 +// ':' = 58, 0x3a + + +// a typedef enum would be nicer but some compilers still have trouble with treating +// enum's as plain numbers when needed + +class ImportCharSet { +public: + enum { + cTabChar = 9, + cLinefeedChar = 10, + cCRChar = 13, + cSpaceChar = 32, + cUpperAChar = 65, + cUpperZChar = 90, + cUnderscoreChar = 95, + cLowerAChar = 97, + cLowerZChar = 122, + cZeroChar = 48, + cNineChar = 57, + + cAlphaNumChar = 1, + cAlphaChar = 2, + cWhiteSpaceChar = 4, + cDigitChar = 8, + c822SpecialChar = 16 + }; + + static char m_upperCaseMap[256]; + static char m_Ascii[256]; + + inline static bool IsUSAscii(uint8_t ch) { return (((ch & (uint8_t)0x80) == 0));} + inline static bool Is822CtlChar(uint8_t ch) { return (ch < 32);} + inline static bool Is822SpecialChar(uint8_t ch) { return ((m_Ascii[ch] & c822SpecialChar) == c822SpecialChar);} + inline static bool IsWhiteSpace(uint8_t ch) { return ((m_Ascii[ch] & cWhiteSpaceChar) == cWhiteSpaceChar); } + inline static bool IsAlphaNum(uint8_t ch) { return ((m_Ascii[ch] & cAlphaNumChar) == cAlphaNumChar); } + inline static bool IsDigit(uint8_t ch) { return ((m_Ascii[ch] & cDigitChar) == cDigitChar); } + + inline static uint8_t ToLower(uint8_t ch) { if ((m_Ascii[ch] & cAlphaChar) == cAlphaChar) { return cLowerAChar + (m_upperCaseMap[ch] - cUpperAChar); } else return ch; } + + inline static long AsciiToLong(const uint8_t * pChar, uint32_t len) { + long num = 0; + while (len) { + if ((m_Ascii[*pChar] & cDigitChar) == 0) + return num; + num *= 10; + num += (*pChar - cZeroChar); + len--; + pChar++; + } + return num; + } + + inline static void ByteToHex(uint8_t byte, uint8_t * pHex) { + uint8_t val = byte; + val /= 16; + if (val < 10) + *pHex = '0' + val; + else + *pHex = 'A' + (val - 10); + pHex++; + val = byte; + val &= 0x0F; + if (val < 10) + *pHex = '0' + val; + else + *pHex = 'A' + (val - 10); + } + + inline static void LongToHexBytes(uint32_t type, uint8_t * pStr) { + ByteToHex((uint8_t) (type >> 24), pStr); + pStr += 2; + ByteToHex((uint8_t) ((type >> 16) & 0x0FF), pStr); + pStr += 2; + ByteToHex((uint8_t) ((type >> 8) & 0x0FF), pStr); + pStr += 2; + ByteToHex((uint8_t) (type & 0x0FF), pStr); + } + + inline static void SkipWhiteSpace(const uint8_t * & pChar, uint32_t & pos, uint32_t max) { + while ((pos < max) && (IsWhiteSpace(*pChar))) { + pos++; pChar++; + } + } + + inline static void SkipSpaceTab(const uint8_t * & pChar, uint32_t& pos, uint32_t max) { + while ((pos < max) && ((*pChar == (uint8_t)cSpaceChar) || (*pChar == (uint8_t)cTabChar))) { + pos++; pChar++; + } + } + + inline static void SkipTilSpaceTab(const uint8_t * & pChar, uint32_t& pos, uint32_t max) { + while ((pos < max) && (*pChar != (uint8_t)cSpaceChar) && (*pChar != (uint8_t)cTabChar)) { + pos++; + pChar++; + } + } + + inline static bool StrNICmp(const uint8_t * pChar, const uint8_t * pSrc, uint32_t len) { + while (len && (m_upperCaseMap[*pChar] == m_upperCaseMap[*pSrc])) { + pChar++; pSrc++; len--; + } + return len == 0; + } + + inline static bool StrNCmp(const uint8_t * pChar, const uint8_t *pSrc, uint32_t len) { + while (len && (*pChar == *pSrc)) { + pChar++; pSrc++; len--; + } + return len == 0; + } + + inline static int FindChar(const uint8_t * pChar, uint8_t ch, uint32_t max) { + uint32_t pos = 0; + while ((pos < max) && (*pChar != ch)) { + pos++; pChar++; + } + if (pos < max) + return (int) pos; + else + return -1; + } + + inline static bool NextChar(const uint8_t * & pChar, uint8_t ch, uint32_t& pos, uint32_t max) { + if ((pos < max) && (*pChar == ch)) { + pos++; + pChar++; + return true; + } + return false; + } + + inline static int32_t strcmp(const char * pS1, const char * pS2) { + while (*pS1 && *pS2 && (*pS1 == *pS2)) { + pS1++; + pS2++; + } + return *pS1 - *pS2; + } + + inline static int32_t stricmp(const char * pS1, const char * pS2) { + while (*pS1 && *pS2 && (m_upperCaseMap[uint8_t(*pS1)] == m_upperCaseMap[uint8_t(*pS2)])) { + pS1++; + pS2++; + } + return m_upperCaseMap[uint8_t(*pS1)] - m_upperCaseMap[uint8_t(*pS2)]; + } + +}; + + +#endif /* ImportCharSet_h__ */ + diff --git a/mailnews/import/src/ImportDebug.h b/mailnews/import/src/ImportDebug.h new file mode 100644 index 000000000..b905aa4a9 --- /dev/null +++ b/mailnews/import/src/ImportDebug.h @@ -0,0 +1,22 @@ +/* -*- 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 ImportDebug_h___ +#define ImportDebug_h___ + +#ifdef NS_DEBUG +#define IMPORT_DEBUG 1 +#endif + +// Use MOZ_LOG for logging. +#include "mozilla/Logging.h" +extern PRLogModuleInfo *IMPORTLOGMODULE; // Logging module + +#define IMPORT_LOG0(x) MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x)) +#define IMPORT_LOG1(x, y) MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y)) +#define IMPORT_LOG2(x, y, z) MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y, z)) +#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d)) + +#endif diff --git a/mailnews/import/src/ImportOutFile.cpp b/mailnews/import/src/ImportOutFile.cpp new file mode 100644 index 000000000..beeb8903a --- /dev/null +++ b/mailnews/import/src/ImportOutFile.cpp @@ -0,0 +1,299 @@ +/* -*- 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 "nscore.h" +#include "nsStringGlue.h" +#include "prio.h" +#include "nsNetUtil.h" +#include "nsISeekableStream.h" +#include "nsMsgUtils.h" +#include "ImportOutFile.h" +#include "ImportCharSet.h" + +#include "ImportDebug.h" + +/* +#ifdef _MAC +#define kMacNoCreator '????' +#define kMacTextFile 'TEXT' +#else +#define kMacNoCreator 0 +#define kMacTextFile 0 +#endif +*/ + +ImportOutFile::ImportOutFile() +{ + m_ownsFileAndBuffer = false; + m_pos = 0; + m_pBuf = nullptr; + m_bufSz = 0; + m_pTrans = nullptr; + m_pTransOut = nullptr; + m_pTransBuf = nullptr; +} + +ImportOutFile::ImportOutFile(nsIFile *pFile, uint8_t * pBuf, uint32_t sz) +{ + m_pTransBuf = nullptr; + m_pTransOut = nullptr; + m_pTrans = nullptr; + m_ownsFileAndBuffer = false; + InitOutFile(pFile, pBuf, sz); +} + +ImportOutFile::~ImportOutFile() +{ + if (m_ownsFileAndBuffer) + { + Flush(); + delete [] m_pBuf; + } + + delete m_pTrans; + delete m_pTransOut; + delete [] m_pTransBuf; +} + +bool ImportOutFile::Set8bitTranslator(nsImportTranslator *pTrans) +{ + if (!Flush()) + return false; + + m_engaged = false; + m_pTrans = pTrans; + m_supports8to7 = pTrans->Supports8bitEncoding(); + + + return true; +} + +bool ImportOutFile::End8bitTranslation(bool *pEngaged, nsCString& useCharset, nsCString& encoding) +{ + if (!m_pTrans) + return false; + + + bool bResult = Flush(); + if (m_supports8to7 && m_pTransOut) { + if (bResult) + bResult = m_pTrans->FinishConvertToFile(m_pTransOut); + if (bResult) + bResult = Flush(); + } + + if (m_supports8to7) { + m_pTrans->GetCharset(useCharset); + m_pTrans->GetEncoding(encoding); + } + else + useCharset.Truncate(); + *pEngaged = m_engaged; + delete m_pTrans; + m_pTrans = nullptr; + delete m_pTransOut; + m_pTransOut = nullptr; + delete [] m_pTransBuf; + m_pTransBuf = nullptr; + + return bResult; +} + +bool ImportOutFile::InitOutFile(nsIFile *pFile, uint32_t bufSz) +{ + if (!bufSz) + bufSz = 32 * 1024; + if (!m_pBuf) + m_pBuf = new uint8_t[ bufSz]; + + if (!m_outputStream) + { + nsresult rv; + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream), + pFile, + PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, + 0644); + + if (NS_FAILED(rv)) + { + IMPORT_LOG0("Couldn't create outfile\n"); + delete [] m_pBuf; + m_pBuf = nullptr; + return false; + } + } + m_pFile = pFile; + m_ownsFileAndBuffer = true; + m_pos = 0; + m_bufSz = bufSz; + return true; +} + +void ImportOutFile::InitOutFile(nsIFile *pFile, uint8_t * pBuf, uint32_t sz) +{ + m_ownsFileAndBuffer = false; + m_pFile = pFile; + m_pBuf = pBuf; + m_bufSz = sz; + m_pos = 0; +} + + + +bool ImportOutFile::Flush(void) +{ + if (!m_pos) + return true; + + uint32_t transLen; + bool duddleyDoWrite = false; + + // handle translations if appropriate + if (m_pTrans) { + if (m_engaged && m_supports8to7) { + // Markers can get confused by this crap!!! + // TLR: FIXME: Need to update the markers based on + // the difference between the translated len and untranslated len + + if (!m_pTrans->ConvertToFile( m_pBuf, m_pos, m_pTransOut, &transLen)) + return false; + if (!m_pTransOut->Flush()) + return false; + // now update our buffer... + if (transLen < m_pos) { + memcpy(m_pBuf, m_pBuf + transLen, m_pos - transLen); + } + m_pos -= transLen; + } + else if (m_engaged) { + // does not actually support translation! + duddleyDoWrite = true; + } + else { + // should we engage? + uint8_t * pChar = m_pBuf; + uint32_t len = m_pos; + while (len) { + if (!ImportCharSet::IsUSAscii(*pChar)) + break; + pChar++; + len--; + } + if (len) { + m_engaged = true; + if (m_supports8to7) { + // allocate our translation output buffer and file... + m_pTransBuf = new uint8_t[m_bufSz]; + m_pTransOut = new ImportOutFile(m_pFile, m_pTransBuf, m_bufSz); + return Flush(); + } + else + duddleyDoWrite = true; + } + else { + duddleyDoWrite = true; + } + } + } + else + duddleyDoWrite = true; + + if (duddleyDoWrite) { + uint32_t written = 0; + nsresult rv = m_outputStream->Write((const char *)m_pBuf, (int32_t)m_pos, &written); + if (NS_FAILED(rv) || ((uint32_t)written != m_pos)) + return false; + m_pos = 0; + } + + return true; +} + +bool ImportOutFile::WriteU8NullTerm(const uint8_t * pSrc, bool includeNull) +{ + while (*pSrc) { + if (m_pos >= m_bufSz) { + if (!Flush()) + return false; + } + *(m_pBuf + m_pos) = *pSrc; + m_pos++; + pSrc++; + } + if (includeNull) { + if (m_pos >= m_bufSz) { + if (!Flush()) + return false; + } + *(m_pBuf + m_pos) = 0; + m_pos++; + } + + return true; +} + +bool ImportOutFile::SetMarker(int markerID) +{ + if (!Flush()) { + return false; + } + + if (markerID < kMaxMarkers) { + int64_t pos = 0; + if (m_outputStream) + { + // do we need to flush for the seek to give us the right pos? + m_outputStream->Flush(); + nsresult rv; + nsCOMPtr <nsISeekableStream> seekStream = do_QueryInterface(m_outputStream, &rv); + NS_ENSURE_SUCCESS(rv, false); + rv = seekStream->Tell(&pos); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error, Tell failed on output stream\n"); + return false; + } + } + m_markers[markerID] = (uint32_t)pos + m_pos; + } + + return true; +} + +void ImportOutFile::ClearMarker(int markerID) +{ + if (markerID < kMaxMarkers) + m_markers[markerID] = 0; +} + +bool ImportOutFile::WriteStrAtMarker(int markerID, const char *pStr) +{ + if (markerID >= kMaxMarkers) + return false; + + if (!Flush()) + return false; + int64_t pos; + m_outputStream->Flush(); + nsresult rv; + nsCOMPtr <nsISeekableStream> seekStream = do_QueryInterface(m_outputStream, &rv); + NS_ENSURE_SUCCESS(rv, false); + rv = seekStream->Tell(&pos); + if (NS_FAILED(rv)) + return false; + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, (int32_t) m_markers[markerID]); + if (NS_FAILED(rv)) + return false; + uint32_t written; + rv = m_outputStream->Write(pStr, strlen(pStr), &written); + if (NS_FAILED(rv)) + return false; + + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, pos); + if (NS_FAILED(rv)) + return false; + + return true; +} + diff --git a/mailnews/import/src/ImportOutFile.h b/mailnews/import/src/ImportOutFile.h new file mode 100644 index 000000000..461728c22 --- /dev/null +++ b/mailnews/import/src/ImportOutFile.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef ImportOutFile_h___ +#define ImportOutFile_h___ + +#include "nsImportTranslator.h" +#include "nsIOutputStream.h" +#include "nsIFile.h" + +#define kMaxMarkers 10 + +class ImportOutFile; + +class ImportOutFile { +public: + ImportOutFile(); + ImportOutFile(nsIFile *pFile, uint8_t * pBuf, uint32_t sz); + ~ImportOutFile(); + + bool InitOutFile(nsIFile *pFile, uint32_t bufSz = 4096); + void InitOutFile(nsIFile *pFile, uint8_t * pBuf, uint32_t sz); + inline bool WriteData(const uint8_t * pSrc, uint32_t len); + inline bool WriteByte(uint8_t byte); + bool WriteStr(const char *pStr) {return WriteU8NullTerm((const uint8_t *) pStr, false); } + bool WriteU8NullTerm(const uint8_t * pSrc, bool includeNull); + bool WriteEol(void) { return WriteStr("\x0D\x0A"); } + bool Done(void) {return Flush();} + + // Marker support + bool SetMarker(int markerID); + void ClearMarker(int markerID); + bool WriteStrAtMarker(int markerID, const char *pStr); + + // 8-bit to 7-bit translation + bool Set8bitTranslator(nsImportTranslator *pTrans); + bool End8bitTranslation(bool *pEngaged, nsCString& useCharset, nsCString& encoding); + +protected: + bool Flush(void); + +protected: + nsCOMPtr <nsIFile> m_pFile; + nsCOMPtr <nsIOutputStream> m_outputStream; + uint8_t * m_pBuf; + uint32_t m_bufSz; + uint32_t m_pos; + bool m_ownsFileAndBuffer; + + // markers + uint32_t m_markers[kMaxMarkers]; + + // 8 bit to 7 bit translations + nsImportTranslator * m_pTrans; + bool m_engaged; + bool m_supports8to7; + ImportOutFile * m_pTransOut; + uint8_t * m_pTransBuf; +}; + +inline bool ImportOutFile::WriteData(const uint8_t * pSrc, uint32_t len) { + while ((len + m_pos) > m_bufSz) { + if ((m_bufSz - m_pos)) { + memcpy(m_pBuf + m_pos, pSrc, m_bufSz - m_pos); + len -= (m_bufSz - m_pos); + pSrc += (m_bufSz - m_pos); + m_pos = m_bufSz; + } + if (!Flush()) + return false; + } + + if (len) { + memcpy(m_pBuf + m_pos, pSrc, len); + m_pos += len; + } + + return true; +} + +inline bool ImportOutFile::WriteByte(uint8_t byte) { + if (m_pos == m_bufSz) { + if (!Flush()) + return false; + } + *(m_pBuf + m_pos) = byte; + m_pos++; + return true; +} + +#endif /* ImportOutFile_h__ */ + + diff --git a/mailnews/import/src/ImportTranslate.cpp b/mailnews/import/src/ImportTranslate.cpp new file mode 100644 index 000000000..f7f32f552 --- /dev/null +++ b/mailnews/import/src/ImportTranslate.cpp @@ -0,0 +1,105 @@ +/* -*- 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 "ImportTranslate.h" + +int ImportTranslate::m_useTranslator = -1; + + +bool ImportTranslate::ConvertString(const nsCString& inStr, nsCString& outStr, bool mimeHeader) +{ + if (inStr.IsEmpty()) { + outStr = inStr; + return true; + } + + nsImportTranslator *pTrans = GetTranslator(); + // int maxLen = (int) pTrans->GetMaxBufferSize(inStr.Length()); + // int hLen = 0; + nsCString set; + nsCString lang; + + if (mimeHeader) { + // add the charset and language + pTrans->GetCharset(set); + pTrans->GetLanguage(lang); + } + + // Unfortunatly, we didn't implement ConvertBuffer for all translators, + // just ConvertToFile. This means that this data will not always + // be converted to the charset of pTrans. In that case... + // We don't always have the data in the same charset as the current + // translator... + // It is safer to leave the charset and language field blank + set.Truncate(); + lang.Truncate(); + + uint8_t * pBuf; + /* + pBuf = (P_U8) outStr.GetBuffer(maxLen); + if (!pBuf) { + delete pTrans; + return FALSE; + } + pTrans->ConvertBuffer((PC_U8)(PC_S8)inStr, inStr.GetLength(), pBuf); + outStr.ReleaseBuffer(); + */ + outStr = inStr; + delete pTrans; + + + // Now I need to run the string through the mime-header special char + // encoder. + + pTrans = new CMHTranslator; + pBuf = new uint8_t[pTrans->GetMaxBufferSize(outStr.Length())]; + pTrans->ConvertBuffer((const uint8_t *)(outStr.get()), outStr.Length(), pBuf); + delete pTrans; + outStr.Truncate(); + if (mimeHeader) { + outStr = set; + outStr += "'"; + outStr += lang; + outStr += "'"; + } + outStr += (const char *)pBuf; + delete [] pBuf; + + return true; +} + + +nsImportTranslator *ImportTranslate::GetTranslator(void) +{ + if (m_useTranslator == -1) { + // get the translator to use... + // CString trans; + // trans.LoadString(IDS_LANGUAGE_TRANSLATION); + m_useTranslator = 0; + // if (!trans.CompareNoCase("iso-2022-jp")) + // gWizData.m_useTranslator = 1; + } + + switch(m_useTranslator) { + case 0: + return new nsImportTranslator; + //case 1: + // return new CSJis2JisTranslator; + default: + return new nsImportTranslator; + } +} + +nsImportTranslator *ImportTranslate::GetMatchingTranslator(const char *pCharSet) +{ +/* + CString jp = "iso-2022-jp"; + if (!jp.CompareNoCase(pCharSet)) + return new CSJis2JisTranslator; +*/ + + return nullptr; +} + diff --git a/mailnews/import/src/ImportTranslate.h b/mailnews/import/src/ImportTranslate.h new file mode 100644 index 000000000..3e6c596d4 --- /dev/null +++ b/mailnews/import/src/ImportTranslate.h @@ -0,0 +1,23 @@ +/* -*- 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 ImportTranslate_h___ +#define ImportTranslate_h___ + +#include "nsStringGlue.h" +#include "nsImportTranslator.h" + +class ImportTranslate { +public: + static bool ConvertString(const nsCString& inStr, nsCString& outStr, bool mimeHeader); + static nsImportTranslator *GetTranslator(void); + static nsImportTranslator *GetMatchingTranslator(const char *pCharSet); + +protected: + static int m_useTranslator; +}; + + +#endif /* ImportTranslate_h__ */ diff --git a/mailnews/import/src/moz.build b/mailnews/import/src/moz.build new file mode 100644 index 000000000..2df4926d5 --- /dev/null +++ b/mailnews/import/src/moz.build @@ -0,0 +1,25 @@ +# 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 += [ + 'ImportCharSet.cpp', + 'ImportOutFile.cpp', + 'ImportTranslate.cpp', + 'nsImportABDescriptor.cpp', + 'nsImportAddressBooks.cpp', + 'nsImportEmbeddedImageData.cpp', + 'nsImportEncodeScan.cpp', + 'nsImportFieldMap.cpp', + 'nsImportMail.cpp', + 'nsImportMailboxDescriptor.cpp', + 'nsImportMimeEncode.cpp', + 'nsImportScanFile.cpp', + 'nsImportService.cpp', + 'nsImportStringBundle.cpp', + 'nsImportTranslator.cpp', +] + +FINAL_LIBRARY = 'import' + diff --git a/mailnews/import/src/nsImportABDescriptor.cpp b/mailnews/import/src/nsImportABDescriptor.cpp new file mode 100644 index 000000000..05605d09e --- /dev/null +++ b/mailnews/import/src/nsImportABDescriptor.cpp @@ -0,0 +1,32 @@ +/* -*- 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 "nscore.h" +#include "nsImportABDescriptor.h" + +//////////////////////////////////////////////////////////////////////// + +NS_METHOD nsImportABDescriptor::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + nsImportABDescriptor *it = new nsImportABDescriptor(); + if (it == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(it); + nsresult rv = it->QueryInterface(aIID, aResult); + NS_RELEASE(it); + return rv; +} + +NS_IMPL_ISUPPORTS(nsImportABDescriptor, nsIImportABDescriptor) + +nsImportABDescriptor::nsImportABDescriptor() + : mId(0), mRef(0), mSize(0), mImport(true) +{ +} diff --git a/mailnews/import/src/nsImportABDescriptor.h b/mailnews/import/src/nsImportABDescriptor.h new file mode 100644 index 000000000..a8fa6fbad --- /dev/null +++ b/mailnews/import/src/nsImportABDescriptor.h @@ -0,0 +1,103 @@ +/* -*- 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 nsImportABDescriptor_h___ +#define nsImportABDescriptor_h___ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsIImportABDescriptor.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" + +//////////////////////////////////////////////////////////////////////// + +class nsImportABDescriptor : public nsIImportABDescriptor +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD GetIdentifier(uint32_t *pIdentifier) override { + *pIdentifier = mId; + return NS_OK; + } + NS_IMETHOD SetIdentifier(uint32_t ident) override { + mId = ident; + return NS_OK; + } + + NS_IMETHOD GetRef(uint32_t *pRef) override { + *pRef = mRef; + return NS_OK; + } + NS_IMETHOD SetRef(uint32_t ref) override { + mRef = ref; + return NS_OK; + } + + /* attribute unsigned long size; */ + NS_IMETHOD GetSize(uint32_t *pSize) override { + *pSize = mSize; + return NS_OK; + } + NS_IMETHOD SetSize(uint32_t theSize) override { + mSize = theSize; + return NS_OK; + } + + /* attribute AString displayName; */ + NS_IMETHOD GetPreferredName(nsAString &aName) override { + aName = mDisplayName; + return NS_OK; + } + NS_IMETHOD SetPreferredName(const nsAString &aName) override { + mDisplayName = aName; + return NS_OK; + } + + /* readonly attribute nsIFile fileSpec; */ + NS_IMETHOD GetAbFile(nsIFile **aFile) override { + if (!mFile) + return NS_ERROR_NULL_POINTER; + + return mFile->Clone(aFile); + } + + NS_IMETHOD SetAbFile(nsIFile *aFile) override { + if (!aFile) { + mFile = nullptr; + return NS_OK; + } + + return aFile->Clone(getter_AddRefs(mFile)); + } + + /* attribute boolean import; */ + NS_IMETHOD GetImport(bool *pImport) override { + *pImport = mImport; + return NS_OK; + } + NS_IMETHOD SetImport(bool doImport) override { + mImport = doImport; + return NS_OK; + } + + nsImportABDescriptor(); + + static NS_METHOD Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +private: + virtual ~nsImportABDescriptor() {} + uint32_t mId; // used by creator of the structure + uint32_t mRef; // depth in the hierarchy + nsString mDisplayName; // name of this mailbox + nsCOMPtr<nsIFile> mFile; // source file (if applicable) + uint32_t mSize; // size + bool mImport; // import it or not? +}; + + +#endif diff --git a/mailnews/import/src/nsImportAddressBooks.cpp b/mailnews/import/src/nsImportAddressBooks.cpp new file mode 100644 index 000000000..8791efb42 --- /dev/null +++ b/mailnews/import/src/nsImportAddressBooks.cpp @@ -0,0 +1,894 @@ +/* -*- 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 "prprf.h" +#include "plstr.h" +#include "nsCOMPtr.h" +#include "nsMsgUtils.h" +#include "nsIImportService.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportGeneric.h" +#include "nsISupportsPrimitives.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportFieldMap.h" +#include "nsStringGlue.h" +#include "nsIFile.h" +#include "nsIAddrDatabase.h" +#include "nsIAbManager.h" +#include "nsIAbLDIFService.h" +#include "nsAbBaseCID.h" +#include "nsIStringBundle.h" +#include "nsImportStringBundle.h" +#include "nsTextFormatter.h" +#include "nsServiceManagerUtils.h" +#include "msgCore.h" +#include "ImportDebug.h" +#include "nsIAbMDBDirectory.h" +#include "nsComponentManagerUtils.h" +#include "nsIArray.h" +#include "nsCOMArray.h" +#include "nsArrayUtils.h" + +static void ImportAddressThread(void *stuff); + +class AddressThreadData; + +class nsImportGenericAddressBooks : public nsIImportGeneric +{ +public: + + nsImportGenericAddressBooks(); + + NS_DECL_THREADSAFE_ISUPPORTS + + /* nsISupports GetData (in string dataId); */ + NS_IMETHOD GetData(const char *dataId, nsISupports **_retval) override; + + NS_IMETHOD SetData(const char *dataId, nsISupports *pData) override; + + NS_IMETHOD GetStatus(const char *statusKind, int32_t *_retval) override; + + NS_IMETHOD WantsProgress(bool *_retval) override; + + NS_IMETHOD BeginImport(nsISupportsString *successLog, nsISupportsString *errorLog, bool *_retval) override; + + NS_IMETHOD ContinueImport(bool *_retval) override; + + NS_IMETHOD GetProgress(int32_t *_retval) override; + + NS_IMETHOD CancelImport(void) override; + +private: + virtual ~nsImportGenericAddressBooks(); + void GetDefaultLocation(void); + void GetDefaultBooks(void); + void GetDefaultFieldMap(void); + +public: + static void SetLogs(nsString& success, nsString& error, nsISupportsString *pSuccess, nsISupportsString *pError); + static void ReportError(const char16_t *pName, nsString *pStream, + nsIStringBundle *aBundle); + +private: + nsIImportAddressBooks * m_pInterface; + nsCOMPtr<nsIArray> m_Books; + nsCOMArray<nsIAddrDatabase> m_DBs; + nsCOMPtr <nsIFile> m_pLocation; + nsIImportFieldMap * m_pFieldMap; + bool m_autoFind; + char16_t * m_description; + bool m_gotLocation; + bool m_found; + bool m_userVerify; + nsISupportsString * m_pSuccessLog; + nsISupportsString * m_pErrorLog; + uint32_t m_totalSize; + bool m_doImport; + AddressThreadData * m_pThreadData; + char * m_pDestinationUri; + nsCOMPtr<nsIStringBundle> m_stringBundle; +}; + +class AddressThreadData { +public: + bool driverAlive; + bool threadAlive; + bool abort; + bool fatalError; + uint32_t currentTotal; + uint32_t currentSize; + nsIArray *books; + nsCOMArray<nsIAddrDatabase>* dBs; + nsCOMPtr<nsIAbLDIFService> ldifService; + nsIImportAddressBooks * addressImport; + nsIImportFieldMap * fieldMap; + nsISupportsString * successLog; + nsISupportsString * errorLog; + char * pDestinationUri; + nsIStringBundle* stringBundle; + + AddressThreadData(); + ~AddressThreadData(); +}; + + +nsresult NS_NewGenericAddressBooks(nsIImportGeneric** aImportGeneric) +{ + NS_PRECONDITION(aImportGeneric != nullptr, "null ptr"); + if (! aImportGeneric) + return NS_ERROR_NULL_POINTER; + + nsImportGenericAddressBooks *pGen = new nsImportGenericAddressBooks(); + + if (pGen == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(pGen); + nsresult rv = pGen->QueryInterface(NS_GET_IID(nsIImportGeneric), (void **)aImportGeneric); + NS_RELEASE(pGen); + + return rv; +} + +nsImportGenericAddressBooks::nsImportGenericAddressBooks() +{ + m_pInterface = nullptr; + m_pSuccessLog = nullptr; + m_pErrorLog = nullptr; + m_totalSize = 0; + m_doImport = false; + m_pThreadData = nullptr; + m_pDestinationUri = nullptr; + m_pFieldMap = nullptr; + + m_autoFind = false; + m_description = nullptr; + m_gotLocation = false; + m_found = false; + m_userVerify = false; + + nsImportStringBundle::GetStringBundle(IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle)); +} + + +nsImportGenericAddressBooks::~nsImportGenericAddressBooks() +{ + if (m_pDestinationUri) + NS_Free(m_pDestinationUri); + + if (m_description) + NS_Free(m_description); + + NS_IF_RELEASE(m_pFieldMap); + NS_IF_RELEASE(m_pInterface); + NS_IF_RELEASE(m_pSuccessLog); + NS_IF_RELEASE(m_pErrorLog); +} + + + +NS_IMPL_ISUPPORTS(nsImportGenericAddressBooks, nsIImportGeneric) + + +NS_IMETHODIMP nsImportGenericAddressBooks::GetData(const char *dataId, nsISupports **_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + *_retval = nullptr; + if (!PL_strcasecmp(dataId, "addressInterface")) { + *_retval = m_pInterface; + NS_IF_ADDREF(m_pInterface); + } + + if (!PL_strcasecmp(dataId, "addressLocation")) { + if (!m_pLocation) + GetDefaultLocation(); + NS_IF_ADDREF(*_retval = m_pLocation); + } + + if (!PL_strcasecmp(dataId, "addressBooks")) { + if (!m_pLocation) + GetDefaultLocation(); + if (!m_Books) + GetDefaultBooks(); + *_retval = m_Books; + } + + if (!PL_strcasecmp(dataId, "addressDestination")) { + if (m_pDestinationUri) { + nsCOMPtr<nsISupportsCString> abString = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + abString->SetData(nsDependentCString(m_pDestinationUri)); + NS_IF_ADDREF(*_retval = abString); + } + } + + if (!PL_strcasecmp(dataId, "fieldMap")) { + if (m_pFieldMap) { + *_retval = m_pFieldMap; + m_pFieldMap->AddRef(); + } + else { + if (m_pInterface && m_pLocation) { + bool needsIt = false; + m_pInterface->GetNeedsFieldMap(m_pLocation, &needsIt); + if (needsIt) { + GetDefaultFieldMap(); + if (m_pFieldMap) { + *_retval = m_pFieldMap; + m_pFieldMap->AddRef(); + } + } + } + } + } + + if (!PL_strncasecmp(dataId, "sampleData-", 11)) { + // extra the record number + const char *pNum = dataId + 11; + int32_t rNum = 0; + while (*pNum) { + rNum *= 10; + rNum += (*pNum - '0'); + pNum++; + } + IMPORT_LOG1("Requesting sample data #: %ld\n", (long)rNum); + if (m_pInterface) { + nsCOMPtr<nsISupportsString> data = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + char16_t * pData = nullptr; + bool found = false; + rv = m_pInterface->GetSampleData(rNum, &found, &pData); + if (NS_FAILED(rv)) + return rv; + if (found) { + data->SetData(nsDependentString(pData)); + *_retval = data; + NS_ADDREF(*_retval); + } + NS_Free(pData); + } + } + + return NS_OK; +} + + +NS_IMETHODIMP nsImportGenericAddressBooks::SetData(const char *dataId, nsISupports *item) +{ + NS_PRECONDITION(dataId != nullptr, "null ptr"); + if (!dataId) + return NS_ERROR_NULL_POINTER; + + if (!PL_strcasecmp(dataId, "addressInterface")) { + NS_IF_RELEASE(m_pInterface); + if (item) + item->QueryInterface(NS_GET_IID(nsIImportAddressBooks), (void **) &m_pInterface); + } + if (!PL_strcasecmp(dataId, "addressBooks")) { + if (item) + item->QueryInterface(NS_GET_IID(nsIArray), (void **) &m_Books); + } + + if (!PL_strcasecmp(dataId, "addressLocation")) { + m_pLocation = nullptr; + + if (item) { + nsresult rv; + m_pLocation = do_QueryInterface(item, &rv); + NS_ENSURE_SUCCESS(rv,rv); + } + + if (m_pInterface) + m_pInterface->SetSampleLocation(m_pLocation); + } + + if (!PL_strcasecmp(dataId, "addressDestination")) { + if (item) { + nsCOMPtr<nsISupportsCString> abString; + item->QueryInterface(NS_GET_IID(nsISupportsCString), getter_AddRefs(abString)); + if (abString) { + if (m_pDestinationUri) + NS_Free(m_pDestinationUri); + m_pDestinationUri = nullptr; + nsAutoCString tempUri; + abString->GetData(tempUri); + m_pDestinationUri = ToNewCString(tempUri); + } + } + } + + if (!PL_strcasecmp(dataId, "fieldMap")) { + NS_IF_RELEASE(m_pFieldMap); + if (item) + item->QueryInterface(NS_GET_IID(nsIImportFieldMap), (void **) &m_pFieldMap); + } + + return NS_OK; +} + +NS_IMETHODIMP nsImportGenericAddressBooks::GetStatus(const char *statusKind, int32_t *_retval) +{ + NS_PRECONDITION(statusKind != nullptr, "null ptr"); + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!statusKind || !_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = 0; + + if (!PL_strcasecmp(statusKind, "isInstalled")) { + GetDefaultLocation(); + *_retval = (int32_t) m_found; + } + + if (!PL_strcasecmp(statusKind, "canUserSetLocation")) { + GetDefaultLocation(); + *_retval = (int32_t) m_userVerify; + } + + if (!PL_strcasecmp(statusKind, "autoFind")) { + GetDefaultLocation(); + *_retval = (int32_t) m_autoFind; + } + + if (!PL_strcasecmp(statusKind, "supportsMultiple")) { + bool multi = false; + if (m_pInterface) + m_pInterface->GetSupportsMultiple(&multi); + *_retval = (int32_t) multi; + } + + if (!PL_strcasecmp(statusKind, "needsFieldMap")) { + bool needs = false; + if (m_pInterface && m_pLocation) + m_pInterface->GetNeedsFieldMap(m_pLocation, &needs); + *_retval = (int32_t) needs; + } + + return NS_OK; +} + +void nsImportGenericAddressBooks::GetDefaultLocation(void) +{ + if (!m_pInterface) + return; + + if ((m_pLocation && m_gotLocation) || m_autoFind) + return; + + if (m_description) + NS_Free(m_description); + m_description = nullptr; + m_pInterface->GetAutoFind(&m_description, &m_autoFind); + m_gotLocation = true; + if (m_autoFind) { + m_found = true; + m_userVerify = false; + return; + } + + nsCOMPtr <nsIFile> pLoc; + m_pInterface->GetDefaultLocation(getter_AddRefs(pLoc), &m_found, &m_userVerify); + if (!m_pLocation) + m_pLocation = pLoc; +} + +void nsImportGenericAddressBooks::GetDefaultBooks(void) +{ + if (!m_pInterface || m_Books) + return; + + if (!m_pLocation && !m_autoFind) + return; + + nsresult rv = m_pInterface->FindAddressBooks(m_pLocation, getter_AddRefs(m_Books)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error: FindAddressBooks failed\n"); + } +} + +void nsImportGenericAddressBooks::GetDefaultFieldMap(void) +{ + if (!m_pInterface || !m_pLocation) + return; + + NS_IF_RELEASE(m_pFieldMap); + + nsresult rv; + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Unable to get nsIImportService.\n"); + return; + } + + rv = impSvc->CreateNewFieldMap(&m_pFieldMap); + if (NS_FAILED(rv)) + return; + + int32_t sz = 0; + rv = m_pFieldMap->GetNumMozFields(&sz); + if (NS_SUCCEEDED(rv)) + rv = m_pFieldMap->DefaultFieldMap(sz); + if (NS_SUCCEEDED(rv)) + rv = m_pInterface->InitFieldMap(m_pFieldMap); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error: Unable to initialize field map\n"); + NS_IF_RELEASE(m_pFieldMap); + } +} + + +NS_IMETHODIMP nsImportGenericAddressBooks::WantsProgress(bool *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + NS_ENSURE_ARG_POINTER(_retval); + + GetDefaultLocation(); + GetDefaultBooks(); + + bool result = false; + + if (m_Books) { + uint32_t count = 0; + uint32_t i; + bool import; + uint32_t size; + uint32_t totalSize = 0; + + m_Books->GetLength(&count); + + for (i = 0; i < count; i++) { + nsCOMPtr<nsIImportABDescriptor> book = do_QueryElementAt(m_Books, i); + if (book) { + import = false; + size = 0; + nsresult rv = book->GetImport(&import); + if (NS_SUCCEEDED(rv) && import) { + (void) book->GetSize(&size); + result = true; + } + totalSize += size; + } + } + + m_totalSize = totalSize; + } + + m_doImport = result; + + *_retval = result; + + return NS_OK; +} + +void nsImportGenericAddressBooks::SetLogs(nsString& success, nsString& error, nsISupportsString *pSuccess, nsISupportsString *pError) +{ + nsAutoString str; + if (pSuccess) { + pSuccess->GetData(str); + str.Append(success); + pSuccess->SetData(success); + } + if (pError) { + pError->GetData(str); + str.Append(error); + pError->SetData(error); + } +} + +already_AddRefed<nsIAddrDatabase> GetAddressBookFromUri(const char *pUri) +{ + if (!pUri) + return nullptr; + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID); + if (!abManager) + return nullptr; + + nsCOMPtr<nsIAbDirectory> directory; + abManager->GetDirectory(nsDependentCString(pUri), + getter_AddRefs(directory)); + if (!directory) + return nullptr; + + nsCOMPtr<nsIAbMDBDirectory> mdbDirectory = do_QueryInterface(directory); + if (!mdbDirectory) + return nullptr; + + nsCOMPtr<nsIAddrDatabase> pDatabase; + mdbDirectory->GetDatabase(getter_AddRefs(pDatabase)); + return pDatabase.forget(); +} + +already_AddRefed<nsIAddrDatabase> GetAddressBook(const char16_t *name, + bool makeNew) +{ + if (!makeNew) { + // FIXME: How do I get the list of address books and look for a + // specific name. Major bogosity! + // For now, assume we didn't find anything with that name + } + + IMPORT_LOG0("In GetAddressBook\n"); + + nsresult rv; + nsCOMPtr<nsIAddrDatabase> pDatabase; + nsCOMPtr<nsIFile> dbPath; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + /* Get the profile directory */ + rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath)); + if (NS_SUCCEEDED(rv)) + { + // Create a new address book file - we don't care what the file + // name is, as long as it's unique + rv = dbPath->Append(NS_LITERAL_STRING("impab.mab")); + if (NS_SUCCEEDED(rv)) + { + rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + + if (NS_SUCCEEDED(rv)) + { + IMPORT_LOG0("Getting the address database factory\n"); + + nsCOMPtr<nsIAddrDatabase> addrDBFactory = + do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return nullptr; + + IMPORT_LOG0("Opening the new address book\n"); + rv = addrDBFactory->Open(dbPath, true, true, + getter_AddRefs(pDatabase)); + } + } + } + } + if (NS_FAILED(rv)) + { + IMPORT_LOG0("Failed to get the user profile directory from the address book session\n"); + } + + if (pDatabase && dbPath) + { + // We made a database, add it to the UI?!?!?!?!?!?! + // This is major bogosity again! Why doesn't the address book + // just handle this properly for me? Uggggg... + + nsCOMPtr<nsIAbDirectory> parentDir; + abManager->GetDirectory(NS_LITERAL_CSTRING(kAllDirectoryRoot), + getter_AddRefs(parentDir)); + if (parentDir) + { + nsAutoCString URI("moz-abmdbdirectory://"); + nsAutoCString leafName; + rv = dbPath->GetNativeLeafName(leafName); + if (NS_FAILED(rv)) + IMPORT_LOG0("*** Error: Unable to get name of database file\n"); + else + { + URI.Append(leafName); + rv = parentDir->CreateDirectoryByURI(nsDependentString(name), URI); + if (NS_FAILED(rv)) + IMPORT_LOG0("*** Error: Unable to create address book directory\n"); + } + } + + if (NS_SUCCEEDED(rv)) + IMPORT_LOG0("Added new address book to the UI\n"); + else + IMPORT_LOG0("*** Error: An error occurred while adding the address book to the UI\n"); + } + + return pDatabase.forget(); +} + +NS_IMETHODIMP nsImportGenericAddressBooks::BeginImport(nsISupportsString *successLog, nsISupportsString *errorLog, bool *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + nsString success; + nsString error; + + if (!m_doImport) { + *_retval = true; + nsImportStringBundle::GetStringByID(IMPORT_NO_ADDRBOOKS, m_stringBundle, + success); + SetLogs(success, error, successLog, errorLog); + return NS_OK; + } + + if (!m_pInterface || !m_Books) { + nsImportStringBundle::GetStringByID(IMPORT_ERROR_AB_NOTINITIALIZED, + m_stringBundle, error); + SetLogs(success, error, successLog, errorLog); + *_retval = false; + return NS_OK; + } + + bool needsFieldMap = false; + + if (NS_FAILED(m_pInterface->GetNeedsFieldMap(m_pLocation, &needsFieldMap)) || + (needsFieldMap && !m_pFieldMap)) { + nsImportStringBundle::GetStringByID(IMPORT_ERROR_AB_NOTINITIALIZED, + m_stringBundle, error); + SetLogs(success, error, successLog, errorLog); + *_retval = false; + return NS_OK; + } + + NS_IF_RELEASE(m_pSuccessLog); + NS_IF_RELEASE(m_pErrorLog); + m_pSuccessLog = successLog; + m_pErrorLog = errorLog; + NS_IF_ADDREF(m_pSuccessLog); + NS_IF_ADDREF(m_pErrorLog); + + + // create the info need to drive address book import. We're + // not going to create a new thread for this since address books + // don't tend to be large, and import is rare. + m_pThreadData = new AddressThreadData(); + m_pThreadData->books = m_Books; + NS_ADDREF(m_Books); + m_pThreadData->addressImport = m_pInterface; + NS_ADDREF(m_pInterface); + m_pThreadData->fieldMap = m_pFieldMap; + NS_IF_ADDREF(m_pFieldMap); + m_pThreadData->errorLog = m_pErrorLog; + NS_IF_ADDREF(m_pErrorLog); + m_pThreadData->successLog = m_pSuccessLog; + NS_IF_ADDREF(m_pSuccessLog); + if (m_pDestinationUri) + m_pThreadData->pDestinationUri = strdup(m_pDestinationUri); + + uint32_t count = 0; + m_Books->GetLength(&count); + // Create/obtain any address books that we need here, so that we don't need + // to do so inside the import thread which would just proxy the create + // operations back to the main thread anyway. + nsCOMPtr<nsIAddrDatabase> db = GetAddressBookFromUri(m_pDestinationUri); + for (uint32_t i = 0; i < count; ++i) + { + nsCOMPtr<nsIImportABDescriptor> book = do_QueryElementAt(m_Books, i); + if (book) + { + if (!db) + { + nsString name; + book->GetPreferredName(name); + db = GetAddressBook(name.get(), true); + } + m_DBs.AppendObject(db); + } + } + m_pThreadData->dBs = &m_DBs; + + NS_IF_ADDREF(m_pThreadData->stringBundle = m_stringBundle); + + nsresult rv; + m_pThreadData->ldifService = do_GetService(NS_ABLDIFSERVICE_CONTRACTID, &rv); + + ImportAddressThread(m_pThreadData); + delete m_pThreadData; + m_pThreadData = nullptr; + *_retval = true; + + return NS_OK; +} + +NS_IMETHODIMP nsImportGenericAddressBooks::ContinueImport(bool *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = true; + if (m_pThreadData) { + if (m_pThreadData->fatalError) + *_retval = false; + } + + return NS_OK; +} + + +NS_IMETHODIMP nsImportGenericAddressBooks::GetProgress(int32_t *_retval) +{ + // This returns the progress from the the currently + // running import mail or import address book thread. + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + if (!m_pThreadData || !(m_pThreadData->threadAlive)) { + *_retval = 100; + return NS_OK; + } + + uint32_t sz = 0; + if (m_pThreadData->currentSize && m_pInterface) { + if (NS_FAILED(m_pInterface->GetImportProgress(&sz))) + sz = 0; + } + + if (m_totalSize) + *_retval = ((m_pThreadData->currentTotal + sz) * 100) / m_totalSize; + else + *_retval = 0; + + // never return less than 5 so it looks like we are doing something! + if (*_retval < 5) + *_retval = 5; + + // as long as the thread is alive don't return completely + // done. + if (*_retval > 99) + *_retval = 99; + + return NS_OK; +} + + +NS_IMETHODIMP nsImportGenericAddressBooks::CancelImport(void) +{ + if (m_pThreadData) { + m_pThreadData->abort = true; + m_pThreadData = nullptr; + } + + return NS_OK; +} + + +AddressThreadData::AddressThreadData() +{ + fatalError = false; + driverAlive = true; + threadAlive = true; + abort = false; + currentTotal = 0; + currentSize = 0; + books = nullptr; + addressImport = nullptr; + successLog = nullptr; + errorLog = nullptr; + pDestinationUri = nullptr; + fieldMap = nullptr; + stringBundle = nullptr; + ldifService = nullptr; +} + +AddressThreadData::~AddressThreadData() +{ + if (pDestinationUri) + NS_Free(pDestinationUri); + + NS_IF_RELEASE(books); + NS_IF_RELEASE(addressImport); + NS_IF_RELEASE(errorLog); + NS_IF_RELEASE(successLog); + NS_IF_RELEASE(fieldMap); + NS_IF_RELEASE(stringBundle); +} + +void nsImportGenericAddressBooks::ReportError(const char16_t *pName, + nsString *pStream, + nsIStringBundle* aBundle) +{ + if (!pStream) + return; + // load the error string + char16_t *pFmt = nsImportStringBundle::GetStringByID(IMPORT_ERROR_GETABOOK, aBundle); + char16_t *pText = nsTextFormatter::smprintf(pFmt, pName); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + NS_Free(pFmt); + pStream->AppendLiteral(MSG_LINEBREAK); +} + +static void ImportAddressThread(void *stuff) +{ + IMPORT_LOG0("In Begin ImportAddressThread\n"); + + AddressThreadData *pData = (AddressThreadData *)stuff; + uint32_t count = 0; + uint32_t i; + bool import; + uint32_t size; + + nsString success; + nsString error; + + (void) pData->books->GetLength(&count); + + for (i = 0; (i < count) && !(pData->abort); i++) { + nsCOMPtr<nsIImportABDescriptor> book = + do_QueryElementAt(pData->books, i); + + if (book) { + import = false; + size = 0; + nsresult rv = book->GetImport(&import); + if (NS_SUCCEEDED(rv) && import) + rv = book->GetSize(&size); + + if (NS_SUCCEEDED(rv) && size && import) { + nsString name; + book->GetPreferredName(name); + + nsCOMPtr<nsIAddrDatabase> db = pData->dBs->ObjectAt(i); + + bool fatalError = false; + pData->currentSize = size; + if (db) { + char16_t *pSuccess = nullptr; + char16_t *pError = nullptr; + + /* + if (pData->fieldMap) { + int32_t sz = 0; + int32_t mapIndex; + bool active; + pData->fieldMap->GetMapSize(&sz); + IMPORT_LOG1("**** Field Map Size: %d\n", (int) sz); + for (int32_t i = 0; i < sz; i++) { + pData->fieldMap->GetFieldMap(i, &mapIndex); + pData->fieldMap->GetFieldActive(i, &active); + IMPORT_LOG3("Field map #%d: index=%d, active=%d\n", (int) i, (int) mapIndex, (int) active); + } + } + */ + + rv = pData->addressImport->ImportAddressBook(book, + db, + pData->fieldMap, + pData->ldifService, + &pError, + &pSuccess, + &fatalError); + if (NS_SUCCEEDED(rv) && pSuccess) { + success.Append(pSuccess); + NS_Free(pSuccess); + } + if (pError) { + error.Append(pError); + NS_Free(pError); + } + } + else { + nsImportGenericAddressBooks::ReportError(name.get(), &error, pData->stringBundle); + } + + pData->currentSize = 0; + pData->currentTotal += size; + + if (db) + db->Close(true); + + if (fatalError) { + pData->fatalError = true; + break; + } + } + } + } + + + nsImportGenericAddressBooks::SetLogs(success, error, pData->successLog, pData->errorLog); + + if (pData->abort || pData->fatalError) { + // FIXME: do what is necessary to get rid of what has been imported so far. + // Nothing if we went into an existing address book! Otherwise, delete + // the ones we created? + } + +} diff --git a/mailnews/import/src/nsImportEmbeddedImageData.cpp b/mailnews/import/src/nsImportEmbeddedImageData.cpp new file mode 100644 index 000000000..526f9c21c --- /dev/null +++ b/mailnews/import/src/nsImportEmbeddedImageData.cpp @@ -0,0 +1,64 @@ +/* -*- 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 "nsImportEmbeddedImageData.h" + +NS_IMPL_ISUPPORTS(nsImportEmbeddedImageData, nsIMsgEmbeddedImageData) + +nsImportEmbeddedImageData::nsImportEmbeddedImageData() +{ +} + +nsImportEmbeddedImageData::nsImportEmbeddedImageData( + nsIURI *aUri, const nsACString &aCid) : m_uri(aUri), m_cid(aCid) +{ +} + +nsImportEmbeddedImageData::nsImportEmbeddedImageData( + nsIURI *aUri, const nsACString &aCid, const nsACString &aName) + : m_uri(aUri), m_cid(aCid), m_name(aName) +{ +} + +nsImportEmbeddedImageData::~nsImportEmbeddedImageData() +{ +} + +NS_IMETHODIMP nsImportEmbeddedImageData::GetUri(nsIURI **aUri) +{ + NS_ENSURE_ARG_POINTER(aUri); + NS_IF_ADDREF(*aUri = m_uri); + return NS_OK; +} + +NS_IMETHODIMP nsImportEmbeddedImageData::SetUri(nsIURI *aUri) +{ + m_uri = aUri; + return NS_OK; +} + +NS_IMETHODIMP nsImportEmbeddedImageData::GetCid(nsACString &aCid) +{ + aCid = m_cid; + return NS_OK; +} + +NS_IMETHODIMP nsImportEmbeddedImageData::SetCid(const nsACString &aCid) +{ + m_cid = aCid; + return NS_OK; +} + +NS_IMETHODIMP nsImportEmbeddedImageData::GetName(nsACString &aName) +{ + aName = m_name; + return NS_OK; +} + +NS_IMETHODIMP nsImportEmbeddedImageData::SetName(const nsACString &aName) +{ + m_name = aName; + return NS_OK; +} diff --git a/mailnews/import/src/nsImportEmbeddedImageData.h b/mailnews/import/src/nsImportEmbeddedImageData.h new file mode 100644 index 000000000..5635dc512 --- /dev/null +++ b/mailnews/import/src/nsImportEmbeddedImageData.h @@ -0,0 +1,32 @@ +/* -*- 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 __IMPORTEMBEDDEDIMAGETDATA_H__ +#define __IMPORTEMBEDDEDIMAGETDATA_H__ + +#include "nsIMsgSend.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" + +class nsImportEmbeddedImageData final : public nsIMsgEmbeddedImageData +{ +public: + nsImportEmbeddedImageData(nsIURI *aUri, const nsACString &aCID); + nsImportEmbeddedImageData(nsIURI *aUri, const nsACString &aCID, const nsACString &aName); + nsImportEmbeddedImageData(); + NS_DECL_NSIMSGEMBEDDEDIMAGEDATA + NS_DECL_ISUPPORTS + + nsCOMPtr<nsIURI> m_uri; + nsCString m_cid; + nsCString m_name; + +private: + ~nsImportEmbeddedImageData(); +}; + + +#endif diff --git a/mailnews/import/src/nsImportEncodeScan.cpp b/mailnews/import/src/nsImportEncodeScan.cpp new file mode 100644 index 000000000..77e89198d --- /dev/null +++ b/mailnews/import/src/nsImportEncodeScan.cpp @@ -0,0 +1,374 @@ +/* -*- 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 "nscore.h" +#include "nsImportEncodeScan.h" +#include "nsNetUtil.h" + +#define kBeginAppleSingle 0 +#define kBeginDataFork 1 +#define kBeginResourceFork 2 +#define kAddEntries 3 +#define kScanningDataFork 4 +#define kScanningRsrcFork 5 +#define kDoneWithFile 6 + +uint32_t gAppleSingleHeader[6] = {0x00051600, 0x00020000, 0, 0, 0, 0}; +#define kAppleSingleHeaderSize (6 * sizeof(uint32_t)) + +#ifdef _MAC_IMPORT_CODE +#include "MoreFilesExtras.h" +#include "MoreDesktopMgr.h" + +CInfoPBRec gCatInfoPB; +U32 g2000Secs = 0; +long gGMTDelta = 0; + +long GetGmtDelta(void); +U32 Get2000Secs(void); + + +long GetGmtDelta(void) +{ + MachineLocation myLocation; + ReadLocation(&myLocation); + long myDelta = BitAnd(myLocation.u.gmtDelta, 0x00FFFFFF); + if (BitTst(&myDelta, 23)) + myDelta = BitOr(myDelta, 0xFF000000); + return myDelta; +} + +U32 Get2000Secs(void) +{ + DateTimeRec dr; + dr.year = 2000; + dr.month = 1; + dr.day = 1; + dr.hour = 0; + dr.minute = 0; + dr.second = 0; + dr.dayOfWeek = 0; + U32 result; + DateToSeconds(&dr, &result); + return result; +} +#endif + +nsImportEncodeScan::nsImportEncodeScan() +{ + m_isAppleSingle = false; + m_encodeScanState = 0; + m_resourceForkSize = 0; + m_dataForkSize = 0; +} + +nsImportEncodeScan::~nsImportEncodeScan() +{ +} + +bool nsImportEncodeScan::InitEncodeScan(bool appleSingleEncode, nsIFile *fileLoc, const char *pName, uint8_t * pBuf, uint32_t sz) +{ + CleanUpEncodeScan(); + m_isAppleSingle = appleSingleEncode; + m_encodeScanState = kBeginAppleSingle; + m_pInputFile = do_QueryInterface(fileLoc); + m_useFileName = pName; + m_pBuf = pBuf; + m_bufSz = sz; + if (!m_isAppleSingle) + { + if (!m_inputStream) + { + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(m_inputStream), m_pInputFile); + NS_ENSURE_SUCCESS(rv, false); + } + + InitScan(m_inputStream, pBuf, sz); + } + else { + #ifdef _MAC_IMPORT_CODE + // Fill in the file sizes + m_resourceForkSize = fileLoc.GetMacFileSize(UFileLocation::eResourceFork); + m_dataForkSize = fileLoc.GetMacFileSize(UFileLocation::eDataFork); + #endif + } + + return true; +} + +void nsImportEncodeScan::CleanUpEncodeScan(void) +{ + m_pInputStream->Close(); + m_pInputStream = nullptr; + m_pInputFile = nullptr; +} + + +// 26 + 12 per entry + +void nsImportEncodeScan::FillInEntries(int numEntries) +{ +#ifdef _MAC_IMPORT_CODE + int len = m_useFileName.GetLength(); + if (len < 32) + len = 32; + long entry[3]; + long fileOffset = 26 + (12 * numEntries); + entry[0] = 3; + entry[1] = fileOffset; + entry[2] = m_useFileName.GetLength(); + fileOffset += len; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + + + Str255 comment; + comment[0] = 0; + OSErr err = FSpDTGetComment(m_inputFileLoc, comment); + if (comment[0] > 200) + comment[0] = 200; + entry[0] = 4; + entry[1] = fileOffset; + entry[2] = comment[0]; + fileOffset += 200; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + + + entry[0] = 8; + entry[1] = fileOffset; + entry[2] = 16; + fileOffset += 16; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + + entry[0] = 9; + entry[1] = fileOffset; + entry[2] = 32; + fileOffset += 32; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + + + entry[0] = 10; + entry[1] = fileOffset; + entry[2] = 4; + fileOffset += 4; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + + if (m_resourceForkSize) { + entry[0] = 2; + entry[1] = fileOffset; + entry[2] = m_resourceForkSize; + fileOffset += m_resourceForkSize; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + } + + if (m_dataForkSize) { + entry[0] = 1; + entry[1] = fileOffset; + entry[2] = m_dataForkSize; + fileOffset += m_dataForkSize; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + } + +#endif +} + +bool nsImportEncodeScan::AddEntries(void) +{ +#ifdef _MAC_IMPORT_CODE + if (!g2000Secs) { + g2000Secs = Get2000Secs(); + gGMTDelta = GetGmtDelta(); + } + MemCpy(m_pBuf + m_bytesInBuf, (PC_S8) m_useFileName, m_useFileName.GetLength()); + m_bytesInBuf += m_useFileName.GetLength(); + if (m_useFileName.GetLength() < 32) { + int len = m_useFileName.GetLength(); + while (len < 32) { + *((P_S8)m_pBuf + m_bytesInBuf) = 0; + m_bytesInBuf++; + len++; + } + } + + Str255 comment; + comment[0] = 0; + OSErr err = FSpDTGetComment(m_inputFileLoc, comment); + comment[0] = 200; + MemCpy(m_pBuf + m_bytesInBuf, &(comment[1]), comment[0]); + m_bytesInBuf += comment[0]; + + long dates[4]; + dates[0] = gCatInfoPB.hFileInfo.ioFlCrDat; + dates[1] = gCatInfoPB.hFileInfo.ioFlMdDat; + dates[2] = gCatInfoPB.hFileInfo.ioFlBkDat; + dates[3] = 0x80000000; + for (short i = 0; i < 3; i++) { + dates[i] -= g2000Secs; + dates[i] += gGMTDelta; + } + MemCpy(m_pBuf + m_bytesInBuf, dates, 16); + m_bytesInBuf += 16; + + + FInfo fInfo = gCatInfoPB.hFileInfo.ioFlFndrInfo; + FXInfo fxInfo = gCatInfoPB.hFileInfo.ioFlXFndrInfo; + fInfo.fdFlags = 0; + fInfo.fdLocation.h = 0; + fInfo.fdLocation.v = 0; + fInfo.fdFldr = 0; + MemSet(&fxInfo, 0, sizeof(fxInfo)); + MemCpy(m_pBuf + m_bytesInBuf, &fInfo, 16); + m_bytesInBuf += 16; + MemCpy(m_pBuf + m_bytesInBuf, &fxInfo, 16); + m_bytesInBuf += 16; + + + dates[0] = 0; + if ((gCatInfoPB.hFileInfo.ioFlAttrib & 1) != 0) + dates[0] |= 1; + MemCpy(m_pBuf + m_bytesInBuf, dates, 4); + m_bytesInBuf += 4; + + +#endif + return true; +} + +bool nsImportEncodeScan::Scan(bool *pDone) +{ + nsresult rv; + + *pDone = false; + if (m_isAppleSingle) { + // Stuff the buffer with things needed to encode the file... + // then just allow UScanFile to handle each fork, but be careful + // when handling eof. + switch(m_encodeScanState) { + case kBeginAppleSingle: { +#ifdef _MAC_IMPORT_CODE + OSErr err = GetCatInfoNoName(m_inputFileLoc.GetVRefNum(), m_inputFileLoc.GetParID(), m_inputFileLoc.GetFileNamePtr(), &gCatInfoPB); + if (err != noErr) + return FALSE; +#endif + m_eof = false; + m_pos = 0; + memcpy(m_pBuf, gAppleSingleHeader, kAppleSingleHeaderSize); + m_bytesInBuf = kAppleSingleHeaderSize; + int numEntries = 5; + if (m_dataForkSize) + numEntries++; + if (m_resourceForkSize) + numEntries++; + memcpy(m_pBuf + m_bytesInBuf, &numEntries, sizeof(numEntries)); + m_bytesInBuf += sizeof(numEntries); + FillInEntries(numEntries); + m_encodeScanState = kAddEntries; + return ScanBuffer(pDone); + } + break; + + case kBeginDataFork: { + if (!m_dataForkSize) { + m_encodeScanState = kDoneWithFile; + return true; + } + // Initialize the scan of the data fork... + if (!m_inputStream) + { + rv = NS_NewLocalFileInputStream(getter_AddRefs(m_inputStream), m_pInputFile); + NS_ENSURE_SUCCESS(rv, false); + } + m_encodeScanState = kScanningDataFork; + return true; + } + break; + + case kScanningDataFork: { + bool result = FillBufferFromFile(); + if (!result) + return false; + if (m_eof) { + m_eof = false; + result = ScanBuffer(pDone); + if (!result) + return false; + m_inputStream->Close(); + m_inputStream = nullptr; + m_encodeScanState = kDoneWithFile; + return true; + } + else + return ScanBuffer(pDone); + } + break; + + case kScanningRsrcFork: { + bool result = FillBufferFromFile(); + if (!result) + return false; + if (m_eof) { + m_eof = false; + result = ScanBuffer(pDone); + if (!result) + return false; + m_inputStream->Close(); + m_inputStream = nullptr; + m_encodeScanState = kBeginDataFork; + return true; + } + else + return ScanBuffer(pDone); + } + break; + + case kBeginResourceFork: { + if (!m_resourceForkSize) { + m_encodeScanState = kBeginDataFork; + return true; + } + /* + // FIXME: Open the resource fork on the Mac!!! + m_fH = UFile::OpenRsrcFileRead(m_inputFileLoc); + if (m_fH == TR_FILE_ERROR) + return FALSE; + */ + m_encodeScanState = kScanningRsrcFork; + return true; + } + break; + + case kAddEntries: { + ShiftBuffer(); + if (!AddEntries()) + return false; + m_encodeScanState = kBeginResourceFork; + return ScanBuffer(pDone); + } + break; + + case kDoneWithFile: { + ShiftBuffer(); + m_eof = true; + if (!ScanBuffer(pDone)) + return false; + *pDone = true; + return true; + } + break; + } + + } + else + return nsImportScanFile::Scan(pDone); + + return false; +} + diff --git a/mailnews/import/src/nsImportEncodeScan.h b/mailnews/import/src/nsImportEncodeScan.h new file mode 100644 index 000000000..3f0e246f1 --- /dev/null +++ b/mailnews/import/src/nsImportEncodeScan.h @@ -0,0 +1,39 @@ +/* -*- 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 nsImportEncodeScan_h___ +#define nsImportEncodeScan_h___ + +#include "mozilla/Attributes.h" +#include "nsIFile.h" +#include "nsImportScanFile.h" +#include "nsStringGlue.h" + +class nsImportEncodeScan : public nsImportScanFile { +public: + nsImportEncodeScan(); + ~nsImportEncodeScan(); + + bool InitEncodeScan(bool appleSingleEncode, nsIFile *pFile, const char *pName, uint8_t * pBuf, uint32_t sz); + void CleanUpEncodeScan(void); + + virtual bool Scan(bool *pDone) override; + +protected: + void FillInEntries(int numEntries); + bool AddEntries(void); + +protected: + bool m_isAppleSingle; + nsCOMPtr<nsIFile> m_pInputFile; + nsCOMPtr<nsIInputStream> m_inputStream; + int m_encodeScanState; + long m_resourceForkSize; + long m_dataForkSize; + nsCString m_useFileName; +}; + +#endif /* nsImportEncodeScan_h__ */ + diff --git a/mailnews/import/src/nsImportFieldMap.cpp b/mailnews/import/src/nsImportFieldMap.cpp new file mode 100644 index 000000000..d5e9748dc --- /dev/null +++ b/mailnews/import/src/nsImportFieldMap.cpp @@ -0,0 +1,384 @@ +/* -*- 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 "nscore.h" +#include "nsIStringBundle.h" +#include "nsImportFieldMap.h" +#include "nsImportStringBundle.h" +#include "nsCRTGlue.h" +#include "ImportDebug.h" +#include "nsCOMPtr.h" + +//////////////////////////////////////////////////////////////////////// + +NS_METHOD nsImportFieldMap::Create(nsIStringBundle *aBundle, nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + nsImportFieldMap *it = new nsImportFieldMap(aBundle); + if (it == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(it); + nsresult rv = it->QueryInterface(aIID, aResult); + NS_RELEASE(it); + return rv; +} + +NS_IMPL_ISUPPORTS(nsImportFieldMap, nsIImportFieldMap) + +NS_IMETHODIMP nsImportFieldMap::GetSkipFirstRecord(bool *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = m_skipFirstRecord; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::SetSkipFirstRecord(bool aResult) +{ + m_skipFirstRecord = aResult; + return NS_OK; +} + +nsImportFieldMap::nsImportFieldMap(nsIStringBundle *aBundle) +{ + m_numFields = 0; + m_pFields = nullptr; + m_pActive = nullptr; + m_allocated = 0; + // need to init the description array + m_mozFieldCount = 0; + m_skipFirstRecord = false; + nsCOMPtr<nsIStringBundle> pBundle = aBundle; + + nsString *pStr; + for (int32_t i = IMPORT_FIELD_DESC_START; i <= IMPORT_FIELD_DESC_END; i++, m_mozFieldCount++) { + pStr = new nsString(); + if (pBundle) { + nsImportStringBundle::GetStringByID(i, pBundle, *pStr); + } + else + pStr->AppendInt(i); + m_descriptions.AppendElement(pStr); + } +} + +nsImportFieldMap::~nsImportFieldMap() +{ + if (m_pFields) + delete [] m_pFields; + if (m_pActive) + delete [] m_pActive; + + nsString * pStr; + for (int32_t i = 0; i < m_mozFieldCount; i++) { + pStr = m_descriptions.ElementAt(i); + delete pStr; + } + m_descriptions.Clear(); +} + + +NS_IMETHODIMP nsImportFieldMap::GetNumMozFields(int32_t *aNumFields) +{ + NS_PRECONDITION(aNumFields != nullptr, "null ptr"); + if (!aNumFields) + return NS_ERROR_NULL_POINTER; + + *aNumFields = m_mozFieldCount; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::GetMapSize(int32_t *aNumFields) +{ + NS_PRECONDITION(aNumFields != nullptr, "null ptr"); + if (!aNumFields) + return NS_ERROR_NULL_POINTER; + + *aNumFields = m_numFields; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::GetFieldDescription(int32_t index, char16_t **_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = nullptr; + if ((index < 0) || ((size_t)index >= m_descriptions.Length())) + return NS_ERROR_FAILURE; + + *_retval = ToNewUnicode(*(m_descriptions.ElementAt(index))); + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::SetFieldMapSize(int32_t size) +{ + nsresult rv = Allocate(size); + if (NS_FAILED(rv)) + return rv; + + m_numFields = size; + + return NS_OK; +} + + +NS_IMETHODIMP nsImportFieldMap::DefaultFieldMap(int32_t size) +{ + nsresult rv = SetFieldMapSize(size); + if (NS_FAILED(rv)) + return rv; + for (int32_t i = 0; i < size; i++) { + m_pFields[i] = i; + m_pActive[i] = true; + } + + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::GetFieldMap(int32_t index, int32_t *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + + if ((index < 0) || (index >= m_numFields)) + return NS_ERROR_FAILURE; + + *_retval = m_pFields[index]; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::SetFieldMap(int32_t index, int32_t fieldNum) +{ + if (index == -1) { + nsresult rv = Allocate(m_numFields + 1); + if (NS_FAILED(rv)) + return rv; + index = m_numFields; + m_numFields++; + } + else { + if ((index < 0) || (index >= m_numFields)) + return NS_ERROR_FAILURE; + } + + if ((fieldNum != -1) && ((fieldNum < 0) || (fieldNum >= m_mozFieldCount))) + return NS_ERROR_FAILURE; + + m_pFields[index] = fieldNum; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::GetFieldActive(int32_t index, bool *active) +{ + NS_PRECONDITION(active != nullptr, "null ptr"); + if (!active) + return NS_ERROR_NULL_POINTER; + if ((index < 0) || (index >= m_numFields)) + return NS_ERROR_FAILURE; + + *active = m_pActive[index]; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::SetFieldActive(int32_t index, bool active) +{ + if ((index < 0) || (index >= m_numFields)) + return NS_ERROR_FAILURE; + + m_pActive[index] = active; + return NS_OK; +} + + +NS_IMETHODIMP nsImportFieldMap::SetFieldValue(nsIAddrDatabase *database, nsIMdbRow *row, int32_t fieldNum, const char16_t *value) +{ + NS_PRECONDITION(database != nullptr, "null ptr"); + NS_PRECONDITION(row != nullptr, "null ptr"); + NS_PRECONDITION(value != nullptr, "null ptr"); + if (!database || !row || !value) + return NS_ERROR_NULL_POINTER; + + // Allow the special value for a null field + if (fieldNum == -1) + return NS_OK; + + if ((fieldNum < 0) || (fieldNum >= m_mozFieldCount)) + return NS_ERROR_FAILURE; + + // UGGG!!!!! lot's of typing here! + nsresult rv; + + nsString str(value); + char *pVal = ToNewUTF8String(str); + + switch(fieldNum) { + case 0: + rv = database->AddFirstName(row, pVal); + break; + case 1: + rv = database->AddLastName(row, pVal); + break; + case 2: + rv = database->AddDisplayName(row, pVal); + break; + case 3: + rv = database->AddNickName(row, pVal); + break; + case 4: + rv = database->AddPrimaryEmail(row, pVal); + break; + case 5: + rv = database->Add2ndEmail(row, pVal); + break; + case 6: + rv = database->AddWorkPhone(row, pVal); + break; + case 7: + rv = database->AddHomePhone(row, pVal); + break; + case 8: + rv = database->AddFaxNumber(row, pVal); + break; + case 9: + rv = database->AddPagerNumber(row, pVal); + break; + case 10: + rv = database->AddCellularNumber(row, pVal); + break; + case 11: + rv = database->AddHomeAddress(row, pVal); + break; + case 12: + rv = database->AddHomeAddress2(row, pVal); + break; + case 13: + rv = database->AddHomeCity(row, pVal); + break; + case 14: + rv = database->AddHomeState(row, pVal); + break; + case 15: + rv = database->AddHomeZipCode(row, pVal); + break; + case 16: + rv = database->AddHomeCountry(row, pVal); + break; + case 17: + rv = database->AddWorkAddress(row, pVal); + break; + case 18: + rv = database->AddWorkAddress2(row, pVal); + break; + case 19: + rv = database->AddWorkCity(row, pVal); + break; + case 20: + rv = database->AddWorkState(row, pVal); + break; + case 21: + rv = database->AddWorkZipCode(row, pVal); + break; + case 22: + rv = database->AddWorkCountry(row, pVal); + break; + case 23: + rv = database->AddJobTitle(row, pVal); + break; + case 24: + rv = database->AddDepartment(row, pVal); + break; + case 25: + rv = database->AddCompany(row, pVal); + break; + case 26: + rv = database->AddWebPage1(row, pVal); + break; + case 27: + rv = database->AddWebPage2(row, pVal); + break; + case 28: + rv = database->AddBirthYear(row, pVal); + break; + case 29: + rv = database->AddBirthMonth(row, pVal); + break; + case 30: + rv = database->AddBirthDay(row, pVal); + break; + case 31: + rv = database->AddCustom1(row, pVal); + break; + case 32: + rv = database->AddCustom2(row, pVal); + break; + case 33: + rv = database->AddCustom3(row, pVal); + break; + case 34: + rv = database->AddCustom4(row, pVal); + break; + case 35: + rv = database->AddNotes(row, pVal); + break; + case 36: + rv = database->AddAimScreenName(row, pVal); + break; + default: + /* Get the field description, and add it as an anonymous attr? */ + /* OR WHAT???? */ + { + rv = NS_ERROR_FAILURE; + } + } + + NS_Free(pVal); + + return rv; +} + + +nsresult nsImportFieldMap::Allocate(int32_t newSize) +{ + if (newSize <= m_allocated) + return NS_OK; + + int32_t sz = m_allocated; + while (sz < newSize) + sz += 30; + + int32_t *pData = new int32_t[ sz]; + if (!pData) + return NS_ERROR_OUT_OF_MEMORY; + bool *pActive = new bool[sz]; + if (!pActive) { + delete [] pData; + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t i; + for (i = 0; i < sz; i++) { + pData[i] = -1; + pActive[i] = true; + } + if (m_numFields) { + for (i = 0; i < m_numFields; i++) { + pData[i] = m_pFields[i]; + pActive[i] = m_pActive[i]; + } + delete [] m_pFields; + delete [] m_pActive; + } + m_allocated = sz; + m_pFields = pData; + m_pActive = pActive; + return NS_OK; +} diff --git a/mailnews/import/src/nsImportFieldMap.h b/mailnews/import/src/nsImportFieldMap.h new file mode 100644 index 000000000..a25069b1e --- /dev/null +++ b/mailnews/import/src/nsImportFieldMap.h @@ -0,0 +1,46 @@ +/* -*- 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 nsImportFieldMap_h___ +#define nsImportFieldMap_h___ + +#include "nscore.h" +#include "nsIImportFieldMap.h" +#include "nsIAddrDatabase.h" +#include "nsTArray.h" +#include "nsString.h" + + +//////////////////////////////////////////////////////////////////////// + +class nsIStringBundle; + +class nsImportFieldMap : public nsIImportFieldMap +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIIMPORTFIELDMAP + + nsImportFieldMap(nsIStringBundle *aBundle); + + static NS_METHOD Create(nsIStringBundle *aBundle, nsISupports *aOuter, REFNSIID aIID, void **aResult); + +private: + virtual ~nsImportFieldMap(); + nsresult Allocate(int32_t newSize); + +private: + int32_t m_numFields; + int32_t * m_pFields; + bool * m_pActive; + int32_t m_allocated; + nsTArray<nsString*> m_descriptions; + int32_t m_mozFieldCount; + bool m_skipFirstRecord; +}; + + +#endif diff --git a/mailnews/import/src/nsImportMail.cpp b/mailnews/import/src/nsImportMail.cpp new file mode 100644 index 000000000..ad584b8a6 --- /dev/null +++ b/mailnews/import/src/nsImportMail.cpp @@ -0,0 +1,1208 @@ +/* -*- 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 "prthread.h" +#include "prprf.h" +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" + +#include "nsIImportMail.h" +#include "nsIImportGeneric.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIImportMailboxDescriptor.h" + +#include "nsStringGlue.h" +#include "nsUnicharUtils.h" + +#include "nsMsgUtils.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgFolder.h" +#include "nsImportStringBundle.h" +#include "nsIStringBundle.h" +#include "nsTextFormatter.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIImportService.h" +#include "ImportDebug.h" +#include "plstr.h" +#include "MailNewsTypes.h" +#include "nsThreadUtils.h" +#include "mozilla/Services.h" + +#define IMPORT_MSGS_URL "chrome://messenger/locale/importMsgs.properties" + +//////////////////////////////////////////////////////////////////////// + +static void ImportMailThread(void *stuff); + +class ImportThreadData; + +class nsImportGenericMail : public nsIImportGeneric +{ +public: + + nsImportGenericMail(); + + NS_DECL_THREADSAFE_ISUPPORTS + + /* nsISupports GetData (in string dataId); */ + NS_IMETHOD GetData(const char *dataId, nsISupports **_retval) override; + + NS_IMETHOD SetData(const char *dataId, nsISupports *pData) override; + + NS_IMETHOD GetStatus(const char *statusKind, int32_t *_retval) override; + + NS_IMETHOD WantsProgress(bool *_retval) override; + + NS_IMETHODIMP BeginImport(nsISupportsString *successLog, nsISupportsString *errorLog, bool *_retval) override; + + NS_IMETHOD ContinueImport(bool *_retval) override; + + NS_IMETHOD GetProgress(int32_t *_retval) override; + + NS_IMETHOD CancelImport(void) override; + +private: + virtual ~nsImportGenericMail(); + bool CreateFolder(nsIMsgFolder **ppFolder); + void GetDefaultMailboxes(void); + void GetDefaultLocation(void); + void GetDefaultDestination(void); + void GetMailboxName(uint32_t index, nsISupportsString *pStr); + +public: + static void SetLogs(nsString& success, nsString& error, nsISupportsString *pSuccess, nsISupportsString *pError); + static void ReportError(int32_t id, const char16_t *pName, nsString *pStream, nsIStringBundle* aBundle); + +private: + nsString m_pName; // module name that created this interface + nsIMsgFolder * m_pDestFolder; + bool m_deleteDestFolder; + bool m_createdFolder; + nsCOMPtr <nsIFile> m_pSrcLocation; + bool m_gotLocation; + bool m_found; + bool m_userVerify; + nsIImportMail *m_pInterface; + nsIArray * m_pMailboxes; + nsISupportsString *m_pSuccessLog; + nsISupportsString *m_pErrorLog; + uint32_t m_totalSize; + bool m_doImport; + ImportThreadData * m_pThreadData; + bool m_performingMigration; + nsCOMPtr<nsIStringBundle> m_stringBundle; +}; + +class ImportThreadData { +public: + bool driverAlive; + bool threadAlive; + bool abort; + bool fatalError; + uint32_t currentTotal; + uint32_t currentSize; + nsIMsgFolder * destRoot; + bool ownsDestRoot; + nsIArray *boxes; + nsIImportMail * mailImport; + nsISupportsString * successLog; + nsISupportsString * errorLog; + uint32_t currentMailbox; + bool performingMigration; + nsIStringBundle *stringBundle; + + ImportThreadData(); + ~ImportThreadData(); + void DriverDelete(); + void ThreadDelete(); + void DriverAbort(); +}; + +// forward decl for proxy methods +nsresult ProxyGetSubFolders(nsIMsgFolder *aFolder); +nsresult ProxyGetChildNamed(nsIMsgFolder *aFolder,const nsAString & aName, + nsIMsgFolder **aChild); +nsresult ProxyGetParent(nsIMsgFolder *aFolder, nsIMsgFolder **aParent); +nsresult ProxyContainsChildNamed(nsIMsgFolder *aFolder, const nsAString &aName, + bool *aResult); +nsresult ProxyGenerateUniqueSubfolderName(nsIMsgFolder *aFolder, + const nsAString& aPrefix, + nsIMsgFolder *aOtherFolder, + nsAString& aName); +nsresult ProxyCreateSubfolder(nsIMsgFolder *aFolder, const nsAString &aName); +nsresult ProxyForceDBClosed(nsIMsgFolder *aFolder); + +nsresult NS_NewGenericMail(nsIImportGeneric** aImportGeneric) +{ + NS_PRECONDITION(aImportGeneric != nullptr, "null ptr"); + if (! aImportGeneric) + return NS_ERROR_NULL_POINTER; + + nsImportGenericMail *pGen = new nsImportGenericMail(); + + if (pGen == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(pGen); + nsresult rv = pGen->QueryInterface(NS_GET_IID(nsIImportGeneric), (void **)aImportGeneric); + NS_RELEASE(pGen); + + return rv; +} + +nsImportGenericMail::nsImportGenericMail() +{ + m_found = false; + m_userVerify = false; + m_gotLocation = false; + m_pInterface = nullptr; + m_pMailboxes = nullptr; + m_pSuccessLog = nullptr; + m_pErrorLog = nullptr; + m_totalSize = 0; + m_doImport = false; + m_pThreadData = nullptr; + + m_pDestFolder = nullptr; + m_deleteDestFolder = false; + m_createdFolder = false; + m_performingMigration = false; + + // Init logging module. + if (!IMPORTLOGMODULE) + IMPORTLOGMODULE = PR_NewLogModule("IMPORT"); + + nsresult rv = nsImportStringBundle::GetStringBundle(IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle)); + if (NS_FAILED(rv)) + IMPORT_LOG0("Failed to get string bundle for Importing Mail"); +} + + +nsImportGenericMail::~nsImportGenericMail() +{ + if (m_pThreadData) { + m_pThreadData->DriverAbort(); + m_pThreadData = nullptr; + } + + NS_IF_RELEASE(m_pDestFolder); + NS_IF_RELEASE(m_pInterface); + NS_IF_RELEASE(m_pMailboxes); + NS_IF_RELEASE(m_pSuccessLog); + NS_IF_RELEASE(m_pErrorLog); +} + + + +NS_IMPL_ISUPPORTS(nsImportGenericMail, nsIImportGeneric) + + +NS_IMETHODIMP nsImportGenericMail::GetData(const char *dataId, nsISupports **_retval) +{ + nsresult rv = NS_OK; + + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = nullptr; + if (!PL_strcasecmp(dataId, "mailInterface")) { + *_retval = m_pInterface; + NS_IF_ADDREF(m_pInterface); + } + + if (!PL_strcasecmp(dataId, "mailBoxes")) { + if (!m_pMailboxes) + GetDefaultMailboxes(); + *_retval = m_pMailboxes; + NS_IF_ADDREF(m_pMailboxes); + } + + if (!PL_strcasecmp(dataId, "mailLocation")) { + if (!m_pSrcLocation) + GetDefaultLocation(); + NS_IF_ADDREF(*_retval = m_pSrcLocation); + } + + if (!PL_strcasecmp(dataId, "mailDestination")) { + if (!m_pDestFolder) + GetDefaultDestination(); + NS_IF_ADDREF(*_retval = m_pDestFolder); + } + + if (!PL_strcasecmp(dataId, "migration")) { + nsCOMPtr<nsISupportsPRBool> migrationString = do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + migrationString->SetData(m_performingMigration); + NS_IF_ADDREF(*_retval = migrationString); + } + + if (!PL_strcasecmp(dataId, "currentMailbox")) { + // create an nsISupportsString, get the current mailbox + // name being imported and put it in the string + nsCOMPtr<nsISupportsString> data = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + if (m_pThreadData) { + GetMailboxName(m_pThreadData->currentMailbox, data); + } + NS_ADDREF(*_retval = data); + } + + return rv; +} + +NS_IMETHODIMP nsImportGenericMail::SetData(const char *dataId, nsISupports *item) +{ + nsresult rv = NS_OK; + NS_PRECONDITION(dataId != nullptr, "null ptr"); + if (!dataId) + return NS_ERROR_NULL_POINTER; + + if (!PL_strcasecmp(dataId, "mailInterface")) { + NS_IF_RELEASE(m_pInterface); + if (item) + item->QueryInterface(NS_GET_IID(nsIImportMail), (void **) &m_pInterface); + } + if (!PL_strcasecmp(dataId, "mailBoxes")) { + NS_IF_RELEASE(m_pMailboxes); + if (item) + item->QueryInterface(NS_GET_IID(nsIArray), (void **) &m_pMailboxes); + } + + if (!PL_strcasecmp(dataId, "mailLocation")) { + NS_IF_RELEASE(m_pMailboxes); + m_pSrcLocation = nullptr; + if (item) { + nsresult rv; + nsCOMPtr <nsIFile> location = do_QueryInterface(item, &rv); + NS_ENSURE_SUCCESS(rv,rv); + m_pSrcLocation = location; + } + } + + if (!PL_strcasecmp(dataId, "mailDestination")) { + NS_IF_RELEASE(m_pDestFolder); + if (item) + item->QueryInterface(NS_GET_IID(nsIMsgFolder), (void **) &m_pDestFolder); + m_deleteDestFolder = false; + } + + if (!PL_strcasecmp(dataId, "name")) { + nsCOMPtr<nsISupportsString> nameString; + if (item) { + item->QueryInterface(NS_GET_IID(nsISupportsString), getter_AddRefs(nameString)); + rv = nameString->GetData(m_pName); + } + } + + if (!PL_strcasecmp(dataId, "migration")) { + nsCOMPtr<nsISupportsPRBool> migrationString; + if (item) { + item->QueryInterface(NS_GET_IID(nsISupportsPRBool), getter_AddRefs(migrationString)); + rv = migrationString->GetData(&m_performingMigration); + } + } + return rv; +} + +NS_IMETHODIMP nsImportGenericMail::GetStatus(const char *statusKind, int32_t *_retval) +{ + NS_PRECONDITION(statusKind != nullptr, "null ptr"); + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!statusKind || !_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = 0; + + if (!PL_strcasecmp(statusKind, "isInstalled")) { + GetDefaultLocation(); + *_retval = (int32_t) m_found; + } + + if (!PL_strcasecmp(statusKind, "canUserSetLocation")) { + GetDefaultLocation(); + *_retval = (int32_t) m_userVerify; + } + + return NS_OK; +} + + +void nsImportGenericMail::GetDefaultLocation(void) +{ + if (!m_pInterface) + return; + + if (m_pSrcLocation && m_gotLocation) + return; + + m_gotLocation = true; + + nsCOMPtr <nsIFile> pLoc; + m_pInterface->GetDefaultLocation(getter_AddRefs(pLoc), &m_found, &m_userVerify); + if (!m_pSrcLocation) + m_pSrcLocation = pLoc; +} + +void nsImportGenericMail::GetDefaultMailboxes(void) +{ + if (!m_pInterface || m_pMailboxes || !m_pSrcLocation) + return; + + m_pInterface->FindMailboxes(m_pSrcLocation, &m_pMailboxes); +} + +void nsImportGenericMail::GetDefaultDestination(void) +{ + if (m_pDestFolder) + return; + if (!m_pInterface) + return; + + nsIMsgFolder * rootFolder; + m_deleteDestFolder = false; + m_createdFolder = false; + if (CreateFolder(&rootFolder)) { + m_pDestFolder = rootFolder; + m_deleteDestFolder = true; + m_createdFolder = true; + return; + } + IMPORT_LOG0("*** GetDefaultDestination: Failed to create a default import destination folder."); +} + +NS_IMETHODIMP nsImportGenericMail::WantsProgress(bool *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + NS_ENSURE_ARG_POINTER(_retval); + + if (m_pThreadData) { + m_pThreadData->DriverAbort(); + m_pThreadData = nullptr; + } + + if (!m_pMailboxes) { + GetDefaultLocation(); + GetDefaultMailboxes(); + } + + if (!m_pDestFolder) { + GetDefaultDestination(); + } + + bool result = false; + + if (m_pMailboxes) { + uint32_t i; + bool import; + uint32_t count = 0; + uint32_t size; + uint32_t totalSize = 0; + + (void) m_pMailboxes->GetLength(&count); + for (i = 0; i < count; i++) { + nsCOMPtr<nsIImportMailboxDescriptor> box = + do_QueryElementAt(m_pMailboxes, i); + if (box) { + import = false; + size = 0; + nsresult rv = box->GetImport(&import); + if (NS_SUCCEEDED(rv) && import) { + (void) box->GetSize(&size); + result = true; + } + totalSize += size; + } + } + + m_totalSize = totalSize; + } + + m_doImport = result; + + *_retval = result; + + return NS_OK; +} + +void nsImportGenericMail::GetMailboxName(uint32_t index, nsISupportsString *pStr) +{ + if (m_pMailboxes) { + nsCOMPtr<nsIImportMailboxDescriptor> box(do_QueryElementAt(m_pMailboxes, index)); + if (box) { + nsAutoString name; + box->GetDisplayName(getter_Copies(name)); + if (!name.IsEmpty()) { + pStr->SetData(name); + } + } + } +} + +NS_IMETHODIMP nsImportGenericMail::BeginImport(nsISupportsString *successLog, nsISupportsString *errorLog, bool *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + nsString success; + nsString error; + + if (!m_doImport) { + nsImportStringBundle::GetStringByID(IMPORT_NO_MAILBOXES, + m_stringBundle, success); + SetLogs(success, error, successLog, errorLog); + *_retval = true; + return NS_OK; + } + + if (!m_pInterface || !m_pMailboxes) { + IMPORT_LOG0("*** BeginImport: Either the interface or source mailbox is not set properly."); + nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NOTINITIALIZED, + m_stringBundle, error); + SetLogs(success, error, successLog, errorLog); + *_retval = false; + return NS_OK; + } + + if (!m_pDestFolder) { + IMPORT_LOG0("*** BeginImport: The destination mailbox is not set properly."); + nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NODESTFOLDER, + m_stringBundle, error); + SetLogs(success, error, successLog, errorLog); + *_retval = false; + return NS_OK; + } + + if (m_pThreadData) { + m_pThreadData->DriverAbort(); + m_pThreadData = nullptr; + } + + NS_IF_RELEASE(m_pSuccessLog); + NS_IF_RELEASE(m_pErrorLog); + m_pSuccessLog = successLog; + m_pErrorLog = errorLog; + NS_IF_ADDREF(m_pSuccessLog); + NS_IF_ADDREF(m_pErrorLog); + + + // kick off the thread to do the import!!!! + m_pThreadData = new ImportThreadData(); + m_pThreadData->boxes = m_pMailboxes; + NS_ADDREF(m_pMailboxes); + m_pThreadData->mailImport = m_pInterface; + NS_ADDREF(m_pInterface); + m_pThreadData->errorLog = m_pErrorLog; + NS_IF_ADDREF(m_pErrorLog); + m_pThreadData->successLog = m_pSuccessLog; + NS_IF_ADDREF(m_pSuccessLog); + + m_pThreadData->ownsDestRoot = m_deleteDestFolder; + m_pThreadData->destRoot = m_pDestFolder; + m_pThreadData->performingMigration = m_performingMigration; + NS_IF_ADDREF(m_pDestFolder); + + NS_IF_ADDREF(m_pThreadData->stringBundle = m_stringBundle); + + PRThread *pThread = PR_CreateThread(PR_USER_THREAD, &ImportMailThread, m_pThreadData, + PR_PRIORITY_NORMAL, + PR_LOCAL_THREAD, + PR_UNJOINABLE_THREAD, + 0); + if (!pThread) { + m_pThreadData->ThreadDelete(); + m_pThreadData->abort = true; + m_pThreadData->DriverAbort(); + m_pThreadData = nullptr; + *_retval = false; + nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NOTHREAD, + m_stringBundle, error); + SetLogs(success, error, successLog, errorLog); + } + else + *_retval = true; + + return NS_OK; + +} + + +NS_IMETHODIMP nsImportGenericMail::ContinueImport(bool *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = true; + if (m_pThreadData) { + if (m_pThreadData->fatalError) + *_retval = false; + } + + return NS_OK; +} + + +NS_IMETHODIMP nsImportGenericMail::GetProgress(int32_t *_retval) +{ + // This returns the progress from the the currently + // running import mail or import address book thread. + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + if (!m_pThreadData || !(m_pThreadData->threadAlive)) { + *_retval = 100; + return NS_OK; + } + + uint32_t sz = 0; + if (m_pThreadData->currentSize && m_pInterface) { + if (NS_FAILED(m_pInterface->GetImportProgress(&sz))) + sz = 0; + } + + + // *_retval = (int32_t) (((uint32_t)(m_pThreadData->currentTotal + sz) * (uint32_t)100) / m_totalSize); + + if (m_totalSize) { + double perc; + perc = (double) m_pThreadData->currentTotal; + perc += sz; + perc *= 100; + perc /= m_totalSize; + *_retval = (int32_t) perc; + if (*_retval > 100) + *_retval = 100; + } + else + *_retval = 0; + + // never return 100% while the thread is still alive + if (*_retval > 99) + *_retval = 99; + + return NS_OK; +} + +void nsImportGenericMail::ReportError(int32_t id, const char16_t *pName, nsString *pStream, nsIStringBundle *aBundle) +{ + if (!pStream) + return; + + // load the error string + char16_t *pFmt = nsImportStringBundle::GetStringByID(id, aBundle); + char16_t *pText = nsTextFormatter::smprintf(pFmt, pName); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + NS_Free(pFmt); + pStream->Append(NS_ConvertASCIItoUTF16(MSG_LINEBREAK)); +} + + +void nsImportGenericMail::SetLogs(nsString& success, nsString& error, nsISupportsString *pSuccess, nsISupportsString *pError) +{ + nsAutoString str; + if (pSuccess) { + pSuccess->GetData(str); + str.Append(success); + pSuccess->SetData(str); + } + if (pError) { + pError->GetData(str); + str.Append(error); + pError->SetData(str); + } +} + +NS_IMETHODIMP nsImportGenericMail::CancelImport(void) +{ + if (m_pThreadData) { + m_pThreadData->abort = true; + m_pThreadData->DriverAbort(); + m_pThreadData = nullptr; + } + + return NS_OK; +} + + +ImportThreadData::ImportThreadData() +{ + fatalError = false; + driverAlive = true; + threadAlive = true; + abort = false; + currentTotal = 0; + currentSize = 0; + destRoot = nullptr; + ownsDestRoot = false; + boxes = nullptr; + mailImport = nullptr; + successLog = nullptr; + errorLog = nullptr; + stringBundle = nullptr; +} + +ImportThreadData::~ImportThreadData() +{ + NS_IF_RELEASE(destRoot); + NS_IF_RELEASE(boxes); + NS_IF_RELEASE(mailImport); + NS_IF_RELEASE(errorLog); + NS_IF_RELEASE(successLog); + NS_IF_RELEASE(stringBundle); +} + +void ImportThreadData::DriverDelete(void) +{ + driverAlive = false; + if (!driverAlive && !threadAlive) + delete this; +} + +void ImportThreadData::ThreadDelete() +{ + threadAlive = false; + if (!driverAlive && !threadAlive) + delete this; +} + +void ImportThreadData::DriverAbort() +{ + if (abort && !threadAlive && destRoot) { + if (ownsDestRoot) { + destRoot->RecursiveDelete(true, nullptr); + } + else { + // FIXME: just delete the stuff we created? + } + } + else + abort = true; + DriverDelete(); +} + + + +static void +ImportMailThread(void *stuff) +{ + ImportThreadData *pData = (ImportThreadData *)stuff; + + IMPORT_LOG0("ImportMailThread: Starting..."); + + nsresult rv = NS_OK; + + nsCOMPtr<nsIMsgFolder> destRoot(pData->destRoot); + + uint32_t count = 0; + rv = pData->boxes->GetLength(&count); + + uint32_t i; + bool import; + uint32_t size; + uint32_t depth = 1; + uint32_t newDepth; + nsString lastName; + char16_t * pName; + + nsCOMPtr<nsIMsgFolder> curFolder(destRoot); + + nsCOMPtr<nsIMsgFolder> newFolder; + nsCOMPtr<nsIMsgFolder> subFolder; + + bool exists; + + nsString success; + nsString error; + + // GetSubFolders() will initialize folders if they are not already initialized. + ProxyGetSubFolders(curFolder); + + IMPORT_LOG1("ImportMailThread: Total number of folders to import = %d.", count); + + // Note that the front-end js script only displays one import result string so + // we combine both good and bad import status into one string (in var 'success'). + + for (i = 0; (i < count) && !(pData->abort); i++) { + nsCOMPtr<nsIImportMailboxDescriptor> box = + do_QueryElementAt(pData->boxes, i); + if (box) { + pData->currentMailbox = i; + + import = false; + size = 0; + rv = box->GetImport(&import); + if (import) + rv = box->GetSize(&size); + rv = box->GetDepth(&newDepth); + if (newDepth > depth) { + // OK, we are going to add a subfolder under the last/previous folder we processed, so + // find this folder (stored in 'lastName') who is going to be the new parent folder. + IMPORT_LOG1("ImportMailThread: Processing child folder '%s'.", NS_ConvertUTF16toUTF8(lastName).get()); + rv = ProxyGetChildNamed(curFolder, lastName, getter_AddRefs(subFolder)); + if (NS_FAILED(rv)) { + IMPORT_LOG1("*** ImportMailThread: Failed to get the interface for child folder '%s'.", NS_ConvertUTF16toUTF8(lastName).get()); + nsImportGenericMail::ReportError(IMPORT_ERROR_MB_FINDCHILD, + lastName.get(), + &error, pData->stringBundle); + pData->fatalError = true; + break; + } + curFolder = subFolder; + // Make sure this new parent folder obj has the correct subfolder list so far. + rv = ProxyGetSubFolders(curFolder); + } + else if (newDepth < depth) { + rv = NS_OK; + while ((newDepth < depth) && NS_SUCCEEDED(rv)) { + rv = curFolder->GetParent(getter_AddRefs(curFolder)); + if (NS_FAILED(rv)) { + IMPORT_LOG1("*** ImportMailThread: Failed to get the interface for parent folder '%s'.", lastName.get()); + nsImportGenericMail::ReportError(IMPORT_ERROR_MB_FINDCHILD, + lastName.get(), &error, + pData->stringBundle); + pData->fatalError = true; + break; + } + depth--; + } + if (NS_FAILED(rv)) { + IMPORT_LOG1("*** ImportMailThread: Failed to get the proxy interface for parent folder '%s'.", lastName.get()); + nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NOPROXY, + pData->stringBundle, error); + pData->fatalError = true; + break; + } + } + depth = newDepth; + pName = nullptr; + box->GetDisplayName(&pName); + if (pName) { + lastName = pName; + NS_Free(pName); + } + else + lastName.AssignLiteral("Unknown!"); + + // translate the folder name if we are doing migration, but + // only for special folders which are at the root level + if (pData->performingMigration && depth == 1) + pData->mailImport->TranslateFolderName(lastName, lastName); + + exists = false; + rv = ProxyContainsChildNamed(curFolder, lastName, &exists); + + // If we are performing profile migration (as opposed to importing) then we are starting + // with empty local folders. In that case, always choose to over-write the existing local folder + // with this name. Don't create a unique subfolder name. Otherwise you end up with "Inbox, Inbox0" + // or "Unsent Folders, UnsentFolders0" + if (exists && !pData->performingMigration) { + nsString subName; + ProxyGenerateUniqueSubfolderName(curFolder, lastName, nullptr, subName); + if (!subName.IsEmpty()) + lastName.Assign(subName); + } + + IMPORT_LOG1("ImportMailThread: Creating new import folder '%s'.", NS_ConvertUTF16toUTF8(lastName).get()); + ProxyCreateSubfolder(curFolder, lastName); // this may fail if the folder already exists..that's ok + + rv = ProxyGetChildNamed(curFolder, lastName, getter_AddRefs(newFolder)); + if (NS_FAILED(rv)) { + IMPORT_LOG1("*** ImportMailThread: Failed to locate subfolder '%s' after it's been created.", lastName.get()); + nsImportGenericMail::ReportError(IMPORT_ERROR_MB_CREATE, lastName.get(), + &error, pData->stringBundle); + } + + if (size && import && newFolder && NS_SUCCEEDED(rv)) { + bool fatalError = false; + pData->currentSize = size; + char16_t *pSuccess = nullptr; + char16_t *pError = nullptr; + rv = pData->mailImport->ImportMailbox(box, newFolder, &pError, &pSuccess, &fatalError); + if (pError) { + error.Append(pError); + NS_Free(pError); + } + if (pSuccess) { + success.Append(pSuccess); + NS_Free(pSuccess); + } + + pData->currentSize = 0; + pData->currentTotal += size; + + // commit to the db synchronously, but using a proxy since it doesn't like being used + // elsewhere than from the main thread. + // OK, we've copied the actual folder/file over if the folder size is not 0 + // (ie, the msg summary is no longer valid) so close the msg database so that + // when the folder is reopened the folder db can be reconstructed (which + // validates msg summary and forces folder to be reparsed). + rv = ProxyForceDBClosed(newFolder); + fatalError = NS_FAILED(rv); + + if (fatalError) { + IMPORT_LOG1("*** ImportMailThread: ImportMailbox returned fatalError, mailbox #%d\n", (int) i); + pData->fatalError = true; + break; + } + } + } + } + + // Now save the new acct info to pref file. + nsCOMPtr <nsIMsgAccountManager> accMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && accMgr) { + rv = accMgr->SaveAccountInfo(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file"); + } + + nsImportGenericMail::SetLogs(success, error, pData->successLog, pData->errorLog); + + if (pData->abort || pData->fatalError) { + IMPORT_LOG0("*** ImportMailThread: Abort or fatalError flag was set\n"); + if (pData->ownsDestRoot) { + IMPORT_LOG0("Calling destRoot->RecursiveDelete\n"); + destRoot->RecursiveDelete(true, nullptr); + } + else { + // FIXME: just delete the stuff we created? + } + } + + IMPORT_LOG1("Import mailbox thread done: %d\n", (int) pData->currentTotal); + + pData->ThreadDelete(); + +} + +// Creates a folder in Local Folders with the module name + mail +// for e.g: Outlook Mail +bool nsImportGenericMail::CreateFolder(nsIMsgFolder **ppFolder) +{ + nsresult rv; + *ppFolder = nullptr; + + nsCOMPtr<nsIStringBundle> bundle; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) + return false; + rv = bundleService->CreateBundle(IMPORT_MSGS_URL, getter_AddRefs(bundle)); + if (NS_FAILED(rv)) + return false; + nsString folderName; + if (!m_pName.IsEmpty()) { + const char16_t *moduleName[] = { m_pName.get() }; + rv = bundle->FormatStringFromName(u"ImportModuleFolderName", + moduleName, 1, + getter_Copies(folderName)); + } + else { + rv = bundle->GetStringFromName(u"DefaultFolderName", + getter_Copies(folderName)); + } + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to get Folder Name!\n"); + return false; + } + nsCOMPtr <nsIMsgAccountManager> accMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create account manager!\n"); + return false; + } + + nsCOMPtr <nsIMsgIncomingServer> server; + rv = accMgr->GetLocalFoldersServer(getter_AddRefs(server)); + // if Local Folders does not exist already, create it + if (NS_FAILED(rv) || !server) + { + rv = accMgr->CreateLocalMailAccount(); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create Local Folders!\n"); + return false; + } + + rv = accMgr->GetLocalFoldersServer(getter_AddRefs(server)); + } + + if (NS_SUCCEEDED(rv) && server) { + nsCOMPtr <nsIMsgFolder> localRootFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(localRootFolder)); + if (localRootFolder) { + // we need to call GetSubFolders() so that the folders get initialized + // if they are not initialized yet. + nsCOMPtr<nsISimpleEnumerator> aEnumerator; + rv = localRootFolder->GetSubFolders(getter_AddRefs(aEnumerator)); + if (NS_SUCCEEDED(rv)) { + // check if the folder name we picked already exists. + bool exists = false; + rv = localRootFolder->ContainsChildNamed(folderName, &exists); + if (exists) { + nsString name; + localRootFolder->GenerateUniqueSubfolderName(folderName, nullptr, name); + if (!name.IsEmpty()) + folderName.Assign(name); + else { + IMPORT_LOG0("*** Failed to find a unique folder name!\n"); + return false; + } + } + IMPORT_LOG1("Creating folder for importing mail: '%s'\n", NS_ConvertUTF16toUTF8(folderName).get()); + + // Bug 564162 identifies a dataloss design flaw. + // A working Thunderbird client can have mail in Local Folders and a + // subsequent import 'Everything' will trigger a migration which + // overwrites existing mailboxes with the imported mailboxes. + rv = localRootFolder->CreateSubfolder(folderName, nullptr); + if (NS_SUCCEEDED(rv)) { + rv = localRootFolder->GetChildNamed(folderName, ppFolder); + if (*ppFolder) { + IMPORT_LOG1("Folder '%s' created successfully\n", NS_ConvertUTF16toUTF8(folderName).get()); + return true; + } + } + } + } // if localRootFolder + } // if server + IMPORT_LOG0("****** FAILED TO CREATE FOLDER FOR IMPORT\n"); + return false; +} + +/** + * These are the proxy objects we use to proxy nsIMsgFolder methods back + * the the main thread. Since there are only five, we can hand roll them. + * A better design might be a co-routine-ish design where the ui thread + * hands off each folder to the import thread and when the thread finishes + * the folder, the main thread hands it the next folder. + */ + +class GetSubFoldersRunnable : public mozilla::Runnable +{ +public: + GetSubFoldersRunnable(nsIMsgFolder *aFolder); + NS_DECL_NSIRUNNABLE +private: + nsCOMPtr<nsIMsgFolder> m_folder; +}; + +GetSubFoldersRunnable::GetSubFoldersRunnable(nsIMsgFolder *aFolder) : + m_folder(aFolder) +{ +} + +NS_IMETHODIMP GetSubFoldersRunnable::Run() +{ + nsCOMPtr<nsISimpleEnumerator> dummy; + return m_folder->GetSubFolders(getter_AddRefs(dummy)); +} + + +nsresult ProxyGetSubFolders(nsIMsgFolder *aFolder) +{ + RefPtr<GetSubFoldersRunnable> getSubFolders = + new GetSubFoldersRunnable(aFolder); + return NS_DispatchToMainThread(getSubFolders, NS_DISPATCH_SYNC); +} + +class GetChildNamedRunnable : public mozilla::Runnable +{ +public: + GetChildNamedRunnable(nsIMsgFolder *aFolder, const nsAString& aName, nsIMsgFolder **aChild); + NS_DECL_NSIRUNNABLE +protected: + nsCOMPtr<nsIMsgFolder> m_folder; + nsString m_name; + nsIMsgFolder **m_child; +}; + +GetChildNamedRunnable::GetChildNamedRunnable(nsIMsgFolder *aFolder, + const nsAString & aName, + nsIMsgFolder **aChild) : + m_folder(aFolder), m_name(aName), m_child(aChild) +{ +} + +NS_IMETHODIMP GetChildNamedRunnable::Run() +{ + return m_folder->GetChildNamed(m_name, m_child); +} + + +nsresult ProxyGetChildNamed(nsIMsgFolder *aFolder, const nsAString & aName, + nsIMsgFolder **aChild) +{ + RefPtr<GetChildNamedRunnable> getChildNamed = + new GetChildNamedRunnable(aFolder, aName, aChild); + return NS_DispatchToMainThread(getChildNamed, NS_DISPATCH_SYNC); +} + +class GetParentRunnable : public mozilla::Runnable +{ +public: + GetParentRunnable(nsIMsgFolder *aFolder, nsIMsgFolder **aParent); + NS_DECL_NSIRUNNABLE +protected: + nsCOMPtr<nsIMsgFolder> m_folder; + nsIMsgFolder **m_parent; +}; + +GetParentRunnable::GetParentRunnable(nsIMsgFolder *aFolder, nsIMsgFolder **aParent) : + m_folder(aFolder), m_parent(aParent) +{ +} + +NS_IMETHODIMP GetParentRunnable::Run() +{ + return m_folder->GetParent(m_parent); +} + + +nsresult ProxyGetParent(nsIMsgFolder *aFolder, nsIMsgFolder **aParent) +{ + RefPtr<GetParentRunnable> getParent = + new GetParentRunnable(aFolder, aParent); + return NS_DispatchToMainThread(getParent, NS_DISPATCH_SYNC); +} + +class ContainsChildNamedRunnable : public mozilla::Runnable +{ +public: + ContainsChildNamedRunnable(nsIMsgFolder *aFolder, const nsAString& aName, bool *aResult); + NS_DECL_NSIRUNNABLE +protected: + nsCOMPtr<nsIMsgFolder> m_folder; + nsString m_name; + bool *m_result; +}; + +ContainsChildNamedRunnable::ContainsChildNamedRunnable(nsIMsgFolder *aFolder, + const nsAString &aName, + bool *aResult) : + m_folder(aFolder), m_name(aName), m_result(aResult) +{ +} + +NS_IMETHODIMP ContainsChildNamedRunnable::Run() +{ + return m_folder->ContainsChildNamed(m_name, m_result); +} + + +nsresult ProxyContainsChildNamed(nsIMsgFolder *aFolder, const nsAString &aName, + bool *aResult) +{ + RefPtr<ContainsChildNamedRunnable> containsChildNamed = + new ContainsChildNamedRunnable(aFolder, aName, aResult); + return NS_DispatchToMainThread(containsChildNamed, NS_DISPATCH_SYNC); +} + + +class GenerateUniqueSubfolderNameRunnable : public mozilla::Runnable +{ +public: + GenerateUniqueSubfolderNameRunnable(nsIMsgFolder *aFolder, + const nsAString& prefix, + nsIMsgFolder *otherFolder, + nsAString& name); + NS_DECL_NSIRUNNABLE +protected: + nsCOMPtr<nsIMsgFolder> m_folder; + nsString m_prefix; + nsCOMPtr<nsIMsgFolder> m_otherFolder; + nsString m_name; +}; + +GenerateUniqueSubfolderNameRunnable::GenerateUniqueSubfolderNameRunnable( + nsIMsgFolder *aFolder, const nsAString& aPrefix, nsIMsgFolder *aOtherFolder, + nsAString& aName) + : m_folder(aFolder), m_prefix(aPrefix), m_otherFolder(aOtherFolder), m_name(aName) +{ +} + +NS_IMETHODIMP GenerateUniqueSubfolderNameRunnable::Run() +{ + return m_folder->GenerateUniqueSubfolderName(m_prefix, m_otherFolder, m_name); +} + + +nsresult ProxyGenerateUniqueSubfolderName(nsIMsgFolder *aFolder, + const nsAString& aPrefix, + nsIMsgFolder *aOtherFolder, + nsAString& aName) + +{ + RefPtr<GenerateUniqueSubfolderNameRunnable> generateUniqueSubfolderName = + new GenerateUniqueSubfolderNameRunnable(aFolder, aPrefix, aOtherFolder, aName); + return NS_DispatchToMainThread(generateUniqueSubfolderName, NS_DISPATCH_SYNC); +} + +class CreateSubfolderRunnable : public mozilla::Runnable +{ +public: + CreateSubfolderRunnable(nsIMsgFolder *aFolder, const nsAString& aName); + NS_DECL_NSIRUNNABLE +protected: + nsCOMPtr<nsIMsgFolder> m_folder; + nsString m_name; +}; + +CreateSubfolderRunnable::CreateSubfolderRunnable(nsIMsgFolder *aFolder, + const nsAString &aName) : + m_folder(aFolder), m_name(aName) +{ +} + +NS_IMETHODIMP CreateSubfolderRunnable::Run() +{ + return m_folder->CreateSubfolder(m_name, nullptr); +} + + +nsresult ProxyCreateSubfolder(nsIMsgFolder *aFolder, const nsAString &aName) +{ + RefPtr<CreateSubfolderRunnable> createSubfolder = + new CreateSubfolderRunnable(aFolder, aName); + return NS_DispatchToMainThread(createSubfolder, NS_DISPATCH_SYNC); +} + +class ForceDBClosedRunnable : public mozilla::Runnable +{ +public: + ForceDBClosedRunnable(nsIMsgFolder *aFolder); + NS_DECL_NSIRUNNABLE +protected: + nsCOMPtr<nsIMsgFolder> m_folder; +}; + +ForceDBClosedRunnable::ForceDBClosedRunnable(nsIMsgFolder *aFolder) : + m_folder(aFolder) +{ +} + +NS_IMETHODIMP ForceDBClosedRunnable::Run() +{ + return m_folder->ForceDBClosed(); +} + +nsresult ProxyForceDBClosed(nsIMsgFolder *aFolder) +{ + RefPtr<ForceDBClosedRunnable> forceDBClosed = + new ForceDBClosedRunnable(aFolder); + return NS_DispatchToMainThread(forceDBClosed, NS_DISPATCH_SYNC); +} + + diff --git a/mailnews/import/src/nsImportMailboxDescriptor.cpp b/mailnews/import/src/nsImportMailboxDescriptor.cpp new file mode 100644 index 000000000..ab0ea5db4 --- /dev/null +++ b/mailnews/import/src/nsImportMailboxDescriptor.cpp @@ -0,0 +1,39 @@ +/* -*- 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 "nscore.h" +#include "nsImportMailboxDescriptor.h" +#include "nsComponentManagerUtils.h" + +//////////////////////////////////////////////////////////////////////// + + + +NS_METHOD nsImportMailboxDescriptor::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + nsImportMailboxDescriptor *it = new nsImportMailboxDescriptor(); + if (it == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(it); + nsresult rv = it->QueryInterface(aIID, aResult); + NS_RELEASE(it); + return rv; +} + +NS_IMPL_ISUPPORTS(nsImportMailboxDescriptor, nsIImportMailboxDescriptor) + +nsImportMailboxDescriptor::nsImportMailboxDescriptor() +{ + m_import = true; + m_size = 0; + m_depth = 0; + m_id = 0; + m_pFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); +} diff --git a/mailnews/import/src/nsImportMailboxDescriptor.h b/mailnews/import/src/nsImportMailboxDescriptor.h new file mode 100644 index 000000000..1f4c30b31 --- /dev/null +++ b/mailnews/import/src/nsImportMailboxDescriptor.h @@ -0,0 +1,63 @@ +/* -*- 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 nsImportMailboxDescriptor_h___ +#define nsImportMailboxDescriptor_h___ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" + +//////////////////////////////////////////////////////////////////////// + + +class nsImportMailboxDescriptor : public nsIImportMailboxDescriptor +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD GetIdentifier(uint32_t *pIdentifier) override { *pIdentifier = m_id; return NS_OK;} + NS_IMETHOD SetIdentifier(uint32_t ident) override { m_id = ident; return NS_OK;} + + /* attribute unsigned long depth; */ + NS_IMETHOD GetDepth(uint32_t *pDepth) override { *pDepth = m_depth; return NS_OK;} + NS_IMETHOD SetDepth(uint32_t theDepth) override { m_depth = theDepth; return NS_OK;} + + /* attribute unsigned long size; */ + NS_IMETHOD GetSize(uint32_t *pSize) override { *pSize = m_size; return NS_OK;} + NS_IMETHOD SetSize(uint32_t theSize) override { m_size = theSize; return NS_OK;} + + /* attribute wstring displayName; */ + NS_IMETHOD GetDisplayName(char16_t **pName) override { *pName = ToNewUnicode(m_displayName); return NS_OK;} + NS_IMETHOD SetDisplayName(const char16_t * pName) override { m_displayName = pName; return NS_OK;} + + /* attribute boolean import; */ + NS_IMETHOD GetImport(bool *pImport) override { *pImport = m_import; return NS_OK;} + NS_IMETHOD SetImport(bool doImport) override { m_import = doImport; return NS_OK;} + + /* readonly attribute nsIFile file; */ + NS_IMETHOD GetFile(nsIFile * *aFile) override { if (m_pFile) { NS_ADDREF(*aFile = m_pFile); return NS_OK;} else return NS_ERROR_FAILURE; } + + + + nsImportMailboxDescriptor(); + + static NS_METHOD Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +private: + virtual ~nsImportMailboxDescriptor() {} + uint32_t m_id; // used by creator of the structure + uint32_t m_depth; // depth in the hierarchy + nsString m_displayName;// name of this mailbox + nsCOMPtr <nsIFile> m_pFile; // source file (if applicable) + uint32_t m_size; + bool m_import; // import it or not? +}; + + +#endif diff --git a/mailnews/import/src/nsImportMimeEncode.cpp b/mailnews/import/src/nsImportMimeEncode.cpp new file mode 100644 index 000000000..e13ea1f94 --- /dev/null +++ b/mailnews/import/src/nsImportMimeEncode.cpp @@ -0,0 +1,411 @@ +/* -*- 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 "nscore.h" +#include "nsImportMimeEncode.h" + +#include "ImportCharSet.h" +#include "ImportTranslate.h" + +#define kNoState 0 +#define kStartState 1 +#define kEncodeState 2 +#define kDoneState 3 + +#define kEncodeBufferSz (8192 * 8) + +nsImportMimeEncode::nsImportMimeEncode() +{ + m_pOut = nullptr; + m_state = kNoState; + m_bytesProcessed = 0; + m_pInputBuf = nullptr; +} + +nsImportMimeEncode::~nsImportMimeEncode() +{ + delete [] m_pInputBuf; +} + +void nsImportMimeEncode::EncodeFile(nsIFile *pInFile, ImportOutFile *pOut, const char *pFileName, const char *pMimeType) +{ + m_fileName = pFileName; + m_mimeType = pMimeType; + + m_pMimeFile = pInFile; + + m_pOut = pOut; + m_state = kStartState; +} + +void nsImportMimeEncode::CleanUp(void) +{ + CleanUpEncodeScan(); +} + +bool nsImportMimeEncode::SetUpEncode(void) +{ + nsCString errStr; + if (!m_pInputBuf) { + m_pInputBuf = new uint8_t[kEncodeBufferSz]; + } + + m_appleSingle = false; + +#ifdef _MAC_IMPORT_CODE + // First let's see just what kind of beast we have? + // For files with only a data fork and a known mime type + // proceed with normal mime encoding just as on the PC. + // For unknown mime types and files with both forks, + // encode as AppleSingle format. + if (m_filePath.GetMacFileSize(UFileLocation::eResourceFork) || !pMimeType) { + m_appleSingle = TRUE; + m_mimeType = "application/applefile"; + } +#endif + + if (!InitEncodeScan(m_appleSingle, m_pMimeFile, m_fileName.get(), m_pInputBuf, kEncodeBufferSz)) { + return false; + } + + m_state = kEncodeState; + m_lineLen = 0; + + // Write out the boundary header + bool bResult = true; + bResult = m_pOut->WriteStr("Content-type: "); + if (bResult) + bResult = m_pOut->WriteStr(m_mimeType.get()); + +#ifdef _MAC_IMPORT_CODE + // include the type an creator here + if (bResult) + bResult = m_pOut->WriteStr("; x-mac-type=\""); + U8 hex[8]; + LongToHexBytes(m_filePath.GetFileType(), hex); + if (bResult) + bResult = m_pOut->WriteData(hex, 8); + LongToHexBytes(m_filePath.GetFileCreator(), hex); + if (bResult) + bResult = m_pOut->WriteStr("\"; x-mac-creator=\""); + if (bResult) + bResult = m_pOut->WriteData(hex, 8); + if (bResult) + bResult = m_pOut->WriteStr("\""); +#endif + + /* + if (bResult) + bResult = m_pOut->WriteStr(gMimeTypeFileName); + */ + if (bResult) + bResult = m_pOut->WriteStr(";\x0D\x0A"); + + nsCString fName; + bool trans = TranslateFileName(m_fileName, fName); + if (bResult) + bResult = WriteFileName(fName, trans, "name"); + if (bResult) + bResult = m_pOut->WriteStr("Content-transfer-encoding: base64"); + if (bResult) + bResult = m_pOut->WriteEol(); + if (bResult) + bResult = m_pOut->WriteStr("Content-Disposition: attachment;\x0D\x0A"); + if (bResult) + bResult = WriteFileName(fName, trans, "filename"); + if (bResult) + bResult = m_pOut->WriteEol(); + + if (!bResult) { + CleanUp(); + } + + return bResult; +} + +bool nsImportMimeEncode::DoWork(bool *pDone) +{ + *pDone = false; + switch(m_state) { + case kNoState: + return false; + break; + case kStartState: + return SetUpEncode(); + break; + case kEncodeState: + if (!Scan(pDone)) { + CleanUp(); + return false; + } + if (*pDone) { + *pDone = false; + m_state = kDoneState; + } + break; + case kDoneState: + CleanUp(); + m_state = kNoState; + *pDone = true; + break; + } + + return true; +} + +static uint8_t gBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +bool nsImportMimeEncode::ScanBuffer(bool *pDone) +{ + + uint32_t pos = m_pos; + uint32_t start = pos; + uint8_t * pChar = m_pBuf + pos; + uint32_t max = m_bytesInBuf; + uint8_t byte[4]; + uint32_t lineLen = m_lineLen; + + while ((pos + 2) < max) { + // Encode 3 bytes + byte[0] = gBase64[*pChar >> 2]; + byte[1] = gBase64[(((*pChar) & 0x3)<< 4) | (((*(pChar + 1)) & 0xF0) >> 4)]; + pChar++; + byte[2] = gBase64[(((*pChar) & 0xF) << 2) | (((*(pChar + 1)) & 0xC0) >>6)]; + pChar++; + byte[3] = gBase64[(*pChar) & 0x3F]; + if (!m_pOut->WriteData(byte, 4)) + return false; + pos += 3; + pChar++; + lineLen += 4; + if (lineLen > 71) { + if (!m_pOut->WriteEol()) + return false; + lineLen = 0; + } + } + + if ((pos < max) && m_eof) { + // Get the last few bytes! + byte[0] = gBase64[*pChar >> 2]; + pos++; + if (pos < max) { + byte[1] = gBase64[(((*pChar) & 0x3)<< 4) | (((*(pChar + 1)) & 0xF0) >> 4)]; + pChar++; + pos++; + if (pos < max) { + // Should be dead code!! (Then why is it here doofus?) + byte[2] = gBase64[(((*pChar) & 0xF) << 2) | (((*(pChar + 1)) & 0xC0) >>6)]; + pChar++; + byte[3] = gBase64[(*pChar) & 0x3F]; + pos++; + } + else { + byte[2] = gBase64[(((*pChar) & 0xF) << 2)]; + byte[3] = '='; + } + } + else { + byte[1] = gBase64[(((*pChar) & 0x3)<< 4)]; + byte[2] = '='; + byte[3] = '='; + } + + if (!m_pOut->WriteData(byte, 4)) + return false; + if (!m_pOut->WriteEol()) + return false; + } + else if (m_eof) { + /* + byte[0] = '='; + if (!m_pOut->WriteData(byte, 1)) + return FALSE; + */ + if (!m_pOut->WriteEol()) + return false; + } + + m_lineLen = (int) lineLen; + m_pos = pos; + m_bytesProcessed += (pos - start); + return true; +} + +bool nsImportMimeEncode::TranslateFileName(nsCString& inFile, nsCString& outFile) +{ + const uint8_t * pIn = (const uint8_t *) inFile.get(); + int len = inFile.Length(); + + while (len) { + if (!ImportCharSet::IsUSAscii(*pIn)) + break; + len--; + pIn++; + } + if (len) { + // non US ascii! + // assume this string needs translating... + if (!ImportTranslate::ConvertString(inFile, outFile, true)) { + outFile = inFile; + return false; + } + else { + return true; + } + } + else { + outFile = inFile; + return false; + } +} + +bool nsImportMimeEncode::WriteFileName(nsCString& fName, bool wasTrans, const char *pTag) +{ + int tagNum = 0; + int idx = 0; + bool result = true; + int len; + nsCString numStr; + + while ((((fName.Length() - idx) + strlen(pTag)) > 70) && result) { + len = 68 - strlen(pTag) - 5; + if (wasTrans) { + if (fName.CharAt(idx + len - 1) == '%') + len--; + else if (fName.CharAt(idx + len - 2) == '%') + len -= 2; + } + + if (result) + result = m_pOut->WriteStr("\x09"); + if (result) + result = m_pOut->WriteStr(pTag); + numStr = "*"; + numStr.AppendInt(tagNum); + if (result) + result = m_pOut->WriteStr(numStr.get()); + if (wasTrans && result) + result = m_pOut->WriteStr("*="); + else if (result) + result = m_pOut->WriteStr("=\""); + if (result) + result = m_pOut->WriteData(((const uint8_t *)fName.get()) + idx, len); + if (wasTrans && result) + result = m_pOut->WriteStr("\x0D\x0A"); + else if (result) + result = m_pOut->WriteStr("\"\x0D\x0A"); + idx += len; + tagNum++; + } + + if (idx) { + if ((fName.Length() - idx) > 0) { + if (result) + result = m_pOut->WriteStr("\x09"); + if (result) + result = m_pOut->WriteStr(pTag); + numStr = "*"; + numStr.AppendInt(tagNum); + if (result) + result = m_pOut->WriteStr(numStr.get()); + if (wasTrans && result) + result = m_pOut->WriteStr("*="); + else if (result) + result = m_pOut->WriteStr("=\""); + if (result) + result = m_pOut->WriteData(((const uint8_t *)fName.get()) + idx, fName.Length() - idx); + if (wasTrans && result) + result = m_pOut->WriteStr("\x0D\x0A"); + else if (result) + result = m_pOut->WriteStr("\"\x0D\x0A"); + } + } + else { + if (result) + result = m_pOut->WriteStr("\x09"); + if (result) + result = m_pOut->WriteStr(pTag); + if (wasTrans && result) + result = m_pOut->WriteStr("*="); + else if (result) + result = m_pOut->WriteStr("=\""); + if (result) + result = m_pOut->WriteStr(fName.get()); + if (wasTrans && result) + result = m_pOut->WriteStr("\x0D\x0A"); + else if (result) + result = m_pOut->WriteStr("\"\x0D\x0A"); + } + + return result; + +} + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +nsIImportMimeEncodeImpl::nsIImportMimeEncodeImpl() +{ + m_pOut = nullptr; + m_pEncode = nullptr; +} + +nsIImportMimeEncodeImpl::~nsIImportMimeEncodeImpl() +{ + if (m_pOut) + delete m_pOut; + if (m_pEncode) + delete m_pEncode; +} + +NS_IMPL_ISUPPORTS(nsIImportMimeEncodeImpl, nsIImportMimeEncode) + +NS_METHOD nsIImportMimeEncodeImpl::EncodeFile(nsIFile *inFile, nsIFile *outFile, const char *fileName, const char *mimeType) +{ + return Initialize(inFile, outFile, fileName, mimeType); +} + +NS_METHOD nsIImportMimeEncodeImpl::DoWork(bool *done, bool *_retval) +{ + if (done && _retval && m_pEncode) { + *_retval = m_pEncode->DoWork(done); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_METHOD nsIImportMimeEncodeImpl::NumBytesProcessed(int32_t *_retval) +{ + if (m_pEncode && _retval) + *_retval = m_pEncode->NumBytesProcessed(); + return NS_OK; +} + +NS_METHOD nsIImportMimeEncodeImpl::DoEncoding(bool *_retval) +{ + if (_retval && m_pEncode) { + bool done = false; + while (m_pEncode->DoWork(&done) && !done); + *_retval = done; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_METHOD nsIImportMimeEncodeImpl::Initialize(nsIFile *inFile, nsIFile *outFile, const char *fileName, const char *mimeType) +{ + delete m_pEncode; + delete m_pOut; + + m_pOut = new ImportOutFile(); + m_pOut->InitOutFile(outFile); + + m_pEncode = new nsImportMimeEncode(); + m_pEncode->EncodeFile(inFile, m_pOut, fileName, mimeType); + + return NS_OK; +} + diff --git a/mailnews/import/src/nsImportMimeEncode.h b/mailnews/import/src/nsImportMimeEncode.h new file mode 100644 index 000000000..1447d11c4 --- /dev/null +++ b/mailnews/import/src/nsImportMimeEncode.h @@ -0,0 +1,73 @@ +/* -*- 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 nsImportMimeEncode_h__ +#define nsImportMimeEncode_h__ + +#include "mozilla/Attributes.h" +#include "nsImportScanFile.h" +#include "ImportOutFile.h" +#include "nsImportEncodeScan.h" +#include "nsStringGlue.h" +#include "nsIImportMimeEncode.h" + + +// Content-Type: image/gif; name="blah.xyz" +// Content-Transfer-Encoding: base64 +// Content-Disposition: attachment; filename="blah.xyz" + +class nsImportMimeEncode : public nsImportEncodeScan { +public: + nsImportMimeEncode(); + ~nsImportMimeEncode(); + + void EncodeFile(nsIFile *pInFile, ImportOutFile *pOut, const char *pFileName, const char *pMimeType); + + bool DoWork(bool *pDone); + + long NumBytesProcessed(void) { long val = m_bytesProcessed; m_bytesProcessed = 0; return val;} + +protected: + void CleanUp(void); + bool SetUpEncode(void); + bool WriteFileName(nsCString& fName, bool wasTrans, const char *pTag); + bool TranslateFileName(nsCString& inFile, nsCString& outFile); + + + virtual bool ScanBuffer(bool *pDone) override; + + +protected: + nsCString m_fileName; + nsCOMPtr <nsIFile> m_pMimeFile; + ImportOutFile * m_pOut; + nsCString m_mimeType; + + int m_state; + long m_bytesProcessed; + uint8_t * m_pInputBuf; + bool m_appleSingle; + + // Actual encoding variables + int m_lineLen; +}; + + +class nsIImportMimeEncodeImpl : public nsIImportMimeEncode { +public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIIMPORTMIMEENCODE + + nsIImportMimeEncodeImpl(); + +private: + virtual ~nsIImportMimeEncodeImpl(); + ImportOutFile * m_pOut; + nsImportMimeEncode * m_pEncode; +}; + + +#endif /* nsImportMimeEncode_h__ */ + diff --git a/mailnews/import/src/nsImportScanFile.cpp b/mailnews/import/src/nsImportScanFile.cpp new file mode 100644 index 000000000..c4eefef3b --- /dev/null +++ b/mailnews/import/src/nsImportScanFile.cpp @@ -0,0 +1,172 @@ +/* -*- 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 "nscore.h" +#include "nsIFile.h" +#include "nsImportScanFile.h" +#include "ImportCharSet.h" + +nsImportScanFile::nsImportScanFile() +{ + m_allocated = false; + m_eof = false; + m_pBuf = nullptr; +} + +nsImportScanFile::~nsImportScanFile() +{ + if (m_allocated) + CleanUpScan(); +} + +void nsImportScanFile::InitScan(nsIInputStream *pInputStream, uint8_t * pBuf, uint32_t sz) +{ + m_pInputStream = pInputStream; + m_pBuf = pBuf; + m_bufSz = sz; + m_bytesInBuf = 0; + m_pos = 0; +} + +void nsImportScanFile::CleanUpScan(void) +{ + m_pInputStream = nullptr; + if (m_allocated) { + delete [] m_pBuf; + m_pBuf = NULL; + } +} + +void nsImportScanFile::ShiftBuffer(void) +{ + uint8_t * pTop; + uint8_t * pCurrent; + + if (m_pos < m_bytesInBuf) { + pTop = m_pBuf; + pCurrent = pTop + m_pos; + uint32_t cnt = m_bytesInBuf - m_pos; + while (cnt) { + *pTop = *pCurrent; + pTop++; pCurrent++; + cnt--; + } + } + + m_bytesInBuf -= m_pos; + m_pos = 0; +} + +bool nsImportScanFile::FillBufferFromFile(void) +{ + uint64_t available; + nsresult rv = m_pInputStream->Available(&available); + if (NS_FAILED(rv)) + return false; + + // Fill up a buffer and scan it + ShiftBuffer(); + + // Read in some more bytes + uint32_t cnt = m_bufSz - m_bytesInBuf; + // To distinguish from disk errors + // Check first for end of file? + // Set a done flag if true... + uint32_t read; + char *pBuf = (char *)m_pBuf; + pBuf += m_bytesInBuf; + rv = m_pInputStream->Read(pBuf, (int32_t) cnt, &read); + + if (NS_FAILED(rv)) + return false; + rv = m_pInputStream->Available(&available); + if (NS_FAILED(rv)) + m_eof = true; + + m_bytesInBuf += cnt; + return true; +} + +bool nsImportScanFile::Scan(bool *pDone) +{ + uint64_t available; + nsresult rv = m_pInputStream->Available(&available); + if (NS_FAILED(rv)) + { + if (m_pos < m_bytesInBuf) + ScanBuffer(pDone); + *pDone = true; + return true; + } + + // Fill up a buffer and scan it + if (!FillBufferFromFile()) + return false; + + return ScanBuffer(pDone); +} + +bool nsImportScanFile::ScanBuffer(bool *) +{ + return true; +} + + +bool nsImportScanFileLines::ScanBuffer(bool *pDone) +{ + // m_pos, m_bytesInBuf, m_eof, m_pBuf are relevant + + uint32_t pos = m_pos; + uint32_t max = m_bytesInBuf; + uint8_t * pChar = m_pBuf + pos; + uint32_t startPos; + + while (pos < max) { + if (m_needEol) { + // Find the next eol... + while ((pos < max) && (*pChar != ImportCharSet::cCRChar) && (*pChar != ImportCharSet::cLinefeedChar)) { + pos++; + pChar++; + } + m_pos = pos; + if (pos < max) + m_needEol = false; + if (pos == max) // need more buffer for an end of line + break; + } + // Skip past any eol characters + while ((pos < max) && ((*pChar == ImportCharSet::cCRChar) || (*pChar == ImportCharSet::cLinefeedChar))) { + pos++; + pChar++; + } + m_pos = pos; + if (pos == max) + break; + // Make sure we can find either the eof or the + // next end of line + startPos = pos; + while ((pos < max) && (*pChar != ImportCharSet::cCRChar) && (*pChar != ImportCharSet::cLinefeedChar)) { + pos++; + pChar++; + } + + // Is line too big for our buffer? + if ((pos == max) && !m_eof) { + if (!m_pos) { // line too big for our buffer + m_pos = pos; + m_needEol = true; + } + break; + } + + if (!ProcessLine(m_pBuf + startPos, pos - startPos, pDone)) { + return false; + } + m_pos = pos; + } + + return true; +} + diff --git a/mailnews/import/src/nsImportScanFile.h b/mailnews/import/src/nsImportScanFile.h new file mode 100644 index 000000000..abe5b1cdd --- /dev/null +++ b/mailnews/import/src/nsImportScanFile.h @@ -0,0 +1,54 @@ +/* -*- 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 nsImportScanFile_h__ +#define nsImportScanFile_h__ +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" + +class nsImportScanFile { +public: + nsImportScanFile(); + virtual ~nsImportScanFile(); + + void InitScan(nsIInputStream *pInputStream, uint8_t * pBuf, uint32_t sz); + + void CleanUpScan(void); + + virtual bool Scan(bool *pDone); + +protected: + void ShiftBuffer(void); + bool FillBufferFromFile(void); + virtual bool ScanBuffer(bool *pDone); + +protected: + nsCOMPtr <nsIInputStream> m_pInputStream; + uint8_t * m_pBuf; + uint32_t m_bufSz; + uint32_t m_bytesInBuf; + uint32_t m_pos; + bool m_eof; + bool m_allocated; +}; + +class nsImportScanFileLines : public nsImportScanFile { +public: + nsImportScanFileLines() {m_needEol = false;} + + void ResetLineScan(void) { m_needEol = false;} + + virtual bool ProcessLine(uint8_t * /* pLine */, uint32_t /* len */, bool * /* pDone */) {return true;} + +protected: + virtual bool ScanBuffer(bool *pDone) override; + + bool m_needEol; + +}; + + +#endif /* nsImportScanFile_h__ */ diff --git a/mailnews/import/src/nsImportService.cpp b/mailnews/import/src/nsImportService.cpp new file mode 100644 index 000000000..0013c1146 --- /dev/null +++ b/mailnews/import/src/nsImportService.cpp @@ -0,0 +1,583 @@ +/* -*- 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 "nsICharsetConverterManager.h" +#include "nsIPlatformCharset.h" +#include "nsICharsetConverterManager.h" + +#include "nsStringGlue.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsMemory.h" +#include "nsIImportModule.h" +#include "nsIImportService.h" +#include "nsImportMailboxDescriptor.h" +#include "nsImportABDescriptor.h" +#include "nsIImportGeneric.h" +#include "nsImportFieldMap.h" +#include "nsICategoryManager.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "plstr.h" +#include "prmem.h" +#include "nsMsgCompCID.h" +#include "nsThreadUtils.h" +#include "nsIEditor.h" +#include "ImportDebug.h" +#include "nsImportService.h" +#include "nsImportStringBundle.h" +#include "nsCRTGlue.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIMutableArray.h" +#include "nsIArray.h" +#include "nsIMsgSend.h" +#include "nsMsgUtils.h" + +PRLogModuleInfo *IMPORTLOGMODULE = nullptr; + +static nsIImportService * gImportService = nullptr; +static const char * kWhitespace = "\b\t\r\n "; + + +//////////////////////////////////////////////////////////////////////// + + +nsImportService::nsImportService() : m_pModules(nullptr) +{ + // Init logging module. + if (!IMPORTLOGMODULE) + IMPORTLOGMODULE = PR_NewLogModule("IMPORT"); + IMPORT_LOG0("* nsImport Service Created\n"); + + m_didDiscovery = false; + m_pDecoder = nullptr; + m_pEncoder = nullptr; + + nsresult rv = nsImportStringBundle::GetStringBundle(IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle)); + if (NS_FAILED(rv)) + IMPORT_LOG0("Failed to get string bundle for Importing Mail"); +} + + +nsImportService::~nsImportService() +{ + NS_IF_RELEASE(m_pDecoder); + NS_IF_RELEASE(m_pEncoder); + + gImportService = nullptr; + + if (m_pModules != nullptr) + delete m_pModules; + + IMPORT_LOG0("* nsImport Service Deleted\n"); +} + + + +NS_IMPL_ISUPPORTS(nsImportService, nsIImportService) + + +NS_IMETHODIMP nsImportService::DiscoverModules(void) +{ + m_didDiscovery = false; + return DoDiscover(); +} + +NS_IMETHODIMP nsImportService::CreateNewFieldMap(nsIImportFieldMap **_retval) +{ + return nsImportFieldMap::Create(m_stringBundle, nullptr, NS_GET_IID(nsIImportFieldMap), (void**)_retval); +} + +NS_IMETHODIMP nsImportService::CreateNewMailboxDescriptor(nsIImportMailboxDescriptor **_retval) +{ + return nsImportMailboxDescriptor::Create(nullptr, NS_GET_IID(nsIImportMailboxDescriptor), (void**)_retval); +} + +NS_IMETHODIMP nsImportService::CreateNewABDescriptor(nsIImportABDescriptor **_retval) +{ + return nsImportABDescriptor::Create(nullptr, NS_GET_IID(nsIImportABDescriptor), (void**)_retval); +} + +extern nsresult NS_NewGenericMail(nsIImportGeneric** aImportGeneric); + +NS_IMETHODIMP nsImportService::CreateNewGenericMail(nsIImportGeneric **_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! _retval) + return NS_ERROR_NULL_POINTER; + + return NS_NewGenericMail(_retval); +} + +extern nsresult NS_NewGenericAddressBooks(nsIImportGeneric** aImportGeneric); + +NS_IMETHODIMP nsImportService::CreateNewGenericAddressBooks(nsIImportGeneric **_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! _retval) + return NS_ERROR_NULL_POINTER; + + return NS_NewGenericAddressBooks(_retval); +} + + +NS_IMETHODIMP nsImportService::GetModuleCount(const char *filter, int32_t *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! _retval) + return NS_ERROR_NULL_POINTER; + + DoDiscover(); + + if (m_pModules != nullptr) { + ImportModuleDesc * pDesc; + int32_t count = 0; + for (int32_t i = 0; i < m_pModules->GetCount(); i++) { + pDesc = m_pModules->GetModuleDesc(i); + if (pDesc->SupportsThings(filter)) + count++; + } + *_retval = count; + } + else + *_retval = 0; + + return NS_OK; +} + +NS_IMETHODIMP nsImportService::GetModuleWithCID(const nsCID& cid, nsIImportModule **ppModule) +{ + NS_PRECONDITION(ppModule != nullptr, "null ptr"); + if (!ppModule) + return NS_ERROR_NULL_POINTER; + + *ppModule = nullptr; + nsresult rv = DoDiscover(); + if (NS_FAILED(rv)) + return rv; + if (m_pModules == nullptr) + return NS_ERROR_FAILURE; + int32_t cnt = m_pModules->GetCount(); + ImportModuleDesc *pDesc; + for (int32_t i = 0; i < cnt; i++) { + pDesc = m_pModules->GetModuleDesc(i); + if (!pDesc) + return NS_ERROR_FAILURE; + if (pDesc->GetCID().Equals(cid)) { + *ppModule = pDesc->GetModule(); + + IMPORT_LOG0("* nsImportService::GetSpecificModule - attempted to load module\n"); + + if (*ppModule == nullptr) + return NS_ERROR_FAILURE; + return NS_OK; + } + } + + IMPORT_LOG0("* nsImportService::GetSpecificModule - module not found\n"); + + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP nsImportService::GetModuleInfo(const char *filter, int32_t index, char16_t **name, char16_t **moduleDescription) +{ + NS_PRECONDITION(name != nullptr, "null ptr"); + NS_PRECONDITION(moduleDescription != nullptr, "null ptr"); + if (!name || !moduleDescription) + return NS_ERROR_NULL_POINTER; + + *name = nullptr; + *moduleDescription = nullptr; + + DoDiscover(); + if (!m_pModules) + return NS_ERROR_FAILURE; + + if ((index < 0) || (index >= m_pModules->GetCount())) + return NS_ERROR_FAILURE; + + ImportModuleDesc * pDesc; + int32_t count = 0; + for (int32_t i = 0; i < m_pModules->GetCount(); i++) { + pDesc = m_pModules->GetModuleDesc(i); + if (pDesc->SupportsThings(filter)) { + if (count == index) { + *name = NS_strdup(pDesc->GetName()); + *moduleDescription = NS_strdup(pDesc->GetDescription()); + return NS_OK; + } + else + count++; + } + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsImportService::GetModuleName(const char *filter, int32_t index, char16_t **_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = nullptr; + + DoDiscover(); + if (!m_pModules) + return NS_ERROR_FAILURE; + + if ((index < 0) || (index >= m_pModules->GetCount())) + return NS_ERROR_FAILURE; + + ImportModuleDesc * pDesc; + int32_t count = 0; + for (int32_t i = 0; i < m_pModules->GetCount(); i++) { + pDesc = m_pModules->GetModuleDesc(i); + if (pDesc->SupportsThings(filter)) { + if (count == index) { + *_retval = NS_strdup(pDesc->GetName()); + return NS_OK; + } + else + count++; + } + } + + return NS_ERROR_FAILURE; +} + + +NS_IMETHODIMP nsImportService::GetModuleDescription(const char *filter, int32_t index, char16_t **_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + *_retval = nullptr; + + DoDiscover(); + if (!m_pModules) + return NS_ERROR_FAILURE; + + if ((index < 0) || (index >= m_pModules->GetCount())) + return NS_ERROR_FAILURE; + + ImportModuleDesc * pDesc; + int32_t count = 0; + for (int32_t i = 0; i < m_pModules->GetCount(); i++) { + pDesc = m_pModules->GetModuleDesc(i); + if (pDesc->SupportsThings(filter)) { + if (count == index) { + *_retval = NS_strdup(pDesc->GetDescription()); + return NS_OK; + } + else + count++; + } + } + + return NS_ERROR_FAILURE; +} + +class nsProxySendRunnable : public mozilla::Runnable +{ +public: + nsProxySendRunnable(nsIMsgIdentity *aIdentity, + nsIMsgCompFields *aMsgFields, + const char *attachment1_type, + const nsACString &attachment1_body, + bool aIsDraft, + nsIArray *aLoadedAttachments, + nsIArray *aEmbeddedAttachments, + nsIMsgSendListener *aListener); + NS_DECL_NSIRUNNABLE +private: + nsCOMPtr<nsIMsgIdentity> m_identity; + nsCOMPtr<nsIMsgCompFields> m_compFields; + bool m_isDraft; + nsCString m_bodyType; + nsCString m_body; + nsCOMPtr<nsIArray> m_loadedAttachments; + nsCOMPtr<nsIArray> m_embeddedAttachments; + nsCOMPtr<nsIMsgSendListener> m_listener; + +}; + +nsProxySendRunnable::nsProxySendRunnable(nsIMsgIdentity *aIdentity, + nsIMsgCompFields *aMsgFields, + const char *aBodyType, + const nsACString &aBody, + bool aIsDraft, + nsIArray *aLoadedAttachments, + nsIArray *aEmbeddedAttachments, + nsIMsgSendListener *aListener) : + m_identity(aIdentity), m_compFields(aMsgFields), + m_isDraft(aIsDraft), m_bodyType(aBodyType), + m_body(aBody), m_loadedAttachments(aLoadedAttachments), + m_embeddedAttachments(aEmbeddedAttachments), + m_listener(aListener) +{ +} + +NS_IMETHODIMP nsProxySendRunnable::Run() +{ + nsresult rv; + nsCOMPtr<nsIMsgSend> msgSend = do_CreateInstance(NS_MSGSEND_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return msgSend->CreateRFC822Message(m_identity, m_compFields, + m_bodyType.get(), m_body, + m_isDraft, m_loadedAttachments, + m_embeddedAttachments, + m_listener); +} + + +NS_IMETHODIMP +nsImportService::CreateRFC822Message(nsIMsgIdentity *aIdentity, + nsIMsgCompFields *aMsgFields, + const char *aBodyType, + const nsACString &aBody, + bool aIsDraft, + nsIArray *aLoadedAttachments, + nsIArray *aEmbeddedAttachments, + nsIMsgSendListener *aListener) +{ + RefPtr<nsProxySendRunnable> runnable = + new nsProxySendRunnable(aIdentity, + aMsgFields, + aBodyType, + aBody, + aIsDraft, + aLoadedAttachments, + aEmbeddedAttachments, + aListener); + // invoke the callback + return NS_DispatchToMainThread(runnable); +} + +NS_IMETHODIMP nsImportService::GetModule(const char *filter, int32_t index, nsIImportModule **_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + *_retval = nullptr; + + DoDiscover(); + if (!m_pModules) + return NS_ERROR_FAILURE; + + if ((index < 0) || (index >= m_pModules->GetCount())) + return NS_ERROR_FAILURE; + + ImportModuleDesc * pDesc; + int32_t count = 0; + for (int32_t i = 0; i < m_pModules->GetCount(); i++) { + pDesc = m_pModules->GetModuleDesc(i); + if (pDesc->SupportsThings(filter)) { + if (count == index) { + *_retval = pDesc->GetModule(); + break; + } + else + count++; + } + } + if (! (*_retval)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + + +nsresult nsImportService::DoDiscover(void) +{ + if (m_didDiscovery) + return NS_OK; + + if (m_pModules != nullptr) + m_pModules->ClearList(); + + nsresult rv; + + nsCOMPtr<nsICategoryManager> catMan = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> e; + rv = catMan->EnumerateCategory("mailnewsimport", getter_AddRefs(e)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsISupportsCString> contractid; + rv = e->GetNext(getter_AddRefs(supports)); + while (NS_SUCCEEDED(rv) && supports) + { + contractid = do_QueryInterface(supports); + if (!contractid) + break; + + nsCString contractIdStr; + contractid->ToString(getter_Copies(contractIdStr)); + nsCString supportsStr; + rv = catMan->GetCategoryEntry("mailnewsimport", contractIdStr.get(), getter_Copies(supportsStr)); + if (NS_SUCCEEDED(rv)) + LoadModuleInfo(contractIdStr.get(), supportsStr.get()); + rv = e->GetNext(getter_AddRefs(supports)); + } + + m_didDiscovery = true; + + return NS_OK; +} + +nsresult nsImportService::LoadModuleInfo(const char *pClsId, const char *pSupports) +{ + if (!pClsId || !pSupports) + return NS_OK; + + if (m_pModules == nullptr) + m_pModules = new nsImportModuleList(); + + // load the component and get all of the info we need from it.... + // then call AddModule + nsresult rv; + + nsCID clsId; + clsId.Parse(pClsId); + nsIImportModule * module; + rv = CallCreateInstance(clsId, &module); + if (NS_FAILED(rv)) return rv; + + nsString theTitle; + nsString theDescription; + rv = module->GetName(getter_Copies(theTitle)); + if (NS_FAILED(rv)) + theTitle.AssignLiteral("Unknown"); + + rv = module->GetDescription(getter_Copies(theDescription)); + if (NS_FAILED(rv)) + theDescription.AssignLiteral("Unknown description"); + + // call the module to get the info we need + m_pModules->AddModule(clsId, pSupports, theTitle.get(), theDescription.get()); + + module->Release(); + + return NS_OK; +} + + +nsIImportModule *ImportModuleDesc::GetModule(bool keepLoaded) +{ + if (m_pModule) + { + m_pModule->AddRef(); + return m_pModule; + } + + nsresult rv; + rv = CallCreateInstance(m_cid, &m_pModule); + if (NS_FAILED(rv)) + { + m_pModule = nullptr; + return nullptr; + } + + if (keepLoaded) + { + m_pModule->AddRef(); + return m_pModule; + } + else + { + nsIImportModule *pModule = m_pModule; + m_pModule = nullptr; + return pModule; + } +} + +void ImportModuleDesc::ReleaseModule(void) +{ + if (m_pModule) + { + m_pModule->Release(); + m_pModule = nullptr; + } +} + +bool ImportModuleDesc::SupportsThings(const char *pThings) +{ + if (!pThings || !*pThings) + return true; + + nsCString thing(pThings); + nsCString item; + int32_t idx; + + while ((idx = thing.FindChar(',')) != -1) + { + item = StringHead(thing, idx); + item.Trim(kWhitespace); + ToLowerCase(item); + if (item.Length() && (m_supports.Find(item) == -1)) + return false; + thing = Substring(thing, idx + 1); + } + thing.Trim(kWhitespace); + ToLowerCase(thing); + return thing.IsEmpty() || (m_supports.Find(thing) != -1); +} + +void nsImportModuleList::ClearList(void) +{ + if (m_pList) + { + for (int i = 0; i < m_count; i++) + { + delete m_pList[i]; + m_pList[i] = nullptr; + } + m_count = 0; + delete [] m_pList; + m_pList = nullptr; + m_alloc = 0; + } + +} + +void nsImportModuleList::AddModule(const nsCID& cid, const char *pSupports, const char16_t *pName, const char16_t *pDesc) +{ + if (!m_pList) + { + m_alloc = 10; + m_pList = new ImportModuleDesc *[m_alloc]; + m_count = 0; + memset(m_pList, 0, sizeof(ImportModuleDesc *) * m_alloc); + } + + if (m_count == m_alloc) + { + ImportModuleDesc **pList = new ImportModuleDesc *[m_alloc + 10]; + memset(&(pList[m_alloc]), 0, sizeof(ImportModuleDesc *) * 10); + memcpy(pList, m_pList, sizeof(ImportModuleDesc *) * m_alloc); + for(int i = 0; i < m_count; i++) + delete m_pList[i]; + delete [] m_pList; + m_pList = pList; + m_alloc += 10; + } + + m_pList[m_count] = new ImportModuleDesc(); + m_pList[m_count]->SetCID(cid); + m_pList[m_count]->SetSupports(pSupports); + m_pList[m_count]->SetName(pName); + m_pList[m_count]->SetDescription(pDesc); + + m_count++; +#ifdef IMPORT_DEBUG + IMPORT_LOG3("* nsImportService registered import module: %s, %s, %s\n", NS_LossyConvertUTF16toASCII(pName).get(), NS_LossyConvertUTF16toASCII(pDesc).get(), pSupports); +#endif +} + diff --git a/mailnews/import/src/nsImportService.h b/mailnews/import/src/nsImportService.h new file mode 100644 index 000000000..889cfa6c4 --- /dev/null +++ b/mailnews/import/src/nsImportService.h @@ -0,0 +1,96 @@ +/* -*- 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 nsImportService_h__ +#define nsImportService_h__ + +#include "nsICharsetConverterManager.h" + +#include "nsStringGlue.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsMemory.h" +#include "nsIImportModule.h" +#include "nsIImportService.h" +#include "nsICategoryManager.h" +#include "nsIStringBundle.h" + +class nsImportModuleList; + +class nsImportService : public nsIImportService +{ +public: + + nsImportService(); + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIIMPORTSERVICE + +private: + virtual ~nsImportService(); + nsresult LoadModuleInfo(const char*pClsId, const char *pSupports); + nsresult DoDiscover(void); + +private: + nsImportModuleList * m_pModules; + bool m_didDiscovery; + nsCString m_sysCharset; + nsIUnicodeDecoder * m_pDecoder; + nsIUnicodeEncoder * m_pEncoder; + nsCOMPtr<nsIStringBundle> m_stringBundle; +}; + +class ImportModuleDesc { +public: + ImportModuleDesc() { m_pModule = nullptr;} + ~ImportModuleDesc() { ReleaseModule(); } + + void SetCID(const nsCID& cid) { m_cid = cid;} + void SetName(const char16_t *pName) { m_name = pName;} + void SetDescription(const char16_t *pDesc) { m_description = pDesc;} + void SetSupports(const char *pSupports) { m_supports = pSupports;} + + nsCID GetCID(void) { return m_cid;} + const char16_t *GetName(void) { return m_name.get();} + const char16_t *GetDescription(void) { return m_description.get();} + const char * GetSupports(void) { return m_supports.get();} + + nsIImportModule * GetModule(bool keepLoaded = false); // Adds ref + void ReleaseModule(void); + + bool SupportsThings(const char *pThings); + +private: + nsCID m_cid; + nsString m_name; + nsString m_description; + nsCString m_supports; + nsIImportModule *m_pModule; +}; + +class nsImportModuleList { +public: + nsImportModuleList() { m_pList = nullptr; m_alloc = 0; m_count = 0;} + ~nsImportModuleList() { ClearList(); } + + void AddModule(const nsCID& cid, const char *pSupports, const char16_t *pName, const char16_t *pDesc); + + void ClearList(void); + + int32_t GetCount(void) { return m_count;} + + ImportModuleDesc * GetModuleDesc(int32_t idx) + { if ((idx < 0) || (idx >= m_count)) return nullptr; else return m_pList[idx];} + +private: + +private: + ImportModuleDesc ** m_pList; + int32_t m_alloc; + int32_t m_count; +}; + +#endif // nsImportService_h__ diff --git a/mailnews/import/src/nsImportStringBundle.cpp b/mailnews/import/src/nsImportStringBundle.cpp new file mode 100644 index 000000000..3adb6655a --- /dev/null +++ b/mailnews/import/src/nsImportStringBundle.cpp @@ -0,0 +1,80 @@ +/* -*- 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 "prprf.h" +#include "prmem.h" +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" +#include "nsImportStringBundle.h" +#include "nsIServiceManager.h" +#include "nsIURI.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/Services.h" + +nsresult nsImportStringBundle::GetStringBundle(const char *aPropertyURL, + nsIStringBundle **aBundle) +{ + nsresult rv; + + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED); + rv = sBundleService->CreateBundle(aPropertyURL, aBundle); + + return rv; +} + +void nsImportStringBundle::GetStringByID(int32_t aStringID, + nsIStringBundle *aBundle, + nsString &aResult) +{ + aResult.Adopt(GetStringByID(aStringID, aBundle)); +} + +char16_t *nsImportStringBundle::GetStringByID(int32_t aStringID, + nsIStringBundle *aBundle) +{ + if (aBundle) + { + char16_t *ptrv = nullptr; + nsresult rv = aBundle->GetStringFromID(aStringID, &ptrv); + + if (NS_SUCCEEDED(rv) && ptrv) + return ptrv; + } + + nsString resultString(NS_LITERAL_STRING("[StringID ")); + resultString.AppendInt(aStringID); + resultString.AppendLiteral("?]"); + + return ToNewUnicode(resultString); +} + +void nsImportStringBundle::GetStringByName(const char *aName, + nsIStringBundle *aBundle, + nsString &aResult) +{ + aResult.Adopt(GetStringByName(aName, aBundle)); +} + +char16_t *nsImportStringBundle::GetStringByName(const char *aName, + nsIStringBundle *aBundle) +{ + if (aBundle) + { + char16_t *ptrv = nullptr; + nsresult rv = aBundle->GetStringFromName( + NS_ConvertUTF8toUTF16(aName).get(), &ptrv); + + if (NS_SUCCEEDED(rv) && ptrv) + return ptrv; + } + + nsString resultString(NS_LITERAL_STRING("[StringName ")); + resultString.Append(NS_ConvertUTF8toUTF16(aName).get()); + resultString.AppendLiteral("?]"); + + return ToNewUnicode(resultString); +} diff --git a/mailnews/import/src/nsImportStringBundle.h b/mailnews/import/src/nsImportStringBundle.h new file mode 100644 index 000000000..c9db012e6 --- /dev/null +++ b/mailnews/import/src/nsImportStringBundle.h @@ -0,0 +1,48 @@ +/* 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 _nsImportStringBundle_H__ +#define _nsImportStringBundle_H__ + +#include "nsStringGlue.h" + +class nsIStringBundle; + +class nsImportStringBundle +{ +public: + static char16_t* GetStringByID(int32_t aStringID, + nsIStringBundle *aBundle = nullptr); + static void GetStringByID(int32_t aStringID, + nsIStringBundle *aBundle, + nsString &aResult); + static char16_t* GetStringByName(const char *aName, + nsIStringBundle *aBundle = nullptr); + static void GetStringByName(const char *aName, + nsIStringBundle *aBundle, + nsString &aResult); + static nsresult GetStringBundle(const char *aPropertyURL, + nsIStringBundle **aBundle); +}; + +#define IMPORT_MSGS_URL "chrome://messenger/locale/importMsgs.properties" + + +#define IMPORT_NO_ADDRBOOKS 2000 +#define IMPORT_ERROR_AB_NOTINITIALIZED 2001 +#define IMPORT_ERROR_AB_NOTHREAD 2002 +#define IMPORT_ERROR_GETABOOK 2003 +#define IMPORT_NO_MAILBOXES 2004 +#define IMPORT_ERROR_MB_NOTINITIALIZED 2005 +#define IMPORT_ERROR_MB_NOTHREAD 2006 +#define IMPORT_ERROR_MB_NOPROXY 2007 +#define IMPORT_ERROR_MB_FINDCHILD 2008 +#define IMPORT_ERROR_MB_CREATE 2009 +#define IMPORT_ERROR_MB_NODESTFOLDER 2010 + +#define IMPORT_FIELD_DESC_START 2100 +#define IMPORT_FIELD_DESC_END 2136 + + +#endif /* _nsImportStringBundle_H__ */ diff --git a/mailnews/import/src/nsImportTranslator.cpp b/mailnews/import/src/nsImportTranslator.cpp new file mode 100644 index 000000000..beec8b93a --- /dev/null +++ b/mailnews/import/src/nsImportTranslator.cpp @@ -0,0 +1,296 @@ +/* -*- 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 "ImportOutFile.h" +#include "nsImportTranslator.h" + +#include "ImportCharSet.h" + + +bool nsImportTranslator::ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed) +{ + if (pProcessed) + *pProcessed = inLen; + return (pOutFile->WriteData(pIn, inLen)); +} + +void CMHTranslator::ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t * pOut) +{ + while (inLen) { + if (!ImportCharSet::IsUSAscii(*pIn) || ImportCharSet::Is822SpecialChar(*pIn) || ImportCharSet::Is822CtlChar(*pIn) || + (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '*') || (*pIn == '\'') || + (*pIn == '%')) { + // needs to be encode as %hex val + *pOut = '%'; pOut++; + ImportCharSet::ByteToHex(*pIn, pOut); + pOut += 2; + } + else { + *pOut = *pIn; + pOut++; + } + pIn++; inLen--; + } + *pOut = 0; +} + +bool CMHTranslator::ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed) +{ + uint8_t hex[2]; + while (inLen) { + if (!ImportCharSet::IsUSAscii(*pIn) || ImportCharSet::Is822SpecialChar(*pIn) || ImportCharSet::Is822CtlChar(*pIn) || + (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '*') || (*pIn == '\'') || + (*pIn == '%')) { + // needs to be encode as %hex val + if (!pOutFile->WriteByte('%')) + return false; + ImportCharSet::ByteToHex(*pIn, hex); + if (!pOutFile->WriteData(hex, 2)) + return false; + } + else { + if (!pOutFile->WriteByte(*pIn)) + return false; + } + pIn++; inLen--; + } + + if (pProcessed) + *pProcessed = inLen; + + return true; +} + + +bool C2047Translator::ConvertToFileQ(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed) +{ + if (!inLen) + return true; + + int maxLineLen = 64; + int curLineLen = m_startLen; + bool startLine = true; + + uint8_t hex[2]; + while (inLen) { + if (startLine) { + if (!pOutFile->WriteStr(" =?")) + return false; + if (!pOutFile->WriteStr(m_charset.get())) + return false; + if (!pOutFile->WriteStr("?q?")) + return false; + curLineLen += (6 + m_charset.Length()); + startLine = false; + } + + if (!ImportCharSet::IsUSAscii(*pIn) || ImportCharSet::Is822SpecialChar(*pIn) || ImportCharSet::Is822CtlChar(*pIn) || + (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '?') || (*pIn == '=')) { + // needs to be encode as =hex val + if (!pOutFile->WriteByte('=')) + return false; + ImportCharSet::ByteToHex(*pIn, hex); + if (!pOutFile->WriteData(hex, 2)) + return false; + curLineLen += 3; + } + else { + if (!pOutFile->WriteByte(*pIn)) + return false; + curLineLen++; + } + pIn++; inLen--; + if (curLineLen > maxLineLen) { + if (!pOutFile->WriteStr("?=")) + return false; + if (inLen) { + if (!pOutFile->WriteStr("\x0D\x0A ")) + return false; + } + + startLine = true; + curLineLen = 0; + } + } + + if (!startLine) { + // end the encoding! + if (!pOutFile->WriteStr("?=")) + return false; + } + + if (pProcessed) + *pProcessed = inLen; + + return true; +} + +bool C2047Translator::ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed) +{ + if (m_useQuotedPrintable) + return ConvertToFileQ(pIn, inLen, pOutFile, pProcessed); + + if (!inLen) + return true; + + int maxLineLen = 64; + int curLineLen = m_startLen; + bool startLine = true; + int encodeMax; + uint8_t * pEncoded = new uint8_t[maxLineLen * 2]; + + while (inLen) { + if (startLine) { + if (!pOutFile->WriteStr(" =?")) { + delete [] pEncoded; + return false; + } + if (!pOutFile->WriteStr(m_charset.get())) { + delete [] pEncoded; + return false; + } + if (!pOutFile->WriteStr("?b?")) { + delete [] pEncoded; + return false; + } + curLineLen += (6 + m_charset.Length()); + startLine = false; + } + encodeMax = maxLineLen - curLineLen; + encodeMax *= 3; + encodeMax /= 4; + if ((uint32_t)encodeMax > inLen) + encodeMax = (int)inLen; + + // encode the line, end the line + // then continue. Update curLineLen, pIn, startLine, and inLen + UMimeEncode::ConvertBuffer(pIn, encodeMax, pEncoded, maxLineLen, maxLineLen, "\x0D\x0A"); + + if (!pOutFile->WriteStr((const char *)pEncoded)) { + delete [] pEncoded; + return false; + } + + pIn += encodeMax; + inLen -= encodeMax; + startLine = true; + curLineLen = 0; + if (!pOutFile->WriteStr("?=")) { + delete [] pEncoded; + return false; + } + if (inLen) { + if (!pOutFile->WriteStr("\x0D\x0A ")) { + delete [] pEncoded; + return false; + } + } + } + + delete [] pEncoded; + + if (pProcessed) + *pProcessed = inLen; + + return true; +} + + +uint32_t UMimeEncode::GetBufferSize(uint32_t inBytes) +{ + // it takes 4 base64 bytes to represent 3 regular bytes + inBytes += 3; + inBytes /= 3; + inBytes *= 4; + // This should be plenty, but just to be safe + inBytes += 4; + + // now allow for end of line characters + inBytes += ((inBytes + 39) / 40) * 4; + + return inBytes; +} + +static uint8_t gBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +uint32_t UMimeEncode::ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t * pOut, uint32_t maxLen, uint32_t firstLineLen, const char * pEolStr) +{ + + uint32_t pos = 0; + uint32_t len = 0; + uint32_t lineLen = 0; + uint32_t maxLine = firstLineLen; + int eolLen = 0; + if (pEolStr) + eolLen = strlen(pEolStr); + + while ((pos + 2) < inLen) { + // Encode 3 bytes + *pOut = gBase64[*pIn >> 2]; + pOut++; len++; lineLen++; + *pOut = gBase64[(((*pIn) & 0x3)<< 4) | (((*(pIn + 1)) & 0xF0) >> 4)]; + pIn++; pOut++; len++; lineLen++; + *pOut = gBase64[(((*pIn) & 0xF) << 2) | (((*(pIn + 1)) & 0xC0) >>6)]; + pIn++; pOut++; len++; lineLen++; + *pOut = gBase64[(*pIn) & 0x3F]; + pIn++; pOut++; len++; lineLen++; + pos += 3; + if (lineLen >= maxLine) { + lineLen = 0; + maxLine = maxLen; + if (pEolStr) { + memcpy(pOut, pEolStr, eolLen); + pOut += eolLen; + len += eolLen; + } + } + } + + if ((pos < inLen) && ((lineLen + 3) > maxLine)) { + lineLen = 0; + maxLine = maxLen; + if (pEolStr) { + memcpy(pOut, pEolStr, eolLen); + pOut += eolLen; + len += eolLen; + } + } + + if (pos < inLen) { + // Get the last few bytes! + *pOut = gBase64[*pIn >> 2]; + pOut++; len++; + pos++; + if (pos < inLen) { + *pOut = gBase64[(((*pIn) & 0x3)<< 4) | (((*(pIn + 1)) & 0xF0) >> 4)]; + pIn++; pOut++; pos++; len++; + if (pos < inLen) { + // Should be dead code!! (Then why is it here doofus?) + *pOut = gBase64[(((*pIn) & 0xF) << 2) | (((*(pIn + 1)) & 0xC0) >>6)]; + pIn++; pOut++; len++; + *pOut = gBase64[(*pIn) & 0x3F]; + pos++; pOut++; len++; + } + else { + *pOut = gBase64[(((*pIn) & 0xF) << 2)]; + pOut++; len++; + *pOut = '='; + pOut++; len++; + } + } + else { + *pOut = gBase64[(((*pIn) & 0x3)<< 4)]; + pOut++; len++; + *pOut = '='; + pOut++; len++; + *pOut = '='; + pOut++; len++; + } + } + + *pOut = 0; + + return len; +} diff --git a/mailnews/import/src/nsImportTranslator.h b/mailnews/import/src/nsImportTranslator.h new file mode 100644 index 000000000..998616063 --- /dev/null +++ b/mailnews/import/src/nsImportTranslator.h @@ -0,0 +1,66 @@ +/* -*- 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 nsImportTranslator_h___ +#define nsImportTranslator_h___ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" + +class ImportOutFile; + +class UMimeEncode { +public: + static uint32_t GetBufferSize(uint32_t inByes); + static uint32_t ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t *pOut, uint32_t maxLen = 72, uint32_t firstLineLen = 72, const char * pEolStr = nullptr); +}; + + +class nsImportTranslator { +public: + virtual ~nsImportTranslator() {} + virtual bool Supports8bitEncoding(void) { return false;} + virtual uint32_t GetMaxBufferSize(uint32_t inLen) { return inLen + 1;} + virtual void ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t * pOut) { memcpy(pOut, pIn, inLen); pOut[inLen] = 0;} + virtual bool ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed = nullptr); + virtual bool FinishConvertToFile(ImportOutFile * /* pOutFile */) { return true;} + + virtual void GetCharset(nsCString& charSet) { charSet = "us-ascii";} + virtual void GetLanguage(nsCString& lang) { lang = "en";} + virtual void GetEncoding(nsCString& encoding) { encoding.Truncate();} +}; + +// Specialized encoder, not a vaild language translator, used for Mime headers. +// rfc2231 +class CMHTranslator : public nsImportTranslator { +public: + virtual uint32_t GetMaxBufferSize(uint32_t inLen) override { return (inLen * 3) + 1;} + virtual void ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t * pOut) override; + virtual bool ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed = nullptr) override; +}; + +// Specialized encoder, not a vaild language translator, used for mail headers +// rfc2047 +class C2047Translator : public nsImportTranslator { +public: + virtual ~C2047Translator() {} + + C2047Translator(const char *pCharset, uint32_t headerLen) { m_charset = pCharset; m_startLen = headerLen; m_useQuotedPrintable = false;} + + void SetUseQuotedPrintable(void) { m_useQuotedPrintable = true;} + + virtual bool ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed = nullptr) override; + bool ConvertToFileQ(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed); + +protected: + bool m_useQuotedPrintable; + nsCString m_charset; + uint32_t m_startLen; +}; + +#endif /* nsImportTranslator_h__ */ + diff --git a/mailnews/import/text/src/TextDebugLog.h b/mailnews/import/text/src/TextDebugLog.h new file mode 100644 index 000000000..3f9bf1ec4 --- /dev/null +++ b/mailnews/import/text/src/TextDebugLog.h @@ -0,0 +1,21 @@ +/* -*- 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 TextDebugLog_h___ +#define TextDebugLog_h___ + +// Use PR_LOG for logging. +#include "mozilla/Logging.h" +extern PRLogModuleInfo *TEXTIMPORTLOGMODULE; // Logging module + +#define IMPORT_LOG0(x) MOZ_LOG(TEXTIMPORTLOGMODULE, mozilla::LogLevel::Debug, (x)) +#define IMPORT_LOG1(x, y) MOZ_LOG(TEXTIMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y)) +#define IMPORT_LOG2(x, y, z) MOZ_LOG(TEXTIMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y, z)) +#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(TEXTIMPORTLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d)) + + + +#endif /* TextDebugLog_h___ */ diff --git a/mailnews/import/text/src/moz.build b/mailnews/import/text/src/moz.build new file mode 100644 index 000000000..1bdbb07c9 --- /dev/null +++ b/mailnews/import/text/src/moz.build @@ -0,0 +1,16 @@ +# 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 += [ + 'nsTextAddress.cpp', + 'nsTextImport.cpp', +] + +FINAL_LIBRARY = 'import' + +LOCAL_INCLUDES += [ + '../../src' +] + diff --git a/mailnews/import/text/src/nsTextAddress.cpp b/mailnews/import/text/src/nsTextAddress.cpp new file mode 100644 index 000000000..6b0b82ed1 --- /dev/null +++ b/mailnews/import/text/src/nsTextAddress.cpp @@ -0,0 +1,471 @@ +/* -*- 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 "nsTextAddress.h" +#include "nsIAddrDatabase.h" +#include "nsNativeCharsetUtils.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsILineInputStream.h" +#include "nsNetUtil.h" +#include "nsMsgI18N.h" +#include "nsMsgUtils.h" +#include "mdb.h" +#include "nsIConverterInputStream.h" +#include "nsIUnicharLineInputStream.h" +#include "nsMsgUtils.h" + +#include "TextDebugLog.h" +#include "plstr.h" +#include "msgCore.h" +#include <algorithm> + +#ifndef MOZILLA_INTERNAL_API +#include "nsMsgI18N.h" +#define NS_CopyNativeToUnicode(source, dest) \ + nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), source, dest) +#endif + +#define kWhitespace " \t\b\r\n" + +nsTextAddress::nsTextAddress() +{ + m_database = nullptr; + m_fieldMap = nullptr; + m_LFCount = 0; + m_CRCount = 0; +} + +nsTextAddress::~nsTextAddress() +{ + NS_IF_RELEASE(m_database); + NS_IF_RELEASE(m_fieldMap); +} + +nsresult nsTextAddress::GetUnicharLineStreamForFile(nsIFile *aFile, + nsIInputStream *aInputStream, + nsIUnicharLineInputStream **aStream) +{ + nsAutoCString charset; + nsresult rv = MsgDetectCharsetFromFile(aFile, charset); + if (NS_FAILED(rv)) { + charset.Assign(nsMsgI18NFileSystemCharset()); + } + + nsCOMPtr<nsIConverterInputStream> converterStream = + do_CreateInstance("@mozilla.org/intl/converter-input-stream;1", &rv); + if (NS_SUCCEEDED(rv)) { + rv = converterStream->Init(aInputStream, + charset.get(), + 8192, + nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER); + } + + return CallQueryInterface(converterStream, aStream); +} + +nsresult nsTextAddress::ImportAddresses(bool *pAbort, const char16_t *pName, nsIFile *pSrc, nsIAddrDatabase *pDb, nsIImportFieldMap *fieldMap, nsString& errors, uint32_t *pProgress) +{ + // Open the source file for reading, read each line and process it! + NS_IF_RELEASE(m_database); + NS_IF_RELEASE(m_fieldMap); + m_database = pDb; + m_fieldMap = fieldMap; + NS_ADDREF(m_fieldMap); + NS_ADDREF(m_database); + + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening address file for reading\n"); + return rv; + } + + // Here we use this to work out the size of the file, so we can update + // an integer as we go through the file which will update a progress + // bar if required by the caller. + uint64_t bytesLeft = 0; + rv = inputStream->Available(&bytesLeft); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error checking address file for size\n"); + inputStream->Close(); + return rv; + } + + uint64_t totalBytes = bytesLeft; + bool skipRecord = false; + + rv = m_fieldMap->GetSkipFirstRecord(&skipRecord); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error checking to see if we should skip the first record\n"); + return rv; + } + + nsCOMPtr<nsIUnicharLineInputStream> lineStream; + rv = GetUnicharLineStreamForFile(pSrc, inputStream, getter_AddRefs(lineStream)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening converter stream for importer\n"); + return rv; + } + + bool more = true; + nsAutoString line; + + // Skip the first record if the user has requested it. + if (skipRecord) + rv = ReadRecord(lineStream, line, &more); + + while (!(*pAbort) && more && NS_SUCCEEDED(rv)) { + // Read the line in + rv = ReadRecord(lineStream, line, &more); + if (NS_SUCCEEDED(rv)) { + // Now proces it to add it to the database + rv = ProcessLine(line, errors); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error processing text record.\n"); + } + } + if (NS_SUCCEEDED(rv) && pProgress) { + // This won't be totally accurate, but its the best we can do + // considering that lineStream won't give us how many bytes + // are actually left. + bytesLeft -= line.Length(); + *pProgress = std::min(totalBytes - bytesLeft, uint64_t(PR_UINT32_MAX)); + } + } + + inputStream->Close(); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error reading the address book - probably incorrect ending\n"); + return NS_ERROR_FAILURE; + } + + return pDb->Commit(nsAddrDBCommitType::kLargeCommit); +} + +nsresult nsTextAddress::ReadRecord(nsIUnicharLineInputStream *aLineStream, + nsAString &aLine, + bool *aMore) +{ + bool more = true; + uint32_t numQuotes = 0; + nsresult rv; + nsAutoString line; + + // ensure aLine is empty + aLine.Truncate(); + + do { + if (!more) { + // No more, so we must have an incorrect file. + rv = NS_ERROR_FAILURE; + } + else { + // Read the line and append it + rv = aLineStream->ReadLine(line, &more); + if (NS_SUCCEEDED(rv)) { + if (!aLine.IsEmpty()) + aLine.AppendLiteral(MSG_LINEBREAK); + aLine.Append(line); + + numQuotes += MsgCountChar(line, char16_t('"')); + } + } + // Continue whilst everything is ok, and we have an odd number of quotes. + } while (NS_SUCCEEDED(rv) && (numQuotes % 2 != 0)); + + *aMore = more; + return rv; +} + +nsresult nsTextAddress::ReadRecordNumber(nsIFile *aSrc, nsAString &aLine, int32_t rNum) +{ + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening address file for reading\n"); + return rv; + } + + int32_t rIndex = 0; + uint64_t bytesLeft = 0; + + rv = inputStream->Available(&bytesLeft); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error checking address file for eof\n"); + inputStream->Close(); + return rv; + } + + nsCOMPtr<nsIUnicharLineInputStream> lineStream; + rv = GetUnicharLineStreamForFile(aSrc, inputStream, getter_AddRefs(lineStream)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening converter stream for importer\n"); + return rv; + } + + bool more = true; + + while (more && (rIndex <= rNum)) { + rv = ReadRecord(lineStream, aLine, &more); + if (NS_FAILED(rv)) { + inputStream->Close(); + return rv; + } + if (rIndex == rNum) { + inputStream->Close(); + return NS_OK; + } + + rIndex++; + } + + return NS_ERROR_FAILURE; +} + +int32_t nsTextAddress::CountFields(const nsAString &aLine, char16_t delim) +{ + int32_t pos = 0; + int32_t maxLen = aLine.Length(); + int32_t count = 0; + char16_t tab = char16_t('\t'); + char16_t doubleQuote = char16_t('"'); + + if (delim == tab) + tab = char16_t('\0'); + + while (pos < maxLen) { + while (((aLine[pos] == char16_t(' ')) || (aLine[pos] == tab)) && + (pos < maxLen)) { + pos++; + } + if ((pos < maxLen) && (aLine[pos] == doubleQuote)) { + pos++; + while ((pos < maxLen) && (aLine[pos] != doubleQuote)) { + pos++; + if (((pos + 1) < maxLen) && + (aLine[pos] == doubleQuote) && + (aLine[pos + 1] == doubleQuote)) { + pos += 2; + } + } + if (pos < maxLen) + pos++; + } + while ((pos < maxLen) && (aLine[pos] != delim)) + pos++; + + count++; + pos++; + } + + return count; +} + +bool nsTextAddress::GetField(const nsAString &aLine, + int32_t index, + nsString &field, + char16_t delim) +{ + bool result = false; + int32_t pos = 0; + int32_t maxLen = aLine.Length(); + char16_t tab = char16_t('\t'); + char16_t doubleQuote = char16_t('"'); + + field.Truncate(); + + if (delim == tab) + tab = 0; + + while (index && (pos < maxLen)) { + while (((aLine[pos] == char16_t(' ')) || (aLine[pos] == tab)) && + (pos < maxLen)) { + pos++; + } + if (pos >= maxLen) + break; + if (aLine[pos] == doubleQuote) { + do { + pos++; + if (((pos + 1) < maxLen) && + (aLine[pos] == doubleQuote) && + (aLine[pos + 1] == doubleQuote)) { + pos += 2; + } + } while ((pos < maxLen) && (aLine[pos] != doubleQuote)); + if (pos < maxLen) + pos++; + } + if (pos >= maxLen) + break; + + while ((pos < maxLen) && (aLine[pos] != delim)) + pos++; + + if (pos >= maxLen) + break; + + index--; + pos++; + } + + if (pos >= maxLen) + return result; + + result = true; + + while ((pos < maxLen) && ((aLine[pos] == ' ') || (aLine[pos] == tab))) + pos++; + + int32_t fLen = 0; + int32_t startPos = pos; + bool quoted = false; + if (aLine[pos] == '"') { + startPos++; + fLen = -1; + do { + pos++; + fLen++; + if (((pos + 1) < maxLen) && + (aLine[pos] == doubleQuote) && + (aLine[pos + 1] == doubleQuote)) { + quoted = true; + pos += 2; + fLen += 2; + } + } while ((pos < maxLen) && (aLine[pos] != doubleQuote)); + } + else { + while ((pos < maxLen) && (aLine[pos] != delim)) { + pos++; + fLen++; + } + } + + if (!fLen) { + return result; + } + + field.Append(nsDependentSubstring(aLine, startPos, fLen)); + field.Trim(kWhitespace); + + if (quoted) { + int32_t offset = field.Find("\"\""); + while (offset != -1) { + field.Cut(offset, 1); + offset = MsgFind(field, "\"\"", false, offset + 1); + } + } + + return result; +} + +nsresult nsTextAddress::DetermineDelim(nsIFile *aSrc) +{ + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening address file for reading\n"); + return rv; + } + + int32_t lineCount = 0; + int32_t tabCount = 0; + int32_t commaCount = 0; + int32_t tabLines = 0; + int32_t commaLines = 0; + nsAutoString line; + bool more = true; + + nsCOMPtr<nsIUnicharLineInputStream> lineStream; + rv = GetUnicharLineStreamForFile(aSrc, inputStream, getter_AddRefs(lineStream)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening converter stream for importer\n"); + return rv; + } + + while (more && NS_SUCCEEDED(rv) && (lineCount < 100)) { + rv = lineStream->ReadLine(line, &more); + if (NS_SUCCEEDED(rv)) { + tabCount = CountFields(line, char16_t('\t')); + commaCount = CountFields(line, char16_t(',')); + if (tabCount > commaCount) + tabLines++; + else if (commaCount) + commaLines++; + } + lineCount++; + } + + rv = inputStream->Close(); + + if (tabLines > commaLines) + m_delim = char16_t('\t'); + else + m_delim = char16_t(','); + + IMPORT_LOG2( "Tab count = %d, Comma count = %d\n", tabLines, commaLines); + + return rv; +} + +/* + This is where the real work happens! + Go through the field map and set the data in a new database row +*/ +nsresult nsTextAddress::ProcessLine(const nsAString &aLine, nsString& errors) +{ + if (!m_fieldMap) { + IMPORT_LOG0("*** Error, text import needs a field map\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv; + + // Wait until we get our first non-empty field, then create a new row, + // fill in the data, then add the row to the database. + nsCOMPtr<nsIMdbRow> newRow; + nsAutoString fieldVal; + int32_t fieldNum; + int32_t numFields = 0; + bool active; + rv = m_fieldMap->GetMapSize(&numFields); + for (int32_t i = 0; (i < numFields) && NS_SUCCEEDED(rv); i++) { + active = false; + rv = m_fieldMap->GetFieldMap(i, &fieldNum); + if (NS_SUCCEEDED(rv)) + rv = m_fieldMap->GetFieldActive(i, &active); + if (NS_SUCCEEDED(rv) && active) { + if (GetField(aLine, i, fieldVal, m_delim)) { + if (!fieldVal.IsEmpty()) { + if (!newRow) { + rv = m_database->GetNewRow(getter_AddRefs(newRow)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error getting new address database row\n"); + } + } + if (newRow) { + rv = m_fieldMap->SetFieldValue(m_database, newRow, fieldNum, fieldVal.get()); + } + } + } + else + break; + } + else if (active) { + IMPORT_LOG1("*** Error getting field map for index %ld\n", (long) i); + } + } + + if (NS_SUCCEEDED(rv) && newRow) + rv = m_database->AddCardRowToDB(newRow); + + return rv; +} + diff --git a/mailnews/import/text/src/nsTextAddress.h b/mailnews/import/text/src/nsTextAddress.h new file mode 100644 index 000000000..69a311be4 --- /dev/null +++ b/mailnews/import/text/src/nsTextAddress.h @@ -0,0 +1,57 @@ +/* -*- 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 nsTextAddress_h__ +#define nsTextAddress_h__ + +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsIImportFieldMap.h" +#include "nsIImportService.h" + +class nsIAddrDatabase; +class nsIFile; +class nsIInputStream; +class nsIUnicharLineInputStream; + +///////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////// + +class nsTextAddress { +public: + nsTextAddress(); + virtual ~nsTextAddress(); + + nsresult ImportAddresses(bool *pAbort, const char16_t *pName, nsIFile *pSrc, nsIAddrDatabase *pDb, nsIImportFieldMap *fieldMap, nsString& errors, uint32_t *pProgress); + + nsresult DetermineDelim(nsIFile *pSrc); + char16_t GetDelim(void) { return m_delim; } + + static nsresult ReadRecordNumber(nsIFile *pSrc, nsAString &aLine, int32_t rNum); + static bool GetField(const nsAString &aLine, int32_t index, nsString &field, char16_t delim); + +private: + nsresult ProcessLine(const nsAString &aLine, nsString &errors); + + static int32_t CountFields(const nsAString &aLine, char16_t delim); + static nsresult ReadRecord(nsIUnicharLineInputStream *pSrc, nsAString &aLine, bool *aMore); + static nsresult GetUnicharLineStreamForFile(nsIFile *aFile, + nsIInputStream *aInputStream, + nsIUnicharLineInputStream **aStream); + + char16_t m_delim; + int32_t m_LFCount; + int32_t m_CRCount; + nsIAddrDatabase *m_database; + nsIImportFieldMap *m_fieldMap; + nsCOMPtr<nsIImportService> m_pService; +}; + + + +#endif /* nsTextAddress_h__ */ + diff --git a/mailnews/import/text/src/nsTextImport.cpp b/mailnews/import/text/src/nsTextImport.cpp new file mode 100644 index 000000000..61615e4d7 --- /dev/null +++ b/mailnews/import/text/src/nsTextImport.cpp @@ -0,0 +1,714 @@ +/* -*- 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/. */ + + +/* + + Text import addressbook interfaces + +*/ +#include "nscore.h" +#include "nsIServiceManager.h" +#include "nsCOMPtr.h" +#include "nsIImportService.h" +#include "nsMsgI18N.h" +#include "nsIComponentManager.h" +#include "nsTextImport.h" +#include "nsIMemory.h" +#include "nsIMutableArray.h" +#include "nsIImportGeneric.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportFieldMap.h" +#include "nsIOutputStream.h" +#include "nsIAddrDatabase.h" +#include "nsIAbLDIFService.h" +#include "nsAbBaseCID.h" +#include "nsTextFormatter.h" +#include "nsImportStringBundle.h" +#include "nsTextAddress.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "TextDebugLog.h" +#include "nsNetUtil.h" +#include "nsMsgUtils.h" + +#define TEXT_MSGS_URL "chrome://messenger/locale/textImportMsgs.properties" +#define TEXTIMPORT_NAME 2000 +#define TEXTIMPORT_DESCRIPTION 2001 +#define TEXTIMPORT_ADDRESS_NAME 2002 +#define TEXTIMPORT_ADDRESS_SUCCESS 2003 +#define TEXTIMPORT_ADDRESS_BADPARAM 2004 +#define TEXTIMPORT_ADDRESS_BADSOURCEFILE 2005 +#define TEXTIMPORT_ADDRESS_CONVERTERROR 2006 + +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); +PRLogModuleInfo* TEXTIMPORTLOGMODULE; + +class ImportAddressImpl final : public nsIImportAddressBooks +{ +public: + ImportAddressImpl(nsIStringBundle* aStringBundle); + + static nsresult Create(nsIImportAddressBooks** aImport, + nsIStringBundle *aStringBundle); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportAddressBooks interface + + NS_IMETHOD GetSupportsMultiple(bool *_retval) override { *_retval = false; return NS_OK;} + + NS_IMETHOD GetAutoFind(char16_t **description, bool *_retval) override; + + NS_IMETHOD GetNeedsFieldMap(nsIFile *location, bool *_retval) override; + + NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found, bool *userVerify) override; + + NS_IMETHOD FindAddressBooks(nsIFile *location, nsIArray **_retval) override; + + NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap) override; + + NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source, + nsIAddrDatabase *destination, + nsIImportFieldMap *fieldMap, + nsISupports *aSupportService, + char16_t **errorLog, + char16_t **successLog, + bool *fatalError) override; + + NS_IMETHOD GetImportProgress(uint32_t *_retval) override; + + NS_IMETHOD GetSampleData(int32_t index, bool *pFound, char16_t **pStr) override; + + NS_IMETHOD SetSampleLocation(nsIFile *) override; + +private: + void ClearSampleFile(void); + void SaveFieldMap(nsIImportFieldMap *pMap); + + static void ReportSuccess(nsString& name, nsString *pStream, + nsIStringBundle* pBundle); + static void SetLogs(nsString& success, nsString& error, char16_t **pError, + char16_t **pSuccess); + static void ReportError(int32_t errorNum, nsString& name, nsString *pStream, + nsIStringBundle* pBundle); + static void SanitizeSampleData(nsString& val); + +private: + ~ImportAddressImpl() {} + nsTextAddress m_text; + bool m_haveDelim; + nsCOMPtr<nsIFile> m_fileLoc; + nsCOMPtr<nsIStringBundle> m_notProxyBundle; + char16_t m_delim; + uint32_t m_bytesImported; +}; + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// + +nsTextImport::nsTextImport() +{ + // Init logging module. + if (!TEXTIMPORTLOGMODULE) + TEXTIMPORTLOGMODULE = PR_NewLogModule("IMPORT"); + IMPORT_LOG0("nsTextImport Module Created\n"); + + nsImportStringBundle::GetStringBundle(TEXT_MSGS_URL, + getter_AddRefs(m_stringBundle)); +} + +nsTextImport::~nsTextImport() +{ + IMPORT_LOG0("nsTextImport Module Deleted\n"); +} + +NS_IMPL_ISUPPORTS(nsTextImport, nsIImportModule) + +NS_IMETHODIMP nsTextImport::GetName(char16_t **name) +{ + NS_ENSURE_ARG_POINTER(name); + *name = nsImportStringBundle::GetStringByID(TEXTIMPORT_NAME, m_stringBundle); + return NS_OK; +} + +NS_IMETHODIMP nsTextImport::GetDescription(char16_t **name) +{ + NS_ENSURE_ARG_POINTER(name); + *name = nsImportStringBundle::GetStringByID(TEXTIMPORT_DESCRIPTION, + m_stringBundle); + + return NS_OK; +} + +NS_IMETHODIMP nsTextImport::GetSupports(char **supports) +{ + NS_ENSURE_ARG_POINTER(supports); + *supports = strdup(kTextSupportsString); + return NS_OK; +} + +NS_IMETHODIMP nsTextImport::GetSupportsUpgrade(bool *pUpgrade) +{ + NS_PRECONDITION(pUpgrade != nullptr, "null ptr"); + if (! pUpgrade) + return NS_ERROR_NULL_POINTER; + + *pUpgrade = false; + return NS_OK; +} + +NS_IMETHODIMP nsTextImport::GetImportInterface(const char *pImportType, nsISupports **ppInterface) +{ + NS_ENSURE_ARG_POINTER(pImportType); + NS_ENSURE_ARG_POINTER(ppInterface); + + *ppInterface = nullptr; + nsresult rv; + + if (!strcmp(pImportType, "addressbook")) { + // create the nsIImportMail interface and return it! + nsIImportAddressBooks * pAddress = nullptr; + nsIImportGeneric * pGeneric = nullptr; + rv = ImportAddressImpl::Create(&pAddress, m_stringBundle); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericAddressBooks(&pGeneric); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("addressInterface", pAddress); + rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface); + } + } + } + NS_IF_RELEASE(pAddress); + NS_IF_RELEASE(pGeneric); + return rv; + } + return NS_ERROR_NOT_AVAILABLE; +} + +///////////////////////////////////////////////////////////////////////////////// + + + +nsresult ImportAddressImpl::Create(nsIImportAddressBooks** aImport, + nsIStringBundle* aStringBundle) +{ + NS_ENSURE_ARG_POINTER(aImport); + *aImport = new ImportAddressImpl(aStringBundle); + if (! *aImport) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aImport); + return NS_OK; +} + +ImportAddressImpl::ImportAddressImpl(nsIStringBundle* aStringBundle) : + m_notProxyBundle(aStringBundle) +{ + m_haveDelim = false; +} + +NS_IMPL_ISUPPORTS(ImportAddressImpl, nsIImportAddressBooks) + + +NS_IMETHODIMP ImportAddressImpl::GetAutoFind(char16_t **addrDescription, bool *_retval) +{ + NS_PRECONDITION(addrDescription != nullptr, "null ptr"); + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! addrDescription || !_retval) + return NS_ERROR_NULL_POINTER; + + nsString str; + *_retval = false; + + if (!m_notProxyBundle) + return NS_ERROR_FAILURE; + + nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_NAME, m_notProxyBundle, str); + *addrDescription = ToNewUnicode(str); + return NS_OK; +} + + +NS_IMETHODIMP ImportAddressImpl::GetDefaultLocation(nsIFile **ppLoc, bool *found, bool *userVerify) +{ + NS_PRECONDITION(found != nullptr, "null ptr"); + NS_PRECONDITION(ppLoc != nullptr, "null ptr"); + NS_PRECONDITION(userVerify != nullptr, "null ptr"); + if (! found || !userVerify || !ppLoc) + return NS_ERROR_NULL_POINTER; + + *ppLoc = nullptr; + *found = false; + *userVerify = true; + return NS_OK; +} + + + +NS_IMETHODIMP ImportAddressImpl::FindAddressBooks(nsIFile *pLoc, nsIArray **ppArray) +{ + NS_PRECONDITION(pLoc != nullptr, "null ptr"); + NS_PRECONDITION(ppArray != nullptr, "null ptr"); + if (!pLoc || !ppArray) + return NS_ERROR_NULL_POINTER; + + ClearSampleFile(); + + *ppArray = nullptr; + bool exists = false; + nsresult rv = pLoc->Exists(&exists); + if (NS_FAILED(rv) || !exists) + return NS_ERROR_FAILURE; + + bool isFile = false; + rv = pLoc->IsFile(&isFile); + if (NS_FAILED(rv) || !isFile) + return NS_ERROR_FAILURE; + + rv = m_text.DetermineDelim(pLoc); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error determining delimitter\n"); + return rv; + } + m_haveDelim = true; + m_delim = m_text.GetDelim(); + + m_fileLoc = do_QueryInterface(pLoc); + + /* Build an address book descriptor based on the file passed in! */ + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("FAILED to allocate the nsIMutableArray\n"); + return rv; + } + + nsString name; + m_fileLoc->GetLeafName(name); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed getting leaf name of file\n"); + return rv; + } + + int32_t idx = name.RFindChar('.'); + if ((idx != -1) && (idx > 0) && ((name.Length() - idx - 1) < 5)) { + name.SetLength(idx); + } + + nsCOMPtr<nsIImportABDescriptor> desc; + nsISupports * pInterface; + + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to obtain the import service\n"); + return rv; + } + + rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc)); + if (NS_SUCCEEDED(rv)) { + int64_t sz = 0; + pLoc->GetFileSize(&sz); + desc->SetPreferredName(name); + desc->SetSize((uint32_t) sz); + desc->SetAbFile(m_fileLoc); + rv = desc->QueryInterface(kISupportsIID, (void **) &pInterface); + array->AppendElement(pInterface, false); + pInterface->Release(); + } + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error creating address book descriptor for text import\n"); + return rv; + } + array.forget(ppArray); + return NS_OK; +} + +void ImportAddressImpl::ReportSuccess(nsString& name, nsString *pStream, + nsIStringBundle* pBundle) +{ + if (!pStream) + return; + + // load the success string + char16_t *pFmt = + nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_SUCCESS, pBundle); + + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + NS_Free(pFmt); + pStream->Append(char16_t('\n')); +} + +void ImportAddressImpl::ReportError(int32_t errorNum, nsString& name, + nsString *pStream, nsIStringBundle* pBundle) +{ + if (!pStream) + return; + + // load the error string + char16_t *pFmt = nsImportStringBundle::GetStringByID(errorNum, pBundle); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + NS_Free(pFmt); + pStream->Append(char16_t('\n')); +} + +void ImportAddressImpl::SetLogs(nsString& success, nsString& error, char16_t **pError, char16_t **pSuccess) +{ + if (pError) + *pError = ToNewUnicode(error); + if (pSuccess) + *pSuccess = ToNewUnicode(success); +} + + +NS_IMETHODIMP +ImportAddressImpl::ImportAddressBook(nsIImportABDescriptor *pSource, + nsIAddrDatabase *pDestination, + nsIImportFieldMap *fieldMap, + nsISupports *aSupportService, + char16_t ** pErrorLog, + char16_t ** pSuccessLog, + bool * fatalError) +{ + NS_PRECONDITION(pSource != nullptr, "null ptr"); + NS_PRECONDITION(pDestination != nullptr, "null ptr"); + NS_PRECONDITION(fatalError != nullptr, "null ptr"); + + m_bytesImported = 0; + + nsString success, error; + if (!pSource || !pDestination || !fatalError) { + IMPORT_LOG0("*** Bad param passed to text address import\n"); + nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_BADPARAM, + m_notProxyBundle, + error); + + SetLogs(success, error, pErrorLog, pSuccessLog); + + if (fatalError) + *fatalError = true; + + return NS_ERROR_NULL_POINTER; + } + + ClearSampleFile(); + + bool addrAbort = false; + nsString name; + pSource->GetPreferredName(name); + + uint32_t addressSize = 0; + pSource->GetSize(&addressSize); + if (addressSize == 0) { + IMPORT_LOG0("Address book size is 0, skipping import.\n"); + ReportSuccess(name, &success, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_OK; + } + + nsCOMPtr<nsIFile> inFile; + if (NS_FAILED(pSource->GetAbFile(getter_AddRefs(inFile)))) { + ReportError(TEXTIMPORT_ADDRESS_BADSOURCEFILE, name, &error, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_ERROR_FAILURE; + } + + if (!aSupportService) { + IMPORT_LOG0("Missing support service to import call"); + return NS_ERROR_FAILURE; + } + + bool isLDIF = false; + nsresult rv; + nsCOMPtr<nsIAbLDIFService> ldifService(do_QueryInterface(aSupportService, &rv)); + + if (NS_SUCCEEDED(rv)) { + rv = ldifService->IsLDIFFile(inFile, &isLDIF); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error reading address file\n"); + } + } + + if (NS_FAILED(rv)) { + ReportError(TEXTIMPORT_ADDRESS_CONVERTERROR, name, &error, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + return rv; + } + + if (isLDIF) { + if (ldifService) + rv = ldifService->ImportLDIFFile(pDestination, inFile, false, &m_bytesImported); + else + return NS_ERROR_FAILURE; + } + else { + rv = m_text.ImportAddresses(&addrAbort, name.get(), inFile, pDestination, fieldMap, error, &m_bytesImported); + SaveFieldMap(fieldMap); + } + + if (NS_SUCCEEDED(rv) && error.IsEmpty()) { + ReportSuccess(name, &success, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + } + else { + ReportError(TEXTIMPORT_ADDRESS_CONVERTERROR, name, &error, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + } + + IMPORT_LOG0("*** Text address import done\n"); + return rv; +} + + +NS_IMETHODIMP ImportAddressImpl::GetImportProgress(uint32_t *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_bytesImported; + return NS_OK; +} + + +NS_IMETHODIMP ImportAddressImpl::GetNeedsFieldMap(nsIFile *aLocation, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_ARG_POINTER(aLocation); + + *_retval = true; + bool exists = false; + bool isFile = false; + + nsresult rv = aLocation->Exists(&exists); + rv = aLocation->IsFile(&isFile); + + if (!exists || !isFile) + return NS_ERROR_FAILURE; + + bool isLDIF = false; + nsCOMPtr<nsIAbLDIFService> ldifService = do_GetService(NS_ABLDIFSERVICE_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + rv = ldifService->IsLDIFFile(aLocation, &isLDIF); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error determining if file is of type LDIF\n"); + return rv; + } + + if (isLDIF) + *_retval = false; + + return NS_OK; +} + +void ImportAddressImpl::SanitizeSampleData(nsString& val) +{ + // remove any line-feeds... + int32_t offset = val.Find(NS_LITERAL_STRING("\x0D\x0A")); + while (offset != -1) { + val.Replace(offset, 2, NS_LITERAL_STRING(", ")); + offset = val.Find(NS_LITERAL_STRING("\x0D\x0A"), offset + 2); + } + offset = val.FindChar(13); + while (offset != -1) { + val.Replace(offset, 1, ','); + offset = val.FindChar(13, offset + 2); + } + offset = val.FindChar(10); + while (offset != -1) { + val.Replace(offset, 1, ','); + offset = val.FindChar(10, offset + 2); + } +} + +NS_IMETHODIMP ImportAddressImpl::GetSampleData(int32_t index, bool *pFound, char16_t **pStr) +{ + NS_PRECONDITION(pFound != nullptr, "null ptr"); + NS_PRECONDITION(pStr != nullptr, "null ptr"); + if (!pFound || !pStr) + return NS_ERROR_NULL_POINTER; + + if (!m_fileLoc) { + IMPORT_LOG0("*** Error, called GetSampleData before SetSampleLocation\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv; + *pStr = nullptr; + char16_t term = 0; + + if (!m_haveDelim) { + rv = m_text.DetermineDelim(m_fileLoc); + NS_ENSURE_SUCCESS(rv, rv); + m_haveDelim = true; + m_delim = m_text.GetDelim(); + } + + bool fileExists; + rv = m_fileLoc->Exists(&fileExists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!fileExists) { + *pFound = false; + *pStr = NS_strdup(&term); + return NS_OK; + } + + nsAutoString line; + rv = nsTextAddress::ReadRecordNumber(m_fileLoc, line, index); + if (NS_SUCCEEDED(rv)) { + nsString str; + nsString field; + int32_t fNum = 0; + while (nsTextAddress::GetField(line, fNum, field, m_delim)) { + if (fNum) + str.Append(char16_t('\n')); + SanitizeSampleData(field); + str.Append(field); + fNum++; + field.Truncate(); + } + + *pStr = ToNewUnicode(str); + *pFound = true; + + /* IMPORT_LOG1("Sample data: %S\n", str.get()); */ + } + else { + *pFound = false; + *pStr = NS_strdup(&term); + } + + return NS_OK; +} + +NS_IMETHODIMP ImportAddressImpl::SetSampleLocation(nsIFile *pLocation) +{ + NS_ENSURE_ARG_POINTER(pLocation); + + m_fileLoc = do_QueryInterface(pLocation); + m_haveDelim = false; + return NS_OK; +} + +void ImportAddressImpl::ClearSampleFile(void) +{ + m_fileLoc = nullptr; + m_haveDelim = false; +} + +NS_IMETHODIMP ImportAddressImpl::InitFieldMap(nsIImportFieldMap *fieldMap) +{ + // Let's remember the last one the user used! + // This should be normal for someone importing multiple times, it's usually + // from the same file format. + + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nsCString prefStr; + rv = prefs->GetCharPref("mailnews.import.text.fieldmap", getter_Copies(prefStr)); + if (NS_SUCCEEDED(rv)) { + const char *pStr = prefStr.get(); + if (pStr) { + fieldMap->SetFieldMapSize(0); + long fNum; + bool active; + long fIndex = 0; + while (*pStr) { + while (*pStr && (*pStr != '+') && (*pStr != '-')) + pStr++; + if (*pStr == '+') + active = true; + else if (*pStr == '-') + active = false; + else + break; + fNum = 0; + while (*pStr && ((*pStr < '0') || (*pStr > '9'))) + pStr++; + if (!(*pStr)) + break; + while (*pStr && (*pStr >= '0') && (*pStr <= '9')) { + fNum *= 10; + fNum += (*pStr - '0'); + pStr++; + } + while (*pStr && (*pStr != ',')) + pStr++; + if (*pStr == ',') + pStr++; + fieldMap->SetFieldMap(-1, fNum); + fieldMap->SetFieldActive(fIndex, active); + fIndex++; + } + if (!fIndex) { + int num; + fieldMap->GetNumMozFields(&num); + fieldMap->DefaultFieldMap(num); + } + } + } + + // Now also get the last used skip first record value. + bool skipFirstRecord = false; + rv = prefs->GetBoolPref("mailnews.import.text.skipfirstrecord", &skipFirstRecord); + if (NS_SUCCEEDED(rv)) + fieldMap->SetSkipFirstRecord(skipFirstRecord); + } + + return NS_OK; +} + + +void ImportAddressImpl::SaveFieldMap(nsIImportFieldMap *pMap) +{ + if (!pMap) + return; + + int size; + int index; + bool active; + nsCString str; + + pMap->GetMapSize(&size); + for (long i = 0; i < size; i++) { + index = i; + active = false; + pMap->GetFieldMap(i, &index); + pMap->GetFieldActive(i, &active); + if (active) + str.Append('+'); + else + str.Append('-'); + + str.AppendInt(index); + str.Append(','); + } + + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + + if (NS_SUCCEEDED(rv)) { + nsCString prefStr; + rv = prefs->GetCharPref("mailnews.import.text.fieldmap", getter_Copies(prefStr)); + if (NS_FAILED(rv) || !str.Equals(prefStr)) + rv = prefs->SetCharPref("mailnews.import.text.fieldmap", str.get()); + } + + // Now also save last used skip first record value. + bool skipFirstRecord = false; + rv = pMap->GetSkipFirstRecord(&skipFirstRecord); + if (NS_SUCCEEDED(rv)) + prefs->SetBoolPref("mailnews.import.text.skipfirstrecord", skipFirstRecord); +} diff --git a/mailnews/import/text/src/nsTextImport.h b/mailnews/import/text/src/nsTextImport.h new file mode 100644 index 000000000..4c3c440e0 --- /dev/null +++ b/mailnews/import/text/src/nsTextImport.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 nsTextImport_h___ +#define nsTextImport_h___ + +#include "nsIImportModule.h" +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" + +#define NS_TEXTIMPORT_CID \ +{ /* A5991D01-ADA7-11d3-A9C2-00A0CC26DA63 */ \ + 0xa5991d01, 0xada7, 0x11d3, \ + {0xa9, 0xc2, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63 }} + +#define kTextSupportsString NS_IMPORT_ADDRESS_STR + +class nsTextImport : public nsIImportModule +{ +public: + nsTextImport(); + + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////////////////////// + // we suppport the nsIImportModule interface + //////////////////////////////////////////////////////////////////////////////////////// + + NS_DECL_NSIIMPORTMODULE + +protected: + virtual ~nsTextImport(); + nsCOMPtr<nsIStringBundle> m_stringBundle; +}; + +#endif /* nsTextImport_h___ */ diff --git a/mailnews/import/vcard/src/moz.build b/mailnews/import/vcard/src/moz.build new file mode 100644 index 000000000..9e6c49698 --- /dev/null +++ b/mailnews/import/vcard/src/moz.build @@ -0,0 +1,20 @@ +# 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 += [ + 'nsVCardAddress.cpp', + 'nsVCardImport.cpp', +] + +EXPORTS += [ + 'nsVCardAddress.h', +] + +FINAL_LIBRARY = 'import' + +LOCAL_INCLUDES += [ + '../../src' +] + diff --git a/mailnews/import/vcard/src/nsVCardAddress.cpp b/mailnews/import/vcard/src/nsVCardAddress.cpp new file mode 100644 index 000000000..7495d4c26 --- /dev/null +++ b/mailnews/import/vcard/src/nsVCardAddress.cpp @@ -0,0 +1,139 @@ +/* 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 "nsAbBaseCID.h" +#include "nsNativeCharsetUtils.h" +#include "nsNetUtil.h" +#include "nsVCardAddress.h" + +#include "nsIAbCard.h" +#include "nsIAbManager.h" +#include "nsIAddrDatabase.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsILineInputStream.h" + +#include "plstr.h" +#include "msgCore.h" +#include "nsMsgUtils.h" + +nsVCardAddress::nsVCardAddress() +{ +} + +nsVCardAddress::~nsVCardAddress() +{ +} + +nsresult nsVCardAddress::ImportAddresses( + bool *pAbort, + const char16_t *pName, + nsIFile *pSrc, + nsIAddrDatabase *pDb, + nsString& errors, + uint32_t *pProgress) +{ + // Open the source file for reading, read each line and process it! + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening address file for reading\n"); + return rv; + } + + // Open the source file for reading, read each line and process it! + // Here we use this to work out the size of the file, so we can update + // an integer as we go through the file which will update a progress + // bar if required by the caller. + uint64_t bytesLeft = 0; + rv = inputStream->Available(&bytesLeft); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error checking address file for size\n"); + inputStream->Close(); + return rv; + } + + uint64_t totalBytes = bytesLeft; + nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(inputStream, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbManager> ab = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool more = true; + nsCString record; + while (!(*pAbort) && more && NS_SUCCEEDED(rv)) { + rv = ReadRecord(lineStream, record, &more); + if (NS_SUCCEEDED(rv) && !record.IsEmpty()) { + // Parse the vCard and build an nsIAbCard from it + nsCOMPtr<nsIAbCard> cardFromVCard; + rv = ab->EscapedVCardToAbCard(record.get(), getter_AddRefs(cardFromVCard)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pDb->CreateNewCardAndAddToDB(cardFromVCard, false, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error processing vCard record.\n"); + } + } + if (NS_SUCCEEDED(rv) && pProgress) { + // This won't be totally accurate, but its the best we can do + // considering that lineStream won't give us how many bytes + // are actually left. + bytesLeft -= record.Length(); + *pProgress = totalBytes - bytesLeft; + } + } + inputStream->Close(); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error reading the address book - probably incorrect ending\n"); + return NS_ERROR_FAILURE; + } + + return pDb->Commit(nsAddrDBCommitType::kLargeCommit); +} + +nsresult nsVCardAddress::ReadRecord( + nsILineInputStream *aLineStream, nsCString &aRecord, bool *aMore) +{ + bool more = true; + nsresult rv; + nsCString line; + + aRecord.Truncate(); + + // remove the empty lines. + do { + rv = aLineStream->ReadLine(line, aMore); + } + while (line.IsEmpty() && *aMore); + if (!*aMore) + return rv; + + // read BEGIN:VCARD + if (!line.LowerCaseEqualsLiteral("begin:vcard")) { + IMPORT_LOG0("*** Expected case-insensitive BEGIN:VCARD at start of vCard\n"); + rv = NS_ERROR_FAILURE; + *aMore = more; + return rv; + } + aRecord.Append(line); + + // read until END:VCARD + do { + if (!more) { + IMPORT_LOG0("*** Expected case-insensitive END:VCARD at start of vCard\n"); + rv = NS_ERROR_FAILURE; + break; + } + rv = aLineStream->ReadLine(line, &more); + aRecord.AppendLiteral(MSG_LINEBREAK); + aRecord.Append(line); + } while (!line.LowerCaseEqualsLiteral("end:vcard")); + + *aMore = more; + return rv; +} diff --git a/mailnews/import/vcard/src/nsVCardAddress.h b/mailnews/import/vcard/src/nsVCardAddress.h new file mode 100644 index 000000000..bc5e2bd06 --- /dev/null +++ b/mailnews/import/vcard/src/nsVCardAddress.h @@ -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/. */ + +#ifndef nsVCardAddress_h__ +#define nsVCardAddress_h__ + +#include "mozilla/Logging.h" + +extern PRLogModuleInfo *VCARDLOGMODULE; // Logging module + +#define IMPORT_LOG0(x) MOZ_LOG(VCARDLOGMODULE, mozilla::LogLevel::Debug, (x)) +#define IMPORT_LOG1(x, y) MOZ_LOG(VCARDLOGMODULE, mozilla::LogLevel::Debug, (x, y)) +#define IMPORT_LOG2(x, y, z) MOZ_LOG(VCARDLOGMODULE, mozilla::LogLevel::Debug, (x, y, z)) +#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(VCARDLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d)) + +class nsIAddrDatabase; +class nsIFile; +class nsILineInputStream; + +class nsVCardAddress { +public: + nsVCardAddress(); + virtual ~nsVCardAddress(); + + nsresult ImportAddresses( + bool *pAbort, + const char16_t *pName, + nsIFile *pSrc, + nsIAddrDatabase *pDb, + nsString& errors, + uint32_t *pProgress); + +private: + static nsresult ReadRecord( + nsILineInputStream *aLineStream, nsCString &aRecord, bool *aMore); +}; + +#endif /* nsVCardAddress_h__ */ + diff --git a/mailnews/import/vcard/src/nsVCardImport.cpp b/mailnews/import/vcard/src/nsVCardImport.cpp new file mode 100644 index 000000000..6081c36d7 --- /dev/null +++ b/mailnews/import/vcard/src/nsVCardImport.cpp @@ -0,0 +1,398 @@ +/* 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/. */ + +/* + VCard import addressbook interfaces +*/ +#include "nscore.h" +#include "nsIAddrDatabase.h" +#include "nsIFile.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportFieldMap.h" +#include "nsIImportGeneric.h" +#include "nsIMutableArray.h" +#include "nsCOMPtr.h" +#include "nsIImportService.h" +#include "nsIFile.h" +#include "nsImportStringBundle.h" +#include "nsMsgUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsTextFormatter.h" +#include "nsVCardAddress.h" +#include "nsVCardImport.h" + +PRLogModuleInfo *VCARDLOGMODULE = nullptr; +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); + +class ImportVCardAddressImpl : public nsIImportAddressBooks +{ +public: + ImportVCardAddressImpl(nsIStringBundle* aStringBundle); + + static nsresult Create( + nsIImportAddressBooks** aImport, nsIStringBundle* aStringBundle); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportAddressBooks interface + + // TODO: support multiple vCard files in future - shouldn't be too hard, + // since you just import each file in turn. + NS_IMETHOD GetSupportsMultiple(bool *_retval) override + { *_retval = false; return NS_OK;} + + NS_IMETHOD GetAutoFind(char16_t **description, bool *_retval) override; + + NS_IMETHOD GetNeedsFieldMap(nsIFile *location, bool *_retval) override + { *_retval = false; return NS_OK;} + + NS_IMETHOD GetDefaultLocation( + nsIFile **location, bool *found, bool *userVerify) override; + + NS_IMETHOD FindAddressBooks(nsIFile *location, nsIArray **_retval) override; + + NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap) override + { return NS_ERROR_FAILURE;} + + NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source, + nsIAddrDatabase *destination, + nsIImportFieldMap *fieldMap, + nsISupports *aSupportService, + char16_t **errorLog, + char16_t **successLog, + bool *fatalError) override; + + NS_IMETHOD GetImportProgress(uint32_t *_retval) override; + + NS_IMETHOD GetSampleData(int32_t index, bool *pFound, char16_t **pStr) override + { return NS_ERROR_FAILURE;} + + NS_IMETHOD SetSampleLocation(nsIFile *) override + { return NS_ERROR_FAILURE; } + +private: + virtual ~ImportVCardAddressImpl(); + static void ReportSuccess( + nsString& name, nsString *pStream, nsIStringBundle* pBundle); + static void SetLogs( + nsString& success, nsString& error, + char16_t **pError, char16_t **pSuccess); + static void ReportError( + const char *errorName, nsString& name, nsString *pStream, + nsIStringBundle* pBundle); + +private: + nsVCardAddress m_vCard; + nsCOMPtr<nsIFile> m_fileLoc; + uint32_t m_bytesImported; + nsCOMPtr<nsIStringBundle> m_notProxyBundle; +}; + +nsVCardImport::nsVCardImport() +{ + if (!VCARDLOGMODULE) + VCARDLOGMODULE = PR_NewLogModule("IMPORT"); + + nsImportStringBundle::GetStringBundle( + VCARDIMPORT_MSGS_URL, getter_AddRefs(m_stringBundle)); + + IMPORT_LOG0("nsVCardImport Module Created\n"); +} + +nsVCardImport::~nsVCardImport() +{ + IMPORT_LOG0("nsVCardImport Module Deleted\n"); +} + +NS_IMPL_ISUPPORTS(nsVCardImport, nsIImportModule) + +NS_IMETHODIMP nsVCardImport::GetName(char16_t **name) +{ + NS_ENSURE_ARG_POINTER(name); + *name = nsImportStringBundle::GetStringByName( + "vCardImportName", m_stringBundle); + return NS_OK; +} + +NS_IMETHODIMP nsVCardImport::GetDescription(char16_t **name) +{ + NS_ENSURE_ARG_POINTER(name); + *name = nsImportStringBundle::GetStringByName( + "vCardImportDescription", m_stringBundle); + return NS_OK; +} + +NS_IMETHODIMP nsVCardImport::GetSupports(char **supports) +{ + NS_ENSURE_ARG_POINTER(supports); + *supports = strdup(NS_IMPORT_ADDRESS_STR); + return NS_OK; +} + +NS_IMETHODIMP nsVCardImport::GetSupportsUpgrade(bool *pUpgrade) +{ + NS_ENSURE_ARG_POINTER(pUpgrade); + *pUpgrade = true; + return NS_OK; +} + +NS_IMETHODIMP nsVCardImport::GetImportInterface( + const char *pImportType, nsISupports **ppInterface) +{ + NS_ENSURE_ARG_POINTER(pImportType); + NS_ENSURE_ARG_POINTER(ppInterface); + *ppInterface = nullptr; + if (!strcmp(pImportType, "addressbook")) { + nsresult rv; + // create the nsIImportMail interface and return it! + nsIImportAddressBooks *pAddress = nullptr; + nsIImportGeneric *pGeneric = nullptr; + rv = ImportVCardAddressImpl::Create(&pAddress, m_stringBundle); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImportService> impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericAddressBooks(&pGeneric); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("addressInterface", pAddress); + rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface); + } + } + } + NS_IF_RELEASE(pAddress); + NS_IF_RELEASE(pGeneric); + return rv; + } + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult ImportVCardAddressImpl::Create( + nsIImportAddressBooks** aImport, nsIStringBundle* aStringBundle) +{ + NS_ENSURE_ARG_POINTER(aImport); + *aImport = new ImportVCardAddressImpl(aStringBundle); + if (!*aImport) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aImport); + return NS_OK; +} + +ImportVCardAddressImpl::ImportVCardAddressImpl( + nsIStringBundle* aStringBundle) : m_notProxyBundle(aStringBundle) +{ +} + +ImportVCardAddressImpl::~ImportVCardAddressImpl() +{ +} + +NS_IMPL_ISUPPORTS(ImportVCardAddressImpl, nsIImportAddressBooks) + +NS_IMETHODIMP ImportVCardAddressImpl::GetAutoFind( + char16_t **addrDescription, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(addrDescription); + NS_ENSURE_ARG_POINTER(_retval); + + nsString str; + *_retval = false; + + if (!m_notProxyBundle) + return NS_ERROR_FAILURE; + + nsImportStringBundle::GetStringByName("vCardImportAddressName", m_notProxyBundle, str); + *addrDescription = ToNewUnicode(str); + return NS_OK; +} + +NS_IMETHODIMP ImportVCardAddressImpl::GetDefaultLocation( + nsIFile **ppLoc, bool *found, bool *userVerify) +{ + NS_ENSURE_ARG_POINTER(found); + NS_ENSURE_ARG_POINTER(ppLoc); + NS_ENSURE_ARG_POINTER(userVerify); + + *ppLoc = nullptr; + *found = false; + *userVerify = true; + return NS_OK; +} + +NS_IMETHODIMP ImportVCardAddressImpl::FindAddressBooks( + nsIFile *pLoc, nsIArray **ppArray) +{ + NS_ENSURE_ARG_POINTER(pLoc); + NS_ENSURE_ARG_POINTER(ppArray); + + *ppArray = nullptr; + bool exists = false; + nsresult rv = pLoc->Exists(&exists); + if (NS_FAILED(rv) || !exists) + return NS_ERROR_FAILURE; + + bool isFile = false; + rv = pLoc->IsFile(&isFile); + if (NS_FAILED(rv) || !isFile) + return NS_ERROR_FAILURE; + + m_fileLoc = do_QueryInterface(pLoc); + + /* Build an address book descriptor based on the file passed in! */ + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("FAILED to allocate the nsIMutableArray\n"); + return rv; + } + + nsString name; + m_fileLoc->GetLeafName(name); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed getting leaf name of file\n"); + return rv; + } + + int32_t idx = name.RFindChar('.'); + if ((idx != -1) && (idx > 0) && ((name.Length() - idx - 1) < 5)) { + name.SetLength(idx); + } + + nsCOMPtr<nsIImportABDescriptor> desc; + nsCOMPtr<nsIImportService> impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to obtain the import service\n"); + return rv; + } + + rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc)); + if (NS_SUCCEEDED(rv)) { + int64_t sz = 0; + pLoc->GetFileSize(&sz); + desc->SetPreferredName(name); + desc->SetSize((uint32_t) sz); + desc->SetAbFile(m_fileLoc); + nsCOMPtr<nsISupports> pInterface(do_QueryInterface(desc, &rv)); + array->AppendElement(pInterface, false); + } + if (NS_FAILED(rv)) { + IMPORT_LOG0( + "*** Error creating address book descriptor for vCard import\n"); + return rv; + } + + array.forget(ppArray); + return NS_OK; +} + +void ImportVCardAddressImpl::ReportSuccess( + nsString& name, nsString *pStream, nsIStringBundle* pBundle) +{ + if (!pStream) + return; + + // load the success string + char16_t *pFmt = nsImportStringBundle::GetStringByName( + "vCardImportAddressSuccess", pBundle); + + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + NS_Free(pFmt); + pStream->Append(char16_t('\n')); +} + +void ImportVCardAddressImpl::ReportError( + const char *errorName, nsString& name, nsString *pStream, + nsIStringBundle* pBundle) +{ + if (!pStream) + return; + + // load the error string + char16_t *pFmt = nsImportStringBundle::GetStringByName(errorName, pBundle); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + NS_Free(pFmt); + pStream->Append(char16_t('\n')); +} + +void ImportVCardAddressImpl::SetLogs( + nsString& success, nsString& error, + char16_t **pError, char16_t **pSuccess) +{ + if (pError) + *pError = ToNewUnicode(error); + if (pSuccess) + *pSuccess = ToNewUnicode(success); +} + +NS_IMETHODIMP ImportVCardAddressImpl::ImportAddressBook( + nsIImportABDescriptor *pSource, + nsIAddrDatabase *pDestination, + nsIImportFieldMap *fieldMap, + nsISupports *aSupportService, + char16_t ** pErrorLog, + char16_t ** pSuccessLog, + bool * fatalError) +{ + NS_ENSURE_ARG_POINTER(pSource); + NS_ENSURE_ARG_POINTER(pDestination); + NS_ENSURE_ARG_POINTER(fatalError); + + if (!m_notProxyBundle) + return NS_ERROR_FAILURE; + + m_bytesImported = 0; + nsString success, error; + bool addrAbort = false; + nsString name; + pSource->GetPreferredName(name); + + uint32_t addressSize = 0; + pSource->GetSize(&addressSize); + if (addressSize == 0) { + IMPORT_LOG0("Address book size is 0, skipping import.\n"); + ReportSuccess(name, &success, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_OK; + } + + nsCOMPtr<nsIFile> inFile; + if (NS_FAILED(pSource->GetAbFile(getter_AddRefs(inFile)))) { + ReportError("vCardImportAddressBadSourceFile", name, &error, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_ERROR_FAILURE; + } + + if (!aSupportService) { + IMPORT_LOG0("Missing support service to import call\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv = m_vCard.ImportAddresses( + &addrAbort, name.get(), inFile, pDestination, error, &m_bytesImported); + + if (NS_SUCCEEDED(rv) && error.IsEmpty()) { + ReportSuccess(name, &success, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + } + else { + ReportError("vCardImportAddressConvertError", name, &error, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + } + + IMPORT_LOG0("*** VCard address import done\n"); + return rv; +} + +NS_IMETHODIMP ImportVCardAddressImpl::GetImportProgress(uint32_t *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_bytesImported; + return NS_OK; +} diff --git a/mailnews/import/vcard/src/nsVCardImport.h b/mailnews/import/vcard/src/nsVCardImport.h new file mode 100644 index 000000000..3204412a2 --- /dev/null +++ b/mailnews/import/vcard/src/nsVCardImport.h @@ -0,0 +1,38 @@ +/* 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 nsVCardImport_h___ +#define nsVCardImport_h___ + +#include "nsIImportModule.h" +#include "nsIStringBundle.h" +#include "nsCOMPtr.h" + +#define NS_VCARDIMPORT_CID \ +{ /* 0EB034A3-964A-4E2F-92EBCC55D9AE9DD2 */ \ + 0x0eb034a3, 0x964a, 0x4e2f, \ + {0x92, 0xeb, 0xcc, 0x55, 0xd9, 0xae, 0x9d, 0xd2}} + +#define VCARDIMPORT_MSGS_URL "chrome://messenger/locale/vCardImportMsgs.properties" + +class nsVCardImport : public nsIImportModule +{ +public: + + nsVCardImport(); + + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////////////////////// + // we suppport the nsIImportModule interface + //////////////////////////////////////////////////////////////////////////////////////// + + NS_DECL_NSIIMPORTMODULE + +protected: + virtual ~nsVCardImport(); + nsCOMPtr<nsIStringBundle> m_stringBundle; +}; + +#endif /* nsVCardImport_h___ */ diff --git a/mailnews/import/winlivemail/WMDebugLog.h b/mailnews/import/winlivemail/WMDebugLog.h new file mode 100644 index 000000000..c565880a5 --- /dev/null +++ b/mailnews/import/winlivemail/WMDebugLog.h @@ -0,0 +1,20 @@ +/* -*- 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 WMDebugLog_h___ +#define WMDebugLog_h___ + +// Use PR_LOG for logging. +#include "mozilla/Logging.h" +extern PRLogModuleInfo *WMLOGMODULE; // Logging module + +#define IMPORT_LOG0(x) MOZ_LOG(WMLOGMODULE, mozilla::LogLevel::Debug, (x)) +#define IMPORT_LOG1(x, y) MOZ_LOG(WMLOGMODULE, mozilla::LogLevel::Debug, (x, y)) +#define IMPORT_LOG2(x, y, z) MOZ_LOG(WMLOGMODULE, mozilla::LogLevel::Debug, (x, y, z)) +#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(WMLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d)) + + + +#endif /* WMDebugLog_h___ */ diff --git a/mailnews/import/winlivemail/moz.build b/mailnews/import/winlivemail/moz.build new file mode 100644 index 000000000..cb69b1548 --- /dev/null +++ b/mailnews/import/winlivemail/moz.build @@ -0,0 +1,14 @@ +# 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 += [ + 'nsWMImport.cpp', + 'nsWMSettings.cpp', + 'nsWMStringBundle.cpp', + 'nsWMUtils.cpp', +] + +FINAL_LIBRARY = 'import' + diff --git a/mailnews/import/winlivemail/nsWMImport.cpp b/mailnews/import/winlivemail/nsWMImport.cpp new file mode 100644 index 000000000..f9795816a --- /dev/null +++ b/mailnews/import/winlivemail/nsWMImport.cpp @@ -0,0 +1,248 @@ +/* -*- 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/. */ + + +/* + + Windows Live Mail (Win32) import mail and addressbook interfaces + +*/ +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsMsgUtils.h" +#include "nsIServiceManager.h" +#include "nsIImportService.h" +#include "nsWMImport.h" +#include "nsIMemory.h" +#include "nsIImportService.h" +#include "nsIImportMail.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIImportGeneric.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportFieldMap.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIOutputStream.h" +#include "nsIAddrDatabase.h" +#include "nsWMSettings.h" +#include "nsTextFormatter.h" +#include "nsWMStringBundle.h" +#include "nsIStringBundle.h" +#include "nsUnicharUtils.h" + +#include "WMDebugLog.h" + +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); +PRLogModuleInfo *WMLOGMODULE = nullptr; + +class ImportWMMailImpl : public nsIImportMail +{ +public: + ImportWMMailImpl(); + + static nsresult Create(nsIImportMail** aImport); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportmail interface + + /* void GetDefaultLocation (out nsIFile location, out boolean found, out boolean userVerify); */ + NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found, bool *userVerify); + + /* nsIArray FindMailboxes (in nsIFile location); */ + NS_IMETHOD FindMailboxes(nsIFile *location, nsIArray **_retval); + + NS_IMETHOD ImportMailbox(nsIImportMailboxDescriptor *source, + nsIMsgFolder *dstFolder, + char16_t **pErrorLog, char16_t **pSuccessLog, + bool *fatalError); + + /* unsigned long GetImportProgress (); */ + NS_IMETHOD GetImportProgress(uint32_t *_retval); + + NS_IMETHOD TranslateFolderName(const nsAString & aFolderName, nsAString & _retval); + +public: + static void ReportSuccess(nsString& name, int32_t count, nsString *pStream); + static void ReportError(int32_t errorNum, nsString& name, nsString *pStream); + static void AddLinebreak(nsString *pStream); + static void SetLogs(nsString& success, nsString& error, char16_t **pError, char16_t **pSuccess); + +private: + virtual ~ImportWMMailImpl(); + uint32_t m_bytesDone; +}; + +nsWMImport::nsWMImport() +{ + // Init logging module. + if (!WMLOGMODULE) + WMLOGMODULE = PR_NewLogModule("IMPORT"); + IMPORT_LOG0("nsWMImport Module Created\n"); + nsWMStringBundle::GetStringBundle(); +} + +nsWMImport::~nsWMImport() +{ + IMPORT_LOG0("nsWMImport Module Deleted\n"); +} + +NS_IMPL_ISUPPORTS(nsWMImport, nsIImportModule) + +NS_IMETHODIMP nsWMImport::GetName(char16_t **name) +{ + NS_ENSURE_ARG_POINTER(name); + // nsString title = "Windows Live Mail"; + // *name = ToNewUnicode(title); + *name = nsWMStringBundle::GetStringByID(WMIMPORT_NAME); + + return NS_OK; +} + +NS_IMETHODIMP nsWMImport::GetDescription(char16_t **name) +{ + NS_ENSURE_ARG_POINTER(name); + + // nsString desc = "Windows Live Mail mail and address books"; + // *name = ToNewUnicode(desc); + *name = nsWMStringBundle::GetStringByID(WMIMPORT_DESCRIPTION); + return NS_OK; +} + +NS_IMETHODIMP nsWMImport::GetSupports(char **supports) +{ + NS_PRECONDITION(supports != nullptr, "null ptr"); + if (! supports) + return NS_ERROR_NULL_POINTER; + + *supports = strdup(kWMSupportsString); + return NS_OK; +} + +NS_IMETHODIMP nsWMImport::GetSupportsUpgrade(bool *pUpgrade) +{ + NS_PRECONDITION(pUpgrade != nullptr, "null ptr"); + if (! pUpgrade) + return NS_ERROR_NULL_POINTER; + + *pUpgrade = true; + return NS_OK; +} + +NS_IMETHODIMP nsWMImport::GetImportInterface(const char *pImportType, + nsISupports **ppInterface) +{ + NS_ENSURE_ARG_POINTER(pImportType); + NS_ENSURE_ARG_POINTER(ppInterface); + + *ppInterface = nullptr; + nsresult rv; + + if (!strcmp(pImportType, "settings")) { + nsIImportSettings *pSettings = nullptr; + rv = nsWMSettings::Create(&pSettings); + if (NS_SUCCEEDED(rv)) { + pSettings->QueryInterface(kISupportsIID, (void **)ppInterface); + } + NS_IF_RELEASE(pSettings); + return rv; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +///////////////////////////////////////////////////////////////////////////////// +nsresult ImportWMMailImpl::Create(nsIImportMail** aImport) +{ + NS_ENSURE_ARG_POINTER(aImport); + *aImport = new ImportWMMailImpl(); + NS_ENSURE_TRUE(*aImport, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(*aImport); + return NS_OK; +} + +ImportWMMailImpl::ImportWMMailImpl() +{ +} + +ImportWMMailImpl::~ImportWMMailImpl() +{ +} + +NS_IMPL_ISUPPORTS(ImportWMMailImpl, nsIImportMail) + +NS_IMETHODIMP ImportWMMailImpl::TranslateFolderName(const nsAString & aFolderName, nsAString & _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP ImportWMMailImpl::GetDefaultLocation(nsIFile **ppLoc, bool *found, + bool *userVerify) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP ImportWMMailImpl::FindMailboxes(nsIFile *pLoc, + nsIArray **ppArray) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +void ImportWMMailImpl::AddLinebreak(nsString *pStream) +{ + if (pStream) + pStream->Append(char16_t('\n')); +} + +void ImportWMMailImpl::ReportSuccess(nsString& name, int32_t count, nsString *pStream) +{ + if (!pStream) + return; + // load the success string + char16_t *pFmt = nsWMStringBundle::GetStringByID(WMIMPORT_MAILBOX_SUCCESS); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get(), count); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + nsWMStringBundle::FreeString(pFmt); + AddLinebreak(pStream); +} + +void ImportWMMailImpl::ReportError(int32_t errorNum, nsString& name, nsString *pStream) +{ + if (!pStream) + return; + // load the error string + char16_t *pFmt = nsWMStringBundle::GetStringByID(errorNum); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + nsWMStringBundle::FreeString(pFmt); + AddLinebreak(pStream); +} + +void ImportWMMailImpl::SetLogs(nsString& success, nsString& error, + char16_t **pError, char16_t **pSuccess) +{ + if (pError) + *pError = ToNewUnicode(error); + if (pSuccess) + *pSuccess = ToNewUnicode(success); +} + +NS_IMETHODIMP ImportWMMailImpl::ImportMailbox(nsIImportMailboxDescriptor *pSource, + nsIMsgFolder *pDstFolder, + char16_t **pErrorLog, + char16_t **pSuccessLog, + bool *fatalError) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP ImportWMMailImpl::GetImportProgress(uint32_t *pDoneSoFar) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/mailnews/import/winlivemail/nsWMImport.h b/mailnews/import/winlivemail/nsWMImport.h new file mode 100644 index 000000000..60b34047c --- /dev/null +++ b/mailnews/import/winlivemail/nsWMImport.h @@ -0,0 +1,38 @@ +/* -*- 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 nsWMImport_h___ +#define nsWMImport_h___ + +#include "nsIImportModule.h" +#include "nsCOMPtr.h" + +#define NS_WMIMPORT_CID \ +{ /* 42bc82bc-8e9f-4597-8b6e-e529daaf3af1 */ \ + 0x42bc82bc, 0x8e9f, 0x4597, \ + {0x8b, 0x6e, 0xe5, 0x29, 0xda, 0xaf, 0x3a, 0xf1}} + +// currently only support setting import +#define kWMSupportsString NS_IMPORT_SETTINGS_STR + +class nsWMImport : public nsIImportModule +{ +public: + + nsWMImport(); + + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////////////////////// + // we suppport the nsIImportModule interface + //////////////////////////////////////////////////////////////////////////////////////// + + NS_DECL_NSIIMPORTMODULE + +protected: + virtual ~nsWMImport(); +}; + +#endif /* nsWMImport_h___ */ diff --git a/mailnews/import/winlivemail/nsWMSettings.cpp b/mailnews/import/winlivemail/nsWMSettings.cpp new file mode 100644 index 000000000..383a31bb8 --- /dev/null +++ b/mailnews/import/winlivemail/nsWMSettings.cpp @@ -0,0 +1,758 @@ +/* -*- 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/. */ + +/* + + Windows Live Mail (Win32) settings + +*/ + +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsMsgUtils.h" +#include "nsWMImport.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAccount.h" +#include "nsIImportSettings.h" +#include "nsWMSettings.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsMsgI18N.h" +#include "nsISmtpService.h" +#include "nsISmtpServer.h" +#include "nsWMStringBundle.h" +#include "WMDebugLog.h" +#include "nsIPop3IncomingServer.h" +#include "nsIImapIncomingServer.h" +#include "nsINntpIncomingServer.h" +#include "stdlib.h" +#include "nsIFile.h" +#include "nsISimpleEnumerator.h" +#include "nsIMutableArray.h" +#include "nsIDOMDocument.h" +#include "nsNetUtil.h" +#include "nsIDOMNodeList.h" +#include "nsIFileStreams.h" +#include "nsIDOMParser.h" +#include "nsIDOMElement.h" +#include "nsTArray.h" +#include <windows.h> +#include "nsIWindowsRegKey.h" +#include "nsCOMArray.h" +#include "nsWMUtils.h" + +class WMSettings { +public: + static bool DoImport(nsIMsgAccount **ppAccount); + static bool DoIMAPServer(nsIMsgAccountManager *pMgr, + nsIDOMDocument *xmlDoc, + const nsString& serverName, + nsIMsgAccount **ppAccount); + static bool DoPOP3Server(nsIMsgAccountManager *pMgr, + nsIDOMDocument *xmlDoc, + const nsString& serverName, + nsIMsgAccount **ppAccount); + static bool DoNNTPServer(nsIMsgAccountManager *pMgr, + nsIDOMDocument *xmlDoc, + const nsString& serverName, + nsIMsgAccount **ppAccount); + static void SetIdentities(nsIMsgAccountManager *pMgr, nsIMsgAccount *pAcc, + nsIDOMDocument *xmlDoc, nsAutoString &userName, + int32_t authMethodIncoming, bool isNNTP); + static void SetSmtpServer(nsIDOMDocument *xmlDoc, nsIMsgIdentity *id, + nsAutoString& inUserName, int32_t authMethodIncoming); +}; + +static int32_t checkNewMailTime;// WM global setting, let's default to 30 +static bool checkNewMail; // WM global setting, let's default to false + // This won't cause unwanted autodownloads- + // user can set prefs after import + +//////////////////////////////////////////////////////////////////////// +nsresult nsWMSettings::Create(nsIImportSettings** aImport) +{ + NS_PRECONDITION(aImport != nullptr, "null ptr"); + if (! aImport) + return NS_ERROR_NULL_POINTER; + + *aImport = new nsWMSettings(); + if (! *aImport) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aImport); + return NS_OK; +} + +nsWMSettings::nsWMSettings() +{ +} + +nsWMSettings::~nsWMSettings() +{ +} + +NS_IMPL_ISUPPORTS(nsWMSettings, nsIImportSettings) + +NS_IMETHODIMP nsWMSettings::AutoLocate(char16_t **description, + nsIFile **location, bool *_retval) +{ + NS_PRECONDITION(description != nullptr, "null ptr"); + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!description || !_retval) + return NS_ERROR_NULL_POINTER; + + *description = nsWMStringBundle::GetStringByID(WMIMPORT_NAME); + *_retval = false; + + if (location) + *location = nullptr; + nsCOMPtr<nsIWindowsRegKey> key; + if (NS_SUCCEEDED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) + *_retval = true; + + return NS_OK; +} + +NS_IMETHODIMP nsWMSettings::SetLocation(nsIFile *location) +{ + return NS_OK; +} + +NS_IMETHODIMP nsWMSettings::Import(nsIMsgAccount **localMailAccount, + bool *_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + + if (WMSettings::DoImport(localMailAccount)) { + *_retval = true; + IMPORT_LOG0("Settings import appears successful\n"); + } + else { + *_retval = false; + IMPORT_LOG0("Settings import returned FALSE\n"); + } + + return NS_OK; +} + +bool WMSettings::DoImport(nsIMsgAccount **ppAccount) +{ + // do the windows registry stuff first + nsCOMPtr<nsIWindowsRegKey> key; + if (NS_FAILED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) { + IMPORT_LOG0("*** Error finding Windows Live Mail registry account keys\n"); + return false; + } + // 'poll for messages' setting in WM is a global setting-Like OE + // for all accounts dword ==0xffffffff for don't poll else 1/60000 = minutes + checkNewMailTime = 30; + checkNewMail = false; + + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> subKey; + if (NS_SUCCEEDED(key->OpenChild(NS_LITERAL_STRING("mail"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE, + getter_AddRefs(subKey)))) { + uint32_t dwordResult = -1; + rv = subKey->ReadIntValue(NS_LITERAL_STRING("Poll For Mail"), &dwordResult); // reg_dword + subKey->Close(); + if (NS_SUCCEEDED(rv) && dwordResult != -1){ + checkNewMail = true; + checkNewMailTime = dwordResult / 60000; + } + } + // these are in main windowsmail key and if they don't exist-not to worry + // (less than 64 chars) e.g. account{4A18B81E-83CA-472A-8D7F-5301C0B97B8D}.oeaccount + nsAutoString defMailAcct, defNewsAcct; + key->ReadStringValue(NS_LITERAL_STRING("Default Mail Account"), defMailAcct); // ref_sz + key->ReadStringValue(NS_LITERAL_STRING("Default News Account"), defNewsAcct); // ref_sz + + nsCOMPtr<nsIMsgAccountManager> accMgr = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create an account manager!\n"); + return false; + } + + nsCOMArray<nsIFile> fileArray; + if (NS_FAILED(nsWMUtils::GetOEAccountFiles(fileArray))) { + IMPORT_LOG0("*** Failed to get .oeaccount file!\n"); + return false; + } + + // Loop through *.oeaccounts files looking for POP3 & IMAP & NNTP accounts + // Ignore LDAP for now! + int accounts = 0; + nsCOMPtr<nsIDOMDocument> xmlDoc; + + for (int32_t i = fileArray.Count() - 1 ; i >= 0; i--){ + nsWMUtils::MakeXMLdoc(getter_AddRefs(xmlDoc), fileArray[i]); + + nsAutoCString name; + fileArray[i]->GetNativeLeafName(name); + nsAutoString value; + nsCOMPtr<nsIMsgAccount> anAccount; + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "IMAP_Server", + value))) + if (DoIMAPServer(accMgr, xmlDoc, value, getter_AddRefs(anAccount))) + accounts++; + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "NNTP_Server", + value))) + if (DoNNTPServer(accMgr, xmlDoc, value, getter_AddRefs(anAccount))) + accounts++; + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "POP3_Server", + value))) + if (DoPOP3Server(accMgr, xmlDoc, value, getter_AddRefs(anAccount))) + accounts++; + + if (anAccount) { + nsString name; + // Is this the default account? + fileArray[i]->GetLeafName(name); + if (defMailAcct.Equals(name)) + accMgr->SetDefaultAccount(anAccount); + } + } + + // Now save the new acct info to pref file. + rv = accMgr->SaveAccountInfo(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file"); + + return accounts != 0; +} + +bool WMSettings::DoIMAPServer(nsIMsgAccountManager *pMgr, + nsIDOMDocument *xmlDoc, + const nsString& serverName, + nsIMsgAccount **ppAccount) +{ + int32_t authMethod; // Secure Password Authentication (SPA) + nsresult errorCode; + if (ppAccount) + *ppAccount = nullptr; + + nsAutoString userName, value; + if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc, + "IMAP_User_Name", + userName))) + return false; + bool result = false; + // I now have a user name/server name pair, find out if it already exists? + nsCOMPtr<nsIMsgIncomingServer> in; + nsresult rv = pMgr->FindServer(NS_ConvertUTF16toUTF8(userName), + NS_ConvertUTF16toUTF8(serverName), + NS_LITERAL_CSTRING("imap"), + getter_AddRefs(in)); + if (NS_FAILED(rv) || (in == nullptr)) { + // Create the incoming server and an account for it? + rv = pMgr->CreateIncomingServer(NS_ConvertUTF16toUTF8(userName), + NS_ConvertUTF16toUTF8(serverName), + NS_LITERAL_CSTRING("imap"), + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv) && in) { + nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(in); + if (!imapServer){ + IMPORT_LOG1("*** Failed to create nsIImapIncomingServer for %S!\n", + serverName.get()); + return false; + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "IMAP_Root_Folder", + value))) { + imapServer->SetServerDirectory(NS_ConvertUTF16toUTF8(value)); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "IMAP_Secure_Connection", + value))) { + if (value.ToInteger(&errorCode, 16)) + in->SetSocketType(nsMsgSocketType::SSL); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "IMAP_Use_Sicily", + value))) { + bool secAuth = (bool)value.ToInteger(&errorCode, 16); + authMethod = secAuth ? nsMsgAuthMethod::secure : + nsMsgAuthMethod::passwordCleartext; + in->SetAuthMethod(authMethod); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "IMAP_Port", + value))) { + in->SetPort(value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "Account_Name", + value))) { + rv = in->SetPrettyName(value); + } + in->SetDoBiff(checkNewMail); + in->SetBiffMinutes(checkNewMailTime); + + IMPORT_LOG2("Created IMAP server named: %S, userName: %S\n", + serverName.get(), userName.get()); + + // We have a server, create an account. + nsCOMPtr<nsIMsgAccount> account; + rv = pMgr->CreateAccount(getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + rv = account->SetIncomingServer(in); + + IMPORT_LOG0("Created an account and set the IMAP server " + "as the incoming server\n"); + + // Fiddle with the identities + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false); + result = true; + if (ppAccount) + account.forget(ppAccount); + } + } + } + else if (NS_SUCCEEDED(rv) && in) { + // for an existing server we create another identity, + // TB lists under 'manage identities' + nsCOMPtr<nsIMsgAccount> account; + rv = pMgr->FindAccountForServer(in, getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + IMPORT_LOG0("Created an identity and added to existing " + "IMAP incoming server\n"); + // Fiddle with the identities + in->GetAuthMethod(&authMethod); + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false); + result = true; + if (ppAccount) + account.forget(ppAccount); + } + } + else + result = true; + return result; +} + +bool WMSettings::DoPOP3Server(nsIMsgAccountManager *pMgr, + nsIDOMDocument *xmlDoc, + const nsString& serverName, + nsIMsgAccount **ppAccount) +{ + int32_t authMethod; // Secure Password Authentication (SPA) + nsresult errorCode; + if (ppAccount) + *ppAccount = nullptr; + + nsAutoString userName, value; + if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc, + "POP3_User_Name", + userName))) + return false; + bool result = false; + // I now have a user name/server name pair, find out if it already exists? + nsCOMPtr<nsIMsgIncomingServer> in; + nsresult rv = pMgr->FindServer(NS_ConvertUTF16toUTF8(userName), + NS_ConvertUTF16toUTF8(serverName), + NS_LITERAL_CSTRING("pop3"), + getter_AddRefs(in)); + if (NS_FAILED(rv) || (in == nullptr)) { + // Create the incoming server and an account for it? + rv = pMgr->CreateIncomingServer(NS_ConvertUTF16toUTF8(userName), + NS_ConvertUTF16toUTF8(serverName), + NS_LITERAL_CSTRING("pop3"), + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv) && in) { + nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(in); + if (!pop3Server){ + IMPORT_LOG1("*** Failed to create nsIPop3IncomingServer for %S!\n", + serverName.get()); + return false; + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "POP3_Secure_Connection", + value)) && + value.ToInteger(&errorCode, 16)) { + in->SetSocketType(nsMsgSocketType::SSL); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "POP3_Use_Sicily", + value))) { + bool secAuth = (bool)value.ToInteger(&errorCode, 16); + authMethod = secAuth ? nsMsgAuthMethod::secure : + nsMsgAuthMethod::passwordCleartext; + in->SetAuthMethod(authMethod); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "POP3_Port", + value))) { + in->SetPort(value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "POP3_Skip_Account", + value))) { + if (!value.IsEmpty()) + // OE:0=='Include this account when receiving mail or synchronizing'== + // TB:1==ActMgr:Server:advanced:Include this server when getting new mail + pop3Server->SetDeferGetNewMail(value.ToInteger(&errorCode, 16) == 0); + else + pop3Server->SetDeferGetNewMail(false); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "Leave_Mail_On_Server", + value))) { + pop3Server->SetLeaveMessagesOnServer((bool)value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "Remove_When_Deleted", + value))) { + pop3Server->SetDeleteMailLeftOnServer((bool)value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "Remove_When_Expired", + value))) { + pop3Server->SetDeleteByAgeFromServer((bool)value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "Expire_Days", + value))) { + pop3Server->SetNumDaysToLeaveOnServer(value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "Account_Name", + value))) { + rv = in->SetPrettyName(value); + } + + in->SetDoBiff(checkNewMail); + in->SetBiffMinutes(checkNewMailTime); + + // set local folders as the Inbox to use for this POP3 server + nsCOMPtr<nsIMsgIncomingServer> localFoldersServer; + pMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + if (!localFoldersServer) { + // XXX: We may need to move this local folder creation + // code to the generic nsImportSettings code + // if the other import modules end up needing to do this too. + // if Local Folders does not exist already, create it + rv = pMgr->CreateLocalMailAccount(); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create Local Folders!\n"); + return false; + } + pMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + } + + // now get the account for this server + nsCOMPtr<nsIMsgAccount> localFoldersAccount; + pMgr->FindAccountForServer(localFoldersServer, + getter_AddRefs(localFoldersAccount)); + if (localFoldersAccount) { + nsCString localFoldersAcctKey; + localFoldersAccount->GetKey(localFoldersAcctKey); + pop3Server->SetDeferredToAccount(localFoldersAcctKey); + } + + IMPORT_LOG2("Created POP3 server named: %S, userName: %S\n", + serverName.get(), userName.get()); + + // We have a server, create an account. + nsCOMPtr<nsIMsgAccount> account; + rv = pMgr->CreateAccount(getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + rv = account->SetIncomingServer(in); + IMPORT_LOG0("Created a new account and set the incoming " + "server to the POP3 server.\n"); + + // Fiddle with the identities + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false); + result = true; + if (ppAccount) + account.forget(ppAccount); + } + } + } + else if (NS_SUCCEEDED(rv) && in) { + IMPORT_LOG2("Existing POP3 server named: %S, userName: %S\n", + serverName.get(), userName.get()); + // for an existing server we create another identity, + // TB listed under 'manage identities' + nsCOMPtr<nsIMsgAccount> account; + rv = pMgr->FindAccountForServer(in, getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + IMPORT_LOG0("Created identity and added to existing POP3 incoming server.\n"); + // Fiddle with the identities + in->GetAuthMethod(&authMethod); + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false); + result = true; + if (ppAccount) + account.forget(ppAccount); + } + } + else + result = true; + return result; +} + +bool WMSettings::DoNNTPServer(nsIMsgAccountManager *pMgr, + nsIDOMDocument *xmlDoc, + const nsString& serverName, + nsIMsgAccount **ppAccount) +{ + int32_t authMethod; + nsresult errorCode; + if (ppAccount) + *ppAccount = nullptr; + + nsAutoString userName, value; + // this only exists if NNTP server requires it or not, anonymous login + nsWMUtils::GetValueForTag(xmlDoc, "NNTP_User_Name", userName); + bool result = false; + + // I now have a user name/server name pair, find out if it already exists? + // NNTP can have empty user name. This is wild card in findserver + nsCOMPtr<nsIMsgIncomingServer> in; + nsresult rv = pMgr->FindServer(EmptyCString(), + NS_ConvertUTF16toUTF8(serverName), + NS_LITERAL_CSTRING("nntp"), + getter_AddRefs(in)); + if (NS_FAILED(rv) || (in == nullptr)) { + // Create the incoming server and an account for it? + rv = pMgr->CreateIncomingServer(nsDependentCString(""), + NS_ConvertUTF16toUTF8(serverName), + NS_LITERAL_CSTRING("nntp"), + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv) && in) { + + nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(in); + if (!nntpServer) { + IMPORT_LOG1("*** Failed to create nsINnntpIncomingServer for %S!\n", + serverName.get()); + return false; + } + if (!userName.IsEmpty()) { // if username req'd then auth req'd + nntpServer->SetPushAuth(true); + in->SetUsername(NS_ConvertUTF16toUTF8(userName)); + } + + nsAutoString value; + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "NNTP_Port", + value))) { + in->SetPort(value.ToInteger(&errorCode, 16)); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "Account_Name", + value))) { + in->SetPrettyName(value); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "NNTP_Use_Sicily", + value))) { + bool secAuth = (bool)value.ToInteger(&errorCode, 16); + authMethod = secAuth ? nsMsgAuthMethod::secure : + nsMsgAuthMethod::passwordCleartext; + in->SetAuthMethod(authMethod); + } + + IMPORT_LOG2("Created NNTP server named: %S, userName: %S\n", + serverName.get(), userName.get()); + + // We have a server, create an account. + nsCOMPtr<nsIMsgAccount> account; + rv = pMgr->CreateAccount(getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + rv = account->SetIncomingServer(in); + + IMPORT_LOG0("Created an account and set the NNTP server " + "as the incoming server\n"); + + // Fiddle with the identities + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, true); + result = true; + if (ppAccount) + account.forget(ppAccount); + } + } + } + else if (NS_SUCCEEDED(rv) && in) { + // for the existing server... + nsCOMPtr<nsIMsgAccount> account; + rv = pMgr->FindAccountForServer(in, getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + IMPORT_LOG0("Using existing account and set the " + "NNTP server as the incoming server\n"); + // Fiddle with the identities + in->GetAuthMethod(&authMethod); + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, true); + result = true; + if (ppAccount) + account.forget(ppAccount); + } + } + else + result = true; + return result; +} + +void WMSettings::SetIdentities(nsIMsgAccountManager *pMgr, nsIMsgAccount *pAcc, + nsIDOMDocument *xmlDoc, nsAutoString &inUserName, + int32_t authMethodIncoming, bool isNNTP) +{ + // Get the relevant information for an identity + nsresult rv; + nsAutoString value; + + nsCOMPtr<nsIMsgIdentity> id; + rv = pMgr->CreateIdentity(getter_AddRefs(id)); + if (id) { + IMPORT_LOG0("Created identity and added to the account\n"); + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + isNNTP ? + "NNTP_Display_Name" : + "SMTP_Display_Name", + value))) { + id->SetFullName(value); + IMPORT_LOG1("\tname: %S\n", value.get()); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + isNNTP ? + "NNTP_Organization_Name" : + "SMTP_Organization_Name", + value))) { + id->SetOrganization(value); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + isNNTP ? + "NNTP_Email_Address" : + "SMTP_Email_Address", + value))) { + id->SetEmail(NS_ConvertUTF16toUTF8(value)); + IMPORT_LOG1("\temail: %S\n", value.get()); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + isNNTP ? + "NNTP_Reply_To_Email_Address" : + "SMTP_Reply_To_Email_Address", + value))) { + id->SetReplyTo(NS_ConvertUTF16toUTF8(value)); + } + + // Windows users are used to top style quoting. + id->SetReplyOnTop(isNNTP ? 0 : 1); + pAcc->AddIdentity(id); + } + + if (!isNNTP) // NNTP does not use SMTP in OE or TB + SetSmtpServer(xmlDoc, id, inUserName, authMethodIncoming); +} + +void WMSettings::SetSmtpServer(nsIDOMDocument *xmlDoc, nsIMsgIdentity *id, + nsAutoString& inUserName, int32_t authMethodIncoming) +{ + nsresult errorCode; + + // set the id.smtpserver accordingly + if (!id) + return; + nsCString smtpServerKey, userName; + nsAutoString value, smtpName; + if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc, "SMTP_Server", smtpName))) + return; + + // first we have to calculate the smtp user name which is based on sicily + // smtp user name depends on sicily which may or not exist + int32_t useSicily = 0; + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "SMTP_Use_Sicily", + value))) { + useSicily = (int32_t)value.ToInteger(&errorCode,16); + } + switch (useSicily) { + case 1 : case 3 : + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "SMTP_User_Name", + value))) { + CopyUTF16toUTF8(value, userName); + } + else { + CopyUTF16toUTF8(inUserName, userName); + } + break; + case 2 : + CopyUTF16toUTF8(inUserName, userName); + break; + default : + break; // initial userName == "" + } + + nsresult rv; + nsCOMPtr<nsISmtpService> + smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && smtpService) { + nsCOMPtr<nsISmtpServer> extgServer; + // don't try to make another server + // regardless if username doesn't match + rv = smtpService->FindServer(userName.get(), + NS_ConvertUTF16toUTF8(smtpName).get(), + getter_AddRefs(extgServer)); + if (NS_SUCCEEDED(rv) && extgServer) { + // set our account keyed to this smptserver key + extgServer->GetKey(getter_Copies(smtpServerKey)); + id->SetSmtpServerKey(smtpServerKey); + + IMPORT_LOG1("SMTP server already exists: %S\n", smtpName); + } + else { + nsCOMPtr<nsISmtpServer> smtpServer; + rv = smtpService->CreateServer(getter_AddRefs(smtpServer)); + if (NS_SUCCEEDED(rv) && smtpServer) { + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "SMTP_Port", + value))) { + smtpServer->SetPort(value.ToInteger(&errorCode,16)); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, + "SMTP_Secure_Connection", + value))) { + if (value.ToInteger(&errorCode, 16) == 1) + smtpServer->SetSocketType(nsMsgSocketType::SSL); + else + smtpServer->SetSocketType(nsMsgSocketType::plain); + } + smtpServer->SetUsername(userName); + switch (useSicily) { + case 1 : + smtpServer->SetAuthMethod(nsMsgAuthMethod::secure); + break; + case 2 : // requires SMTP authentication to use the incoming server settings + smtpServer->SetAuthMethod(authMethodIncoming); + break; + case 3 : + smtpServer->SetAuthMethod(nsMsgAuthMethod::passwordCleartext); + break; + default: + smtpServer->SetAuthMethod(nsMsgAuthMethod::none); + } + + smtpServer->SetHostname(NS_ConvertUTF16toUTF8(smtpName)); + + smtpServer->GetKey(getter_Copies(smtpServerKey)); + id->SetSmtpServerKey(smtpServerKey); + + IMPORT_LOG1("Created new SMTP server: %S\n", smtpName); + } + } + } +} diff --git a/mailnews/import/winlivemail/nsWMSettings.h b/mailnews/import/winlivemail/nsWMSettings.h new file mode 100644 index 000000000..3a17e7999 --- /dev/null +++ b/mailnews/import/winlivemail/nsWMSettings.h @@ -0,0 +1,22 @@ +/* -*- 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 nsWMSettings_h___ +#define nsWMSettings_h___ + +#include "nsIImportSettings.h" + +class nsWMSettings : public nsIImportSettings { +public: + nsWMSettings(); + static nsresult Create(nsIImportSettings** aImport); + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTSETTINGS + +private: + virtual ~nsWMSettings(); +}; + +#endif /* nsWMSettings_h___ */ diff --git a/mailnews/import/winlivemail/nsWMStringBundle.cpp b/mailnews/import/winlivemail/nsWMStringBundle.cpp new file mode 100644 index 000000000..8edd21513 --- /dev/null +++ b/mailnews/import/winlivemail/nsWMStringBundle.cpp @@ -0,0 +1,71 @@ +/* -*- 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 "prprf.h" +#include "prmem.h" +#include "nsCOMPtr.h" +#include "nsMsgUtils.h" +#include "nsIStringBundle.h" +#include "nsWMStringBundle.h" +#include "nsIServiceManager.h" +#include "nsIURI.h" +#include "mozilla/Services.h" + +#define WM_MSGS_URL "chrome://messenger/locale/wmImportMsgs.properties" + +nsIStringBundle * nsWMStringBundle::m_pBundle = nullptr; + +nsIStringBundle *nsWMStringBundle::GetStringBundle(void) +{ + if (m_pBundle) + return m_pBundle; + + char* propertyURL = WM_MSGS_URL; + nsIStringBundle* sBundle = nullptr; + + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + if (sBundleService) { + sBundleService->CreateBundle(propertyURL, &sBundle); + } + + m_pBundle = sBundle; + + return sBundle; +} + +void nsWMStringBundle::GetStringByID(int32_t stringID, nsString& result) +{ + char16_t *ptrv = GetStringByID(stringID); + result = ptrv; + FreeString(ptrv); +} + +char16_t *nsWMStringBundle::GetStringByID(int32_t stringID) +{ + if (!m_pBundle) + m_pBundle = GetStringBundle(); + + if (m_pBundle) { + char16_t *ptrv = nullptr; + nsresult rv = m_pBundle->GetStringFromID(stringID, &ptrv); + + if (NS_SUCCEEDED(rv) && ptrv) + return ptrv; + } + + nsString resultString; + resultString.AppendLiteral("[StringID "); + resultString.AppendInt(stringID); + resultString.AppendLiteral("?]"); + + return ToNewUnicode(resultString); +} + +void nsWMStringBundle::Cleanup(void) +{ + if (m_pBundle) + m_pBundle->Release(); + m_pBundle = nullptr; +} diff --git a/mailnews/import/winlivemail/nsWMStringBundle.h b/mailnews/import/winlivemail/nsWMStringBundle.h new file mode 100644 index 000000000..414d66435 --- /dev/null +++ b/mailnews/import/winlivemail/nsWMStringBundle.h @@ -0,0 +1,38 @@ +/* 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 _nsWMStringBundle_H__ +#define _nsWMStringBundle_H__ + +#include "nsStringGlue.h" + +class nsIStringBundle; + +class nsWMStringBundle { +public: + static char16_t * GetStringByID(int32_t stringID); + static void GetStringByID(int32_t stringID, nsString& result); + static nsIStringBundle * GetStringBundle(void); // don't release + static void FreeString(char16_t *pStr) { NS_Free(pStr);} + static void Cleanup(void); + +private: + static nsIStringBundle * m_pBundle; +}; + + + +#define WMIMPORT_NAME 2000 +#define WMIMPORT_DESCRIPTION 2001 +#define WMIMPORT_MAILBOX_SUCCESS 2002 +#define WMIMPORT_MAILBOX_BADPARAM 2003 +#define WMIMPORT_MAILBOX_BADSOURCEFILE 2004 +#define WMIMPORT_MAILBOX_CONVERTERROR 2005 +#define WMIMPORT_DEFAULT_NAME 2006 +#define WMIMPORT_AUTOFIND 2007 +#define WMIMPORT_ADDRESS_SUCCESS 2008 +#define WMIMPORT_ADDRESS_CONVERTERROR 2009 +#define WMIMPORT_ADDRESS_BADPARAM 2010 + +#endif /* _nsWMStringBundle_H__ */ diff --git a/mailnews/import/winlivemail/nsWMUtils.cpp b/mailnews/import/winlivemail/nsWMUtils.cpp new file mode 100644 index 000000000..3e60597b8 --- /dev/null +++ b/mailnews/import/winlivemail/nsWMUtils.cpp @@ -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/. */ + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsNetCID.h" +#include "nsStringGlue.h" +#include "nsWMUtils.h" +#include "nsIDOMDocument.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMParser.h" +#include "nsIFileStreams.h" +#include "nsIFile.h" +#include "nsISimpleEnumerator.h" +#include "WMDebugLog.h" +#include "prio.h" + +nsresult +nsWMUtils::FindWMKey(nsIWindowsRegKey **aKey) +{ + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("Software\\Microsoft\\Windows Live Mail"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(*aKey = key); + return rv; + } + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("Software\\Microsoft\\Windows Mail"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + key.forget(aKey); + return rv; +} + +nsresult +nsWMUtils::GetRootFolder(nsIFile **aRootFolder) +{ + nsCOMPtr<nsIWindowsRegKey> key; + if (NS_FAILED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) { + IMPORT_LOG0("*** Error finding Windows Live Mail registry account keys\n"); + return NS_ERROR_NOT_AVAILABLE; + } + // This is essential to proceed; it is the location on disk of xml-type account files; + // it is in reg_expand_sz so it will need expanding to absolute path. + nsString storeRoot; + nsresult rv = key->ReadStringValue(NS_LITERAL_STRING("Store Root"), storeRoot); + key->Close(); // Finished with windows registry key. We do not want to return before this closing + if (NS_FAILED(rv) || storeRoot.IsEmpty()) { + IMPORT_LOG0("*** Error finding Windows Live Mail Store Root\n"); + return rv; + } + + uint32_t size = ::ExpandEnvironmentStringsW((LPCWSTR)storeRoot.get(), nullptr, 0); + nsString expandedStoreRoot; + expandedStoreRoot.SetLength(size - 1); + if (expandedStoreRoot.Length() != size - 1) + return NS_ERROR_FAILURE; + ::ExpandEnvironmentStringsW((LPCWSTR)storeRoot.get(), + (LPWSTR)expandedStoreRoot.BeginWriting(), + size); + storeRoot = expandedStoreRoot; + + nsCOMPtr<nsIFile> rootFolder(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = rootFolder->InitWithPath(storeRoot); + NS_ENSURE_SUCCESS(rv, rv); + + rootFolder.forget(aRootFolder); + + return NS_OK; +} + +nsresult +nsWMUtils::GetOEAccountFiles(nsCOMArray<nsIFile> &aFileArray) +{ + nsCOMPtr<nsIFile> rootFolder; + + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + return GetOEAccountFilesInFolder(rootFolder, aFileArray); +} + +nsresult +nsWMUtils::GetOEAccountFilesInFolder(nsIFile *aFolder, + nsCOMArray<nsIFile> &aFileArray) +{ + nsCOMPtr<nsISimpleEnumerator> entries; + nsresult rv = aFolder->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_FAILED(rv) || !entries) + return NS_ERROR_FAILURE; + + bool hasMore; + while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> supports; + rv = entries->GetNext(getter_AddRefs(supports)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> file = do_QueryInterface(supports); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (isDirectory) { + GetOEAccountFilesInFolder(file, aFileArray); + } + else { + nsString name; + rv = file->GetLeafName(name); + NS_ENSURE_SUCCESS(rv, rv); + if (StringEndsWith(name, NS_LITERAL_STRING(".oeaccount"))) + aFileArray.AppendObject(file); + } + } + return NS_OK; +} + +nsresult +nsWMUtils::MakeXMLdoc(nsIDOMDocument **aXmlDoc, + nsIFile *aFile) +{ + nsresult rv; + nsCOMPtr<nsIFileInputStream> stream = + do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stream->Init(aFile, PR_RDONLY, -1, 0); + nsCOMPtr<nsIDOMParser> parser = do_CreateInstance(NS_DOMPARSER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t filesize; + aFile->GetFileSize(&filesize); + return parser->ParseFromStream(stream, nullptr, int32_t(filesize), + "application/xml", aXmlDoc); +} + +nsresult +nsWMUtils::GetValueForTag(nsIDOMDocument *aXmlDoc, + const char *aTagName, + nsAString &aValue) +{ + nsAutoString tagName; + tagName.AssignASCII(aTagName); + nsCOMPtr<nsIDOMNodeList> list; + if (NS_FAILED(aXmlDoc->GetElementsByTagName(tagName, getter_AddRefs(list)))) + return NS_ERROR_FAILURE; + nsCOMPtr<nsIDOMNode> domNode; + list->Item(0, getter_AddRefs(domNode)); + if (!domNode) + return NS_ERROR_FAILURE; + return domNode->GetTextContent(aValue); +} + diff --git a/mailnews/import/winlivemail/nsWMUtils.h b/mailnews/import/winlivemail/nsWMUtils.h new file mode 100644 index 000000000..e1bf54286 --- /dev/null +++ b/mailnews/import/winlivemail/nsWMUtils.h @@ -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/. */ + +#ifndef nsWMUtils_h___ +#define nsWMUtils_h___ + +#include <windows.h> +#include "nsIWindowsRegKey.h" + +class nsIDOMDocument; + +class nsWMUtils { +public: + static nsresult FindWMKey(nsIWindowsRegKey **aKey); + static nsresult GetRootFolder(nsIFile **aRootFolder); + static nsresult GetOEAccountFiles(nsCOMArray<nsIFile> &aFileArray); + static nsresult GetOEAccountFilesInFolder(nsIFile *aFolder, + nsCOMArray<nsIFile> &aFileArray); + static nsresult MakeXMLdoc(nsIDOMDocument **aXmlDoc, + nsIFile *aFile); + static nsresult GetValueForTag(nsIDOMDocument *aXmlDoc, + const char *aTagName, + nsAString &aValue); +}; + +#endif /* nsWMUtils_h___ */ |