diff options
Diffstat (limited to 'comm/mailnews/compose/src/nsMsgCompUtils.cpp')
-rw-r--r-- | comm/mailnews/compose/src/nsMsgCompUtils.cpp | 1164 |
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(); +} |