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