diff options
Diffstat (limited to 'mailnews/import/oexpress/nsOE5File.cpp')
-rw-r--r-- | mailnews/import/oexpress/nsOE5File.cpp | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/mailnews/import/oexpress/nsOE5File.cpp b/mailnews/import/oexpress/nsOE5File.cpp new file mode 100644 index 0000000000..fd1fd0e157 --- /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; + +} + |