From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mailnews/db/msgdb/src/nsMsgHdr.cpp | 936 ++++++++++++++++++++++++++++++++ 1 file changed, 936 insertions(+) create mode 100644 comm/mailnews/db/msgdb/src/nsMsgHdr.cpp (limited to 'comm/mailnews/db/msgdb/src/nsMsgHdr.cpp') diff --git a/comm/mailnews/db/msgdb/src/nsMsgHdr.cpp b/comm/mailnews/db/msgdb/src/nsMsgHdr.cpp new file mode 100644 index 0000000000..c3d36d9b9b --- /dev/null +++ b/comm/mailnews/db/msgdb/src/nsMsgHdr.cpp @@ -0,0 +1,936 @@ +/* -*- 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; +} -- cgit v1.2.3