/* -*- Mode: C++; tab-width: 2; 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 "nsEmlxHelperUtils.h" #include "nsIOutputStream.h" #include "nsNetUtil.h" #include "nsCOMPtr.h" #include "nsObjCExceptions.h" #include "nsMsgMessageFlags.h" #include "nsMsgLocalFolderHdrs.h" #include "msgCore.h" #include "nsTArray.h" #include "nsAppleMailImport.h" #include "prprf.h" #include "nsIFile.h" #import nsresult nsEmlxHelperUtils::ConvertToMozillaStatusFlags(const char* aXMLBufferStart, const char* aXMLBufferEnd, uint32_t* aMozillaStatusFlags) { // create a NSData wrapper around the buffer, so we can use the Cocoa call below NSData* metadata = [[[NSData alloc] initWithBytesNoCopy:(void*)aXMLBufferStart length:(aXMLBufferEnd - aXMLBufferStart) freeWhenDone:NO] autorelease]; // get the XML data as a dictionary NSPropertyListFormat format; id plist = [NSPropertyListSerialization propertyListWithData:metadata options:NSPropertyListImmutable format:&format error:NULL]; if (!plist) return NS_ERROR_FAILURE; // find the ... value and convert to int const uint32_t emlxMessageFlags = [[(NSDictionary*)plist objectForKey:@"flags"] intValue]; if (emlxMessageFlags == 0) return NS_ERROR_FAILURE; if (emlxMessageFlags & nsEmlxHelperUtils::kRead) *aMozillaStatusFlags |= nsMsgMessageFlags::Read; if (emlxMessageFlags & nsEmlxHelperUtils::kForwarded) *aMozillaStatusFlags |= nsMsgMessageFlags::Forwarded; if (emlxMessageFlags & nsEmlxHelperUtils::kAnswered) *aMozillaStatusFlags |= nsMsgMessageFlags::Replied; if (emlxMessageFlags & nsEmlxHelperUtils::kFlagged) *aMozillaStatusFlags |= nsMsgMessageFlags::Marked; return NS_OK; } nsresult nsEmlxHelperUtils::ConvertToMboxRD(const char* aMessageBufferStart, const char* aMessageBufferEnd, nsCString& aOutBuffer) { nsTArray foundFromLines; const char* cur = aMessageBufferStart; while (cur < aMessageBufferEnd) { const char* foundFromStr = strnstr(cur, "From ", aMessageBufferEnd - cur); if (foundFromStr) { // skip all prepending '>' chars const char* fromLineStart = foundFromStr; while (fromLineStart-- >= aMessageBufferStart) { if (*fromLineStart == '\n' || fromLineStart == aMessageBufferStart) { if (fromLineStart > aMessageBufferStart) fromLineStart++; foundFromLines.AppendElement(fromLineStart); break; } else if (*fromLineStart != '>') break; } // advance past the last found From string. cur = foundFromStr + 5; // look for more From lines. continue; } break; } // go through foundFromLines if (foundFromLines.Length()) { const char* chunkStart = aMessageBufferStart; for (unsigned i = 0; i < foundFromLines.Length(); ++i) { aOutBuffer.Append(chunkStart, (foundFromLines[i] - chunkStart)); aOutBuffer.Append(">"_ns); chunkStart = foundFromLines[i]; } aOutBuffer.Append(chunkStart, (aMessageBufferEnd - chunkStart)); } return NS_OK; } nsresult nsEmlxHelperUtils::AddEmlxMessageToStream(nsIFile* aMessage, nsIOutputStream* aOut) { NS_OBJC_BEGIN_TRY_BLOCK_RETURN; // needed to be sure autoreleased objects are released too, which they might not // in a C++ environment where the main event loop has no autorelease pool (e.g on a XPCOM thread) NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; nsresult rv = NS_ERROR_FAILURE; nsAutoCString path; aMessage->GetNativePath(path); NSData* data = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:path.get()]]; if (!data) { [pool release]; return NS_ERROR_FAILURE; } char* startOfMessageData = NULL; uint32_t actualBytesWritten = 0; // The anatomy of an EMLX file: // // ------------------------------- // < A number describing how many bytes ahead there is message data > // < Message data > // < XML metadata for this message > // ------------------------------- // read the first line of the emlx file, which is a number of how many bytes ahead the actual // message data is. uint64_t numberOfBytesToRead = strtol((char*)[data bytes], &startOfMessageData, 10); if (numberOfBytesToRead <= 0 || !startOfMessageData) { [pool release]; return NS_ERROR_FAILURE; } // skip whitespace while (*startOfMessageData == ' ' || *startOfMessageData == '\n' || *startOfMessageData == '\r' || *startOfMessageData == '\t') ++startOfMessageData; constexpr auto kBogusFromLine = "From \n"_ns; constexpr auto kEndOfMessage = "\n\n"_ns; // write the bogus "From " line which is a magic separator in the mbox format rv = aOut->Write(kBogusFromLine.get(), kBogusFromLine.Length(), &actualBytesWritten); if (NS_FAILED(rv)) { [pool release]; return rv; } // now read the XML metadata, so we can extract info like which flags (read? replied? flagged? // etc) this message has. const char* startOfXMLMetadata = startOfMessageData + numberOfBytesToRead; const char* endOfXMLMetadata = (char*)[data bytes] + [data length]; uint32_t x_mozilla_flags = 0; ConvertToMozillaStatusFlags(startOfXMLMetadata, endOfXMLMetadata, &x_mozilla_flags); // write the X-Mozilla-Status header according to which flags we've gathered above. uint32_t dummyRv; nsAutoCString buf(PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, x_mozilla_flags)); NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status header"); if (buf.IsEmpty()) { [pool release]; return rv; } rv = aOut->Write(buf.get(), buf.Length(), &dummyRv); if (NS_FAILED(rv)) { [pool release]; return rv; } // write out X-Mozilla-Keywords header as well to reserve some space for it // in the mbox file. rv = aOut->Write(X_MOZILLA_KEYWORDS, X_MOZILLA_KEYWORDS_LEN, &dummyRv); if (NS_FAILED(rv)) { [pool release]; return rv; } // write out empty X-Mozilla_status2 header buf.Adopt(PR_smprintf(X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, 0)); NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status2 header"); if (buf.IsEmpty()) { [pool release]; return NS_ERROR_OUT_OF_MEMORY; } rv = aOut->Write(buf.get(), buf.Length(), &dummyRv); if (NS_FAILED(rv)) { [pool release]; return rv; } // do any conversion needed for the mbox data to be valid mboxrd. nsCString convertedData; rv = ConvertToMboxRD(startOfMessageData, (startOfMessageData + numberOfBytesToRead), convertedData); if (NS_FAILED(rv)) { [pool release]; return rv; } // write the actual message data. if (convertedData.IsEmpty()) rv = aOut->Write(startOfMessageData, (uint32_t)numberOfBytesToRead, &actualBytesWritten); else { IMPORT_LOG1("Escaped From-lines in %s!", path.get()); rv = aOut->Write(convertedData.get(), convertedData.Length(), &actualBytesWritten); } if (NS_FAILED(rv)) { [pool release]; return rv; } NS_ASSERTION(actualBytesWritten == (convertedData.IsEmpty() ? numberOfBytesToRead : convertedData.Length()), "Didn't write as many bytes as expected for .emlx file?"); // add newlines to denote the end of this message in the mbox rv = aOut->Write(kEndOfMessage.get(), kEndOfMessage.Length(), &actualBytesWritten); [pool release]; return rv; NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); }