/* -*- 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 "nsCOMPtr.h"
#include "nsMsgCompUtils.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsStringFwd.h"
#include "prmem.h"
#include "nsIStringBundle.h"
#include "nsIIOService.h"
#include "nsIHttpProtocolHandler.h"
#include "nsMailHeaders.h"
#include "nsMsgI18N.h"
#include "nsINntpService.h"
#include "nsMimeTypes.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIURI.h"
#include "nsNetCID.h"
#include "nsMsgPrompts.h"
#include "nsMsgUtils.h"
#include "nsCExternalHandlerService.h"
#include "nsIMIMEService.h"
#include "nsComposeStrings.h"
#include "nsIMsgCompUtils.h"
#include "nsIMsgMdnGenerator.h"
#include "nsServiceManagerUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsMemory.h"
#include "nsCRTGlue.h"
#include <ctype.h>
#include "mozilla/dom/Element.h"
#include "mozilla/EncodingDetector.h"
#include "mozilla/Components.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/dom/Document.h"
#include "nsIMIMEInfo.h"
#include "nsIMsgHeaderParser.h"
#include "nsIMutableArray.h"
#include "nsIRandomGenerator.h"
#include "nsID.h"

void msg_generate_message_id(nsIMsgIdentity* identity,
                             const nsACString& customHost,
                             nsACString& messageID);

NS_IMPL_ISUPPORTS(nsMsgCompUtils, nsIMsgCompUtils)

nsMsgCompUtils::nsMsgCompUtils() {}

nsMsgCompUtils::~nsMsgCompUtils() {}

NS_IMETHODIMP nsMsgCompUtils::MimeMakeSeparator(const char* prefix,
                                                char** _retval) {
  NS_ENSURE_ARG_POINTER(prefix);
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = mime_make_separator(prefix);
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompUtils::MsgGenerateMessageId(nsIMsgIdentity* identity,
                                                   const nsACString& host,
                                                   nsACString& messageID) {
  // We don't check `host` because it's allowed to be a null pointer (which
  // means we should ignore it for message ID generation).
  NS_ENSURE_ARG_POINTER(identity);
  msg_generate_message_id(identity, host, messageID);
  return NS_OK;
}

NS_IMETHODIMP nsMsgCompUtils::GetMsgMimeConformToStandard(bool* _retval) {
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = nsMsgMIMEGetConformToStandard();
  return NS_OK;
}

NS_IMETHODIMP
nsMsgCompUtils::DetectCharset(const nsACString& aContent,
                              nsACString& aCharset) {
  mozilla::UniquePtr<mozilla::EncodingDetector> detector =
      mozilla::EncodingDetector::Create();
  mozilla::Span<const uint8_t> src = mozilla::AsBytes(
      mozilla::Span(ToNewCString(aContent), aContent.Length()));
  mozilla::Unused << detector->Feed(src, true);
  auto encoding = detector->Guess(nullptr, true);
  encoding->Name(aCharset);
  return NS_OK;
}

//
// Create a file for the a unique temp file
// on the local machine. Caller must free memory
//
nsresult nsMsgCreateTempFile(const char* tFileName, nsIFile** tFile) {
  if ((!tFileName) || (!*tFileName)) tFileName = "nsmail.tmp";

  nsresult rv =
      GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, tFileName, tFile);

  NS_ENSURE_SUCCESS(rv, rv);

  rv = (*tFile)->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
  if (NS_FAILED(rv)) NS_RELEASE(*tFile);

  return rv;
}

// This is the value a caller will Get if they don't Set first (like MDN
// sending a return receipt), so init to the default value of the
// mail.strictly_mime_headers preference.
static bool mime_headers_use_quoted_printable_p = true;

bool nsMsgMIMEGetConformToStandard(void) {
  return mime_headers_use_quoted_printable_p;
}

void nsMsgMIMESetConformToStandard(bool conform_p) {
  /*
   * If we are conforming to mime standard no matter what we set
   * for the headers preference when generating mime headers we should
   * also conform to the standard. Otherwise, depends the preference
   * we set. For now, the headers preference is not accessible from UI.
   */
  if (conform_p)
    mime_headers_use_quoted_printable_p = true;
  else {
    nsresult rv;
    nsCOMPtr<nsIPrefBranch> prefs(
        do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
    if (NS_SUCCEEDED(rv)) {
      prefs->GetBoolPref("mail.strictly_mime_headers",
                         &mime_headers_use_quoted_printable_p);
    }
  }
}

/**
 * Checks if the recipient fields have sane values for message send.
 */
nsresult mime_sanity_check_fields_recipients(const char* to, const char* cc,
                                             const char* bcc,
                                             const char* newsgroups) {
  if (to)
    while (IS_SPACE(*to)) to++;
  if (cc)
    while (IS_SPACE(*cc)) cc++;
  if (bcc)
    while (IS_SPACE(*bcc)) bcc++;
  if (newsgroups)
    while (IS_SPACE(*newsgroups)) newsgroups++;

  if ((!to || !*to) && (!cc || !*cc) && (!bcc || !*bcc) &&
      (!newsgroups || !*newsgroups))
    return NS_MSG_NO_RECIPIENTS;

  return NS_OK;
}

/**
 * Checks if the compose fields have sane values for message send.
 */
