/* -*- 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: " 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 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. int32_t rootIndex; nsCOMPtr 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 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" #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 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->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(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::GetPropertyEnumerator(nsIUTF8StringEnumerator** _result) { nsMsgPropertyEnumerator* enumerator = new nsMsgPropertyEnumerator(this); if (!enumerator) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*_result = enumerator); return NS_OK; }