summaryrefslogtreecommitdiffstats
path: root/mailnews/db/msgdb/src/nsMsgHdr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/db/msgdb/src/nsMsgHdr.cpp')
-rw-r--r--mailnews/db/msgdb/src/nsMsgHdr.cpp1098
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;
+}