nsresult mime_sanity_check_fields(
    const char* from, const char* reply_to, const char* to, const char* cc,
    const char* bcc, const char* fcc, const char* newsgroups,
    const char* followup_to, const char* /*subject*/,
    const char* /*references*/, const char* /*organization*/,
    const char* /*other_random_headers*/) {
  if (from)
    while (IS_SPACE(*from)) from++;
  if (reply_to)
    while (IS_SPACE(*reply_to)) reply_to++;
  if (fcc)
    while (IS_SPACE(*fcc)) fcc++;
  if (followup_to)
    while (IS_SPACE(*followup_to)) followup_to++;

  // TODO: sanity check other_random_headers for newline conventions
  if (!from || !*from) return NS_MSG_NO_SENDER;

  return mime_sanity_check_fields_recipients(to, cc, bcc, newsgroups);
}

// Helper macro for generating the X-Mozilla-Draft-Info header.
#define APPEND_BOOL(method, param)         \
  do {                                     \
    bool val = false;                      \
    fields->Get##method(&val);             \
    if (val)                               \
      draftInfo.AppendLiteral(param "=1"); \
    else                                   \
      draftInfo.AppendLiteral(param "=0"); \
  } while (false)

nsresult mime_generate_headers(nsIMsgCompFields* fields,
                               nsMsgDeliverMode deliver_mode,
                               msgIWritableStructuredHeaders* finalHeaders) {
  nsresult rv = NS_OK;

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

  bool isDraft = deliver_mode == nsIMsgSend::nsMsgSaveAsDraft ||
                 deliver_mode == nsIMsgSend::nsMsgSaveAsTemplate ||
                 deliver_mode == nsIMsgSend::nsMsgQueueForLater ||
                 deliver_mode == nsIMsgSend::nsMsgDeliverBackground;

  bool hasDisclosedRecipient = false;

  MOZ_ASSERT(fields, "null fields");
  NS_ENSURE_ARG_POINTER(fields);

  nsTArray<RefPtr<msgIAddressObject>> from;
  fields->GetAddressingHeader("From", true, from);

  // Copy all headers from the original compose field.
  rv = finalHeaders->AddAllHeaders(fields);
  NS_ENSURE_SUCCESS(rv, rv);

  bool hasMessageId = false;
  if (NS_SUCCEEDED(fields->HasHeader("Message-ID", &hasMessageId)) &&
      hasMessageId) {
    /* MDN request header requires to have MessageID header presented
     * in the message in order to
     * coorelate the MDN reports to the original message. Here will be
     * the right place
     */

    bool returnReceipt = false;
    fields->GetReturnReceipt(&returnReceipt);
    if (returnReceipt && (deliver_mode != nsIMsgSend::nsMsgSaveAsDraft &&
                          deliver_mode != nsIMsgSend::nsMsgSaveAsTemplate)) {
      int32_t receipt_header_type = nsIMsgMdnGenerator::eDntType;
      fields->GetReceiptHeaderType(&receipt_header_type);

      // nsIMsgMdnGenerator::eDntType = MDN Disposition-Notification-To: ;
      // nsIMsgMdnGenerator::eRrtType = Return-Receipt-To: ;
      // nsIMsgMdnGenerator::eDntRrtType = both MDN DNT and RRT headers .
      if (receipt_header_type != nsIMsgMdnGenerator::eRrtType)
        finalHeaders->SetAddressingHeader("Disposition-Notification-To", from);
      if (receipt_header_type != nsIMsgMdnGenerator::eDntType)
        finalHeaders->SetAddressingHeader("Return-Receipt-To", from);
    }
  }

  PRExplodedTime now;
  PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
  int gmtoffset =
      (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60;

  // Use PR_FormatTimeUSEnglish() to format the date in US English format,
  // then figure out what our local GMT offset is, and append it (since
  // PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as
  // per RFC 1123 (superseding RFC 822.)
  char dateString[130];
  PR_FormatTimeUSEnglish(dateString, sizeof(dateString),
                         "%a, %d %b %Y %H:%M:%S ", &now);

  char* entryPoint = dateString + strlen(dateString);
  PR_snprintf(entryPoint, sizeof(dateString) - (entryPoint - dateString),
              "%c%02d%02d" CRLF, (gmtoffset >= 0 ? '+' : '-'),
              ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60),
              ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60));
  finalHeaders->SetRawHeader("Date", nsDependentCString(dateString));

  // X-Mozilla-Draft-Info
  if (isDraft) {
    nsAutoCString draftInfo;
    draftInfo.AppendLiteral("internal/draft; ");
    APPEND_BOOL(AttachVCard, "vcard");
    draftInfo.AppendLiteral("; ");
    bool hasReturnReceipt = false;
    fields->GetReturnReceipt(&hasReturnReceipt);
    if (hasReturnReceipt) {
      // slight change compared to 4.x; we used to use receipt= to tell
      // whether the draft/template has request for either MDN or DNS or both
      // return receipt; since the DNS is out of the picture we now use the
      // header type + 1 to tell whether user has requested the return receipt
      int32_t headerType = 0;
      fields->GetReceiptHeaderType(&headerType);
      draftInfo.AppendLiteral("receipt=");
      draftInfo.AppendInt(headerType + 1);
    } else
      draftInfo.AppendLiteral("receipt=0");
    draftInfo.AppendLiteral("; ");
    APPEND_BOOL(DSN, "DSN");
    draftInfo.AppendLiteral("; ");
    draftInfo.AppendLiteral("uuencode=0");
    draftInfo.AppendLiteral("; ");
    APPEND_BOOL(AttachmentReminder, "attachmentreminder");
    draftInfo.AppendLiteral("; ");
    int32_t deliveryFormat;
    fields->GetDeliveryFormat(&deliveryFormat);
    draftInfo.AppendLiteral("deliveryformat=");
    draftInfo.AppendInt(deliveryFormat);

    finalHeaders->SetRawHeader(HEADER_X_MOZILLA_DRAFT_INFO, draftInfo);
  }

  bool sendUserAgent = false;
  if (prefs) {
    prefs->GetBoolPref("mailnews.headers.sendUserAgent", &sendUserAgent);
  }
  if (sendUserAgent) {
    bool useMinimalUserAgent = false;
    if (prefs) {
      prefs->GetBoolPref("mailnews.headers.useMinimalUserAgent",
                         &useMinimalUserAgent);
    }
    if (useMinimalUserAgent) {
      nsCOMPtr<nsIStringBundleService> bundleService =
          mozilla::components::StringBundle::Service();
      if (bundleService) {
        nsCOMPtr<nsIStringBundle> brandBundle;
        rv = bundleService->CreateBundle(
            "chrome://branding/locale/brand.properties",
            getter_AddRefs(brandBundle));
        if (NS_SUCCEEDED(rv)) {
          nsString brandName;
          brandBundle->GetStringFromName("brandFullName", brandName);
          if (!brandName.IsEmpty())
            finalHeaders->SetUnstructuredHeader("User-Agent", brandName);
        }
      }
    } else {
      nsCOMPtr<nsIHttpProtocolHandler> pHTTPHandler =
          do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv);
      if (NS_SUCCEEDED(rv) && pHTTPHandler) {
        nsAutoCString userAgentString;
        // Ignore error since we're testing the return value.
        mozilla::Unused << pHTTPHandler->GetUserAgent(userAgentString);

        if (!userAgentString.IsEmpty())
          finalHeaders->SetUnstructuredHeader(
              "User-Agent", NS_ConvertUTF8toUTF16(userAgentString));
      }
    }
  }

  finalHeaders->SetUnstructuredHeader("MIME-Version", u"1.0"_ns);

  nsAutoCString newsgroups;
  finalHeaders->GetRawHeader("Newsgroups", newsgroups);
  if (!newsgroups.IsEmpty()) {
    // Since the newsgroup header can contain data in the form of:
    // "news://news.mozilla.org/netscape.test,news://news.mozilla.org/netscape.junk"
    // we need to turn that into: "netscape.test,netscape.junk"
    // (XXX: can it really?)
    nsCOMPtr<nsINntpService> nntpService =
        do_GetService("@mozilla.org/messenger/nntpservice;1", &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCString newsgroupsHeaderVal;
    nsCString newshostHeaderVal;
    rv = nntpService->GenerateNewsHeaderValsForPosting(
        newsgroups, getter_Copies(newsgroupsHeaderVal),
        getter_Copies(newshostHeaderVal));
    NS_ENSURE_SUCCESS(rv, rv);
    finalHeaders->SetRawHeader("Newsgroups", newsgroupsHeaderVal);

    // If we are here, we are NOT going to send this now. (i.e. it is a Draft,
    // Send Later file, etc...). Because of that, we need to store what the user
    // typed in on the original composition window for use later when rebuilding
    // the headers
    if (deliver_mode != nsIMsgSend::nsMsgDeliverNow &&
        deliver_mode != nsIMsgSend::nsMsgSendUnsent) {
      // This is going to be saved for later, that means we should just store
      // what the user typed into the "Newsgroup" line in the
      // HEADER_X_MOZILLA_NEWSHOST header for later use by "Send Unsent
      // Messages", "Drafts" or "Templates"
      finalHeaders->SetRawHeader(HEADER_X_MOZILLA_NEWSHOST, newshostHeaderVal);
    }

    // Newsgroups are a recipient...
    hasDisclosedRecipient = true;
  }

  nsTArray<RefPtr<msgIAddressObject>> recipients;
  finalHeaders->GetAddressingHeader("To", false, recipients);
  hasDisclosedRecipient |= !recipients.IsEmpty();
  finalHeaders->GetAddressingHeader("Cc", false, recipients);
  hasDisclosedRecipient |= !recipients.IsEmpty();

  // If we don't have disclosed recipient (only Bcc), address the message to
  // undisclosed-recipients to prevent problem with some servers

  // If we are saving the message as a draft, don't bother inserting the
  // undisclosed recipients field. We'll take care of that when we really send
  // the message.
  if (!hasDisclosedRecipient &&
      (!isDraft || deliver_mode == nsIMsgSend::nsMsgQueueForLater)) {
    bool bAddUndisclosedRecipients = true;
    prefs->GetBoolPref("mail.compose.add_undisclosed_recipients",
                       &bAddUndisclosedRecipients);
    if (bAddUndisclosedRecipients) {
      bool hasBcc = false;
      fields->HasHeader("Bcc", &hasBcc);
      if (hasBcc) {
        nsCOMPtr<nsIStringBundleService> stringService =
            mozilla::components::StringBundle::Service();
        if (stringService) {
          nsCOMPtr<nsIStringBundle> composeStringBundle;
          rv = stringService->CreateBundle(
              "chrome://messenger/locale/messengercompose/"
              "composeMsgs.properties",
              getter_AddRefs(composeStringBundle));
          if (NS_SUCCEEDED(rv)) {
            nsString undisclosedRecipients;
            rv = composeStringBundle->GetStringFromName("undisclosedRecipients",
                                                        undisclosedRecipients);
            if (NS_SUCCEEDED(rv) && !undisclosedRecipients.IsEmpty()) {
              nsCOMPtr<nsIMsgHeaderParser> headerParser(
                  mozilla::components::HeaderParser::Service());
              nsCOMPtr<msgIAddressObject> group;
              nsTArray<RefPtr<msgIAddressObject>> noRecipients;
              headerParser->MakeGroupObject(undisclosedRecipients, noRecipients,
                                            getter_AddRefs(group));
              recipients.AppendElement(group);
              finalHeaders->SetAddressingHeader("To", recipients);
            }
          }
        }
      }
    }
  }

  // We don't want to emit a Bcc header to the output. If we are saving this to
  // Drafts/Sent, this is re-added later in nsMsgSend.cpp.
  finalHeaders->DeleteHeader("bcc");

  // Skip no or empty priority.
  nsAutoCString priority;
  rv = fields->GetRawHeader("X-Priority", priority);
  if (NS_SUCCEEDED(rv) && !priority.IsEmpty()) {
    nsMsgPriorityValue priorityValue;

    NS_MsgGetPriorityFromString(priority.get(), priorityValue);

    // Skip default priority.
    if (priorityValue != nsMsgPriority::Default) {
      nsAutoCString priorityName;
      nsAutoCString priorityValueString;

      NS_MsgGetPriorityValueString(priorityValue, priorityValueString);
      NS_MsgGetUntranslatedPriorityName(priorityValue, priorityName);

      // Output format: [X-Priority: <pValue> (<pName>)]
      priorityValueString.AppendLiteral(" (");
      priorityValueString += priorityName;
      priorityValueString.Append(')');
      finalHeaders->SetRawHeader("X-Priority", priorityValueString);
    }
  }

  nsAutoCString references;
  finalHeaders->GetRawHeader("References", references);
  if (!references.IsEmpty()) {
    // The References header should be kept under 998 characters: if it's too
    // long, trim out the earliest references to make it smaller.
    if (references.Length() > 986) {
      int32_t firstRef = references.FindChar('<');
      int32_t secondRef = references.FindChar('<', firstRef + 1);
      if (secondRef > 0) {
        nsAutoCString newReferences(StringHead(references, secondRef));
        int32_t bracket = references.FindChar(
            '<', references.Length() + newReferences.Length() - 986);
        if (bracket > 0) {
          newReferences.Append(Substring(references, bracket));
          finalHeaders->SetRawHeader("References", newReferences);
        }
      }
    }
    // The In-Reply-To header is the last entry in the references header...
    int32_t bracket = references.RFind("<");
    if (bracket >= 0)
      finalHeaders->SetRawHeader("In-Reply-To", Substring(references, bracket));
  }

  return NS_OK;
}

