Back to index

lightning-sunbird  0.9+nobinonly
nsMessenger.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   philip zhao <philip.zhao@sun.com>
00024  *   Seth Spitzer <sspitzer@netscape.com>
00025  *   Brodie Thiesfield <brofield@jellycan.com>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either of the GNU General Public License Version 2 or later (the "GPL"),
00029  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 #include "prsystem.h"
00042 
00043 #include "nsMessenger.h"
00044 
00045 // xpcom
00046 #include "nsIComponentManager.h"
00047 #include "nsIServiceManager.h"
00048 #include "nsFileStream.h"
00049 #include "nsIStringStream.h"
00050 #include "nsEscape.h"
00051 #include "nsXPIDLString.h"
00052 #include "nsReadableUtils.h"
00053 #include "nsIFileSpec.h"
00054 #include "nsILocalFile.h"
00055 #include "nsISupportsObsolete.h"
00056 #include "nsSpecialSystemDirectory.h"
00057 #include "nsQuickSort.h"
00058 #if defined(XP_MAC) || defined(XP_MACOSX)
00059 #include "nsIAppleFileDecoder.h"
00060 #if defined(XP_MACOSX)
00061 #include "nsILocalFileMac.h"
00062 #include "MoreFilesX.h"
00063 #endif
00064 #endif
00065 #include "nsNativeCharsetUtils.h"
00066 
00067 // necko
00068 #include "nsMimeTypes.h"
00069 #include "nsIURL.h"
00070 #include "nsIPrompt.h"
00071 #include "nsIStreamListener.h"
00072 #include "nsIStreamConverterService.h"
00073 #include "nsNetUtil.h"
00074 #include "nsIFileURL.h"
00075 
00076 // rdf
00077 #include "nsIRDFCompositeDataSource.h"
00078 #include "nsIRDFResource.h"
00079 #include "nsIRDFService.h"
00080 #include "nsRDFCID.h"
00081 
00082 // gecko
00083 #include "nsLayoutCID.h"
00084 #include "nsIMarkupDocumentViewer.h"
00085 #include "nsIContentViewer.h" 
00086 
00087 // embedding
00088 #include "nsIWebBrowserPrint.h"
00089 
00090 /* for access to docshell */
00091 #include "nsIDOMWindowInternal.h"
00092 #include "nsIScriptGlobalObject.h"
00093 #include "nsIDocShell.h"
00094 #include "nsIDocShellLoadInfo.h"
00095 #include "nsIDocShellTreeItem.h"
00096 #include "nsIDocShellTreeNode.h"
00097 #include "nsIWebNavigation.h"
00098 
00099 // mail
00100 #include "nsIMsgMailNewsUrl.h"
00101 #include "nsMsgUtils.h"
00102 #include "nsMsgBaseCID.h"
00103 #include "nsIMsgAccountManager.h"
00104 #include "nsIMsgMailSession.h"
00105 #include "nsIMailboxUrl.h"
00106 #include "nsIMsgFolder.h"
00107 #include "nsMsgFolderFlags.h"
00108 #include "nsIMsgIncomingServer.h"
00109 
00110 #include "nsIMsgMessageService.h"
00111 
00112 #include "nsIMsgStatusFeedback.h"
00113 #include "nsMsgRDFUtils.h"
00114 
00115 #include "nsIMsgHdr.h"
00116 #include "nsIMimeMiscStatus.h"
00117 // compose
00118 #include "nsMsgCompCID.h"
00119 #include "nsMsgI18N.h"
00120 #include "nsNativeCharsetUtils.h"
00121 
00122 // draft/folders/sendlater/etc
00123 #include "nsIMsgCopyService.h"
00124 #include "nsIMsgCopyServiceListener.h"
00125 #include "nsIMsgSendLater.h" 
00126 #include "nsIMsgSendLaterListener.h"
00127 #include "nsIUrlListener.h"
00128 
00129 // undo
00130 #include "nsITransaction.h"
00131 #include "nsMsgTxn.h"
00132 
00133 // charset conversions
00134 #include "nsMsgMimeCID.h"
00135 #include "nsIMimeConverter.h"
00136 
00137 // Printing
00138 #include "nsMsgPrintEngine.h"
00139 
00140 // Save As
00141 #include "nsIFilePicker.h"
00142 #include "nsIStringBundle.h"
00143 #include "nsIPrefService.h"
00144 #include "nsIPrefBranch.h"
00145 #include "nsIPrefBranch2.h"
00146 #include "nsCExternalHandlerService.h"
00147 #include "nsIExternalProtocolService.h"
00148 #include "nsIMIMEService.h"
00149 #include "nsITransfer.h"
00150 
00151 #include "nsILinkHandler.h"                                                                              
00152 
00153 static NS_DEFINE_CID(kRDFServiceCID,      NS_RDFSERVICE_CID);
00154 static NS_DEFINE_CID(kMsgSendLaterCID, NS_MSGSENDLATER_CID); 
00155 
00156 #define FOUR_K 4096
00157 #define MESSENGER_SAVE_DIR_PREF_NAME "messenger.save.dir"
00158 #define MAILNEWS_ALLOW_PLUGINS_PREF_NAME "mailnews.message_display.allow.plugins"
00159 
00160 #define MIMETYPE_DELETED    "text/x-moz-deleted"
00161 
00162 //
00163 // Convert an nsString buffer to plain text...
00164 //
00165 #include "nsIParser.h"
00166 #include "nsParserCIID.h"
00167 #include "nsICharsetConverterManager.h"
00168 #include "nsIContentSink.h"
00169 #include "nsIHTMLToTextSink.h"
00170 
00171 static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
00172 
00173 static nsresult
00174 ConvertBufToPlainText(nsString &aConBuf)
00175 {
00176   if (aConBuf.IsEmpty())
00177     return NS_OK;
00178 
00179   nsresult rv;
00180   nsCOMPtr<nsIParser> parser = do_CreateInstance(kCParserCID, &rv);
00181   if (NS_SUCCEEDED(rv) && parser)
00182   {
00183     nsCOMPtr<nsIContentSink> sink;
00184 
00185     sink = do_CreateInstance(NS_PLAINTEXTSINK_CONTRACTID);
00186     NS_ENSURE_TRUE(sink, NS_ERROR_FAILURE);
00187 
00188     nsCOMPtr<nsIHTMLToTextSink> textSink(do_QueryInterface(sink));
00189     NS_ENSURE_TRUE(textSink, NS_ERROR_FAILURE);
00190 
00191     nsAutoString convertedText;
00192     textSink->Initialize(&convertedText, 0, 72);
00193 
00194     parser->SetContentSink(sink);
00195 
00196     parser->Parse(aConBuf, 0, NS_LITERAL_CSTRING("text/html"), PR_FALSE, PR_TRUE);
00197 
00198     //
00199     // Now if we get here, we need to get from ASCII text to 
00200     // UTF-8 format or there is a problem downstream...
00201     //
00202     if (NS_SUCCEEDED(rv))
00203     {
00204       aConBuf = convertedText;
00205     }
00206   }
00207 
00208   return rv;
00209 }
00210 
00211 nsresult ConvertAndSanitizeFileName(const char * displayName, PRUnichar ** unicodeResult, char ** result)
00212 {
00213   nsCAutoString unescapedName(displayName);
00214 
00215   /* we need to convert the UTF-8 fileName to platform specific character set.
00216      The display name is in UTF-8 because it has been escaped from JS
00217   */ 
00218   NS_UnescapeURL(unescapedName);
00219   NS_ConvertUTF8toUCS2 ucs2Str(unescapedName);
00220 
00221   nsresult rv = NS_OK;
00222 #if defined(XP_MAC)  /* reviewed for 1.4, XP_MACOSX not needed */
00223   /* We need to truncate the name to 31 characters, this even on MacOS X until the file API
00224      correctly support long file name. Using a nsILocalFile will do the trick...
00225   */
00226   nsCOMPtr<nsILocalFile> aLocalFile(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
00227   if (NS_SUCCEEDED(aLocalFile->SetLeafName(ucs2Str)))
00228   {
00229     aLocalFile->GetLeafName(ucs2Str);
00230   }
00231 #endif
00232 
00233   // replace platform specific path separator and illegale characters to avoid any confusion
00234   ucs2Str.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
00235 
00236   if (result) {
00237     nsCAutoString nativeStr;
00238     rv =  NS_CopyUnicodeToNative(ucs2Str, nativeStr);
00239     *result = ToNewCString(nativeStr);
00240   }
00241 
00242   if (unicodeResult)
00243     *unicodeResult = ToNewUnicode(ucs2Str);
00244 
00245  return rv;
00246 }
00247 
00248 // ***************************************************
00249 // jefft - this is a rather obscured class serves for Save Message As File,
00250 // Save Message As Template, and Save Attachment to a file
00251 // 
00252 class nsSaveAllAttachmentsState;
00253 
00254 class nsSaveMsgListener : public nsIUrlListener,
00255                           public nsIMsgCopyServiceListener,
00256                           public nsIStreamListener,
00257                           public nsICancelable
00258 {
00259 public:
00260     nsSaveMsgListener(nsIFileSpec* fileSpec, nsMessenger* aMessenger);
00261     virtual ~nsSaveMsgListener();
00262 
00263     NS_DECL_ISUPPORTS
00264 
00265     NS_DECL_NSIURLLISTENER
00266     NS_DECL_NSIMSGCOPYSERVICELISTENER
00267     NS_DECL_NSISTREAMLISTENER
00268     NS_DECL_NSIREQUESTOBSERVER
00269     NS_DECL_NSICANCELABLE
00270 
00271     nsCOMPtr<nsIFileSpec> m_fileSpec;
00272     nsCOMPtr<nsIOutputStream> m_outputStream;
00273     char *m_dataBuffer;
00274     nsCOMPtr<nsIChannel> m_channel;
00275     nsXPIDLCString m_templateUri;
00276     nsMessenger *m_messenger; // not ref counted
00277     nsSaveAllAttachmentsState *m_saveAllAttachmentsState;
00278 
00279     // rhp: For character set handling
00280     PRBool        m_doCharsetConversion;
00281     nsString      m_charset;
00282     enum {
00283            eUnknown,
00284            ePlainText,
00285            eHTML
00286     }             m_outputFormat;
00287     nsString      m_msgBuffer;
00288 
00289     nsCString     m_contentType;    // used only when saving attachment
00290 
00291     nsCOMPtr<nsITransfer> mTransfer;
00292     PRInt32 mProgress;
00293     PRInt32 mContentLength; 
00294     PRBool  mCanceled;
00295     PRBool  mInitialized;
00296     nsresult InitializeDownload(nsIRequest * aRequest, PRInt32 aBytesDownloaded);
00297 };
00298 
00299 class nsSaveAllAttachmentsState
00300 {
00301 public:
00302     nsSaveAllAttachmentsState(PRUint32 count,
00303                               const char **contentTypeArray,
00304                               const char **urlArray,
00305                               const char **displayNameArray,
00306                               const char **messageUriArray,
00307                               const char *directoryName,
00308                               PRBool detachingAttachments);
00309     virtual ~nsSaveAllAttachmentsState();
00310 
00311     PRUint32 m_count;
00312     PRUint32 m_curIndex;
00313     char* m_directoryName;
00314     char** m_contentTypeArray;
00315     char** m_urlArray;
00316     char** m_displayNameArray;
00317     char** m_messageUriArray;
00318     PRBool m_detachingAttachments;
00319     nsCStringArray m_savedFiles; // if detaching first, remember where we saved to.
00320 };
00321 
00322 //
00323 // nsMessenger
00324 //
00325 nsMessenger::nsMessenger() 
00326 {
00327   mScriptObject = nsnull;
00328   mMsgWindow = nsnull;
00329   mStringBundle = nsnull;
00330   mSendingUnsentMsgs = PR_FALSE;
00331   mCurHistoryPos = -2; // first message selected goes at position 0.
00332   //   InitializeFolderRoot();
00333 }
00334 
00335 nsMessenger::~nsMessenger()
00336 {
00337     // Release search context.
00338     mSearchContext = nsnull;
00339 }
00340 
00341 
00342 NS_IMPL_ISUPPORTS4(nsMessenger, nsIMessenger, nsIObserver, nsISupportsWeakReference, nsIFolderListener)
00343 NS_IMPL_GETSET(nsMessenger, SendingUnsentMsgs, PRBool, mSendingUnsentMsgs)
00344 
00345 NS_IMETHODIMP    
00346 nsMessenger::SetWindow(nsIDOMWindowInternal *aWin, nsIMsgWindow *aMsgWindow)
00347 {
00348   nsCOMPtr<nsIPrefBranch2> pbi = do_GetService(NS_PREFSERVICE_CONTRACTID);
00349 
00350   if(!aWin)
00351   {
00352     // it isn't an error to pass in null for aWin, in fact it means we are shutting
00353     // down and we should start cleaning things up...
00354     
00355     if (mMsgWindow)
00356     {
00357       nsCOMPtr<nsIMsgStatusFeedback> aStatusFeedback;
00358       
00359       mMsgWindow->GetStatusFeedback(getter_AddRefs(aStatusFeedback));
00360       if (aStatusFeedback)
00361         aStatusFeedback->SetDocShell(nsnull, nsnull);
00362       
00363       // Remove pref observer
00364       if (pbi)
00365         pbi->RemoveObserver(MAILNEWS_ALLOW_PLUGINS_PREF_NAME, this);
00366     }
00367     
00368     return NS_OK;
00369   }
00370   
00371   mMsgWindow = aMsgWindow;
00372   
00373   mWindow = aWin;
00374   
00375   nsCOMPtr<nsIScriptGlobalObject> globalObj( do_QueryInterface(aWin) );
00376   NS_ENSURE_TRUE(globalObj, NS_ERROR_FAILURE);
00377 
00378   nsIDocShell *docShell = globalObj->GetDocShell();
00379   nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(docShell));
00380   NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_FAILURE);
00381   
00382   nsCOMPtr<nsIDocShellTreeItem> rootDocShellAsItem;
00383   docShellAsItem->GetSameTypeRootTreeItem(getter_AddRefs(rootDocShellAsItem));
00384   
00385   nsCOMPtr<nsIDocShellTreeNode> rootDocShellAsNode(do_QueryInterface(rootDocShellAsItem));
00386 
00387   nsresult rv;
00388   nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
00389   NS_ENSURE_SUCCESS(rv,rv);
00390   rv = mailSession->AddFolderListener(this, nsIFolderListener::removed);
00391   
00392   if (rootDocShellAsNode) 
00393   {
00394     nsCOMPtr<nsIDocShellTreeItem> childAsItem;
00395     nsresult rv = rootDocShellAsNode->FindChildWithName(NS_LITERAL_STRING("messagepane").get(),
00396       PR_TRUE, PR_FALSE, nsnull, nsnull, getter_AddRefs(childAsItem));
00397     
00398     mDocShell = do_QueryInterface(childAsItem);
00399     
00400     if (NS_SUCCEEDED(rv) && mDocShell) {
00401       mCurrentDisplayCharset = ""; // Important! Clear out mCurrentDisplayCharset so we reset a default charset on mDocshell the next time we try to load something into it.
00402       
00403       if (aMsgWindow) 
00404       {
00405         nsCOMPtr<nsIMsgStatusFeedback> aStatusFeedback;
00406         
00407         aMsgWindow->GetStatusFeedback(getter_AddRefs(aStatusFeedback));
00408         if (aStatusFeedback)
00409           aStatusFeedback->SetDocShell(mDocShell, mWindow);
00410 
00411         aMsgWindow->GetTransactionManager(getter_AddRefs(mTxnMgr));
00412         
00413         // Add pref observer
00414         if (pbi)
00415           pbi->AddObserver(MAILNEWS_ALLOW_PLUGINS_PREF_NAME, this, PR_TRUE);
00416         SetDisplayProperties();
00417       }
00418     }
00419   }
00420   
00421   // we don't always have a message pane, like in the addressbook
00422   // so if we don't havea docshell, use the one for the xul window.
00423   // we do this so OpenURL() will work.
00424   if (!mDocShell)
00425     mDocShell = docShell;
00426   
00427   return NS_OK;
00428 }
00429 
00430 NS_IMETHODIMP nsMessenger::SetDisplayCharset(const char * aCharset)
00431 {
00432   // libmime always converts to UTF-8 (both HTML and XML)
00433   if (mDocShell) 
00434   {
00435     nsCOMPtr<nsIContentViewer> cv;
00436     mDocShell->GetContentViewer(getter_AddRefs(cv));
00437     if (cv) 
00438     {
00439       nsCOMPtr<nsIMarkupDocumentViewer> muDV = do_QueryInterface(cv);
00440       if (muDV) 
00441       {
00442         muDV->SetHintCharacterSet(nsDependentCString(aCharset));
00443         muDV->SetHintCharacterSetSource(9);
00444       }
00445 
00446       mCurrentDisplayCharset = aCharset;
00447     }
00448   }
00449 
00450   return NS_OK;
00451 }
00452 
00453 nsresult
00454 nsMessenger::SetDisplayProperties()
00455 {
00456   // For now, the only property we will set is allowPlugins but we might do more in the future...
00457 
00458   nsresult rv;
00459 
00460   if (!mDocShell)
00461     return NS_ERROR_FAILURE;
00462  
00463   PRBool allowPlugins = PR_FALSE;
00464 
00465   nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
00466   if (NS_SUCCEEDED(rv))
00467     prefBranch->GetBoolPref(MAILNEWS_ALLOW_PLUGINS_PREF_NAME, &allowPlugins);
00468   
00469   return mDocShell->SetAllowPlugins(allowPlugins);
00470 }
00471 
00472 NS_IMETHODIMP
00473 nsMessenger::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
00474 {
00475   if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID))
00476   {
00477     nsDependentString prefName(aData);
00478     if (prefName.EqualsLiteral(MAILNEWS_ALLOW_PLUGINS_PREF_NAME))
00479       SetDisplayProperties();
00480   }
00481 
00482   return NS_OK;
00483 }
00484 
00485 nsresult
00486 nsMessenger::PromptIfFileExists(nsFileSpec &fileSpec)
00487 {
00488     nsresult rv = NS_ERROR_FAILURE;
00489     if (fileSpec.Exists())
00490     {
00491         nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
00492         if (!dialog) return rv;
00493         nsAutoString path;
00494         PRBool dialogResult = PR_FALSE;
00495         nsXPIDLString errorMessage;
00496 
00497         NS_CopyNativeToUnicode(
00498             nsDependentCString(fileSpec.GetNativePathCString()), path);
00499         const PRUnichar *pathFormatStrings[] = { path.get() };
00500 
00501         if (!mStringBundle)
00502         {
00503             rv = InitStringBundle();
00504             if (NS_FAILED(rv)) return rv;
00505         }
00506         rv = mStringBundle->FormatStringFromName(NS_LITERAL_STRING("fileExists").get(),
00507                                                  pathFormatStrings, 1,
00508                                                  getter_Copies(errorMessage));
00509         if (NS_FAILED(rv)) return rv;
00510         rv = dialog->Confirm(nsnull, errorMessage, &dialogResult);
00511         if (NS_FAILED(rv)) return rv;
00512 
00513         if (dialogResult)
00514         {
00515             return NS_OK; // user says okay to replace
00516         }
00517         else
00518         {
00519             // if we don't re-init the path for redisplay the picker will
00520             // show the full path, not just the file name
00521             nsCOMPtr<nsILocalFile> currentFile = do_CreateInstance("@mozilla.org/file/local;1");
00522             if (!currentFile) return NS_ERROR_FAILURE;
00523 
00524             rv = currentFile->InitWithPath(path);
00525             if (NS_FAILED(rv)) return rv;
00526 
00527             nsAutoString leafName;
00528             currentFile->GetLeafName(leafName);
00529             if (!leafName.IsEmpty())
00530                 path.Assign(leafName); // path should be a copy of leafName
00531 
00532             nsCOMPtr<nsIFilePicker> filePicker =
00533                 do_CreateInstance("@mozilla.org/filepicker;1", &rv);
00534             if (NS_FAILED(rv)) return rv;
00535             filePicker->Init(mWindow,
00536                              GetString(NS_LITERAL_STRING("SaveAttachment")),
00537                              nsIFilePicker::modeSave);
00538             filePicker->SetDefaultString(path);
00539             filePicker->AppendFilters(nsIFilePicker::filterAll);
00540 
00541             nsCOMPtr <nsILocalFile> lastSaveDir;
00542             rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
00543             if (NS_SUCCEEDED(rv) && lastSaveDir) {
00544               filePicker->SetDisplayDirectory(lastSaveDir);
00545             }
00546 
00547             PRInt16 dialogReturn;
00548             rv = filePicker->Show(&dialogReturn);
00549             if (NS_FAILED(rv) || dialogReturn == nsIFilePicker::returnCancel) {
00550                 // XXX todo
00551                 // don't overload the return value like this
00552                 // change this function to have an out boolean
00553                 // that we check to see if the user cancelled
00554                 return NS_ERROR_FAILURE;
00555             }
00556 
00557             nsCOMPtr<nsILocalFile> localFile;
00558             nsCAutoString filePath;
00559 
00560             rv = filePicker->GetFile(getter_AddRefs(localFile));
00561             if (NS_FAILED(rv)) return rv;
00562 
00563             rv = SetLastSaveDirectory(localFile);
00564             NS_ENSURE_SUCCESS(rv,rv);
00565 
00566             rv = localFile->GetNativePath(filePath);
00567             if (NS_FAILED(rv)) return rv;
00568 
00569             fileSpec = filePath.get();
00570             return NS_OK;
00571         }
00572     }
00573     else
00574     {
00575         return NS_OK;
00576     }
00577     return rv;
00578 }
00579 
00580 void nsMessenger::AddMsgUrlToNavigateHistory(const char *aURL)
00581 {
00582   // mNavigatingToUri is set to a url if we're already doing a back/forward,
00583   // in which case we don't want to add the url to the history list.
00584   // Or if the entry at the cur history pos is the same as what we're loading, don't
00585   // add it to the list.
00586   if (!mNavigatingToUri.Equals(aURL) && (mCurHistoryPos < 0 || !mLoadedMsgHistory[mCurHistoryPos]->Equals(aURL)))
00587   {
00588     mNavigatingToUri = aURL;
00589     nsXPIDLCString curLoadedFolderUri;
00590     nsCOMPtr <nsIMsgFolder> curLoadedFolder;
00591     
00592     mMsgWindow->GetOpenFolder(getter_AddRefs(curLoadedFolder));
00593     // for virtual folders, we want to select the right folder,
00594     // which isn't the same as the folder specified in the msg uri.
00595     // So add the uri for the currently loaded folder to the history list.
00596     if (curLoadedFolder)
00597       curLoadedFolder->GetURI(getter_Copies(curLoadedFolderUri));
00598     
00599     mLoadedMsgHistory.InsertCStringAt(mNavigatingToUri, mCurHistoryPos++ + 2);
00600     mLoadedMsgHistory.InsertCStringAt(curLoadedFolderUri, mCurHistoryPos++ + 2);
00601     // we may want to prune this history if it gets large, but I think it's
00602     // more interesting to prune the back and forward menu.
00603   }
00604 }
00605 
00606 NS_IMETHODIMP
00607 nsMessenger::OpenURL(const char *aURL)
00608 {
00609   NS_ENSURE_ARG_POINTER(aURL);
00610 
00611   // This is to setup the display DocShell as UTF-8 capable...
00612   SetDisplayCharset("UTF-8");
00613 
00614   nsCOMPtr <nsIMsgMessageService> messageService;
00615   nsresult rv = GetMessageServiceFromURI(aURL, getter_AddRefs(messageService));
00616   
00617   if (NS_SUCCEEDED(rv) && messageService)
00618   {
00619     messageService->DisplayMessage(aURL, mDocShell, mMsgWindow, nsnull, nsnull, nsnull);
00620     AddMsgUrlToNavigateHistory(aURL);
00621     mLastDisplayURI = aURL; // remember the last uri we displayed....
00622     return NS_OK;
00623   }
00624 
00625   nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
00626   if(!webNav)
00627     return NS_ERROR_FAILURE;
00628   rv = webNav->LoadURI(NS_ConvertASCIItoUTF16(aURL).get(),   // URI string
00629                        nsIWebNavigation::LOAD_FLAGS_IS_LINK, // Load flags
00630                        nsnull,                               // Referring URI
00631                        nsnull,                               // Post stream
00632                        nsnull);                              // Extra headers
00633   return rv;
00634 }
00635 
00636 NS_IMETHODIMP nsMessenger::LaunchExternalURL(const char * aURL)
00637 {
00638   nsresult rv = NS_OK;
00639   
00640   nsCOMPtr<nsIURI> uri;
00641   rv = NS_NewURI(getter_AddRefs(uri), aURL);
00642   NS_ENSURE_SUCCESS(rv, rv);
00643 
00644   nsCOMPtr<nsIExternalProtocolService> extProtService = do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID, &rv);
00645   NS_ENSURE_SUCCESS(rv,rv);
00646   return extProtService->LoadUrl(uri); 
00647 }
00648 
00649 NS_IMETHODIMP
00650 nsMessenger::LoadURL(nsIDOMWindowInternal *aWin, const char *aURL)
00651 {
00652   NS_ENSURE_ARG_POINTER(aURL);
00653   
00654   nsresult rv;
00655 
00656   SetDisplayCharset("UTF-8");
00657   
00658   NS_ConvertASCIItoUTF16 uriString(aURL);
00659   // Cleanup the empty spaces that might be on each end.
00660   uriString.Trim(" ");
00661   // Eliminate embedded newlines, which single-line text fields now allow:
00662   uriString.StripChars("\r\n");
00663   NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE);
00664   
00665   PRBool loadingFromFile = PR_FALSE;
00666   PRBool getDummyMsgHdr = PR_FALSE;
00667   PRInt64 fileSize;
00668 
00669   if (StringBeginsWith(uriString, NS_LITERAL_STRING("file:")))
00670   {
00671 
00672     nsCOMPtr<nsIURI> fileUri;
00673     rv = NS_NewURI(getter_AddRefs(fileUri), uriString);
00674     NS_ENSURE_SUCCESS(rv, rv);
00675     nsCOMPtr <nsIFileURL> fileUrl = do_QueryInterface(fileUri, &rv);
00676     NS_ENSURE_SUCCESS(rv, rv);
00677     nsCOMPtr <nsIFile> file;
00678     rv = fileUrl->GetFile(getter_AddRefs(file));
00679     NS_ENSURE_SUCCESS(rv, rv);
00680     file->GetFileSize(&fileSize);
00681     uriString.ReplaceSubstring(NS_LITERAL_STRING("file:"), NS_LITERAL_STRING("mailbox:"));
00682     uriString.Append(NS_LITERAL_STRING("&number=0"));
00683     loadingFromFile = PR_TRUE;
00684     getDummyMsgHdr = PR_TRUE;
00685   }
00686   else if (FindInReadable(NS_LITERAL_STRING("type=application/x-message-display"), uriString))
00687     getDummyMsgHdr = PR_TRUE;
00688 
00689   nsCOMPtr<nsIURI> uri;
00690   rv = NS_NewURI(getter_AddRefs(uri), uriString);
00691   NS_ENSURE_SUCCESS(rv, rv);
00692 
00693   NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
00694   nsCOMPtr<nsIMsgMailNewsUrl> msgurl = do_QueryInterface(uri);
00695   if (msgurl)
00696   {
00697     msgurl->SetMsgWindow(mMsgWindow);
00698     if (loadingFromFile || getDummyMsgHdr)
00699     {
00700       if (loadingFromFile)
00701       {
00702         nsCOMPtr <nsIMailboxUrl> mailboxUrl = do_QueryInterface(msgurl, &rv);
00703         mailboxUrl->SetMessageSize((PRUint32) fileSize);
00704       }
00705       if (getDummyMsgHdr)
00706       {
00707         nsCOMPtr <nsIMsgHeaderSink> headerSink;
00708          // need to tell the header sink to capture some headers to create a fake db header
00709          // so we can do reply to a .eml file or a rfc822 msg attachment.
00710         mMsgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink));
00711         if (headerSink)
00712         {
00713           nsCOMPtr <nsIMsgDBHdr> dummyHeader;
00714           headerSink->GetDummyMsgHeader(getter_AddRefs(dummyHeader));
00715           if (dummyHeader && loadingFromFile)
00716             dummyHeader->SetMessageSize((PRUint32) fileSize);
00717         }
00718       }
00719     }
00720   }
00721   
00722   nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
00723   rv = mDocShell->CreateLoadInfo(getter_AddRefs(loadInfo));
00724   NS_ENSURE_SUCCESS(rv, rv);
00725   loadInfo->SetLoadType(nsIDocShellLoadInfo::loadNormal);
00726   AddMsgUrlToNavigateHistory(aURL);
00727   mNavigatingToUri.Truncate();
00728   return mDocShell->LoadURI(uri, loadInfo, 0, PR_TRUE);
00729 }
00730 
00731 nsresult
00732 nsMessenger::SaveAttachment(nsIFileSpec * fileSpec,
00733                             const char * url,
00734                             const char * messageUri,
00735                             const char * contentType,
00736                             void *closure)
00737 {
00738   nsIMsgMessageService * messageService = nsnull;
00739   nsSaveAllAttachmentsState *saveState= (nsSaveAllAttachmentsState*) closure;
00740   nsCOMPtr<nsIMsgMessageFetchPartService> fetchService;
00741   nsCAutoString urlString;
00742   nsCOMPtr<nsIURI> URL;
00743   nsCAutoString fullMessageUri(messageUri);
00744 
00745   // XXX todo
00746   // document the ownership model of saveListener
00747   // whacky ref counting here...what's the deal? when does saveListener get released? it's not clear.
00748   nsSaveMsgListener *saveListener = new nsSaveMsgListener(fileSpec, this);
00749   if (!saveListener)
00750     return NS_ERROR_OUT_OF_MEMORY;
00751 
00752   NS_ADDREF(saveListener);
00753 
00754   saveListener->m_contentType = contentType;
00755   if (saveState)
00756   {
00757     saveListener->m_saveAllAttachmentsState = saveState;
00758     if (saveState->m_detachingAttachments)
00759     {
00760 
00761       nsFileSpec realSpec;
00762       fileSpec->GetFileSpec(&realSpec);
00763 
00764       // Create nsILocalFile from a nsFileSpec.
00765       nsCOMPtr<nsILocalFile> outputFile;
00766       nsresult rv = NS_FileSpecToIFile(&realSpec, getter_AddRefs(outputFile)); 
00767       NS_ENSURE_SUCCESS(rv, rv);
00768 
00769       nsCOMPtr<nsIURI> outputURI;
00770       rv = NS_NewFileURI(getter_AddRefs(outputURI), outputFile);
00771       NS_ENSURE_SUCCESS(rv, rv);
00772       nsCAutoString fileUriSpec;
00773       outputURI->GetSpec(fileUriSpec);
00774       saveState->m_savedFiles.AppendCString(fileUriSpec);
00775     }
00776   }
00777 
00778   urlString = url;
00779   // strip out ?type=application/x-message-display because it confuses libmime
00780 
00781   PRInt32 typeIndex = urlString.Find("?type=application/x-message-display");
00782   if (typeIndex != kNotFound)
00783   {
00784     urlString.Cut(typeIndex, sizeof("?type=application/x-message-display") - 1);
00785     // we also need to replace the next '&' with '?'
00786     PRInt32 firstPartIndex = urlString.FindChar('&');
00787     if (firstPartIndex != kNotFound)
00788       urlString.SetCharAt('?', firstPartIndex);
00789   }
00790 
00791   urlString.ReplaceSubstring("/;section", "?section");
00792   nsresult rv = CreateStartupUrl(urlString.get(), getter_AddRefs(URL));
00793 
00794   if (NS_SUCCEEDED(rv))
00795   {
00796     rv = GetMessageServiceFromURI(messageUri, &messageService);
00797     if (NS_SUCCEEDED(rv))
00798     {
00799       fetchService = do_QueryInterface(messageService);
00800       // if the message service has a fetch part service then we know we can fetch mime parts...
00801       if (fetchService)
00802       {
00803         PRInt32 sectionPos = urlString.Find("?section");
00804         nsCString mimePart;
00805         urlString.Right(mimePart, urlString.Length() - sectionPos);
00806         fullMessageUri.Append(mimePart);
00807    
00808         messageUri = fullMessageUri.get();
00809       }
00810 
00811       nsCOMPtr<nsIStreamListener> convertedListener;
00812       saveListener->QueryInterface(NS_GET_IID(nsIStreamListener),
00813                                  getter_AddRefs(convertedListener));
00814 
00815 #if !defined(XP_MAC) && !defined(XP_MACOSX)
00816       // if the content type is bin hex we are going to do a hokey hack and make sure we decode the bin hex 
00817       // when saving an attachment to disk..
00818       if (contentType && !nsCRT::strcasecmp(APPLICATION_BINHEX, contentType))
00819       {
00820         nsCOMPtr<nsIStreamListener> listener (do_QueryInterface(convertedListener));
00821         nsCOMPtr<nsIStreamConverterService> streamConverterService = do_GetService("@mozilla.org/streamConverters;1", &rv);
00822         nsCOMPtr<nsISupports> channelSupport = do_QueryInterface(saveListener->m_channel);
00823           
00824         rv = streamConverterService->AsyncConvertData(APPLICATION_BINHEX,
00825                                                       "*/*", 
00826                                                       listener,
00827                                                       channelSupport,
00828                                                       getter_AddRefs(convertedListener));
00829       }
00830 #endif
00831       if (fetchService)
00832         rv = fetchService->FetchMimePart(URL, messageUri, convertedListener, mMsgWindow, nsnull,nsnull);
00833       else
00834         rv = messageService->DisplayMessage(messageUri, convertedListener, mMsgWindow, nsnull, nsnull, nsnull); 
00835     } // if we got a message service
00836   } // if we created a url
00837 
00838   if (NS_FAILED(rv))
00839   {
00840       NS_IF_RELEASE(saveListener);
00841       Alert("saveAttachmentFailed");
00842   }
00843   return rv;
00844 }
00845 
00846 NS_IMETHODIMP
00847 nsMessenger::OpenAttachment(const char * aContentType, const char * aURL, const
00848                             char * aDisplayName, const char * aMessageUri, PRBool aIsExternalAttachment)
00849 {
00850   nsresult rv = NS_OK;
00851 
00852   // open external attachments inside our message pane which in turn should trigger the
00853   // helper app dialog...
00854   if (aIsExternalAttachment)
00855     rv = OpenURL(aURL); 
00856   else
00857   {
00858     nsCOMPtr <nsIMsgMessageService> messageService;
00859     rv = GetMessageServiceFromURI(aMessageUri, getter_AddRefs(messageService));
00860     if (messageService)
00861       rv = messageService->OpenAttachment(aContentType, aDisplayName, aURL, aMessageUri, mDocShell, mMsgWindow, nsnull);
00862   }
00863 
00864   return rv;
00865 }
00866 
00867 NS_IMETHODIMP
00868 nsMessenger::SaveAttachmentToFolder(const char * contentType, const char * url, const char * displayName, 
00869                                     const char * messageUri, nsILocalFile * aDestFolder, nsILocalFile ** aOutFile)
00870 {
00871   NS_ENSURE_ARG_POINTER(aDestFolder);
00872   nsresult rv;
00873 
00874   nsCOMPtr<nsIFile> clone;
00875   rv = aDestFolder->Clone(getter_AddRefs(clone));
00876   NS_ENSURE_SUCCESS(rv, rv);
00877 
00878   nsCOMPtr<nsILocalFile> attachmentDestination = do_QueryInterface(clone);
00879 
00880   nsXPIDLCString unescapedFileName;
00881   rv = ConvertAndSanitizeFileName(displayName, nsnull, getter_Copies(unescapedFileName));
00882   NS_ENSURE_SUCCESS(rv, rv);
00883 
00884   rv = attachmentDestination->AppendNative(unescapedFileName);
00885   NS_ENSURE_SUCCESS(rv, rv);
00886 
00887   nsCOMPtr<nsIFileSpec> fileSpec;
00888   rv = NS_NewFileSpecFromIFile(attachmentDestination, getter_AddRefs(fileSpec));
00889   NS_ENSURE_SUCCESS(rv, rv);
00890 
00891   rv = SaveAttachment(fileSpec, url, messageUri, contentType, nsnull);
00892 
00893   attachmentDestination.swap(*aOutFile);
00894   return rv;
00895 }
00896 
00897 NS_IMETHODIMP
00898 nsMessenger::SaveAttachment(const char * contentType, const char * url,
00899                             const char * displayName, const char * messageUri, PRBool aIsExternalAttachment)
00900 {
00901   NS_ENSURE_ARG_POINTER(url);
00902 
00903   // open external attachments inside our message pane which in turn should trigger the
00904   // helper app dialog...
00905   if (aIsExternalAttachment)
00906     return OpenURL(url); 
00907 
00908   nsresult rv = NS_ERROR_OUT_OF_MEMORY;
00909   nsCOMPtr<nsIFilePicker> filePicker =
00910       do_CreateInstance("@mozilla.org/filepicker;1", &rv);
00911   NS_ENSURE_SUCCESS(rv, rv);
00912 
00913   PRInt16 dialogResult;
00914   nsCOMPtr<nsILocalFile> localFile;
00915   nsCOMPtr<nsILocalFile> lastSaveDir;
00916   nsCOMPtr<nsIFileSpec> fileSpec;
00917   nsXPIDLCString filePath;
00918 
00919   
00920   nsXPIDLString defaultDisplayString;
00921   rv = ConvertAndSanitizeFileName(displayName, getter_Copies(defaultDisplayString), nsnull);
00922   if (NS_FAILED(rv)) goto done;
00923 
00924   filePicker->Init(mWindow, GetString(NS_LITERAL_STRING("SaveAttachment")),
00925                    nsIFilePicker::modeSave);
00926   filePicker->SetDefaultString(defaultDisplayString);
00927   filePicker->AppendFilters(nsIFilePicker::filterAll);
00928   
00929   rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
00930   if (NS_SUCCEEDED(rv) && lastSaveDir) {
00931     filePicker->SetDisplayDirectory(lastSaveDir);
00932   }
00933 
00934   rv = filePicker->Show(&dialogResult);
00935   if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel)
00936     goto done;
00937 
00938   rv = filePicker->GetFile(getter_AddRefs(localFile));
00939   if (NS_FAILED(rv)) goto done;
00940   
00941   (void)SetLastSaveDirectory(localFile);
00942 
00943   rv = NS_NewFileSpecFromIFile(localFile, getter_AddRefs(fileSpec));
00944   if (NS_FAILED(rv)) goto done;
00945 
00946   rv = SaveAttachment(fileSpec, url, messageUri, contentType, nsnull);
00947 
00948 done:
00949   return rv;
00950 }
00951 
00952 
00953 NS_IMETHODIMP
00954 nsMessenger::SaveAllAttachments(PRUint32 count,
00955                                 const char **contentTypeArray,
00956                                 const char **urlArray,
00957                                 const char **displayNameArray,
00958                                 const char **messageUriArray)
00959 {
00960   if (!count)
00961     return NS_ERROR_INVALID_ARG;
00962   return SaveAllAttachments(count, contentTypeArray, urlArray, displayNameArray, messageUriArray, PR_FALSE);
00963 }
00964 
00965 nsresult 
00966 nsMessenger::SaveAllAttachments(PRUint32 count,
00967                                 const char **contentTypeArray,
00968                                 const char **urlArray,
00969                                 const char **displayNameArray,
00970                                 const char **messageUriArray,
00971                                 PRBool detaching)
00972 {
00973     nsresult rv = NS_ERROR_OUT_OF_MEMORY;
00974     nsCOMPtr<nsIFilePicker> filePicker =
00975         do_CreateInstance("@mozilla.org/filepicker;1", &rv);
00976     nsCOMPtr<nsILocalFile> localFile;
00977     nsCOMPtr<nsILocalFile> lastSaveDir;
00978     nsCOMPtr<nsIFileSpec> fileSpec;
00979     nsXPIDLCString dirName;
00980     nsSaveAllAttachmentsState *saveState = nsnull;
00981     PRInt16 dialogResult;
00982 
00983     if (NS_FAILED(rv)) goto done;
00984 
00985     filePicker->Init(mWindow,
00986                      GetString(NS_LITERAL_STRING("SaveAllAttachments")),
00987                      nsIFilePicker::modeGetFolder);
00988 
00989     rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
00990     if (NS_SUCCEEDED(rv) && lastSaveDir) {
00991       filePicker->SetDisplayDirectory(lastSaveDir);
00992     }
00993     
00994     rv = filePicker->Show(&dialogResult);
00995     if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel)
00996         goto done;
00997 
00998     rv = filePicker->GetFile(getter_AddRefs(localFile));
00999     if (NS_FAILED(rv)) goto done;
01000 
01001     rv = SetLastSaveDirectory(localFile);
01002     if (NS_FAILED(rv)) 
01003       goto done;
01004 
01005     rv = localFile->GetNativePath(dirName);
01006     if (NS_FAILED(rv)) goto done;
01007     rv = NS_NewFileSpec(getter_AddRefs(fileSpec));
01008     if (NS_FAILED(rv)) goto done;
01009 
01010     saveState = new nsSaveAllAttachmentsState(count,
01011                                               contentTypeArray,
01012                                               urlArray,
01013                                               displayNameArray,
01014                                               messageUriArray, 
01015                                               (const char*) dirName, detaching);
01016     {
01017         nsFileSpec aFileSpec((const char *) dirName);
01018 
01019         nsXPIDLCString unescapedName;
01020         rv = ConvertAndSanitizeFileName(displayNameArray[0], nsnull, getter_Copies(unescapedName));
01021         if (NS_FAILED(rv))
01022           goto done;
01023 
01024         aFileSpec += unescapedName.get();
01025         rv = PromptIfFileExists(aFileSpec);
01026         if (NS_FAILED(rv)) 
01027           return rv;
01028         fileSpec->SetFromFileSpec(aFileSpec);
01029         rv = SaveAttachment(fileSpec, urlArray[0], messageUriArray[0], 
01030                             contentTypeArray[0], (void *)saveState);
01031     }
01032 done:
01033 
01034     return rv;
01035 }
01036 
01037 enum MESSENGER_SAVEAS_FILE_TYPE 
01038 {
01039  EML_FILE_TYPE =  0,
01040  HTML_FILE_TYPE = 1,
01041  TEXT_FILE_TYPE = 2,
01042  ANY_FILE_TYPE =  3
01043 };
01044 #define HTML_FILE_EXTENSION ".htm" 
01045 #define HTML_FILE_EXTENSION2 ".html"
01046 #define TEXT_FILE_EXTENSION ".txt"
01047 #define EML_FILE_EXTENSION  ".eml"
01048 
01049 NS_IMETHODIMP
01050 nsMessenger::SaveAs(const char *aURI, PRBool aAsFile, nsIMsgIdentity *aIdentity, const PRUnichar *aMsgFilename)
01051 {
01052   NS_ENSURE_ARG_POINTER(aURI);
01053   
01054   nsCOMPtr<nsIMsgMessageService> messageService;
01055   nsCOMPtr<nsIUrlListener> urlListener;
01056   nsSaveMsgListener *saveListener = nsnull;
01057   nsCOMPtr<nsIURI> url;
01058   nsCOMPtr<nsIStreamListener> convertedListener;
01059   PRInt32 saveAsFileType = EML_FILE_TYPE;
01060   
01061   nsresult rv = GetMessageServiceFromURI(aURI, getter_AddRefs(messageService));
01062   if (NS_FAILED(rv)) 
01063     goto done;
01064   
01065   if (aAsFile) 
01066   {
01067     nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1", &rv);
01068     if (NS_FAILED(rv)) 
01069       goto done;
01070 
01071     filePicker->Init(mWindow, GetString(NS_LITERAL_STRING("SaveMailAs")),
01072                      nsIFilePicker::modeSave);
01073 
01074     // if we have a non-null filename use it, otherwise use default save message one
01075     if (aMsgFilename)    
01076       filePicker->SetDefaultString(nsDependentString(aMsgFilename));
01077     else {
01078       filePicker->SetDefaultString(GetString(NS_LITERAL_STRING("defaultSaveMessageAsFileName")));
01079     }
01080 
01081     // because we will be using GetFilterIndex()
01082     // we must call AppendFilters() one at a time, 
01083     // in MESSENGER_SAVEAS_FILE_TYPE order
01084     filePicker->AppendFilter(GetString(NS_LITERAL_STRING("EMLFiles")),
01085                              NS_LITERAL_STRING("*.eml"));
01086     filePicker->AppendFilters(nsIFilePicker::filterHTML);     
01087     filePicker->AppendFilters(nsIFilePicker::filterText);
01088     filePicker->AppendFilters(nsIFilePicker::filterAll);
01089     
01090     // by default, set the filter type as "all"
01091     // because the default string is "message.eml"
01092     // and type is "all", we will use the filename extension, and will save as rfc/822 (.eml)
01093     // but if the user just changes the name (to .html or .txt), we will save as those types
01094     // if the type is "all".
01095     // http://bugzilla.mozilla.org/show_bug.cgi?id=96134#c23
01096     filePicker->SetFilterIndex(ANY_FILE_TYPE);
01097 
01098     PRInt16 dialogResult;
01099     
01100     nsCOMPtr <nsILocalFile> lastSaveDir;
01101     rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
01102     if (NS_SUCCEEDED(rv) && lastSaveDir) 
01103     {
01104       filePicker->SetDisplayDirectory(lastSaveDir);
01105     }
01106     
01107     nsCOMPtr<nsILocalFile> localFile;
01108     nsAutoString fileName;
01109     rv = filePicker->Show(&dialogResult);
01110     if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel)
01111       goto done;
01112     
01113     rv = filePicker->GetFile(getter_AddRefs(localFile));
01114     if (NS_FAILED(rv)) 
01115       goto done;
01116 
01117     if (dialogResult == nsIFilePicker::returnReplace) {
01118       // be extra safe and only delete when the file is really a file
01119       PRBool isFile;
01120       rv = localFile->IsFile(&isFile);
01121       if (NS_SUCCEEDED(rv) && isFile) {
01122         rv = localFile->Remove(PR_FALSE /* recursive delete */);
01123         NS_ENSURE_SUCCESS(rv, rv);
01124       }
01125     }
01126     
01127     rv = SetLastSaveDirectory(localFile);
01128     if (NS_FAILED(rv)) 
01129       goto done;
01130     
01131     rv = filePicker->GetFilterIndex(&saveAsFileType);
01132     if (NS_FAILED(rv)) 
01133       goto done;
01134 
01135     rv = localFile->GetLeafName(fileName);
01136     NS_ENSURE_SUCCESS(rv, rv);
01137 
01138     switch ( saveAsFileType )
01139     {
01140       // Add the right extenstion based on filter index and build a new localFile.
01141       case HTML_FILE_TYPE:
01142           if ( (fileName.RFind(HTML_FILE_EXTENSION, PR_TRUE, -1, sizeof(HTML_FILE_EXTENSION)-1) == kNotFound) &&
01143                (fileName.RFind(HTML_FILE_EXTENSION2, PR_TRUE, -1, sizeof(HTML_FILE_EXTENSION2)-1) == kNotFound) ) {
01144            fileName.AppendLiteral(HTML_FILE_EXTENSION2);
01145            localFile->SetLeafName(fileName);
01146         }
01147         break;
01148       case TEXT_FILE_TYPE:
01149         if (fileName.RFind(TEXT_FILE_EXTENSION, PR_TRUE, -1, sizeof(TEXT_FILE_EXTENSION)-1) == kNotFound) {
01150          fileName.AppendLiteral(TEXT_FILE_EXTENSION);
01151          localFile->SetLeafName(fileName);
01152         }
01153         break;
01154       case EML_FILE_TYPE:
01155         if (fileName.RFind(EML_FILE_EXTENSION, PR_TRUE, -1, sizeof(EML_FILE_EXTENSION)-1) == kNotFound) {
01156          fileName.AppendLiteral(EML_FILE_EXTENSION);
01157          localFile->SetLeafName(fileName);
01158         }
01159         break;
01160       case ANY_FILE_TYPE:
01161       default:
01162         // If no extension found then default it to .eml. Otherwise, 
01163         // set the right file type based on the specified extension.
01164         PRBool noExtensionFound = PR_FALSE;
01165         if (fileName.RFind(".", 1) != kNotFound)
01166         {
01167           if ( (fileName.RFind(HTML_FILE_EXTENSION, PR_TRUE, -1, sizeof(HTML_FILE_EXTENSION)-1) != kNotFound) ||
01168                (fileName.RFind(HTML_FILE_EXTENSION2, PR_TRUE, -1, sizeof(HTML_FILE_EXTENSION2)-1) != kNotFound) )
01169             saveAsFileType = HTML_FILE_TYPE;
01170           else if (fileName.RFind(TEXT_FILE_EXTENSION, PR_TRUE, -1, sizeof(TEXT_FILE_EXTENSION)-1) != kNotFound)
01171             saveAsFileType = TEXT_FILE_TYPE;
01172           else if (fileName.RFind(EML_FILE_EXTENSION, PR_TRUE, -1, sizeof(EML_FILE_EXTENSION)-1) != kNotFound)
01173             saveAsFileType = EML_FILE_TYPE;
01174           else
01175             noExtensionFound = PR_TRUE;  
01176         } 
01177         else 
01178           noExtensionFound = PR_TRUE;
01179         // Set default file type here.
01180         if (noExtensionFound)
01181         {
01182           saveAsFileType = EML_FILE_TYPE; 
01183           fileName.AppendLiteral(EML_FILE_EXTENSION);
01184           localFile->SetLeafName(fileName);
01185         }
01186         break;
01187     }
01188    
01189     // XXX argh!  converting from nsILocalFile to nsFileSpec ... oh baby, lets drop from unicode to ascii too
01190     //        nsXPIDLString path;
01191     //        localFile->GetUnicodePath(getter_Copies(path));
01192     nsCOMPtr<nsIFileSpec> fileSpec;
01193     rv = NS_NewFileSpecFromIFile(localFile, getter_AddRefs(fileSpec));
01194     if (NS_FAILED(rv)) 
01195       goto done;
01196     
01197     // XXX todo
01198     // document the ownership model of saveListener
01199     saveListener = new nsSaveMsgListener(fileSpec, this);
01200     if (!saveListener) {
01201       rv = NS_ERROR_OUT_OF_MEMORY;
01202       goto done;
01203     }
01204     NS_ADDREF(saveListener);
01205     
01206     rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
01207     if (NS_FAILED(rv)) 
01208       goto done;
01209     
01210     if (saveAsFileType == EML_FILE_TYPE) 
01211     {
01212       rv = messageService->SaveMessageToDisk(aURI, fileSpec, PR_FALSE,
01213         urlListener, nsnull,
01214         PR_FALSE, mMsgWindow);
01215     }
01216     else
01217     {      
01218       nsCAutoString urlString(aURI);
01219       
01220       // we can't go RFC822 to TXT until bug #1775 is fixed
01221       // so until then, do the HTML to TXT conversion in
01222       // nsSaveMsgListener::OnStopRequest(), see ConvertBufToPlainText()
01223       //
01224       // Setup the URL for a "Save As..." Operation...
01225       // For now, if this is a save as TEXT operation, then do
01226       // a "printing" operation
01227       if (saveAsFileType == TEXT_FILE_TYPE) 
01228       {
01229         saveListener->m_outputFormat = nsSaveMsgListener::ePlainText;
01230         saveListener->m_doCharsetConversion = PR_TRUE;
01231         urlString.AppendLiteral("?header=print");  
01232       }
01233       else 
01234       {
01235         saveListener->m_outputFormat = nsSaveMsgListener::eHTML;
01236         saveListener->m_doCharsetConversion = PR_FALSE;
01237         urlString.AppendLiteral("?header=saveas");  
01238       }
01239       
01240       rv = CreateStartupUrl(urlString.get(), getter_AddRefs(url));
01241       NS_ASSERTION(NS_SUCCEEDED(rv), "CreateStartupUrl failed");
01242       if (NS_FAILED(rv)) 
01243         goto done;
01244       
01245       saveListener->m_channel = nsnull;
01246       rv = NS_NewInputStreamChannel(getter_AddRefs(saveListener->m_channel),
01247         url, 
01248         nsnull);                // inputStream
01249       NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewInputStreamChannel failed");
01250       if (NS_FAILED(rv)) 
01251         goto done;
01252       
01253       nsCOMPtr<nsIStreamConverterService> streamConverterService = do_GetService("@mozilla.org/streamConverters;1");
01254       nsCOMPtr<nsISupports> channelSupport = do_QueryInterface(saveListener->m_channel);
01255       
01256       // we can't go RFC822 to TXT until bug #1775 is fixed
01257       // so until then, do the HTML to TXT conversion in
01258       // nsSaveMsgListener::OnStopRequest(), see ConvertBufToPlainText()
01259       rv = streamConverterService->AsyncConvertData(MESSAGE_RFC822,
01260         TEXT_HTML,
01261         saveListener,
01262         channelSupport,
01263         getter_AddRefs(convertedListener));
01264       NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncConvertData failed");
01265       if (NS_FAILED(rv)) 
01266         goto done;
01267       
01268       rv = messageService->DisplayMessage(urlString.get(), convertedListener, mMsgWindow,
01269         nsnull, nsnull, nsnull);
01270     }
01271   }
01272   else
01273   {
01274     // ** save as Template
01275     nsCOMPtr<nsIFileSpec> fileSpec;
01276     nsFileSpec tmpFileSpec("nsmail.tmp");
01277     rv = NS_NewFileSpecWithSpec(tmpFileSpec, getter_AddRefs(fileSpec));
01278     if (NS_FAILED(rv)) goto done;
01279     
01280     // XXX todo
01281     // document the ownership model of saveListener
01282     saveListener = new nsSaveMsgListener(fileSpec, this);
01283     if (!saveListener) {
01284       rv = NS_ERROR_OUT_OF_MEMORY;
01285       goto done;
01286     }
01287     NS_ADDREF(saveListener);
01288     
01289     if (aIdentity)
01290       rv = aIdentity->GetStationeryFolder(getter_Copies(saveListener->m_templateUri));
01291     if (NS_FAILED(rv)) 
01292       goto done;
01293 
01294     PRBool needDummyHeader =
01295       PL_strcasestr(saveListener->m_templateUri, "mailbox://") 
01296       != nsnull;
01297     PRBool canonicalLineEnding =
01298       PL_strcasestr(saveListener->m_templateUri, "imap://")
01299       != nsnull;
01300 
01301     rv = saveListener->QueryInterface(
01302       NS_GET_IID(nsIUrlListener),
01303       getter_AddRefs(urlListener));
01304     if (NS_FAILED(rv)) 
01305       goto done;
01306     
01307     rv = messageService->SaveMessageToDisk(aURI, fileSpec, 
01308       needDummyHeader,
01309       urlListener, nsnull,
01310       canonicalLineEnding, mMsgWindow); 
01311   }
01312 
01313 done:
01314   if (NS_FAILED(rv)) 
01315   {
01316     // XXX todo
01317     // document the ownership model of saveListener
01318     NS_IF_RELEASE(saveListener);
01319     Alert("saveMessageFailed");
01320   }
01321   return rv;
01322 }
01323 
01324 nsresult
01325 nsMessenger::Alert(const char *stringName)
01326 {
01327     nsresult rv = NS_OK;
01328 
01329     if (mDocShell)
01330     {
01331         nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
01332 
01333         if (dialog) {
01334             rv = dialog->Alert(nsnull,
01335                                GetString(NS_ConvertASCIItoUCS2(stringName)).get());
01336         }
01337     }
01338     return rv;
01339 }
01340 
01341 nsresult
01342 nsMessenger::DoCommand(nsIRDFCompositeDataSource* db, const nsACString& command,
01343                        nsISupportsArray *srcArray, 
01344                        nsISupportsArray *argumentArray)
01345 {
01346 
01347        nsresult rv;
01348 
01349     nsCOMPtr<nsIRDFService> rdfService(do_GetService(kRDFServiceCID, &rv));
01350        if(NS_FAILED(rv))
01351               return rv;
01352 
01353        nsCOMPtr<nsIRDFResource> commandResource;
01354        rv = rdfService->GetResource(command, getter_AddRefs(commandResource));
01355        if(NS_SUCCEEDED(rv))
01356        {
01357               rv = db->DoCommand(srcArray, commandResource, argumentArray);
01358        }
01359 
01360        return rv;
01361 
01362 }
01363 
01364 NS_IMETHODIMP nsMessenger::DeleteFolders(nsIRDFCompositeDataSource *db,
01365                                          nsIRDFResource *parentResource,
01366                                          nsIRDFResource *deletedFolderResource)
01367 {
01368        nsresult rv;
01369 
01370        if(!db || !parentResource || !deletedFolderResource)
01371               return NS_ERROR_NULL_POINTER;
01372 
01373        nsCOMPtr<nsISupportsArray> parentArray, deletedArray;
01374 
01375        rv = NS_NewISupportsArray(getter_AddRefs(parentArray));
01376 
01377        if(NS_FAILED(rv))
01378        {
01379               return NS_ERROR_OUT_OF_MEMORY;
01380        }
01381 
01382        rv = NS_NewISupportsArray(getter_AddRefs(deletedArray));
01383 
01384        if(NS_FAILED(rv))
01385        {
01386               return NS_ERROR_OUT_OF_MEMORY;
01387        }
01388 
01389        parentArray->AppendElement(parentResource);
01390        deletedArray->AppendElement(deletedFolderResource);
01391         deletedArray->AppendElement(mMsgWindow);
01392 
01393        rv = DoCommand(db, NS_LITERAL_CSTRING(NC_RDF_DELETE), parentArray, deletedArray);
01394 
01395        return NS_OK;
01396 }
01397 
01398 NS_IMETHODIMP
01399 nsMessenger::CopyMessages(nsIRDFCompositeDataSource *database,
01400                           nsIRDFResource *srcResource, // folder
01401                                                         nsIRDFResource *dstResource,
01402                           nsISupportsArray *argumentArray, // nsIMessages
01403                           PRBool isMove)
01404 {
01405        nsresult rv;
01406 
01407   NS_ENSURE_ARG_POINTER(srcResource);
01408   NS_ENSURE_ARG_POINTER(dstResource);
01409   NS_ENSURE_ARG_POINTER(argumentArray);
01410        
01411   nsCOMPtr<nsIMsgFolder> srcFolder;
01412   nsCOMPtr<nsISupportsArray> folderArray;
01413     
01414   srcFolder = do_QueryInterface(srcResource);
01415   if(!srcFolder)
01416     return NS_ERROR_NO_INTERFACE;
01417   
01418   nsCOMPtr<nsISupports> srcFolderSupports(do_QueryInterface(srcFolder));
01419   if(srcFolderSupports)
01420     argumentArray->InsertElementAt(srcFolderSupports, 0);
01421 
01422   rv = NS_NewISupportsArray(getter_AddRefs(folderArray));
01423   NS_ENSURE_SUCCESS(rv, rv);
01424   
01425   folderArray->AppendElement(dstResource);
01426   if (isMove)
01427     rv = DoCommand(database, NS_LITERAL_CSTRING(NC_RDF_MOVE), folderArray, argumentArray);
01428   else
01429     rv = DoCommand(database, NS_LITERAL_CSTRING(NC_RDF_COPY), folderArray, argumentArray);
01430   return rv;
01431 
01432 }
01433 
01434 NS_IMETHODIMP
01435 nsMessenger::MessageServiceFromURI(const char *aUri, nsIMsgMessageService **aMsgService)
01436 {
01437    NS_ENSURE_ARG_POINTER(aUri);
01438    NS_ENSURE_ARG_POINTER(aMsgService);
01439  
01440    return GetMessageServiceFromURI(aUri, aMsgService);
01441 }
01442 
01443 NS_IMETHODIMP
01444 nsMessenger::MsgHdrFromURI(const char *aUri, nsIMsgDBHdr **aMsgHdr)
01445 {
01446   NS_ENSURE_ARG_POINTER(aUri);
01447   NS_ENSURE_ARG_POINTER(aMsgHdr);
01448   nsCOMPtr <nsIMsgMessageService> msgService;
01449   nsresult rv;
01450  
01451   if (mMsgWindow && (!strncmp(aUri, "file:", 5) || PL_strstr(aUri, "type=application/x-message-display")))
01452   {
01453     nsCOMPtr <nsIMsgHeaderSink> headerSink;
01454     mMsgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink));
01455     if (headerSink)
01456     {
01457       rv = headerSink->GetDummyMsgHeader(aMsgHdr);
01458       // Is there a way to check if they're asking for the hdr currently 
01459       // displayed in a stand-alone msg window from a .eml file?
01460       // (pretty likely if this is a file: uri)
01461       return rv;
01462     }
01463   }
01464 
01465   rv = GetMessageServiceFromURI(aUri, getter_AddRefs(msgService));
01466   NS_ENSURE_SUCCESS(rv, rv);
01467   return msgService->MessageURIToMsgHdr(aUri, aMsgHdr);
01468 }
01469 
01470 NS_IMETHODIMP
01471 nsMessenger::CopyFolders(nsIRDFCompositeDataSource *database,
01472                           nsIRDFResource *dstResource,
01473                           nsISupportsArray *argumentArray, // nsIFolders
01474                           PRBool isMoveFolder)
01475 {
01476        nsresult rv;
01477 
01478        if(!dstResource || !argumentArray)
01479               return NS_ERROR_NULL_POINTER;
01480 
01481        nsCOMPtr<nsISupportsArray> folderArray;
01482     
01483        rv = NS_NewISupportsArray(getter_AddRefs(folderArray));
01484 
01485        NS_ENSURE_SUCCESS(rv,rv);
01486 
01487        folderArray->AppendElement(dstResource);
01488        
01489   if (isMoveFolder)
01490     return DoCommand(database, NS_LITERAL_CSTRING(NC_RDF_MOVEFOLDER), folderArray, argumentArray);
01491 
01492   return DoCommand(database, NS_LITERAL_CSTRING(NC_RDF_COPYFOLDER), folderArray, argumentArray);
01493        
01494 }
01495 
01496 NS_IMETHODIMP
01497 nsMessenger::RenameFolder(nsIRDFCompositeDataSource* db,
01498                           nsIRDFResource* folderResource,
01499                           const PRUnichar* name)
01500 {
01501   nsresult rv = NS_ERROR_NULL_POINTER;
01502   if (!db || !folderResource || !name || !*name) return rv;
01503     nsCOMPtr<nsISupportsArray> folderArray;
01504     nsCOMPtr<nsISupportsArray> argsArray;
01505 
01506   rv = NS_NewISupportsArray(getter_AddRefs(folderArray));
01507   if (NS_FAILED(rv)) return rv;
01508   folderArray->AppendElement(folderResource);
01509   rv = NS_NewISupportsArray(getter_AddRefs(argsArray));
01510   if (NS_FAILED(rv)) return rv;
01511   nsCOMPtr<nsIRDFService> rdfService(do_GetService(kRDFServiceCID, &rv));
01512   if(NS_SUCCEEDED(rv))
01513   {
01514     nsCOMPtr<nsIRDFLiteral> nameLiteral;
01515 
01516     rdfService->GetLiteral(name, getter_AddRefs(nameLiteral));
01517     argsArray->AppendElement(nameLiteral);
01518     rv = DoCommand(db, NS_LITERAL_CSTRING(NC_RDF_RENAME), folderArray, argsArray);
01519   }
01520   return rv;
01521 }
01522 
01523 NS_IMETHODIMP
01524 nsMessenger::CompactFolder(nsIRDFCompositeDataSource* db,
01525                            nsIRDFResource* folderResource, PRBool forAll)
01526 {
01527   nsresult rv = NS_ERROR_NULL_POINTER;
01528   
01529   if (!db || !folderResource) return rv;
01530   nsCOMPtr<nsISupportsArray> folderArray;
01531 
01532   rv = NS_NewISupportsArray(getter_AddRefs(folderArray));
01533   if (NS_FAILED(rv)) return rv;
01534   folderArray->AppendElement(folderResource);
01535   if (forAll)
01536     rv = DoCommand(db, NS_LITERAL_CSTRING(NC_RDF_COMPACTALL),  folderArray, nsnull);
01537   else
01538     rv = DoCommand(db, NS_LITERAL_CSTRING(NC_RDF_COMPACT),  folderArray, nsnull);
01539   if (NS_SUCCEEDED(rv) && mTxnMgr)
01540       mTxnMgr->Clear();
01541   return rv;
01542 }
01543 
01544 NS_IMETHODIMP
01545 nsMessenger::EmptyTrash(nsIRDFCompositeDataSource* db,
01546                         nsIRDFResource* folderResource)
01547 {
01548   nsresult rv = NS_ERROR_NULL_POINTER;
01549   
01550   if (!db || !folderResource) return rv;
01551   nsCOMPtr<nsISupportsArray> folderArray;
01552 
01553   rv = NS_NewISupportsArray(getter_AddRefs(folderArray));
01554   if (NS_FAILED(rv)) return rv;
01555   folderArray->AppendElement(folderResource);
01556   rv = DoCommand(db, NS_LITERAL_CSTRING(NC_RDF_EMPTYTRASH), folderArray, nsnull);
01557   if (NS_SUCCEEDED(rv) && mTxnMgr)
01558       mTxnMgr->Clear();
01559   return rv;
01560 }
01561 
01562 NS_IMETHODIMP nsMessenger::GetUndoTransactionType(PRUint32 *txnType)
01563 {
01564     nsresult rv = NS_ERROR_NULL_POINTER;
01565     if (!txnType || !mTxnMgr)
01566         return rv;
01567     *txnType = nsMessenger::eUnknown;
01568     nsCOMPtr<nsITransaction> txn;
01569     rv = mTxnMgr->PeekUndoStack(getter_AddRefs(txn));
01570     if (NS_SUCCEEDED(rv) && txn)
01571     {
01572       nsCOMPtr <nsIPropertyBag2> propertyBag = do_QueryInterface(txn, &rv);
01573       NS_ENSURE_SUCCESS(rv, rv);
01574       return propertyBag->GetPropertyAsUint32(NS_LITERAL_STRING("type"), txnType);
01575     }
01576     return rv;
01577 }
01578 
01579 NS_IMETHODIMP nsMessenger::CanUndo(PRBool *bValue)
01580 {
01581     nsresult rv = NS_ERROR_NULL_POINTER;
01582     if (!bValue || !mTxnMgr)
01583         return rv;
01584     *bValue = PR_FALSE;
01585     PRInt32 count = 0;
01586     rv = mTxnMgr->GetNumberOfUndoItems(&count);
01587     if (NS_SUCCEEDED(rv) && count > 0)
01588         *bValue = PR_TRUE;
01589     return rv;
01590 }
01591 
01592 NS_IMETHODIMP nsMessenger::GetRedoTransactionType(PRUint32 *txnType)
01593 {
01594     nsresult rv = NS_ERROR_NULL_POINTER;
01595     if (!txnType || !mTxnMgr)
01596         return rv;
01597     *txnType = nsMessenger::eUnknown;
01598     nsCOMPtr<nsITransaction> txn;
01599     rv = mTxnMgr->PeekRedoStack(getter_AddRefs(txn));
01600     if (NS_SUCCEEDED(rv) && txn)
01601     {
01602       nsCOMPtr <nsIPropertyBag2> propertyBag = do_QueryInterface(txn, &rv);
01603       NS_ENSURE_SUCCESS(rv, rv);
01604       return propertyBag->GetPropertyAsUint32(NS_LITERAL_STRING("type"), txnType);
01605     }
01606     return rv;
01607 }
01608 
01609 NS_IMETHODIMP nsMessenger::CanRedo(PRBool *bValue)
01610 {
01611     nsresult rv = NS_ERROR_NULL_POINTER;
01612     if (!bValue || !mTxnMgr)
01613         return rv;
01614     *bValue = PR_FALSE;
01615     PRInt32 count = 0;
01616     rv = mTxnMgr->GetNumberOfRedoItems(&count);
01617     if (NS_SUCCEEDED(rv) && count > 0)
01618         *bValue = PR_TRUE;
01619     return rv;
01620 }
01621 
01622 NS_IMETHODIMP
01623 nsMessenger::Undo(nsIMsgWindow *msgWindow)
01624 {
01625   nsresult rv = NS_OK;
01626   if (mTxnMgr)
01627   {
01628     PRInt32 numTxn = 0;
01629     rv = mTxnMgr->GetNumberOfUndoItems(&numTxn);
01630     if (NS_SUCCEEDED(rv) && numTxn > 0)
01631     {
01632         nsCOMPtr<nsITransaction> txn;
01633         rv = mTxnMgr->PeekUndoStack(getter_AddRefs(txn));
01634         if (NS_SUCCEEDED(rv) && txn)
01635         {
01636             NS_STATIC_CAST(nsMsgTxn*, NS_STATIC_CAST(nsITransaction*, txn.get()))->SetMsgWindow(msgWindow);
01637         }
01638         mTxnMgr->UndoTransaction();
01639     }
01640   }
01641   return rv;
01642 }
01643 
01644 NS_IMETHODIMP
01645 nsMessenger::Redo(nsIMsgWindow *msgWindow)
01646 {
01647   nsresult rv = NS_OK;
01648   if (mTxnMgr)
01649   {
01650     PRInt32 numTxn = 0;
01651     rv = mTxnMgr->GetNumberOfRedoItems(&numTxn);
01652     if (NS_SUCCEEDED(rv) && numTxn > 0)
01653     {
01654         nsCOMPtr<nsITransaction> txn;
01655         rv = mTxnMgr->PeekRedoStack(getter_AddRefs(txn));
01656         if (NS_SUCCEEDED(rv) && txn)
01657         {
01658             NS_STATIC_CAST(nsMsgTxn*, NS_STATIC_CAST(nsITransaction*, txn.get()))->SetMsgWindow(msgWindow);
01659         }
01660         mTxnMgr->RedoTransaction();
01661     }
01662   }
01663   return rv;
01664 }
01665 
01666 NS_IMETHODIMP
01667 nsMessenger::GetTransactionManager(nsITransactionManager* *aTxnMgr)
01668 {
01669   if (!mTxnMgr || !aTxnMgr)
01670     return NS_ERROR_NULL_POINTER;
01671 
01672   *aTxnMgr = mTxnMgr;
01673   NS_ADDREF(*aTxnMgr);
01674 
01675   return NS_OK;
01676 }
01677 
01678 NS_IMETHODIMP nsMessenger::SetDocumentCharset(const char *characterSet)
01679 {
01680        // We want to redisplay the currently selected message (if any) but forcing the 
01681   // redisplay to use characterSet
01682   if (!mLastDisplayURI.IsEmpty())
01683   {
01684     SetDisplayCharset("UTF-8");
01685 
01686     nsCOMPtr <nsIMsgMessageService> messageService;
01687     nsresult rv = GetMessageServiceFromURI(mLastDisplayURI.get(), getter_AddRefs(messageService));
01688     
01689     if (NS_SUCCEEDED(rv) && messageService)
01690     {
01691       messageService->DisplayMessage(mLastDisplayURI.get(), mDocShell, mMsgWindow, nsnull, characterSet, nsnull);
01692     }
01693   }
01694   
01695   return NS_OK;
01696 }
01697 
01698 NS_IMETHODIMP
01699 nsMessenger::GetLastDisplayedMessageUri(char ** aLastDisplayedMessageUri)
01700 {
01701   if (!aLastDisplayedMessageUri)
01702     return NS_ERROR_NULL_POINTER;
01703   *aLastDisplayedMessageUri = NS_STATIC_CAST(char*, 
01704     nsMemory::Clone(mLastDisplayURI.get(), mLastDisplayURI.Length()+1));
01705   if (!*aLastDisplayedMessageUri)
01706     return NS_ERROR_OUT_OF_MEMORY;
01707   return NS_OK;
01708 }
01709 
01711 // This is the listener class for the send operation. 
01713 class SendLaterListener: public nsIMsgSendLaterListener
01714 {
01715 public:
01716   SendLaterListener(nsIMessenger *);
01717   virtual ~SendLaterListener(void);
01718 
01719   // nsISupports interface
01720   NS_DECL_ISUPPORTS
01721 
01722   /* void OnStartSending (in PRUint32 aTotalMessageCount); */
01723   NS_IMETHOD OnStartSending(PRUint32 aTotalMessageCount);
01724 
01725   /* void OnProgress (in PRUint32 aCurrentMessage, in PRUint32 aTotalMessage); */
01726   NS_IMETHOD OnProgress(PRUint32 aCurrentMessage, PRUint32 aTotalMessage);
01727 
01728   /* void OnStatus (in wstring aMsg); */
01729   NS_IMETHOD OnStatus(const PRUnichar *aMsg);
01730 
01731   /* void OnStopSending (in nsresult aStatus, in wstring aMsg, in PRUint32 aTotalTried, in PRUint32 aSuccessful); */
01732   NS_IMETHOD OnStopSending(nsresult aStatus, const PRUnichar *aMsg, PRUint32 aTotalTried, PRUint32 aSuccessful);
01733 protected:
01734   nsWeakPtr m_messenger; 
01735 };
01736 
01737 NS_IMPL_ISUPPORTS1(SendLaterListener, nsIMsgSendLaterListener)
01738 
01739 SendLaterListener::SendLaterListener(nsIMessenger *aMessenger)
01740 {
01741   m_messenger = do_GetWeakReference(aMessenger);
01742 }
01743 
01744 SendLaterListener::~SendLaterListener()
01745 {
01746   nsCOMPtr <nsIMessenger> messenger = do_QueryReferent(m_messenger);
01747   // best to be defensive about this, in case OnStopSending doesn't get called.
01748   if (messenger)
01749     messenger->SetSendingUnsentMsgs(PR_FALSE);
01750   m_messenger = nsnull;
01751 }
01752 
01753 nsresult
01754 SendLaterListener::OnStartSending(PRUint32 aTotalMessageCount)
01755 {
01756   // this never gets called :-(
01757   nsCOMPtr <nsIMessenger> messenger = do_QueryReferent(m_messenger);
01758   if (messenger)
01759     messenger->SetSendingUnsentMsgs(PR_TRUE);
01760   return NS_OK;
01761 }
01762 
01763 nsresult
01764 SendLaterListener::OnProgress(PRUint32 aCurrentMessage, PRUint32 aTotalMessage)
01765 {
01766   return NS_OK;
01767 }
01768 
01769 nsresult
01770 SendLaterListener::OnStatus(const PRUnichar *aMsg)
01771 {
01772   return NS_OK;
01773 }
01774 
01775 nsresult
01776 SendLaterListener::OnStopSending(nsresult aStatus, const PRUnichar *aMsg, PRUint32 aTotalTried, 
01777                                  PRUint32 aSuccessful) 
01778 {
01779 #ifdef NS_DEBUG
01780   if (NS_SUCCEEDED(aStatus))
01781     printf("SendLaterListener::OnStopSending: Tried to send %d messages. %d successful.\n",
01782             aTotalTried, aSuccessful);
01783 #endif
01784 
01785   nsCOMPtr <nsIMessenger> messenger = do_QueryReferent(m_messenger);
01786   if (messenger)
01787     messenger->SetSendingUnsentMsgs(PR_FALSE);
01788   return NS_OK;
01789 }
01790 
01791 NS_IMETHODIMP
01792 nsMessenger::SendUnsentMessages(nsIMsgIdentity *aIdentity, nsIMsgWindow *aMsgWindow)
01793 {
01794   nsresult rv;
01795   nsCOMPtr<nsIMsgSendLater> pMsgSendLater = do_CreateInstance(kMsgSendLaterCID, &rv);
01796   if (NS_SUCCEEDED(rv) && pMsgSendLater)
01797   { 
01798 #ifdef DEBUG
01799         printf("We succesfully obtained a nsIMsgSendLater interface....\n"); 
01800 #endif
01801 
01802     SendLaterListener *sendLaterListener = new SendLaterListener(this);
01803     if (!sendLaterListener)
01804       return NS_ERROR_OUT_OF_MEMORY;
01805 
01806     NS_ADDREF(sendLaterListener);
01807     pMsgSendLater->AddListener(sendLaterListener);
01808     pMsgSendLater->SetMsgWindow(aMsgWindow);
01809     mSendingUnsentMsgs = PR_TRUE;
01810  
01811     pMsgSendLater->SendUnsentMessages(aIdentity); 
01812     NS_RELEASE(sendLaterListener);
01813        } 
01814        return NS_OK;
01815 }
01816 
01817 nsSaveMsgListener::nsSaveMsgListener(nsIFileSpec* aSpec, nsMessenger *aMessenger)
01818 {
01819     m_fileSpec = do_QueryInterface(aSpec);
01820     m_messenger = aMessenger;
01821 
01822     // rhp: for charset handling
01823     m_doCharsetConversion = PR_FALSE;
01824     m_saveAllAttachmentsState = nsnull;
01825     mProgress = 0;
01826     mContentLength = -1;
01827     mCanceled = PR_FALSE;
01828     m_outputFormat = eUnknown;
01829     mInitialized = PR_FALSE;
01830     if (m_fileSpec)
01831       m_fileSpec->GetOutputStream(getter_AddRefs(m_outputStream));
01832     m_dataBuffer = (char*) PR_CALLOC(FOUR_K+1);
01833 }
01834 
01835 nsSaveMsgListener::~nsSaveMsgListener()
01836 {
01837 }
01838 
01839 // 
01840 // nsISupports
01841 //
01842 NS_IMPL_ISUPPORTS4(nsSaveMsgListener, nsIUrlListener, nsIMsgCopyServiceListener, nsIStreamListener, nsICancelable)
01843 
01844 
01845 NS_IMETHODIMP
01846 nsSaveMsgListener::Cancel(nsresult status)
01847 {
01848   mCanceled = PR_TRUE;
01849   return NS_OK;
01850 }
01851 
01852 // 
01853 // nsIUrlListener
01854 // 
01855 NS_IMETHODIMP
01856 nsSaveMsgListener::OnStartRunningUrl(nsIURI* url)
01857 {
01858     return NS_OK;
01859 }
01860 
01861 NS_IMETHODIMP
01862 nsSaveMsgListener::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
01863 {
01864   nsresult rv = exitCode;
01865   PRBool killSelf = PR_TRUE;
01866 
01867   if (m_fileSpec)
01868   {
01869     m_fileSpec->Flush();
01870     m_fileSpec->CloseStream();
01871     if (NS_FAILED(rv)) goto done;
01872     if (m_templateUri) { // ** save as template goes here
01873         nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
01874         if (NS_FAILED(rv)) goto done;
01875         nsCOMPtr<nsIRDFResource> res;
01876         rv = rdf->GetResource(m_templateUri, getter_AddRefs(res));
01877         if (NS_FAILED(rv)) goto done;
01878         nsCOMPtr<nsIMsgFolder> templateFolder;
01879         templateFolder = do_QueryInterface(res, &rv);
01880         if (NS_FAILED(rv)) goto done;
01881         nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID);
01882         if (copyService)
01883           rv = copyService->CopyFileMessage(m_fileSpec, templateFolder, nsnull, 
01884                                         PR_TRUE, MSG_FLAG_READ, this, nsnull);
01885         killSelf = PR_FALSE;
01886     }
01887   }
01888 
01889 done:
01890   if (NS_FAILED(rv))
01891   {
01892     if (m_fileSpec)
01893     {
01894       nsFileSpec realSpec;
01895       m_fileSpec->GetFileSpec(&realSpec);
01896       realSpec.Delete(PR_FALSE);
01897     }
01898     if (m_messenger)
01899         m_messenger->Alert("saveMessageFailed");
01900   }
01901   if (killSelf)
01902       Release(); // no more work needs to be done; kill ourself
01903 
01904   return rv;
01905 }
01906 
01907 NS_IMETHODIMP
01908 nsSaveMsgListener::OnStartCopy(void)
01909 {
01910   return NS_OK;
01911 }
01912 
01913 NS_IMETHODIMP
01914 nsSaveMsgListener::OnProgress(PRUint32 aProgress, PRUint32 aProgressMax)
01915 {
01916   return NS_OK;
01917 }
01918 
01919 NS_IMETHODIMP
01920 nsSaveMsgListener::SetMessageKey(PRUint32 aKey)
01921 {
01922   return NS_ERROR_NOT_IMPLEMENTED;
01923 }
01924 
01925 NS_IMETHODIMP
01926 nsSaveMsgListener::GetMessageId(nsCString* aMessageId)
01927 {
01928   return NS_ERROR_NOT_IMPLEMENTED;
01929 }
01930 
01931 NS_IMETHODIMP
01932 nsSaveMsgListener::OnStopCopy(nsresult aStatus)
01933 {
01934   if (m_fileSpec)
01935   {
01936     nsFileSpec realSpec;
01937     m_fileSpec->GetFileSpec(&realSpec);
01938     realSpec.Delete(PR_FALSE);
01939   }
01940   Release(); // all done kill ourself
01941   return aStatus;
01942 }
01943 
01944 // initializes the progress window if we are going to show one
01945 // and for OSX, sets creator flags on the output file
01946 nsresult nsSaveMsgListener::InitializeDownload(nsIRequest * aRequest, PRInt32 aBytesDownloaded)
01947 {
01948   nsresult rv = NS_OK;
01949 
01950   mInitialized = PR_TRUE;
01951   nsCOMPtr<nsIChannel> channel (do_QueryInterface(aRequest));
01952 
01953   if (!channel)
01954     return rv;
01955 
01956   // Set content length if we haven't already got it.
01957   if (mContentLength == -1)
01958       channel->GetContentLength(&mContentLength);
01959 
01960   if (!m_contentType.IsEmpty())
01961   {
01962       nsCOMPtr<nsIMIMEService> mimeService (do_GetService(NS_MIMESERVICE_CONTRACTID));
01963       nsCOMPtr<nsIMIMEInfo> mimeinfo;
01964 
01965       mimeService->GetFromTypeAndExtension(m_contentType, EmptyCString(), getter_AddRefs(mimeinfo)); 
01966       nsFileSpec realSpec;
01967       m_fileSpec->GetFileSpec(&realSpec);
01968 
01969       // Create nsILocalFile from a nsFileSpec.
01970       nsCOMPtr<nsILocalFile> outputFile;
01971       NS_FileSpecToIFile(&realSpec, getter_AddRefs(outputFile)); 
01972 
01973       // create a download progress window
01974       // XXX: we don't want to show the progress dialog if the download is really small.
01975       // but what is a small download? Well that's kind of arbitrary
01976       // so make an arbitrary decision based on the content length of the attachment
01977       if (mContentLength != -1 && mContentLength > aBytesDownloaded * 2)
01978       {
01979         nsCOMPtr<nsITransfer> tr = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
01980         if (tr && outputFile)
01981         {
01982           PRTime timeDownloadStarted = PR_Now();
01983 
01984           nsCOMPtr<nsIURI> outputURI;
01985           NS_NewFileURI(getter_AddRefs(outputURI), outputFile);
01986 
01987           nsCOMPtr<nsIURI> url;
01988           channel->GetURI(getter_AddRefs(url));
01989           rv = tr->Init(url, outputURI, EmptyString(), mimeinfo,
01990                         timeDownloadStarted, nsnull, this);
01991 
01992           // now store the web progresslistener 
01993           mTransfer = tr;
01994         }
01995       }
01996 
01997 #if defined(XP_MAC) || defined(XP_MACOSX)
01998       /* if we are saving an appledouble or applesingle attachment, we need to use an Apple File Decoder */
01999       if ((nsCRT::strcasecmp(m_contentType.get(), APPLICATION_APPLEFILE) == 0) ||
02000           (nsCRT::strcasecmp(m_contentType.get(), MULTIPART_APPLEDOUBLE) == 0))
02001       {        
02002 
02003         nsCOMPtr<nsIAppleFileDecoder> appleFileDecoder = do_CreateInstance(NS_IAPPLEFILEDECODER_CONTRACTID, &rv);
02004         if (NS_SUCCEEDED(rv) && appleFileDecoder)
02005         {
02006           rv = appleFileDecoder->Initialize(m_outputStream, outputFile);
02007           if (NS_SUCCEEDED(rv))
02008             m_outputStream = do_QueryInterface(appleFileDecoder, &rv);
02009         }
02010       }
02011       else
02012       {
02013           if (mimeinfo)
02014           {
02015             PRUint32 aMacType;
02016             PRUint32 aMacCreator;
02017             if (NS_SUCCEEDED(mimeinfo->GetMacType(&aMacType)) && NS_SUCCEEDED(mimeinfo->GetMacCreator(&aMacCreator)))
02018             {
02019               nsCOMPtr<nsILocalFileMac> macFile =  do_QueryInterface(outputFile, &rv);
02020               if (NS_SUCCEEDED(rv) && macFile)
02021               {
02022                 macFile->SetFileCreator((OSType)aMacCreator);
02023                 macFile->SetFileType((OSType)aMacType);
02024               }
02025             }
02026           }
02027       }
02028 #endif // XP_MACOSX
02029   }
02030 
02031   return rv;
02032 }
02033 
02034 NS_IMETHODIMP
02035 nsSaveMsgListener::OnStartRequest(nsIRequest* request, nsISupports* aSupport)
02036 {
02037   if (!m_outputStream)
02038     {
02039       mCanceled = PR_TRUE;
02040       if (m_messenger)
02041         m_messenger->Alert("saveAttachmentFailed");
02042     }
02043 
02044   return NS_OK;
02045 }
02046 
02047 NS_IMETHODIMP
02048 nsSaveMsgListener::OnStopRequest(nsIRequest* request, nsISupports* aSupport,
02049                                 nsresult status)
02050 {
02051   nsresult    rv = NS_OK;
02052 
02053   // rhp: If we are doing the charset conversion magic, this is different
02054   // processing, otherwise, its just business as usual.
02055   //
02056   if ( (m_doCharsetConversion) && (m_fileSpec) )
02057   {
02058     char        *conBuf = nsnull;
02059     PRUint32    conLength = 0; 
02060 
02061     // If we need text/plain, then we need to convert the HTML and then convert
02062     // to the systems charset
02063     //
02064     if (m_outputFormat == ePlainText)
02065     {
02066       ConvertBufToPlainText(m_msgBuffer);
02067       rv = nsMsgI18NSaveAsCharset(TEXT_PLAIN, nsMsgI18NFileSystemCharset(), 
02068                                   m_msgBuffer.get(), &conBuf); 
02069       if ( NS_SUCCEEDED(rv) && (conBuf) )
02070         conLength = strlen(conBuf);
02071     }
02072 
02073     if ( (NS_SUCCEEDED(rv)) && (conBuf) )
02074     {
02075       PRUint32      writeCount;
02076       rv = m_outputStream->Write(conBuf, conLength, &writeCount);
02077       if (conLength != writeCount)
02078         rv = NS_ERROR_FAILURE;
02079     }
02080 
02081     PR_FREEIF(conBuf);
02082   }
02083 
02084   // close down the file stream and release ourself
02085   if (m_fileSpec)
02086   {
02087     m_fileSpec->Flush();
02088     m_fileSpec->CloseStream();
02089     m_outputStream = nsnull;
02090   }
02091   
02092   if (m_saveAllAttachmentsState)
02093   {
02094       m_saveAllAttachmentsState->m_curIndex++;
02095       if (!mCanceled && m_saveAllAttachmentsState->m_curIndex <
02096           m_saveAllAttachmentsState->m_count)
02097       {
02098           nsSaveAllAttachmentsState *state = m_saveAllAttachmentsState;
02099           PRUint32 i = state->m_curIndex;
02100           nsCOMPtr<nsIFileSpec> fileSpec;
02101           nsFileSpec aFileSpec ((const char *) state->m_directoryName);
02102 
02103           nsXPIDLCString unescapedName;
02104           rv = NS_NewFileSpec(getter_AddRefs(fileSpec));
02105           if (NS_FAILED(rv)) goto done;
02106  
02107           rv = ConvertAndSanitizeFileName(state->m_displayNameArray[i], nsnull, 
02108                                           getter_Copies(unescapedName));
02109           if (NS_FAILED(rv))
02110             goto done;
02111 
02112           aFileSpec += unescapedName;
02113           rv = m_messenger->PromptIfFileExists(aFileSpec);
02114           if (NS_FAILED(rv)) goto done;
02115           fileSpec->SetFromFileSpec(aFileSpec);
02116           rv = m_messenger->SaveAttachment(fileSpec,
02117                                            state->m_urlArray[i],
02118                                            state->m_messageUriArray[i],
02119                                            state->m_contentTypeArray[i],
02120                                            (void *)state);
02121       done:
02122           if (NS_FAILED(rv))
02123           {
02124               delete state;
02125               m_saveAllAttachmentsState = nsnull;
02126           }
02127       }
02128       else
02129       {
02130           // check if we're saving attachments prior to detaching them.
02131         if (m_saveAllAttachmentsState->m_detachingAttachments && !mCanceled)
02132         {
02133           nsSaveAllAttachmentsState *state = m_saveAllAttachmentsState;
02134           m_messenger->DetachAttachments(state->m_count,
02135                                   (const char **) state->m_contentTypeArray,
02136                                   (const char **) state->m_urlArray,
02137                                   (const char **) state->m_displayNameArray,
02138                                   (const char **) state->m_messageUriArray,
02139                                   &state->m_savedFiles);
02140         }
02141 
02142         delete m_saveAllAttachmentsState;
02143         m_saveAllAttachmentsState = nsnull;
02144       }
02145   }
02146 
02147   if(mTransfer)
02148   {
02149     mTransfer->OnProgressChange(nsnull, nsnull, mContentLength, mContentLength, mContentLength, mContentLength);
02150     mTransfer->OnStateChange(nsnull, nsnull, nsIWebProgressListener::STATE_STOP, NS_OK);
02151     mTransfer = nsnull; // break any circular dependencies between the progress dialog and use
02152   }
02153 
02154   Release(); // all done kill ourself
02155   return NS_OK;
02156 }
02157 
02158 NS_IMETHODIMP
02159 nsSaveMsgListener::OnDataAvailable(nsIRequest* request, 
02160                                   nsISupports* aSupport,
02161                                   nsIInputStream* inStream, 
02162                                   PRUint32 srcOffset,
02163                                   PRUint32 count)
02164 {
02165   nsresult rv = NS_ERROR_FAILURE;
02166   // first, check to see if we've been canceled....
02167   if (mCanceled) // then go cancel our underlying channel too
02168     return request->Cancel(NS_BINDING_ABORTED);
02169 
02170   if (!mInitialized)
02171     InitializeDownload(request, count);
02172 
02173 
02174   if (m_dataBuffer && m_outputStream)
02175   {
02176     mProgress += count;
02177     PRUint32 available, readCount, maxReadCount = FOUR_K;
02178     PRUint32 writeCount;
02179     rv = inStream->Available(&available);
02180     while (NS_SUCCEEDED(rv) && available)
02181     {
02182       if (maxReadCount > available)
02183         maxReadCount = available;
02184       memset(m_dataBuffer, 0, FOUR_K+1);
02185       rv = inStream->Read(m_dataBuffer, maxReadCount, &readCount);
02186 
02187       // rhp:
02188       // Ok, now we do one of two things. If we are sending out HTML, then
02189       // just write it to the HTML stream as it comes along...but if this is
02190       // a save as TEXT operation, we need to buffer this up for conversion 
02191       // when we are done. When the stream converter for HTML-TEXT gets in place,
02192       // this magic can go away.
02193       //
02194       if (NS_SUCCEEDED(rv))
02195       {
02196         if ( (m_doCharsetConversion) && (m_outputFormat == ePlainText) )
02197           AppendUTF8toUTF16(Substring(m_dataBuffer, m_dataBuffer + readCount),
02198                             m_msgBuffer);
02199         else
02200           rv = m_outputStream->Write(m_dataBuffer, readCount, &writeCount);
02201 
02202         available -= readCount;
02203       }
02204     }
02205 
02206     if (NS_SUCCEEDED(rv) && mTransfer) // Send progress notification.
02207         mTransfer->OnProgressChange(nsnull, request, mProgress, mContentLength, mProgress, mContentLength);
02208   }
02209   return rv;
02210 }
02211 
02212 #define MESSENGER_STRING_URL       "chrome://messenger/locale/messenger.properties"
02213 
02214 nsresult
02215 nsMessenger::InitStringBundle()
02216 {
02217     nsresult res = NS_OK;
02218     if (!mStringBundle)
02219     {
02220               const char propertyURL[] = MESSENGER_STRING_URL;
02221 
02222               nsCOMPtr<nsIStringBundleService> sBundleService = 
02223                        do_GetService(NS_STRINGBUNDLE_CONTRACTID, &res);
02224               if (NS_SUCCEEDED(res) && (nsnull != sBundleService)) 
02225               {
02226                      res = sBundleService->CreateBundle(propertyURL,
02227                                                getter_AddRefs(mStringBundle));
02228               }
02229     }
02230     return res;
02231 }
02232 
02233 const nsAdoptingString
02234 nsMessenger::GetString(const nsAFlatString& aStringName)
02235 {
02236     nsresult rv = NS_OK;
02237     PRUnichar *ptrv = nsnull;
02238 
02239     if (!mStringBundle)
02240         rv = InitStringBundle();
02241 
02242     if (mStringBundle)
02243         rv = mStringBundle->GetStringFromName(aStringName.get(), &ptrv);
02244 
02245     if (NS_FAILED(rv) || !ptrv)
02246         ptrv = ToNewUnicode(aStringName);
02247 
02248     return nsAdoptingString(ptrv);
02249 }
02250 
02251 nsSaveAllAttachmentsState::nsSaveAllAttachmentsState(PRUint32 count,
02252                                                      const char **contentTypeArray,
02253                                                      const char **urlArray,
02254                                                      const char **nameArray,
02255                                                      const char **uriArray,
02256                                                      const char *dirName,
02257                                                      PRBool detachingAttachments)
02258 {
02259     PRUint32 i;
02260     NS_ASSERTION(count && urlArray && nameArray && uriArray && dirName, 
02261                  "fatal - invalid parameters\n");
02262     
02263     m_count = count;
02264     m_curIndex = 0;
02265     m_contentTypeArray = new char*[count];
02266     m_urlArray = new char*[count];
02267     m_displayNameArray = new char*[count];
02268     m_messageUriArray = new char*[count];
02269     for (i = 0; i < count; i++)
02270     {
02271         m_contentTypeArray[i] = nsCRT::strdup(contentTypeArray[i]);
02272         m_urlArray[i] = nsCRT::strdup(urlArray[i]);
02273         m_displayNameArray[i] = nsCRT::strdup(nameArray[i]);
02274         m_messageUriArray[i] = nsCRT::strdup(uriArray[i]);
02275     }
02276     m_directoryName = nsCRT::strdup(dirName);
02277     m_detachingAttachments = detachingAttachments;
02278 }
02279 
02280 nsSaveAllAttachmentsState::~nsSaveAllAttachmentsState()
02281 {
02282     PRUint32 i;
02283     for (i = 0; i < m_count; i++)
02284     {
02285         nsCRT::free(m_contentTypeArray[i]);
02286         nsCRT::free(m_urlArray[i]);
02287         nsCRT::free(m_displayNameArray[i]);
02288         nsCRT::free(m_messageUriArray[i]);
02289     }
02290     delete[] m_contentTypeArray;
02291     delete[] m_urlArray;
02292     delete[] m_displayNameArray;
02293     delete[] m_messageUriArray;
02294     nsCRT::free(m_directoryName);
02295 }
02296 
02297 nsresult
02298 nsMessenger::GetLastSaveDirectory(nsILocalFile **aLastSaveDir)
02299 {
02300   nsresult rv;
02301   nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
02302   NS_ENSURE_SUCCESS(rv,rv);
02303 
02304   // this can fail, and it will, on the first time we call it, as there is no default for this pref.
02305   nsCOMPtr <nsILocalFile> localFile;
02306   rv = prefBranch->GetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME, NS_GET_IID(nsILocalFile), getter_AddRefs(localFile));
02307   if (NS_SUCCEEDED(rv)) {
02308     NS_IF_ADDREF(*aLastSaveDir = localFile);
02309   }
02310   return rv;
02311 }
02312 
02313 nsresult
02314 nsMessenger::SetLastSaveDirectory(nsILocalFile *aLocalFile)
02315 {
02316   nsresult rv;
02317   nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
02318   NS_ENSURE_SUCCESS(rv,rv);
02319 
02320   nsCOMPtr <nsIFile> file = do_QueryInterface(aLocalFile, &rv);
02321   NS_ENSURE_SUCCESS(rv,rv);
02322 
02323   // if the file is a directory, just use it for the last dir chosen
02324   // otherwise, use the parent of the file as the last dir chosen.
02325   // IsDirectory() will return error on saving a file, as the
02326   // file doesn't exist yet.
02327   PRBool isDirectory;
02328   rv = file->IsDirectory(&isDirectory);
02329   if (NS_SUCCEEDED(rv) && isDirectory) {
02330     rv = prefBranch->SetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME, NS_GET_IID(nsILocalFile), aLocalFile);
02331     NS_ENSURE_SUCCESS(rv,rv);
02332   }
02333   else {
02334     nsCOMPtr <nsIFile> parent;
02335     rv = file->GetParent(getter_AddRefs(parent));
02336     NS_ENSURE_SUCCESS(rv,rv);
02337    
02338     nsCOMPtr <nsILocalFile> parentLocalFile = do_QueryInterface(parent, &rv);
02339     NS_ENSURE_SUCCESS(rv,rv);
02340 
02341     rv = prefBranch->SetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME, NS_GET_IID(nsILocalFile), parentLocalFile);
02342     NS_ENSURE_SUCCESS(rv,rv);
02343   }
02344   return NS_OK;
02345 }
02346 
02347 /* void getUrisAtNavigatePos (in long aPos, out ACString aFolderUri, out ACString aMsgUri); */
02348 // aPos is relative to the current history cursor - 1 is forward, -1 is back.
02349 NS_IMETHODIMP nsMessenger::GetMsgUriAtNavigatePos(PRInt32 aPos, char ** aMsgUri)
02350 {
02351   PRInt32 desiredArrayIndex = (mCurHistoryPos + (aPos << 1));
02352   if (desiredArrayIndex >= 0 && desiredArrayIndex < mLoadedMsgHistory.Count())
02353   {
02354     mNavigatingToUri = mLoadedMsgHistory[desiredArrayIndex]->get();
02355     *aMsgUri = ToNewCString(mNavigatingToUri);
02356     return NS_OK;
02357   }
02358   return NS_ERROR_FAILURE;
02359 }
02360 
02361 NS_IMETHODIMP nsMessenger::SetNavigatePos(PRInt32 aPos)
02362 {
02363   if ((aPos << 1) < mLoadedMsgHistory.Count())
02364   {   
02365     mCurHistoryPos = aPos << 1;
02366     return NS_OK;
02367   }
02368   else
02369     return NS_ERROR_INVALID_ARG;
02370 }
02371 
02372 NS_IMETHODIMP nsMessenger::GetNavigatePos(PRInt32 *aPos)
02373 {
02374   NS_ENSURE_ARG_POINTER(aPos);
02375   *aPos = mCurHistoryPos >> 1;
02376   return NS_OK;
02377 }
02378 
02379 // aPos is relative to the current history cursor - 1 is forward, -1 is back.
02380 NS_IMETHODIMP nsMessenger::GetFolderUriAtNavigatePos(PRInt32 aPos, char ** aFolderUri)
02381 {
02382   PRInt32 desiredArrayIndex = (mCurHistoryPos + (aPos << 1));
02383   if (desiredArrayIndex >= 0 && desiredArrayIndex < mLoadedMsgHistory.Count())
02384   {
02385     mNavigatingToUri = mLoadedMsgHistory[desiredArrayIndex + 1]->get();
02386     *aFolderUri = ToNewCString(mNavigatingToUri);
02387     return NS_OK;
02388   }
02389   return NS_ERROR_FAILURE;
02390 }
02391 
02392 NS_IMETHODIMP nsMessenger::GetNavigateHistory(PRUint32 *aCurPos, PRUint32 *aCount, char *** aHistoryUris)
02393 {
02394   NS_ENSURE_ARG_POINTER(aCount);
02395   NS_ENSURE_ARG_POINTER(aCurPos);
02396   
02397   *aCurPos = mCurHistoryPos >> 1;
02398   *aCount = mLoadedMsgHistory.Count();
02399   // for just enabling commands, we don't need the history uris.
02400   if (!aHistoryUris)
02401     return NS_OK;
02402   
02403   char **outArray, **next;
02404   next = outArray = (char **)nsMemory::Alloc(*aCount * sizeof(char *));
02405   if (!outArray) return NS_ERROR_OUT_OF_MEMORY;
02406   for (PRUint32 i = 0; i < *aCount; i++) 
02407   {
02408     *next = ToNewCString(*(mLoadedMsgHistory[i]));
02409     if (!*next) 
02410       return NS_ERROR_OUT_OF_MEMORY;
02411     next++;
02412   }
02413   *aHistoryUris = outArray;
02414   return NS_OK;
02415 }
02416 
02417 /* void OnItemAdded (in nsIRDFResource parentItem, in nsISupports item); */
02418 NS_IMETHODIMP nsMessenger::OnItemAdded(nsIRDFResource *parentItem, nsISupports *item)
02419 {
02420   return NS_ERROR_NOT_IMPLEMENTED;
02421 }
02422 
02423 /* void OnItemRemoved (in nsIRDFResource parentItem, in nsISupports item); */
02424 NS_IMETHODIMP nsMessenger::OnItemRemoved(nsIRDFResource *parentItem, nsISupports *item)
02425 {
02426   // check if this item is a message header that's in our history list. If so,
02427   // remove it from the history list.
02428   nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryInterface(item);
02429   if (msgHdr)
02430   {
02431     nsCOMPtr <nsIMsgFolder> folder;
02432     msgHdr->GetFolder(getter_AddRefs(folder));
02433     if (folder)
02434     {
02435       nsXPIDLCString msgUri;
02436       nsMsgKey msgKey;
02437       msgHdr->GetMessageKey(&msgKey);
02438       folder->GenerateMessageURI(msgKey, getter_Copies(msgUri));
02439       // need to remove the correspnding folder entry, and
02440       // adjust the current history pos.
02441       PRInt32 uriPos = mLoadedMsgHistory.IndexOf(nsDependentCString(msgUri));
02442       if (uriPos != kNotFound)
02443       {
02444         mLoadedMsgHistory.RemoveCStringAt(uriPos);
02445         mLoadedMsgHistory.RemoveCStringAt(uriPos); // and the folder uri entry
02446         if ((PRInt32) mCurHistoryPos >= uriPos)
02447           mCurHistoryPos -= 2;
02448       }
02449     }
02450   }
02451   return NS_OK;
02452 }
02453 
02454 /* void OnItemPropertyChanged (in nsIRDFResource item, in nsIAtom property, in string oldValue, in string newValue); */
02455 NS_IMETHODIMP nsMessenger::OnItemPropertyChanged(nsIRDFResource *item, nsIAtom *property, const char *oldValue, const char *newValue)
02456 {
02457   return NS_ERROR_NOT_IMPLEMENTED;
02458 }
02459 
02460 /* void OnItemIntPropertyChanged (in nsIRDFResource item, in nsIAtom property, in long oldValue, in long newValue); */
02461 NS_IMETHODIMP nsMessenger::OnItemIntPropertyChanged(nsIRDFResource *item, nsIAtom *property, PRInt32 oldValue, PRInt32 newValue)
02462 {
02463   return NS_ERROR_NOT_IMPLEMENTED;
02464 }
02465 
02466 /* void OnItemBoolPropertyChanged (in nsIRDFResource item, in nsIAtom property, in boolean oldValue, in boolean newValue); */
02467 NS_IMETHODIMP nsMessenger::OnItemBoolPropertyChanged(nsIRDFResource *item, nsIAtom *property, PRBool oldValue, PRBool newValue)
02468 {
02469   return NS_ERROR_NOT_IMPLEMENTED;
02470 }
02471 
02472 /* void OnItemUnicharPropertyChanged (in nsIRDFResource item, in nsIAtom property, in wstring oldValue, in wstring newValue); */
02473 NS_IMETHODIMP nsMessenger::OnItemUnicharPropertyChanged(nsIRDFResource *item, nsIAtom *property, const PRUnichar *oldValue, const PRUnichar *newValue)
02474 {
02475   return NS_ERROR_NOT_IMPLEMENTED;
02476 }
02477 
02478 /* void OnItemPropertyFlagChanged (in nsIMsgDBHdr item, in nsIAtom property, in unsigned long oldFlag, in unsigned long newFlag); */
02479 NS_IMETHODIMP nsMessenger::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, PRUint32 oldFlag, PRUint32 newFlag)
02480 {
02481   return NS_ERROR_NOT_IMPLEMENTED;
02482 }
02483 
02484 /* void OnItemEvent (in nsIMsgFolder item, in nsIAtom event); */
02485 NS_IMETHODIMP nsMessenger::OnItemEvent(nsIMsgFolder *item, nsIAtom *event)
02486 {
02487   return NS_ERROR_NOT_IMPLEMENTED;
02488 }
02489 
02491 // Detach/Delete Attachments 
02493 
02494 static char * GetAttachmentPartId(const char * aAttachmentUrl)
02495 {
02496   static const char partIdPrefix[] = "part=";
02497   char * partId = PL_strstr(aAttachmentUrl, partIdPrefix);
02498   return partId ? (partId + sizeof(partIdPrefix) - 1) : nsnull;
02499 }
02500 
02501 static int CompareAttachmentPartId(const char * aAttachUrlLeft, const char * aAttachUrlRight)
02502 {
02503   // part ids are numbers separated by periods, like "1.2.3.4".
02504   // we sort by doing a numerical comparison on each item in turn. e.g. "1.4" < "1.25"
02505   // shorter entries come before longer entries. e.g. "1.4" < "1.4.1.2"
02506   // return values:
02507   //  -2  left is a parent of right
02508   //  -1  left is less than right
02509   //   0  left == right
02510   //   1  right is greater than left
02511   //   2  right is a parent of left
02512 
02513   char * partIdLeft  = GetAttachmentPartId(aAttachUrlLeft);
02514   char * partIdRight = GetAttachmentPartId(aAttachUrlRight);
02515 
02516   // for detached attachments the URL does not contain any "part=xx"
02517   if(!partIdLeft)
02518     partIdLeft = "0";
02519 
02520   if(!partIdRight)
02521     partIdRight = "0";
02522 
02523   long idLeft, idRight;
02524   do
02525   {
02526     NS_ABORT_IF_FALSE(partIdLeft && NS_IS_DIGIT(*partIdLeft), "Invalid character in part id string");
02527     NS_ABORT_IF_FALSE(partIdRight && NS_IS_DIGIT(*partIdRight), "Invalid character in part id string");
02528 
02529     // if the part numbers are different then the numerically smaller one is first
02530     idLeft  = strtol(partIdLeft, &partIdLeft, 10);
02531     idRight = strtol(partIdRight, &partIdRight, 10);
02532     if (idLeft != idRight)
02533       return idLeft < idRight ? -1 : 1;
02534     
02535     // if one part id is complete but the other isn't, then the shortest one 
02536     // is first (parents before children)
02537     if (*partIdLeft != *partIdRight)
02538       return *partIdRight ? -2 : 2;
02539 
02540     // if both part ids are complete (*partIdLeft == *partIdRight now) then
02541     // they are equal
02542     if (!*partIdLeft)
02543       return 0;
02544 
02545     NS_ABORT_IF_FALSE(*partIdLeft == '.', "Invalid character in part id string");
02546     NS_ABORT_IF_FALSE(*partIdRight == '.', "Invalid character in part id string");
02547 
02548     ++partIdLeft;
02549     ++partIdRight;
02550   }
02551   while (PR_TRUE);
02552   return 0;
02553 }
02554 
02555 // ------------------------------------
02556 
02557 // struct on purpose -> show that we don't ever want a vtable
02558 struct msgAttachment
02559 {
02560   msgAttachment() 
02561     : mContentType(nsnull),
02562       mUrl(nsnull),
02563       mDisplayName(nsnull),
02564       mMessageUri(nsnull)
02565   { 
02566   }
02567 
02568   ~msgAttachment()
02569   {
02570     Clear();
02571   }
02572 
02573   void Clear()
02574   {
02575     CRTFREEIF(mContentType);
02576     CRTFREEIF(mUrl);
02577     CRTFREEIF(mDisplayName);
02578     CRTFREEIF(mMessageUri);
02579   }
02580 
02581   PRBool Init(const char * aContentType, const char * aUrl,
02582               const char * aDisplayName, const char * aMessageUri)
02583   {
02584     Clear();
02585     mContentType = nsCRT::strdup(aContentType);
02586     mUrl = nsCRT::strdup(aUrl);
02587     mDisplayName = nsCRT::strdup(aDisplayName);
02588     mMessageUri = nsCRT::strdup(aMessageUri);
02589     return (mContentType && mUrl && mDisplayName && mMessageUri);
02590   }
02591 
02592   // take the pointers from aSource
02593   void Adopt(msgAttachment & aSource)
02594   {
02595     Clear();
02596 
02597     mContentType = aSource.mContentType;
02598     mUrl = aSource.mUrl;
02599     mDisplayName = aSource.mDisplayName;
02600     mMessageUri = aSource.mMessageUri;
02601 
02602     aSource.mContentType = nsnull;
02603     aSource.mUrl = nsnull;
02604     aSource.mDisplayName = nsnull;
02605     aSource.mMessageUri = nsnull;
02606   }
02607 
02608   char* mContentType;
02609   char* mUrl;
02610   char* mDisplayName;
02611   char* mMessageUri;
02612 
02613 private:
02614   // disable by not implementing
02615   msgAttachment(const msgAttachment & rhs);
02616   msgAttachment & operator=(const msgAttachment & rhs);
02617 };
02618 
02619 // ------------------------------------
02620 
02621 class nsAttachmentState
02622 {
02623 public:
02624   nsAttachmentState();
02625   ~nsAttachmentState();
02626   nsresult Init(PRUint32 aCount,
02627                 const char **aContentTypeArray,
02628                 const char **aUrlArray,
02629                 const char **aDisplayNameArray,
02630                 const char **aMessageUriArray);
02631   nsresult PrepareForAttachmentDelete();
02632 
02633 private:
02634   static int SortAttachmentsByPartId(const void * aLeft, const void * aRight, void *);
02635 
02636 public:
02637   PRUint32        mCount;
02638   PRUint32        mCurIndex;
02639   msgAttachment*  mAttachmentArray;
02640 };
02641 
02642 nsAttachmentState::nsAttachmentState()
02643   : mCount(0),
02644     mCurIndex(0),
02645     mAttachmentArray(nsnull)
02646 {
02647 }
02648 
02649 nsAttachmentState::~nsAttachmentState()
02650 {
02651   delete[] mAttachmentArray;
02652 }
02653 
02654 nsresult 
02655 nsAttachmentState::Init(PRUint32 aCount, const char ** aContentTypeArray,
02656                         const char ** aUrlArray, const char ** aDisplayNameArray,
02657                         const char ** aMessageUriArray)
02658 {
02659   NS_ABORT_IF_FALSE(aCount > 0, "count is invalid");
02660 
02661   mCount = aCount;
02662   mCurIndex = 0;
02663   delete[] mAttachmentArray;
02664 
02665   mAttachmentArray = new msgAttachment[aCount];
02666   if (!mAttachmentArray) 
02667     return NS_ERROR_OUT_OF_MEMORY;
02668 
02669   for(PRUint32 u = 0; u < aCount; ++u)
02670   {
02671     if (!mAttachmentArray[u].Init(aContentTypeArray[u], aUrlArray[u], 
02672       aDisplayNameArray[u], aMessageUriArray[u]))
02673       return NS_ERROR_OUT_OF_MEMORY;
02674   }
02675 
02676   return NS_OK;
02677 }
02678 
02679 nsresult 
02680 nsAttachmentState::PrepareForAttachmentDelete()
02681 {
02682   // this must be called before any processing
02683   if (mCurIndex != 0)
02684     return NS_ERROR_FAILURE;
02685 
02686   // this prepares the attachment list for use in deletion. In order to prepare, we
02687   // sort the attachments in numerical ascending order on their part id, remove all
02688   // duplicates and remove any subparts which will be removed automatically by the
02689   // removal of the parent.
02690   // 
02691   // e.g. the attachment list processing (showing only part ids)
02692   // before: 1.11, 1.3, 1.2, 1.2.1.3, 1.4.1.2
02693   // sorted: 1.2, 1.2.1.3, 1.3, 1.4.1.2, 1.11
02694   // after:  1.2, 1.3, 1.4.1.2, 1.11
02695 
02696   // sort
02697   NS_QuickSort(mAttachmentArray, mCount, sizeof(msgAttachment), SortAttachmentsByPartId, nsnull);
02698 
02699   // remove duplicates and sub-items
02700   int nCompare;
02701   for(PRUint32 u = 1; u < mCount;)
02702   {
02703     nCompare = ::CompareAttachmentPartId(mAttachmentArray[u-1].mUrl, mAttachmentArray[u].mUrl);
02704     if (nCompare == 0 || nCompare == -2) // [u-1] is the same as or a parent of [u]
02705     {
02706       // shuffle the array down (and thus keeping the sorted order)
02707       // this will get rid of the current unnecessary element
02708       for (PRUint32 i = u + 1; i < mCount; ++i)
02709       {
02710         mAttachmentArray[i-1].Adopt(mAttachmentArray[i]);
02711       }
02712       --mCount;
02713     }
02714     else
02715     {
02716       ++u;
02717     }
02718   }
02719 
02720   return NS_OK;
02721 }
02722 
02723 int 
02724 nsAttachmentState::SortAttachmentsByPartId(const void * aLeft, const void * aRight, void *)
02725 {
02726   msgAttachment & attachLeft  = *((msgAttachment*) aLeft);
02727   msgAttachment & attachRight = *((msgAttachment*) aRight);
02728   return ::CompareAttachmentPartId(attachLeft.mUrl, attachRight.mUrl);
02729 }
02730 
02731 // ------------------------------------
02732 
02733 class nsDelAttachListener : public nsIStreamListener, 
02734                             public nsIUrlListener, 
02735                             public nsIMsgCopyServiceListener
02736 {
02737 public:
02738   NS_DECL_ISUPPORTS
02739   NS_DECL_NSISTREAMLISTENER
02740   NS_DECL_NSIREQUESTOBSERVER
02741   NS_DECL_NSIURLLISTENER
02742   NS_DECL_NSIMSGCOPYSERVICELISTENER
02743 
02744 public:
02745   nsDelAttachListener();
02746   virtual ~nsDelAttachListener();
02747   nsresult StartProcessing(nsMessenger * aMessenger, nsIMsgWindow * aMsgWindow, 
02748     nsAttachmentState * aAttach, PRBool aSaveFirst);
02749   nsresult DeleteOriginalMessage();
02750   void SelectNewMessage();
02751 
02752 public:
02753   nsAttachmentState * mAttach;                      // list of attachments to process
02754   PRBool mSaveFirst;                                // detach (PR_TRUE) or delete (PR_FALSE)
02755   nsCOMPtr<nsIFileSpec> mMsgFileSpec;               // temporary file (processed mail)
02756   nsCOMPtr<nsIOutputStream> mMsgFileStream;         // temporary file (processed mail)
02757   nsCOMPtr<nsIMsgMessageService> mMessageService;   // original message service
02758   nsCOMPtr<nsIMsgDBHdr> mOriginalMessage;           // original message header
02759   nsCOMPtr<nsIMsgFolder> mMessageFolder;            // original message folder
02760   nsCOMPtr<nsIMessenger> mMessenger;                // our messenger instance
02761   nsCOMPtr<nsIMsgWindow> mMsgWindow;                // our UI window
02762   PRUint32 mNewMessageKey;                          // new message key
02763   PRUint32 mOrigMsgFlags;
02764 
02765   
02766    enum {
02767       eStarting,
02768       eCopyingNewMsg,
02769       eUpdatingFolder, // for IMAP
02770       eDeletingOldMessage,
02771       eSelectingNewMessage
02772     } m_state;
02773    // temp
02774   PRBool mWrittenExtra;
02775   PRBool mDetaching;
02776   nsCStringArray mDetachedFileUris;
02777 };
02778 
02779 // 
02780 // nsISupports
02781 //
02782 NS_IMPL_ISUPPORTS3(nsDelAttachListener,nsIStreamListener,nsIUrlListener,nsIMsgCopyServiceListener)
02783 
02784 // 
02785 // nsIRequestObserver
02786 // 
02787 NS_IMETHODIMP
02788 nsDelAttachListener::OnStartRequest(nsIRequest * aRequest, nsISupports * aContext)
02789 {
02790   // called when we start processing the StreamMessage request. 
02791   // This is called after OnStartRunningUrl().
02792   return NS_OK;
02793 }
02794 
02795 NS_IMETHODIMP
02796 nsDelAttachListener::OnStopRequest(nsIRequest * aRequest, nsISupports * aContext, nsresult aStatusCode)
02797 {
02798   // called when we have completed processing the StreamMessage request. 
02799   // This is called after OnStopRequest(). This means that we have now
02800   // received all data of the message and we have completed processing.
02801   // We now start to copy the processed message from the temporary file
02802   // back into the message store, replacing the original message.
02803 
02804   mMessageFolder->CopyDataDone();
02805   if (NS_FAILED(aStatusCode))
02806     return aStatusCode;
02807 
02808   // called when we complete processing of the StreamMessage request.
02809   // This is called before OnStopRunningUrl().
02810   nsresult rv;
02811 
02812   // copy the file back into the folder. Note: if we set msgToReplace then 
02813   // CopyFileMessage() fails, do the delete ourselves
02814   nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService;
02815   rv = this->QueryInterface( NS_GET_IID(nsIMsgCopyServiceListener), getter_AddRefs(listenerCopyService) );
02816   NS_ENSURE_SUCCESS(rv,rv);
02817 
02818   mMsgFileStream = nsnull;
02819   mMsgFileSpec->CloseStream();
02820   mNewMessageKey = PR_UINT32_MAX;
02821   nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID);
02822   m_state = eCopyingNewMsg;
02823   if (copyService)
02824     rv = copyService->CopyFileMessage(mMsgFileSpec, mMessageFolder, nsnull, PR_FALSE, 
02825                                       mOrigMsgFlags, listenerCopyService, mMsgWindow);
02826   return rv;
02827 }
02828 
02829 //
02830 // nsIStreamListener
02831 //
02832 
02833 NS_IMETHODIMP
02834 nsDelAttachListener::OnDataAvailable(nsIRequest * aRequest, nsISupports * aSupport, 
02835                                      nsIInputStream * aInStream, PRUint32 aSrcOffset,
02836                                      PRUint32 aCount)
02837 {
02838   if (!mMsgFileStream)
02839     return NS_ERROR_NULL_POINTER;
02840   return mMessageFolder->CopyDataToOutputStreamForAppend(aInStream, aCount, mMsgFileStream);
02841 }
02842 
02843 // 
02844 // nsIUrlListener
02845 //
02846 
02847 NS_IMETHODIMP
02848 nsDelAttachListener::OnStartRunningUrl(nsIURI * aUrl)
02849 {
02850   // called when we start processing the StreamMessage request. This is 
02851   // called before OnStartRequest().
02852   return NS_OK;
02853 }
02854 
02855 nsresult nsDelAttachListener::DeleteOriginalMessage()
02856 {
02857   nsCOMPtr<nsISupportsArray> messageArray;
02858   nsresult rv = NS_NewISupportsArray(getter_AddRefs(messageArray));
02859   NS_ENSURE_SUCCESS(rv,rv);
02860   rv = messageArray->AppendElement(mOriginalMessage);
02861   NS_ENSURE_SUCCESS(rv,rv);
02862   nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService;
02863 
02864   QueryInterface( NS_GET_IID(nsIMsgCopyServiceListener), getter_AddRefs(listenerCopyService) );
02865 
02866   mOriginalMessage = nsnull;
02867   m_state = eDeletingOldMessage;
02868   return mMessageFolder->DeleteMessages( 
02869     messageArray,         // messages
02870     mMsgWindow,           // msgWindow
02871     PR_TRUE,              // deleteStorage
02872     PR_TRUE,              // isMove
02873     listenerCopyService,  // listener
02874     PR_FALSE);            // allowUndo
02875 }
02876 
02877 void nsDelAttachListener::SelectNewMessage()
02878 {
02879   nsXPIDLCString displayUri;
02880   // all attachments refer to the same message
02881   const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri;
02882   mMessenger->GetLastDisplayedMessageUri(getter_Copies(displayUri));
02883   if (displayUri.Equals(messageUri))
02884   {
02885     mMessageFolder->GenerateMessageURI(mNewMessageKey, getter_Copies(displayUri));
02886     if (displayUri)
02887     {
02888       mMsgWindow->SelectMessage(displayUri);
02889     }
02890   }
02891   mNewMessageKey = PR_UINT32_MAX;
02892 }
02893 
02894 NS_IMETHODIMP
02895 nsDelAttachListener::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode)
02896 {
02897   nsresult rv = NS_OK;
02898   const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri;
02899   if (mOriginalMessage && !strncmp(messageUri, "imap-message:", 13))
02900   {
02901     if (m_state == eUpdatingFolder)
02902       rv = DeleteOriginalMessage();
02903   }
02904   // check if we've deleted the original message, and we know the new msg id.
02905   else if (m_state == eDeletingOldMessage && mMsgWindow)
02906     SelectNewMessage();
02907 
02908   return rv;
02909 }
02910 
02911 // 
02912 // nsIMsgCopyServiceListener
02913 //
02914 
02915 NS_IMETHODIMP
02916 nsDelAttachListener::OnStartCopy(void)
02917 {
02918   // never called?
02919   return NS_OK;
02920 }
02921 
02922 NS_IMETHODIMP
02923 nsDelAttachListener::OnProgress(PRUint32 aProgress, PRUint32 aProgressMax)
02924 {
02925   // never called?
02926   return NS_OK;
02927 }
02928 
02929 NS_IMETHODIMP
02930 nsDelAttachListener::SetMessageKey(PRUint32 aKey)
02931 {
02932   // called during the copy of the modified message back into the message 
02933   // store to notify us of the message key of the newly created message.
02934   mNewMessageKey = aKey;
02935   return NS_OK;
02936 }
02937 
02938 NS_IMETHODIMP
02939 nsDelAttachListener::GetMessageId(nsCString * aMessageId)
02940 {
02941   // never called?
02942   return NS_ERROR_NOT_IMPLEMENTED;
02943 }
02944 
02945 NS_IMETHODIMP
02946 nsDelAttachListener::OnStopCopy(nsresult aStatus)
02947 {
02948   // only if the currently selected message is the one that we are about to delete then we 
02949   // change the selection to the new message that we just added. Failures in this code are not fatal. 
02950   // Note that can only do this if we have the new message key, which we don't always get from IMAP.
02951   // delete the original message
02952   if (NS_FAILED(aStatus))
02953     return aStatus;
02954 
02955   // check if we've deleted the original message, and we know the new msg id.
02956   if (m_state == eDeletingOldMessage && mMsgWindow)
02957     SelectNewMessage();
02958   // do this for non-imap messages - for imap, we'll do the delete in
02959   // OnStopRunningUrl. For local messages, we won't get an OnStopRunningUrl
02960   // notification. And for imap, it's too late to delete the message here,
02961   // because we'll be updating the folder naturally as a result of
02962   // running an append url. If we delete the header here, that folder
02963   // update will think we need to download the header...If we do it
02964   // in OnStopRunningUrl, we'll issue the delete before we do the
02965   // update....all nasty stuff.
02966   const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri;
02967   if (mOriginalMessage && strncmp(messageUri, "imap-message:", 13))
02968     return DeleteOriginalMessage();
02969   else
02970     m_state = eUpdatingFolder;
02971   return NS_OK;
02972 }
02973 
02974 // 
02975 // local methods
02976 //
02977 
02978 nsDelAttachListener::nsDelAttachListener() 
02979 { 
02980   mAttach = nsnull;
02981   mSaveFirst = PR_FALSE;
02982   mWrittenExtra = PR_FALSE;
02983   mNewMessageKey = PR_UINT32_MAX;
02984   m_state = eStarting;
02985 }
02986 
02987 nsDelAttachListener::~nsDelAttachListener()
02988 {
02989   if (mAttach) 
02990   {
02991     delete mAttach;
02992   }
02993   if (mMsgFileStream)
02994   {
02995     mMsgFileStream->Close();
02996     mMsgFileStream = 0;
02997   }
02998   if (mMsgFileSpec) 
02999   {
03000     mMsgFileSpec->Flush();
03001     mMsgFileSpec->CloseStream();
03002     mMsgFileSpec->Delete(PR_FALSE);
03003   }
03004 }
03005 
03006 nsresult 
03007 nsDelAttachListener::StartProcessing(nsMessenger * aMessenger, nsIMsgWindow * aMsgWindow, 
03008                                      nsAttachmentState * aAttach, PRBool detaching)
03009 {
03010   aMessenger->QueryInterface(NS_GET_IID(nsIMessenger), getter_AddRefs(mMessenger));
03011   mMsgWindow = aMsgWindow;
03012   mAttach    = aAttach;
03013   mDetaching = detaching;
03014 
03015   nsresult rv;
03016 
03017   // all attachments refer to the same message
03018   const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri;
03019 
03020   // get the message service, original message and folder for this message
03021   rv = GetMessageServiceFromURI(messageUri, getter_AddRefs(mMessageService));
03022   NS_ENSURE_SUCCESS(rv,rv);
03023   rv = mMessageService->MessageURIToMsgHdr(messageUri, getter_AddRefs(mOriginalMessage));
03024   NS_ENSURE_SUCCESS(rv,rv);
03025   rv = mOriginalMessage->GetFolder(getter_AddRefs(mMessageFolder));
03026   NS_ENSURE_SUCCESS(rv,rv);
03027   mOriginalMessage->GetFlags(&mOrigMsgFlags);
03028 
03029   // ensure that we can store and delete messages in this folder, if we 
03030   // can't then we can't do attachment deleting
03031   PRBool canDelete = PR_FALSE;
03032   mMessageFolder->GetCanDeleteMessages(&canDelete);
03033   PRBool canFile = PR_FALSE;
03034   mMessageFolder->GetCanFileMessages(&canFile);
03035   if (!canDelete || !canFile)
03036     return NS_ERROR_FAILURE;
03037 
03038   // create an output stream on a temporary file. This stream will save the modified 
03039   // message data to a file which we will later use to replace the existing message.
03040   // The file is removed in the destructor.
03041   nsFileSpec * msgFileSpec = new nsFileSpec(
03042     nsSpecialSystemDirectory(nsSpecialSystemDirectory::OS_TemporaryDirectory) );
03043   if (!msgFileSpec) return NS_ERROR_OUT_OF_MEMORY;
03044   *msgFileSpec += "nsmail.tmp";
03045   msgFileSpec->MakeUnique();
03046   rv = NS_NewFileSpecWithSpec(*msgFileSpec, getter_AddRefs(mMsgFileSpec));
03047   nsCOMPtr<nsILocalFile> msgFile;
03048   if (NS_SUCCEEDED(rv))
03049     rv = NS_FileSpecToIFile(msgFileSpec, getter_AddRefs(msgFile));
03050   delete msgFileSpec;
03051   NS_ENSURE_SUCCESS(rv,rv);
03052   nsCOMPtr<nsIOutputStream> fileOutputStream;
03053   rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileOutputStream), msgFile, -1, 00600);
03054   NS_ENSURE_SUCCESS(rv,rv);
03055   rv = NS_NewBufferedOutputStream(getter_AddRefs(mMsgFileStream), fileOutputStream, FOUR_K);
03056   NS_ENSURE_SUCCESS(rv,rv);
03057 
03058   // create the additional header for data conversion. This will tell the stream converter
03059   // which MIME emitter we want to use, and it will tell the MIME emitter which attachments
03060   // should be deleted.
03061   const char * partId;
03062   const char * nextField;
03063   nsCAutoString sHeader("attach&del=");
03064   nsCAutoString detachToHeader("&detachTo=");
03065   for (PRUint32 u = 0; u < mAttach->mCount; ++u)
03066   {
03067     if (u > 0)
03068     {
03069       sHeader.Append(",");
03070       if (detaching)
03071         detachToHeader.Append(",");
03072     }
03073     partId = GetAttachmentPartId(mAttach->mAttachmentArray[u].mUrl);
03074     nextField = PL_strchr(partId, '&');
03075     sHeader.Append(partId, nextField ? nextField - partId : -1);
03076     if (detaching)
03077       detachToHeader.Append(mDetachedFileUris.CStringAt(u)->get());
03078   }
03079 
03080   if (detaching)
03081     sHeader.Append(detachToHeader);
03082   // stream this message to our listener converting it via the attachment mime
03083   // converter. The listener will just write the converted message straight to disk.
03084   nsCOMPtr<nsISupports> listenerSupports;
03085   rv = this->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(listenerSupports));
03086   NS_ENSURE_SUCCESS(rv,rv);
03087   nsCOMPtr<nsIUrlListener> listenerUrlListener = do_QueryInterface(listenerSupports, &rv);
03088   NS_ENSURE_SUCCESS(rv,rv);
03089   rv = mMessageService->StreamMessage(
03090     messageUri,           // aMessageURI 
03091     listenerSupports,     // aConsumer 
03092     mMsgWindow,           // aMsgWindow 
03093     listenerUrlListener,  // aUrlListener
03094     PR_TRUE,              // aConvertData
03095     sHeader.get(),        // aAdditionalHeader
03096     nsnull );             // requestUri
03097   NS_ENSURE_SUCCESS(rv,rv);
03098 
03099   return NS_OK;
03100 }
03101 
03102 // ------------------------------------
03103 
03104 NS_IMETHODIMP
03105 nsMessenger::DetachAttachment(const char * aContentType, const char * aUrl,
03106                               const char * aDisplayName, const char * aMessageUri,
03107                               PRBool aSaveFirst)
03108 {
03109   NS_ENSURE_ARG_POINTER(aContentType);
03110   NS_ENSURE_ARG_POINTER(aUrl);
03111   NS_ENSURE_ARG_POINTER(aDisplayName);
03112   NS_ENSURE_ARG_POINTER(aMessageUri);
03113 
03114   // convenience function for JS, processing handled by DetachAllAttachments()
03115   return DetachAllAttachments(1, &aContentType, &aUrl, &aDisplayName, &aMessageUri, aSaveFirst);
03116 }
03117 
03118 NS_IMETHODIMP
03119 nsMessenger::DetachAllAttachments(PRUint32 aCount,
03120                                   const char ** aContentTypeArray,
03121                                   const char ** aUrlArray,
03122                                   const char ** aDisplayNameArray,
03123                                   const char ** aMessageUriArray,
03124                                   PRBool aSaveFirst)
03125 {
03126   NS_ENSURE_ARG_MIN(aCount, 1);
03127   NS_ENSURE_ARG_POINTER(aContentTypeArray);
03128   NS_ENSURE_ARG_POINTER(aUrlArray);
03129   NS_ENSURE_ARG_POINTER(aDisplayNameArray);
03130   NS_ENSURE_ARG_POINTER(aMessageUriArray);
03131 
03132   if (aSaveFirst)
03133     return SaveAllAttachments(aCount, aContentTypeArray, aUrlArray, aDisplayNameArray, aMessageUriArray, PR_TRUE);
03134   else
03135     return DetachAttachments(aCount, aContentTypeArray, aUrlArray, aDisplayNameArray, aMessageUriArray, nsnull);
03136 }
03137 
03138 nsresult
03139 nsMessenger::DetachAttachments(PRUint32 aCount,
03140                                   const char ** aContentTypeArray,
03141                                   const char ** aUrlArray,
03142                                   const char ** aDisplayNameArray,
03143                                   const char ** aMessageUriArray,
03144                                   nsCStringArray *saveFileUris)
03145 {
03146   if (NS_FAILED(PromptIfDeleteAttachments(saveFileUris != nsnull, aCount, aDisplayNameArray)))
03147     return NS_OK;
03148   nsresult rv = NS_OK;
03149 
03150 
03151   // ensure that our arguments are valid
03152 //  char * partId;
03153   for (PRUint32 u = 0; u < aCount; ++u)
03154   {
03155     // ensure all of the message URI are the same, we cannot process 
03156     // attachments from different messages
03157     if (u > 0 && 0 != nsCRT::strcmp(aMessageUriArray[0], aMessageUriArray[u]))
03158     {
03159       rv = NS_ERROR_INVALID_ARG;
03160       break;
03161     }
03162 
03163     // ensure that we don't have deleted messages in this list
03164     if (0 == nsCRT::strcmp(aContentTypeArray[u], MIMETYPE_DELETED))
03165     {
03166       rv = NS_ERROR_INVALID_ARG;
03167       break;
03168     }
03169 
03170     // for the moment we prevent any attachments other than root level
03171     // attachments being deleted (i.e. you can't delete attachments from a
03172     // email forwarded as an attachment). We do this by ensuring that the
03173     // part id only has a single period in it (e.g. "1.2").
03174     //TODO: support non-root level attachment delete
03175 //    partId = ::GetAttachmentPartId(aUrlArray[u]);
03176 //    if (!partId || PL_strchr(partId, '.') != PL_strrchr(partId, '.'))
03177 //    {
03178 //      rv = NS_ERROR_INVALID_ARG;
03179 //      break;
03180 //    }
03181   }
03182   if (NS_FAILED(rv))
03183   {
03184     Alert("deleteAttachmentFailure");
03185     return rv;
03186   }
03187 
03188   //TODO: ensure that nothing else is processing this message uri at the same time
03189 
03190   //TODO: if any of the selected attachments are messages that contain other 
03191   // attachments we need to warn the user that all sub-attachments of those
03192   // messages will also be deleted. Best to display a list of them.
03193 
03194   // get the listener for running the url
03195   nsDelAttachListener * listener = new nsDelAttachListener;
03196   if (!listener) 
03197     return NS_ERROR_OUT_OF_MEMORY;
03198   nsCOMPtr<nsISupports> listenerSupports; // auto-delete of the listener with error
03199   listener->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(listenerSupports));
03200 
03201   if (saveFileUris)
03202     listener->mDetachedFileUris = *saveFileUris;
03203   // create the attachments for use by the listener
03204   nsAttachmentState * attach = new nsAttachmentState;
03205   if (!attach) 
03206     return NS_ERROR_OUT_OF_MEMORY;
03207   rv = attach->Init(aCount, aContentTypeArray, aUrlArray, aDisplayNameArray, aMessageUriArray);
03208   if (NS_SUCCEEDED(rv))
03209     rv = attach->PrepareForAttachmentDelete();
03210   if (NS_FAILED(rv)) 
03211   {
03212     delete attach;
03213     return rv;
03214   }
03215 
03216   // initialize our listener with the attachments and details. The listener takes ownership
03217   // of 'attach' immediately irrespective of the return value (error or not).
03218   return listener->StartProcessing(this, mMsgWindow, attach, saveFileUris != nsnull);
03219 }
03220 
03221 nsresult 
03222 nsMessenger::PromptIfDeleteAttachments(PRBool aSaveFirst,
03223                                        PRUint32 aCount,
03224                                        const char ** aDisplayNameArray)
03225 {
03226   nsresult rv = NS_ERROR_FAILURE;
03227 
03228   nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
03229   if (!dialog) return rv;
03230 
03231   if (!mStringBundle)
03232   {
03233     rv = InitStringBundle();
03234     NS_ENSURE_SUCCESS(rv, rv);
03235   }
03236 
03237   // create the list of attachments we are removing
03238   nsXPIDLString displayString;
03239   nsXPIDLString attachmentList;
03240   for (PRUint32 u = 0; u < aCount; ++u)
03241   {
03242     rv = ConvertAndSanitizeFileName(aDisplayNameArray[u], getter_Copies(displayString), nsnull);
03243     if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
03244     attachmentList.Append(displayString);
03245     attachmentList.Append(PRUnichar('\n'));
03246   }
03247   const PRUnichar *formatStrings[] = { attachmentList.get() };
03248 
03249   // format the message and display
03250   nsXPIDLString promptMessage;
03251   const PRUnichar * propertyName = aSaveFirst ? 
03252     NS_LITERAL_STRING("detachAttachments").get() : NS_LITERAL_STRING("deleteAttachments").get();
03253   rv = mStringBundle->FormatStringFromName(propertyName, formatStrings, 1,getter_Copies(promptMessage));
03254   NS_ENSURE_SUCCESS(rv, rv);
03255 
03256   PRBool dialogResult = PR_FALSE;
03257   rv = dialog->Confirm(nsnull, promptMessage, &dialogResult);
03258   NS_ENSURE_SUCCESS(rv, rv);
03259   
03260   return dialogResult ? NS_OK : NS_ERROR_FAILURE;
03261 }
03262