/* -*- 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 "nsMsgMdnGenerator.h" #include "nsImapCore.h" #include "nsIMsgImapMailFolder.h" #include "nsIMsgAccountManager.h" #include "nsMimeTypes.h" #include "prprf.h" #include "prmem.h" #include "prsystem.h" #include "nsMsgI18N.h" #include "nsMailHeaders.h" #include "nsMsgLocalFolderHdrs.h" #include "nsIHttpProtocolHandler.h" #include "nsISmtpService.h" // for actually sending the message... #include "nsComposeStrings.h" #include "nsISmtpServer.h" #include "nsIPrompt.h" #include "nsIMsgCompUtils.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsIStringBundle.h" #include "nsDirectoryServiceDefs.h" #include "nsMsgUtils.h" #include "nsNetUtil.h" #include "nsIMsgDatabase.h" #include "mozilla/Components.h" #include "mozilla/mailnews/MimeHeaderParser.h" #include "mozilla/Unused.h" #include "nsIPromptService.h" #include "nsEmbedCID.h" using namespace mozilla::mailnews; #define MDN_NOT_IN_TO_CC ((int)0x0001) #define MDN_OUTSIDE_DOMAIN ((int)0x0002) #define HEADER_RETURN_PATH "Return-Path" #define HEADER_DISPOSITION_NOTIFICATION_TO "Disposition-Notification-To" #define HEADER_APPARENTLY_TO "Apparently-To" #define HEADER_ORIGINAL_RECIPIENT "Original-Recipient" #define HEADER_REPORTING_UA "Reporting-UA" #define HEADER_MDN_GATEWAY "MDN-Gateway" #define HEADER_FINAL_RECIPIENT "Final-Recipient" #define HEADER_DISPOSITION "Disposition" #define HEADER_ORIGINAL_MESSAGE_ID "Original-Message-ID" #define HEADER_FAILURE "Failure" #define HEADER_ERROR "Error" #define HEADER_WARNING "Warning" #define HEADER_RETURN_RECEIPT_TO "Return-Receipt-To" #define HEADER_X_ACCEPT_LANGUAGE "X-Accept-Language" #define PUSH_N_FREE_STRING(p) \ do { \ if (p) { \ rv = WriteString(p); \ PR_smprintf_free(p); \ p = 0; \ if (NS_FAILED(rv)) return rv; \ } else { \ return NS_ERROR_OUT_OF_MEMORY; \ } \ } while (0) // String bundle for mdn. Class static. #define MDN_STRINGBUNDLE_URL "chrome://messenger/locale/msgmdn.properties" #if defined(DEBUG_jefft) # define DEBUG_MDN(s) printf("%s\n", s) #else # define DEBUG_MDN(s) #endif // machine parsible string; should not be localized char DispositionTypes[7][16] = { "displayed", "dispatched", "processed", "deleted", "denied", "failed", ""}; NS_IMPL_ISUPPORTS(nsMsgMdnGenerator, nsIMsgMdnGenerator, nsIUrlListener) nsMsgMdnGenerator::nsMsgMdnGenerator() : m_disposeType(eDisplayed), m_key(nsMsgKey_None), m_notInToCcOp(eNeverSendOp), m_outsideDomainOp(eNeverSendOp), m_otherOp(eNeverSendOp), m_reallySendMdn(false), m_autoSend(false), m_autoAction(false), m_mdnEnabled(false) {} nsMsgMdnGenerator::~nsMsgMdnGenerator() {} nsresult nsMsgMdnGenerator::FormatStringFromName(const char* aName, const nsString& aString, nsAString& aResultString) { DEBUG_MDN("nsMsgMdnGenerator::FormatStringFromName"); nsCOMPtr bundleService = mozilla::components::StringBundle::Service(); NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); nsCOMPtr bundle; nsresult rv = bundleService->CreateBundle(MDN_STRINGBUNDLE_URL, getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); AutoTArray formatStrings = {aString}; rv = bundle->FormatStringFromName(aName, formatStrings, aResultString); NS_ENSURE_SUCCESS(rv, rv); return rv; } nsresult nsMsgMdnGenerator::GetStringFromName(const char* aName, nsAString& aResultString) { DEBUG_MDN("nsMsgMdnGenerator::GetStringFromName"); nsCOMPtr bundleService = mozilla::components::StringBundle::Service(); NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); nsCOMPtr bundle; nsresult rv = bundleService->CreateBundle(MDN_STRINGBUNDLE_URL, getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); rv = bundle->GetStringFromName(aName, aResultString); NS_ENSURE_SUCCESS(rv, rv); return rv; } nsresult nsMsgMdnGenerator::StoreMDNSentFlag(nsIMsgFolder* folder, nsMsgKey key) { DEBUG_MDN("nsMsgMdnGenerator::StoreMDNSentFlag"); nsCOMPtr msgDB; nsresult rv = folder->GetMsgDatabase(getter_AddRefs(msgDB)); NS_ENSURE_SUCCESS(rv, rv); rv = msgDB->MarkMDNSent(key, true, nullptr); nsCOMPtr imapFolder = do_QueryInterface(folder); // Store the $MDNSent flag if the folder is an Imap Mail Folder if (imapFolder) return imapFolder->StoreImapFlags(kImapMsgMDNSentFlag, true, {key}, nullptr); return rv; } nsresult nsMsgMdnGenerator::ClearMDNNeededFlag(nsIMsgFolder* folder, nsMsgKey key) { DEBUG_MDN("nsMsgMdnGenerator::ClearMDNNeededFlag"); nsCOMPtr msgDB; nsresult rv = folder->GetMsgDatabase(getter_AddRefs(msgDB)); NS_ENSURE_SUCCESS(rv, rv); return msgDB->MarkMDNNeeded(key, false, nullptr); } bool nsMsgMdnGenerator::ProcessSendMode() { DEBUG_MDN("nsMsgMdnGenerator::ProcessSendMode"); int32_t miscState = 0; if (m_identity) { m_identity->GetEmail(m_email); if (m_email.IsEmpty()) return m_reallySendMdn; const char* accountDomain = strchr(m_email.get(), '@'); if (!accountDomain) return m_reallySendMdn; if (MailAddrMatch(m_email.get(), m_dntRrt.get())) // return address is self, don't send return false; // *** fix me see Bug 132504 for more information // *** what if the message has been filtered to different account if (!PL_strcasestr(m_dntRrt.get(), accountDomain)) miscState |= MDN_OUTSIDE_DOMAIN; if (NotInToOrCc()) miscState |= MDN_NOT_IN_TO_CC; m_reallySendMdn = true; // ********* // How are we gona deal with the auto forwarding issues? Some server // didn't bother to add addition header or modify existing header to // the message when forwarding. They simply copy the exact same // message to another user's mailbox. Some change To: to // Apparently-To: // Unfortunately, there is nothing we can do. It's out of our control. // ********* // starting from lowest denominator to highest if (!miscState) { // under normal situation: recipent is in to and cc list, // and the sender is from the same domain switch (m_otherOp) { default: case eNeverSendOp: m_reallySendMdn = false; break; case eAutoSendOp: m_autoSend = true; break; case eAskMeOp: m_autoSend = false; break; case eDeniedOp: m_autoSend = true; m_disposeType = eDenied; break; } } else if (miscState == (MDN_OUTSIDE_DOMAIN | MDN_NOT_IN_TO_CC)) { if (m_outsideDomainOp != m_notInToCcOp) { m_autoSend = false; // ambiguous; always ask user } else { switch (m_outsideDomainOp) { default: case eNeverSendOp: m_reallySendMdn = false; break; case eAutoSendOp: m_autoSend = true; break; case eAskMeOp: m_autoSend = false; break; } } } else if (miscState & MDN_OUTSIDE_DOMAIN) { switch (m_outsideDomainOp) { default: case eNeverSendOp: m_reallySendMdn = false; break; case eAutoSendOp: m_autoSend = true; break; case eAskMeOp: m_autoSend = false; break; } } else if (miscState & MDN_NOT_IN_TO_CC) { switch (m_notInToCcOp) { default: case eNeverSendOp: m_reallySendMdn = false; break; case eAutoSendOp: m_autoSend = true; break; case eAskMeOp: m_autoSend = false; break; } } } return m_reallySendMdn; } bool nsMsgMdnGenerator::MailAddrMatch(const char* addr1, const char* addr2) { // Comparing two email addresses returns true if matched; local/account // part comparison is case sensitive; domain part comparison is case // insensitive DEBUG_MDN("nsMsgMdnGenerator::MailAddrMatch"); bool isMatched = true; const char *atSign1 = nullptr, *atSign2 = nullptr; const char *lt = nullptr, *local1 = nullptr, *local2 = nullptr; const char *end1 = nullptr, *end2 = nullptr; if (!addr1 || !addr2) return false; lt = strchr(addr1, '<'); local1 = !lt ? addr1 : lt + 1; lt = strchr(addr2, '<'); local2 = !lt ? addr2 : lt + 1; end1 = strchr(local1, '>'); if (!end1) end1 = addr1 + strlen(addr1); end2 = strchr(local2, '>'); if (!end2) end2 = addr2 + strlen(addr2); atSign1 = strchr(local1, '@'); atSign2 = strchr(local2, '@'); if (!atSign1 || !atSign2 // ill formed addr spec || (atSign1 - local1) != (atSign2 - local2)) isMatched = false; else if (strncmp(local1, local2, (atSign1 - local1))) // case sensitive // compare for local part isMatched = false; else if ((end1 - atSign1) != (end2 - atSign2) || PL_strncasecmp(atSign1, atSign2, (end1 - atSign1))) // case // insensitive compare for domain part isMatched = false; return isMatched; } bool nsMsgMdnGenerator::NotInToOrCc() { DEBUG_MDN("nsMsgMdnGenerator::NotInToOrCc"); nsCString reply_to; nsCString to; nsCString cc; m_identity->GetReplyTo(reply_to); m_headers->ExtractHeader(HEADER_TO, true, to); m_headers->ExtractHeader(HEADER_CC, true, cc); // start with a simple check if ((!to.IsEmpty() && PL_strcasestr(to.get(), m_email.get())) || (!cc.IsEmpty() && PL_strcasestr(cc.get(), m_email.get()))) { return false; } if ((!reply_to.IsEmpty() && !to.IsEmpty() && PL_strcasestr(to.get(), reply_to.get())) || (!reply_to.IsEmpty() && !cc.IsEmpty() && PL_strcasestr(cc.get(), reply_to.get()))) { return false; } return true; } bool nsMsgMdnGenerator::ValidateReturnPath() { DEBUG_MDN("nsMsgMdnGenerator::ValidateReturnPath"); // ValidateReturnPath applies to Automatic Send Mode only. If we were not // in auto send mode we simply by passing the check if (!m_autoSend) return m_reallySendMdn; nsCString returnPath; m_headers->ExtractHeader(HEADER_RETURN_PATH, false, returnPath); if (returnPath.IsEmpty()) { m_autoSend = false; return m_reallySendMdn; } m_autoSend = MailAddrMatch(returnPath.get(), m_dntRrt.get()); return m_reallySendMdn; } nsresult nsMsgMdnGenerator::CreateMdnMsg() { DEBUG_MDN("nsMsgMdnGenerator::CreateMdnMsg"); nsresult rv; nsCOMPtr tmpFile; rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "mdnmsg", getter_AddRefs(m_file)); NS_ENSURE_SUCCESS(rv, rv); rv = m_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); NS_ENSURE_SUCCESS(rv, rv); rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream), m_file, PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, 0664); NS_ASSERTION(NS_SUCCEEDED(rv), "creating mdn: failed to output stream"); if (NS_FAILED(rv)) return NS_OK; rv = CreateFirstPart(); if (NS_SUCCEEDED(rv)) { rv = CreateSecondPart(); if (NS_SUCCEEDED(rv)) rv = CreateThirdPart(); } if (m_outputStream) { m_outputStream->Flush(); m_outputStream->Close(); } if (NS_FAILED(rv)) m_file->Remove(false); else rv = SendMdnMsg(); return NS_OK; } nsresult nsMsgMdnGenerator::CreateFirstPart() { DEBUG_MDN("nsMsgMdnGenerator::CreateFirstPart"); char *convbuf = nullptr, *tmpBuffer = nullptr; char* parm = nullptr; nsString firstPart1; nsString firstPart2; nsresult rv = NS_OK; nsCOMPtr compUtils; if (m_mimeSeparator.IsEmpty()) { compUtils = do_GetService("@mozilla.org/messengercompose/computils;1", &rv); NS_ENSURE_SUCCESS(rv, rv); rv = compUtils->MimeMakeSeparator("mdn", getter_Copies(m_mimeSeparator)); NS_ENSURE_SUCCESS(rv, rv); } if (m_mimeSeparator.IsEmpty()) return NS_ERROR_OUT_OF_MEMORY; tmpBuffer = (char*)PR_CALLOC(256); if (!tmpBuffer) return NS_ERROR_OUT_OF_MEMORY; PRExplodedTime now; PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now); int gmtoffset = (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60; /* Use PR_FormatTimeUSEnglish() to format the date in US English format, then figure out what our local GMT offset is, and append it (since PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as per RFC 1123 (superseding RFC 822.) */ PR_FormatTimeUSEnglish(tmpBuffer, 100, "Date: %a, %d %b %Y %H:%M:%S ", &now); PR_snprintf(tmpBuffer + strlen(tmpBuffer), 100, "%c%02d%02d" CRLF, (gmtoffset >= 0 ? '+' : '-'), ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60), ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60)); rv = WriteString(tmpBuffer); PR_Free(tmpBuffer); if (NS_FAILED(rv)) return rv; bool conformToStandard = false; if (compUtils) compUtils->GetMsgMimeConformToStandard(&conformToStandard); nsString fullName; m_identity->GetFullName(fullName); nsCString fullAddress; // convert fullName to UTF8 before passing it to MakeMimeAddress MakeMimeAddress(NS_ConvertUTF16toUTF8(fullName), m_email, fullAddress); convbuf = nsMsgI18NEncodeMimePartIIStr(fullAddress.get(), true, "UTF-8", 0, conformToStandard); parm = PR_smprintf("From: %s" CRLF, convbuf ? convbuf : m_email.get()); rv = FormatStringFromName("MsgMdnMsgSentTo", NS_ConvertASCIItoUTF16(m_email), firstPart1); if (NS_FAILED(rv)) return rv; PUSH_N_FREE_STRING(parm); PR_Free(convbuf); if (compUtils) { nsCString msgId; rv = compUtils->MsgGenerateMessageId(m_identity, ""_ns, msgId); NS_ENSURE_SUCCESS(rv, rv); tmpBuffer = PR_smprintf("Message-ID: %s" CRLF, msgId.get()); PUSH_N_FREE_STRING(tmpBuffer); } nsString receipt_string; switch (m_disposeType) { case nsIMsgMdnGenerator::eDisplayed: rv = GetStringFromName("MdnDisplayedReceipt", receipt_string); break; case nsIMsgMdnGenerator::eDispatched: rv = GetStringFromName("MdnDispatchedReceipt", receipt_string); break; case nsIMsgMdnGenerator::eProcessed: rv = GetStringFromName("MdnProcessedReceipt", receipt_string); break; case nsIMsgMdnGenerator::eDeleted: rv = GetStringFromName("MdnDeletedReceipt", receipt_string); break; case nsIMsgMdnGenerator::eDenied: rv = GetStringFromName("MdnDeniedReceipt", receipt_string); break; case nsIMsgMdnGenerator::eFailed: rv = GetStringFromName("MdnFailedReceipt", receipt_string); break; default: rv = NS_ERROR_INVALID_ARG; break; } if (NS_FAILED(rv)) return rv; receipt_string.AppendLiteral(" - "); char* encodedReceiptString = nsMsgI18NEncodeMimePartIIStr(NS_ConvertUTF16toUTF8(receipt_string).get(), false, "UTF-8", 0, conformToStandard); nsCString subject; m_headers->ExtractHeader(HEADER_SUBJECT, false, subject); convbuf = nsMsgI18NEncodeMimePartIIStr( subject.Length() ? subject.get() : "[no subject]", false, "UTF-8", 0, conformToStandard); tmpBuffer = PR_smprintf( "Subject: %s%s" CRLF, encodedReceiptString, (convbuf ? convbuf : (subject.Length() ? subject.get() : "[no subject]"))); PUSH_N_FREE_STRING(tmpBuffer); PR_Free(convbuf); PR_Free(encodedReceiptString); convbuf = nsMsgI18NEncodeMimePartIIStr(m_dntRrt.get(), true, "UTF-8", 0, conformToStandard); tmpBuffer = PR_smprintf("To: %s" CRLF, convbuf ? convbuf : m_dntRrt.get()); PUSH_N_FREE_STRING(tmpBuffer); PR_Free(convbuf); // *** This is not in the spec. I am adding this so we could do // threading m_headers->ExtractHeader(HEADER_MESSAGE_ID, false, m_messageId); if (!m_messageId.IsEmpty()) { if (*m_messageId.get() == '<') tmpBuffer = PR_smprintf("References: %s" CRLF, m_messageId.get()); else tmpBuffer = PR_smprintf("References: <%s>" CRLF, m_messageId.get()); PUSH_N_FREE_STRING(tmpBuffer); } tmpBuffer = PR_smprintf("%s" CRLF, "MIME-Version: 1.0"); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf( "Content-Type: multipart/report; \ report-type=disposition-notification;\r\n\tboundary=\"%s\"" CRLF CRLF, m_mimeSeparator.get()); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf("--%s" CRLF, m_mimeSeparator.get()); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf("Content-Type: text/plain; charset=UTF-8" CRLF); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf("Content-Transfer-Encoding: %s" CRLF CRLF, ENCODING_8BIT); PUSH_N_FREE_STRING(tmpBuffer); if (!firstPart1.IsEmpty()) { tmpBuffer = PR_smprintf("%s" CRLF CRLF, NS_ConvertUTF16toUTF8(firstPart1).get()); PUSH_N_FREE_STRING(tmpBuffer); } switch (m_disposeType) { case nsIMsgMdnGenerator::eDisplayed: rv = GetStringFromName("MsgMdnDisplayed", firstPart2); break; case nsIMsgMdnGenerator::eDispatched: rv = GetStringFromName("MsgMdnDispatched", firstPart2); break; case nsIMsgMdnGenerator::eProcessed: rv = GetStringFromName("MsgMdnProcessed", firstPart2); break; case nsIMsgMdnGenerator::eDeleted: rv = GetStringFromName("MsgMdnDeleted", firstPart2); break; case nsIMsgMdnGenerator::eDenied: rv = GetStringFromName("MsgMdnDenied", firstPart2); break; case nsIMsgMdnGenerator::eFailed: rv = GetStringFromName("MsgMdnFailed", firstPart2); break; default: rv = NS_ERROR_INVALID_ARG; break; } if (NS_FAILED(rv)) return rv; if (!firstPart2.IsEmpty()) { tmpBuffer = PR_smprintf("%s" CRLF CRLF, NS_ConvertUTF16toUTF8(firstPart2).get()); PUSH_N_FREE_STRING(tmpBuffer); } return rv; } nsresult nsMsgMdnGenerator::CreateSecondPart() { DEBUG_MDN("nsMsgMdnGenerator::CreateSecondPart"); char* tmpBuffer = nullptr; char* convbuf = nullptr; nsresult rv = NS_OK; nsCOMPtr compUtils; bool conformToStandard = false; tmpBuffer = PR_smprintf("--%s" CRLF, m_mimeSeparator.get()); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf("%s" CRLF, "Content-Type: message/disposition-notification; " "name=\042MDNPart2.txt\042"); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf("%s" CRLF, "Content-Disposition: inline"); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf("Content-Transfer-Encoding: %s" CRLF CRLF, ENCODING_7BIT); PUSH_N_FREE_STRING(tmpBuffer); nsCOMPtr pHTTPHandler = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv); if (NS_SUCCEEDED(rv) && pHTTPHandler) { bool sendUserAgent = false; nsCOMPtr prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv) && prefBranch) { prefBranch->GetBoolPref("mailnews.headers.sendUserAgent", &sendUserAgent); } if (sendUserAgent) { bool useMinimalUserAgent = false; if (prefBranch) { prefBranch->GetBoolPref("mailnews.headers.useMinimalUserAgent", &useMinimalUserAgent); } if (useMinimalUserAgent) { nsCOMPtr bundleService = mozilla::components::StringBundle::Service(); if (bundleService) { nsCOMPtr brandBundle; rv = bundleService->CreateBundle( "chrome://branding/locale/brand.properties", getter_AddRefs(brandBundle)); if (NS_SUCCEEDED(rv)) { nsString brandName; brandBundle->GetStringFromName("brandFullName", brandName); if (!brandName.IsEmpty()) { NS_ConvertUTF16toUTF8 ua8(brandName); tmpBuffer = PR_smprintf("Reporting-UA: %s" CRLF, ua8.get()); PUSH_N_FREE_STRING(tmpBuffer); } } } } else { nsAutoCString userAgentString; // Ignore error since we're testing the return value. mozilla::Unused << pHTTPHandler->GetUserAgent(userAgentString); if (!userAgentString.IsEmpty()) { // Prepend the product name with the dns name according to RFC 3798. char hostName[256]; PR_GetSystemInfo(PR_SI_HOSTNAME_UNTRUNCATED, hostName, sizeof hostName); if ((hostName[0] != '\0') && (strchr(hostName, '.') != NULL)) { userAgentString.InsertLiteral("; ", 0); userAgentString.Insert(nsDependentCString(hostName), 0); } tmpBuffer = PR_smprintf("Reporting-UA: %s" CRLF, userAgentString.get()); PUSH_N_FREE_STRING(tmpBuffer); } } } } nsCString originalRecipient; m_headers->ExtractHeader(HEADER_ORIGINAL_RECIPIENT, false, originalRecipient); if (!originalRecipient.IsEmpty()) { tmpBuffer = PR_smprintf("Original-Recipient: %s" CRLF, originalRecipient.get()); PUSH_N_FREE_STRING(tmpBuffer); } compUtils = do_GetService("@mozilla.org/messengercompose/computils;1", &rv); if (compUtils) compUtils->GetMsgMimeConformToStandard(&conformToStandard); convbuf = nsMsgI18NEncodeMimePartIIStr(m_email.get(), true, "UTF-8", 0, conformToStandard); tmpBuffer = PR_smprintf("Final-Recipient: rfc822;%s" CRLF, convbuf ? convbuf : m_email.get()); PUSH_N_FREE_STRING(tmpBuffer); PR_Free(convbuf); if (*m_messageId.get() == '<') tmpBuffer = PR_smprintf("Original-Message-ID: %s" CRLF, m_messageId.get()); else tmpBuffer = PR_smprintf("Original-Message-ID: <%s>" CRLF, m_messageId.get()); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf("Disposition: %s/%s; %s" CRLF CRLF, (m_autoAction ? "automatic-action" : "manual-action"), (m_autoSend ? "MDN-sent-automatically" : "MDN-sent-manually"), DispositionTypes[(int)m_disposeType]); PUSH_N_FREE_STRING(tmpBuffer); return rv; } nsresult nsMsgMdnGenerator::CreateThirdPart() { DEBUG_MDN("nsMsgMdnGenerator::CreateThirdPart"); char* tmpBuffer = nullptr; nsresult rv = NS_OK; tmpBuffer = PR_smprintf("--%s" CRLF, m_mimeSeparator.get()); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf( "%s" CRLF, "Content-Type: text/rfc822-headers; name=\042MDNPart3.txt\042"); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf("%s" CRLF, "Content-Transfer-Encoding: 7bit"); PUSH_N_FREE_STRING(tmpBuffer); tmpBuffer = PR_smprintf("%s" CRLF CRLF, "Content-Disposition: inline"); PUSH_N_FREE_STRING(tmpBuffer); rv = OutputAllHeaders(); if (NS_FAILED(rv)) return rv; rv = WriteString(CRLF); if (NS_FAILED(rv)) return rv; tmpBuffer = PR_smprintf("--%s--" CRLF, m_mimeSeparator.get()); PUSH_N_FREE_STRING(tmpBuffer); return rv; } nsresult nsMsgMdnGenerator::OutputAllHeaders() { DEBUG_MDN("nsMsgMdnGenerator::OutputAllHeaders"); nsCString all_headers; int32_t all_headers_size = 0; nsresult rv = NS_OK; rv = m_headers->GetAllHeaders(all_headers); if (NS_FAILED(rv)) return rv; all_headers_size = all_headers.Length(); char *buf = (char*)all_headers.get(), *buf_end = (char*)all_headers.get() + all_headers_size; char *start = buf, *end = buf; while (buf < buf_end) { switch (*buf) { case 0: if (*(buf + 1) == '\n') { // *buf = '\r'; end = buf; } else if (*(buf + 1) == 0) { // the case of message id *buf = '>'; } break; case '\r': end = buf; *buf = 0; break; case '\n': if (buf > start && *(buf - 1) == 0) { start = buf + 1; end = start; } else { end = buf; } *buf = 0; break; default: break; } buf++; if (end > start && *end == 0) { // strip out private X-Mozilla-Status header & X-Mozilla-Draft-Info && // envelope header if (!PL_strncasecmp(start, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN) || !PL_strncasecmp(start, X_MOZILLA_DRAFT_INFO, X_MOZILLA_DRAFT_INFO_LEN) || !PL_strncasecmp(start, "From ", 5)) { while (end < buf_end && (*end == '\n' || *end == '\r' || *end == 0)) end++; start = end; } else { NS_ASSERTION(*end == 0, "content of end should be null"); rv = WriteString(start); NS_ENSURE_SUCCESS(rv, rv); rv = WriteString(CRLF); NS_ENSURE_SUCCESS(rv, rv); while (end < buf_end && (*end == '\n' || *end == '\r' || *end == 0)) end++; start = end; } buf = start; } } return NS_OK; } nsresult nsMsgMdnGenerator::SendMdnMsg() { DEBUG_MDN("nsMsgMdnGenerator::SendMdnMsg"); nsresult rv; nsCOMPtr smtpService = do_GetService("@mozilla.org/messengercompose/smtp;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr aUri; nsCOMPtr aRequest; nsCString identEmail; m_identity->GetEmail(identEmail); smtpService->SendMailMessage(m_file, m_dntRrt.get(), m_identity, identEmail.get(), EmptyString(), this, nullptr, nullptr, false, ""_ns, getter_AddRefs(aUri), getter_AddRefs(aRequest)); return NS_OK; } nsresult nsMsgMdnGenerator::WriteString(const char* str) { NS_ENSURE_ARG(str); uint32_t len = strlen(str); uint32_t wLen = 0; return m_outputStream->Write(str, len, &wLen); } nsresult nsMsgMdnGenerator::InitAndProcess(bool* needToAskUser) { DEBUG_MDN("nsMsgMdnGenerator::InitAndProcess"); nsresult rv = m_folder->GetServer(getter_AddRefs(m_server)); nsCOMPtr accountManager = do_GetService("@mozilla.org/messenger/account-manager;1", &rv); if (accountManager && m_server) { if (!m_identity) { // check if this is a message delivered to the global inbox, // in which case we find the originating account's identity. nsCString accountKey; m_headers->ExtractHeader(HEADER_X_MOZILLA_ACCOUNT_KEY, false, accountKey); nsCOMPtr account; if (!accountKey.IsEmpty()) accountManager->GetAccount(accountKey, getter_AddRefs(account)); if (account) account->GetIncomingServer(getter_AddRefs(m_server)); if (m_server) { // Find the correct identity based on the "To:" and "Cc:" header nsCString mailTo; nsCString mailCC; m_headers->ExtractHeader(HEADER_TO, true, mailTo); m_headers->ExtractHeader(HEADER_CC, true, mailCC); nsTArray> servIdentities; accountManager->GetIdentitiesForServer(m_server, servIdentities); // First check in the "To:" header for (auto ident : servIdentities) { nsCString identEmail; ident->GetEmail(identEmail); if (!mailTo.IsEmpty() && !identEmail.IsEmpty() && FindInReadable(identEmail, mailTo, nsCaseInsensitiveCStringComparator)) { m_identity = ident; break; } } // If no match, check the "Cc:" header if (!m_identity) { for (auto ident : servIdentities) { nsCString identEmail; ident->GetEmail(identEmail); if (!mailCC.IsEmpty() && !identEmail.IsEmpty() && FindInReadable(identEmail, mailCC, nsCaseInsensitiveCStringComparator)) { m_identity = ident; break; } } } // If still no match, use the first identity if (!m_identity) { rv = accountManager->GetFirstIdentityForServer( m_server, getter_AddRefs(m_identity)); } } } NS_ENSURE_SUCCESS(rv, rv); if (m_identity) { bool useCustomPrefs = false; m_identity->GetBoolAttribute("use_custom_prefs", &useCustomPrefs); if (useCustomPrefs) { bool bVal = false; m_server->GetBoolValue("mdn_report_enabled", &bVal); m_mdnEnabled = bVal; m_server->GetIntValue("mdn_not_in_to_cc", &m_notInToCcOp); m_server->GetIntValue("mdn_outside_domain", &m_outsideDomainOp); m_server->GetIntValue("mdn_other", &m_otherOp); } else { bool bVal = false; nsCOMPtr prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); if (NS_FAILED(rv)) return rv; if (prefBranch) { prefBranch->GetBoolPref("mail.mdn.report.enabled", &bVal); m_mdnEnabled = bVal; prefBranch->GetIntPref("mail.mdn.report.not_in_to_cc", &m_notInToCcOp); prefBranch->GetIntPref("mail.mdn.report.outside_domain", &m_outsideDomainOp); prefBranch->GetIntPref("mail.mdn.report.other", &m_otherOp); } } } } if (m_mdnEnabled) { m_headers->ExtractHeader(HEADER_DISPOSITION_NOTIFICATION_TO, false, m_dntRrt); if (m_dntRrt.IsEmpty()) m_headers->ExtractHeader(HEADER_RETURN_RECEIPT_TO, false, m_dntRrt); if (!m_dntRrt.IsEmpty() && ProcessSendMode() && ValidateReturnPath()) { if (!m_autoSend) { *needToAskUser = true; rv = NS_OK; } else { *needToAskUser = false; rv = UserAgreed(); } } } return rv; } NS_IMETHODIMP nsMsgMdnGenerator::Process(EDisposeType type, nsIMsgWindow* aWindow, nsIMsgFolder* folder, nsMsgKey key, nsIMimeHeaders* headers, bool autoAction, bool* _retval) { DEBUG_MDN("nsMsgMdnGenerator::Process"); NS_ENSURE_ARG_POINTER(folder); NS_ENSURE_ARG_POINTER(headers); NS_ENSURE_ARG_POINTER(aWindow); NS_ENSURE_TRUE(key != nsMsgKey_None, NS_ERROR_INVALID_ARG); m_disposeType = type; m_autoAction = autoAction; m_window = aWindow; m_folder = folder; m_headers = headers; m_key = key; mozilla::DebugOnly rv = InitAndProcess(_retval); NS_ASSERTION(NS_SUCCEEDED(rv), "InitAndProcess failed"); return NS_OK; } NS_IMETHODIMP nsMsgMdnGenerator::UserAgreed() { DEBUG_MDN("nsMsgMdnGenerator::UserAgreed"); (void)NoteMDNRequestHandled(); return CreateMdnMsg(); } NS_IMETHODIMP nsMsgMdnGenerator::UserDeclined() { DEBUG_MDN("nsMsgMdnGenerator::UserDeclined"); return NoteMDNRequestHandled(); } /** * Set/clear flags appropriately so we won't ask user again about MDN * request for this message. */ nsresult nsMsgMdnGenerator::NoteMDNRequestHandled() { nsresult rv = StoreMDNSentFlag(m_folder, m_key); NS_ASSERTION(NS_SUCCEEDED(rv), "StoreMDNSentFlag failed"); rv = ClearMDNNeededFlag(m_folder, m_key); NS_ASSERTION(NS_SUCCEEDED(rv), "ClearMDNNeededFlag failed"); return rv; } NS_IMETHODIMP nsMsgMdnGenerator::OnStartRunningUrl(nsIURI* url) { DEBUG_MDN("nsMsgMdnGenerator::OnStartRunningUrl"); return NS_OK; } NS_IMETHODIMP nsMsgMdnGenerator::OnStopRunningUrl(nsIURI* url, nsresult aExitCode) { nsresult rv; DEBUG_MDN("nsMsgMdnGenerator::OnStopRunningUrl"); if (m_file) m_file->Remove(false); if (NS_SUCCEEDED(aExitCode)) return NS_OK; const char* exitString; switch (aExitCode) { case NS_ERROR_UNKNOWN_HOST: case NS_ERROR_UNKNOWN_PROXY_HOST: exitString = "smtpSendFailedUnknownServer"; break; case NS_ERROR_CONNECTION_REFUSED: case NS_ERROR_PROXY_CONNECTION_REFUSED: exitString = "smtpSendRequestRefused"; break; case NS_ERROR_NET_INTERRUPT: case NS_ERROR_ABORT: // we have no proper string for error code // NS_ERROR_ABORT in compose bundle exitString = "smtpSendInterrupted"; break; case NS_ERROR_NET_TIMEOUT: case NS_ERROR_NET_RESET: exitString = "smtpSendTimeout"; break; default: exitString = errorStringNameForErrorCode(aExitCode); break; } nsCOMPtr smtpService( do_GetService("@mozilla.org/messengercompose/smtp;1", &rv)); NS_ENSURE_SUCCESS(rv, rv); // Get the smtp hostname and format the string. nsCString smtpHostName; nsCOMPtr smtpServer; rv = smtpService->GetServerByIdentity(m_identity, getter_AddRefs(smtpServer)); if (NS_SUCCEEDED(rv)) smtpServer->GetHostname(smtpHostName); AutoTArray params; CopyASCIItoUTF16(smtpHostName, *params.AppendElement()); nsCOMPtr bundle; nsCOMPtr bundleService = mozilla::components::StringBundle::Service(); NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); rv = bundleService->CreateBundle( "chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); nsString failed_msg, dialogTitle; bundle->FormatStringFromName(exitString, params, failed_msg); bundle->GetStringFromName("sendMessageErrorTitle", dialogTitle); nsCOMPtr domWindow; m_window->GetDomWindow(getter_AddRefs(domWindow)); nsCOMPtr dlgService( do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); dlgService->Alert(domWindow, dialogTitle.get(), failed_msg.get()); return NS_OK; }