#undef APPEND_BOOL  // X-Mozilla-Draft-Info helper macro

static void GenerateGlobalRandomBytes(unsigned char* buf, int32_t len) {
  // Attempt to generate bytes from system entropy-based RNG.
  nsCOMPtr<nsIRandomGenerator> randomGenerator(
      do_GetService("@mozilla.org/security/random-generator;1"));
  MOZ_ASSERT(randomGenerator, "nsIRandomGenerator service not retrievable");
  uint8_t* tempBuffer;
  nsresult rv = randomGenerator->GenerateRandomBytes(len, &tempBuffer);
  if (NS_SUCCEEDED(rv)) {
    memcpy(buf, tempBuffer, len);
    free(tempBuffer);
    return;
  }
  // nsIRandomGenerator failed -- fall back to low entropy PRNG.
  static bool firstTime = true;
  if (firstTime) {
    // Seed the random-number generator with current time so that
    // the numbers will be different every time we run.
    srand((unsigned)PR_Now());
    firstTime = false;
  }

  for (int32_t i = 0; i < len; i++) buf[i] = rand() % 256;
}

char* mime_make_separator(const char* prefix) {
  unsigned char rand_buf[13];
  GenerateGlobalRandomBytes(rand_buf, 12);

  return PR_smprintf(
      "------------%s"
      "%02X%02X%02X%02X"
      "%02X%02X%02X%02X"
      "%02X%02X%02X%02X",
      prefix, rand_buf[0], rand_buf[1], rand_buf[2], rand_buf[3], rand_buf[4],
      rand_buf[5], rand_buf[6], rand_buf[7], rand_buf[8], rand_buf[9],
      rand_buf[10], rand_buf[11]);
}

