mailnews/imap/src/nsIMAPBodyShell.cpp
author Rob Lemley <rob@thunderbird.net>
Mon, 27 Apr 2020 19:51:09 -0400
changeset 38064 39e1d01fb0c37b7f70bc92d58a9a06f8148e6981
parent 37655 3528b15b3c008d410a37abdb2c363fc85954a051
child 38590 9c08c80ec341936cd58f8a8fbdf01a59bec5fd17
permissions -rw-r--r--
Bug 1577518 - Update libgcrypt to version 1.8.5. r=kaie

/* -*- 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 "msgCore.h"
#include "nsIMAPBodyShell.h"
#include "nsImapProtocol.h"

#include "nsMimeTypes.h"
#include "nsServiceManagerUtils.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(nsImapProtocol *protocolConnection,
                                 nsIMAPBodypartMessage *message, uint32_t UID,
                                 uint32_t UIDValidity, 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_UID = "";
  m_UID.AppendInt(UID);
  m_UID_validity = m_UID;
  m_UID_validity.AppendInt(UIDValidity);
#ifdef DEBUG_chrisf
  NS_ASSERTION(folderName);
#endif
  if (!folderName) return;
  m_folderName = NS_xstrdup(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;
  m_prefetchQueue.Clear();
  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(fields, partNumber);
  m_prefetchQueue.AppendElement(newPart);
}

// 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.Clear();
}

// 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());
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("Generate(): Set IMAP_CONTENT_NOT MODIFIED"));
    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;
      }
      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;
  }
  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;
  }

  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);
  }

  // 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);
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("GeneratePart(): Call FetchTryChunking() part length=%" PRIi32
             ", part number=%s",
             m_partLength, 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);
    }

    // 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;
    }
    // HandleMemoryFailure();
    return 0;
  }
  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();
  }
  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;
    }

    // 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;
  }

  // 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->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;
  }

  // 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_partList = new nsTArray<nsIMAPBodypart *>();
  m_bodyType = NS_xstrdup("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;
  }

  // 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_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, 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_validity(), 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_validity(), getter_AddRefs(foundShell));
  if (foundShell) {
    m_shellHash.Remove(foundShell->GetUID_validity());
    m_shellList->RemoveElement(foundShell);
  }

  // Add the new one to the cache
  m_shellList->AppendElement(shell);

  m_shellHash.Put(shell->GetUID_validity(), RefPtr{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) {}