/* 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 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 iniFile; nsBeckyUtils::GetMailboxINIFile(aMailbox, getter_AddRefs(iniFile)); if (iniFile) { nsCOMPtr 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 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 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 lineStream; rv = nsBeckyUtils::CreateLineInputStream(aListFile, getter_AddRefs(lineStream)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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 folderListFile; rv = nsBeckyUtils::GetFolderListFile(aDirectory, getter_AddRefs(folderListFile)); bool folderListExists = false; if (NS_SUCCEEDED(rv)) { rv = CollectMailboxesInFolderListFile(folderListFile, aDepth, aCollected); folderListExists = true; } nsCOMPtr entries; rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); NS_ENSURE_SUCCESS(rv, rv); bool more; while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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(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 mMessageFile; nsCOMPtr 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 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 parentDirectory; rv = aMailboxFile->GetParent(getter_AddRefs(parentDirectory)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr attachmentFile; rv = GetAttachmentFile(parentDirectory, aHeader, getter_AddRefs(attachmentFile)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 msgStore; nsresult rv = mFolder->GetMsgStore(getter_AddRefs(msgStore)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr lineStream; rv = nsBeckyUtils::CreateLineInputStream(mMessageFile, getter_AddRefs(lineStream)); NS_ENSURE_SUCCESS(rv, rv); bool reusable; nsCOMPtr msgHdr; nsCOMPtr 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 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 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); }