// Tests if the content of a string is a valid host name.
// In this case, a valid host name is any non-empty string that only contains
// letters (a-z + A-Z), numbers (0-9) and the characters '-', '_' and '.'.
static bool isValidHost(const nsCString& host) {
  if (host.IsEmpty()) {
    return false;
  }

  const auto* cur = host.BeginReading();
  const auto* end = host.EndReading();
  for (; cur < end; ++cur) {
    if (!isalpha(*cur) && !isdigit(*cur) && *cur != '-' && *cur != '_' &&
        *cur != '.') {
      return false;
    }
  }

  return true;
}

// Extract the domain name from an address.
// If none could be found (i.e. the address does not contain an '@' sign, or
// the value following it is not a valid domain), then nullptr is returned.
void msg_domain_name_from_address(const nsACString& address, nsACString& host) {
  auto atIndex = address.FindChar('@');

  if (address.IsEmpty() || atIndex == kNotFound) {
    return;
  }

  // Substring() should handle cases where we would go out of bounds (by
  // preventing the index from exceeding the length of the source string), so we
  // don't need to handle this here.
  host = Substring(address, atIndex + 1);
}

// Generate a value for a Message-Id header using the identity and optional
// hostname provided.
void msg_generate_message_id(nsIMsgIdentity* identity,
                             const nsACString& customHost,
                             nsACString& messageID) {
  nsCString host;

  // Check if the identity forces host name. This is sometimes the case when
  // using newsgroup.
  nsCString forcedFQDN;
  nsresult rv = identity->GetCharAttribute("FQDN", forcedFQDN);
  if (NS_SUCCEEDED(rv) && !forcedFQDN.IsEmpty()) {
    host = forcedFQDN;
  }

  // If no valid host name has been set, try using the value defined by the
  // caller, if any.
  if (!isValidHost(host)) {
    host = customHost;
  }

  // If no valid host name has been set, try extracting one from the email
  // address associated with the identity.
  if (!isValidHost(host)) {
    nsCString from;
    rv = identity->GetEmail(from);
    if (NS_SUCCEEDED(rv) && !from.IsEmpty()) {
      msg_domain_name_from_address(from, host);
    }
  }

  // If we still couldn't find a valid host name to use, we can't generate a
  // valid message ID, so bail, and let NNTP and SMTP generate them.
  if (!isValidHost(host)) {
    return;
  }

  // Generate 128-bit UUID for the local part of the ID. `nsID` provides us with
  // cryptographically-secure generation.
  nsID uuid = nsID::GenerateUUID();
  char uuidString[NSID_LENGTH];
  uuid.ToProvidedString(uuidString);
  // Drop first and last characters (curly braces).
  uuidString[NSID_LENGTH - 2] = 0;

  messageID.AppendPrintf("<%s@%s>", uuidString + 1, host.get());
}

