diff options
Diffstat (limited to 'mailnews/compose/src/nsMsgCompUtils.cpp')
-rw-r--r-- | mailnews/compose/src/nsMsgCompUtils.cpp | 1803 |
1 files changed, 1803 insertions, 0 deletions
diff --git a/mailnews/compose/src/nsMsgCompUtils.cpp b/mailnews/compose/src/nsMsgCompUtils.cpp new file mode 100644 index 000000000..4615f0f36 --- /dev/null +++ b/mailnews/compose/src/nsMsgCompUtils.cpp @@ -0,0 +1,1803 @@ +/* -*- 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 "prmem.h" +#include "nsMsgSend.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/mailnews/Services.h" +#include "mozilla/Services.h" +#include "nsIMIMEInfo.h" +#include "nsIMsgHeaderParser.h" +#include "nsIRandomGenerator.h" +#include "nsID.h" + +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, + char **_retval) +{ + NS_ENSURE_ARG_POINTER(identity); + NS_ENSURE_ARG_POINTER(_retval); + *_retval = msg_generate_message_id(identity); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompUtils::GetMsgMimeConformToStandard(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nsMsgMIMEGetConformToStandard(); + 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; +} + +// +// Create a file spec for the a unique temp file +// on the local machine. Caller must free memory +// returned +// +char * +nsMsgCreateTempFileName(const char *tFileName) +{ + if ((!tFileName) || (!*tFileName)) + tFileName = "nsmail.tmp"; + + nsCOMPtr<nsIFile> tmpFile; + + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + tFileName, + getter_AddRefs(tmpFile)); + if (NS_FAILED(rv)) + return nullptr; + + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + if (NS_FAILED(rv)) + return nullptr; + + nsCString tempString; + rv = tmpFile->GetNativePath(tempString); + if (NS_FAILED(rv)) + return nullptr; + + char *tString = ToNewCString(tempString); + if (!tString) + return PL_strdup("mozmail.tmp"); // No need to I18N + + return tString; +} + +// 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); +} + +// +// Generate the message headers for the new RFC822 message +// +#define UA_PREF_PREFIX "general.useragent." + +// 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); + + nsCOMArray<msgIAddressObject> from; + fields->GetAddressingHeader("From", from, true); + + // 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 (superceding 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), nullptr); + + // 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, nullptr); + } + + nsCOMPtr<nsIHttpProtocolHandler> pHTTPHandler = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv); + if (NS_SUCCEEDED(rv) && pHTTPHandler) + { + nsAutoCString userAgentString; + pHTTPHandler->GetUserAgent(userAgentString); + + if (!userAgentString.IsEmpty()) + finalHeaders->SetUnstructuredHeader("User-Agent", + NS_ConvertUTF8toUTF16(userAgentString)); + } + + finalHeaders->SetUnstructuredHeader("MIME-Version", NS_LITERAL_STRING("1.0")); + + 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, nullptr); + + // 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, + nullptr); + } + + // Newsgroups are a recipient... + hasDisclosedRecipient = true; + } + + nsCOMArray<msgIAddressObject> recipients; + finalHeaders->GetAddressingHeader("To", recipients); + hasDisclosedRecipient |= !recipients.IsEmpty(); + finalHeaders->GetAddressingHeader("Cc", 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) + { + 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::services::GetStringBundleService(); + 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(u"undisclosedRecipients", + getter_Copies(undisclosedRecipients)); + if (NS_SUCCEEDED(rv) && !undisclosedRecipients.IsEmpty()) + { + nsCOMPtr<nsIMsgHeaderParser> headerParser( + mozilla::services::GetHeaderParser()); + nsCOMPtr<msgIAddressObject> group; + headerParser->MakeGroupObject(undisclosedRecipients, + nullptr, 0, 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 readded 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.AppendLiteral(")"); + finalHeaders->SetRawHeader("X-Priority", priorityValueString, nullptr); + } + } + + 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, nullptr); + } + } + } + // 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), + nullptr); + } + + 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]); +} + +static char * +RFC2231ParmFolding(const char *parmName, const nsCString& charset, + const char *language, const nsString& parmValue); + +static char * +LegacyParmFolding(const nsCString& aCharset, + const nsCString& aFileName, int32_t aParmFolding); + +char * +mime_generate_attachment_headers (const char *type, + const char *type_param, + const char *encoding, + const char *description, + const char *x_mac_type, + const char *x_mac_creator, + const char *real_name, + const char *base_url, + bool /*digest_p*/, + nsMsgAttachmentHandler * /*ma*/, + const char *attachmentCharset, + const char *bodyCharset, + bool bodyIsAsciiOnly, + const char *content_id, + bool aBodyDocument) +{ + NS_ASSERTION (encoding, "null encoding"); + + int32_t parmFolding = 2; // RFC 2231-compliant + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + prefs->GetIntPref("mail.strictly_mime.parm_folding", &parmFolding); + + /* Let's encode the real name */ + char *encodedRealName = nullptr; + nsCString charset; // actual charset used for MIME encode + nsAutoString realName; + if (real_name) + { + // first try main body's charset to encode the file name, + // then try local file system charset if fails + CopyUTF8toUTF16(nsDependentCString(real_name), realName); + if (bodyCharset && *bodyCharset && + nsMsgI18Ncheck_data_in_charset_range(bodyCharset, realName.get())) + charset.Assign(bodyCharset); + else + { + charset.Assign(nsMsgI18NFileSystemCharset()); + if (!nsMsgI18Ncheck_data_in_charset_range(charset.get(), realName.get())) + charset.Assign("UTF-8"); // set to UTF-8 if fails again + } + + encodedRealName = RFC2231ParmFolding("filename", charset, nullptr, + realName); + // somehow RFC2231ParamFolding failed. fall back to legacy method + if (!encodedRealName || !*encodedRealName) { + PR_FREEIF(encodedRealName); + parmFolding = 0; + // Not RFC 2231 style encoding (it's not standard-compliant) + encodedRealName = + LegacyParmFolding(charset, nsDependentCString(real_name), parmFolding); + } + } + + nsCString buf; // very likely to be longer than 64 characters + buf.Append("Content-Type: "); + buf.Append(type); + if (type_param && *type_param) + { + if (*type_param != ';') + buf.Append("; "); + buf.Append(type_param); + } + + if (mime_type_needs_charset (type)) + { + + char charset_label[65] = ""; // Content-Type: charset + if (attachmentCharset) + { + PL_strncpy(charset_label, attachmentCharset, sizeof(charset_label)-1); + charset_label[sizeof(charset_label)-1] = '\0'; + } + + /* If the characters are all 7bit, arguably it's better to + claim the charset to be US-ASCII. However, it causes + a major 'interoperability problem' with MS OE, which makes it hard + to sell Mozilla/TB to people most of whose correspondents use + MS OE. MS OE turns all non-ASCII characters to question marks + in replies to messages labeled as US-ASCII if users select 'send as is' + with MIME turned on. (with MIME turned off, this happens without + any warning.) To avoid this, we use the label 'US-ASCII' only when + it's explicitly requested by setting the hidden pref. + 'mail.label_ascii_only_mail_as_us_ascii'. (bug 247958) */ + bool labelAsciiAsAscii = false; + if (prefs) + prefs->GetBoolPref("mail.label_ascii_only_mail_as_us_ascii", + &labelAsciiAsAscii); + if (labelAsciiAsAscii && encoding && + !PL_strcasecmp (encoding, "7bit") && bodyIsAsciiOnly) + PL_strcpy (charset_label, "us-ascii"); + + // If charset is multibyte then no charset to be specified (apply base64 instead). + // The list of file types match with PickEncoding() where we put base64 label. + if ( ((attachmentCharset && !nsMsgI18Nmultibyte_charset(attachmentCharset)) || + ((PL_strcasecmp(type, TEXT_HTML) == 0) || + (PL_strcasecmp(type, TEXT_MDL) == 0) || + (PL_strcasecmp(type, TEXT_PLAIN) == 0) || + (PL_strcasecmp(type, TEXT_RICHTEXT) == 0) || + (PL_strcasecmp(type, TEXT_ENRICHED) == 0) || + (PL_strcasecmp(type, TEXT_VCARD) == 0) || + (PL_strcasecmp(type, APPLICATION_DIRECTORY) == 0) || /* text/x-vcard synonym */ + (PL_strcasecmp(type, TEXT_CSS) == 0) || + (PL_strcasecmp(type, TEXT_JSSS) == 0)) || + (PL_strcasecmp(encoding, ENCODING_BASE64) != 0)) && + (*charset_label)) + { + buf.Append("; charset="); + buf.Append(charset_label); + } + } + + // Only do this if we are in the body of a message + if (aBodyDocument) + { + // Add format=flowed as in RFC 2646 if we are using that + if(type && !PL_strcasecmp(type, "text/plain")) + { + bool flowed, delsp, formatted, disallowBreaks; + GetSerialiserFlags(bodyCharset, &flowed, &delsp, &formatted, &disallowBreaks); + if(flowed) + buf.Append("; format=flowed"); + if (delsp) + buf.Append("; delsp=yes"); + // else + // { + // Don't add a markup. Could use + // PUSH_STRING ("; format=fixed"); + // but it is equivalent to nothing at all and we do want + // to save bandwidth. Don't we? + // } + } + } + + if (x_mac_type && *x_mac_type) { + buf.Append("; x-mac-type=\""); + buf.Append(x_mac_type); + buf.Append("\""); + } + + if (x_mac_creator && *x_mac_creator) { + buf.Append("; x-mac-creator=\""); + buf.Append(x_mac_creator); + buf.Append("\""); + } + +#ifdef EMIT_NAME_IN_CONTENT_TYPE + if (encodedRealName && *encodedRealName) { + // Note that we don't need to output the name field if the name encoding is + // RFC 2231. If the MUA knows the RFC 2231, it should know the RFC 2183 too. + if (parmFolding != 2) { + // The underlying JS MIME code will only handle UTF-8 here. + char *nameValue = LegacyParmFolding(NS_LITERAL_CSTRING("UTF-8"), + nsDependentCString(real_name), + parmFolding); + if (!nameValue || !*nameValue) { + PR_FREEIF(nameValue); + nameValue = encodedRealName; + } + buf.Append(";\r\n name=\""); + buf.Append(nameValue); + buf.Append("\""); + if (nameValue != encodedRealName) + PR_FREEIF(nameValue); + } + } +#endif /* EMIT_NAME_IN_CONTENT_TYPE */ + buf.Append(CRLF); + + buf.Append("Content-Transfer-Encoding: "); + buf.Append(encoding); + buf.Append(CRLF); + + if (description && *description) { + char *s = mime_fix_header (description); + if (s) { + buf.Append("Content-Description: "); + buf.Append(s); + buf.Append(CRLF); + PR_Free(s); + } + } + + if ( (content_id) && (*content_id) ) + { + buf.Append("Content-ID: <"); + buf.Append(content_id); + buf.Append(">"); + buf.Append(CRLF); + } + + if (encodedRealName && *encodedRealName) { + char *period = PL_strrchr(encodedRealName, '.'); + int32_t pref_content_disposition = 0; + + if (prefs) { + mozilla::DebugOnly<nsresult> rv = prefs->GetIntPref("mail.content_disposition_type", + &pref_content_disposition); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get mail.content_disposition_type"); + } + + buf.Append("Content-Disposition: "); + + // If this is an attachment which is part of the message body and therefore has a + // Content-ID (e.g, image in HTML msg), then Content-Disposition has to be inline + if (content_id && *content_id) + buf.Append("inline"); + else if (pref_content_disposition == 1) + buf.Append("attachment"); + else + if (pref_content_disposition == 2 && + (!PL_strcasecmp(type, TEXT_PLAIN) || + (period && !PL_strcasecmp(period, ".txt")))) + buf.Append("attachment"); + + /* If this document is an anonymous binary file or a vcard, + then always show it as an attachment, never inline. */ + else + if (!PL_strcasecmp(type, APPLICATION_OCTET_STREAM) || + !PL_strcasecmp(type, TEXT_VCARD) || + !PL_strcasecmp(type, APPLICATION_DIRECTORY)) /* text/x-vcard synonym */ + buf.Append("attachment"); + else + buf.Append("inline"); + + buf.Append(";\r\n "); + buf.Append(encodedRealName); + buf.Append(CRLF); + } + else + if (type && + (!PL_strcasecmp (type, MESSAGE_RFC822) || + !PL_strcasecmp (type, MESSAGE_NEWS))) + buf.Append("Content-Disposition: inline" CRLF); + +#ifdef GENERATE_CONTENT_BASE + /* If this is an HTML document, and we know the URL it originally + came from, write out a Content-Base header. */ + if (type && + (!PL_strcasecmp (type, TEXT_HTML) || + !PL_strcasecmp (type, TEXT_MDL)) && + base_url && *base_url) + { + int32_t col = 0; + const char *s = base_url; + const char *colon = PL_strchr (s, ':'); + bool useContentLocation = false; /* rhp - add this */ + + if (!colon) + goto GIVE_UP_ON_CONTENT_BASE; /* malformed URL? */ + + /* Don't emit a content-base that points to (or into) a news or + mail message. */ + if (!PL_strncasecmp (s, "news:", 5) || + !PL_strncasecmp (s, "snews:", 6) || + !PL_strncasecmp (s, "IMAP:", 5) || + !PL_strncasecmp (s, "file:", 5) || /* rhp: fix targets from included HTML files */ + !PL_strncasecmp (s, "mailbox:", 8)) + goto GIVE_UP_ON_CONTENT_BASE; + + /* rhp - Put in a pref for using Content-Location instead of Content-Base. + This will get tweaked to default to true in 5.0 + */ + if (prefs) + prefs->GetBoolPref("mail.use_content_location_on_send", &useContentLocation); + + if (useContentLocation) + buf.Append("Content-Location: \""); + else + buf.Append("Content-Base: \""); + /* rhp - Pref for Content-Location usage */ + +/* rhp: this is to work with the Content-Location stuff */ +CONTENT_LOC_HACK: + + while (*s != 0 && *s != '#') + { + uint32_t ot=buf.Length(); + char tmp[]="\x00\x00"; + /* URLs must be wrapped at 40 characters or less. */ + if (col >= 38) { + buf.Append(CRLF "\t"); + col = 0; + } + + if (*s == ' ') + buf.Append("%20"); + else if (*s == '\t') + buf.Append("%09"); + else if (*s == '\n') + buf.Append("%0A"); + else if (*s == '\r') + buf.Append("%0D"); + else { + tmp[0]=*s; + buf.Append(tmp); + } + s++; + col += (buf.Length() - ot); + } + buf.Append("\"" CRLF); + + /* rhp: this is to try to get around this fun problem with Content-Location */ + if (!useContentLocation) { + buf.Append("Content-Location: \""); + s = base_url; + col = 0; + useContentLocation = true; + goto CONTENT_LOC_HACK; + } + /* rhp: this is to try to get around this fun problem with Content-Location */ + +GIVE_UP_ON_CONTENT_BASE: + ; + } +#endif /* GENERATE_CONTENT_BASE */ + + /* realloc it smaller... */ + +#ifdef DEBUG_jungshik + printf ("header=%s\n", buf.get()); +#endif + PR_Free(encodedRealName); + return PL_strdup(buf.get()); +} + +static bool isValidHost( const char* host ) +{ + if ( host ) + for (const char *s = host; *s; ++s) + if ( !isalpha(*s) + && !isdigit(*s) + && *s != '-' + && *s != '_' + && *s != '.' + ) + { + host = nullptr; + break; + } + + return nullptr != host; +} + +char * +msg_generate_message_id (nsIMsgIdentity *identity) +{ + const char *host = 0; + + nsCString forcedFQDN; + nsCString from; + nsresult rv = NS_OK; + + rv = identity->GetCharAttribute("FQDN", forcedFQDN); + + if (NS_SUCCEEDED(rv) && !forcedFQDN.IsEmpty()) + host = forcedFQDN.get(); + + if (!isValidHost(host)) + { + nsresult rv = identity->GetEmail(from); + if (NS_SUCCEEDED(rv) && !from.IsEmpty()) + host = strchr(from.get(),'@'); + + // No '@'? Munged address, anti-spam? + // see bug #197203 + if (host) + ++host; + } + + if (!isValidHost(host)) + /* If we 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. */ + return 0; + + // Generate 128-bit UUID for the local part. We use the high-entropy + // GenerateGlobalRandomBytes to make tracking more difficult. + nsID uuid; + GenerateGlobalRandomBytes((unsigned char*) &uuid, sizeof(nsID)); + char uuidString[NSID_LENGTH]; + uuid.ToProvidedString(uuidString); + // Drop first and last characters (curly braces). + uuidString[NSID_LENGTH - 2] = 0; + return PR_smprintf("<%s@%s>", uuidString + 1, host); +} + + +inline static bool is7bitCharset(const nsCString& charset) +{ + // charset name is canonical (no worry about case-sensitivity) + return Substring(charset, 0, 8).EqualsLiteral("ISO-2022-"); +} + +#define PR_MAX_FOLDING_LEN 75 // this is to gurantee the folded line will + // never be greater than 78 = 75 + CRLFLWSP +/*static */ char * +RFC2231ParmFolding(const char *parmName, const nsCString& charset, + const char *language, const nsString& parmValue) +{ + NS_ENSURE_TRUE(parmName && *parmName && !parmValue.IsEmpty(), nullptr); + + bool needEscape; + nsCString dupParm; + + if (!NS_IsAscii(parmValue.get()) || is7bitCharset(charset)) { + needEscape = true; + nsAutoCString nativeParmValue; + ConvertFromUnicode(charset.get(), parmValue, nativeParmValue); + MsgEscapeString(nativeParmValue, nsINetUtil::ESCAPE_ALL, dupParm); + } + else { + needEscape = false; + dupParm.Adopt( + msg_make_filename_qtext(NS_LossyConvertUTF16toASCII(parmValue).get(), + true)); + } + + if (dupParm.IsEmpty()) + return nullptr; + + int32_t parmNameLen = PL_strlen(parmName); + int32_t parmValueLen = dupParm.Length(); + + parmNameLen += 5; // *=__'__'___ or *[0]*=__'__'__ or *[1]*=___ or *[0]="___" + + int32_t languageLen = language ? PL_strlen(language) : 0; + int32_t charsetLen = charset.Length(); + char *foldedParm = nullptr; + + if ((parmValueLen + parmNameLen + charsetLen + languageLen) < + PR_MAX_FOLDING_LEN) + { + foldedParm = PL_strdup(parmName); + if (needEscape) + { + NS_MsgSACat(&foldedParm, "*="); + if (charsetLen) + NS_MsgSACat(&foldedParm, charset.get()); + NS_MsgSACat(&foldedParm, "'"); + if (languageLen) + NS_MsgSACat(&foldedParm, language); + NS_MsgSACat(&foldedParm, "'"); + } + 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) + { + if (charsetLen) + NS_MsgSACat(&foldedParm, charset.get()); + NS_MsgSACat(&foldedParm, "'"); + if (languageLen) + NS_MsgSACat(&foldedParm, language); + NS_MsgSACat(&foldedParm, "'"); + curLineLen += charsetLen; + curLineLen += languageLen; + } + } + 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 + if (*end == '%') + { + tmp = '%'; *end = 0; + } + else if (end-1 > start && *(end-1) == '%') + { + end -= 1; tmp = '%'; *end = 0; + } + else if (end-2 > start && *(end-2) == '%') + { + end -= 2; tmp = '%'; *end = 0; + } + else + { + tmp = *end; *end = 0; + } + } + else + { + // XXX should check if we are in the middle of escaped char (RFC 822) + tmp = *end; *end = 0; + } + NS_MsgSACat(&foldedParm, start); + if (!needEscape) + NS_MsgSACat(&foldedParm, "\""); + + parmValueLen -= (end-start); + if (tmp) + *end = tmp; + start = end; + } + } + + return foldedParm; +} + +/*static */ char * +LegacyParmFolding(const nsCString& aCharset, + const nsCString& aFileName, int32_t aParmFolding) +{ + bool usemime = nsMsgMIMEGetConformToStandard(); + char *encodedRealName = + nsMsgI18NEncodeMimePartIIStr(aFileName.get(), false, aCharset.get(), + 0, usemime); + + if (!encodedRealName || !*encodedRealName) { + PR_FREEIF(encodedRealName); + encodedRealName = (char *) PR_Malloc(aFileName.Length() + 1); + if (encodedRealName) + PL_strcpy(encodedRealName, aFileName.get()); + } + + // Now put backslashes before special characters per RFC 822 + char *qtextName = + msg_make_filename_qtext(encodedRealName, aParmFolding == 0); + if (qtextName) { + PR_FREEIF(encodedRealName); + encodedRealName = qtextName; + } + return encodedRealName; +} + +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 disasterous.) 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; +} + +/* Rip apart the URL and extract a reasonable value for the `real_name' slot. + */ +void +msg_pick_real_name (nsMsgAttachmentHandler *attachment, const char16_t *proposedName, const char *charset) +{ + const char *s, *s2; + + if (!attachment->m_realName.IsEmpty()) + return; + + if (proposedName && *proposedName) + { + attachment->m_realName.Adopt(ToNewUTF8String(nsAutoString(proposedName))); + } + else //Let's extract the name from the URL + { + nsCString url; + nsresult rv = attachment->mURL->GetSpec(url); + if (NS_FAILED(rv)) + return; + + s = url.get(); + s2 = PL_strchr (s, ':'); + if (s2) + s = s2 + 1; + /* If we know the URL doesn't have a sensible file name in it, + don't bother emitting a content-disposition. */ + if (StringBeginsWith (url, NS_LITERAL_CSTRING("news:"), nsCaseInsensitiveCStringComparator()) || + StringBeginsWith (url, NS_LITERAL_CSTRING("snews:"), nsCaseInsensitiveCStringComparator()) || + StringBeginsWith (url, NS_LITERAL_CSTRING("IMAP:"), nsCaseInsensitiveCStringComparator()) || + StringBeginsWith (url, NS_LITERAL_CSTRING("mailbox:"), nsCaseInsensitiveCStringComparator())) + return; + + if (StringBeginsWith(url, NS_LITERAL_CSTRING("data:"), + nsCaseInsensitiveCStringComparator())) + { + int32_t endNonData = url.FindChar(','); + if (endNonData == -1) + return; + nsCString nonDataPart(Substring(url, 5, endNonData - 5)); + int32_t filenamePos = nonDataPart.Find("filename="); + if (filenamePos != -1) + { + filenamePos += 9; + int32_t endFilename = nonDataPart.FindChar(';', filenamePos); + if (endFilename == -1) + endFilename = endNonData; + attachment->m_realName = Substring(nonDataPart, filenamePos, + endFilename - filenamePos); + } + else + { + // no filename; need to construct one based on the content type. + nsCOMPtr<nsIMIMEService> mimeService(do_GetService(NS_MIMESERVICE_CONTRACTID)); + if (!mimeService) + return; + nsCOMPtr<nsIMIMEInfo> mimeInfo; + nsCString mediaType(Substring(nonDataPart, 0, nonDataPart.FindChar(';'))); + mimeService->GetFromTypeAndExtension(mediaType, EmptyCString(), getter_AddRefs(mimeInfo)); + if (!mimeInfo) + return; + nsCString filename; + nsCString extension; + mimeInfo->GetPrimaryExtension(extension); + unsigned char filePrefixBytes[8]; + GenerateGlobalRandomBytes(filePrefixBytes, 8); + // Create a filename prefix with 16 lowercase letters, + // representing 8 bytes. + for (int32_t i = 0; i < 8; i++) + { + // A pair of letters, each any of (a-p). + filename.Append((filePrefixBytes[i] & 0xF) + 'a'); + filename.Append((filePrefixBytes[i] >> 4) + 'a'); + } + filename.Append('.'); + filename.Append(extension); + attachment->m_realName = filename; + } + } + else + { + /* Take the part of the file name after the last / or \ */ + s2 = PL_strrchr (s, '/'); + if (s2) s = s2+1; + s2 = PL_strrchr (s, '\\'); + + if (s2) s = s2+1; + /* Copy it into the attachment struct. */ + attachment->m_realName = s; + int32_t charPos = attachment->m_realName.FindChar('?'); + if (charPos != -1) + attachment->m_realName.SetLength(charPos); + /* Now trim off any named anchors or search data. */ + charPos = attachment->m_realName.FindChar('#'); + if (charPos != -1) + attachment->m_realName.SetLength(charPos); + } + /* Now lose the %XX crap. */ + nsCString unescaped_real_name; + MsgUnescapeString(attachment->m_realName, 0, unescaped_real_name); + attachment->m_realName = unescaped_real_name; + } + + /* Now a special case for attaching uuencoded files... + + If we attach a file "foo.txt.uu", we will send it out with + Content-Type: text/plain; Content-Transfer-Encoding: x-uuencode. + When saving such a file, a mail reader will generally decode it first + (thus removing the uuencoding.) So, let's make life a little easier by + removing the indication of uuencoding from the file name itself. (This + will presumably make the file name in the Content-Disposition header be + the same as the file name in the "begin" line of the uuencoded data.) + + However, since there are mailers out there (including earlier versions of + Mozilla) that will use "foo.txt.uu" as the file name, we still need to + cope with that; the code which copes with that is in the MIME parser, in + libmime/mimei.c. + */ + if (attachment->m_already_encoded_p && !attachment->m_encoding.IsEmpty()) + { + /* #### TOTAL KLUDGE. + I'd like to ask the mime.types file, "what extensions correspond + to obj->encoding (which happens to be "x-uuencode") but doing that + in a non-sphagetti way would require brain surgery. So, since + currently uuencode is the only content-transfer-encoding which we + understand which traditionally has an extension, we just special- + case it here! + + Note that it's special-cased in a similar way in libmime/mimei.c. + */ + if (attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE) || + attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE2) || + attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE3) || + attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE4)) + { + if (StringEndsWith(attachment->m_realName, NS_LITERAL_CSTRING(".uu"))) + attachment->m_realName.Cut(attachment->m_realName.Length() - 3, 3); + else if (StringEndsWith(attachment->m_realName, NS_LITERAL_CSTRING(".uue"))) + attachment->m_realName.Cut(attachment->m_realName.Length() - 4, 4); + } + } +} + +// Utility to create a nsIURI object... +nsresult +nsMsgNewURL(nsIURI** aInstancePtrResult, const char * aSpec) +{ + nsresult rv = NS_OK; + if (nullptr == aInstancePtrResult) + return NS_ERROR_NULL_POINTER; + nsCOMPtr<nsIIOService> pNetService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(pNetService, NS_ERROR_UNEXPECTED); + if (PL_strstr(aSpec, "://") == nullptr && strncmp(aSpec, "data:", 5)) + { + //XXXjag Temporary fix for bug 139362 until the real problem(bug 70083) get fixed + nsAutoCString uri(NS_LITERAL_CSTRING("http://")); + uri.Append(aSpec); + rv = pNetService->NewURI(uri, nullptr, nullptr, aInstancePtrResult); + } + else + rv = pNetService->NewURI(nsDependentCString(aSpec), nullptr, nullptr, aInstancePtrResult); + return rv; +} + +bool +nsMsgIsLocalFile(const char *url) +{ + /* + A url is considered as a local file if it's start with file:// + But on Window, we need to filter UNC file url because there + are not really local file. Those start with file://// + */ + if (PL_strncasecmp(url, "file://", 7) == 0) + { +#ifdef XP_WIN + if (PL_strncasecmp(url, "file:////", 9) == 0) + return false; +#endif + return true; + } + else + return false; +} + +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, 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->GetPath(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", getter_Copies(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) + { + MsgReplaceSubstring(uri, " ", "%20"); + prefs->SetCharPref("mail.default_sendlater_uri", uri.get()); + } + } + 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(const char* charset, bool* flowed, bool* delsp, bool* formatted, bool* disallowBreaks) +{ + *flowed = false; + *delsp = false; + *formatted = true; + *disallowBreaks = 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); + } + + // We could test statefulCharset(charset) here, but since ISO-2022-JP is the + // only one left we support, we might as well check for it directly. + if (PL_strcasecmp(charset, "ISO-2022-JP") == 0) { + // Make sure we honour RFC 1468. For encoding in ISO-2022-JP we need to + // send short lines to allow 7bit transfer encoding. + *disallowBreaks = false; + if (*flowed) + *delsp = true; + } +} |