diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mailnews/base/src/nsMsgUtils.cpp | 1926 |
1 files changed, 1926 insertions, 0 deletions
diff --git a/comm/mailnews/base/src/nsMsgUtils.cpp b/comm/mailnews/base/src/nsMsgUtils.cpp new file mode 100644 index 0000000000..661b4fb219 --- /dev/null +++ b/comm/mailnews/base/src/nsMsgUtils.cpp @@ -0,0 +1,1926 @@ +/* -*- 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 "msgCore.h" +#include "nsIMsgHdr.h" +#include "nsMsgUtils.h" +#include "nsMsgFolderFlags.h" +#include "nsMsgMessageFlags.h" +#include "nsString.h" +#include "nsIServiceManager.h" +#include "nsCOMPtr.h" +#include "nsIFolderLookupService.h" +#include "nsIImapUrl.h" +#include "nsIMailboxUrl.h" +#include "nsINntpUrl.h" +#include "nsMsgI18N.h" +#include "nsNativeCharsetUtils.h" +#include "nsCharTraits.h" +#include "prprf.h" +#include "prmem.h" +#include "nsNetCID.h" +#include "nsIIOService.h" +#include "nsIMimeConverter.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsISupportsPrimitives.h" +#include "nsIPrefLocalizedString.h" +#include "nsIRelativeFilePref.h" +#include "mozilla/nsRelativeFilePref.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsISpamSettings.h" +#include "nsICryptoHash.h" +#include "nsNativeCharsetUtils.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIRssIncomingServer.h" +#include "nsIMsgFolder.h" +#include "nsIMsgProtocolInfo.h" +#include "nsIMsgMessageService.h" +#include "nsIMsgAccountManager.h" +#include "nsIOutputStream.h" +#include "nsMsgFileStream.h" +#include "nsIFileURL.h" +#include "nsNetUtil.h" +#include "nsProtocolProxyService.h" +#include "nsIProtocolProxyCallback.h" +#include "nsICancelable.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIStringBundle.h" +#include "nsIMsgWindow.h" +#include "nsIWindowWatcher.h" +#include "nsIPrompt.h" +#include "nsIMsgSearchTerm.h" +#include "nsTextFormatter.h" +#include "nsIStreamListener.h" +#include "nsReadLine.h" +#include "nsILineInputStream.h" +#include "nsIParserUtils.h" +#include "nsICharsetConverterManager.h" +#include "nsIDocumentEncoder.h" +#include "mozilla/Components.h" +#include "locale.h" +#include "nsStringStream.h" +#include "nsIInputStreamPump.h" +#include "nsIInputStream.h" +#include "nsIChannel.h" +#include "nsIURIMutator.h" +#include "mozilla/Unused.h" +#include "mozilla/Preferences.h" +#include "mozilla/Encoding.h" +#include "mozilla/EncodingDetector.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Utf8.h" +#include "mozilla/Buffer.h" +#include "nsIPromptService.h" +#include "nsEmbedCID.h" + +/* for logging to Error Console */ +#include "nsIScriptError.h" +#include "nsIConsoleService.h" + +// Log an error string to the error console +// (adapted from nsContentUtils::LogSimpleConsoleError). +// Flag can indicate error, warning or info. +NS_MSG_BASE void MsgLogToConsole4(const nsAString& aErrorText, + const nsAString& aFilename, + uint32_t aLinenumber, uint32_t aFlag) { + nsCOMPtr<nsIScriptError> scriptError = + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + if (NS_WARN_IF(!scriptError)) return; + nsCOMPtr<nsIConsoleService> console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (NS_WARN_IF(!console)) return; + if (NS_FAILED(scriptError->Init(aErrorText, aFilename, EmptyString(), + aLinenumber, 0, aFlag, "mailnews"_ns, false, + false))) + return; + console->LogMessage(scriptError); + return; +} + +using namespace mozilla; +using namespace mozilla::net; + +#define ILLEGAL_FOLDER_CHARS ";#" +#define ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER "." +#define ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER ".~ " + +nsresult GetMessageServiceContractIDForURI(const char* uri, + nsCString& contractID) { + nsresult rv = NS_OK; + // Find protocol + nsAutoCString uriStr(uri); + int32_t pos = uriStr.FindChar(':'); + if (pos == -1) return NS_ERROR_FAILURE; + + nsAutoCString protocol(StringHead(uriStr, pos)); + + if (protocol.EqualsLiteral("file")) { + protocol.AssignLiteral("mailbox"); + } + // Build message service contractid + contractID = "@mozilla.org/messenger/messageservice;1?type="; + contractID += protocol.get(); + + return rv; +} + +// Note: This function is also implemented in JS, see MailServices.jsm. +nsresult GetMessageServiceFromURI(const nsACString& uri, + nsIMsgMessageService** aMessageService) { + nsresult rv; + + nsAutoCString contractID; + rv = GetMessageServiceContractIDForURI(PromiseFlatCString(uri).get(), + contractID); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgMessageService> msgService = + do_GetService(contractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + + msgService.forget(aMessageService); + return rv; +} + +nsresult GetMsgDBHdrFromURI(const nsACString& uri, nsIMsgDBHdr** msgHdr) { + nsCOMPtr<nsIMsgMessageService> msgMessageService; + nsresult rv = + GetMessageServiceFromURI(uri, getter_AddRefs(msgMessageService)); + NS_ENSURE_SUCCESS(rv, rv); + if (!msgMessageService) return NS_ERROR_FAILURE; + + return msgMessageService->MessageURIToMsgHdr(uri, msgHdr); +} + +// Where should this live? It's a utility used to convert a string priority, +// e.g., "High, Low, Normal" to an enum. +// Perhaps we should have an interface that groups together all these +// utilities... +nsresult NS_MsgGetPriorityFromString(const char* const priority, + nsMsgPriorityValue& outPriority) { + if (!priority) return NS_ERROR_NULL_POINTER; + + // Note: Checking the values separately and _before_ the names, + // hoping for a much faster match; + // Only _drawback_, as "priority" handling is not truly specified: + // some software may have the number meanings reversed (1=Lowest) !? + if (PL_strchr(priority, '1')) + outPriority = nsMsgPriority::highest; + else if (PL_strchr(priority, '2')) + outPriority = nsMsgPriority::high; + else if (PL_strchr(priority, '3')) + outPriority = nsMsgPriority::normal; + else if (PL_strchr(priority, '4')) + outPriority = nsMsgPriority::low; + else if (PL_strchr(priority, '5')) + outPriority = nsMsgPriority::lowest; + else if (PL_strcasestr(priority, "Highest")) + outPriority = nsMsgPriority::highest; + // Important: "High" must be tested after "Highest" ! + else if (PL_strcasestr(priority, "High") || PL_strcasestr(priority, "Urgent")) + outPriority = nsMsgPriority::high; + else if (PL_strcasestr(priority, "Normal")) + outPriority = nsMsgPriority::normal; + else if (PL_strcasestr(priority, "Lowest")) + outPriority = nsMsgPriority::lowest; + // Important: "Low" must be tested after "Lowest" ! + else if (PL_strcasestr(priority, "Low") || + PL_strcasestr(priority, "Non-urgent")) + outPriority = nsMsgPriority::low; + else + // "Default" case gets default value. + outPriority = nsMsgPriority::Default; + + return NS_OK; +} + +nsresult NS_MsgGetPriorityValueString(const nsMsgPriorityValue p, + nsACString& outValueString) { + switch (p) { + case nsMsgPriority::highest: + outValueString.Assign('1'); + break; + case nsMsgPriority::high: + outValueString.Assign('2'); + break; + case nsMsgPriority::normal: + outValueString.Assign('3'); + break; + case nsMsgPriority::low: + outValueString.Assign('4'); + break; + case nsMsgPriority::lowest: + outValueString.Assign('5'); + break; + case nsMsgPriority::none: + case nsMsgPriority::notSet: + // Note: '0' is a "fake" value; we expect to never be in this case. + outValueString.Assign('0'); + break; + default: + NS_ASSERTION(false, "invalid priority value"); + } + + return NS_OK; +} + +nsresult NS_MsgGetUntranslatedPriorityName(const nsMsgPriorityValue p, + nsACString& outName) { + switch (p) { + case nsMsgPriority::highest: + outName.AssignLiteral("Highest"); + break; + case nsMsgPriority::high: + outName.AssignLiteral("High"); + break; + case nsMsgPriority::normal: + outName.AssignLiteral("Normal"); + break; + case nsMsgPriority::low: + outName.AssignLiteral("Low"); + break; + case nsMsgPriority::lowest: + outName.AssignLiteral("Lowest"); + break; + case nsMsgPriority::none: + case nsMsgPriority::notSet: + // Note: 'None' is a "fake" value; we expect to never be in this case. + outName.AssignLiteral("None"); + break; + default: + NS_ASSERTION(false, "invalid priority value"); + } + + return NS_OK; +} + +/* this used to be XP_StringHash2 from xp_hash.c */ +/* phong's linear congruential hash */ +static uint32_t StringHash(const char* ubuf, int32_t len = -1) { + unsigned char* buf = (unsigned char*)ubuf; + uint32_t h = 1; + unsigned char* end = buf + (len == -1 ? strlen(ubuf) : len); + while (buf < end) { + h = 0x63c63cd9 * h + 0x9c39c33d + (int32_t)*buf; + buf++; + } + return h; +} + +inline uint32_t StringHash(const nsAutoString& str) { + const char16_t* strbuf = str.get(); + return StringHash(reinterpret_cast<const char*>(strbuf), str.Length() * 2); +} + +/* Utility functions used in a few places in mailnews */ +int32_t MsgFindCharInSet(const nsCString& aString, const char* aChars, + uint32_t aOffset) { + return aString.FindCharInSet(aChars, aOffset); +} + +int32_t MsgFindCharInSet(const nsString& aString, const char16_t* aChars, + uint32_t aOffset) { + return aString.FindCharInSet(aChars, aOffset); +} + +static bool ConvertibleToNative(const nsAutoString& str) { + nsAutoCString native; + nsAutoString roundTripped; + NS_CopyUnicodeToNative(str, native); + NS_CopyNativeToUnicode(native, roundTripped); + return str.Equals(roundTripped); +} + +#if defined(XP_UNIX) +const static uint32_t MAX_LEN = 55; +#elif defined(XP_WIN) +const static uint32_t MAX_LEN = 55; +#else +# error need_to_define_your_max_filename_length +#endif + +nsresult NS_MsgHashIfNecessary(nsAutoCString& name) { + if (name.IsEmpty()) return NS_OK; // Nothing to do. + nsAutoCString str(name); + + // Given a filename, make it safe for filesystem + // certain filenames require hashing because they + // are too long or contain illegal characters + int32_t illegalCharacterIndex = MsgFindCharInSet( + str, FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS ILLEGAL_FOLDER_CHARS, 0); + + // Need to check the first ('.') and last ('.', '~' and ' ') char + if (illegalCharacterIndex == -1) { + int32_t lastIndex = str.Length() - 1; + if (nsLiteralCString(ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER) + .FindChar(str[0]) != -1) + illegalCharacterIndex = 0; + else if (nsLiteralCString(ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER) + .FindChar(str[lastIndex]) != -1) + illegalCharacterIndex = lastIndex; + else + illegalCharacterIndex = -1; + } + + char hashedname[MAX_LEN + 1]; + if (illegalCharacterIndex == -1) { + // no illegal chars, it's just too long + // keep the initial part of the string, but hash to make it fit + if (str.Length() > MAX_LEN) { + PL_strncpy(hashedname, str.get(), MAX_LEN + 1); + PR_snprintf(hashedname + MAX_LEN - 8, 9, "%08lx", + (unsigned long)StringHash(str.get())); + name = hashedname; + } + } else { + // found illegal chars, hash the whole thing + // if we do substitution, then hash, two strings + // could hash to the same value. + // for example, on mac: "foo__bar", "foo:_bar", "foo::bar" + // would map to "foo_bar". this way, all three will map to + // different values + PR_snprintf(hashedname, 9, "%08lx", (unsigned long)StringHash(str.get())); + name = hashedname; + } + + return NS_OK; +} + +// XXX : The number of UTF-16 2byte code units are half the number of +// bytes in legacy encodings for CJK strings and non-Latin1 in UTF-8. +// The ratio can be 1/3 for CJK strings in UTF-8. However, we can +// get away with using the same MAX_LEN for nsCString and nsString +// because MAX_LEN is defined rather conservatively in the first place. +nsresult NS_MsgHashIfNecessary(nsAutoString& name) { + if (name.IsEmpty()) return NS_OK; // Nothing to do. + int32_t illegalCharacterIndex = MsgFindCharInSet( + name, + u"" FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS ILLEGAL_FOLDER_CHARS, 0); + + // Need to check the first ('.') and last ('.', '~' and ' ') char + if (illegalCharacterIndex == -1) { + int32_t lastIndex = name.Length() - 1; + if (NS_LITERAL_STRING_FROM_CSTRING(ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER) + .FindChar(name[0]) != -1) + illegalCharacterIndex = 0; + else if (NS_LITERAL_STRING_FROM_CSTRING(ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER) + .FindChar(name[lastIndex]) != -1) + illegalCharacterIndex = lastIndex; + else + illegalCharacterIndex = -1; + } + + char hashedname[9]; + int32_t keptLength = -1; + if (illegalCharacterIndex != -1) + keptLength = illegalCharacterIndex; + else if (!ConvertibleToNative(name)) + keptLength = 0; + else if (name.Length() > MAX_LEN) { + keptLength = MAX_LEN - 8; + // To avoid keeping only the high surrogate of a surrogate pair + if (NS_IS_HIGH_SURROGATE(name.CharAt(keptLength - 1))) --keptLength; + } + + if (keptLength >= 0) { + PR_snprintf(hashedname, 9, "%08lx", (unsigned long)StringHash(name)); + name.SetLength(keptLength); + name.Append(NS_ConvertASCIItoUTF16(hashedname)); + } + + return NS_OK; +} + +nsresult FormatFileSize(int64_t size, bool useKB, nsAString& formattedSize) { + const char* sizeAbbrNames[] = { + "byteAbbreviation2", "kiloByteAbbreviation2", "megaByteAbbreviation2", + "gigaByteAbbreviation2", "teraByteAbbreviation2", "petaByteAbbreviation2", + }; + + nsresult rv; + + nsCOMPtr<nsIStringBundleService> bundleSvc = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + double unitSize = size < 0 ? 0.0 : size; + uint32_t unitIndex = 0; + + if (useKB) { + // Start by formatting in kilobytes + unitSize /= 1024; + if (unitSize < 0.1 && unitSize != 0) unitSize = 0.1; + unitIndex++; + } + + // Convert to next unit if it needs 4 digits (after rounding), but only if + // we know the name of the next unit + while ((unitSize >= 999.5) && (unitIndex < ArrayLength(sizeAbbrNames) - 1)) { + unitSize /= 1024; + unitIndex++; + } + + // Grab the string for the appropriate unit + nsString sizeAbbr; + rv = bundle->GetStringFromName(sizeAbbrNames[unitIndex], sizeAbbr); + NS_ENSURE_SUCCESS(rv, rv); + + // Get rid of insignificant bits by truncating to 1 or 0 decimal points + // 0.1 -> 0.1; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235 + nsTextFormatter::ssprintf( + formattedSize, sizeAbbr.get(), + (unitIndex != 0) && (unitSize < 99.95 && unitSize != 0) ? 1 : 0, + unitSize); + + int32_t separatorPos = formattedSize.FindChar('.'); + if (separatorPos != kNotFound) { + // The ssprintf returned a decimal number using a dot (.) as the decimal + // separator. Now we try to localize the separator. + // Try to get the decimal separator from the system's locale. + char* decimalPoint; +#ifdef HAVE_LOCALECONV + struct lconv* locale = localeconv(); + decimalPoint = locale->decimal_point; +#else + decimalPoint = getenv("LOCALE_DECIMAL_POINT"); +#endif + NS_ConvertUTF8toUTF16 decimalSeparator(decimalPoint); + if (decimalSeparator.IsEmpty()) decimalSeparator.Assign('.'); + + formattedSize.Replace(separatorPos, 1, decimalSeparator); + } + + return NS_OK; +} + +nsresult NS_MsgCreatePathStringFromFolderURI(const char* aFolderURI, + nsCString& aPathCString, + const nsCString& aScheme, + bool aIsNewsFolder) { + // A file name has to be in native charset. Here we convert + // to UTF-16 and check for 'unsafe' characters before converting + // to native charset. + NS_ENSURE_TRUE(mozilla::IsUtf8(nsDependentCString(aFolderURI)), + NS_ERROR_UNEXPECTED); + NS_ConvertUTF8toUTF16 oldPath(aFolderURI); + + nsAutoString pathPiece, path; + + int32_t startSlashPos = oldPath.FindChar('/'); + int32_t endSlashPos = (startSlashPos >= 0) + ? oldPath.FindChar('/', startSlashPos + 1) - 1 + : oldPath.Length() - 1; + if (endSlashPos < 0) endSlashPos = oldPath.Length(); +#if defined(XP_UNIX) || defined(XP_MACOSX) + bool isLocalUri = aScheme.EqualsLiteral("none") || + aScheme.EqualsLiteral("pop3") || + aScheme.EqualsLiteral("rss"); +#endif + // trick to make sure we only add the path to the first n-1 folders + bool haveFirst = false; + while (startSlashPos != -1) { + pathPiece.Assign( + Substring(oldPath, startSlashPos + 1, endSlashPos - startSlashPos)); + // skip leading '/' (and other // style things) + if (!pathPiece.IsEmpty()) { + // add .sbd onto the previous path + if (haveFirst) { + path.AppendLiteral(FOLDER_SUFFIX "/"); + } + + if (aIsNewsFolder) { + nsAutoCString tmp; + CopyUTF16toMUTF7(pathPiece, tmp); + CopyASCIItoUTF16(tmp, pathPiece); + } +#if defined(XP_UNIX) || defined(XP_MACOSX) + // Don't hash path pieces because local mail folder uri's have already + // been hashed. We're only doing this on the mac to limit potential + // regressions. + if (!isLocalUri) +#endif + NS_MsgHashIfNecessary(pathPiece); + path += pathPiece; + haveFirst = true; + } + // look for the next slash + startSlashPos = endSlashPos + 1; + + endSlashPos = (startSlashPos >= 0) + ? oldPath.FindChar('/', startSlashPos + 1) - 1 + : oldPath.Length() - 1; + if (endSlashPos < 0) endSlashPos = oldPath.Length(); + + if (startSlashPos >= endSlashPos) break; + } + return NS_CopyUnicodeToNative(path, aPathCString); +} + +bool NS_MsgStripRE(const nsCString& subject, nsCString& modifiedSubject) { + bool result = false; + + // Get localizedRe pref. + nsresult rv; + nsString utf16LocalizedRe; + NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, "mailnews.localizedRe", + EmptyString(), utf16LocalizedRe); + NS_ConvertUTF16toUTF8 localizedRe(utf16LocalizedRe); + + // Hardcoded "Re" so that no one can configure Mozilla standards incompatible. + nsAutoCString checkString("Re,RE,re,rE"); + if (!localizedRe.IsEmpty()) { + checkString.Append(','); + checkString.Append(localizedRe); + } + + // Decode the string. + nsCString decodedString; + nsCOMPtr<nsIMimeConverter> mimeConverter; + // We cannot strip "Re:" for RFC2047-encoded subject without modifying the + // original. + if (subject.Find("=?") != kNotFound) { + mimeConverter = + do_GetService("@mozilla.org/messenger/mimeconverter;1", &rv); + if (NS_SUCCEEDED(rv)) + rv = mimeConverter->DecodeMimeHeaderToUTF8(subject, nullptr, false, true, + decodedString); + } + + const char *s, *s_end; + if (decodedString.IsEmpty()) { + s = subject.BeginReading(); + s_end = s + subject.Length(); + } else { + s = decodedString.BeginReading(); + s_end = s + decodedString.Length(); + } + +AGAIN: + while (s < s_end && IS_SPACE(*s)) s++; + + const char* tokPtr = checkString.get(); + while (*tokPtr) { + // Tokenize the comma separated list. + size_t tokenLength = 0; + while (*tokPtr && *tokPtr != ',') { + tokenLength++; + tokPtr++; + } + // Check if the beginning of s is the actual token. + if (tokenLength && !strncmp(s, tokPtr - tokenLength, tokenLength)) { + if (s[tokenLength] == ':') { + s = s + tokenLength + 1; /* Skip over "Re:" */ + result = true; /* Yes, we stripped it. */ + goto AGAIN; /* Skip whitespace and try again. */ + } else if (s[tokenLength] == '[' || s[tokenLength] == '(') { + const char* s2 = s + tokenLength + 1; /* Skip over "Re[" */ + + // Skip forward over digits after the "[". + while (s2 < (s_end - 2) && isdigit((unsigned char)*s2)) s2++; + + // Now ensure that the following thing is "]:". + // Only if it is do we alter `s`. + if ((s2[0] == ']' || s2[0] == ')') && s2[1] == ':') { + s = s2 + 2; /* Skip over "]:" */ + result = true; /* Yes, we stripped it. */ + goto AGAIN; /* Skip whitespace and try again. */ + } + } + } + if (*tokPtr) tokPtr++; + } + + // If we didn't strip anything, we can return here. + if (!result) return false; + + if (decodedString.IsEmpty()) { + // We didn't decode anything, so just return a new string. + modifiedSubject.Assign(s); + return true; + } + + // We decoded the string, so we need to encode it again. We always encode in + // UTF-8. + mimeConverter->EncodeMimePartIIStr_UTF8( + nsDependentCString(s), false, sizeof("Subject:"), + nsIMimeConverter::MIME_ENCODED_WORD_SIZE, modifiedSubject); + return true; +} + +/* Very similar to strdup except it free's too + */ +char* NS_MsgSACopy(char** destination, const char* source) { + if (*destination) { + PR_Free(*destination); + *destination = 0; + } + if (!source) + *destination = nullptr; + else { + *destination = (char*)PR_Malloc(PL_strlen(source) + 1); + if (*destination == nullptr) return (nullptr); + + PL_strcpy(*destination, source); + } + return *destination; +} + +/* Again like strdup but it concatenates and free's and uses Realloc. + */ +char* NS_MsgSACat(char** destination, const char* source) { + if (source && *source) { + int destLength = *destination ? PL_strlen(*destination) : 0; + char* newDestination = + (char*)PR_Realloc(*destination, destLength + PL_strlen(source) + 1); + if (newDestination == nullptr) return nullptr; + + *destination = newDestination; + PL_strcpy(*destination + destLength, source); + } + return *destination; +} + +nsresult NS_MsgEscapeEncodeURLPath(const nsAString& aStr, nsCString& aResult) { + return MsgEscapeString(NS_ConvertUTF16toUTF8(aStr), + nsINetUtil::ESCAPE_URL_PATH, aResult); +} + +nsresult NS_MsgDecodeUnescapeURLPath(const nsACString& aPath, + nsAString& aResult) { + nsAutoCString unescapedName; + MsgUnescapeString( + aPath, + nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED, + unescapedName); + CopyUTF8toUTF16(unescapedName, aResult); + return NS_OK; +} + +bool WeAreOffline() { + bool offline = false; + + nsCOMPtr<nsIIOService> ioService = mozilla::components::IO::Service(); + if (ioService) ioService->GetOffline(&offline); + + return offline; +} + +// Find a folder by URL. If it doesn't exist, null will be returned +// via aFolder. +nsresult FindFolder(const nsACString& aFolderURI, nsIMsgFolder** aFolder) { + NS_ENSURE_ARG_POINTER(aFolder); + + *aFolder = nullptr; + + nsresult rv; + nsCOMPtr<nsIFolderLookupService> fls(do_GetService(NSIFLS_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // GetFolderForURL returns NS_OK and null for non-existent folders + rv = fls->GetFolderForURL(aFolderURI, aFolder); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +// Fetch an existing folder by URL +// The returned aFolder will be non-null if and only if result is NS_OK. +// NS_OK - folder was found +// NS_MSG_FOLDER_MISSING - if aFolderURI not found +nsresult GetExistingFolder(const nsACString& aFolderURI, + nsIMsgFolder** aFolder) { + nsresult rv = FindFolder(aFolderURI, aFolder); + NS_ENSURE_SUCCESS(rv, rv); + return *aFolder ? NS_OK : NS_MSG_ERROR_FOLDER_MISSING; +} + +nsresult GetOrCreateFolder(const nsACString& aFolderURI, + nsIMsgFolder** aFolder) { + NS_ENSURE_ARG_POINTER(aFolder); + + *aFolder = nullptr; + + nsresult rv; + nsCOMPtr<nsIFolderLookupService> fls(do_GetService(NSIFLS_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = fls->GetOrCreateFolderForURL(aFolderURI, aFolder); + NS_ENSURE_SUCCESS(rv, rv); + + return *aFolder ? NS_OK : NS_ERROR_FAILURE; +} + +bool IsAFromSpaceLine(char* start, const char* end) { + bool rv = false; + while ((start < end) && (*start == '>')) start++; + // If the leading '>'s are followed by an 'F' then we have a possible case + // here. + if ((*start == 'F') && (end - start > 4) && !strncmp(start, "From ", 5)) + rv = true; + return rv; +} + +// +// This function finds all lines starting with "From " or "From " preceding +// with one or more '>' (ie, ">From", ">>From", etc) in the input buffer +// (between 'start' and 'end') and prefix them with a ">" . +// +nsresult EscapeFromSpaceLine(nsIOutputStream* outputStream, char* start, + const char* end) { + nsresult rv; + char* pChar; + uint32_t written; + + pChar = start; + while (start < end) { + while ((pChar < end) && (*pChar != '\r') && ((pChar + 1) < end) && + (*(pChar + 1) != '\n')) + pChar++; + if ((pChar + 1) == end) pChar++; + + if (pChar < end) { + // Found a line so check if it's a qualified "From " line. + if (IsAFromSpaceLine(start, pChar)) { + rv = outputStream->Write(">", 1, &written); + NS_ENSURE_SUCCESS(rv, rv); + } + int32_t lineTerminatorCount = (*(pChar + 1) == '\n') ? 2 : 1; + rv = outputStream->Write(start, pChar - start + lineTerminatorCount, + &written); + NS_ENSURE_SUCCESS(rv, rv); + pChar += lineTerminatorCount; + start = pChar; + } else if (start < end) { + // Check and flush out the remaining data and we're done. + if (IsAFromSpaceLine(start, end)) { + rv = outputStream->Write(">", 1, &written); + NS_ENSURE_SUCCESS(rv, rv); + } + rv = outputStream->Write(start, end - start, &written); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + } + return NS_OK; +} + +nsresult IsRFC822HeaderFieldName(const char* aHdr, bool* aResult) { + NS_ENSURE_ARG_POINTER(aHdr); + NS_ENSURE_ARG_POINTER(aResult); + uint32_t length = strlen(aHdr); + for (uint32_t i = 0; i < length; i++) { + char c = aHdr[i]; + if (c < '!' || c == ':' || c > '~') { + *aResult = false; + return NS_OK; + } + } + *aResult = true; + return NS_OK; +} + +// Warning, currently this routine only works for the Junk Folder +nsresult GetOrCreateJunkFolder(const nsACString& aURI, + nsIUrlListener* aListener) { + nsresult rv; + + nsCOMPtr<nsIMsgFolder> folder; + rv = GetOrCreateFolder(aURI, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + // don't check validity of folder - caller will handle creating it + nsCOMPtr<nsIMsgIncomingServer> server; + // make sure that folder hierarchy is built so that legitimate parent-child + // relationship is established + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + if (!server) return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIMsgFolder> msgFolder; + rv = server->GetMsgFolderFromURI(folder, aURI, getter_AddRefs(msgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> parent; + rv = msgFolder->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv) || !parent) { + nsCOMPtr<nsIFile> folderPath; + // for local folders, path is to the berkeley mailbox. + // for imap folders, path needs to have .msf appended to the name + msgFolder->GetFilePath(getter_AddRefs(folderPath)); + + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + rv = server->GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isAsyncFolder; + rv = protocolInfo->GetFoldersCreatedAsync(&isAsyncFolder); + NS_ENSURE_SUCCESS(rv, rv); + + // if we can't get the path from the folder, then try to create the storage. + // for imap, it doesn't matter if the .msf file exists - it still might not + // exist on the server, so we should try to create it + bool exists = false; + if (!isAsyncFolder && folderPath) folderPath->Exists(&exists); + if (!exists) { + // Hack to work around a localization bug with the Junk Folder. + // Please see Bug #270261 for more information... + nsString localizedJunkName; + msgFolder->GetName(localizedJunkName); + + // force the junk folder name to be Junk so it gets created on disk + // correctly... + msgFolder->SetName(u"Junk"_ns); + msgFolder->SetFlag(nsMsgFolderFlags::Junk); + rv = msgFolder->CreateStorageIfMissing(aListener); + NS_ENSURE_SUCCESS(rv, rv); + + // now restore the localized folder name... + msgFolder->SetName(localizedJunkName); + + // XXX TODO + // JUNK MAIL RELATED + // ugh, I hate this hack + // we have to do this (for now) + // because imap and local are different (one creates folder asynch, the + // other synch) one will notify the listener, one will not. I blame + // nsMsgCopy. we should look into making it so no matter what the folder + // type we always call the listener this code should move into local + // folder's version of CreateStorageIfMissing() + if (!isAsyncFolder && aListener) { + rv = aListener->OnStartRunningUrl(nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aListener->OnStopRunningUrl(nullptr, NS_OK); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } else { + // if the folder exists, we should set the junk flag on it + // which is what the listener will do + if (aListener) { + rv = aListener->OnStartRunningUrl(nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aListener->OnStopRunningUrl(nullptr, NS_OK); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +nsresult IsRSSArticle(nsIURI* aMsgURI, bool* aIsRSSArticle) { + nsresult rv; + *aIsRSSArticle = false; + + nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(aMsgURI, &rv); + if (NS_FAILED(rv)) return rv; + + nsCString resourceURI; + msgUrl->GetUri(resourceURI); + + // get the msg service for this URI + nsCOMPtr<nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(resourceURI, getter_AddRefs(msgService)); + NS_ENSURE_SUCCESS(rv, rv); + + // Check if the message is a feed message, regardless of folder. + uint32_t flags; + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = msgService->MessageURIToMsgHdr(resourceURI, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::FeedMsg) { + *aIsRSSArticle = true; + return rv; + } + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aMsgURI, &rv); + mozilla::Unused << mailnewsUrl; + NS_ENSURE_SUCCESS(rv, rv); + + // get the folder and the server from the msghdr + nsCOMPtr<nsIMsgFolder> folder; + rv = msgHdr->GetFolder(getter_AddRefs(folder)); + if (NS_SUCCEEDED(rv) && folder) { + nsCOMPtr<nsIMsgIncomingServer> server; + folder->GetServer(getter_AddRefs(server)); + nsCOMPtr<nsIRssIncomingServer> rssServer = do_QueryInterface(server); + + if (rssServer) *aIsRSSArticle = true; + } + + return rv; +} + +// digest needs to be a pointer to a DIGEST_LENGTH (16) byte buffer +nsresult MSGCramMD5(const char* text, int32_t text_len, const char* key, + int32_t key_len, unsigned char* digest) { + nsresult rv; + + nsAutoCString hash; + nsCOMPtr<nsICryptoHash> hasher = + do_CreateInstance("@mozilla.org/security/hash;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // this code adapted from + // http://www.cis.ohio-state.edu/cgi-bin/rfc/rfc2104.html + + char innerPad[65]; /* inner padding - key XORd with innerPad */ + char outerPad[65]; /* outer padding - key XORd with outerPad */ + int i; + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (key_len > 64) { + rv = hasher->Init(nsICryptoHash::MD5); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Update((const uint8_t*)key, key_len); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Finish(false, hash); + NS_ENSURE_SUCCESS(rv, rv); + + key = hash.get(); + key_len = DIGEST_LENGTH; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR outerPad, MD5(K XOR innerPad, text)) + * + * where K is an n byte key + * innerPad is the byte 0x36 repeated 64 times + * outerPad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + memset(innerPad, 0, sizeof innerPad); + memset(outerPad, 0, sizeof outerPad); + memcpy(innerPad, key, key_len); + memcpy(outerPad, key, key_len); + + /* XOR key with innerPad and outerPad values */ + for (i = 0; i < 64; i++) { + innerPad[i] ^= 0x36; + outerPad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ + nsAutoCString result; + rv = hasher->Init(nsICryptoHash::MD5); /* init context for 1st pass */ + rv = hasher->Update((const uint8_t*)innerPad, 64); /* start with inner pad */ + rv = hasher->Update((const uint8_t*)text, + text_len); /* then text of datagram */ + rv = hasher->Finish(false, result); /* finish up 1st pass */ + + /* + * perform outer MD5 + */ + hasher->Init(nsICryptoHash::MD5); /* init context for 2nd pass */ + rv = hasher->Update((const uint8_t*)outerPad, 64); /* start with outer pad */ + rv = hasher->Update((const uint8_t*)result.get(), + 16); /* then results of 1st hash */ + rv = hasher->Finish(false, result); /* finish up 2nd pass */ + + if (result.Length() != DIGEST_LENGTH) return NS_ERROR_UNEXPECTED; + + memcpy(digest, result.get(), DIGEST_LENGTH); + + return rv; +} + +// digest needs to be a pointer to a DIGEST_LENGTH (16) byte buffer +nsresult MSGApopMD5(const char* text, int32_t text_len, const char* password, + int32_t password_len, unsigned char* digest) { + nsresult rv; + nsAutoCString result; + + nsCOMPtr<nsICryptoHash> hasher = + do_CreateInstance("@mozilla.org/security/hash;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Init(nsICryptoHash::MD5); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Update((const uint8_t*)text, text_len); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Update((const uint8_t*)password, password_len); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Finish(false, result); + NS_ENSURE_SUCCESS(rv, rv); + + if (result.Length() != DIGEST_LENGTH) return NS_ERROR_UNEXPECTED; + + memcpy(digest, result.get(), DIGEST_LENGTH); + return rv; +} + +NS_MSG_BASE nsresult NS_GetPersistentFile(const char* relPrefName, + const char* absPrefName, + const char* dirServiceProp, + bool& gotRelPref, nsIFile** aFile, + nsIPrefBranch* prefBranch) { + NS_ENSURE_ARG_POINTER(aFile); + *aFile = nullptr; + NS_ENSURE_ARG(relPrefName); + NS_ENSURE_ARG(absPrefName); + gotRelPref = false; + + nsCOMPtr<nsIPrefBranch> mainBranch; + if (!prefBranch) { + nsCOMPtr<nsIPrefService> prefService( + do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (!prefService) return NS_ERROR_FAILURE; + prefService->GetBranch(nullptr, getter_AddRefs(mainBranch)); + if (!mainBranch) return NS_ERROR_FAILURE; + prefBranch = mainBranch; + } + + nsCOMPtr<nsIFile> localFile; + + // Get the relative first + nsCOMPtr<nsIRelativeFilePref> relFilePref; + prefBranch->GetComplexValue(relPrefName, NS_GET_IID(nsIRelativeFilePref), + getter_AddRefs(relFilePref)); + if (relFilePref) { + relFilePref->GetFile(getter_AddRefs(localFile)); + NS_ASSERTION(localFile, "An nsIRelativeFilePref has no file."); + if (localFile) gotRelPref = true; + } + + // If not, get the old absolute + if (!localFile) { + prefBranch->GetComplexValue(absPrefName, NS_GET_IID(nsIFile), + getter_AddRefs(localFile)); + + // If not, and given a dirServiceProp, use directory service. + if (!localFile && dirServiceProp) { + nsCOMPtr<nsIProperties> dirService( + do_GetService("@mozilla.org/file/directory_service;1")); + if (!dirService) return NS_ERROR_FAILURE; + dirService->Get(dirServiceProp, NS_GET_IID(nsIFile), + getter_AddRefs(localFile)); + if (!localFile) return NS_ERROR_FAILURE; + } + } + + if (localFile) { + localFile->Normalize(); + localFile.forget(aFile); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_MSG_BASE nsresult NS_SetPersistentFile(const char* relPrefName, + const char* absPrefName, + nsIFile* aFile, + nsIPrefBranch* prefBranch) { + NS_ENSURE_ARG(relPrefName); + NS_ENSURE_ARG(absPrefName); + NS_ENSURE_ARG(aFile); + + nsCOMPtr<nsIPrefBranch> mainBranch; + if (!prefBranch) { + nsCOMPtr<nsIPrefService> prefService( + do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (!prefService) return NS_ERROR_FAILURE; + prefService->GetBranch(nullptr, getter_AddRefs(mainBranch)); + if (!mainBranch) return NS_ERROR_FAILURE; + prefBranch = mainBranch; + } + + // Write the absolute for backwards compatibilty's sake. + // Or, if aPath is on a different drive than the profile dir. + nsresult rv = + prefBranch->SetComplexValue(absPrefName, NS_GET_IID(nsIFile), aFile); + + // Write the relative path. + nsCOMPtr<nsIRelativeFilePref> relFilePref = new nsRelativeFilePref(); + mozilla::Unused << relFilePref->SetFile(aFile); + mozilla::Unused << relFilePref->SetRelativeToKey( + nsLiteralCString(NS_APP_USER_PROFILE_50_DIR)); + + nsresult rv2 = prefBranch->SetComplexValue( + relPrefName, NS_GET_IID(nsIRelativeFilePref), relFilePref); + if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) + prefBranch->ClearUserPref(relPrefName); + + return rv; +} + +NS_MSG_BASE nsresult NS_GetUnicharPreferenceWithDefault( + nsIPrefBranch* prefBranch, // can be null, if so uses the root branch + const char* prefName, const nsAString& defValue, nsAString& prefValue) { + NS_ENSURE_ARG(prefName); + + nsCOMPtr<nsIPrefBranch> pbr; + if (!prefBranch) { + pbr = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefBranch = pbr; + } + + nsCString valueUtf8; + nsresult rv = + prefBranch->GetStringPref(prefName, EmptyCString(), 0, valueUtf8); + if (NS_SUCCEEDED(rv)) + CopyUTF8toUTF16(valueUtf8, prefValue); + else + prefValue = defValue; + return NS_OK; +} + +NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreferenceWithDefault( + nsIPrefBranch* prefBranch, // can be null, if so uses the root branch + const char* prefName, const nsAString& defValue, nsAString& prefValue) { + NS_ENSURE_ARG(prefName); + + nsCOMPtr<nsIPrefBranch> pbr; + if (!prefBranch) { + pbr = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefBranch = pbr; + } + + nsCOMPtr<nsIPrefLocalizedString> str; + nsresult rv = prefBranch->GetComplexValue( + prefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str)); + if (NS_SUCCEEDED(rv)) { + nsString tmpValue; + str->ToString(getter_Copies(tmpValue)); + prefValue.Assign(tmpValue); + } else + prefValue = defValue; + return NS_OK; +} + +NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreference( + nsIPrefBranch* prefBranch, // can be null, if so uses the root branch + const char* prefName, nsAString& prefValue) { + NS_ENSURE_ARG_POINTER(prefName); + + nsCOMPtr<nsIPrefBranch> pbr; + if (!prefBranch) { + pbr = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefBranch = pbr; + } + + nsCOMPtr<nsIPrefLocalizedString> str; + nsresult rv = prefBranch->GetComplexValue( + prefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString tmpValue; + str->ToString(getter_Copies(tmpValue)); + prefValue.Assign(tmpValue); + return NS_OK; +} + +void PRTime2Seconds(PRTime prTime, uint32_t* seconds) { + *seconds = (uint32_t)(prTime / PR_USEC_PER_SEC); +} + +void PRTime2Seconds(PRTime prTime, int32_t* seconds) { + *seconds = (int32_t)(prTime / PR_USEC_PER_SEC); +} + +void Seconds2PRTime(uint32_t seconds, PRTime* prTime) { + *prTime = (PRTime)seconds * PR_USEC_PER_SEC; +} + +nsresult GetSummaryFileLocation(nsIFile* fileLocation, + nsIFile** summaryLocation) { + nsresult rv; + nsCOMPtr<nsIFile> newSummaryLocation = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + newSummaryLocation->InitWithFile(fileLocation); + nsString fileName; + + rv = newSummaryLocation->GetLeafName(fileName); + if (NS_FAILED(rv)) return rv; + + fileName.AppendLiteral(SUMMARY_SUFFIX); + rv = newSummaryLocation->SetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + newSummaryLocation.forget(summaryLocation); + return NS_OK; +} + +void MsgGenerateNowStr(nsACString& nowStr) { + char dateBuf[100]; + dateBuf[0] = '\0'; + PRExplodedTime exploded; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded); + PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y", + &exploded); + nowStr.Assign(dateBuf); +} + +// Gets a special directory and appends the supplied file name onto it. +nsresult GetSpecialDirectoryWithFileName(const char* specialDirName, + const char* fileName, + nsIFile** result) { + nsresult rv = NS_GetSpecialDirectory(specialDirName, result); + NS_ENSURE_SUCCESS(rv, rv); + + return (*result)->AppendNative(nsDependentCString(fileName)); +} + +// Cleans up temp files with matching names +nsresult MsgCleanupTempFiles(const char* fileName, const char* extension) { + nsCOMPtr<nsIFile> tmpFile; + nsCString rootName(fileName); + rootName.Append('.'); + rootName.Append(extension); + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, rootName.get(), + getter_AddRefs(tmpFile)); + + NS_ENSURE_SUCCESS(rv, rv); + int index = 1; + bool exists; + do { + tmpFile->Exists(&exists); + if (exists) { + tmpFile->Remove(false); + nsCString leafName(fileName); + leafName.Append('-'); + leafName.AppendInt(index); + leafName.Append('.'); + leafName.Append(extension); + // start with "Picture-1.jpg" after "Picture.jpg" exists + tmpFile->SetNativeLeafName(leafName); + } + } while (exists && index++ < 10000); + return NS_OK; +} + +nsresult MsgGetFileStream(nsIFile* file, nsIOutputStream** fileStream) { + RefPtr<nsMsgFileStream> newFileStream = new nsMsgFileStream; + nsresult rv = newFileStream->InitWithFile(file); + NS_ENSURE_SUCCESS(rv, rv); + newFileStream.forget(fileStream); + return NS_OK; +} + +nsresult MsgNewBufferedFileOutputStream(nsIOutputStream** aResult, + nsIFile* aFile, int32_t aIOFlags, + int32_t aPerm) { + nsCOMPtr<nsIOutputStream> stream; + nsresult rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile, + aIOFlags, aPerm); + if (NS_SUCCEEDED(rv)) + rv = NS_NewBufferedOutputStream(aResult, stream.forget(), + FILE_IO_BUFFER_SIZE); + return rv; +} + +nsresult MsgNewSafeBufferedFileOutputStream(nsIOutputStream** aResult, + nsIFile* aFile, int32_t aIOFlags, + int32_t aPerm) { + nsCOMPtr<nsIOutputStream> stream; + nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), aFile, + aIOFlags, aPerm); + if (NS_SUCCEEDED(rv)) + rv = NS_NewBufferedOutputStream(aResult, stream.forget(), + FILE_IO_BUFFER_SIZE); + return rv; +} + +bool MsgFindKeyword(const nsCString& keyword, nsCString& keywords, + int32_t* aStartOfKeyword, int32_t* aLength) { +// nsTString_CharT::Find(const nsCString& aString, +// bool aIgnoreCase=false, +// int32_t aOffset=0, +// int32_t aCount=-1 ) const; +#define FIND_KEYWORD(keywords, keyword, offset) \ + ((keywords).Find((keyword), (offset))) + // 'keyword' is the single keyword we're looking for + // 'keywords' is a space delimited list of keywords to be searched, + // which may be just a single keyword or even be empty + const int32_t kKeywordLen = keyword.Length(); + const char* start = keywords.BeginReading(); + const char* end = keywords.EndReading(); + *aStartOfKeyword = FIND_KEYWORD(keywords, keyword, 0); + while (*aStartOfKeyword >= 0) { + const char* matchStart = start + *aStartOfKeyword; + const char* matchEnd = matchStart + kKeywordLen; + // For a real match, matchStart must be the start of keywords or preceded + // by a space and matchEnd must be the end of keywords or point to a space. + if ((matchStart == start || *(matchStart - 1) == ' ') && + (matchEnd == end || *matchEnd == ' ')) { + *aLength = kKeywordLen; + return true; + } + *aStartOfKeyword = + FIND_KEYWORD(keywords, keyword, *aStartOfKeyword + kKeywordLen); + } + + *aLength = 0; + return false; +#undef FIND_KEYWORD +} + +bool MsgHostDomainIsTrusted(nsCString& host, nsCString& trustedMailDomains) { + const char* end; + uint32_t hostLen, domainLen; + bool domainIsTrusted = false; + + const char* domain = trustedMailDomains.BeginReading(); + const char* domainEnd = trustedMailDomains.EndReading(); + const char* hostStart = host.BeginReading(); + hostLen = host.Length(); + + do { + // skip any whitespace + while (*domain == ' ' || *domain == '\t') ++domain; + + // find end of this domain in the string + end = strchr(domain, ','); + if (!end) end = domainEnd; + + // to see if the hostname is in the domain, check if the domain + // matches the end of the hostname. + domainLen = end - domain; + if (domainLen && hostLen >= domainLen) { + const char* hostTail = hostStart + hostLen - domainLen; + if (PL_strncasecmp(domain, hostTail, domainLen) == 0) { + // now, make sure either that the hostname is a direct match or + // that the hostname begins with a dot. + if (hostLen == domainLen || *hostTail == '.' || + *(hostTail - 1) == '.') { + domainIsTrusted = true; + break; + } + } + } + + domain = end + 1; + } while (*end); + return domainIsTrusted; +} + +nsresult MsgGetLocalFileFromURI(const nsACString& aUTF8Path, nsIFile** aFile) { + nsresult rv; + nsCOMPtr<nsIURI> argURI; + rv = NS_NewURI(getter_AddRefs(argURI), aUTF8Path); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFileURL> argFileURL(do_QueryInterface(argURI, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> argFile; + rv = argFileURL->GetFile(getter_AddRefs(argFile)); + NS_ENSURE_SUCCESS(rv, rv); + + argFile.forget(aFile); + return NS_OK; +} + +NS_MSG_BASE void MsgStripQuotedPrintable(nsCString& aSrc) { + // decode quoted printable text in place + + if (aSrc.IsEmpty()) return; + + char* src = aSrc.BeginWriting(); + char* dest = src; + int srcIdx = 0, destIdx = 0; + + while (src[srcIdx] != 0) { + // Decode sequence of '=XY' into a character with code XY. + if (src[srcIdx] == '=') { + if (MsgIsHex((const char*)src + srcIdx + 1, 2)) { + // If we got here, we successfully decoded a quoted printable sequence, + // so bump each pointer past it and move on to the next char. + dest[destIdx++] = MsgUnhex((const char*)src + srcIdx + 1, 2); + srcIdx += 3; + } else { + // If first char after '=' isn't hex check if it's a normal char + // or a soft line break. If it's a soft line break, eat the + // CR/LF/CRLF. + if (src[srcIdx + 1] == '\r' || src[srcIdx + 1] == '\n') { + srcIdx++; // soft line break, ignore the '='; + if (src[srcIdx] == '\r' || src[srcIdx] == '\n') { + srcIdx++; + if (src[srcIdx] == '\n') srcIdx++; + } + } else // The first or second char after '=' isn't hex, just copy the + // '='. + { + dest[destIdx++] = src[srcIdx++]; + } + continue; + } + } else + dest[destIdx++] = src[srcIdx++]; + } + + dest[destIdx] = src[srcIdx]; // null terminate + aSrc.SetLength(destIdx); +} + +NS_MSG_BASE nsresult MsgEscapeString(const nsACString& aStr, uint32_t aType, + nsACString& aResult) { + nsresult rv; + nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return nu->EscapeString(aStr, aType, aResult); +} + +NS_MSG_BASE nsresult MsgUnescapeString(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult) { + nsresult rv; + nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return nu->UnescapeString(aStr, aFlags, aResult); +} + +NS_MSG_BASE nsresult MsgEscapeURL(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult) { + nsresult rv; + nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return nu->EscapeURL(aStr, aFlags, aResult); +} + +NS_MSG_BASE nsresult +MsgGetHeadersFromKeys(nsIMsgDatabase* aDB, const nsTArray<nsMsgKey>& aMsgKeys, + nsTArray<RefPtr<nsIMsgDBHdr>>& aHeaders) { + NS_ENSURE_ARG_POINTER(aDB); + aHeaders.Clear(); + aHeaders.SetCapacity(aMsgKeys.Length()); + + for (auto key : aMsgKeys) { + // This function silently skips when the key is not found. This is an + // expected case. + bool hasKey; + nsresult rv = aDB->ContainsKey(key, &hasKey); + NS_ENSURE_SUCCESS(rv, rv); + if (hasKey) { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = aDB->GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + aHeaders.AppendElement(msgHdr); + } + } + return NS_OK; +} + +bool MsgAdvanceToNextLine(const char* buffer, uint32_t& bufferOffset, + uint32_t maxBufferOffset) { + bool result = false; + for (; bufferOffset < maxBufferOffset; bufferOffset++) { + if (buffer[bufferOffset] == '\r' || buffer[bufferOffset] == '\n') { + bufferOffset++; + if (buffer[bufferOffset - 1] == '\r' && buffer[bufferOffset] == '\n') + bufferOffset++; + result = true; + break; + } + } + return result; +} + +NS_MSG_BASE nsresult MsgExamineForProxyAsync(nsIChannel* channel, + nsIProtocolProxyCallback* listener, + nsICancelable** result) { + nsresult rv; + +#ifdef DEBUG + nsCOMPtr<nsIURI> uri; + rv = channel->GetURI(getter_AddRefs(uri)); + NS_ASSERTION(NS_SUCCEEDED(rv) && uri, + "The URI needs to be set before calling the proxy service"); +#endif + + nsCOMPtr<nsIProtocolProxyService> pps = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return pps->AsyncResolve(channel, 0, listener, nullptr, result); +} + +NS_MSG_BASE nsresult MsgPromptLoginFailed(nsIMsgWindow* aMsgWindow, + const nsACString& aHostname, + const nsACString& aUsername, + const nsAString& aAccountname, + int32_t* aResult) { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + if (aMsgWindow) { + aMsgWindow->GetDomWindow(getter_AddRefs(domWindow)); + } + + nsresult rv; + nsCOMPtr<nsIPromptService> dlgService( + do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundleService> bundleSvc = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString message; + AutoTArray<nsString, 2> formatStrings; + CopyUTF8toUTF16(aHostname, *formatStrings.AppendElement()); + CopyUTF8toUTF16(aUsername, *formatStrings.AppendElement()); + + rv = bundle->FormatStringFromName("mailServerLoginFailed2", formatStrings, + message); + NS_ENSURE_SUCCESS(rv, rv); + + nsString title; + if (aAccountname.IsEmpty()) { + // Account name may be empty e.g. on a SMTP server. + rv = bundle->GetStringFromName("mailServerLoginFailedTitle", title); + } else { + AutoTArray<nsString, 1> formatStrings = {nsString(aAccountname)}; + rv = bundle->FormatStringFromName("mailServerLoginFailedTitleWithAccount", + formatStrings, title); + } + NS_ENSURE_SUCCESS(rv, rv); + + nsString button0; + rv = bundle->GetStringFromName("mailServerLoginFailedRetryButton", button0); + NS_ENSURE_SUCCESS(rv, rv); + + nsString button2; + rv = bundle->GetStringFromName("mailServerLoginFailedEnterNewPasswordButton", + button2); + NS_ENSURE_SUCCESS(rv, rv); + + bool dummyValue = false; + return dlgService->ConfirmEx( + domWindow, title.get(), message.get(), + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) + + (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1) + + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2), + button0.get(), nullptr, button2.get(), nullptr, &dummyValue, aResult); +} + +NS_MSG_BASE PRTime MsgConvertAgeInDaysToCutoffDate(int32_t ageInDays) { + PRTime now = PR_Now(); + + return now - PR_USEC_PER_DAY * ageInDays; +} + +NS_MSG_BASE nsresult +MsgTermListToString(nsTArray<RefPtr<nsIMsgSearchTerm>> const& aTermList, + nsCString& aOutString) { + nsresult rv = NS_OK; + for (nsIMsgSearchTerm* term : aTermList) { + nsAutoCString stream; + + if (aOutString.Length() > 1) aOutString += ' '; + + bool booleanAnd; + bool matchAll; + term->GetBooleanAnd(&booleanAnd); + term->GetMatchAll(&matchAll); + if (matchAll) { + aOutString += "ALL"; + continue; + } else if (booleanAnd) + aOutString += "AND ("; + else + aOutString += "OR ("; + + rv = term->GetTermAsString(stream); + NS_ENSURE_SUCCESS(rv, rv); + + aOutString += stream; + aOutString += ')'; + } + return rv; +} + +NS_MSG_BASE uint64_t ParseUint64Str(const char* str) { +#ifdef XP_WIN + { + char* endPtr; + return _strtoui64(str, &endPtr, 10); + } +#else + return strtoull(str, nullptr, 10); +#endif +} + +NS_MSG_BASE uint64_t MsgUnhex(const char* aHexString, size_t aNumChars) { + // Large numbers will not fit into uint64_t. + NS_ASSERTION(aNumChars <= 16, "Hex literal too long to convert!"); + + uint64_t result = 0; + for (size_t i = 0; i < aNumChars; i++) { + unsigned char c = aHexString[i]; + uint8_t digit; + if ((c >= '0') && (c <= '9')) + digit = (c - '0'); + else if ((c >= 'a') && (c <= 'f')) + digit = ((c - 'a') + 10); + else if ((c >= 'A') && (c <= 'F')) + digit = ((c - 'A') + 10); + else + break; + + result = (result << 4) | digit; + } + + return result; +} + +NS_MSG_BASE bool MsgIsHex(const char* aHexString, size_t aNumChars) { + for (size_t i = 0; i < aNumChars; i++) { + if (!isxdigit(aHexString[i])) return false; + } + return true; +} + +NS_MSG_BASE nsresult MsgStreamMsgHeaders(nsIInputStream* aInputStream, + nsIStreamListener* aConsumer) { + mozilla::UniquePtr<nsLineBuffer<char>> lineBuffer(new nsLineBuffer<char>); + + nsresult rv; + + nsAutoCString msgHeaders; + nsAutoCString curLine; + + bool more = true; + + // We want to NS_ReadLine until we get to a blank line (the end of the + // headers) + while (more) { + rv = NS_ReadLine(aInputStream, lineBuffer.get(), curLine, &more); + NS_ENSURE_SUCCESS(rv, rv); + if (curLine.IsEmpty()) break; + msgHeaders.Append(curLine); + msgHeaders.AppendLiteral("\r\n"); + } + lineBuffer.reset(); + nsCOMPtr<nsIStringInputStream> hdrsStream = + do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + hdrsStream->SetData(msgHeaders.get(), msgHeaders.Length()); + + nsCOMPtr<nsIInputStreamPump> pump; + rv = NS_NewInputStreamPump(getter_AddRefs(pump), hdrsStream.forget()); + NS_ENSURE_SUCCESS(rv, rv); + + return pump->AsyncRead(aConsumer); +} + +NS_MSG_BASE nsresult MsgDetectCharsetFromFile(nsIFile* aFile, + nsACString& aCharset) { + // We do the detection in this order: + // Check BOM. + // If no BOM, run localized detection (Russian, Ukrainian or Japanese). + // We need to run this first, since ISO-2022-JP is 7bit ASCII and would be + // detected as UTF-8. If ISO-2022-JP not detected, check for UTF-8. If no + // UTF-8, but detector detected something, use that, otherwise return an + // error. + aCharset.Truncate(); + + nsresult rv; + nsCOMPtr<nsIInputStream> inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile); + NS_ENSURE_SUCCESS(rv, rv); + + // Check the BOM. + char sniffBuf[3]; + uint32_t numRead; + rv = inputStream->Read(sniffBuf, sizeof(sniffBuf), &numRead); + + if (numRead >= 2 && sniffBuf[0] == (char)0xfe && sniffBuf[1] == (char)0xff) { + aCharset = "UTF-16BE"; + } else if (numRead >= 2 && sniffBuf[0] == (char)0xff && + sniffBuf[1] == (char)0xfe) { + aCharset = "UTF-16LE"; + } else if (numRead >= 3 && sniffBuf[0] == (char)0xef && + sniffBuf[1] == (char)0xbb && sniffBuf[2] == (char)0xbf) { + aCharset = "UTF-8"; + } + if (!aCharset.IsEmpty()) return NS_OK; + + // Position back to the beginning. + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(inputStream); + if (seekStream) seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); + + // Use detector. + mozilla::UniquePtr<mozilla::EncodingDetector> detector = + mozilla::EncodingDetector::Create(); + char buffer[1024]; + numRead = 0; + while (NS_SUCCEEDED(inputStream->Read(buffer, sizeof(buffer), &numRead))) { + mozilla::Span<const uint8_t> src = + mozilla::AsBytes(mozilla::Span(buffer, numRead)); + Unused << detector->Feed(src, false); + if (numRead == 0) { + break; + } + } + Unused << detector->Feed(nullptr, true); + auto encoding = detector->Guess(nullptr, true); + encoding->Name(aCharset); + return NS_OK; +} + +/* + * Converts a buffer to plain text. Some conversions may + * or may not work with certain end charsets which is why we + * need that as an argument to the function. If charset is + * unknown or deemed of no importance NULL could be passed. + */ +NS_MSG_BASE nsresult ConvertBufToPlainText(nsString& aConBuf, bool formatFlowed, + bool formatOutput, + bool disallowBreaks) { + if (aConBuf.IsEmpty()) return NS_OK; + + int32_t wrapWidth = 72; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + if (pPrefBranch) { + pPrefBranch->GetIntPref("mailnews.wraplength", &wrapWidth); + // Let sanity reign! + if (wrapWidth == 0 || wrapWidth > 990) + wrapWidth = 990; + else if (wrapWidth < 10) + wrapWidth = 10; + } + + uint32_t converterFlags = nsIDocumentEncoder::OutputPersistNBSP; + if (formatFlowed) converterFlags |= nsIDocumentEncoder::OutputFormatFlowed; + if (formatOutput) converterFlags |= nsIDocumentEncoder::OutputFormatted; + if (disallowBreaks) + converterFlags |= nsIDocumentEncoder::OutputDisallowLineBreaking; + + nsCOMPtr<nsIParserUtils> utils = do_GetService(NS_PARSERUTILS_CONTRACTID); + return utils->ConvertToPlainText(aConBuf, converterFlags, wrapWidth, aConBuf); +} + +NS_MSG_BASE nsMsgKey msgKeyFromInt(uint32_t aValue) { return aValue; } + +NS_MSG_BASE nsMsgKey msgKeyFromInt(uint64_t aValue) { + NS_ASSERTION(aValue <= PR_UINT32_MAX, "Msg key value too big!"); + return aValue; +} + +NS_MSG_BASE uint32_t msgKeyToInt(nsMsgKey aMsgKey) { return (uint32_t)aMsgKey; } + +// Helper function to extract a query qualifier. +nsCString MsgExtractQueryPart(const nsACString& spec, + const char* queryToExtract) { + nsCString queryPart; + int32_t queryIndex = PromiseFlatCString(spec).Find(queryToExtract); + if (queryIndex == kNotFound) return queryPart; + + int32_t queryEnd = spec.FindChar('&', queryIndex + 1); + if (queryEnd == kNotFound) queryEnd = spec.FindChar('?', queryIndex + 1); + if (queryEnd == kNotFound) { + // Nothing follows, so return from where the query qualifier started. + queryPart.Assign(Substring(spec, queryIndex)); + } else { + // Return the substring that represents the query qualifier. + queryPart.Assign(Substring(spec, queryIndex, queryEnd - queryIndex)); + } + return queryPart; +} + +// Helper function to remove query part from URL spec or path. +void MsgRemoveQueryPart(nsCString& aSpec) { + // Sadly the query part can have different forms, these were seen + // "in the wild", even with two ?: + // /;section=2?part=1.2&filename=A01.JPG + // ?section=2?part=1.2&filename=A01.JPG&type=image/jpeg&filename=A01.JPG + // ?header=quotebody/;section=2.2?part=1.2.2&filename=lijbmghmkilicioj.png + // ?part=1.2&type=image/jpeg&filename=IMG_C0030.jpg + // ?header=quotebody&part=1.2&filename=lijbmghmkilicioj.png + + // Truncate path at the first of /; or ? + int32_t ind = aSpec.FindChar('?'); + if (ind != kNotFound) aSpec.SetLength(ind); + ind = aSpec.Find("/;"); + if (ind != kNotFound) aSpec.SetLength(ind); +} + +// Perform C-style string escaping. +// e.g. "foo\r\n" => "foo\\r\\n" +// (See also CEscape(), in protobuf, for similar function). +nsCString CEscapeString(nsACString const& s) { + nsCString out; + for (size_t i = 0; i < s.Length(); ++i) { + char c = s[i]; + if (c & 0x80) { + out.AppendPrintf("\\x%02x", (uint8_t)c); + continue; + } + switch (c) { + case '\a': + out += "\\a"; + break; + case '\b': + out += "\\b"; + break; + case '\f': + out += "\\f"; + break; + case '\n': + out += "\\n"; + break; + case '\r': + out += "\\r"; + break; + case '\t': + out += "\\t"; + break; + case '\v': + out += "\\v"; + break; + default: + if (c < ' ') { + out.AppendPrintf("\\x%02x", (uint8_t)c); + } else { + out += c; + } + break; + } + } + return out; +} + +nsresult SyncCopyStream(nsIInputStream* src, nsIOutputStream* dest, + uint64_t& bytesCopied, size_t bufSize) { + mozilla::Buffer<char> buf(bufSize); + nsresult rv; + + bytesCopied = 0; + while (1) { + uint32_t numRead; + rv = src->Read(buf.Elements(), buf.Length(), &numRead); + NS_ENSURE_SUCCESS(rv, rv); + if (numRead == 0) { + break; // EOF. + } + uint32_t pos = 0; + while (pos < numRead) { + uint32_t n; + rv = dest->Write(&buf[pos], numRead - pos, &n); + NS_ENSURE_SUCCESS(rv, rv); + pos += n; + bytesCopied += n; + } + } + return NS_OK; +} + +// Used for "@mozilla.org/network/sync-stream-listener;1". +already_AddRefed<nsIStreamListener> SyncStreamListenerCreate() { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIStreamListener> listener; + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewSyncStreamListener(getter_AddRefs(listener), + getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, nullptr); + return listener.forget(); +} + +// Determine if folder1 and folder2 reside on the same server +nsresult IsOnSameServer(nsIMsgFolder* folder1, nsIMsgFolder* folder2, + bool* sameServer) { + NS_ENSURE_ARG_POINTER(folder1); + NS_ENSURE_ARG_POINTER(folder2); + NS_ENSURE_ARG_POINTER(sameServer); + + nsCOMPtr<nsIMsgIncomingServer> server1; + nsresult rv = folder1->GetServer(getter_AddRefs(server1)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr<nsIMsgIncomingServer> server2; + rv = folder2->GetServer(getter_AddRefs(server2)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + NS_ENSURE_TRUE(server2, NS_ERROR_NULL_POINTER); + return server2->Equals(server1, sameServer); +} |