Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsImapBodyShell.h"
#include "nsImapProtocol.h"
#include "nsMimeTypes.h"
#include "mozilla/Logging.h"
// need to talk to Rich about this...
#define IMAP_EXTERNAL_CONTENT_HEADER "X-Mozilla-IMAP-Part"
using namespace mozilla;
extern LazyLogModule IMAPCache; // defined in nsImapProtocol.cpp
// 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(nsIMAPBodypartMessage* message, uint32_t UID,
uint32_t UIDValidity, const char* folderName,
bool showAttachmentsInline)
: m_message(message),
m_isValid(false),
m_folderName(folderName),
m_generatingPart(nullptr),
m_isBeingGenerated(false),
m_cached(false),
m_generatingWholeMessage(false),
m_contentModified(IMAP_ContentModifiedType::IMAP_CONTENT_NOT_MODIFIED) {
m_UID = "";
m_UID.AppendInt(UID);
m_UID_validity = m_UID;
m_UID_validity.AppendInt(UIDValidity);
m_contentModified = showAttachmentsInline
? IMAP_CONTENT_MODIFIED_VIEW_INLINE
: IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS;
SetIsValid(m_message != nullptr);
}
nsImapBodyShell::~nsImapBodyShell() {
delete m_message;
m_prefetchQueue.Clear();
}
void nsImapBodyShell::SetIsValid(bool valid) { m_isValid = valid; }
// 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(fields, partNumber);
m_prefetchQueue.AppendElement(newPart);
}
// 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(nsImapProtocol* conn, char* partNum) {
// Hold the connection in existence for the duration.
RefPtr<nsImapProtocol> kungFuDeathGrip(conn);
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 = conn->GetMessageSize(GetUID());
MOZ_LOG(IMAPCache, LogLevel::Debug,
("Generate(): Set IMAP_CONTENT_NOT MODIFIED"));
// So that when we cache it, we know we have the whole message.
conn->SetContentModified(IMAP_CONTENT_NOT_MODIFIED);
if (!conn->DeathSignalReceived())
conn->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 (!conn->GetPseudoInterrupted())
m_message->Generate(
this, conn, false,
true); // This queues up everything we need to prefetch
// Now, run a single pipelined prefetch (neato!)
conn->PipelinedFetchMessageParts(GetUID(), m_prefetchQueue);
m_prefetchQueue.Clear();
////// 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 (!conn->GetPseudoInterrupted())
contentLength = m_message->Generate(this, conn, false, false);
// Setup the stream
if (!conn->GetPseudoInterrupted() && !conn->DeathSignalReceived()) {
nsresult rv = conn->BeginMessageDownLoad(contentLength, MESSAGE_RFC822);
if (NS_FAILED(rv)) {
m_generatingPart = nullptr;
conn->AbortMessageDownLoad();
return 0;
}
streamCreated = true;
}
////// PASS 3 : GENERATE ///////
// Generate the message
if (!conn->GetPseudoInterrupted() && !conn->DeathSignalReceived())
m_message->Generate(this, conn, true, false);
// Close the stream here - normal. If pseudointerrupted, the connection
// will abort the download stream
if (!conn->GetPseudoInterrupted() && !conn->DeathSignalReceived())
conn->NormalMessageEndDownload();
else if (streamCreated)
conn->AbortMessageDownLoad();
m_generatingPart = NULL;
}
m_isBeingGenerated = false;
return contentLength;
}
///////////// 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::QueuePrefetchMIMEHeader(nsImapBodyShell* aShell) {
aShell->AddPrefetchToQueue(kMIMEHeader, m_partNumberString);
}
int32_t nsIMAPBodypart::GenerateMIMEHeader(nsImapBodyShell* aShell,
nsImapProtocol* conn, bool stream,
bool prefetch) {
if (prefetch && !m_headerData) {
QueuePrefetchMIMEHeader(aShell);
return 0;
}
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) {
conn->Log("SHELL", "GENERATE-XHeader", m_partNumberString);
conn->HandleMessageDownLoadLine(xPartHeader, false);
}
mimeHeaderLength += PL_strlen(xPartHeader);
PR_Free(xPartHeader);
}
}
mimeHeaderLength += PL_strlen(m_headerData);
if (stream) {
conn->Log("SHELL", "GENERATE-MIMEHeader", m_partNumberString);
conn->HandleMessageDownLoadLine(m_headerData,
false); // all one line? Can we do that?
}
return mimeHeaderLength;
}
SetIsValid(false); // prefetch didn't adopt a MIME header
return 0;
}
int32_t nsIMAPBodypart::GeneratePart(nsImapBodyShell* aShell,
nsImapProtocol* conn, 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) {
conn->Log("SHELL", "GENERATE-Part-Prefetched", m_partNumberString);
conn->HandleMessageDownLoadLine(m_partData, false);
}
return PL_strlen(m_partData);
}
// we are fetching and streaming this part's body as we go
if (stream && !conn->DeathSignalReceived()) {
char* generatingPart = aShell->GetGeneratingPart();
bool fetchingSpecificPart =
(generatingPart && !PL_strcmp(generatingPart, m_partNumberString));
conn->Log("SHELL", "GENERATE-Part-Inline", m_partNumberString);
MOZ_LOG(IMAPCache, LogLevel::Debug,
("GeneratePart(): Call FetchTryChunking() part length=%" PRIi32
", part number=%s",
m_partLength, m_partNumberString));
conn->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,
nsImapProtocol* conn, bool stream,
bool prefetch, bool lastBoundary) {
if (prefetch) return 0; // don't need to prefetch anything
if (m_boundaryData) {
if (!lastBoundary) {
if (stream) {
conn->Log("SHELL", "GENERATE-Boundary", m_partNumberString);
conn->HandleMessageDownLoadLine(m_boundaryData, false);
}
return PL_strlen(m_boundaryData);
}
// the last boundary
char* lastBoundaryData = PR_smprintf("%s--", m_boundaryData);
if (lastBoundaryData) {
if (stream) {
conn->Log("SHELL", "GENERATE-Boundary-Last", m_partNumberString);
conn->HandleMessageDownLoadLine(lastBoundaryData, false);
}
int32_t rv = PL_strlen(lastBoundaryData);
PR_Free(lastBoundaryData);
return rv;
}
// HandleMemoryFailure();
return 0;
}
return 0;
}
int32_t nsIMAPBodypart::GenerateEmptyFilling(nsImapBodyShell* aShell,
nsImapProtocol* conn, bool stream,
bool prefetch) {
if (prefetch) return 0; // don't need to prefetch anything
const nsString& emptyString = conn->GetEmptyMimePartString();
if (!emptyString.IsEmpty()) {
if (stream) {
conn->Log("SHELL", "GENERATE-Filling", m_partNumberString);
conn->HandleMessageDownLoadLine(NS_ConvertUTF16toUTF8(emptyString).get(),
false);
}
return emptyString.Length();
}
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,
nsImapProtocol* conn, bool stream,
bool prefetch) {
int32_t len = 0;
if (GetIsValid()) {
if (stream && !prefetch)
conn->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) &&
!conn->GetPseudoInterrupted())
len += GenerateMIMEHeader(aShell, conn, stream, prefetch);
if (!conn->GetPseudoInterrupted()) {
if (ShouldFetchInline(aShell)) {
// Fetch and stream the content of this part
len += GeneratePart(aShell, conn, stream, prefetch);
} else {
// fill in the filling within the empty part
len += GenerateEmptyFilling(aShell, conn, 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;
}
// 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 (!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;
}
// 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);
}
// it is applesingle
return false; // we can leave it on the server
}
#endif // XP_MACOSX
// Fetch type APPLICATION now if the subtype is a signature or if it's an
// octet-stream. Otherwise, fetch on demand.
if (!PL_strcasecmp(m_bodyType, "APPLICATION") &&
PL_strncasecmp(m_bodySubType, "x-pkcs7", 7) &&
PL_strcasecmp(m_bodySubType, "octet-stream"))
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[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,
nsImapProtocol* conn, bool stream,
bool prefetch) {
if (!GetIsValid()) return 0;
m_contentLength = 0;
if (stream && !prefetch)
conn->Log("SHELL", "GENERATE-MessageRFC822", m_partNumberString);
if (!m_topLevelMessage &&
!conn->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, conn, stream, prefetch);
}
if (!conn->GetPseudoInterrupted())
m_contentLength += m_headers->Generate(aShell, conn, stream, prefetch);
if (!conn->GetPseudoInterrupted())
m_contentLength += m_body->Generate(aShell, conn, 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;
}
// 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_xstrdup(m_parentPart->GetPartNumberString());
}
m_bodyType = NS_xstrdup("multipart");
if (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[i];
}
}
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,
nsImapProtocol* conn, bool stream,
bool prefetch) {
int32_t len = 0;
if (GetIsValid()) {
if (stream && !prefetch)
conn->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 &&
!conn->GetPseudoInterrupted()) // not a message body's type
{
len += GenerateMIMEHeader(aShell, conn, stream, prefetch);
}
if (ShouldFetchInline(aShell)) {
for (auto part : m_partList) {
if (!conn->GetPseudoInterrupted())
len += GenerateBoundary(aShell, conn, stream, prefetch, false);
if (!conn->GetPseudoInterrupted())
len += part->Generate(aShell, conn, stream, prefetch);
}
if (!conn->GetPseudoInterrupted())
len += GenerateBoundary(aShell, conn, stream, prefetch, true);
} else {
// fill in the filling within the empty part
if (!conn->GetPseudoInterrupted())
len += GenerateEmptyFilling(aShell, conn, 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;
}
// 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[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[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_xstrdup(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,
nsImapProtocol* conn, bool stream,
bool prefetch) {
// prefetch the header
if (prefetch && !m_partData && !conn->DeathSignalReceived()) {
QueuePrefetchMessageHeaders(aShell);
}
if (stream && !prefetch)
conn->Log("SHELL", "GENERATE-MessageHeaders", m_partNumberString);
// stream out the part data
if (ShouldFetchInline(aShell)) {
if (!conn->GetPseudoInterrupted())
m_contentLength = GeneratePart(aShell, conn, 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 ////////////////////////////////////
nsImapBodyShellCache::nsImapBodyShellCache()
: m_shellList(kMaxEntries), m_shellHash(kMaxEntries) {}
// 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.
void nsImapBodyShellCache::EjectEntry() {
MOZ_ASSERT(!m_shellList.IsEmpty());
nsImapBodyShell* removedShell = m_shellList.ElementAt(0);
m_shellList.RemoveElementAt(0);
m_shellHash.Remove(removedShell->GetUID());
}
void nsImapBodyShellCache::Clear() {
m_shellList.ClearAndRetainStorage();
m_shellHash.Clear();
}
void 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_validity(), shell->GetFolderName(),
shell->GetContentModified())) {
return;
}
// 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_validity(), getter_AddRefs(foundShell));
if (foundShell) {
m_shellHash.Remove(foundShell->GetUID_validity());
m_shellList.RemoveElement(foundShell);
}
// Make sure there's enough room
while (m_shellList.Length() > (kMaxEntries - 1)) {
EjectEntry();
}
// Add the new one to the cache
m_shellList.AppendElement(shell);
m_shellHash.InsertOrUpdate(shell->GetUID_validity(), RefPtr{shell});
shell->SetIsCached(true);
}
nsImapBodyShell* nsImapBodyShellCache::FindShellForUID(
nsACString const& UID, nsACString const& 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 (!mailboxName.Equals(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) {}