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 /**
7    Class for handling Maildir stores.
8 */
9 
10 #include "prprf.h"
11 #include "msgCore.h"
12 #include "nsMsgMaildirStore.h"
13 #include "nsIMsgFolder.h"
14 #include "nsIMsgFolderNotificationService.h"
15 #include "nsISimpleEnumerator.h"
16 #include "nsIDirectoryEnumerator.h"
17 #include "nsMsgFolderFlags.h"
18 #include "nsCOMArray.h"
19 #include "nsIFile.h"
20 #include "nsNetUtil.h"
21 #include "nsIMsgDatabase.h"
22 #include "nsNativeCharsetUtils.h"
23 #include "nsMsgUtils.h"
24 #include "nsMsgDBCID.h"
25 #include "nsIDBFolderInfo.h"
26 #include "nsMailHeaders.h"
27 #include "nsParseMailbox.h"
28 #include "nsMsgLocalCID.h"
29 #include "nsIMsgLocalMailFolder.h"
30 #include "nsITimer.h"
31 #include "nsIMailboxUrl.h"
32 #include "nsIMsgMailNewsUrl.h"
33 #include "nsIMsgFilterPlugin.h"
34 #include "nsLocalUndoTxn.h"
35 #include "nsIMessenger.h"
36 #include "mozilla/Logging.h"
37 #include "mozilla/UniquePtr.h"
38 
39 static mozilla::LazyLogModule MailDirLog("MailDirStore");
40 
41 // Helper function to produce a safe filename from a Message-ID value.
42 // We'll percent-encode anything not in this set: [-+.%=@_0-9a-zA-Z]
43 // This is an overly-picky set, but should:
44 //  - leave most sane Message-IDs unchanged
45 //  - be safe on windows (the pickiest case)
46 //  - avoid chars that can trip up shell scripts (spaces, semicolons etc)
47 // If input contains malicious binary (or multibyte chars) it'll be
48 // safely encoded as individual bytes.
percentEncode(nsACString const & in,nsACString & out)49 static void percentEncode(nsACString const& in, nsACString& out) {
50   const char* end = in.EndReading();
51   const char* cur;
52   // We know the output will be at least as long as the input.
53   out.SetLength(0);
54   out.SetCapacity(in.Length());
55   for (cur = in.BeginReading(); cur < end; ++cur) {
56     const char c = *cur;
57     bool whitelisted = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
58                        (c >= 'a' && c <= 'z') || c == '-' || c == '+' ||
59                        c == '.' || c == '%' || c == '=' || c == '@' || c == '_';
60     if (whitelisted) {
61       out.Append(c);
62     } else {
63       out.AppendPrintf("%%%02x", (unsigned char)c);
64     }
65   }
66 }
67 
nsMsgMaildirStore()68 nsMsgMaildirStore::nsMsgMaildirStore() {}
69 
~nsMsgMaildirStore()70 nsMsgMaildirStore::~nsMsgMaildirStore() {}
71 
NS_IMPL_ISUPPORTS(nsMsgMaildirStore,nsIMsgPluggableStore)72 NS_IMPL_ISUPPORTS(nsMsgMaildirStore, nsIMsgPluggableStore)
73 
74 // Iterates over the folders in the "path" directory, and adds subfolders to
75 // parent for each Maildir folder found.
76 nsresult nsMsgMaildirStore::AddSubFolders(nsIMsgFolder* parent, nsIFile* path,
77                                           bool deep) {
78   nsCOMArray<nsIFile> currentDirEntries;
79 
80   nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
81   nsresult rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
82   NS_ENSURE_SUCCESS(rv, rv);
83 
84   bool hasMore;
85   while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
86          hasMore) {
87     nsCOMPtr<nsIFile> currentFile;
88     rv = directoryEnumerator->GetNextFile(getter_AddRefs(currentFile));
89     if (NS_SUCCEEDED(rv) && currentFile) {
90       nsAutoString leafName;
91       currentFile->GetLeafName(leafName);
92       bool isDirectory = false;
93       currentFile->IsDirectory(&isDirectory);
94       // Make sure this really is a mail folder dir (i.e., a directory that
95       // contains cur and tmp sub-dirs, and not a .sbd or .mozmsgs dir).
96       if (isDirectory && !nsShouldIgnoreFile(leafName, currentFile))
97         currentDirEntries.AppendObject(currentFile);
98     }
99   }
100 
101   // add the folders
102   int32_t count = currentDirEntries.Count();
103   for (int32_t i = 0; i < count; ++i) {
104     nsCOMPtr<nsIFile> currentFile(currentDirEntries[i]);
105 
106     nsAutoString leafName;
107     currentFile->GetLeafName(leafName);
108 
109     nsCOMPtr<nsIMsgFolder> child;
110     rv = parent->AddSubfolder(leafName, getter_AddRefs(child));
111     if (child) {
112       nsString folderName;
113       child->GetName(folderName);  // try to get it from cache/db
114       if (folderName.IsEmpty()) child->SetPrettyName(leafName);
115       if (deep) {
116         nsCOMPtr<nsIFile> path;
117         rv = child->GetFilePath(getter_AddRefs(path));
118         NS_ENSURE_SUCCESS(rv, rv);
119 
120         // Construct the .sbd directory path for the possible children of the
121         // folder.
122         GetDirectoryForFolder(path);
123         bool directory = false;
124         // Check that <folder>.sbd really is a directory.
125         path->IsDirectory(&directory);
126         if (directory) AddSubFolders(child, path, true);
127       }
128     }
129   }
130   return rv == NS_MSG_FOLDER_EXISTS ? NS_OK : rv;
131 }
132 
DiscoverSubFolders(nsIMsgFolder * aParentFolder,bool aDeep)133 NS_IMETHODIMP nsMsgMaildirStore::DiscoverSubFolders(nsIMsgFolder* aParentFolder,
134                                                     bool aDeep) {
135   NS_ENSURE_ARG_POINTER(aParentFolder);
136 
137   nsCOMPtr<nsIFile> path;
138   nsresult rv = aParentFolder->GetFilePath(getter_AddRefs(path));
139   NS_ENSURE_SUCCESS(rv, rv);
140 
141   bool isServer, directory = false;
142   aParentFolder->GetIsServer(&isServer);
143   if (!isServer) GetDirectoryForFolder(path);
144 
145   path->IsDirectory(&directory);
146   if (directory) rv = AddSubFolders(aParentFolder, path, aDeep);
147 
148   return (rv == NS_MSG_FOLDER_EXISTS) ? NS_OK : rv;
149 }
150 
151 /**
152  * Create if missing a Maildir-style folder with "tmp" and "cur" subfolders
153  * but no "new" subfolder, because it doesn't make sense in the mail client
154  * context. ("new" directory is for messages on the server that haven't been
155  *  seen by a mail client).
156  * aFolderName is already "safe" - it has been through NS_MsgHashIfNecessary.
157  */
CreateMaildir(nsIFile * path)158 nsresult nsMsgMaildirStore::CreateMaildir(nsIFile* path) {
159   nsresult rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
160   if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
161     NS_WARNING("Could not create root directory for message folder");
162     return rv;
163   }
164 
165   // Create tmp, cur leaves
166   nsCOMPtr<nsIFile> leaf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
167   NS_ENSURE_SUCCESS(rv, rv);
168 
169   leaf->InitWithFile(path);
170 
171   leaf->AppendNative("tmp"_ns);
172   rv = leaf->Create(nsIFile::DIRECTORY_TYPE, 0700);
173   if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
174     NS_WARNING("Could not create tmp directory for message folder");
175     return rv;
176   }
177 
178   leaf->SetNativeLeafName("cur"_ns);
179   rv = leaf->Create(nsIFile::DIRECTORY_TYPE, 0700);
180   if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
181     NS_WARNING("Could not create cur directory for message folder");
182     return rv;
183   }
184 
185   return NS_OK;
186 }
187 
CreateFolder(nsIMsgFolder * aParent,const nsAString & aFolderName,nsIMsgFolder ** aResult)188 NS_IMETHODIMP nsMsgMaildirStore::CreateFolder(nsIMsgFolder* aParent,
189                                               const nsAString& aFolderName,
190                                               nsIMsgFolder** aResult) {
191   NS_ENSURE_ARG_POINTER(aParent);
192   NS_ENSURE_ARG_POINTER(aResult);
193   if (aFolderName.IsEmpty()) return NS_MSG_ERROR_INVALID_FOLDER_NAME;
194 
195   nsCOMPtr<nsIFile> path;
196   nsresult rv = aParent->GetFilePath(getter_AddRefs(path));
197   NS_ENSURE_SUCCESS(rv, rv);
198 
199   // Get a directory based on our current path
200   bool isServer;
201   aParent->GetIsServer(&isServer);
202   rv = CreateDirectoryForFolder(path, isServer);
203   NS_ENSURE_SUCCESS(rv, rv);
204 
205   // Make sure the new folder name is valid
206   nsAutoString safeFolderName(aFolderName);
207   NS_MsgHashIfNecessary(safeFolderName);
208 
209   path->Append(safeFolderName);
210   bool exists;
211   path->Exists(&exists);
212   if (exists)  // check this because localized names are different from disk
213                // names
214     return NS_MSG_FOLDER_EXISTS;
215 
216   rv = CreateMaildir(path);
217   NS_ENSURE_SUCCESS(rv, rv);
218 
219   nsCOMPtr<nsIMsgFolder> child;
220   // GetFlags and SetFlags in AddSubfolder will fail because we have no db at
221   // this point but mFlags is set.
222   rv = aParent->AddSubfolder(safeFolderName, getter_AddRefs(child));
223   if (!child || NS_FAILED(rv)) {
224     path->Remove(true);  // recursive
225     return rv;
226   }
227 
228   // Create an empty database for this mail folder, set its name from the user
229   nsCOMPtr<nsIMsgDBService> msgDBService =
230       do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
231   if (msgDBService) {
232     nsCOMPtr<nsIMsgDatabase> unusedDB;
233     rv = msgDBService->OpenFolderDB(child, true, getter_AddRefs(unusedDB));
234     if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
235       rv = msgDBService->CreateNewDB(child, getter_AddRefs(unusedDB));
236 
237     if ((NS_SUCCEEDED(rv) || rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) &&
238         unusedDB) {
239       // need to set the folder name
240       nsCOMPtr<nsIDBFolderInfo> folderInfo;
241       rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
242       if (NS_SUCCEEDED(rv)) folderInfo->SetMailboxName(safeFolderName);
243 
244       unusedDB->SetSummaryValid(true);
245       unusedDB->Close(true);
246       aParent->UpdateSummaryTotals(true);
247     } else {
248       MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
249               ("CreateFolder - failed creating db for new folder"));
250       path->Remove(true);  // recursive
251       rv = NS_MSG_CANT_CREATE_FOLDER;
252     }
253   }
254   child.forget(aResult);
255   return rv;
256 }
257 
HasSpaceAvailable(nsIMsgFolder * aFolder,int64_t aSpaceRequested,bool * aResult)258 NS_IMETHODIMP nsMsgMaildirStore::HasSpaceAvailable(nsIMsgFolder* aFolder,
259                                                    int64_t aSpaceRequested,
260                                                    bool* aResult) {
261   NS_ENSURE_ARG_POINTER(aResult);
262   NS_ENSURE_ARG_POINTER(aFolder);
263 
264   nsCOMPtr<nsIFile> pathFile;
265   nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
266   NS_ENSURE_SUCCESS(rv, rv);
267 
268   *aResult = DiskSpaceAvailableInStore(pathFile, aSpaceRequested);
269   if (!*aResult) return NS_ERROR_FILE_NO_DEVICE_SPACE;
270 
271   return NS_OK;
272 }
273 
IsSummaryFileValid(nsIMsgFolder * aFolder,nsIMsgDatabase * aDB,bool * aResult)274 NS_IMETHODIMP nsMsgMaildirStore::IsSummaryFileValid(nsIMsgFolder* aFolder,
275                                                     nsIMsgDatabase* aDB,
276                                                     bool* aResult) {
277   NS_ENSURE_ARG_POINTER(aFolder);
278   NS_ENSURE_ARG_POINTER(aDB);
279   NS_ENSURE_ARG_POINTER(aResult);
280   *aResult = true;
281   nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
282   aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
283   nsresult rv =
284       dbFolderInfo->GetBooleanProperty("maildirValid", false, aResult);
285   if (!*aResult) {
286     nsCOMPtr<nsIFile> newFile;
287     rv = aFolder->GetFilePath(getter_AddRefs(newFile));
288     NS_ENSURE_SUCCESS(rv, rv);
289     newFile->Append(u"cur"_ns);
290 
291     // If the "cur" sub-dir doesn't exist, and there are no messages
292     // in the db, then the folder is probably new and the db is valid.
293     bool exists;
294     newFile->Exists(&exists);
295     if (!exists) {
296       int32_t numMessages;
297       dbFolderInfo->GetNumMessages(&numMessages);
298       if (!numMessages) *aResult = true;
299     }
300   }
301   return rv;
302 }
303 
SetSummaryFileValid(nsIMsgFolder * aFolder,nsIMsgDatabase * aDB,bool aValid)304 NS_IMETHODIMP nsMsgMaildirStore::SetSummaryFileValid(nsIMsgFolder* aFolder,
305                                                      nsIMsgDatabase* aDB,
306                                                      bool aValid) {
307   NS_ENSURE_ARG_POINTER(aFolder);
308   NS_ENSURE_ARG_POINTER(aDB);
309   nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
310   aDB->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
311   NS_ENSURE_STATE(dbFolderInfo);
312   return dbFolderInfo->SetBooleanProperty("maildirValid", aValid);
313 }
314 
DeleteFolder(nsIMsgFolder * aFolder)315 NS_IMETHODIMP nsMsgMaildirStore::DeleteFolder(nsIMsgFolder* aFolder) {
316   NS_ENSURE_ARG_POINTER(aFolder);
317   bool exists;
318 
319   // Delete the Maildir itself.
320   nsCOMPtr<nsIFile> pathFile;
321   nsresult rv = aFolder->GetFilePath(getter_AddRefs(pathFile));
322   NS_ENSURE_SUCCESS(rv, rv);
323 
324   exists = false;
325   pathFile->Exists(&exists);
326   if (exists) {
327     rv = pathFile->Remove(true);
328     NS_ENSURE_SUCCESS(rv, rv);
329   }
330 
331   // Delete any subfolders (.sbd-suffixed directories).
332   AddDirectorySeparator(pathFile);
333   exists = false;
334   pathFile->Exists(&exists);
335   if (exists) {
336     rv = pathFile->Remove(true);
337     NS_ENSURE_SUCCESS(rv, rv);
338   }
339 
340   return NS_OK;
341 }
342 
RenameFolder(nsIMsgFolder * aFolder,const nsAString & aNewName,nsIMsgFolder ** aNewFolder)343 NS_IMETHODIMP nsMsgMaildirStore::RenameFolder(nsIMsgFolder* aFolder,
344                                               const nsAString& aNewName,
345                                               nsIMsgFolder** aNewFolder) {
346   NS_ENSURE_ARG_POINTER(aFolder);
347   NS_ENSURE_ARG_POINTER(aNewFolder);
348 
349   // old path
350   nsCOMPtr<nsIFile> oldPathFile;
351   nsresult rv = aFolder->GetFilePath(getter_AddRefs(oldPathFile));
352   NS_ENSURE_SUCCESS(rv, rv);
353 
354   // old sbd directory
355   nsCOMPtr<nsIFile> sbdPathFile;
356   uint32_t numChildren;
357   aFolder->GetNumSubFolders(&numChildren);
358   if (numChildren > 0) {
359     sbdPathFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
360     NS_ENSURE_SUCCESS(rv, rv);
361     rv = sbdPathFile->InitWithFile(oldPathFile);
362     NS_ENSURE_SUCCESS(rv, rv);
363     GetDirectoryForFolder(sbdPathFile);
364   }
365 
366   // old summary
367   nsCOMPtr<nsIFile> oldSummaryFile;
368   rv = aFolder->GetSummaryFile(getter_AddRefs(oldSummaryFile));
369   NS_ENSURE_SUCCESS(rv, rv);
370 
371   // Validate new name
372   nsAutoString safeName(aNewName);
373   NS_MsgHashIfNecessary(safeName);
374 
375   aFolder->ForceDBClosed();
376 
377   // rename folder
378   rv = oldPathFile->MoveTo(nullptr, safeName);
379   NS_ENSURE_SUCCESS(rv, rv);
380 
381   if (numChildren > 0) {
382     // rename "*.sbd" directory
383     nsAutoString sbdName = safeName;
384     sbdName.AppendLiteral(FOLDER_SUFFIX);
385     sbdPathFile->MoveTo(nullptr, sbdName);
386   }
387 
388   // rename summary
389   nsAutoString summaryName(safeName);
390   summaryName.AppendLiteral(SUMMARY_SUFFIX);
391   oldSummaryFile->MoveTo(nullptr, summaryName);
392 
393   nsCOMPtr<nsIMsgFolder> parentFolder;
394   rv = aFolder->GetParent(getter_AddRefs(parentFolder));
395   if (!parentFolder) return NS_ERROR_NULL_POINTER;
396 
397   return parentFolder->AddSubfolder(safeName, aNewFolder);
398 }
399 
CopyFolder(nsIMsgFolder * aSrcFolder,nsIMsgFolder * aDstFolder,bool aIsMoveFolder,nsIMsgWindow * aMsgWindow,nsIMsgCopyServiceListener * aListener,const nsAString & aNewName)400 NS_IMETHODIMP nsMsgMaildirStore::CopyFolder(
401     nsIMsgFolder* aSrcFolder, nsIMsgFolder* aDstFolder, bool aIsMoveFolder,
402     nsIMsgWindow* aMsgWindow, nsIMsgCopyServiceListener* aListener,
403     const nsAString& aNewName) {
404   NS_ENSURE_ARG_POINTER(aSrcFolder);
405   NS_ENSURE_ARG_POINTER(aDstFolder);
406 
407   nsAutoString folderName;
408   if (aNewName.IsEmpty())
409     aSrcFolder->GetName(folderName);
410   else
411     folderName.Assign(aNewName);
412 
413   nsAutoString safeFolderName(folderName);
414   NS_MsgHashIfNecessary(safeFolderName);
415   aSrcFolder->ForceDBClosed();
416 
417   nsCOMPtr<nsIFile> oldPath;
418   nsresult rv = aSrcFolder->GetFilePath(getter_AddRefs(oldPath));
419   NS_ENSURE_SUCCESS(rv, rv);
420 
421   nsCOMPtr<nsIFile> summaryFile;
422   GetSummaryFileLocation(oldPath, getter_AddRefs(summaryFile));
423 
424   nsCOMPtr<nsIFile> newPath;
425   rv = aDstFolder->GetFilePath(getter_AddRefs(newPath));
426   NS_ENSURE_SUCCESS(rv, rv);
427 
428   // create target directory based on our current path
429   bool isServer;
430   aDstFolder->GetIsServer(&isServer);
431   rv = CreateDirectoryForFolder(newPath, isServer);
432   NS_ENSURE_SUCCESS(rv, rv);
433 
434   nsCOMPtr<nsIFile> origPath;
435   oldPath->Clone(getter_AddRefs(origPath));
436 
437   rv = oldPath->CopyTo(newPath, safeFolderName);
438   NS_ENSURE_SUCCESS(rv, rv);  // will fail if a file by that name exists
439 
440   // Copy to dir can fail if file does not exist. If copy fails, we test
441   // if the file exists or not, if it does not that's ok, we continue
442   // without copying it. If it fails and file exist and is not zero sized
443   // there is real problem.
444   nsAutoString dbName(safeFolderName);
445   dbName.AppendLiteral(SUMMARY_SUFFIX);
446   rv = summaryFile->CopyTo(newPath, dbName);
447   if (!NS_SUCCEEDED(rv)) {
448     // Test if the file is not empty
449     bool exists;
450     int64_t fileSize;
451     summaryFile->Exists(&exists);
452     summaryFile->GetFileSize(&fileSize);
453     if (exists && fileSize > 0)
454       NS_ENSURE_SUCCESS(rv, rv);  // Yes, it should have worked!
455     // else case is file is zero sized, no need to copy it,
456     // not an error
457     // else case is file does not exist - not an error
458   }
459 
460   nsCOMPtr<nsIMsgFolder> newMsgFolder;
461   rv = aDstFolder->AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder));
462   NS_ENSURE_SUCCESS(rv, rv);
463 
464   newMsgFolder->SetPrettyName(folderName);
465   uint32_t flags;
466   aSrcFolder->GetFlags(&flags);
467   newMsgFolder->SetFlags(flags);
468   bool changed = false;
469   rv = aSrcFolder->MatchOrChangeFilterDestination(newMsgFolder, true, &changed);
470   if (changed) aSrcFolder->AlertFilterChanged(aMsgWindow);
471 
472   nsTArray<RefPtr<nsIMsgFolder>> subFolders;
473   rv = aSrcFolder->GetSubFolders(subFolders);
474   NS_ENSURE_SUCCESS(rv, rv);
475 
476   // Copy subfolders to the new location
477   nsresult copyStatus = NS_OK;
478   nsCOMPtr<nsIMsgLocalMailFolder> localNewFolder(
479       do_QueryInterface(newMsgFolder, &rv));
480   if (NS_SUCCEEDED(rv)) {
481     for (nsIMsgFolder* folder : subFolders) {
482       copyStatus =
483           localNewFolder->CopyFolderLocal(folder, false, aMsgWindow, aListener);
484       // Test if the call succeeded, if not we have to stop recursive call
485       if (NS_FAILED(copyStatus)) {
486         // Copy failed we have to notify caller to handle the error and stop
487         // moving the folders. In case this happens to the topmost level of
488         // recursive call, then we just need to break from the while loop and
489         // go to error handling code.
490         if (!aIsMoveFolder) return copyStatus;
491         break;
492       }
493     }
494   }
495 
496   if (aIsMoveFolder && NS_SUCCEEDED(copyStatus)) {
497     if (localNewFolder) {
498       nsCOMPtr<nsISupports> srcSupport(do_QueryInterface(aSrcFolder));
499       localNewFolder->OnCopyCompleted(srcSupport, true);
500     }
501 
502     // Notify that the folder that was dragged and dropped has been created.
503     // No need to do this for its subfolders - isMoveFolder will be true for
504     // folder.
505     aDstFolder->NotifyItemAdded(newMsgFolder);
506 
507     nsCOMPtr<nsIMsgFolder> msgParent;
508     aSrcFolder->GetParent(getter_AddRefs(msgParent));
509     aSrcFolder->SetParent(nullptr);
510     if (msgParent) {
511       // The files have already been moved, so delete storage false
512       msgParent->PropagateDelete(aSrcFolder, false, aMsgWindow);
513       oldPath->Remove(true);
514       aSrcFolder->DeleteStorage();
515 
516       nsCOMPtr<nsIFile> parentPath;
517       rv = msgParent->GetFilePath(getter_AddRefs(parentPath));
518       NS_ENSURE_SUCCESS(rv, rv);
519 
520       AddDirectorySeparator(parentPath);
521       nsCOMPtr<nsIDirectoryEnumerator> children;
522       parentPath->GetDirectoryEntries(getter_AddRefs(children));
523       bool more;
524       // checks if the directory is empty or not
525       if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more)
526         parentPath->Remove(true);
527     }
528   } else {
529     // This is the case where the copy of a subfolder failed.
530     // We have to delete the newDirectory tree to make a "rollback".
531     // Someone should add a popup to warn the user that the move was not
532     // possible.
533     if (aIsMoveFolder && NS_FAILED(copyStatus)) {
534       nsCOMPtr<nsIMsgFolder> msgParent;
535       newMsgFolder->ForceDBClosed();
536       newMsgFolder->GetParent(getter_AddRefs(msgParent));
537       newMsgFolder->SetParent(nullptr);
538       if (msgParent) {
539         msgParent->PropagateDelete(newMsgFolder, false, aMsgWindow);
540         newMsgFolder->DeleteStorage();
541         AddDirectorySeparator(newPath);
542         newPath->Remove(true);  // berkeley mailbox
543       }
544       return NS_ERROR_FAILURE;
545     }
546   }
547   return NS_OK;
548 }
549 
550 NS_IMETHODIMP
GetNewMsgOutputStream(nsIMsgFolder * aFolder,nsIMsgDBHdr ** aNewMsgHdr,bool * aReusable,nsIOutputStream ** aResult)551 nsMsgMaildirStore::GetNewMsgOutputStream(nsIMsgFolder* aFolder,
552                                          nsIMsgDBHdr** aNewMsgHdr,
553                                          bool* aReusable,
554                                          nsIOutputStream** aResult) {
555   NS_ENSURE_ARG_POINTER(aFolder);
556   NS_ENSURE_ARG_POINTER(aNewMsgHdr);
557   NS_ENSURE_ARG_POINTER(aReusable);
558   NS_ENSURE_ARG_POINTER(aResult);
559 
560   *aReusable = false;  // message per file
561 
562   nsCOMPtr<nsIMsgDatabase> db;
563   nsresult rv = aFolder->GetMsgDatabase(getter_AddRefs(db));
564   NS_ENSURE_SUCCESS(rv, rv);
565 
566   if (!*aNewMsgHdr) {
567     rv = db->CreateNewHdr(nsMsgKey_None, aNewMsgHdr);
568     NS_ENSURE_SUCCESS(rv, rv);
569   }
570   // With maildir, messages have whole file to themselves.
571   (*aNewMsgHdr)->SetMessageOffset(0);
572 
573   // We're going to save the new message into the maildir 'tmp' folder.
574   // When the message is completed, it can be moved to 'cur'.
575   nsCOMPtr<nsIFile> newFile;
576   rv = aFolder->GetFilePath(getter_AddRefs(newFile));
577   NS_ENSURE_SUCCESS(rv, rv);
578   newFile->Append(u"tmp"_ns);
579 
580   // let's check if the folder exists
581   // XXX TODO: kill this and make sure maildir creation includes cur/tmp
582   bool exists;
583   newFile->Exists(&exists);
584   if (!exists) {
585     MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
586             ("GetNewMsgOutputStream - tmp subfolder does not exist!!"));
587     rv = newFile->Create(nsIFile::DIRECTORY_TYPE, 0755);
588     NS_ENSURE_SUCCESS(rv, rv);
589   }
590 
591   // Generate the 'tmp' file name based on timestamp.
592   // (We'll use the Message-ID as the basis for the final filename,
593   // but we don't have headers at this point).
594   nsAutoCString newName;
595   newName.AppendInt(static_cast<int64_t>(PR_Now()));
596   newFile->AppendNative(newName);
597 
598   // CreateUnique, in case we get more than one message per millisecond :-)
599   rv = newFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
600   NS_ENSURE_SUCCESS(rv, rv);
601   newFile->GetNativeLeafName(newName);
602   // save the file name in the message header - otherwise no way to retrieve it
603   (*aNewMsgHdr)->SetStringProperty("storeToken", newName.get());
604   return MsgNewBufferedFileOutputStream(aResult, newFile,
605                                         PR_WRONLY | PR_CREATE_FILE, 00600);
606 }
607 
608 NS_IMETHODIMP
DiscardNewMessage(nsIOutputStream * aOutputStream,nsIMsgDBHdr * aNewHdr)609 nsMsgMaildirStore::DiscardNewMessage(nsIOutputStream* aOutputStream,
610                                      nsIMsgDBHdr* aNewHdr) {
611   NS_ENSURE_ARG_POINTER(aOutputStream);
612   NS_ENSURE_ARG_POINTER(aNewHdr);
613 
614   aOutputStream->Close();
615   // file path is stored in message header property "storeToken"
616   nsAutoCString fileName;
617   aNewHdr->GetStringProperty("storeToken", getter_Copies(fileName));
618   if (fileName.IsEmpty()) return NS_ERROR_FAILURE;
619 
620   nsCOMPtr<nsIFile> path;
621   nsCOMPtr<nsIMsgFolder> folder;
622   nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder));
623   NS_ENSURE_SUCCESS(rv, rv);
624   rv = folder->GetFilePath(getter_AddRefs(path));
625   NS_ENSURE_SUCCESS(rv, rv);
626 
627   // path to the message download folder
628   path->Append(u"tmp"_ns);
629   path->AppendNative(fileName);
630 
631   return path->Remove(false);
632 }
633 
634 NS_IMETHODIMP
FinishNewMessage(nsIOutputStream * aOutputStream,nsIMsgDBHdr * aNewHdr)635 nsMsgMaildirStore::FinishNewMessage(nsIOutputStream* aOutputStream,
636                                     nsIMsgDBHdr* aNewHdr) {
637   NS_ENSURE_ARG_POINTER(aOutputStream);
638   NS_ENSURE_ARG_POINTER(aNewHdr);
639 
640   aOutputStream->Close();
641 
642   nsCOMPtr<nsIFile> folderPath;
643   nsCOMPtr<nsIMsgFolder> folder;
644   nsresult rv = aNewHdr->GetFolder(getter_AddRefs(folder));
645   NS_ENSURE_SUCCESS(rv, rv);
646   rv = folder->GetFilePath(getter_AddRefs(folderPath));
647   NS_ENSURE_SUCCESS(rv, rv);
648 
649   // tmp filename is stored in "storeToken".
650   // By now we'll have the Message-ID, which we'll base the final filename on.
651   nsAutoCString tmpName;
652   aNewHdr->GetStringProperty("storeToken", getter_Copies(tmpName));
653   if (tmpName.IsEmpty()) {
654     NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!");
655     return NS_ERROR_FAILURE;
656   }
657 
658   // path to the new destination
659   nsCOMPtr<nsIFile> curPath;
660   folderPath->Clone(getter_AddRefs(curPath));
661   curPath->Append(u"cur"_ns);
662 
663   // let's check if the folder exists
664   // XXX TODO: kill this and make sure maildir creation includes cur/tmp
665   bool exists;
666   curPath->Exists(&exists);
667   if (!exists) {
668     rv = curPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
669     NS_ENSURE_SUCCESS(rv, rv);
670   }
671 
672   // path to the downloaded message
673   nsCOMPtr<nsIFile> fromPath;
674   folderPath->Clone(getter_AddRefs(fromPath));
675   fromPath->Append(u"tmp"_ns);
676   fromPath->AppendNative(tmpName);
677 
678   // Check that the message is still in tmp.
679   // XXX TODO: revisit this. I think it's needed because the
680   // pairing rules for:
681   // GetNewMsgOutputStream(), FinishNewMessage(),
682   // MoveNewlyDownloadedMessage() and DiscardNewMessage()
683   // are not well defined.
684   // If they are sorted out, this code can be removed.
685   fromPath->Exists(&exists);
686   if (!exists) {
687     // Perhaps the message has already moved. See bug 1028372 to fix this.
688     nsCOMPtr<nsIFile> existingPath;
689     curPath->Clone(getter_AddRefs(existingPath));
690     existingPath->AppendNative(tmpName);
691     existingPath->Exists(&exists);
692     if (exists)  // then there is nothing to do
693       return NS_OK;
694 
695     NS_ERROR("FinishNewMessage - oops! file does not exist!");
696     return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
697   }
698 
699   nsCString msgID;
700   aNewHdr->GetMessageId(getter_Copies(msgID));
701 
702   nsCString baseName;
703   // For missing or suspiciously-short Message-IDs, use a timestamp
704   // instead.
705   // This also avoids some special filenames we can't use in windows (CON,
706   // AUX, NUL, LPT1 etc...). With an extension (eg "LPT4.txt") they're all
707   // below 9 chars.
708   if (msgID.Length() < 9) {
709     baseName.AppendInt(static_cast<int64_t>(PR_Now()));
710   } else {
711     percentEncode(msgID, baseName);
712     // No length limit on Message-Id header, but lets clip our filenames
713     // well below any MAX_PATH limits.
714     if (baseName.Length() > (128 - 4)) {
715       baseName.SetLength(128 - 4);  // (4 for ".eml")
716     }
717   }
718 
719   nsCOMPtr<nsIFile> toPath;
720   curPath->Clone(getter_AddRefs(toPath));
721   nsCString toName(baseName);
722   toName.Append(".eml");
723   toPath->AppendNative(toName);
724 
725   // Using CreateUnique in case we have duplicate Message-Ids
726   rv = toPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
727   if (NS_FAILED(rv)) {
728     // NS_ERROR_FILE_TOO_BIG means CreateUnique() bailed out at 10000 attempts.
729     if (rv != NS_ERROR_FILE_TOO_BIG) {
730       NS_ENSURE_SUCCESS(rv, rv);
731     }
732     // As a last resort, fall back to using timestamp as filename.
733     toName.SetLength(0);
734     toName.AppendInt(static_cast<int64_t>(PR_Now()));
735     toName.Append(".eml");
736     toPath->SetNativeLeafName(toName);
737     rv = toPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
738     NS_ENSURE_SUCCESS(rv, rv);
739   }
740 
741   // Move into place (using whatever name CreateUnique() settled upon).
742   toPath->GetNativeLeafName(toName);
743   rv = fromPath->MoveToNative(curPath, toName);
744   NS_ENSURE_SUCCESS(rv, rv);
745   // Update the db to reflect the final filename.
746   aNewHdr->SetStringProperty("storeToken", toName.get());
747   return NS_OK;
748 }
749 
750 NS_IMETHODIMP
MoveNewlyDownloadedMessage(nsIMsgDBHdr * aHdr,nsIMsgFolder * aDestFolder,bool * aResult)751 nsMsgMaildirStore::MoveNewlyDownloadedMessage(nsIMsgDBHdr* aHdr,
752                                               nsIMsgFolder* aDestFolder,
753                                               bool* aResult) {
754   NS_ENSURE_ARG_POINTER(aHdr);
755   NS_ENSURE_ARG_POINTER(aDestFolder);
756   NS_ENSURE_ARG_POINTER(aResult);
757 
758   nsCOMPtr<nsIFile> folderPath;
759   nsCOMPtr<nsIMsgFolder> folder;
760   nsresult rv = aHdr->GetFolder(getter_AddRefs(folder));
761   NS_ENSURE_SUCCESS(rv, rv);
762   rv = folder->GetFilePath(getter_AddRefs(folderPath));
763   NS_ENSURE_SUCCESS(rv, rv);
764 
765   // file path is stored in message header property
766   nsAutoCString fileName;
767   aHdr->GetStringProperty("storeToken", getter_Copies(fileName));
768   if (fileName.IsEmpty()) {
769     NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!");
770     return NS_ERROR_FAILURE;
771   }
772 
773   // path to the downloaded message
774   nsCOMPtr<nsIFile> fromPath;
775   folderPath->Clone(getter_AddRefs(fromPath));
776   fromPath->Append(u"cur"_ns);
777   fromPath->AppendNative(fileName);
778 
779   // let's check if the tmp file exists
780   bool exists;
781   fromPath->Exists(&exists);
782   if (!exists) {
783     NS_ERROR("FinishNewMessage - oops! file does not exist!");
784     return NS_ERROR_FAILURE;
785   }
786 
787   // move to the "cur" subfolder
788   nsCOMPtr<nsIFile> toPath;
789   aDestFolder->GetFilePath(getter_AddRefs(folderPath));
790   folderPath->Clone(getter_AddRefs(toPath));
791   toPath->Append(u"cur"_ns);
792 
793   // let's check if the folder exists
794   // XXX TODO: kill this and make sure maildir creation includes cur/tmp
795   toPath->Exists(&exists);
796   if (!exists) {
797     rv = toPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
798     NS_ENSURE_SUCCESS(rv, rv);
799   }
800 
801   nsCOMPtr<nsIMsgDatabase> destMailDB;
802   rv = aDestFolder->GetMsgDatabase(getter_AddRefs(destMailDB));
803   NS_WARNING_ASSERTION(destMailDB && NS_SUCCEEDED(rv),
804                        "failed to open mail db moving message");
805 
806   nsCOMPtr<nsIMsgDBHdr> newHdr;
807   if (destMailDB)
808     rv = destMailDB->CopyHdrFromExistingHdr(nsMsgKey_None, aHdr, true,
809                                             getter_AddRefs(newHdr));
810   if (NS_SUCCEEDED(rv) && !newHdr) rv = NS_ERROR_UNEXPECTED;
811 
812   if (NS_FAILED(rv)) {
813     aDestFolder->ThrowAlertMsg("filterFolderHdrAddFailed", nullptr);
814     return rv;
815   }
816 
817   nsCOMPtr<nsIFile> existingPath;
818   toPath->Clone(getter_AddRefs(existingPath));
819   existingPath->AppendNative(fileName);
820   existingPath->Exists(&exists);
821 
822   if (exists) {
823     rv = existingPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
824     NS_ENSURE_SUCCESS(rv, rv);
825     existingPath->GetNativeLeafName(fileName);
826     newHdr->SetStringProperty("storeToken", fileName.get());
827   }
828 
829   rv = fromPath->MoveToNative(toPath, fileName);
830   *aResult = NS_SUCCEEDED(rv);
831   if (NS_FAILED(rv))
832     aDestFolder->ThrowAlertMsg("filterFolderWriteFailed", nullptr);
833 
834   if (NS_FAILED(rv)) {
835     if (destMailDB) destMailDB->Close(true);
836 
837     return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
838   }
839 
840   bool movedMsgIsNew = false;
841   // if we have made it this far then the message has successfully been
842   // written to the new folder now add the header to the destMailDB.
843 
844   uint32_t newFlags;
845   newHdr->GetFlags(&newFlags);
846   nsMsgKey msgKey;
847   newHdr->GetMessageKey(&msgKey);
848   if (!(newFlags & nsMsgMessageFlags::Read)) {
849     nsCString junkScoreStr;
850     (void)newHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr));
851     if (atoi(junkScoreStr.get()) != nsIJunkMailPlugin::IS_SPAM_SCORE) {
852       newHdr->OrFlags(nsMsgMessageFlags::New, &newFlags);
853       destMailDB->AddToNewList(msgKey);
854       movedMsgIsNew = true;
855     }
856   }
857 
858   nsCOMPtr<nsIMsgFolderNotificationService> notifier(
859       do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
860   if (notifier) notifier->NotifyMsgAdded(newHdr);
861 
862   if (movedMsgIsNew) {
863     aDestFolder->SetHasNewMessages(true);
864 
865     // Notify the message was moved.
866     if (notifier) {
867       notifier->NotifyMsgUnincorporatedMoved(folder, newHdr);
868     }
869   }
870 
871   nsCOMPtr<nsIMsgDatabase> sourceDB;
872   rv = folder->GetMsgDatabase(getter_AddRefs(sourceDB));
873 
874   if (NS_SUCCEEDED(rv) && sourceDB) sourceDB->RemoveHeaderMdbRow(aHdr);
875 
876   destMailDB->SetSummaryValid(true);
877   aDestFolder->UpdateSummaryTotals(true);
878   destMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
879   return rv;
880 }
881 
882 NS_IMETHODIMP
GetMsgInputStream(nsIMsgFolder * aMsgFolder,const nsACString & aMsgToken,int64_t * aOffset,nsIMsgDBHdr * aMsgHdr,bool * aReusable,nsIInputStream ** aResult)883 nsMsgMaildirStore::GetMsgInputStream(nsIMsgFolder* aMsgFolder,
884                                      const nsACString& aMsgToken,
885                                      int64_t* aOffset, nsIMsgDBHdr* aMsgHdr,
886                                      bool* aReusable,
887                                      nsIInputStream** aResult) {
888   NS_ENSURE_ARG_POINTER(aMsgFolder);
889   NS_ENSURE_ARG_POINTER(aOffset);
890   NS_ENSURE_ARG_POINTER(aResult);
891 
892   *aReusable = false;  // message per file
893   *aOffset = 0;
894 
895   // construct path to file
896   nsCOMPtr<nsIFile> path;
897   nsresult rv = aMsgFolder->GetFilePath(getter_AddRefs(path));
898   NS_ENSURE_SUCCESS(rv, rv);
899 
900   if (aMsgToken.IsEmpty()) {
901     MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
902             ("GetMsgInputStream - empty storeToken!!"));
903     return NS_ERROR_FAILURE;
904   }
905 
906   path->Append(u"cur"_ns);
907 
908   // let's check if the folder exists
909   // XXX TODO: kill this and make sure maildir creation includes cur/tmp
910   bool exists;
911   path->Exists(&exists);
912   if (!exists) {
913     MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
914             ("GetMsgInputStream - oops! cur subfolder does not exist!"));
915     rv = path->Create(nsIFile::DIRECTORY_TYPE, 0755);
916     NS_ENSURE_SUCCESS(rv, rv);
917   }
918 
919   path->AppendNative(aMsgToken);
920   return NS_NewLocalFileInputStream(aResult, path);
921 }
922 
DeleteMessages(const nsTArray<RefPtr<nsIMsgDBHdr>> & aHdrArray)923 NS_IMETHODIMP nsMsgMaildirStore::DeleteMessages(
924     const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray) {
925   nsCOMPtr<nsIMsgFolder> folder;
926 
927   for (auto msgHdr : aHdrArray) {
928     msgHdr->GetFolder(getter_AddRefs(folder));
929     nsCOMPtr<nsIFile> path;
930     nsresult rv = folder->GetFilePath(getter_AddRefs(path));
931     NS_ENSURE_SUCCESS(rv, rv);
932     nsAutoCString fileName;
933     msgHdr->GetStringProperty("storeToken", getter_Copies(fileName));
934 
935     if (fileName.IsEmpty()) {
936       MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
937               ("DeleteMessages - empty storeToken!!"));
938       // Perhaps an offline store has not downloaded this particular message.
939       continue;
940     }
941 
942     path->Append(u"cur"_ns);
943     path->AppendNative(fileName);
944 
945     // Let's check if the message exists.
946     bool exists;
947     path->Exists(&exists);
948     if (!exists) {
949       MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
950               ("DeleteMessages - file does not exist !!"));
951       // Perhaps an offline store has not downloaded this particular message.
952       continue;
953     }
954     path->Remove(false);
955   }
956   return NS_OK;
957 }
958 
959 NS_IMETHODIMP
CopyMessages(bool aIsMove,const nsTArray<RefPtr<nsIMsgDBHdr>> & aHdrArray,nsIMsgFolder * aDstFolder,nsIMsgCopyServiceListener * aListener,nsTArray<RefPtr<nsIMsgDBHdr>> & aDstHdrs,nsITransaction ** aUndoAction,bool * aCopyDone)960 nsMsgMaildirStore::CopyMessages(bool aIsMove,
961                                 const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray,
962                                 nsIMsgFolder* aDstFolder,
963                                 nsIMsgCopyServiceListener* aListener,
964                                 nsTArray<RefPtr<nsIMsgDBHdr>>& aDstHdrs,
965                                 nsITransaction** aUndoAction, bool* aCopyDone) {
966   NS_ENSURE_ARG_POINTER(aDstFolder);
967   NS_ENSURE_ARG_POINTER(aCopyDone);
968   NS_ENSURE_ARG_POINTER(aUndoAction);
969 
970   *aCopyDone = false;
971   if (aHdrArray.IsEmpty()) {
972     return NS_ERROR_INVALID_ARG;
973   }
974   nsCOMPtr<nsIMsgFolder> srcFolder;
975   nsresult rv;
976   nsIMsgDBHdr* msgHdr = aHdrArray[0];
977   rv = msgHdr->GetFolder(getter_AddRefs(srcFolder));
978   NS_ENSURE_SUCCESS(rv, rv);
979 
980   // Both source and destination folders must use maildir type store.
981   nsCOMPtr<nsIMsgPluggableStore> srcStore;
982   nsAutoCString srcType;
983   srcFolder->GetMsgStore(getter_AddRefs(srcStore));
984   if (srcStore) srcStore->GetStoreType(srcType);
985   nsCOMPtr<nsIMsgPluggableStore> dstStore;
986   nsAutoCString dstType;
987   aDstFolder->GetMsgStore(getter_AddRefs(dstStore));
988   if (dstStore) dstStore->GetStoreType(dstType);
989   if (!srcType.EqualsLiteral("maildir") || !dstType.EqualsLiteral("maildir"))
990     return NS_OK;
991 
992   // Both source and destination must be local folders. In theory we could
993   //   do efficient copies of the offline store of IMAP, but this is not
994   //   supported yet. For that, we need to deal with both correct handling
995   //   of deletes from the src server, and msgKey = UIDL in the dst folder.
996   nsCOMPtr<nsIMsgLocalMailFolder> destLocalFolder(
997       do_QueryInterface(aDstFolder));
998   if (!destLocalFolder) return NS_OK;
999   nsCOMPtr<nsIMsgLocalMailFolder> srcLocalFolder(do_QueryInterface(srcFolder));
1000   if (!srcLocalFolder) return NS_OK;
1001 
1002   // We should be able to use a file move for an efficient copy.
1003 
1004   nsCOMPtr<nsIFile> destFolderPath;
1005   nsCOMPtr<nsIMsgDatabase> destDB;
1006   aDstFolder->GetMsgDatabase(getter_AddRefs(destDB));
1007   rv = aDstFolder->GetFilePath(getter_AddRefs(destFolderPath));
1008   NS_ENSURE_SUCCESS(rv, rv);
1009   destFolderPath->Append(u"cur"_ns);
1010 
1011   nsCOMPtr<nsIFile> srcFolderPath;
1012   rv = srcFolder->GetFilePath(getter_AddRefs(srcFolderPath));
1013   NS_ENSURE_SUCCESS(rv, rv);
1014   srcFolderPath->Append(u"cur"_ns);
1015 
1016   nsCOMPtr<nsIMsgDatabase> srcDB;
1017   srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
1018   RefPtr<nsLocalMoveCopyMsgTxn> msgTxn = new nsLocalMoveCopyMsgTxn;
1019   NS_ENSURE_TRUE(msgTxn, NS_ERROR_OUT_OF_MEMORY);
1020   if (NS_SUCCEEDED(msgTxn->Init(srcFolder, aDstFolder, aIsMove))) {
1021     if (aIsMove)
1022       msgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
1023     else
1024       msgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
1025   }
1026 
1027   if (aListener) aListener->OnStartCopy();
1028 
1029   aDstHdrs.Clear();
1030   aDstHdrs.SetCapacity(aHdrArray.Length());
1031 
1032   for (auto srcHdr : aHdrArray) {
1033     nsMsgKey srcKey;
1034     srcHdr->GetMessageKey(&srcKey);
1035     msgTxn->AddSrcKey(srcKey);
1036     nsAutoCString fileName;
1037     srcHdr->GetStringProperty("storeToken", getter_Copies(fileName));
1038     if (fileName.IsEmpty()) {
1039       MOZ_LOG(MailDirLog, mozilla::LogLevel::Info,
1040               ("GetMsgInputStream - empty storeToken!!"));
1041       return NS_ERROR_FAILURE;
1042     }
1043 
1044     nsCOMPtr<nsIFile> srcFile;
1045     rv = srcFolderPath->Clone(getter_AddRefs(srcFile));
1046     NS_ENSURE_SUCCESS(rv, rv);
1047     srcFile->AppendNative(fileName);
1048 
1049     nsCOMPtr<nsIFile> destFile;
1050     destFolderPath->Clone(getter_AddRefs(destFile));
1051     destFile->AppendNative(fileName);
1052     bool exists;
1053     destFile->Exists(&exists);
1054     if (exists) {
1055       rv = destFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
1056       NS_ENSURE_SUCCESS(rv, rv);
1057       destFile->GetNativeLeafName(fileName);
1058     }
1059     if (aIsMove)
1060       rv = srcFile->MoveToNative(destFolderPath, fileName);
1061     else
1062       rv = srcFile->CopyToNative(destFolderPath, fileName);
1063     NS_ENSURE_SUCCESS(rv, rv);
1064 
1065     nsCOMPtr<nsIMsgDBHdr> destHdr;
1066     if (destDB) {
1067       rv = destDB->CopyHdrFromExistingHdr(nsMsgKey_None, srcHdr, true,
1068                                           getter_AddRefs(destHdr));
1069       NS_ENSURE_SUCCESS(rv, rv);
1070       destHdr->SetStringProperty("storeToken", fileName.get());
1071       aDstHdrs.AppendElement(destHdr);
1072       nsMsgKey dstKey;
1073       destHdr->GetMessageKey(&dstKey);
1074       msgTxn->AddDstKey(dstKey);
1075       if (aListener) aListener->SetMessageKey(dstKey);
1076     }
1077   }
1078   nsCOMPtr<nsIMsgFolderNotificationService> notifier(
1079       do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
1080   if (notifier) {
1081     notifier->NotifyMsgsMoveCopyCompleted(aIsMove, aHdrArray, aDstFolder,
1082                                           aDstHdrs);
1083   }
1084 
1085   // For now, we only support local dest folders, and for those we are done and
1086   // can delete the messages. Perhaps this should be moved into the folder
1087   // when we try to support other folder types.
1088   if (aIsMove) {
1089     for (auto msgDBHdr : aHdrArray) {
1090       srcDB->DeleteHeader(msgDBHdr, nullptr, false, true);
1091     }
1092   }
1093 
1094   *aCopyDone = true;
1095   nsCOMPtr<nsISupports> srcSupports(do_QueryInterface(srcFolder));
1096   if (destLocalFolder) destLocalFolder->OnCopyCompleted(srcSupports, true);
1097   if (aListener) aListener->OnStopCopy(NS_OK);
1098   msgTxn.forget(aUndoAction);
1099   return NS_OK;
1100 }
1101 
1102 NS_IMETHODIMP
GetSupportsCompaction(bool * aSupportsCompaction)1103 nsMsgMaildirStore::GetSupportsCompaction(bool* aSupportsCompaction) {
1104   NS_ENSURE_ARG_POINTER(aSupportsCompaction);
1105   *aSupportsCompaction = false;
1106   return NS_OK;
1107 }
1108 
CompactFolder(nsIMsgFolder * aFolder,nsIUrlListener * aListener,nsIMsgWindow * aMsgWindow)1109 NS_IMETHODIMP nsMsgMaildirStore::CompactFolder(nsIMsgFolder* aFolder,
1110                                                nsIUrlListener* aListener,
1111                                                nsIMsgWindow* aMsgWindow) {
1112   return NS_OK;
1113 }
1114 
1115 class MaildirStoreParser {
1116  public:
1117   MaildirStoreParser(nsIMsgFolder* aFolder, nsIMsgDatabase* aMsgDB,
1118                      nsIDirectoryEnumerator* aDirectoryEnumerator,
1119                      nsIUrlListener* aUrlListener);
1120   virtual ~MaildirStoreParser();
1121 
1122   nsresult ParseNextMessage(nsIFile* aFile);
1123   static void TimerCallback(nsITimer* aTimer, void* aClosure);
1124   nsresult StartTimer();
1125 
1126   nsCOMPtr<nsIDirectoryEnumerator> m_directoryEnumerator;
1127   nsCOMPtr<nsIMsgFolder> m_folder;
1128   nsCOMPtr<nsIMsgDatabase> m_db;
1129   nsCOMPtr<nsITimer> m_timer;
1130   nsCOMPtr<nsIUrlListener> m_listener;
1131 };
1132 
MaildirStoreParser(nsIMsgFolder * aFolder,nsIMsgDatabase * aMsgDB,nsIDirectoryEnumerator * aDirEnum,nsIUrlListener * aUrlListener)1133 MaildirStoreParser::MaildirStoreParser(nsIMsgFolder* aFolder,
1134                                        nsIMsgDatabase* aMsgDB,
1135                                        nsIDirectoryEnumerator* aDirEnum,
1136                                        nsIUrlListener* aUrlListener) {
1137   m_folder = aFolder;
1138   m_db = aMsgDB;
1139   m_directoryEnumerator = aDirEnum;
1140   m_listener = aUrlListener;
1141 }
1142 
~MaildirStoreParser()1143 MaildirStoreParser::~MaildirStoreParser() {}
1144 
ParseNextMessage(nsIFile * aFile)1145 nsresult MaildirStoreParser::ParseNextMessage(nsIFile* aFile) {
1146   nsresult rv;
1147   NS_ENSURE_TRUE(m_db, NS_ERROR_NULL_POINTER);
1148   nsCOMPtr<nsIInputStream> inputStream;
1149   nsCOMPtr<nsIMsgParseMailMsgState> msgParser =
1150       do_CreateInstance(NS_PARSEMAILMSGSTATE_CONTRACTID, &rv);
1151   NS_ENSURE_SUCCESS(rv, rv);
1152   msgParser->SetMailDB(m_db);
1153   nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
1154   rv = m_db->CreateNewHdr(nsMsgKey_None, getter_AddRefs(newMsgHdr));
1155   NS_ENSURE_SUCCESS(rv, rv);
1156 
1157   newMsgHdr->SetMessageOffset(0);
1158 
1159   rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
1160   if (NS_SUCCEEDED(rv) && inputStream) {
1161     RefPtr<nsMsgLineStreamBuffer> inputStreamBuffer =
1162         new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE, true, false);
1163     int64_t fileSize;
1164     aFile->GetFileSize(&fileSize);
1165     msgParser->SetNewMsgHdr(newMsgHdr);
1166     msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
1167     msgParser->SetEnvelopePos(0);
1168     bool needMoreData = false;
1169     char* newLine = nullptr;
1170     uint32_t numBytesInLine = 0;
1171     // we only have to read the headers, because we know the message size
1172     // from the file size. So we can do this in one time slice.
1173     do {
1174       newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine,
1175                                                 needMoreData);
1176       if (newLine) {
1177         msgParser->ParseAFolderLine(newLine, numBytesInLine);
1178         free(newLine);
1179       }
1180     } while (newLine && numBytesInLine > 0);
1181 
1182     msgParser->FinishHeader();
1183     // A single message needs to be less than 4GB
1184     newMsgHdr->SetMessageSize((uint32_t)fileSize);
1185     m_db->AddNewHdrToDB(newMsgHdr, true);
1186     nsAutoCString storeToken;
1187     aFile->GetNativeLeafName(storeToken);
1188     newMsgHdr->SetStringProperty("storeToken", storeToken.get());
1189   }
1190   NS_ENSURE_SUCCESS(rv, rv);
1191   return rv;
1192 }
1193 
TimerCallback(nsITimer * aTimer,void * aClosure)1194 void MaildirStoreParser::TimerCallback(nsITimer* aTimer, void* aClosure) {
1195   MaildirStoreParser* parser = (MaildirStoreParser*)aClosure;
1196   bool hasMore;
1197   parser->m_directoryEnumerator->HasMoreElements(&hasMore);
1198   if (!hasMore) {
1199     nsCOMPtr<nsIMsgPluggableStore> store;
1200     parser->m_folder->GetMsgStore(getter_AddRefs(store));
1201     parser->m_timer->Cancel();
1202     if (parser->m_db) parser->m_db->SetSummaryValid(true);
1203     // store->SetSummaryFileValid(parser->m_folder, parser->m_db, true);
1204     if (parser->m_listener) {
1205       nsresult rv;
1206       nsCOMPtr<nsIMailboxUrl> mailboxurl =
1207           do_CreateInstance(NS_MAILBOXURL_CONTRACTID, &rv);
1208       if (NS_SUCCEEDED(rv) && mailboxurl) {
1209         nsCOMPtr<nsIMsgMailNewsUrl> url = do_QueryInterface(mailboxurl);
1210         url->SetUpdatingFolder(true);
1211         nsAutoCString uriSpec("mailbox://");
1212         // ### TODO - what if SetSpec fails?
1213         (void)url->SetSpecInternal(uriSpec);
1214         parser->m_listener->OnStopRunningUrl(url, NS_OK);
1215       }
1216     }
1217     // Parsing complete and timer cancelled, so we release the parser object.
1218     delete parser;
1219     return;
1220   }
1221   nsCOMPtr<nsIFile> currentFile;
1222   nsresult rv =
1223       parser->m_directoryEnumerator->GetNextFile(getter_AddRefs(currentFile));
1224   if (NS_SUCCEEDED(rv)) rv = parser->ParseNextMessage(currentFile);
1225   if (NS_FAILED(rv) && parser->m_listener)
1226     parser->m_listener->OnStopRunningUrl(nullptr, NS_ERROR_FAILURE);
1227 }
1228 
StartTimer()1229 nsresult MaildirStoreParser::StartTimer() {
1230   nsresult rv;
1231   m_timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
1232   NS_ENSURE_SUCCESS(rv, rv);
1233   m_timer->InitWithNamedFuncCallback(TimerCallback, (void*)this, 0,
1234                                      nsITimer::TYPE_REPEATING_SLACK,
1235                                      "MaildirStoreParser::TimerCallback");
1236   return NS_OK;
1237 }
1238 
RebuildIndex(nsIMsgFolder * aFolder,nsIMsgDatabase * aMsgDB,nsIMsgWindow * aMsgWindow,nsIUrlListener * aListener)1239 NS_IMETHODIMP nsMsgMaildirStore::RebuildIndex(nsIMsgFolder* aFolder,
1240                                               nsIMsgDatabase* aMsgDB,
1241                                               nsIMsgWindow* aMsgWindow,
1242                                               nsIUrlListener* aListener) {
1243   NS_ENSURE_ARG_POINTER(aFolder);
1244   // This code needs to iterate over the maildir files, and parse each
1245   // file and add a msg hdr to the db for the file.
1246   nsCOMPtr<nsIFile> path;
1247   nsresult rv = aFolder->GetFilePath(getter_AddRefs(path));
1248   NS_ENSURE_SUCCESS(rv, rv);
1249   path->Append(u"cur"_ns);
1250 
1251   nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
1252   rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
1253   NS_ENSURE_SUCCESS(rv, rv);
1254 
1255   MaildirStoreParser* fileParser =
1256       new MaildirStoreParser(aFolder, aMsgDB, directoryEnumerator, aListener);
1257   NS_ENSURE_TRUE(fileParser, NS_ERROR_OUT_OF_MEMORY);
1258   fileParser->StartTimer();
1259   ResetForceReparse(aMsgDB);
1260   return NS_OK;
1261 }
1262 
ChangeFlags(const nsTArray<RefPtr<nsIMsgDBHdr>> & aHdrArray,uint32_t aFlags,bool aSet)1263 NS_IMETHODIMP nsMsgMaildirStore::ChangeFlags(
1264     const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray, uint32_t aFlags,
1265     bool aSet) {
1266   for (auto msgHdr : aHdrArray) {
1267     // get output stream for header
1268     nsCOMPtr<nsIOutputStream> outputStream;
1269     nsresult rv = GetOutputStream(msgHdr, outputStream);
1270     NS_ENSURE_SUCCESS(rv, rv);
1271     // Seek to x-mozilla-status offset and rewrite value.
1272     rv = UpdateFolderFlag(msgHdr, aSet, aFlags, outputStream);
1273     if (NS_FAILED(rv)) NS_WARNING("updateFolderFlag failed");
1274   }
1275   return NS_OK;
1276 }
1277 
1278 // get output stream from header
GetOutputStream(nsIMsgDBHdr * aHdr,nsCOMPtr<nsIOutputStream> & aOutputStream)1279 nsresult nsMsgMaildirStore::GetOutputStream(
1280     nsIMsgDBHdr* aHdr, nsCOMPtr<nsIOutputStream>& aOutputStream) {
1281   // file name is stored in message header property "storeToken"
1282   nsAutoCString fileName;
1283   aHdr->GetStringProperty("storeToken", getter_Copies(fileName));
1284   if (fileName.IsEmpty()) return NS_ERROR_FAILURE;
1285 
1286   nsCOMPtr<nsIMsgFolder> folder;
1287   nsresult rv = aHdr->GetFolder(getter_AddRefs(folder));
1288   NS_ENSURE_SUCCESS(rv, rv);
1289 
1290   nsCOMPtr<nsIFile> folderPath;
1291   rv = folder->GetFilePath(getter_AddRefs(folderPath));
1292   NS_ENSURE_SUCCESS(rv, rv);
1293 
1294   nsCOMPtr<nsIFile> maildirFile;
1295   folderPath->Clone(getter_AddRefs(maildirFile));
1296   maildirFile->Append(u"cur"_ns);
1297   maildirFile->AppendNative(fileName);
1298 
1299   return MsgGetFileStream(maildirFile, getter_AddRefs(aOutputStream));
1300 }
1301 
ChangeKeywords(const nsTArray<RefPtr<nsIMsgDBHdr>> & aHdrArray,const nsACString & aKeywords,bool aAdd)1302 NS_IMETHODIMP nsMsgMaildirStore::ChangeKeywords(
1303     const nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray, const nsACString& aKeywords,
1304     bool aAdd) {
1305   if (aHdrArray.IsEmpty()) return NS_ERROR_INVALID_ARG;
1306 
1307   nsCOMPtr<nsIOutputStream> outputStream;
1308   nsCOMPtr<nsISeekableStream> seekableStream;
1309 
1310   mozilla::UniquePtr<nsLineBuffer<char>> lineBuffer(new nsLineBuffer<char>);
1311 
1312   nsTArray<nsCString> keywordArray;
1313   ParseString(aKeywords, ' ', keywordArray);
1314 
1315   for (auto message : aHdrArray)  // for each message
1316   {
1317     // get output stream for header
1318     nsCOMPtr<nsIOutputStream> outputStream;
1319     nsresult rv = GetOutputStream(message, outputStream);
1320     NS_ENSURE_SUCCESS(rv, rv);
1321     nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(outputStream, &rv);
1322     NS_ENSURE_SUCCESS(rv, rv);
1323     nsCOMPtr<nsISeekableStream> seekableStream(
1324         do_QueryInterface(inputStream, &rv));
1325     NS_ENSURE_SUCCESS(rv, rv);
1326     uint32_t statusOffset = 0;
1327     (void)message->GetStatusOffset(&statusOffset);
1328     uint64_t desiredOffset = statusOffset;
1329 
1330     ChangeKeywordsHelper(message, desiredOffset, *lineBuffer, keywordArray,
1331                          aAdd, outputStream, seekableStream, inputStream);
1332     if (inputStream) inputStream->Close();
1333     // ### TODO - if growKeywords property is set on the message header,
1334     // we need to rewrite the message file with extra room for the keywords,
1335     // or schedule some sort of background task to do this.
1336   }
1337   return NS_OK;
1338 }
1339 
GetStoreType(nsACString & aType)1340 NS_IMETHODIMP nsMsgMaildirStore::GetStoreType(nsACString& aType) {
1341   aType.AssignLiteral("maildir");
1342   return NS_OK;
1343 }
1344 
1345 /**
1346  * Finds the directory associated with this folder. That is if the path is
1347  * c:\Inbox, it will return c:\Inbox.sbd if it succeeds. Path is strictly
1348  * an out parameter.
1349  */
GetDirectoryForFolder(nsIFile * path)1350 nsresult nsMsgMaildirStore::GetDirectoryForFolder(nsIFile* path) {
1351   // add directory separator to the path
1352   nsAutoString leafName;
1353   path->GetLeafName(leafName);
1354   leafName.AppendLiteral(FOLDER_SUFFIX);
1355   return path->SetLeafName(leafName);
1356 }
1357 
CreateDirectoryForFolder(nsIFile * path,bool aIsServer)1358 nsresult nsMsgMaildirStore::CreateDirectoryForFolder(nsIFile* path,
1359                                                      bool aIsServer) {
1360   nsresult rv = NS_OK;
1361   if (!aIsServer) {
1362     rv = GetDirectoryForFolder(path);
1363     NS_ENSURE_SUCCESS(rv, rv);
1364   }
1365   bool pathIsDirectory = false;
1366   path->IsDirectory(&pathIsDirectory);
1367   if (!pathIsDirectory) {
1368     bool pathExists;
1369     path->Exists(&pathExists);
1370     // If for some reason there's a file with the directory separator
1371     // then we are going to fail.
1372     rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY
1373                     : path->Create(nsIFile::DIRECTORY_TYPE, 0700);
1374   }
1375   return rv;
1376 }
1377