/* -*- 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 "mozilla/mailnews/MimeHeaderParser.h" #include "nsMsgHdr.h" #include "nsMsgDatabase.h" #include "nsMsgUtils.h" #include "nsMsgMessageFlags.h" #include "nsIMsgThread.h" #include "mozilla/Attributes.h" #include "nsStringEnumerator.h" #ifdef DEBUG # include "nsPrintfCString.h" #endif using namespace mozilla::mailnews; NS_IMPL_ISUPPORTS(nsMsgHdr, nsIMsgDBHdr) #define FLAGS_INITED 0x1 #define CACHED_VALUES_INITED 0x2 #define REFERENCES_INITED 0x4 #define THREAD_PARENT_INITED 0x8 nsMsgHdr::nsMsgHdr(nsMsgDatabase* db, nsIMdbRow* dbRow) { m_mdb = db; Init(); m_mdbRow = dbRow; if (m_mdb) { NS_ADDREF(m_mdb); // Released in DTOR. mdbOid outOid; if (dbRow && NS_SUCCEEDED(dbRow->GetOid(m_mdb->GetEnv(), &outOid))) { m_messageKey = outOid.mOid_Id; m_mdb->AddHdrToUseCache((nsIMsgDBHdr*)this, m_messageKey); } } } void nsMsgHdr::Init() { m_initedValues = 0; m_messageKey = nsMsgKey_None; m_messageSize = 0; m_date = 0; m_flags = 0; m_mdbRow = NULL; m_threadId = nsMsgKey_None; m_threadParent = nsMsgKey_None; } nsresult nsMsgHdr::InitCachedValues() { nsresult err = NS_OK; if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER; if (!(m_initedValues & CACHED_VALUES_INITED)) { uint32_t uint32Value; mdbOid outOid; if (NS_SUCCEEDED(m_mdbRow->GetOid(m_mdb->GetEnv(), &outOid))) m_messageKey = outOid.mOid_Id; err = GetUInt32Column(m_mdb->m_messageSizeColumnToken, &m_messageSize); err = GetUInt32Column(m_mdb->m_dateColumnToken, &uint32Value); Seconds2PRTime(uint32Value, &m_date); err = GetUInt32Column(m_mdb->m_messageThreadIdColumnToken, &m_threadId); if (NS_SUCCEEDED(err)) m_initedValues |= CACHED_VALUES_INITED; } return err; } nsresult nsMsgHdr::InitFlags() { nsresult err = NS_OK; if (!m_mdb) return NS_ERROR_NULL_POINTER; if (!(m_initedValues & FLAGS_INITED)) { err = GetUInt32Column(m_mdb->m_flagsColumnToken, &m_flags); m_flags &= ~nsMsgMessageFlags::New; // don't get new flag from MDB if (NS_SUCCEEDED(err)) m_initedValues |= FLAGS_INITED; } return err; } nsMsgHdr::~nsMsgHdr() { if (m_mdbRow) { if (m_mdb) { NS_RELEASE(m_mdbRow); m_mdb->RemoveHdrFromUseCache((nsIMsgDBHdr*)this, m_messageKey); } } NS_IF_RELEASE(m_mdb); } NS_IMETHODIMP nsMsgHdr::GetMessageKey(nsMsgKey* result) { if (m_messageKey == nsMsgKey_None && m_mdbRow != NULL) { mdbOid outOid; if (NS_SUCCEEDED(m_mdbRow->GetOid(m_mdb->GetEnv(), &outOid))) m_messageKey = outOid.mOid_Id; } *result = m_messageKey; return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetThreadId(nsMsgKey* result) { if (!(m_initedValues & CACHED_VALUES_INITED)) InitCachedValues(); if (result) { *result = m_threadId; return NS_OK; } return NS_ERROR_NULL_POINTER; } NS_IMETHODIMP nsMsgHdr::SetThreadId(nsMsgKey inKey) { m_threadId = inKey; return SetUInt32Column(m_threadId, m_mdb->m_messageThreadIdColumnToken); } NS_IMETHODIMP nsMsgHdr::SetMessageKey(nsMsgKey value) { m_messageKey = value; return NS_OK; } nsresult nsMsgHdr::GetRawFlags(uint32_t* result) { if (!(m_initedValues & FLAGS_INITED)) InitFlags(); *result = m_flags; return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetFlags(uint32_t* result) { if (!(m_initedValues & FLAGS_INITED)) InitFlags(); if (m_mdb) *result = m_mdb->GetStatusFlags(this, m_flags); else *result = m_flags; #ifdef DEBUG_bienvenu NS_ASSERTION(!(*result & (nsMsgMessageFlags::Elided)), "shouldn't be set in db"); #endif return NS_OK; } NS_IMETHODIMP nsMsgHdr::SetFlags(uint32_t flags) { #ifdef DEBUG_bienvenu NS_ASSERTION(!(flags & (nsMsgMessageFlags::Elided)), "shouldn't set this flag on db"); #endif m_initedValues |= FLAGS_INITED; m_flags = flags; // don't write out nsMsgMessageFlags::New to MDB. return SetUInt32Column(m_flags & ~nsMsgMessageFlags::New, m_mdb->m_flagsColumnToken); } NS_IMETHODIMP nsMsgHdr::OrFlags(uint32_t flags, uint32_t* result) { if (!(m_initedValues & FLAGS_INITED)) InitFlags(); if ((m_flags & flags) != flags) SetFlags(m_flags | flags); *result = m_flags; return NS_OK; } NS_IMETHODIMP nsMsgHdr::AndFlags(uint32_t flags, uint32_t* result) { if (!(m_initedValues & FLAGS_INITED)) InitFlags(); if ((m_flags & flags) != m_flags) SetFlags(m_flags & flags); *result = m_flags; return NS_OK; } NS_IMETHODIMP nsMsgHdr::MarkHasAttachments(bool bHasAttachments) { nsresult rv = NS_OK; if (m_mdb) { nsMsgKey key; rv = GetMessageKey(&key); if (NS_SUCCEEDED(rv)) rv = m_mdb->MarkHasAttachments(key, bHasAttachments, nullptr); } return rv; } NS_IMETHODIMP nsMsgHdr::MarkRead(bool bRead) { nsresult rv = NS_OK; if (m_mdb) { nsMsgKey key; rv = GetMessageKey(&key); if (NS_SUCCEEDED(rv)) rv = m_mdb->MarkRead(key, bRead, nullptr); } return rv; } NS_IMETHODIMP nsMsgHdr::MarkFlagged(bool bFlagged) { nsresult rv = NS_OK; if (m_mdb) { nsMsgKey key; rv = GetMessageKey(&key); if (NS_SUCCEEDED(rv)) rv = m_mdb->MarkMarked(key, bFlagged, nullptr); } return rv; } NS_IMETHODIMP nsMsgHdr::SetStringProperty(const char* propertyName, const nsACString& propertyValue) { NS_ENSURE_ARG_POINTER(propertyName); if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER; return m_mdb->SetProperty(m_mdbRow, propertyName, PromiseFlatCString(propertyValue).get()); } NS_IMETHODIMP nsMsgHdr::GetStringProperty(const char* propertyName, nsACString& aPropertyValue) { NS_ENSURE_ARG_POINTER(propertyName); if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER; return m_mdb->GetProperty(m_mdbRow, propertyName, getter_Copies(aPropertyValue)); } NS_IMETHODIMP nsMsgHdr::GetUint32Property(const char* propertyName, uint32_t* pResult) { NS_ENSURE_ARG_POINTER(propertyName); if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER; return m_mdb->GetUint32Property(m_mdbRow, propertyName, pResult); } NS_IMETHODIMP nsMsgHdr::SetUint32Property(const char* propertyName, uint32_t value) { NS_ENSURE_ARG_POINTER(propertyName); if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER; return m_mdb->SetUint32Property(m_mdbRow, propertyName, value); } NS_IMETHODIMP nsMsgHdr::GetNumReferences(uint16_t* result) { if (!(m_initedValues & REFERENCES_INITED)) { const char* references; if (NS_SUCCEEDED(m_mdb->RowCellColumnToConstCharPtr( GetMDBRow(), m_mdb->m_referencesColumnToken, &references))) ParseReferences(references); m_initedValues |= REFERENCES_INITED; } if (result) *result = m_references.Length(); // there is no real failure here; if there are no references, there are no // references. return NS_OK; } nsresult nsMsgHdr::ParseReferences(const char* references) { const char* startNextRef = references; nsAutoCString resultReference; nsCString messageId; GetMessageId(getter_Copies(messageId)); while (startNextRef && *startNextRef) { startNextRef = GetNextReference(startNextRef, resultReference, startNextRef == references); // Don't add self-references. if (!resultReference.IsEmpty() && !resultReference.Equals(messageId)) m_references.AppendElement(resultReference); } return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetStringReference(int32_t refNum, nsACString& resultReference) { nsresult err = NS_OK; if (!(m_initedValues & REFERENCES_INITED)) GetNumReferences(nullptr); // it can handle the null if ((uint32_t)refNum < m_references.Length()) resultReference = m_references.ElementAt(refNum); else err = NS_ERROR_ILLEGAL_VALUE; return err; } NS_IMETHODIMP nsMsgHdr::GetDate(PRTime* result) { if (!(m_initedValues & CACHED_VALUES_INITED)) InitCachedValues(); *result = m_date; return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetDateInSeconds(uint32_t* aResult) { return GetUInt32Column(m_mdb->m_dateColumnToken, aResult); } NS_IMETHODIMP nsMsgHdr::SetMessageId(const char* messageId) { if (messageId && *messageId == '<') { nsAutoCString tempMessageID(messageId + 1); if (tempMessageID.CharAt(tempMessageID.Length() - 1) == '>') tempMessageID.SetLength(tempMessageID.Length() - 1); return SetStringColumn(tempMessageID.get(), m_mdb->m_messageIdColumnToken); } return SetStringColumn(messageId, m_mdb->m_messageIdColumnToken); } NS_IMETHODIMP nsMsgHdr::SetSubject(const nsACString& subject) { return SetStringColumn(PromiseFlatCString(subject).get(), m_mdb->m_subjectColumnToken); } NS_IMETHODIMP nsMsgHdr::SetAuthor(const char* author) { return SetStringColumn(author, m_mdb->m_senderColumnToken); } NS_IMETHODIMP nsMsgHdr::SetReferences(const nsACString& references) { m_references.Clear(); ParseReferences(PromiseFlatCString(references).get()); m_initedValues |= REFERENCES_INITED; return SetStringColumn(PromiseFlatCString(references).get(), m_mdb->m_referencesColumnToken); } NS_IMETHODIMP nsMsgHdr::SetRecipients(const char* recipients) { // need to put in rfc822 address parsing code here (or make caller do it...) return SetStringColumn(recipients, m_mdb->m_recipientsColumnToken); } NS_IMETHODIMP nsMsgHdr::SetCcList(const char* ccList) { return SetStringColumn(ccList, m_mdb->m_ccListColumnToken); } NS_IMETHODIMP nsMsgHdr::SetBccList(const char* bccList) { return SetStringColumn(bccList, m_mdb->m_bccListColumnToken); } NS_IMETHODIMP nsMsgHdr::SetMessageSize(uint32_t messageSize) { SetUInt32Column(messageSize, m_mdb->m_messageSizeColumnToken); m_messageSize = messageSize; return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetOfflineMessageSize(uint32_t* result) { uint32_t size; nsresult res = GetUInt32Column(m_mdb->m_offlineMessageSizeColumnToken, &size); *result = size; return res; } NS_IMETHODIMP nsMsgHdr::SetOfflineMessageSize(uint32_t messageSize) { return SetUInt32Column(messageSize, m_mdb->m_offlineMessageSizeColumnToken); } NS_IMETHODIMP nsMsgHdr::SetLineCount(uint32_t lineCount) { SetUInt32Column(lineCount, m_mdb->m_numLinesColumnToken); return NS_OK; } NS_IMETHODIMP nsMsgHdr::SetDate(PRTime date) { m_date = date; uint32_t seconds; PRTime2Seconds(date, &seconds); return SetUInt32Column((uint32_t)seconds, m_mdb->m_dateColumnToken); } NS_IMETHODIMP nsMsgHdr::SetPriority(nsMsgPriorityValue priority) { SetUInt32Column((uint32_t)priority, m_mdb->m_priorityColumnToken); return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetPriority(nsMsgPriorityValue* result) { if (!result) return NS_ERROR_NULL_POINTER; uint32_t priority = 0; nsresult rv = GetUInt32Column(m_mdb->m_priorityColumnToken, &priority); if (NS_FAILED(rv)) return rv; *result = (nsMsgPriorityValue)priority; return NS_OK; } // I'd like to not store the account key, if the msg is in // the same account as it was received in, to save disk space and memory. // This might be problematic when a message gets moved... // And I'm not sure if we should short circuit it here, // or at a higher level where it might be more efficient. NS_IMETHODIMP nsMsgHdr::SetAccountKey(const char* aAccountKey) { return SetStringProperty("account", nsDependentCString(aAccountKey)); } NS_IMETHODIMP nsMsgHdr::GetAccountKey(char** aResult) { NS_ENSURE_ARG_POINTER(aResult); nsCString key; nsresult rv = GetStringProperty("account", key); NS_ENSURE_SUCCESS(rv, rv); *aResult = ToNewCString(key); return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetMessageOffset(uint64_t* result) { NS_ENSURE_ARG(result); (void)GetUInt64Column(m_mdb->m_offlineMsgOffsetColumnToken, result, (unsigned)-1); if (*result == (unsigned)-1) { // It's unset. Unfortunately there's not much we can do here. There's // a lot of code which relies on being able to read .messageOffset, // even if it doesn't require it to return anything sensible. // (For example - in js unit tests - Assert.equals() stringifies the // attributes of it's expected/actual values to produce an error // message even if the assert passes). #ifdef DEBUG nsCString tok; GetStringProperty("storeToken", tok); nsPrintfCString err("Missing .messageOffset (key=%u, storeToken='%s')", m_messageKey, tok.get()); NS_WARNING(err.get()); #endif // Return something obviously broken. *result = 12345678; } return NS_OK; } NS_IMETHODIMP nsMsgHdr::SetMessageOffset(uint64_t offset) { SetUInt64Column(offset, m_mdb->m_offlineMsgOffsetColumnToken); return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetMessageSize(uint32_t* result) { uint32_t size; nsresult res = GetUInt32Column(m_mdb->m_messageSizeColumnToken, &size); *result = size; return res; } NS_IMETHODIMP nsMsgHdr::GetLineCount(uint32_t* result) { uint32_t linecount; nsresult res = GetUInt32Column(m_mdb->m_numLinesColumnToken, &linecount); *result = linecount; return res; } NS_IMETHODIMP nsMsgHdr::GetAuthor(char** resultAuthor) { return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor); } NS_IMETHODIMP nsMsgHdr::GetSubject(nsACString& resultSubject) { return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_subjectColumnToken, getter_Copies(resultSubject)); } NS_IMETHODIMP nsMsgHdr::GetRecipients(char** resultRecipients) { return m_mdb->RowCellColumnToCharPtr( GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients); } NS_IMETHODIMP nsMsgHdr::GetCcList(char** resultCCList) { return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_ccListColumnToken, resultCCList); } NS_IMETHODIMP nsMsgHdr::GetBccList(char** resultBCCList) { return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_bccListColumnToken, resultBCCList); } NS_IMETHODIMP nsMsgHdr::GetMessageId(char** resultMessageId) { return m_mdb->RowCellColumnToCharPtr( GetMDBRow(), m_mdb->m_messageIdColumnToken, resultMessageId); } NS_IMETHODIMP nsMsgHdr::GetMime2DecodedAuthor(nsAString& resultAuthor) { return m_mdb->RowCellColumnToMime2DecodedString( GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor); } NS_IMETHODIMP nsMsgHdr::GetMime2DecodedSubject(nsAString& resultSubject) { return m_mdb->RowCellColumnToMime2DecodedString( GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject); } NS_IMETHODIMP nsMsgHdr::GetMime2DecodedRecipients(nsAString& resultRecipients) { return m_mdb->RowCellColumnToMime2DecodedString( GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients); } NS_IMETHODIMP nsMsgHdr::GetAuthorCollationKey(nsTArray& resultAuthor) { return m_mdb->RowCellColumnToAddressCollationKey( GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor); } NS_IMETHODIMP nsMsgHdr::GetSubjectCollationKey( nsTArray& resultSubject) { return m_mdb->RowCellColumnToCollationKey( GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject); } NS_IMETHODIMP nsMsgHdr::GetRecipientsCollationKey( nsTArray& resultRecipients) { return m_mdb->RowCellColumnToCollationKey( GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients); } NS_IMETHODIMP nsMsgHdr::GetCharset(char** aCharset) { return m_mdb->RowCellColumnToCharPtr( GetMDBRow(), m_mdb->m_messageCharSetColumnToken, aCharset); } NS_IMETHODIMP nsMsgHdr::SetCharset(const char* aCharset) { return SetStringColumn(aCharset, m_mdb->m_messageCharSetColumnToken); } NS_IMETHODIMP nsMsgHdr::GetEffectiveCharset(nsACString& resultCharset) { return m_mdb->GetEffectiveCharset(m_mdbRow, resultCharset); } NS_IMETHODIMP nsMsgHdr::SetThreadParent(nsMsgKey inKey) { m_threadParent = inKey; if (inKey == m_messageKey) NS_ASSERTION(false, "can't be your own parent"); SetUInt32Column(m_threadParent, m_mdb->m_threadParentColumnToken); m_initedValues |= THREAD_PARENT_INITED; return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetThreadParent(nsMsgKey* result) { nsresult res; if (!(m_initedValues & THREAD_PARENT_INITED)) { res = GetUInt32Column(m_mdb->m_threadParentColumnToken, &m_threadParent, nsMsgKey_None); if (NS_SUCCEEDED(res)) m_initedValues |= THREAD_PARENT_INITED; } *result = m_threadParent; return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetFolder(nsIMsgFolder** result) { NS_ENSURE_ARG(result); if (m_mdb && m_mdb->m_folder) { NS_ADDREF(*result = m_mdb->m_folder); } else *result = nullptr; return NS_OK; } nsresult nsMsgHdr::SetStringColumn(const char* str, mdb_token token) { NS_ENSURE_ARG_POINTER(str); return m_mdb->CharPtrToRowCellColumn(m_mdbRow, token, str); } nsresult nsMsgHdr::SetUInt32Column(uint32_t value, mdb_token token) { return m_mdb->UInt32ToRowCellColumn(m_mdbRow, token, value); } nsresult nsMsgHdr::GetUInt32Column(mdb_token token, uint32_t* pvalue, uint32_t defaultValue) { return m_mdb->RowCellColumnToUInt32(GetMDBRow(), token, pvalue, defaultValue); } nsresult nsMsgHdr::SetUInt64Column(uint64_t value, mdb_token token) { return m_mdb->UInt64ToRowCellColumn(m_mdbRow, token, value); } nsresult nsMsgHdr::GetUInt64Column(mdb_token token, uint64_t* pvalue, uint64_t defaultValue) { return m_mdb->RowCellColumnToUInt64(GetMDBRow(), token, pvalue, defaultValue); } /** * Roughly speaking, get the next message-id (starts with a '<' ends with a * '>'). Except, we also try to handle the case where your reference is of * a prehistoric vintage that just stuck any old random junk in there. Our * old logic would (unintentionally?) just trim the whitespace off the front * and hand you everything after that. We change things at all because that * same behaviour does not make sense if we have already seen a proper message * id. We keep the old behaviour at all because it would seem to have * benefits. (See jwz's non-zero stats: http://www.jwz.org/doc/threading.html) * So, to re-state, if there is a valid message-id in there at all, we only * return valid message-id's (sans bracketing '<' and '>'). If there isn't, * our result (via "references") is a left-trimmed copy of the string. If * there is nothing in there, our result is an empty string.) We do require * that you pass allowNonDelimitedReferences what it demands, though. * For example: " this stuff is invalid" would net you * "valid@stuff" and "this stuff is invalid" as results. We now only would * provide "valid-stuff" and an empty string (which you should ignore) as * results. However "this stuff is invalid" would return itself, allowing * anything relying on that behaviour to keep working. * * Note: We accept anything inside the '<' and '>'; technically, we should want * at least a '@' in there (per rfc 2822). But since we're going out of our * way to support weird things... * * @param startNextRef The position to start at; this should either be the start * of your references string or our return value from a previous call. * @param reference You pass a nsCString by reference, we put the reference we * find in it, if we find one. It may be empty! Beware! * @param allowNonDelimitedReferences Should we support the * pre-reasonable-standards form of In-Reply-To where it could be any * arbitrary string and our behaviour was just to take off leading * whitespace. It only makes sense to pass true for your first call to this * function, as if you are around to make a second call, it means we found * a properly formatted message-id and so we should only look for more * properly formatted message-ids. * NOTE: this option will also strip off a single leading '<' if there is * one. Some examples: * " foo" => "foo" * " "bar" * "<< "<" => "foo@bar" (completed message-id) * @returns The next starting position of this routine, which may be pointing at * a nul '\0' character to indicate termination. */ const char* nsMsgHdr::GetNextReference(const char* startNextRef, nsCString& reference, bool acceptNonDelimitedReferences) { const char* ptr = startNextRef; const char* whitespaceEndedAt = nullptr; const char* firstMessageIdChar = nullptr; // make the reference result string empty by default; we will set it to // something valid if the time comes. reference.Truncate(); // walk until we find a '<', but keep track of the first point we found that // was not whitespace (as defined by previous versions of this code.) for (bool foundLessThan = false; !foundLessThan; ptr++) { switch (*ptr) { case '\0': // if we are at the end of the string, we found some non-whitespace, and // the caller requested that we accept non-delimited whitespace, // give them that as their reference. (otherwise, leave it empty) if (acceptNonDelimitedReferences && whitespaceEndedAt) reference = whitespaceEndedAt; return ptr; case ' ': case '\r': case '\n': case '\t': // do nothing, make default case mean you didn't get whitespace break; case '<': firstMessageIdChar = ptr + 1; // skip over the '<' foundLessThan = true; // (flag to stop) // Ensure whitespaceEndedAt skips the leading '<' and is set to // a non-NULL value, just in case the message-id is not valid (no '>') // and the old-school support is desired. if (!whitespaceEndedAt) whitespaceEndedAt = ptr + 1; break; default: if (!whitespaceEndedAt) whitespaceEndedAt = ptr; break; } } // keep going until we hit a '>' or hit the end of the string for (; *ptr; ptr++) { if (*ptr == '>') { // it's valid, update reference, making sure to stop before the '>' reference.Assign(firstMessageIdChar, ptr - firstMessageIdChar); // and return a start point just after the '>' return ++ptr; } } // we did not have a fully-formed, valid message-id, so consider falling back if (acceptNonDelimitedReferences && whitespaceEndedAt) reference = whitespaceEndedAt; return ptr; } bool nsMsgHdr::IsParentOf(nsIMsgDBHdr* possibleChild) { uint16_t referenceToCheck = 0; possibleChild->GetNumReferences(&referenceToCheck); nsAutoCString reference; nsCString messageId; GetMessageId(getter_Copies(messageId)); while (referenceToCheck > 0) { possibleChild->GetStringReference(referenceToCheck - 1, reference); if (reference.Equals(messageId)) return true; // if reference didn't match, check if this ref is for a non-existent // header. If it is, continue looking at ancestors. nsCOMPtr refHdr; if (!m_mdb) break; (void)m_mdb->GetMsgHdrForMessageID(reference.get(), getter_AddRefs(refHdr)); if (refHdr) break; referenceToCheck--; } return false; } bool nsMsgHdr::IsAncestorOf(nsIMsgDBHdr* possibleChild) { const char* references; nsMsgHdr* curHdr = static_cast(possibleChild); // closed system, cast ok m_mdb->RowCellColumnToConstCharPtr( curHdr->GetMDBRow(), m_mdb->m_referencesColumnToken, &references); if (!references) return false; nsCString messageId; // should put < > around message id to make strstr strictly match GetMessageId(getter_Copies(messageId)); return (strstr(references, messageId.get()) != nullptr); } NS_IMETHODIMP nsMsgHdr::GetIsRead(bool* isRead) { NS_ENSURE_ARG_POINTER(isRead); if (!(m_initedValues & FLAGS_INITED)) InitFlags(); *isRead = !!(m_flags & nsMsgMessageFlags::Read); return NS_OK; } NS_IMETHODIMP nsMsgHdr::GetIsFlagged(bool* isFlagged) { NS_ENSURE_ARG_POINTER(isFlagged); if (!(m_initedValues & FLAGS_INITED)) InitFlags(); *isFlagged = !!(m_flags & nsMsgMessageFlags::Marked); return NS_OK; } void nsMsgHdr::ReparentInThread(nsIMsgThread* thread) { NS_WARNING("Borked message header, attempting to fix!"); uint32_t numChildren; thread->GetNumChildren(&numChildren); // bail out early for the singleton thread case. if (numChildren == 1) { SetThreadParent(nsMsgKey_None); return; } else { nsCOMPtr curHdr; // loop through thread, looking for our proper parent. for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) { thread->GetChildHdrAt(childIndex, getter_AddRefs(curHdr)); // closed system, cast ok nsMsgHdr* curMsgHdr = static_cast(curHdr.get()); if (curHdr && curMsgHdr->IsParentOf(this)) { nsMsgKey curHdrKey; curHdr->GetMessageKey(&curHdrKey); SetThreadParent(curHdrKey); return; } } // we didn't find it. So either the root header is our parent, // or we're the root. nsCOMPtr rootHdr; thread->GetRootHdr(getter_AddRefs(rootHdr)); NS_ASSERTION(rootHdr, "thread has no root hdr - shouldn't happen"); if (rootHdr) { nsMsgKey rootKey; rootHdr->GetMessageKey(&rootKey); // if we're the root, our thread parent is -1. SetThreadParent(rootKey == m_messageKey ? nsMsgKey_None : rootKey); } } } bool nsMsgHdr::IsAncestorKilled(uint32_t ancestorsToCheck) { if (!(m_initedValues & FLAGS_INITED)) InitFlags(); bool isKilled = m_flags & nsMsgMessageFlags::Ignored; if (!isKilled) { nsMsgKey threadParent; GetThreadParent(&threadParent); if (threadParent == m_messageKey) { // isKilled is false by virtue of the enclosing if statement NS_ERROR("Thread is parent of itself, please fix!"); nsCOMPtr thread; (void)m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread)); if (!thread) return false; ReparentInThread(thread); // Something's wrong, but the problem happened some time ago, so erroring // out now is probably not a good idea. Ergo, we'll pretend to be OK, show // the user the thread (err on the side of caution), and let the assertion // alert debuggers to a problem. return false; } if (threadParent != nsMsgKey_None) { nsCOMPtr parentHdr; (void)m_mdb->GetMsgHdrForKey(threadParent, getter_AddRefs(parentHdr)); if (parentHdr) { // More proofing against crashers. This crasher was derived from the // fact that something got borked, leaving is in hand with a circular // reference to borked headers inducing these loops. The defining // characteristic of these headers is that they don't actually seat // themselves in the thread properly. nsCOMPtr thread; (void)m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread)); if (thread) { nsCOMPtr claimant; (void)thread->GetChild(threadParent, getter_AddRefs(claimant)); if (!claimant) { // attempt to reparent, and say the thread isn't killed, // erring on the side of safety. ReparentInThread(thread); return false; } } if (!ancestorsToCheck) { // We think we have a parent, but we have no more ancestors to check NS_ASSERTION(false, "cycle in parent relationship, please fix!"); return false; } // closed system, cast ok nsMsgHdr* parent = static_cast(parentHdr.get()); return parent->IsAncestorKilled(ancestorsToCheck - 1); } } } return isKilled; } NS_IMETHODIMP nsMsgHdr::GetIsKilled(bool* isKilled) { NS_ENSURE_ARG_POINTER(isKilled); *isKilled = false; nsCOMPtr thread; (void)m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread)); // if we can't find the thread, let's at least check one level; maybe // the header hasn't been added to a thread yet. uint32_t numChildren = 1; if (thread) thread->GetNumChildren(&numChildren); if (!numChildren) return NS_ERROR_FAILURE; // We can't have as many ancestors as there are messages in the thread, // so tell IsAncestorKilled to only check numChildren - 1 ancestors. *isKilled = IsAncestorKilled(numChildren - 1); return NS_OK; } //////////////////////////////////////////////////////////////////////////////// #include "nsIStringEnumerator.h" #define NULL_MORK_COLUMN 0 class nsMsgPropertyEnumerator : public nsStringEnumeratorBase { public: NS_DECL_ISUPPORTS NS_DECL_NSIUTF8STRINGENUMERATOR using nsStringEnumeratorBase::GetNext; explicit nsMsgPropertyEnumerator(nsMsgHdr* aHdr); void PrefetchNext(); protected: virtual ~nsMsgPropertyEnumerator(); nsCOMPtr mRowCellCursor; nsCOMPtr m_mdbEnv; nsCOMPtr m_mdbStore; // Hold a reference to the hdr so it will hold an xpcom reference to the // underlying mdb row. The row cell cursor will crash if the underlying // row goes away. RefPtr m_hdr; bool mNextPrefetched; mdb_column mNextColumn; }; nsMsgPropertyEnumerator::nsMsgPropertyEnumerator(nsMsgHdr* aHdr) : mNextPrefetched(false), mNextColumn(NULL_MORK_COLUMN) { RefPtr mdb; nsCOMPtr mdbRow; if (aHdr && (mdbRow = aHdr->GetMDBRow()) && (m_hdr = aHdr) && (mdb = aHdr->GetMdb()) && (m_mdbEnv = mdb->m_mdbEnv) && (m_mdbStore = mdb->m_mdbStore)) { mdbRow->GetRowCellCursor(m_mdbEnv, -1, getter_AddRefs(mRowCellCursor)); } } nsMsgPropertyEnumerator::~nsMsgPropertyEnumerator() { // Need to clear this before the nsMsgHdr and its corresponding // nsIMdbRow potentially go away. mRowCellCursor = nullptr; } NS_IMPL_ISUPPORTS(nsMsgPropertyEnumerator, nsIUTF8StringEnumerator, nsIStringEnumerator) NS_IMETHODIMP nsMsgPropertyEnumerator::GetNext(nsACString& aItem) { PrefetchNext(); if (mNextColumn == NULL_MORK_COLUMN) return NS_ERROR_FAILURE; // call HasMore first if (!m_mdbStore || !m_mdbEnv) return NS_ERROR_NOT_INITIALIZED; mNextPrefetched = false; char columnName[100]; struct mdbYarn colYarn = {columnName, 0, sizeof(columnName), 0, 0, nullptr}; // Get the column of the cell nsresult rv = m_mdbStore->TokenToString(m_mdbEnv, mNextColumn, &colYarn); NS_ENSURE_SUCCESS(rv, rv); aItem.Assign(static_cast(colYarn.mYarn_Buf), colYarn.mYarn_Fill); return NS_OK; } NS_IMETHODIMP nsMsgPropertyEnumerator::HasMore(bool* aResult) { NS_ENSURE_ARG_POINTER(aResult); PrefetchNext(); *aResult = (mNextColumn != NULL_MORK_COLUMN); return NS_OK; } void nsMsgPropertyEnumerator::PrefetchNext(void) { if (!mNextPrefetched && m_mdbEnv && mRowCellCursor) { mNextPrefetched = true; nsCOMPtr cell; mRowCellCursor->NextCell(m_mdbEnv, getter_AddRefs(cell), &mNextColumn, nullptr); if (mNextColumn == NULL_MORK_COLUMN) { // free up references m_mdbStore = nullptr; m_mdbEnv = nullptr; mRowCellCursor = nullptr; } } } //////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsMsgHdr::GetProperties(nsTArray& headers) { nsCOMPtr propertyEnumerator = new nsMsgPropertyEnumerator(this); bool hasMore; while (NS_SUCCEEDED(propertyEnumerator->HasMore(&hasMore)) && hasMore) { nsAutoCString property; propertyEnumerator->GetNext(property); headers.AppendElement(property); } return NS_OK; }