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