1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "prsystem.h"
7 
8 #include "nsMessenger.h"
9 
10 // xpcom
11 #include "nsIComponentManager.h"
12 #include "nsIServiceManager.h"
13 #include "nsIStringStream.h"
14 #include "nsLocalFile.h"
15 #include "nsDirectoryServiceDefs.h"
16 #include "nsQuickSort.h"
17 #include "nsNativeCharsetUtils.h"
18 #include "mozilla/Path.h"
19 #include "mozilla/Services.h"
20 #include "mozilla/dom/LoadURIOptionsBinding.h"
21 
22 // necko
23 #include "nsMimeTypes.h"
24 #include "nsIURL.h"
25 #include "nsIPrompt.h"
26 #include "nsIStreamListener.h"
27 #include "nsIStreamConverterService.h"
28 #include "nsNetUtil.h"
29 #include "nsIFileURL.h"
30 #include "nsIMIMEInfo.h"
31 
32 // gecko
33 #include "nsLayoutCID.h"
34 #include "nsIContentViewer.h"
35 
36 /* for access to docshell */
37 #include "nsPIDOMWindow.h"
38 #include "nsIDocShell.h"
39 #include "nsIDocShellTreeItem.h"
40 #include "nsIWebNavigation.h"
41 #include "nsContentUtils.h"
42 #include "nsDocShellLoadState.h"
43 #include "mozilla/dom/Element.h"
44 #include "mozilla/dom/XULFrameElement.h"
45 #include "nsFrameLoader.h"
46 #include "mozilla/dom/Document.h"
47 
48 // mail
49 #include "nsIMsgMailNewsUrl.h"
50 #include "nsMsgBaseCID.h"
51 #include "nsIMsgAccountManager.h"
52 #include "nsIMsgMailSession.h"
53 #include "nsIMailboxUrl.h"
54 #include "nsIMsgFolder.h"
55 #include "nsMsgMessageFlags.h"
56 #include "nsIMsgIncomingServer.h"
57 
58 #include "nsIMsgMessageService.h"
59 
60 #include "nsIMsgHdr.h"
61 #include "nsIMimeMiscStatus.h"
62 // compose
63 #include "nsMsgCompCID.h"
64 #include "nsNativeCharsetUtils.h"
65 
66 // draft/folders/sendlater/etc
67 #include "nsIMsgCopyService.h"
68 #include "nsIMsgCopyServiceListener.h"
69 #include "nsIUrlListener.h"
70 
71 // undo
72 #include "nsITransaction.h"
73 #include "nsMsgTxn.h"
74 
75 // charset conversions
76 #include "nsMsgMimeCID.h"
77 #include "nsIMimeConverter.h"
78 
79 // Save As
80 #include "nsIStringBundle.h"
81 #include "nsIPrefService.h"
82 #include "nsIPrefBranch.h"
83 #include "nsCExternalHandlerService.h"
84 #include "nsIExternalProtocolService.h"
85 #include "nsIMIMEService.h"
86 #include "nsITransfer.h"
87 
88 #define MESSENGER_SAVE_DIR_PREF_NAME "messenger.save.dir"
89 #define MIMETYPE_DELETED "text/x-moz-deleted"
90 #define ATTACHMENT_PERMISSION 00664
91 
92 //
93 // Convert an nsString buffer to plain text...
94 //
95 #include "nsMsgUtils.h"
96 #include "nsCharsetSource.h"
97 #include "nsIChannel.h"
98 #include "nsIOutputStream.h"
99 #include "nsIPrincipal.h"
100 
101 #include "mozilla/dom/BrowserParent.h"
102 #include "mozilla/dom/CanonicalBrowsingContext.h"
103 
104 #include "mozilla/NullPrincipal.h"
105 #include "mozilla/dom/RemoteType.h"
106 #include "nsQueryObject.h"
107 
108 using namespace mozilla;
109 using namespace mozilla::dom;
110 
ConvertAndSanitizeFileName(const nsACString & displayName,nsString & aResult)111 static void ConvertAndSanitizeFileName(const nsACString& displayName,
112                                        nsString& aResult) {
113   nsCString unescapedName;
114 
115   /* we need to convert the UTF-8 fileName to platform specific character set.
116      The display name is in UTF-8 because it has been escaped from JS
117   */
118   MsgUnescapeString(displayName, 0, unescapedName);
119   CopyUTF8toUTF16(unescapedName, aResult);
120 
121   // replace platform specific path separator and illegale characters to avoid
122   // any confusion
123   aResult.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
124 }
125 
126 // ***************************************************
127 // jefft - this is a rather obscured class serves for Save Message As File,
128 // Save Message As Template, and Save Attachment to a file
129 //
130 class nsSaveAllAttachmentsState;
131 
132 class nsSaveMsgListener : public nsIUrlListener,
133                           public nsIMsgCopyServiceListener,
134                           public nsIStreamListener,
135                           public nsICancelable {
136   using PathChar = mozilla::filesystem::Path::value_type;
137 
138  public:
139   nsSaveMsgListener(nsIFile* file, nsMessenger* aMessenger,
140                     nsIUrlListener* aListener);
141 
142   NS_DECL_ISUPPORTS
143 
144   NS_DECL_NSIURLLISTENER
145   NS_DECL_NSIMSGCOPYSERVICELISTENER
146   NS_DECL_NSISTREAMLISTENER
147   NS_DECL_NSIREQUESTOBSERVER
148   NS_DECL_NSICANCELABLE
149 
150   nsCOMPtr<nsIFile> m_file;
151   nsCOMPtr<nsIOutputStream> m_outputStream;
152   char m_dataBuffer[FILE_IO_BUFFER_SIZE];
153   nsCOMPtr<nsIChannel> m_channel;
154   nsCString m_templateUri;
155   RefPtr<nsMessenger> m_messenger;
156   nsSaveAllAttachmentsState* m_saveAllAttachmentsState;
157 
158   // rhp: For character set handling
159   bool m_doCharsetConversion;
160   nsString m_charset;
161   enum { eUnknown, ePlainText, eHTML } m_outputFormat;
162   nsCString m_msgBuffer;
163 
164   nsCString m_contentType;  // used only when saving attachment
165 
166   nsCOMPtr<nsITransfer> mTransfer;
167   nsCOMPtr<nsIUrlListener> mListener;
168   nsCOMPtr<nsIURI> mListenerUri;
169   int64_t mProgress;
170   int64_t mMaxProgress;
171   bool mCanceled;
172   bool mInitialized;
173   bool mUrlHasStopped;
174   bool mRequestHasStopped;
175   nsresult InitializeDownload(nsIRequest* aRequest);
176 
177  private:
178   virtual ~nsSaveMsgListener();
179 };
180 
181 class nsSaveAllAttachmentsState {
182   using PathChar = mozilla::filesystem::Path::value_type;
183 
184  public:
185   nsSaveAllAttachmentsState(const nsTArray<nsCString>& contentTypeArray,
186                             const nsTArray<nsCString>& urlArray,
187                             const nsTArray<nsCString>& displayNameArray,
188                             const nsTArray<nsCString>& messageUriArray,
189                             const PathChar* directoryName,
190                             bool detachingAttachments);
191   virtual ~nsSaveAllAttachmentsState();
192 
193   uint32_t m_count;
194   uint32_t m_curIndex;
195   PathChar* m_directoryName;
196   nsTArray<nsCString> m_contentTypeArray;
197   nsTArray<nsCString> m_urlArray;
198   nsTArray<nsCString> m_displayNameArray;
199   nsTArray<nsCString> m_messageUriArray;
200   bool m_detachingAttachments;
201 
202   // if detaching, do without warning? Will create unique files instead of
203   // prompting if duplicate files exist.
204   bool m_withoutWarning;
205   // if detaching first, remember where we saved to.
206   nsTArray<nsCString> m_savedFiles;
207 };
208 
209 //
210 // nsMessenger
211 //
nsMessenger()212 nsMessenger::nsMessenger() {
213   mCurHistoryPos = -2;  // first message selected goes at position 0.
214   //  InitializeFolderRoot();
215 }
216 
~nsMessenger()217 nsMessenger::~nsMessenger() {}
218 
NS_IMPL_ISUPPORTS(nsMessenger,nsIMessenger,nsISupportsWeakReference,nsIFolderListener)219 NS_IMPL_ISUPPORTS(nsMessenger, nsIMessenger, nsISupportsWeakReference,
220                   nsIFolderListener)
221 
222 NS_IMETHODIMP nsMessenger::SetWindow(mozIDOMWindowProxy* aWin,
223                                      nsIMsgWindow* aMsgWindow) {
224   nsresult rv;
225 
226   nsCOMPtr<nsIMsgMailSession> mailSession =
227       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
228   NS_ENSURE_SUCCESS(rv, rv);
229 
230   // Remove the folder listener if we added it, i.e. if mWindow is non-null.
231   if (mWindow) {
232     rv = mailSession->RemoveFolderListener(this);
233     NS_ENSURE_SUCCESS(rv, rv);
234   }
235 
236   if (aWin) {
237     mMsgWindow = aMsgWindow;
238     mWindow = aWin;
239 
240     rv = mailSession->AddFolderListener(this, nsIFolderListener::removed);
241     NS_ENSURE_SUCCESS(rv, rv);
242 
243     NS_ENSURE_TRUE(aWin, NS_ERROR_FAILURE);
244     nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(aWin);
245     nsIDocShell* rootShell = win->GetDocShell();
246     NS_ENSURE_STATE(rootShell);
247     RefPtr<mozilla::dom::Element> el =
248         rootShell->GetDocument()->GetElementById(u"messagepane"_ns);
249     RefPtr<mozilla::dom::XULFrameElement> frame =
250         mozilla::dom::XULFrameElement::FromNodeOrNull(el);
251     mDocShell = nullptr;
252     RefPtr<mozilla::dom::Document> doc;
253     if (frame) doc = frame->GetContentDocument();
254     if (doc) mDocShell = doc->GetDocShell();
255     if (mDocShell) {
256       // Important! Clear out mCurrentDisplayCharset so we reset a default
257       // charset on mDocShell the next time we try to load something into it.
258       mCurrentDisplayCharset = "";
259 
260       if (aMsgWindow)
261         aMsgWindow->GetTransactionManager(getter_AddRefs(mTxnMgr));
262     }
263 
264     // We don't always have a message pane, like in the addressbook
265     // so if we don't have a docshell, use the one for the app window.
266     // we do this so OpenURL() will work.
267     if (!mDocShell) mDocShell = rootShell;
268   } else {
269     mWindow = nullptr;
270   }
271 
272   return NS_OK;
273 }
274 
SetDisplayCharset(const nsACString & aCharset)275 NS_IMETHODIMP nsMessenger::SetDisplayCharset(const nsACString& aCharset) {
276   // libmime always converts to UTF-8 (both HTML and XML)
277   if (mDocShell) {
278     const Encoding* encoding = nullptr;
279     nsCOMPtr<nsIContentViewer> cv;
280     mDocShell->GetContentViewer(getter_AddRefs(cv));
281     if (cv) {
282       if (!aCharset.IsEmpty()) {
283         if (!(encoding = Encoding::ForLabel(aCharset))) {
284           return NS_ERROR_INVALID_ARG;
285         }
286         cv->SetReloadEncodingAndSource(encoding, kCharsetFromBuiltIn);
287         mCurrentDisplayCharset = aCharset;
288       }
289     }
290   }
291 
292   return NS_OK;
293 }
294 
NS_IMPL_ISUPPORTS(nsMessenger::nsFilePickerShownCallback,nsIFilePickerShownCallback)295 NS_IMPL_ISUPPORTS(nsMessenger::nsFilePickerShownCallback,
296                   nsIFilePickerShownCallback)
297 nsMessenger::nsFilePickerShownCallback::nsFilePickerShownCallback() {
298   mResult = nsIFilePicker::returnOK;
299   mPickerDone = false;
300 }
301 
302 NS_IMETHODIMP
Done(int16_t aResult)303 nsMessenger::nsFilePickerShownCallback::Done(int16_t aResult) {
304   mResult = aResult;
305   mPickerDone = true;
306   return NS_OK;
307 }
308 
ShowPicker(nsIFilePicker * aPicker,int16_t * aResult)309 nsresult nsMessenger::ShowPicker(nsIFilePicker* aPicker, int16_t* aResult) {
310   nsCOMPtr<nsIFilePickerShownCallback> callback =
311       new nsMessenger::nsFilePickerShownCallback();
312   nsFilePickerShownCallback* cb =
313       static_cast<nsFilePickerShownCallback*>(callback.get());
314 
315   nsresult rv;
316   rv = aPicker->Open(callback);
317   NS_ENSURE_SUCCESS(rv, rv);
318 
319   // Spin the event loop until the callback was called.
320   nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
321   while (!cb->mPickerDone) {
322     NS_ProcessNextEvent(thread, true);
323   }
324 
325   *aResult = cb->mResult;
326   return NS_OK;
327 }
328 
PromptIfFileExists(nsIFile * file)329 nsresult nsMessenger::PromptIfFileExists(nsIFile* file) {
330   nsresult rv = NS_ERROR_FAILURE;
331   bool exists;
332   file->Exists(&exists);
333   if (!exists) return NS_OK;
334 
335   nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
336   if (!dialog) return rv;
337   nsAutoString path;
338   bool dialogResult = false;
339   nsString errorMessage;
340 
341   file->GetPath(path);
342   AutoTArray<nsString, 1> pathFormatStrings = {path};
343 
344   if (!mStringBundle) {
345     rv = InitStringBundle();
346     NS_ENSURE_SUCCESS(rv, rv);
347   }
348   rv = mStringBundle->FormatStringFromName("fileExists", pathFormatStrings,
349                                            errorMessage);
350   NS_ENSURE_SUCCESS(rv, rv);
351   rv = dialog->Confirm(nullptr, errorMessage.get(), &dialogResult);
352   NS_ENSURE_SUCCESS(rv, rv);
353 
354   if (dialogResult) return NS_OK;  // user says okay to replace
355 
356   // if we don't re-init the path for redisplay the picker will
357   // show the full path, not just the file name
358   nsCOMPtr<nsIFile> currentFile =
359       do_CreateInstance("@mozilla.org/file/local;1");
360   if (!currentFile) return NS_ERROR_FAILURE;
361 
362   rv = currentFile->InitWithPath(path);
363   NS_ENSURE_SUCCESS(rv, rv);
364 
365   nsAutoString leafName;
366   currentFile->GetLeafName(leafName);
367   if (!leafName.IsEmpty())
368     path.Assign(leafName);  // path should be a copy of leafName
369 
370   nsCOMPtr<nsIFilePicker> filePicker =
371       do_CreateInstance("@mozilla.org/filepicker;1", &rv);
372   NS_ENSURE_SUCCESS(rv, rv);
373   nsString saveAttachmentStr;
374   GetString(u"SaveAttachment"_ns, saveAttachmentStr);
375   filePicker->Init(mWindow, saveAttachmentStr, nsIFilePicker::modeSave);
376   filePicker->SetDefaultString(path);
377   filePicker->AppendFilters(nsIFilePicker::filterAll);
378 
379   nsCOMPtr<nsIFile> lastSaveDir;
380   rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
381   if (NS_SUCCEEDED(rv) && lastSaveDir) {
382     filePicker->SetDisplayDirectory(lastSaveDir);
383   }
384 
385   int16_t dialogReturn;
386   rv = ShowPicker(filePicker, &dialogReturn);
387   if (NS_FAILED(rv) || dialogReturn == nsIFilePicker::returnCancel) {
388     // XXX todo
389     // don't overload the return value like this
390     // change this function to have an out boolean
391     // that we check to see if the user cancelled
392     return NS_ERROR_FAILURE;
393   }
394 
395   nsCOMPtr<nsIFile> localFile;
396 
397   rv = filePicker->GetFile(getter_AddRefs(localFile));
398   NS_ENSURE_SUCCESS(rv, rv);
399 
400   rv = SetLastSaveDirectory(localFile);
401   NS_ENSURE_SUCCESS(rv, rv);
402 
403   // reset the file to point to the new path
404   return file->InitWithFile(localFile);
405 }
406 
407 NS_IMETHODIMP
AddMsgUrlToNavigateHistory(const nsACString & aURL)408 nsMessenger::AddMsgUrlToNavigateHistory(const nsACString& aURL) {
409   // mNavigatingToUri is set to a url if we're already doing a back/forward,
410   // in which case we don't want to add the url to the history list.
411   // Or if the entry at the cur history pos is the same as what we're loading,
412   // don't add it to the list.
413   if (!mNavigatingToUri.Equals(aURL) &&
414       (mCurHistoryPos < 0 || !mLoadedMsgHistory[mCurHistoryPos].Equals(aURL))) {
415     mNavigatingToUri = aURL;
416     nsCString curLoadedFolderUri;
417     nsCOMPtr<nsIMsgFolder> curLoadedFolder;
418 
419     mMsgWindow->GetOpenFolder(getter_AddRefs(curLoadedFolder));
420     // for virtual folders, we want to select the right folder,
421     // which isn't the same as the folder specified in the msg uri.
422     // So add the uri for the currently loaded folder to the history list.
423     if (curLoadedFolder) curLoadedFolder->GetURI(curLoadedFolderUri);
424 
425     mLoadedMsgHistory.InsertElementAt(mCurHistoryPos++ + 2, mNavigatingToUri);
426     mLoadedMsgHistory.InsertElementAt(mCurHistoryPos++ + 2, curLoadedFolderUri);
427     // we may want to prune this history if it gets large, but I think it's
428     // more interesting to prune the back and forward menu.
429   }
430   return NS_OK;
431 }
432 
433 NS_IMETHODIMP
AbortPendingOpenURL()434 nsMessenger::AbortPendingOpenURL() {
435   mURLToLoad.Truncate();
436   return NS_OK;
437 }
438 
CompleteOpenURL()439 nsresult nsMessenger::CompleteOpenURL() {
440   if (mURLToLoad.IsEmpty() || !mDocShell) {
441     return NS_OK;
442   }
443 
444   if (mMsgWindow) {
445     mMsgWindow->GetTransactionManager(getter_AddRefs(mTxnMgr));
446   }
447 
448   // This is to setup the display DocShell as UTF-8 capable...
449   mCurrentDisplayCharset = "";
450   SetDisplayCharset("UTF-8"_ns);
451 
452   // Disable auth and DNS prefetch in all mail docShells.
453   mDocShell->SetAllowAuth(false);
454   mDocShell->SetAllowDNSPrefetch(false);
455 
456   nsCOMPtr<nsIMsgMessageService> messageService;
457   nsresult rv =
458       GetMessageServiceFromURI(mURLToLoad, getter_AddRefs(messageService));
459 
460   if (NS_SUCCEEDED(rv) && messageService) {
461     nsCOMPtr<nsIURI> dummyNull;
462     messageService->DisplayMessage(mURLToLoad, mDocShell, mMsgWindow, nullptr,
463                                    false, getter_AddRefs(dummyNull));
464     AddMsgUrlToNavigateHistory(mURLToLoad);
465     mLastDisplayURI = mURLToLoad;  // remember the last uri we displayed....
466     return NS_OK;
467   }
468 
469   nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
470   if (!webNav) return NS_ERROR_FAILURE;
471   mozilla::dom::LoadURIOptions loadURIOptions;
472   loadURIOptions.mLoadFlags = nsIWebNavigation::LOAD_FLAGS_IS_LINK;
473   loadURIOptions.mTriggeringPrincipal = nsContentUtils::GetSystemPrincipal();
474   return webNav->LoadURI(NS_ConvertASCIItoUTF16(mURLToLoad), loadURIOptions);
475 }
476 
477 NS_IMETHODIMP
OpenURL(const nsACString & aURL)478 nsMessenger::OpenURL(const nsACString& aURL) {
479   mURLToLoad = aURL;
480   RemotenessChangeOptions changeState;
481 
482   nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(mWindow);
483   nsIDocShell* rootShell = win->GetDocShell();
484   NS_ENSURE_STATE(rootShell);
485   RefPtr<mozilla::dom::Element> el =
486       rootShell->GetDocument()->GetElementById(u"messagepane"_ns);
487 
488   RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(el);
489   RefPtr<CanonicalBrowsingContext> canonicalBrowsingContext =
490       flo->GetBrowsingContext()->Canonical();
491 
492   nsCString remoteType;
493   ErrorResult er;
494   canonicalBrowsingContext->GetCurrentRemoteType(remoteType, er);
495   if (remoteType.Equals(NOT_REMOTE_TYPE)) {
496     // This browsing context is in the parent process. Load the message.
497     mDocShell = canonicalBrowsingContext->GetDocShell();
498     return CompleteOpenURL();
499   }
500 
501   // This browsing context is in a child process. Change it to the parent
502   // process, then load the message.
503   changeState.mRemoteType = NOT_REMOTE_TYPE;
504   canonicalBrowsingContext
505       ->ChangeRemoteness(changeState, nsContentUtils::GenerateLoadIdentifier())
506       ->Then(
507           GetMainThreadSerialEventTarget(), __func__,
508           [flo, self = RefPtr{this}](
509               BrowserParent* aBrowserParent /* always null */) {
510             RefPtr<BrowsingContext> browsingContext = flo->GetBrowsingContext();
511             if (!browsingContext) {
512               return NS_ERROR_FAILURE;
513             }
514 
515             self->mDocShell = browsingContext->GetDocShell();
516 
517             nsCOMPtr<nsIWebProgress> webProgress =
518                 browsingContext->Canonical()->GetWebProgress();
519             nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
520             self->mMsgWindow->GetStatusFeedback(getter_AddRefs(statusFeedback));
521             nsCOMPtr<nsIWebProgressListener> webProgressListener =
522                 do_QueryInterface(statusFeedback);
523 
524             webProgress->AddProgressListener(webProgressListener,
525                                              nsIWebProgress::NOTIFY_ALL);
526 
527             return self->CompleteOpenURL();
528           },
529           [self = RefPtr{this}](nsresult aStatusCode) {});
530 
531   return NS_OK;
532 }
533 
LaunchExternalURL(const nsACString & aURL)534 NS_IMETHODIMP nsMessenger::LaunchExternalURL(const nsACString& aURL) {
535   nsresult rv;
536 
537   nsCOMPtr<nsIURI> uri;
538   rv = NS_NewURI(getter_AddRefs(uri), aURL);
539   NS_ENSURE_SUCCESS(rv, rv);
540 
541   nsCOMPtr<nsIExternalProtocolService> extProtService =
542       do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID, &rv);
543   NS_ENSURE_SUCCESS(rv, rv);
544   return extProtService->LoadURI(uri, nullptr, nullptr, nullptr, false);
545 }
546 
547 NS_IMETHODIMP
LoadURL(mozIDOMWindowProxy * aWin,const nsACString & aURL)548 nsMessenger::LoadURL(mozIDOMWindowProxy* aWin, const nsACString& aURL) {
549   nsresult rv;
550 
551   SetDisplayCharset("UTF-8"_ns);
552 
553   NS_ConvertASCIItoUTF16 uriString(aURL);
554   // Cleanup the empty spaces that might be on each end.
555   uriString.Trim(" ");
556   // Eliminate embedded newlines, which single-line text fields now allow:
557   uriString.StripChars("\r\n");
558   NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE);
559 
560   bool loadingFromFile = false;
561   bool getDummyMsgHdr = false;
562   int64_t fileSize;
563 
564   if (StringBeginsWith(uriString, u"file:"_ns)) {
565     nsCOMPtr<nsIURI> fileUri;
566     rv = NS_NewURI(getter_AddRefs(fileUri), uriString);
567     NS_ENSURE_SUCCESS(rv, rv);
568     nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(fileUri, &rv);
569     NS_ENSURE_SUCCESS(rv, rv);
570     nsCOMPtr<nsIFile> file;
571     rv = fileUrl->GetFile(getter_AddRefs(file));
572     NS_ENSURE_SUCCESS(rv, rv);
573     file->GetFileSize(&fileSize);
574     uriString.Replace(0, 5, u"mailbox:"_ns);
575     uriString.AppendLiteral(u"&number=0");
576     loadingFromFile = true;
577     getDummyMsgHdr = true;
578   } else if (StringBeginsWith(uriString, u"mailbox:"_ns) &&
579              (CaseInsensitiveFindInReadable(u".eml?"_ns, uriString))) {
580     // if we have a mailbox:// url that points to an .eml file, we have to read
581     // the file size as well
582     uriString.Replace(0, 8, u"file:"_ns);
583     nsCOMPtr<nsIURI> fileUri;
584     rv = NS_NewURI(getter_AddRefs(fileUri), uriString);
585     NS_ENSURE_SUCCESS(rv, rv);
586     nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(fileUri, &rv);
587     NS_ENSURE_SUCCESS(rv, rv);
588     nsCOMPtr<nsIFile> file;
589     rv = fileUrl->GetFile(getter_AddRefs(file));
590     NS_ENSURE_SUCCESS(rv, rv);
591     file->GetFileSize(&fileSize);
592     uriString.Replace(0, 5, u"mailbox:"_ns);
593     loadingFromFile = true;
594     getDummyMsgHdr = true;
595   } else if (uriString.Find("type=application/x-message-display") >= 0)
596     getDummyMsgHdr = true;
597 
598   nsCOMPtr<nsIURI> uri;
599   rv = NS_NewURI(getter_AddRefs(uri), uriString);
600   NS_ENSURE_SUCCESS(rv, rv);
601 
602   NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
603   nsCOMPtr<nsIMsgMailNewsUrl> msgurl = do_QueryInterface(uri);
604   if (msgurl) {
605     msgurl->SetMsgWindow(mMsgWindow);
606     if (loadingFromFile || getDummyMsgHdr) {
607       if (loadingFromFile) {
608         nsCOMPtr<nsIMailboxUrl> mailboxUrl = do_QueryInterface(msgurl, &rv);
609         mailboxUrl->SetMessageSize((uint32_t)fileSize);
610       }
611       if (getDummyMsgHdr) {
612         nsCOMPtr<nsIMsgHeaderSink> headerSink;
613         // need to tell the header sink to capture some headers to create a fake
614         // db header so we can do reply to a .eml file or a rfc822 msg
615         // attachment.
616         mMsgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink));
617         if (headerSink) {
618           nsCOMPtr<nsIMsgDBHdr> dummyHeader;
619           headerSink->GetDummyMsgHeader(getter_AddRefs(dummyHeader));
620           if (dummyHeader && loadingFromFile)
621             dummyHeader->SetMessageSize((uint32_t)fileSize);
622         }
623       }
624     }
625   }
626 
627   AddMsgUrlToNavigateHistory(aURL);
628   mNavigatingToUri.Truncate();
629   mLastDisplayURI = aURL;  // Remember the last uri we displayed.
630   RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(uri);
631   loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
632   loadState->SetFirstParty(true);
633   loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
634   return mDocShell->LoadURI(loadState, false);
635 }
636 
SaveAttachmentToFile(nsIFile * aFile,const nsACString & aURL,const nsACString & aMessageUri,const nsACString & aContentType,nsIUrlListener * aListener)637 NS_IMETHODIMP nsMessenger::SaveAttachmentToFile(nsIFile* aFile,
638                                                 const nsACString& aURL,
639                                                 const nsACString& aMessageUri,
640                                                 const nsACString& aContentType,
641                                                 nsIUrlListener* aListener) {
642   return SaveAttachment(aFile, aURL, aMessageUri, aContentType, nullptr,
643                         aListener);
644 }
645 
646 NS_IMETHODIMP
DetachAttachmentsWOPrompts(nsIFile * aDestFolder,const nsTArray<nsCString> & aContentTypeArray,const nsTArray<nsCString> & aUrlArray,const nsTArray<nsCString> & aDisplayNameArray,const nsTArray<nsCString> & aMessageUriArray,nsIUrlListener * aListener)647 nsMessenger::DetachAttachmentsWOPrompts(
648     nsIFile* aDestFolder, const nsTArray<nsCString>& aContentTypeArray,
649     const nsTArray<nsCString>& aUrlArray,
650     const nsTArray<nsCString>& aDisplayNameArray,
651     const nsTArray<nsCString>& aMessageUriArray, nsIUrlListener* aListener) {
652   NS_ENSURE_ARG_POINTER(aDestFolder);
653   MOZ_ASSERT(aContentTypeArray.Length() == aUrlArray.Length() &&
654              aUrlArray.Length() == aDisplayNameArray.Length() &&
655              aDisplayNameArray.Length() == aMessageUriArray.Length());
656 
657   if (!aContentTypeArray.Length()) return NS_OK;
658   nsSaveAllAttachmentsState* saveState;
659   nsCOMPtr<nsIFile> attachmentDestination;
660   nsresult rv = aDestFolder->Clone(getter_AddRefs(attachmentDestination));
661   NS_ENSURE_SUCCESS(rv, rv);
662 
663   PathString path = attachmentDestination->NativePath();
664 
665   nsAutoString unescapedFileName;
666   ConvertAndSanitizeFileName(aDisplayNameArray[0], unescapedFileName);
667   rv = attachmentDestination->Append(unescapedFileName);
668   NS_ENSURE_SUCCESS(rv, rv);
669   rv = attachmentDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE,
670                                            ATTACHMENT_PERMISSION);
671   NS_ENSURE_SUCCESS(rv, rv);
672 
673   saveState = new nsSaveAllAttachmentsState(aContentTypeArray, aUrlArray,
674                                             aDisplayNameArray, aMessageUriArray,
675                                             path.get(), true);
676 
677   // This method is used in filters, where we don't want to warn
678   saveState->m_withoutWarning = true;
679   rv = SaveAttachment(attachmentDestination, aUrlArray[0], aMessageUriArray[0],
680                       aContentTypeArray[0], (void*)saveState, aListener);
681   return rv;
682 }
683 
SaveAttachment(nsIFile * aFile,const nsACString & aURL,const nsACString & aMessageUri,const nsACString & aContentType,void * closure,nsIUrlListener * aListener)684 nsresult nsMessenger::SaveAttachment(nsIFile* aFile, const nsACString& aURL,
685                                      const nsACString& aMessageUri,
686                                      const nsACString& aContentType,
687                                      void* closure, nsIUrlListener* aListener) {
688   nsCOMPtr<nsIMsgMessageService> messageService;
689   nsSaveAllAttachmentsState* saveState = (nsSaveAllAttachmentsState*)closure;
690   nsCOMPtr<nsIMsgMessageFetchPartService> fetchService;
691   nsAutoCString urlString;
692   nsAutoCString fullMessageUri(aMessageUri);
693 
694   // This instance will be held onto by the listeners, and will be released once
695   // the transfer has been completed.
696   RefPtr<nsSaveMsgListener> saveListener(
697       new nsSaveMsgListener(aFile, this, aListener));
698 
699   saveListener->m_contentType = aContentType;
700   if (saveState) {
701     saveListener->m_saveAllAttachmentsState = saveState;
702     if (saveState->m_detachingAttachments) {
703       nsCOMPtr<nsIURI> outputURI;
704       nsresult rv = NS_NewFileURI(getter_AddRefs(outputURI), aFile);
705       NS_ENSURE_SUCCESS(rv, rv);
706       nsAutoCString fileUriSpec;
707       rv = outputURI->GetSpec(fileUriSpec);
708       NS_ENSURE_SUCCESS(rv, rv);
709       saveState->m_savedFiles.AppendElement(fileUriSpec);
710     }
711   }
712 
713   urlString = aURL;
714   // strip out ?type=application/x-message-display because it confuses libmime
715 
716   int32_t typeIndex = urlString.Find("?type=application/x-message-display");
717   if (typeIndex != kNotFound) {
718     urlString.Cut(typeIndex, sizeof("?type=application/x-message-display") - 1);
719     // we also need to replace the next '&' with '?'
720     int32_t firstPartIndex = urlString.FindChar('&');
721     if (firstPartIndex != kNotFound) urlString.SetCharAt('?', firstPartIndex);
722   }
723 
724   urlString.ReplaceSubstring("/;section", "?section");
725   nsCOMPtr<nsIURI> URL;
726   nsresult rv = NS_NewURI(getter_AddRefs(URL), urlString);
727 
728   if (NS_SUCCEEDED(rv)) {
729     rv = GetMessageServiceFromURI(aMessageUri, getter_AddRefs(messageService));
730     if (NS_SUCCEEDED(rv)) {
731       fetchService = do_QueryInterface(messageService);
732       // if the message service has a fetch part service then we know we can
733       // fetch mime parts...
734       if (fetchService) {
735         int32_t partPos = urlString.FindChar('?');
736         if (partPos == kNotFound) return NS_ERROR_FAILURE;
737         fullMessageUri.Append(Substring(urlString, partPos));
738       }
739 
740       nsCOMPtr<nsIStreamListener> convertedListener;
741       saveListener->QueryInterface(NS_GET_IID(nsIStreamListener),
742                                    getter_AddRefs(convertedListener));
743 
744       nsCOMPtr<nsIURI> dummyNull;
745       if (fetchService)
746         rv = fetchService->FetchMimePart(URL, fullMessageUri, convertedListener,
747                                          mMsgWindow, saveListener,
748                                          getter_AddRefs(dummyNull));
749       else
750         rv = messageService->DisplayMessage(fullMessageUri, convertedListener,
751                                             mMsgWindow, nullptr, false,
752                                             getter_AddRefs(dummyNull));
753     }  // if we got a message service
754   }    // if we created a url
755 
756   if (NS_FAILED(rv)) Alert("saveAttachmentFailed");
757 
758   return rv;
759 }
760 
761 NS_IMETHODIMP
OpenAttachment(const nsACString & aContentType,const nsACString & aURL,const nsACString & aDisplayName,const nsACString & aMessageUri,bool aIsExternalAttachment)762 nsMessenger::OpenAttachment(const nsACString& aContentType,
763                             const nsACString& aURL,
764                             const nsACString& aDisplayName,
765                             const nsACString& aMessageUri,
766                             bool aIsExternalAttachment) {
767   nsresult rv = NS_OK;
768 
769   // open external attachments inside our message pane which in turn should
770   // trigger the helper app dialog...
771   if (aIsExternalAttachment)
772     rv = OpenURL(aURL);
773   else {
774     nsCOMPtr<nsIMsgMessageService> messageService;
775     rv = GetMessageServiceFromURI(aMessageUri, getter_AddRefs(messageService));
776     if (messageService)
777       rv = messageService->OpenAttachment(
778           PromiseFlatCString(aContentType).get(),
779           PromiseFlatCString(aDisplayName).get(),
780           PromiseFlatCString(aURL).get(), PromiseFlatCString(aMessageUri).get(),
781           mDocShell, mMsgWindow, nullptr);
782   }
783 
784   return rv;
785 }
786 
787 NS_IMETHODIMP
SaveAttachmentToFolder(const nsACString & contentType,const nsACString & url,const nsACString & displayName,const nsACString & messageUri,nsIFile * aDestFolder,nsIFile ** aOutFile)788 nsMessenger::SaveAttachmentToFolder(const nsACString& contentType,
789                                     const nsACString& url,
790                                     const nsACString& displayName,
791                                     const nsACString& messageUri,
792                                     nsIFile* aDestFolder, nsIFile** aOutFile) {
793   NS_ENSURE_ARG_POINTER(aDestFolder);
794   nsresult rv;
795 
796   nsCOMPtr<nsIFile> attachmentDestination;
797   rv = aDestFolder->Clone(getter_AddRefs(attachmentDestination));
798   NS_ENSURE_SUCCESS(rv, rv);
799 
800   nsString unescapedFileName;
801   ConvertAndSanitizeFileName(displayName, unescapedFileName);
802   rv = attachmentDestination->Append(unescapedFileName);
803   NS_ENSURE_SUCCESS(rv, rv);
804 #ifdef XP_MACOSX
805   rv = attachmentDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE,
806                                            ATTACHMENT_PERMISSION);
807   NS_ENSURE_SUCCESS(rv, rv);
808 #endif
809 
810   rv = SaveAttachment(attachmentDestination, url, messageUri, contentType,
811                       nullptr, nullptr);
812   attachmentDestination.forget(aOutFile);
813   return rv;
814 }
815 
816 NS_IMETHODIMP
SaveAttachment(const nsACString & aContentType,const nsACString & aURL,const nsACString & aDisplayName,const nsACString & aMessageUri,bool aIsExternalAttachment)817 nsMessenger::SaveAttachment(const nsACString& aContentType,
818                             const nsACString& aURL,
819                             const nsACString& aDisplayName,
820                             const nsACString& aMessageUri,
821                             bool aIsExternalAttachment) {
822   // open external attachments inside our message pane which in turn should
823   // trigger the helper app dialog...
824   if (aIsExternalAttachment) return OpenURL(aURL);
825   return SaveOneAttachment(aContentType, aURL, aDisplayName, aMessageUri,
826                            false);
827 }
828 
SaveOneAttachment(const nsACString & aContentType,const nsACString & aURL,const nsACString & aDisplayName,const nsACString & aMessageUri,bool detaching)829 nsresult nsMessenger::SaveOneAttachment(const nsACString& aContentType,
830                                         const nsACString& aURL,
831                                         const nsACString& aDisplayName,
832                                         const nsACString& aMessageUri,
833                                         bool detaching) {
834   nsresult rv = NS_ERROR_OUT_OF_MEMORY;
835   nsCOMPtr<nsIFilePicker> filePicker =
836       do_CreateInstance("@mozilla.org/filepicker;1", &rv);
837   NS_ENSURE_SUCCESS(rv, rv);
838 
839   int16_t dialogResult;
840   nsCOMPtr<nsIFile> localFile;
841   nsCOMPtr<nsIFile> lastSaveDir;
842   nsCString filePath;
843   nsString saveAttachmentStr;
844   nsString defaultDisplayString;
845   ConvertAndSanitizeFileName(aDisplayName, defaultDisplayString);
846 
847   if (detaching) {
848     GetString(u"DetachAttachment"_ns, saveAttachmentStr);
849   } else {
850     GetString(u"SaveAttachment"_ns, saveAttachmentStr);
851   }
852   filePicker->Init(mWindow, saveAttachmentStr, nsIFilePicker::modeSave);
853   filePicker->SetDefaultString(defaultDisplayString);
854 
855   // Check if the attachment file name has an extension (which must not
856   // contain spaces) and set it as the default extension for the attachment.
857   int32_t extensionIndex = defaultDisplayString.RFindChar('.');
858   if (extensionIndex > 0 &&
859       defaultDisplayString.FindChar(' ', extensionIndex) == kNotFound) {
860     nsString extension;
861     extension = Substring(defaultDisplayString, extensionIndex + 1);
862     filePicker->SetDefaultExtension(extension);
863     if (!mStringBundle) {
864       rv = InitStringBundle();
865       NS_ENSURE_SUCCESS(rv, rv);
866     }
867 
868     nsString filterName;
869     AutoTArray<nsString, 1> extensionParam = {extension};
870     rv = mStringBundle->FormatStringFromName("saveAsType", extensionParam,
871                                              filterName);
872     NS_ENSURE_SUCCESS(rv, rv);
873 
874     extension.InsertLiteral(u"*.", 0);
875     filePicker->AppendFilter(filterName, extension);
876   }
877 
878   filePicker->AppendFilters(nsIFilePicker::filterAll);
879 
880   rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
881   if (NS_SUCCEEDED(rv) && lastSaveDir)
882     filePicker->SetDisplayDirectory(lastSaveDir);
883 
884   rv = ShowPicker(filePicker, &dialogResult);
885   if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel) return rv;
886 
887   rv = filePicker->GetFile(getter_AddRefs(localFile));
888   NS_ENSURE_SUCCESS(rv, rv);
889 
890   SetLastSaveDirectory(localFile);
891 
892   PathString dirName = localFile->NativePath();
893 
894   AutoTArray<nsCString, 1> contentTypeArray = {
895       PromiseFlatCString(aContentType)};
896   AutoTArray<nsCString, 1> urlArray = {PromiseFlatCString(aURL)};
897   AutoTArray<nsCString, 1> displayNameArray = {
898       PromiseFlatCString(aDisplayName)};
899   AutoTArray<nsCString, 1> messageUriArray = {PromiseFlatCString(aMessageUri)};
900   nsSaveAllAttachmentsState* saveState = new nsSaveAllAttachmentsState(
901       contentTypeArray, urlArray, displayNameArray, messageUriArray,
902       dirName.get(), detaching);
903 
904   return SaveAttachment(localFile, aURL, aMessageUri, aContentType,
905                         (void*)saveState, nullptr);
906 }
907 
908 NS_IMETHODIMP
SaveAllAttachments(const nsTArray<nsCString> & contentTypeArray,const nsTArray<nsCString> & urlArray,const nsTArray<nsCString> & displayNameArray,const nsTArray<nsCString> & messageUriArray)909 nsMessenger::SaveAllAttachments(const nsTArray<nsCString>& contentTypeArray,
910                                 const nsTArray<nsCString>& urlArray,
911                                 const nsTArray<nsCString>& displayNameArray,
912                                 const nsTArray<nsCString>& messageUriArray) {
913   uint32_t len = contentTypeArray.Length();
914   NS_ENSURE_TRUE(urlArray.Length() == len, NS_ERROR_INVALID_ARG);
915   NS_ENSURE_TRUE(displayNameArray.Length() == len, NS_ERROR_INVALID_ARG);
916   NS_ENSURE_TRUE(messageUriArray.Length() == len, NS_ERROR_INVALID_ARG);
917   if (len == 0) {
918     return NS_OK;
919   }
920   return SaveAllAttachments(contentTypeArray, urlArray, displayNameArray,
921                             messageUriArray, false);
922 }
923 
SaveAllAttachments(const nsTArray<nsCString> & contentTypeArray,const nsTArray<nsCString> & urlArray,const nsTArray<nsCString> & displayNameArray,const nsTArray<nsCString> & messageUriArray,bool detaching)924 nsresult nsMessenger::SaveAllAttachments(
925     const nsTArray<nsCString>& contentTypeArray,
926     const nsTArray<nsCString>& urlArray,
927     const nsTArray<nsCString>& displayNameArray,
928     const nsTArray<nsCString>& messageUriArray, bool detaching) {
929   nsresult rv = NS_ERROR_OUT_OF_MEMORY;
930   nsCOMPtr<nsIFilePicker> filePicker =
931       do_CreateInstance("@mozilla.org/filepicker;1", &rv);
932   nsCOMPtr<nsIFile> localFile;
933   nsCOMPtr<nsIFile> lastSaveDir;
934   int16_t dialogResult;
935   nsString saveAttachmentStr;
936 
937   NS_ENSURE_SUCCESS(rv, rv);
938   if (detaching) {
939     GetString(u"DetachAllAttachments"_ns, saveAttachmentStr);
940   } else {
941     GetString(u"SaveAllAttachments"_ns, saveAttachmentStr);
942   }
943   filePicker->Init(mWindow, saveAttachmentStr, nsIFilePicker::modeGetFolder);
944 
945   rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
946   if (NS_SUCCEEDED(rv) && lastSaveDir)
947     filePicker->SetDisplayDirectory(lastSaveDir);
948 
949   rv = ShowPicker(filePicker, &dialogResult);
950   if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel) return rv;
951 
952   rv = filePicker->GetFile(getter_AddRefs(localFile));
953   NS_ENSURE_SUCCESS(rv, rv);
954 
955   rv = SetLastSaveDirectory(localFile);
956   NS_ENSURE_SUCCESS(rv, rv);
957 
958   nsSaveAllAttachmentsState* saveState = nullptr;
959   PathString dirName = localFile->NativePath();
960 
961   saveState = new nsSaveAllAttachmentsState(contentTypeArray, urlArray,
962                                             displayNameArray, messageUriArray,
963                                             dirName.get(), detaching);
964   nsString unescapedName;
965   ConvertAndSanitizeFileName(displayNameArray[0], unescapedName);
966   rv = localFile->Append(unescapedName);
967   NS_ENSURE_SUCCESS(rv, rv);
968 
969   rv = PromptIfFileExists(localFile);
970   NS_ENSURE_SUCCESS(rv, rv);
971 
972   rv = SaveAttachment(localFile, urlArray[0], messageUriArray[0],
973                       contentTypeArray[0], (void*)saveState, nullptr);
974   return rv;
975 }
976 
977 enum MESSENGER_SAVEAS_FILE_TYPE {
978   EML_FILE_TYPE = 0,
979   HTML_FILE_TYPE = 1,
980   TEXT_FILE_TYPE = 2,
981   ANY_FILE_TYPE = 3
982 };
983 #define HTML_FILE_EXTENSION ".htm"
984 #define HTML_FILE_EXTENSION2 ".html"
985 #define TEXT_FILE_EXTENSION ".txt"
986 
987 /**
988  * Adjust the file name, removing characters from the middle of the name if
989  * the name would otherwise be too long - too long for what file systems
990  * usually support.
991  */
AdjustFileIfNameTooLong(nsIFile * aFile)992 nsresult nsMessenger::AdjustFileIfNameTooLong(nsIFile* aFile) {
993   NS_ENSURE_ARG_POINTER(aFile);
994   nsAutoString path;
995   nsresult rv = aFile->GetPath(path);
996   NS_ENSURE_SUCCESS(rv, rv);
997   // Most common file systems have a max filename length of 255. On windows, the
998   // total path length is (at least for all practical purposees) limited to 255.
999   // Let's just don't allow paths longer than that elsewhere either for
1000   // simplicity.
1001   uint32_t MAX = 255;
1002   if (path.Length() > MAX) {
1003     nsAutoString leafName;
1004     rv = aFile->GetLeafName(leafName);
1005     NS_ENSURE_SUCCESS(rv, rv);
1006 
1007     uint32_t pathLengthUpToLeaf = path.Length() - leafName.Length();
1008     if (pathLengthUpToLeaf >= MAX - 8) {  // want at least 8 chars for name
1009       return NS_ERROR_FILE_NAME_TOO_LONG;
1010     }
1011     uint32_t x = MAX - pathLengthUpToLeaf;  // x = max leaf size
1012     nsAutoString truncatedLeaf;
1013     truncatedLeaf.Append(Substring(leafName, 0, x / 2));
1014     truncatedLeaf.AppendLiteral("...");
1015     truncatedLeaf.Append(
1016         Substring(leafName, leafName.Length() - x / 2 + 3, leafName.Length()));
1017     rv = aFile->SetLeafName(truncatedLeaf);
1018   }
1019   return rv;
1020 }
1021 
1022 NS_IMETHODIMP
SaveAs(const nsACString & aURI,bool aAsFile,nsIMsgIdentity * aIdentity,const nsAString & aMsgFilename,bool aBypassFilePicker)1023 nsMessenger::SaveAs(const nsACString& aURI, bool aAsFile,
1024                     nsIMsgIdentity* aIdentity, const nsAString& aMsgFilename,
1025                     bool aBypassFilePicker) {
1026   nsCOMPtr<nsIMsgMessageService> messageService;
1027   nsCOMPtr<nsIUrlListener> urlListener;
1028   RefPtr<nsSaveMsgListener> saveListener;
1029   nsCOMPtr<nsIStreamListener> convertedListener;
1030   int32_t saveAsFileType = EML_FILE_TYPE;
1031 
1032   nsresult rv = GetMessageServiceFromURI(aURI, getter_AddRefs(messageService));
1033   if (NS_FAILED(rv)) goto done;
1034 
1035   if (aAsFile) {
1036     nsCOMPtr<nsIFile> saveAsFile;
1037     // show the file picker if BypassFilePicker is not specified (null) or false
1038     if (!aBypassFilePicker) {
1039       rv = GetSaveAsFile(aMsgFilename, &saveAsFileType,
1040                          getter_AddRefs(saveAsFile));
1041       // A null saveAsFile means that the user canceled the save as
1042       if (NS_FAILED(rv) || !saveAsFile) goto done;
1043     } else {
1044       saveAsFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1045       rv = saveAsFile->InitWithPath(aMsgFilename);
1046       if (NS_FAILED(rv)) goto done;
1047       if (StringEndsWith(aMsgFilename,
1048                          NS_LITERAL_STRING_FROM_CSTRING(TEXT_FILE_EXTENSION),
1049                          nsCaseInsensitiveStringComparator))
1050         saveAsFileType = TEXT_FILE_TYPE;
1051       else if ((StringEndsWith(
1052                    aMsgFilename,
1053                    NS_LITERAL_STRING_FROM_CSTRING(HTML_FILE_EXTENSION),
1054                    nsCaseInsensitiveStringComparator)) ||
1055                (StringEndsWith(
1056                    aMsgFilename,
1057                    NS_LITERAL_STRING_FROM_CSTRING(HTML_FILE_EXTENSION2),
1058                    nsCaseInsensitiveStringComparator)))
1059         saveAsFileType = HTML_FILE_TYPE;
1060       else
1061         saveAsFileType = EML_FILE_TYPE;
1062     }
1063 
1064     rv = AdjustFileIfNameTooLong(saveAsFile);
1065     NS_ENSURE_SUCCESS(rv, rv);
1066 
1067     rv = PromptIfFileExists(saveAsFile);
1068     if (NS_FAILED(rv)) {
1069       goto done;
1070     }
1071 
1072     // After saveListener goes out of scope, the listener will be owned by
1073     // whoever the listener is registered with, usually a URL.
1074     saveListener = new nsSaveMsgListener(saveAsFile, this, nullptr);
1075     rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener),
1076                                       getter_AddRefs(urlListener));
1077     if (NS_FAILED(rv)) goto done;
1078 
1079     if (saveAsFileType == EML_FILE_TYPE) {
1080       nsCOMPtr<nsIURI> dummyNull;
1081       rv = messageService->SaveMessageToDisk(
1082           aURI, saveAsFile, false, urlListener, getter_AddRefs(dummyNull), true,
1083           mMsgWindow);
1084     } else {
1085       nsAutoCString urlString(aURI);
1086 
1087       // we can't go RFC822 to TXT until bug #1775 is fixed
1088       // so until then, do the HTML to TXT conversion in
1089       // nsSaveMsgListener::OnStopRequest(), see ConvertBufToPlainText()
1090       //
1091       // Setup the URL for a "Save As..." Operation...
1092       // For now, if this is a save as TEXT operation, then do
1093       // a "printing" operation
1094       if (saveAsFileType == TEXT_FILE_TYPE) {
1095         saveListener->m_outputFormat = nsSaveMsgListener::ePlainText;
1096         saveListener->m_doCharsetConversion = true;
1097         urlString.AppendLiteral("?header=print");
1098       } else {
1099         saveListener->m_outputFormat = nsSaveMsgListener::eHTML;
1100         saveListener->m_doCharsetConversion = false;
1101         urlString.AppendLiteral("?header=saveas");
1102       }
1103 
1104       nsCOMPtr<nsIURI> url;
1105       rv = NS_NewURI(getter_AddRefs(url), urlString);
1106       NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewURI failed");
1107       if (NS_FAILED(rv)) goto done;
1108 
1109       nsCOMPtr<nsIPrincipal> nullPrincipal =
1110           NullPrincipal::CreateWithoutOriginAttributes();
1111 
1112       saveListener->m_channel = nullptr;
1113       rv = NS_NewInputStreamChannel(
1114           getter_AddRefs(saveListener->m_channel), url, nullptr, nullPrincipal,
1115           nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
1116           nsIContentPolicy::TYPE_OTHER);
1117       NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewChannel failed");
1118       if (NS_FAILED(rv)) goto done;
1119 
1120       nsCOMPtr<nsIStreamConverterService> streamConverterService =
1121           do_GetService("@mozilla.org/streamConverters;1");
1122       nsCOMPtr<nsISupports> channelSupport =
1123           do_QueryInterface(saveListener->m_channel);
1124 
1125       // we can't go RFC822 to TXT until bug #1775 is fixed
1126       // so until then, do the HTML to TXT conversion in
1127       // nsSaveMsgListener::OnStopRequest(), see ConvertBufToPlainText()
1128       rv = streamConverterService->AsyncConvertData(
1129           MESSAGE_RFC822, TEXT_HTML, saveListener, channelSupport,
1130           getter_AddRefs(convertedListener));
1131       NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncConvertData failed");
1132       if (NS_FAILED(rv)) goto done;
1133 
1134       nsCOMPtr<nsIURI> dummyNull;
1135       rv = messageService->DisplayMessage(urlString, convertedListener,
1136                                           mMsgWindow, nullptr, false,
1137                                           getter_AddRefs(dummyNull));
1138     }
1139   } else {
1140     // ** save as Template
1141     nsCOMPtr<nsIFile> tmpFile;
1142     nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "nsmail.tmp",
1143                                                   getter_AddRefs(tmpFile));
1144 
1145     NS_ENSURE_SUCCESS(rv, rv);
1146 
1147     // For temp file, we should use restrictive 00600 instead of
1148     // ATTACHMENT_PERMISSION
1149     rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
1150     if (NS_FAILED(rv)) goto done;
1151 
1152     // The saveListener is owned by whoever we ultimately register the
1153     // listener with, generally a URL.
1154     saveListener = new nsSaveMsgListener(tmpFile, this, nullptr);
1155 
1156     if (aIdentity)
1157       rv = aIdentity->GetStationeryFolder(saveListener->m_templateUri);
1158     if (NS_FAILED(rv)) goto done;
1159 
1160     bool needDummyHeader =
1161         StringBeginsWith(saveListener->m_templateUri, "mailbox://"_ns);
1162     bool canonicalLineEnding =
1163         StringBeginsWith(saveListener->m_templateUri, "imap://"_ns);
1164 
1165     rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener),
1166                                       getter_AddRefs(urlListener));
1167     if (NS_FAILED(rv)) goto done;
1168 
1169     nsCOMPtr<nsIURI> dummyNull;
1170     rv = messageService->SaveMessageToDisk(
1171         aURI, tmpFile, needDummyHeader, urlListener, getter_AddRefs(dummyNull),
1172         canonicalLineEnding, mMsgWindow);
1173   }
1174 
1175 done:
1176   if (NS_FAILED(rv)) {
1177     Alert("saveMessageFailed");
1178   }
1179   return rv;
1180 }
1181 
GetSaveAsFile(const nsAString & aMsgFilename,int32_t * aSaveAsFileType,nsIFile ** aSaveAsFile)1182 nsresult nsMessenger::GetSaveAsFile(const nsAString& aMsgFilename,
1183                                     int32_t* aSaveAsFileType,
1184                                     nsIFile** aSaveAsFile) {
1185   nsresult rv;
1186   nsCOMPtr<nsIFilePicker> filePicker =
1187       do_CreateInstance("@mozilla.org/filepicker;1", &rv);
1188   NS_ENSURE_SUCCESS(rv, rv);
1189   nsString saveMailAsStr;
1190   GetString(u"SaveMailAs"_ns, saveMailAsStr);
1191   filePicker->Init(mWindow, saveMailAsStr, nsIFilePicker::modeSave);
1192 
1193   // if we have a non-null filename use it, otherwise use default save message
1194   // one
1195   if (aMsgFilename.IsEmpty()) {
1196     nsString saveMsgStr;
1197     GetString(u"defaultSaveMessageAsFileName"_ns, saveMsgStr);
1198     filePicker->SetDefaultString(saveMsgStr);
1199   } else {
1200     filePicker->SetDefaultString(aMsgFilename);
1201   }
1202 
1203   // because we will be using GetFilterIndex()
1204   // we must call AppendFilters() one at a time,
1205   // in MESSENGER_SAVEAS_FILE_TYPE order
1206   nsString emlFilesStr;
1207   GetString(u"EMLFiles"_ns, emlFilesStr);
1208   filePicker->AppendFilter(emlFilesStr, u"*.eml"_ns);
1209   filePicker->AppendFilters(nsIFilePicker::filterHTML);
1210   filePicker->AppendFilters(nsIFilePicker::filterText);
1211   filePicker->AppendFilters(nsIFilePicker::filterAll);
1212 
1213   // Save as the "All Files" file type by default. We want to save as .eml by
1214   // default, but the filepickers on some platforms don't switch extensions
1215   // based on the file type selected (bug 508597).
1216   filePicker->SetFilterIndex(ANY_FILE_TYPE);
1217   // Yes, this is fine even if we ultimately save as HTML or text. On Windows,
1218   // this actually is a boolean telling the file picker to automatically add
1219   // the correct extension depending on the filter. On Mac or Linux this is a
1220   // no-op.
1221   filePicker->SetDefaultExtension(u"eml"_ns);
1222 
1223   int16_t dialogResult;
1224 
1225   nsCOMPtr<nsIFile> lastSaveDir;
1226   rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
1227   if (NS_SUCCEEDED(rv) && lastSaveDir)
1228     filePicker->SetDisplayDirectory(lastSaveDir);
1229 
1230   nsCOMPtr<nsIFile> localFile;
1231   rv = ShowPicker(filePicker, &dialogResult);
1232   NS_ENSURE_SUCCESS(rv, rv);
1233   if (dialogResult == nsIFilePicker::returnCancel) {
1234     // We'll indicate this by setting the outparam to null.
1235     *aSaveAsFile = nullptr;
1236     return NS_OK;
1237   }
1238 
1239   rv = filePicker->GetFile(getter_AddRefs(localFile));
1240   NS_ENSURE_SUCCESS(rv, rv);
1241 
1242   rv = SetLastSaveDirectory(localFile);
1243   NS_ENSURE_SUCCESS(rv, rv);
1244 
1245   int32_t selectedSaveAsFileType;
1246   rv = filePicker->GetFilterIndex(&selectedSaveAsFileType);
1247   NS_ENSURE_SUCCESS(rv, rv);
1248 
1249   // If All Files was selected, look at the extension
1250   if (selectedSaveAsFileType == ANY_FILE_TYPE) {
1251     nsAutoString fileName;
1252     rv = localFile->GetLeafName(fileName);
1253     NS_ENSURE_SUCCESS(rv, rv);
1254 
1255     if (StringEndsWith(fileName,
1256                        NS_LITERAL_STRING_FROM_CSTRING(HTML_FILE_EXTENSION),
1257                        nsCaseInsensitiveStringComparator) ||
1258         StringEndsWith(fileName,
1259                        NS_LITERAL_STRING_FROM_CSTRING(HTML_FILE_EXTENSION2),
1260                        nsCaseInsensitiveStringComparator))
1261       *aSaveAsFileType = HTML_FILE_TYPE;
1262     else if (StringEndsWith(fileName,
1263                             NS_LITERAL_STRING_FROM_CSTRING(TEXT_FILE_EXTENSION),
1264                             nsCaseInsensitiveStringComparator))
1265       *aSaveAsFileType = TEXT_FILE_TYPE;
1266     else
1267       // The default is .eml
1268       *aSaveAsFileType = EML_FILE_TYPE;
1269   } else {
1270     *aSaveAsFileType = selectedSaveAsFileType;
1271   }
1272 
1273   if (dialogResult == nsIFilePicker::returnReplace) {
1274     // be extra safe and only delete when the file is really a file
1275     bool isFile;
1276     rv = localFile->IsFile(&isFile);
1277     if (NS_SUCCEEDED(rv) && isFile) {
1278       rv = localFile->Remove(false /* recursive delete */);
1279       NS_ENSURE_SUCCESS(rv, rv);
1280     } else {
1281       // We failed, or this isn't a file. We can't do anything about it.
1282       return NS_ERROR_FAILURE;
1283     }
1284   }
1285 
1286   *aSaveAsFile = nullptr;
1287   localFile.forget(aSaveAsFile);
1288   return NS_OK;
1289 }
1290 
1291 /**
1292  * Show a Save All dialog allowing the user to pick which folder to save
1293  * messages to.
1294  * @param [out] aSaveDir directory to save to. Will be null on cancel.
1295  */
GetSaveToDir(nsIFile ** aSaveDir)1296 nsresult nsMessenger::GetSaveToDir(nsIFile** aSaveDir) {
1297   nsresult rv;
1298   nsCOMPtr<nsIFilePicker> filePicker =
1299       do_CreateInstance("@mozilla.org/filepicker;1", &rv);
1300   NS_ENSURE_SUCCESS(rv, rv);
1301 
1302   nsString chooseFolderStr;
1303   GetString(u"ChooseFolder"_ns, chooseFolderStr);
1304   filePicker->Init(mWindow, chooseFolderStr, nsIFilePicker::modeGetFolder);
1305 
1306   nsCOMPtr<nsIFile> lastSaveDir;
1307   rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
1308   if (NS_SUCCEEDED(rv) && lastSaveDir)
1309     filePicker->SetDisplayDirectory(lastSaveDir);
1310 
1311   int16_t dialogResult;
1312   rv = ShowPicker(filePicker, &dialogResult);
1313   if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel) {
1314     // We'll indicate this by setting the outparam to null.
1315     *aSaveDir = nullptr;
1316     return NS_OK;
1317   }
1318 
1319   nsCOMPtr<nsIFile> dir;
1320   rv = filePicker->GetFile(getter_AddRefs(dir));
1321   NS_ENSURE_SUCCESS(rv, rv);
1322 
1323   rv = SetLastSaveDirectory(dir);
1324   NS_ENSURE_SUCCESS(rv, rv);
1325 
1326   *aSaveDir = nullptr;
1327   dir.forget(aSaveDir);
1328   return NS_OK;
1329 }
1330 
1331 NS_IMETHODIMP
SaveMessages(const nsTArray<nsString> & aFilenameArray,const nsTArray<nsCString> & aMessageUriArray)1332 nsMessenger::SaveMessages(const nsTArray<nsString>& aFilenameArray,
1333                           const nsTArray<nsCString>& aMessageUriArray) {
1334   MOZ_ASSERT(aFilenameArray.Length() == aMessageUriArray.Length());
1335 
1336   nsresult rv;
1337 
1338   nsCOMPtr<nsIFile> saveDir;
1339   rv = GetSaveToDir(getter_AddRefs(saveDir));
1340   NS_ENSURE_SUCCESS(rv, rv);
1341   if (!saveDir)  // A null saveDir means that the user canceled the save.
1342     return NS_OK;
1343 
1344   for (uint32_t i = 0; i < aFilenameArray.Length(); i++) {
1345     nsCOMPtr<nsIFile> saveToFile =
1346         do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1347     NS_ENSURE_SUCCESS(rv, rv);
1348     rv = saveToFile->InitWithFile(saveDir);
1349     NS_ENSURE_SUCCESS(rv, rv);
1350 
1351     rv = saveToFile->Append(aFilenameArray[i]);
1352     NS_ENSURE_SUCCESS(rv, rv);
1353 
1354     rv = AdjustFileIfNameTooLong(saveToFile);
1355     NS_ENSURE_SUCCESS(rv, rv);
1356 
1357     rv = PromptIfFileExists(saveToFile);
1358     if (NS_FAILED(rv)) continue;
1359 
1360     nsCOMPtr<nsIMsgMessageService> messageService;
1361     nsCOMPtr<nsIUrlListener> urlListener;
1362 
1363     rv = GetMessageServiceFromURI(aMessageUriArray[i],
1364                                   getter_AddRefs(messageService));
1365     if (NS_FAILED(rv)) {
1366       Alert("saveMessageFailed");
1367       return rv;
1368     }
1369 
1370     RefPtr<nsSaveMsgListener> saveListener =
1371         new nsSaveMsgListener(saveToFile, this, nullptr);
1372 
1373     rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener),
1374                                       getter_AddRefs(urlListener));
1375     if (NS_FAILED(rv)) {
1376       Alert("saveMessageFailed");
1377       return rv;
1378     }
1379 
1380     // Ok, now save the message.
1381     nsCOMPtr<nsIURI> dummyNull;
1382     rv = messageService->SaveMessageToDisk(
1383         aMessageUriArray[i], saveToFile, false, urlListener,
1384         getter_AddRefs(dummyNull), true, mMsgWindow);
1385     if (NS_FAILED(rv)) {
1386       Alert("saveMessageFailed");
1387       return rv;
1388     }
1389   }
1390   return rv;
1391 }
1392 
Alert(const char * stringName)1393 nsresult nsMessenger::Alert(const char* stringName) {
1394   nsresult rv = NS_OK;
1395 
1396   if (mDocShell) {
1397     nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
1398 
1399     if (dialog) {
1400       nsString alertStr;
1401       GetString(NS_ConvertASCIItoUTF16(stringName), alertStr);
1402       rv = dialog->Alert(nullptr, alertStr.get());
1403     }
1404   }
1405   return rv;
1406 }
1407 
1408 NS_IMETHODIMP
MessageServiceFromURI(const nsACString & aUri,nsIMsgMessageService ** aMsgService)1409 nsMessenger::MessageServiceFromURI(const nsACString& aUri,
1410                                    nsIMsgMessageService** aMsgService) {
1411   NS_ENSURE_ARG_POINTER(aMsgService);
1412   return GetMessageServiceFromURI(aUri, aMsgService);
1413 }
1414 
1415 NS_IMETHODIMP
MsgHdrFromURI(const nsACString & aUri,nsIMsgDBHdr ** aMsgHdr)1416 nsMessenger::MsgHdrFromURI(const nsACString& aUri, nsIMsgDBHdr** aMsgHdr) {
1417   NS_ENSURE_ARG_POINTER(aMsgHdr);
1418   nsCOMPtr<nsIMsgMessageService> msgService;
1419   nsresult rv;
1420 
1421   if (mMsgWindow && (StringBeginsWith(aUri, "file:"_ns) ||
1422                      PromiseFlatCString(aUri).Find(
1423                          "type=application/x-message-display") >= 0)) {
1424     nsCOMPtr<nsIMsgHeaderSink> headerSink;
1425     mMsgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink));
1426     if (headerSink) {
1427       rv = headerSink->GetDummyMsgHeader(aMsgHdr);
1428       // Is there a way to check if they're asking for the hdr currently
1429       // displayed in a stand-alone msg window from a .eml file?
1430       // (pretty likely if this is a file: uri)
1431       return rv;
1432     }
1433   }
1434 
1435   rv = GetMessageServiceFromURI(aUri, getter_AddRefs(msgService));
1436   NS_ENSURE_SUCCESS(rv, rv);
1437   return msgService->MessageURIToMsgHdr(aUri, aMsgHdr);
1438 }
1439 
GetUndoTransactionType(uint32_t * txnType)1440 NS_IMETHODIMP nsMessenger::GetUndoTransactionType(uint32_t* txnType) {
1441   NS_ENSURE_TRUE(txnType && mTxnMgr, NS_ERROR_NULL_POINTER);
1442 
1443   nsresult rv;
1444   *txnType = nsMessenger::eUnknown;
1445   nsCOMPtr<nsITransaction> txn;
1446   rv = mTxnMgr->PeekUndoStack(getter_AddRefs(txn));
1447   if (NS_SUCCEEDED(rv) && txn) {
1448     nsCOMPtr<nsIPropertyBag2> propertyBag = do_QueryInterface(txn, &rv);
1449     NS_ENSURE_SUCCESS(rv, rv);
1450     return propertyBag->GetPropertyAsUint32(u"type"_ns, txnType);
1451   }
1452   return rv;
1453 }
1454 
CanUndo(bool * bValue)1455 NS_IMETHODIMP nsMessenger::CanUndo(bool* bValue) {
1456   NS_ENSURE_TRUE(bValue && mTxnMgr, NS_ERROR_NULL_POINTER);
1457 
1458   nsresult rv;
1459   *bValue = false;
1460   int32_t count = 0;
1461   rv = mTxnMgr->GetNumberOfUndoItems(&count);
1462   if (NS_SUCCEEDED(rv) && count > 0) *bValue = true;
1463   return rv;
1464 }
1465 
GetRedoTransactionType(uint32_t * txnType)1466 NS_IMETHODIMP nsMessenger::GetRedoTransactionType(uint32_t* txnType) {
1467   NS_ENSURE_TRUE(txnType && mTxnMgr, NS_ERROR_NULL_POINTER);
1468 
1469   nsresult rv;
1470   *txnType = nsMessenger::eUnknown;
1471   nsCOMPtr<nsITransaction> txn;
1472   rv = mTxnMgr->PeekRedoStack(getter_AddRefs(txn));
1473   if (NS_SUCCEEDED(rv) && txn) {
1474     nsCOMPtr<nsIPropertyBag2> propertyBag = do_QueryInterface(txn, &rv);
1475     NS_ENSURE_SUCCESS(rv, rv);
1476     return propertyBag->GetPropertyAsUint32(u"type"_ns, txnType);
1477   }
1478   return rv;
1479 }
1480 
CanRedo(bool * bValue)1481 NS_IMETHODIMP nsMessenger::CanRedo(bool* bValue) {
1482   NS_ENSURE_TRUE(bValue && mTxnMgr, NS_ERROR_NULL_POINTER);
1483 
1484   nsresult rv;
1485   *bValue = false;
1486   int32_t count = 0;
1487   rv = mTxnMgr->GetNumberOfRedoItems(&count);
1488   if (NS_SUCCEEDED(rv) && count > 0) *bValue = true;
1489   return rv;
1490 }
1491 
1492 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
Undo(nsIMsgWindow * msgWindow)1493 nsMessenger::Undo(nsIMsgWindow* msgWindow) {
1494   nsresult rv = NS_OK;
1495   if (mTxnMgr) {
1496     int32_t numTxn = 0;
1497     rv = mTxnMgr->GetNumberOfUndoItems(&numTxn);
1498     if (NS_SUCCEEDED(rv) && numTxn > 0) {
1499       nsCOMPtr<nsITransaction> txn;
1500       rv = mTxnMgr->PeekUndoStack(getter_AddRefs(txn));
1501       if (NS_SUCCEEDED(rv) && txn) {
1502         static_cast<nsMsgTxn*>(static_cast<nsITransaction*>(txn.get()))
1503             ->SetMsgWindow(msgWindow);
1504       }
1505       nsCOMPtr<nsITransactionManager> txnMgr = mTxnMgr;
1506       txnMgr->UndoTransaction();
1507     }
1508   }
1509   return rv;
1510 }
1511 
1512 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
Redo(nsIMsgWindow * msgWindow)1513 nsMessenger::Redo(nsIMsgWindow* msgWindow) {
1514   nsresult rv = NS_OK;
1515   if (mTxnMgr) {
1516     int32_t numTxn = 0;
1517     rv = mTxnMgr->GetNumberOfRedoItems(&numTxn);
1518     if (NS_SUCCEEDED(rv) && numTxn > 0) {
1519       nsCOMPtr<nsITransaction> txn;
1520       rv = mTxnMgr->PeekRedoStack(getter_AddRefs(txn));
1521       if (NS_SUCCEEDED(rv) && txn) {
1522         static_cast<nsMsgTxn*>(static_cast<nsITransaction*>(txn.get()))
1523             ->SetMsgWindow(msgWindow);
1524       }
1525       nsCOMPtr<nsITransactionManager> txnMgr = mTxnMgr;
1526       txnMgr->RedoTransaction();
1527     }
1528   }
1529   return rv;
1530 }
1531 
1532 NS_IMETHODIMP
GetTransactionManager(nsITransactionManager ** aTxnMgr)1533 nsMessenger::GetTransactionManager(nsITransactionManager** aTxnMgr) {
1534   NS_ENSURE_TRUE(mTxnMgr && aTxnMgr, NS_ERROR_NULL_POINTER);
1535   NS_ADDREF(*aTxnMgr = mTxnMgr);
1536   return NS_OK;
1537 }
1538 
ForceDetectDocumentCharset()1539 NS_IMETHODIMP nsMessenger::ForceDetectDocumentCharset() {
1540   // We want to redisplay the currently selected message (if any) but forcing
1541   // the redisplay with an autodetected charset
1542   if (!mLastDisplayURI.IsEmpty()) {
1543     SetDisplayCharset("UTF-8"_ns);
1544 
1545     nsCOMPtr<nsIMsgMessageService> messageService;
1546     nsresult rv = GetMessageServiceFromURI(mLastDisplayURI,
1547                                            getter_AddRefs(messageService));
1548 
1549     if (NS_SUCCEEDED(rv) && messageService) {
1550       nsCOMPtr<nsIURI> dummyNull;
1551       messageService->DisplayMessage(mLastDisplayURI, mDocShell, mMsgWindow,
1552                                      nullptr, true, getter_AddRefs(dummyNull));
1553     }
1554   }
1555 
1556   return NS_OK;
1557 }
1558 
1559 NS_IMETHODIMP
GetLastDisplayedMessageUri(nsACString & aLastDisplayedMessageUri)1560 nsMessenger::GetLastDisplayedMessageUri(nsACString& aLastDisplayedMessageUri) {
1561   aLastDisplayedMessageUri = mLastDisplayURI;
1562   return NS_OK;
1563 }
1564 
nsSaveMsgListener(nsIFile * aFile,nsMessenger * aMessenger,nsIUrlListener * aListener)1565 nsSaveMsgListener::nsSaveMsgListener(nsIFile* aFile, nsMessenger* aMessenger,
1566                                      nsIUrlListener* aListener) {
1567   m_file = aFile;
1568   m_messenger = aMessenger;
1569   mListener = aListener;
1570   mUrlHasStopped = false;
1571   mRequestHasStopped = false;
1572 
1573   // rhp: for charset handling
1574   m_doCharsetConversion = false;
1575   m_saveAllAttachmentsState = nullptr;
1576   mProgress = 0;
1577   mMaxProgress = -1;
1578   mCanceled = false;
1579   m_outputFormat = eUnknown;
1580   mInitialized = false;
1581 }
1582 
~nsSaveMsgListener()1583 nsSaveMsgListener::~nsSaveMsgListener() {}
1584 
1585 //
1586 // nsISupports
1587 //
NS_IMPL_ISUPPORTS(nsSaveMsgListener,nsIUrlListener,nsIMsgCopyServiceListener,nsIStreamListener,nsIRequestObserver,nsICancelable)1588 NS_IMPL_ISUPPORTS(nsSaveMsgListener, nsIUrlListener, nsIMsgCopyServiceListener,
1589                   nsIStreamListener, nsIRequestObserver, nsICancelable)
1590 
1591 NS_IMETHODIMP
1592 nsSaveMsgListener::Cancel(nsresult status) {
1593   mCanceled = true;
1594   return NS_OK;
1595 }
1596 
1597 //
1598 // nsIUrlListener
1599 //
1600 NS_IMETHODIMP
OnStartRunningUrl(nsIURI * url)1601 nsSaveMsgListener::OnStartRunningUrl(nsIURI* url) {
1602   if (mListener) mListener->OnStartRunningUrl(url);
1603   return NS_OK;
1604 }
1605 
1606 NS_IMETHODIMP
OnStopRunningUrl(nsIURI * url,nsresult exitCode)1607 nsSaveMsgListener::OnStopRunningUrl(nsIURI* url, nsresult exitCode) {
1608   nsresult rv = exitCode;
1609   mUrlHasStopped = true;
1610 
1611   // ** save as template goes here
1612   if (!m_templateUri.IsEmpty()) {
1613     nsCOMPtr<nsIMsgFolder> templateFolder;
1614     rv = GetOrCreateFolder(m_templateUri, getter_AddRefs(templateFolder));
1615     if (NS_FAILED(rv)) goto done;
1616     nsCOMPtr<nsIMsgCopyService> copyService =
1617         do_GetService(NS_MSGCOPYSERVICE_CONTRACTID);
1618     if (copyService) {
1619       nsCOMPtr<nsIFile> clone;
1620       m_file->Clone(getter_AddRefs(clone));
1621       rv = copyService->CopyFileMessage(clone, templateFolder, nullptr, true,
1622                                         nsMsgMessageFlags::Read, EmptyCString(),
1623                                         this, nullptr);
1624       // Clear this so we don't end up in a loop if OnStopRunningUrl gets
1625       // called again.
1626       m_templateUri.Truncate();
1627     }
1628   } else if (m_outputStream && mRequestHasStopped) {
1629     m_outputStream->Close();
1630     m_outputStream = nullptr;
1631   }
1632 
1633 done:
1634   if (NS_FAILED(rv)) {
1635     if (m_file) m_file->Remove(false);
1636     if (m_messenger) m_messenger->Alert("saveMessageFailed");
1637   }
1638 
1639   if (mRequestHasStopped && mListener)
1640     mListener->OnStopRunningUrl(url, exitCode);
1641   else
1642     mListenerUri = url;
1643 
1644   return rv;
1645 }
1646 
1647 NS_IMETHODIMP
OnStartCopy(void)1648 nsSaveMsgListener::OnStartCopy(void) { return NS_OK; }
1649 
1650 NS_IMETHODIMP
OnProgress(uint32_t aProgress,uint32_t aProgressMax)1651 nsSaveMsgListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) {
1652   return NS_OK;
1653 }
1654 
1655 NS_IMETHODIMP
SetMessageKey(nsMsgKey aKey)1656 nsSaveMsgListener::SetMessageKey(nsMsgKey aKey) {
1657   return NS_ERROR_NOT_IMPLEMENTED;
1658 }
1659 
1660 NS_IMETHODIMP
GetMessageId(nsACString & aMessageId)1661 nsSaveMsgListener::GetMessageId(nsACString& aMessageId) {
1662   return NS_ERROR_NOT_IMPLEMENTED;
1663 }
1664 
1665 NS_IMETHODIMP
OnStopCopy(nsresult aStatus)1666 nsSaveMsgListener::OnStopCopy(nsresult aStatus) {
1667   if (m_file) m_file->Remove(false);
1668   return aStatus;
1669 }
1670 
1671 // initializes the progress window if we are going to show one
1672 // and for OSX, sets creator flags on the output file
InitializeDownload(nsIRequest * aRequest)1673 nsresult nsSaveMsgListener::InitializeDownload(nsIRequest* aRequest) {
1674   nsresult rv = NS_OK;
1675 
1676   mInitialized = true;
1677   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
1678 
1679   if (!channel) return rv;
1680 
1681   // Get the max progress from the URL if we haven't already got it.
1682   if (mMaxProgress == -1) {
1683     nsCOMPtr<nsIURI> uri;
1684     channel->GetURI(getter_AddRefs(uri));
1685     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(uri));
1686     if (mailnewsUrl) mailnewsUrl->GetMaxProgress(&mMaxProgress);
1687   }
1688 
1689   if (!m_contentType.IsEmpty()) {
1690     nsCOMPtr<nsIMIMEService> mimeService(
1691         do_GetService(NS_MIMESERVICE_CONTRACTID));
1692     nsCOMPtr<nsIMIMEInfo> mimeinfo;
1693 
1694     mimeService->GetFromTypeAndExtension(m_contentType, EmptyCString(),
1695                                          getter_AddRefs(mimeinfo));
1696 
1697     // create a download progress window
1698 
1699     // Set saveToDisk explicitly to avoid launching the saved file.
1700     // See
1701     // https://hg.mozilla.org/mozilla-central/file/814a6f071472/toolkit/components/jsdownloads/src/DownloadLegacy.js#l164
1702     mimeinfo->SetPreferredAction(nsIHandlerInfo::saveToDisk);
1703 
1704     // When we don't allow warnings, also don't show progress, as this
1705     //  is an environment (typically filters) where we don't want
1706     //  interruption.
1707     bool allowProgress = true;
1708     if (m_saveAllAttachmentsState)
1709       allowProgress = !m_saveAllAttachmentsState->m_withoutWarning;
1710     if (allowProgress) {
1711       nsCOMPtr<nsITransfer> tr = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
1712       if (tr && m_file) {
1713         PRTime timeDownloadStarted = PR_Now();
1714 
1715         nsCOMPtr<nsIURI> outputURI;
1716         NS_NewFileURI(getter_AddRefs(outputURI), m_file);
1717 
1718         nsCOMPtr<nsIURI> url;
1719         channel->GetURI(getter_AddRefs(url));
1720         rv = tr->Init(url, outputURI, EmptyString(), mimeinfo,
1721                       timeDownloadStarted, nullptr, this, false,
1722                       nsITransfer::DOWNLOAD_ACCEPTABLE);
1723 
1724         // now store the web progresslistener
1725         mTransfer = tr;
1726       }
1727     }
1728   }
1729   return rv;
1730 }
1731 
1732 NS_IMETHODIMP
OnStartRequest(nsIRequest * request)1733 nsSaveMsgListener::OnStartRequest(nsIRequest* request) {
1734   if (m_file)
1735     MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream), m_file, -1,
1736                                    ATTACHMENT_PERMISSION);
1737   if (!m_outputStream) {
1738     mCanceled = true;
1739     if (m_messenger) m_messenger->Alert("saveAttachmentFailed");
1740   }
1741   return NS_OK;
1742 }
1743 
1744 NS_IMETHODIMP
OnStopRequest(nsIRequest * request,nsresult status)1745 nsSaveMsgListener::OnStopRequest(nsIRequest* request, nsresult status) {
1746   nsresult rv = NS_OK;
1747   mRequestHasStopped = true;
1748 
1749   // rhp: If we are doing the charset conversion magic, this is different
1750   // processing, otherwise, its just business as usual.
1751   // If we need text/plain, then we need to convert the HTML and then convert
1752   // to the systems charset.
1753   if (m_doCharsetConversion && m_outputStream) {
1754     // For HTML, code is emitted immediately in OnDataAvailable.
1755     MOZ_ASSERT(m_outputFormat == ePlainText,
1756                "For HTML, m_doCharsetConversion shouldn't be set");
1757     NS_ConvertUTF8toUTF16 utf16Buffer(m_msgBuffer);
1758     ConvertBufToPlainText(utf16Buffer, false, false, false);
1759 
1760     nsCString outCString;
1761     // NS_CopyUnicodeToNative() doesn't return an error, so we have no choice
1762     // but to always use UTF-8.
1763     CopyUTF16toUTF8(utf16Buffer, outCString);
1764     uint32_t writeCount;
1765     rv = m_outputStream->Write(outCString.get(), outCString.Length(),
1766                                &writeCount);
1767     if (outCString.Length() != writeCount) rv = NS_ERROR_FAILURE;
1768   }
1769 
1770   if (m_outputStream) {
1771     m_outputStream->Close();
1772     m_outputStream = nullptr;
1773   }
1774 
1775   if (m_saveAllAttachmentsState) {
1776     m_saveAllAttachmentsState->m_curIndex++;
1777     if (!mCanceled && m_saveAllAttachmentsState->m_curIndex <
1778                           m_saveAllAttachmentsState->m_count) {
1779       nsSaveAllAttachmentsState* state = m_saveAllAttachmentsState;
1780       uint32_t i = state->m_curIndex;
1781       nsString unescapedName;
1782       RefPtr<nsLocalFile> localFile =
1783           new nsLocalFile(nsTDependentString<PathChar>(state->m_directoryName));
1784       if (localFile->NativePath().IsEmpty()) {
1785         rv = NS_ERROR_FAILURE;
1786         goto done;
1787       }
1788 
1789       ConvertAndSanitizeFileName(state->m_displayNameArray[i], unescapedName);
1790       rv = localFile->Append(unescapedName);
1791       if (NS_FAILED(rv)) goto done;
1792 
1793       // When we are running with no warnings (typically filters and other
1794       // automatic uses), then don't prompt for duplicates, but create a unique
1795       // file instead.
1796       if (!m_saveAllAttachmentsState->m_withoutWarning) {
1797         rv = m_messenger->PromptIfFileExists(localFile);
1798         if (NS_FAILED(rv)) goto done;
1799       } else {
1800         rv = localFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE,
1801                                      ATTACHMENT_PERMISSION);
1802         if (NS_FAILED(rv)) goto done;
1803       }
1804       rv = m_messenger->SaveAttachment(
1805           localFile, state->m_urlArray[i], state->m_messageUriArray[i],
1806           state->m_contentTypeArray[i], (void*)state, nullptr);
1807     done:
1808       if (NS_FAILED(rv)) {
1809         delete state;
1810         m_saveAllAttachmentsState = nullptr;
1811       }
1812     } else {
1813       // check if we're saving attachments prior to detaching them.
1814       if (m_saveAllAttachmentsState->m_detachingAttachments && !mCanceled) {
1815         nsSaveAllAttachmentsState* state = m_saveAllAttachmentsState;
1816         m_messenger->DetachAttachments(
1817             state->m_contentTypeArray, state->m_urlArray,
1818             state->m_displayNameArray, state->m_messageUriArray,
1819             &state->m_savedFiles, state->m_withoutWarning);
1820       }
1821 
1822       delete m_saveAllAttachmentsState;
1823       m_saveAllAttachmentsState = nullptr;
1824     }
1825   }
1826 
1827   if (mTransfer) {
1828     mTransfer->OnProgressChange64(nullptr, nullptr, mMaxProgress, mMaxProgress,
1829                                   mMaxProgress, mMaxProgress);
1830     mTransfer->OnStateChange(nullptr, nullptr,
1831                              nsIWebProgressListener::STATE_STOP |
1832                                  nsIWebProgressListener::STATE_IS_NETWORK,
1833                              NS_OK);
1834     mTransfer = nullptr;  // break any circular dependencies between the
1835                           // progress dialog and use
1836   }
1837 
1838   if (mUrlHasStopped && mListener)
1839     mListener->OnStopRunningUrl(mListenerUri, rv);
1840 
1841   return NS_OK;
1842 }
1843 
1844 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsIInputStream * inStream,uint64_t srcOffset,uint32_t count)1845 nsSaveMsgListener::OnDataAvailable(nsIRequest* request,
1846                                    nsIInputStream* inStream, uint64_t srcOffset,
1847                                    uint32_t count) {
1848   nsresult rv = NS_ERROR_FAILURE;
1849   // first, check to see if we've been canceled....
1850   if (mCanceled)  // then go cancel our underlying channel too
1851     return request->Cancel(NS_BINDING_ABORTED);
1852 
1853   if (!mInitialized) InitializeDownload(request);
1854 
1855   if (m_outputStream) {
1856     mProgress += count;
1857     uint64_t available;
1858     uint32_t readCount, maxReadCount = sizeof(m_dataBuffer);
1859     uint32_t writeCount;
1860     rv = inStream->Available(&available);
1861     while (NS_SUCCEEDED(rv) && available) {
1862       if (maxReadCount > available) maxReadCount = (uint32_t)available;
1863       rv = inStream->Read(m_dataBuffer, maxReadCount, &readCount);
1864 
1865       // rhp:
1866       // Ok, now we do one of two things. If we are sending out HTML, then
1867       // just write it to the HTML stream as it comes along...but if this is
1868       // a save as TEXT operation, we need to buffer this up for conversion
1869       // when we are done. When the stream converter for HTML-TEXT gets in
1870       // place, this magic can go away.
1871       //
1872       if (NS_SUCCEEDED(rv)) {
1873         if ((m_doCharsetConversion) && (m_outputFormat == ePlainText))
1874           m_msgBuffer.Append(Substring(m_dataBuffer, m_dataBuffer + readCount));
1875         else
1876           rv = m_outputStream->Write(m_dataBuffer, readCount, &writeCount);
1877 
1878         available -= readCount;
1879       }
1880     }
1881 
1882     if (NS_SUCCEEDED(rv) && mTransfer)  // Send progress notification.
1883       mTransfer->OnProgressChange64(nullptr, request, mProgress, mMaxProgress,
1884                                     mProgress, mMaxProgress);
1885   }
1886   return rv;
1887 }
1888 
1889 #define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
1890 
InitStringBundle()1891 nsresult nsMessenger::InitStringBundle() {
1892   if (mStringBundle) return NS_OK;
1893 
1894   const char propertyURL[] = MESSENGER_STRING_URL;
1895   nsCOMPtr<nsIStringBundleService> sBundleService =
1896       mozilla::services::GetStringBundleService();
1897   NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
1898   return sBundleService->CreateBundle(propertyURL,
1899                                       getter_AddRefs(mStringBundle));
1900 }
1901 
GetString(const nsString & aStringName,nsString & aValue)1902 void nsMessenger::GetString(const nsString& aStringName, nsString& aValue) {
1903   nsresult rv;
1904   aValue.Truncate();
1905 
1906   if (!mStringBundle) rv = InitStringBundle();
1907 
1908   if (mStringBundle)
1909     rv = mStringBundle->GetStringFromName(
1910         NS_ConvertUTF16toUTF8(aStringName).get(), aValue);
1911   else
1912     rv = NS_ERROR_FAILURE;
1913 
1914   if (NS_FAILED(rv) || aValue.IsEmpty()) aValue = aStringName;
1915   return;
1916 }
1917 
nsSaveAllAttachmentsState(const nsTArray<nsCString> & contentTypeArray,const nsTArray<nsCString> & urlArray,const nsTArray<nsCString> & displayNameArray,const nsTArray<nsCString> & messageUriArray,const PathChar * dirName,bool detachingAttachments)1918 nsSaveAllAttachmentsState::nsSaveAllAttachmentsState(
1919     const nsTArray<nsCString>& contentTypeArray,
1920     const nsTArray<nsCString>& urlArray,
1921     const nsTArray<nsCString>& displayNameArray,
1922     const nsTArray<nsCString>& messageUriArray, const PathChar* dirName,
1923     bool detachingAttachments)
1924     : m_contentTypeArray(contentTypeArray.Clone()),
1925       m_urlArray(urlArray.Clone()),
1926       m_displayNameArray(displayNameArray.Clone()),
1927       m_messageUriArray(messageUriArray.Clone()),
1928       m_detachingAttachments(detachingAttachments),
1929       m_withoutWarning(false) {
1930   m_count = contentTypeArray.Length();
1931   m_curIndex = 0;
1932   m_directoryName = NS_xstrdup(dirName);
1933 }
1934 
~nsSaveAllAttachmentsState()1935 nsSaveAllAttachmentsState::~nsSaveAllAttachmentsState() {
1936   free(m_directoryName);
1937 }
1938 
GetLastSaveDirectory(nsIFile ** aLastSaveDir)1939 nsresult nsMessenger::GetLastSaveDirectory(nsIFile** aLastSaveDir) {
1940   nsresult rv;
1941   nsCOMPtr<nsIPrefBranch> prefBranch =
1942       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1943   NS_ENSURE_SUCCESS(rv, rv);
1944 
1945   // this can fail, and it will, on the first time we call it, as there is no
1946   // default for this pref.
1947   nsCOMPtr<nsIFile> localFile;
1948   rv = prefBranch->GetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME,
1949                                    NS_GET_IID(nsIFile),
1950                                    getter_AddRefs(localFile));
1951   if (NS_SUCCEEDED(rv)) localFile.forget(aLastSaveDir);
1952   return rv;
1953 }
1954 
SetLastSaveDirectory(nsIFile * aLocalFile)1955 nsresult nsMessenger::SetLastSaveDirectory(nsIFile* aLocalFile) {
1956   NS_ENSURE_ARG_POINTER(aLocalFile);
1957   nsresult rv;
1958   nsCOMPtr<nsIPrefBranch> prefBranch =
1959       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1960   NS_ENSURE_SUCCESS(rv, rv);
1961 
1962   // if the file is a directory, just use it for the last dir chosen
1963   // otherwise, use the parent of the file as the last dir chosen.
1964   // IsDirectory() will return error on saving a file, as the
1965   // file doesn't exist yet.
1966   bool isDirectory;
1967   rv = aLocalFile->IsDirectory(&isDirectory);
1968   if (NS_SUCCEEDED(rv) && isDirectory) {
1969     rv = prefBranch->SetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME,
1970                                      NS_GET_IID(nsIFile), aLocalFile);
1971     NS_ENSURE_SUCCESS(rv, rv);
1972   } else {
1973     nsCOMPtr<nsIFile> parent;
1974     rv = aLocalFile->GetParent(getter_AddRefs(parent));
1975     NS_ENSURE_SUCCESS(rv, rv);
1976 
1977     rv = prefBranch->SetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME,
1978                                      NS_GET_IID(nsIFile), parent);
1979     NS_ENSURE_SUCCESS(rv, rv);
1980   }
1981   return NS_OK;
1982 }
1983 
1984 /* void getUrisAtNavigatePos (in long aPos, out ACString aFolderUri, out
1985  * ACString aMsgUri); */
1986 // aPos is relative to the current history cursor - 1 is forward, -1 is back.
GetMsgUriAtNavigatePos(int32_t aPos,nsACString & aMsgUri)1987 NS_IMETHODIMP nsMessenger::GetMsgUriAtNavigatePos(int32_t aPos,
1988                                                   nsACString& aMsgUri) {
1989   int32_t desiredArrayIndex = (mCurHistoryPos + (aPos << 1));
1990   if (desiredArrayIndex >= 0 &&
1991       desiredArrayIndex < (int32_t)mLoadedMsgHistory.Length()) {
1992     mNavigatingToUri = mLoadedMsgHistory[desiredArrayIndex];
1993     aMsgUri = mNavigatingToUri;
1994     return NS_OK;
1995   }
1996   return NS_ERROR_FAILURE;
1997 }
1998 
SetNavigatePos(int32_t aPos)1999 NS_IMETHODIMP nsMessenger::SetNavigatePos(int32_t aPos) {
2000   if ((aPos << 1) < (int32_t)mLoadedMsgHistory.Length()) {
2001     mCurHistoryPos = aPos << 1;
2002     return NS_OK;
2003   } else
2004     return NS_ERROR_INVALID_ARG;
2005 }
2006 
GetNavigatePos(int32_t * aPos)2007 NS_IMETHODIMP nsMessenger::GetNavigatePos(int32_t* aPos) {
2008   NS_ENSURE_ARG_POINTER(aPos);
2009   *aPos = mCurHistoryPos >> 1;
2010   return NS_OK;
2011 }
2012 
2013 // aPos is relative to the current history cursor - 1 is forward, -1 is back.
GetFolderUriAtNavigatePos(int32_t aPos,nsACString & aFolderUri)2014 NS_IMETHODIMP nsMessenger::GetFolderUriAtNavigatePos(int32_t aPos,
2015                                                      nsACString& aFolderUri) {
2016   int32_t desiredArrayIndex = (mCurHistoryPos + (aPos << 1));
2017   if (desiredArrayIndex >= 0 &&
2018       desiredArrayIndex < (int32_t)mLoadedMsgHistory.Length()) {
2019     mNavigatingToUri = mLoadedMsgHistory[desiredArrayIndex + 1];
2020     aFolderUri = mNavigatingToUri;
2021     return NS_OK;
2022   }
2023   return NS_ERROR_FAILURE;
2024 }
2025 
GetNavigateHistory(nsTArray<nsCString> & aHistoryUris)2026 NS_IMETHODIMP nsMessenger::GetNavigateHistory(
2027     nsTArray<nsCString>& aHistoryUris) {
2028   aHistoryUris = mLoadedMsgHistory.Clone();
2029   return NS_OK;
2030 }
2031 
2032 NS_IMETHODIMP
FormatFileSize(uint64_t aSize,bool aUseKB,nsAString & aFormattedSize)2033 nsMessenger::FormatFileSize(uint64_t aSize, bool aUseKB,
2034                             nsAString& aFormattedSize) {
2035   return ::FormatFileSize(aSize, aUseKB, aFormattedSize);
2036 }
2037 
OnItemAdded(nsIMsgFolder * parentItem,nsISupports * item)2038 NS_IMETHODIMP nsMessenger::OnItemAdded(nsIMsgFolder* parentItem,
2039                                        nsISupports* item) {
2040   return NS_ERROR_NOT_IMPLEMENTED;
2041 }
2042 
OnItemRemoved(nsIMsgFolder * parentItem,nsISupports * item)2043 NS_IMETHODIMP nsMessenger::OnItemRemoved(nsIMsgFolder* parentItem,
2044                                          nsISupports* item) {
2045   // check if this item is a message header that's in our history list. If so,
2046   // remove it from the history list.
2047   nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryInterface(item);
2048   if (msgHdr) {
2049     nsCOMPtr<nsIMsgFolder> folder;
2050     msgHdr->GetFolder(getter_AddRefs(folder));
2051     if (folder) {
2052       nsCString msgUri;
2053       nsMsgKey msgKey;
2054       msgHdr->GetMessageKey(&msgKey);
2055       folder->GenerateMessageURI(msgKey, msgUri);
2056       // need to remove the corresponding folder entry, and
2057       // adjust the current history pos.
2058       size_t uriPos = mLoadedMsgHistory.IndexOf(msgUri);
2059       if (uriPos != mLoadedMsgHistory.NoIndex) {
2060         mLoadedMsgHistory.RemoveElementAt(uriPos);
2061         mLoadedMsgHistory.RemoveElementAt(uriPos);  // and the folder uri entry
2062         if (mCurHistoryPos >= (int32_t)uriPos) mCurHistoryPos -= 2;
2063       }
2064     }
2065   }
2066   return NS_OK;
2067 }
2068 
OnItemPropertyChanged(nsIMsgFolder * item,const nsACString & property,const nsACString & oldValue,const nsACString & newValue)2069 NS_IMETHODIMP nsMessenger::OnItemPropertyChanged(nsIMsgFolder* item,
2070                                                  const nsACString& property,
2071                                                  const nsACString& oldValue,
2072                                                  const nsACString& newValue) {
2073   return NS_ERROR_NOT_IMPLEMENTED;
2074 }
2075 
OnItemIntPropertyChanged(nsIMsgFolder * item,const nsACString & property,int64_t oldValue,int64_t newValue)2076 NS_IMETHODIMP nsMessenger::OnItemIntPropertyChanged(nsIMsgFolder* item,
2077                                                     const nsACString& property,
2078                                                     int64_t oldValue,
2079                                                     int64_t newValue) {
2080   return NS_ERROR_NOT_IMPLEMENTED;
2081 }
2082 
OnItemBoolPropertyChanged(nsIMsgFolder * item,const nsACString & property,bool oldValue,bool newValue)2083 NS_IMETHODIMP nsMessenger::OnItemBoolPropertyChanged(nsIMsgFolder* item,
2084                                                      const nsACString& property,
2085                                                      bool oldValue,
2086                                                      bool newValue) {
2087   return NS_ERROR_NOT_IMPLEMENTED;
2088 }
2089 
OnItemUnicharPropertyChanged(nsIMsgFolder * item,const nsACString & property,const nsAString & oldValue,const nsAString & newValue)2090 NS_IMETHODIMP nsMessenger::OnItemUnicharPropertyChanged(
2091     nsIMsgFolder* item, const nsACString& property, const nsAString& oldValue,
2092     const nsAString& newValue) {
2093   return NS_ERROR_NOT_IMPLEMENTED;
2094 }
2095 
OnItemPropertyFlagChanged(nsIMsgDBHdr * item,const nsACString & property,uint32_t oldFlag,uint32_t newFlag)2096 NS_IMETHODIMP nsMessenger::OnItemPropertyFlagChanged(nsIMsgDBHdr* item,
2097                                                      const nsACString& property,
2098                                                      uint32_t oldFlag,
2099                                                      uint32_t newFlag) {
2100   return NS_ERROR_NOT_IMPLEMENTED;
2101 }
2102 
OnItemEvent(nsIMsgFolder * item,const nsACString & event)2103 NS_IMETHODIMP nsMessenger::OnItemEvent(nsIMsgFolder* item,
2104                                        const nsACString& event) {
2105   return NS_ERROR_NOT_IMPLEMENTED;
2106 }
2107 
2108 ///////////////////////////////////////////////////////////////////////////////
2109 // Detach/Delete Attachments
2110 ///////////////////////////////////////////////////////////////////////////////
2111 
GetAttachmentPartId(const char * aAttachmentUrl)2112 static const char* GetAttachmentPartId(const char* aAttachmentUrl) {
2113   static const char partIdPrefix[] = "part=";
2114   const char* partId = PL_strstr(aAttachmentUrl, partIdPrefix);
2115   return partId ? (partId + sizeof(partIdPrefix) - 1) : nullptr;
2116 }
2117 
CompareAttachmentPartId(const char * aAttachUrlLeft,const char * aAttachUrlRight)2118 static int CompareAttachmentPartId(const char* aAttachUrlLeft,
2119                                    const char* aAttachUrlRight) {
2120   // part ids are numbers separated by periods, like "1.2.3.4".
2121   // we sort by doing a numerical comparison on each item in turn. e.g. "1.4" <
2122   // "1.25" shorter entries come before longer entries. e.g. "1.4" < "1.4.1.2"
2123   // return values:
2124   //  -2  left is a parent of right
2125   //  -1  left is less than right
2126   //   0  left == right
2127   //   1  right is greater than left
2128   //   2  right is a parent of left
2129 
2130   const char* partIdLeft = GetAttachmentPartId(aAttachUrlLeft);
2131   const char* partIdRight = GetAttachmentPartId(aAttachUrlRight);
2132 
2133   // for detached attachments the URL does not contain any "part=xx"
2134   if (!partIdLeft) partIdLeft = "0";
2135 
2136   if (!partIdRight) partIdRight = "0";
2137 
2138   long idLeft, idRight;
2139   do {
2140     MOZ_ASSERT(partIdLeft && IS_DIGIT(*partIdLeft),
2141                "Invalid character in part id string");
2142     MOZ_ASSERT(partIdRight && IS_DIGIT(*partIdRight),
2143                "Invalid character in part id string");
2144 
2145     // if the part numbers are different then the numerically smaller one is
2146     // first
2147     char* fixConstLoss;
2148     idLeft = strtol(partIdLeft, &fixConstLoss, 10);
2149     partIdLeft = fixConstLoss;
2150     idRight = strtol(partIdRight, &fixConstLoss, 10);
2151     partIdRight = fixConstLoss;
2152     if (idLeft != idRight) return idLeft < idRight ? -1 : 1;
2153 
2154     // if one part id is complete but the other isn't, then the shortest one
2155     // is first (parents before children)
2156     if (*partIdLeft != *partIdRight) return *partIdRight ? -2 : 2;
2157 
2158     // if both part ids are complete (*partIdLeft == *partIdRight now) then
2159     // they are equal
2160     if (!*partIdLeft) return 0;
2161 
2162     MOZ_ASSERT(*partIdLeft == '.', "Invalid character in part id string");
2163     MOZ_ASSERT(*partIdRight == '.', "Invalid character in part id string");
2164 
2165     ++partIdLeft;
2166     ++partIdRight;
2167   } while (true);
2168 }
2169 
2170 // ------------------------------------
2171 
2172 // struct on purpose -> show that we don't ever want a vtable
2173 struct msgAttachment {
msgAttachmentmsgAttachment2174   msgAttachment(const nsACString& aContentType, const nsACString& aUrl,
2175                 const nsACString& aDisplayName, const nsACString& aMessageUri)
2176       : mContentType(aContentType),
2177         mUrl(aUrl),
2178         mDisplayName(aDisplayName),
2179         mMessageUri(aMessageUri) {}
2180 
2181   nsCString mContentType;
2182   nsCString mUrl;
2183   nsCString mDisplayName;
2184   nsCString mMessageUri;
2185 };
2186 
2187 // ------------------------------------
2188 
2189 class nsAttachmentState {
2190  public:
2191   nsAttachmentState();
2192   nsresult Init(const nsTArray<nsCString>& aContentTypeArray,
2193                 const nsTArray<nsCString>& aUrlArray,
2194                 const nsTArray<nsCString>& aDisplayNameArray,
2195                 const nsTArray<nsCString>& aMessageUriArray);
2196   nsresult PrepareForAttachmentDelete();
2197 
2198  private:
2199   static int CompareAttachmentsByPartId(const void* aLeft, const void* aRight);
2200 
2201  public:
2202   uint32_t mCurIndex;
2203   nsTArray<msgAttachment> mAttachmentArray;
2204 };
2205 
nsAttachmentState()2206 nsAttachmentState::nsAttachmentState() : mCurIndex(0) {}
2207 
Init(const nsTArray<nsCString> & aContentTypeArray,const nsTArray<nsCString> & aUrlArray,const nsTArray<nsCString> & aDisplayNameArray,const nsTArray<nsCString> & aMessageUriArray)2208 nsresult nsAttachmentState::Init(const nsTArray<nsCString>& aContentTypeArray,
2209                                  const nsTArray<nsCString>& aUrlArray,
2210                                  const nsTArray<nsCString>& aDisplayNameArray,
2211                                  const nsTArray<nsCString>& aMessageUriArray) {
2212   MOZ_ASSERT(aContentTypeArray.Length() > 0);
2213   MOZ_ASSERT(aContentTypeArray.Length() == aUrlArray.Length() &&
2214              aUrlArray.Length() == aDisplayNameArray.Length() &&
2215              aDisplayNameArray.Length() == aMessageUriArray.Length());
2216 
2217   uint32_t count = aContentTypeArray.Length();
2218   mCurIndex = 0;
2219   mAttachmentArray.Clear();
2220   mAttachmentArray.SetCapacity(count);
2221 
2222   for (uint32_t u = 0; u < count; ++u) {
2223     mAttachmentArray.AppendElement(
2224         msgAttachment(aContentTypeArray[u], aUrlArray[u], aDisplayNameArray[u],
2225                       aMessageUriArray[u]));
2226   }
2227 
2228   return NS_OK;
2229 }
2230 
PrepareForAttachmentDelete()2231 nsresult nsAttachmentState::PrepareForAttachmentDelete() {
2232   // this must be called before any processing
2233   if (mCurIndex != 0) return NS_ERROR_FAILURE;
2234 
2235   // this prepares the attachment list for use in deletion. In order to prepare,
2236   // we sort the attachments in numerical ascending order on their part id,
2237   // remove all duplicates and remove any subparts which will be removed
2238   // automatically by the removal of the parent.
2239   //
2240   // e.g. the attachment list processing (showing only part ids)
2241   // before: 1.11, 1.3, 1.2, 1.2.1.3, 1.4.1.2
2242   // sorted: 1.2, 1.2.1.3, 1.3, 1.4.1.2, 1.11
2243   // after:  1.2, 1.3, 1.4.1.2, 1.11
2244 
2245   // sort
2246   qsort(mAttachmentArray.Elements(), mAttachmentArray.Length(),
2247         sizeof(msgAttachment), CompareAttachmentsByPartId);
2248 
2249   // remove duplicates and sub-items
2250   int nCompare;
2251   for (uint32_t u = 1; u < mAttachmentArray.Length();) {
2252     nCompare = ::CompareAttachmentPartId(mAttachmentArray[u - 1].mUrl.get(),
2253                                          mAttachmentArray[u].mUrl.get());
2254     if (nCompare == 0 ||
2255         nCompare == -2)  // [u-1] is the same as or a parent of [u]
2256     {
2257       // shuffle the array down (and thus keeping the sorted order)
2258       mAttachmentArray.RemoveElementAt(u);
2259     } else {
2260       ++u;
2261     }
2262   }
2263 
2264   return NS_OK;
2265 }
2266 
2267 // Static compare callback for sorting.
CompareAttachmentsByPartId(const void * aLeft,const void * aRight)2268 int nsAttachmentState::CompareAttachmentsByPartId(const void* aLeft,
2269                                                   const void* aRight) {
2270   msgAttachment& attachLeft = *((msgAttachment*)aLeft);
2271   msgAttachment& attachRight = *((msgAttachment*)aRight);
2272   return ::CompareAttachmentPartId(attachLeft.mUrl.get(),
2273                                    attachRight.mUrl.get());
2274 }
2275 
2276 // ------------------------------------
2277 
2278 class nsDelAttachListener : public nsIStreamListener,
2279                             public nsIUrlListener,
2280                             public nsIMsgCopyServiceListener {
2281  public:
2282   NS_DECL_ISUPPORTS
2283   NS_DECL_NSISTREAMLISTENER
2284   NS_DECL_NSIREQUESTOBSERVER
2285   NS_DECL_NSIURLLISTENER
2286   NS_DECL_NSIMSGCOPYSERVICELISTENER
2287 
2288  public:
2289   nsDelAttachListener();
2290   nsresult StartProcessing(nsMessenger* aMessenger, nsIMsgWindow* aMsgWindow,
2291                            nsAttachmentState* aAttach, bool aSaveFirst);
2292   nsresult DeleteOriginalMessage();
2293   void SelectNewMessage();
2294 
2295  public:
2296   nsAttachmentState* mAttach;                // list of attachments to process
2297   bool mSaveFirst;                           // detach (true) or delete (false)
2298   nsCOMPtr<nsIFile> mMsgFile;                // temporary file (processed mail)
2299   nsCOMPtr<nsIOutputStream> mMsgFileStream;  // temporary file (processed mail)
2300   nsCOMPtr<nsIMsgMessageService> mMessageService;  // original message service
2301   nsCOMPtr<nsIMsgDBHdr> mOriginalMessage;          // original message header
2302   nsCOMPtr<nsIMsgFolder> mMessageFolder;           // original message folder
2303   nsCOMPtr<nsIMessenger> mMessenger;               // our messenger instance
2304   nsCOMPtr<nsIMsgWindow> mMsgWindow;               // our UI window
2305   nsMsgKey mOriginalMessageKey;                    // old message key
2306   nsMsgKey mNewMessageKey;                         // new message key
2307   uint32_t mOrigMsgFlags;
2308 
2309   enum {
2310     eStarting,
2311     eCopyingNewMsg,
2312     eUpdatingFolder,  // for IMAP
2313     eDeletingOldMessage,
2314     eSelectingNewMessage
2315   } m_state;
2316   // temp
2317   nsTArray<nsCString> mDetachedFileUris;
2318 
2319  private:
2320   virtual ~nsDelAttachListener();
2321 };
2322 
2323 //
2324 // nsISupports
2325 //
NS_IMPL_ISUPPORTS(nsDelAttachListener,nsIStreamListener,nsIRequestObserver,nsIUrlListener,nsIMsgCopyServiceListener)2326 NS_IMPL_ISUPPORTS(nsDelAttachListener, nsIStreamListener, nsIRequestObserver,
2327                   nsIUrlListener, nsIMsgCopyServiceListener)
2328 
2329 //
2330 // nsIRequestObserver
2331 //
2332 NS_IMETHODIMP
2333 nsDelAttachListener::OnStartRequest(nsIRequest* aRequest) {
2334   // called when we start processing the StreamMessage request.
2335   // This is called after OnStartRunningUrl().
2336   return NS_OK;
2337 }
2338 
2339 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult aStatusCode)2340 nsDelAttachListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
2341   // called when we have completed processing the StreamMessage request.
2342   // This is called before OnStopRunningUrl(). This means that we have now
2343   // received all data of the message and we have completed processing.
2344   // We now start to copy the processed message from the temporary file
2345   // back into the message store, replacing the original message.
2346 
2347   mMessageFolder->CopyDataDone();
2348   if (NS_FAILED(aStatusCode)) return aStatusCode;
2349 
2350   // copy the file back into the folder. Note: setting msgToReplace only copies
2351   // metadata, so we do the delete ourselves
2352   nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService;
2353   nsresult rv = this->QueryInterface(NS_GET_IID(nsIMsgCopyServiceListener),
2354                                      getter_AddRefs(listenerCopyService));
2355   NS_ENSURE_SUCCESS(rv, rv);
2356 
2357   mMsgFileStream->Close();
2358   mMsgFileStream = nullptr;
2359   mNewMessageKey = nsMsgKey_None;
2360   nsCOMPtr<nsIMsgCopyService> copyService =
2361       do_GetService(NS_MSGCOPYSERVICE_CONTRACTID);
2362   m_state = eCopyingNewMsg;
2363   // clone file because nsIFile on Windows caches the wrong file size.
2364   nsCOMPtr<nsIFile> clone;
2365   mMsgFile->Clone(getter_AddRefs(clone));
2366   if (copyService) {
2367     nsCString originalKeys;
2368     mOriginalMessage->GetStringProperty("keywords",
2369                                         getter_Copies(originalKeys));
2370     rv = copyService->CopyFileMessage(clone, mMessageFolder, mOriginalMessage,
2371                                       false, mOrigMsgFlags, originalKeys,
2372                                       listenerCopyService, mMsgWindow);
2373   }
2374   return rv;
2375 }
2376 
2377 //
2378 // nsIStreamListener
2379 //
2380 
2381 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsIInputStream * aInStream,uint64_t aSrcOffset,uint32_t aCount)2382 nsDelAttachListener::OnDataAvailable(nsIRequest* aRequest,
2383                                      nsIInputStream* aInStream,
2384                                      uint64_t aSrcOffset, uint32_t aCount) {
2385   if (!mMsgFileStream) return NS_ERROR_NULL_POINTER;
2386   return mMessageFolder->CopyDataToOutputStreamForAppend(aInStream, aCount,
2387                                                          mMsgFileStream);
2388 }
2389 
2390 //
2391 // nsIUrlListener
2392 //
2393 
2394 NS_IMETHODIMP
OnStartRunningUrl(nsIURI * aUrl)2395 nsDelAttachListener::OnStartRunningUrl(nsIURI* aUrl) {
2396   // called when we start processing the StreamMessage request. This is
2397   // called before OnStartRequest().
2398   return NS_OK;
2399 }
2400 
DeleteOriginalMessage()2401 nsresult nsDelAttachListener::DeleteOriginalMessage() {
2402   nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService;
2403   QueryInterface(NS_GET_IID(nsIMsgCopyServiceListener),
2404                  getter_AddRefs(listenerCopyService));
2405 
2406   RefPtr<nsIMsgDBHdr> doomed(mOriginalMessage);
2407   mOriginalMessage = nullptr;
2408   m_state = eDeletingOldMessage;
2409   return mMessageFolder->DeleteMessages({doomed},             // messages
2410                                         mMsgWindow,           // msgWindow
2411                                         true,                 // deleteStorage
2412                                         false,                // isMove
2413                                         listenerCopyService,  // listener
2414                                         false);               // allowUndo
2415 }
2416 
SelectNewMessage()2417 void nsDelAttachListener::SelectNewMessage() {
2418   nsCString displayUri;
2419   // all attachments refer to the same message
2420   const nsCString& messageUri(mAttach->mAttachmentArray[0].mMessageUri);
2421   mMessenger->GetLastDisplayedMessageUri(displayUri);
2422   if (displayUri.Equals(messageUri)) {
2423     mMessageFolder->GenerateMessageURI(mNewMessageKey, displayUri);
2424     if (!displayUri.IsEmpty() && mMsgWindow) {
2425       nsCOMPtr<nsIMsgWindowCommands> windowCommands;
2426       mMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
2427       if (windowCommands) windowCommands->SelectMessage(displayUri);
2428     }
2429   }
2430   mNewMessageKey = nsMsgKey_None;
2431 }
2432 
2433 NS_IMETHODIMP
OnStopRunningUrl(nsIURI * aUrl,nsresult aExitCode)2434 nsDelAttachListener::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
2435   nsresult rv = NS_OK;
2436   if (mOriginalMessage && m_state == eUpdatingFolder)
2437     rv = DeleteOriginalMessage();
2438 
2439   return rv;
2440 }
2441 
2442 //
2443 // nsIMsgCopyServiceListener
2444 //
2445 
2446 NS_IMETHODIMP
OnStartCopy(void)2447 nsDelAttachListener::OnStartCopy(void) {
2448   // never called?
2449   return NS_OK;
2450 }
2451 
2452 NS_IMETHODIMP
OnProgress(uint32_t aProgress,uint32_t aProgressMax)2453 nsDelAttachListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) {
2454   // never called?
2455   return NS_OK;
2456 }
2457 
2458 class CStringWriter final : public mozilla::JSONWriteFunc {
2459  public:
Write(const mozilla::Span<const char> & aStr)2460   void Write(const mozilla::Span<const char>& aStr) override {
2461     mBuf.Append(aStr);
2462   }
2463 
Get() const2464   const nsCString& Get() const { return mBuf; }
2465 
2466  private:
2467   nsCString mBuf;
2468 };
2469 
2470 NS_IMETHODIMP
SetMessageKey(nsMsgKey aKey)2471 nsDelAttachListener::SetMessageKey(nsMsgKey aKey) {
2472   // called during the copy of the modified message back into the message
2473   // store to notify us of the message key of the newly created message.
2474   mNewMessageKey = aKey;
2475 
2476   nsCString folderURI;
2477   nsresult rv = mMessageFolder->GetURI(folderURI);
2478   NS_ENSURE_SUCCESS(rv, rv);
2479 
2480   mozilla::JSONWriter data(mozilla::MakeUnique<CStringWriter>());
2481   data.Start();
2482   data.IntProperty("oldMessageKey", mOriginalMessageKey);
2483   data.IntProperty("newMessageKey", aKey);
2484   data.StringProperty("folderURI", folderURI);
2485   data.End();
2486 
2487   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
2488   if (obs) {
2489     obs->NotifyObservers(
2490         nullptr, "attachment-delete-msgkey-changed",
2491         NS_ConvertUTF8toUTF16(
2492             static_cast<CStringWriter*>(data.WriteFunc())->Get())
2493             .get());
2494   }
2495   return NS_OK;
2496 }
2497 
2498 NS_IMETHODIMP
GetMessageId(nsACString & aMessageId)2499 nsDelAttachListener::GetMessageId(nsACString& aMessageId) {
2500   // never called?
2501   return NS_ERROR_NOT_IMPLEMENTED;
2502 }
2503 
2504 NS_IMETHODIMP
OnStopCopy(nsresult aStatus)2505 nsDelAttachListener::OnStopCopy(nsresult aStatus) {
2506   if (NS_FAILED(aStatus)) return aStatus;
2507 
2508   // This is called via `CopyFileMessage()` and `DeleteMessages()`.
2509   // `m_state` tells us which callback it is.
2510   if (m_state == eDeletingOldMessage) {
2511     m_state = eSelectingNewMessage;
2512     if (mMsgWindow) SelectNewMessage();
2513     return NS_OK;
2514   }
2515 
2516   // For non-IMAP messages, the original is deleted here, for IMAP messages
2517   // that happens in `OnStopRunningUrl()` which isn't called for non-IMAP
2518   // messages.
2519   const nsACString& messageUri = mAttach->mAttachmentArray[0].mMessageUri;
2520   if (mOriginalMessage &&
2521       !Substring(messageUri, 0, 13).EqualsLiteral("imap-message:")) {
2522     return DeleteOriginalMessage();
2523   } else {
2524     // Arrange for the message to be deleted in the next `OnStopRunningUrl()`
2525     // call.
2526     m_state = eUpdatingFolder;
2527   }
2528 
2529   return NS_OK;
2530 }
2531 
2532 //
2533 // local methods
2534 //
2535 
nsDelAttachListener()2536 nsDelAttachListener::nsDelAttachListener() {
2537   mAttach = nullptr;
2538   mSaveFirst = false;
2539   mNewMessageKey = nsMsgKey_None;
2540   m_state = eStarting;
2541 }
2542 
~nsDelAttachListener()2543 nsDelAttachListener::~nsDelAttachListener() {
2544   if (mAttach) {
2545     delete mAttach;
2546   }
2547   if (mMsgFileStream) {
2548     mMsgFileStream->Close();
2549     mMsgFileStream = nullptr;
2550   }
2551   if (mMsgFile) {
2552     mMsgFile->Remove(false);
2553   }
2554 }
2555 
StartProcessing(nsMessenger * aMessenger,nsIMsgWindow * aMsgWindow,nsAttachmentState * aAttach,bool detaching)2556 nsresult nsDelAttachListener::StartProcessing(nsMessenger* aMessenger,
2557                                               nsIMsgWindow* aMsgWindow,
2558                                               nsAttachmentState* aAttach,
2559                                               bool detaching) {
2560   aMessenger->QueryInterface(NS_GET_IID(nsIMessenger),
2561                              getter_AddRefs(mMessenger));
2562   mMsgWindow = aMsgWindow;
2563   mAttach = aAttach;
2564 
2565   nsresult rv;
2566 
2567   // all attachments refer to the same message
2568   const nsCString& messageUri = mAttach->mAttachmentArray[0].mMessageUri;
2569 
2570   // get the message service, original message and folder for this message
2571   rv = GetMessageServiceFromURI(messageUri, getter_AddRefs(mMessageService));
2572   NS_ENSURE_SUCCESS(rv, rv);
2573   rv = mMessageService->MessageURIToMsgHdr(messageUri,
2574                                            getter_AddRefs(mOriginalMessage));
2575   NS_ENSURE_SUCCESS(rv, rv);
2576   rv = mOriginalMessage->GetMessageKey(&mOriginalMessageKey);
2577   NS_ENSURE_SUCCESS(rv, rv);
2578   rv = mOriginalMessage->GetFolder(getter_AddRefs(mMessageFolder));
2579   NS_ENSURE_SUCCESS(rv, rv);
2580   mOriginalMessage->GetFlags(&mOrigMsgFlags);
2581 
2582   // ensure that we can store and delete messages in this folder, if we
2583   // can't then we can't do attachment deleting
2584   bool canDelete = false;
2585   mMessageFolder->GetCanDeleteMessages(&canDelete);
2586   bool canFile = false;
2587   mMessageFolder->GetCanFileMessages(&canFile);
2588   if (!canDelete || !canFile) return NS_ERROR_FAILURE;
2589 
2590   // create an output stream on a temporary file. This stream will save the
2591   // modified message data to a file which we will later use to replace the
2592   // existing message. The file is removed in the destructor.
2593   rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "nsmail.tmp",
2594                                        getter_AddRefs(mMsgFile));
2595   NS_ENSURE_SUCCESS(rv, rv);
2596 
2597   // For temp file, we should use restrictive 00600 instead of
2598   // ATTACHMENT_PERMISSION
2599   rv = mMsgFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
2600   NS_ENSURE_SUCCESS(rv, rv);
2601 
2602   rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mMsgFileStream), mMsgFile,
2603                                       -1, ATTACHMENT_PERMISSION);
2604 
2605   // create the additional header for data conversion. This will tell the stream
2606   // converter which MIME emitter we want to use, and it will tell the MIME
2607   // emitter which attachments should be deleted.
2608   const char* partId;
2609   const char* nextField;
2610   nsAutoCString sHeader("attach&del=");
2611   nsAutoCString detachToHeader("&detachTo=");
2612   for (uint32_t u = 0; u < mAttach->mAttachmentArray.Length(); ++u) {
2613     if (u > 0) {
2614       sHeader.Append(',');
2615       if (detaching) detachToHeader.Append(',');
2616     }
2617     partId = GetAttachmentPartId(mAttach->mAttachmentArray[u].mUrl.get());
2618     if (partId) {
2619       nextField = PL_strchr(partId, '&');
2620       sHeader.Append(partId, nextField ? nextField - partId : -1);
2621     }
2622     if (detaching) detachToHeader.Append(mDetachedFileUris[u]);
2623   }
2624 
2625   if (detaching) sHeader.Append(detachToHeader);
2626   // stream this message to our listener converting it via the attachment mime
2627   // converter. The listener will just write the converted message straight to
2628   // disk.
2629   nsCOMPtr<nsISupports> listenerSupports;
2630   rv = this->QueryInterface(NS_GET_IID(nsISupports),
2631                             getter_AddRefs(listenerSupports));
2632   NS_ENSURE_SUCCESS(rv, rv);
2633   nsCOMPtr<nsIUrlListener> listenerUrlListener =
2634       do_QueryInterface(listenerSupports, &rv);
2635   NS_ENSURE_SUCCESS(rv, rv);
2636 
2637   nsCOMPtr<nsIURI> dummyNull;
2638   rv = mMessageService->StreamMessage(messageUri, listenerSupports, mMsgWindow,
2639                                       listenerUrlListener, true, sHeader, false,
2640                                       getter_AddRefs(dummyNull));
2641   NS_ENSURE_SUCCESS(rv, rv);
2642 
2643   return NS_OK;
2644 }
2645 
2646 // ------------------------------------
2647 
2648 NS_IMETHODIMP
DetachAttachment(const nsACString & aContentType,const nsACString & aURL,const nsACString & aDisplayName,const nsACString & aMessageUri,bool aSaveFirst,bool withoutWarning=false)2649 nsMessenger::DetachAttachment(const nsACString& aContentType,
2650                               const nsACString& aURL,
2651                               const nsACString& aDisplayName,
2652                               const nsACString& aMessageUri, bool aSaveFirst,
2653                               bool withoutWarning = false) {
2654   if (aSaveFirst)
2655     return SaveOneAttachment(aContentType, aURL, aDisplayName, aMessageUri,
2656                              true);
2657   AutoTArray<nsCString, 1> contentTypeArray = {
2658       PromiseFlatCString(aContentType)};
2659   AutoTArray<nsCString, 1> urlArray = {PromiseFlatCString(aURL)};
2660   AutoTArray<nsCString, 1> displayNameArray = {
2661       PromiseFlatCString(aDisplayName)};
2662   AutoTArray<nsCString, 1> messageUriArray = {PromiseFlatCString(aMessageUri)};
2663   return DetachAttachments(contentTypeArray, urlArray, displayNameArray,
2664                            messageUriArray, nullptr, withoutWarning);
2665 }
2666 
2667 NS_IMETHODIMP
DetachAllAttachments(const nsTArray<nsCString> & aContentTypeArray,const nsTArray<nsCString> & aUrlArray,const nsTArray<nsCString> & aDisplayNameArray,const nsTArray<nsCString> & aMessageUriArray,bool aSaveFirst,bool withoutWarning=false)2668 nsMessenger::DetachAllAttachments(const nsTArray<nsCString>& aContentTypeArray,
2669                                   const nsTArray<nsCString>& aUrlArray,
2670                                   const nsTArray<nsCString>& aDisplayNameArray,
2671                                   const nsTArray<nsCString>& aMessageUriArray,
2672                                   bool aSaveFirst,
2673                                   bool withoutWarning = false) {
2674   NS_ENSURE_ARG_MIN(aContentTypeArray.Length(), 1);
2675   MOZ_ASSERT(aContentTypeArray.Length() == aUrlArray.Length() &&
2676              aUrlArray.Length() == aDisplayNameArray.Length() &&
2677              aDisplayNameArray.Length() == aMessageUriArray.Length());
2678 
2679   if (aSaveFirst)
2680     return SaveAllAttachments(aContentTypeArray, aUrlArray, aDisplayNameArray,
2681                               aMessageUriArray, true);
2682   else
2683     return DetachAttachments(aContentTypeArray, aUrlArray, aDisplayNameArray,
2684                              aMessageUriArray, nullptr, withoutWarning);
2685 }
2686 
DetachAttachments(const nsTArray<nsCString> & aContentTypeArray,const nsTArray<nsCString> & aUrlArray,const nsTArray<nsCString> & aDisplayNameArray,const nsTArray<nsCString> & aMessageUriArray,nsTArray<nsCString> * saveFileUris,bool withoutWarning)2687 nsresult nsMessenger::DetachAttachments(
2688     const nsTArray<nsCString>& aContentTypeArray,
2689     const nsTArray<nsCString>& aUrlArray,
2690     const nsTArray<nsCString>& aDisplayNameArray,
2691     const nsTArray<nsCString>& aMessageUriArray,
2692     nsTArray<nsCString>* saveFileUris, bool withoutWarning) {
2693   // if withoutWarning no dialog for user
2694   if (!withoutWarning && NS_FAILED(PromptIfDeleteAttachments(
2695                              saveFileUris != nullptr, aDisplayNameArray)))
2696     return NS_OK;
2697 
2698   nsresult rv = NS_OK;
2699 
2700   // ensure that our arguments are valid
2701   //  char * partId;
2702   for (uint32_t u = 0; u < aContentTypeArray.Length(); ++u) {
2703     // ensure all of the message URI are the same, we cannot process
2704     // attachments from different messages
2705     if (u > 0 && aMessageUriArray[0] != aMessageUriArray[u]) {
2706       rv = NS_ERROR_INVALID_ARG;
2707       break;
2708     }
2709 
2710     // ensure that we don't have deleted messages in this list
2711     if (aContentTypeArray[u].EqualsLiteral(MIMETYPE_DELETED)) {
2712       rv = NS_ERROR_INVALID_ARG;
2713       break;
2714     }
2715 
2716     // for the moment we prevent any attachments other than root level
2717     // attachments being deleted (i.e. you can't delete attachments from a
2718     // email forwarded as an attachment). We do this by ensuring that the
2719     // part id only has a single period in it (e.g. "1.2").
2720     // TODO: support non-root level attachment delete
2721     //    partId = ::GetAttachmentPartId(aUrlArray[u]);
2722     //    if (!partId || PL_strchr(partId, '.') != PL_strrchr(partId, '.'))
2723     //    {
2724     //      rv = NS_ERROR_INVALID_ARG;
2725     //      break;
2726     //    }
2727   }
2728   if (NS_FAILED(rv)) {
2729     Alert("deleteAttachmentFailure");
2730     return rv;
2731   }
2732 
2733   // TODO: ensure that nothing else is processing this message uri at the same
2734   // time
2735 
2736   // TODO: if any of the selected attachments are messages that contain other
2737   // attachments we need to warn the user that all sub-attachments of those
2738   // messages will also be deleted. Best to display a list of them.
2739 
2740   // get the listener for running the url
2741   nsDelAttachListener* listener = new nsDelAttachListener;
2742   if (!listener) return NS_ERROR_OUT_OF_MEMORY;
2743   nsCOMPtr<nsISupports>
2744       listenerSupports;  // auto-delete of the listener with error
2745   listener->QueryInterface(NS_GET_IID(nsISupports),
2746                            getter_AddRefs(listenerSupports));
2747 
2748   if (saveFileUris) {
2749     listener->mDetachedFileUris = saveFileUris->Clone();
2750   }
2751   // create the attachments for use by the listener
2752   nsAttachmentState* attach = new nsAttachmentState;
2753   rv = attach->Init(aContentTypeArray, aUrlArray, aDisplayNameArray,
2754                     aMessageUriArray);
2755   if (NS_SUCCEEDED(rv)) rv = attach->PrepareForAttachmentDelete();
2756   if (NS_FAILED(rv)) {
2757     delete attach;
2758     return rv;
2759   }
2760 
2761   // initialize our listener with the attachments and details. The listener
2762   // takes ownership of 'attach' immediately irrespective of the return value
2763   // (error or not).
2764   return listener->StartProcessing(this, mMsgWindow, attach,
2765                                    saveFileUris != nullptr);
2766 }
2767 
PromptIfDeleteAttachments(bool aSaveFirst,const nsTArray<nsCString> & aDisplayNameArray)2768 nsresult nsMessenger::PromptIfDeleteAttachments(
2769     bool aSaveFirst, const nsTArray<nsCString>& aDisplayNameArray) {
2770   nsresult rv = NS_ERROR_FAILURE;
2771 
2772   nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
2773   if (!dialog) return rv;
2774 
2775   if (!mStringBundle) {
2776     rv = InitStringBundle();
2777     NS_ENSURE_SUCCESS(rv, rv);
2778   }
2779 
2780   // create the list of attachments we are removing
2781   nsString displayString;
2782   nsString attachmentList;
2783   for (uint32_t u = 0; u < aDisplayNameArray.Length(); ++u) {
2784     ConvertAndSanitizeFileName(aDisplayNameArray[u], displayString);
2785     attachmentList.Append(displayString);
2786     attachmentList.Append(char16_t('\n'));
2787   }
2788   AutoTArray<nsString, 1> formatStrings = {attachmentList};
2789 
2790   // format the message and display
2791   nsString promptMessage;
2792   const char* propertyName =
2793       aSaveFirst ? "detachAttachments" : "deleteAttachments";
2794   rv = mStringBundle->FormatStringFromName(propertyName, formatStrings,
2795                                            promptMessage);
2796   NS_ENSURE_SUCCESS(rv, rv);
2797 
2798   bool dialogResult = false;
2799   rv = dialog->Confirm(nullptr, promptMessage.get(), &dialogResult);
2800   NS_ENSURE_SUCCESS(rv, rv);
2801 
2802   return dialogResult ? NS_OK : NS_ERROR_FAILURE;
2803 }
2804