RetroZilla/mailnews/import/oexpress/nsOEMailbox.cpp
2015-10-20 23:03:22 -04:00

683 lines
18 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsOEMailbox.h"
#include "OEDebugLog.h"
#include "msgCore.h"
#include "prprf.h"
#include "nsMsgLocalFolderHdrs.h"
class CMbxScanner {
public:
CMbxScanner( nsString& name, nsIFileSpec * mbxFile, nsIFileSpec * dstFile);
~CMbxScanner();
virtual PRBool Initialize( void);
virtual PRBool DoWork( PRBool *pAbort, PRUint32 *pDone, PRUint32 *pCount);
PRBool WasErrorFatal( void) { return( m_fatalError);}
PRUint32 BytesProcessed( void) { return( m_didBytes);}
protected:
PRBool WriteMailItem( PRUint32 flags, PRUint32 offset, PRUint32 size, PRUint32 *pTotalMsgSize = nsnull);
virtual void CleanUp( void);
private:
void ReportWriteError( nsIFileSpec * file, PRBool fatal = PR_TRUE);
void ReportReadError( nsIFileSpec * file, PRBool fatal = PR_TRUE);
PRBool CopyMbxFileBytes(PRUint32 flags, PRUint32 numBytes);
PRBool IsFromLineKey( PRUint8 *pBuf, PRUint32 max);
public:
PRUint32 m_msgCount;
protected:
PRUint32 * m_pDone;
nsString m_name;
nsIFileSpec * m_mbxFile;
nsIFileSpec * m_dstFile;
PRUint8 * m_pInBuffer;
PRUint8 * m_pOutBuffer;
PRUint32 m_bufSz;
PRUint32 m_didBytes;
PRBool m_fatalError;
PRUint32 m_mbxFileSize;
PRUint32 m_mbxOffset;
static const char * m_pFromLine;
};
class CIndexScanner : public CMbxScanner {
public:
CIndexScanner( nsString& name, nsIFileSpec * idxFile, nsIFileSpec * mbxFile, nsIFileSpec *dstFile);
~CIndexScanner();
virtual PRBool Initialize( void);
virtual PRBool DoWork( PRBool *pAbort, PRUint32 *pDone, PRUint32 *pCount);
protected:
virtual void CleanUp( void);
private:
PRBool ValidateIdxFile( void);
PRBool GetMailItem( PRUint32 *pFlags, PRUint32 *pOffset, PRUint32 *pSize);
private:
nsIFileSpec * m_idxFile;
PRUint32 m_numMessages;
PRUint32 m_idxOffset;
PRUint32 m_curItemIndex;
};
PRBool CImportMailbox::ImportMailbox( PRUint32 *pDone, PRBool *pAbort, nsString& name, nsIFileSpec * inFile, nsIFileSpec * outFile, PRUint32 *pCount)
{
PRBool done = PR_FALSE;
nsIFileSpec *idxFile;
if (NS_FAILED( NS_NewFileSpec( &idxFile))) {
IMPORT_LOG0( "New file spec failed!\n");
return( PR_FALSE);
}
idxFile->FromFileSpec( inFile);
if (GetIndexFile( idxFile)) {
IMPORT_LOG1( "Using index file for: %S\n", name.get());
CIndexScanner *pIdxScanner = new CIndexScanner( name, idxFile, inFile, outFile);
if (pIdxScanner->Initialize()) {
if (pIdxScanner->DoWork( pAbort, pDone, pCount)) {
done = PR_TRUE;
}
else {
IMPORT_LOG0( "CIndexScanner::DoWork() failed\n");
}
}
else {
IMPORT_LOG0( "CIndexScanner::Initialize() failed\n");
}
delete pIdxScanner;
}
idxFile->Release();
if (done)
return( done);
/*
something went wrong with the index file, just scan the mailbox
file itself.
*/
CMbxScanner *pMbx = new CMbxScanner( name, inFile, outFile);
if (pMbx->Initialize()) {
if (pMbx->DoWork( pAbort, pDone, pCount)) {
done = PR_TRUE;
}
else {
IMPORT_LOG0( "CMbxScanner::DoWork() failed\n");
}
}
else {
IMPORT_LOG0( "CMbxScanner::Initialize() failed\n");
}
delete pMbx;
return( done);
}
PRBool CImportMailbox::GetIndexFile( nsIFileSpec* file)
{
char *pLeaf = nsnull;
if (NS_FAILED( file->GetLeafName( &pLeaf)))
return( PR_FALSE);
PRInt32 len = strlen( pLeaf);
if (len < 5) {
nsCRT::free( pLeaf);
return( PR_FALSE);
}
pLeaf[len - 1] = 'x';
pLeaf[len - 2] = 'd';
pLeaf[len - 3] = 'i';
IMPORT_LOG1( "Looking for index leaf name: %s\n", pLeaf);
nsresult rv;
rv = file->SetLeafName( pLeaf);
nsCRT::free( pLeaf);
PRBool isFile = PR_FALSE;
PRBool exists = PR_FALSE;
if (NS_SUCCEEDED( rv)) rv = file->IsFile( &isFile);
if (NS_SUCCEEDED( rv)) rv = file->Exists( &exists);
if (isFile && exists)
return( PR_TRUE);
else
return( PR_FALSE);
}
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, nsIFileSpec* mbxFile, nsIFileSpec* dstFile)
{
m_msgCount = 0;
m_name = name;
m_mbxFile = mbxFile;
m_mbxFile->AddRef();
m_dstFile = dstFile;
m_dstFile->AddRef();
m_pInBuffer = nsnull;
m_pOutBuffer = nsnull;
m_bufSz = 0;
m_fatalError = PR_FALSE;
m_didBytes = 0;
m_mbxFileSize = 0;
m_mbxOffset = 0;
}
CMbxScanner::~CMbxScanner()
{
CleanUp();
if (m_mbxFile)
m_mbxFile->Release();
if (m_dstFile)
m_dstFile->Release();
}
void CMbxScanner::ReportWriteError( nsIFileSpec * file, PRBool fatal)
{
m_fatalError = fatal;
}
void CMbxScanner::ReportReadError( nsIFileSpec * file, PRBool fatal)
{
m_fatalError = fatal;
}
PRBool CMbxScanner::Initialize( void)
{
m_bufSz = (kBufferKB * 1024);
m_pInBuffer = new PRUint8[m_bufSz];
m_pOutBuffer = new PRUint8[m_bufSz];
if (!m_pInBuffer || !m_pOutBuffer) {
return( PR_FALSE);
}
m_mbxFile->GetFileSize( &m_mbxFileSize);
// open the mailbox file...
if (NS_FAILED( m_mbxFile->OpenStreamForReading())) {
CleanUp();
return( PR_FALSE);
}
if (NS_FAILED( m_dstFile->OpenStreamForWriting())) {
CleanUp();
return( PR_FALSE);
}
return( PR_TRUE);
}
#define kMbxHeaderSize 0x0054
#define kMbxMessageHeaderSz 16
PRBool CMbxScanner::DoWork( PRBool *pAbort, PRUint32 *pDone, PRUint32 *pCount)
{
m_mbxOffset = kMbxHeaderSize;
m_didBytes = kMbxHeaderSize;
while (!(*pAbort) && ((m_mbxOffset + kMbxMessageHeaderSz) < m_mbxFileSize)) {
PRUint32 msgSz;
if (!WriteMailItem( 0, m_mbxOffset, 0, &msgSz)) {
if (!WasErrorFatal())
ReportReadError( m_mbxFile);
return( PR_FALSE);
}
m_mbxOffset += msgSz;
m_didBytes += msgSz;
m_msgCount++;
if (pDone)
*pDone = m_didBytes;
if (pCount)
*pCount = m_msgCount;
}
CleanUp();
return( PR_TRUE);
}
void CMbxScanner::CleanUp( void)
{
m_mbxFile->CloseStream();
m_dstFile->CloseStream();
if (m_pInBuffer != nsnull) {
delete [] m_pInBuffer;
m_pInBuffer = nsnull;
}
if (m_pOutBuffer != nsnull) {
delete [] m_pOutBuffer;
m_pOutBuffer = nsnull;
}
}
#define kNumMbxLongsToRead 4
PRBool CMbxScanner::WriteMailItem( PRUint32 flags, PRUint32 offset, PRUint32 size, PRUint32 *pTotalMsgSize)
{
PRUint32 values[kNumMbxLongsToRead];
PRInt32 cnt = kNumMbxLongsToRead * sizeof( PRUint32);
nsresult rv;
PRBool failed = PR_FALSE;
PRInt32 cntRead;
PRInt8 * pChar = (PRInt8 *) values;
rv = m_mbxFile->Seek( offset);
m_mbxFile->Failed( &failed);
if (NS_FAILED( rv) || failed) {
IMPORT_LOG1( "Mbx seek error: 0x%lx\n", offset);
return( PR_FALSE);
}
rv = m_mbxFile->Read( (char **) &pChar, cnt, &cntRead);
if (NS_FAILED( rv) || (cntRead != cnt)) {
IMPORT_LOG1( "Mbx read error at: 0x%lx\n", offset);
return( PR_FALSE);
}
if (values[0] != 0x7F007F00) {
IMPORT_LOG2( "Mbx tag field doesn't match: 0x%lx, at offset: 0x%lx\n", values[0], offset);
return( PR_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( PR_FALSE);
}
if (pTotalMsgSize != nsnull)
*pTotalMsgSize = values[2];
// everything looks kosher...
// the actual message text follows and is values[3] bytes long...
return( CopyMbxFileBytes(flags, values[3]));
}
PRBool CMbxScanner::IsFromLineKey( PRUint8 * pBuf, PRUint32 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))
PRBool CMbxScanner::CopyMbxFileBytes(PRUint32 flags, PRUint32 numBytes)
{
if (!numBytes)
return( PR_TRUE);
PRUint32 cnt;
PRUint8 last[2] = {0, 0};
PRUint32 inIdx = 0;
PRBool first = PR_TRUE;
PRUint8 * pIn;
PRUint8 * pStart;
PRInt32 fromLen = strlen( m_pFromLine);
nsresult rv;
PRInt32 cntRead;
PRUint8 * 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_mbxFile->Read( (char **) &pChar, (PRInt32)cnt, &cntRead);
if (NS_FAILED( rv) || (cntRead != (PRInt32)cnt)) {
ReportReadError( m_mbxFile);
return( PR_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] != 0x0D))
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( PR_TRUE);
}
}
// Begin every message with a From separator
rv = m_dstFile->Write( m_pFromLine, fromLen, &cntRead);
if (NS_FAILED( rv) || (cntRead != fromLen)) {
ReportWriteError( m_dstFile);
return( PR_FALSE);
}
char statusLine[50];
PRUint32 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_dstFile->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_dstFile->Write(statusLine, strlen(statusLine), &cntRead);
}
if (NS_FAILED( rv) || (cntRead != fromLen)) {
ReportWriteError( m_dstFile);
return( PR_FALSE);
}
first = PR_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 == 0x0D) {
// need more in buffer?
if ((cnt < 7) && numBytes) {
break;
}
else if (cnt > 6) {
if ((pIn[1] == 0x0A) && (IsFromLineKey( pIn + 2, cnt))) {
inIdx += 2;
// Match, escape it
rv = m_dstFile->Write( (const char *)pStart, (PRInt32)inIdx, &cntRead);
if (NS_SUCCEEDED( rv) && (cntRead == (PRInt32)inIdx))
rv = m_dstFile->Write( ">", 1, &cntRead);
if (NS_FAILED( rv) || (cntRead != 1)) {
ReportWriteError( m_dstFile);
return( PR_FALSE);
}
cnt -= 2;
pIn += 2;
inIdx = 0;
pStart = pIn;
continue;
}
}
} // == 0x0D
cnt--;
inIdx++;
pIn++;
}
rv = m_dstFile->Write( (const char *)pStart, (PRInt32)inIdx, &cntRead);
if (NS_FAILED( rv) || (cntRead != (PRInt32)inIdx)) {
ReportWriteError( m_dstFile);
return( PR_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] != 0x0D) || (last[1] != 0x0A)) {
rv = m_dstFile->Write( "\x0D\x0A", 2, &cntRead);
if (NS_FAILED( rv) || (cntRead != 2)) {
ReportWriteError( m_dstFile);
return( PR_FALSE);
}
// }
return( PR_TRUE);
}
CIndexScanner::CIndexScanner( nsString& name, nsIFileSpec * idxFile, nsIFileSpec * mbxFile, nsIFileSpec * dstFile)
: CMbxScanner( name, mbxFile, dstFile)
{
m_idxFile = idxFile;
m_idxFile->AddRef();
m_curItemIndex = 0;
m_idxOffset = 0;
}
CIndexScanner::~CIndexScanner()
{
CleanUp();
if (m_idxFile)
m_idxFile->Release();
}
PRBool CIndexScanner::Initialize( void)
{
if (!CMbxScanner::Initialize())
return( PR_FALSE);
nsresult rv = m_idxFile->OpenStreamForReading();
if (NS_FAILED( rv)) {
CleanUp();
return( PR_FALSE);
}
return( PR_TRUE);
}
PRBool CIndexScanner::ValidateIdxFile( void)
{
PRInt8 id[4];
PRInt32 cnt = 4;
nsresult rv;
PRInt32 cntRead;
PRInt8 * pReadTo;
pReadTo = id;
rv = m_idxFile->Read( (char **) &pReadTo, cnt, &cntRead);
if (NS_FAILED( rv) || (cntRead != cnt))
return( PR_FALSE);
if ((id[0] != 'J') || (id[1] != 'M') || (id[2] != 'F') || (id[3] != '9'))
return( PR_FALSE);
cnt = 4;
PRUint32 subId;
pReadTo = (PRInt8 *) &subId;
rv = m_idxFile->Read( (char **) &pReadTo, cnt, &cntRead);
if (NS_FAILED( rv) || (cntRead != cnt))
return( PR_FALSE);
if (subId != 0x00010004) {
IMPORT_LOG1( "Idx file subid doesn't match: 0x%lx\n", subId);
return( PR_FALSE);
}
pReadTo = (PRInt8 *) &m_numMessages;
rv = m_idxFile->Read( (char **) &pReadTo, cnt, &cntRead);
if (NS_FAILED( rv) || (cntRead != cnt))
return( PR_FALSE);
IMPORT_LOG1( "Idx file num messages: %ld\n", m_numMessages);
m_didBytes += 80;
m_idxOffset = 80;
return( PR_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
PRBool CIndexScanner::GetMailItem( PRUint32 *pFlags, PRUint32 *pOffset, PRUint32 *pSize)
{
PRUint32 values[kNumIdxLongsToRead];
PRInt32 cnt = kNumIdxLongsToRead * sizeof( PRUint32);
PRInt8 * pReadTo = (PRInt8 *) values;
PRInt32 cntRead;
nsresult rv;
rv = m_idxFile->Seek( m_idxOffset);
if (NS_FAILED( rv))
return( PR_FALSE);
rv = m_idxFile->Read( (char **) &pReadTo, cnt, &cntRead);
if (NS_FAILED( rv) || (cntRead != cnt))
return( PR_FALSE);
if (values[3] != m_idxOffset) {
IMPORT_LOG2( "Self pointer invalid: m_idxOffset=0x%lx, self=0x%lx\n", m_idxOffset, values[3]);
return( PR_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]);
m_idxFile->seek( m_idxOffset + 212);
PRUint32 subSz = 0;
cnt = 4;
pReadTo = (PRInt8 *) &subSz;
m_idxFile->Read( (char **) &pReadTo, cnt, &cntRead);
if ((subSz >= 0) && (subSz < 1024)) {
char *pSub = new char[subSz + 1];
m_idxFile->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( PR_TRUE);
}
#define kOEDeletedFlag 0x0001
PRBool CIndexScanner::DoWork( PRBool *pAbort, PRUint32 *pDone, PRUint32 *pCount)
{
m_didBytes = 0;
if (!ValidateIdxFile())
return( PR_FALSE);
PRBool failed = PR_FALSE;
while ((m_curItemIndex < m_numMessages) && !failed && !(*pAbort)) {
PRUint32 flags, offset, size;
if (!GetMailItem( &flags, &offset, &size)) {
CleanUp();
return( PR_FALSE);
}
m_curItemIndex++;
if (!(flags & kOEDeletedFlag)) {
if (!WriteMailItem( flags, offset, size))
failed = PR_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_idxFile->CloseStream();
}