// this is to guarantee the folded line will never be greater
// than 78 = 75 + CRLFLWSP
#define PR_MAX_FOLDING_LEN 75

/*static */ char* RFC2231ParmFolding(const char* parmName,
                                     const char* parmValue) {
  NS_ENSURE_TRUE(parmName && *parmName && parmValue && *parmValue, nullptr);

  bool needEscape;
  nsCString dupParm;
  nsCString charset("UTF-8");

  if (!mozilla::IsAsciiNullTerminated(parmValue)) {
    needEscape = true;
    dupParm.Assign(parmValue);
    MsgEscapeString(dupParm, nsINetUtil::ESCAPE_ALL, dupParm);
  } else {
    needEscape = false;
    dupParm.Adopt(msg_make_filename_qtext(parmValue, true));
  }

  int32_t parmNameLen = PL_strlen(parmName);
  int32_t parmValueLen = dupParm.Length();

  parmNameLen += 5;  // *=__'__'___ or *[0]*=__'__'__ or *[1]*=___ or *[0]="___"

  char* foldedParm = nullptr;

  if ((parmValueLen + parmNameLen + strlen("UTF-8")) < PR_MAX_FOLDING_LEN) {
    foldedParm = PL_strdup(parmName);
    if (needEscape) {
      NS_MsgSACat(&foldedParm, "*=");
      NS_MsgSACat(&foldedParm, "UTF-8");
      NS_MsgSACat(&foldedParm, "''");  // We don't support language.
    } else
      NS_MsgSACat(&foldedParm, "=\"");
    NS_MsgSACat(&foldedParm, dupParm.get());
    if (!needEscape) NS_MsgSACat(&foldedParm, "\"");
  } else {
    int curLineLen = 0;
    int counter = 0;
    char digits[32];
    char* start = dupParm.BeginWriting();
    char* end = NULL;
    char tmp = 0;

    while (parmValueLen > 0) {
      curLineLen = 0;
      if (counter == 0) {
        PR_FREEIF(foldedParm)
        foldedParm = PL_strdup(parmName);
      } else {
        NS_MsgSACat(&foldedParm, ";\r\n ");
        NS_MsgSACat(&foldedParm, parmName);
      }
      PR_snprintf(digits, sizeof(digits), "*%d", counter);
      NS_MsgSACat(&foldedParm, digits);
      curLineLen += PL_strlen(digits);
      if (needEscape) {
        NS_MsgSACat(&foldedParm, "*=");
        if (counter == 0) {
          NS_MsgSACat(&foldedParm, "UTF-8");
          NS_MsgSACat(&foldedParm, "''");  // We don't support language.
          curLineLen += strlen("UTF-8");
        }
      } else {
        NS_MsgSACat(&foldedParm, "=\"");
      }
      counter++;
      curLineLen += parmNameLen;
      if (parmValueLen <= PR_MAX_FOLDING_LEN - curLineLen)
        end = start + parmValueLen;
      else
        end = start + (PR_MAX_FOLDING_LEN - curLineLen);

      tmp = 0;
      if (*end && needEscape) {
        // Check to see if we are in the middle of escaped char.
        // We use ESCAPE_ALL, so every third character is a '%'.
        if (end - 1 > start && *(end - 1) == '%') {
          end -= 1;
        } else if (end - 2 > start && *(end - 2) == '%') {
          end -= 2;
        }
        // *end is now a '%'.
        // Check if the following UTF-8 octet is a continuation.
        while (end - 3 > start && (*(end + 1) == '8' || *(end + 1) == '9' ||
                                   *(end + 1) == 'A' || *(end + 1) == 'B')) {
          end -= 3;
        }
        tmp = *end;
        *end = 0;
      } else {
        tmp = *end;
        *end = 0;
      }
      NS_MsgSACat(&foldedParm, start);
      if (!needEscape) NS_MsgSACat(&foldedParm, "\"");

      parmValueLen -= (end - start);
      if (tmp) *end = tmp;
      start = end;
    }
  }

  return foldedParm;
}

