/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape 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/NPL/ * * 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 Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ #include "nsOE5File.h" #include "nsCRT.h" #include "OEDebugLog.h" #define kIndexGrowBy 100 #define kSignatureSize 12 #define kDontSeek 0xFFFFFFFF static char *gSig = "\xCF\xAD\x12\xFE\xC5\xFD\x74\x6F\x66\xE3\xD1\x11"; PRBool nsOE5File::VerifyLocalMailFile( nsIFileSpec *pFile) { char sig[kSignatureSize]; if (!ReadBytes( pFile, sig, 0, kSignatureSize)) { return( PR_FALSE); } PRBool result = PR_TRUE; for (int i = 0; (i < kSignatureSize) && result; i++) { if (sig[i] != gSig[i]) { result = PR_FALSE; } } char storeName[14]; if (!ReadBytes( pFile, storeName, 0x24C1, 12)) result = PR_FALSE; storeName[12] = 0; if (nsCRT::strcasecmp( "LocalStore", storeName)) result = PR_FALSE; return( result); } PRBool nsOE5File::IsLocalMailFile( nsIFileSpec *pFile) { nsresult rv; PRBool isFile = PR_FALSE; rv = pFile->IsFile( &isFile); if (NS_FAILED( rv) || !isFile) return( PR_FALSE); rv = pFile->OpenStreamForReading(); if (NS_FAILED( rv)) return( PR_FALSE); PRBool result = VerifyLocalMailFile( pFile); pFile->CloseStream(); return( result); } PRBool nsOE5File::ReadIndex( nsIFileSpec *pFile, PRUint32 **ppIndex, PRUint32 *pSize) { *ppIndex = nsnull; *pSize = 0; char signature[4]; if (!ReadBytes( pFile, signature, 0, 4)) return( PR_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( PR_FALSE); } } PRUint32 offset = 0x00e4; PRUint32 indexStart = 0; if (!ReadBytes( pFile, &indexStart, offset, 4)) { IMPORT_LOG0( "*** Unable to read offset to index start\n"); return( PR_FALSE); } PRUint32Array array; array.count = 0; array.alloc = kIndexGrowBy; array.pIndex = new PRUint32[kIndexGrowBy]; PRUint32 next = ReadMsgIndex( pFile, indexStart, &array); while (next) { next = ReadMsgIndex( pFile, next, &array); } if (array.count) { *pSize = array.count; *ppIndex = array.pIndex; return( PR_TRUE); } delete [] array.pIndex; return( PR_FALSE); } PRUint32 nsOE5File::ReadMsgIndex( nsIFileSpec *file, PRUint32 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 PRUint32 marker; if (!ReadBytes( file, &marker, offset, 4)) return( 0); if (marker != offset) return( 0); PRUint32 vals[3]; if (!ReadBytes( file, vals, offset + 4, 12)) return( 0); PRUint8 len[4]; if (!ReadBytes( file, len, offset + 16, 4)) return( 0); PRUint32 cnt = (PRUint32) len[1]; cnt *= 3; PRUint32 *pData = new PRUint32[cnt]; if (!ReadBytes( file, pData, offset + 24, cnt * 4)) { delete [] pData; return( 0); } PRUint32 next; PRUint32 indexOffset; PRUint32 * pRecord = pData; PRUint32 * pNewIndex; for (PRUint8 i = 0; i < (PRUint8)len[1]; i++, pRecord += 3) { indexOffset = pRecord[0]; if (pArray->count >= pArray->alloc) { pNewIndex = new PRUint32[ pArray->alloc + kIndexGrowBy]; nsCRT::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( file, next, pArray)) != 0); } delete pData; // return the pointer to the next subIndex return( vals[1]); } PRBool nsOE5File::IsFromLine( char *pLine, PRUint32 len) { if (len >= 5) { if ( (*pLine == 'F') && (pLine[1] = 'r') && (pLine[2] == 'o') && (pLine[3] == 'm') && (pLine[4] == ' ')) return( PR_TRUE); } return( PR_FALSE); } // Anything over 16K will be assumed BAD, BAD, BAD! #define kMailboxBufferSize 0x4000 const char *nsOE5File::m_pFromLineSep = "From ????@???? 1 Jan 1965 00:00:00\x0D\x0A"; nsresult nsOE5File::ImportMailbox( PRUint32 *pBytesDone, PRBool *pAbort, nsString& name, nsIFileSpec *inFile, nsIFileSpec *pDestination, PRUint32 *pCount) { nsresult rv; PRInt32 msgCount = 0; if (pCount) *pCount = 0; rv = inFile->OpenStreamForReading(); if (NS_FAILED( rv)) return( rv); rv = pDestination->OpenStreamForWriting(); if (NS_FAILED( rv)) { inFile->CloseStream(); return( rv); } PRUint32 * pIndex; PRUint32 indexSize; if (!ReadIndex( inFile, &pIndex, &indexSize)) { IMPORT_LOG1( "No messages found in mailbox: %S\n", name.GetUnicode()); inFile->CloseStream(); pDestination->CloseStream(); return( NS_OK); } char * pBuffer = new char[kMailboxBufferSize]; if (!(*pAbort)) ConvertIndex( inFile, pBuffer, pIndex, indexSize); PRUint32 block[4]; PRInt32 sepLen = (PRInt32) nsCRT::strlen( m_pFromLineSep); PRInt32 written; /* Each block is: marker - matches file offset block length text length in block pointer to next block. (0 if end) So what we do for each message is: Read the first block Write out the From message separator if the message doesn't already start with one. Write out all subsequent blocks of text keeping track of the last 2 bytes written of the message. if the last 2 bytes aren't and end of line then write one out before proceeding to the next message. */ PRUint32 didBytes = 0; char last2[2] = {0, 0}; PRUint32 next; rv = NS_OK; for (PRUint32 i = 0; (i < indexSize) && !(*pAbort); i++) { if (pIndex[i]) { if (ReadBytes( inFile, block, pIndex[i], 16) && (block[0] == pIndex[i]) && (block[2] < kMailboxBufferSize) && (ReadBytes( inFile, pBuffer, kDontSeek, block[2]))) { // write out the from separator. if (!IsFromLine( pBuffer, block[2])) { rv = pDestination->Write( m_pFromLineSep, sepLen, &written); // FIXME: Do I need to check the return value of written??? if (NS_FAILED( rv)) break; last2[0] = 13; last2[1] = 10; } else { last2[0] = 0; last2[1] = 0; } rv = WriteMessageBuffer( pDestination, pBuffer, block[2], last2); didBytes += block[2]; if (NS_FAILED( rv)) break; next = block[3]; while (next && !(*pAbort)) { if (ReadBytes( inFile, block, next, 16) && (block[0] == next) && (block[2] < kMailboxBufferSize) && (ReadBytes( inFile, pBuffer, kDontSeek, block[2]))) { if (block[2]) rv = WriteMessageBuffer( pDestination, pBuffer, block[2], last2); didBytes += block[2]; if (NS_FAILED( rv)) break; next = block[3]; } else { IMPORT_LOG2( "Error reading message from %S at 0x%lx\n", name.GetUnicode(), pIndex[i]); pDestination->Write( "\x0D\x0A", 2, &written); next = 0; last2[0] = 13; last2[1] = 10; } } if (NS_FAILED( rv) || (*pAbort)) break; if ((last2[0] != 13) || (last2[1] != 10)) { pDestination->Write( "\x0D\x0A", 2, &written); last2[0] = 13; last2[1] = 10; } 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", name.GetUnicode(), pIndex[i]); } } } delete [] pBuffer; inFile->CloseStream(); pDestination->CloseStream(); return( rv); } #define ISFROMLINE( pChar, i) (*(pChar + i) == 'F') && (*(pChar + i + 1) == 'r') && \ (*(pChar + i + 2) == 'o') && (*(pChar + i + 3) == 'm') && \ (*(pChar + i + 4) == ' ') nsresult nsOE5File::WriteMessageBuffer( nsIFileSpec *stream, char *pBuffer, PRUint32 size, char *last2) { if (!size) return( NS_OK); PRInt32 written; nsresult rv = NS_OK; //check the very beginning of the buffer to make sure it's not a from // line if ((size > 4) && ((last2[0] = 0x0D) && (last2[1] == 0x0A)) || (last2[1] == 0x0D)) { if ((last2[1] == 0x0D) && (size > 5) && (*pBuffer == 0x0A) && ISFROMLINE( pBuffer, 1)) { rv = stream->Write( pBuffer, 1, &written); pBuffer++; size--; if (NS_SUCCEEDED( rv)) rv = stream->Write( ">", 1, &written); } else if ((last2[0] == 0x0D) && ISFROMLINE( pBuffer, 0)) { rv = stream->Write( ">", 1, &written); } if (NS_FAILED( rv)) return( rv); } // examine the rest of the buffer for from problems! PRUint32 cnt = 0; char * pChar = pBuffer; if (size > 6) { while (cnt < (size - 6)) { if ((*pChar == 0x0D) && (*(pChar + 1) == 0x0A) && ISFROMLINE( pChar, 2)) { rv = stream->Write( pBuffer, cnt + 2, &written); pBuffer += (cnt + 2); size -= (cnt + 2); pChar += 2; cnt = 0; if (NS_SUCCEEDED( rv)) { rv = stream->Write( ">", 1, &written); } if (NS_FAILED( rv)) return( rv); } else { cnt++; pChar++; } } } rv = stream->Write( pBuffer, (PRInt32) size, &written); if (NS_SUCCEEDED( rv)) { if (size > 1) { last2[0] = *(pBuffer + size - 2); last2[1] = *(pBuffer + size - 1); } else { last2[0] = last2[1]; last2[1] = *pBuffer; } } 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 unknown 1 bytes - number of attributes 1 byte unknown 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. Current known tags are: 0x02 - a time value 0x04 - text offset pointer, the data is the offset after the attribute of a 4 byte pointer to the message text 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 0x80 - msgId 0x84 - direct text offset, direct pointer to message text */ void nsOE5File::ConvertIndex( nsIFileSpec *pFile, char *pBuffer, PRUint32 *pIndex, PRUint32 size) { // 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. PRUint8 recordHead[12]; PRUint32 marker; PRUint32 recordSize; PRUint32 numAttrs; PRUint32 offset; PRUint32 attrIndex; PRUint32 attrOffset; PRUint8 tag; PRUint32 tagData; for (PRUint32 i = 0; i < size; i++) { offset = 0; if (ReadBytes( pFile, recordHead, pIndex[i], 12)) { nsCRT::memcpy( &marker, recordHead, 4); nsCRT::memcpy( &recordSize, recordHead + 4, 4); numAttrs = (PRUint32) recordHead[10]; if ((marker == pIndex[i]) && (recordSize < kMailboxBufferSize) && ((numAttrs * 4) <= recordSize)) { if (ReadBytes( pFile, pBuffer, kDontSeek, recordSize)) { attrOffset = 0; for (attrIndex = 0; attrIndex < numAttrs; attrIndex++, attrOffset += 4) { tag = (PRUint8) pBuffer[attrOffset]; if (tag == (PRUint8) 0x84) { tagData = 0; nsCRT::memcpy( &tagData, pBuffer + attrOffset + 1, 3); offset = tagData; break; } else if (tag == (PRUint8) 0x04) { tagData = 0; nsCRT::memcpy( &tagData, pBuffer + attrOffset + 1, 3); if (((numAttrs * 4) + tagData + 4) <= recordSize) nsCRT::memcpy( &offset, pBuffer + (numAttrs * 4) + tagData, 4); } } } } } pIndex[i] = offset; } } PRBool nsOE5File::ReadBytes( nsIFileSpec *stream, void *pBuffer, PRUint32 offset, PRUint32 bytes) { nsresult rv; if (offset != kDontSeek) { rv = stream->Seek( offset); if (NS_FAILED( rv)) return( PR_FALSE); } if (!bytes) return( PR_TRUE); PRInt32 cntRead; char * pReadTo = (char *)pBuffer; rv = stream->Read( &pReadTo, bytes, &cntRead); if (NS_FAILED( rv) || ((PRUint32)cntRead != bytes)) return( PR_FALSE); return( PR_TRUE); }