/* -*- 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 "msgCore.h"
#include "nsCOMPtr.h"
#include "nsEudoraMailbox.h"
#include "nsDirectoryServiceDefs.h"
#include "nsEudoraCompose.h"
#include "nspr.h"
#include "nsMsgMessageFlags.h"
#include "nsMailHeaders.h"
#include "nsMsgLocalFolderHdrs.h"
#include "nsIMsgFolder.h"
#include "nsIMsgHdr.h"
#include "nsIMsgPluggableStore.h"
#include "nsMsgUtils.h"
#include "nsCRT.h"
#include "nsNetUtil.h"
#include "EudoraDebugLog.h"
#include "nsISeekableStream.h"
#define kCopyBufferSize 8192
#define kMailReadBufferSize 16384
#define DATE_STR_LEN 64 // 64 bytes is plenty to hold the date header.
#define kWhitespace " \t\b\r\n"
const char *eudoraFromLine = "From - Mon Jan 1 00:00:00 1965\x0D\x0A";
#ifdef IMPORT_DEBUG
void DUMP_FILENAME(nsIFile *pFile, bool endLine);
void DUMP_FILENAME(nsIFile *pFile, bool endLine)
{
nsCString pPath;
if (pFile)
pFile->GetNativePath(pPath);
if (!pPath.IsEmpty())
IMPORT_LOG1("%s", pPath.get());
else
IMPORT_LOG0("Unknown");
if (endLine)
IMPORT_LOG0("\n");
}
// #define DONT_DELETE_EUDORA_TEMP_FILES 1
#else
#define DUMP_FILENAME(x, y)
#endif
static const char *eudoraWeekDays[7] = {
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"
};
static const char *eudoraMonths[12] = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
};
uint16_t EudoraTOCEntry::GetMozillaStatusFlags()
{
// Return the mozilla equivalent of flags that Eudora supports.
uint16_t flags = 0;
#ifndef XP_MACOSX
switch (m_State)
{
case MS_UNREAD:
flags = 0;
break;
case MS_READ:
flags = nsMsgMessageFlags::Read;
break;
case MS_REPLIED:
flags = nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied;
break;
case MS_FORWARDED:
flags = nsMsgMessageFlags::Read | nsMsgMessageFlags::Forwarded;
break;
case MS_REDIRECT:
// Redirect doesn't really mean forwarded, but forwarded
// seems to be the closest equivalent for now.
flags = nsMsgMessageFlags::Read | nsMsgMessageFlags::Forwarded;
break;
case MS_UNSENDABLE:
case MS_SENDABLE:
case MS_QUEUED:
case MS_SENT:
case MS_UNSENT:
case MS_TIME_QUEUED:
case MS_SPOOLED:
// To do: Add more sent message flag handling.
flags = 0;
break;
case MS_RECOVERED:
flags = nsMsgMessageFlags::Read;
break;
}
// Range check priority just to be sure
if (m_Priority < MSP_HIGHEST)
m_Priority = MSP_HIGHEST;
if (m_Priority > MSP_LOWEST)
m_Priority = MSP_LOWEST;
// Translate priority into format used in mozilla status flags
// (which is reversed for some reason)
flags |= (7-m_Priority) << 13;
#endif
return flags;
}
uint32_t EudoraTOCEntry::GetMozillaStatus2Flags()
{
#ifdef XP_MACOSX
return 0;
#else
// Return the mozilla equivalent of flags that Eudora supports.
uint32_t flags = 0;
if (m_Imflags & IMFLAGS_DELETED)
flags |= nsMsgMessageFlags::IMAPDeleted;
if (m_Flags & MSF_READ_RECEIPT)
flags |= nsMsgMessageFlags::MDNReportNeeded;
return flags;
#endif
}
nsEudoraMailbox::nsEudoraMailbox()
: m_fromLen(0)
{
}
nsEudoraMailbox::~nsEudoraMailbox()
{
EmptyAttachments();
}
nsresult nsEudoraMailbox::CreateTempFile(nsIFile **ppFile)
{
nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
"impmail.txt",
ppFile);
NS_ENSURE_SUCCESS(rv, rv);
return (*ppFile)->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
}
nsresult nsEudoraMailbox::DeleteFile(nsIFile *pFile)
{
bool result;
nsresult rv = NS_OK;
result = false;
pFile->Exists(&result);
if (result) {
result = false;
pFile->IsFile(&result);
if (result) {
#ifndef DONT_DELETE_EUDORA_TEMP_FILES
rv = pFile->Remove(false);
#endif
}
}
return rv;
}
#define kComposeErrorStr "X-Eudora-Compose-Error: *****" "\x0D\x0A"
#define kHTMLTag "<html>"
nsresult nsEudoraMailbox::ImportMailbox(uint32_t *pBytes, bool *pAbort,
const char16_t *pName, nsIFile *pSrc,
nsIMsgFolder *dstFolder,
int32_t *pMsgCount)
{
nsCOMPtr<nsIFile> tocFile;
nsCOMPtr<nsIInputStream> srcInputStream;
nsCOMPtr<nsIInputStream> tocInputStream;
nsCOMPtr<nsIOutputStream> mailOutputStream;
bool importWithoutToc = true;
bool deleteToc = false;
nsCOMPtr<nsIFile> mailFile;
if (pMsgCount)
*pMsgCount = 0;
nsresult rv = pSrc->GetFileSize(&m_mailSize);
rv = NS_NewLocalFileInputStream(getter_AddRefs(srcInputStream), pSrc);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> srcFile(pSrc);
// First, get the index file for this mailbox
rv = FindTOCFile(pSrc, getter_AddRefs(tocFile), &deleteToc);
if (NS_SUCCEEDED(rv) && tocFile)
{
IMPORT_LOG0("Reading euroda toc file: ");
DUMP_FILENAME(tocFile, true);
// Read the toc and import the messages
rv = ImportMailboxUsingTOC(pBytes, pAbort, srcInputStream, tocFile,
dstFolder, pMsgCount);
// clean up
if (deleteToc)
DeleteFile(tocFile);
// If we were able to import with the TOC, then we don't need to bother
// importing without the TOC.
if (NS_SUCCEEDED(rv)) {
importWithoutToc = false;
IMPORT_LOG0("Imported mailbox: "); DUMP_FILENAME(pSrc, false);
IMPORT_LOG0(" Using TOC: "); DUMP_FILENAME(tocFile, true);
}
else {
IMPORT_LOG0("*** Error importing with TOC - will import without TOC.\n");
}
}
// pSrc must be Released before returning
if (importWithoutToc) {
// The source file contains partially constructed mail messages,
// and attachments. We should first investigate if we can use the mailnews msgCompose
// stuff to do the work for us. If not we have to scan the mailboxes and do TONS
// of work to properly reconstruct the message - Eudora is so nice that it strips things
// like MIME headers, character encoding, and attachments - beautiful!
rv = pSrc->GetFileSize(&m_mailSize);
SimpleBufferTonyRCopiedOnce readBuffer;
SimpleBufferTonyRCopiedOnce headers;
SimpleBufferTonyRCopiedOnce body;
SimpleBufferTonyRCopiedOnce copy;
headers.m_convertCRs = true;
body.m_convertCRs = true;
copy.Allocate(kCopyBufferSize);
readBuffer.Allocate(kMailReadBufferSize);
ReadFileState state;
state.offset = 0;
state.size = m_mailSize;
state.pFile = pSrc;
IMPORT_LOG0("Reading mailbox\n");
if (NS_SUCCEEDED(rv))
{
nsCString defaultDate;
nsAutoCString bodyType;
IMPORT_LOG0("Reading first message\n");
nsCOMPtr<nsIOutputStream> outputStream;
nsCOMPtr<nsIMsgPluggableStore> msgStore;
rv = dstFolder->GetMsgStore(getter_AddRefs(msgStore));
NS_ENSURE_SUCCESS(rv, rv);
while (!*pAbort && NS_SUCCEEDED(rv = ReadNextMessage( &state, readBuffer, headers, body, defaultDate, bodyType, NULL))) {
if (pBytes)
*pBytes += body.m_writeOffset - 1 + headers.m_writeOffset - 1;
nsCOMPtr<nsIMsgDBHdr> msgHdr;
bool reusable;
rv = msgStore->GetNewMsgOutputStream(dstFolder, getter_AddRefs(msgHdr), &reusable,
getter_AddRefs(outputStream));
if (NS_FAILED(rv))
break;
rv = ImportMessage(headers, body, defaultDate, bodyType, outputStream, pMsgCount);
if (NS_SUCCEEDED(rv))
msgStore->FinishNewMessage(outputStream, msgHdr);
else {
msgStore->DiscardNewMessage(outputStream, msgHdr);
IMPORT_LOG0( "*** Error importing message\n");
}
if (!reusable)
outputStream->Close();
if (!readBuffer.m_bytesInBuf && (state.offset >= state.size))
break;
}
if (outputStream)
outputStream->Close();
}
else {
IMPORT_LOG0("*** Error creating file spec for composition\n");
}
}
return rv;
}
#ifdef XP_MACOSX
#define kMsgHeaderSize 220
#define kMsgFirstOffset 278
#else
#define kMsgHeaderSize 218
#define kMsgFirstOffset 104
#endif
nsresult nsEudoraMailbox::ImportMailboxUsingTOC(
uint32_t *pBytes,
bool *pAbort,
nsIInputStream *pInputStream,
nsIFile *tocFile,
nsIMsgFolder *dstFolder,
int32_t *pMsgCount)
{
nsresult rv = NS_OK;
int64_t mailSize = m_mailSize;
int64_t tocSize = 0;
uint32_t saveBytes = pBytes ? *pBytes : 0;
nsCOMPtr <nsIInputStream> tocInputStream;
rv = tocFile->GetFileSize(&tocSize);
// if the index or the mail file is empty then just
// use the original mail file.
if (!mailSize || !tocSize)
return NS_ERROR_FAILURE;
rv = NS_NewLocalFileInputStream(getter_AddRefs(tocInputStream), tocFile);
NS_ENSURE_SUCCESS(rv, rv);
SimpleBufferTonyRCopiedOnce readBuffer;
SimpleBufferTonyRCopiedOnce headers;
SimpleBufferTonyRCopiedOnce body;
SimpleBufferTonyRCopiedOnce copy;
int32_t tocOffset = kMsgFirstOffset;
EudoraTOCEntry tocEntry;
copy.Allocate(kCopyBufferSize);
readBuffer.Allocate(kMailReadBufferSize);
IMPORT_LOG0("Importing mailbox using TOC: ");
DUMP_FILENAME(tocFile, true);
nsCOMPtr<nsISeekableStream> tocSeekableStream = do_QueryInterface(tocInputStream);
nsCOMPtr<nsISeekableStream> mailboxSeekableStream = do_QueryInterface(pInputStream);
nsCOMPtr<nsIOutputStream> outputStream;
nsCOMPtr<nsIMsgPluggableStore> msgStore;
rv = dstFolder->GetMsgStore(getter_AddRefs(msgStore));
NS_ENSURE_SUCCESS(rv, rv);
while (!*pAbort && (tocOffset < (int32_t)tocSize)) {
if (NS_FAILED(rv = tocSeekableStream->Seek(nsISeekableStream::NS_SEEK_SET, tocOffset)))
break;
if (NS_FAILED(rv = ReadTOCEntry(tocInputStream, tocEntry)))
break;
nsCOMPtr<nsIMsgDBHdr> msgHdr;
bool reusable;
rv = msgStore->GetNewMsgOutputStream(dstFolder, getter_AddRefs(msgHdr), &reusable,
getter_AddRefs(outputStream));
if (NS_FAILED(rv))
break;
// Quick and dirty way to read in and parse the message the way the rest
// of the code expects.
nsCString defaultDate;
nsAutoCString bodyType;
ReadFileState state;
// Seek to the start of the email message.
mailboxSeekableStream->Seek(nsISeekableStream::NS_SEEK_SET, tocEntry.m_Offset);
// We're fudging the data to make ReadNextMessage happy. In particular
// state.size is meant to be the size of the entire file, because it's
// assumed that ReadNextMessage will actually have to parse. We know
// exactly how big the message is, so we simply set the "size" to be
// immediately where the message ends.
state.offset = tocEntry.m_Offset;
state.pInputStream = pInputStream;
state.size = state.offset + tocEntry.m_Length;
if (NS_SUCCEEDED(rv = ReadNextMessage(&state, readBuffer, headers, body, defaultDate, bodyType, &tocEntry)))
{
rv = ImportMessage(headers, body, defaultDate, bodyType, outputStream,
pMsgCount);
if (pBytes)
*pBytes += tocEntry.m_Length;
if (NS_SUCCEEDED(rv))
msgStore->FinishNewMessage(outputStream, msgHdr);
else {
msgStore->DiscardNewMessage(outputStream, msgHdr);
IMPORT_LOG0( "*** Error importing message\n");
}
}
// We currently don't consider an error from ReadNextMessage or ImportMessage to be fatal.
// Reset the error back to no error in case this is the last time through the loop.
rv = NS_OK;
tocOffset += kMsgHeaderSize;
if (!reusable)
outputStream->Close();
}
if (outputStream)
outputStream->Close();
if (NS_SUCCEEDED(rv)) {
IMPORT_LOG0(" finished\n");
}
else {
// We failed somewhere important enough that we kept the error.
// Bail on all that we imported since we'll be importing everything
// again using just the mailbox.
IMPORT_LOG0("*** Error importing mailbox using TOC: ");
// DUMP_FILENAME(pMail, true);
// Reset pBytes back to where it was before we imported this mailbox.
// This will likely result in a funky progress bar which will move
// backwards, but that's probably the best we can do to keep the
// progress accurate since we'll be re-importing the same mailbox.
if (pBytes)
*pBytes = saveBytes;
// Set the message count back to 0.
if (pMsgCount)
*pMsgCount = 0;
}
return rv;
}
nsresult nsEudoraMailbox::ReadTOCEntry(nsIInputStream *pToc, EudoraTOCEntry& tocEntry)
{
#define READ_TOC_FIELD(entry) pBuffer = (char *)&entry;\
if (NS_FAILED(pToc->Read(pBuffer, sizeof(entry), &bytesRead)) || (bytesRead != sizeof(entry)))\
return NS_ERROR_FAILURE
uint32_t bytesRead = 0;
char * pBuffer;
// Here we'll read any initial data that's in the same format on both Mac and Windows
READ_TOC_FIELD(tocEntry.m_Offset);
READ_TOC_FIELD(tocEntry.m_Length);
#ifdef XP_MACOSX
// Read Mac specific data
#else
// Read Windows specific data
int16_t x1 = 0, y1 = 0, x2 = 400, y2 = 300, tzm = 0;
int8_t nIgnoreChar;
uint8_t cJunkInfo = 0;
uint32_t ulJunkPluginID;
READ_TOC_FIELD(tocEntry.m_Seconds);
READ_TOC_FIELD(tocEntry.m_State);
READ_TOC_FIELD(tocEntry.m_Flags);
READ_TOC_FIELD(tocEntry.m_Priority);
READ_TOC_FIELD(nIgnoreChar);
READ_TOC_FIELD(tocEntry.m_Date);
READ_TOC_FIELD(tocEntry.m_lArrivalSeconds);
READ_TOC_FIELD(tocEntry.m_From);
READ_TOC_FIELD(tocEntry.m_Subject);
// We'll read but ignore window position
READ_TOC_FIELD(x1);
READ_TOC_FIELD(y1);
READ_TOC_FIELD(x2);
READ_TOC_FIELD(y2);
READ_TOC_FIELD(tocEntry.m_Label);
READ_TOC_FIELD(tocEntry.m_Hash);
READ_TOC_FIELD(tocEntry.m_UniqueMessageId);
READ_TOC_FIELD(tocEntry.m_FlagsEx);
READ_TOC_FIELD(tocEntry.m_PersonaHash);
READ_TOC_FIELD(tzm);
tocEntry.m_TimeZoneMinutes = tzm;
// IMAP specific info (present, but not useful when POP mailbox)
READ_TOC_FIELD(tocEntry.m_Imflags);
READ_TOC_FIELD(tocEntry.m_MsgSize);
// Moodwatch info - may never be worth importing
READ_TOC_FIELD(tocEntry.m_nMood);
// Get the junk score byte
READ_TOC_FIELD(cJunkInfo);
if (cJunkInfo & 0x80)
{
// If the high bit is set note that this message was manually junked
// and unset the high bit.
tocEntry.m_bManuallyJunked = true;
cJunkInfo &= 0x7F;
}
else
{
tocEntry.m_bManuallyJunked = false;
}
tocEntry.m_ucJunkScore = cJunkInfo;
READ_TOC_FIELD(ulJunkPluginID);
#endif
return NS_OK;
}
nsresult nsEudoraMailbox::ImportMessage(
SimpleBufferTonyRCopiedOnce &headers,
SimpleBufferTonyRCopiedOnce &body,
nsCString& defaultDate,
nsAutoCString& bodyType,
nsIOutputStream *pDst,
int32_t *pMsgCount)
{
nsresult rv = NS_OK;
uint32_t written = 0;
nsEudoraCompose compose;
// Unfortunately Eudora stores HTML messages in the sent folder
// without any content type header at all. If the first line of the message body is <html>
// then mark the message as html internally...See Bug #258489
if (body.m_pBuffer && (body.m_writeOffset > (int32_t)strlen(kHTMLTag)) && (strncmp(body.m_pBuffer, kHTMLTag, strlen(kHTMLTag)) == 0))
bodyType = "text/html"; // ignore whatever body type we were given...force html
compose.SetBody(body.m_pBuffer, body.m_writeOffset - 1, bodyType);
compose.SetHeaders(headers.m_pBuffer, headers.m_writeOffset - 1);
compose.SetAttachments(&m_attachments);
compose.SetDefaultDate(defaultDate);
nsCOMPtr <nsIFile> compositionFile;
rv = compose.SendTheMessage(m_mailImportLocation, getter_AddRefs(compositionFile));
if (NS_SUCCEEDED(rv)) {
nsCString fromLine(eudoraFromLine);
SimpleBufferTonyRCopiedOnce copy;
copy.Allocate(kCopyBufferSize);
/* IMPORT_LOG0("Composed message in file: "); DUMP_FILENAME(compositionFile, true); */
// copy the resulting file into the destination file!
rv = compose.CopyComposedMessage(fromLine, compositionFile, pDst, copy);
DeleteFile(compositionFile);
if (NS_FAILED(rv))
IMPORT_LOG0("*** Error copying composed message to destination mailbox\n");
if (pMsgCount)
(*pMsgCount)++;
}
else {
IMPORT_LOG0("*** Error composing message, writing raw message\n");
rv = WriteFromSep(pDst);
rv = pDst->Write(kComposeErrorStr, strlen(kComposeErrorStr), &written);
if (NS_SUCCEEDED(rv))
rv = pDst->Write(headers.m_pBuffer, headers.m_writeOffset - 1, &written);
if (NS_SUCCEEDED(rv) && (written == (headers.m_writeOffset - 1)))
rv = pDst->Write("\x0D\x0A" "\x0D\x0A", 4, &written);
if (NS_SUCCEEDED(rv) && (written == 4))
rv = pDst->Write(body.m_pBuffer, body.m_writeOffset - 1, &written);
if (NS_SUCCEEDED(rv) && (written == (body.m_writeOffset - 1))) {
rv = pDst->Write("\x0D\x0A", 2, &written);
if (written != 2)
rv = NS_ERROR_FAILURE;
}
if (NS_FAILED(rv))
IMPORT_LOG0("*** Error writing to destination mailbox\n");
}
return rv;
}
nsresult nsEudoraMailbox::ReadNextMessage(ReadFileState *pState, SimpleBufferTonyRCopiedOnce& copy,
SimpleBufferTonyRCopiedOnce& header, SimpleBufferTonyRCopiedOnce& body,
nsCString& defaultDate, nsCString& bodyType, EudoraTOCEntry *pTocEntry)
{
header.m_writeOffset = 0;
body.m_writeOffset = 0;
nsresult rv;
int32_t lineLen;
char endBuffer = 0;
lineLen = -1;
// Find the from separator - we should actually be positioned at the
// from separator, but for now, we'll verify this.
while (lineLen == -1) {
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error, FillMailBuffer FAILED in ReadNextMessage\n");
return rv;
}
lineLen = IsEudoraFromSeparator(copy.m_pBuffer + copy.m_writeOffset, copy.m_bytesInBuf - copy.m_writeOffset, defaultDate);
if (lineLen == -1) {
while ((lineLen = FindStartLine(copy)) == -1) {
copy.m_writeOffset = copy.m_bytesInBuf;
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error, FillMailBuffer FAILED in ReadNextMessage, looking for next start line\n");
return rv;
}
if (!copy.m_bytesInBuf) {
IMPORT_LOG0("*** Error, ReadNextMessage, looking for start of next line, got end of file.\n");
return NS_ERROR_FAILURE;
}
}
copy.m_writeOffset += lineLen;
lineLen = -1;
}
}
// Skip past the from line separator
while ((lineLen = FindStartLine(copy)) == -1) {
copy.m_writeOffset = copy.m_bytesInBuf;
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error, ReadNextMessage, FillMailBuffer failed looking for from sep\n");
return rv;
}
if (!copy.m_bytesInBuf) {
IMPORT_LOG0("*** Error, ReadNextMessage, end of file looking for from sep\n");
return NS_ERROR_FAILURE;
}
}
copy.m_writeOffset += lineLen;
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error, Unable to fill mail buffer after from sep.\n");
return rv;
}
// This should be the headers...
int32_t endLen = -1;
while ((endLen = IsEndHeaders(copy)) == -1) {
while ((lineLen = FindNextEndLine(copy)) == -1) {
copy.m_writeOffset = copy.m_bytesInBuf;
if (!header.Write(copy.m_pBuffer, copy.m_writeOffset)) {
IMPORT_LOG0("*** ERROR, writing headers\n");
return NS_ERROR_FAILURE;
}
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error reading message headers\n");
return rv;
}
if (!copy.m_bytesInBuf) {
IMPORT_LOG0("*** Error, end of file while reading headers\n");
return NS_ERROR_FAILURE;
}
}
copy.m_writeOffset += lineLen;
if ((copy.m_writeOffset + 4) >= copy.m_bytesInBuf) {
if (!header.Write(copy.m_pBuffer, copy.m_writeOffset)) {
IMPORT_LOG0("*** ERROR, writing headers 2\n");
return NS_ERROR_FAILURE;
}
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error reading message headers 2\n");
return rv;
}
}
}
if (!header.Write(copy.m_pBuffer, copy.m_writeOffset)) {
IMPORT_LOG0("*** Error writing final headers\n");
return NS_ERROR_FAILURE;
}
if (pTocEntry) {
// This is not the prettiest spot to stick this code, but it works and it was convenient.
char header_str[128];
// Write X-Mozilla-Status header
PR_snprintf(header_str, 128, MSG_LINEBREAK X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, pTocEntry->GetMozillaStatusFlags());
header.Write(header_str, strlen(header_str));
// Write X-Mozilla-Status2 header
PR_snprintf(header_str, 128, X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, pTocEntry->GetMozillaStatus2Flags());
header.Write(header_str, strlen(header_str));
// Format and write X-Mozilla-Keys header
nsCString keywordHdr(X_MOZILLA_KEYWORDS);
if (pTocEntry->HasEudoraLabel()) {
PR_snprintf(header_str, 128, "eudoralabel%d", pTocEntry->GetLabelNumber());
keywordHdr.Replace(sizeof(HEADER_X_MOZILLA_KEYWORDS) + 1, strlen(header_str), header_str);
}
header.Write(keywordHdr.get(), keywordHdr.Length());
}
if (!header.Write(&endBuffer, 1)) {
IMPORT_LOG0("*** Error writing header trailing null\n");
return NS_ERROR_FAILURE;
}
copy.m_writeOffset += endLen;
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error reading beginning of message body\n");
return rv;
}
EmptyAttachments();
// Get the body!
// Read one line at a time here and look for the next separator
nsCString tmp;
bool insideEudoraTags = false;
// by default we consider the body text to be plain text
bodyType = "text/plain";
while ((lineLen = IsEudoraFromSeparator(copy.m_pBuffer + copy.m_writeOffset, copy.m_bytesInBuf - copy.m_writeOffset, tmp)) == -1) {
int32_t tagLength = 0;
if (IsEudoraTag (copy.m_pBuffer + copy.m_writeOffset, copy.m_bytesInBuf - copy.m_writeOffset, insideEudoraTags, bodyType, tagLength)) {
// We don't want to keep eudora tags so skip over them.
// let's write the previous text
if (!body.Write(copy.m_pBuffer, copy.m_writeOffset)) {
IMPORT_LOG0("*** Error writing to message body\n");
return NS_ERROR_FAILURE;
}
// we want to skip over the tag...for now we are assuming the tag is always at the start of line.
copy.m_writeOffset += tagLength;
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error reading message body\n");
return rv;
}
if (!copy.m_bytesInBuf)
break;
continue;
}
// Eudora Attachment lines are always outside Eudora Tags
// so we shouldn't try to find one here
if (!insideEudoraTags) {
// Debatable is whether or not to exclude these lines from the
// text of the message, I prefer not to in case the original
// attachment is actually missing.
rv = ExamineAttachment(copy);
if (NS_FAILED(rv)) {
IMPORT_LOG0("*** Error examining attachment line\n");
return rv;
}
}
while (((lineLen = FindStartLine(copy)) == -1) && copy.m_bytesInBuf) {
copy.m_writeOffset = copy.m_bytesInBuf;
if (!body.Write(copy.m_pBuffer, copy.m_writeOffset)) {
IMPORT_LOG0("*** Error writing to message body\n");
return NS_ERROR_FAILURE;
}
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error reading message body\n");
return rv;
}
}
if (!copy.m_bytesInBuf)
break;
copy.m_writeOffset += lineLen;
// found the start of the next line
// make sure it's long enough to check for the from line
if ((copy.m_writeOffset + 2048) >= copy.m_bytesInBuf) {
if (!body.Write(copy.m_pBuffer, copy.m_writeOffset)) {
IMPORT_LOG0("*** Error writing to message body 2\n");
return NS_ERROR_FAILURE;
}
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error reading message body 2\n");
return rv;
}
}
}
// the start of the current line is a from, we-re done
if (!body.Write(copy.m_pBuffer, copy.m_writeOffset)) {
IMPORT_LOG0("*** Error writing final message body\n");
return NS_ERROR_FAILURE;
}
if (!body.Write(&endBuffer, 1)) {
IMPORT_LOG0("*** Error writing body trailing null\n");
IMPORT_LOG2("\tbody.m_size: %ld, body.m_writeOffset: %ld\n", body.m_size, body.m_writeOffset);
return NS_ERROR_FAILURE;
}
if (NS_FAILED(rv = FillMailBuffer(pState, copy))) {
IMPORT_LOG0("*** Error filling mail buffer for next read message\n");
return rv;
}
return NS_OK;
}
int32_t nsEudoraMailbox::FindStartLine(SimpleBufferTonyRCopiedOnce& data)
{
int32_t len = data.m_bytesInBuf - data.m_writeOffset;
if (!len)
return -1;
int32_t count = 0;
const char *pData = data.m_pBuffer + data.m_writeOffset;
// Skip to next end of line.
while ((count < len) && (*pData != nsCRT::CR) && (*pData != nsCRT::LF)) {
pData++;
count++;
}
if (count == len)
return -1;
// Skip over end of line(s).
while ((count < len) && ((*pData == nsCRT::CR) || (*pData == nsCRT::LF))) {
pData++;
count++;
}
if (count < len)
return count;
return -1;
}
int32_t nsEudoraMailbox::FindNextEndLine(SimpleBufferTonyRCopiedOnce& data)
{
int32_t len = data.m_bytesInBuf - data.m_writeOffset;
if (!len)
return -1;
int32_t count = 0;
const char *pData = data.m_pBuffer + data.m_writeOffset;
// Skip over end of line(s).
while ((count < len) && ((*pData == nsCRT::CR) || (*pData == nsCRT::LF))) {
pData++;
count++;
}
// Skip to next end of line.
while ((count < len) && (*pData != nsCRT::CR) && (*pData != nsCRT::LF)) {
pData++;
count++;
}
if (count < len)
return count;
return -1;
}
int32_t nsEudoraMailbox::IsEndHeaders(SimpleBufferTonyRCopiedOnce& data)
{
int32_t len = data.m_bytesInBuf - data.m_writeOffset;
if (len < 2)
return -1;
const char *pChar = data.m_pBuffer + data.m_writeOffset;
// Double nsCRT::CR.
if ((*pChar == nsCRT::CR) && (*(pChar + 1) == nsCRT::CR))
return 2;
if (len < 4)
return -1;
// Double (nsCRT::CR + nsCRT::LF).
if ((*pChar == nsCRT::CR) && (*(pChar + 1) == nsCRT::LF) &&
(*(pChar + 2) == nsCRT::CR) && (*(pChar + 3) == nsCRT::LF))
return 4;
return -1;
}
static const char *eudoraTag[] = {
"<x-html>",
"</x-html>",
"<x-rich>",
"</x-rich>",
"<x-flowed>",
"</x-flowed>"
};
static int32_t eudoraTagLen[] = {
8,
9,
8,
9,
10,
11,
0
};
static const char *TagContentType[] = {
"text/html",
"text/html",
"text/enriched",
"text/enriched",
"text/plain",
"text/plain",
};
// Determine if this line contains an eudora special tag
bool nsEudoraMailbox::IsEudoraTag(const char *pChar, int32_t maxLen, bool &insideEudoraTags, nsCString &bodyType, int32_t &tagLength)
{
int32_t idx = 0;
while ((tagLength = eudoraTagLen[idx]) != 0) {
if (maxLen >= tagLength && !strncmp(eudoraTag[idx], pChar, tagLength)) {
insideEudoraTags = (pChar[1] != '/');
bodyType = TagContentType[idx];
return true;
}
idx++;
}
return false;
}
// Determine if this line meets Eudora standards for a separator line
// This logic is based on Eudora 1.3.1's strict requirements for what
// makes a valid separator line. This may need to be relaxed for newer
// versions of Eudora.
// A sample from line:
// From john@uxc.cso.uiuc.edu Wed Jan 14 12:36:18 1989
int32_t nsEudoraMailbox::IsEudoraFromSeparator(const char *pChar, int32_t maxLen, nsCString& defaultDate)
{
if (maxLen < 12)
return -1;
int32_t len = 0;
if ((*pChar != 'F') || (*(pChar + 1) != 'r') || (*(pChar + 2) != 'o') || (*(pChar + 3) != 'm'))
return -1;
pChar += 4;
len += 4;
// According to Eudora the next char MUST be a space, and there can only be 1 space
// before the return mail address.
// I'll be nicer and allow any amount of whitespace
while (((*pChar == ' ') || (*pChar == '\t')) && (len < maxLen)) {
pChar++;
len++;
}
if (len == maxLen)
return -1;
// Determine the length of the line
int32_t lineLen = len;
const char * pTok = pChar;
// Skip to next end of line.
while ((lineLen < maxLen) && (*pTok != nsCRT::CR) && (*pTok != nsCRT::LF)) {
lineLen++;
pTok++;
}
if (len >= lineLen)
return -1;
// Eudora allows the return address to be double quoted or not at all..
// I'll allow single or double quote, but other than that, just skip
// the return address until you hit a space char (I allow tab as well)
char quote = *pChar;
if ((quote == '"') || (quote == '\'')) {
pChar++;
len++;
while ((len < lineLen) && (*pChar != quote)) {
pChar++;
len++;
}
if (len == lineLen)
return -1;
len++;
pChar++;
}
else {
while ((len < lineLen) && (*pChar != ' ') && (*pChar != '\t')) {
pChar++;
len++;
}
}
while (((*pChar == ' ') || (*pChar == '\t')) && (len < lineLen)) {
pChar++;
len++;
}
if (len == lineLen)
return -1;
// we've passed the address, now check for the remaining data
// Now it gets really funky!
// In no particular order, with token separators space, tab, comma, newline
// a - the phrase "remote from", remote must be first, from is optional. 2 froms or 2 remotes fails
// b - one and only one time value xx:xx or xx:xx:xx
// c - one and only one day, 1 to 31
// d - one and only one year, 2 digit anything or 4 digit > 1900
// e - one and only one weekday, 3 letter abreviation
// f - one and only one month, 3 letter abreviation
// 2 allowable "other" tokens
// to be valid, day, year, month, & tym must exist and other must be less than 3
int day = 0;
int month = 0;
int year = 0;
int weekDay = 0;
int other = 0;
int result;
char tymStr[9]; // Make it a null terminated string (used in PR_snprintf() call()).
bool tym = false;
bool remote = false;
bool from = false;
int32_t tokLen;
int32_t tokStart;
int32_t num;
while ((len < lineLen) && (other < 3)) {
pTok = pChar;
tokStart = len;
while ((len < lineLen) && (*pChar != ' ') && (*pChar != '\t') && (*pChar != ',')) {
pChar++;
len++;
}
tokLen = len - tokStart;
if (tokLen) {
num = AsciiToLong(pTok, tokLen);
if ((tokLen == 3) && ((result = IsWeekDayStr(pTok)) != 0)) {
if (weekDay)
return -1;
weekDay = result;
}
else if ((tokLen == 3) && ((result = IsMonthStr(pTok)) != 0)) {
if (month)
return -1;
month = result;
}
else if ((tokLen == 6) && !PL_strncasecmp(pTok, "remote", 6)) {
if (remote || from)
return -1;
remote = true;
}
else if ((tokLen == 4) && !PL_strncasecmp(pTok, "from", 4)) {
if (!remote || from)
return -1;
from = true;
}
else if ((tokLen == 4) && ((num > 1900) || !strncmp(pTok, "0000", 4))) {
if (year)
return -1;
year = (int)num;
if (!year)
year = 1900;
}
else if (!year && day && (tokLen == 2) && (*(pTok + 1) >= '0') && (*(pTok + 1) <= '9')) {
if (num < 65)
num += 1900;
else
num += 2000;
year = (int) num;
}
else if ((tokLen <= 2) && (*pTok >= '0') && (*pTok <= '9')) {
day = (int) num;
if ((day < 1) || (day > 31))
day = 1;
}
else if ((tokLen >= 5) && (pTok[2] == ':') && ((tokLen == 5) || ((tokLen == 8) && (pTok[5] == ':')))) {
// looks like the tym...
for (result = 0; result < (int)tokLen; result++) {
if ((result != 2) && (result != 5)) {
if ((pTok[result] < '0') || (pTok[result] > '9')) {
break;
}
}
}
if (result == tokLen) {
if (tym)
return -1;
tym = true;
// for future use, get the time value
memcpy(tymStr, pTok, tokLen);
if (tokLen == 5) {
tymStr[5] = ':';
tymStr[6] = '0';
tymStr[7] = '0';
}
tymStr[8] = 0;
}
else {
other++;
}
}
else
other++;
}
// Skip the space chars...
while ((len < lineLen) && ((*pChar == ' ') || (*pChar == '\t') || (*pChar == ','))) {
pChar++;
len++;
}
} // end while (len < lineLen) token loop
// Now let's see what we found on the line
if (day && year && month && tym && (other < 3)) {
// Now we need to make sure the next line
// isn't blank!
while (len < lineLen) {
len++;
pChar++;
}
if (len == maxLen)
return -1;
if (*pChar == nsCRT::CR) {
len++;
pChar++;
if (*pChar == nsCRT::LF) {
len++;
pChar++;
}
}
else if (*pChar == nsCRT::LF) {
len++;
pChar++;
}
else
return -1;
if (len >= maxLen)
return -1;
while (len < maxLen) {
if ((*pChar == nsCRT::CR) || (*pChar == nsCRT::LF))
return -1;
if ((*pChar != ' ') && (*pChar != '\t'))
break;
pChar++;
len++;
}
// Whew!, the next line isn't blank.
// Generate the default date header in case the date header is missing when we
// write out headers later. The header looks like "Date: Tue, 5 Feb 2002 23:05:04"
char date_header_str[DATE_STR_LEN];
PR_snprintf(date_header_str, DATE_STR_LEN, "Date: %s, %2d %s %4d %s", eudoraWeekDays[weekDay-1], day, eudoraMonths[month-1], year, tymStr);
defaultDate.Assign(date_header_str);
return lineLen;
}
return -1;
}
int32_t nsEudoraMailbox::AsciiToLong(const char *pChar, int32_t len)
{
int32_t num = 0;
while (len) {
if ((*pChar < '0') || (*pChar > '9'))
return num;
num *= 10;
num += (*pChar - '0');
len--;
pChar++;
}
return num;
}
int nsEudoraMailbox::IsWeekDayStr(const char *pStr)
{
for (int i = 0; i < 7; i++) {
if (!PL_strncasecmp(pStr, eudoraWeekDays[i], 3))
return i + 1;
}
return 0;
}
int nsEudoraMailbox::IsMonthStr(const char *pStr)
{
for (int i = 0; i < 12; i++) {
if (!PL_strncasecmp(pStr, eudoraMonths[i], 3))
return i + 1;
}
return 0;
}
nsresult nsEudoraMailbox::WriteFromSep(nsIOutputStream *pDst)
{
if (!m_fromLen)
m_fromLen = strlen(eudoraFromLine);
uint32_t written = 0;
nsresult rv = pDst->Write(eudoraFromLine, m_fromLen, &written);
if (NS_SUCCEEDED(rv) && (written != m_fromLen))
return NS_ERROR_FAILURE;
return rv;
}
void nsEudoraMailbox::EmptyAttachments(void)
{
int32_t max = m_attachments.Length();
ImportAttachment * pAttach;
for (int32_t i = 0; i < max; i++) {
pAttach = m_attachments.ElementAt(i);
if (pAttach) {
NS_Free(pAttach->description);
NS_Free(pAttach->mimeType);
delete pAttach;
}
}
m_attachments.Clear();
}
static const char *eudoraAttachLines[] = {
"Attachment Converted:",
"Attachment converted:",
"Pi�ce jointe convertie :",
// Japanese text encoded with Shift-JIS.
// The meaning is "Restored attached file".
"\x95\x9c\x8c\xb3\x82\xb3\x82\xea\x82\xbd\x93\x59\x95\x74\x83\x74\x83\x40\x83\x43\x83\x8b\x81\x46"
};
static int32_t eudoraAttachLen[] = {
21,
21,
24,
24,
0
};
nsresult nsEudoraMailbox::ExamineAttachment(SimpleBufferTonyRCopiedOnce& data)
{
// get the file, then get the mime type, and add it to the array
// of attachments.
int32_t len = data.m_bytesInBuf - data.m_writeOffset;
const char *pChar = data.m_pBuffer + data.m_writeOffset;
const char *pData;
const char *pStart;
int32_t nameLen;
char quote;
int32_t cnt;
int32_t idx = 0;
while ((cnt = eudoraAttachLen[idx]) != 0) {
if (!strncmp(eudoraAttachLines[idx], pChar, cnt)) {
pData = pChar + cnt;
while (((*pData == ' ') || (*pData == '\t')) && (cnt < len)) {
cnt++;
pData++;
}
if (pData != pChar) {
quote = *pData;
nameLen = 0;
if ((quote == '"') || (quote == '\'')) {
pData++;
cnt++;
pStart = pData;
while ((*pData != quote) && (cnt < len)) {
cnt++;
pData++;
nameLen++;
}
}
else {
pStart = pData;
// Skip to next end of line.
while ((cnt < len) &&
(*pData != nsCRT::CR) && (*pData != nsCRT::LF)) {
pData++;
cnt++;
nameLen++;
}
}
nsCString fileName;
fileName.Append(pStart, nameLen);
fileName.Trim(kWhitespace);
if (fileName.Length()) {
#ifdef XP_MACOSX
return NS_OK;
#else
if(AddAttachment(fileName))
return NS_OK;
#endif
}
}
}
idx++;
}
return NS_OK;
}
bool nsEudoraMailbox::AddAttachment(nsCString& fileName)
{
IMPORT_LOG1("Found attachment: %s\n", fileName.get());
nsresult rv;
nsCOMPtr <nsIFile> pFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
if (NS_FAILED(rv))
return false;
nsCString mimeType;
nsCString attachmentName;
if (NS_FAILED(GetAttachmentInfo(fileName.get(), pFile, mimeType, attachmentName)))
return false;
ImportAttachment *a = new ImportAttachment;
a->mimeType = ToNewCString(mimeType);
a->description = !attachmentName.IsEmpty() ? ToNewCString(attachmentName) : strdup("Attached File");
a->pAttachment = pFile;
m_attachments.AppendElement(a);
return true;
}
nsresult nsEudoraMailbox::FillMailBuffer(ReadFileState *pState, SimpleBufferTonyRCopiedOnce& read)
{
if (read.m_writeOffset >= read.m_bytesInBuf) {
read.m_writeOffset = 0;
read.m_bytesInBuf = 0;
}
else if (read.m_writeOffset) {
memcpy(read.m_pBuffer, read.m_pBuffer + read.m_writeOffset, read.m_bytesInBuf - read.m_writeOffset);
read.m_bytesInBuf -= read.m_writeOffset;
read.m_writeOffset = 0;
}
uint32_t count = read.m_size - read.m_bytesInBuf;
if ((count + pState->offset) > pState->size)
count = pState->size - pState->offset;
if (count) {
uint32_t bytesRead = 0;
char * pBuffer = read.m_pBuffer + read.m_bytesInBuf;
nsresult rv = pState->pInputStream->Read(pBuffer, count, &bytesRead);
if (NS_FAILED(rv))
return rv;
if (bytesRead != uint32_t(count))
return NS_ERROR_FAILURE;
read.m_bytesInBuf += bytesRead;
pState->offset += bytesRead;
}
return NS_OK;
}