nsresult nsMsgCompose::CreateMessage()

in mailnews/compose/src/nsMsgCompose.cpp [1496:1932]


nsresult nsMsgCompose::CreateMessage(const nsACString& originalMsgURI,
                                     MSG_ComposeType type,
                                     nsIMsgCompFields* compFields) {
  nsresult rv = NS_OK;
  mType = type;
  mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_None;

  mDeleteDraft = (type == nsIMsgCompType::Draft);
  nsAutoCString msgUri(originalMsgURI);
  bool fileUrl = StringBeginsWith(msgUri, "file:"_ns);
  int32_t typeIndex = msgUri.Find("type=application/x-message-display");
  if (typeIndex != kNotFound && typeIndex > 0) {
    // Strip out type=application/x-message-display because it confuses libmime.
    msgUri.Cut(typeIndex, sizeof("type=application/x-message-display"));
    if (fileUrl)  // we're dealing with an .eml file msg
    {
      // We have now removed the type from the uri. Make sure we don't have
      // an uri with "&&" now. If we do, remove the second '&'.
      if (msgUri.CharAt(typeIndex) == '&') msgUri.Cut(typeIndex, 1);
      // Remove possible trailing '?'.
      if (msgUri.CharAt(msgUri.Length() - 1) == '?')
        msgUri.Cut(msgUri.Length() - 1, 1);
    } else  // we're dealing with a message/rfc822 attachment
    {
      // nsURLFetcher will check for "realtype=message/rfc822" and will set the
      // content type to message/rfc822 in the forwarded message.
      msgUri.AppendLiteral("&realtype=message/rfc822");
    }
  }

  if (compFields) {
    m_compFields = reinterpret_cast<nsMsgCompFields*>(compFields);
  } else {
    m_compFields = new nsMsgCompFields();
  }

  if (m_identity && mType != nsIMsgCompType::Draft) {
    // Setup reply-to field.
    nsCString replyTo;
    m_identity->GetReplyTo(replyTo);
    if (!replyTo.IsEmpty()) {
      nsCString resultStr;
      RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetReplyTo()),
                               replyTo, resultStr);
      if (!resultStr.IsEmpty()) {
        replyTo.Append(',');
        replyTo.Append(resultStr);
      }
      m_compFields->SetReplyTo(replyTo.get());
    }

    // Setup auto-Cc field.
    bool doCc;
    m_identity->GetDoCc(&doCc);
    if (doCc) {
      nsCString ccList;
      m_identity->GetDoCcList(ccList);

      nsCString resultStr;
      RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetCc()),
                               ccList, resultStr);
      if (!resultStr.IsEmpty()) {
        ccList.Append(',');
        ccList.Append(resultStr);
      }
      m_compFields->SetCc(ccList.get());
    }

    // Setup auto-Bcc field.
    bool doBcc;
    m_identity->GetDoBcc(&doBcc);
    if (doBcc) {
      nsCString bccList;
      m_identity->GetDoBccList(bccList);

      nsCString resultStr;
      RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetBcc()),
                               bccList, resultStr);
      if (!resultStr.IsEmpty()) {
        bccList.Append(',');
        bccList.Append(resultStr);
      }
      m_compFields->SetBcc(bccList.get());
    }
  }

  if (mType == nsIMsgCompType::Draft) {
    nsCString curDraftIdURL;
    rv = m_compFields->GetDraftId(curDraftIdURL);
    // Skip if no draft id (probably a new draft msg).
    if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty()) {
      nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
      rv = GetMsgDBHdrFromURI(curDraftIdURL, getter_AddRefs(msgDBHdr));
      NS_ASSERTION(NS_SUCCEEDED(rv),
                   "CreateMessage can't get msg header DB interface pointer.");
      if (msgDBHdr) {
        nsCString queuedDisposition;
        msgDBHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY,
                                    queuedDisposition);
        // We need to retrieve the original URI from the database so we can
        // set the disposition flags correctly if the draft is a reply or
        // forwarded message.
        nsCString originalMsgURIfromDB;
        msgDBHdr->GetStringProperty(ORIG_URI_PROPERTY, originalMsgURIfromDB);
        mOriginalMsgURI = originalMsgURIfromDB;
        if (!queuedDisposition.IsEmpty()) {
          if (queuedDisposition.EqualsLiteral("replied"))
            mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Replied;
          else if (queuedDisposition.EqualsLiteral("forward"))
            mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Forwarded;
          else if (queuedDisposition.EqualsLiteral("redirected"))
            mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Redirected;
        }
      }
    } else {
      NS_WARNING("CreateMessage can't get draft id");
    }
  }

  // If we don't have an original message URI, nothing else to do...
  if (msgUri.IsEmpty()) return NS_OK;

  // store the original message URI so we can extract it after we send the
  // message to properly mark any disposition flags like replied or forwarded on
  // the message.
  if (mOriginalMsgURI.IsEmpty()) mOriginalMsgURI = msgUri;

  nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  // "Forward inline" and "Reply with template" processing.
  // Note the early return at the end of the block.
  if (type == nsIMsgCompType::ForwardInline ||
      type == nsIMsgCompType::ReplyWithTemplate) {
    // We want to treat this message as a reference too
    nsCOMPtr<nsIMsgDBHdr> msgHdr;
    rv = GetMsgDBHdrFromURI(msgUri, getter_AddRefs(msgHdr));
    if (NS_SUCCEEDED(rv)) {
      nsAutoCString messageId;
      msgHdr->GetMessageId(messageId);

      nsAutoCString reference;
      // When forwarding we only use the original message for "References:" -
      // recipients don't have the other messages anyway.
      // For reply with template we want to preserve all the references.
      if (type == nsIMsgCompType::ReplyWithTemplate) {
        uint16_t numReferences = 0;
        msgHdr->GetNumReferences(&numReferences);
        for (int32_t i = 0; i < numReferences; i++) {
          nsAutoCString ref;
          msgHdr->GetStringReference(i, ref);
          if (!ref.IsEmpty()) {
            reference.Append('<');
            reference.Append(ref);
            reference.AppendLiteral("> ");
          }
        }
        reference.Trim(" ", false, true);
      }
      msgHdr->GetMessageId(messageId);
      reference.Append('<');
      reference.Append(messageId);
      reference.Append('>');
      m_compFields->SetReferences(reference.get());

      if (type == nsIMsgCompType::ForwardInline) {
        nsString subject;
        msgHdr->GetMime2DecodedSubject(subject);
        nsCString fwdPrefix;
        prefs->GetCharPrefWithDefault("mail.forward_subject_prefix", "Fwd"_ns,
                                      1, fwdPrefix);
        nsString unicodeFwdPrefix;
        CopyUTF8toUTF16(fwdPrefix, unicodeFwdPrefix);
        unicodeFwdPrefix.AppendLiteral(": ");
        subject.Insert(unicodeFwdPrefix, 0);
        m_compFields->SetSubject(subject);
      }
    }

    // Early return for "ForwardInline" and "ReplyWithTemplate" processing.
    return NS_OK;
  }

  // All other processing.

  // Note the following:
  // LoadDraftOrTemplate() is run in nsMsgComposeService::OpenComposeWindow()
  // for five compose types: ForwardInline, ReplyWithTemplate (both covered
  // in the code block above) and Draft, Template and Redirect. For these
  // compose types, the charset is already correct (incl. MIME-applied override)
  // unless the default charset should be used.

  bool isFirstPass = true;
  char* uriList = ToNewCString(msgUri);
  char* uri = uriList;
  char* nextUri;
  do {
    nextUri = strstr(uri, "://");
    if (nextUri) {
      // look for next ://, and then back up to previous ','
      nextUri = strstr(nextUri + 1, "://");
      if (nextUri) {
        *nextUri = '\0';
        char* saveNextUri = nextUri;
        nextUri = strrchr(uri, ',');
        if (nextUri) *nextUri = '\0';
        *saveNextUri = ':';
      }
    }

    nsCOMPtr<nsIMsgDBHdr> msgHdr;
    if (mOrigMsgHdr)
      msgHdr = mOrigMsgHdr;
    else {
      rv = GetMsgDBHdrFromURI(nsDependentCString(uri), getter_AddRefs(msgHdr));
      NS_ENSURE_SUCCESS(rv, rv);
    }
    if (msgHdr) {
      nsString subject;
      rv = msgHdr->GetMime2DecodedSubject(subject);
      if (NS_FAILED(rv)) return rv;

      // Check if (was: is present in the subject
      int32_t wasOffset = subject.RFind(u" (was:"_ns);
      bool strip = true;

      if (wasOffset >= 0) {
        // Check the number of references, to check if was: should be stripped
        // First, assume that it should be stripped; the variable will be set to
        // false later if stripping should not happen.
        uint16_t numRef;
        msgHdr->GetNumReferences(&numRef);
        if (numRef) {
          // If there are references, look for the first message in the thread
          // firstly, get the database via the folder
          nsCOMPtr<nsIMsgFolder> folder;
          msgHdr->GetFolder(getter_AddRefs(folder));
          if (folder) {
            nsCOMPtr<nsIMsgDatabase> db;
            folder->GetMsgDatabase(getter_AddRefs(db));

            if (db) {
              nsAutoCString reference;
              msgHdr->GetStringReference(0, reference);

              nsCOMPtr<nsIMsgDBHdr> refHdr;
              db->GetMsgHdrForMessageID(reference.get(),
                                        getter_AddRefs(refHdr));

              if (refHdr) {
                nsCString refSubject;
                rv = refHdr->GetSubject(refSubject);
                if (NS_SUCCEEDED(rv)) {
                  if (refSubject.Find(" (was:") >= 0) strip = false;
                }
              }
            }
          }
        } else
          strip = false;
      }

      if (strip && wasOffset >= 0) {
        // Strip off the "(was: old subject)" part
        subject.Assign(Substring(subject, 0, wasOffset));
      }

      switch (type) {
        default:
          break;
        case nsIMsgCompType::Draft:
        case nsIMsgCompType::Template:
        case nsIMsgCompType::EditTemplate:
        case nsIMsgCompType::EditAsNew: {
          // If opening from file, preseve the subject already present, since
          // we can't get a subject from db there.
          if (mOriginalMsgURI.Find("&realtype=message/rfc822") != -1) {
            break;
          }
          // Otherwise, set up the subject from db, with possible modifications.
          uint32_t flags;
          msgHdr->GetFlags(&flags);
          if (flags & nsMsgMessageFlags::HasRe) {
            subject.InsertLiteral(u"Re: ", 0);
          }
          // Set subject from db, where it's already decrypted. The raw
          // header may be encrypted.
          m_compFields->SetSubject(subject);
          break;
        }
        case nsIMsgCompType::Reply:
        case nsIMsgCompType::ReplyAll:
        case nsIMsgCompType::ReplyToList:
        case nsIMsgCompType::ReplyToGroup:
        case nsIMsgCompType::ReplyToSender:
        case nsIMsgCompType::ReplyToSenderAndGroup: {
          if (!isFirstPass)  // safeguard, just in case...
          {
            PR_Free(uriList);
            return rv;
          }
          mQuotingToFollow = true;

          subject.InsertLiteral(u"Re: ", 0);
          m_compFields->SetSubject(subject);

          // Setup quoting callbacks for later...
          mWhatHolder = 1;
          break;
        }
        case nsIMsgCompType::ForwardAsAttachment: {
          // Add the forwarded message in the references, first
          nsAutoCString messageId;
          msgHdr->GetMessageId(messageId);
          if (isFirstPass) {
            nsAutoCString reference;
            reference.Append('<');
            reference.Append(messageId);
            reference.Append('>');
            m_compFields->SetReferences(reference.get());
          } else {
            nsAutoCString references;
            m_compFields->GetReferences(getter_Copies(references));
            references.AppendLiteral(" <");
            references.Append(messageId);
            references.Append('>');
            m_compFields->SetReferences(references.get());
          }

          uint32_t flags;
          msgHdr->GetFlags(&flags);
          if (flags & nsMsgMessageFlags::HasRe)
            subject.InsertLiteral(u"Re: ", 0);

          // Setup quoting callbacks for later...
          mQuotingToFollow =
              false;  // We don't need to quote the original message.
          nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(
              "@mozilla.org/messengercompose/attachment;1", &rv);
          if (NS_SUCCEEDED(rv) && attachment) {
            bool addExtension = true;
            nsString sanitizedSubj;
            prefs->GetBoolPref("mail.forward_add_extension", &addExtension);

            // copy subject string to sanitizedSubj, use default if empty
            if (subject.IsEmpty()) {
              nsresult rv;
              nsCOMPtr<nsIStringBundleService> bundleService =
                  mozilla::components::StringBundle::Service();
              NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
              nsCOMPtr<nsIStringBundle> composeBundle;
              rv = bundleService->CreateBundle(
                  "chrome://messenger/locale/messengercompose/"
                  "composeMsgs.properties",
                  getter_AddRefs(composeBundle));
              NS_ENSURE_SUCCESS(rv, rv);
              composeBundle->GetStringFromName("messageAttachmentSafeName",
                                               sanitizedSubj);
            } else
              sanitizedSubj.Assign(subject);

            // set the file size
            uint32_t messageSize;
            msgHdr->GetMessageSize(&messageSize);
            attachment->SetSize(messageSize);

            // change all '.' to '_'  see bug #271211
            sanitizedSubj.ReplaceChar(u".", u'_');
            if (addExtension) sanitizedSubj.AppendLiteral(".eml");
            attachment->SetName(NS_ConvertUTF16toUTF8(sanitizedSubj));
            attachment->SetUrl(nsDependentCString(uri));
            attachment->SetContentType("message/rfc822"_ns);
            m_compFields->AddAttachment(attachment);
          }

          if (isFirstPass) {
            nsCString fwdPrefix;
            prefs->GetCharPrefWithDefault("mail.forward_subject_prefix",
                                          "Fwd"_ns, 1, fwdPrefix);
            nsString unicodeFwdPrefix;
            CopyUTF8toUTF16(fwdPrefix, unicodeFwdPrefix);
            unicodeFwdPrefix.AppendLiteral(": ");
            subject.Insert(unicodeFwdPrefix, 0);
            m_compFields->SetSubject(subject);
          }
          break;
        }
        case nsIMsgCompType::Redirect: {
          // For a redirect, set the Reply-To: header to what was in the
          // original From: header...
          nsAutoCString author;
          msgHdr->GetAuthor(author);
          m_compFields->SetSubject(subject);
          m_compFields->SetReplyTo(author.get());

          // ... and empty out the various recipient headers
          nsAutoString empty;
          m_compFields->SetTo(empty);
          m_compFields->SetCc(empty);
          m_compFields->SetBcc(empty);
          m_compFields->SetNewsgroups(empty);
          m_compFields->SetFollowupTo(empty);

          // Add the redirected message in the references so that threading
          // will work when the new recipient eventually replies to the
          // original sender.
          nsAutoCString messageId;
          msgHdr->GetMessageId(messageId);
          if (isFirstPass) {
            nsAutoCString reference;
            reference.Append('<');
            reference.Append(messageId);
            reference.Append('>');
            m_compFields->SetReferences(reference.get());
          } else {
            nsAutoCString references;
            m_compFields->GetReferences(getter_Copies(references));
            references.AppendLiteral(" <");
            references.Append(messageId);
            references.Append('>');
            m_compFields->SetReferences(references.get());
          }
          break;
        }
      }
    }
    isFirstPass = false;
    if (nextUri) {
      // `nextUri` can be a null pointer if `strstr` did not find `://` in the
      // URI earlier. Only increment it if that is not the case, to avoid
      // undefined behaviors.
      uri = nextUri + 1;
    }
  } while (nextUri);
  PR_Free(uriList);
  return rv;
}