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