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 "nsIURI.h"
8 #include "nsIChannel.h"
9 #include "nsParseMailbox.h"
10 #include "nsIMsgHdr.h"
11 #include "nsIMsgDatabase.h"
12 #include "nsMsgMessageFlags.h"
13 #include "nsIDBFolderInfo.h"
14 #include "nsIInputStream.h"
15 #include "nsIFile.h"
16 #include "nsMsgLocalFolderHdrs.h"
17 #include "nsMsgBaseCID.h"
18 #include "nsMsgDBCID.h"
19 #include "nsIMailboxUrl.h"
20 #include "nsNetUtil.h"
21 #include "nsMsgFolderFlags.h"
22 #include "nsIMsgFolder.h"
23 #include "nsIMsgFolderNotificationService.h"
24 #include "nsIMsgMailNewsUrl.h"
25 #include "nsIMsgFilter.h"
26 #include "nsIMsgFilterPlugin.h"
27 #include "nsIMsgFilterHitNotify.h"
28 #include "nsIIOService.h"
29 #include "nsNetCID.h"
30 #include "nsMsgI18N.h"
31 #include "nsAppDirectoryServiceDefs.h"
32 #include "nsIMsgLocalMailFolder.h"
33 #include "nsMsgUtils.h"
34 #include "prprf.h"
35 #include "prmem.h"
36 #include "nsMsgSearchCore.h"
37 #include "nsMailHeaders.h"
38 #include "nsIMsgMailSession.h"
39 #include "nsMsgCompCID.h"
40 #include "nsIPrefBranch.h"
41 #include "nsIPrefService.h"
42 #include "nsIMsgComposeService.h"
43 #include "nsIMsgCopyService.h"
44 #include "nsICryptoHash.h"
45 #include "nsIStringBundle.h"
46 #include "nsIMsgFilterCustomAction.h"
47 #include <ctype.h>
48 #include "nsIMsgPluggableStore.h"
49 #include "mozilla/Services.h"
50 #include "nsQueryObject.h"
51 #include "nsIOutputStream.h"
52 #include "mozilla/Attributes.h"
53 #include "mozilla/Logging.h"
54
55 using namespace mozilla;
56
57 extern LazyLogModule FILTERLOGMODULE;
58
59 /* the following macros actually implement addref, release and query interface
60 * for our component. */
NS_IMPL_ISUPPORTS_INHERITED(nsMsgMailboxParser,nsParseMailMessageState,nsIStreamListener,nsIRequestObserver)61 NS_IMPL_ISUPPORTS_INHERITED(nsMsgMailboxParser, nsParseMailMessageState,
62 nsIStreamListener, nsIRequestObserver)
63
64 // Whenever data arrives from the connection, core netlib notifices the protocol
65 // by calling OnDataAvailable. We then read and process the incoming data from
66 // the input stream.
67 NS_IMETHODIMP nsMsgMailboxParser::OnDataAvailable(nsIRequest* request,
68 nsIInputStream* aIStream,
69 uint64_t sourceOffset,
70 uint32_t aLength) {
71 return ProcessMailboxInputStream(aIStream, aLength);
72 }
73
OnStartRequest(nsIRequest * request)74 NS_IMETHODIMP nsMsgMailboxParser::OnStartRequest(nsIRequest* request) {
75 m_startTime = PR_Now();
76
77 // extract the appropriate event sinks from the url and initialize them in our
78 // protocol data the URL should be queried for a nsIMailboxURL. If it doesn't
79 // support a mailbox URL interface then we have an error.
80 nsresult rv = NS_OK;
81
82 nsCOMPtr<nsIIOService> ioServ = mozilla::services::GetIOService();
83 NS_ENSURE_TRUE(ioServ, NS_ERROR_UNEXPECTED);
84
85 // We know the request is an nsIChannel we can get a URI from, but this is
86 // probably bad form. See Bug 1528662.
87 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
88 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
89 "error QI nsIRequest to nsIChannel failed");
90 NS_ENSURE_SUCCESS(rv, rv);
91 nsCOMPtr<nsIURI> uri;
92 rv = channel->GetURI(getter_AddRefs(uri));
93 NS_ENSURE_SUCCESS(rv, rv);
94
95 nsCOMPtr<nsIMailboxUrl> runningUrl = do_QueryInterface(uri, &rv);
96
97 nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(uri);
98 nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
99
100 if (NS_SUCCEEDED(rv) && runningUrl && folder) {
101 url->GetStatusFeedback(getter_AddRefs(m_statusFeedback));
102
103 // okay, now fill in our event sinks...Note that each getter ref counts
104 // before it returns the interface to us...we'll release when we are done
105
106 folder->GetName(m_folderName);
107
108 nsCOMPtr<nsIFile> path;
109 folder->GetFilePath(getter_AddRefs(path));
110
111 if (path) {
112 int64_t fileSize;
113 path->GetFileSize(&fileSize);
114 // the size of the mailbox file is our total base line for measuring
115 // progress
116 m_graph_progress_total = fileSize;
117 UpdateStatusText("buildingSummary");
118 nsCOMPtr<nsIMsgDBService> msgDBService =
119 do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
120 if (msgDBService) {
121 // Use OpenFolderDB to always open the db so that db's m_folder
122 // is set correctly.
123 rv = msgDBService->OpenFolderDB(folder, true, getter_AddRefs(m_mailDB));
124 if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
125 rv = msgDBService->CreateNewDB(folder, getter_AddRefs(m_mailDB));
126
127 if (m_mailDB) m_mailDB->AddListener(this);
128 }
129 NS_ASSERTION(m_mailDB, "failed to open mail db parsing folder");
130
131 // try to get a backup message database
132 nsresult rvignore =
133 folder->GetBackupMsgDatabase(getter_AddRefs(m_backupMailDB));
134
135 // We'll accept failures and move on, as we're dealing with some
136 // sort of unknown problem to begin with.
137 if (NS_FAILED(rvignore)) {
138 if (m_backupMailDB) m_backupMailDB->RemoveListener(this);
139 m_backupMailDB = nullptr;
140 } else if (m_backupMailDB) {
141 m_backupMailDB->AddListener(this);
142 }
143 }
144 }
145
146 // need to get the mailbox name out of the url and call SetMailboxName with
147 // it. then, we need to open the mail db for this parser.
148 return rv;
149 }
150
151 // stop binding is a "notification" informing us that the stream associated with
152 // aURL is going away.
OnStopRequest(nsIRequest * request,nsresult aStatus)153 NS_IMETHODIMP nsMsgMailboxParser::OnStopRequest(nsIRequest* request,
154 nsresult aStatus) {
155 DoneParsingFolder(aStatus);
156 // what can we do? we can close the stream?
157 m_urlInProgress =
158 false; // don't close the connection...we may be re-using it.
159
160 if (m_mailDB) m_mailDB->RemoveListener(this);
161 // and we want to mark ourselves for deletion or some how inform our protocol
162 // manager that we are available for another url if there is one....
163
164 ReleaseFolderLock();
165 // be sure to clear any status text and progress info..
166 m_graph_progress_received = 0;
167 UpdateProgressPercent();
168 UpdateStatusText("localStatusDocumentDone");
169
170 return NS_OK;
171 }
172
173 NS_IMETHODIMP
OnHdrPropertyChanged(nsIMsgDBHdr * aHdrToChange,bool aPreChange,uint32_t * aStatus,nsIDBChangeListener * aInstigator)174 nsParseMailMessageState::OnHdrPropertyChanged(
175 nsIMsgDBHdr* aHdrToChange, bool aPreChange, uint32_t* aStatus,
176 nsIDBChangeListener* aInstigator) {
177 return NS_OK;
178 }
179
180 NS_IMETHODIMP
OnHdrFlagsChanged(nsIMsgDBHdr * aHdrChanged,uint32_t aOldFlags,uint32_t aNewFlags,nsIDBChangeListener * aInstigator)181 nsParseMailMessageState::OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged,
182 uint32_t aOldFlags,
183 uint32_t aNewFlags,
184 nsIDBChangeListener* aInstigator) {
185 return NS_OK;
186 }
187
188 NS_IMETHODIMP
OnHdrDeleted(nsIMsgDBHdr * aHdrChanged,nsMsgKey aParentKey,int32_t aFlags,nsIDBChangeListener * aInstigator)189 nsParseMailMessageState::OnHdrDeleted(nsIMsgDBHdr* aHdrChanged,
190 nsMsgKey aParentKey, int32_t aFlags,
191 nsIDBChangeListener* aInstigator) {
192 return NS_OK;
193 }
194
195 NS_IMETHODIMP
OnHdrAdded(nsIMsgDBHdr * aHdrAdded,nsMsgKey aParentKey,int32_t aFlags,nsIDBChangeListener * aInstigator)196 nsParseMailMessageState::OnHdrAdded(nsIMsgDBHdr* aHdrAdded, nsMsgKey aParentKey,
197 int32_t aFlags,
198 nsIDBChangeListener* aInstigator) {
199 return NS_OK;
200 }
201
202 /* void OnParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in
203 * nsMsgKey newParent, in nsIDBChangeListener aInstigator); */
204 NS_IMETHODIMP
OnParentChanged(nsMsgKey aKeyChanged,nsMsgKey oldParent,nsMsgKey newParent,nsIDBChangeListener * aInstigator)205 nsParseMailMessageState::OnParentChanged(nsMsgKey aKeyChanged,
206 nsMsgKey oldParent, nsMsgKey newParent,
207 nsIDBChangeListener* aInstigator) {
208 return NS_OK;
209 }
210
211 /* void OnAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */
212 NS_IMETHODIMP
OnAnnouncerGoingAway(nsIDBChangeAnnouncer * instigator)213 nsParseMailMessageState::OnAnnouncerGoingAway(
214 nsIDBChangeAnnouncer* instigator) {
215 if (m_backupMailDB && m_backupMailDB == instigator) {
216 m_backupMailDB->RemoveListener(this);
217 m_backupMailDB = nullptr;
218 } else if (m_mailDB) {
219 m_mailDB->RemoveListener(this);
220 m_mailDB = nullptr;
221 m_newMsgHdr = nullptr;
222 }
223 return NS_OK;
224 }
225
OnEvent(nsIMsgDatabase * aDB,const char * aEvent)226 NS_IMETHODIMP nsParseMailMessageState::OnEvent(nsIMsgDatabase* aDB,
227 const char* aEvent) {
228 return NS_OK;
229 }
230
231 /* void OnReadChanged (in nsIDBChangeListener instigator); */
232 NS_IMETHODIMP
OnReadChanged(nsIDBChangeListener * instigator)233 nsParseMailMessageState::OnReadChanged(nsIDBChangeListener* instigator) {
234 return NS_OK;
235 }
236
237 /* void OnJunkScoreChanged (in nsIDBChangeListener instigator); */
238 NS_IMETHODIMP
OnJunkScoreChanged(nsIDBChangeListener * instigator)239 nsParseMailMessageState::OnJunkScoreChanged(nsIDBChangeListener* instigator) {
240 return NS_OK;
241 }
242
nsMsgMailboxParser()243 nsMsgMailboxParser::nsMsgMailboxParser() : nsMsgLineBuffer() { Init(); }
244
nsMsgMailboxParser(nsIMsgFolder * aFolder)245 nsMsgMailboxParser::nsMsgMailboxParser(nsIMsgFolder* aFolder)
246 : nsMsgLineBuffer(),
247 m_parsingDone(false),
248 m_startTime(0),
249 m_urlInProgress(false) {
250 m_folder = do_GetWeakReference(aFolder);
251 }
252
~nsMsgMailboxParser()253 nsMsgMailboxParser::~nsMsgMailboxParser() { ReleaseFolderLock(); }
254
Init()255 nsresult nsMsgMailboxParser::Init() {
256 m_obuffer = nullptr;
257 m_obuffer_size = 0;
258 m_graph_progress_total = 0;
259 m_graph_progress_received = 0;
260 return AcquireFolderLock();
261 }
262
UpdateStatusText(const char * stringName)263 void nsMsgMailboxParser::UpdateStatusText(const char* stringName) {
264 if (m_statusFeedback) {
265 nsresult rv;
266 nsCOMPtr<nsIStringBundleService> bundleService =
267 mozilla::services::GetStringBundleService();
268 if (!bundleService) return;
269 nsCOMPtr<nsIStringBundle> bundle;
270 rv = bundleService->CreateBundle(
271 "chrome://messenger/locale/localMsgs.properties",
272 getter_AddRefs(bundle));
273 if (NS_FAILED(rv)) return;
274 nsString finalString;
275 AutoTArray<nsString, 1> stringArray = {m_folderName};
276 rv = bundle->FormatStringFromName(stringName, stringArray, finalString);
277 m_statusFeedback->ShowStatusString(finalString);
278 }
279 }
280
UpdateProgressPercent()281 void nsMsgMailboxParser::UpdateProgressPercent() {
282 if (m_statusFeedback && m_graph_progress_total != 0) {
283 // prevent overflow by dividing both by 100
284 int64_t progressTotal = m_graph_progress_total / 100;
285 int64_t progressReceived = m_graph_progress_received / 100;
286 if (progressTotal > 0)
287 m_statusFeedback->ShowProgress((100 * (progressReceived)) /
288 progressTotal);
289 }
290 }
291
ProcessMailboxInputStream(nsIInputStream * aIStream,uint32_t aLength)292 nsresult nsMsgMailboxParser::ProcessMailboxInputStream(nsIInputStream* aIStream,
293 uint32_t aLength) {
294 nsresult ret = NS_OK;
295
296 uint32_t bytesRead = 0;
297
298 if (NS_SUCCEEDED(m_inputStream.GrowBuffer(aLength))) {
299 // OK, this sucks, but we're going to have to copy into our
300 // own byte buffer, and then pass that to the line buffering code,
301 // which means a couple buffer copies.
302 ret = aIStream->Read(m_inputStream.GetBuffer(), aLength, &bytesRead);
303 if (NS_SUCCEEDED(ret))
304 ret = BufferInput(m_inputStream.GetBuffer(), bytesRead);
305 }
306 if (m_graph_progress_total > 0) {
307 if (NS_SUCCEEDED(ret)) m_graph_progress_received += bytesRead;
308 }
309 return (ret);
310 }
311
DoneParsingFolder(nsresult status)312 void nsMsgMailboxParser::DoneParsingFolder(nsresult status) {
313 // End of file. Flush out any data remaining in the buffer.
314 Flush();
315 PublishMsgHeader(nullptr);
316
317 // only mark the db valid if we've succeeded.
318 if (NS_SUCCEEDED(status) &&
319 m_mailDB) // finished parsing, so flush db folder info
320 UpdateDBFolderInfo();
321 else if (m_mailDB)
322 m_mailDB->SetSummaryValid(false);
323
324 // remove the backup database
325 if (m_backupMailDB) {
326 nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
327 if (folder) folder->RemoveBackupMsgDatabase();
328 m_backupMailDB = nullptr;
329 }
330
331 // if (m_folder != nullptr)
332 // m_folder->SummaryChanged();
333 FreeBuffers();
334 }
335
FreeBuffers()336 void nsMsgMailboxParser::FreeBuffers() {
337 /* We're done reading the folder - we don't need these things
338 any more. */
339 PR_FREEIF(m_obuffer);
340 m_obuffer_size = 0;
341 }
342
UpdateDBFolderInfo()343 void nsMsgMailboxParser::UpdateDBFolderInfo() { UpdateDBFolderInfo(m_mailDB); }
344
345 // update folder info in db so we know not to reparse.
UpdateDBFolderInfo(nsIMsgDatabase * mailDB)346 void nsMsgMailboxParser::UpdateDBFolderInfo(nsIMsgDatabase* mailDB) {
347 mailDB->SetSummaryValid(true);
348 }
349
350 // Tell the world about the message header (add to db, and view, if any)
PublishMsgHeader(nsIMsgWindow * msgWindow)351 int32_t nsMsgMailboxParser::PublishMsgHeader(nsIMsgWindow* msgWindow) {
352 FinishHeader();
353 if (m_newMsgHdr) {
354 char storeToken[100];
355 PR_snprintf(storeToken, sizeof(storeToken), "%lld", m_envelope_pos);
356 m_newMsgHdr->SetStringProperty("storeToken", storeToken);
357
358 uint32_t flags;
359 (void)m_newMsgHdr->GetFlags(&flags);
360 if (flags & nsMsgMessageFlags::Expunged) {
361 nsCOMPtr<nsIDBFolderInfo> folderInfo;
362 m_mailDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
363 uint32_t size;
364 (void)m_newMsgHdr->GetMessageSize(&size);
365 folderInfo->ChangeExpungedBytes(size);
366 m_newMsgHdr = nullptr;
367 } else if (m_mailDB) {
368 // add hdr but don't notify - shouldn't be requiring notifications
369 // during summary file rebuilding
370 m_mailDB->AddNewHdrToDB(m_newMsgHdr, false);
371 m_newMsgHdr = nullptr;
372 } else
373 NS_ASSERTION(
374 false,
375 "no database while parsing local folder"); // should have a DB, no?
376 } else if (m_mailDB) {
377 nsCOMPtr<nsIDBFolderInfo> folderInfo;
378 m_mailDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
379 if (folderInfo)
380 folderInfo->ChangeExpungedBytes(m_position - m_envelope_pos);
381 }
382 return 0;
383 }
384
AbortNewHeader()385 void nsMsgMailboxParser::AbortNewHeader() {
386 if (m_newMsgHdr && m_mailDB) m_newMsgHdr = nullptr;
387 }
388
OnNewMessage(nsIMsgWindow * msgWindow)389 void nsMsgMailboxParser::OnNewMessage(nsIMsgWindow* msgWindow) {
390 PublishMsgHeader(msgWindow);
391 Clear();
392 }
393
HandleLine(const char * line,uint32_t lineLength)394 nsresult nsMsgMailboxParser::HandleLine(const char* line, uint32_t lineLength) {
395 /* If this is the very first line of a non-empty folder, make sure it's an
396 * envelope */
397 if (m_graph_progress_received == 0) {
398 /* This is the first block from the file. Check to see if this
399 looks like a mail file. */
400 const char* s = line;
401 const char* end = s + lineLength;
402 while (s < end && IS_SPACE(*s)) s++;
403 if ((end - s) < 20 || !IsEnvelopeLine(s, end - s)) {
404 // char buf[500];
405 // PR_snprintf (buf, sizeof(buf),
406 // XP_GetString(MK_MSG_NON_MAIL_FILE_READ_QUESTION),
407 // folder_name);
408 // else if (!FE_Confirm (m_context, buf))
409 // return NS_MSG_NOT_A_MAIL_FOLDER; /* #### NOT_A_MAIL_FILE */
410 }
411 }
412 // m_graph_progress_received += lineLength;
413
414 // mailbox parser needs to do special stuff when it finds an envelope
415 // after parsing a message body. So do that.
416 if (line[0] == 'F' && IsEnvelopeLine(line, lineLength)) {
417 // **** This used to be
418 // PR_ASSERT (m_parseMsgState->m_state == nsMailboxParseBodyState);
419 // **** I am not sure this is a right thing to do. This happens when
420 // going online, downloading a message while playing back append
421 // draft/template offline operation. We are mixing
422 // nsMailboxParseBodyState &&
423 // nsMailboxParseHeadersState. David I need your help here too. **** jt
424
425 NS_ASSERTION(m_state == nsIMsgParseMailMsgState::ParseBodyState ||
426 m_state == nsIMsgParseMailMsgState::ParseHeadersState,
427 "invalid parse state"); /* else folder corrupted */
428 OnNewMessage(nullptr);
429 nsresult rv = StartNewEnvelope(line, lineLength);
430 NS_ASSERTION(NS_SUCCEEDED(rv), " error starting envelope parsing mailbox");
431 // at the start of each new message, update the progress bar
432 UpdateProgressPercent();
433 return rv;
434 }
435
436 // otherwise, the message parser can handle it completely.
437 if (m_mailDB != nullptr) // if no DB, do we need to parse at all?
438 return ParseFolderLine(line, lineLength);
439
440 return NS_ERROR_NULL_POINTER; // need to error out if we don't have a db.
441 }
442
ReleaseFolderLock()443 void nsMsgMailboxParser::ReleaseFolderLock() {
444 nsresult result;
445 nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
446 if (!folder) return;
447 bool haveSemaphore;
448 nsCOMPtr<nsISupports> supports =
449 do_QueryInterface(static_cast<nsIMsgParseMailMsgState*>(this));
450 result = folder->TestSemaphore(supports, &haveSemaphore);
451 if (NS_SUCCEEDED(result) && haveSemaphore)
452 (void)folder->ReleaseSemaphore(supports);
453 }
454
AcquireFolderLock()455 nsresult nsMsgMailboxParser::AcquireFolderLock() {
456 nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_folder);
457 if (!folder) return NS_ERROR_NULL_POINTER;
458 nsCOMPtr<nsISupports> supports = do_QueryObject(this);
459 return folder->AcquireSemaphore(supports);
460 }
461
NS_IMPL_ISUPPORTS(nsParseMailMessageState,nsIMsgParseMailMsgState,nsIDBChangeListener)462 NS_IMPL_ISUPPORTS(nsParseMailMessageState, nsIMsgParseMailMsgState,
463 nsIDBChangeListener)
464
465 nsParseMailMessageState::nsParseMailMessageState() {
466 m_position = 0;
467 m_new_key = nsMsgKey_None;
468 m_IgnoreXMozillaStatus = false;
469 m_state = nsIMsgParseMailMsgState::ParseBodyState;
470
471 // setup handling of custom db headers, headers that are added to .msf files
472 // as properties of the nsMsgHdr objects, controlled by the
473 // pref mailnews.customDBHeaders, a space-delimited list of headers.
474 // E.g., if mailnews.customDBHeaders is "X-Spam-Score", and we're parsing
475 // a mail message with the X-Spam-Score header, we'll set the
476 // "x-spam-score" property of nsMsgHdr to the value of the header.
477 m_customDBHeaderValues = nullptr;
478 nsCString customDBHeaders; // not shown in search UI
479 nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
480 if (pPrefBranch) {
481 pPrefBranch->GetCharPref("mailnews.customDBHeaders", customDBHeaders);
482 ToLowerCase(customDBHeaders);
483 if (customDBHeaders.Find("content-base") == -1)
484 customDBHeaders.InsertLiteral("content-base ", 0);
485 ParseString(customDBHeaders, ' ', m_customDBHeaders);
486
487 // now add customHeaders
488 nsCString customHeadersString; // shown in search UI
489 nsTArray<nsCString> customHeadersArray;
490 pPrefBranch->GetCharPref("mailnews.customHeaders", customHeadersString);
491 ToLowerCase(customHeadersString);
492 customHeadersString.StripWhitespace();
493 ParseString(customHeadersString, ':', customHeadersArray);
494 for (uint32_t i = 0; i < customHeadersArray.Length(); i++) {
495 if (!m_customDBHeaders.Contains(customHeadersArray[i]))
496 m_customDBHeaders.AppendElement(customHeadersArray[i]);
497 }
498
499 if (m_customDBHeaders.Length()) {
500 m_customDBHeaderValues =
501 new struct message_header[m_customDBHeaders.Length()];
502 if (!m_customDBHeaderValues) m_customDBHeaders.Clear();
503 }
504 }
505 Clear();
506 }
507
~nsParseMailMessageState()508 nsParseMailMessageState::~nsParseMailMessageState() {
509 ClearAggregateHeader(m_toList);
510 ClearAggregateHeader(m_ccList);
511 delete[] m_customDBHeaderValues;
512 }
513
Init(uint64_t fileposition)514 void nsParseMailMessageState::Init(uint64_t fileposition) {
515 m_state = nsIMsgParseMailMsgState::ParseBodyState;
516 m_position = fileposition;
517 m_newMsgHdr = nullptr;
518 }
519
Clear()520 NS_IMETHODIMP nsParseMailMessageState::Clear() {
521 m_message_id.length = 0;
522 m_references.length = 0;
523 m_date.length = 0;
524 m_delivery_date.length = 0;
525 m_from.length = 0;
526 m_sender.length = 0;
527 m_newsgroups.length = 0;
528 m_subject.length = 0;
529 m_status.length = 0;
530 m_mozstatus.length = 0;
531 m_mozstatus2.length = 0;
532 m_envelope_from.length = 0;
533 m_envelope_date.length = 0;
534 m_priority.length = 0;
535 m_keywords.length = 0;
536 m_mdn_dnt.length = 0;
537 m_return_path.length = 0;
538 m_account_key.length = 0;
539 m_in_reply_to.length = 0;
540 m_replyTo.length = 0;
541 m_content_type.length = 0;
542 m_mdn_original_recipient.length = 0;
543 m_bccList.length = 0;
544 m_body_lines = 0;
545 m_lastLineBlank = 0;
546 m_newMsgHdr = nullptr;
547 m_envelope_pos = 0;
548 m_new_key = nsMsgKey_None;
549 ClearAggregateHeader(m_toList);
550 ClearAggregateHeader(m_ccList);
551 m_headers.ResetWritePos();
552 m_envelope.ResetWritePos();
553 m_receivedTime = 0;
554 m_receivedValue.Truncate();
555 for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++) {
556 m_customDBHeaderValues[i].length = 0;
557 }
558 m_headerstartpos = 0;
559 return NS_OK;
560 }
561
SetState(nsMailboxParseState aState)562 NS_IMETHODIMP nsParseMailMessageState::SetState(nsMailboxParseState aState) {
563 m_state = aState;
564 return NS_OK;
565 }
566
GetState(nsMailboxParseState * aState)567 NS_IMETHODIMP nsParseMailMessageState::GetState(nsMailboxParseState* aState) {
568 if (!aState) return NS_ERROR_NULL_POINTER;
569
570 *aState = m_state;
571 return NS_OK;
572 }
573
574 NS_IMETHODIMP
GetEnvelopePos(uint64_t * aEnvelopePos)575 nsParseMailMessageState::GetEnvelopePos(uint64_t* aEnvelopePos) {
576 NS_ENSURE_ARG_POINTER(aEnvelopePos);
577
578 *aEnvelopePos = m_envelope_pos;
579 return NS_OK;
580 }
581
SetEnvelopePos(uint64_t aEnvelopePos)582 NS_IMETHODIMP nsParseMailMessageState::SetEnvelopePos(uint64_t aEnvelopePos) {
583 m_envelope_pos = aEnvelopePos;
584 m_position = m_envelope_pos;
585 m_headerstartpos = m_position;
586 return NS_OK;
587 }
588
GetNewMsgHdr(nsIMsgDBHdr ** aMsgHeader)589 NS_IMETHODIMP nsParseMailMessageState::GetNewMsgHdr(nsIMsgDBHdr** aMsgHeader) {
590 NS_ENSURE_ARG_POINTER(aMsgHeader);
591 NS_IF_ADDREF(*aMsgHeader = m_newMsgHdr);
592 return m_newMsgHdr ? NS_OK : NS_ERROR_NULL_POINTER;
593 }
594
SetNewMsgHdr(nsIMsgDBHdr * aMsgHeader)595 NS_IMETHODIMP nsParseMailMessageState::SetNewMsgHdr(nsIMsgDBHdr* aMsgHeader) {
596 m_newMsgHdr = aMsgHeader;
597 return NS_OK;
598 }
599
ParseAFolderLine(const char * line,uint32_t lineLength)600 NS_IMETHODIMP nsParseMailMessageState::ParseAFolderLine(const char* line,
601 uint32_t lineLength) {
602 ParseFolderLine(line, lineLength);
603 return NS_OK;
604 }
605
ParseFolderLine(const char * line,uint32_t lineLength)606 nsresult nsParseMailMessageState::ParseFolderLine(const char* line,
607 uint32_t lineLength) {
608 nsresult rv;
609
610 if (m_state == nsIMsgParseMailMsgState::ParseHeadersState) {
611 if (EMPTY_MESSAGE_LINE(line)) {
612 /* End of headers. Now parse them. */
613 rv = ParseHeaders();
614 NS_ASSERTION(NS_SUCCEEDED(rv), "error parsing headers parsing mailbox");
615 NS_ENSURE_SUCCESS(rv, rv);
616
617 rv = FinalizeHeaders();
618 NS_ASSERTION(NS_SUCCEEDED(rv),
619 "error finalizing headers parsing mailbox");
620 NS_ENSURE_SUCCESS(rv, rv);
621
622 m_state = nsIMsgParseMailMsgState::ParseBodyState;
623 } else {
624 /* Otherwise, this line belongs to a header. So append it to the
625 header data, and stay in MBOX `MIME_PARSE_HEADERS' state.
626 */
627 m_headers.AppendBuffer(line, lineLength);
628 }
629 } else if (m_state == nsIMsgParseMailMsgState::ParseBodyState) {
630 m_body_lines++;
631 // See comment in msgCore.h for why we use `IS_MSG_LINEBREAK` rather than
632 // just comparing `line` to `MSG_LINEBREAK`.
633 m_lastLineBlank = IS_MSG_LINEBREAK(line);
634 }
635
636 m_position += lineLength;
637
638 return NS_OK;
639 }
640
SetMailDB(nsIMsgDatabase * mailDB)641 NS_IMETHODIMP nsParseMailMessageState::SetMailDB(nsIMsgDatabase* mailDB) {
642 m_mailDB = mailDB;
643 return NS_OK;
644 }
645
SetBackupMailDB(nsIMsgDatabase * aBackupMailDB)646 NS_IMETHODIMP nsParseMailMessageState::SetBackupMailDB(
647 nsIMsgDatabase* aBackupMailDB) {
648 m_backupMailDB = aBackupMailDB;
649 if (m_backupMailDB) m_backupMailDB->AddListener(this);
650 return NS_OK;
651 }
652
SetNewKey(nsMsgKey aKey)653 NS_IMETHODIMP nsParseMailMessageState::SetNewKey(nsMsgKey aKey) {
654 m_new_key = aKey;
655 return NS_OK;
656 }
657
658 /* #define STRICT_ENVELOPE */
659
IsEnvelopeLine(const char * buf,int32_t buf_size)660 bool nsParseMailMessageState::IsEnvelopeLine(const char* buf,
661 int32_t buf_size) {
662 #ifdef STRICT_ENVELOPE
663 /* The required format is
664 From jwz Fri Jul 1 09:13:09 1994
665 But we should also allow at least:
666 From jwz Fri, Jul 01 09:13:09 1994
667 From jwz Fri Jul 1 09:13:09 1994 PST
668 From jwz Fri Jul 1 09:13:09 1994 (+0700)
669
670 We can't easily call XP_ParseTimeString() because the string is not
671 null terminated (ok, we could copy it after a quick check...) but
672 XP_ParseTimeString() may be too lenient for our purposes.
673
674 DANGER!! The released version of 2.0b1 was (on some systems,
675 some Unix, some NT, possibly others) writing out envelope lines
676 like "From - 10/13/95 11:22:33" which STRICT_ENVELOPE will reject!
677 */
678 const char *date, *end;
679
680 if (buf_size < 29) return false;
681 if (*buf != 'F') return false;
682 if (strncmp(buf, "From ", 5)) return false;
683
684 end = buf + buf_size;
685 date = buf + 5;
686
687 /* Skip horizontal whitespace between "From " and user name. */
688 while ((*date == ' ' || *date == '\t') && date < end) date++;
689
690 /* If at the end, it doesn't match. */
691 if (IS_SPACE(*date) || date == end) return false;
692
693 /* Skip over user name. */
694 while (!IS_SPACE(*date) && date < end) date++;
695
696 /* Skip horizontal whitespace between user name and date. */
697 while ((*date == ' ' || *date == '\t') && date < end) date++;
698
699 /* Don't want this to be localized. */
700 # define TMP_ISALPHA(x) \
701 (((x) >= 'A' && (x) <= 'Z') || ((x) >= 'a' && (x) <= 'z'))
702
703 /* take off day-of-the-week. */
704 if (date >= end - 3) return false;
705 if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2]))
706 return false;
707 date += 3;
708 /* Skip horizontal whitespace (and commas) between dotw and month. */
709 if (*date != ' ' && *date != '\t' && *date != ',') return false;
710 while ((*date == ' ' || *date == '\t' || *date == ',') && date < end) date++;
711
712 /* take off month. */
713 if (date >= end - 3) return false;
714 if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2]))
715 return false;
716 date += 3;
717 /* Skip horizontal whitespace between month and dotm. */
718 if (date == end || (*date != ' ' && *date != '\t')) return false;
719 while ((*date == ' ' || *date == '\t') && date < end) date++;
720
721 /* Skip over digits and whitespace. */
722 while (((*date >= '0' && *date <= '9') || *date == ' ' || *date == '\t') &&
723 date < end)
724 date++;
725 /* Next character should be a colon. */
726 if (date >= end || *date != ':') return false;
727
728 /* Ok, that ought to be enough... */
729
730 # undef TMP_ISALPHA
731
732 #else /* !STRICT_ENVELOPE */
733
734 if (buf_size < 5) return false;
735 if (*buf != 'F') return false;
736 if (strncmp(buf, "From ", 5)) return false;
737
738 #endif /* !STRICT_ENVELOPE */
739
740 return true;
741 }
742
743 // We've found the start of the next message, so finish this one off.
FinishHeader()744 NS_IMETHODIMP nsParseMailMessageState::FinishHeader() {
745 if (m_newMsgHdr) {
746 m_newMsgHdr->SetMessageOffset(m_envelope_pos);
747 if (m_lastLineBlank) m_body_lines--;
748 m_newMsgHdr->SetMessageSize(m_position - m_envelope_pos - m_lastLineBlank);
749 m_newMsgHdr->SetLineCount(m_body_lines);
750 }
751 return NS_OK;
752 }
753
GetAllHeaders(char ** pHeaders,int32_t * pHeadersSize)754 NS_IMETHODIMP nsParseMailMessageState::GetAllHeaders(char** pHeaders,
755 int32_t* pHeadersSize) {
756 if (!pHeaders || !pHeadersSize) return NS_ERROR_NULL_POINTER;
757 *pHeaders = m_headers.GetBuffer();
758 *pHeadersSize = m_headers.GetBufferPos();
759 return NS_OK;
760 }
761
762 // generate headers as a string, with CRLF between the headers
GetHeaders(char ** pHeaders)763 NS_IMETHODIMP nsParseMailMessageState::GetHeaders(char** pHeaders) {
764 NS_ENSURE_ARG_POINTER(pHeaders);
765 nsCString crlfHeaders;
766 char* curHeader = m_headers.GetBuffer();
767 for (uint32_t headerPos = 0; headerPos < m_headers.GetBufferPos();) {
768 crlfHeaders.Append(curHeader);
769 crlfHeaders.Append(CRLF);
770 int32_t headerLen = strlen(curHeader);
771 curHeader += headerLen + 1;
772 headerPos += headerLen + 1;
773 }
774 *pHeaders = ToNewCString(crlfHeaders);
775 return NS_OK;
776 }
777
GetNextHeaderInAggregate(nsTArray<struct message_header * > & list)778 struct message_header* nsParseMailMessageState::GetNextHeaderInAggregate(
779 nsTArray<struct message_header*>& list) {
780 // When parsing a message with multiple To or CC header lines, we're storing
781 // each line in a list, where the list represents the "aggregate" total of all
782 // the header. Here we get a new line for the list
783
784 struct message_header* header =
785 (struct message_header*)PR_Calloc(1, sizeof(struct message_header));
786 list.AppendElement(header);
787 return header;
788 }
789
GetAggregateHeader(nsTArray<struct message_header * > & list,struct message_header * outHeader)790 void nsParseMailMessageState::GetAggregateHeader(
791 nsTArray<struct message_header*>& list, struct message_header* outHeader) {
792 // When parsing a message with multiple To or CC header lines, we're storing
793 // each line in a list, where the list represents the "aggregate" total of all
794 // the header. Here we combine all the lines together, as though they were
795 // really all found on the same line
796
797 struct message_header* header = nullptr;
798 int length = 0;
799 size_t i;
800
801 // Count up the bytes required to allocate the aggregated header
802 for (i = 0; i < list.Length(); i++) {
803 header = list.ElementAt(i);
804 length += (header->length + 1); //+ for ","
805 }
806
807 if (length > 0) {
808 char* value = (char*)PR_CALLOC(length + 1); //+1 for null term
809 if (value) {
810 // Catenate all the To lines together, separated by commas
811 value[0] = '\0';
812 size_t size = list.Length();
813 for (i = 0; i < size; i++) {
814 header = list.ElementAt(i);
815 PL_strncat(value, header->value, header->length);
816 if (i + 1 < size) PL_strcat(value, ",");
817 }
818 outHeader->length = length;
819 outHeader->value = value;
820 }
821 } else {
822 outHeader->length = 0;
823 outHeader->value = nullptr;
824 }
825 }
826
ClearAggregateHeader(nsTArray<struct message_header * > & list)827 void nsParseMailMessageState::ClearAggregateHeader(
828 nsTArray<struct message_header*>& list) {
829 // Reset the aggregate headers. Free only the message_header struct since
830 // we don't own the value pointer
831
832 for (size_t i = 0; i < list.Length(); i++) PR_Free(list.ElementAt(i));
833 list.Clear();
834 }
835
836 // We've found a new envelope to parse.
StartNewEnvelope(const char * line,uint32_t lineLength)837 nsresult nsParseMailMessageState::StartNewEnvelope(const char* line,
838 uint32_t lineLength) {
839 m_envelope_pos = m_position;
840 m_state = nsIMsgParseMailMsgState::ParseHeadersState;
841 m_position += lineLength;
842 m_headerstartpos = m_position;
843 return ParseEnvelope(line, lineLength);
844 }
845
846 /* largely lifted from mimehtml.c, which does similar parsing, sigh...
847 */
ParseHeaders()848 nsresult nsParseMailMessageState::ParseHeaders() {
849 char* buf = m_headers.GetBuffer();
850 uint32_t buf_length = m_headers.GetBufferPos();
851 if (buf_length == 0) {
852 // No header of an expected type is present. Consider this a successful
853 // parse so email still shows on summary and can be accessed and deleted.
854 return NS_OK;
855 }
856 char* buf_end = buf + buf_length;
857 if (!(buf_length > 1 &&
858 (buf[buf_length - 1] == '\r' || buf[buf_length - 1] == '\n'))) {
859 NS_WARNING("Header text should always end in a newline");
860 return NS_ERROR_UNEXPECTED;
861 }
862 while (buf < buf_end) {
863 char* colon = PL_strnchr(buf, ':', buf_end - buf);
864 char* value = 0;
865 struct message_header* header = 0;
866 struct message_header receivedBy;
867
868 if (!colon) break;
869
870 nsDependentCSubstring headerStr(buf, colon);
871 ToLowerCase(headerStr);
872
873 // Obtain firstChar in headerStr. But if headerStr is empty, just set it to
874 // the colon. This is needed because First() asserts on an empty string.
875 char firstChar = !headerStr.IsEmpty() ? headerStr.First() : *colon;
876
877 // See RFC 5322 section 3.6 for min-max number for given header.
878 // If multiple headers exist we need to make sure to use the first one.
879
880 switch (firstChar) {
881 case 'b':
882 if (headerStr.EqualsLiteral("bcc") && !m_bccList.length)
883 header = &m_bccList;
884 break;
885 case 'c':
886 if (headerStr.EqualsLiteral("cc")) // XXX: RFC 5322 says it's 0 or 1.
887 header = GetNextHeaderInAggregate(m_ccList);
888 else if (headerStr.EqualsLiteral("content-type"))
889 header = &m_content_type;
890 break;
891 case 'd':
892 if (headerStr.EqualsLiteral("date") && !m_date.length)
893 header = &m_date;
894 else if (headerStr.EqualsLiteral("disposition-notification-to"))
895 header = &m_mdn_dnt;
896 else if (headerStr.EqualsLiteral("delivery-date"))
897 header = &m_delivery_date;
898 break;
899 case 'f':
900 if (headerStr.EqualsLiteral("from") && !m_from.length) {
901 header = &m_from;
902 }
903 break;
904 case 'i':
905 if (headerStr.EqualsLiteral("in-reply-to") && !m_in_reply_to.length)
906 header = &m_in_reply_to;
907 break;
908 case 'm':
909 if (headerStr.EqualsLiteral("message-id") && !m_message_id.length)
910 header = &m_message_id;
911 break;
912 case 'n':
913 if (headerStr.EqualsLiteral("newsgroups")) header = &m_newsgroups;
914 break;
915 case 'o':
916 if (headerStr.EqualsLiteral("original-recipient"))
917 header = &m_mdn_original_recipient;
918 break;
919 case 'p':
920 // we could very well care what the priority header was when we
921 // remember its value. If so, need to remember it here. Also,
922 // different priority headers can appear in the same message,
923 // but we only remember the last one that we see. Applies also to
924 // x-priority checked below.
925 if (headerStr.EqualsLiteral("priority")) header = &m_priority;
926 break;
927 case 'r':
928 if (headerStr.EqualsLiteral("references") && !m_references.length)
929 header = &m_references;
930 else if (headerStr.EqualsLiteral("return-path"))
931 header = &m_return_path;
932 // treat conventional Return-Receipt-To as MDN
933 // Disposition-Notification-To
934 else if (headerStr.EqualsLiteral("return-receipt-to"))
935 header = &m_mdn_dnt;
936 else if (headerStr.EqualsLiteral("reply-to") && !m_replyTo.length)
937 header = &m_replyTo;
938 else if (headerStr.EqualsLiteral("received")) {
939 header = &receivedBy;
940 header->length = 0;
941 }
942 break;
943 case 's':
944 if (headerStr.EqualsLiteral("subject") && !m_subject.length)
945 header = &m_subject;
946 else if (headerStr.EqualsLiteral("sender") && !m_sender.length)
947 header = &m_sender;
948 else if (headerStr.EqualsLiteral("status"))
949 header = &m_status;
950 break;
951 case 't':
952 if (headerStr.EqualsLiteral("to")) // XXX: RFC 5322 says it's 0 or 1.
953 header = GetNextHeaderInAggregate(m_toList);
954 break;
955 case 'x':
956 if (headerStr.EqualsIgnoreCase(X_MOZILLA_STATUS2) &&
957 !m_IgnoreXMozillaStatus && !m_mozstatus2.length)
958 header = &m_mozstatus2;
959 else if (headerStr.EqualsIgnoreCase(X_MOZILLA_STATUS) &&
960 !m_IgnoreXMozillaStatus && !m_mozstatus.length)
961 header = &m_mozstatus;
962 else if (headerStr.EqualsIgnoreCase(HEADER_X_MOZILLA_ACCOUNT_KEY) &&
963 !m_account_key.length)
964 header = &m_account_key;
965 else if (headerStr.EqualsLiteral("x-priority")) // See case 'p' above.
966 header = &m_priority;
967 else if (headerStr.EqualsIgnoreCase(HEADER_X_MOZILLA_KEYWORDS) &&
968 !m_keywords.length)
969 header = &m_keywords;
970 break;
971 }
972
973 if (!header && m_customDBHeaders.Length()) {
974 size_t customHeaderIndex = m_customDBHeaders.IndexOf(headerStr);
975 if (customHeaderIndex != m_customDBHeaders.NoIndex)
976 header = &m_customDBHeaderValues[customHeaderIndex];
977 }
978
979 buf = colon + 1;
980 // We will be shuffling downwards, so this is our insertion point.
981 char* bufWrite = buf;
982
983 SEARCH_NEWLINE:
984 // move past any non terminating characters, rewriting them if folding white
985 // space exists
986 while (buf < buf_end && *buf != '\r' && *buf != '\n') {
987 if (buf != bufWrite) *bufWrite = *buf;
988 buf++;
989 bufWrite++;
990 }
991
992 // Look for folding, so CRLF, CR or LF followed by space or tab.
993 if ((buf + 2 < buf_end && (buf[0] == '\r' && buf[1] == '\n') &&
994 (buf[2] == ' ' || buf[2] == '\t')) ||
995 (buf + 1 < buf_end && (buf[0] == '\r' || buf[0] == '\n') &&
996 (buf[1] == ' ' || buf[1] == '\t'))) {
997 // Remove trailing spaces at the "write position" and add a single
998 // folding space.
999 while (*(bufWrite - 1) == ' ' || *(bufWrite - 1) == '\t') bufWrite--;
1000 *(bufWrite++) = ' ';
1001
1002 // Skip CRLF, CR+space or LF+space ...
1003 buf += 2;
1004
1005 // ... and skip leading spaces in that line.
1006 while (buf < buf_end && (*buf == ' ' || *buf == '\t')) buf++;
1007
1008 // If we get here, the message headers ended in an empty line, like:
1009 // To: blah blah blah<CR><LF> <CR><LF>[end of buffer]. The code below
1010 // requires buf to land on a newline to properly null-terminate the
1011 // string, so back up a tad so that it is pointing to one.
1012 if (buf == buf_end) {
1013 --buf;
1014 MOZ_ASSERT(*buf == '\n' || *buf == '\r',
1015 "Header text should always end in a newline.");
1016 }
1017 goto SEARCH_NEWLINE;
1018 }
1019
1020 if (header) {
1021 value = colon + 1;
1022 // eliminate trailing blanks after the colon
1023 while (value < bufWrite && (*value == ' ' || *value == '\t')) value++;
1024
1025 header->value = value;
1026 header->length = bufWrite - value;
1027 if (header->length < 0) header->length = 0;
1028 }
1029 if (*buf == '\r' || *buf == '\n') {
1030 char* last = bufWrite;
1031 char* saveBuf = buf;
1032 if (*buf == '\r' && buf + 1 < buf_end && buf[1] == '\n') buf++;
1033 buf++;
1034 // null terminate the left-over slop so we don't confuse msg filters.
1035 *saveBuf = 0;
1036 *last = 0; /* short-circuit const, and null-terminate header. */
1037 }
1038
1039 if (header) {
1040 /* More const short-circuitry... */
1041 /* strip trailing whitespace */
1042 while (header->length > 0 && IS_SPACE(header->value[header->length - 1]))
1043 ((char*)header->value)[--header->length] = 0;
1044 if (header == &receivedBy) {
1045 if (m_receivedTime == 0) {
1046 // parse Received: header for date.
1047 // We trust the first header as that is closest to recipient,
1048 // and less likely to be spoofed.
1049 nsAutoCString receivedHdr(header->value, header->length);
1050 int32_t lastSemicolon = receivedHdr.RFindChar(';');
1051 if (lastSemicolon != -1) {
1052 nsAutoCString receivedDate;
1053 receivedDate = Substring(receivedHdr, lastSemicolon + 1);
1054 receivedDate.Trim(" \t\b\r\n");
1055 PRTime resultTime;
1056 if (PR_ParseTimeString(receivedDate.get(), false, &resultTime) ==
1057 PR_SUCCESS)
1058 m_receivedTime = resultTime;
1059 else
1060 NS_WARNING("PR_ParseTimeString failed in ParseHeaders().");
1061 }
1062 }
1063 // Someone might want the received header saved.
1064 if (m_customDBHeaders.Length()) {
1065 if (m_customDBHeaders.Contains("received"_ns)) {
1066 if (!m_receivedValue.IsEmpty()) m_receivedValue.Append(' ');
1067 m_receivedValue.Append(header->value, header->length);
1068 }
1069 }
1070 }
1071
1072 MOZ_ASSERT(header->value[header->length] == 0,
1073 "Non-null-terminated strings cause very, very bad problems");
1074 }
1075 }
1076 return NS_OK;
1077 }
1078
1079 // Try and glean a sender and/or timestamp from the "From " line, to use
1080 // as last-ditch fallbacks if the message is missing "From"/"Sender" or
1081 // "Date" headers.
ParseEnvelope(const char * line,uint32_t line_size)1082 nsresult nsParseMailMessageState::ParseEnvelope(const char* line,
1083 uint32_t line_size) {
1084 const char* end;
1085 char* s;
1086
1087 m_envelope.AppendBuffer(line, line_size);
1088 end = m_envelope.GetBuffer() + line_size;
1089 s = m_envelope.GetBuffer() + 5;
1090
1091 while (s < end && IS_SPACE(*s)) s++;
1092 m_envelope_from.value = s;
1093 while (s < end && !IS_SPACE(*s)) s++;
1094 m_envelope_from.length = s - m_envelope_from.value;
1095
1096 while (s < end && IS_SPACE(*s)) s++;
1097 m_envelope_date.value = s;
1098 m_envelope_date.length = (uint16_t)(line_size - (s - m_envelope.GetBuffer()));
1099
1100 while (m_envelope_date.length > 0 &&
1101 IS_SPACE(m_envelope_date.value[m_envelope_date.length - 1]))
1102 m_envelope_date.length--;
1103
1104 /* #### short-circuit const */
1105 ((char*)m_envelope_from.value)[m_envelope_from.length] = 0;
1106 ((char*)m_envelope_date.value)[m_envelope_date.length] = 0;
1107
1108 return NS_OK;
1109 }
1110
InternSubject(struct message_header * header)1111 nsresult nsParseMailMessageState::InternSubject(struct message_header* header) {
1112 if (!header || header->length == 0) {
1113 m_newMsgHdr->SetSubject("");
1114 return NS_OK;
1115 }
1116
1117 const char* key = header->value;
1118
1119 uint32_t flags;
1120 (void)m_newMsgHdr->GetFlags(&flags);
1121 /* strip "Re: " */
1122 /**
1123 We trust the X-Mozilla-Status line to be the smartest in almost
1124 all things. One exception, however, is the HAS_RE flag. Since
1125 we just parsed the subject header anyway, we expect that parsing
1126 to be smartest. (After all, what if someone just went in and
1127 edited the subject line by hand?)
1128 */
1129 nsCString modifiedSubject;
1130 bool strippedRE = NS_MsgStripRE(nsDependentCString(key), modifiedSubject);
1131 if (strippedRE)
1132 flags |= nsMsgMessageFlags::HasRe;
1133 else
1134 flags &= ~nsMsgMessageFlags::HasRe;
1135 m_newMsgHdr->SetFlags(flags); // this *does not* update the mozilla-status
1136 // header in the local folder
1137
1138 m_newMsgHdr->SetSubject(strippedRE ? modifiedSubject.get() : key);
1139
1140 return NS_OK;
1141 }
1142
1143 // we've reached the end of the envelope, and need to turn all our accumulated
1144 // message_headers into a single nsIMsgDBHdr to store in a database.
FinalizeHeaders()1145 nsresult nsParseMailMessageState::FinalizeHeaders() {
1146 nsresult rv;
1147 struct message_header* sender;
1148 struct message_header* recipient;
1149 struct message_header* subject;
1150 struct message_header* id;
1151 struct message_header* inReplyTo;
1152 struct message_header* replyTo;
1153 struct message_header* references;
1154 struct message_header* date;
1155 struct message_header* deliveryDate;
1156 struct message_header* statush;
1157 struct message_header* mozstatus;
1158 struct message_header* mozstatus2;
1159 struct message_header* priority;
1160 struct message_header* keywords;
1161 struct message_header* account_key;
1162 struct message_header* ccList;
1163 struct message_header* bccList;
1164 struct message_header* mdn_dnt;
1165 struct message_header md5_header;
1166 struct message_header* content_type;
1167 char md5_data[50];
1168
1169 uint32_t flags = 0;
1170 uint32_t deltaToMozStatus = 0;
1171 nsMsgPriorityValue priorityFlags = nsMsgPriority::notSet;
1172 uint32_t labelFlags = 0;
1173
1174 if (!m_mailDB) // if we don't have a valid db, skip the header.
1175 return NS_OK;
1176
1177 struct message_header to;
1178 GetAggregateHeader(m_toList, &to);
1179 struct message_header cc;
1180 GetAggregateHeader(m_ccList, &cc);
1181 // we don't aggregate bcc, as we only generate it locally,
1182 // and we don't use multiple lines
1183
1184 // clang-format off
1185 sender = (m_from.length ? &m_from :
1186 m_sender.length ? &m_sender :
1187 m_envelope_from.length ? &m_envelope_from : 0);
1188 recipient = (to.length ? &to :
1189 cc.length ? &cc :
1190 m_newsgroups.length ? &m_newsgroups : 0);
1191 ccList = (cc.length ? &cc : 0);
1192 bccList = (m_bccList.length ? &m_bccList : 0);
1193 subject = (m_subject.length ? &m_subject : 0);
1194 id = (m_message_id.length ? &m_message_id : 0);
1195 references = (m_references.length ? &m_references : 0);
1196 statush = (m_status.length ? &m_status : 0);
1197 mozstatus = (m_mozstatus.length ? &m_mozstatus : 0);
1198 mozstatus2 = (m_mozstatus2.length ? &m_mozstatus2 : 0);
1199 date = (m_date.length ? &m_date :
1200 m_envelope_date.length ? &m_envelope_date : 0);
1201 deliveryDate = (m_delivery_date.length ? &m_delivery_date : 0);
1202 priority = (m_priority.length ? &m_priority : 0);
1203 keywords = (m_keywords.length ? &m_keywords : 0);
1204 mdn_dnt = (m_mdn_dnt.length ? &m_mdn_dnt : 0);
1205 inReplyTo = (m_in_reply_to.length ? &m_in_reply_to : 0);
1206 replyTo = (m_replyTo.length ? &m_replyTo : 0);
1207 content_type = (m_content_type.length ? &m_content_type : 0);
1208 account_key = (m_account_key.length ? &m_account_key : 0);
1209 // clang-format on
1210
1211 if (mozstatus) {
1212 if (mozstatus->length == 4) {
1213 NS_ASSERTION(MsgIsHex(mozstatus->value, 4),
1214 "Expected 4 hex digits for flags.");
1215 flags = MsgUnhex(mozstatus->value, 4);
1216 // strip off and remember priority bits.
1217 flags &= ~nsMsgMessageFlags::RuntimeOnly;
1218 priorityFlags =
1219 (nsMsgPriorityValue)((flags & nsMsgMessageFlags::Priorities) >> 13);
1220 flags &= ~nsMsgMessageFlags::Priorities;
1221 }
1222
1223 deltaToMozStatus =
1224 m_headerstartpos + (mozstatus->value - m_headers.GetBuffer()) -
1225 (X_MOZILLA_STATUS_LEN + 2 /* for ": " */) - m_envelope_pos;
1226 }
1227
1228 if (mozstatus2) {
1229 uint32_t flags2 = 0;
1230 sscanf(mozstatus2->value, " %x ", &flags2);
1231 flags |= flags2;
1232 }
1233
1234 if (!(flags & nsMsgMessageFlags::Expunged)) // message was deleted, don't
1235 // bother creating a hdr.
1236 {
1237 // We'll need the message id first to recover data from the backup database
1238 nsAutoCString rawMsgId;
1239 /* Take off <> around message ID. */
1240 if (id) {
1241 if (id->length > 0 && id->value[0] == '<') {
1242 id->length--;
1243 id->value++;
1244 }
1245
1246 NS_WARNING_ASSERTION(id->length > 0,
1247 "id->length failure in FinalizeHeaders().");
1248
1249 if (id->length > 0 && id->value[id->length - 1] == '>')
1250 /* generate a new null-terminated string without the final > */
1251 rawMsgId.Assign(id->value, id->length - 1);
1252 else
1253 rawMsgId.Assign(id->value);
1254 }
1255
1256 /*
1257 * Try to copy the data from the backup database, referencing the MessageID
1258 * If that fails, just create a new header
1259 */
1260 nsCOMPtr<nsIMsgDBHdr> oldHeader;
1261 nsresult ret = NS_OK;
1262
1263 if (m_backupMailDB && !rawMsgId.IsEmpty())
1264 ret = m_backupMailDB->GetMsgHdrForMessageID(rawMsgId.get(),
1265 getter_AddRefs(oldHeader));
1266
1267 // m_new_key is set in nsImapMailFolder::ParseAdoptedHeaderLine to be
1268 // the UID of the message, so that the key can get created as UID. That of
1269 // course is extremely confusing, and we really need to clean that up. We
1270 // really should not conflate the meaning of envelope position, key, and
1271 // UID.
1272 if (NS_SUCCEEDED(ret) && oldHeader)
1273 ret = m_mailDB->CopyHdrFromExistingHdr(m_new_key, oldHeader, false,
1274 getter_AddRefs(m_newMsgHdr));
1275 else if (!m_newMsgHdr) {
1276 // Should assert that this is not a local message
1277 ret = m_mailDB->CreateNewHdr(m_new_key, getter_AddRefs(m_newMsgHdr));
1278 }
1279
1280 if (NS_SUCCEEDED(ret) && m_newMsgHdr) {
1281 uint32_t origFlags;
1282 (void)m_newMsgHdr->GetFlags(&origFlags);
1283 if (origFlags & nsMsgMessageFlags::HasRe)
1284 flags |= nsMsgMessageFlags::HasRe;
1285 else
1286 flags &= ~nsMsgMessageFlags::HasRe;
1287
1288 flags &=
1289 ~nsMsgMessageFlags::Offline; // don't keep nsMsgMessageFlags::Offline
1290 // for local msgs
1291 if (mdn_dnt && !(origFlags & nsMsgMessageFlags::Read) &&
1292 !(origFlags & nsMsgMessageFlags::MDNReportSent) &&
1293 !(flags & nsMsgMessageFlags::MDNReportSent))
1294 flags |= nsMsgMessageFlags::MDNReportNeeded;
1295
1296 m_newMsgHdr->SetFlags(flags);
1297 if (priorityFlags != nsMsgPriority::notSet)
1298 m_newMsgHdr->SetPriority(priorityFlags);
1299
1300 // if we have a reply to header, and it's different from the from: header,
1301 // set the "replyTo" attribute on the msg hdr.
1302 if (replyTo && (!sender || replyTo->length != sender->length ||
1303 strncmp(replyTo->value, sender->value, sender->length)))
1304 m_newMsgHdr->SetStringProperty("replyTo", replyTo->value);
1305 // convert the flag values (0xE000000) to label values (0-5)
1306 if (mozstatus2) // only do this if we have a mozstatus2 header
1307 {
1308 labelFlags = ((flags & nsMsgMessageFlags::Labels) >> 25);
1309 m_newMsgHdr->SetLabel(labelFlags);
1310 }
1311 NS_ASSERTION(!mozstatus || deltaToMozStatus < 0xffff,
1312 "unexpected deltaToMozStatus");
1313 if (mozstatus &&
1314 deltaToMozStatus < 0xffff) { /* Only use if fits in 16 bits. */
1315 m_newMsgHdr->SetStatusOffset((uint16_t)deltaToMozStatus);
1316 if (!m_IgnoreXMozillaStatus) { // imap doesn't care about
1317 // X-MozillaStatus
1318 // TODO: Clarify, why is it necessary to query the value we've just
1319 // set? Does querying trigger some kind of side effect? Or is the
1320 // following assertion check the only need for querying? If it seems
1321 // unnecessary, the following lines should be removed.
1322 uint32_t offset;
1323 (void)m_newMsgHdr->GetStatusOffset(&offset);
1324 NS_ASSERTION(offset < 10000,
1325 "invalid status offset"); /* ### Debugging hack */
1326 }
1327 }
1328 if (sender) m_newMsgHdr->SetAuthor(sender->value);
1329 if (recipient == &m_newsgroups) {
1330 /* In the case where the recipient is a newsgroup, truncate the string
1331 at the first comma. This is used only for presenting the thread
1332 list, and newsgroup lines tend to be long and non-shared, and tend to
1333 bloat the string table. So, by only showing the first newsgroup, we
1334 can reduce memory and file usage at the expense of only showing the
1335 one group in the summary list, and only being able to sort on the
1336 first group rather than the whole list. It's worth it. */
1337 char* ch;
1338 ch = PL_strchr(recipient->value, ',');
1339 if (ch) {
1340 /* generate a new string that terminates before the , */
1341 nsAutoCString firstGroup;
1342 firstGroup.Assign(recipient->value, ch - recipient->value);
1343 m_newMsgHdr->SetRecipients(firstGroup.get());
1344 }
1345 m_newMsgHdr->SetRecipients(recipient->value);
1346 } else if (recipient) {
1347 m_newMsgHdr->SetRecipients(recipient->value);
1348 }
1349 if (ccList) {
1350 m_newMsgHdr->SetCcList(ccList->value);
1351 }
1352
1353 if (bccList) {
1354 m_newMsgHdr->SetBccList(bccList->value);
1355 }
1356
1357 rv = InternSubject(subject);
1358 if (NS_SUCCEEDED(rv)) {
1359 if (!id) {
1360 // what to do about this? we used to do a hash of all the headers...
1361 nsAutoCString hash;
1362 const char* md5_b64 = "dummy.message.id";
1363 nsresult rv;
1364 nsCOMPtr<nsICryptoHash> hasher =
1365 do_CreateInstance("@mozilla.org/security/hash;1", &rv);
1366 if (NS_SUCCEEDED(rv)) {
1367 if (NS_SUCCEEDED(hasher->Init(nsICryptoHash::MD5)) &&
1368 NS_SUCCEEDED(
1369 hasher->Update((const uint8_t*)m_headers.GetBuffer(),
1370 m_headers.GetBufferPos())) &&
1371 NS_SUCCEEDED(hasher->Finish(true, hash)))
1372 md5_b64 = hash.get();
1373 }
1374 PR_snprintf(md5_data, sizeof(md5_data), "<md5:%s>", md5_b64);
1375 md5_header.value = md5_data;
1376 md5_header.length = strlen(md5_data);
1377 id = &md5_header;
1378 }
1379
1380 if (!rawMsgId.IsEmpty())
1381 m_newMsgHdr->SetMessageId(rawMsgId.get());
1382 else
1383 m_newMsgHdr->SetMessageId(id->value);
1384 m_mailDB->UpdatePendingAttributes(m_newMsgHdr);
1385
1386 if (!mozstatus && statush) {
1387 /* Parse a little bit of the Berkeley Mail status header. */
1388 for (const char* s = statush->value; *s; s++) {
1389 uint32_t msgFlags = 0;
1390 (void)m_newMsgHdr->GetFlags(&msgFlags);
1391 switch (*s) {
1392 case 'R':
1393 case 'r':
1394 m_newMsgHdr->SetFlags(msgFlags | nsMsgMessageFlags::Read);
1395 break;
1396 case 'D':
1397 case 'd':
1398 /* msg->flags |= nsMsgMessageFlags::Expunged; ### Is this
1399 * reasonable? */
1400 break;
1401 case 'N':
1402 case 'n':
1403 case 'U':
1404 case 'u':
1405 m_newMsgHdr->SetFlags(msgFlags & ~nsMsgMessageFlags::Read);
1406 break;
1407 default: // Should check for corrupt file.
1408 NS_ERROR("Corrupt file. Should not happen.");
1409 break;
1410 }
1411 }
1412 }
1413
1414 if (account_key != nullptr)
1415 m_newMsgHdr->SetAccountKey(account_key->value);
1416 // use in-reply-to header as references, if there's no references header
1417 if (references != nullptr)
1418 m_newMsgHdr->SetReferences(references->value);
1419 else if (inReplyTo != nullptr)
1420 m_newMsgHdr->SetReferences(inReplyTo->value);
1421
1422 // 'Received' should be as reliable an indicator of the receipt
1423 // date+time as possible, whilst always giving something *from
1424 // the message*. It won't use PR_Now() under any circumstance.
1425 // Therefore, the fall-thru order for 'Received' is:
1426 // Received: -> Delivery-date: -> date
1427 // 'Date' uses:
1428 // date -> 'Received' -> PR_Now()
1429 //
1430 // date is:
1431 // Date: -> m_envelope_date
1432
1433 uint32_t rcvTimeSecs = 0;
1434 PRTime datePRTime = 0;
1435 if (date) {
1436 // Date:
1437 if (PR_ParseTimeString(date->value, false, &datePRTime) ==
1438 PR_SUCCESS) {
1439 // Convert to seconds as default value for 'Received'.
1440 PRTime2Seconds(datePRTime, &rcvTimeSecs);
1441 } else {
1442 NS_WARNING(
1443 "PR_ParseTimeString of date failed in FinalizeHeader().");
1444 }
1445 }
1446 if (m_receivedTime) {
1447 // Upgrade 'Received' to Received: ?
1448 PRTime2Seconds(m_receivedTime, &rcvTimeSecs);
1449 if (datePRTime == 0) datePRTime = m_receivedTime;
1450 } else if (deliveryDate) {
1451 // Upgrade 'Received' to Delivery-date: ?
1452 PRTime resultTime;
1453 if (PR_ParseTimeString(deliveryDate->value, false, &resultTime) ==
1454 PR_SUCCESS) {
1455 PRTime2Seconds(resultTime, &rcvTimeSecs);
1456 if (datePRTime == 0) datePRTime = resultTime;
1457 } else {
1458 // TODO/FIXME: We need to figure out what to do in this case!
1459 NS_WARNING(
1460 "PR_ParseTimeString of delivery date failed in "
1461 "FinalizeHeader().");
1462 }
1463 }
1464 m_newMsgHdr->SetUint32Property("dateReceived", rcvTimeSecs);
1465
1466 if (datePRTime == 0) {
1467 // If there was some problem parsing the Date header *AND* we
1468 // couldn't get a valid envelope date *AND* we couldn't get a valid
1469 // Received: header date, use now as the time.
1470 // This doesn't affect local (POP3) messages, because we use the
1471 // envelope date if there's no Date: header, but it will affect IMAP
1472 // msgs w/o a Date: header or Received: headers.
1473 datePRTime = PR_Now();
1474 }
1475 m_newMsgHdr->SetDate(datePRTime);
1476
1477 if (priority)
1478 m_newMsgHdr->SetPriorityString(priority->value);
1479 else if (priorityFlags == nsMsgPriority::notSet)
1480 m_newMsgHdr->SetPriority(nsMsgPriority::none);
1481 if (keywords) {
1482 // When there are many keywords, some may not have been written
1483 // to the message file, so add extra keywords from the backup
1484 nsAutoCString oldKeywords;
1485 m_newMsgHdr->GetStringProperty("keywords",
1486 getter_Copies(oldKeywords));
1487 nsTArray<nsCString> newKeywordArray, oldKeywordArray;
1488 ParseString(
1489 Substring(keywords->value, keywords->value + keywords->length),
1490 ' ', newKeywordArray);
1491 ParseString(oldKeywords, ' ', oldKeywordArray);
1492 for (uint32_t i = 0; i < oldKeywordArray.Length(); i++)
1493 if (!newKeywordArray.Contains(oldKeywordArray[i]))
1494 newKeywordArray.AppendElement(oldKeywordArray[i]);
1495 nsAutoCString newKeywords;
1496 for (uint32_t i = 0; i < newKeywordArray.Length(); i++) {
1497 if (i) newKeywords.Append(' ');
1498 newKeywords.Append(newKeywordArray[i]);
1499 }
1500 m_newMsgHdr->SetStringProperty("keywords", newKeywords.get());
1501 }
1502 for (uint32_t i = 0; i < m_customDBHeaders.Length(); i++) {
1503 if (m_customDBHeaderValues[i].length)
1504 m_newMsgHdr->SetStringProperty(m_customDBHeaders[i].get(),
1505 m_customDBHeaderValues[i].value);
1506 // The received header is accumulated separately
1507 if (m_customDBHeaders[i].EqualsLiteral("received") &&
1508 !m_receivedValue.IsEmpty())
1509 m_newMsgHdr->SetStringProperty("received", m_receivedValue.get());
1510 }
1511 if (content_type) {
1512 char* substring = PL_strstr(content_type->value, "charset");
1513 if (substring) {
1514 char* charset = PL_strchr(substring, '=');
1515 if (charset) {
1516 charset++;
1517 /* strip leading whitespace and double-quote */
1518 while (*charset && (IS_SPACE(*charset) || '\"' == *charset))
1519 charset++;
1520 /* strip trailing whitespace and double-quote */
1521 char* end = charset;
1522 while (*end && !IS_SPACE(*end) && '\"' != *end && ';' != *end)
1523 end++;
1524 if (*charset) {
1525 if (*end != '\0') {
1526 // if we're not at the very end of the line, we need
1527 // to generate a new string without the trailing crud
1528 nsAutoCString rawCharSet;
1529 rawCharSet.Assign(charset, end - charset);
1530 m_newMsgHdr->SetCharset(rawCharSet.get());
1531 } else {
1532 m_newMsgHdr->SetCharset(charset);
1533 }
1534 }
1535 }
1536 }
1537 substring = PL_strcasestr(content_type->value, "multipart/mixed");
1538 if (substring) {
1539 uint32_t newFlags;
1540 m_newMsgHdr->OrFlags(nsMsgMessageFlags::Attachment, &newFlags);
1541 }
1542 }
1543 }
1544 } else {
1545 NS_ASSERTION(false, "error creating message header");
1546 rv = NS_ERROR_OUT_OF_MEMORY;
1547 }
1548 } else
1549 rv = NS_OK;
1550
1551 //### why is this stuff const?
1552 char* tmp = (char*)to.value;
1553 PR_Free(tmp);
1554 tmp = (char*)cc.value;
1555 PR_Free(tmp);
1556
1557 return rv;
1558 }
1559
nsParseNewMailState()1560 nsParseNewMailState::nsParseNewMailState() : m_disableFilters(false) {
1561 m_ibuffer = nullptr;
1562 m_ibuffer_size = 0;
1563 m_ibuffer_fp = 0;
1564 m_numNotNewMessages = 0;
1565 }
1566
NS_IMPL_ISUPPORTS_INHERITED(nsParseNewMailState,nsMsgMailboxParser,nsIMsgFilterHitNotify)1567 NS_IMPL_ISUPPORTS_INHERITED(nsParseNewMailState, nsMsgMailboxParser,
1568 nsIMsgFilterHitNotify)
1569
1570 nsresult nsParseNewMailState::Init(nsIMsgFolder* serverFolder,
1571 nsIMsgFolder* downloadFolder,
1572 nsIMsgWindow* aMsgWindow, nsIMsgDBHdr* aHdr,
1573 nsIOutputStream* aOutputStream) {
1574 NS_ENSURE_ARG_POINTER(serverFolder);
1575 nsresult rv;
1576 Clear();
1577 m_rootFolder = serverFolder;
1578 m_msgWindow = aMsgWindow;
1579 m_downloadFolder = downloadFolder;
1580
1581 m_newMsgHdr = aHdr;
1582 m_outputStream = aOutputStream;
1583 // the new mail parser isn't going to get the stream input, it seems, so we
1584 // can't use the OnStartRequest mechanism the mailbox parser uses. So, let's
1585 // open the db right now.
1586 nsCOMPtr<nsIMsgDBService> msgDBService =
1587 do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
1588 if (msgDBService && !m_mailDB)
1589 rv = msgDBService->OpenFolderDB(downloadFolder, false,
1590 getter_AddRefs(m_mailDB));
1591 NS_ENSURE_SUCCESS(rv, rv);
1592
1593 nsCOMPtr<nsIMsgIncomingServer> server;
1594 rv = serverFolder->GetServer(getter_AddRefs(server));
1595 if (NS_SUCCEEDED(rv)) {
1596 nsString serverName;
1597 server->GetPrettyName(serverName);
1598 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1599 ("(Local) Detected new local messages on account '%s'",
1600 NS_ConvertUTF16toUTF8(serverName).get()));
1601 rv = server->GetFilterList(aMsgWindow, getter_AddRefs(m_filterList));
1602
1603 if (m_filterList) rv = server->ConfigureTemporaryFilters(m_filterList);
1604 // check if this server defers to another server, in which case
1605 // we'll use that server's filters as well.
1606 nsCOMPtr<nsIMsgFolder> deferredToRootFolder;
1607 server->GetRootMsgFolder(getter_AddRefs(deferredToRootFolder));
1608 if (serverFolder != deferredToRootFolder) {
1609 nsCOMPtr<nsIMsgIncomingServer> deferredToServer;
1610 deferredToRootFolder->GetServer(getter_AddRefs(deferredToServer));
1611 if (deferredToServer)
1612 deferredToServer->GetFilterList(
1613 aMsgWindow, getter_AddRefs(m_deferredToServerFilterList));
1614 }
1615 }
1616 m_disableFilters = false;
1617 return NS_OK;
1618 }
1619
~nsParseNewMailState()1620 nsParseNewMailState::~nsParseNewMailState() {
1621 if (m_mailDB) m_mailDB->Close(true);
1622 if (m_backupMailDB) m_backupMailDB->ForceClosed();
1623 #ifdef DOING_JSFILTERS
1624 JSFilter_cleanup();
1625 #endif
1626 }
1627
1628 // not an IMETHOD so we don't need to do error checking or return an error.
1629 // We only have one caller.
GetMsgWindow(nsIMsgWindow ** aMsgWindow)1630 void nsParseNewMailState::GetMsgWindow(nsIMsgWindow** aMsgWindow) {
1631 NS_IF_ADDREF(*aMsgWindow = m_msgWindow);
1632 }
1633
1634 // This gets called for every message because libnet calls IncorporateBegin,
1635 // IncorporateWrite (once or more), and IncorporateComplete for every message.
DoneParsingFolder(nsresult status)1636 void nsParseNewMailState::DoneParsingFolder(nsresult status) {
1637 /* End of file. Flush out any partial line remaining in the buffer. */
1638 if (m_ibuffer_fp > 0) {
1639 ParseFolderLine(m_ibuffer, m_ibuffer_fp);
1640 m_ibuffer_fp = 0;
1641 }
1642 PublishMsgHeader(nullptr);
1643 if (m_mailDB) // finished parsing, so flush db folder info
1644 UpdateDBFolderInfo();
1645
1646 /* We're done reading the folder - we don't need these things
1647 any more. */
1648 PR_FREEIF(m_ibuffer);
1649 m_ibuffer_size = 0;
1650 PR_FREEIF(m_obuffer);
1651 m_obuffer_size = 0;
1652 }
1653
OnNewMessage(nsIMsgWindow * msgWindow)1654 void nsParseNewMailState::OnNewMessage(nsIMsgWindow* msgWindow) {}
1655
PublishMsgHeader(nsIMsgWindow * msgWindow)1656 int32_t nsParseNewMailState::PublishMsgHeader(nsIMsgWindow* msgWindow) {
1657 bool moved = false;
1658 FinishHeader();
1659
1660 if (m_newMsgHdr) {
1661 uint32_t newFlags, oldFlags;
1662 m_newMsgHdr->GetFlags(&oldFlags);
1663 if (!(oldFlags &
1664 nsMsgMessageFlags::Read)) // don't mark read messages as new.
1665 m_newMsgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
1666
1667 if (!m_disableFilters) {
1668 uint64_t msgOffset;
1669 (void)m_newMsgHdr->GetMessageOffset(&msgOffset);
1670 m_curHdrOffset = msgOffset;
1671
1672 nsCOMPtr<nsIMsgIncomingServer> server;
1673 nsresult rv = m_rootFolder->GetServer(getter_AddRefs(server));
1674 NS_ENSURE_SUCCESS(rv, 0);
1675 int32_t duplicateAction;
1676 server->GetIncomingDuplicateAction(&duplicateAction);
1677 if (duplicateAction != nsIMsgIncomingServer::keepDups) {
1678 bool isDup;
1679 server->IsNewHdrDuplicate(m_newMsgHdr, &isDup);
1680 if (isDup) {
1681 // we want to do something similar to applying filter hits.
1682 // if a dup is marked read, it shouldn't trigger biff.
1683 // Same for deleting it or moving it to trash.
1684 switch (duplicateAction) {
1685 case nsIMsgIncomingServer::deleteDups: {
1686 nsCOMPtr<nsIMsgPluggableStore> msgStore;
1687 nsresult rv =
1688 m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
1689 if (NS_SUCCEEDED(rv)) {
1690 rv = msgStore->DiscardNewMessage(m_outputStream, m_newMsgHdr);
1691 if (NS_FAILED(rv))
1692 m_rootFolder->ThrowAlertMsg("dupDeleteFolderTruncateFailed",
1693 msgWindow);
1694 }
1695 m_mailDB->RemoveHeaderMdbRow(m_newMsgHdr);
1696 } break;
1697
1698 case nsIMsgIncomingServer::moveDupsToTrash: {
1699 nsCOMPtr<nsIMsgFolder> trash;
1700 GetTrashFolder(getter_AddRefs(trash));
1701 if (trash) {
1702 uint32_t newFlags;
1703 bool msgMoved;
1704 m_newMsgHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
1705 nsCOMPtr<nsIMsgPluggableStore> msgStore;
1706 rv = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
1707 if (NS_SUCCEEDED(rv))
1708 rv = msgStore->MoveNewlyDownloadedMessage(m_newMsgHdr, trash,
1709 &msgMoved);
1710 if (NS_SUCCEEDED(rv) && !msgMoved) {
1711 rv = MoveIncorporatedMessage(m_newMsgHdr, m_mailDB, trash,
1712 nullptr, msgWindow);
1713 if (NS_SUCCEEDED(rv))
1714 rv = m_mailDB->RemoveHeaderMdbRow(m_newMsgHdr);
1715 }
1716 if (NS_FAILED(rv))
1717 NS_WARNING("moveDupsToTrash failed for some reason.");
1718 }
1719 } break;
1720 case nsIMsgIncomingServer::markDupsRead:
1721 MarkFilteredMessageRead(m_newMsgHdr);
1722 break;
1723 }
1724 int32_t numNewMessages;
1725 m_downloadFolder->GetNumNewMessages(false, &numNewMessages);
1726 m_downloadFolder->SetNumNewMessages(numNewMessages - 1);
1727
1728 m_newMsgHdr = nullptr;
1729 return 0;
1730 }
1731 }
1732
1733 ApplyFilters(&moved, msgWindow, msgOffset);
1734 }
1735 if (!moved) {
1736 if (m_mailDB) {
1737 m_mailDB->AddNewHdrToDB(m_newMsgHdr, true);
1738 nsCOMPtr<nsIMsgFolderNotificationService> notifier(
1739 do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
1740 if (notifier) notifier->NotifyMsgAdded(m_newMsgHdr);
1741 // mark the header as not yet reported classified
1742 nsMsgKey msgKey;
1743 m_newMsgHdr->GetMessageKey(&msgKey);
1744 m_downloadFolder->OrProcessingFlags(
1745 msgKey, nsMsgProcessingFlags::NotReportedClassified);
1746 }
1747 } // if it was moved by imap filter, m_parseMsgState->m_newMsgHdr ==
1748 // nullptr
1749 m_newMsgHdr = nullptr;
1750 }
1751 return 0;
1752 }
1753
1754 // We've found the start of the next message, so finish this one off.
FinishHeader()1755 NS_IMETHODIMP nsParseNewMailState::FinishHeader() {
1756 if (m_newMsgHdr) {
1757 if (m_lastLineBlank) m_body_lines--;
1758 m_newMsgHdr->SetMessageSize(m_position - m_envelope_pos - m_lastLineBlank);
1759 m_newMsgHdr->SetLineCount(m_body_lines);
1760 }
1761 return NS_OK;
1762 }
1763
GetTrashFolder(nsIMsgFolder ** pTrashFolder)1764 nsresult nsParseNewMailState::GetTrashFolder(nsIMsgFolder** pTrashFolder) {
1765 nsresult rv = NS_ERROR_UNEXPECTED;
1766 if (!pTrashFolder) return NS_ERROR_NULL_POINTER;
1767
1768 if (m_downloadFolder) {
1769 nsCOMPtr<nsIMsgIncomingServer> incomingServer;
1770 m_downloadFolder->GetServer(getter_AddRefs(incomingServer));
1771 nsCOMPtr<nsIMsgFolder> rootMsgFolder;
1772 incomingServer->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
1773 if (rootMsgFolder) {
1774 rv = rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
1775 pTrashFolder);
1776 if (!*pTrashFolder) rv = NS_ERROR_FAILURE;
1777 }
1778 }
1779 return rv;
1780 }
1781
ApplyFilters(bool * pMoved,nsIMsgWindow * msgWindow,uint64_t msgOffset)1782 void nsParseNewMailState::ApplyFilters(bool* pMoved, nsIMsgWindow* msgWindow,
1783 uint64_t msgOffset) {
1784 m_msgMovedByFilter = m_msgCopiedByFilter = false;
1785 m_curHdrOffset = msgOffset;
1786
1787 if (!m_disableFilters) {
1788 nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr;
1789 nsCOMPtr<nsIMsgFolder> downloadFolder = m_downloadFolder;
1790 if (m_rootFolder) {
1791 if (!downloadFolder)
1792 m_rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
1793 getter_AddRefs(downloadFolder));
1794 if (downloadFolder) downloadFolder->GetURI(m_inboxUri);
1795 char* headers = m_headers.GetBuffer();
1796 uint32_t headersSize = m_headers.GetBufferPos();
1797 if (m_filterList) {
1798 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1799 ("(Local) Running filters on 1 message at offset %" PRIu64,
1800 msgOffset));
1801 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1802 ("(Local) Using filters from the original account"));
1803 (void)m_filterList->ApplyFiltersToHdr(
1804 nsMsgFilterType::InboxRule, msgHdr, downloadFolder, m_mailDB,
1805 nsDependentCSubstring(headers, headersSize), this, msgWindow);
1806 }
1807 if (!m_msgMovedByFilter && m_deferredToServerFilterList) {
1808 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1809 ("(Local) Running filters on 1 message at offset %" PRIu64,
1810 msgOffset));
1811 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1812 ("(Local) Using filters from the deferred to account"));
1813 (void)m_deferredToServerFilterList->ApplyFiltersToHdr(
1814 nsMsgFilterType::InboxRule, msgHdr, downloadFolder, m_mailDB,
1815 nsDependentCSubstring(headers, headersSize), this, msgWindow);
1816 }
1817 }
1818 }
1819 if (pMoved) *pMoved = m_msgMovedByFilter;
1820 }
1821
ApplyFilterHit(nsIMsgFilter * filter,nsIMsgWindow * msgWindow,bool * applyMore)1822 NS_IMETHODIMP nsParseNewMailState::ApplyFilterHit(nsIMsgFilter* filter,
1823 nsIMsgWindow* msgWindow,
1824 bool* applyMore) {
1825 NS_ENSURE_ARG_POINTER(filter);
1826 NS_ENSURE_ARG_POINTER(applyMore);
1827
1828 uint32_t newFlags;
1829 nsresult rv = NS_OK;
1830
1831 *applyMore = true;
1832
1833 nsCOMPtr<nsIMsgDBHdr> msgHdr = m_newMsgHdr;
1834
1835 nsTArray<RefPtr<nsIMsgRuleAction>> filterActionList;
1836 rv = filter->GetSortedActionList(filterActionList);
1837 NS_ENSURE_SUCCESS(rv, rv);
1838
1839 uint32_t numActions = filterActionList.Length();
1840
1841 nsCString msgId;
1842 msgHdr->GetMessageId(getter_Copies(msgId));
1843 nsMsgKey msgKey;
1844 msgHdr->GetMessageKey(&msgKey);
1845 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1846 ("(Local) Applying %" PRIu32
1847 " filter actions on message with key %" PRIu32,
1848 numActions, msgKeyToInt(msgKey)));
1849 MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
1850 ("(Local) Message ID: %s", msgId.get()));
1851
1852 bool loggingEnabled = false;
1853 if (m_filterList && numActions)
1854 m_filterList->GetLoggingEnabled(&loggingEnabled);
1855
1856 bool msgIsNew = true;
1857 nsresult finalResult = NS_OK; // result of all actions
1858 for (uint32_t actionIndex = 0; actionIndex < numActions && *applyMore;
1859 actionIndex++) {
1860 nsCOMPtr<nsIMsgRuleAction> filterAction(filterActionList[actionIndex]);
1861 if (!filterAction) {
1862 MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
1863 ("(Local) Filter action at index %" PRIu32 " invalid, skipping",
1864 actionIndex));
1865 continue;
1866 }
1867
1868 nsMsgRuleActionType actionType;
1869 if (NS_SUCCEEDED(filterAction->GetType(&actionType))) {
1870 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1871 ("(Local) Running filter action at index %" PRIu32
1872 ", action type = %i",
1873 actionIndex, actionType));
1874 if (loggingEnabled) (void)filter->LogRuleHit(filterAction, msgHdr);
1875
1876 nsCString actionTargetFolderUri;
1877 if (actionType == nsMsgFilterAction::MoveToFolder ||
1878 actionType == nsMsgFilterAction::CopyToFolder) {
1879 rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
1880 if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty()) {
1881 // clang-format off
1882 MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
1883 ("(Local) Target URI for Copy/Move action is empty, skipping"));
1884 // clang-format on
1885 NS_ASSERTION(false, "actionTargetFolderUri is empty");
1886 continue;
1887 }
1888 }
1889
1890 rv = NS_OK; // result of the current action
1891 switch (actionType) {
1892 case nsMsgFilterAction::Delete: {
1893 nsCOMPtr<nsIMsgFolder> trash;
1894 // set value to trash folder
1895 rv = GetTrashFolder(getter_AddRefs(trash));
1896 if (NS_SUCCEEDED(rv) && trash) {
1897 rv = trash->GetURI(actionTargetFolderUri);
1898 if (NS_FAILED(rv)) break;
1899 }
1900
1901 rv = msgHdr->OrFlags(nsMsgMessageFlags::Read,
1902 &newFlags); // mark read in trash.
1903 msgIsNew = false;
1904 }
1905 // FALLTHROUGH
1906 [[fallthrough]];
1907 case nsMsgFilterAction::MoveToFolder: {
1908 // if moving to a different file, do it.
1909 if (!actionTargetFolderUri.IsEmpty() &&
1910 !m_inboxUri.Equals(actionTargetFolderUri,
1911 nsCaseInsensitiveCStringComparator)) {
1912 nsCOMPtr<nsIMsgFolder> destIFolder;
1913 // XXX TODO: why do we create the folder here, while we do not in
1914 // the Copy action?
1915 rv = GetOrCreateFolder(actionTargetFolderUri,
1916 getter_AddRefs(destIFolder));
1917 if (NS_FAILED(rv)) {
1918 MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
1919 ("(Local) Target Folder for Move action does not exist"));
1920 break;
1921 }
1922 bool msgMoved = false;
1923 // If we're moving to an imap folder, or this message has already
1924 // has a pending copy action, use the imap coalescer so that
1925 // we won't truncate the inbox before the copy fires.
1926 if (m_msgCopiedByFilter ||
1927 StringBeginsWith(actionTargetFolderUri, "imap:"_ns)) {
1928 if (!m_moveCoalescer)
1929 m_moveCoalescer =
1930 new nsImapMoveCoalescer(m_downloadFolder, m_msgWindow);
1931 NS_ENSURE_TRUE(m_moveCoalescer, NS_ERROR_OUT_OF_MEMORY);
1932 rv = m_moveCoalescer->AddMove(destIFolder, msgKey);
1933 msgIsNew = false;
1934 if (NS_FAILED(rv)) break;
1935 } else {
1936 nsCOMPtr<nsIMsgPluggableStore> msgStore;
1937 rv = m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
1938 if (NS_SUCCEEDED(rv))
1939 rv = msgStore->MoveNewlyDownloadedMessage(msgHdr, destIFolder,
1940 &msgMoved);
1941 if (NS_SUCCEEDED(rv) && !msgMoved)
1942 rv = MoveIncorporatedMessage(msgHdr, m_mailDB, destIFolder,
1943 filter, msgWindow);
1944 m_msgMovedByFilter = NS_SUCCEEDED(rv);
1945 if (!m_msgMovedByFilter /* == NS_FAILED(err) */) {
1946 // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands.
1947 if (loggingEnabled) {
1948 (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
1949 "filterFailureMoveFailed"_ns);
1950 }
1951 }
1952 }
1953 } else {
1954 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1955 ("(Local) Target folder is the same as source folder, "
1956 "skipping"));
1957 rv = NS_OK;
1958 }
1959 *applyMore = false;
1960 } break;
1961 case nsMsgFilterAction::CopyToFolder: {
1962 nsCString uri;
1963 rv = m_rootFolder->GetURI(uri);
1964
1965 if (!actionTargetFolderUri.IsEmpty() &&
1966 !actionTargetFolderUri.Equals(uri)) {
1967 nsCOMPtr<nsIMsgFolder> dstFolder;
1968 nsCOMPtr<nsIMsgCopyService> copyService;
1969 rv = GetExistingFolder(actionTargetFolderUri,
1970 getter_AddRefs(dstFolder));
1971 if (NS_FAILED(rv)) {
1972 // Let's show a more specific warning.
1973 MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
1974 ("(Local) Target Folder for Copy action does not exist"));
1975 NS_WARNING("Target Folder does not exist.");
1976 break;
1977 }
1978
1979 copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
1980 if (NS_SUCCEEDED(rv))
1981 rv = copyService->CopyMessages(m_downloadFolder, {&*msgHdr},
1982 dstFolder, false, nullptr,
1983 msgWindow, false);
1984
1985 if (NS_FAILED(rv)) {
1986 // XXX: Invoke MSG_LOG_TO_CONSOLE once bug 1135265 lands.
1987 if (loggingEnabled) {
1988 (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
1989 "filterFailureCopyFailed"_ns);
1990 }
1991 } else
1992 m_msgCopiedByFilter = true;
1993 } else {
1994 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
1995 ("(Local) Target folder is the same as source folder, "
1996 "skipping"));
1997 break;
1998 }
1999 } break;
2000 case nsMsgFilterAction::MarkRead:
2001 msgIsNew = false;
2002 MarkFilteredMessageRead(msgHdr);
2003 rv = NS_OK;
2004 break;
2005 case nsMsgFilterAction::MarkUnread:
2006 msgIsNew = true;
2007 MarkFilteredMessageUnread(msgHdr);
2008 rv = NS_OK;
2009 break;
2010 case nsMsgFilterAction::KillThread:
2011 rv = msgHdr->SetUint32Property("ProtoThreadFlags",
2012 nsMsgMessageFlags::Ignored);
2013 break;
2014 case nsMsgFilterAction::KillSubthread:
2015 rv = msgHdr->OrFlags(nsMsgMessageFlags::Ignored, &newFlags);
2016 break;
2017 case nsMsgFilterAction::WatchThread:
2018 rv = msgHdr->OrFlags(nsMsgMessageFlags::Watched, &newFlags);
2019 break;
2020 case nsMsgFilterAction::MarkFlagged: {
2021 rv = m_downloadFolder->MarkMessagesFlagged({&*msgHdr}, true);
2022 } break;
2023 case nsMsgFilterAction::ChangePriority: {
2024 nsMsgPriorityValue filterPriority;
2025 filterAction->GetPriority(&filterPriority);
2026 rv = msgHdr->SetPriority(filterPriority);
2027 } break;
2028 case nsMsgFilterAction::AddTag: {
2029 nsCString keyword;
2030 filterAction->GetStrValue(keyword);
2031 rv = m_downloadFolder->AddKeywordsToMessages({&*msgHdr}, keyword);
2032 break;
2033 }
2034 case nsMsgFilterAction::Label: {
2035 nsMsgLabelValue filterLabel;
2036 filterAction->GetLabel(&filterLabel);
2037 rv = m_mailDB->SetLabel(msgKey, filterLabel);
2038 } break;
2039 case nsMsgFilterAction::JunkScore: {
2040 nsAutoCString junkScoreStr;
2041 int32_t junkScore;
2042 filterAction->GetJunkScore(&junkScore);
2043 junkScoreStr.AppendInt(junkScore);
2044 if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) msgIsNew = false;
2045 rv = msgHdr->SetStringProperty("junkscore", junkScoreStr.get());
2046 msgHdr->SetStringProperty("junkscoreorigin", "filter");
2047 } break;
2048 case nsMsgFilterAction::Forward: {
2049 nsCString forwardTo;
2050 filterAction->GetStrValue(forwardTo);
2051 m_forwardTo.AppendElement(forwardTo);
2052 m_msgToForwardOrReply = msgHdr;
2053 rv = NS_OK;
2054 } break;
2055 case nsMsgFilterAction::Reply: {
2056 nsCString replyTemplateUri;
2057 filterAction->GetStrValue(replyTemplateUri);
2058 m_replyTemplateUri.AppendElement(replyTemplateUri);
2059 m_msgToForwardOrReply = msgHdr;
2060 m_ruleAction = filterAction;
2061 m_filter = filter;
2062 rv = NS_OK;
2063 } break;
2064 case nsMsgFilterAction::DeleteFromPop3Server: {
2065 nsCOMPtr<nsIMsgFolder> downloadFolder;
2066 msgHdr->GetFolder(getter_AddRefs(downloadFolder));
2067 nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
2068 do_QueryInterface(downloadFolder, &rv);
2069 if (NS_FAILED(rv) || !localFolder) {
2070 MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
2071 ("(Local) Couldn't find local mail folder"));
2072 break;
2073 }
2074 // This action ignores the deleteMailLeftOnServer preference
2075 rv = localFolder->MarkMsgsOnPop3Server({&*msgHdr}, POP3_FORCE_DEL);
2076
2077 // If this is just a header, throw it away. It's useless now
2078 // that the server copy is being deleted.
2079 uint32_t flags = 0;
2080 msgHdr->GetFlags(&flags);
2081 if (flags & nsMsgMessageFlags::Partial) {
2082 m_msgMovedByFilter = true;
2083 msgIsNew = false;
2084 }
2085 } break;
2086 case nsMsgFilterAction::FetchBodyFromPop3Server: {
2087 nsCOMPtr<nsIMsgFolder> downloadFolder;
2088 msgHdr->GetFolder(getter_AddRefs(downloadFolder));
2089 nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
2090 do_QueryInterface(downloadFolder, &rv);
2091 if (NS_FAILED(rv) || !localFolder) {
2092 MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
2093 ("(Local) Couldn't find local mail folder"));
2094 break;
2095 }
2096 uint32_t flags = 0;
2097 msgHdr->GetFlags(&flags);
2098 if (flags & nsMsgMessageFlags::Partial) {
2099 rv = localFolder->MarkMsgsOnPop3Server({&*msgHdr}, POP3_FETCH_BODY);
2100 // Don't add this header to the DB, we're going to replace it
2101 // with the full message.
2102 m_msgMovedByFilter = true;
2103 msgIsNew = false;
2104 // Don't do anything else in this filter, wait until we
2105 // have the full message.
2106 *applyMore = false;
2107 }
2108 } break;
2109
2110 case nsMsgFilterAction::StopExecution: {
2111 // don't apply any more filters
2112 *applyMore = false;
2113 rv = NS_OK;
2114 } break;
2115
2116 case nsMsgFilterAction::Custom: {
2117 nsCOMPtr<nsIMsgFilterCustomAction> customAction;
2118 rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
2119 if (NS_FAILED(rv)) break;
2120
2121 nsAutoCString value;
2122 rv = filterAction->GetStrValue(value);
2123 if (NS_FAILED(rv)) break;
2124
2125 rv = customAction->ApplyAction({&*msgHdr}, value, nullptr,
2126 nsMsgFilterType::InboxRule, msgWindow);
2127 } break;
2128
2129 default:
2130 // XXX should not be reached. Check in debug build.
2131 NS_ERROR("unexpected filter action");
2132 rv = NS_ERROR_UNEXPECTED;
2133 break;
2134 }
2135 }
2136 if (NS_FAILED(rv)) {
2137 finalResult = rv;
2138 MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
2139 ("(Local) Action execution failed with error: %" PRIx32,
2140 static_cast<uint32_t>(rv)));
2141 if (loggingEnabled) {
2142 (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
2143 "filterFailureAction"_ns);
2144 }
2145 } else {
2146 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2147 ("(Local) Action execution succeeded"));
2148 }
2149 }
2150 if (!msgIsNew) {
2151 int32_t numNewMessages;
2152 m_downloadFolder->GetNumNewMessages(false, &numNewMessages);
2153 if (numNewMessages > 0)
2154 m_downloadFolder->SetNumNewMessages(numNewMessages - 1);
2155 m_numNotNewMessages++;
2156 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2157 ("(Local) Message will not be marked new"));
2158 }
2159 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2160 ("(Local) Finished executing actions"));
2161 return finalResult;
2162 }
2163
2164 // this gets run in a second pass, after apply filters to a header.
ApplyForwardAndReplyFilter(nsIMsgWindow * msgWindow)2165 nsresult nsParseNewMailState::ApplyForwardAndReplyFilter(
2166 nsIMsgWindow* msgWindow) {
2167 nsresult rv = NS_OK;
2168 nsCOMPtr<nsIMsgIncomingServer> server;
2169
2170 uint32_t i;
2171 uint32_t count = m_forwardTo.Length();
2172 nsMsgKey msgKey;
2173 if (count > 0 && m_msgToForwardOrReply) {
2174 m_msgToForwardOrReply->GetMessageKey(&msgKey);
2175 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2176 ("(Local) Forwarding message with key %" PRIu32 " to %" PRIu32
2177 " addresses",
2178 msgKeyToInt(msgKey), count));
2179 }
2180
2181 for (i = 0; i < count; i++) {
2182 if (!m_forwardTo[i].IsEmpty()) {
2183 nsAutoString forwardStr;
2184 CopyASCIItoUTF16(m_forwardTo[i], forwardStr);
2185 rv = m_rootFolder->GetServer(getter_AddRefs(server));
2186 NS_ENSURE_SUCCESS(rv, rv);
2187 {
2188 nsCOMPtr<nsIMsgComposeService> compService =
2189 do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
2190 NS_ENSURE_SUCCESS(rv, rv);
2191 rv = compService->ForwardMessage(
2192 forwardStr, m_msgToForwardOrReply, msgWindow, server,
2193 nsIMsgComposeService::kForwardAsDefault);
2194 if (NS_FAILED(rv))
2195 MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
2196 ("(Local) Forwarding failed"));
2197 }
2198 }
2199 }
2200 m_forwardTo.Clear();
2201
2202 count = m_replyTemplateUri.Length();
2203 if (count > 0 && m_msgToForwardOrReply) {
2204 MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
2205 ("(Local) Replying message with key %" PRIu32 " to %" PRIu32
2206 " addresses",
2207 msgKeyToInt(msgKey), count));
2208 }
2209
2210 for (i = 0; i < count; i++) {
2211 if (!m_replyTemplateUri[i].IsEmpty()) {
2212 // copy this and truncate the original, so we don't accidentally re-use it
2213 // on the next hdr.
2214 rv = m_rootFolder->GetServer(getter_AddRefs(server));
2215 if (server) {
2216 nsCOMPtr<nsIMsgComposeService> compService =
2217 do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID);
2218 if (compService) {
2219 rv = compService->ReplyWithTemplate(
2220 m_msgToForwardOrReply, m_replyTemplateUri[i], msgWindow, server);
2221 if (NS_FAILED(rv)) {
2222 NS_WARNING("ReplyWithTemplate failed");
2223 MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
2224 ("(Local) Replying failed"));
2225 if (rv == NS_ERROR_ABORT) {
2226 (void)m_filter->LogRuleHitFail(
2227 m_ruleAction, m_msgToForwardOrReply, rv,
2228 "filterFailureSendingReplyAborted"_ns);
2229 } else {
2230 (void)m_filter->LogRuleHitFail(
2231 m_ruleAction, m_msgToForwardOrReply, rv,
2232 "filterFailureSendingReplyError"_ns);
2233 }
2234 }
2235 }
2236 }
2237 }
2238 }
2239 m_replyTemplateUri.Clear();
2240 m_msgToForwardOrReply = nullptr;
2241 return rv;
2242 }
2243
MarkFilteredMessageRead(nsIMsgDBHdr * msgHdr)2244 void nsParseNewMailState::MarkFilteredMessageRead(nsIMsgDBHdr* msgHdr) {
2245 m_downloadFolder->MarkMessagesRead({msgHdr}, true);
2246 }
2247
MarkFilteredMessageUnread(nsIMsgDBHdr * msgHdr)2248 void nsParseNewMailState::MarkFilteredMessageUnread(nsIMsgDBHdr* msgHdr) {
2249 uint32_t newFlags;
2250 if (m_mailDB) {
2251 nsMsgKey msgKey;
2252 msgHdr->GetMessageKey(&msgKey);
2253 m_mailDB->AddToNewList(msgKey);
2254 } else {
2255 msgHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
2256 }
2257 m_downloadFolder->MarkMessagesRead({msgHdr}, false);
2258 }
2259
EndMsgDownload()2260 nsresult nsParseNewMailState::EndMsgDownload() {
2261 if (m_moveCoalescer) m_moveCoalescer->PlaybackMoves();
2262
2263 // need to do this for all folders that had messages filtered into them
2264 uint32_t serverCount = m_filterTargetFolders.Count();
2265 nsresult rv;
2266 nsCOMPtr<nsIMsgMailSession> session =
2267 do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
2268 if (NS_SUCCEEDED(rv) && session) // don't use NS_ENSURE_SUCCESS here - we
2269 // need to release semaphore below
2270 {
2271 for (uint32_t index = 0; index < serverCount; index++) {
2272 bool folderOpen;
2273 session->IsFolderOpenInWindow(m_filterTargetFolders[index], &folderOpen);
2274 if (!folderOpen) {
2275 uint32_t folderFlags;
2276 m_filterTargetFolders[index]->GetFlags(&folderFlags);
2277 if (!(folderFlags &
2278 (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox))) {
2279 bool filtersRun;
2280 m_filterTargetFolders[index]->CallFilterPlugins(nullptr, &filtersRun);
2281 if (!filtersRun)
2282 m_filterTargetFolders[index]->SetMsgDatabase(nullptr);
2283 }
2284 }
2285 }
2286 }
2287 m_filterTargetFolders.Clear();
2288 return rv;
2289 }
2290
AppendMsgFromStream(nsIInputStream * fileStream,nsIMsgDBHdr * aHdr,uint32_t length,nsIMsgFolder * destFolder)2291 nsresult nsParseNewMailState::AppendMsgFromStream(nsIInputStream* fileStream,
2292 nsIMsgDBHdr* aHdr,
2293 uint32_t length,
2294 nsIMsgFolder* destFolder) {
2295 nsCOMPtr<nsIMsgPluggableStore> store;
2296 nsCOMPtr<nsIOutputStream> destOutputStream;
2297 nsresult rv = destFolder->GetMsgStore(getter_AddRefs(store));
2298 NS_ENSURE_SUCCESS(rv, rv);
2299 bool reusable;
2300 rv = store->GetNewMsgOutputStream(destFolder, &aHdr, &reusable,
2301 getter_AddRefs(destOutputStream));
2302 NS_ENSURE_SUCCESS(rv, rv);
2303
2304 if (!m_ibuffer) {
2305 m_ibuffer_size = FILE_IO_BUFFER_SIZE;
2306 m_ibuffer = (char*)PR_Malloc(m_ibuffer_size);
2307 NS_ASSERTION(m_ibuffer != nullptr, "couldn't get memory to move msg");
2308 }
2309 m_ibuffer_fp = 0;
2310
2311 while (length > 0 && m_ibuffer) {
2312 uint32_t nRead;
2313 fileStream->Read(m_ibuffer,
2314 length > m_ibuffer_size ? m_ibuffer_size : length, &nRead);
2315 if (nRead == 0) break;
2316
2317 uint32_t bytesWritten;
2318 // Check the number of bytes actually written to the stream.
2319 destOutputStream->Write(m_ibuffer, nRead, &bytesWritten);
2320 if (bytesWritten != nRead) {
2321 destOutputStream->Close();
2322 return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
2323 }
2324
2325 length -= nRead;
2326 }
2327
2328 NS_ASSERTION(length == 0,
2329 "didn't read all of original message in filter move");
2330
2331 // non-reusable streams will get closed by the store.
2332 if (reusable) destOutputStream->Close();
2333 return store->FinishNewMessage(destOutputStream, aHdr);
2334 }
2335
2336 /*
2337 * Moves message pointed to by mailHdr into folder destIFolder.
2338 * After successful move mailHdr is no longer usable by the caller.
2339 */
MoveIncorporatedMessage(nsIMsgDBHdr * mailHdr,nsIMsgDatabase * sourceDB,nsIMsgFolder * destIFolder,nsIMsgFilter * filter,nsIMsgWindow * msgWindow)2340 nsresult nsParseNewMailState::MoveIncorporatedMessage(nsIMsgDBHdr* mailHdr,
2341 nsIMsgDatabase* sourceDB,
2342 nsIMsgFolder* destIFolder,
2343 nsIMsgFilter* filter,
2344 nsIMsgWindow* msgWindow) {
2345 NS_ENSURE_ARG_POINTER(destIFolder);
2346 nsresult rv = NS_OK;
2347
2348 // check if the destination is a real folder (by checking for null parent)
2349 // and if it can file messages (e.g., servers or news folders can't file
2350 // messages). Or read only imap folders...
2351 bool canFileMessages = true;
2352 nsCOMPtr<nsIMsgFolder> parentFolder;
2353 destIFolder->GetParent(getter_AddRefs(parentFolder));
2354 if (parentFolder) destIFolder->GetCanFileMessages(&canFileMessages);
2355 if (!parentFolder || !canFileMessages) {
2356 if (filter) {
2357 filter->SetEnabled(false);
2358 // we need to explicitly save the filter file.
2359 if (m_filterList) m_filterList->SaveToDefaultFile();
2360 destIFolder->ThrowAlertMsg("filterDisabled", msgWindow);
2361 }
2362 return NS_MSG_NOT_A_MAIL_FOLDER;
2363 }
2364
2365 uint32_t messageLength;
2366 mailHdr->GetMessageSize(&messageLength);
2367
2368 nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(destIFolder);
2369 if (localFolder) {
2370 bool destFolderTooBig = true;
2371 rv = localFolder->WarnIfLocalFileTooBig(msgWindow, messageLength,
2372 &destFolderTooBig);
2373 if (NS_FAILED(rv) || destFolderTooBig)
2374 return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
2375 }
2376
2377 nsCOMPtr<nsISupports> myISupports =
2378 do_QueryInterface(static_cast<nsIMsgParseMailMsgState*>(this));
2379
2380 // Make sure no one else is writing into this folder
2381 if (NS_FAILED(rv = destIFolder->AcquireSemaphore(myISupports))) {
2382 destIFolder->ThrowAlertMsg("filterFolderDeniedLocked", msgWindow);
2383 return rv;
2384 }
2385 nsCOMPtr<nsIInputStream> inputStream;
2386 bool reusable;
2387 rv = m_downloadFolder->GetMsgInputStream(mailHdr, &reusable,
2388 getter_AddRefs(inputStream));
2389 if (!inputStream) {
2390 NS_ERROR("couldn't get source msg input stream in move filter");
2391 destIFolder->ReleaseSemaphore(myISupports);
2392 return NS_MSG_FOLDER_UNREADABLE; // ### dmb
2393 }
2394
2395 nsCOMPtr<nsIMsgDatabase> destMailDB;
2396
2397 if (!localFolder) return NS_MSG_POP_FILTER_TARGET_ERROR;
2398
2399 // don't force upgrade in place - open the db here before we start writing to
2400 // the destination file because XP_Stat can return file size including bytes
2401 // written...
2402 rv = localFolder->GetDatabaseWOReparse(getter_AddRefs(destMailDB));
2403 NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv),
2404 "failed to open mail db parsing folder");
2405 nsCOMPtr<nsIMsgDBHdr> newHdr;
2406
2407 if (destMailDB)
2408 rv = destMailDB->CopyHdrFromExistingHdr(m_new_key, mailHdr, true,
2409 getter_AddRefs(newHdr));
2410 if (NS_SUCCEEDED(rv) && !newHdr) rv = NS_ERROR_UNEXPECTED;
2411
2412 if (NS_FAILED(rv)) {
2413 destIFolder->ThrowAlertMsg("filterFolderHdrAddFailed", msgWindow);
2414 } else {
2415 rv = AppendMsgFromStream(inputStream, newHdr, messageLength, destIFolder);
2416 if (NS_FAILED(rv))
2417 destIFolder->ThrowAlertMsg("filterFolderWriteFailed", msgWindow);
2418 }
2419
2420 if (NS_FAILED(rv)) {
2421 if (destMailDB) destMailDB->Close(true);
2422
2423 destIFolder->ReleaseSemaphore(myISupports);
2424
2425 return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
2426 }
2427
2428 bool movedMsgIsNew = false;
2429 // if we have made it this far then the message has successfully been written
2430 // to the new folder now add the header to the destMailDB.
2431
2432 uint32_t newFlags;
2433 newHdr->GetFlags(&newFlags);
2434 nsMsgKey msgKey;
2435 newHdr->GetMessageKey(&msgKey);
2436 if (!(newFlags & nsMsgMessageFlags::Read)) {
2437 nsCString junkScoreStr;
2438 (void)newHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
2439 if (atoi(junkScoreStr.get()) == nsIJunkMailPlugin::IS_HAM_SCORE) {
2440 newHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
2441 destMailDB->AddToNewList(msgKey);
2442 movedMsgIsNew = true;
2443 }
2444 }
2445 nsCOMPtr<nsIMsgFolderNotificationService> notifier(
2446 do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
2447 if (notifier) notifier->NotifyMsgAdded(newHdr);
2448 // mark the header as not yet reported classified
2449 destIFolder->OrProcessingFlags(msgKey,
2450 nsMsgProcessingFlags::NotReportedClassified);
2451 m_msgToForwardOrReply = newHdr;
2452
2453 if (movedMsgIsNew) destIFolder->SetHasNewMessages(true);
2454 if (!m_filterTargetFolders.Contains(destIFolder))
2455 m_filterTargetFolders.AppendObject(destIFolder);
2456
2457 destIFolder->ReleaseSemaphore(myISupports);
2458
2459 (void)localFolder->RefreshSizeOnDisk();
2460
2461 // Notify the message was moved.
2462 if (notifier) {
2463 nsCOMPtr<nsIMsgFolder> folder;
2464 nsresult rv = mailHdr->GetFolder(getter_AddRefs(folder));
2465 if (NS_SUCCEEDED(rv)) {
2466 notifier->NotifyMsgUnincorporatedMoved(folder, newHdr);
2467 } else {
2468 NS_WARNING("Can't get folder for message that was moved.");
2469 }
2470 }
2471
2472 nsCOMPtr<nsIMsgPluggableStore> store;
2473 rv = m_downloadFolder->GetMsgStore(getter_AddRefs(store));
2474 if (store) store->DiscardNewMessage(m_outputStream, mailHdr);
2475 if (sourceDB) sourceDB->RemoveHeaderMdbRow(mailHdr);
2476
2477 // update the folder size so we won't reparse.
2478 UpdateDBFolderInfo(destMailDB);
2479 destIFolder->UpdateSummaryTotals(true);
2480
2481 destMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
2482 return rv;
2483 }
2484