Back to index

lightning-sunbird  0.9+nobinonly
nsOE5File.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsOE5File.h"
00039 #include "nsCRT.h"
00040 #include "OEDebugLog.h"
00041 #include "nsMsgUtils.h"
00042 #include "msgCore.h"
00043 #include "prprf.h"
00044 #include "nsMsgLocalFolderHdrs.h"
00045 
00046 
00047 #define       kIndexGrowBy         100
00048 #define       kSignatureSize              12
00049 #define       kDontSeek                   0xFFFFFFFF
00050 
00051 static char *gSig = 
00052        "\xCF\xAD\x12\xFE\xC5\xFD\x74\x6F\x66\xE3\xD1\x11";
00053 
00054 
00055 PRBool nsOE5File::VerifyLocalMailFile( nsIFileSpec *pFile)
00056 {
00057        char          sig[kSignatureSize];
00058        
00059        if (!ReadBytes( pFile, sig, 0, kSignatureSize)) {
00060               return( PR_FALSE);
00061        }
00062        
00063        PRBool result = PR_TRUE;
00064 
00065        for (int i = 0; (i < kSignatureSize) && result; i++) {
00066               if (sig[i] != gSig[i]) {
00067                      result = PR_FALSE;
00068               }
00069        }
00070        
00071        char   storeName[14];
00072        if (!ReadBytes( pFile, storeName, 0x24C1, 12))
00073               result = PR_FALSE;
00074 
00075        storeName[12] = 0;
00076        
00077        if (nsCRT::strcasecmp( "LocalStore", storeName))
00078               result = PR_FALSE;
00079        
00080        return( result);
00081 }
00082 
00083 PRBool nsOE5File::IsLocalMailFile( nsIFileSpec *pFile)
00084 {
00085        nsresult      rv;
00086        PRBool        isFile = PR_FALSE;
00087 
00088        rv = pFile->IsFile( &isFile);
00089        if (NS_FAILED( rv) || !isFile)
00090               return( PR_FALSE);
00091        
00092        rv = pFile->OpenStreamForReading();       
00093        if (NS_FAILED( rv))
00094               return( PR_FALSE);
00095        
00096        PRBool result = VerifyLocalMailFile( pFile);
00097        pFile->CloseStream();
00098 
00099        return( result);
00100 }
00101 
00102 PRBool nsOE5File::ReadIndex( nsIFileSpec *pFile, PRUint32 **ppIndex, PRUint32 *pSize)
00103 {
00104   *ppIndex = nsnull;
00105   *pSize = 0;
00106   
00107   char        signature[4];
00108   if (!ReadBytes( pFile, signature, 0, 4))
00109     return( PR_FALSE);
00110   
00111   for (int i = 0; i < 4; i++) {
00112     if (signature[i] != gSig[i]) {
00113       IMPORT_LOG0( "*** Outlook 5.0 dbx file signature doesn't match\n");
00114       return( PR_FALSE);
00115     }
00116   }
00117   
00118   PRUint32    offset = 0x00e4;                          
00119   PRUint32    indexStart = 0;
00120   if (!ReadBytes( pFile, &indexStart, offset, 4)) {
00121     IMPORT_LOG0( "*** Unable to read offset to index start\n");
00122     return( PR_FALSE);
00123   }
00124   
00125   PRUint32Array array;
00126   array.count = 0;
00127   array.alloc = kIndexGrowBy;
00128   array.pIndex = new PRUint32[kIndexGrowBy];
00129   
00130   PRUint32 next = ReadMsgIndex( pFile, indexStart, &array);
00131   while (next) {
00132     next = ReadMsgIndex( pFile, next, &array);
00133   }
00134   
00135   if (array.count) {
00136     *pSize = array.count;
00137     *ppIndex = array.pIndex;
00138     return( PR_TRUE);
00139   }           
00140   
00141   delete [] array.pIndex;
00142   return( PR_FALSE);
00143 }
00144 
00145 
00146 PRUint32 nsOE5File::ReadMsgIndex( nsIFileSpec *file, PRUint32 offset, PRUint32Array *pArray)
00147 {
00148   // Record is:
00149               // 4 byte marker
00150               // 4 byte unknown
00151               // 4 byte nextSubIndex
00152               // 4 byte (parentIndex?)
00153               // 2 bytes unknown
00154               // 1 byte length - # of entries in this record
00155               // 1 byte unknown
00156               // 4 byte unknown
00157               // length records consisting of 3 longs
00158   //   1 - pointer to record
00159   //   2 - child index pointer
00160   //   3 - number of records in child
00161   
00162   PRUint32    marker;
00163   
00164   if (!ReadBytes( file, &marker, offset, 4))
00165     return( 0);
00166   
00167   if (marker != offset)
00168     return( 0);
00169   
00170   
00171   PRUint32    vals[3];
00172   
00173   if (!ReadBytes( file, vals, offset + 4, 12))
00174     return( 0);
00175   
00176   
00177   PRUint8     len[4];
00178   if (!ReadBytes( file, len, offset + 16, 4))
00179     return( 0);
00180   
00181   
00182   
00183   PRUint32    cnt = (PRUint32) len[1];
00184   cnt *= 3;
00185   PRUint32    *pData = new PRUint32[cnt];
00186   
00187   if (!ReadBytes( file, pData, offset + 24, cnt * 4)) {
00188     delete [] pData;
00189     return( 0);
00190   }    
00191   
00192   PRUint32    next;
00193   PRUint32    indexOffset;
00194   PRUint32 *  pRecord = pData;
00195   PRUint32 *  pNewIndex;
00196   
00197   for (PRUint8 i = 0; i < (PRUint8)len[1]; i++, pRecord += 3) {
00198     indexOffset = pRecord[0];
00199     
00200     if (pArray->count >= pArray->alloc) {
00201       pNewIndex = new PRUint32[ pArray->alloc + kIndexGrowBy];
00202       memcpy( pNewIndex, pArray->pIndex, (pArray->alloc * 4));
00203       (pArray->alloc) += kIndexGrowBy;           
00204       delete [] pArray->pIndex;
00205       pArray->pIndex = pNewIndex;
00206     }
00207     
00208     /* 
00209     We could do some checking here if we wanted - 
00210     make sure the index is within the file,
00211     make sure there isn't a duplicate index, etc.
00212     */
00213     
00214     pArray->pIndex[pArray->count] = indexOffset;
00215     (pArray->count)++;
00216     
00217     
00218     
00219     next = pRecord[1];
00220     if (next)
00221       while ((next = ReadMsgIndex( file, next, pArray)) != 0);
00222   }
00223   delete [] pData;
00224   
00225   // return the pointer to the next subIndex
00226   return( vals[1]);
00227 }
00228 
00229 PRBool nsOE5File::IsFromLine( char *pLine, PRUint32 len)
00230 {
00231    return (len > 5 && (pLine[0] == 'F') && (pLine[1] == 'r') && (pLine[2] == 'o') && (pLine[3] == 'm') && (pLine[4] == ' '));
00232 }
00233 
00234 // Anything over 16K will be assumed BAD, BAD, BAD!
00235 #define       kMailboxBufferSize   0x4000
00236 const char *nsOE5File::m_pFromLineSep = "From - Mon Jan 1 00:00:00 1965\x0D\x0A";
00237 
00238 nsresult nsOE5File::ImportMailbox( PRUint32 *pBytesDone, PRBool *pAbort, nsString& name, nsIFileSpec *inFile, nsIFileSpec *pDestination, PRUint32 *pCount)
00239 {
00240   nsresult    rv;
00241   PRInt32            msgCount = 0;
00242   if (pCount)
00243     *pCount = 0;
00244   
00245   rv = inFile->OpenStreamForReading();
00246   if (NS_FAILED( rv)) return( rv);
00247   rv = pDestination->OpenStreamForWriting();
00248   if (NS_FAILED( rv)) {
00249     inFile->CloseStream();
00250     return( rv);
00251   }
00252   
00253   PRUint32 *  pIndex;
00254   PRUint32    indexSize;
00255   
00256   if (!ReadIndex( inFile, &pIndex, &indexSize)) {
00257     IMPORT_LOG1( "No messages found in mailbox: %S\n", name.get());
00258     inFile->CloseStream();
00259     pDestination->CloseStream();
00260     return( NS_OK);
00261   }    
00262   
00263   char *  pBuffer = new char[kMailboxBufferSize];
00264   if (!(*pAbort))
00265     ConvertIndex( inFile, pBuffer, pIndex, indexSize);
00266   
00267   PRUint32  block[4];
00268   PRInt32   sepLen = (PRInt32) strlen( m_pFromLineSep);
00269   PRInt32   written;
00270   
00271   /*
00272       Each block is:
00273       marker - matches file offset
00274       block length
00275       text length in block
00276       pointer to next block. (0 if end)
00277       
00278       Each message is made up of a linked list of block data.
00279       So what we do for each message is:
00280       1. Read the first block data.
00281       2. Write out the From message separator if the message doesn't already
00282       start with one.
00283       3. If the block of data doesn't end with CRLF then a line is broken into two blocks,
00284       so save the incomplete line for later process when we read the next block. Then
00285       write out the block excluding the partial line at the end of the block (if exists).
00286       4. If there's next block of data then read next data block. Otherwise we're done.
00287       If we found a partial line in step #3 then find the rest of the line from the
00288       current block and write out this line separately.
00289       5. Reset some of the control variables and repeat step #3.
00290   */
00291   
00292   PRUint32    didBytes = 0;
00293   PRUint32    next, size;
00294   char *pStart, *pEnd, *partialLineStart;
00295   nsCAutoString partialLine, tempLine;
00296   rv = NS_OK;
00297   
00298   for (PRUint32 i = 0; (i < indexSize) && !(*pAbort); i++)
00299   {
00300     if (! pIndex[i])
00301       continue;
00302     
00303     if (ReadBytes( inFile, block, pIndex[i], 16) && (block[0] == pIndex[i]) &&
00304       (block[2] < kMailboxBufferSize) && (ReadBytes( inFile, pBuffer, kDontSeek, block[2])))
00305     {
00306       // block[2] contains the chars in the buffer (ie, buf content size).
00307       // block[3] contains offset to the next block of data (0 means no more data).
00308       size = block[2];
00309       pStart = pBuffer;
00310       pEnd = pStart + size;
00311       
00312       // write out the from separator.
00313       if (IsFromLine( pBuffer, size))
00314       {
00315         char *pChar = pStart;
00316         while ((pChar < pEnd) && (*pChar != nsCRT::CR) && (*(pChar+1) != nsCRT::LF))
00317           pChar++;
00318         
00319         if (pChar < pEnd)
00320         {
00321           // Get the "From " line so write it out.
00322           rv = pDestination->Write(pStart, pChar-pStart+2, &written);
00323           NS_ENSURE_SUCCESS(rv,rv);
00324           // Now buffer starts from the 2nd line.
00325           pStart = pChar + 2;
00326         }
00327       }
00328       else
00329       {
00330         // Write out the default from line since there is none in the msg.
00331         rv = pDestination->Write( m_pFromLineSep, sepLen, &written);
00332         // FIXME: Do I need to check the return value of written???
00333         if (NS_FAILED( rv))
00334           break;
00335       }
00336       
00337       char statusLine[50];
00338       PRUint32 msgFlags = 0; // need to convert from OE flags to mozilla flags
00339       PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF);
00340       rv = pDestination->Write(statusLine, strlen(statusLine), &written);
00341       NS_ENSURE_SUCCESS(rv,rv);
00342       PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000);
00343       rv = pDestination->Write(statusLine, strlen(statusLine), &written);
00344       NS_ENSURE_SUCCESS(rv,rv);
00345 
00346       do 
00347       {
00348         partialLine.Truncate();
00349         partialLineStart = pEnd;
00350         
00351         // If the buffer doesn't end with CRLF then a line is broken into two blocks,
00352         // so save the incomplete line for later process when we read the next block.
00353         if ( (size > 1) && !(*(pEnd - 2) == nsCRT::CR && *(pEnd - 1) == nsCRT::LF) )
00354         {
00355           partialLineStart -= 2;
00356           while ((partialLineStart >= pStart) && (*partialLineStart != nsCRT::CR) && (*(partialLineStart+1) != nsCRT::LF))
00357             partialLineStart--;
00358           if (partialLineStart != (pEnd - 2))
00359             partialLineStart += 2; // skip over CRLF if we find them.
00360           partialLine.Assign(partialLineStart, pEnd - partialLineStart);
00361         }
00362         
00363         // Now process the block of data which ends with CRLF.
00364         rv = EscapeFromSpaceLine(pDestination, pStart, partialLineStart);
00365         if (NS_FAILED(rv))
00366           break;
00367         
00368         didBytes += block[2];
00369         
00370         next = block[3];
00371         if (! next)
00372         {
00373           // OK, we're done so flush out the partial line if it's not empty.
00374           if (partialLine.Length())
00375             rv = EscapeFromSpaceLine(pDestination, (char *)partialLine.get(), (partialLine.get()+partialLine.Length()));
00376         }
00377         else
00378           if (ReadBytes(inFile, block, next, 16) && (block[0] == next) &&
00379             (block[2] < kMailboxBufferSize) && (ReadBytes(inFile, pBuffer, kDontSeek, block[2])))
00380           {
00381             // See if we have a partial line from previous block. If so then build a complete 
00382             // line (ie, take the remaining chars from this block) and process this line. Need
00383             // to adjust where data start and size in this case.
00384             size = block[2];
00385             pStart = pBuffer;
00386             pEnd = pStart + size;
00387             if (partialLine.Length())
00388             {
00389               while ((pStart < pEnd) && (*pStart != nsCRT::CR) && (*(pStart+1) != nsCRT::LF))
00390                 pStart++;
00391               if (pStart < pEnd)  // if we found a CRLF ..
00392                 pStart += 2;      // .. then copy that too.
00393               tempLine.Assign(pBuffer, pStart - pBuffer);
00394               partialLine.Append(tempLine);
00395               rv = EscapeFromSpaceLine(pDestination, (char *)partialLine.get(), (partialLine.get()+partialLine.Length()));
00396               if (NS_FAILED(rv))
00397                 break;
00398               
00399               // Adjust where data start and size (since some of the data has been processed).
00400               size -= (pStart - pBuffer);
00401             }
00402           }
00403           else
00404           {
00405             IMPORT_LOG2( "Error reading message from %S at 0x%lx\n", name.get(), pIndex[i]);
00406             rv = pDestination->Write( "\x0D\x0A", 2, &written);
00407             next = 0;
00408           }
00409       } while (next);
00410       
00411       // Always end a msg with CRLF. This will make sure that OE msgs without body is
00412       // correctly recognized as msgs. Otherwise, we'll end up with the following in
00413       // the msg folder where the 2nd msg starts right after the headers of the 1st msg:
00414       //
00415       // From - Jan 1965 00:00:00     <<<--- 1st msg starts here
00416       // Subject: Test msg
00417       // . . . (more headers)
00418       // To: <someone@netscape.com>
00419       // From - Jan 1965 00:00:00     <<<--- 2nd msg starts here
00420       // Subject: How are you
00421       // . . .(more headers)
00422       //
00423       // In this case, the 1st msg is not recognized as a msg (it's skipped)
00424       // when you open the folder.
00425       rv = pDestination->Write( "\x0D\x0A", 2, &written);
00426       
00427       if (NS_FAILED(rv))
00428         break;
00429       
00430       msgCount++;
00431       if (pCount)
00432         *pCount = msgCount;
00433       if (pBytesDone)
00434         *pBytesDone = didBytes;
00435     }
00436     else {
00437       // Error reading message, should this be logged???
00438       IMPORT_LOG2( "Error reading message from %S at 0x%lx\n", name.get(), pIndex[i]);
00439       *pAbort = PR_TRUE;
00440     }                
00441   }
00442         
00443   delete [] pBuffer;
00444   inFile->CloseStream();
00445   pDestination->CloseStream();
00446   
00447   if (NS_FAILED(rv))
00448     *pAbort = PR_TRUE;
00449   
00450   return( rv);
00451 }
00452 
00453 
00454 /*
00455   A message index record consists of:
00456   4 byte marker - matches record offset
00457   4 bytes size - size of data after this header
00458   2 bytes header length
00459   (2 bytes - flag offset in msg hdr?) or the following?
00460   1 bytes - number of attributes
00461   1 byte changes on this object
00462   Each attribute is a 4 byte value with the 1st byte being the tag
00463   and the remaing 3 bytes being data.  The data is either a direct
00464   offset of an offset within the message index that points to the
00465   data for the tag.
00466 
00467   Current known tags are:
00468   0x02 - a time value
00469   0x04 - text offset pointer, the data is the offset after the attribute
00470          of a 4 byte pointer to the message text
00471   0x05 - offset to truncated subject
00472   0x08 - offste to subject
00473   0x0D - offset to from
00474   0x0E - offset to from addresses
00475   0x13 - offset to to name
00476   0x45 - offset to to address
00477   0x80 - msgId
00478   0x84 - direct text offset, direct pointer to message text
00479 
00480    flags we're interested in:
00481       kMarked= 0x20;
00482       kRead = 0x80;
00483       kHasAttachment = 0x4000;
00484       kIsReply= 0x80000;
00485 
00486 */
00487                 
00488 void nsOE5File::ConvertIndex( nsIFileSpec *pFile, char *pBuffer, PRUint32 *pIndex, PRUint32 size)
00489 {
00490   // for each index record, get the actual message offset!  If there is a problem
00491   // just record the message offset as 0 and the message reading code
00492   // can log that error information.
00493   
00494   PRUint8   recordHead[12];
00495   PRUint32  marker;
00496   PRUint32  recordSize;
00497   PRUint32  numAttrs;
00498   PRUint32  offset;
00499   PRUint32  attrIndex;
00500   PRUint32  attrOffset;
00501   PRUint8   tag;
00502   PRUint32  tagData;
00503   
00504   for (PRUint32 i = 0; i < size; i++) {
00505     offset = 0;
00506     if (ReadBytes( pFile, recordHead, pIndex[i], 12)) {
00507       memcpy( &marker, recordHead, 4);
00508       memcpy( &recordSize, recordHead + 4, 4);
00509       numAttrs = (PRUint32) recordHead[10];
00510       if ((marker == pIndex[i]) && (recordSize < kMailboxBufferSize) && ((numAttrs * 4) <= recordSize)) {
00511         if (ReadBytes( pFile, pBuffer, kDontSeek, recordSize)) {
00512           attrOffset = 0;
00513           for (attrIndex = 0; attrIndex < numAttrs; attrIndex++, attrOffset += 4) {
00514             tag = (PRUint8) pBuffer[attrOffset];
00515             if (tag == (PRUint8) 0x84) {
00516               tagData = 0;
00517               memcpy( &tagData, pBuffer + attrOffset + 1, 3);
00518               offset = tagData;
00519               break;
00520             }
00521             else if (tag == (PRUint8) 0x04) {
00522               tagData = 0;
00523               memcpy( &tagData, pBuffer + attrOffset + 1, 3);
00524               if (((numAttrs * 4) + tagData + 4) <= recordSize)
00525                 memcpy( &offset, pBuffer + (numAttrs * 4) + tagData, 4);
00526             }
00527           }
00528         }
00529       }
00530     }
00531     pIndex[i] = offset;
00532   }
00533 }
00534 
00535 
00536 PRBool nsOE5File::ReadBytes( nsIFileSpec *stream, void *pBuffer, PRUint32 offset, PRUint32 bytes)
00537 {
00538   nsresult    rv;
00539   
00540   if (offset != kDontSeek) {
00541     rv = stream->Seek( offset);
00542     if (NS_FAILED( rv))
00543       return( PR_FALSE);
00544   }
00545   
00546   if (!bytes)
00547     return( PR_TRUE);
00548   
00549   PRInt32     cntRead;
00550   char *      pReadTo = (char *)pBuffer;
00551   rv = stream->Read( &pReadTo, bytes, &cntRead); 
00552   if (NS_FAILED( rv) || ((PRUint32)cntRead != bytes))
00553     return( PR_FALSE);
00554   return( PR_TRUE);
00555   
00556 }
00557