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