bool mime_7bit_data_p(const char* string, uint32_t size) {
  if ((!string) || (!*string)) return true;

  char* ptr = (char*)string;
  for (uint32_t i = 0; i < size; i++) {
    if ((unsigned char)ptr[i] > 0x7F) return false;
  }
  return true;
}

// Strips whitespace, and expands newlines into newline-tab for use in
// mail headers.  Returns a new string or 0 (if it would have been empty.)
// If addr_p is true, the addresses will be parsed and reemitted as
// rfc822 mailboxes.
char* mime_fix_header_1(const char* string, bool addr_p, bool news_p) {
  char* new_string;
  const char* in;
  char* out;
  int32_t i, old_size, new_size;

  if (!string || !*string) return 0;

  if (addr_p) {
    return strdup(string);
  }

  old_size = PL_strlen(string);
  new_size = old_size;
  for (i = 0; i < old_size; i++)
    if (string[i] == '\r' || string[i] == '\n') new_size += 2;

  new_string = (char*)PR_Malloc(new_size + 1);
  if (!new_string) return 0;

  in = string;
  out = new_string;

  /* strip leading whitespace. */
  while (IS_SPACE(*in)) in++;

  /* replace CR, LF, or CRLF with CRLF-TAB. */
  while (*in) {
    if (*in == '\r' || *in == '\n') {
      if (*in == '\r' && in[1] == '\n') in++;
      in++;
      *out++ = '\r';
      *out++ = '\n';
      *out++ = '\t';
    } else if (news_p && *in == ',') {
      *out++ = *in++;
      /* skip over all whitespace after a comma. */
      while (IS_SPACE(*in)) in++;
    } else
      *out++ = *in++;
  }
  *out = 0;

  /* strip trailing whitespace. */
  while (out > in && IS_SPACE(out[-1])) *out-- = 0;

  /* If we ended up throwing it all away, use 0 instead of "". */
  if (!*new_string) {
    PR_Free(new_string);
    new_string = 0;
  }

  return new_string;
}

char* mime_fix_header(const char* string) {
  return mime_fix_header_1(string, false, false);
}

char* mime_fix_addr_header(const char* string) {
  return mime_fix_header_1(string, true, false);
}

char* mime_fix_news_header(const char* string) {
  return mime_fix_header_1(string, false, true);
}

bool mime_type_requires_b64_p(const char* type) {
  if (!type || !PL_strcasecmp(type, UNKNOWN_CONTENT_TYPE))
    // Unknown types don't necessarily require encoding.  (Note that
    // "unknown" and "application/octet-stream" aren't the same.)
    return false;

  else if (!PL_strncasecmp(type, "image/", 6) ||
           !PL_strncasecmp(type, "audio/", 6) ||
           !PL_strncasecmp(type, "video/", 6) ||
           !PL_strncasecmp(type, "application/", 12)) {
    // The following types are application/ or image/ types that are actually
    // known to contain textual data (meaning line-based, not binary, where
    // CRLF conversion is desired rather than disastrous.)  So, if the type
    // is any of these, it does not *require* base64, and if we do need to
    // encode it for other reasons, we'll probably use quoted-printable.
    // But, if it's not one of these types, then we assume that any subtypes
    // of the non-"text/" types are binary data, where CRLF conversion would
    // corrupt it, so we use base64 right off the bat.

    // The reason it's desirable to ship these as text instead of just using
    // base64 all the time is mainly to preserve the readability of them for
    // non-MIME users: if I mail a /bin/sh script to someone, it might not
    // need to be encoded at all, so we should leave it readable if we can.

    // This list of types was derived from the comp.mail.mime FAQ, section
    // 10.2.2, "List of known unregistered MIME types" on 2-Feb-96.
    static const char* app_and_image_types_which_are_really_text[] = {
        "application/mac-binhex40", /* APPLICATION_BINHEX */
        "application/pgp",          /* APPLICATION_PGP */
        "application/pgp-keys",
        "application/x-pgp-message", /* APPLICATION_PGP2 */
        "application/postscript",    /* APPLICATION_POSTSCRIPT */
        "application/x-uuencode",    /* APPLICATION_UUENCODE */
        "application/x-uue",         /* APPLICATION_UUENCODE2 */
        "application/uue",           /* APPLICATION_UUENCODE4 */
        "application/uuencode",      /* APPLICATION_UUENCODE3 */
        "application/sgml",
        "application/x-csh",
        "application/javascript",
        "application/ecmascript",
        "application/x-javascript",
        "application/x-latex",
        "application/x-macbinhex40",
        "application/x-ns-proxy-autoconfig",
        "application/x-www-form-urlencoded",
        "application/x-perl",
        "application/x-sh",
        "application/x-shar",
        "application/x-tcl",
        "application/x-tex",
        "application/x-texinfo",
        "application/x-troff",
        "application/x-troff-man",
        "application/x-troff-me",
        "application/x-troff-ms",
        "application/x-troff-ms",
        "application/x-wais-source",
        "image/x-bitmap",
        "image/x-pbm",
        "image/x-pgm",
        "image/x-portable-anymap",
        "image/x-portable-bitmap",
        "image/x-portable-graymap",
        "image/x-portable-pixmap", /* IMAGE_PPM */
        "image/x-ppm",
        "image/x-xbitmap", /* IMAGE_XBM */
        "image/x-xbm",     /* IMAGE_XBM2 */
        "image/xbm",       /* IMAGE_XBM3 */
        "image/x-xpixmap",
        "image/x-xpm",
        0};
    const char** s;
    for (s = app_and_image_types_which_are_really_text; *s; s++)
      if (!PL_strcasecmp(type, *s)) return false;

    /* All others must be assumed to be binary formats, and need Base64. */
    return true;
  }

  else
    return false;
}

