/* -*- 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 #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 detector = mozilla::EncodingDetector::Create(); mozilla::Span 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 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 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> 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 bundleService = mozilla::components::StringBundle::Service(); if (bundleService) { nsCOMPtr 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 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 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> 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 stringService = mozilla::components::StringBundle::Service(); if (stringService) { nsCOMPtr 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 headerParser( mozilla::components::HeaderParser::Service()); nsCOMPtr group; nsTArray> 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: ()] 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 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 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: // 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(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 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 prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { prefs->GetBoolPref("mailnews.send_plaintext_flowed", flowed); } } already_AddRefed GetEmbeddedObjects( mozilla::dom::Document* aDocument) { nsCOMPtr 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(); }