1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "msgCore.h"
7 #include "mozilla/mailnews/MimeHeaderParser.h"
8 #include "nsMsgHdr.h"
9 #include "nsMsgDatabase.h"
10 #include "nsMsgUtils.h"
11 #include "nsMsgMessageFlags.h"
12 #include "nsIMsgThread.h"
13 #include "nsMsgMimeCID.h"
14 #include "mozilla/Attributes.h"
15 #include "nsStringEnumerator.h"
16 
17 using namespace mozilla::mailnews;
18 
NS_IMPL_ISUPPORTS(nsMsgHdr,nsIMsgDBHdr)19 NS_IMPL_ISUPPORTS(nsMsgHdr, nsIMsgDBHdr)
20 
21 #define FLAGS_INITED 0x1
22 #define CACHED_VALUES_INITED 0x2
23 #define REFERENCES_INITED 0x4
24 #define THREAD_PARENT_INITED 0x8
25 
26 nsMsgHdr::nsMsgHdr(nsMsgDatabase* db, nsIMdbRow* dbRow) {
27   m_mdb = db;
28   Init();
29   m_mdbRow = dbRow;
30   if (m_mdb) {
31     NS_ADDREF(m_mdb);  // Released in DTOR.
32     mdbOid outOid;
33     if (dbRow && NS_SUCCEEDED(dbRow->GetOid(m_mdb->GetEnv(), &outOid))) {
34       m_messageKey = outOid.mOid_Id;
35       m_mdb->AddHdrToUseCache((nsIMsgDBHdr*)this, m_messageKey);
36     }
37   }
38 }
39 
Init()40 void nsMsgHdr::Init() {
41   m_initedValues = 0;
42   m_statusOffset = 0xffffffff;
43   m_messageKey = nsMsgKey_None;
44   m_messageSize = 0;
45   m_date = 0;
46   m_flags = 0;
47   m_mdbRow = NULL;
48   m_threadId = nsMsgKey_None;
49   m_threadParent = nsMsgKey_None;
50 }
51 
InitCachedValues()52 nsresult nsMsgHdr::InitCachedValues() {
53   nsresult err = NS_OK;
54 
55   if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER;
56 
57   if (!(m_initedValues & CACHED_VALUES_INITED)) {
58     uint32_t uint32Value;
59     mdbOid outOid;
60     if (NS_SUCCEEDED(m_mdbRow->GetOid(m_mdb->GetEnv(), &outOid)))
61       m_messageKey = outOid.mOid_Id;
62 
63     err = GetUInt32Column(m_mdb->m_messageSizeColumnToken, &m_messageSize);
64 
65     err = GetUInt32Column(m_mdb->m_dateColumnToken, &uint32Value);
66     Seconds2PRTime(uint32Value, &m_date);
67 
68     err = GetUInt32Column(m_mdb->m_messageThreadIdColumnToken, &m_threadId);
69 
70     if (NS_SUCCEEDED(err)) m_initedValues |= CACHED_VALUES_INITED;
71   }
72   return err;
73 }
74 
InitFlags()75 nsresult nsMsgHdr::InitFlags() {
76   nsresult err = NS_OK;
77 
78   if (!m_mdb) return NS_ERROR_NULL_POINTER;
79 
80   if (!(m_initedValues & FLAGS_INITED)) {
81     err = GetUInt32Column(m_mdb->m_flagsColumnToken, &m_flags);
82     m_flags &= ~nsMsgMessageFlags::New;  // don't get new flag from MDB
83 
84     if (NS_SUCCEEDED(err)) m_initedValues |= FLAGS_INITED;
85   }
86 
87   return err;
88 }
89 
~nsMsgHdr()90 nsMsgHdr::~nsMsgHdr() {
91   if (m_mdbRow) {
92     if (m_mdb) {
93       NS_RELEASE(m_mdbRow);
94       m_mdb->RemoveHdrFromUseCache((nsIMsgDBHdr*)this, m_messageKey);
95     }
96   }
97   NS_IF_RELEASE(m_mdb);
98 }
99 
GetMessageKey(nsMsgKey * result)100 NS_IMETHODIMP nsMsgHdr::GetMessageKey(nsMsgKey* result) {
101   if (m_messageKey == nsMsgKey_None && m_mdbRow != NULL) {
102     mdbOid outOid;
103     if (NS_SUCCEEDED(m_mdbRow->GetOid(m_mdb->GetEnv(), &outOid)))
104       m_messageKey = outOid.mOid_Id;
105   }
106   *result = m_messageKey;
107   return NS_OK;
108 }
109 
GetThreadId(nsMsgKey * result)110 NS_IMETHODIMP nsMsgHdr::GetThreadId(nsMsgKey* result) {
111   if (!(m_initedValues & CACHED_VALUES_INITED)) InitCachedValues();
112 
113   if (result) {
114     *result = m_threadId;
115     return NS_OK;
116   }
117   return NS_ERROR_NULL_POINTER;
118 }
119 
SetThreadId(nsMsgKey inKey)120 NS_IMETHODIMP nsMsgHdr::SetThreadId(nsMsgKey inKey) {
121   m_threadId = inKey;
122   return SetUInt32Column(m_threadId, m_mdb->m_messageThreadIdColumnToken);
123 }
124 
SetMessageKey(nsMsgKey value)125 NS_IMETHODIMP nsMsgHdr::SetMessageKey(nsMsgKey value) {
126   m_messageKey = value;
127   return NS_OK;
128 }
129 
GetRawFlags(uint32_t * result)130 nsresult nsMsgHdr::GetRawFlags(uint32_t* result) {
131   if (!(m_initedValues & FLAGS_INITED)) InitFlags();
132   *result = m_flags;
133   return NS_OK;
134 }
135 
GetFlags(uint32_t * result)136 NS_IMETHODIMP nsMsgHdr::GetFlags(uint32_t* result) {
137   if (!(m_initedValues & FLAGS_INITED)) InitFlags();
138   if (m_mdb)
139     *result = m_mdb->GetStatusFlags(this, m_flags);
140   else
141     *result = m_flags;
142 #ifdef DEBUG_bienvenu
143   NS_ASSERTION(!(*result & (nsMsgMessageFlags::Elided)),
144                "shouldn't be set in db");
145 #endif
146   return NS_OK;
147 }
148 
SetFlags(uint32_t flags)149 NS_IMETHODIMP nsMsgHdr::SetFlags(uint32_t flags) {
150 #ifdef DEBUG_bienvenu
151   NS_ASSERTION(!(flags & (nsMsgMessageFlags::Elided)),
152                "shouldn't set this flag on db");
153 #endif
154   m_initedValues |= FLAGS_INITED;
155   m_flags = flags;
156   // don't write out nsMsgMessageFlags::New to MDB.
157   return SetUInt32Column(m_flags & ~nsMsgMessageFlags::New,
158                          m_mdb->m_flagsColumnToken);
159 }
160 
OrFlags(uint32_t flags,uint32_t * result)161 NS_IMETHODIMP nsMsgHdr::OrFlags(uint32_t flags, uint32_t* result) {
162   if (!(m_initedValues & FLAGS_INITED)) InitFlags();
163   if ((m_flags & flags) != flags) SetFlags(m_flags | flags);
164   *result = m_flags;
165   return NS_OK;
166 }
167 
AndFlags(uint32_t flags,uint32_t * result)168 NS_IMETHODIMP nsMsgHdr::AndFlags(uint32_t flags, uint32_t* result) {
169   if (!(m_initedValues & FLAGS_INITED)) InitFlags();
170   if ((m_flags & flags) != m_flags) SetFlags(m_flags & flags);
171   *result = m_flags;
172   return NS_OK;
173 }
174 
MarkHasAttachments(bool bHasAttachments)175 NS_IMETHODIMP nsMsgHdr::MarkHasAttachments(bool bHasAttachments) {
176   nsresult rv = NS_OK;
177 
178   if (m_mdb) {
179     nsMsgKey key;
180     rv = GetMessageKey(&key);
181     if (NS_SUCCEEDED(rv))
182       rv = m_mdb->MarkHasAttachments(key, bHasAttachments, nullptr);
183   }
184   return rv;
185 }
186 
MarkRead(bool bRead)187 NS_IMETHODIMP nsMsgHdr::MarkRead(bool bRead) {
188   nsresult rv = NS_OK;
189 
190   if (m_mdb) {
191     nsMsgKey key;
192     rv = GetMessageKey(&key);
193     if (NS_SUCCEEDED(rv)) rv = m_mdb->MarkRead(key, bRead, nullptr);
194   }
195   return rv;
196 }
197 
MarkFlagged(bool bFlagged)198 NS_IMETHODIMP nsMsgHdr::MarkFlagged(bool bFlagged) {
199   nsresult rv = NS_OK;
200 
201   if (m_mdb) {
202     nsMsgKey key;
203     rv = GetMessageKey(&key);
204     if (NS_SUCCEEDED(rv)) rv = m_mdb->MarkMarked(key, bFlagged, nullptr);
205   }
206   return rv;
207 }
208 
GetProperty(const char * propertyName,nsAString & resultProperty)209 NS_IMETHODIMP nsMsgHdr::GetProperty(const char* propertyName,
210                                     nsAString& resultProperty) {
211   NS_ENSURE_ARG_POINTER(propertyName);
212   if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER;
213   return m_mdb->GetPropertyAsNSString(m_mdbRow, propertyName, resultProperty);
214 }
215 
SetProperty(const char * propertyName,const nsAString & propertyStr)216 NS_IMETHODIMP nsMsgHdr::SetProperty(const char* propertyName,
217                                     const nsAString& propertyStr) {
218   NS_ENSURE_ARG_POINTER(propertyName);
219   if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER;
220   return m_mdb->SetPropertyFromNSString(m_mdbRow, propertyName, propertyStr);
221 }
222 
SetStringProperty(const char * propertyName,const char * propertyValue)223 NS_IMETHODIMP nsMsgHdr::SetStringProperty(const char* propertyName,
224                                           const char* propertyValue) {
225   NS_ENSURE_ARG_POINTER(propertyName);
226   if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER;
227   return m_mdb->SetProperty(m_mdbRow, propertyName, propertyValue);
228 }
229 
GetStringProperty(const char * propertyName,char ** aPropertyValue)230 NS_IMETHODIMP nsMsgHdr::GetStringProperty(const char* propertyName,
231                                           char** aPropertyValue) {
232   NS_ENSURE_ARG_POINTER(propertyName);
233   if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER;
234   return m_mdb->GetProperty(m_mdbRow, propertyName, aPropertyValue);
235 }
236 
GetUint32Property(const char * propertyName,uint32_t * pResult)237 NS_IMETHODIMP nsMsgHdr::GetUint32Property(const char* propertyName,
238                                           uint32_t* pResult) {
239   NS_ENSURE_ARG_POINTER(propertyName);
240   if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER;
241   return m_mdb->GetUint32Property(m_mdbRow, propertyName, pResult);
242 }
243 
SetUint32Property(const char * propertyName,uint32_t value)244 NS_IMETHODIMP nsMsgHdr::SetUint32Property(const char* propertyName,
245                                           uint32_t value) {
246   NS_ENSURE_ARG_POINTER(propertyName);
247   if (!m_mdb || !m_mdbRow) return NS_ERROR_NULL_POINTER;
248   return m_mdb->SetUint32Property(m_mdbRow, propertyName, value);
249 }
250 
GetNumReferences(uint16_t * result)251 NS_IMETHODIMP nsMsgHdr::GetNumReferences(uint16_t* result) {
252   if (!(m_initedValues & REFERENCES_INITED)) {
253     const char* references;
254     if (NS_SUCCEEDED(m_mdb->RowCellColumnToConstCharPtr(
255             GetMDBRow(), m_mdb->m_referencesColumnToken, &references)))
256       ParseReferences(references);
257     m_initedValues |= REFERENCES_INITED;
258   }
259 
260   if (result) *result = m_references.Length();
261   // there is no real failure here; if there are no references, there are no
262   //  references.
263   return NS_OK;
264 }
265 
ParseReferences(const char * references)266 nsresult nsMsgHdr::ParseReferences(const char* references) {
267   const char* startNextRef = references;
268   nsAutoCString resultReference;
269   nsCString messageId;
270   GetMessageId(getter_Copies(messageId));
271 
272   while (startNextRef && *startNextRef) {
273     startNextRef = GetNextReference(startNextRef, resultReference,
274                                     startNextRef == references);
275     // Don't add self-references.
276     if (!resultReference.IsEmpty() && !resultReference.Equals(messageId))
277       m_references.AppendElement(resultReference);
278   }
279   return NS_OK;
280 }
281 
GetStringReference(int32_t refNum,nsACString & resultReference)282 NS_IMETHODIMP nsMsgHdr::GetStringReference(int32_t refNum,
283                                            nsACString& resultReference) {
284   nsresult err = NS_OK;
285 
286   if (!(m_initedValues & REFERENCES_INITED))
287     GetNumReferences(nullptr);  // it can handle the null
288 
289   if ((uint32_t)refNum < m_references.Length())
290     resultReference = m_references.ElementAt(refNum);
291   else
292     err = NS_ERROR_ILLEGAL_VALUE;
293   return err;
294 }
295 
GetDate(PRTime * result)296 NS_IMETHODIMP nsMsgHdr::GetDate(PRTime* result) {
297   if (!(m_initedValues & CACHED_VALUES_INITED)) InitCachedValues();
298 
299   *result = m_date;
300   return NS_OK;
301 }
302 
GetDateInSeconds(uint32_t * aResult)303 NS_IMETHODIMP nsMsgHdr::GetDateInSeconds(uint32_t* aResult) {
304   return GetUInt32Column(m_mdb->m_dateColumnToken, aResult);
305 }
306 
SetMessageId(const char * messageId)307 NS_IMETHODIMP nsMsgHdr::SetMessageId(const char* messageId) {
308   if (messageId && *messageId == '<') {
309     nsAutoCString tempMessageID(messageId + 1);
310     if (tempMessageID.CharAt(tempMessageID.Length() - 1) == '>')
311       tempMessageID.SetLength(tempMessageID.Length() - 1);
312     return SetStringColumn(tempMessageID.get(), m_mdb->m_messageIdColumnToken);
313   }
314   return SetStringColumn(messageId, m_mdb->m_messageIdColumnToken);
315 }
316 
SetSubject(const char * subject)317 NS_IMETHODIMP nsMsgHdr::SetSubject(const char* subject) {
318   return SetStringColumn(subject, m_mdb->m_subjectColumnToken);
319 }
320 
SetAuthor(const char * author)321 NS_IMETHODIMP nsMsgHdr::SetAuthor(const char* author) {
322   return SetStringColumn(author, m_mdb->m_senderColumnToken);
323 }
324 
SetReferences(const char * references)325 NS_IMETHODIMP nsMsgHdr::SetReferences(const char* references) {
326   NS_ENSURE_ARG_POINTER(references);
327   m_references.Clear();
328   ParseReferences(references);
329 
330   m_initedValues |= REFERENCES_INITED;
331 
332   return SetStringColumn(references, m_mdb->m_referencesColumnToken);
333 }
334 
SetRecipients(const char * recipients)335 NS_IMETHODIMP nsMsgHdr::SetRecipients(const char* recipients) {
336   // need to put in rfc822 address parsing code here (or make caller do it...)
337   return SetStringColumn(recipients, m_mdb->m_recipientsColumnToken);
338 }
339 
SetCcList(const char * ccList)340 NS_IMETHODIMP nsMsgHdr::SetCcList(const char* ccList) {
341   return SetStringColumn(ccList, m_mdb->m_ccListColumnToken);
342 }
343 
SetBccList(const char * bccList)344 NS_IMETHODIMP nsMsgHdr::SetBccList(const char* bccList) {
345   return SetStringColumn(bccList, m_mdb->m_bccListColumnToken);
346 }
347 
SetMessageSize(uint32_t messageSize)348 NS_IMETHODIMP nsMsgHdr::SetMessageSize(uint32_t messageSize) {
349   SetUInt32Column(messageSize, m_mdb->m_messageSizeColumnToken);
350   m_messageSize = messageSize;
351   return NS_OK;
352 }
353 
GetOfflineMessageSize(uint32_t * result)354 NS_IMETHODIMP nsMsgHdr::GetOfflineMessageSize(uint32_t* result) {
355   uint32_t size;
356   nsresult res = GetUInt32Column(m_mdb->m_offlineMessageSizeColumnToken, &size);
357 
358   *result = size;
359   return res;
360 }
361 
SetOfflineMessageSize(uint32_t messageSize)362 NS_IMETHODIMP nsMsgHdr::SetOfflineMessageSize(uint32_t messageSize) {
363   return SetUInt32Column(messageSize, m_mdb->m_offlineMessageSizeColumnToken);
364 }
365 
SetLineCount(uint32_t lineCount)366 NS_IMETHODIMP nsMsgHdr::SetLineCount(uint32_t lineCount) {
367   SetUInt32Column(lineCount, m_mdb->m_numLinesColumnToken);
368   return NS_OK;
369 }
370 
SetStatusOffset(uint32_t statusOffset)371 NS_IMETHODIMP nsMsgHdr::SetStatusOffset(uint32_t statusOffset) {
372   return SetUInt32Column(statusOffset, m_mdb->m_statusOffsetColumnToken);
373 }
374 
SetDate(PRTime date)375 NS_IMETHODIMP nsMsgHdr::SetDate(PRTime date) {
376   m_date = date;
377   uint32_t seconds;
378   PRTime2Seconds(date, &seconds);
379   return SetUInt32Column((uint32_t)seconds, m_mdb->m_dateColumnToken);
380 }
381 
GetStatusOffset(uint32_t * result)382 NS_IMETHODIMP nsMsgHdr::GetStatusOffset(uint32_t* result) {
383   uint32_t offset = 0;
384   nsresult res = GetUInt32Column(m_mdb->m_statusOffsetColumnToken, &offset);
385 
386   *result = offset;
387   return res;
388 }
389 
SetPriority(nsMsgPriorityValue priority)390 NS_IMETHODIMP nsMsgHdr::SetPriority(nsMsgPriorityValue priority) {
391   SetUInt32Column((uint32_t)priority, m_mdb->m_priorityColumnToken);
392   return NS_OK;
393 }
394 
GetPriority(nsMsgPriorityValue * result)395 NS_IMETHODIMP nsMsgHdr::GetPriority(nsMsgPriorityValue* result) {
396   if (!result) return NS_ERROR_NULL_POINTER;
397 
398   uint32_t priority = 0;
399   nsresult rv = GetUInt32Column(m_mdb->m_priorityColumnToken, &priority);
400   if (NS_FAILED(rv)) return rv;
401 
402   *result = (nsMsgPriorityValue)priority;
403   return NS_OK;
404 }
405 
SetLabel(nsMsgLabelValue label)406 NS_IMETHODIMP nsMsgHdr::SetLabel(nsMsgLabelValue label) {
407   SetUInt32Column((uint32_t)label, m_mdb->m_labelColumnToken);
408   return NS_OK;
409 }
410 
GetLabel(nsMsgLabelValue * result)411 NS_IMETHODIMP nsMsgHdr::GetLabel(nsMsgLabelValue* result) {
412   NS_ENSURE_ARG_POINTER(result);
413 
414   return GetUInt32Column(m_mdb->m_labelColumnToken, result);
415 }
416 
417 // I'd like to not store the account key, if the msg is in
418 // the same account as it was received in, to save disk space and memory.
419 // This might be problematic when a message gets moved...
420 // And I'm not sure if we should short circuit it here,
421 // or at a higher level where it might be more efficient.
SetAccountKey(const char * aAccountKey)422 NS_IMETHODIMP nsMsgHdr::SetAccountKey(const char* aAccountKey) {
423   return SetStringProperty("account", aAccountKey);
424 }
425 
GetAccountKey(char ** aResult)426 NS_IMETHODIMP nsMsgHdr::GetAccountKey(char** aResult) {
427   NS_ENSURE_ARG_POINTER(aResult);
428 
429   return GetStringProperty("account", aResult);
430 }
431 
GetMessageOffset(uint64_t * result)432 NS_IMETHODIMP nsMsgHdr::GetMessageOffset(uint64_t* result) {
433   NS_ENSURE_ARG(result);
434 
435   // if there is a message offset, use it, otherwise, we'll use the message key.
436   (void)GetUInt64Column(m_mdb->m_offlineMsgOffsetColumnToken, result,
437                         (unsigned)-1);
438   if (*result == (unsigned)-1) *result = m_messageKey;
439   return NS_OK;
440 }
441 
SetMessageOffset(uint64_t offset)442 NS_IMETHODIMP nsMsgHdr::SetMessageOffset(uint64_t offset) {
443   SetUInt64Column(offset, m_mdb->m_offlineMsgOffsetColumnToken);
444   return NS_OK;
445 }
446 
GetMessageSize(uint32_t * result)447 NS_IMETHODIMP nsMsgHdr::GetMessageSize(uint32_t* result) {
448   uint32_t size;
449   nsresult res = GetUInt32Column(m_mdb->m_messageSizeColumnToken, &size);
450 
451   *result = size;
452   return res;
453 }
454 
GetLineCount(uint32_t * result)455 NS_IMETHODIMP nsMsgHdr::GetLineCount(uint32_t* result) {
456   uint32_t linecount;
457   nsresult res = GetUInt32Column(m_mdb->m_numLinesColumnToken, &linecount);
458   *result = linecount;
459   return res;
460 }
461 
SetPriorityString(const char * priority)462 NS_IMETHODIMP nsMsgHdr::SetPriorityString(const char* priority) {
463   nsMsgPriorityValue priorityVal = nsMsgPriority::Default;
464 
465   // We can ignore |NS_MsgGetPriorityFromString()| return value,
466   // since we set a default value for |priorityVal|.
467   NS_MsgGetPriorityFromString(priority, priorityVal);
468 
469   return SetPriority(priorityVal);
470 }
471 
GetAuthor(char ** resultAuthor)472 NS_IMETHODIMP nsMsgHdr::GetAuthor(char** resultAuthor) {
473   return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_senderColumnToken,
474                                        resultAuthor);
475 }
476 
GetSubject(char ** resultSubject)477 NS_IMETHODIMP nsMsgHdr::GetSubject(char** resultSubject) {
478   return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_subjectColumnToken,
479                                        resultSubject);
480 }
481 
GetRecipients(char ** resultRecipients)482 NS_IMETHODIMP nsMsgHdr::GetRecipients(char** resultRecipients) {
483   return m_mdb->RowCellColumnToCharPtr(
484       GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients);
485 }
486 
GetCcList(char ** resultCCList)487 NS_IMETHODIMP nsMsgHdr::GetCcList(char** resultCCList) {
488   return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_ccListColumnToken,
489                                        resultCCList);
490 }
491 
GetBccList(char ** resultBCCList)492 NS_IMETHODIMP nsMsgHdr::GetBccList(char** resultBCCList) {
493   return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_bccListColumnToken,
494                                        resultBCCList);
495 }
496 
GetMessageId(char ** resultMessageId)497 NS_IMETHODIMP nsMsgHdr::GetMessageId(char** resultMessageId) {
498   return m_mdb->RowCellColumnToCharPtr(
499       GetMDBRow(), m_mdb->m_messageIdColumnToken, resultMessageId);
500 }
501 
GetMime2DecodedAuthor(nsAString & resultAuthor)502 NS_IMETHODIMP nsMsgHdr::GetMime2DecodedAuthor(nsAString& resultAuthor) {
503   return m_mdb->RowCellColumnToMime2DecodedString(
504       GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor);
505 }
506 
GetMime2DecodedSubject(nsAString & resultSubject)507 NS_IMETHODIMP nsMsgHdr::GetMime2DecodedSubject(nsAString& resultSubject) {
508   return m_mdb->RowCellColumnToMime2DecodedString(
509       GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject);
510 }
511 
GetMime2DecodedRecipients(nsAString & resultRecipients)512 NS_IMETHODIMP nsMsgHdr::GetMime2DecodedRecipients(nsAString& resultRecipients) {
513   return m_mdb->RowCellColumnToMime2DecodedString(
514       GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients);
515 }
516 
GetAuthorCollationKey(nsTArray<uint8_t> & resultAuthor)517 NS_IMETHODIMP nsMsgHdr::GetAuthorCollationKey(nsTArray<uint8_t>& resultAuthor) {
518   return m_mdb->RowCellColumnToAddressCollationKey(
519       GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor);
520 }
521 
GetSubjectCollationKey(nsTArray<uint8_t> & resultSubject)522 NS_IMETHODIMP nsMsgHdr::GetSubjectCollationKey(
523     nsTArray<uint8_t>& resultSubject) {
524   return m_mdb->RowCellColumnToCollationKey(
525       GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject);
526 }
527 
GetRecipientsCollationKey(nsTArray<uint8_t> & resultRecipients)528 NS_IMETHODIMP nsMsgHdr::GetRecipientsCollationKey(
529     nsTArray<uint8_t>& resultRecipients) {
530   return m_mdb->RowCellColumnToCollationKey(
531       GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients);
532 }
533 
GetCharset(char ** aCharset)534 NS_IMETHODIMP nsMsgHdr::GetCharset(char** aCharset) {
535   return m_mdb->RowCellColumnToCharPtr(
536       GetMDBRow(), m_mdb->m_messageCharSetColumnToken, aCharset);
537 }
538 
SetCharset(const char * aCharset)539 NS_IMETHODIMP nsMsgHdr::SetCharset(const char* aCharset) {
540   return SetStringColumn(aCharset, m_mdb->m_messageCharSetColumnToken);
541 }
542 
GetEffectiveCharset(nsACString & resultCharset)543 NS_IMETHODIMP nsMsgHdr::GetEffectiveCharset(nsACString& resultCharset) {
544   return m_mdb->GetEffectiveCharset(m_mdbRow, resultCharset);
545 }
546 
SetThreadParent(nsMsgKey inKey)547 NS_IMETHODIMP nsMsgHdr::SetThreadParent(nsMsgKey inKey) {
548   m_threadParent = inKey;
549   if (inKey == m_messageKey) NS_ASSERTION(false, "can't be your own parent");
550   SetUInt32Column(m_threadParent, m_mdb->m_threadParentColumnToken);
551   m_initedValues |= THREAD_PARENT_INITED;
552   return NS_OK;
553 }
554 
GetThreadParent(nsMsgKey * result)555 NS_IMETHODIMP nsMsgHdr::GetThreadParent(nsMsgKey* result) {
556   nsresult res;
557   if (!(m_initedValues & THREAD_PARENT_INITED)) {
558     res = GetUInt32Column(m_mdb->m_threadParentColumnToken, &m_threadParent,
559                           nsMsgKey_None);
560     if (NS_SUCCEEDED(res)) m_initedValues |= THREAD_PARENT_INITED;
561   }
562   *result = m_threadParent;
563   return NS_OK;
564 }
565 
GetFolder(nsIMsgFolder ** result)566 NS_IMETHODIMP nsMsgHdr::GetFolder(nsIMsgFolder** result) {
567   NS_ENSURE_ARG(result);
568 
569   if (m_mdb && m_mdb->m_folder) {
570     NS_ADDREF(*result = m_mdb->m_folder);
571   } else
572     *result = nullptr;
573   return NS_OK;
574 }
575 
SetStringColumn(const char * str,mdb_token token)576 nsresult nsMsgHdr::SetStringColumn(const char* str, mdb_token token) {
577   NS_ENSURE_ARG_POINTER(str);
578   return m_mdb->CharPtrToRowCellColumn(m_mdbRow, token, str);
579 }
580 
SetUInt32Column(uint32_t value,mdb_token token)581 nsresult nsMsgHdr::SetUInt32Column(uint32_t value, mdb_token token) {
582   return m_mdb->UInt32ToRowCellColumn(m_mdbRow, token, value);
583 }
584 
GetUInt32Column(mdb_token token,uint32_t * pvalue,uint32_t defaultValue)585 nsresult nsMsgHdr::GetUInt32Column(mdb_token token, uint32_t* pvalue,
586                                    uint32_t defaultValue) {
587   return m_mdb->RowCellColumnToUInt32(GetMDBRow(), token, pvalue, defaultValue);
588 }
589 
SetUInt64Column(uint64_t value,mdb_token token)590 nsresult nsMsgHdr::SetUInt64Column(uint64_t value, mdb_token token) {
591   return m_mdb->UInt64ToRowCellColumn(m_mdbRow, token, value);
592 }
593 
GetUInt64Column(mdb_token token,uint64_t * pvalue,uint64_t defaultValue)594 nsresult nsMsgHdr::GetUInt64Column(mdb_token token, uint64_t* pvalue,
595                                    uint64_t defaultValue) {
596   return m_mdb->RowCellColumnToUInt64(GetMDBRow(), token, pvalue, defaultValue);
597 }
598 
599 /**
600  * Roughly speaking, get the next message-id (starts with a '<' ends with a
601  *  '>').  Except, we also try to handle the case where your reference is of
602  *  a prehistoric vintage that just stuck any old random junk in there.  Our
603  *  old logic would (unintentionally?) just trim the whitespace off the front
604  *  and hand you everything after that.  We change things at all because that
605  *  same behaviour does not make sense if we have already seen a proper message
606  *  id.  We keep the old behaviour at all because it would seem to have
607  *  benefits.  (See jwz's non-zero stats: http://www.jwz.org/doc/threading.html)
608  * So, to re-state, if there is a valid message-id in there at all, we only
609  *  return valid message-id's (sans bracketing '<' and '>').  If there isn't,
610  *  our result (via "references") is a left-trimmed copy of the string.  If
611  *  there is nothing in there, our result is an empty string.)  We do require
612  *  that you pass allowNonDelimitedReferences what it demands, though.
613  * For example: "<valid@stuff> this stuff is invalid" would net you
614  *  "valid@stuff" and "this stuff is invalid" as results.  We now only would
615  *  provide "valid-stuff" and an empty string (which you should ignore) as
616  *  results.  However "this stuff is invalid" would return itself, allowing
617  *  anything relying on that behaviour to keep working.
618  *
619  * Note: We accept anything inside the '<' and '>'; technically, we should want
620  *  at least a '@' in there (per rfc 2822).  But since we're going out of our
621  *  way to support weird things...
622  *
623  * @param startNextRef The position to start at; this should either be the start
624  *     of your references string or our return value from a previous call.
625  * @param reference You pass a nsCString by reference, we put the reference we
626  *     find in it, if we find one.  It may be empty!  Beware!
627  * @param allowNonDelimitedReferences Should we support the
628  *     pre-reasonable-standards form of In-Reply-To where it could be any
629  *     arbitrary string and our behaviour was just to take off leading
630  *     whitespace.  It only makes sense to pass true for your first call to this
631  *     function, as if you are around to make a second call, it means we found
632  *     a properly formatted message-id and so we should only look for more
633  *     properly formatted message-ids.
634  *     NOTE: this option will also strip off a single leading '<' if there is
635  *     one. Some examples:
636  *        "   foo" => "foo"
637  *        "  <bar" => "bar"
638  *        "<<<foo" => "<<foo"
639  *        "<foo@bar>" => "foo@bar"  (completed message-id)
640  * @returns The next starting position of this routine, which may be pointing at
641  *     a nul '\0' character to indicate termination.
642  */
GetNextReference(const char * startNextRef,nsCString & reference,bool acceptNonDelimitedReferences)643 const char* nsMsgHdr::GetNextReference(const char* startNextRef,
644                                        nsCString& reference,
645                                        bool acceptNonDelimitedReferences) {
646   const char* ptr = startNextRef;
647   const char* whitespaceEndedAt = nullptr;
648   const char* firstMessageIdChar = nullptr;
649 
650   // make the reference result string empty by default; we will set it to
651   //  something valid if the time comes.
652   reference.Truncate();
653 
654   // walk until we find a '<', but keep track of the first point we found that
655   //  was not whitespace (as defined by previous versions of this code.)
656   for (bool foundLessThan = false; !foundLessThan; ptr++) {
657     switch (*ptr) {
658       case '\0':
659         // if we are at the end of the string, we found some non-whitespace, and
660         //  the caller requested that we accept non-delimited whitespace,
661         //  give them that as their reference.  (otherwise, leave it empty)
662         if (acceptNonDelimitedReferences && whitespaceEndedAt)
663           reference = whitespaceEndedAt;
664         return ptr;
665       case ' ':
666       case '\r':
667       case '\n':
668       case '\t':
669         // do nothing, make default case mean you didn't get whitespace
670         break;
671       case '<':
672         firstMessageIdChar = ptr + 1;  // skip over the '<'
673         foundLessThan = true;          // (flag to stop)
674         // Ensure whitespaceEndedAt skips the leading '<' and is set to
675         //  a non-NULL value, just in case the message-id is not valid (no '>')
676         //  and the old-school support is desired.
677         if (!whitespaceEndedAt) whitespaceEndedAt = ptr + 1;
678         break;
679       default:
680         if (!whitespaceEndedAt) whitespaceEndedAt = ptr;
681         break;
682     }
683   }
684 
685   // keep going until we hit a '>' or hit the end of the string
686   for (; *ptr; ptr++) {
687     if (*ptr == '>') {
688       // it's valid, update reference, making sure to stop before the '>'
689       reference.Assign(firstMessageIdChar, ptr - firstMessageIdChar);
690       // and return a start point just after the '>'
691       return ++ptr;
692     }
693   }
694 
695   // we did not have a fully-formed, valid message-id, so consider falling back
696   if (acceptNonDelimitedReferences && whitespaceEndedAt)
697     reference = whitespaceEndedAt;
698   return ptr;
699 }
700 
IsParentOf(nsIMsgDBHdr * possibleChild)701 bool nsMsgHdr::IsParentOf(nsIMsgDBHdr* possibleChild) {
702   uint16_t referenceToCheck = 0;
703   possibleChild->GetNumReferences(&referenceToCheck);
704   nsAutoCString reference;
705 
706   nsCString messageId;
707   GetMessageId(getter_Copies(messageId));
708 
709   while (referenceToCheck > 0) {
710     possibleChild->GetStringReference(referenceToCheck - 1, reference);
711 
712     if (reference.Equals(messageId)) return true;
713     // if reference didn't match, check if this ref is for a non-existent
714     // header. If it is, continue looking at ancestors.
715     nsCOMPtr<nsIMsgDBHdr> refHdr;
716     if (!m_mdb) break;
717     (void)m_mdb->GetMsgHdrForMessageID(reference.get(), getter_AddRefs(refHdr));
718     if (refHdr) break;
719     referenceToCheck--;
720   }
721   return false;
722 }
723 
IsAncestorOf(nsIMsgDBHdr * possibleChild)724 bool nsMsgHdr::IsAncestorOf(nsIMsgDBHdr* possibleChild) {
725   const char* references;
726   nsMsgHdr* curHdr =
727       static_cast<nsMsgHdr*>(possibleChild);  // closed system, cast ok
728   m_mdb->RowCellColumnToConstCharPtr(
729       curHdr->GetMDBRow(), m_mdb->m_referencesColumnToken, &references);
730   if (!references) return false;
731 
732   nsCString messageId;
733   // should put < > around message id to make strstr strictly match
734   GetMessageId(getter_Copies(messageId));
735   return (strstr(references, messageId.get()) != nullptr);
736 }
737 
GetIsRead(bool * isRead)738 NS_IMETHODIMP nsMsgHdr::GetIsRead(bool* isRead) {
739   NS_ENSURE_ARG_POINTER(isRead);
740   if (!(m_initedValues & FLAGS_INITED)) InitFlags();
741   *isRead = !!(m_flags & nsMsgMessageFlags::Read);
742   return NS_OK;
743 }
744 
GetIsFlagged(bool * isFlagged)745 NS_IMETHODIMP nsMsgHdr::GetIsFlagged(bool* isFlagged) {
746   NS_ENSURE_ARG_POINTER(isFlagged);
747   if (!(m_initedValues & FLAGS_INITED)) InitFlags();
748   *isFlagged = !!(m_flags & nsMsgMessageFlags::Marked);
749   return NS_OK;
750 }
751 
ReparentInThread(nsIMsgThread * thread)752 void nsMsgHdr::ReparentInThread(nsIMsgThread* thread) {
753   NS_WARNING("Borked message header, attempting to fix!");
754   uint32_t numChildren;
755   thread->GetNumChildren(&numChildren);
756   // bail out early for the singleton thread case.
757   if (numChildren == 1) {
758     SetThreadParent(nsMsgKey_None);
759     return;
760   } else {
761     nsCOMPtr<nsIMsgDBHdr> curHdr;
762     // loop through thread, looking for our proper parent.
763     for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
764       thread->GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
765       // closed system, cast ok
766       nsMsgHdr* curMsgHdr = static_cast<nsMsgHdr*>(curHdr.get());
767       if (curHdr && curMsgHdr->IsParentOf(this)) {
768         nsMsgKey curHdrKey;
769         curHdr->GetMessageKey(&curHdrKey);
770         SetThreadParent(curHdrKey);
771         return;
772       }
773     }
774     // we didn't find it. So either the root header is our parent,
775     // or we're the root.
776     int32_t rootIndex;
777     nsCOMPtr<nsIMsgDBHdr> rootHdr;
778     thread->GetRootHdr(&rootIndex, getter_AddRefs(rootHdr));
779     NS_ASSERTION(rootHdr, "thread has no root hdr - shouldn't happen");
780     if (rootHdr) {
781       nsMsgKey rootKey;
782       rootHdr->GetMessageKey(&rootKey);
783       // if we're the root, our thread parent is -1.
784       SetThreadParent(rootKey == m_messageKey ? nsMsgKey_None : rootKey);
785     }
786   }
787 }
788 
IsAncestorKilled(uint32_t ancestorsToCheck)789 bool nsMsgHdr::IsAncestorKilled(uint32_t ancestorsToCheck) {
790   if (!(m_initedValues & FLAGS_INITED)) InitFlags();
791   bool isKilled = m_flags & nsMsgMessageFlags::Ignored;
792 
793   if (!isKilled) {
794     nsMsgKey threadParent;
795     GetThreadParent(&threadParent);
796 
797     if (threadParent == m_messageKey) {
798       // isKilled is false by virtue of the enclosing if statement
799       NS_ERROR("Thread is parent of itself, please fix!");
800       nsCOMPtr<nsIMsgThread> thread;
801       (void)m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread));
802       if (!thread) return false;
803       ReparentInThread(thread);
804       // Something's wrong, but the problem happened some time ago, so erroring
805       // out now is probably not a good idea. Ergo, we'll pretend to be OK, show
806       // the user the thread (err on the side of caution), and let the assertion
807       // alert debuggers to a problem.
808       return false;
809     }
810     if (threadParent != nsMsgKey_None) {
811       nsCOMPtr<nsIMsgDBHdr> parentHdr;
812       (void)m_mdb->GetMsgHdrForKey(threadParent, getter_AddRefs(parentHdr));
813 
814       if (parentHdr) {
815         // More proofing against crashers. This crasher was derived from the
816         // fact that something got borked, leaving is in hand with a circular
817         // reference to borked headers inducing these loops. The defining
818         // characteristic of these headers is that they don't actually seat
819         // themselves in the thread properly.
820         nsCOMPtr<nsIMsgThread> thread;
821         (void)m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread));
822         if (thread) {
823           nsCOMPtr<nsIMsgDBHdr> claimant;
824           (void)thread->GetChild(threadParent, getter_AddRefs(claimant));
825           if (!claimant) {
826             // attempt to reparent, and say the thread isn't killed,
827             // erring on the side of safety.
828             ReparentInThread(thread);
829             return false;
830           }
831         }
832 
833         if (!ancestorsToCheck) {
834           // We think we have a parent, but we have no more ancestors to check
835           NS_ASSERTION(false, "cycle in parent relationship, please fix!");
836           return false;
837         }
838         // closed system, cast ok
839         nsMsgHdr* parent = static_cast<nsMsgHdr*>(parentHdr.get());
840         return parent->IsAncestorKilled(ancestorsToCheck - 1);
841       }
842     }
843   }
844   return isKilled;
845 }
846 
GetIsKilled(bool * isKilled)847 NS_IMETHODIMP nsMsgHdr::GetIsKilled(bool* isKilled) {
848   NS_ENSURE_ARG_POINTER(isKilled);
849   *isKilled = false;
850   nsCOMPtr<nsIMsgThread> thread;
851   (void)m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread));
852   // if we can't find the thread, let's at least check one level; maybe
853   // the header hasn't been added to a thread yet.
854   uint32_t numChildren = 1;
855   if (thread) thread->GetNumChildren(&numChildren);
856   if (!numChildren) return NS_ERROR_FAILURE;
857   // We can't have as many ancestors as there are messages in the thread,
858   // so tell IsAncestorKilled to only check numChildren - 1 ancestors.
859   *isKilled = IsAncestorKilled(numChildren - 1);
860   return NS_OK;
861 }
862 
863 ////////////////////////////////////////////////////////////////////////////////
864 
865 #include "nsIStringEnumerator.h"
866 #define NULL_MORK_COLUMN 0
867 class nsMsgPropertyEnumerator : public nsStringEnumeratorBase {
868  public:
869   NS_DECL_ISUPPORTS
870   NS_DECL_NSIUTF8STRINGENUMERATOR
871 
872   using nsStringEnumeratorBase::GetNext;
873 
874   explicit nsMsgPropertyEnumerator(nsMsgHdr* aHdr);
875   void PrefetchNext();
876 
877  protected:
878   virtual ~nsMsgPropertyEnumerator();
879   nsCOMPtr<nsIMdbRowCellCursor> mRowCellCursor;
880   nsCOMPtr<nsIMdbEnv> m_mdbEnv;
881   nsCOMPtr<nsIMdbStore> m_mdbStore;
882   // Hold a reference to the hdr so it will hold an xpcom reference to the
883   // underlying mdb row. The row cell cursor will crash if the underlying
884   // row goes away.
885   RefPtr<nsMsgHdr> m_hdr;
886   bool mNextPrefetched;
887   mdb_column mNextColumn;
888 };
889 
nsMsgPropertyEnumerator(nsMsgHdr * aHdr)890 nsMsgPropertyEnumerator::nsMsgPropertyEnumerator(nsMsgHdr* aHdr)
891     : mNextPrefetched(false), mNextColumn(NULL_MORK_COLUMN) {
892   RefPtr<nsMsgDatabase> mdb;
893   nsCOMPtr<nsIMdbRow> mdbRow;
894 
895   if (aHdr && (mdbRow = aHdr->GetMDBRow()) && (m_hdr = aHdr) &&
896       (mdb = aHdr->GetMdb()) && (m_mdbEnv = mdb->m_mdbEnv) &&
897       (m_mdbStore = mdb->m_mdbStore)) {
898     mdbRow->GetRowCellCursor(m_mdbEnv, -1, getter_AddRefs(mRowCellCursor));
899   }
900 }
901 
~nsMsgPropertyEnumerator()902 nsMsgPropertyEnumerator::~nsMsgPropertyEnumerator() {
903   // Need to clear this before the nsMsgHdr and its corresponding
904   // nsIMdbRow potentially go away.
905   mRowCellCursor = nullptr;
906 }
907 
NS_IMPL_ISUPPORTS(nsMsgPropertyEnumerator,nsIUTF8StringEnumerator,nsIStringEnumerator)908 NS_IMPL_ISUPPORTS(nsMsgPropertyEnumerator, nsIUTF8StringEnumerator,
909                   nsIStringEnumerator)
910 
911 NS_IMETHODIMP nsMsgPropertyEnumerator::GetNext(nsACString& aItem) {
912   PrefetchNext();
913   if (mNextColumn == NULL_MORK_COLUMN)
914     return NS_ERROR_FAILURE;  // call HasMore first
915   if (!m_mdbStore || !m_mdbEnv) return NS_ERROR_NOT_INITIALIZED;
916   mNextPrefetched = false;
917   char columnName[100];
918   struct mdbYarn colYarn = {columnName, 0, sizeof(columnName), 0, 0, nullptr};
919   // Get the column of the cell
920   nsresult rv = m_mdbStore->TokenToString(m_mdbEnv, mNextColumn, &colYarn);
921   NS_ENSURE_SUCCESS(rv, rv);
922 
923   aItem.Assign(static_cast<char*>(colYarn.mYarn_Buf), colYarn.mYarn_Fill);
924   return NS_OK;
925 }
926 
HasMore(bool * aResult)927 NS_IMETHODIMP nsMsgPropertyEnumerator::HasMore(bool* aResult) {
928   NS_ENSURE_ARG_POINTER(aResult);
929 
930   PrefetchNext();
931   *aResult = (mNextColumn != NULL_MORK_COLUMN);
932   return NS_OK;
933 }
934 
PrefetchNext(void)935 void nsMsgPropertyEnumerator::PrefetchNext(void) {
936   if (!mNextPrefetched && m_mdbEnv && mRowCellCursor) {
937     mNextPrefetched = true;
938     nsCOMPtr<nsIMdbCell> cell;
939     mRowCellCursor->NextCell(m_mdbEnv, getter_AddRefs(cell), &mNextColumn,
940                              nullptr);
941     if (mNextColumn == NULL_MORK_COLUMN) {
942       // free up references
943       m_mdbStore = nullptr;
944       m_mdbEnv = nullptr;
945       mRowCellCursor = nullptr;
946     }
947   }
948 }
949 
950 ////////////////////////////////////////////////////////////////////////////////
951 
GetPropertyEnumerator(nsIUTF8StringEnumerator ** _result)952 NS_IMETHODIMP nsMsgHdr::GetPropertyEnumerator(
953     nsIUTF8StringEnumerator** _result) {
954   NS_ADDREF(*_result = new nsMsgPropertyEnumerator(this));
955   return NS_OK;
956 }
957