diff options
Diffstat (limited to 'mailnews/db/msgdb/src/nsMsgHdr.cpp')
-rw-r--r-- | mailnews/db/msgdb/src/nsMsgHdr.cpp | 1098 |
1 files changed, 1098 insertions, 0 deletions
diff --git a/mailnews/db/msgdb/src/nsMsgHdr.cpp b/mailnews/db/msgdb/src/nsMsgHdr.cpp new file mode 100644 index 000000000..ba1663e3b --- /dev/null +++ b/mailnews/db/msgdb/src/nsMsgHdr.cpp @@ -0,0 +1,1098 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIMsgThread.h" +#include "nsMsgMimeCID.h" +#include "nsIMimeConverter.h" +#include "mozilla/Attributes.h" + +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) + { + m_mdb->AddRef(); + 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_statusOffset = 0xffffffff; + 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::GetProperty(const char *propertyName, nsAString &resultProperty) +{ + NS_ENSURE_ARG_POINTER(propertyName); + if (!m_mdb || !m_mdbRow) + return NS_ERROR_NULL_POINTER; + return m_mdb->GetPropertyAsNSString(m_mdbRow, propertyName, resultProperty); +} + +NS_IMETHODIMP nsMsgHdr::SetProperty(const char *propertyName, const nsAString &propertyStr) +{ + NS_ENSURE_ARG_POINTER(propertyName); + if (!m_mdb || !m_mdbRow) + return NS_ERROR_NULL_POINTER; + return m_mdb->SetPropertyFromNSString(m_mdbRow, propertyName, propertyStr); +} + +NS_IMETHODIMP nsMsgHdr::SetStringProperty(const char *propertyName, const char *propertyValue) +{ + NS_ENSURE_ARG_POINTER(propertyName); + if (!m_mdb || !m_mdbRow) + return NS_ERROR_NULL_POINTER; + return m_mdb->SetProperty(m_mdbRow, propertyName, propertyValue); +} + +NS_IMETHODIMP nsMsgHdr::GetStringProperty(const char *propertyName, char **aPropertyValue) +{ + NS_ENSURE_ARG_POINTER(propertyName); + if (!m_mdb || !m_mdbRow) + return NS_ERROR_NULL_POINTER; + return m_mdb->GetProperty(m_mdbRow, propertyName, 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 char *subject) +{ + return SetStringColumn(subject, m_mdb->m_subjectColumnToken); +} + +NS_IMETHODIMP nsMsgHdr::SetAuthor(const char *author) +{ + return SetStringColumn(author, m_mdb->m_senderColumnToken); +} + +NS_IMETHODIMP nsMsgHdr::SetReferences(const char *references) +{ + NS_ENSURE_ARG_POINTER(references); + m_references.Clear(); + ParseReferences(references); + + m_initedValues |= REFERENCES_INITED; + + return SetStringColumn(references, 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::SetStatusOffset(uint32_t statusOffset) +{ + return SetUInt32Column(statusOffset, m_mdb->m_statusOffsetColumnToken); +} + +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::GetStatusOffset(uint32_t *result) +{ + uint32_t offset = 0; + nsresult res = GetUInt32Column(m_mdb->m_statusOffsetColumnToken, &offset); + + *result = offset; + return res; +} + +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; +} + +NS_IMETHODIMP nsMsgHdr::SetLabel(nsMsgLabelValue label) +{ + SetUInt32Column((uint32_t) label, m_mdb->m_labelColumnToken); + return NS_OK; +} + +NS_IMETHODIMP nsMsgHdr::GetLabel(nsMsgLabelValue *result) +{ + NS_ENSURE_ARG_POINTER(result); + + return GetUInt32Column(m_mdb->m_labelColumnToken, result); +} + +// 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", aAccountKey); +} + +NS_IMETHODIMP nsMsgHdr::GetAccountKey(char **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + return GetStringProperty("account", aResult); +} + + +NS_IMETHODIMP nsMsgHdr::GetMessageOffset(uint64_t *result) +{ + NS_ENSURE_ARG(result); + + // if there is a message offset, use it, otherwise, we'll use the message key. + (void) GetUInt64Column(m_mdb->m_offlineMsgOffsetColumnToken, result, (unsigned)-1); + if (*result == (unsigned)-1) + *result = m_messageKey; + 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::SetPriorityString(const char *priority) +{ + nsMsgPriorityValue priorityVal = nsMsgPriority::Default; + + // We can ignore |NS_MsgGetPriorityFromString()| return value, + // since we set a default value for |priorityVal|. + NS_MsgGetPriorityFromString(priority, priorityVal); + + return SetPriority(priorityVal); +} + +NS_IMETHODIMP nsMsgHdr::GetAuthor(char* *resultAuthor) +{ + return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor); +} + +NS_IMETHODIMP nsMsgHdr::GetSubject(char* *resultSubject) +{ + return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_subjectColumnToken, 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(uint32_t *len, uint8_t **resultAuthor) +{ + return m_mdb->RowCellColumnToAddressCollationKey(GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor, len); +} + +NS_IMETHODIMP nsMsgHdr::GetSubjectCollationKey(uint32_t *len, uint8_t **resultSubject) +{ + return m_mdb->RowCellColumnToCollationKey(GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject, len); +} + +NS_IMETHODIMP nsMsgHdr::GetRecipientsCollationKey(uint32_t *len, uint8_t **resultRecipients) +{ + return m_mdb->RowCellColumnToCollationKey(GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients, len); +} + +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) + { + *result = m_mdb->m_folder; + NS_ADDREF(*result); + } + 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: "<valid@stuff> 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. + * @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; // skip over the '<' + foundLessThan = true; // (flag to stop) + // intentional fallthrough so whitespaceEndedAt will definitely have + // a non-NULL value, just in case the message-id is not valid (no '>') + // and the old-school support is desired. + MOZ_FALLTHROUGH; + 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 <nsIMsgDBHdr> 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<nsMsgHdr*>(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<nsIMsgDBHdr> 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<nsMsgHdr*>(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. + int32_t rootIndex; + nsCOMPtr<nsIMsgDBHdr> rootHdr; + thread->GetRootHdr(&rootIndex, 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<nsIMsgThread> 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<nsIMsgDBHdr> 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<nsIMsgThread> thread; + (void) m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread)); + if (thread) + { + nsCOMPtr<nsIMsgDBHdr> 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<nsMsgHdr*>(parentHdr.get()); + return parent->IsAncestorKilled(ancestorsToCheck - 1); + } + } + } + return isKilled; +} + +NS_IMETHODIMP nsMsgHdr::GetIsKilled(bool *isKilled) +{ + NS_ENSURE_ARG_POINTER(isKilled); + *isKilled = false; + nsCOMPtr<nsIMsgThread> 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" +#include "nsAutoPtr.h" +#define NULL_MORK_COLUMN 0 +class nsMsgPropertyEnumerator : public nsIUTF8StringEnumerator +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIUTF8STRINGENUMERATOR + + nsMsgPropertyEnumerator(nsMsgHdr* aHdr); + void PrefetchNext(); + +protected: + virtual ~nsMsgPropertyEnumerator(); + nsCOMPtr<nsIMdbRowCellCursor> mRowCellCursor; + nsCOMPtr<nsIMdbEnv> m_mdbEnv; + nsCOMPtr<nsIMdbStore> 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<nsMsgHdr> m_hdr; + bool mNextPrefetched; + mdb_column mNextColumn; +}; + +nsMsgPropertyEnumerator::nsMsgPropertyEnumerator(nsMsgHdr* aHdr) + : mNextPrefetched(false), + mNextColumn(NULL_MORK_COLUMN) +{ + RefPtr<nsMsgDatabase> mdb; + nsCOMPtr<nsIMdbRow> mdbRow; + + if (aHdr && + (mdbRow = aHdr->GetMDBRow()) && + (m_hdr = aHdr) && + (mdb = aHdr->m_mdb) && + (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) + +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<char *>(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<nsIMdbCell> 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::GetPropertyEnumerator(nsIUTF8StringEnumerator** _result) +{ + nsMsgPropertyEnumerator* enumerator = new nsMsgPropertyEnumerator(this); + if (!enumerator) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*_result = enumerator); + return NS_OK; +} |