summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/compose/src/nsMsgCompUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/compose/src/nsMsgCompUtils.cpp')
-rw-r--r--comm/mailnews/compose/src/nsMsgCompUtils.cpp1164
1 files changed, 1164 insertions, 0 deletions
diff --git a/comm/mailnews/compose/src/nsMsgCompUtils.cpp b/comm/mailnews/compose/src/nsMsgCompUtils.cpp
new file mode 100644
index 0000000000..50ce86497b
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgCompUtils.cpp
@@ -0,0 +1,1164 @@
+/* -*- 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();
+}