//
// Some types should have a "charset=" parameter, and some shouldn't.
// This is what decides.
//
bool mime_type_needs_charset(const char* type) {
  /* Only text types should have charset. */
  if (!type || !*type)
    return false;
  else if (!PL_strncasecmp(type, "text", 4))
    return true;
  else
    return false;
}

// Given a string, convert it to 'qtext' (quoted text) for RFC822 header
// purposes.
char* msg_make_filename_qtext(const char* srcText, bool stripCRLFs) {
  /* newString can be at most twice the original string (every char quoted). */
  char* newString = (char*)PR_Malloc(PL_strlen(srcText) * 2 + 1);
  if (!newString) return NULL;

  const char* s = srcText;
  const char* end = srcText + PL_strlen(srcText);
  char* d = newString;

  while (*s) {
    // Put backslashes in front of existing backslashes, or double quote
    // characters.
    // If stripCRLFs is true, don't write out CRs or LFs. Otherwise,
    // write out a backslash followed by the CR but not
    // linear-white-space.
    // We might already have quoted pair of "\ " or "\\t" skip it.
    if (*s == '\\' || *s == '"' ||
        (!stripCRLFs &&
         (*s == '\r' && (s[1] != '\n' ||
                         (s[1] == '\n' && (s + 2) < end && !IS_SPACE(s[2]))))))
      *d++ = '\\';

    if (stripCRLFs && *s == '\r' && s[1] == '\n' && (s + 2) < end &&
        IS_SPACE(s[2])) {
      s += 3;  // skip CRLFLWSP
    } else {
      *d++ = *s++;
    }
  }
  *d = 0;

  return newString;
}

// Utility to create a nsIURI object...
nsresult nsMsgNewURL(nsIURI** aInstancePtrResult, const nsCString& aSpec) {
  nsresult rv = NS_OK;
  if (nullptr == aInstancePtrResult) return NS_ERROR_NULL_POINTER;
  nsCOMPtr<nsIIOService> pNetService = mozilla::components::IO::Service();
  NS_ENSURE_TRUE(pNetService, NS_ERROR_UNEXPECTED);
  if (aSpec.Find("://") == kNotFound && !StringBeginsWith(aSpec, "data:"_ns)) {
    // XXXjag Temporary fix for bug 139362 until the real problem(bug 70083) get
    // fixed
    nsAutoCString uri("http://"_ns);
    uri.Append(aSpec);
    rv = pNetService->NewURI(uri, nullptr, nullptr, aInstancePtrResult);
  } else
    rv = pNetService->NewURI(aSpec, nullptr, nullptr, aInstancePtrResult);
  return rv;
}

char* nsMsgGetLocalFileFromURL(const char* url) {
  char* finalPath;
  NS_ASSERTION(PL_strncasecmp(url, "file://", 7) == 0, "invalid url");
  finalPath = (char*)PR_Malloc(strlen(url));
  if (finalPath == NULL) return NULL;
  strcpy(finalPath, url + 6 + 1);
  return finalPath;
}

char* nsMsgParseURLHost(const char* url) {
  nsIURI* workURI = nullptr;
  nsresult rv;

  rv = nsMsgNewURL(&workURI, nsDependentCString(url));
  if (NS_FAILED(rv) || !workURI) return nullptr;

  nsAutoCString host;
  rv = workURI->GetHost(host);
  NS_IF_RELEASE(workURI);
  if (NS_FAILED(rv)) return nullptr;

  return ToNewCString(host);
}

char* GenerateFileNameFromURI(nsIURI* aURL) {
  nsresult rv;
  nsCString file;
  nsCString spec;
  char* returnString;
  char* cp = nullptr;
  char* cp1 = nullptr;

  rv = aURL->GetPathQueryRef(file);
  if (NS_SUCCEEDED(rv) && !file.IsEmpty()) {
    char* newFile = ToNewCString(file);
    if (!newFile) return nullptr;

    // strip '/'
    cp = PL_strrchr(newFile, '/');
    if (cp)
      ++cp;
    else
      cp = newFile;

    if (*cp) {
      if ((cp1 = PL_strchr(cp, '/'))) *cp1 = 0;
      if ((cp1 = PL_strchr(cp, '?'))) *cp1 = 0;
      if ((cp1 = PL_strchr(cp, '>'))) *cp1 = 0;
      if (*cp != '\0') {
        returnString = PL_strdup(cp);
        PR_FREEIF(newFile);
        return returnString;
      }
    } else
      return nullptr;
  }

  cp = nullptr;
  cp1 = nullptr;

  rv = aURL->GetSpec(spec);
  if (NS_SUCCEEDED(rv) && !spec.IsEmpty()) {
    char* newSpec = ToNewCString(spec);
    if (!newSpec) return nullptr;

    char *cp2 = NULL, *cp3 = NULL;

    // strip '"'
    cp2 = newSpec;
    while (*cp2 == '"') cp2++;
    if ((cp3 = PL_strchr(cp2, '"'))) *cp3 = 0;

    char* hostStr = nsMsgParseURLHost(cp2);
    if (!hostStr) hostStr = PL_strdup(cp2);

    bool isHTTP = false;
    if (NS_SUCCEEDED(aURL->SchemeIs("http", &isHTTP)) && isHTTP) {
      returnString = PR_smprintf("%s.html", hostStr);
      PR_FREEIF(hostStr);
    } else
      returnString = hostStr;

    PR_FREEIF(newSpec);
    return returnString;
  }

  return nullptr;
}

