in mailnews/local/src/nsParseMailbox.cpp [787:1195]
nsresult nsParseMailMessageState::FinalizeHeaders() {
nsresult rv;
HeaderData* sender;
HeaderData* recipient;
HeaderData* subject;
HeaderData* id;
HeaderData* inReplyTo;
HeaderData* replyTo;
HeaderData* references;
HeaderData* date;
HeaderData* deliveryDate;
HeaderData* statush;
HeaderData* mozstatus;
HeaderData* mozstatus2;
HeaderData* priority;
HeaderData* keywords;
HeaderData* account_key;
HeaderData* ccList;
HeaderData* bccList;
HeaderData* mdn_dnt;
HeaderData* content_type;
uint32_t flags = 0;
nsMsgPriorityValue priorityFlags = nsMsgPriority::notSet;
if (!m_mailDB) // if we don't have a valid db, skip the header.
return NS_OK;
// Unlike RFC 5322, we support multiple "Cc:" or "To:" header lines. In this
// case, this function combines these lines into one and stores it in the
// given nsCString, returning a HeaderData object pointing to it.
auto getAggregateHeaderData = [](nsTArray<HeaderData>& list,
nsCString& buffer) -> HeaderData {
size_t size = list.Length();
if (size < 1) {
return {};
}
if (size == 1) {
return list[0];
}
for (size_t i = 0; i < size; i++) {
const auto& header = list[i];
buffer.Append(header.value, header.length);
if (i + 1 < size) {
buffer.Append(",");
}
}
MOZ_ASSERT(strlen(buffer.get()) == buffer.Length(),
"Aggregate header should have the correct length.");
return {buffer.get(), buffer.Length()};
};
nsCString aggregateToHeaders;
HeaderData to = getAggregateHeaderData(m_toList, aggregateToHeaders);
nsCString aggregateCcHeaders;
HeaderData cc = getAggregateHeaderData(m_ccList, aggregateCcHeaders);
// we don't aggregate bcc, as we only generate it locally,
// and we don't use multiple lines
// clang-format off
sender = (m_from.length ? &m_from :
m_sender.length ? &m_sender : 0);
recipient = (to.length ? &to :
cc.length ? &cc :
m_newsgroups.length ? &m_newsgroups : 0);
ccList = (cc.length ? &cc : 0);
bccList = (m_bccList.length ? &m_bccList : 0);
subject = (m_subject.length ? &m_subject : 0);
id = (m_message_id.length ? &m_message_id : 0);
references = (m_references.length ? &m_references : 0);
statush = (m_status.length ? &m_status : 0);
mozstatus = (m_mozstatus.length ? &m_mozstatus : 0);
mozstatus2 = (m_mozstatus2.length ? &m_mozstatus2 : 0);
date = (m_date.length ? &m_date : 0);
deliveryDate = (m_delivery_date.length ? &m_delivery_date : 0);
priority = (m_priority.length ? &m_priority : 0);
keywords = (m_keywords.length ? &m_keywords : 0);
mdn_dnt = (m_mdn_dnt.length ? &m_mdn_dnt : 0);
inReplyTo = (m_in_reply_to.length ? &m_in_reply_to : 0);
replyTo = (m_replyTo.length ? &m_replyTo : 0);
content_type = (m_content_type.length ? &m_content_type : 0);
account_key = (m_account_key.length ? &m_account_key : 0);
// clang-format on
if (mozstatus) {
if (mozstatus->length == 4) {
NS_ASSERTION(MsgIsHex(mozstatus->value, 4),
"Expected 4 hex digits for X-Mozilla-Status.");
flags = MsgUnhex(mozstatus->value, 4);
// strip off and remember priority bits.
flags &= ~nsMsgMessageFlags::RuntimeOnly;
priorityFlags =
(nsMsgPriorityValue)((flags & nsMsgMessageFlags::Priorities) >> 13);
flags &= ~nsMsgMessageFlags::Priorities;
}
}
if (mozstatus2) {
if (mozstatus2->length == 8) {
NS_ASSERTION(MsgIsHex(mozstatus2->value, 8),
"Expected 8 hex digits for X-Mozilla-Status2.");
uint32_t flags2 = MsgUnhex(mozstatus2->value, 8);
flags2 &= ~nsMsgMessageFlags::RuntimeOnly;
flags |= flags2 & 0xFFFF0000;
}
}
if (!(flags & nsMsgMessageFlags::Expunged)) // message was deleted, don't
// bother creating a hdr.
{
// We'll need the message id first to recover data from the backup database
nsAutoCString rawMsgId;
if (id) {
// Take off <> around message ID.
if (MOZ_LIKELY(id->length > 0 && id->value[0] == '<')) {
--id->length;
++id->value;
}
if (MOZ_LIKELY(id->length > 0 && id->value[id->length - 1] == '>')) {
--id->length;
}
rawMsgId.Assign(id->value, id->length);
}
/*
* Try to copy the data from the backup database, referencing the MessageID
* If that fails, just create a new header
*/
nsCOMPtr<nsIMsgDBHdr> oldHeader;
nsresult ret = NS_OK;
if (m_backupMailDB && !rawMsgId.IsEmpty())
ret = m_backupMailDB->GetMsgHdrForMessageID(rawMsgId.get(),
getter_AddRefs(oldHeader));
// m_new_key is set in nsImapMailFolder::ParseAdoptedHeaderLine to be
// the UID of the message, so that the key can get created as UID. That of
// course is extremely confusing, and we really need to clean that up. We
// really should not conflate the meaning of envelope position, key, and
// UID.
if (NS_SUCCEEDED(ret) && oldHeader)
ret = m_mailDB->CopyHdrFromExistingHdr(m_new_key, oldHeader, false,
getter_AddRefs(m_newMsgHdr));
else if (!m_newMsgHdr) {
// Should assert that this is not a local message
ret = m_mailDB->CreateNewHdr(m_new_key, getter_AddRefs(m_newMsgHdr));
}
if (NS_SUCCEEDED(ret) && m_newMsgHdr) {
uint32_t origFlags;
(void)m_newMsgHdr->GetFlags(&origFlags);
if (origFlags & nsMsgMessageFlags::HasRe)
flags |= nsMsgMessageFlags::HasRe;
else
flags &= ~nsMsgMessageFlags::HasRe;
flags &=
~nsMsgMessageFlags::Offline; // don't keep nsMsgMessageFlags::Offline
// for local msgs
if (mdn_dnt && !(origFlags & nsMsgMessageFlags::Read) &&
!(origFlags & nsMsgMessageFlags::MDNReportSent) &&
!(flags & nsMsgMessageFlags::MDNReportSent))
flags |= nsMsgMessageFlags::MDNReportNeeded;
m_newMsgHdr->SetFlags(flags);
if (priorityFlags != nsMsgPriority::notSet)
m_newMsgHdr->SetPriority(priorityFlags);
// if we have a reply to header, and it's different from the from: header,
// set the "replyTo" attribute on the msg hdr.
if (replyTo && (!sender || replyTo->length != sender->length ||
strncmp(replyTo->value, sender->value, sender->length)))
m_newMsgHdr->SetStringProperty("replyTo",
nsDependentCString(replyTo->value));
if (sender) {
m_newMsgHdr->SetAuthor(nsDependentCString(sender->value));
}
if (recipient == &m_newsgroups) {
/* In the case where the recipient is a newsgroup, truncate the string
at the first comma. This is used only for presenting the thread
list, and newsgroup lines tend to be long and non-shared, and tend to
bloat the string table. So, by only showing the first newsgroup, we
can reduce memory and file usage at the expense of only showing the
one group in the summary list, and only being able to sort on the
first group rather than the whole list. It's worth it. */
char* ch;
ch = PL_strchr(recipient->value, ',');
if (ch) {
/* generate a new string that terminates before the , */
nsAutoCString firstGroup;
firstGroup.Assign(recipient->value, ch - recipient->value);
m_newMsgHdr->SetRecipients(firstGroup);
}
m_newMsgHdr->SetRecipients(nsDependentCString(recipient->value));
} else if (recipient) {
m_newMsgHdr->SetRecipients(nsDependentCString(recipient->value));
}
if (ccList) {
m_newMsgHdr->SetCcList(nsDependentCString(ccList->value));
}
if (bccList) {
m_newMsgHdr->SetBccList(nsDependentCString(bccList->value));
}
rv = InternSubject(subject);
if (NS_SUCCEEDED(rv)) {
if (rawMsgId.IsEmpty()) {
// Generate an MD5 hash of all the headers.
const char* md5_b64 = "dummy.message.id";
nsresult rv;
nsCOMPtr<nsICryptoHash> hasher =
do_CreateInstance("@mozilla.org/security/hash;1", &rv);
nsAutoCString hash;
if (NS_SUCCEEDED(rv)) {
if (NS_SUCCEEDED(hasher->Init(nsICryptoHash::MD5)) &&
NS_SUCCEEDED(hasher->Update((const uint8_t*)m_headers.begin(),
m_headers.length())) &&
NS_SUCCEEDED(hasher->Finish(true, hash))) {
md5_b64 = hash.get();
}
}
rawMsgId.Assign("md5:");
rawMsgId.Append(md5_b64);
}
m_newMsgHdr->SetMessageId(rawMsgId);
m_mailDB->UpdatePendingAttributes(m_newMsgHdr);
if (!mozstatus && statush) {
// Parse a little bit of the Berkeley Mail status header.
for (const char* s = statush->value; *s; s++) {
uint32_t msgFlags = 0;
(void)m_newMsgHdr->GetFlags(&msgFlags);
switch (*s) {
case 'R':
case 'O':
case 'r':
m_newMsgHdr->SetFlags(msgFlags | nsMsgMessageFlags::Read);
break;
case 'D':
case 'd':
// msg->flags |= nsMsgMessageFlags::Expunged; // Maybe?
break;
case 'N':
case 'n':
case 'U':
case 'u':
m_newMsgHdr->SetFlags(msgFlags & ~nsMsgMessageFlags::Read);
break;
default:
NS_WARNING(nsPrintfCString("Unexpected status for %s: %s",
rawMsgId.get(), statush->value)
.get());
break;
}
}
}
if (account_key != nullptr)
m_newMsgHdr->SetAccountKey(nsDependentCString(account_key->value));
// use in-reply-to header as references, if there's no references header
if (references != nullptr) {
m_newMsgHdr->SetReferences(nsDependentCString(references->value));
} else if (inReplyTo != nullptr) {
m_newMsgHdr->SetReferences(nsDependentCString(inReplyTo->value));
} else {
m_newMsgHdr->SetReferences(""_ns);
}
// 'Received' should be as reliable an indicator of the receipt
// date+time as possible, whilst always giving something *from
// the message*. It won't use PR_Now() under any circumstance.
// Therefore, the fall-thru order for 'Received' is:
// Received: -> Delivery-date: -> date
// 'Date' uses:
// date -> 'Received' -> EnvDate -> PR_Now()
// (where EnvDate was passed in from outside via SetEnvDetails()).
uint32_t rcvTimeSecs = 0;
PRTime datePRTime = m_EnvDate;
if (date) {
// Date:
if (PR_ParseTimeString(date->value, false, &datePRTime) ==
PR_SUCCESS) {
// Convert to seconds as default value for 'Received'.
PRTime2Seconds(datePRTime, &rcvTimeSecs);
} else {
NS_WARNING(
"PR_ParseTimeString of date failed in FinalizeHeader().");
}
}
if (m_receivedTime) {
// Upgrade 'Received' to Received: ?
PRTime2Seconds(m_receivedTime, &rcvTimeSecs);
if (datePRTime == 0) datePRTime = m_receivedTime;
} else if (deliveryDate) {
// Upgrade 'Received' to Delivery-date: ?
PRTime resultTime;
if (PR_ParseTimeString(deliveryDate->value, false, &resultTime) ==
PR_SUCCESS) {
PRTime2Seconds(resultTime, &rcvTimeSecs);
if (datePRTime == 0) datePRTime = resultTime;
} else {
// TODO/FIXME: We need to figure out what to do in this case!
NS_WARNING(
"PR_ParseTimeString of delivery date failed in "
"FinalizeHeader().");
}
}
m_newMsgHdr->SetUint32Property("dateReceived", rcvTimeSecs);
if (datePRTime == 0) {
// If there was some problem parsing the Date header *AND* we
// couldn't get a valid envelope date *AND* we couldn't get a valid
// Received: header date, use now as the time.
// This doesn't affect local (POP3) messages, because we use the
// envelope date if there's no Date: header, but it will affect IMAP
// msgs w/o a Date: header or Received: headers.
datePRTime = PR_Now();
}
m_newMsgHdr->SetDate(datePRTime);
if (priority) {
nsMsgPriorityValue priorityVal = nsMsgPriority::Default;
// We can ignore |NS_MsgGetPriorityFromString()| return value,
// since we set a default value for |priorityVal|.
NS_MsgGetPriorityFromString(priority->value, priorityVal);
m_newMsgHdr->SetPriority(priorityVal);
} else if (priorityFlags == nsMsgPriority::notSet)
m_newMsgHdr->SetPriority(nsMsgPriority::none);
if (keywords) {
// When there are many keywords, some may not have been written
// to the message file, so add extra keywords from the backup
nsAutoCString oldKeywords;
m_newMsgHdr->GetStringProperty("keywords", oldKeywords);
nsTArray<nsCString> newKeywordArray, oldKeywordArray;
ParseString(
Substring(keywords->value, keywords->value + keywords->length),
' ', newKeywordArray);
ParseString(oldKeywords, ' ', oldKeywordArray);
for (uint32_t i = 0; i < oldKeywordArray.Length(); i++)
if (!newKeywordArray.Contains(oldKeywordArray[i]))
newKeywordArray.AppendElement(oldKeywordArray[i]);
nsAutoCString newKeywords;
for (uint32_t i = 0; i < newKeywordArray.Length(); i++) {
if (i) newKeywords.Append(' ');
newKeywords.Append(newKeywordArray[i]);
}
m_newMsgHdr->SetStringProperty("keywords", newKeywords);
}
MOZ_ASSERT(m_customDBHeaders.Length() == m_customDBHeaderData.Length(),
"m_customDBHeaderData should be in sync.");
for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++) {
if (m_customDBHeaderData[i].length)
m_newMsgHdr->SetStringProperty(
m_customDBHeaders[i].get(),
nsDependentCString(m_customDBHeaderData[i].value));
// The received header is accumulated separately
if (m_customDBHeaders[i].EqualsLiteral("received") &&
!m_receivedValue.IsEmpty())
m_newMsgHdr->SetStringProperty("received", m_receivedValue);
}
if (content_type) {
char* substring = PL_strstr(content_type->value, "charset");
if (substring) {
char* charset = PL_strchr(substring, '=');
if (charset) {
charset++;
/* strip leading whitespace and double-quote */
while (*charset && (IS_SPACE(*charset) || '\"' == *charset))
charset++;
/* strip trailing whitespace and double-quote */
char* end = charset;
while (*end && !IS_SPACE(*end) && '\"' != *end && ';' != *end)
end++;
if (*charset) {
if (*end != '\0') {
// if we're not at the very end of the line, we need
// to generate a new string without the trailing crud
nsAutoCString rawCharSet;
rawCharSet.Assign(charset, end - charset);
m_newMsgHdr->SetCharset(rawCharSet);
} else {
m_newMsgHdr->SetCharset(nsDependentCString(charset));
}
}
}
}
substring = PL_strcasestr(content_type->value, "multipart/mixed");
if (substring) {
uint32_t newFlags;
m_newMsgHdr->OrFlags(nsMsgMessageFlags::Attachment, &newFlags);
}
}
}
} else {
NS_ASSERTION(false, "error creating message header");
rv = NS_ERROR_OUT_OF_MEMORY;
}
} else
rv = NS_OK;
return rv;
}