diff options
Diffstat (limited to 'mailnews/imap/src/nsIMAPBodyShell.cpp')
-rw-r--r-- | mailnews/imap/src/nsIMAPBodyShell.cpp | 1333 |
1 files changed, 1333 insertions, 0 deletions
diff --git a/mailnews/imap/src/nsIMAPBodyShell.cpp b/mailnews/imap/src/nsIMAPBodyShell.cpp new file mode 100644 index 000000000..087fe2033 --- /dev/null +++ b/mailnews/imap/src/nsIMAPBodyShell.cpp @@ -0,0 +1,1333 @@ +/* -*- 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 "msgCore.h" +#include "nsIMAPHostSessionList.h" +#include "nsIMAPBodyShell.h" +#include "nsImapProtocol.h" +#include "nsImapStringBundle.h" + +#include "nsMimeTypes.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsITransport.h" +#include "nsServiceManagerUtils.h" + +// need to talk to Rich about this... +#define IMAP_EXTERNAL_CONTENT_HEADER "X-Mozilla-IMAP-Part" + +// imapbody.cpp +// Implementation of the nsIMAPBodyShell and associated classes +// These are used to parse IMAP BODYSTRUCTURE responses, and intelligently (?) +// figure out what parts we need to display inline. + +/* + Create a nsIMAPBodyShell from a full BODYSTRUCUTRE response from the parser. + + The body shell represents a single, top-level object, the message. The message body + might be treated as either a container or a leaf (just like any arbitrary part). + + Steps for creating a part: + 1. Pull out the paren grouping for the part + 2. Create a generic part object with that buffer + 3. The factory will return either a leaf or container, depending on what it really is. + 4. It is responsible for parsing its children, if there are any +*/ + + +///////////// nsIMAPBodyShell //////////////////////////////////// + +NS_IMPL_ISUPPORTS0(nsIMAPBodyShell) + +nsIMAPBodyShell::nsIMAPBodyShell(nsImapProtocol *protocolConnection, + nsIMAPBodypartMessage *message, uint32_t UID, + const char *folderName) +{ + m_isValid = false; + m_isBeingGenerated = false; + m_cached = false; + m_gotAttachmentPref = false; + m_generatingWholeMessage = false; + m_generatingPart = NULL; + m_protocolConnection = protocolConnection; + m_message = message; + NS_ASSERTION(m_protocolConnection, "non null connection"); + if (!m_protocolConnection) + return; + m_prefetchQueue = new nsIMAPMessagePartIDArray(); + if (!m_prefetchQueue) + return; + m_UID = ""; + m_UID.AppendInt(UID); +#ifdef DEBUG_chrisf + NS_ASSERTION(folderName); +#endif + if (!folderName) + return; + m_folderName = NS_strdup(folderName); + if (!m_folderName) + return; + + SetContentModified(GetShowAttachmentsInline() ? IMAP_CONTENT_MODIFIED_VIEW_INLINE : IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS); + + SetIsValid(m_message != nullptr); +} + +nsIMAPBodyShell::~nsIMAPBodyShell() +{ + delete m_message; + delete m_prefetchQueue; + PR_Free(m_folderName); +} + +void nsIMAPBodyShell::SetIsValid(bool valid) +{ + m_isValid = valid; +} + +bool nsIMAPBodyShell::GetShowAttachmentsInline() +{ + if (!m_gotAttachmentPref) + { + m_showAttachmentsInline = !m_protocolConnection || m_protocolConnection->GetShowAttachmentsInline(); + m_gotAttachmentPref = true; + } + + return m_showAttachmentsInline; +} + +// Fills in buffer (and adopts storage) for header object +void nsIMAPBodyShell::AdoptMessageHeaders(char *headers, const char *partNum) +{ + if (!GetIsValid()) + return; + + if (!partNum) + partNum = "0"; + + // we are going to say that a message header object only has + // part data, and no header data. + nsIMAPBodypart *foundPart = m_message->FindPartWithNumber(partNum); + if (foundPart) + { + nsIMAPBodypartMessage *messageObj = foundPart->GetnsIMAPBodypartMessage(); + if (messageObj) + { + messageObj->AdoptMessageHeaders(headers); + if (!messageObj->GetIsValid()) + SetIsValid(false); + } + else + { + // We were filling in message headers for a given part number. + // We looked up that part number, found an object, but it + // wasn't of type message/rfc822. + // Something's wrong. + NS_ASSERTION(false, "object not of type message rfc822"); + } + } + else + SetIsValid(false); +} + +// Fills in buffer (and adopts storage) for MIME headers in appropriate object. +// If object can't be found, sets isValid to false. +void nsIMAPBodyShell::AdoptMimeHeader(const char *partNum, char *mimeHeader) +{ + if (!GetIsValid()) + return; + + NS_ASSERTION(partNum, "null partnum in body shell"); + + nsIMAPBodypart *foundPart = m_message->FindPartWithNumber(partNum); + + if (foundPart) + { + foundPart->AdoptHeaderDataBuffer(mimeHeader); + if (!foundPart->GetIsValid()) + SetIsValid(false); + } + else + { + SetIsValid(false); + } +} + + +void nsIMAPBodyShell::AddPrefetchToQueue(nsIMAPeFetchFields fields, const char *partNumber) +{ + nsIMAPMessagePartID *newPart = new nsIMAPMessagePartID(fields, partNumber); + if (newPart) + { + m_prefetchQueue->AppendElement(newPart); + } + else + { + // HandleMemoryFailure(); + } +} + +// Flushes all of the prefetches that have been queued up in the prefetch queue, +// freeing them as we go +void nsIMAPBodyShell::FlushPrefetchQueue() +{ + m_protocolConnection->PipelinedFetchMessageParts(GetUID(), m_prefetchQueue); + m_prefetchQueue->RemoveAndFreeAll(); +} + +// Requires that the shell is valid when called +// Performs a preflight check on all message parts to see if they are all +// inline. Returns true if all parts are inline, false otherwise. +bool nsIMAPBodyShell::PreflightCheckAllInline() +{ + bool rv = m_message->PreflightCheckAllInline(this); + // if (rv) + // MOZ_LOG(IMAP, out, ("BODYSHELL: All parts inline. Reverting to whole message download.")); + return rv; +} + +// When partNum is NULL, Generates a whole message and intelligently +// leaves out parts that are not inline. + +// When partNum is not NULL, Generates a MIME part that hasn't been downloaded yet +// Ok, here's how we're going to do this. Essentially, this +// will be the mirror image of the "normal" generation. +// All parts will be left out except a single part which is +// explicitly specified. All relevant headers will be included. +// Libmime will extract only the part of interest, so we don't +// have to worry about the other parts. This also has the +// advantage that it looks like it will be more workable for +// nested parts. For instance, if a user clicks on a link to +// a forwarded message, then that forwarded message may be +// generated along with any images that the forwarded message +// contains, for instance. + + +int32_t nsIMAPBodyShell::Generate(char *partNum) +{ + m_isBeingGenerated = true; + m_generatingPart = partNum; + int32_t contentLength = 0; + + if (!GetIsValid() || PreflightCheckAllInline()) + { + // We don't have a valid shell, or all parts are going to be inline anyway. Fall back to fetching the whole message. +#ifdef DEBUG_chrisf + NS_ASSERTION(GetIsValid()); +#endif + m_generatingWholeMessage = true; + uint32_t messageSize = m_protocolConnection->GetMessageSize(GetUID().get(), true); + m_protocolConnection->SetContentModified(IMAP_CONTENT_NOT_MODIFIED); // So that when we cache it, we know we have the whole message + if (!DeathSignalReceived()) + m_protocolConnection->FallbackToFetchWholeMsg(GetUID(), messageSize); + contentLength = (int32_t) messageSize; // ugh + } + else + { + // We have a valid shell. + bool streamCreated = false; + m_generatingWholeMessage = false; + + ////// PASS 1 : PREFETCH /////// + // First, prefetch any additional headers/data that we need + if (!GetPseudoInterrupted()) + m_message->Generate(this, false, true); // This queues up everything we need to prefetch + // Now, run a single pipelined prefetch (neato!) + FlushPrefetchQueue(); + + ////// PASS 2 : COMPUTE STREAM SIZE /////// + // Next, figure out the size from the parts that we're going to fill in, + // plus all of the MIME headers, plus the message header itself + if (!GetPseudoInterrupted()) + contentLength = m_message->Generate(this, false, false); + + // Setup the stream + if (!GetPseudoInterrupted() && !DeathSignalReceived()) + { + nsresult rv = + m_protocolConnection->BeginMessageDownLoad(contentLength, MESSAGE_RFC822); + if (NS_FAILED(rv)) + { + m_generatingPart = nullptr; + m_protocolConnection->AbortMessageDownLoad(); + return 0; + } + else + { + streamCreated = true; + } + } + + ////// PASS 3 : GENERATE /////// + // Generate the message + if (!GetPseudoInterrupted() && !DeathSignalReceived()) + m_message->Generate(this, true, false); + + // Close the stream here - normal. If pseudointerrupted, the connection will abort the download stream + if (!GetPseudoInterrupted() && !DeathSignalReceived()) + m_protocolConnection->NormalMessageEndDownload(); + else if (streamCreated) + m_protocolConnection->AbortMessageDownLoad(); + + m_generatingPart = NULL; + + } + + m_isBeingGenerated = false; + return contentLength; +} + +bool nsIMAPBodyShell::GetPseudoInterrupted() +{ + bool rv = m_protocolConnection->GetPseudoInterrupted(); + return rv; +} + +bool nsIMAPBodyShell::DeathSignalReceived() +{ + bool rv = m_protocolConnection->DeathSignalReceived(); + return rv; +} + + +///////////// nsIMAPBodypart //////////////////////////////////// + +nsIMAPBodypart::nsIMAPBodypart(char *partNumber, nsIMAPBodypart *parentPart) +{ + SetIsValid(true); + m_parentPart = parentPart; + m_partNumberString = partNumber; // storage adopted + m_partData = NULL; + m_headerData = NULL; + m_boundaryData = NULL; // initialize from parsed BODYSTRUCTURE + m_contentLength = 0; + m_partLength = 0; + + m_contentType = NULL; + m_bodyType = NULL; + m_bodySubType = NULL; + m_bodyID = NULL; + m_bodyDescription = NULL; + m_bodyEncoding = NULL; +} + +nsIMAPBodypart::~nsIMAPBodypart() +{ + PR_FREEIF(m_partNumberString); + PR_FREEIF(m_contentType); + PR_FREEIF(m_bodyType); + PR_FREEIF(m_bodySubType); + PR_FREEIF(m_bodyID); + PR_FREEIF(m_bodyDescription); + PR_FREEIF(m_bodyEncoding); + PR_FREEIF(m_partData); + PR_FREEIF(m_headerData); + PR_FREEIF(m_boundaryData); +} + +void nsIMAPBodypart::SetIsValid(bool valid) +{ + m_isValid = valid; + if (!m_isValid) + { + //MOZ_LOG(IMAP, out, ("BODYSHELL: Part is invalid. Part Number: %s Content-Type: %s", m_partNumberString, m_contentType)); + } +} + +// Adopts storage for part data buffer. If NULL, sets isValid to false. +void nsIMAPBodypart::AdoptPartDataBuffer(char *buf) +{ + m_partData = buf; + if (!m_partData) + { + SetIsValid(false); + } +} + +// Adopts storage for header data buffer. If NULL, sets isValid to false. +void nsIMAPBodypart::AdoptHeaderDataBuffer(char *buf) +{ + m_headerData = buf; + if (!m_headerData) + { + SetIsValid(false); + } +} + +// Finds the part with given part number +// Returns a nsIMAPBodystructure of the matched part if it is this +// or one of its children. Returns NULL otherwise. +nsIMAPBodypart *nsIMAPBodypart::FindPartWithNumber(const char *partNum) +{ + // either brute force, or do it the smart way - look at the number. + // (the parts should be ordered, and hopefully indexed by their number) + + if (m_partNumberString && !PL_strcasecmp(partNum, m_partNumberString)) + return this; + + //if (!m_partNumberString && !PL_strcasecmp(partNum, "1")) + // return this; + + return NULL; +} + +/* +void nsIMAPBodypart::PrefetchMIMEHeader() +{ +if (!m_headerData && !m_shell->DeathSignalReceived()) +{ +m_shell->GetConnection()->FetchMessage(m_shell->GetUID(), kMIMEHeader, true, 0, 0, m_partNumberString); +// m_headerLength will be filled in when it is adopted from the parser +} +if (!m_headerData) +{ +SetIsValid(false); +} +} +*/ + +void nsIMAPBodypart::QueuePrefetchMIMEHeader(nsIMAPBodyShell *aShell) +{ + aShell->AddPrefetchToQueue(kMIMEHeader, m_partNumberString); +} + +int32_t nsIMAPBodypart::GenerateMIMEHeader(nsIMAPBodyShell *aShell, bool stream, bool prefetch) +{ + if (prefetch && !m_headerData) + { + QueuePrefetchMIMEHeader(aShell); + return 0; + } + else if (m_headerData) + { + int32_t mimeHeaderLength = 0; + + if (!ShouldFetchInline(aShell)) + { + // if this part isn't inline, add the X-Mozilla-IMAP-Part header + char *xPartHeader = PR_smprintf("%s: %s", IMAP_EXTERNAL_CONTENT_HEADER, m_partNumberString); + if (xPartHeader) + { + if (stream) + { + aShell->GetConnection()->Log("SHELL","GENERATE-XHeader",m_partNumberString); + aShell->GetConnection()->HandleMessageDownLoadLine(xPartHeader, false); + } + mimeHeaderLength += PL_strlen(xPartHeader); + PR_Free(xPartHeader); + } + } + + mimeHeaderLength += PL_strlen(m_headerData); + if (stream) + { + aShell->GetConnection()->Log("SHELL","GENERATE-MIMEHeader",m_partNumberString); + aShell->GetConnection()->HandleMessageDownLoadLine(m_headerData, false); // all one line? Can we do that? + } + + return mimeHeaderLength; + } + else + { + SetIsValid(false); // prefetch didn't adopt a MIME header + return 0; + } +} + +int32_t nsIMAPBodypart::GeneratePart(nsIMAPBodyShell *aShell, bool stream, bool prefetch) +{ + if (prefetch) + return 0; // don't need to prefetch anything + + if (m_partData) // we have prefetched the part data + { + if (stream) + { + aShell->GetConnection()->Log("SHELL","GENERATE-Part-Prefetched",m_partNumberString); + aShell->GetConnection()->HandleMessageDownLoadLine(m_partData, false); + } + return PL_strlen(m_partData); + } + else // we are fetching and streaming this part's body as we go + { + if (stream && !aShell->DeathSignalReceived()) + { + char *generatingPart = aShell->GetGeneratingPart(); + bool fetchingSpecificPart = (generatingPart && !PL_strcmp(generatingPart, m_partNumberString)); + + aShell->GetConnection()->Log("SHELL","GENERATE-Part-Inline",m_partNumberString); + aShell->GetConnection()->FetchTryChunking(aShell->GetUID(), kMIMEPart, true, m_partNumberString, m_partLength, !fetchingSpecificPart); + } + return m_partLength; // the part length has been filled in from the BODYSTRUCTURE response + } +} + +int32_t nsIMAPBodypart::GenerateBoundary(nsIMAPBodyShell *aShell, bool stream, bool prefetch, bool lastBoundary) +{ + if (prefetch) + return 0; // don't need to prefetch anything + + if (m_boundaryData) + { + if (!lastBoundary) + { + if (stream) + { + aShell->GetConnection()->Log("SHELL","GENERATE-Boundary",m_partNumberString); + aShell->GetConnection()->HandleMessageDownLoadLine(m_boundaryData, false); + } + return PL_strlen(m_boundaryData); + } + else // the last boundary + { + char *lastBoundaryData = PR_smprintf("%s--", m_boundaryData); + if (lastBoundaryData) + { + if (stream) + { + aShell->GetConnection()->Log("SHELL","GENERATE-Boundary-Last",m_partNumberString); + aShell->GetConnection()->HandleMessageDownLoadLine(lastBoundaryData, false); + } + int32_t rv = PL_strlen(lastBoundaryData); + PR_Free(lastBoundaryData); + return rv; + } + else + { + //HandleMemoryFailure(); + return 0; + } + } + } + else + return 0; +} + +int32_t nsIMAPBodypart::GenerateEmptyFilling(nsIMAPBodyShell *aShell, bool stream, bool prefetch) +{ + if (prefetch) + return 0; // don't need to prefetch anything + + const nsString &emptyString = aShell->GetConnection()-> + GetEmptyMimePartString(); + if (!emptyString.IsEmpty()) + { + if (stream) + { + nsImapProtocol *conn = aShell->GetConnection(); + conn->Log("SHELL", "GENERATE-Filling", m_partNumberString); + conn->HandleMessageDownLoadLine(NS_ConvertUTF16toUTF8(emptyString).get(), + false); + } + return emptyString.Length(); + } + else + return 0; +} + + +// Returns true if the prefs say that this content type should +// explicitly be kept in when filling in the shell +bool nsIMAPBodypart::ShouldExplicitlyFetchInline() +{ + return false; +} + + +// Returns true if the prefs say that this content type should +// explicitly be left out when filling in the shell +bool nsIMAPBodypart::ShouldExplicitlyNotFetchInline() +{ + return false; +} + + +///////////// nsIMAPBodypartLeaf ///////////////////////////// + + +nsIMAPBodypartLeaf::nsIMAPBodypartLeaf(char *partNum, + nsIMAPBodypart *parentPart, + char *bodyType, char *bodySubType, + char *bodyID, char *bodyDescription, + char *bodyEncoding, int32_t partLength, + bool preferPlainText) + : nsIMAPBodypart(partNum, parentPart), mPreferPlainText(preferPlainText) +{ + m_bodyType = bodyType; + m_bodySubType = bodySubType; + m_bodyID = bodyID; + m_bodyDescription = bodyDescription; + m_bodyEncoding = bodyEncoding; + m_partLength = partLength; + if (m_bodyType && m_bodySubType) + { + m_contentType = PR_smprintf("%s/%s", m_bodyType, m_bodySubType); + } + SetIsValid(true); +} + +nsIMAPBodypartType nsIMAPBodypartLeaf::GetType() +{ + return IMAP_BODY_LEAF; +} + +int32_t nsIMAPBodypartLeaf::Generate(nsIMAPBodyShell *aShell, bool stream, bool prefetch) +{ + int32_t len = 0; + + if (GetIsValid()) + { + + if (stream && !prefetch) + aShell->GetConnection()->Log("SHELL","GENERATE-Leaf",m_partNumberString); + + // Stream out the MIME part boundary + //GenerateBoundary(); + NS_ASSERTION(m_parentPart, "part has no parent"); + //nsIMAPBodypartMessage *parentMessage = m_parentPart ? m_parentPart->GetnsIMAPBodypartMessage() : NULL; + + // Stream out the MIME header of this part, if this isn't the only body part of a message + //if (parentMessage ? !parentMessage->GetIsTopLevelMessage() : true) + if ((m_parentPart->GetType() != IMAP_BODY_MESSAGE_RFC822) + && !aShell->GetPseudoInterrupted()) + len += GenerateMIMEHeader(aShell, stream, prefetch); + + if (!aShell->GetPseudoInterrupted()) + { + if (ShouldFetchInline(aShell)) + { + // Fetch and stream the content of this part + len += GeneratePart(aShell, stream, prefetch); + } + else + { + // fill in the filling within the empty part + len += GenerateEmptyFilling(aShell, stream, prefetch); + } + } + } + m_contentLength = len; + return m_contentLength; +} + + + +// returns true if this part should be fetched inline for generation. +bool nsIMAPBodypartLeaf::ShouldFetchInline(nsIMAPBodyShell *aShell) +{ + char *generatingPart = aShell->GetGeneratingPart(); + if (generatingPart) + { + // If we are generating a specific part + if (!PL_strcmp(generatingPart, m_partNumberString)) + { + // This is the part we're generating + return true; + } + else + { + // If this is the only body part of a message, and that + // message is the part being generated, then this leaf should + // be inline as well. + if ((m_parentPart->GetType() == IMAP_BODY_MESSAGE_RFC822) && + (!PL_strcmp(m_parentPart->GetPartNumberString(), generatingPart))) + return true; + + // The parent of this part is a multipart + if (m_parentPart->GetType() == IMAP_BODY_MULTIPART) + { + // This is the first text part of a forwarded message + // with a multipart body, and that message is being generated, + // then generate this part. + nsIMAPBodypart *grandParent = m_parentPart->GetParentPart(); + // grandParent must exist, since multiparts need parents + NS_ASSERTION(grandParent, "grandparent doesn't exist for multi-part alt"); + if (grandParent && + (grandParent->GetType() == IMAP_BODY_MESSAGE_RFC822) && + (!PL_strcmp(grandParent->GetPartNumberString(), generatingPart)) && + (m_partNumberString[PL_strlen(m_partNumberString)-1] == '1') && + !PL_strcasecmp(m_bodyType, "text")) + return true; // we're downloading it inline + + + // This is a child of a multipart/appledouble attachment, + // and that multipart/appledouble attachment is being generated + if (m_parentPart && + !PL_strcasecmp(m_parentPart->GetBodySubType(), "appledouble") && + !PL_strcmp(m_parentPart->GetPartNumberString(), generatingPart)) + return true; // we're downloading it inline + } + + // Leave out all other leaves if this isn't the one + // we're generating. + // Maybe change later to check parents, etc. + return false; + } + } + else + { + // We are generating the whole message, possibly (hopefully) + // leaving out non-inline parts + + if (ShouldExplicitlyFetchInline()) + return true; + if (ShouldExplicitlyNotFetchInline()) + return false; + + // If the parent is a message (this is the only body part of that + // message), and that message should be inline, then its body + // should inherit the inline characteristics of that message + if (m_parentPart->GetType() == IMAP_BODY_MESSAGE_RFC822) + return m_parentPart->ShouldFetchInline(aShell); + + // View Attachments As Links is on. + if (!(aShell->GetContentModified() == IMAP_CONTENT_MODIFIED_VIEW_INLINE)) + { + // The last text part is still displayed inline, + // even if View Attachments As Links is on. + nsIMAPBodypart *grandParentPart = m_parentPart->GetParentPart(); + if ((mPreferPlainText || + !PL_strcasecmp(m_parentPart->GetBodySubType(), "mixed")) && + !PL_strcmp(m_partNumberString, "1") && + !PL_strcasecmp(m_bodyType, "text")) + return true; // we're downloading it inline + + if ((!PL_strcasecmp(m_parentPart->GetBodySubType(), "alternative") || + (grandParentPart && + !PL_strcasecmp(grandParentPart->GetBodySubType(), "alternative"))) && + !PL_strcasecmp(m_bodyType, "text") && + ((!PL_strcasecmp(m_bodySubType, "plain") && mPreferPlainText) || + (!PL_strcasecmp(m_bodySubType, "html") && !mPreferPlainText))) + return true; + + // This is the first text part of a top-level multipart. + // For instance, a message with multipart body, where the first + // part is multipart, and this is the first leaf of that first part. + if (m_parentPart->GetType() == IMAP_BODY_MULTIPART && + (PL_strlen(m_partNumberString) >= 2) && + !PL_strcmp(m_partNumberString + PL_strlen(m_partNumberString) - 2, ".1") && // this is the first text type on this level + (!PL_strcmp(m_parentPart->GetPartNumberString(), "1") || !PL_strcmp(m_parentPart->GetPartNumberString(), "2")) && + !PL_strcasecmp(m_bodyType, "text")) + return true; + // This is the first text part of a top-level multipart of the toplevelmessage + // This 'assumes' the text body is first leaf. This is not required for valid email. + // The only other way is to get content-disposition = attachment and exclude those text parts. + if (m_parentPart->GetType() == IMAP_BODY_MULTIPART && + !PL_strcasecmp(m_bodyType, "text") && + !PL_strcmp(m_parentPart->GetPartNumberString(), "0") && + !PL_strcmp(m_partNumberString, "1")) + return true; + + // we may have future problems needing tests here + + return false; // we can leave it on the server + } +#ifdef XP_MACOSX + // If it is either applesingle, or a resource fork for appledouble + if (!PL_strcasecmp(m_contentType, "application/applefile")) + { + // if it is appledouble + if (m_parentPart->GetType() == IMAP_BODY_MULTIPART && + !PL_strcasecmp(m_parentPart->GetBodySubType(), "appledouble")) + { + // This is the resource fork of a multipart/appledouble. + // We inherit the inline attributes of the parent, + // which was derived from its OTHER child. (The data fork.) + return m_parentPart->ShouldFetchInline(aShell); + } + else // it is applesingle + { + return false; // we can leave it on the server + } + } +#endif // XP_MACOSX + + // Leave out parts with type application/* + if (!PL_strcasecmp(m_bodyType, "APPLICATION") && // If it is of type "application" + PL_strncasecmp(m_bodySubType, "x-pkcs7", 7) // and it's not a signature (signatures are inline) + ) + return false; // we can leave it on the server + if (!PL_strcasecmp(m_bodyType, "AUDIO")) + return false; + // Here's where we can add some more intelligence -- let's leave out + // any other parts that we know we can't display inline. + return true; // we're downloading it inline + } +} + + + +bool nsIMAPBodypartMultipart::IsLastTextPart(const char *partNumberString) +{ + // iterate backwards over the parent's part list and if the part is + // text, compare it to the part number string + for (int i = m_partList->Length() - 1; i >= 0; i--) + { + nsIMAPBodypart *part = m_partList->ElementAt(i); + if (!PL_strcasecmp(part->GetBodyType(), "text")) + return !PL_strcasecmp(part->GetPartNumberString(), partNumberString); + } + return false; +} + +bool nsIMAPBodypartLeaf::PreflightCheckAllInline(nsIMAPBodyShell *aShell) +{ + // only need to check this part, since it has no children. + return ShouldFetchInline(aShell); +} + + +///////////// nsIMAPBodypartMessage //////////////////////// + +nsIMAPBodypartMessage::nsIMAPBodypartMessage(char *partNum, + nsIMAPBodypart *parentPart, + bool topLevelMessage, + char *bodyType, char *bodySubType, + char *bodyID, + char *bodyDescription, + char *bodyEncoding, + int32_t partLength, + bool preferPlainText) + : nsIMAPBodypartLeaf(partNum, parentPart, bodyType, bodySubType, bodyID, + bodyDescription, bodyEncoding, partLength, + preferPlainText) +{ + m_topLevelMessage = topLevelMessage; + if (m_topLevelMessage) + { + m_partNumberString = PR_smprintf("0"); + if (!m_partNumberString) + { + SetIsValid(false); + return; + } + } + m_body = NULL; + m_headers = new nsIMAPMessageHeaders(m_partNumberString, this); // We always have a Headers object + if (!m_headers || !m_headers->GetIsValid()) + { + SetIsValid(false); + return; + } + SetIsValid(true); +} + +void nsIMAPBodypartMessage::SetBody(nsIMAPBodypart *body) +{ + if (m_body) + delete m_body; + m_body = body; +} + + +nsIMAPBodypartType nsIMAPBodypartMessage::GetType() +{ + return IMAP_BODY_MESSAGE_RFC822; +} + +nsIMAPBodypartMessage::~nsIMAPBodypartMessage() +{ + delete m_headers; + delete m_body; +} + +int32_t nsIMAPBodypartMessage::Generate(nsIMAPBodyShell *aShell, bool stream, bool prefetch) +{ + if (!GetIsValid()) + return 0; + + m_contentLength = 0; + + if (stream && !prefetch) + aShell->GetConnection()->Log("SHELL","GENERATE-MessageRFC822",m_partNumberString); + + if (!m_topLevelMessage && !aShell->GetPseudoInterrupted()) // not the top-level message - we need the MIME header as well as the message header + { + // but we don't need the MIME headers of a message/rfc822 part if this content + // type is in (part of) the main msg header. In other words, we still need + // these MIME headers if this message/rfc822 body part is enclosed in the msg + // body (most likely as a body part of a multipart/mixed msg). + // Don't fetch (bug 128888) Do fetch (bug 168097) + // ---------------------------------- ----------------------------------- + // message/rfc822 (parent part) message/rfc822 + // message/rfc822 <<<--- multipart/mixed (parent part) + // multipart/mixed message/rfc822 <<<--- + // text/html (body text) multipart/mixed + // text/plain (attachment) text/html (body text) + // application/msword (attachment) text/plain (attachment) + // application/msword (attachment) + // "<<<---" points to the part we're examining here. + if ( PL_strcasecmp(m_bodyType, "message") || PL_strcasecmp(m_bodySubType, "rfc822") || + PL_strcasecmp(m_parentPart->GetBodyType(), "message") || PL_strcasecmp(m_parentPart->GetBodySubType(), "rfc822") ) + m_contentLength += GenerateMIMEHeader(aShell, stream, prefetch); + } + + if (!aShell->GetPseudoInterrupted()) + m_contentLength += m_headers->Generate(aShell, stream, prefetch); + if (!aShell->GetPseudoInterrupted()) + m_contentLength += m_body->Generate(aShell, stream, prefetch); + + return m_contentLength; +} + + + + +bool nsIMAPBodypartMessage::ShouldFetchInline(nsIMAPBodyShell *aShell) +{ + if (m_topLevelMessage) // the main message should always be defined as "inline" + return true; + + char *generatingPart = aShell->GetGeneratingPart(); + if (generatingPart) + { + // If we are generating a specific part + // Always generate containers (just don't fill them in) + // because it is low cost (everything is cached) + // and it gives the message its full MIME structure, + // to avoid any potential mishap. + return true; + } + else + { + // Generating whole message + + if (ShouldExplicitlyFetchInline()) + return true; + if (ShouldExplicitlyNotFetchInline()) + return false; + + + // Message types are inline, by default. + return true; + } +} + +bool nsIMAPBodypartMessage::PreflightCheckAllInline(nsIMAPBodyShell *aShell) +{ + if (!ShouldFetchInline(aShell)) + return false; + + return m_body->PreflightCheckAllInline(aShell); +} + +// Fills in buffer (and adopts storage) for header object +void nsIMAPBodypartMessage::AdoptMessageHeaders(char *headers) +{ + if (!GetIsValid()) + return; + + // we are going to say that the message headers only have + // part data, and no header data. + m_headers->AdoptPartDataBuffer(headers); + if (!m_headers->GetIsValid()) + SetIsValid(false); +} + +// Finds the part with given part number +// Returns a nsIMAPBodystructure of the matched part if it is this +// or one of its children. Returns NULL otherwise. +nsIMAPBodypart *nsIMAPBodypartMessage::FindPartWithNumber(const char *partNum) +{ + // either brute force, or do it the smart way - look at the number. + // (the parts should be ordered, and hopefully indexed by their number) + + if (!PL_strcasecmp(partNum, m_partNumberString)) + return this; + + return m_body->FindPartWithNumber(partNum); +} + +///////////// nsIMAPBodypartMultipart //////////////////////// + + +nsIMAPBodypartMultipart::nsIMAPBodypartMultipart(char *partNum, nsIMAPBodypart *parentPart) : +nsIMAPBodypart(partNum, parentPart) +{ + if (!m_parentPart || (m_parentPart->GetType() == IMAP_BODY_MESSAGE_RFC822)) + { + // the multipart (this) will inherit the part number of its parent + PR_FREEIF(m_partNumberString); + if (!m_parentPart) + { + m_partNumberString = PR_smprintf("0"); + } + else + m_partNumberString = NS_strdup(m_parentPart->GetPartNumberString()); + } + m_partList = new nsTArray<nsIMAPBodypart*>(); + m_bodyType = NS_strdup("multipart"); + if (m_partList && m_parentPart && m_bodyType) + SetIsValid(true); + else + SetIsValid(false); +} + +nsIMAPBodypartType nsIMAPBodypartMultipart::GetType() +{ + return IMAP_BODY_MULTIPART; +} + +nsIMAPBodypartMultipart::~nsIMAPBodypartMultipart() +{ + for (int i = m_partList->Length() - 1; i >= 0; i--) + { + delete m_partList->ElementAt(i); + } + delete m_partList; +} + +void +nsIMAPBodypartMultipart::SetBodySubType(char *bodySubType) +{ + PR_FREEIF(m_bodySubType); + PR_FREEIF(m_contentType); + m_bodySubType = bodySubType; + if (m_bodyType && m_bodySubType) + m_contentType = PR_smprintf("%s/%s", m_bodyType, m_bodySubType); +} + + +int32_t nsIMAPBodypartMultipart::Generate(nsIMAPBodyShell *aShell, bool stream, bool prefetch) +{ + int32_t len = 0; + + if (GetIsValid()) + { + if (stream && !prefetch) + aShell->GetConnection()->Log("SHELL","GENERATE-Multipart",m_partNumberString); + + // Stream out the MIME header of this part + + bool parentIsMessageType = GetParentPart() ? (GetParentPart()->GetType() == IMAP_BODY_MESSAGE_RFC822) : true; + + // If this is multipart/signed, then we always want to generate the MIME headers of this multipart. + // Otherwise, we only want to do it if the parent is not of type "message" + bool needMIMEHeader = !parentIsMessageType; // !PL_strcasecmp(m_bodySubType, "signed") ? true : !parentIsMessageType; + if (needMIMEHeader && !aShell->GetPseudoInterrupted()) // not a message body's type + { + len += GenerateMIMEHeader(aShell, stream, prefetch); + } + + if (ShouldFetchInline(aShell)) + { + for (size_t i = 0; i < m_partList->Length(); i++) + { + if (!aShell->GetPseudoInterrupted()) + len += GenerateBoundary(aShell, stream, prefetch, false); + if (!aShell->GetPseudoInterrupted()) + len += m_partList->ElementAt(i)->Generate(aShell, stream, prefetch); + } + if (!aShell->GetPseudoInterrupted()) + len += GenerateBoundary(aShell, stream, prefetch, true); + } + else + { + // fill in the filling within the empty part + if (!aShell->GetPseudoInterrupted()) + len += GenerateEmptyFilling(aShell, stream, prefetch); + } + } + m_contentLength = len; + return m_contentLength; +} + + +bool nsIMAPBodypartMultipart::ShouldFetchInline(nsIMAPBodyShell *aShell) +{ + char *generatingPart = aShell->GetGeneratingPart(); + if (generatingPart) + { + // If we are generating a specific part + // Always generate containers (just don't fill them in) + // because it is low cost (everything is cached) + // and it gives the message its full MIME structure, + // to avoid any potential mishap. + return true; + } + else + { + // Generating whole message + + if (ShouldExplicitlyFetchInline()) + return true; + if (ShouldExplicitlyNotFetchInline()) + return false; + + if (!PL_strcasecmp(m_bodySubType, "alternative")) + return true; + + nsIMAPBodypart *grandparentPart = m_parentPart->GetParentPart(); + + // if we're a multipart sub-part of multipart alternative, we need to + // be fetched because mime will always display us. + if (!PL_strcasecmp(m_parentPart->GetBodySubType(), "alternative") && + GetType() == IMAP_BODY_MULTIPART) + return true; + // If "Show Attachments as Links" is on, and + // the parent of this multipart is not a message, + // then it's not inline. + if (!(aShell->GetContentModified() == IMAP_CONTENT_MODIFIED_VIEW_INLINE) && + (m_parentPart->GetType() != IMAP_BODY_MESSAGE_RFC822) && + (m_parentPart->GetType() == IMAP_BODY_MULTIPART ? + (grandparentPart ? grandparentPart->GetType() != IMAP_BODY_MESSAGE_RFC822 : true) + : true)) + return false; + + // multiparts are always inline (even multipart/appledouble) + // (their children might not be, though) + return true; + } +} + +bool nsIMAPBodypartMultipart::PreflightCheckAllInline(nsIMAPBodyShell *aShell) +{ + bool rv = ShouldFetchInline(aShell); + + size_t i = 0; + while (rv && (i < m_partList->Length())) + { + rv = m_partList->ElementAt(i)->PreflightCheckAllInline(aShell); + i++; + } + + return rv; +} + +nsIMAPBodypart *nsIMAPBodypartMultipart::FindPartWithNumber(const char *partNum) +{ + NS_ASSERTION(partNum, "null part passed into FindPartWithNumber"); + + // check this + if (!PL_strcmp(partNum, m_partNumberString)) + return this; + + // check children + for (int i = m_partList->Length() - 1; i >= 0; i--) + { + nsIMAPBodypart *foundPart = m_partList->ElementAt(i)->FindPartWithNumber(partNum); + if (foundPart) + return foundPart; + } + + // not this, or any of this's children + return NULL; +} + + + +///////////// nsIMAPMessageHeaders //////////////////////////////////// + + + +nsIMAPMessageHeaders::nsIMAPMessageHeaders(char *partNum, nsIMAPBodypart *parentPart) : +nsIMAPBodypart(partNum, parentPart) +{ + if (!partNum) + { + SetIsValid(false); + return; + } + m_partNumberString = NS_strdup(partNum); + if (!m_partNumberString) + { + SetIsValid(false); + return; + } + if (!m_parentPart || !m_parentPart->GetnsIMAPBodypartMessage()) + { + // Message headers created without a valid Message parent + NS_ASSERTION(false, "creating message headers with invalid message parent"); + SetIsValid(false); + } +} + +nsIMAPBodypartType nsIMAPMessageHeaders::GetType() +{ + return IMAP_BODY_MESSAGE_HEADER; +} + +void nsIMAPMessageHeaders::QueuePrefetchMessageHeaders(nsIMAPBodyShell *aShell) +{ + + if (!m_parentPart->GetnsIMAPBodypartMessage()->GetIsTopLevelMessage()) // not top-level headers + aShell->AddPrefetchToQueue(kRFC822HeadersOnly, m_partNumberString); + else + aShell->AddPrefetchToQueue(kRFC822HeadersOnly, NULL); +} + +int32_t nsIMAPMessageHeaders::Generate(nsIMAPBodyShell *aShell, bool stream, bool prefetch) +{ + // prefetch the header + if (prefetch && !m_partData && !aShell->DeathSignalReceived()) + { + QueuePrefetchMessageHeaders(aShell); + } + + if (stream && !prefetch) + aShell->GetConnection()->Log("SHELL","GENERATE-MessageHeaders",m_partNumberString); + + // stream out the part data + if (ShouldFetchInline(aShell)) + { + if (!aShell->GetPseudoInterrupted()) + m_contentLength = GeneratePart(aShell, stream, prefetch); + } + else + { + m_contentLength = 0; // don't fill in any filling for the headers + } + return m_contentLength; +} + +bool nsIMAPMessageHeaders::ShouldFetchInline(nsIMAPBodyShell *aShell) +{ + return m_parentPart->ShouldFetchInline(aShell); +} + + +///////////// nsIMAPBodyShellCache //////////////////////////////////// + +#if 0 // mscott - commenting out because it does not appear to be used +static int +imap_shell_cache_strcmp (const void *a, const void *b) +{ + return PL_strcmp ((const char *) a, (const char *) b); +} +#endif + +nsIMAPBodyShellCache::nsIMAPBodyShellCache() +: m_shellHash(20) +{ + m_shellList = new nsTArray<nsIMAPBodyShell*>(); +} + +/* static */ nsIMAPBodyShellCache *nsIMAPBodyShellCache::Create() +{ + nsIMAPBodyShellCache *cache = new nsIMAPBodyShellCache(); + if (!cache || !cache->m_shellList) + return NULL; + + return cache; +} + +nsIMAPBodyShellCache::~nsIMAPBodyShellCache() +{ + while (EjectEntry()) ; + delete m_shellList; +} + +// We'll use an LRU scheme here. +// We will add shells in numerical order, so the +// least recently used one will be in slot 0. +bool nsIMAPBodyShellCache::EjectEntry() +{ + if (m_shellList->Length() < 1) + return false; + + nsIMAPBodyShell *removedShell = m_shellList->ElementAt(0); + + m_shellList->RemoveElementAt(0); + m_shellHash.Remove(removedShell->GetUID()); + + return true; +} + +void nsIMAPBodyShellCache::Clear() +{ + while (EjectEntry()) ; +} + +bool nsIMAPBodyShellCache::AddShellToCache(nsIMAPBodyShell *shell) +{ + // If it's already in the cache, then just return. + // This has the side-effect of re-ordering the LRU list + // to put this at the top, which is good, because it's what we want. + if (FindShellForUID(shell->GetUID(), shell->GetFolderName(), shell->GetContentModified())) + return true; + + // OK, so it's not in the cache currently. + + // First, for safety sake, remove any entry with the given UID, + // just in case we have a collision between two messages in different + // folders with the same UID. + RefPtr<nsIMAPBodyShell> foundShell; + m_shellHash.Get(shell->GetUID(), getter_AddRefs(foundShell)); + if (foundShell) + { + m_shellHash.Remove(foundShell->GetUID()); + m_shellList->RemoveElement(foundShell); + } + + // Add the new one to the cache + m_shellList->AppendElement(shell); + + m_shellHash.Put(shell->GetUID(), shell); + shell->SetIsCached(true); + + // while we're not over our size limit, eject entries + bool rv = true; + while (GetSize() > GetMaxSize()) + rv = EjectEntry(); + + return rv; + +} + +nsIMAPBodyShell *nsIMAPBodyShellCache::FindShellForUID(nsCString &UID, const char *mailboxName, + IMAP_ContentModifiedType modType) +{ + RefPtr<nsIMAPBodyShell> foundShell; + m_shellHash.Get(UID, getter_AddRefs(foundShell)); + if (!foundShell) + return nullptr; + // Make sure the content-modified types are compatible. + // This allows us to work seamlessly while people switch between + // View Attachments Inline and View Attachments As Links. + // Enforce the invariant that any cached shell we use + // match the current content-modified settings. + if (modType != foundShell->GetContentModified()) + return nullptr; + + // mailbox names must match also. + if (PL_strcmp(mailboxName, foundShell->GetFolderName())) + return nullptr; + + // adjust the LRU stuff. This defeats the performance gain of the hash if + // it actually is found since this is linear. + m_shellList->RemoveElement(foundShell); + m_shellList->AppendElement(foundShell);// Adds to end + + return foundShell; +} + +///////////// nsIMAPMessagePartID //////////////////////////////////// + + +nsIMAPMessagePartID::nsIMAPMessagePartID(nsIMAPeFetchFields fields, const char *partNumberString) +: m_partNumberString(partNumberString), + m_fields(fields) +{ +} + +nsIMAPMessagePartIDArray::nsIMAPMessagePartIDArray() +{ +} + +nsIMAPMessagePartIDArray::~nsIMAPMessagePartIDArray() +{ + RemoveAndFreeAll(); +} + +void nsIMAPMessagePartIDArray::RemoveAndFreeAll() +{ + uint32_t n = Length(); + for (uint32_t i = 0; i < n; i++) + { + nsIMAPMessagePartID *part = GetPart(i); + delete part; + } + Clear(); +} |