//
// This routine will generate a content id for use in a mail part.
// It will take the part number passed in as well as the email
// address. If the email address is null or invalid, we will simply
// use netscape.com for the interesting part. The content ID's will
// look like the following:
//
//      Content-ID: <part1.36DF1DCE.73B5A330@netscape.com>
//
char* mime_gen_content_id(uint32_t aPartNum, const char* aEmailAddress) {
  int32_t randLen = 5;
  unsigned char rand_buf1[5];
  unsigned char rand_buf2[5];
  const char* domain = nullptr;
  const char* defaultDomain = "@netscape.com";

  memset(rand_buf1, 0, randLen - 1);
  memset(rand_buf2, 0, randLen - 1);

  GenerateGlobalRandomBytes(rand_buf1, randLen);
  GenerateGlobalRandomBytes(rand_buf2, randLen);

  // Find the @domain.com string...
  if (aEmailAddress && *aEmailAddress)
    domain = const_cast<const char*>(PL_strchr(aEmailAddress, '@'));

  if (!domain) domain = defaultDomain;

  char* retVal = PR_smprintf(
      "part%d."
      "%02X%02X%02X%02X"
      "."
      "%02X%02X%02X%02X"
      "%s",
      aPartNum, rand_buf1[0], rand_buf1[1], rand_buf1[2], rand_buf1[3],
      rand_buf2[0], rand_buf2[1], rand_buf2[2], rand_buf2[3], domain);

  return retVal;
}

void GetFolderURIFromUserPrefs(nsMsgDeliverMode aMode, nsIMsgIdentity* identity,
                               nsCString& uri) {
  nsresult rv;
  uri.Truncate();

  // QueueForLater (Outbox)
  if (aMode == nsIMsgSend::nsMsgQueueForLater ||
      aMode == nsIMsgSend::nsMsgDeliverBackground) {
    nsCOMPtr<nsIPrefBranch> prefs(
        do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
    if (NS_FAILED(rv)) return;
    rv = prefs->GetCharPref("mail.default_sendlater_uri", uri);
    if (NS_FAILED(rv) || uri.IsEmpty())
      uri.AssignLiteral(ANY_SERVER);
    else {
      // check if uri is unescaped, and if so, escape it and reset the pef.
      if (uri.FindChar(' ') != kNotFound) {
        uri.ReplaceSubstring(" ", "%20");
        prefs->SetCharPref("mail.default_sendlater_uri", uri);
      }
    }
    return;
  }

  if (!identity) return;

  if (aMode == nsIMsgSend::nsMsgSaveAsDraft)  // SaveAsDraft (Drafts)
    rv = identity->GetDraftFolder(uri);
  else if (aMode ==
           nsIMsgSend::nsMsgSaveAsTemplate)  // SaveAsTemplate (Templates)
    rv = identity->GetStationeryFolder(uri);
  else {
    bool doFcc = false;
    rv = identity->GetDoFcc(&doFcc);
    if (doFcc) rv = identity->GetFccFolder(uri);
  }
  return;
}

/**
 * Check if we should use format=flowed (RFC 2646) for a mail.
 * We will use format=flowed unless the preference tells us not to do so.
 * In this function we set all the serialiser flags.
 * 'formatted' is always 'true'.
 */
void GetSerialiserFlags(bool* flowed, bool* formatted) {
  *flowed = false;
  *formatted = true;

  // Set format=flowed as in RFC 2646 according to the preference.
  nsresult rv;
  nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  if (NS_SUCCEEDED(rv)) {
    prefs->GetBoolPref("mailnews.send_plaintext_flowed", flowed);
  }
}

already_AddRefed<nsIArray> GetEmbeddedObjects(
    mozilla::dom::Document* aDocument) {
  nsCOMPtr<nsIMutableArray> nodes = do_CreateInstance(NS_ARRAY_CONTRACTID);
  if (NS_WARN_IF(!nodes)) {
    return nullptr;
  }

  mozilla::PostContentIterator iter;
  nsresult rv = iter.Init(aDocument->GetRootElement());
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nullptr;
  }

  // Loop through the content iterator for each content node.
  while (!iter.IsDone()) {
    nsINode* node = iter.GetCurrentNode();
    if (node->IsElement()) {
      mozilla::dom::Element* element = node->AsElement();

      // See if it's an image or also include all links.
      // Let mail decide which link to send or not
      if (element->IsAnyOfHTMLElements(nsGkAtoms::img, nsGkAtoms::a) ||
          (element->IsHTMLElement(nsGkAtoms::body) &&
           element->HasAttr(kNameSpaceID_None, nsGkAtoms::background))) {
        nodes->AppendElement(node);
      }
    }
    iter.Next();
  }

  return nodes.forget();
}
